Skip to content

fix(spec-docs): one shared def renders one optionality face on every reference page - #21478

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21466-docs-gen-shared-def-face
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-21466-docs-gen-shared-def-face

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

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[] rendered order? / collapsed?, and data[provider='api'].read / .write rendered method?, while the same defs on the ObjectKanbanProps, ObjectGanttProps, ObjectMapProps and ObjectTreeProps rows 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 .shape getter or a def cache decided by whichever caller came first. Measured, there is none:

Corpus reading on main 6210f8870a, 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) in format-type.ts: default decides, and required breaks the tie. Both optionality positions read it: the shape summary's key?: marker and renderRequiredCell, whose output is unchanged byte for byte.

What does not change:

Afterwards the same corpus measurement reads 0 of 1398 differing (0 lines) on 6210f8870a, and 0 of 1392 on the final head d5d88f55a5. The population is smaller on the final head because ObjectGridProps is input-only there and main's agent.lifecycle retirement removed some exports.

Regenerated pages (the second pin)

check:generated --fix on 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.

main was merged in twice, and each time the colliding generated page was regenerated on the merged tree rather than text-merged:

Against main at 0b8239111f (and identically against 6e33b67912), 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 and method: 0 times; collapsed?: 3 times and collapsed: 0 times.
  • The grid's and the kanban's grouping.fields rows are byte-identical.
  • The four data[provider='api'] read rows are byte-identical, and so are the four write rows.

The same JSON from #21463's head, rendered with the old renderer, gives the card's exact rows (order? on the grid and order on the kanban). Rendered with this branch's renderer, every shared-def row is identical.

Pin

packages/spec/scripts/schema-section.test.ts has 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.

  • Two precondition cases prove the fixture really is one output-mode and one input-mode document, and that their required arrays disagree. So the identical rendering comes from the renderer, not the fixture.
  • The render case requires the shared defs' rows to be byte-identical and on the input face.
  • A fourth case pins the marker on hand-written nodes.

Reverse verification used the committed fix at 3a62f4a391. The mutation went through scripts/ablation-replace.mjs: anchor count 1 to 0, blob changed. The restore was proven: blob equals HEAD and git diff HEAD is empty. Putting back the old required-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 no dist/ 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:docs and check: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.json and the test program): exit 0. The three touched TS files are in the tsconfig.scripts.json program (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 includes check:generated, check:docs and check:nul-bytes. This full union was not re-run at d5d88f55a5.
  • Lint was narrowed to the 3 touched TS files: eslint --no-inline-config --format json reads 3 files, 0 errors, 0 warnings. The .mdx pages match no lint files glob in eslint.config.mjs. That config enables no type-aware linting (no parserOptions.project), so this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint is left to CI.

Changeset

skip-changeset, measured:

  • @objectstack/spec's files[] is dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface and spec-changes.json.
  • This diff touches only packages/spec/scripts/** and content/docs/references/**. The docs site, apps/docs, is private.
  • After a build, isAuthorOmittable and carriesDefault match 0 files under those paths. The positive control, lazySchema, matches 240.
  • check:generated moved no published artifact.

Acceptance notes

  • The two-order harness and the corpus io-face measurement were one-time readings from scratch scripts and are not committed. The committed pin is the two-parent-row block above.
  • Open edge, with no instances today: the predicate reads a member's own default. A member spelled as a bare $ref to a defaulted def would carry default only 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.
  • The io fallback is per document by design: one transform anywhere moves the whole document to the input projection. After this change that no longer shows on the page. It does still decide which JSON Schema face json-schema/ publishes for that document, and that is out of this card's scope.

Generated by Claude Code

claude added 6 commits October 2, 2026 21:09
… 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>
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>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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): node scripts/docs-audit/affected-docs.mjs --json 0b8239111fe2195a8d6d120568367ee547d96003 → packageMentionDocs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 2, 2026
claude added 2 commits October 2, 2026 22:53
…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
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 23:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 23:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit fd96a84 Oct 3, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21466-docs-gen-shared-def-face branch October 3, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

2 participants