Skip to content

docs(spec): record the live read point of page:accordion items[].icon (#9881) - #9971

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-9881-accordion-icon-liveness
Aug 19, 2026
Merged

docs(spec): record the live read point of page:accordion items[].icon (#9881)#9971
os-elon merged 1 commit into
mainfrom
claude/issue-9881-accordion-icon-liveness

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#9881

PageAccordionProps.items[].icon parsed, rendered, and said nothing about itself. A liveness sweep therefore read it as declared-but-unenforced and opened a retirement candidate against it — which cost a full dispatch cycle before the cross-repo read point was found and the candidate was closed premise-overtaken. Nothing on the spec side recorded that liveness, so the next sweep would have derived the same false candidate from the same absence.

This PR records it: a .describe() naming the consumer, and an accept-pin that keeps it named.

Premise re-verification — the read point is still live

Re-derived from the objectui pin this repo actually builds against rather than taken from the card. .objectui-sha = 82a94170c4058d451ce3ac179d99296d90554479, and the anchors were read at that ref (git show 82a94170c:…), not at the sibling checkout's HEAD, which has since moved to 7260a1ed4:

AnchorAt the pinVerdict
packages/components/src/renderers/layout/containers.tsx:851-853guarded on item.icon, renders a LazyIcon element whose name prop is item.icon (classes h-3.5 w-3.5 shrink-0 opacity-70, aria-hidden), inside the AccordionTriggerlive reader
containers.tsx:898ComponentRegistry.register('accordion', …)'s items input, described as [{ label, icon?, collapsed?, children }]key published to the Studio block designer

Both anchors in the card resolve exactly, :898 included. The vocabulary is Lucide, resolved through objectui's LazyIcon (lib/lazy-icon.tsx normalises kebab-case or PascalCase and falls back when the name is not a real Lucide icon).

The describe

Placed in the file's house idiom rather than the card's literal wording. Nothing in component.zod.ts puts a containers.tsx file:line inside a .describe() — every one of the ~35 describes that mentions a renderer does so behaviourally, and the file:line anchors live in the TSDoc block above the key (variant, recordChrome, showStar, showCopyId all take that shape). So this follows the same split, with record:alert's own icon as the nearest sibling for the "this one IS read, contrast the one that isn't" construction:

'Lucide icon name rendered in the panel trigger, left of the label. Read on this
component — the renderer draws it via `LazyIcon`; contrast the item `value`
beside it, which the renderer overwrites with `panel-` plus the item index.'

(In the source the final token is written the way the existing value prescription writes it — panel- followed by the word index in angle brackets. It is spelled out in words here, and only here, so the body sanitizer cannot swallow it; the committed string is the ordinary one.)

The docblock above it carries the anchors, the pin, and why the key has a docblock at all. The item value prescribed against one line up is the deliberate contrast: same renderer, same item, but it overwrites that key with an index-derived panel id — a read point is precisely what separates the two verdicts, and the two now sit next to each other saying so.

The accept-pin

Three assertions in component.test.ts, beside the existing page:accordion.variant block, with the objectui consumer named in the block's prose:

  1. the key parses on a page:accordion item and survives to the parsed output;
  2. an undeclared sibling (iconName) on the same item is still refused with unrecognized_keys — without this the first assertion would be equally green on a schema that had stopped being strict, which is the vacuity an accept-pin exists to exclude;
  3. the .describe() still names the consumer. Deleting the describe is what re-opens the false candidate, so it is pinned rather than left to review.

Reverse-verified from the committed state, both legs, with the tree proven byte-identical to HEAD afterwards (git hash-object == git rev-parse HEAD: on the same path, porcelain empty). No rebuild was needed or performed for either leg, and that is a property of this pair rather than an omission: the test imports ./component.zodrelative, inside its own package, so vitest resolves it to src/ and never to dist/ — demonstrated by the pin passing on the very first run, which happened before any build existed in this worktree.

AblationPredictedObserved
remove the .describe()the describe pin reds, alone1 failed | 187 passed — only assertion 3
remove the icon key entirely (what the sweep proposed)accept pin and describe pin red2 failed | 186 passed — assertions 1 and 3

Regeneration

pnpm --filter @objectstack/spec build && check:generated after the change: all 13 generated artifacts already up to date, so there is no regen commit and none was hand-written.

That is a measured result rather than a skipped step, and it is worth stating because the standing rule is that a .describe() moves content/docs/references/. It does — for top-level props. This key is one level down, and the docs generator renders a nested item object as a type signature in the parent row rather than as its own table: PageAccordionProps.items prints as { label: …; icon?: string; collapsed: boolean; children: any[] }[] with an empty Description cell, while its siblings allowMultiple and variant carry their describes. So the string genuinely reaches no checked-in artifact, check:docs is correctly green, and the describe's audience here is the source reader and the auditor, not the published reference.

Verification

Gates re-derived on the actual diff with node scripts/pm/dispatch-gates.mjs (no paths passed — it takes its own change set from the merge base), which placed 18 path-matched families plus 5 convention-triggered ones. The whole union was run at the final commit, 3ae2b59, and every one is green:

  • spec suitepnpm --filter @objectstack/spec test: Test Files 414 passed (414) / Tests 11022 passed (11022)
  • spec typechecktsc --noEmit + check:scripts-typecheck + check:test-typecheck; the new test file compiles under tsconfig.test.json with the debt ledger held, not grown: 55 file(s) / 263 error(s), no GREW verdict
  • spec generated artifactscheck:generated: All 13 generated artifacts are up to date
  • spec liveness familycheck:liveness, check:empty-state, check:strictness-ledger, check:variant-docs
  • changeset familycheck:changeset-gate-self-tests, check:objectui-changeset, check-adr-0087-registration, check-changeset-no-major, check-empty-changeset
  • repo gatescheck:cross-package-test-inputs, check:merge-driver, check:slot-lookup, check:spec-parsed-alias, check:type-source-resolution, check:dev-prereqs, check:doc-formula-expressions, check-affected-docs, check:nul-bytes
  • convention-triggered by the new test filecheck:query-options-erasure, check:type-check-coverage, check:engine-double-contract, check:where-matcher, and the ratchet half check:type-check-debt --re-measure: 33 ledger entr(ies) re-measured in 205.4s, 1925 raw tsc error(s) total, none above its recorded number

check:react-declaration-parity is the one gate that cannot run here at all — it needs objectui's sdui.manifest.json, which only a pin bump produces. Nothing in this diff moves the pin.

Scope

No acceptance change. The key was already declared and already optional; nothing is widened, narrowed, retired or renamed. Clause ② does not apply.


Generated by Claude Code

…on (#9881)
A `.describe()` naming the objectui consumer plus an accept-pin, so a liveness
sweep stops re-deriving a false retirement candidate for a key that renders.
Re-verified at the pin this repo builds against (`.objectui-sha` = 82a94170c):
`containers.tsx:851-853` renders `{item.icon && <LazyIcon name={item.icon} …/>}`
inside the AccordionTrigger, and `:898` publishes the key to the Studio block
designer in the `items` input.
Nothing about what parses changes — the key was already declared and optional.
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see

Coarse fallback — 115 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6aceca97143088c9c33e568c5dd49e5b7cfdf347packageMentionDocs.

Which tree this was computed on

This run read content/docs from fa2d3221cefdddb5c536a48d34be416e53e10942 — the merge of head 3ae2b5916ca0968e425f88e9fff69ab9f4b5feea into base 6aceca97143088c9c33e568c5dd49e5b7cfdf347, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fa2d3221cefdddb5c536a48d34be416e53e10942 && git checkout fa2d3221cefdddb5c536a48d34be416e53e10942
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6aceca97143088c9c33e568c5dd49e5b7cfdf347 3ae2b5916ca0968e425f88e9fff69ab9f4b5feea && git checkout -B drift-repro 6aceca97143088c9c33e568c5dd49e5b7cfdf347 && git merge --no-ff 3ae2b5916ca0968e425f88e9fff69ab9f4b5feea
node scripts/docs-audit/affected-docs.mjs --json 6aceca97143088c9c33e568c5dd49e5b7cfdf347

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

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

Labels

documentationImprovements or additions to documentationprotocol:uisize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] page:accordionitems[].icon has no .describe() naming its read point and no accept-pin — the next liveness audit will re-flag a live key

2 participants

@os-elon@claude