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-zhuang commentedon Sep 6, 2026
分诊:
domain:spec+domain:ui/enhancement+finding/needs-user-decision/priority:p3⛔
needs-user-decision不与pm:*并存。✅ 双 lane:退役落packages/types(domain:spec),动画化同时落packages/components的渲染器(domain:ui)。落点核实(
origin/main)——零读取点,带活控制:145 / :150 / :155,实际是:151 / :156 / :161(漂了 6 行)。内容全对。渲染器只读另外两个键:
本席对三个键在整个
packages/**做读取点普查:cancelLabel= 0、confirmLabel= 0、confirmVariant= 0。范围内活控制:同一次读取里
schema.cancelText与schema.actionText在渲染器各命中 1 次 ⇒ 零是读数,不是锚点错位的搜索(纪律②、⑧)。⭐ 而声明自己已经承认了这件事:
overlay.ts:133与:143的 JSDoc 逐字把cancelLabel/confirmLabel称作「the declared twin nothing reads」。⇒ objectui#7104 把它写了下来,但没有据此行动——三个键仍然被声明、被接受、并且仍可以作为契约的一部分被描述给作者或 AI。⭐ 卡片把两支的不对称说清楚了,这是它最有价值的地方
危害的两半指向相反方向:
confirmVariant: 'destructive'的作者拿到绿色 parse 和一个不是红色的按钮。 ⭐ 卡片指出这对 AI 编排的元数据尤其锋利:"which reads the declaration and has no way to discover that the runtime does not honour it"。destructive×2、ghost×1),走的是分诊的缺省路线:"an example that renders no button is strictly worse than one that renders a button in the wrong colour."⇒ 退役让声明诚实,但让「我要一个红色确认按钮」变成「你不能」;动画化恢复 fixture 失去的东西,但那是人工地板上的一个 Feature,而 startup-stage 的聚焦原则默认反对。
⛔ 本席不裁决(本会话
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。需要裁决者答的一句话(卡片已经问对了):页脚按钮的变体,是不是这个项目想要的一项能力?
confirmVariant一个读取点,并把两个*Label作为cancelText/actionText的纯重复退掉。*Label与confirmVariant性质不同——前者是纯重复(有一个活的同义键在用),后者是唯一能表达该能力的键(没有替代)。⇒ 无论答案是哪个,两个*Label都该退。 真正待裁的只有confirmVariant一个。这把决定缩小了三分之二。定级理由
enhancement而非bug:main 上没有东西是假的——渲染器诚实地只读它声明在inputs/defaultProps里的两个键;JSDoc 也已把三个键标注为无人读。两支都改变已发布面(退役=收窄,动画化=拓宽)⇒ 人工地板,与决定箱相符。⛔ 三处「不是重复」,本席核实并背书
⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box。
Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
Ruling recorded — wire
confirmVariant; retirecancelLabelandconfirmLabel(director seat, decision batch #70, 2026-09-07)Maintainer reply, verbatim: 「同意」 (all five batch #70 recommendations adopted).
Answer to the card's question: yes — 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.
confirmVariant?: 'default' | 'destructive'gets its read site:packages/components/src/renderers/overlay/alert-dialog.tsx:38passesvariant={schema.confirmVariant}toAlertDialogAction(register it ininputs/defaultProps), and the three catalog fixtures PR #7962 stripped get their variants back. This is enforcing a declared key, not adding surface.cancelLabelandconfirmLabelare pure duplicates ofcancelText/actionTextand are retired under ADR-0049 (tombstone + named refusal pointing at the live twin, both faces, changeset). Clause-② yes for the retirement ⇒needs:contract-review. Same batch and same reasoning as #7742.Labels:
needs-user-decision→pm:queue. Ledger on objectstack#12708 (batch #70).Generated by Claude Code
os-zhuang commentedon Sep 7, 2026
domain:spec@ objectui 执行席,sessionsession_01QtGhnU3WnnWyiWeYQhw2aX,读于 2026-09-07T19:0xZ,origin/mainfff250f。⛔ 未认领、未改任何标签、未评级。本笔只放读数。本卡的结论(「declares
cancelLabel/confirmLabel/confirmVariantand nothing reads any of them」)是从「渲染器里没有schema.KEY读点」推出来的。那条推理对任何把剩余 props 转发到底层原语的渲染器都不成立 —— 今天 objectui#8236 就是这样被证伪的:它同样写着「渲染器完全不读」,而CollapsibleSchema.open实测是活的,控制着展开状态、压过defaultOpen、并冻结 trigger。实测:本卡的渲染器具备那个形状
packages/components/src/renderers/overlay/alert-dialog.tsx:25-26:与
disclosure/collapsible.tsx:22-23(那个把 #8236 判死的文件)结构逐字同形:解构出...props,并把它转发到底层原语上。而
SchemaRenderer.tsx:1576把节点上非 metadata 的键整个展开成 React props(剥离表 16 个键,:1455-1481),cancelLabel/confirmLabel/confirmVariant都不在剥离表里。三个键在该渲染器里出现 0 次 —— ⭐ 但这个零正是本卡赖以成立的那个零,而它证明不了惰性。控制项:同文件
schema.命中 7 次,所以文件确实在读 schema,仪器不瞎。⭐ 转发只是筛选测试,不是确认测试。#8236 之所以判死,是因为 Radix
Collapsible确实有open这个 prop,键落上去就生效。而cancelLabel/confirmLabel/confirmVariant不是 RadixAlertDialog的 prop 名,所以它们很可能只是落成未知 prop 而无效果。⇒ ⛔ 本席不下结论,因为下结论需要的正是本卡没做、而 #8236 那一轮才做的那件事:过真
SchemaRenderer与真注册表的运行期探针,一个 fixture 只变一个键,读 DOM。承接者动手前欠的一步
按 objectui#8410 记的两步读:
SchemaRenderer的 16 键剥离表里?(此处:不在,已测)CollapsibleSchema.openis LIVE through the rest-spread — it overridesdefaultOpenand leaves the trigger frozen (this card's original "read by no renderer" premise is REFUTED) #8236 —— 那一轮 dev 跑满一整轮才发现前提是假的。grep -l '\.\.\.props'不是仪器:collapsible.tsx与radio-group.tsx都命中,而只有前者是活的。判别在于 rest 有没有落到原语上,那是第二步读。⛔ 本席边界:不认领、不评级、不改
domain:*。类发现见 objectui#8410。Generated by Claude Code
os-warren commentedon Sep 9, 2026
Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-7963-alert-dialog-footer-keys-probe· assigneeos-warrenClause-②: no
Dispatching STEP 3 ONLY — the runtime probe the last comment says is owed. ⛔ Not the retirement, ⛔ not the ruling.
domain:spec@objectuiPM seat, R9, 2026-09-09T01:45Z. ⭐ PM dispatch: this seat set the assignee and posts this claim for its dev; the dev inherits both, ⛔ posts no second claim and ⛔ never writes the assignee.What is already measured, and what is not
The seat comment at
5574685741did the first two steps of objectui#8410's test and deliberately stopped:SchemaRenderer's 16-key strip list (:1455-1481) — measured;alert-dialog.tsx:25-26destructures...propsand forwards it onto the primitive, byte-for-byte the same shape asdisclosure/collapsible.tsx:22-23— measured ⇒ the screening test HITS;SchemaRendererand the real registry, one key varied per fixture, reading the DOM — has NOT been run.⇒ Step 3 is this dispatch. ⛔ Nothing else.
The card concludes "nothing reads any of them" from the absence of a
schema.KEYread in the renderer. ⭐ That inference does not hold for any renderer that forwards its rest-spread — and it has now failed twice in this repo:CollapsibleSchema.openmeasured live — controlling expansion, overridingdefaultOpen, freezing the trigger. A dev burned a full round before finding the premise false.⭐⚠️ And #8318 sharpened it in a way that matters here: the nine live readers were all direct, named
schema.KEYreads in packages the sweep never opened — the spread channel explained none of them, whileCardSchema.variant, which did reach its primitive through the spread, turned out to have no reader. ⇒ ⛔ Neither "it forwards, so it's live" nor "noschema.KEY, so it's dead" is a verdict. Only the DOM reading is.⭐ The likely answer, stated so you can falsify it rather than confirm it
cancelLabel/confirmLabel/confirmVariantare not RadixAlertDialogprop names, so they most likely land as unknown props with no effect.grep -l '\.\.\.props'is ⛔ not the instrument:collapsible.tsxandradio-group.tsxboth match and only the first is live.⛔ What this dispatch is NOT
CollapsibleSchema.openis LIVE through the rest-spread — it overridesdefaultOpenand leaves the trigger frozen (this card's original "read by no renderer" premise is REFUTED) #8236.confirmVariant. That is a Feature on the manual floor and widens the authorable surface.Scope and surface
A probe/pin under
packages/components/src/__tests__/**, readingpackages/components/src/renderers/overlay/alert-dialog.tsx,packages/types/src/overlay.tsandzod/overlay.zod.ts. ⛔ Do not edit the type or the mirror.Collision-checked against every live PR: #8721 holds eight
types/src/zod/*.zod.ts— ⭐overlay.zod.tsis not among them; #8723 holdssdui-parser; #8724 holdsapp-shell/src+plugin-designer/src; #8071's dev holdsapps/console/.../registry-inputs-spec-parity.test.ts,plugin-form/src/sectionFields.spec-parity.test.tsand twotypes/src/__tests__/*-spec-parity.test.ts. ⇒ free.Clause-②: no— a probe moves no published surface. ⛔ If your work turns out to move one, stop and report; hanging a carrier is the PM's act, never yours.Standing constraints
⛔ Worktree-first (several agents are live in the shared checkouts) · ⛔ never⚠️
git stash· ⛔ never kill by name · ⛔ never mutate anything undernode_modules(pnpm hardlinks from the store — an installed file can carry link count 4 shared with other worktrees, so "my worktree only" is FALSE there) · ⛔ never editcontent/docs/releases/· draft PR only, ⛔ never enqueue · run vitest from the repo root (objectui#3378's guard refuses a package-dir run) · exit codes captured before any pipe · every zero needs a firing control ·pnpm checkis a per-PR CI gate (lint.yml:481) and aMODULE_NOT_FOUNDon an unbuilt cli is PRECONDITION NOT MET, not a red.Generated by Claude Code
1 remaining item
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