docs(spec): the master-detail detail entry's inlineMode and formFields describes state both renderer paths (#21284) - #21307
Conversation
…s describes state both renderer paths An entry that names both relationshipField and at least one column is kept as authored by the renderer: an omitted inlineMode is not resolved from the relationship's inlineEdit and an omitted formFields is not derived. The two describes, and the factory's docblock clause that repeated the inlineMode claim, now say what happens on that path and on the derived one. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…l entry describes Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6ddc13d60642d2c79528b6df5f77530f4e9bb1a7 && git checkout 6ddc13d60642d2c79528b6df5f77530f4e9bb1a7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 393ae878d3d52fe843c56b4621c004b934dcf853 e21f622b6512432103df79a6df6eb53a721722d6 && git checkout -B drift-repro 393ae878d3d52fe843c56b4621c004b934dcf853 && git merge --no-ff e21f622b6512432103df79a6df6eb53a721722d6
node scripts/docs-audit/affected-docs.mjs --json 393ae878d3d52fe843c56b4621c004b934dcf853 |
Contract reviewServed-tier: Isolated contract review of PR #21307 (card #21284), written 2026-10-02T04:11Z. Inputs: the card and its three comments (triage ① Derived judgmentsAccept set and public surface: nothing moves — right. Three files, +18/−5. In
Consistency with spec code on The TSDoc clause — same defect, same ruling, true, no other claim. The The changeset's prose — right. It restates the two paths exactly as the diff and the pin have them (derived: ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #21284
Clause-②: no
What changed
An
object-master-detail-formdetail entry has two describes that say what an omitted key means:inlineModeandformFields. Both now state the renderer's two paths. The reference page that lifts them (content/docs/references/ui/component.mdx) is regenerated bycheck:generated --fix. No schema accepts or refuses anything new. There is no shape, default or nullability change, and no edit to objectui orpackages/lint.inlineMode, before:after:
formFields, before:after:
What the renderer does (objectui at the
.objectui-shapin31971ff1e28f, read withgit show)packages/plugin-form/src/MasterDetailForm.tsx:needsDerive: an entry needs resolution when it has norelationshipField, no columns, or an untyped column.relationshipFieldset and every column typed. The entry is returned unchanged ({ ...entry, status: 'ready' }).relationshipFieldset andcolumnsnon-empty. The config becomes{ ...d, columns: derived.columns, amountField, sortField }, soformFieldsandinlineModestay as authored.formFields: d.formFields ?? derived.formFields(1063) andinlineMode: d.inlineMode ?? derived.mode(1064).derived.modecomes fromresolveInlineMode(deriveMasterDetail.ts536–539 and 456–470). It is the relationship field'sinlineEditwhen that isgridorform, and otherwise a default chosen from the child object's shape.d.inlineMode === 'form', or whenformFieldsis longer thancolumns. At 850,displayModeislistonly forform; otherwise it isgrid.fieldsonly whenformFieldsis non-empty. Without them,ObjectForm(ObjectForm.tsx961) drawsObject.keys(objectSchema.fields), which is every field the child object declares.So, for the question the card left open about
formFieldson the kept-as-authored path: when it is omitted, nothing is derived. A grid never offers the per-row form, because zero fields is never more than the column count. The form is offered only wheninlineModeisform, and it then draws the child object's full field list. The old text was false there, so this PR rewords it. It also drops "editable": the derived list keepsreadonlyfields (deriveFormFieldsat the pin, 404–420, andderiveInlineRowFormFieldsinpackages/spec/src/data/inline-grid-columns.ts).The wording matches what the spec already says about both paths. "Kept as authored" means an entry that names both
relationshipFieldand at least one column, as in thecreditAuthoredRowFormdocblock inpackages/lint/src/validate-field-consumers.ts. The offer condition isisInlineRowFormOfferedin@objectstack/spec/data.A bounded in-place fix, declared
The
masterDetailDetailEntryfactory's TSDoc (component.zod.ts, the paragraph above the factory) saidinlineMode's "absence takes the relationship's own resolution". That is the same claim, true on only one path, and it ships in the tarball undersrc/**/*.zod.ts. It now says this holds only on an entry the renderer derives, and points to the describe. This is outside the claim's declared file surface, which names the two describes. It is made under the bounded in-place rule: it is the same defect, this card's ruling fixes its form, no other claim holds that region of the file, and it adds no new gate. It is a comment-only change of one added line.Released surface
The card says the sentence "is not yet in a release". Measured today,
@objectstack/spec@17.6.0is published aslatest, and its tarball carries the sentence once each insrc/ui/component.zod.tsanddist/ui/index.js. So 17.6.0 already ships it. Thispatchcorrects it in the next release.Acceptance notes
sortField's describe, "(derived from aposition/sort_order/ … field when omitted)", is false on the fast path. At 967, an entry withrelationshipFieldand every column typed is returned as authored, so an omittedsortFieldis not derived. The hydrate path at 1037–1051 does derive it, since objectui#11144. This PR does not touch it: it is outside this card's surface, and someone must decide whether the renderer's fast path or the describe changes. It is reported to the seat.Verification (every reading below is on HEAD
e21f622b65)pnpm --filter @objectstack/spec build:VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generated: the first run reported1 of 15 artifact(s) stale: content/docs/references/**, and every other artifact was current, the authorable surface and JSON schemas included.--fixregenerated that one, and its re-check gave✓ check:docs. Run again in the gate union: exit 0.pnpm --filter @objectstack/spec typecheck: exit 0.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2:Test Files 597 passed (597),Tests 17484 passed | 1 todo.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run with no paths: 102 commands, the same set as the dispatch's derivation. All were run, and--rananswers102 derived famil(ies) accounted for — 101 run, 1 NOT-MEASURED.check:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-driftandcheck:lean-entry-closure. After building that closure (turbo run buildfor formula, lint, client-react and objectql: 34 tasks), all five exit 0.pnpm check:dual-build-cjs-loads. It needs every workspace package'sdist, which needs a full-repo build. As a narrower check, all 19requireentry points of@objectstack/specload. CI runs the full gate.component.zod.tsis in eslint's population. For the.mdand.mdxfiles, eslint answers "File ignored because no matching configuration was supplied".--format jsonreports 1 file, 0 errors and 0 warnings. That file's resolved config hasparserOptionswithoutproject, so linting is not type-aware and this diff cannot change the verdict for any untouched file. The fullpnpm lintruns in CI.Generated by Claude Code