Skip to content

plugin-detail: a record:details node with no sections renders an empty body, though the spec declares that omitting sections falls back to the object's highlightFields; the served approval-request page's Details tab is blank #12433

Closed

Description

@objectstack-fleet

Filing gate ① a measured defect, class b (a declared behaviour the renderer does not honour). It comes from the objectui#12045 dev's report on PR #12432, measured on a real RecordDetailView.

The declaration

@objectstack/spec ui/component.zod.ts, the record:details retired-layout entry, read on objectstack main (both the TSDoc and the refusal text the spec serves):

sections renders the explicit groups (the old custom), and omitting it falls back to the object's highlightFields (the old auto).

The served page's own comment expects the same tab to show "the request's own fields by the object's field groups" (sys-approval-request.page.ts:35–:36).

What objectui does

A record:details node with no sections and no fields renders one empty space-y-6 div: neither the highlightFields fallback the spec declares nor the object's field groups. Measured by the #12045 dev on a real RecordDetailView mount of the served page's shape.

So on the served request page:

  • the A2 record-pointer card for record_id, which the details grid is where record_id was meant to draw, is drawn nowhere;
  • every request field outside the highlights strip is drawn nowhere.

For the claimant

  • Seam: spec:RecordDetailsProps.sections (omitted) → renderer: plugin-detail's record:details renderer.
    • Per the base principle, a declared fallback that the renderer does not honour is an implementation gap: build it, ⛔ do not narrow at the consumer.
    • Whether the fallback reads highlightFields (the spec's words) or the object's field groups (the page comment's words) is the spec's text to settle. The spec's sentence governs unless the spec seat says otherwise.
  • Hidden-field caution:
    • If the answer draws the object's field groups, sys_approval_request's State group carries payload_json and node_config_json. Those must not reach a business approver as raw text; the inbox gates its raw-payload panel to studio.access (objectui#5553).
    • The highlightFields route avoids that, because the details grid already hides the fields the strip shows.

Dedupe: semantic search over objectui issues, closed included, for record:details no sections, empty body, highlightFields fallback: 0 matching (the nearest, objectui#7064 and objectui#12348, are about hideEmpty).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p1 + target:v18 · domain:ui · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T05:54Z

    Read fresh. A record:details node with no sections renders one empty div. The spec declares that omitting sections falls back to the object's highlightFields (the retired layout: 'auto'). This was measured on a real RecordDetailView. The served sys_approval_request_detail page's Details tab is exactly that node, so every approver sees a blank Details tab: no request fields and no record-pointer card.

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (priority:p1, taken first per the lane's P0/P1 read)
    Session: session_01JZnppgHRp4sREcouhkQYDB
    Account: sunhaolin
    Branch: claude/issue-12433-record-details-fallback
    Worktree: objectui-issue-12433
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/plugin-detail/src/renderers/record-details.tsx (the no-sections branch) and whatever it already reads the object definition and the HighlightFieldsContext registration through, the tests beside it, and one .changeset/12433-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
    Clause-②: no
    Responsibility: plugin-detail's record:details renderer, which draws an empty body when sections is omitted | @objectstack/spec declares the fallback ("omitting it falls back to the object's highlightFields", ui/component.zod.ts, the retired-layout entry), so the fix is the renderer honouring a declared behaviour | every approver who opens an approval request (objectstack plugin-approvals' served sys_approval_request_detail Details tab is a bare record:details), and any page an author or AI writes the same way
    Thread-read: 6185500844
    Serial constraints cleared: none. Read at objectui main after 6843f7599. No open PR and no in-flight claim names packages/plugin-detail/. Seat 2's objectui#12413, #12422 and #12426 and seat 3's objectui#12423 touch data-objectstack, plugin-list, AppContent.tsx and the list toolbar, all disjoint.

    • Ruling carried: triage 6185500844: "Build the declared fallback; ⛔ do not narrow at the consumer. highlightFields, the spec's words, governs unless the spec seat says otherwise."
    • Accept-set reading: no type, prop or export changes. The renderer draws what the spec already declares.

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12433,
    "status": "done",
    "branch": "claude/issue-12433-record-details-fallback",
    "pr": "#12441",
    "session": "session_01JZnppgHRp4sREcouhkQYDB",
    "premise_still_valid": true,
    "summary": "A1 confirmed on main 6843f75 before any edit: a record:details node with neither sections nor fields drew one empty space-y-6 div (new pin file: 8 failed / 5 passed). The renderer now reads the object's highlightFields as the body list when the node authors neither key, and runs that list through the authored-fields path: the enforceFieldSecurity/redactFields fold, the label ladder, and the strip (HighlightFieldsContext) and page-H1 dedupe. An authored sections or fields, an empty one included, is unchanged; a list the fold empties stays empty; an object with no highlightFields keeps its empty body (A3: the spec names no further fallback, so it is pinned and returned as the open question below). A2 on the real RecordDetailView (scratch probe of the console served-page harness): the served sys_approval_request_detail Details tab went from an empty record:details to exactly one row, Record ID, drawn by the host record-pointer card; the strip's six fields are not repeated and payload_json/node_config_json never reach the tab. A5 is partly falsified: the runtime default page passes explicit sections, but buildDefaultPageSchema called without sections (Studio page-create seed, Studio slotted preview) emits a bare node for an object with no fieldGroups, and the Studio add-block inserts a bare node. No type, prop or export changed (Clause-2 no holds); one patch changeset.",
    "tests": "Final union at head 9726530, through the verify lock (VERDICT command-exit 0, 12m45s): pnpm --filter @object-ui/plugin-detail type-check exit 0 (tsc --noEmit && tsc -p tsconfig.test.json; --listFiles counts the new pin file 1, control 1); pnpm exec vitest run packages/plugin-detail/ -> 'Test Files 271 passed | 1 skipped (272)', 'Tests 2627 passed | 14 skipped (2641)'; pnpm exec vitest run packages/app-shell/src/views/RecordDetailView (all 58 RecordDetailView suites) + apps/console/src/services/approvalRequestsDataSource* + metadata-admin PagePreview*, page-preview-record-type-10482, palette-discussion-alias, dataPreviews.i18n-10862-s3 -> 'Test Files 71 passed (71)', 'Tests 532 passed (532)'. New pin file record-details.highlightFieldsFallback-12433.test.tsx, 13 cases: bare node draws highlightFields in order with declared labels; H1 dedupe; mounted strip's fields not repeated; CONTROL authored sections / authored fields / authored empty fields:[] and sections:[]; enforceFieldSecurity-refused field not drawn; redacted field not drawn; fold-emptied list stays empty; no-highlightFields object stays empty plus lit control; served page shape draws exactly the record_id pair {objectName:'invoice',recordId:'inv_1'} and never the raw JSON columns. Red on main first: 8 failed / 5 passed. Ablations (predictions written first; objectstack scripts/ablation-replace.mjs: anchor 1 -> 0, restore blob == HEAD 6438c0ed2678, git diff HEAD empty; tests import src via root vitest aliases, so no dist leg): AB1 fallback dropped -> 8 failed / 5 passed, as predicted. AB2 block fold skipped for the top-level list -> 2 failed (redact, fold-emptied) / 11 passed, as predicted: the enforceFieldSecurity case is also held by DetailView's own field-read gate, so the redact case is the one that discriminates the block fold (said beside the case). A2 real-view probe: after = tab panel text 'Record ID' + record card (data-record-preview, unreadable state because the harness adapter double serves no invoice row); before (fallback ablated) = empty record:details div. The probe file was never committed; its copy is in the session scratchpad.",
    "gates": "Local subset, exit codes captured to files before any pipe, at 9726530: exit 0 with each gate's own pass line: check:control-bytes ('OK (scanned 8777 tracked text file(s)'), check:new-line-citations ('0 new citation(s), enforcement report-only -> exit 0'), check-changeset-no-major, check-changeset-overwrite ('1 changeset(s) added, 0 modified, 0 deleted'), check-changeset-presence ('2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check:pending-changeset-literals, check:changeset-claims, check-changeset-fixed, check:test-path-roots, check:vi-mock-override-shape. eslint on the two touched TS files: exit 0, 0 errors; the renderer has 1 no-explicit-any warning fewer than base (29 vs 30 measured). check-governed-queue-guard --test on the 3 paths: NOT GOVERNED. Dependency closure built for type-check: turbo run build --filter=@object-ui/plugin-detail^... --concurrency=2, VERDICT command-exit 0. Not run: check:eager-closure (the renderer's imports did not change: 0 import lines in the diff). No --ran reconciliation: dispatch-gates refuses a sister repo, so the list was derived by hand from objectui package.json and lint.yml / ci.yml / changeset-guard.yml. CI on PR 12441 head 9726530 at report time: 36 check-runs, label success, 3 skipped (Test (coverage), its shard matrix, dependabot), Changeset Declaration queued, 31 in_progress (Type Check, Lint, Test shards 1-16, Build and E2E, Repo Gates, Line Citation Gate, changeset gates and the rest); not awaited, per contract. Known red possible: objectui#12422 (plugin-list type-check race), not addressed here.",
    "mcp_calls": "5, all reads: mcp__github__issue_read get (objectui#12433), issue_read get_comments (objectui#12433), issue_read get_comments (objectui#12045, for dev report 6185129577), mcp__github__pull_request_read get (objectui#12441, body read-back), pull_request_read get_check_runs (objectui#12441). No MCP write tool.",
    "api_writes": "2 relay strokes plus git pushes. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos//issues/12441/assignees (sunhaolin); read-back 8138 of 8138 bytes identical, assignees sunhaolin. (2) This os-dev-report comment through scripts/pm/post-stamped.mjs: POST /repos//issues/12433/comments. git push of claude/issue-12433-record-details-fallback three times (empty branch probe, d088c5c, 9726530). No label write.",
    "open_questions": [
    {
    "question": "A3: what should a bare record:details draw on an object that declares no highlightFields? Today, and after this PR, an empty body: RecordDetailsProps names highlightFields and nothing further, and the object's highlightFields describe names no detail-body fallback.",
    "options": [
    "A, keep it empty and have the spec seat say so in the RecordDetailsProps describe (one sentence: an object with no highlightFields renders an empty body; author sections or fields). Cost: one describe line in objectstack spec, about a tenth of a round. Business need: no measured page hits the case today; the one served bare node's object declares highlightFields. Long-term: declaration equals behaviour, no new surface. AI-proofing: the outcome is written where an author or AI reads the contract, though nothing refuses the bare node. Startup: smallest.",
    "B, the spec declares a further fallback, the object's field-group body (what the synthesized default page draws: fieldGroups references, else every visible non-audit field), and the renderer builds it. Cost: a contract review in objectstack plus moving the ungrouped field walk out of app-shell into plugin-detail, about 1 to 2 rounds. Business need: covers the Studio add-block and page-create seed on objects without highlightFields (about 31 of 111 objectstack object files declare none, a rough file census). Long-term: widens the declared chain and couples the block to the synth field walk. AI-proofing: a bare node always draws something, which hides the omission, and on an object like sys_approval_request it would draw raw columns such as payload_json (the card's own caution). Startup: widens the spec.",
    "C, refuse at authoring: os validate / lint flags a bare record:details on an object with no highlightFields. Cost: a new lint rule, about one round. Business need: no measured incident. Long-term: contract-first, publish-time loud. AI-proofing: strongest. Startup: a new gate, which the frame defaults to no without a maintainer's naming."
    ],
    "recommendation": "A. Business need: no measured page reaches the empty case today. Long-term: it makes the declaration match the behaviour with no new surface, and B can still be carded if a real page needs it. AI-proofing: B's always-draw-something tolerance is the pattern the frame warns against, and it would surface raw columns; C is stronger but is a new gate with no maintainer naming. Startup focus: A is a one-line spec edit."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · objectstack plugin-approvals sys-approval-request.page.ts's strategy comment says the Details tab shows 'the request's own fields by the object's field groups'; with the declared fallback it draws the record_id pointer card only (the highlightFields minus the strip), per triage's highlightFields ruling. The comment is now inaccurate; the edit is one comment line in objectstack. Noted, not filed.",
    "carrier: 承接者:无 · Studio slotted preview (PagePreview) and page-create seed (anchors.ts createSeed) call buildDefaultPageSchema without sections, so for an object with no fieldGroups they emit a bare record:details, which now draws highlightFields while the runtime default page draws the full field split; resolveDetailSections' doc still says 'falls back to its flat layout'. Read-only inference, not reproduced in a browser. Recorded in the PR's Acceptance notes. Noted, not filed."
    ],
    "deviations": [
    "The served-page pin is a composed harness inside plugin-detail (one record context, the highlight registry, the strip and the body), because the real RecordDetailView lives in app-shell/console outside the file surface; the real-view reading was taken with a scratch probe in apps/console, never committed and deleted.",
    "AB2 (block fold skipped) ran beyond the requested ablation, to say which pin discriminates the block fold; it found the enforceFieldSecurity pin is held by two layers, and the test now says so beside the case (commit 00c12c0).",
    "The first item under out_of_scope_findings (the objectstack page comment) did not make the PR's Acceptance notes; the PR body is written once, so it is carried here only.",
    "Commit trailers use objectui AGENTS' model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named trailer. The PR body ends with the session-URL footer the agent definition prescribes, not the harness's 'Generated with' form.",
    "Worktree removed: node_modules first, then git worktree remove, exit 0; my private fetch ref refs/dev-12433/main deleted."
    ],
    "files_changed": [
    ".changeset/12433-record-details-highlight-fallback.md",
    "packages/plugin-detail/src/renderers/record-details.tsx",
    "packages/plugin-detail/src/renderers/tests/record-details.highlightFieldsFallback-12433.test.tsx"
    ],
    "line_budget": "not applicable (no skills/** surface)"
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T07:24Z

    • Merged: PR fix(plugin-detail): a record:details node with no sections and no fields draws the object's highlightFields (objectui#12433) #12441 landed through the merge queue as main 9782cb226 at 2026-10-15T07:23Z. It has one parent, 431cece41, and is an ancestor of origin/main.
    • Verified by content: all 3 files the PR changes are blob-identical on 9782cb226 to the accepted head 9726530a69.
    • Mis-close scan: the PR body's only closing keyword is Fixes #12433, and the only issue closed with the merge is this card, completed.
    • What now holds:
      • A record:details node that authors neither sections nor fields draws the object's highlightFields. This is the fallback @objectstack/spec declares. The list passes through the field-security fold, and the strip and H1 dedupe.
      • On the served sys_approval_request_detail page, the Details tab now shows the request's target record (record_id) as its record card. The raw payload_json / node_config_json columns never reach it.
    • The chain: claim 6185732199, report 6186395799, ACCEPT 6186443949. No contract face applied.
    • Carried on:
      • objectui#12442: Studio's page-create seed and preview build the default page without the runtime's detail sections. It is the one route into the no-highlightFields empty case the seat answered in the ACCEPT.
      • objectui#12438: the inbox decision, which still awaits the maintainer.
    • Label: pm:dispatched is removed in the same act as this note. The assignee stays, as the record of who drove it.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions