Skip to content

feat(types)!: retire cancelLabel / confirmLabel / confirmVariant from AlertDialogSchema - #8967

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-7963-alert-dialog-footer-keys-retire
Sep 10, 2026
Merged

feat(types)!: retire cancelLabel / confirmLabel / confirmVariant from AlertDialogSchema#8967
os-warren merged 1 commit into
mainfrom
claude/issue-7963-alert-dialog-footer-keys-retire

Conversation

@os-warren

@os-warren os-warren commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7963

Executes the maintainer ruling of 2026-09-10, whose provenance is recorded on the card at #7963 (comment) (verbatim words, the relay named, and why decision batch #70's contrary confirmVariant row — 5565626809 — is superseded: its premise was falsified by the 2026-09-09 probe, which measured the key dead. The capability batch #70 actually wanted, the red destructive confirm button, is preserved as objectui#8978). All three retire from AlertDialogSchema on both faces, ADR-0049 enforce-or-remove. cancelText / actionText are the surviving spellings, and confirmVariant has no survivor at all.

Clause-②: yes — this narrows a published accept set on @object-ui/types. ⛔ Stays draft; the PM lands it.

The measurement, re-derived on this branch's own base 72bcd7783

⛔ Not quoted from the card. A point-access probe, tree-wide:

key declared schema.KEY reads
cancelLabel types/src/overlay.ts:151 + zod/overlay.zod.ts:62 0
confirmLabel overlay.ts:156 + overlay.zod.ts:63 0
confirmVariant overlay.ts:161 + overlay.zod.ts:64 0

FIRING CONTROLS, same instrument, firing 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.

A bare-word probe would have lied, and towards "live". These spellings are overloaded across this tree and every other owner is a live key on a different declaration: FormSchema.cancelLabel (types/src/form.ts:1243, read at form.tsx:1063,3266), objectql.ts:508, plugin-designer/src/components/ConfirmDialog.tsx:26,28 (React props), plugin-grid/src/components/BulkActionDialog.tsx:552, and plugin-form's ModalForm.tsx:579 / DrawerForm.tsx:528, which build a local cancelLabel from schema.cancelText — the opposite direction. None is an AlertDialogSchema; none is touched, and a pin asserts it.

The rest-spread near-miss is closed by measurement, not by reasoning. The keys do reach the primitive (not on SchemaRenderer's strip list, so they ride componentProps into the renderer's rest-spread onto the Radix root) — the same channel that made CollapsibleSchema.open live in objectui#8236. The DOM reading settles it: the AlertDialog root renders a context provider, not an element, so an unknown prop is dropped before reaching any node.

What landed

A named refusal, ⛔ not a bare deletion. BaseSchemaCore ends .passthrough() and the TypeScript BaseSchema closes with an any-valued index signature, so a dropped member key is kept, not refused — deleting the declarations alone would have left the silent accept exactly as it was and thrown the diagnostic away with it. Each key stays declared and unwritable: retirementTombstone() on the Zod face, ?: never on the TypeScript face, in the shape page-actions-refusal-7926 / page-breadcrumbs-refusal-8871 established.

retired key what the message names as the remedy
cancelLabel cancelText
confirmLabel actionText
confirmVariant nothing — see below

⚠️ confirmVariant has no surviving twin, and its message says so plainly rather than pointing at a key that does not do the same job: it names cancelText / actionText only to rule them out ("those are the footer's two LABELS, not a variant"), states that the node declares no variant key at all, and sends the question to its own card. ⛔ No replacement was invented.

The two existing pins are RE-POINTED, ⛔ not deleted

  • packages/types/src/__tests__/alert-dialog-read-dialect-7104.test.ts — its INERT_DECLARED became wrong in its own terms (they are now refused, not merely inert), so it is RETIRED_DECLARED; the three type-level StillDeclared legs flip from string | undefined to the ?: never reading and gain an authoring-site leg where the refusal actually bites; the @default docblock leg — which asserted the shipped type promised a default nothing applied — is replaced by one asserting the docblocks now teach the retirement. objectui#7104 wrote that block so that this PR would re-derive it deliberately; that is what happened, and it went red exactly there.
  • packages/components/src/__tests__/alert-dialog-footer-keys-liveness-7963.test.tsx — the liveness measurement this card rests on. Every DOM reading is kept: a retirement does not retire the evidence that justified it. Re-pointed from "this reading is the ruling's INPUT" to the ruling as taken, plus the note that matters most here — the renderer does not validate, so "a document written strictly against the retired keys renders an empty footer" is still true of a raw node; what moved is the gate one step earlier. A closure leg ties the two halves together in one place.

