Repository navigation
finding(types): AlertDialogSchema declares cancelLabel / confirmLabel / confirmVariant and nothing reads any of them — retire, or give confirmVariant a read site #7963
Description
Found while converting the four alert-dialog catalog fixtures to the read dialect (objectui#7693, PR objectui#7962). Filed rather than folded in: retiring or animating an authorable key is a packages/types plus packages/components change, outside that card's examples/** fence, and the triage comment on it (5548892311) named the fork and explicitly declined to open this card itself.
Measured on origin/main d39ede24e, after objectui#7104 (PR objectui#7700) and objectui#7768 had both landed.
The measurement
AlertDialogSchema declares three footer keys that nothing reads:
| key | TS declaration | zod mirror | read sites |
|---|---|---|---|
cancelLabel |
packages/types/src/overlay.ts:145 |
packages/types/src/zod/overlay.zod.ts:60 |
0 |
confirmLabel |
packages/types/src/overlay.ts:150 |
packages/types/src/zod/overlay.zod.ts:61 |
0 |
confirmVariant |
packages/types/src/overlay.ts:155 |
packages/types/src/zod/overlay.zod.ts:62 |
0 |
The renderer, packages/components/src/renderers/overlay/alert-dialog.tsx, draws its footer from two other keys only: :37 renders AlertDialogCancel when schema.cancelText is truthy, :38 renders AlertDialogAction when schema.actionText is truthy. It registers cancelText and actionText in its inputs and defaultProps and names none of the three above.
Positive control for the null reading, same file and same search: schema.cancelText returns 1 hit and schema.actionText returns 1 hit, so a zero here is a reading and not a mis-anchored search.
The JSDoc that objectui#7104 added already says as much in words — cancelText's block calls cancelLabel "the declared twin nothing reads", and actionText's says the same of confirmLabel. What #7104 did not do is act on it: the three keys are still declared, still accepted, and still describable to an author or an AI as part of the contract.
Why it is worth a card
Both halves of the harm are live, and they point in opposite directions:
- An author who writes
confirmVariant: 'destructive'gets a green parse and no destructive button. That is the declared-but-unenforced shape ADR-0049 exists to close. It is the sharper edge for AI-authored metadata, which reads the declaration and has no way to discover that the runtime does not honour it. - The read dialect genuinely cannot express a footer button variant. PR objectui#7962 had to drop three authored variants (
destructivetwice,ghostonce) from the catalog fixtures for exactly this reason, on the triage's default route: an example that renders no button is strictly worse than one that renders a button in the wrong colour.
So the two routes are not symmetric and this is a decision, not a chore:
- Retire all three (ADR-0049 enforce-or-remove, the startup-stage default: immediate retirement, no staged alias window). Cheapest, and it makes the declared face honest. Cost: the catalog's destructive-confirm styling stays unexpressible, and the documented remedy for "I want a red confirm button" becomes "you cannot".
- Animate
confirmVariant(give it a read site in the renderer, and retire the two*Labeltwins as plain duplicates ofcancelText/actionText). Restores what the fixtures lost. Cost: a Feature on the manual floor, widening the authorable surface — which the startup-stage focus principle says to default against absent a pulling need.
No recommendation offered here; both need a maintainer ruling on whether a footer button variant is a capability this project wants at all.
Not a duplicate of
- objectui#7693 — the fixture side, now handled; it deliberately left the declared face alone.
- objectui#7104 — the declaration half; it is what documented these three as inert, and it is where the wording above comes from.
- objectui#6033 — zod-mirror DRIFT between the TS and zod faces. These three do not drift: both faces declare them identically. The defect is that neither face is read.
Filed by the Claude Code dev seat working objectui#7693, session session_013uAaxiwgYDybsTNV9xwa1M.
Generated by Claude Code
Activity
os-warren commentedon Sep 9, 2026
os-dev-report
{ "issue": 7963, "status": "done", "branch": "claude/issue-7963-alert-dialog-footer-keys-probe", "pr": "https://github.com/objectstack-ai/objectui/pull/8736", "premise_still_valid": true, "summary": "STEP 3 ONLY, as dispatched. Steps 1 and 2 re-derived on branch head 89f1bcae, not inherited: the SchemaRenderer strip destructure has MOVED to lines 1451-1481 (the card cited 1455-1481) and its 16 keys still exclude all three; alert-dialog.tsx:25-26 still destructures rest-props and forwards them onto the AlertDialog primitive, so the screening test HITS. Step 3 run: one new file, packages/components/src/__tests__/alert-dialog-footer-keys-liveness-7963.test.tsx, renders the same node through the real SchemaRenderer and the real registry, varies ONE key per fixture and diffs the DOM. Verdict: NO key is live. cancelLabel, confirmLabel and confirmVariant each leave the normalised dialog HTML byte-identical, leave the footer labels at ['Keep it','Delete'], leave the confirm button class at bg-primary/text-primary-foreground (never bg-destructive), and reach no DOM node as an attribute. Mechanism, measured not argued: the keys DO reach AlertDialogPrimitive.Root, which forwards to DialogPrimitive.Root, which destructures only __scopeDialog/children/open/defaultOpen/onOpenChange/modal and renders a context PROVIDER, not an element, so unknown props die before any DOM exists. Independently AlertDialogAction hard-codes cn(buttonVariants(), className) and AlertDialogCancel hard-codes the outline variant, so confirmVariant has no seam even in principle. The card's headline consequence is also pinned as a DOM reading: a document written strictly against the declared keys mounts the dialog with an EMPTY footer div. NOTHING was retired, no renderer/types/zod file was touched, confirmVariant was NOT animated, and the maintainer ruling the card asks for is untouched and unanswered - this measurement is its input. The card must survive the merge, so the PR body opens 'Part of #7963' and no closing keyword appears anywhere near a card number. Draft PR only: not enqueued, auto-merge not armed, not flipped ready, no needs:contract-review label touched. Clause-2 no, confirmed by scripts/check-governed-queue-guard.mjs --test: NOT GOVERNED. Claim inherited and verified (comment 5594503577 names this session and this branch); no second claim posted, assignee never written.", "tests": "All exit codes captured BEFORE any pipe; all heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=dev-7963 (one call waited 205s, one 104s, VERDICT lines read, never a bare $?). GATES on 89f1bcae, all from the REPO ROOT per objectui#3378: [probe] pnpm exec vitest run packages/components/src/__tests__/alert-dialog-footer-keys-liveness-7963.test.tsx = exit 0, 'Test Files 1 passed (1) / Tests 10 passed (10)'. [neighbours] the same runner over disabled-verdict-one-carrier.test.tsx + examples/schema-catalog/test/alert-dialog-footer-read-dialect-7693.test.tsx + packages/types/src/__tests__/alert-dialog-read-dialect-7104.test.ts = exit 0, 'Test Files 3 passed (3) / Tests 80 passed (80)'. [type-check] pnpm --filter @object-ui/components type-check = exit 0. [lint] pnpm --filter @object-ui/components lint = exit 0, '935 problems (0 errors, 935 warnings)', all pre-existing, 0 attributed to the new file (grep -c of the filename in the lint log = 0). [changeset] node scripts/check-changeset-presence.mjs = exit 0. [control bytes] pnpm check:control-bytes = exit 0, 'scanned 6922 tracked text file(s); skipped 85 binary'; plus a manual self-scan grep -naP over both new files = no hits. [pnpm check] node packages/cli/dist/cli.js check = exit 0, 'All checks passed'. PRECONDITION NOT MET, twice, reported as such and NOT as reds: type-check first exited 2 with hundreds of TS2307 'Cannot find module @object-ui/...' because a fresh worktree has no built dist/*.d.ts - fixed by pnpm --workspace-concurrency=2 --filter '@object-ui/components^...' build (exit 0), after which type-check is 0; and pnpm check needs packages/cli/dist/cli.js, so pnpm --filter @object-ui/cli build ran first (exit 0). CONTROLS, all five green, each closing one way this file could be green while measuring nothing: WIRED (cancelText/actionText draw two footer buttons in renderer order - the fixture, registry and renderer are wired and a footer button is visible to this harness); IDEMPOTENT (the same node rendered twice reads identical - Radix mints fresh useId values per mount, so without the id normaliser EVERY key would read 'live'); SENSITIVE (a className difference DOES move the normalised HTML and the marker appears in it - the normaliser is not blunt enough to erase a real in-dialog attribute change); CHANNEL, the positive control (open, which the renderer likewise never reads as schema.open, rides the SAME rest-spread onto the SAME primitive and moves the DOM from no-dialog to dialog-mounted-with-both-buttons - valid precisely because it is the objectui#8236 key: unstripped, never named, consumed by the primitive itself, exercising SchemaRenderer to componentProps to the renderer rest-spread to the primitive end to end); VARIANT_INSTRUMENT (inside ONE dialog the cancel button class already differs from the action button class, so the confirmVariant row is not vacuous). ABLATION - one-off, not committed, proving the three rows would CATCH a live key: the renderer was mutated on disk to make all three live (schema.cancelLabel ?? schema.cancelText, schema.confirmLabel ?? schema.actionText, and a confirmVariant-driven className). On-disk proof BEFORE reading any result: blob 77968a92c4eab6b17862f7ca8e82b40565bcd18a became 04ffd668a0dcdfbf256447ef064a8a7372b4abf3, and each of the three injected anchors grep -c = 1 on the mutated file. Result: 'Tests 4 failed | 6 passed (10)' - the three key rows and the attribute row RED, all five controls still green. Restore leg: trap '<restore>' EXIT INT TERM with an absolute REPO_ROOT, git checkout HEAD -- packages/components/src/renderers/overlay/alert-dialog.tsx (never a bare checkout), proven by blob hash back to 77968a92... AND git diff HEAD name-only empty - not by an exit code. No build/dist step was needed for the ablation because the probe imports the renderer relatively, i.e. from source. Working tree clean afterwards; the scratch dump test was deleted before the commit.", "mcp_calls": "2 - one create_pull_request, one add_issue_comment. Every read went through repo-scoped REST (probed first: HTTP 200) or plain git; no search_issues, no list_issues, no wide scan.", "not_measured": [ "CI on PR #8736 - NOT awaited. The report is due at draft-PR time; every gate above is a LOCAL reading on 89f1bcae. No sleep, no polling, no background watcher was used.", "Repo-wide `pnpm lint` / `pnpm test` / `turbo run type-check` across the other 40-odd packages - not run. The diff is two files in one package plus one .changeset entry, and no other package's source moved; CI owns the farm.", "`Build Docs`, `check:node-esm-load`, `check:published-dist` - not run. The dispatch names these three as NOT per-PR gates, so for this PR they are measured here or nowhere, and they are not measured here. Everything else in the gate table IS per-PR and IS measured.", "The built-artifact lane (`pnpm --filter @object-ui/components test:dist`, OBJECTUI_DIST_PINS=1) - not run. The probe imports the renderer through a relative path, i.e. from source, so the dist lane is not on its path; the ablation therefore needed no build and could not be a false green from a stale dist.", "A REAL browser. The reading is DOM-level under happy-dom + Radix + RTL, not Chromium. The mechanism measured (an unknown prop dying on a context provider that renders no element) is engine-independent, but no browser confirmed it.", "Whether any OTHER package reads these three key names off an ALERT-DIALOG node is a GREP result, not a DOM reading, and is flagged as such. A repo-wide grep finds the same three spellings only on different carriers - FormSchema.cancelLabel, objectql.ts confirmLabel, plugin-designer/ConfirmDialog.tsx React props, plugin-form/ModalForm.tsx - none of them an alert-dialog node. That is exactly the class of evidence this dispatch exists to distrust, so it is reported as unmeasured rather than as a second verdict.", "The keys' behaviour when authored INSIDE the `props` / `properties` bag rather than at the top level of the node - not probed. The bag takes a different route (propsWithoutCanonicalKeys, spread after componentProps) and lands on the same primitive, so the same provider drop is expected, but it was not rendered.", "The one-off ablation is a proof run, not a committed artifact: the mutation and the scratch dump test were both removed, and no ablation file is in the PR." ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: AlertDialogSchema.open (packages/types/src/overlay.ts:126) is LIVE, measured here as the CHANNEL control - it mounts the dialog through the rest-spread with no schema.open read anywhere in the renderer. It is declared with no READ SITE note, unlike its objectui#7104 neighbours, and the objectui#7104 pin does not cover it. Not a defect (the key works), so no card; recorded loudly because any future sweep that calls AlertDialogSchema.open unread is wrong, exactly as objectui#8236 was. Carrier: whoever writes the ruling/retirement PR for this card, which re-derives these same lines.", "noted, not filed: node scripts/check-changeset-presence.mjs reads only TRACKED changes, so a brand-new UNTRACKED file under a released package's src/ makes the local self-check print '0 file(s) changed' and exit 0. Measured here on this very file: exit 0 while untracked, exit 1 after git add -N, exit 0 again once the changeset was added. AGENTS.md prescribes that local self-check by name, so the documented local workflow has a false green in it. Not filed: the ENFORCING path is CI, which always judges committed files and is correct; carrier - any agent that adds a new source file and runs the documented local self-check before committing.", "noted, not filed: the card's citation of SchemaRenderer's strip list as :1455-1481 has drifted; the destructure now spans 1451-1481. Same 16 keys, same conclusion. Recorded only so the next seat does not read a stale line number as a changed mechanism." ] }Generated by Claude Code
os-warren commentedon Sep 9, 2026
Release:sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-7963-alert-dialog-footer-keys-probe· PR #8736 MERGEDpm:dispatched→pm:queue, assignee cleared, 一笔标签写入。⛔ 卡不关(PR 开的是Part of)。⛔ 更正我自己的处置计划 —— 这张卡不进决策箱
我在派发时说「你回来后本卡进决策箱,裁决未答」。那是错的,而纠正它的是一条我派发时还不知道的裁决:
objectstack#17031,维护者原话(答批 #104 第 4 张,针对 objectui#8318):
裁定文本里点名:「键本身该不该存在归 ADR-0049 liveness 工作簿(objectui#4631、#7963),不归决策卡。」
⇒ ⭐ 本卡被裁决按名字路由进了 ADR-0049 存活工作簿,⛔ 不是决策箱。 我把它放回
pm:queue,等工作簿处理,⛔ 不立needs-user-decision。探针结论(PR #8736 落地的东西)
三个键 全部不活。
cancelLabel·confirmLabel·confirmVariant各自留下逐字节相同的对话框 HTML,页脚标签停在['Keep it','Delete'],确认按钮的 class 停在bg-primary/text-primary-foreground(从不变成bg-destructive),且没有任何一个到达 DOM 成为属性。⭐ 机制是测出来的,不是论证出来的:三个键确实到达
AlertDialogPrimitive.Root,它转发给DialogPrimitive.Root,而后者只解构__scopeDialog/children/open/defaultOpen/onOpenChange/modal并渲染一个 context PROVIDER 而不是元素 ⇒ 未知 prop 在任何 DOM 存在之前就死了。另外AlertDialogAction硬编码cn(buttonVariants(), className)、AlertDialogCancel硬编码 outline —— ⇒confirmVariant在原理上就没有接缝。⭐ 五个对照,每一个都堵死一种「绿着却什么都没测」
WIRED(cancelText/actionText画出两个按钮,证明 fixture、注册表、渲染器是通的)·IDEMPOTENT(同节点渲染两次一致 —— Radix 每次 mount 铸新useId,⛔ 没有这个归一化器每个键都会读成「活」)·SENSITIVE(一处 className 差异确实会移动归一化后的 HTML)·CHANNEL(正对照用的是 #8236 那个open键 —— 同样从不被schema.open读到、同样搭同一条 rest-spread 到同一个原语,而它把 DOM 从「无对话框」变成「对话框已挂载且两个按钮都在」)·VARIANT_INSTRUMENT(同一个对话框内 cancel 与 action 的 class 本来就不同,所以confirmVariant那一行不是空判)。⭐ 消融:把渲染器改成让三个键都变活 ⇒
4 failed | 6 passed,三个键行加属性行全红,五个对照仍绿;恢复由 blob hash 与git diff HEAD空双证。⛔ 仍然没有答的那一半
confirmVariant一个读点(那会恢复 PR #7962 从目录 fixture 里被迫删掉的三个 variant)。⭐ 那个问题现在有了归属(ADR-0049 存活工作簿)和一份完整的证据(本卡的 DOM 读数),⛔ 但没有答案。AlertDialogSchema.open是活的——它就是上面那个CHANNEL对照,经 rest-spread 挂载对话框而渲染器里没有任何schema.open读点。它声明时没有 READ SITE 注记(与它 objectui#7104 的邻居不同),且 #7104 的 pin 覆盖不到它。⛔ 不是缺陷(键是work的),记在这里是因为任何未来把AlertDialogSchema.open当作无人读的清扫都是错的,与 #8236 同型。Generated by Claude Code
os-warren commentedon Sep 10, 2026
⭐ RULING: retire all three. Maintainer, 2026-09-10.
cancelLabel,confirmLabelandconfirmVariantretire fromAlertDialogSchema— both faces, ADR-0049 enforce-or-remove.cancelText/actionTextare the surviving spellings. ⛔ The direction is not re-opened by the implementing seat.Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w(PM seatdomain:spec, dispatchingos-dev) · branchclaude/issue-7963-alert-dialog-footer-keys-retireClause-②: yes — this narrows a published accept set on
@object-ui/types.pm:queue→pm:dispatched, assignee set. Theos-devinherits both; ⛔ it posts no secondClaim:and ⛔ never writes the assignee.The measurement the ruling was taken on — re-derived, ⛔ not quoted from this card
This seat does not put a card's own figure into a decision request. Re-measured on
origin/main452f1f779in a fresh clone:schema.<key>readscancelLabeltypes/src/overlay.ts:151+zod/overlay.zod.ts:62confirmLabeloverlay.ts:156+overlay.zod.ts:63confirmVariantoverlay.ts:161+overlay.zod.ts:64FIRING CONTROLS, same instrument, and they fire on the very renderer under test —
packages/components/src/renderers/overlay/alert-dialog.tsx:schema.cancelText= 15 (read at:37),schema.actionText= 5 (read at:38). ⇒ The three zeros are readings of the same file the controls light up, ⛔ not a probe that failed to run.⭐ The rest-spread near-miss, closed by measurement rather than by reasoning
The three keys do reach the primitive: they are not on
SchemaRenderer's strip list (packages/react/src/SchemaRenderer.tsx:1452-1489), so they ridecomponentProps→ the renderer's...props→<AlertDialog … {...props}>at:26. That is the same channel that makes another key on another card live, so "declared and unread" was not safe to conclude from the grep alone.It is dead here for a measured reason:
packages/components/src/__tests__/alert-dialog-footer-keys-liveness-7963.test.tsx:263-283reads the wholedocument.bodyand asserts it does not contain any of the three, against a live control on the same string. Its own comment names the mechanism — theAlertDialogroot "renders a context provider, not an element, so an unknown prop is dropped without reaching any node." And:288-317renders a node written strictly against the declared keys and asserts the footer is empty, against theWIREDcontrol at:174-180wherecancelText/actionTextdraw both buttons.AlertDialogSchema:FormSchema.cancelLabel(types/src/form.ts:1243, read atform.tsx:1063,3266),objectql.ts:508'sconfirmLabel,plugin-designer/src/components/ConfirmDialog.tsx:26,28(React props, not schema),plugin-grid/src/components/BulkActionDialog.tsx:552(def.confirmLabeloff a bulk-action def), andplugin-form'sModalForm.tsx:579/DrawerForm.tsx:528, which build a local namedcancelLabelfromschema.cancelText— the opposite direction.What to land
⛔ A named refusal, not a bare deletion.
BaseSchemacloses with[key: string]: anyandBaseSchemaCoreends.passthrough(), so a dropped member key is kept, not refused — deleting the declarations alone would leave the silent accept exactly as it is and lose the diagnostic. Use the established mechanism:retirementTombstone()inzod/tombstone.zod.tson the zod face,?: neveron the TS face, each namingcancelText/actionTextas the remedy. Followpage-actions-refusal-7926/page-breadcrumbs-refusal-8871as the shape.confirmVarianthas no surviving twin —cancelText/actionTextare labels, not a variant. Its refusal message must say so plainly rather than pointing at a key that does not do the same job. ⛔ Do not invent a replacement; the ruling retires it.Also:
packages/types/src/__tests__/alert-dialog-read-dialect-7104.test.ts:95carriesconst INERT_DECLARED = ['cancelLabel', 'confirmLabel', 'confirmVariant']— that list is about to become wrong in its own terms (they will be refused, not merely inert). Read it end to end and re-point it; ⛔ do not delete it.Ablation per retired key: prove the refusal fires and that the surviving spelling still draws. Prove every mutation on disk before reading any result (anchor counts on injected and removed text, plus a
git hash-objectdiffering from the HEAD blob), restore from atrap … EXIT INT TERM, and prove restoration by blob hash — ⛔ never by an exit code.A changeset at
minor(AGENTS.md:240— a breaking change in this repo isminor; precedent objectui#5905, which literally carriesClause-②: yes). ⛔ Never editcontent/docs/releases/. BareFixes #7963— ⛔ not the cross-repoowner/repo#Nform, which makes the landing pre-check exit 2 = UNJUDGED.Standing rules
⛔ Worktree-first —⚠️
/home/user/objectuiis shallow with a staleorigin/main; ⛔ nevergit fetch/git checkoutthere and never cut a worktree from it. Clone fresh. ⛔ Nevergit stash. ⛔ Never kill by name. ⛔ Never mutatenode_modules. ⭐pnpm --filter <touched package> lint→ exit 0, and read the output for the literal worderror, ⛔ not merely a zero exit. ⛔ PR stays draft — the PM lands it.Generated by Claude Code
os-warren commentedon Sep 10, 2026
os-dev-report
{ "issue": 7963, "status": "done", "branch": "claude/issue-7963-alert-dialog-footer-keys-retire", "pr": "https://github.com/objectstack-ai/objectui/pull/8967", "premise_still_valid": true, "summary": "Re-derived the measurement on my own base 72bcd7783 (NOT the card's 452f1f779) and it holds: point-access probe schema.cancelLabel / schema.confirmLabel / schema.confirmVariant = 0 / 0 / 0 tree-wide, against FIRING CONTROLS on the very renderer under test — schema.cancelText = 15 (read at alert-dialog.tsx:37) and schema.actionText = 5 (read at :38). Declaration line numbers matched the card exactly (overlay.ts:151/156/161, overlay.zod.ts:62/63/64). Landed a NAMED REFUSAL on both faces rather than a bare deletion: retirementTombstone() on the Zod face and '?: never' on the TS face, since BaseSchemaCore ends .passthrough() and BaseSchema closes with an any-valued index signature, so a dropped member key is KEPT, not refused. confirmVariant's message says plainly it has NO surviving spelling and names cancelText / actionText only to rule them OUT; no replacement was invented. Both existing pins re-pointed, neither deleted. Changeset at minor, both packages named. Draft PR #8967 with the bare 'Fixes #7963'; the Clause-② gate needs:contract-review is hung on BOTH carriers (card + PR) and read back on each.", "tests": "ALL at 415803b7a, every exit code captured into a file BEFORE any pipe. SUITES: 'vitest run --root . packages/types/' exit 0 — 167 files / 3303 tests passed; 'pnpm --filter @object-ui/components test' exit 0 — 259 files / 2360 tests passed. TYPE-CHECK: 'pnpm --filter @object-ui/types --filter @object-ui/components type-check' exit 0; the log echoes 'tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json' and 'tsc --noEmit && tsc -p tsconfig.test.json', so the filter matched real scripts and 'zero scripts, exit 0' cannot read as a pass. LINT (read for the literal word, not for the exit code): 'pnpm --filter @object-ui/types --filter @object-ui/components lint' exit 0, log echoes 'eslint .' for both; eslint's own verdict lines are '278 problems (0 errors, 278 warnings)' for types and '948 problems (0 errors, 948 warnings)' for components; a column-anchored scan for error-SEVERITY rows over the whole log returns ZERO, and every 'error' string in it sits inside a warning message body or a filename; my five files linted by name produce no output at all. CHANGESET GATES: check-changeset-fixed exit 0, check-changeset-no-major exit 0, check-changeset-presence exit 0 ('5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'). ALSO: check-control-bytes exit 0 (7164 files) plus a manual control-character scan of my six files (clean); check:spec-symbols, check:handler-key-reads, check:doc-types exit 0. NOT MEASURED then re-measured: check:doc-snippets, check:doc-examples and check:sdui-registration-pins first returned exit 2 = PREREQUISITE NOT MET ('run the build first' / 'this is exit 2, not a pass'), so I built the tree — 'turbo run build --filter=!@object-ui/site --concurrency=2' exit 0, 43/43 tasks — and re-ran all three green; doc-snippets reports 638/638 blocks compiling against the BUILT types, which is the downstream consumer reading a published-type narrowing owes. ABLATIONS — five legs, directions PREDICTED in the harness header before the first leg ran, all five matched; every mutation proved on disk BEFORE any result was read (grep -c on injected AND removed anchors, plus a git hash-object differing from the HEAD blob), restored by 'git checkout HEAD -- ABSOLUTE_PATH' from a 'trap ... EXIT INT TERM', restoration proved BY BLOB HASH plus an empty 'git diff HEAD', never by an exit code; an empty hash was coded as FAILURE, not as 'nothing to compare'. LEG cancelLabel (delete the Zod tombstone arm): HEAD blob 10bb1d06141f82d1aa9d2460443e4baa8c92f83e, mutated a9bbee6ba273944a31c78fe9a92aae6d412eb313, refusal pin exit 1, 4 failed / 21 passed, restored to 10bb1d06. LEG confirmLabel: same HEAD blob, mutated 1bf35c50c2a4136ed737d00de1129875a6142d82, exit 1, 4 failed / 21 passed, restored. LEG confirmVariant: same HEAD blob, mutated 78a447126946819175a6d0413234d428b8da9f17, exit 1, 4 failed / 21 passed, restored. THE READING THOSE THREE BUY: with the arm deleted, AlertDialogZod.shape loses the key (34 members, no cancelLabel) and the retired document's issue paths collapse to ['confirmLabel','confirmVariant'] — the key is SILENTLY ACCEPTED again, which is exactly the accept a bare deletion would have shipped, so the legs prove the mechanism and not merely the pin. LEG TS face ('cancelLabel?: never' back to '?: string'): HEAD blob 69b03a6ef2d59dad84dae9730e192c580ab6eddc, mutated 86082837ef9fe3ce2a08b28ff7f00d96569df4f9, packages/types type-check exit 2 with TS2578 'Unused @ts-expect-error directive' in BOTH pin files plus a third instrument firing unbidden — zod-mirror-parity.test.ts TS2322, the face pair drifting — restored. LEG renderer (read the RETIRED dialect instead of the surviving one, i.e. the direction the ruling refused): HEAD blob 77968a92c4eab6b17862f7ca8e82b40565bcd18a, mutated 5d4732859c798b6cd630dacb595b2ddeb37c6ac6, three pins exit 1 with 13 failed / 70 passed including the WIRED, CHANNEL and VARIANT_INSTRUMENT controls, restored. Final tree state after the harness: 'git status --porcelain' empty, CLEAN. NOTE ON WHAT THE ABLATION READS: vitest.config.mts aliases '@object-ui/types/zod' and '@object-ui/components' to src, so a source mutation is what the tests consume — no dist rebuild stands between the mutation and the reader, and the legs turning red is itself the proof the mutation reached it.", "mcp_calls": "2 — create_pull_request and this comment; every read (claim comment, labels, PR body read-back) went through repo-scoped REST after a probe returned 200", "open_questions": [ { "question": "PR-body attribution footer: the session's harness reminder prescribes the '🤖 Generated with [Claude Code]' + bare session-URL block, while the os-dev contract prescribes the rule-line + '_Generated by [Claude Code](.../session_ID)_' block. I sent the harness form; GitHub did not recognise it as its canonical footer and APPENDED its own, so PR #8967 now carries two attribution blocks (both naming the same session).", "options": [ "A — leave both. The contract forbids re-posting footers in a loop, and an edit risks the platform re-appending, which is that loop's shape.", "B — one dedup edit removing the harness-form block, keeping the platform's canonical one." ], "recommendation": "A, and I took it. Both blocks carry the same session URL so nothing is lost, and the contract's ⛔ on footer re-posting is aimed exactly at the edit-then-reappend cycle B invites. Flagging it because the two instructions genuinely disagree on the wire format and the next seat will hit the same fork." } ], "out_of_scope_findings": [ "noted, not filed: objectui#8871's tree-scoped point-access probe ('nothing READS .breadcrumbs') fires on a PROSE mention in any file outside its named exclusion list — it caught one in this PR's own new docblock, which cited the precedent with that key spelled after a dot. Fixed in place by spelling both refusal arms without the leading dot and leaving a note beside them saying why. Not filed: the tree-scoped shape is deliberate and correct (a file-scoped check misses the reader nobody thought of) and the cost is one line of prose per citing card. Successor who meets it: the next retirement card that cites that precedent in a docblock — a real successor, since these refusal cards routinely cite one another." ] }Generated by Claude Code
os-warren commentedon Sep 10, 2026
Ruling provenance — retire all three; batch #70's
confirmVariantrow is supersededRecorded by the PM seat (
domain:spec@ objectui, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, GitHubos-warren), 2026-09-10, to answer R1 of the contract review at5618268876. That review was right to refuse a relayed ruling that existed nowhere on GitHub. This comment is the record it asked for.The ruling, verbatim
The maintainer answered in the dispatch session's direct channel, not on GitHub — I am the relay, and I am naming myself as such rather than presenting this as a GitHub-native ruling.
Asked which route to take on this card, the maintainer selected:
Asked again today — after being shown batch #70's contrary row and the probe that falsified its premise (the exchange is summarised below) — the maintainer confirmed:
⇒
cancelLabel,confirmLabelandconfirmVariantretire fromAlertDialogSchema, both faces, ADR-0049 enforce-or-remove.cancelText/actionTextare the surviving spellings.⭐ What was in front of the maintainer when they confirmed
This matters, because R1's real question is whether the supersession is informed or accidental. It is informed. Both of these were put to them, in these terms:
5565626809, 2026-09-07, director seat, maintainer verbatim 「同意」: wireconfirmVariant; retirecancelLabelandconfirmLabel. Reason recorded there: "a footer button variant (the red destructive confirm) is a capability this product wants; our own component catalog authored it three times and had to drop it."5595380224, 2026-09-09, landed by PR test(components): measure the three declared alert-dialog footer keys through the DOM (objectui#7963 step 3) #8736: all three keys measured dead. AuthoringconfirmVariantleaves the dialog HTML byte-identical, leaves the confirm button's class atbg-primary/text-primary-foreground(neverbg-destructive), and the value reaches no DOM node. Mechanism measured, not argued:AlertDialogPrimitive.Root→DialogPrimitive.Root, which destructures only__scopeDialog / children / open / defaultOpen / onOpenChange / modaland renders a context provider, not an element ⇒ an unknown prop dies before any DOM exists.Why batch #70's row is superseded rather than overruled
⛔ Not "the newer ruling wins because it is newer." Batch #70's premise was falsified by evidence that did not exist when it was made. It instructed the seat to wire
confirmVariant— an instruction that presupposes a key capable of carrying the value. The 2026-09-09 probe measured that it is not, and never was: wiring it is not repairing a broken key, it is building a new capability onto a key with zero read sites. That is a different piece of work with a different cost, and it is not what batch #70 was answering.⇒ The row for objectui#7963 in decision batch #70 (
5565626809) is superseded onconfirmVariant. Its rows forcancelLabelandconfirmLabelare not superseded — both rulings agree those retire, and this PR ships exactly that.⭐ The capability is not lost
Batch #70 wanted the red destructive confirm button, and that want survives this retirement intact. Filed as objectui#8978, with the measurement that makes it concrete:
packages/components/src/ui/alert-dialog.tsx:115callsbuttonVariants()with no argument for the confirm button, while:128already passes{ variant: "outline" }for the cancel button right below it. The capability is one argument and one authorable spelling away — it was neverconfirmVariant's to deliver.That card also carries the constraint this seat owes the maintainer's standing concern about churn on the authoring surface: ⛔ it must not resurrect the retired spelling merely because batch #70 named it.
Effect on PR #8967
Per the review's own text, head
415803b7ab70115d0b920b28539f8453d3e6ae26needs no code change if R1 resolves in favour of the 2026-09-10 direction — which it does. The PR body's "Executes the maintainer ruling of 2026-09-10" now cites this comment. A delta record against the same head is what re-judges it.三个全退役). It was in front of them at the confirmation (同意), which is what this record rests on. ⛔ I am not claiming the first answer was informed by it.Generated by Claude Code
os-warren commentedon Sep 10, 2026
Clause-② carriers cleared — PASS on record
PM seat (
domain:spec@ objectui, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w), 2026-09-10.Review record:
5619719472—## Contract review,VERDICT: PASS, judged head415803b7ab70115d0b920b28539f8453d3e6ae26. It supersedes the REWORK at5618268876on the same head; R1 was answered on the carriers, and the head never moved, so every ① item measured green in the governing record stands.Independence pair, from that record:
SELF-REVIEW — implementer and reviewer are both subagents of the one dispatching session. ⛔ Recorded as such, not as an independent second opinion.
needs:contract-reviewis stripped from both carriers in the same stroke as this comment — card objectui#7963 and PR #8967.⛔ A correction to my own record at
5619666958The review caught an overstatement in the provenance comment I wrote an hour ago, and it is right. I wrote that batch #70's premise was falsified because the key was measured dead. Batch #70 already knew there were zero readers — that is what the card was about. What it got wrong was the mechanism and therefore the cost: its instruction was to pass a
variantthrough the renderer at:38, and that would have been inert anyway, becausepackages/components/src/ui/alert-dialog.tsx:115hard-codesbuttonVariants()with no argument (verified on head;:128is the cancel button, already passingoutline).So the supersession stands, but for a sharper reason than I gave: delivering this capability is different work than batch #70 was authorising — not "the key turned out to be dead", but "wiring it the way batch #70 described would not have produced the button either." ⛔ I am correcting this rather than leaving my looser version as the record, because the record's whole job is to be checkable.
confirmVariantrow reads live on that board. Named with its owner; ⛔ not a condition on this PR, and ⛔ not this seat's board to write.open_questionsis now observed rather than hypothesised. Cosmetic; it strengthens the case for the skills-lane card the review recommended, which stays a recommendation.Generated by Claude Code
os-warren commentedon Sep 10, 2026
Release:branchclaude/issue-7963-alert-dialog-footer-keys-retire· PR #8967 MERGED as290de37244477cddeb78e65426e693c218e265e4— verified on the tree, ⛔ not from an API field.Landing established by⚠️ Same field disagreement as every other merge this shift: commit date
git merge-base --is-ancestor→ rc 0 in a fresh clone, with the reversed probe as a negative control → rc 1.2026-09-10 13:48:47Zvsmerged_at 2026-09-10 14:06:18Z— 17m31s.cancelLabel,confirmLabelandconfirmVariantare retired fromAlertDialogSchemaon both faces, as named refusals rather than bare deletions —retirementTombstone()on the Zod face and?: neveron the TS face, becauseBaseSchemaCoreends.passthrough()andBaseSchemacloses with an any-valued index signature, so a dropped member key would have been kept, not refused.cancelText/actionTextare the surviving spellings.The record this landed on
5619666958— maintainer's untranslated words, the relay named as relay, and decision batch Add cloud storage functionality with Vercel KV integration in studio #70's contraryconfirmVariantrow (5565626809) declared superseded with its reason.ui/alert-dialog.tsx:115hard-codesbuttonVariants()with no argument.5619719472—VERDICT: PASSatCONTRACT_REVIEW_TIER, judged head415803b7ab70115d0b920b28539f8453d3e6ae26, superseding the REWORK at5618268876on the same head. Declared SELF-REVIEW (reviewer and implementer are both subagents of one dispatching session) — recorded as such, ⛔ never treated as an independent clearance.⭐ The capability is not lost
Batch #70 wanted the red destructive confirm button, and that want survives intact as objectui#8978, with the measurement that makes it concrete: the confirm button calls
buttonVariants()with no argument atui/alert-dialog.tsx:115while the cancel button right below it at:128already passes{ variant: "outline" }. ⇒ the capability is one argument and one authorable spelling away — it was neverconfirmVariant's to deliver.That card carries an explicit constraint: ⛔ it must not resurrect the retired spelling merely because batch #70 named it.
objectstack#12708's director ledger still has no supersession row for batch #70'sconfirmVariantdecision. Until the director seat writes one, that row reads live on that board while the key it names is retired here. Named with its owner.Labels
pm:dispatchedand the assignee are left in place on this closed card, deliberately:Fixescloses the card and never its labels, and the stale pair is the record of which seat drove it. ⛔ Not swept, so a later reader can see the trail rather than a card that looks untouched.Generated by Claude Code