fix(spec-docs): one shared def renders one optionality face on every reference page - #21478
Merged
objectstack-fleet[bot] merged 8 commits intoOct 3, 2026
Merged
Conversation
… Required column
The reference generator's `{ … }` shape summary marked a member optional from
the enclosing object's `required` array alone. `build-schemas.ts` emits each
published document in one io mode (output by default, input when the output
projection throws on a transform), and the two modes disagree about a
`.default()` member's `required` entry, so one shared def rendered `order:` in
an output-mode document and `order?:` in an input-mode one on the same page.
The summary now asks the same predicate the Required column already asks:
`default` decides, `required` breaks the tie. Over the corpus, 1398 exports
project in both io modes; before, 215 rendered differently (483 lines, every
one this marker); after, 0.
Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d
Co-authored-by: Claude <noreply@anthropic.com>
… in every shape summary `check:generated --fix` after the shape-summary fix: 459 rows on 86 pages, every one adding `?` markers to defaulted members (932 markers), none removing one, no other byte moved. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Two parent rows share a grouping def and a request def through the generator's own projection; one parent projects in output mode, the other only in input mode (a transform member). The precondition cases prove the two documents disagree about `required`; the render case requires the shared defs' rows to be byte-identical and the input face. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…cs-gen-shared-def-face
…cs-gen-shared-def-face
The merge of main brought the object-grid typed members; component.mdx is generated, so it is regenerated from the merged tree rather than text-merged. Against main's copy: 14 rows move, every one adding `?` to a defaulted member, none removing one. Every shared def on the page now renders one face. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…cs-gen-shared-def-face # Conflicts: # content/docs/references/automation/state-machine.mdx
The merge of main brought the agent.lifecycle retirement. agent.mdx is generated, so it is regenerated from the merged tree instead of being text-merged, and state-machine.mdx stays deleted as main has it. Against main's copy, the references delta is 453 rows on 85 pages. Each row only adds `?` to a defaulted member (925 markers), and no other byte moves. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-21466-docs-gen-shared-def-face
branch
October 3, 2026 00:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #21466
Clause-②: no
What was wrong
A generated reference page gave two optionality answers for one shared def. The card measured it on PR #21463's regeneration of
content/docs/references/ui/component.mdx:ObjectGridProps.grouping.fields[]renderedorder?/collapsed?, anddata[provider='api'].read/.writerenderedmethod?, while the same defs on theObjectKanbanProps,ObjectGanttProps,ObjectMapPropsandObjectTreePropsrows rendered them required.Mechanism: measured, and it is not evaluation order
The card's working hypothesis was an evaluation-order dependence: a memoised conversion, a
.shapegetter or a def cache decided by whichever caller came first. Measured, there is none:projectPublishedJsonSchemaand its output-to-input fallback, under 2 import orders (ui/view.zodfirst orui/component.zodfirst) times 2 emission orders (grid first or grid last), withOS_EAGER_SCHEMAS=1asgen:schemaruns. All four orders print byte-identical output, with 1 distinct output per tree. That holds onmainat6210f8870a, on feat(spec)!: an object-grid page block types the seven members the grid reads, and resizableColumns retires for resizable (#21445) #21463's head38159d1362, and on this branch's final head.ui/ObjectGridProps.jsonis the one props schema emitted "(input shape)". Its output projection throws "Transforms cannot be represented in JSON Schema", because one newly typed member carries a transform. Sobuild-schemas.tsfalls back toio: 'input'for the whole document. Input mode leaves.default()members out ofrequired, output mode lists them, and both keepdefault.{ … }shape summary marked a key optional fromrequiredalone (scripts/lib/format-type.ts). The Required column already readdefault(renderRequiredCell, [finding] reference-doc tables render.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's rule). Before this change, [finding] reference-doc tables render.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's rule covered only one of the two places a page states optionality.Corpus reading on
main6210f8870a, with the old renderer: 1398 exports project in both io modes. When each section is rendered from both projections, 215 of them differ, over 483 lines. Each of the 483 lines differs only by this marker.Fix
There is one predicate now,
isAuthorOmittable(prop, required)informat-type.ts:defaultdecides, andrequiredbreaks the tie. Both optionality positions read it: the shape summary'skey?:marker andrenderRequiredCell, whose output is unchanged byte for byte.What does not change:
.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's design, the published files keep describing the post-parse shape.packages/spec/src/**change.Afterwards the same corpus measurement reads 0 of 1398 differing (0 lines) on
6210f8870a, and 0 of 1392 on the final headd5d88f55a5. The population is smaller on the final head becauseObjectGridPropsis input-only there andmain's agent.lifecycle retirement removed some exports.Regenerated pages (the second pin)
check:generated --fixon the base changed 459 rows on 86 pages. Each changed row only adds?to defaulted members (932 markers in total). No row removes one, and no other byte moved.mainwas merged in twice, and each time the colliding generated page was regenerated on the merged tree rather than text-merged:component.mdx.mainat0b8239111f(the agent.lifecycle retirement plus one non-docs commit) and regeneratedagent.mdx.automation/state-machine.mdxstays deleted, asmainhas it.Against
mainat0b8239111f(and identically against6e33b67912), the references delta is 453 rows on 85 pages, 925 markers added. With every?:normalised to:, each of the 85 pages is byte-identical to main's copy. No row removes a marker.On the final
component.mdx:method?:appears 8 times andmethod:0 times;collapsed?:3 times andcollapsed:0 times.grouping.fieldsrows are byte-identical.data[provider='api']readrows are byte-identical, and so are the fourwriterows.The same JSON from #21463's head, rendered with the old renderer, gives the card's exact rows (
order?on the grid andorderon the kanban). Rendered with this branch's renderer, every shared-def row is identical.Pin
packages/spec/scripts/schema-section.test.tshas a new block, "one shared def renders one face, whichever io mode its document took". Two parent rows share a grouping def and a{ url, method }def through the generator's own projection. One parent projects in output mode; the other projects only in input mode, because it has a transform member.requiredarrays disagree. So the identical rendering comes from the renderer, not the fixture.Reverse verification used the committed fix at
3a62f4a391. The mutation went throughscripts/ablation-replace.mjs: anchor count 1 to 0, blob changed. The restore was proven: blob equals HEAD andgit diff HEADis empty. Putting back the oldrequired-only marker turns 2 of 137 cases red: the render case and the hand-written summary case. The two precondition cases stay green, as designed. The subject resolves to source through a relative import, so nodist/leg applies.Gates
At the final head
d5d88f55a5, after the second merge:pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 602 files, 17769 passed, 1 todo.check:generated("All 15 generated artifacts are up to date"),check:docsandcheck:nul-bytes: exit 0.At
ce1d8ceaf8, before the second merge, which brought only main's commits and one regenerated page:pnpm --filter @objectstack/spec typecheck(the build program,tsconfig.scripts.jsonand the test program): exit 0. The three touched TS files are in thetsconfig.scripts.jsonprogram (checked with--listFilesOnly).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN, every one exit 0. That includescheck:generated,check:docsandcheck:nul-bytes. This full union was not re-run atd5d88f55a5.eslint --no-inline-config --format jsonreads 3 files, 0 errors, 0 warnings. The.mdxpages match no lintfilesglob ineslint.config.mjs. That config enables no type-aware linting (noparserOptions.project), so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintis left to CI.Changeset
skip-changeset, measured:@objectstack/spec'sfiles[]isdist,json-schema,liveness,prompts,llms.txt,README.md,src/**/*.zod.ts,CHANGELOG.md,api-surfaceandspec-changes.json.packages/spec/scripts/**andcontent/docs/references/**. The docs site,apps/docs, is private.isAuthorOmittableandcarriesDefaultmatch 0 files under those paths. The positive control,lazySchema, matches 240.check:generatedmoved no published artifact.Acceptance notes
default. A member spelled as a bare$refto a defaulted def would carrydefaultonly on the def, not on the property node. The corpus reads 0 such members (0 differing lines in either column after the change). Carrier: none.json-schema/publishes for that document, and that is out of this card's scope.Generated by Claude Code