Ablations — five legs, every mutation proved on disk BEFORE any result was read

Each leg: anchor grep -c on injected and removed text, a git hash-object differing from the HEAD blob, restore via git checkout HEAD -- ABSOLUTE_PATH from a trap … EXIT INT TERM, and restoration proved by blob hash plus an empty git diff HEAD — ⛔ never by an exit code. Directions were predicted in the harness before the first leg ran; all five matched.

leg mutation HEAD blob mutated blob result
cancelLabel delete the Zod tombstone arm 10bb1d06… a9bbee6b… refusal pin exit 1, 4 failed / 21 passed
confirmLabel delete the Zod tombstone arm 10bb1d06… 1bf35c50… refusal pin exit 1, 4 failed / 21 passed
confirmVariant delete the Zod tombstone arm 10bb1d06… 78a44712… refusal pin exit 1, 4 failed / 21 passed
TS face cancelLabel?: never back to ?: string 69b03a6e… 86082837… packages/types type-check exit 2, TS2578 Unused '@ts-expect-error' in both pin files, plus zod-mirror-parity drift
renderer read the retired dialect instead of the surviving one 77968a92… 5d473285… 3 pins exit 1, 13 failed — WIRED, CHANNEL and VARIANT_INSTRUMENT controls all red

⭐ The three deletion legs are the argument for the mechanism, not just for the pin: with the arm deleted, AlertDialogZod.shape loses the key (34 members, no cancelLabel) and the retired document's issue paths drop to ['confirmLabel', 'confirmVariant']the key is silently accepted again. That is precisely the silent accept a bare deletion would have shipped.

Tree state after the harness: git status --porcelain empty, CLEAN.

Verification — exit codes captured into files BEFORE any pipe

All at 415803b7a.

what command reading
types suite vitest run --root . packages/types/ exit 0 — 167 files / 3303 tests passed
components suite 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; 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
lint pnpm --filter @object-ui/types --filter @object-ui/components lint exit 0, log echoes eslint . for both. ⭐ Read for the literal word: ✖ 278 problems (0 errors, 278 warnings) (types) and ✖ 948 problems (0 errors, 948 warnings) (components). Zero column-anchored error-severity rows in the whole log; every error string in it is inside a warning message body or a filename. My five files lint with no output at all.
changeset gates check-changeset-fixed / check-changeset-no-major / check-changeset-presence exit 0 / 0 / 0 — presence: "5 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)"
control bytes check-control-bytes exit 0, 7164 files scanned
whole-tree build turbo run build --filter='!@object-ui/site' --concurrency=2 exit 0 — 43/43 tasks
doc gates check:doc-snippets / check:doc-examples / check:doc-types exit 0 — 638/638 doc snippets compile against the BUILT types (the downstream reading a published-type narrowing owes)
others check:spec-symbols, check:handler-key-reads, check:sdui-registration-pins exit 0

⚠️ On the first pass check:doc-snippets, check:doc-examples and check:sdui-registration-pins returned exit 2 = PREREQUISITE NOT MET ("run the build first" / "this is exit 2, not a pass"). Those are NOT MEASURED, not red — they were re-run green after the whole-tree build, and are reported from that run.

Declared narrowing: the repository-wide pnpm lint and turbo run type-check across all 47 projects are CI's runs, not run here. The narrowing's downstream risk is instead answered by measurement: no fixture, catalog schema, example app, doc fence or template authors any of the three on an alert-dialog node anywhere in the tree, so no shipped document is stranded — and the whole-tree build plus 638 doc snippets compiling against the emitted .d.ts is the consumer-side reading.

Changeset

