diff --git a/.changeset/references-empty-category-meta.md b/.changeset/references-empty-category-meta.md new file mode 100644 index 0000000000..129dbe93c1 --- /dev/null +++ b/.changeset/references-empty-category-meta.md @@ -0,0 +1,32 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): stop emitting a `meta.json` for a reference category that publishes no page (#7303) + +`gen:docs` wrote `content/docs/references//meta.json` for every category +it iterated, including one whose page list came out empty. The result was a +directory holding a single `{ "title": …, "pages": [] }` and nothing else: no +reference page, no `index.mdx` (§2.5 already skipped that), and no entry in the +root `meta.json`, so the route could not resolve and the link checker never saw +it. Its one measurable effect was on whoever enumerated the tree — human or +agent — who counted one category more than the docs actually publish. + +`contracts/` was the standing case. It holds TypeScript service interfaces +rather than `.zod.ts` schemas, so `gen:schema` creates `json-schema/contracts/` +and leaves it empty; `conversions`/`migrations` have no schema directory at all +and are skipped outright, while `contracts` reached the emit with zero pages. +The `meta.json` emit is now guarded on the page list being non-empty, mirroring +the guard the category `index.mdx` emit already carries, so all three behave +alike. `content/docs/references/` goes from 15 category directories to 14. + +The guard is on the page count, not on that schema-directory asymmetry, so a +future category in either shape lands the same way. + +No published page changes: the emitted file count goes 231 → 230, and the one +file that stops being written is the empty `meta.json`. The Contracts Protocol +prose documentation is unaffected — it lives at `content/docs/kernel/contracts/` +and is not generated from this tree. `contracts` also remains a declared +category in `scripts/lib/category-title.ts`; `resolveCategoryTitles` is total +over the directories in `packages/spec/src/`, so that declaration is mandatory +while the module exists. diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index d5106a7e79..0989ba0fea 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -233,15 +233,14 @@ Testing and quality assurance. ## Categories Without a Section -`content/docs/references/` holds two more category directories that deliberately get no +`content/docs/references/` holds one more category directory that deliberately gets no section above. Curation happens at the category level as well as inside each table, and this is where it is stated. The same gate reads this table, so a new category directory — -or a page landing in one of these — goes red until this page is updated. +or a page landing in this one — goes red until this page is updated. | Category directory | Pages | Why it has no section | |:---|---:|:---| | [`studio`](/docs/references/studio) | 3 | Designer-facing metadata (`flow-builder`, `object-designer`, `plugin`) — Studio's own authoring surfaces, not protocols an app declares. Reach them from the [reference index](/docs/references). | -| `contracts` | 0 | Publishes no reference page at all; the directory holds only a `meta.json` left over from an earlier layout. There is nothing to link. | --- diff --git a/content/docs/references/contracts/meta.json b/content/docs/references/contracts/meta.json deleted file mode 100644 index 532dffd336..0000000000 --- a/content/docs/references/contracts/meta.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "title": "Contracts Protocol", - "pages": [] -} \ No newline at end of file diff --git a/packages/spec/scripts/build-docs.ts b/packages/spec/scripts/build-docs.ts index 8f956cffa2..aa854926bc 100644 --- a/packages/spec/scripts/build-docs.ts +++ b/packages/spec/scripts/build-docs.ts @@ -837,9 +837,27 @@ PAGES_BY_CATEGORY.forEach((zodFileSchemas, category) => { // Generate Category Meta. Group into fumadocs `---Section---` separators when the // category has a SECTION_GROUPS entry; otherwise a flat sorted list (see #1880). + const pages = buildCategoryPages(category, Array.from(zodFileSchemas.keys())); + + // A category that published no page gets no `meta.json` — and so no directory + // at all — the same way §2.5 below skips the `index.mdx` of one. What this + // guard removes is a folder holding a single `{ "pages": [] }`: no page, no + // `index.mdx`, and no entry in the root `meta.json`, so it is unroutable, and + // invisible to the link checker. Its one measurable effect was that anyone + // enumerating the tree counted one category more than exists — which is how + // #7303 came to be filed. + // + // `contracts/` is the case: it holds TypeScript service interfaces rather than + // `.zod.ts` schemas, so `gen:schema` creates `json-schema/contracts/` and + // leaves it empty; unlike `conversions`/`migrations` (no schema directory at + // all, so `groupSchemasByPage` skips them outright) it reaches this loop with + // zero pages. The guard is on the pages, not on that asymmetry, so any future + // category in either shape lands the same way. + if (pages.length === 0) return; + const meta = { title: CATEGORIES[category], - pages: buildCategoryPages(category, Array.from(zodFileSchemas.keys())) + pages }; emit(path.join(categoryDir, 'meta.json'), JSON.stringify(meta, null, 2)); }); diff --git a/scripts/check-quick-reference-counts.mjs b/scripts/check-quick-reference-counts.mjs index 179a5cf21f..6f7b632ead 100644 --- a/scripts/check-quick-reference-counts.mjs +++ b/scripts/check-quick-reference-counts.mjs @@ -77,12 +77,12 @@ * * ## Curation also happens one level up * - * `references/studio/` (3 pages) and `references/contracts/` (0 pages) have no - * section on the page at all. Left implicit, that is the same unwatched drift - * one level up, so the page carries a `## Categories Without a Section` table - * and this gate reads it: every category directory under - * `content/docs/references/` must either be a section's derived category or be - * declared there with its real page count — never both, never neither. + * `references/studio/` (3 pages) has no section on the page at all. Left + * implicit, that is the same unwatched drift one level up, so the page carries + * a `## Categories Without a Section` table and this gate reads it: every + * category directory under `content/docs/references/` must either be a + * section's derived category or be declared there with its real page count — + * never both, never neither. * * ## Absence must be loud *