Manifest builder
How docs.json + on-disk MDX become a typed, navigable manifest.
~ 1 min read
Manifest builder
packages/tangly/src/manifest/ reads docs.json and the MDX files in your project, then produces a single typed Manifest object that the runtime consumes for every page render.
Data flow
flowchart TD A1[docs.json] --> P1["@tanglydocs/schema<br/>parseDocsJson"] A2["*.mdx on disk"] --> S1[scan-pages.ts] S1 --> S2["frontmatter via gray-matter<br/>+ Zod validate"] P1 --> R1[resolve-nav.ts] R1 --> R2[walk tabs → groups → pages] R2 --> R3[per-tab sidebar trees] S2 --> M1[build-manifest.ts] R3 --> M1 M1 --> M2[for each nav slug:<br/>breadcrumbs<br/>prev / next<br/>section defaults] M1 --> M3[OpenAPI expansion<br/>build-openapi-pages] M1 --> M4[content collections<br/>load-collections] M2 --> O[Manifest] M3 --> O M4 --> O M1 --> W[warnings: orphans,<br/>broken refs,<br/>frontmatter errors] W --> O
Shape
interface Manifest {
config: DocsJson;
pages: Map<string, PageEntry>;
navigation: ResolvedNavigation;
orphans: string[];
warnings: ManifestWarning[];
collections?: Record<string, unknown[]>;
root: string;
}PageEntry carries everything a page render needs:
interface PageEntry {
slug: string;
file: string;
frontmatter: Frontmatter;
breadcrumbs: { title: string; slug?: string }[];
sidebar: SidebarItem[];
tab?: { slug: string; title: string };
prev?: { slug: string; title: string };
next?: { slug: string; title: string };
draft: boolean;
blocks?: Record<string, string>; // for <Embed>
}The three steps
-
scan-pages.ts
Walks the project root recursively, reads MDX frontmatter via
gray-matter, validates against the Zod frontmatter schema. Skips:node_modules,dist,.tangly,components/,templates/, hidden dirs, files starting with_(those are section-default sources, surfaced separately). -
resolve-nav.ts
Walks the recursive nav tree (tabs → groups → pages, plus anchors and dropdowns). Computes per-tab sidebars and collects all referenced slugs. Recursive — groups can nest arbitrarily deep.
-
build-manifest.ts
Stitches the two together. For each nav slug, resolves section defaults by walking outward to find
_section.mdx/_meta.json, computes breadcrumbs + prev/next from the flat sidebar order, and extracts named blocks for<Embed>. Reports orphans (MDX files on disk not in nav) and surfaces them as routable but un-linked.
OpenAPI expansion
When docs.json declares an OpenAPI spec at the tab level:
{
"tab": "API",
"openapi": "https://api.example.com/openapi.json"
}build-openapi-pages.ts fetches the spec, generates one synthetic PageEntry per operation, and appends them to the tab’s sidebar. The catch-all renders these via <OpenApiEndpoint> instead of MDX.
Content collections
If the user ships a tangly.config.ts with defineCollections({ ... }), those collection sources are loaded, validated against their Zod schemas, and serialized into manifest.collections. The runtime exposes them via virtual:tangly/manifest.
Warnings
The manifest never throws on user-content issues — it collects them as warnings: ManifestWarning[]:
- Frontmatter validation errors (per file).
- Nav references with no MDX file on disk.
- Unsupported nav node shapes.
- Failed OpenAPI fetch / parse.
- Collection schema violations.
The CLI surfaces these via tangly dev’s startup banner and tangly check.