Repository navigation
docs(spec): record:line_items columns hydrate from the child field, like the master-detail carriers - #24188
Conversation
…carriers RecordLineItemsProps.columns said the block does not hydrate a column from the child object's field. The shared InlineGridColumnSchema it takes by reference says a column's label defaults to the child field's label via hydration, and the master-detail carriers honour that. The describe now states the carriers' rule in their words: identity-only entries hydrate label, type and options from the child field, so the heading translates with the child field's label; declaring type opts out. The TSDoc names the renderer half (objectui LineItemsPanel reusing hydrateColumns) and the defineStack resolved-type check that still walks only the three master-detail carriers. Describe text only: no key, no accept/reject change. Claude-Session: https://claude.ai/code/session_01TyRhWbVbbQpQJTDbW8VahJ Co-authored-by: Claude <noreply@anthropic.com>
…umns describe Claude-Session: https://claude.ai/code/session_01TyRhWbVbbQpQJTDbW8VahJ 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 — 143 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 64556c2676dbc9ccdaa3775b97db6fd869aee0ed && git checkout 64556c2676dbc9ccdaa3775b97db6fd869aee0ed
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 727091c821d7dcaedb4eea547b77933838b471ed a66b47182e19561578a63d0c3c4c7955dabca68d && git checkout -B drift-repro 727091c821d7dcaedb4eea547b77933838b471ed && git merge --no-ff a66b47182e19561578a63d0c3c4c7955dabca68d
node scripts/docs-audit/affected-docs.mjs --json 727091c821d7dcaedb4eea547b77933838b471ed |
Contract reviewServed-tier: Inputs: card #24140 (body, comments 6229394877 / 6234986989 / 6236296173), PR #24188 (body, file list, net diff against ① Derived judgments
② Semver level
③ Boundary flags
Check-runs on Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT · PR #24188 @
|
…he child field (objectstack-ai#12914) Fixes objectstack-ai#12854 Clause-②: no The objectui half of objectstack-ai/objectstack#24140 (the spec describe landed in objectstack-ai/objectstack#24188). ## What changed - `LineItemsPanel` (`record:line_items`) now passes its authored `columns` through `hydrateColumns` from `deriveMasterDetail.ts`, the same function the master-detail carriers call through `deriveDetail`. It was already exported, so `deriveMasterDetail.ts` is untouched. There is no second resolver. - The child schema it hydrates from is the one the panel already loads with `getObjectSchema(childObject)` for the save sanitize and the line affordances (A1). `hydrateColumns` takes it as is: its parameter is the structural `ChildObjectSchemaLike` (`name?`, `fields?`), and the panel's state is that shape or `null` (passed as `undefined`). - A column without a `type` takes the child field's `label`, `type` and `options`. Through the same function it also takes `required`, a lookup's `reference` / `displayField`, file props, `readonlyWhen` / `requiredWhen` and a formula's `computed` / `expr`, exactly as a master-detail grid's column does. An authored `label` or `type` still wins. A column whose field the child object does not declare is returned as written. Until the schema answers, the columns render as authored. ## Translation path The master-detail carriers do not translate a column heading on the client. The hydrated `label` is the one the object-metadata endpoint serves, and that endpoint translates the object document into the session locale (`translateObject` in `@objectstack/spec/system`, reading `objects.CHILD.fields.F.label`). The console adapter's schema cache is cleared on a language switch, so the next read is in the new locale. `record:line_items` now takes its heading from the same served label, so it translates the same way. The zh pin serves the child schema the way that endpoint answers a zh session. ## Print page (A3) A print page does not use a different renderer. `record:line_items` has one registration (`ComponentRegistry.register('line_items', LineItemsPanelRenderer, { namespace: 'record' })` in `packages/plugin-form/src/index.tsx`), and it renders `LineItemsPanel`. On a page that declares `print`, the panel only reads `usePrintPage()` to name its row cap. Printed headings therefore go through the same hydration. ## Pins New `packages/plugin-form/src/LineItemsPanel.childFieldHydration-12854.test.tsx`, through the real panel and `SchemaRendererProvider`: - (a) an identity-only column is headed `Quantity` / `Line status` (the child field labels), not `quantity` / `status`; - (a, zh) under a zh `I18nProvider`, with the schema served in zh, the headings are `数量` / `行状态`; - (b) the `status` cell is a select picker named by the hydrated label, showing the option label `Open` (zh: `未结`) rather than the stored value `open`; - (c) CONTROL: authored labels `Units` / `State` are kept, and the field's type and options still apply; - (c) CONTROL: an undeclared `note` column is headed `note` and edits as a plain text input over the stored value. Ablation, after the fix was committed (`f58f1ce764`), with `scripts/ablation-replace.mjs` from the objectstack checkout and a `trap` restore. `hydrateColumns(schema.columns, childSchema ?? undefined)` was replaced with `schema.columns` plus a marker comment. Anchor count went 1 to 0, and the blob went `892b35f0be3b` to `85ce270d9929`. All 4 tests went red: (a) failed with `Unable to find role="columnheader" and name "Quantity"`, the zh case on `数量`, and both controls on their hydration-dependent assertions. After the restore the blob read `892b35f0be3b`, matching the HEAD blob, and `git diff HEAD` was empty. A first attempt that put back a bare `schema.columns` was refused by the tool before the run (the replacement text already occurred in the anchor, so its count could not rise), and nothing was run on it. ## Verification (on `f58f1ce764`) - New test file: `Tests 4 passed (4)`, lock verdict `command-exit 0`. - Closure build `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build`: exit 0. - `pnpm --filter @object-ui/plugin-form run type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`): exit 0. `--listFilesOnly` on `tsconfig.test.json` counts the new test file once. - eslint `--no-inline-config` on the 2 changed source files: 0 errors and 17 warnings. All warnings are on lines this PR did not touch (`no-explicit-any`, `set-state-in-effect`, `exhaustive-deps`). - The pre-tsc battery all exited 0: `check:phantom-deps`, `self-import`, `unreferenced-sources`, `doc-example-readers`, `handler-key-reads`, `side-effects-array`, `element-data-source-declaration`, `spec-symbols`, `action-forward-parity`, `designer-field-key-parity`, `icon-record-names`, `i18n-keys`, `i18n-drift`, `i18n-designer-parity`, `control-bytes`, `scripts/check-hand-rolled-comment-mask.mjs`, `scripts/check-changeset-fixed.mjs` and `scripts/check-changeset-presence.mjs`. - `check:spec-floors`: exit 1, `no-artifact` (the workspace was not fully built). Recorded, not fixed. - NOT MEASURED: `plugin-form` package dir, reason: lock queue. Three timed acquisitions (300 s, 360 s, 420 s) never reached the head while one holder ran a full `packages/components/` vitest for more than 24 minutes, and an earlier whole-package run was cut by the foreground cap. This run is declared to CI. ## Acceptance notes - The read-only table of the grid (`GridField` with `readonly`) draws a `select` cell as the stored value. Its cell sends only file and temporal columns through `displayText`, so `displayText`'s select branch is not reached from that table. A read-only line-items panel therefore shows `open`, not `Open`, even after this change (measured while writing the pins). That is not this PR's scope, and it is reported to the seat. - Riders for objectstack's pin bump that carries this: see the `domain:spec` seat's note on the card. Changeset: `.changeset/12854-line-items-hydrate.md` (`@object-ui/plugin-form: patch`). Session: `https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB` --- _Generated by [Claude Code](https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #24140
Clause-②: no
The spec half of route B from the triage grade (comment 6229394877):
record:line_itemscolumnsnow says that the block hydrates an identity-only column from the child object's field, as the master-detail carriers do. The heading of an identity-only column is then the child field's label, and it translates with that label. This change adds no key and changes no accept/reject result. The renderer half is objectstack-ai/objectui#12854 (itsBlocked-by:names this card).What changed
packages/spec/src/ui/component.zod.ts,RecordLineItemsProps.columnsdescribe. Before, it said "this block does NOT hydrate a column from the child object's field: declarelabel,typeandoptionsyourself". It now states the carriers' rule in the carriers' words: "identity-only entries ({ name }) hydrate everything else from the child object's fields,label,typeandoptionsincluded, so an identity-only column is headed by the child field's label and translates with it". It also states the opt-out inInlineGridColumnSchema.type's words: "Declaringtypeopts a column out of hydration — supply itslabel,optionsand other extras yourself". The "Required, with at least one column" sentence is kept.RecordLineItemsPropsTSDoc. The paragraph that explained the old difference is rewritten. It now says that the column means what it means on the carriers, becauseInlineGridColumnSchema.label's own describe governs every carrier. It names the renderer half (LineItemsPanelreusinghydrateColumns, not a second resolver). It also says that until the.objectui-shapin carries that half, an identity-only column still draws as a text cell headed by its name. Last, it says thatdefineStack's resolved-type check (collectHydratedInlineColumnErrors) walks only the three master-detail carriers. One phrase moved in the presence paragraph: "drawscolumnsas given" became "draws only the authoredcolumns". The column set is still never derived here.content/docs/references/ui/component.mdx. Regenerated bygen:docs: one table row changed.check:generatednamed only this artifact as stale..changeset/24140-line-items-columns-hydrate.md.@objectstack/specpatch, because the describe text ships indistand in the JSON Schema.packages/spec/src/data/field.zod.tsis untouched.InlineGridColumnSchema.label("defaults to the child field's label via hydration") andtype("Declaring it opts the column out of schema hydration") were false for this one block. After this change they are true for every carrier, so they do not need to namerecord:line_items.Measured before editing (origin/main cb8756b)
component.zod.ts:2494carried the "does NOT hydrate" describe, andfield.zod.ts:971/:972carried the hydration describes. On the renderer side, objectui main26323c4packages/plugin-form/src/LineItemsPanel.tsxhandsapplyColumnPermissions(schema.columns, …)to the grid. A grep forhydratein that file returns zero hits. Control: the same grep findshydrateColumnsinderiveMasterDetail.ts,MasterDetailForm.tsxand their tests.inlineColumns, andcolumnson anobject-master-detail-formdetail entry) is "identity-only entries ({ name }) hydrate everything else from the child object's fields". objectuihydrateColumns(deriveMasterDetail.ts, objectui26323c4) hydrates a column only when it declares notype, and it keeps an authoredlabel. It fillslabel,type,required,options, the lookup target, the file props,readonlyWhen/requiredWhenand a computedexpr. The new describe reuses the carriers' sentence and names the three keys the card names. It adds no narrower or wider rule.line_itemsinpackages/spec/liveness/hits onlypage.json:57andview.json:351. In thepage.jsonrowregions, entry [15] citescolumns L#gridField (applyColumnPermissions). That is a read point, not a quote of the old describe. No row quotes "does NOT hydrate", so the ledger is unchanged. Control:related_listhits twice each infield.jsonandpage.json. Thefield.jsoninline-column rows (inlineColumns, lines 410-423) citehydrateColumns, which stays true.content/docs/**(outsidereferences/andreleases/) contains no sentence about line-items hydration. Positive control: the same grep findsrecord:line_itemsincontent/docs/ui/react-pages.mdx, andlayout-dsl.mdx:788states the carriers' rule forobject-master-detail-form.packages/linthas no rule that prescribes or enforces alabelon these columns.validate-field-consumers.ts:392says the block "derives nothing", which is about the column set and stays true. Theskills/**andexamples/**hits are listed under Acceptance notes and were not edited.H4: accept/reject unchanged, measured on the built package
pnpm --filter @objectstack/spec buildran at the base cb8756b and again at a66b471. The script compares every JSON Schema withdescriptionremoved and lists the description paths that changed:ui/RecordLineItemsProps.json: structure identical once descriptions are removed; one description path changed,/properties/columns.objectstack.json: structure identical once descriptions are removed; one description path changed,/$defs/ui/RecordLineItemsProps/properties/columns..build-input-hash-schema.RecordHistoryProps.jsonagainstRecordLineItemsProps.jsonreports structureFalse, so the comparison can fail.packages/spec/authorable-surface/is byte-identical before and after the build.inline-grid-column-carriers.test.tspasses unchanged, including the line-items section. Its test "CONTROL — defineStack does not judge an identity-only line-items column" still asserts thatbuild()accepts{ name: 'amount', scale: 2 }over a currency child field onrecord:line_itemsand refuses the same column underobject-master-detail-form.Tests (all at a66b471)
pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/inline-grid-column-carriers.test.ts src/ui/component-props-unknown-members.pin.test.ts src/ui/page-print.test.ts:Test Files 3 passed (3),Tests 138 passed (138).pnpm --filter @objectstack/spec test:Test Files 692 passed (692),Tests 20974 passed | 1 todo (20975), exit 0.pnpm --filter @objectstack/spec typecheck: exit 0. This coverstsc --noEmit,check:scripts-typecheckandcheck:test-typecheck(OK, the debt ledger held).Gates (local subset, at a66b471)
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 102 commands over 3 paths, measured against the merge base cb8756b.pnpm --workspace-concurrency=2 --filter '@objectstack/lint...' --filter '@objectstack/client-react...' --filter '@objectstack/objectql...' --filter '@objectstack/formula...' run buildunblockedcheck:doc-formula-expressions,check:doc-security-posture,check:skill-examples,check:docs-transcript-driftandcheck:lean-entry-closure.621a4876unblockedcheck-plugin-teardown-shape --self-test.pnpm check:dual-build-cjs-loadsneeds every workspace package built. CI runs it inLint & Repo Gates.--ranreconciliation: "102 derived famil(ies) accounted for — 101 run, 1 NOT-MEASURED".pnpm --filter @objectstack/spec check:generatedreportedcontent/docs/references/**stale before regeneration, and only that artifact.Acceptance notes
defineStack's resolved-type check does not reachrecord:line_items.collectHydratedInlineColumnErrors(packages/spec/src/stack.zod.ts) judges "inline grid columns whose TYPE comes from the child field" on three carriers. Once the renderer half lands, an identity-onlyrecord:line_itemscolumn takes its type from the child field too. A{ name: 'amount', scale: 2 }column over acurrencyfield will then publish green on this block and be reported only at render time, whileobject-master-detail-formrefuses the same column at publish. Bringing the block into that check changes what publishes, so it is out of this Clause-②nocard. It is reported for a separate card that depends on plugin-form: record:line_items hydrates an identity-only column from the child field (label, type, options) as the master-detail carriers do, so printed line-item headings translate (the objectui half of objectstack-ai/objectstack#24140) objectui#12854. The carriers test's CONTROL title ("the panel draws it unhydrated") describes the pinned renderer, and it flips with that card..objectui-shapin carries it, an identity-onlyrecord:line_itemscolumn still draws as a text cell headed by its field name. The TSDoc and the changeset both say this. The pin bump that carries objectui#12854 should delete the TSDoc sentence that begins "Until the.objectui-shapin carries it".examples/app-showcase/src/ui/pages/invoice-document.page.ts(around lines 59-66) still states the old behaviour, and so does the docblock ofexamples/app-showcase/test/invoice-document-line-items.test.ts(lines 12-15). These belong to the showcase invoice card (showcase: the invoice print page's line-items table prints the raw field namesdescription/quantity/unit_price/amountas its headings — itsrecord:line_itemscolumns declare nolabel, and that block draws a column exactly as declared #23985): its PR decides whether to drop the literal labels.skills/objectstack-ui/rules/pages.md:112-113(Tier H, not edited). The print-page example writes identity-onlyrecord:line_itemscolumns ({ name: 'product' }, …). That matches the new contract. Before objectui#12854 lands, those columns print raw field names.mainmoved while this was open.maingained feat(spec)!: record:json is retired — refused by name at the schema door, the approval request page's raw JSON panel it was declared for was dropped (#24162) #24170 (therecord:jsonretirement), which editscomponent.zod.tsat:458and:2571and removes therecord:jsonsection ofcomponent.mdx(lines 1793-1805). Neither touches this diff's hunks, somainwas not merged in, per the dispatch's merge-on-conflict-only instruction. The PR's CI and the merge queue validate the merge.Generated by Claude Code