minor — AGENTS.md :240: a breaking change in this repo is minor (a major would drag the whole 39-package fixed group off @objectstack's cadence). Precedent objectui#5905, which likewise carries Clause-②: yes. @object-ui/types: minor, @object-ui/components: patch. ⛔ content/docs/releases/ untouched.

验收备注

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, and it caught one in this PR's own new docblock, which cited the precedent as PageNodeSchema.actions / that key spelled with a leading dot. Fixed here by spelling both arms without the leading dot and leaving a note beside them saying why. Not filed: the pin's tree-scoped shape is deliberate and correct (a file-scoped check would miss the reader nobody thought of), and the friction is one line of prose per citing card. Successor who meets it: the next retirement card that cites that precedent in a docblock — which is a real successor, since these refusal cards routinely cite each other.


🤖 Generated with Claude Code

https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w


Generated by Claude Code


Generated by Claude Code

…` from `AlertDialogSchema`

Executes the maintainer ruling of 2026-09-10 on objectui#7963: all three retire
from `AlertDialogSchema` on both faces under ADR-0049 enforce-or-remove.
`cancelText` / `actionText` are the surviving spellings, and `confirmVariant`
has no survivor at all.

Measured on `72bcd7783` with a point-access probe: `schema.cancelLabel`,
`schema.confirmLabel` and `schema.confirmVariant` each score 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`). A bare-word probe would have lied
towards "live": every other owner of these spellings is a live key on a
different declaration, and none of them is an `AlertDialogSchema`.

A NAMED REFUSAL, not a deletion. `BaseSchemaCore` ends `.passthrough()` and the
TypeScript `BaseSchema` closes with `[key: string]: any`, so a dropped member
key is KEPT, not refused — deleting the declarations alone would have left the
silent accept exactly as it was. Each key stays declared and unwritable:
`retirementTombstone()` on the zod face, `?: never` on the TypeScript face.

`confirmVariant`'s message says plainly that it has no surviving spelling and
rules `cancelText` / `actionText` out by name — they are the footer's two
labels, not a variant. No replacement was invented.

Both existing pins are re-pointed rather than deleted: objectui#7104's
`INERT_DECLARED` becomes `RETIRED_DECLARED` and its type-level legs flip from
`string | undefined` to the `?: never` reading, and the objectui#7963 liveness
pin keeps every DOM reading while gaining the closure leg tying the refusal to
the measurement that justified it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.1 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-BNy_263s.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 500.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 205.46KB 56.78KB
fields (index.js) 247.15KB 62.32KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.63KB 19.99KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.65KB 40.91KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 46.06KB 14.30KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review (clause ②) — PR #8967 @ 415803b7ab70115d0b920b28539f8453d3e6ae26

Card: #7963 · branch claude/issue-7963-alert-dialog-footer-keys-retire · reviewed head 415803b7ab70115d0b920b28539f8453d3e6ae26 · origin/main at review time 2c208d5bb9d5e989e280fb0091711e8b49046cbd, merge-base 72bcd7783a473bd7bdd2884c21787187f3214c65. Adversarial reading, fed only the card, its rulings, and the PR itself.

Tier reading (fuse)

  • CONTRACT_REVIEW_TIER grepped from scripts/pm/dispatch-gates.mjs: 'claude-fable-5-1' — at the container checkout eabdd66f45f402eba0f8404a8a9de4a501fc83a6 (line 10176) and at objectstack origin/main 776d64cd3da79e30c8fc87783020e3e52c3a7c28 (line 10494), same value.
  • Own transcript located by a unique marker (agent-a5b86d46f269f155a.jsonl), harness-stamped model values: first read 17/17 claude-fable-5-1; re-read immediately before posting 124/124 claude-fable-5-1; exactly one distinct value; identity attachment modelId: claude-fable-5-1; no fallback / switch keys in the transcript. Reading = tier ⇒ this record is at tier.

Independence pair

Implemented-by: claude/issue-7963-alert-dialog-footer-keys-retire (mode:subagent)
Reviewed-by: session_01Jmxdo7bmeqCQHLSfmLVX9w (contract-review subagent agent-a5b86d46f269f155a, mode:subagent, context-isolated; GitHub identity os-warren)

SELF-REVIEW. The implementer (os-dev subagent; the claim at comment 5617198887 names this session and this branch) and this reviewer are both subagents of the same dispatch session session_01Jmxdo7bmeqCQHLSfmLVX9w. Per contract-review.md (「两者同 session ⇒ 报 SELF-REVIEW,⛔ 不作独立复核」) this record does NOT count as an independent review; context isolation does not change that, and no downstream mechanism (enqueue gate, landing precondition ①, audit sweep) may read it as one. The technical findings below are stated at full strength regardless.

Protocol-face reading — does the maintainer principle bite?

