sahin.io

All skills and agents

page-taxonomy

The Category and Sub-Category collection pages and their investor directories on sahin.io/angel-investments (/{category}, /{category}/{sub}, and each one's /angel-investors directory). Use when building, rebuilding or changing these pages, their editorial Markdown, their noindex rules, titles or schema, or when a new Category or Sub-Category appears.

Source file
.claude/skills/page-taxonomy/SKILL.md
Routes covered
kind:categorykind:subcategorykind:categoryInvestorskind:subcategoryInvestors

Category and Sub-Category pages

Routes

URL Route kind Count today Render path
https://sahin.io/angel-investments/{category} (e.g. https://sahin.io/angel-investments/ai) category 14 static only
https://sahin.io/angel-investments/{category}/{sub} subcategory 69 static only
https://sahin.io/angel-investments/{category}/angel-investors categoryInvestors 14 static body + React categoryInvestorsView on top
https://sahin.io/angel-investments/{category}/{sub}/angel-investors subcategoryInvestors 54 (5 noindex) static body + React categoryInvestorsView on top
  • Paths: taxonomyPath(kind, slug) / taxonomyInvestorsPath(kind, slug) (shared/publicUrls.ts). A Sub-Category's parent comes only from data/subcategory-parents.json (subcategoryParent throws when missing).
  • Manifest (createSeoRouteManifest(), server/seoRouteManifest.ts): for each term from taxonomyTerms(portfolio.companies, kind) push the collection route (title from approved Markdown or taxonomyPageTitle(label), lastModified from lastReviewed); push the investor route only when collectionHasEligibleInvestors(...) (at least COLLECTION_INVESTOR_MINIMUM = 3 companies with verified research), titled by sectorInvestorDirectory(...).title, and noindex when its key is in duplicateInvestorDirectoryKeys(...) (server/sectorInvestorDirectory.ts).
  • An approved Markdown file whose slug matches no live term fails the build.
  • Dispatch: renderPublicTaxonomyPage and renderPublicInvestorCollectionPage; reactPageForRoute() maps both investor kinds to categoryInvestorsView, which is in EMBED_SERVER_BODY_KINDS (server/publicReactDocument.ts; the static sector-angels section is dropped because the React view shows angels first).

Files

  • Renderers/helpers: renderPublicTaxonomyPage, taxonomyInvestorCoverage, renderTaxonomyInvestorCoverage, renderTaxonomySources, renderTaxonomyFaqs, taxonomyCrumbs, renderPublicInvestorCollectionPage, renderSectorAngels, investorCollections (server/seoRouteManifest.ts).
  • Directory model: sectorInvestorDirectory, sectorDirectoryTitle, sectorListPath, duplicateInvestorDirectoryKeys, investorDirectoryCompanySets (server/sectorInvestorDirectory.ts).
  • Editorial Markdown: content/taxonomy/{slug}.md (today ai, climate, developer-tools, fintech, robotics), loaded by loadTaxonomyContent / findTaxonomyContent (server/taxonomyContent.ts); frontmatter slug, kind, title, description, h1, targetQueries, faqs, sources, lastReviewed, status; body 600-1200 words, exactly 3 FAQs, title ≤ 60, description ≤ 155. Icons: content/taxonomy/icons.yaml.
  • Copy: content/pages/taxonomy-page.yaml (taxonomyCopy()), content/pages/investor-directories.yaml (investorCopy().collection), content/pages/category-investors.yaml (React view).
  • React: server/publicPages/categoryInvestorsView.ts (buildCategoryInvestorsViewProps), client/src/publicPages/CategoryInvestorsViewPage.tsx + categoryInvestorsViewTypes.ts, CSS client/src/publicPages/styles/categoryInvestorsView.css. Static CSS: .taxonomy-*, .sector-*, .prose, .faq, .sources in PUBLIC_PAGE_CSS (server/publicChrome.ts).
  • Data: client/public/portfolio-data.json (category / subcategory arrays), data/public-investor-research.json, data/subcategory-parents.json, data/public-company-valuations.json.

Sections on the page

Collection page — with approved Markdown: <article class="taxonomy-editorial"> header (eyebrow, h1, updated date, answer-first intro, collection size, investor-directory link) → prose (internal links resolved against the manifest; FAQ section placed at the FAQ slot or appended) → investor coverage panel (top 8 investors across the term's companies, linked to firm pages) → sources. Without Markdown: only a header from taxonomy-page.yaml (eyebrow, h1, description, investor link). Then, in both cases: companies grid with list controls (#taxonomy-company-list, sortByMarketCap) → related pages → footer link.

Investor directory — React view: "Who invests in {X} startups?" (when a sourced list owns "{X} angel investors") or "{X} angel investors and VCs", angels, companies ranked by company value. Static body below: header links (best list when it exists, all startups in the term, all investors) → company cards linking to each /{company}/angel-investors → related pages → back link.

SEO

  • Collection: title/description/h1 from Markdown frontmatter, else taxonomyPageTitle(label) and taxonomy-page.yaml; JSON-LD CollectionPage + ItemList of companies + FAQPage when FAQs exist + dateModified from lastReviewed.
  • Directory: title/description from sectorInvestorDirectory; CollectionPage + ItemList of company investor pages; breadcrumbs hub → Category (→ Sub-Category) → "Angel investors".
  • A noindex route still renders but renderRouteHtml throws unless the robots meta is present; it is left out of the sitemap, llms files and IndexNow (isIndexableRoute). Details: seo-head-and-schema.

Rules

  • Editorial gate: only status: approved Markdown is loaded; drafts never publish (preview with pnpm content:preview).
  • A new Category/Sub-Category from a sync needs a slug and a parent pin first — public-url-conventions.
  • Counts ("N companies") computed, never typed — portfolio-counts-single-source. Words in YAML/MD — page-content-yaml.
  • Directory wording: "investor"; never "co-invested", "led", rounds, amounts, dates; the owner is left out of names and counts — private-data-boundary.

Tests

server/taxonomy-content.test.ts, server/sector-investor-directories.test.ts, server/route-indexing.test.ts, server/seo-route-manifest.test.ts, server/public-schema.test.ts, server/public-urls.test.ts, server/public-investor-research.test.ts, server/keyword-[redacted].test.ts, server/llms-text.test.ts, server/legacy-redirects.test.ts, server/seo-watchlist.test.ts.

Rebuild from scratch

  1. Verify every Sub-Category has a parent in data/subcategory-parents.json.
  2. Keep the taxonomy loop in createSeoRouteManifest() including the eligibility check and duplicateInvestorDirectoryKeys noindex.
  3. Write taxonomy-page.yaml, investor-directories.yaml (collection block), category-investors.yaml; run node scripts/sync-site-copy-module.mjs.
  4. Implement renderPublicTaxonomyPage (editorial and fallback headers) and renderPublicInvestorCollectionPage on top of sectorInvestorDirectory.
  5. Implement buildCategoryInvestorsViewProps + CategoryInvestorsViewPage.tsx, register per new-react-page, add the kind to EMBED_SERVER_BODY_KINDS.
  6. Add or edit content/taxonomy/{slug}.md drafts; set status: approved only after owner review.
  7. Run deploy-gate → ui-before-after (a Category, a Sub-Category, one directory; desktop + mobile) → ship-to-production.