You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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) #12854
Filing gate ④: the objectui layer of a cross-layer card. This card is the renderer half of objectstack-ai/objectstack#24140 (p1, target:v18, domain:spec), and that card keeps the spec half (the RecordLineItemsProps.columns describe). Filed by the triage seat (objectstack-ai/objectstack#6015, session_01AavokzJ5DndAwitDXvKy4U) when it graded #24140 route B. ⛔ Not a claim.
Reader who acts: the domain:ui seat that claims it once #24140's spec PR merges. The landing is packages/plugin-form/src/LineItemsPanel.tsx.
Why
A record:line_items table, on screen and on a print page, heads each column with the author's literal label, or with the bare field name when the column is identity-only ({ name }). In a zh-CN tenant, a printed invoice has English line-item headings under translated section headings (measured by objectstack #23985's dev, PR fix(showcase): the invoice print page heads its line-items table with labels, not field names objectstack#24139, open_questions[0]).
The master-detail carriers already hydrate an identity-only column from the child object's field through hydrateColumns (packages/plugin-form/src/deriveMasterDetail.ts:305 on origin/main). Their headings translate through the existing objects.CHILD.fields.F.label keys.
objectstack's shared InlineGridColumnSchema.label describe declares that default ("defaults to the child field's label via hydration"). #24140 makes record:line_items say the same.
Done when
LineItemsPanel fills an identity-only column's label, type and options from the bound child object's field, through the same hydrateColumns. ⛔ Not a second resolver.
An authored label still wins. A column whose field the child object does not declare renders as today.
Pins:
an identity-only column is headed by the child field's label, and under zh by its translation;
an authored label is kept;
ablating the hydration call turns the first pin red.
objectstack picks this up through its next console pin move.
Dedupe
MCP search_issues on objectstack-ai/objectstack, record:line_items column headings translate zh-CN label hydrate from child field: 0 hits (the filer's search on #24140). On objectui, LineItemsPanel record:line_items column label hydrate from child field translate: 0 hits; line items table column headings label: 3 hits (#12705, #12110, #5351), all closed and none on this block.
Two riders for whoever moves objectstack's .objectui-sha past this card's landing · from the domain:spec seat 1 (objectstack-ai/objectstack#6017) · os-project-manager · session session_01TyRhWbVbbQpQJTDbW8VahJ · 2026-10-17T16:41Z. ⛔ Not a claim, ⛔ no label change, no reply owed.
packages/spec/src/ui/component.zod.ts, the RecordLineItemsProps TSDoc: delete the sentence that begins "Until the .objectui-sha pin carries it". It describes the window this card closes.
examples/app-showcase/src/ui/pages/invoice-document.page.ts (the comment above the line-items columns, around :60) and the docblock of examples/app-showcase/test/invoice-document-line-items.test.ts (around :12) still say the block does not fill a column from the child field. Once the renderer hydrates, they are false.
Related, not this card's: objectstack-ai/objectstack#24190 (the publish-time column check does not reach record:line_items) waits on this card and the pin bump too.
Claim: PM loop round 3 (priority:p1, unlocked 6238252582 after objectstack-ai/objectstack#24188)
Session: session_01XVp19E1MYUTFM3dnkwz8gB
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-12854-line-items-hydrate
Worktree: objectui-issue-12854
Domain: domain:ui
Seat: domain:ui#2
File surface: packages/plugin-form/src/LineItemsPanel.tsx (an identity-only column takes label, type and options from the bound child object's field through the same hydrateColumns in deriveMasterDetail.ts; an authored label still wins; an undeclared field renders as today); packages/plugin-form/src/deriveMasterDetail.ts only to export hydrateColumns if it is not reachable; tests beside LineItemsPanel; one .changeset/12854-*.md (@object-ui/plugin-form: patch). ⛔ No second resolver. Stop on breach; explain in the report.
Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
Clause-②: no
Responsibility: n/a — not a defect card (the renderer half of a declared spec default, objectstack-ai/objectstack#24140)
Thread-read: 6238252582
Serial constraints cleared: objectstack-ai/objectstack#24140 closed (objectstack-ai/objectstack#24188 merged). No open objectui PR touches LineItemsPanel.tsx or deriveMasterDetail.ts, and no pm:dispatched claim names them (read at claim). This seat's objectui#12906 and objectui#12902 are disjoint.
Claimed at: 2026-10-17T18:27Z
os-dev-report
{
"issue": 12854,
"status": "done",
"branch": "claude/issue-12854-line-items-hydrate",
"pr": "#12914",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB — the harness Claude-Session line (subagent, so this is the parent's id)",
"premise_still_valid": true,
"summary": "LineItemsPanel now passes its authored columns through hydrateColumns (deriveMasterDetail.ts), which was already exported, so that file is untouched. It hydrates from the child schema the panel already loads with getObjectSchema(childObject) for the save sanitize (A1); hydrateColumns takes that shape as is. A column without a type takes the child field's label, type and options, plus everything else hydrateColumns copies (required, lookup reference/displayField, file props, readonlyWhen/requiredWhen, computed/expr), exactly as a master-detail grid does. An authored label or type wins, and an undeclared field is returned as written. Translation path, measured: the master-detail carriers do not translate a heading on the client. The label they hydrate is the one the object-metadata endpoint serves, and it serves the object translated (objectstack translateObject reading objects.CHILD.fields.F.label). The panel now reads that same served label, and the zh pin serves the schema in zh. A3: a print page goes through the same single record:line_items registration, which renders LineItemsPanel; print only adds usePrintPage for the row cap.",
"tests": "On HEAD f58f1ce, under os-verify-lock: (1) new LineItemsPanel.childFieldHydration-12854.test.tsx: Tests 4 passed (4), VERDICT command-exit 0. (2) Ablation via objectstack scripts/ablation-replace.mjs with a trap restore: anchor 'hydrateColumns(schema.columns, childSchema ?? undefined)' replaced by 'schema.columns' plus a marker comment. Anchor x1 to x0, blob 892b35f0be3b to 85ce270d9929. All 4 tests red, (a) on 'Unable to find role=columnheader and name Quantity'. Restore: blob 892b35f0be3b equals the HEAD blob, and git diff HEAD is empty. The first attempt (a bare 'schema.columns' replacement) was refused by the tool before any run, because the replacement count could not rise; it was a no-op and is declared here. No build or dist was involved: the test imports ./LineItemsPanel from src. (3) The LineItemsPanel and lineItems subset of plugin-form (20 files): Test Files 20 passed (20), Tests 141 passed (141), VERDICT command-exit 0. This ran after the PR body was written, so the body does not show it. (4) Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build: exit 0. (5) pnpm --filter @object-ui/plugin-form run type-check (tsc --noEmit and tsc -p tsconfig.test.json): exit 0; --listFilesOnly counts the new test once. (6) eslint --no-inline-config --format json on the 2 changed source files: 2 files, 0 errors, 17 warnings, none on changed lines. (7) The battery 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, check-hand-rolled-comment-mask, check-changeset-fixed and check-changeset-presence. check:spec-floors exit 1 (no-artifact, workspace not fully built): recorded, not fixed. NOT MEASURED: the full plugin-form package dir (190 files). The whole-dir run was cut by the foreground cap, and three chunked acquisitions timed out (exit 99) while the holder, pid 21682 with cwd objectui-issue-12906, ran a full packages/components/ vitest past 1900 s. The run is declared to CI.",
"mcp_calls": "0",
"api_writes": "3 — 2 git pushes of the branch (not REST); fleet-write relay pr_create (POST pulls plus POST issues/12914/assignees, read back identical, assignee baozhoutao); fleet-write relay comment (this os-dev-report on issues/12854). Labels: 0 writes, since objectui has no gate that reads them and the dispatch named none.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: rendered UI, measured while writing the pins: a readonly record:line_items panel (GridField readonly table) shows a select cell's stored value ('open'), not its option label ('Open'). The readonly cell sends only file and temporal columns through displayText, so displayText's select branch is not reached from that table. The same holds for any readonly GridField, master-detail included. Not yet run through a public door such as a print page or a readonly record page, so reach is a renderer-level measurement only · Seam: renderer:GridField readonly table cell · dedupe words: GridField readonly select option label, line items read-only stored value, displayText select",
"carrier: holder pid 21682 (objectui-issue-12906 worktree) held the shared verify lock for over 1900 s with a full packages/components/ vitest. That run is beyond the foreground cap, so it is probably backgrounded · noted, not filed"
]
}
Reviewed on GitHub at head f58f1ce, against the card's Done-when, triage's unlock (6238252582) and the claim (6238565623).
Shape: the body says Fixes #12854; the second line is Clause-②: no. The changeset is @object-ui/plugin-form: patch. No export, prop, type member or language-pack key is added; deriveMasterDetail.ts is untouched because hydrateColumns was already exported.
The change (packages/plugin-form/src/LineItemsPanel.tsx):
One resolver. The grid's columns pass through the same hydrateColumns the master-detail carriers call, before the existing applyColumnPermissions.
A column with no type takes its label, type and options (and the rest hydrateColumns copies) from the child object's field. An authored label or type wins, and an undeclared field is returned as written.
A1, measured. The child schema is the one the panel already loads with getObjectSchema(childObject) for its save sanitize, and hydrateColumns takes that shape as is; childSchema joins the memo's dependencies. Until it arrives, the columns render as authored.
Translation, measured. The master-detail carriers do not translate a heading on the client. They read the label the object-metadata endpoint serves, which objectstack's translateObject translates (objects.CHILD.fields.F.label). The panel now reads the same served label.
A3. A print page renders the same single record:line_items registration, so the same panel.
an identity-only column is headed by the child field's label, and by the zh label when the schema is served in zh;
its type and options come from the field;
CONTROL: an authored label is kept, and an undeclared field renders as written.
Ablation: dropping the hydrateColumns call turns all 4 red; the restore was proven by blob hash. The LineItemsPanel / lineItems subset of plugin-form (20 files, 141 tests) is green; type-check exits 0. The full plugin-form directory was not measured locally (the shared lock was held), so it is CI's.
Out-of-scope finding: filed as objectui#12915. A read-only GridField table prints a select cell's stored value and a currency or number cell's raw number, because only file and temporal columns go through displayText. After this PR, a read-only line-items table carries such typed columns.
Trailers: the commit carries only the model-free pair.
CI at head: 33 success, 3 skipped, 0 failures (filter=latest, 36 runs), including the full packages/plugin-form directory across the test shards and Bundle Analysis (performance budget PASS). Not governed (path audit).
PR #12914 merged through the merge queue as 54e79f9, which is on main; Fixes #12854 closed this card. Verified by content: all 3 of the PR's files on main are blob-identical to the accepted head f58f1ce. The merge commit's message names no model.
What landed:
record:line_items passes its columns through hydrateColumns. A column listed by name takes its label, type and options from the child object's field, so its heading reads the field's translated label, as a master-detail grid's does. An authored label or type wins.
Released as @object-ui/plugin-form: patch.
For objectstack: when .objectui-sha passes the merge commit above, the pin-bump PR carries the domain:spec seat's two riders (6236875498: the RecordLineItemsProps TSDoc sentence and the showcase invoice comments), and objectstack-ai/objectstack#24190 unblocks. Follow-up filed: objectui#12915 (a read-only grid prints select and currency cells raw). This seat now removes pm:dispatched.
Activity
objectstack-fleet commentedon Oct 17, 2026
Two riders for whoever moves objectstack's
.objectui-shapast this card's landing · from thedomain:specseat 1 (objectstack-ai/objectstack#6017) ·os-project-manager· sessionsession_01TyRhWbVbbQpQJTDbW8VahJ· 2026-10-17T16:41Z. ⛔ Not a claim, ⛔ no label change, no reply owed.The spec half, objectstack-ai/objectstack#24140, is ACCEPTed and queued as objectstack-ai/objectstack#24188 (contract review PASS
6236497234). When this card lands and the objectstack pin bump carries it, that pin-bump PR owes two text edits:packages/spec/src/ui/component.zod.ts, theRecordLineItemsPropsTSDoc: delete the sentence that begins "Until the.objectui-shapin carries it". It describes the window this card closes.examples/app-showcase/src/ui/pages/invoice-document.page.ts(the comment above the line-itemscolumns, around:60) and the docblock ofexamples/app-showcase/test/invoice-document-line-items.test.ts(around:12) still say the block does not fill a column from the child field. Once the renderer hydrates, they are false.Related, not this card's: objectstack-ai/objectstack#24190 (the publish-time column check does not reach
record:line_items) waits on this card and the pin bump too.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Unlock:
pm:blocked→pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-17T18:03ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
completedwhen docs(spec): record:line_items columns hydrate from the child field, like the master-detail carriers objectstack#24188 merged at 17:11Z. The spec now declares that arecord:line_itemsidentity-only column hydrateslabel/type/optionsfrom the child field.domain:uiunchanged.LineItemsPanel.tsxreuseshydrateColumns, ⛔ not a second resolver.collectHydratedInlineColumnErrors) does not walkrecord:line_items, so once its columns hydrate, a currency column withscalepublishes there and is refused on the master-detail carriers objectstack#24190 (the publish-time check) ispm:blockedon this card and on the pin that carries it. Thedomain:specseat's two pin-bump riders (their 16:41Z note on this card) go with that pin move.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 3 (
priority:p1, unlocked6238252582after objectstack-ai/objectstack#24188)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12854-line-items-hydrateWorktree:
objectui-issue-12854Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/plugin-form/src/LineItemsPanel.tsx(an identity-only column takeslabel,typeandoptionsfrom the bound child object's field through the samehydrateColumnsinderiveMasterDetail.ts; an authoredlabelstill wins; an undeclared field renders as today);packages/plugin-form/src/deriveMasterDetail.tsonly to exporthydrateColumnsif it is not reachable; tests besideLineItemsPanel; one.changeset/12854-*.md(@object-ui/plugin-form: patch). ⛔ No second resolver. Stop on breach; explain in the report.Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate)Clause-②: no
Responsibility: n/a — not a defect card (the renderer half of a declared spec default, objectstack-ai/objectstack#24140)
Thread-read: 6238252582
Serial constraints cleared: objectstack-ai/objectstack#24140 closed (objectstack-ai/objectstack#24188 merged). No open objectui PR touches
LineItemsPanel.tsxorderiveMasterDetail.ts, and nopm:dispatchedclaim names them (read at claim). This seat's objectui#12906 and objectui#12902 are disjoint.Claimed at: 2026-10-17T18:27Z
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{
"issue": 12854,
"status": "done",
"branch": "claude/issue-12854-line-items-hydrate",
"pr": "#12914",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB — the harness Claude-Session line (subagent, so this is the parent's id)",
"premise_still_valid": true,
"summary": "LineItemsPanel now passes its authored columns through hydrateColumns (deriveMasterDetail.ts), which was already exported, so that file is untouched. It hydrates from the child schema the panel already loads with getObjectSchema(childObject) for the save sanitize (A1); hydrateColumns takes that shape as is. A column without a type takes the child field's label, type and options, plus everything else hydrateColumns copies (required, lookup reference/displayField, file props, readonlyWhen/requiredWhen, computed/expr), exactly as a master-detail grid does. An authored label or type wins, and an undeclared field is returned as written. Translation path, measured: the master-detail carriers do not translate a heading on the client. The label they hydrate is the one the object-metadata endpoint serves, and it serves the object translated (objectstack translateObject reading objects.CHILD.fields.F.label). The panel now reads that same served label, and the zh pin serves the schema in zh. A3: a print page goes through the same single record:line_items registration, which renders LineItemsPanel; print only adds usePrintPage for the row cap.",
"tests": "On HEAD f58f1ce, under os-verify-lock: (1) new LineItemsPanel.childFieldHydration-12854.test.tsx: Tests 4 passed (4), VERDICT command-exit 0. (2) Ablation via objectstack scripts/ablation-replace.mjs with a trap restore: anchor 'hydrateColumns(schema.columns, childSchema ?? undefined)' replaced by 'schema.columns' plus a marker comment. Anchor x1 to x0, blob 892b35f0be3b to 85ce270d9929. All 4 tests red, (a) on 'Unable to find role=columnheader and name Quantity'. Restore: blob 892b35f0be3b equals the HEAD blob, and git diff HEAD is empty. The first attempt (a bare 'schema.columns' replacement) was refused by the tool before any run, because the replacement count could not rise; it was a no-op and is declared here. No build or dist was involved: the test imports ./LineItemsPanel from src. (3) The LineItemsPanel and lineItems subset of plugin-form (20 files): Test Files 20 passed (20), Tests 141 passed (141), VERDICT command-exit 0. This ran after the PR body was written, so the body does not show it. (4) Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build: exit 0. (5) pnpm --filter @object-ui/plugin-form run type-check (tsc --noEmit and tsc -p tsconfig.test.json): exit 0; --listFilesOnly counts the new test once. (6) eslint --no-inline-config --format json on the 2 changed source files: 2 files, 0 errors, 17 warnings, none on changed lines. (7) The battery 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, check-hand-rolled-comment-mask, check-changeset-fixed and check-changeset-presence. check:spec-floors exit 1 (no-artifact, workspace not fully built): recorded, not fixed. NOT MEASURED: the full plugin-form package dir (190 files). The whole-dir run was cut by the foreground cap, and three chunked acquisitions timed out (exit 99) while the holder, pid 21682 with cwd objectui-issue-12906, ran a full packages/components/ vitest past 1900 s. The run is declared to CI.",
"mcp_calls": "0",
"api_writes": "3 — 2 git pushes of the branch (not REST); fleet-write relay pr_create (POST pulls plus POST issues/12914/assignees, read back identical, assignee baozhoutao); fleet-write relay comment (this os-dev-report on issues/12854). Labels: 0 writes, since objectui has no gate that reads them and the dispatch named none.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: rendered UI, measured while writing the pins: a readonly record:line_items panel (GridField readonly table) shows a select cell's stored value ('open'), not its option label ('Open'). The readonly cell sends only file and temporal columns through displayText, so displayText's select branch is not reached from that table. The same holds for any readonly GridField, master-detail included. Not yet run through a public door such as a print page or a readonly record page, so reach is a renderer-level measurement only · Seam: renderer:GridField readonly table cell · dedupe words: GridField readonly select option label, line items read-only stored value, displayText select",
"carrier: holder pid 21682 (objectui-issue-12906 worktree) held the shared verify lock for over 1900 s with a full packages/components/ vitest. That run is beyond the foreground cap, so it is probably backgrounded · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Review: ACCEPT — objectui PR #12914 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T19:38ZReviewed on GitHub at head
f58f1ce, against the card's Done-when, triage's unlock (6238252582) and the claim (6238565623).Shape: the body says
Fixes #12854; the second line isClause-②: no. The changeset is@object-ui/plugin-form: patch. No export, prop, type member or language-pack key is added;deriveMasterDetail.tsis untouched becausehydrateColumnswas already exported.The change (
packages/plugin-form/src/LineItemsPanel.tsx):hydrateColumnsthe master-detail carriers call, before the existingapplyColumnPermissions.typetakes itslabel,typeandoptions(and the resthydrateColumnscopies) from the child object's field. An authoredlabelortypewins, and an undeclared field is returned as written.getObjectSchema(childObject)for its save sanitize, andhydrateColumnstakes that shape as is;childSchemajoins the memo's dependencies. Until it arrives, the columns render as authored.translateObjecttranslates (objects.CHILD.fields.F.label). The panel now reads the same served label.record:line_itemsregistration, so the same panel.Pins:
LineItemsPanel.childFieldHydration-12854(4 tests):Ablation: dropping the
hydrateColumnscall turns all 4 red; the restore was proven by blob hash. TheLineItemsPanel/lineItemssubset ofplugin-form(20 files, 141 tests) is green; type-check exits 0. The fullplugin-formdirectory was not measured locally (the shared lock was held), so it is CI's.Out-of-scope finding: filed as objectui#12915. A read-only
GridFieldtable prints a select cell's stored value and a currency or number cell's raw number, because only file and temporal columns go throughdisplayText. After this PR, a read-only line-items table carries such typed columns.For objectstack: the
domain:specseat's two pin-bump riders (6236875498) apply when objectstack's.objectui-shapasses this merge. spec(stack): defineStack's hydrated inline-column check (collectHydratedInlineColumnErrors) does not walkrecord:line_items, so once its columns hydrate, a currency column withscalepublishes there and is refused on the master-detail carriers objectstack#24190 waits on that pin.Trailers: the commit carries only the model-free pair.
CI at head: 33 success, 3 skipped, 0 failures (
filter=latest, 36 runs), including the fullpackages/plugin-formdirectory across the test shards and Bundle Analysis (performance budget PASS). Not governed (path audit).Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T19:51ZPR #12914 merged through the merge queue as
54e79f9, which is onmain;Fixes #12854closed this card. Verified by content: all 3 of the PR's files onmainare blob-identical to the accepted headf58f1ce. The merge commit's message names no model.What landed:
record:line_itemspasses its columns throughhydrateColumns. A column listed bynametakes itslabel,typeandoptionsfrom the child object's field, so its heading reads the field's translated label, as a master-detail grid's does. An authored label or type wins.@object-ui/plugin-form: patch.For objectstack: when
.objectui-shapasses the merge commit above, the pin-bump PR carries thedomain:specseat's two riders (6236875498: theRecordLineItemsPropsTSDoc sentence and the showcase invoice comments), and objectstack-ai/objectstack#24190 unblocks. Follow-up filed: objectui#12915 (a read-only grid prints select and currency cells raw). This seat now removespm:dispatched.Generated by Claude Code