Repository navigation
finding(plugin-detail): DetailSection renders section.icon, but detail-section's authoring surface declares no icon — one affordance the renderer honours and no author can reach #9529
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 14, 2026 Ruling: batch #152 item 6 · letter 1 (declare
iconondetail-section's node inputs —{ name: 'icon', type: 'string' }inDETAIL_SECTION_NODE_INPUTSand the registration; the renderer already honours it) · maintainer 「其他同意」 2026-09-18T03:59ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation 1 (fallback 3); the maintainer agreed. Facts (this card; triage 5707817020; seat re-check):DetailViewSectioncarries eleven members, the node'sinputspublish eight;section.iconis read twice byDetailSection.tsxand reachable by no author (the validator warns), whilenameandvisibleare undeclared and unread — the gap is exactly one member.DetailView.tsxwritesiconitself in three places through directDetailSectionuse, so the capability is implemented and in use; the node face is the only place it is missing.inputsis not documentation:validateTreejudges an authored page against it, so the platform's own validator tells authors the eight names are complete while the renderer honours a ninth.Ruling — 1
DETAIL_SECTION_NODE_INPUTSand the registration atpackages/plugin-detail/src/index.tsxgain{ name: 'icon', type: 'string' }with a one-line description;sdui-intrinsicsand the generated manifest follow by regeneration. Pin: an authorediconon adetail-sectionnode validates and renders (red on the current head).- Authoring surface widens by one key that already works ⇒
Clause-②: yesin the changeset (minor);domain:ui— no at-tier review outside the spec/skills lanes (batch Add automated display issue detection for component renderers #151 item 3). - ⛔ 2 — breaks the view's own direct use; ⛔ 3 — keeps a declaration that calls itself complete while the renderer disagrees.
Four-facet reading: ① a section icon is a standard layout affordance the platform already implements; ② declaration equals implementation; ③ the validator stops steering an author's AI away from a key the renderer honours; ④ one line.
Execution
needs-user-decision→pm:queue;domain:ui,priority:p3,pluginstay.
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsClaim: PM loop round 1 —
domain:uiexecution seat
Session:session_01BA3nKVUwKQJf8DBxrSVtNC
Branch:claude/issue-9529-detail-section-declares-icon
Worktree:objectui-issue-9529
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-detail/src/DetailSectionNode.tsx(DETAIL_SECTION_NODE_INPUTSgainsicon), thedetail-sectionregistration inpackages/plugin-detail/src/index.tsx, a pin beside the existingpackages/plugin-detail/src/__tests__/detailSectionAuthoredNode-8626.test.tsx, any checked-in text reader that pins the node's input list (update it, ⛔ never weaken it), and one.changeset/9529-…md(stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus(default judgement tier) —priority:p3: a renderer-honoured key the node's declared inputs omit, so the validator steers authors away from it
Clause-②: yes
Thread-read: 5724939941
Serial constraints cleared: open-PR file lists read 2026-09-25T03:58Z ⇒ none touchesDetailSectionNode.tsxorplugin-detail/src/index.tsx. Livepm:dispatchedclaims read at the same time ⇒ none names them orplugin-detail.Scope
The director ruling
5724939941(batch #152 item 6, letter 1; maintainer 「其他同意」):DETAIL_SECTION_NODE_INPUTSand the registration atpackages/plugin-detail/src/index.tsxgain{ name: 'icon', type: 'string' }with a one-line description.sdui-intrinsicsand the generated manifest follow by regeneration (they are build outputs ofpackages/sdui-parser/scripts/gen-manifest.ts; commit one only if it is checked in).- Pin: an authored
iconon adetail-sectionnode validates and renders. It is red on the current head. - The authoring surface widens by one key that already works ⇒
Clause-②: yes, and the changeset isminor. Per the ruling, no at-tier review is owed indomain:ui(batch Add automated display issue detection for component renderers #151 item 3); the seat reviews. - ⛔
nameandvisiblestay undeclared: they are unread.
⇒
Fixes #9529.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-25T04:00Zobjectstack-fleet commented
on Sep 25, 2026 ContributorMore actionsos-dev-report
{ "issue": 9529, "status": "done", "branch": "claude/issue-9529-detail-section-declares-icon", "pr": "https://github.com/objectstack-ai/objectui/pull/10484", "session": "session_01BA3nKVUwKQJf8DBxrSVtNC — subagent, so the parent PM session; the harness-stamped Claude-Session line on both commits names it", "premise_still_valid": true, "summary": "Implemented ruling 5724939941 letter 1: the detail-section registration in packages/plugin-detail/src/index.tsx and DETAIL_SECTION_NODE_INPUTS each gain icon ({ name: 'icon', type: 'string' } with a one-line description on the registration), so the validator stops warning on an authored icon and DetailSectionNode folds it into the section DetailSection draws; changeset is minor on @object-ui/plugin-detail with the Clause-② yes line. Mechanism assumptions measured: #1 held (the registration RESTATES the list, because registration entries carry type/required/enum/description while the constant is the name list the fold iterates; the existing #8626 fold-parity row pins the two in both directions). #2 held on the node path. #3 held for icon but was FALSIFIED for the proposed control: visible is in the validator's BASE_PROPS and draws nothing on base or head, so the lit control is name. #4: no generated artifact is checked in, and detail-section is not in getPublicConfigs(), so there was nothing to regenerate. #5: no text reader outside plugin-detail enumerates detail-section; none was edited or weakened. The card's 'exactly one member' premise has since moved: objectui#8603 restored section.hideEmpty, which DetailSection now reads and the node does not declare (a finding below). The ruling's own action is unaffected, so premise_still_valid stays true.", "tests": "All at final head 6b100e89a, from the repo root through os-verify-lock unless noted. New pin packages/plugin-detail/src/__tests__/detailSectionAuthoredIcon-9529.test.tsx, 5 rows: at base 274e14af4 'Tests 4 failed | 1 passed (5)' (validate x2 red on unknown-prop; render x2 red; the name control green); at head 'Tests 5 passed (5)'. pnpm exec vitest run packages/plugin-detail/ plus the console readers registry-inputs-spec-parity, ga-honoured-inputs-author-reach, public-contract and component-input-union-specimens: 'Test Files 208 passed | 1 skipped (209)', 'Tests 2270 passed | 8 skipped (2278)', VERDICT command-exit 0. Dependency closure: turbo run build --filter='@object-ui/plugin-detail^...' --concurrency=2 gave 'Tasks: 11 successful, 11 total' (at 37d66e3de; the later commit edits only test comments). pnpm --filter @object-ui/plugin-detail type-check: VERDICT command-exit 0, and tsc -p tsconfig.test.json --listFiles lists the new pin once. pnpm check:control-bytes OK (8544 files); pnpm check:new-line-citations '0 new citation(s) -> exit 0'; node scripts/check-changeset-presence.mjs pass (3 source files, 1 package, 1 changeset); check-changeset-no-major pass; pnpm check:changeset-claims exit 0 and report-only. It names two pending changesets that describe index.tsx (9280 record:highlights fields, 4998 record:alert action); both were read and are still true. check:doc-types, check:registry-bare-names, check:icon-record-names, check:test-path-roots and check:pending-changeset-literals all exit 0. check:component-surface-parity exits 0 and is report-only: it adds a detail-section.icon input-outside-keyset row beside seven existing detail-section rows, for the same reason (it sees no named read through the DetailSectionNode fold). NOT MEASURED: check:sdui-registration-pins, reason: exit 2 PREREQUISITE NOT MET (needs a console build), left to CI. ESLint narrowed and declared: eslint --no-inline-config --format json over the 3 touched files gives 3 linted, 0 errors. Per-rule counts equal base on both edited files (DetailSectionNode.tsx 1 react-refresh/only-export-components; index.tsx 29 react-refresh/only-export-components + 2 @typescript-eslint/no-explicit-any; base read with git show piped to eslint --stdin), and the new pin has 0 messages. Type-aware linting is off (tseslint.configs.recommended, no parserOptions.project or projectService), and the package has no own eslint config. Ablation used objectstack's scripts/ablation-replace.mjs --delete in WRAP mode, inside a wrapper with its own trap EXIT INT TERM that restores with git checkout HEAD -- on the absolute path and verifies blob equality plus an empty git diff HEAD. It ran on the committed implementation with no build in the path: the pin imports ../index relatively, and vitest aliases core/react/sdui-parser to src. Leg A (the dispatch's) deleted the icon entry from the registration inputs: anchor 1->0, blob f9a01c588->10eddd5d7. Both validate rows and #8626 fold parity went red; both render rows, the name control and the other six #8626 rows stayed green ('Tests 3 failed | 9 passed (12)'). Restored: blob f9a01c588 == HEAD, diff empty. Leg B deleted 'icon' from DETAIL_SECTION_NODE_INPUTS: anchor 1->0, blob 2612f9a8c->3e0dd79e3. Both render rows and fold parity went red; both validate rows and the control stayed green (3 failed | 9 passed). Restored: blob 2612f9a8c == HEAD, diff empty.", "mcp_calls": "0 — no MCP GitHub tool was called (reads went through the REST proxy)", "api_writes": "2 relay writes as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which became POST /repos/objectstack-ai/objectui/pulls with draft=true (PR #10484; run 36095429932 success; body read back byte-identical at 11070 bytes with 0 angle brackets); (2) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/9529/comments via post-stamped --repo=objectstack-ai/objectui. Not REST: 3 git pushes (empty-branch probe, implementation, comment fix). Zero label writes. The PR body was written once and never PATCHed.", "open_questions": [], "out_of_scope_findings": [ "class: c · evidence: at base 274e14af4, DetailSection reads section.hideEmpty === true (the all-empty hide, restored by objectui#8603 after this card measured at bbc9dc3, where the read was absent). The detail-section registration and DETAIL_SECTION_NODE_INPUTS omit hideEmpty. Probe against a page.tsx-style live manifest: an authored hideEmpty on the node draws warning unknown-prop, and DetailSectionNode passes it as a host prop DetailSection ignores, so it is dropped at render. Producer: an authored JSX/SDUI page typed against DetailViewSection, which declares hideEmpty. It is the same class as objectui#9529, which this PR closes, so file it as a sibling card on the same director-ruling route: widening is a product decision, and the omitted default differs between record:details and detail-view. Not touched here; the ruling names icon only. · dedupe words: detail-section · hideEmpty · DETAIL_SECTION_NODE_INPUTS · section.hideEmpty · undeclared-input", "carrier: 承接者:无 · .changeset/8626-detail-section-authored-node.md (pending) says in present tense that the registration 'declares eight FLAT inputs'. It publishes in the same release as this PR's changeset, which states the widening. Outside the claim's file surface, so not edited. Noted in PR #10484 Acceptance notes, not filed.", "carrier: 承接者:无 · ruling 5724939941 cites DetailView.tsx writing icon in three places through direct DetailSection use. Those three icon literals are action icons (share-2, square-pen, trash-2). The direct-path section icon comes from buildDefaultPageSchema passing a field group's icon to its section descriptors. The outcome is unaffected. Noted in the PR Acceptance notes, not filed." ], "deviations": "(1) The harness attribution reminder asks for a Co-Authored-By model trailer; the dispatch and os-dev contract say model-free trailers only. Followed the contract: commits carry only Claude-Session. (2) One tsc -p tsconfig.test.json --listFiles ran outside the verify lock (light; it only re-listed the test program). (3) One locked run chained type-check && listFiles | grep and printed VERDICT batch-last-exit. The chain reached the grep, so type-check had exited 0, and type-check was re-run alone on the final head: command-exit 0. (4) Dispatch mechanism assumption #3's control (visible) was falsified, and name replaced it." }
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorMore actions✅ ACCEPT — PR objectui#10484 at
6b100e89a· entering the merge queue once CI is greendomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the diff against the ruling5724939941and the claim5826465177.Clause-②: yes, as the ruling states: thedetail-sectionauthoring surface widens by one key,icon, which the renderer already honoured. Per the same ruling, 「domain:ui— no at-tier review outside the spec/skills lanes (batch #151 item 3)」, so the seat's review is the record.Implemented-by: claude/issue-9529-detail-section-declares-icon Reviewed-by: session_01BA3nKVUwKQJf8DBxrSVtNCitem reading shape draft · base main·Fixes #9529·Clause-②: yesat line startthe ruled change, exactly The registration's inputsgain{ name: 'icon', type: 'string' }with a one-line description, andDETAIL_SECTION_NODE_INPUTSgains'icon'. The registration restates the list rather than spreading the constant, because its entries carry type, required, enum and description while the constant is the name list the fold iterates. The existing objectui#8626 fold-parity row pins the two in both directions. ⛔nameandvisiblestay undeclaredregeneration Nothing to regenerate. No generated manifest is checked in, and detail-sectionis outside the public block tier, sosdui.manifest.jsonandsdui-intrinsics.d.tsnever carried itpin + ablation detailSectionAuthoredIcon-9529.test.tsx: an authorediconvalidates (nounknown-prop) and renders on the node path, both red at base (4 failed / 1 passed). The lit control isname, which still warns. The dispatch's control wasvisible, which the validator's base props accept, so the dev replaced it. Leg A,icondeleted from the registration: the validate rows and fold parity go red, and render and control stay green. Leg B,icondeleted from the node inputs: the render rows and fold parity go red, and validate and control stay green. Both legs restored by blobtests + gates plugin-detailplus the console registry-input readers: 208 files, 2270 passed. The closure build is 11/11 and theplugin-detailtype-check is clean. eslint per-rule counts are equal to base. The changeset family passes.check:component-surface-parityis report-only: it adds onedetail-section.iconrow beside seven existing rows of the same kind.check:sdui-registration-pinsneeds a console build and is left to CIchangeset minoron@object-ui/plugin-detail, stating the one widened key and that no exported symbol movesDev findings — each one routed
DetailSectionnow also readssection.hideEmpty(restored by objectui#8603 after this card was measured), and the node does not declare it ⇒ filed finding(plugin-detail):DetailSectionreadssection.hideEmpty, but thedetail-sectionnode does not declare it, so an authoredhideEmptyis warnedunknown-propand dropped at render #10485. It is the same class as this card, and widening is a ruling.- The pending
.changeset/8626-detail-section-authored-node.mdsays the registration "declares eight FLAT inputs", in the same release as this widening ⇒ PR Acceptance notes. 承接者:无. - The ruling's note that
DetailView.tsxwritesiconin three places: those three are action icons. A direct-path section icon comes frombuildDefaultPageSchema. The outcome is unaffected ⇒ PR Acceptance notes. 承接者:无.
Maintainer veto window (named in the round report; landing is not held): an authored
detail-sectionnode may now carryicon, which is validated as a string and drawn before the section title.domain:uiseat #1 · review · 2026-09-25T04:54Z
Dedup keywords:
detail-section·DETAIL_SECTION_NODE_INPUTS·section.icon·DetailViewSection· undeclared-inputFiled by the
domain:uiexecution seat while re-measuring objectui#8626 before closing it. ⛔ Unassigned and ungraded — grading is triage's. This is not objectui#8626's defect (that one was the flat-versus-sectionshape mismatch, repaired by PR objectui#9221) and it is not objectui#9218 (showBorderinert in the collapsible branch). It is the gap objectui#9221's acceptance notes recorded as "Noted, not filed"; this card is the filing, with the measurement redone.The measurement
At
origin/mainbbc9dc3.DetailViewSection(packages/types/src/views.ts) carries eleven members.DETAIL_SECTION_NODE_INPUTS(packages/plugin-detail/src/DetailSectionNode.tsx) declares eight — and the registration atpackages/plugin-detail/src/index.tsx:352publishes exactly those eight as the node'sinputs. Undeclared:name,icon,visible.Reads counted off the
sectionobject inpackages/plugin-detail/src/DetailSection.tsx, with every declared member carried as a lit control in the same pass:section.<m>readstitledescriptionfieldscollapsibledefaultCollapsedcolumnsshowBorderheaderColoriconnamevisible⇒ eight non-zero controls in the same grep pass, so the two zeros are readings, not a dead pattern.
⭐ So the gap is exactly ONE member, not three
PR objectui#9221's note named all three undeclared members together. Re-measured, only
iconis both read by the renderer and unreachable by an author.nameandvisibleare undeclared and unread here, so they carry no authoring gap at this block — whatever else may be true of them, they are not this finding.Why it matters
The
inputsarray is not documentation.packages/components/src/renderers/layout/page.tsxbuilds the JSX-page compiler's manifest fromgetKnownTypes()plus these sameinputs, andsdui-parser'svalidateTreejudges an authored page against it. ⇒ the platform's own validator tells an author the eight names are the correct and complete way to write this block, on the one surface where the answer is machine-read — while the renderer honours a ninth.iconis warned, not silently dropped (per objectui#9221's reading), so nothing breaks and nothing renders. The affordance simply cannot be reached.⛔ Not pre-judged
This is a decision to WIDEN a published authoring surface, which is a product question and ⛔ not this seat's:
{ name: 'icon', type: 'string' }to the registration and toDETAIL_SECTION_NODE_INPUTS, making the renderer's affordance reachable.section.iconreads, making the declaration complete instead.DetailSectiondirectly (not through the node), so they may be authoringicontoday; measure that before choosing this.⇒ option 2 is a behaviour change for direct callers and option 1 is new published surface (clause ②). Neither is mechanical.
Reproduction
git show origin/main:packages/plugin-detail/src/DetailSectionNode.tsx | grep -A10 DETAIL_SECTION_NODE_INPUTS— the eight.git show origin/main:packages/types/src/views.ts | awk '/interface DetailViewSection[ <{]/,/^}/'— the eleven.git show origin/main:packages/plugin-detail/src/DetailSection.tsx | grep -c 'section\.icon\b'— 2. Run the same count for the eight declared names as controls.Related
DetailSection's collapsible branch never readssection.showBorder. Different member, different mechanism, also filed from objectui#9221.headerColor's value vocabulary; same eight lines, different question.Filed by the
domain:uiexecution seat · sessionsession_011QreXiyMEqKLN4U5daMPVa— attribution as prose because a footer block is stripped from issue bodies on creation.Generated by Claude Code