feat(plugin-detail): declare icon on the detail-section node (objectui#9529) - #10484
Conversation
…i#9529)
DetailSection draws section.icon in both header branches, but the
detail-section registration did not declare icon among its inputs and the
node adapter folded only declared names, so an authored icon drew an
unknown-prop warning from the JSX-page validator and rendered nothing.
Declare { name: 'icon', type: 'string' } on the registration and in
DETAIL_SECTION_NODE_INPUTS, so the adapter folds it. The new pin reads the
validator verdict, the declared type, and the drawn glyph in both header
branches, with name as the lit unknown-prop control.
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
Name the AGENTS.md test-discipline section in English and state only the module relationship that was read off lucide's ESM entry. Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #9529
Clause-②: yes
Declares
iconon thedetail-sectionnode, per the director ruling on the card (comment5724939941, letter 1):{ name: 'icon', type: 'string' }with a one-line description on the registration inpackages/plugin-detail/src/index.tsx, and'icon'inDETAIL_SECTION_NODE_INPUTS.DetailSectionalready drawssection.iconin both header branches; the node now declares the key, and its adapter folds it through.What changed
packages/plugin-detail/src/index.tsxdetail-sectionregistration gains{ name: 'icon', type: 'string', description }; two comment phrases counting "the eight declared inputs" made count-freepackages/plugin-detail/src/DetailSectionNode.tsxDETAIL_SECTION_NODE_INPUTSgains'icon', so the adapter folds it intosection; present-tense "eight" claims in its comments made count-free (the #8626 history keeps its past-tense count)packages/plugin-detail/src/__tests__/detailSectionAuthoredIcon-9529.test.tsx.changeset/9529-detail-section-declares-icon.mdminoron@object-ui/plugin-detail; states the one widened key and carries the Clause-② lineTwo edits, not one source — measured, not chosen. The registration RESTATES the list rather than spreading the constant: each registration entry carries a type,
required, an enum or a description, whileDETAIL_SECTION_NODE_INPUTSis the name list the fold iterates. The two are already held together by the fold-parity row ofdetailSectionAuthoredNode-8626.test.tsx, which compares them in both directions and goes red under both ablation legs below. Deriving one from the other is a refactor this card does not carry.Premises, measured on base
274e14af4before the editDETAIL_SECTION_NODE_INPUTSlives inDetailSectionNode.tsx; the registration restates it (above). Held.DetailSection.tsxreadssection.icontwice, once per header branch (the non-collapsibleCardheader and the collapsible trigger header). Measured on the NODE path: the pin renders through the realSchemaRendererand the real registry, as the finding(plugin-detail): everydetail-sectioninput is a FLAT prop the renderer never reads —DetailSectionreadssection.*only, and an authored node hands itsection === undefined#8626 pin does. Held.icontoday: a probe against a manifest built the waypage.tsxbuilds it returnedwarning unknown-propforicon. Held foricon; falsified for the proposed control.visibledraws NOTHING on base or head: it is in the validator'sBASE_PROPS(sdui-parser'svalidate.ts), the keys every node may carry, which are never judged againstinputs. The pin's lit control isnameinstead:warning unknown-propon both sides.sdui.manifest.json,sdui-intrinsics.d.ts,sdui-blocks.mdis checked in (git ls-filesfinds none of the three; control: the same listing findsgen-manifest.ts). Anddetail-sectionis outside the public tier: the probe readgetPublicConfigs()without it (false, whilerecord:detailsreadstrue), sogen-manifest.tswould not emit it even if run. Nothing to regenerate.plugin-detailnamesdetail-section. The four console readers the dispatch named have 0 hits each (record:detailsis the lit control in three of them), and they stay green. The in-package readers that do name it, the finding(plugin-detail): everydetail-sectioninput is a FLAT prop the renderer never reads —DetailSectionreadssection.*only, and an authored node hands itsection === undefined#8626 pin (fold parity, both directions) and thedetail-sectionstill offersheaderColoras a free-formstringinput, so the SDUI authoring surface invites values the published validator now refuses #6955 pin, stay green unmodified. None was weakened.nameandvisiblestay undeclared.The pin:
detailSectionAuthoredIcon-9529.test.tsx274e14af4unknown-propicon: 42drawstype-mismatchnamingicon)unknown-propnamestill drawsunknown-propcollapsible: false); the same node withouticondraws nothing therecollapsible: trueBase:
Tests 4 failed | 1 passed (5). Head:Tests 5 passed (5).The render verdict is the GLYPH: the drawn svg's markup must equal
MapPin's.LazyIcondraws theDatabaseglyph while it loads and for an unknown name, so a row that only asked "is there an svg" would pass on the fallback.Ablation: two legs, each proven on disk and restored by blob
Mutations went through
node ../objectstack/scripts/ablation-replace.mjs --delete(the anchor must hit exactly once, the blob must change; restore isgit checkout HEAD --on an absolute path, verified by blob equality with HEAD and an emptygit diff HEAD), inside a wrapper carrying its owntrap … EXIT INT TERMthat repeats that verification. Both legs ran on the committed implementation. No build sits between mutation and assertion: the pin imports../indexrelatively, and the root vitest config aliases the@object-ui/*packages it imports (core,react,sdui-parser) to theirsrc.iconentry from the registration'sinputsf9a01c588to10eddd5d7namecontrol, the other six #8626 rowsf9a01c588equals HEAD, diff empty'icon'fromDETAIL_SECTION_NODE_INPUTS2612f9a8cto3e0dd79e32612f9a8cequals HEAD, diff emptyEach leg read
Tests 3 failed | 9 passed (12)over the #9529 and #8626 pins. Each row reddens under the mutation of the thing it names, and only under that one.Verification, at the final head
6b100e89apnpm exec vitest run packages/plugin-detail/plus the four console readers (registry-inputs-spec-parity,ga-honoured-inputs-author-reach,public-contract,component-input-union-specimens), from the repo rootTest Files 208 passed / 1 skipped (209),Tests 2270 passed / 8 skipped (2278)pnpm exec turbo run build --filter='@object-ui/plugin-detail^...' --concurrency=2Tasks: 11 successful, 11 total(run at37d66e3de; the only later commit edits comments in the new test file)pnpm --filter @object-ui/plugin-detail type-checktsc -p tsconfig.test.json --listFileslists the new pin (1 hit)pnpm check:control-bytesOK (scanned 8544 tracked text file(s); skipped 85 binary)pnpm check:new-line-citations0 new citation(s), enforcement report-only -> exit 0node scripts/check-changeset-presence.mjs3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)node scripts/check-changeset-no-major.mjsNo changeset declares a major bumppnpm check:changeset-claimspackages/plugin-detail/src/index.tsx:9280-record-highlights-entry-icon-retired.md(therecord:highlightsfieldsdescription) andrecord-alert-cta-label-i18n-4998.md(record:alert'saction). Both paragraphs read; both still true, since this diff touches neither registrationcheck:doc-types,check:registry-bare-names,check:icon-record-names,check:test-path-roots,check:pending-changeset-literalspnpm check:component-surface-paritydetail-section.iconasinput-outside-keysetbeside seven of the pre-existingdetail-sectioninputs, for their reason: the gate sees no named read throughDetailSectionNode, which folds by iterating the listpnpm check:sdui-registration-pinsPREREQUISITE NOT MET(it needs a console build); left to CILint is narrowed, and the narrowing is declared.
eslint --no-inline-config --format jsonover the three touched source files: 3 files linted, 0 errors. Per-rule counts on the two edited files equal base (base read withgit show 274e14af4:PATHpiped toeslint --stdin --stdin-filename PATH):DetailSectionNode.tsx1react-refresh/only-export-components;index.tsx29react-refresh/only-export-componentsand 2@typescript-eslint/no-explicit-any. The new pin: 0 messages. The narrowing is a measurement because type-aware linting is not enabled:eslint.config.jsextendstseslint.configs.recommendedand declares noparserOptions.projectorprojectService, so this diff cannot move the verdict on any file it does not contain. The package's owneslint .resolves the same root config (there is no package-level one). The repo-wide run is CI's.Acceptance notes
hideEmptyis now a second member of this card's class. The card's "exactly ONE member" was measured atbbc9dc3, whereDetailSectionhad no livesection.hideEmptyread andDetailViewSectionhad nohideEmpty. objectui#8603 has since restored both. At base274e14af4,DetailSectionreadssection.hideEmpty === true(the all-empty hide), the node does not declarehideEmpty, an authored one drawsunknown-prop(probe), and the adapter does not fold it. Not touched here: the ruling namesicononly, and a widening is a product decision, the more so for a key whose omitted default differs betweenrecord:detailsanddetail-view(theDetailViewSection.hideEmptydoc block records that split). Handed to the seat in the report.DetailView.tsxwritesiconitself in three places through directDetailSectionuse; the threeicon:literals there are ACTION icons (share-2,square-pen,trash-2). A direct-path section icon does exist, throughbuildDefaultPageSchema, which passes a field group'siconto its section descriptors. The ruling's outcome is unaffected, and nothing in this PR depends on it..changeset/8626-detail-section-authored-node.md(unreleased) says in present tense that the registration "declares eight FLAT inputs"; it will publish in the same release as this PR's changeset, which states the widening. Not edited: it is outside this claim's file surface. Whether it wants a prose correction is the seat's call.packages/plugin-detail/README.mddocuments no input list fordetail-section(its one node example sits insidetabs[].contentand writesfields), so no doc line became false. None added, to stay inside the claimed file surface.Dispatched by the
domain:uiseat #1; sessionhttps://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC.Generated by Claude Code