fix(spec): render the reference section for a non-object root schema (#7658) - #7808
fix(spec): render the reference section for a non-object root schema (#7658)#7808os-help wants to merge 2 commits into
Conversation
…7658) `build-docs.ts` picked the node it was documenting by enumerating shapes — `properties`, `enum`, `anyOf`, `oneOf` — and answered "none of those" with `return ''`. A JSON Schema root is routinely none of those: `z.string().describe(…)` compiles to a bare scalar, `z.record(…)` to an object with `additionalProperties` and no `properties`, `z.array(…)` to an array, `z.intersection(…)` to an `allOf`. Each lost its whole `## Name` section — heading included, and with it the `.describe()` prose written to be read — while the page's `## TypeScript Usage` block, spelled from the export surface rather than from this function, went on naming the export. Measured on today's tree: 45 published schemas were in that state, 33 carrying a description. The card counted 22 (`~23`), having scanned only the bare-scalar spelling. Regenerating adds 244 lines across 27 pages and removes none, so the 1533 sections that already rendered are byte-identical. Emitted files 230 → 230: an empty section never removed a page, so no category's `pages.length` moves and the #7303 `meta.json` guard is untouched. The renderer moves to `scripts/lib/schema-section.ts` so it can be pinned directly — the extraction `lib/format-type.ts` got at #4912, for the same reason. Its output here was the empty string, which is precisely what grepping emitted `.mdx` cannot see, and why `check:docs` stayed green over this for months. Reverse-verified: restoring the old fallback turns 7 of the 13 new cases red and leaves the other 6 green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RpBD5DrXb9gsPKkRHL8FHG
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
|
CI red on Cause — - apiMethods: ['get', 'list'],
+ apiMethods: ['get', 'list', 'update'],Granting a single-record write verb without Why That cache blind spot is worth a card of its own: a cross-package scanning test whose inputs turbo cannot express will keep reporting the last state anyone happened to invalidate it in. Why I am not fixing it in this PR. The test's own docblock says to pick one deliberately — grant Every gate this PR is responsible for is green: Generated by Claude Code |
|
Correcting the "Pre-existing failure, not from this diff" note at the bottom of the body — it was the right call, but argued from an empty diff rather than a measurement, and it did not name the card that already owns it.
The sibling-PR control that motivated the send-back doesn't hold: #7810's three No assertion was weakened and nothing was pushed — the branch is unchanged at Generated by Claude Code |
Fixes #7658
What was wrong
packages/spec/scripts/build-docs.ts'sgenerateMarkdown()picked the node it was documenting by enumerating shapes —properties,enum,anyOf,oneOf— and answered "none of those" withreturn ''.A JSON Schema root is routinely none of those:
z.string().describe(…){ type: 'string', description }z.record(…)type: object+additionalProperties, nopropertiesz.array(…){ type: 'array', items }z.intersection(…){ allOf: [...] }Each lost its entire
## Namesection — the heading, and with it the.describe()prose an author wrote to be read — while the page's## TypeScript Usageblock, which is spelled from the export surface rather than from this function, went on naming the export. The page read as if it had forgotten to finish rendering an entry it had just imported.The count, re-measured today
The card says "~23 exports with a real
.describe()". Re-deriving it by replayinggenerateMarkdown's exact root-selection over all 1578 published schemas (wc -lon the emitted lists, no pager in the pipeline):return '')descriptionThe card scanned only the bare-scalar spelling. The other 13 are record maps, one array root (
system/OidcProvidersConfig) and oneallOf(data/FilterCondition) — the same defect through the same line, so they are fixed with it. Scope expansion, disclosed rather than absorbed.Positive control on the same page, before the fix:
content/docs/references/cloud/tenant.mdxrenders## TenantContext,## TenantDatabaseand 7 more, and does not render## TenantPlan— while line 26 of that same file importsTenantPlanSchema. The zero is a measurement, not a dead walk.The fix
selectRootDefgains a fourth, unconditional step: the document itself is the schema. Appended rather than folded into the root-shape test — a document that reaches the "first$defsentry" step today must keep reaching it, or a root that merely looks renderable would start outranking the definition with the actual content.elserenders the description plus one**Type:**line, using the sameformatTypeevery property row on the page already uses, so a schema's own section and a property typed with it cannot disagree.allOfis spelled as an&intersection (root-only spellingformatTypehas never met in a property position), with identical members collapsed — safe here becauseA & AisA, and deliberately not the union rule, where arity is the point (gen:docs 深度预算落地后,11 个单元格出现object | object | object | object—— 同形变体是否该去重,落在 #6226 的裁决面上 #6569).anyis withheld rather than printed: on this page it reads as "free-form slot, nothing validates it", the misreading [docs-gen] 生成的 reference 把retiredKey()墓碑渲染成any—— 嵌套两层时连[REMOVED]处方都没有,退役键读起来像自由槽 #5606 fixed for tombstones.pattern,minLength,minimum) are deliberately not spelled — this renderer prints none in any position, andjson-schema/stays the authority.The renderer moves to
packages/spec/scripts/lib/schema-section.tsso it can be pinned directly: the extractionlib/format-type.tsgot at #4912, for the same reason. Its output here was the empty string, which is exactly what grepping emitted.mdxcannot see — and whycheck:docs(generated vs. committed) stayed green over this for months.Regeneration
##headings are exactly the 45 measured dropped names (set diff empty); none of the 1533 already-rendering sections moves a byte.pages.lengthchanges — [finding]content/docs/references/contracts/是一个只剩meta.json的空目录,build-docs.ts 已不再产出它 #7303'spages.length === 0meta.jsonguard is untouched, andcontracts/neither gains nor loses a directory (14 category dirs before and after).gen:docsrun twice: second run byte-identical to the first (same tree sha256, same 27 changed files).github-slugger— 0 collisions. No heading renamed, so there is no old fragment to grep for ([finding] No CI gate checks documentation anchors —lychee.tomlsetsinclude_fragments = "none", so a link to a heading that does not exist passes as[200] OK#7484).Gates
pnpm --filter @objectstack/spec run check:docspnpm --filter @objectstack/spec run gen:docs×2pnpm --filter @objectstack/spec exec vitest run scripts/schema-section.test.tspnpm --filter @objectstack/spec run typecheckpnpm --filter @objectstack/spec run check:api-surfacepnpm --filter @objectstack/spec run check:generatedpnpm exec eslint <changed files>pnpm run check:doc-authoring/check:empty-changeset/check:adr-0087-registrationpnpm --filter @objectstack/spec run testReverse verification: restoring the old behaviour (dropping the unconditional root fallback and returning
'') turns 7 of the 13 new cases red — the six render cases lose their whole section, heading included — and leaves the other 6 green. That asymmetry is the point: the four shapes the old code enumerated were always correct, which is why this survived unnoticed.Pre-existing failure, not from this diff:
src/data/api-methods-batch-conformance.test.tsfails onsys_api_key: [get, list, update] grants single-record writes but not 'bulk'inpackages/platform-objects/src/identity/sys-api-key.object.ts— a file this branch does not touch (git diff --name-only origin/main -- packages/platform-objects→ 0).