-
Notifications
You must be signed in to change notification settings - Fork 28
Docsite Architecture
The Astryx docsite lives at xds-sandbox.vercel.app and is a Next.js 15 app that renders documentation from generated registries. All content originates from .doc.mjs files colocated with source code — there is no separate CMS or markdown content directory.
| What | Where |
|---|---|
| Docsite app | apps/docsite/ |
| Data generation script | apps/docsite/scripts/generate-data.mjs |
| Generated registries |
apps/docsite/src/generated/ (gitignored, built at dev/build time) |
| Component docs source | packages/core/src/<Component>/<Name>.doc.mjs |
| Reference docs source | packages/cli/docs/<topic>.doc.mjs |
| Example blocks source | packages/cli/templates/blocks/components/<Name>/ |
| Page templates source | packages/cli/templates/pages/<slug>/ |
| Doc type definitions | packages/core/src/docs-types.ts |
| Shell / sidebar nav | apps/docsite/src/components/DocsShell.tsx |
| Deployment | Vercel (auto-deploy on push to main) |
URL: /
What it shows: Hero section, package grid (libraries + themes), and foundations card grid.
Data source: packageRegistry, componentRegistry (count), docsRegistry (foundations topics), themeRegistry (live theme previews).
Code: apps/docsite/src/app/(docs)/page.tsx
URL pattern: /components/[name]
What it shows: Two tabs — Overview (showcase preview, usage description, import path, best practices, examples) and Playground (interactive preview with live prop controls).
Data source:
- Component metadata →
componentRegistry(frompackages/core/src/<Dir>/<Name>.doc.mjs) - Showcase preview →
showcaseRegistry(frompackages/cli/templates/blocks/components/<Name>/<Name>Showcase.tsx) - Example blocks →
exampleRegistry(from non-showcase.doc.mjs+.tsxpairs in the blocks directory) - Package info →
packageRegistry
Code:
- Route:
apps/docsite/src/app/(docs)/components/[name]/page.tsx - Client component:
apps/docsite/src/components/component-detail/ComponentDetailClient.tsx - Sub-components:
apps/docsite/src/components/component-detail/(PropsTable, BestPractices, ExampleBlock, ShowcasePreview, InteractivePreview, HookSignature, PlaygroundPropsTable)
URL pattern: /packages/[name]
What it shows: Adapts based on package type:
-
Component package (
@xds/core): heading + grouped component grid with preview cards -
Theme package (
@xds/theme-*): live theme preview with install steps -
Other (
@xds/cli): README rendered as markdown
Data source: packageRegistry, groupedComponentRegistry, themeRegistry
Code: apps/docsite/src/app/(docs)/packages/[name]/page.tsx
URL pattern: /docs/[topic]
What it shows: Long-form reference documentation. Two rendering modes:
-
Token topics (color, elevation, motion, shape, spacing, typography, tokens): rendered via
TokensDocViewwith live token tables -
Other docs (principles, styling, theme, getting-started): rendered via
ReferenceDocViewwith prose/code/table/list blocks
Data source: docsRegistry (from packages/cli/docs/<topic>.doc.mjs)
Code:
- Route:
apps/docsite/src/app/(docs)/docs/[topic]/page.tsx - Token view:
apps/docsite/src/components/docs/TokensDocView.tsx - Reference view:
apps/docsite/src/components/docs/ReferenceDocView.tsx - Block renderer:
apps/docsite/src/components/docs/ContentBlockRenderer.tsx
URL: /changelog
What it shows: Aggregated changelogs from all non-private packages, with component name cross-referencing for linking.
Data source: packageRegistry (the changelog field, read from packages/<name>/CHANGELOG.md), componentRegistry (component names for linking)
Code: apps/docsite/src/app/(docs)/changelog/page.tsx, apps/docsite/src/components/ChangelogView.tsx
URL: /craft
What it shows: Tabbed gallery of templates, themes, and component showcases with hover-overlay cards.
Data source: templateRegistry, blockRegistry (showcases), themeRegistry, packageRegistry
Code: apps/docsite/src/app/craft/page.tsx
URL pattern: /craft/templates/[slug]
What it shows: Individual template preview (WIP — currently shows title + description).
Data source: templateRegistry
Code: apps/docsite/src/app/craft/templates/[slug]/page.tsx
URL: /craft/theme
What it shows: Live theme customization (WIP — placeholder page).
Code: apps/docsite/src/app/craft/theme/page.tsx
URL: /playground
What it shows: Full-page Monaco code editor with live preview iframe. Users write TSX and see it rendered.
Data source: Component scope generated by scripts/generate-playground-types.mjs
Code: apps/docsite/src/app/playground/page.tsx, apps/docsite/src/app/playground/PlaygroundClient.tsx
Source files (.doc.mjs) → generate-data.mjs → src/generated/*.ts → Next.js pages
The script runs automatically before dev and build (via predev / prebuild in package.json). You can also run it manually:
cd apps/docsite
node scripts/generate-data.mjs| Registry | File | Sources | Contains |
|---|---|---|---|
packageRegistry |
packageRegistry.ts |
packages/*/package.json + README + CHANGELOG |
Package metadata, versions, descriptions |
componentRegistry |
componentRegistry.ts |
packages/core/src/*/*.doc.mjs |
Full component docs: props, usage, theming, keywords |
groupedComponentRegistry |
groupedComponentRegistry.ts |
Derived from componentRegistry
|
Nav-ready grouped/sorted component lists |
blockRegistry |
blockRegistry.ts |
packages/cli/templates/blocks/**/*.doc.mjs |
Example blocks with source code |
showcaseRegistry |
showcaseRegistry.ts |
Subset of blocks where isShowcase: true
|
Dynamic imports for showcase previews |
exampleRegistry |
exampleRegistry.ts |
Subset of blocks where isShowcase: false
|
Dynamic imports + source for example blocks |
templateRegistry |
templateRegistry.ts |
packages/cli/templates/pages/*/template.doc.mjs |
Page template metadata |
docsRegistry |
docsRegistry.ts |
packages/cli/docs/*.doc.mjs |
Full doc topics with sections and content blocks |
themeRegistry |
themeRegistry.ts |
Theme packages installed in docsite | Live theme objects for preview rendering |
Packages are auto-discovered from the root package.json workspaces field. Only packages/* directories are included (not apps/* or internal/*). Private packages and packages not installed in the docsite's dependencies are filtered out.
interface ComponentEntry {
name: string; // "Button", "useToast"
moduleName: string; // "Button", "useToast"
directory: string; // "Button", "hooks"
group: string | null; // Grouping label for nav (e.g. "Button")
description: string; // From usage.description
keywords: string[]; // Search terms
hidden: boolean; // Excluded from nav if true
parentDoc: string | null; // Parent component for sub-components/hooks
props: PropDoc[]; // Prop documentation
usage: UsageDoc | null; // Best practices, anatomy, features
theming: ThemingDoc | null; // CSS class targets, vars
params: HookParamDoc[] | null; // Hook parameters (null for components)
returns: HookReturnDoc[] | null; // Hook return (null for components)
playground: { defaults: Record<string, unknown> } | null;
}interface DocTopic {
topic: string; // URL slug: "tokens", "getting-started"
title: string; // Display title
description: string; // Short description
category: string | null; // "guide" | "foundations" | null
sections: DocSection[]; // Array of {title, content: ContentBlock[]}
}type ContentBlock =
| { type: 'prose'; text: string }
| { type: 'code'; lang: string; code: string; label?: string }
| { type: 'table'; headers: string[]; rows: string[][] }
| { type: 'list'; style: 'ordered' | 'unordered' | 'do' | 'dont'; items: string[] }interface BlockEntry {
dirName: string; // File basename
name: string; // Display name
description: string;
exampleFor: string; // Component name this example belongs to
isShowcase: boolean; // Hero preview vs. inline example
aspectRatio: number; // For card thumbnails
componentsUsed: string[];
category: string; // Path like "components/Button"
source: string; // Raw TSX source
}The sidebar navigation is built in DocsShell.tsx from the generated registries. Here's the mapping:
| Nav Section | Data Source | How Items Are Derived |
|---|---|---|
| Home | Hardcoded | Single link to /
|
| What's New | Hardcoded | Single link to /changelog
|
| Guide | docsRegistry |
Topics with category === 'guide', sorted alphabetically |
| Guide → Foundations | docsRegistry |
Topics with category === 'foundations', "tokens" pinned first, rest alphabetical |
| Libraries | packageRegistry |
Non-theme packages, filtered to non-private + installed in docsite |
| Themes | packageRegistry |
Packages where name includes theme-, uses displayName
|
| Components | groupedComponentRegistry |
Grouped items from @xds/core (see grouping logic below) |
| Utilities | groupedComponentRegistry |
Hooks/utilities separated from main component list |
The generateGroupedComponentRegistry function in generate-data.mjs groups components for the nav sidebar:
-
Utilities bucket — components with
group === 'Utilities'or standalone hooks in thehooks/directory -
Explicit groups — components with a
groupfield in their.doc.mjs -
Directory-based groups — hooks without
parentDocthat live in a non-hooks directory are grouped by directory name -
Parent-child groups — hooks with a
parentDocfield are nested under their parent component - Ungrouped — everything else appears as standalone nav items
Groups with a single item render as a plain entry. Groups with multiple items render as a collapsible section.
To add a new doc topic category (beyond guide and foundations):
- Add
category: 'your-category'to the.doc.mjsfile inpackages/cli/docs/ - In
DocsShell.tsx, add filtering logic:const yourTopics = docTopics.filter(d => d.category === 'your-category') - Add a new
<SideNavSection>or nested<SideNavItem>for the section - Add active-state detection for the new category
- Create
packages/core/src/<ComponentDir>/<Name>.doc.mjs - Export a
docsobject typed asComponentDoc(seepackages/core/src/docs-types.ts) - Run
node apps/docsite/scripts/generate-data.mjsto regenerate registries - The component automatically appears in nav and at
/components/<Name>
- Create
packages/cli/templates/blocks/components/<Name>/<Name>Showcase.doc.mjswithisShowcase: trueandexampleFor: '<Name>' - Create the matching
<Name>Showcase.tsxwith adefaultexport - Regenerate data — the showcase appears on the component's Overview tab
- Create
packages/cli/templates/blocks/components/<Name>/<ExampleName>.doc.mjswithisShowcase: falseandexampleFor: '<Name>' - Create the matching
.tsxfile with adefaultexport - Regenerate — the example appears in the component's "Examples" section
- Create
packages/cli/docs/<topic>.doc.mjs - Export a
docsobject typed asReferenceDoc - Set
category: 'guide'orcategory: 'foundations'to control nav placement - Regenerate — the topic appears in the sidebar under its category
Set hidden: true in the component's .doc.mjs. It won't appear in the sidebar but the page still exists at /components/<Name>.
cd apps/docsite
pnpm install
pnpm dev # Runs generate + next devThe predev script auto-regenerates data. After changing any .doc.mjs file, restart the dev server or manually run:
node scripts/generate-data.mjspnpm test # Runs data extraction testsTests live in apps/docsite/src/__tests__/data-extraction.test.ts and verify the generation pipeline produces valid registries.
The docsite deploys to Vercel at xds-sandbox.vercel.app. It uses Next.js static export (generateStaticParams) for component and doc pages. The build step runs generate-data.mjs automatically via prebuild.