Principle, verbatim (objectui#8934 comment 5617613810): 「我们的项目以objectstack 协议为准,文档应该以实际实现为准。协议不正确的应该先修改协议。」

Measured on /home/user/objectstack at eabdd66f45f402eba0f8404a8a9de4a501fc83a6 (read-only, git status clean):

  • packages/spec/src/ has no non-test file or symbol spelled alert-dialog / AlertDialog / alertDialog / confirm-dialog (0 hits).
  • ComponentPropsMap (packages/spec/src/ui/component.zod.ts:2931–3073, all 48 rows enumerated) has no alert-dialog / confirm row; PageComponentType (packages/spec/src/ui/page.zod.ts:113–149) has none; STRING_ARM_REGISTERED_TYPES (packages/spec/src/ui/component-type-vocabulary.ts:68) is ['record:line_items'] only.
  • The three spellings exist in spec ONLY on other owners: confirmLabel at ui/bulk-action.zod.ts:208 (bulk-action confirm button), system/i18n-resolver.ts:118,830–866 and system/translation.zod.ts:270–309 (action translation). cancelLabel and confirmVariant: 0 hits anywhere under packages/spec/src.

The principle does not bite. @objectstack/spec neither declares any of the three keys on an alert-dialog surface nor governs that surface at all (no component-map row, no enum entry). objectui's AlertDialogSchema (type: 'alert-dialog', a non-namespaced kebab string) lives in the union's open string arm that the spec deliberately leaves open (component-type-vocabulary.ts:10–34). Retiring the keys locally is not a local narrowing of a protocol declaration; no spec card is owed. This is a reading, not an inference.

① Derived judgments (accept set / public face)

Diff: 6 files, +718/−65 — packages/types/src/overlay.ts, packages/types/src/zod/overlay.zod.ts, two re-pointed pins, one new pin, one changeset. No renderer, no content/docs, no skills/** touched.

  1. Before/after per key — measured on my own clone, not copied from the PR. At the merge-base AlertDialogZod.safeParse({ type: 'alert-dialog', cancelLabel: 'x' }) is green and the key is KEPT (BaseSchemaCore ends .passthrough(), base.zod.ts:449); TS cancelLabel?: string. At head each of cancelLabel / confirmLabel / confirmVariant refuses at parse with code: invalid_type, path: [key] and the tombstone message; TS ?: never, refused at the authoring site (both pin files' @ts-expect-error consumed; pnpm --filter @object-ui/types type-check exit 0). Correct.
  2. "A bare deletion would be KEPT, not refused" — verified, not accepted. tombstone.zod.ts:64–66: retirementTombstone = z.never({ error }).optional().describe(). base.zod.ts:449 .passthrough(); base.ts:492 [key: string]: any. Counterfactual run directly: AlertDialogZod.omit({ cancelLabel, confirmLabel, confirmVariant }).safeParse({ …all three })success: true, data keys ["type","cancelLabel","confirmLabel","confirmVariant"], cancelLabel === 'x'. The foundation holds; the named-refusal weight is right, and it is the shape of feat(types)!: retire ComponentInput.inputType as an ADR-0049 tombstone (objectui#5905, ruled option B) #7639 / feat(types)!: retire ComponentInput.label / defaultValue / advanced as ADR-0049 tombstones (objectui#7493 item 1 / objectui#7781) #7932 / fix(types,docs): refuse actions by name on the page node, teach the shape that draws (objectui#7926) #8870 / fix(types): refuse breadcrumbs by name on the page node (objectui#8871) #8930 / feat(types,plugin-kanban,plugin-gantt)!: retire the bare kanban / gantt node keys and the kanban-ui / kanban-enhanced registrations #8865.
  3. Union entry, not only the direct schema. AnyComponentSchema.safeParse refuses all three (paths ["cancelLabel","confirmLabel","confirmVariant"]); validateSchema() (= AnyComponentSchema.parse) throws with cancelLabel:invalid_type; the OverlaySchema discriminated union refuses. The refusal pin exercises only AlertDialogZod directly; this reading closes the gap it leaves.
  4. TS face vs Zod face — no one-sided seam. undefined: accepted on both (?: never admits undefined; z.never().optional() too; exactOptionalPropertyTypes is set nowhere in the tree). null: refused on both. '': refused on both. One limit shared equally by both faces: a value authored inside the props / properties bag ({ type: 'alert-dialog', props: { confirmVariant: 'destructive' } }) still parses green (Zod) and compiles (TS index signature) — a member-level tombstone cannot reach into the bag. This is the mechanism's known limit shared with every precedent (page actions / breadcrumbs), not this PR's defect. Noted, non-blocking.
  5. confirmVariant has no surviving spelling — true. Renderer alert-dialog.tsx:36–39 draws AlertDialogCancel / AlertDialogAction with no variant seam; AlertDialogAction hard-codes buttonVariants(); the registration's inputs are title/description/cancelText/actionText/defaultOpen/trigger/content/className. cancelText / actionText are labels. There is no migration path to cut; the message rules the labels out by name and the pin asserts the ruling-out. Correct as an execution of "retire".
  6. The re-pointed pins mask nothing. alert-dialog-read-dialect-7104.test.ts: the three StillDeclared type legs flip to Equal<…, undefined> AND gain an authoring-site @ts-expect-error leg (which turns the flip into an assertion rather than a tautology); the @default legs become negative assertions plus a docblock-names-the-card assertion; the docs-page leg (the page does not teach the trio) is kept. alert-dialog-footer-keys-liveness-7963.test.tsx: every DOM reading kept verbatim, the EMPTY footer reading kept (still true — the renderer does not validate), a closure leg and a green CONTROL added. Nothing that would have gone red was deleted.
  7. Ablation legs. Not re-run as the dev ran them; replaced by the direct omit() counterfactual in item 2 (same mechanism) and the TS-face pins. The renderer-reads-the-retired-dialect leg and the TS-face leg are covered by standing pins (still reads none of the three; the @ts-expect-error legs).
  8. Tree reading. git grep of the three spellings at head: 155 hits in 31 files; every hit outside the two declaration files, the three pins and the changesets belongs to a different owner (FormSchema.cancelLabel, objectql.ts, plugin-designer/ConfirmDialog.tsx, plugin-form ModalForm/DrawerForm building a local cancelLabel FROM cancelText, plugin-grid def.confirmLabel). The two content/docs hits (api/schema-reference.md:285,311, plugins/plugin-form.mdx:61) are the FormSchema section. content/docs/components/overlay/alert-dialog.mdx and skills/**: 0 hits. Point-access probe at the merge-base: schema.cancelLabel / schema.confirmLabel / schema.confirmVariant = 0 / 0 / 0 tree-wide; controls schema.cancelText = 15 and schema.actionText = 5 (alert-dialog.tsx:37,38). No shipped document is stranded — verified.
  9. Base moved, no conflict. origin/main is 2 commits past the merge-base (58be55e78, 2c208d5bb); neither touches this PR's files; git merge-tree --write-tree origin/main HEAD exit 0.
  10. ⛔ Authorisation of the confirmVariant third — the finding that decides this record. The card carries two maintainer rulings that point opposite ways on confirmVariant:

② semver

③ Boundary flags

  • open_questions[0] — two attribution footers on the PR body. The dev chose A (leave both). A stands for this PR: both blocks name the same session, nothing is lost, and a dedup edit invites the re-append loop. Escalate to a card (skills lane, objectstack): the harness attribution block (🤖 Generated with … + bare session URL) and the os-dev contract footer (_Generated by [Claude Code](…/session_ID)_) disagree on the wire format for PR bodies; every future mode:subagent dev hits the same fork until one is declared canonical.
  • out_of_scope_findings[0] — objectui#8871's tree-scoped probe trips on a prose mention. Verified at head: git grep -nE '\.breadcrumbs|\.actions\b' over overlay.zod.ts, overlay.ts and the new pin = 0 hits (exit 1), so the in-place respelling is complete. Decline to file: the probe's tree scope is deliberate (page-breadcrumbs-refusal-8871.test.ts:262–312) and the cost is one line of prose per citing docblock. The new refusal pin adopts the same tree-scoped shape with its own EXCLUDED list, which is consistent with it.

Tests run by this seat

Own fresh clone at 415803b7ab70115d0b920b28539f8453d3e6ae26; pnpm install --frozen-lockfile --ignore-scripts exit 0; vitest aliases @object-ui/* to src; every exit code captured to a file before any pipe.

  • alert-dialog-footer-keys-refusal-7963.test.ts + alert-dialog-read-dialect-7104.test.ts + zod-mirror-parity.test.ts: exit 0 — 3 files, 103 tests passed (111 including this seat's 8-leg probe, which produced the union / bag / null / counterfactual readings above and was deleted afterwards; tree back to git status --porcelain empty).
  • alert-dialog-footer-keys-liveness-7963.test.tsx: exit 0 — 12/12.
  • pnpm --filter @object-ui/types type-check (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json): exit 0.
  • CI at this head: all 33 check runs completed, 0 failures (Test shards 1–4, Type Check, Lint, Doc Snippet Type Check, the four Changeset gates, Governed Surface Queue Guard all success; coverage jobs skipped by design).

Verdict

VERDICT: REWORK

Required, one item, on the carriers rather than on code:

Advisory, non-blocking: drop @object-ui/components: patch from the changeset (test-only, probe-PR precedent); record the props / properties-bag limit of member-level tombstones once, at workbook level.

Not measured by this seat

  • The full packages/types (167 files) and packages/components (259 files) suites, the whole-tree build, check:doc-snippets / check:doc-examples / check:sdui-registration-pins, pnpm lint — taken from CI's green runs at this head, not re-run here.
  • The five ablation legs as the dev ran them (blob hashes not reproduced); replaced by the direct omit() counterfactual and the TS-face pins.
  • A real browser; the DOM reading is happy-dom through the kept liveness pin.
  • The props / properties bag route at runtime through SchemaRenderer (only its parse / compile behaviour was read).
  • node scripts/pm/check-clause2-carriers.mjs --pair 8967 — the PM's landing pre-check, not run here.
  • Anything the maintainer may have said on 2026-09-10 in a session chat: not verifiable from GitHub, which is exactly R1.
  • Labels were not touched: needs:contract-review remains on both carriers.

Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review (clause ②, delta on R1) — PR #8967 @ 415803b7ab70115d0b920b28539f8453d3e6ae26

Delta record against the governing review at 5618268876. Reviewed head 415803b7ab70115d0b920b28539f8453d3e6ae26unmoved (PR head.sha read back identical; my clone at that sha, git status --porcelain empty). Base 2c208d5bb9d5e989e280fb0091711e8b49046cbd, mergeable_state: clean. No code measurement re-run: nothing R1 resolved changes any ① answer on this head.

Tier reading (fuse, this round)

  • CONTRACT_REVIEW_TIER re-grepped from objectstack origin/main 776d64cd3da79e30c8fc87783020e3e52c3a7c28 (scripts/pm/dispatch-gates.mjs:10494): 'claude-fable-5-1'.
  • Own transcript (agent-a5b86d46f269f155a.jsonl), harness-stamped model values read immediately before this record: 145/145 claude-fable-5-1, exactly one distinct value, no fallback / switch keys. (The earlier identity-attachment line did not match this round's grep; the per-message stamps are the reading.) Reading = tier ⇒ this record is at tier.

Independence pair

Implemented-by: claude/issue-7963-alert-dialog-footer-keys-retire (mode:subagent)
Reviewed-by: session_01Jmxdo7bmeqCQHLSfmLVX9w (contract-review subagent agent-a5b86d46f269f155a, mode:subagent, context-isolated; GitHub identity os-warren)

SELF-REVIEW. Same dispatch session on both lines, as in the governing record; this delta does not count as an independent review and no downstream mechanism may read it as one. Findings are stated at full strength regardless.

What changed since 5618268876 (read, not relayed)

R1 — judged item by item

  1. Does the record qualify? Yes, at the lane level. What my standard refused was a ruling that existed nowhere on GitHub; what exists now is a GitHub-native record with the maintainer's untranslated words — 「三个全退役(推荐)」 first, 「同意」 at confirmation — the relay named as such, the superseded row cited by id, and the supersession reasoned. That is the same evidentiary class as batch Add cloud storage functionality with Vercel KV integration in studio #70 itself: 5565626809 is also a seat's relayed 「同意」 from a session channel, plus a director ledger row written by that same seat. Neither is a platform-stamped maintainer act; no ruling in this family is. Holding the 09-10 ruling to a standard batch Add cloud storage functionality with Vercel KV integration in studio #70 never met would be asymmetric. The one real asymmetry — no director ledger row — is bookkeeping on objectstack#12708, not a contract fact on this head; see item 4.
  2. Is "premise falsified" sound, or just newer-wins? Sound in substance, with one precision the record slightly overstates. Batch Add cloud storage functionality with Vercel KV integration in studio #70 did not believe the key was live — the card already said 0 readers; what batch Add cloud storage functionality with Vercel KV integration in studio #70 assumed was the mechanism and cost: "alert-dialog.tsx:38 passes variant={schema.confirmVariant} to AlertDialogAction… enforcing a declared key, not adding surface." That instruction as written would have been inert: AlertDialogAction hard-codes buttonVariants() with no argument (packages/components/src/ui/alert-dialog.tsx:115, verified on this head; :128 is the cancel button already passing { variant: "outline" }), so the value would have died on the primitive exactly as the 09-09 probe measured for the rest-spread path. Delivering the red confirm requires changing the ui primitive and adding an authorable spelling — different work, different surface cost, not the one-line enforcement batch Add cloud storage functionality with Vercel KV integration in studio #70 priced. That is a genuine supersession of a falsified premise, and the maintainer confirmed with the contrary row and the probe in front of them. The two label rows of batch Add cloud storage functionality with Vercel KV integration in studio #70 are untouched and agree with this PR.
  3. Is objectui#8978 a real carrier of the capability, or a placebo? Real. It is queued (pm:queue), it carries the concrete measurement (:115 vs :128, which I verified), an explicit not-measured list (admitted variant values, the three catalog sites, blast radius of changing the default), the spelling constraint (do not resurrect confirmVariant merely because batch Add cloud storage functionality with Vercel KV integration in studio #70 named it), and its own spec-scope probe (alert-dialog is objectui-local — consistent with my protocol-face reading at objectstack eabdd66f45f402eba0f8404a8a9de4a501fc83a6). Batch Add cloud storage functionality with Vercel KV integration in studio #70's want — the red destructive confirm — survives with an actionable path.
  4. The two declared gaps — do they block landing? (a) Whether batch Add cloud storage functionality with Vercel KV integration in studio #70's row was in front of the maintainer at the first answer: moot for this PR; the operative ruling is the confirmation, which the record states was informed, and the first answer is not what the supersession rests on. Non-blocking. (b) No director ledger row on objectstack#12708: non-blocking for this head — it changes no accept set and the lane-level record exists on the card — but until the director seat writes the supersession there, the batch Add cloud storage functionality with Vercel KV integration in studio #70 row (5565637374) reads as live to anyone auditing from the ledger rather than the card. Owed by the director seat, named here so it is not lost; ⛔ not a condition on this PR.

① Derived judgments — delta

  • Items 1–9 of 5618268876: unchanged; head identical, no re-measurement warranted.
  • Item 10 (authorisation of the confirmVariant third): resolved by 5619666958 per R1 items 1–4 above. The confirmVariant tombstone is now an authorised execution of "retire", not only a correct one.

② semver — delta

  • Unchanged: changeset untouched (@object-ui/types: minor correct by precedent and by AGENTS.md 版本号策略; major mechanically excluded). Advisory stands: @object-ui/components: patch for a test-only change is inconsistent with the probe PR's precedent; non-blocking.

③ Boundary flags — delta

  • Attribution footers: the PR-body edit appended a third _Generated by [Claude Code](https://claude.ai/code)_ block — the re-append loop the dev predicted, now observed. Cosmetic; it strengthens the recommendation to file the skills-lane card (harness attribution block vs os-dev contract footer) so PR bodies have one canonical format. Still not a condition on this PR.
  • objectui#8871 prose-trip respelling: unchanged, verified clean at head in the governing record.

Verdict

VERDICT: PASS

R1 is resolved on the carriers; head 415803b7ab70115d0b920b28539f8453d3e6ae26 is the head every ① item was measured green on. Two non-blocking items remain, each with an owner: the director ledger row superseding batch #70's confirmVariant line (director seat, objectstack#12708); the skills-lane card on the PR-body attribution fork (PM seat). Landing per landing-operations.md is the PM seat's act, including clearing needs:contract-review on both carriers with a provenance comment citing this record and the head above.

Not measured by this seat, this round

  • No code, test, type-check or build was re-run; readings from 5618268876 stand on the identical head.
  • The maintainer's session-channel exchange itself: only its record on GitHub was read; the record's account of what was in front of the maintainer at confirmation is the seat's own statement.
  • node scripts/pm/check-clause2-carriers.mjs --pair 8967 — the PM's landing pre-check, not run here.
  • objectui#8978's own not-measured list (admitted variant values, catalog sites, blast radius) — that card's implementer's work, not this review's.
  • Labels were not touched.

Generated by Claude Code

@os-warren
os-warren marked this pull request as ready for review September 10, 2026 13:48
@os-warren
os-warren added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 290de37 Sep 10, 2026
35 checks passed
@os-warren
os-warren deleted the claude/issue-7963-alert-dialog-footer-keys-retire branch September 10, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants