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
console shows internal representations to users: composite / record-map field values as raw JSON on the record page, and the Audit Log Browser actor as a raw user id #11697
Filing gate: ① user-visible UX defect — reach: record page and Setup → Audit Log Browser, measured; separate card under the maintainer's quota exception (provenance below).
Reader: objectui triage first-touch → domain:ui lane (packages/fields, apps/console); may split into two PRs.
Dedup (semantic search, open + closed, objectui): "record detail shows raw JSON for composite object field, audit log browser actor shows raw user id instead of name" → 7 hits, all closed; objectui#4037 fixed the same class for address, objectui#10112 for user columns in RelatedList; neither covers these two surfaces.
Environment: framework ce577ec4 showcase booted with objectstack dev --ui --seed-admin on its own port and SQLite file; console = objectui f9f4a62d (apps/console Vite dev server, and a production vite build for timings); Chromium 141 headless, 1440×900, seeded admin.
Repro
Showcase → Data Model → Field Zoo → "Specimen — Full". Actual: "Composite (embedded object)" renders {"width":10,"height":20} and "Record (name-keyed map)" renders {"primary":{"name":"A","score":… truncated — raw JSON in a monospace face.
Setup → Diagnostics → Audit Log Browser. Actual: the Actor column shows a truncated user id (D5HVjHiXQRsZLSYMO…) and the filter asks for "Actor (user id)". The object list of the same rows (Setup → Audit Logs) shows the user's name ("Dev Admin").
Where
packages/fields/src/widgets/ObjectField.tsx — the read face renders JSON.stringify(value, null, 2) (likely the path for composite / record values; the exact display branch was not instrumented).
apps/console/src/pages/system/AuditLogPage.tsx — the Actor cell renders truncate(r.user_id, 18); the actor filter is a free-text user id.
Expected
Structured values render as labelled key/value rows (the composite's declared sub-fields); the audit actor renders the user's display name (id on hover), and the actor filter picks a user rather than taking a typed id.
Provenance: filed as a separate card on the maintainer's explicit instruction in the dispatching session, which lifts the per-fire filing quota for this batch of seven, verbatim — 「重新思考以上和用户体验相关的问题,哪些需要立卡」 then 「分开立 7 张」. Found during a browser dogfood sweep of the console; companion checklist card #11672. Filed by session session_01WEARdKfDHDT7rTuEhLyT1k.
Path: ② the capabilities an end user meets in the app — record pages | 缺项 | P2
Triage: first grade — enhancement · priority:p3 · domain:ui · area:records · pm:queue. Composite and record field values render as labelled fields, not raw JSON. The audit log actor half is split out
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T07:00Z. ⛔ Not a claim, ⛔ not a dispatch. Re-read on objectui main (c3623eb116): live, and not a duplicate of objectui#11674–#11690.
Triage: lands in packages/fields/src/index.tsx (composite/record → JsonCellRenderer, about :3029 and :3450–:3451), reached from the record page through DetailSection.tsx (about :442) ⇒ domain:ui; rationale: the cell renderer stringifies the value.
Correction to the card: the record page reaches the value through DetailSection.tsx → fields/src/index.tsx, not ObjectField.tsx.
Scope: this card is the composite and record half only. The audit log's raw actor id is split out as objectui#11701, because it is a different renderer and file.
Done when:
A composite or record value renders its sub-fields with their labels and display faces, through the field metadata.
JSON stays available behind an explicit raw view, if one exists.
Pin: a composite field renders labelled sub-values on the record page.
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · read 2026-10-06T18:41Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage's landing site for this card is packages/fields/src/index.tsx (composite/record → JsonCellRenderer, about :3029 and :3450–:3451; 6011141430). That file is in flight under another seat's area:records card: objectui#11690's open PR objectui#11732 edits it. objectui#11683 waits behind the same PR (6019992174). The pm-dispatch rule allows one in-flight card per area:* unless the file surfaces are disjoint, and a shared file is not disjoint. So this card waits for PR objectui#11732 to land or close.
For the claimant: re-read JsonCellRenderer and its two registrations on main after PR objectui#11732 lands, because the line numbers will have moved. This card and objectui#11683 both edit cell renderers in the same file, so the two are not dispatched together either.
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · read 2026-10-06T20:43Z. ⛔ Not a claim.
PR objectui#11732 (objectui#11690) merged, but objectui#11683 is now in flight (seat 3, claim 6024485871) and edits the same packages/fields/src/index.tsx. This card still waits, now for objectui#11683's PR to land or close. The earlier note (6023062558) otherwise stands.
Claim: PM loop round 1
Session: session_01CGZy1BGCjdN5cXqL9cnvB8
Account: os-support-ai
Branch: claude/issue-11697-composite-record-display
Worktree: objectui-issue-11697
Domain: domain:ui
Seat: domain:ui#3
File surface: packages/fields/src/index.tsx: the composite / record entries of buildStandardCellRendererMap (about :3502–:3503 on 055d350) and a display renderer for them beside JsonCellRenderer. json and object keep JsonCellRenderer. Also the tests beside it, and .changeset/11697-*.md. packages/plugin-detail/src/DetailSection.tsx is in scope only if the record page needs a face the cell contract cannot express; the dev reports why before opening the PR (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)
Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6025101546
Serial constraints cleared: none blocking. objectui#11683 (PR objectui#11747, the formula and datetime cells in packages/fields/src/index.tsx) merged as 055d350 at 2026-10-06T21:28Z, which frees this seat's serial slot (seat post §3). Seat 1's deferral 6025101546 named the same constraint. No open objectui PR touches packages/fields/src/index.tsx or DetailSection.tsx (read 2026-10-06T21:35Z; open: #11742 a dependency bump, #11729, #11600, #11069). Same axis in flight: objectui#11699 (area:records, this seat's) is on packages/app-shell/src/views/RecordDetailView.tsx; the file surfaces are disjoint.
Why Clause-②: no: one display renderer changes what it draws for two field types. No accepted input or prop is added. The new renderer stays module-local, so no export is added. If it needs an export or a language-pack key, the dev reports it before opening the PR, and the seat amends this line.
Scope note: triage's Done-when (6011141430) asks for sub-fields "with their labels and display faces, through the field metadata". On objectstack main60ccda5a, the data FieldSchema (packages/spec/src/data/field.zod.ts) has no sub-field declaration for composite / record. Its key list has no fields / subFields entry. The only data producers are the showcase Field Zoo's f_composite and f_record, which declare a type and a label only. So the dispatch tells the dev to measure which labels reach the renderer before choosing one. Their keys, humanized, are the fallback the platform already names for undeclared sub-fields (i18n-resolver.ts, the composite / repeater sub-field branch).
os-dev-report
{
"issue": 11697,
"status": "done",
"branch": "claude/issue-11697-composite-record-display",
"pr": "#11749",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent session id)",
"premise_still_valid": true,
"summary": "composite and record values now read as labelled sub-values on the record page instead of raw JSON. In packages/fields/src/index.tsx, buildStandardCellRendererMap maps composite to CompositeCellRenderer and record to RecordMapCellRenderer. Both are module-local and share StructuredValueCell, beside JsonCellRenderer; json and object keep JsonCellRenderer. Composite draws Width 10 · Height 20. Record draws one labelled group per entry, Primary (Name A · Score 9) · Backup (Name B · Score 7), and a scalar entry as a pair. A number sub-value goes through formatNumberFieldValue with no scale, a boolean through useBooleanValueLabel (the existing common.yes / common.no keys), a string as itself, and a floor member as EmptyValue. A nested object or array stays compact JSON through a new compactJsonText helper, which is JsonCellRenderer's own stringify moved out with the same bytes. The face is one truncated line (block max-w-full truncate) with the full text in its title. It is a description list, and the separators and brackets are aria-hidden. The floor, [], {} and non-object values (a JSON string included) still go to JsonCellRenderer, so the objectui#8474 and #8481 fence pins pass unedited. DetailSection.tsx is untouched: the record page row already draws one truncated line with a title, so the cell contract expresses this face. Zone 2 hypotheses: (1) the path is confirmed by ablation. (2) No sub-field declaration reaches the renderer. On installed @objectstack/spec 17.6.0, FieldSchema.safeParse refuses fields and subFields on a composite field as unrecognized_keys. The control relatedListColumns parses. The 75-key shape has no fields, subFields, schema or itemSchema, and objectstack main 60ccda5a is the same. objectui's FieldMetadata union has no composite or record member, and ObjectFieldMetadata.schema exists only for type 'object'. So labels are humanizeLabel(key), and no spec declaration was added. (3) The Field Zoo seeds are the fixtures. The driver-sql and dogfood shape {home,work} (scalar record entries) is pinned as pairs. (4) humanizeLabel from core is the in-package key fallback; humanizeFieldKey (camelCase split) lives in plugin-dashboard, outside this package's dependency edge. (5) The address precedent was followed. Raw view: none existed and none was added. The record page copy button still copies the stored JSON, because composite and record are inline-excluded. The Done-when clause "through the field metadata" cannot be met on the current spec, because the spec has no sub-field declaration. That is reported below as a finding with a Seam line, per Zone 3.",
"tests": "All at HEAD 22274e7. FULL: pnpm exec vitest run packages/fields/ --maxWorkers=2 gave Test Files 238 passed | 1 skipped (239), Tests 3723 passed | 7 skipped (3730), exit 0. pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2 gave Test Files 245 passed | 1 skipped (246), Tests 2408 passed | 8 skipped (2416), exit 0. Both ran in one os-verify-lock run, each part's exit echoed separately: PD_EXIT=0, FIELDS_EXIT=0. NEW PINS: packages/fields/src/tests/compositeRecordCell-11697.test.tsx has 9 tests. They cover composite pairs, record groups, scalar record entries, nested JSON, the per-type sub-value faces (number compared against the number cell's own text: 1,234.5; Yes / No; the No value affordance), the one-line contract with the title equal to the text, the unchanged floor and fallbacks ([], {}, a JSON string never parsed), and the json / object control. packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx has 2 tests: the triage pin, a composite field renders labelled sub-values on the record page, plus the record field. MOVED PINS: AddressCellRenderer.test.tsx's JSON control now lists json and object only. The summaryChip.badgeFitCensus-8464 composite and record rows now read Id acct-1 · Name Acme Corp, still fit. The first full fields run on the fix commit 9159c55, before any pin moved, showed exactly 1 red (Tests 1 failed | 3713 passed): the address control naming composite and record as JSON. ABLATION, after committing the fix: node /home/user/objectstack/scripts/ablation-replace.mjs replaced the two table entries with JsonCellRenderer (anchor x1 -> x0, replacement x0 -> x1, blob 6e801a91c25e -> ff9c729d76ec). On disk, grep -c counted composite: JsonCellRenderer 1 and composite: CompositeCellRenderer 0. The two new pin files then gave Tests 8 failed | 3 passed (11). All 6 contract tests and both record-page pins went red. The 3 green tests are the floor and fallback cases and the json / object control, which do not depend on the fix. The direction was red, as expected. Restore: blob after restore 6e801a91c25e == HEAD, git diff HEAD empty, diff-HEAD-bytes=0. No build or dist step was needed: vitest.config.mts aliases @object-ui/fields to packages/fields/src. TYPE-CHECK: pnpm --filter @object-ui/fields type-check exit 0 and pnpm --filter @object-ui/plugin-detail type-check exit 0, after pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' --filter '@object-ui/plugin-detail^...' build (exit 0). tsc -p tsconfig.test.json --listFilesOnly includes the new test files (1 in plugin-detail, 2 in fields). BROWSER, not a pin: the built dist was server-rendered with the built components and fields CSS and viewed in Chromium at /opt/pw-browsers/chromium. Every face is one line, 20 to 21px high. At 120px and 180px it shows text-overflow ellipsis with white-space nowrap, and the title holds the full text. This was not checked in the running console app.",
"mcp_calls": "0 (none)",
"api_writes": "3 REST writes, each sent as one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) and executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, which opened draft #11749 (read-back identical, 9135 bytes); (2) label-write --assign os-support-ai, POST /repos//issues/11749/assignees (read-back MATCHES); (3) this os-dev-report comment, POST /repos//issues/11697/comments. Plus 3 git pushes (not REST): the empty-branch probe and two commits.",
"open_questions": [
{
"question": "Triage's Done-when asks for sub-field labels "through the field metadata". The spec has no sub-field declaration for composite or record: FieldSchema refuses fields and subFields. Should one be added so labels come from metadata?",
"options": [
"A: no declaration now. Labels stay humanized keys (what this PR ships). The FieldType enum comment "with declared sub-fields" is reworded only if a carrier touches field.zod.ts.",
"B: add a sub-field member to FieldSchema for composite and record (name, label, type), have the renderer read labels from it, and enforce values against it at write and publish time."
],
"recommendation": "A. Real business need: the measured producers are the showcase Field Zoo (type and label only) and two test fixtures, and none of them declares or wants sub-fields, so there is zero pull. Long-term soundness: a declaration belongs with enforcement (value validation against the sub-fields). A label-only member would be a capability declared but not enforced, which is the shape enforce-or-remove retires. Preventing AI mistakes: today an AI that writes fields on a composite field is refused loudly at parse, which is the correct strictness, while a label-only member would accept sub-field types and requirements that nothing enforces. Startup-stage scope: no pull means no new declaration surface. The four axes do not conflict."
}
],
"out_of_scope_findings": [
"class: b (candidate, below the filing bar) · reach: NOT MEASURED at a public door. The reading is schema-level only: installed @objectstack/spec 17.6.0 FieldSchema.safeParse({name, type: 'composite', label, fields: [...]}) refuses fields, and subFields likewise, as unrecognized_keys, while the control relatedListColumns parses. No real producer was found · evidence: the objectstack spec FieldType enum comment reads 'composite' = "Single embedded sub-object with declared sub-fields" and 'repeater' = "Repeating embedded sub-object array with declared sub-fields", while the FieldSchema key set has no sub-field member · Seam: spec:FieldType 'composite' / 'record' (enum comment 'with declared sub-fields') → runtime: FieldSchema key set (no sub-field member; fields / subFields refused) | renderer: getCellRenderer('composite' / 'record') StructuredValueCell (labels = humanizeLabel(key)) · reported per dispatch Zone 3, not filed · dedupe words: composite declared sub-fields, FieldSchema subFields composite, composite record sub-field labels, embedded sub-object field schema",
"carrier: none · noted, not filed: camelCase sub-field keys stay unsplit (unitPrice reads UnitPrice) because humanizeLabel is the VALUE convention. humanizeFieldKey (the KEY convention) lives in @object-ui/plugin-dashboard, and the core humanize-label docstring says converging them needs its own card. Also in the PR Acceptance notes.",
"carrier: none · noted, not filed: ObjectField's read-only branch (the form widget for object, composite, record and json) still shows pretty-printed JSON in a read-only form. The card's 'Where' named it, triage re-routed the record page to the cell table, and it was not exercised at a public door. Also in the PR Acceptance notes."
],
"gates": [
{
"command": "pnpm exec vitest run packages/fields/ --maxWorkers=2",
"exit": 0,
"verdict": "Test Files 238 passed | 1 skipped (239); Tests 3723 passed | 7 skipped (3730)"
},
{
"command": "pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2",
"exit": 0,
"verdict": "Test Files 245 passed | 1 skipped (246); Tests 2408 passed | 8 skipped (2416)"
},
{
"command": "pnpm --filter @object-ui/fields type-check",
"exit": 0,
"verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output"
},
{
"command": "pnpm --filter @object-ui/plugin-detail type-check",
"exit": 0,
"verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output"
},
{
"command": "pnpm exec eslint (the 5 touched .ts/.tsx files)",
"exit": 0,
"verdict": "0 errors, 143 warnings, none on an added line"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7734 tracked text file(s); skipped 85 binary)."
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/11697-composite-record-display.md."
},
{
"command": "pnpm check:doc-fences",
"exit": 0,
"verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript"
},
{
"command": "pnpm check:doc-types",
"exit": 0,
"verdict": "Every documented component type is registered."
},
{
"command": "pnpm check:doc-example-ids",
"exit": 0,
"verdict": "415 real reference(s) all resolve in the catalog registry."
},
{
"command": "node scripts/check-doc-links.mjs (docs:check-links)",
"exit": 0,
"verdict": "Links are valid across 17 scan roots."
},
{
"command": "pnpm check:doc-snippets",
"exit": 2,
"verdict": "NOT MEASURED: prerequisite not met (scoped build of 34 packages; unbuilt-package). The diff adds no TypeScript fence."
},
{
"command": "pnpm check:doc-examples",
"exit": 2,
"verdict": "NOT MEASURED: THE GATE COULD NOT RUN (the packages these examples import are not built). The diff touches no example."
},
{
"command": "CI on PR #11749 at 22274e7",
"exit": null,
"verdict": "in_progress (42 check runs: 23 completed, 19 in_progress, 0 failed at report time)"
}
],
"deviations": [
"File surface beyond the claim (declared, not DetailSection.tsx): (a) a new record-page pin, packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx, because the triage pin says "on the record page"; (b) packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx, where the composite and record rows plus one docblock cell moved with the face; (c) content/docs/fields/object.mdx, whose cell-renderer section said composite and record share the JSON cell, a statement this change made false (AGENTS.md #2).",
"Zone 3 route, refined: each scalar sub-value is drawn through the cell renderers' shared formatting calls (formatNumberFieldValue; useBooleanValueLabel), not through the cell components. TextCellRenderer and BooleanCellRenderer render block-level nodes (a block span, a checkbox div), which break a one-line face, so a boolean reads as the Yes / No word, not a checkbox.",
"Attribution: commits end with the model-free trailer pair from AGENTS.md (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with the os-dev session-URL footer, not the harness PR footer text.",
"The changeset declares @object-ui/fields only. The presence gate counts plugin-detail src/tests files as published source of a second package and is green with the one changeset.",
"The full fields package run took about 575s against the 10-minute foreground cap. The final full runs were therefore started in the background under the lock (nohup) and awaited in the foreground with tail --pid."
],
"files_changed": [
"packages/fields/src/index.tsx",
"packages/fields/src/tests/compositeRecordCell-11697.test.tsx",
"packages/fields/src/tests/AddressCellRenderer.test.tsx",
"packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"content/docs/fields/object.mdx",
".changeset/11697-composite-record-display.md"
],
"line_budget": "not applicable (no skills/** surface)"
}
PR shape: draft against main. First line Fixes #11697, no other closing keyword. Clause-②: no at line start.
Scope: 7 files.
Inside the claim (6025894062): packages/fields/src/index.tsx (the composite / record table entries and a module-local renderer beside JsonCellRenderer), the fields tests beside it (a new pin, and the address control narrowed to json / object), and the changeset.
File-surface amendment, recorded here. Three files sit outside the claim's list, each moving with the face: the triage pin "on the record page" (packages/plugin-detail/src/__tests__/DetailSection.compositeRecord-11697.test.tsx); the summary-chip census rows for composite / record (packages/plugin-detail/src/__tests__/summaryChip.badgeFitCensus-8464.test.tsx); and content/docs/fields/object.mdx, whose sentence "shared with json, composite and record" this change made false.
DetailSection.tsx is untouched, because the cell contract expresses the face. No governed path.
Diff read:
Both hooks (useDisplayLocale, useBooleanValueLabel) run before the early returns.
The floor, [], {} and every non-object value go to JsonCellRenderer unchanged.
compactJsonText is JsonCellRenderer's former stringify, byte for byte.
CompositeCellRenderer, RecordMapCellRenderer, StructuredValueCell and compactJsonText are not exported.
The <dl> holds div → dt / dd groups, a nested <dl> sits inside a dd, and separators are aria-hidden inside the terms.
Host check: none of the render sites that call getCellRenderer(...) (plugin-detail, -grid, -kanban, -list, -tree, -report, -dashboard, the console approvals inbox) draws the value inside a <p>. Control: the scan finds the <p> elements in HeaderHighlight.tsx and ApprovalsInboxPage.tsx, both closed before the cell.
Changeset sentences checked against the head:
grid and record page share the table;
the two example faces;
"Labels are the humanized key", with the spec refusing fields / subFields (dev's safeParse on the installed 17.6.0, and the same key set on objectstack main60ccda5a);
number via formatNumberFieldValue with no scale, boolean via useBooleanValueLabel, the "No value" dash, nested JSON;
one truncated line with title; a description list;
"The record page's copy button still copies the stored JSON" (DetailSection.tsxhandleCopyField stringifies the stored value);
"Nothing is added to the package entry".
patch on @object-ui/fields only, matching Clause-②: no. The plugin-detail changes are tests.
Reverse verification (dev report 6026647409): with both table entries put back to JsonCellRenderer (the blob moved, the replacement is on disk), the two new pin files gave 8 red and 3 green out of 11. The 3 green are the floor, the fallbacks and the json / object control. Restore was proven by blob match.
Gates: CI on 22274e7 (head_sha equals the PR head): 43 runs, 40 success, 3 skipped (the two coverage jobs and dependabot), 0 failure; commit status success; 572 lines changed.
Contract review: not required (Clause-②: no).
Open question, answered by the seat (no-escalation class: existing rules settle it). Should the spec gain a sub-field declaration so labels come "through the field metadata"? No; option A, as this PR ships.
The measured producers are the showcase Field Zoo (type and label only) and test fixtures. That is zero pull, and "零拉动 ⇒ 荐不扩散".
A label-only member would be declared but not enforced, which is the shape enforce-or-remove retires.
Today a fields key on a composite field is refused loudly at parse, which is the stricter state.
So triage's "through the field metadata" reads, on today's contract, as "routed by the field's declared type, labelled from the value's keys". A future sub-field declaration is a domain:spec card when a producer pulls for it. The maintainer can veto this reading.
Findings:
the spec's FieldType enum comment says composite / repeater hold "declared sub-fields", but FieldSchema has no sub-field member → Acceptance notes (a schema-level reading; no reach at a public door, no producer)
camelCase sub-field keys stay unsplit (unitPrice → UnitPrice), because humanizeLabel is the value convention → Acceptance notes (no measured producer uses camelCase keys)
ObjectField's read-only branch (the form widget) still shows pretty-printed JSON → Acceptance notes (not exercised at a public door; the record page goes through the cell table)
Landing: ready + auto-merge through the queue, now. objectui#11748 (a formula's display by returnType) is serial behind this PR for packages/fields/src/index.tsx (seat post §3, deferral 6026496534).
Landed — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-06T23:03Z. PR objectui#11749 merged through the queue as db4cb6b.
The merge. One parent, 055d350. It is an ancestor of origin/main. The landed tree equals git merge-tree of that parent and the accepted head 22274e7 (tree 869aa40f on both). 7 files, +549/−23.
Content check on packages/fields/src/index.tsx, 055d350 → db4cb6b:
composite: CompositeCellRenderer: 0 → 1;
record: RecordMapCellRenderer: 0 → 1;
function StructuredValueCell: 0 → 1;
composite: JsonCellRenderer: 1 → 0.
Control: json: JsonCellRenderer is 1 on both.
Closures. The PR's only closing keyword was Fixes #11697. objectui#11697 is the only issue closed since 22:40Z. pm:dispatched removed. The audit-log actor half stays open as objectui#11701.
Serial queue. objectui#11748 (a formula's display by returnType) was serial behind this PR for packages/fields/src/index.tsx, and is now free (seat post §3).
Activity
objectstack-fleet commentedon Oct 6, 2026
Path: ② the capabilities an end user meets in the app — record pages | 缺项 | P2
Triage: first grade —
enhancement·priority:p3·domain:ui·area:records·pm:queue. Composite and record field values render as labelled fields, not raw JSON. The audit log actor half is split outTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T07:00Z. ⛔ Not a claim, ⛔ not a dispatch. Re-read on objectuimain(c3623eb116): live, and not a duplicate of objectui#11674–#11690.Triage: lands in
packages/fields/src/index.tsx(composite/record→JsonCellRenderer, about:3029and:3450–:3451), reached from the record page throughDetailSection.tsx(about:442) ⇒domain:ui; rationale: the cell renderer stringifies the value.DetailSection.tsx→fields/src/index.tsx, notObjectField.tsx.true) #11686 are the same family ("a value shown in its stored form"), but in different renderers, so they stay separate.Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
Deferred (serial), not claimed
domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· read 2026-10-06T18:41Z. ⛔ Not a claim, ⛔ not a dispatch.Triage's landing site for this card is
packages/fields/src/index.tsx(composite/record→JsonCellRenderer, about:3029and:3450–:3451;6011141430). That file is in flight under another seat'sarea:recordscard: objectui#11690's open PR objectui#11732 edits it. objectui#11683 waits behind the same PR (6019992174). The pm-dispatch rule allows one in-flight card perarea:*unless the file surfaces are disjoint, and a shared file is not disjoint. So this card waits for PR objectui#11732 to land or close.For the claimant: re-read
JsonCellRendererand its two registrations onmainafter PR objectui#11732 lands, because the line numbers will have moved. This card and objectui#11683 both edit cell renderers in the same file, so the two are not dispatched together either.Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
Deferred (serial) — the blocker changed
domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· read 2026-10-06T20:43Z. ⛔ Not a claim.PR objectui#11732 (objectui#11690) merged, but objectui#11683 is now in flight (seat 3, claim
6024485871) and edits the samepackages/fields/src/index.tsx. This card still waits, now for objectui#11683's PR to land or close. The earlier note (6023062558) otherwise stands.Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
Claim: PM loop round 1
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11697-composite-record-displayWorktree:
objectui-issue-11697Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/fields/src/index.tsx: thecomposite/recordentries ofbuildStandardCellRendererMap(about:3502–:3503on055d350) and a display renderer for them besideJsonCellRenderer.jsonandobjectkeepJsonCellRenderer. Also the tests beside it, and.changeset/11697-*.md.packages/plugin-detail/src/DetailSection.tsxis in scope only if the record page needs a face the cell contract cannot express; the dev reports why before opening the PR (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: 6025101546
Serial constraints cleared:
noneblocking. objectui#11683 (PR objectui#11747, the formula and datetime cells inpackages/fields/src/index.tsx) merged as055d350at 2026-10-06T21:28Z, which frees this seat's serial slot (seat post §3). Seat 1's deferral6025101546named the same constraint. No open objectui PR touchespackages/fields/src/index.tsxorDetailSection.tsx(read 2026-10-06T21:35Z; open: #11742 a dependency bump, #11729, #11600, #11069). Same axis in flight: objectui#11699 (area:records, this seat's) is onpackages/app-shell/src/views/RecordDetailView.tsx; the file surfaces are disjoint.Why
Clause-②: no: one display renderer changes what it draws for two field types. No accepted input or prop is added. The new renderer stays module-local, so no export is added. If it needs an export or a language-pack key, the dev reports it before opening the PR, and the seat amends this line.Scope note: triage's Done-when (
6011141430) asks for sub-fields "with their labels and display faces, through the field metadata". On objectstackmain60ccda5a, the dataFieldSchema(packages/spec/src/data/field.zod.ts) has no sub-field declaration forcomposite/record. Its key list has nofields/subFieldsentry. The only data producers are the showcase Field Zoo'sf_compositeandf_record, which declare a type and a label only. So the dispatch tells the dev to measure which labels reach the renderer before choosing one. Their keys, humanized, are the fallback the platform already names for undeclared sub-fields (i18n-resolver.ts, the composite / repeater sub-field branch).Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
os-dev-report
{
"issue": 11697,
"status": "done",
"branch": "claude/issue-11697-composite-record-display",
"pr": "#11749",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent session id)",
"premise_still_valid": true,
"summary": "composite and record values now read as labelled sub-values on the record page instead of raw JSON. In packages/fields/src/index.tsx, buildStandardCellRendererMap maps composite to CompositeCellRenderer and record to RecordMapCellRenderer. Both are module-local and share StructuredValueCell, beside JsonCellRenderer; json and object keep JsonCellRenderer. Composite draws
Width 10 · Height 20. Record draws one labelled group per entry,Primary (Name A · Score 9) · Backup (Name B · Score 7), and a scalar entry as a pair. A number sub-value goes through formatNumberFieldValue with no scale, a boolean through useBooleanValueLabel (the existing common.yes / common.no keys), a string as itself, and a floor member as EmptyValue. A nested object or array stays compact JSON through a new compactJsonText helper, which is JsonCellRenderer's own stringify moved out with the same bytes. The face is one truncated line (block max-w-full truncate) with the full text in its title. It is a description list, and the separators and brackets are aria-hidden. The floor, [], {} and non-object values (a JSON string included) still go to JsonCellRenderer, so the objectui#8474 and #8481 fence pins pass unedited. DetailSection.tsx is untouched: the record page row already draws one truncated line with a title, so the cell contract expresses this face. Zone 2 hypotheses: (1) the path is confirmed by ablation. (2) No sub-field declaration reaches the renderer. On installed @objectstack/spec 17.6.0, FieldSchema.safeParse refusesfieldsandsubFieldson a composite field as unrecognized_keys. The control relatedListColumns parses. The 75-key shape has no fields, subFields, schema or itemSchema, and objectstack main 60ccda5a is the same. objectui's FieldMetadata union has no composite or record member, and ObjectFieldMetadata.schema exists only for type 'object'. So labels are humanizeLabel(key), and no spec declaration was added. (3) The Field Zoo seeds are the fixtures. The driver-sql and dogfood shape{home,work}(scalar record entries) is pinned as pairs. (4) humanizeLabel from core is the in-package key fallback; humanizeFieldKey (camelCase split) lives in plugin-dashboard, outside this package's dependency edge. (5) The address precedent was followed. Raw view: none existed and none was added. The record page copy button still copies the stored JSON, because composite and record are inline-excluded. The Done-when clause "through the field metadata" cannot be met on the current spec, because the spec has no sub-field declaration. That is reported below as a finding with a Seam line, per Zone 3.","tests": "All at HEAD 22274e7. FULL:
pnpm exec vitest run packages/fields/ --maxWorkers=2gave Test Files 238 passed | 1 skipped (239), Tests 3723 passed | 7 skipped (3730), exit 0.pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2gave Test Files 245 passed | 1 skipped (246), Tests 2408 passed | 8 skipped (2416), exit 0. Both ran in one os-verify-lock run, each part's exit echoed separately: PD_EXIT=0, FIELDS_EXIT=0. NEW PINS: packages/fields/src/tests/compositeRecordCell-11697.test.tsx has 9 tests. They cover composite pairs, record groups, scalar record entries, nested JSON, the per-type sub-value faces (number compared against the number cell's own text: 1,234.5; Yes / No; the No value affordance), the one-line contract with the title equal to the text, the unchanged floor and fallbacks ([], {}, a JSON string never parsed), and the json / object control. packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx has 2 tests: the triage pin, a composite field renders labelled sub-values on the record page, plus the record field. MOVED PINS: AddressCellRenderer.test.tsx's JSON control now lists json and object only. The summaryChip.badgeFitCensus-8464 composite and record rows now readId acct-1 · Name Acme Corp, still fit. The first full fields run on the fix commit 9159c55, before any pin moved, showed exactly 1 red (Tests 1 failed | 3713 passed): the address control naming composite and record as JSON. ABLATION, after committing the fix:node /home/user/objectstack/scripts/ablation-replace.mjsreplaced the two table entries with JsonCellRenderer (anchor x1 -> x0, replacement x0 -> x1, blob 6e801a91c25e -> ff9c729d76ec). On disk, grep -c countedcomposite: JsonCellRenderer1 andcomposite: CompositeCellRenderer0. The two new pin files then gave Tests 8 failed | 3 passed (11). All 6 contract tests and both record-page pins went red. The 3 green tests are the floor and fallback cases and the json / object control, which do not depend on the fix. The direction was red, as expected. Restore: blob after restore 6e801a91c25e == HEAD,git diff HEADempty, diff-HEAD-bytes=0. No build or dist step was needed: vitest.config.mts aliases @object-ui/fields to packages/fields/src. TYPE-CHECK:pnpm --filter @object-ui/fields type-checkexit 0 andpnpm --filter @object-ui/plugin-detail type-checkexit 0, afterpnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' --filter '@object-ui/plugin-detail^...' build(exit 0).tsc -p tsconfig.test.json --listFilesOnlyincludes the new test files (1 in plugin-detail, 2 in fields). BROWSER, not a pin: the built dist was server-rendered with the built components and fields CSS and viewed in Chromium at /opt/pw-browsers/chromium. Every face is one line, 20 to 21px high. At 120px and 180px it shows text-overflow ellipsis with white-space nowrap, and the title holds the full text. This was not checked in the running console app.","mcp_calls": "0 (none)",
"api_writes": "3 REST writes, each sent as one fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) and executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, which opened draft #11749 (read-back identical, 9135 bytes); (2) label-write --assign os-support-ai, POST /repos//issues/11749/assignees (read-back MATCHES); (3) this os-dev-report comment, POST /repos//issues/11697/comments. Plus 3 git pushes (not REST): the empty-branch probe and two commits.",
"open_questions": [
{
"question": "Triage's Done-when asks for sub-field labels "through the field metadata". The spec has no sub-field declaration for composite or record: FieldSchema refuses fields and subFields. Should one be added so labels come from metadata?",
"options": [
"A: no declaration now. Labels stay humanized keys (what this PR ships). The FieldType enum comment "with declared sub-fields" is reworded only if a carrier touches field.zod.ts.",
"B: add a sub-field member to FieldSchema for composite and record (name, label, type), have the renderer read labels from it, and enforce values against it at write and publish time."
],
"recommendation": "A. Real business need: the measured producers are the showcase Field Zoo (type and label only) and two test fixtures, and none of them declares or wants sub-fields, so there is zero pull. Long-term soundness: a declaration belongs with enforcement (value validation against the sub-fields). A label-only member would be a capability declared but not enforced, which is the shape enforce-or-remove retires. Preventing AI mistakes: today an AI that writes
fieldson a composite field is refused loudly at parse, which is the correct strictness, while a label-only member would accept sub-field types and requirements that nothing enforces. Startup-stage scope: no pull means no new declaration surface. The four axes do not conflict."}
],
"out_of_scope_findings": [
"class: b (candidate, below the filing bar) · reach: NOT MEASURED at a public door. The reading is schema-level only: installed @objectstack/spec 17.6.0 FieldSchema.safeParse({name, type: 'composite', label, fields: [...]}) refuses
fields, andsubFieldslikewise, as unrecognized_keys, while the control relatedListColumns parses. No real producer was found · evidence: the objectstack spec FieldType enum comment reads 'composite' = "Single embedded sub-object with declared sub-fields" and 'repeater' = "Repeating embedded sub-object array with declared sub-fields", while the FieldSchema key set has no sub-field member · Seam: spec:FieldType 'composite' / 'record' (enum comment 'with declared sub-fields') → runtime: FieldSchema key set (no sub-field member; fields / subFields refused) | renderer: getCellRenderer('composite' / 'record') StructuredValueCell (labels = humanizeLabel(key)) · reported per dispatch Zone 3, not filed · dedupe words: composite declared sub-fields, FieldSchema subFields composite, composite record sub-field labels, embedded sub-object field schema","carrier: none · noted, not filed: camelCase sub-field keys stay unsplit (
unitPricereadsUnitPrice) because humanizeLabel is the VALUE convention. humanizeFieldKey (the KEY convention) lives in @object-ui/plugin-dashboard, and the core humanize-label docstring says converging them needs its own card. Also in the PR Acceptance notes.","carrier: none · noted, not filed: ObjectField's read-only branch (the form widget for object, composite, record and json) still shows pretty-printed JSON in a read-only form. The card's 'Where' named it, triage re-routed the record page to the cell table, and it was not exercised at a public door. Also in the PR Acceptance notes."
],
"gates": [
{
"command": "pnpm exec vitest run packages/fields/ --maxWorkers=2",
"exit": 0,
"verdict": "Test Files 238 passed | 1 skipped (239); Tests 3723 passed | 7 skipped (3730)"
},
{
"command": "pnpm exec vitest run packages/plugin-detail/ --maxWorkers=2",
"exit": 0,
"verdict": "Test Files 245 passed | 1 skipped (246); Tests 2408 passed | 8 skipped (2416)"
},
{
"command": "pnpm --filter @object-ui/fields type-check",
"exit": 0,
"verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output"
},
{
"command": "pnpm --filter @object-ui/plugin-detail type-check",
"exit": 0,
"verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output"
},
{
"command": "pnpm exec eslint (the 5 touched .ts/.tsx files)",
"exit": 0,
"verdict": "0 errors, 143 warnings, none on an added line"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7734 tracked text file(s); skipped 85 binary)."
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s): .changeset/11697-composite-record-display.md."
},
{
"command": "pnpm check:doc-fences",
"exit": 0,
"verdict": "every TypeScript block in 227 document(s) is fenced ts/tsx/typescript"
},
{
"command": "pnpm check:doc-types",
"exit": 0,
"verdict": "Every documented component type is registered."
},
{
"command": "pnpm check:doc-example-ids",
"exit": 0,
"verdict": "415 real reference(s) all resolve in the catalog registry."
},
{
"command": "node scripts/check-doc-links.mjs (docs:check-links)",
"exit": 0,
"verdict": "Links are valid across 17 scan roots."
},
{
"command": "pnpm check:doc-snippets",
"exit": 2,
"verdict": "NOT MEASURED: prerequisite not met (scoped build of 34 packages; unbuilt-package). The diff adds no TypeScript fence."
},
{
"command": "pnpm check:doc-examples",
"exit": 2,
"verdict": "NOT MEASURED: THE GATE COULD NOT RUN (the packages these examples import are not built). The diff touches no example."
},
{
"command": "CI on PR #11749 at 22274e7",
"exit": null,
"verdict": "in_progress (42 check runs: 23 completed, 19 in_progress, 0 failed at report time)"
}
],
"deviations": [
"File surface beyond the claim (declared, not DetailSection.tsx): (a) a new record-page pin, packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx, because the triage pin says "on the record page"; (b) packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx, where the composite and record rows plus one docblock cell moved with the face; (c) content/docs/fields/object.mdx, whose cell-renderer section said composite and record share the JSON cell, a statement this change made false (AGENTS.md #2).",
"Zone 3 route, refined: each scalar sub-value is drawn through the cell renderers' shared formatting calls (formatNumberFieldValue; useBooleanValueLabel), not through the cell components. TextCellRenderer and BooleanCellRenderer render block-level nodes (a block span, a checkbox div), which break a one-line face, so a boolean reads as the Yes / No word, not a checkbox.",
"Attribution: commits end with the model-free trailer pair from AGENTS.md (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By. The PR body ends with the os-dev session-URL footer, not the harness PR footer text.",
"The changeset declares @object-ui/fields only. The presence gate counts plugin-detail src/tests files as published source of a second package and is green with the one changeset.",
"The full fields package run took about 575s against the 10-minute foreground cap. The final full runs were therefore started in the background under the lock (nohup) and awaited in the foreground with tail --pid."
],
"files_changed": [
"packages/fields/src/index.tsx",
"packages/fields/src/tests/compositeRecordCell-11697.test.tsx",
"packages/fields/src/tests/AddressCellRenderer.test.tsx",
"packages/plugin-detail/src/tests/DetailSection.compositeRecord-11697.test.tsx",
"packages/plugin-detail/src/tests/summaryChip.badgeFitCensus-8464.test.tsx",
"content/docs/fields/object.mdx",
".changeset/11697-composite-record-display.md"
],
"line_budget": "not applicable (no skills/** surface)"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-06T22:43Z. PR objectui#11749, head22274e7.PR shape: draft against
main. First lineFixes #11697, no other closing keyword.Clause-②: noat line start.Scope: 7 files.
6025894062):packages/fields/src/index.tsx(thecomposite/recordtable entries and a module-local renderer besideJsonCellRenderer), the fields tests beside it (a new pin, and the address control narrowed tojson/object), and the changeset.packages/plugin-detail/src/__tests__/DetailSection.compositeRecord-11697.test.tsx); the summary-chip census rows forcomposite/record(packages/plugin-detail/src/__tests__/summaryChip.badgeFitCensus-8464.test.tsx); andcontent/docs/fields/object.mdx, whose sentence "shared withjson,compositeandrecord" this change made false.DetailSection.tsxis untouched, because the cell contract expresses the face. No governed path.Diff read:
useDisplayLocale,useBooleanValueLabel) run before the early returns.[],{}and every non-object value go toJsonCellRendererunchanged.compactJsonTextisJsonCellRenderer's former stringify, byte for byte.CompositeCellRenderer,RecordMapCellRenderer,StructuredValueCellandcompactJsonTextare not exported.<dl>holdsdiv→dt/ddgroups, a nested<dl>sits inside add, and separators arearia-hiddeninside the terms.getCellRenderer(...)(plugin-detail, -grid, -kanban, -list, -tree, -report, -dashboard, the console approvals inbox) draws the value inside a<p>. Control: the scan finds the<p>elements inHeaderHighlight.tsxandApprovalsInboxPage.tsx, both closed before the cell.Changeset sentences checked against the head:
fields/subFields(dev'ssafeParseon the installed 17.6.0, and the same key set on objectstackmain60ccda5a);formatNumberFieldValuewith noscale, boolean viauseBooleanValueLabel, the "No value" dash, nested JSON;title; a description list;DetailSection.tsxhandleCopyFieldstringifies the stored value);patchon@object-ui/fieldsonly, matchingClause-②: no. The plugin-detail changes are tests.Reverse verification (dev report
6026647409): with both table entries put back toJsonCellRenderer(the blob moved, the replacement is on disk), the two new pin files gave 8 red and 3 green out of 11. The 3 green are the floor, the fallbacks and thejson/objectcontrol. Restore was proven by blob match.Gates: CI on
22274e7(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (the two coverage jobs anddependabot), 0 failure; commit statussuccess; 572 lines changed.Contract review: not required (
Clause-②: no).Open question, answered by the seat (no-escalation class: existing rules settle it). Should the spec gain a sub-field declaration so labels come "through the field metadata"? No; option A, as this PR ships.
fieldskey on a composite field is refused loudly at parse, which is the stricter state.domain:speccard when a producer pulls for it. The maintainer can veto this reading.Findings:
FieldTypeenum comment sayscomposite/repeaterhold "declared sub-fields", butFieldSchemahas no sub-field member →Acceptance notes(a schema-level reading; no reach at a public door, no producer)unitPrice→UnitPrice), becausehumanizeLabelis the value convention →Acceptance notes(no measured producer uses camelCase keys)ObjectField's read-only branch (the form widget) still shows pretty-printed JSON →Acceptance notes(not exercised at a public door; the record page goes through the cell table)Landing: ready + auto-merge through the queue, now. objectui#11748 (a formula's display by
returnType) is serial behind this PR forpackages/fields/src/index.tsx(seat post §3, deferral6026496534).Generated by Claude Code
objectstack-fleet commentedon Oct 6, 2026
Landed —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-06T23:03Z. PR objectui#11749 merged through the queue asdb4cb6b.055d350. It is an ancestor oforigin/main. The landed tree equalsgit merge-treeof that parent and the accepted head22274e7(tree869aa40fon both). 7 files, +549/−23.packages/fields/src/index.tsx,055d350→db4cb6b:composite: CompositeCellRenderer: 0 → 1;record: RecordMapCellRenderer: 0 → 1;function StructuredValueCell: 0 → 1;composite: JsonCellRenderer: 1 → 0.json: JsonCellRendereris 1 on both.Fixes #11697. objectui#11697 is the only issue closed since 22:40Z.pm:dispatchedremoved. The audit-log actor half stays open as objectui#11701.returnType) was serial behind this PR forpackages/fields/src/index.tsx, and is now free (seat post §3).Generated by Claude Code
fix(fields): composite and record values read as labelled sub-values,…