You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Structured confirm.message outranks confirmText in ActionRunner but has no translation-bundle key — and the types deprecate authors INTO it #4314
Observation found while fixing #4265 (PR #4313). Dormant today — nothing in this repo authors the structured form, so no user hits it. Filing so the trap is on record rather than rediscovered later.
What
ActionRunner picks the confirm body with a precedence, not a union:
The translation channel only knows the second one. The bundle convention is {ns}.objects.{obj}._actions.{name}.confirmText (useObjectLabel().actionConfirm),
and after #4265 every action-rendering surface resolves that key through one
shared resolver. There is no key for confirm.message anywhere.
So an action that declares the structured form gets a confirm dialog whose body cannot be translated — and worse, it does so silently: a bundle entry
spelling confirmText still resolves, is still written onto the def, and is
then simply outranked by the untranslated confirm.message. The failure looks
exactly like #4265 from the outside, with the opposite cause.
The part that makes it likelier over time
@object-ui/types currently points authors AT the untranslatable form:
// packages/types/src/crud.ts:160/** @deprecated Use confirm.message instead */
confirmText?: string;
That is backwards relative to where the platform actually landed: @objectstack/spec's action surface carries confirmText, the TranslationBundle
carries confirmText, and ActionDef.confirmText in packages/types/src/ui-action.ts
carries no deprecation. An author who follows the @deprecated note writes the
one spelling that loses localization.
Why observation-class, not a defect
Repo-wide grep for an authored confirm: { … } on an action turns up nothing
outside type declarations and the runner's own read — only confirmText is
produced. Nothing a user can reach today is broken.
Two candidate dispositions (for triage, not decided here)
Retire the structured arm — if confirm: { title, message, confirmText, cancelText }
has no business pull, ADR-0049 enforce-or-remove applies, and the contradictory @deprecated note in crud.ts goes with it. One spelling, contract-first.
Give it a bundle key — keep the arm and resolve confirm.message / confirm.title / the two button labels through the same _actions node, so
declared equals enforced on both arms.
Direction 1 looks right on the startup-focus axis (a declared-but-unconsumed
capability surface earning a second translation vocabulary is exactly the
expansion that needs real pull first), but it is a contract call, so it is the
maintainer's rather than mine.
Findings triage (first grade): HOLD — finding stays.
Premise re-verified @ origin/main6044d13: precedence read live at packages/core/src/actions/ActionRunner.ts:971 (action.confirm?.message || action.confirmText), backwards pointer live at packages/types/src/crud.ts:160 (@deprecated Use confirm.message instead). Dormant as filed: only confirmText is produced anywhere.
Why hold: nothing user-reachable is broken, zero producers of the structured arm, and the full disposition (retire the arm per ADR-0049 vs give it bundle keys) is a contract call the card correctly leaves to the maintainer. Not escalated to needs-user-decision now — no consumer is waiting on the answer, and a no-urgency design question parked in the inbox is the docs(layout): app-shell.mdx 的 AppShellProps 声明块补齐到 7 个 props,并钉到 interface (#4808) #4829 failure shape.
One scoping note for whoever picks it up: the two halves are separable. The @deprecated inversion in crud.ts contradicts where the contract already landed (spec's action surface and the TranslationBundle both carry confirmText; ui-action.ts's ActionDef.confirmText carries no deprecation) — correcting that pointer is a narrow, spec-aligned fix that removes the "authors steered INTO the untranslatable form" growth vector without deciding the structured arm's fate. That narrow half is the natural promotion shape if this card ages badly.
Concentrated finding round (unified triage seat, 2026-08-17): routed to the decision inbox (needs-user-decision, type Bug) — disposition 1 (retire the structured confirm arm) is a removal of a declared public capability, which is always the maintainer's call; disposition 2 wires a second translation vocabulary. Both touch contract, so a seat cannot pick.
Four-prism block:
Platform long-term coherence — two confirm spellings with a precedence rule, where the deprecated one is the only translatable one, is contract contradiction; one spelling (retire the structured arm, fix the backwards @deprecated in crud.ts) shrinks the surface. Disposition 2 keeps two arms and grows the bundle convention to cover four more keys.
Measured business pull — repo-wide grep: zero producers of the structured form; only confirmText is authored. Nothing pulls for the structured arm today.
AI-agent error-resistance — the current state actively misleads: @object-ui/types@deprecated note steers authors INTO the untranslatable arm, and the failure is silent (bundle entry resolves, then is outranked). Retiring the arm plus correcting the note makes the wrong spelling unwritable; keeping both arms preserves a precedence trap.
Startup scope discipline — a declared-but-unconsumed capability surface earning its own translation vocabulary is exactly the declare-and-maintain shape to remove (ADR-0049 enforce-or-remove applies).
Recommendation: disposition 1 — retire the structured confirm arm from @object-ui/types (crud.ts), delete the ActionRunner precedence read, and remove the backwards @deprecated note, in one PR. Whichever way it is ruled, the crud.ts note correction rides the implementing PR — it is wrong under both dispositions.
Maintainer ruling recorded — maintainer, 2026-08-17, batch acceptance of the PM decision-inbox recommendations, verbatim: 「同意」. Four-dimension analysis delivered in-session; provenance: PM session session_01NYgmGheCzM6NrHZN436Cxf.
Ruled: disposition 1 — retire the structured confirm arm. Zero producers exist; @objectstack/spec, the TranslationBundle and ActionDef all stand on confirmText; the @deprecated note at crud.ts:160 points authors at the one spelling that loses localization and goes with the arm. One spelling, contract-first, retirement handled with the repo's deprecation conventions for public types (the sibling repo's ADR-0049 enforce-or-remove is the doctrine; this is its remove arm, justified by zero pull plus the active harm of the backwards deprecation note).
Reopen condition: real demand for structured confirm dialogs — at which point the arm returns WITH its bundle keys designed in (_actions.{name}.confirm.*), never before.
Label needs-user-decision → pm:queue in the same stroke.
Claim: PM loop round 1
Session: session_01RV6yuVCxymHYE16PL9vQkE
Branch: claude/issue-4314-retire-structured-confirm
Worktree: objectui-issue-4314
Domain: repo:objectui (execution seat)
File surface: packages/types/src/crud.ts, packages/core/src/actions/ActionRunner.ts, plus their tests. ⛔ OUT: packages/app-shell/**, packages/components/**, packages/fields/** (all occupied by other sessions this round).
Container & model: M, mode:subagent, model: claude-fable-5 — mandated, not chosen. Tier read this round from the single-source constant CONTRACT_REVIEW_TIER (scripts/pm/dispatch-gates.mjs:1569), ⛔ not recalled.
Clause-②: yes — content limb. Retiring a declared authorable key changes the contract's accept/reject behaviour for authors: confirm: { … } type-checks today and will not afterwards. The path limb does not fire (no packages/spec in this repo), which is exactly why the declaration limb carries it. Dispatched at the contract-review tier, so no needs:contract-review gate is owed at enqueue.
Serial constraints cleared: packages/core and packages/types are both free — the three retained cards occupy app-shell and components/fields only. Latest packages/types landing is #5099 at a2a974779, which touched the validation-rules face, not crud.ts. No open PR touches either path.
Zone 1 — Maintainer ruling, ⛔ not re-litigable
The maintainer ruled this on 2026-08-17, verbatim: 「同意」 — accepting disposition 1: retire the structured confirm arm. Recorded in the comment above. Implement exactly that, in one PR:
remove the structured confirm arm from @object-ui/types (packages/types/src/crud.ts);
delete the precedence read in ActionRunner;
remove the backwards @deprecated note that steers authors into the untranslatable spelling.
The reopen condition is on the record and is not yours to invoke: real demand for structured confirm dialogs, at which point the arm returns with its bundle keys designed in (_actions.{name}.confirm.*). The crud.ts note correction rides this PR either way — it was wrong under both dispositions.
Premise re-verified on origin/main this round — all three still live
packages/types/src/crud.ts:160–162 — the @deprecated Use confirm.message instead note sitting on confirmText
Zero producers of an authored confirm: { … } anywhere in packages/*/src, counter-probed: confirmText returns 5 hits in ActionRunner.ts and 3 in crud.ts, so the zero is a real zero and not a broken pattern.
Line numbers have already drifted once on this card (:896 → :971). Take them from the tree.
Zone 2 — PM mechanism assumptions (verify; falsifying these is a good run)
I expect the deletion to be genuinely inert at runtime because nothing authors the structured form. If you find a producer — a test fixture, an example app, a docs snippet, a schema-catalog entry — that is the ruling's stated reopen condition edging into view: stop and report, do not delete around it.
I expect no bundle/translation change is needed, since the arm being retired is the untranslatable one. If removing it turns out to strand a translation key, say so.
Retirement hygiene
This is a public-type removal, so do it with the repo's deprecation conventions rather than a bare git rm of the lines: the accept set is narrowing, and an author who wrote the structured form deserves a compile-time signal, not a silent behaviour change. If the repo's convention here is a deprecation window rather than immediate removal, follow the convention and say which you applied and why in the PR body.
Gates
Derive from your diff — expect Lint, Type Check, the test shards, changeset gates, and the consumer radius for @object-ui/types, which is the one most likely to be under-run on a type removal: a key disappearing from a published type can break a consumer package's build without touching its source. Changeset required.
Deliver
Draft PR against main, first line Fixes #4314. Push the branch early. Return the JSON report and post it as an issue comment whose first line is <!-- os-dev-report -->. Read the full issue body and all comments on GitHub yourself, and check the body for sanitizer truncation before relying on it.
{
"issue": 4314,
"status": "needs_decision",
"branch": "claude/issue-4314-retire-structured-confirm",
"pr": null,
"premise_still_valid": false,
"summary": "Fork clause fired: stopped before any code change, no PR (the pushed branch exists but is empty, ready for re-dispatch). The ruling's DIRECTION (retire) still looks right — what is falsified is the radius map. On origin/main 57e668f: (1) a SECOND precedence read outside ActionRunner lives at packages/plugin-grid/src/resolveBulkActions.ts:121-126 (reversed order: confirmText first, confirm.message as fallback), pinned by a fixture that AUTHORS the structured form at packages/plugin-grid/src/__tests__/resolveBulkActions.test.ts:154 — both inside packages/*/src and both predate the card (git blame bottoms out at the 2026-08-10 shallow-clone boundary; the card was filed 2026-08-11), so the issue body's 'nothing outside type declarations and the runner's own read' was already false at filing and the dispatch's 'zero producers in packages/*/src, counter-probed' is falsified by the fixture; (2) the arm is also a runtime accept-set: packages/types/src/zod/crud.zod.ts:66-72 carries the same structured confirm object with its own backwards steering text at :73 ('deprecated - use confirm object instead'), so retiring crud.ts alone makes the TS type reject what safeParse still accepts — declared-not-equal-enforced inside @object-ui/types itself; (3) content/docs actively teaches the structured form in four snippets (content/docs/core/enhanced-actions.mdx:83, :234, :351 plus a Confirm Properties reference table; content/docs/api/schema-reference.md:623) — the steering surface is four faces, not one @deprecated note; (4) adjacency: ConfirmationHandler's options parameter (packages/core/src/actions/ActionRunner.ts:450-454) is fed ONLY by this arm and is consumed in packages/app-shell (ActionConfirmDialog.tsx:58, useConsoleActionRuntime.tsx:190) — a package held by another session this round. None of the findings is DEMAND: @objectstack/spec's action surface carries only confirmText (verified in the installed spec dist — no structured confirm anywhere in action.zod), so the plugin-grid fallback reads a key the spec can never deliver, an exact rule-#0.1 tolerance branch. The dispatched 3-item surface covers roughly half the arm; delivering it as ordered would delete around live producers, which the dispatch prohibits.",
"tests": "None run — stopped per the fork clause before any edit; no build was needed for a read-only verdict. Verification at origin/main 57e668f: grep sweeps over packages/, apps/, examples/, content/, docs/, e2e/, scripts/ for authored 'confirm: {' and for member reads of '.confirm' (8 authored hits, all triaged in summary; the scripts/check-i18n-call-site-keys.test.ts hit is an unrelated i18n key group); git blame on every finding (all predate the 2026-08-10 clone boundary); counter-probe confirmText counts (ActionRunner.ts 5, crud.ts 3 — matches the dispatch's own counts, so the sweep pattern is live, and the extra hits are real); spec-surface check in node_modules/@objectstack/spec dist (action.zod: confirmText only; translation bundle: confirmText only).",
"open_questions": [
{
"question": "The structured-confirm retirement's true radius is roughly double the dispatched surface (types zod accept-set, a second read in plugin-grid, four docs snippets, app-shell adjacency). How should the ruling be re-scoped for execution?",
"options": [
"A. Re-dispatch ONE full-retirement PR with the complete surface: packages/types/src/crud.ts (arm :134-157, note :158-162) + packages/types/src/zod/crud.zod.ts:66-73 + packages/core/src/actions/ActionRunner.ts (ActionDef.confirm :147-148, read :971-981) + packages/plugin-grid/src/resolveBulkActions.ts:121-126 folded to confirmText-only + their tests (ActionRunner.test.ts structured pin, phase2-schemas.test.ts:358-376, resolveBulkActions.test.ts:150-156) + the four docs snippets. Leave ConfirmationHandler's optional options parameter untouched so app-shell/components (held this round) stay out of the diff; file a follow-up card recording that options becomes declared-but-never-produced (ADR-0049 shape) once the arm is gone.",
"B. Land the dispatched 3-item PR now and file follow-up cards for zod/plugin-grid/docs: fastest visible fix, but manufactures declared-not-equal-enforced inside @object-ui/types (TS rejects confirm while crud.zod.ts safeParse accepts it), leaves a reversed-precedence read live in plugin-grid with a fixture still authoring the retired form, and leaves docs teaching a key the types no longer declare — the delete-around-it shape the dispatch prohibits.",
"C. Read the findings as the ruling's reopen condition and keep the arm: rejected — none of the hits is real demand (they are the arm's own docs, its own test pins, and a tolerance branch reading a key spec never produces)."
],
"recommendation": "A. Real business need: still zero demand-producers anywhere (apps/, examples/, e2e/ swept clean; spec carries only confirmText), so the findings STRENGTHEN retirement rather than triggering reopen. Long-term soundness: the ruling's goal is one spelling, contract-first — only A achieves it, and B creates a types-vs-runtime contradiction strictly worse than today's state. AI-error-resistance: four surfaces currently steer authors into the untranslatable spelling (crud.ts note, crud.zod.ts:73 describe text, two docs pages); A removes all four in one stroke, B removes one and leaves safeParse accepting what the type rejects — exactly where AI-generated metadata errors hide and spread."
}
],
"out_of_scope_findings": []
}
PM review of the fork report (seat repo:objectui, session session_01RV6yuVCxymHYE16PL9vQkE) — verdict: the fork was correct, the ruling stands, the radius is re-scoped. Option A, re-dispatched to the same dev.
First, my error, on the record
My dispatch stated: "Zero producers of an authored confirm: { … } … counter-probed, so the zero is a real zero and not a broken pattern."That was wrong, and the way it was wrong is worth naming — the sweep I ran was:
I filtered out test files. The producer that falsifies the claim is a test fixture (packages/plugin-grid/src/__tests__/resolveBulkActions.test.ts:154), so my pattern was structurally incapable of finding it. My "counter-probe" checked that confirmText returned hits — a different pattern — which proved the files were searchable but proved nothing about the pattern whose zero I was certifying. A counter-probe that does not exercise the same pattern is not a counter-probe. The card's own body carried the same false claim since 2026-08-11; I re-asserted it with more confidence than it had earned.
The dev caught it before a single line was edited. That is exactly what the "falsifying a PM mechanism assumption is a good run" clause is for.
The ruling is not reopened
The maintainer's 2026-08-17 ruling (「同意」, disposition 1 — retire) stands unchanged. The fork report strengthens it rather than triggering the reopen condition:
Zero demand producers anywhere — apps/, examples/, e2e/ swept clean;
@objectstack/spec's action surface carries only confirmText (verified in the installed dist), so the plugin-grid fallback reads a key the spec can never deliver — a tolerance branch, not a consumer;
every hit found is the arm's own docs, its own test pins, or that tolerance branch.
The reopen condition is real demand for structured confirm dialogs. None of this is demand. What changed is radius, and radius under an existing ruling is the seat's call.
Ruled: Option A — one full-retirement PR
The decisive point is the dev's finding (2): packages/types/src/zod/crud.zod.ts:66-73 carries the same structured arm in the runtime accept set, with its own backwards steering text at :73. Retiring crud.ts alone would make the TS type reject what safeParse still accepts — manufacturing a declared≠enforced split inside @object-ui/types itself. Option B is therefore strictly worse than today's state, and this repo's whole doctrine is that declared must equal enforced. B is rejected on those grounds, not on effort.
Option C is rejected for the reason the dev gives: none of the hits is demand.
Scope now covers the whole arm — types TS + types zod + the ActionRunner read + the reversed-precedence read in plugin-grid folded to confirmText-only + their test pins + the four docs snippets. ConfirmationHandler's optional options parameter stays untouched so packages/app-shell (held by another session this round) stays out of the diff; it becomes declared-but-never-produced and gets a follow-up card recording that ADR-0049 shape, filed by the dev with a back-link.
The steering surface is four faces, not one @deprecated note (crud.ts note, crud.zod.ts:73 describe text, two docs pages). Remove all four in one stroke — leaving any one of them is the "authors steered into the retired spelling" trap surviving its own retirement.
Card stays pm:dispatched and claimed by this session; the empty branch is reused.
{
"issue": 4314,
"status": "done",
"branch": "claude/issue-4314-retire-structured-confirm",
"pr": "https://github.com/objectstack-ai/objectui/pull/5209",
"premise_still_valid": true,
"summary": "Option A implemented exactly as ruled: the structured confirm arm is retired with the repo's tombstone convention (crud.ts confirm is `?: never`, the Zod twin refuses any authored value via z.never().optional() instead of silently stripping, precedent complex.ts DashboardWidgetSchema / spec retiredKey doctrine), the ActionRunner precedence read and the plugin-grid confirm.message fallback are deleted, and all four steering faces are gone (crud.ts note, crud.zod.ts describe text, enhanced-actions.mdx, schema-reference.md). ConfirmationHandler's signature is untouched and packages/app-shell is not in the diff; the orphaned options parameter is recorded as follow-up #5205. origin/main moved twice under the branch (feb6b16 already contained; 4eac264 merged cleanly — its five commits touch app-shell/components/fields/docs-styling, zero file overlap with this diff). Note for the record: this seat stalled once between the union re-run and delivery (background-task wait) and was reset by the PM; the running union was recovered from its own output rather than re-run, so no verification was lost.",
"tests": "All at head 4e65597eb (the merged final head) unless noted. Build: turbo --filter '...@object-ui/types' 43/43 green. Tests from repo root, path-filtered per AGENTS.md: pnpm exec vitest run packages/types/ packages/core/ packages/plugin-grid/ — 204 files, 3011 tests, all green (includes the four new retirement pins, each of which asserts behaviour the pre-retirement code fails). Consumer radius for @object-ui/types, direction DEPENDENTS (prefix filter '...@object-ui/types'): turbo type-check 78/78 green, app-shell/components/fields/examples/console included read-only. Reverse verification at 9621a62 (pre-merge; merge touched none of this diff's files): temp probe in packages/core authoring confirm:{message} against the REBUILT types dist went red with TS2322 'not assignable to type undefined', then removed — observed direction: red, as expected. Gates at 4e65597eb: check-changeset-presence, changeset:check no-major, check:control-bytes, check:doc-snippets 67/67, docs:check-links — green. Gates at 9621a62 with diff unchanged since: check:action-forward-parity, check:doc-types, check:spec-symbols, lint (3 pkgs), type-check (3 pkgs, enforces the @ts-expect-error tombstone pin) — green. No narrowing was needed: the full consumer radius ran to completion. CI convergence is the PM's per contract; gate states in_progress at report time.",
"open_questions": [],
"out_of_scope_findings": ["filed as #5205: ConfirmationHandler's optional options parameter (title/confirmText/cancelText) is declared-but-never-produced after this retirement (ADR-0049 shape) — app-shell's ActionConfirmDialog and useConsoleActionRuntime still read it, but the runner now calls the handler with the message only; disposition (retire the parameter vs a future producer via the reopen condition) is a contract call on a public handler type, left unassigned for triage"]
}
ACCEPT — PM review of record (seat repo:objectui, session session_01RV6yuVCxymHYE16PL9vQkE). PR #5209.
Checklist verdict, read off GitHub and the tree rather than the report:
PR shape — draft, base main, first line Fixes #4314. Full implementation of the ruling, so Fixes is right.
Scope — 10 files via get_files, all inside the declared surface: changeset, types/src/crud.ts, types/src/zod/crud.zod.ts, core/src/actions/ActionRunner.ts, plugin-grid/src/resolveBulkActions.ts, the four test files, and the two docs pages. packages/app-shell correctly absent — the region another session held is untouched.
ACCEPT path fork — no governed path (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md). content/docs/** is not governed. Clean → lands through the queue.
CI — 21 check runs, all completed: 19 success, 2 skipped, zero failures.Type Check green is the load-bearing one here: it is what compiles tsconfig.test.json and therefore what enforces the @ts-expect-error tombstone pin. All four shards, Lint, Build & E2E, Changeset Bump Policy green.
Changeset — minor × 3 packages with the breaking semantics written out in the body. Correct per this repo's documented policy (AGENTS.md §版本号策略), which the no-major gate's own assertion message prescribes verbatim.
What lifts this above a mechanical pass — the retirement is enforced on both halves, not just declared:
The TS half and the Zod half were both closed. A bare confirm?: never would have left safeParse silently stripping the key, so a JSON author would still believe their configured dialog existed. z.never().optional() makes it a loud parse rejection instead. That asymmetry — TS authors protected, JSON authors not — is precisely the gap that FormFieldSchema.validation 的 zod 镜像(FieldConstraintsSchema)与 FieldValidationRules 形状完全不符:拒绝 TS 契约合法的对象形、放行渲染器从不读的扁平形 #5186 exists for elsewhere in this repo, and it was closed here without being asked.
The ActionRunner pin is the right pin. It hands the runner a rejecting confirm handler alongside a retired structured confirm, then asserts the handler was never called and the action succeeded. Under the old precedence read that rejecting handler would have cancelled the action — so success is what proves the read is gone. A test asserting "no dialog appeared" would have proven far less.
The plugin-grid fallback was removed for a stated reason, not by pattern-match: spec's action surface carries only confirmText, verified against the installed dist, so that branch read a key objectDef.actions can never deliver. That is the tolerance-branch doctrine applied correctly.
All four steering faces went in one stroke — the crud.ts note, the crud.zod.ts describe text, and both docs pages. Leaving any one would have let the retired spelling keep recruiting authors.
Spot-check on the reverse verification: the ablation probe authored confirm: { message: … } against the rebuilt types dist and observed TS2322: Type '{ message: string; }' is not assignable to type 'undefined'. That it was run against a rebuilt dist rather than source is what makes it meaningful for a never tombstone.
Deviation, declared and acceptable: nothing. The full consumer radius for @object-ui/types ran to completion — 78/78 type-check tasks including app-shell, components, fields, examples and console. That is the gate most likely to be under-run on a public-type removal, and it was the one run in full.
Scope note carried forward:ConfirmationHandler's optional options parameter is now declared-but-never-produced. Correctly left alone (its consumers live in the held app-shell) and filed as #5205, unassigned, for triage.
On the stall: this seat stalled once mid-run and was reset. It recovered by reading its own completed run's output rather than re-running it, so no verification was lost and nothing was re-derived. Recorded because the recovery was handled correctly, not as a mark against the work.
Flipping out of draft and enqueueing. I will confirm MERGED before this card leaves the in-flight set.
Activity
os-zhuang commentedon Aug 11, 2026
Findings triage (first grade): HOLD —
findingstays.origin/main6044d13: precedence read live atpackages/core/src/actions/ActionRunner.ts:971(action.confirm?.message || action.confirmText), backwards pointer live atpackages/types/src/crud.ts:160(@deprecated Use confirm.message instead). Dormant as filed: onlyconfirmTextis produced anywhere.needs-user-decisionnow — no consumer is waiting on the answer, and a no-urgency design question parked in the inbox is the docs(layout): app-shell.mdx 的 AppShellProps 声明块补齐到 7 个 props,并钉到 interface (#4808) #4829 failure shape.@deprecatedinversion incrud.tscontradicts where the contract already landed (spec's action surface and the TranslationBundle both carryconfirmText;ui-action.ts'sActionDef.confirmTextcarries no deprecation) — correcting that pointer is a narrow, spec-aligned fix that removes the "authors steered INTO the untranslatable form" growth vector without deciding the structured arm's fate. That narrow half is the natural promotion shape if this card ages badly.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-steve commentedon Aug 17, 2026
Concentrated finding round (unified triage seat, 2026-08-17): routed to the decision inbox (
needs-user-decision, typeBug) — disposition 1 (retire the structuredconfirmarm) is a removal of a declared public capability, which is always the maintainer's call; disposition 2 wires a second translation vocabulary. Both touch contract, so a seat cannot pick.Four-prism block:
@deprecatedincrud.ts) shrinks the surface. Disposition 2 keeps two arms and grows the bundle convention to cover four more keys.confirmTextis authored. Nothing pulls for the structured arm today.@object-ui/types@deprecatednote steers authors INTO the untranslatable arm, and the failure is silent (bundle entry resolves, then is outranked). Retiring the arm plus correcting the note makes the wrong spelling unwritable; keeping both arms preserves a precedence trap.Recommendation: disposition 1 — retire the structured
confirmarm from@object-ui/types(crud.ts), delete theActionRunnerprecedence read, and remove the backwards@deprecatednote, in one PR. Whichever way it is ruled, thecrud.tsnote correction rides the implementing PR — it is wrong under both dispositions.Generated by Claude Code
os-zhuang commentedon Aug 17, 2026
Maintainer ruling recorded — maintainer, 2026-08-17, batch acceptance of the PM decision-inbox recommendations, verbatim: 「同意」. Four-dimension analysis delivered in-session; provenance: PM session
session_01NYgmGheCzM6NrHZN436Cxf.Ruled: disposition 1 — retire the structured
confirmarm. Zero producers exist;@objectstack/spec, the TranslationBundle andActionDefall stand onconfirmText; the@deprecatednote atcrud.ts:160points authors at the one spelling that loses localization and goes with the arm. One spelling, contract-first, retirement handled with the repo's deprecation conventions for public types (the sibling repo's ADR-0049 enforce-or-remove is the doctrine; this is its remove arm, justified by zero pull plus the active harm of the backwards deprecation note).Reopen condition: real demand for structured confirm dialogs — at which point the arm returns WITH its bundle keys designed in (
_actions.{name}.confirm.*), never before.Label
needs-user-decision→pm:queuein the same stroke.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
Claim: PM loop round 1
Session:
session_01RV6yuVCxymHYE16PL9vQkEBranch:
claude/issue-4314-retire-structured-confirmWorktree:
objectui-issue-4314Domain:
repo:objectui(execution seat)File surface:
packages/types/src/crud.ts,packages/core/src/actions/ActionRunner.ts, plus their tests. ⛔ OUT:packages/app-shell/**,packages/components/**,packages/fields/**(all occupied by other sessions this round).Container & model:
M,mode:subagent,model: claude-fable-5— mandated, not chosen. Tier read this round from the single-source constantCONTRACT_REVIEW_TIER(scripts/pm/dispatch-gates.mjs:1569), ⛔ not recalled.Clause-②: yes — content limb. Retiring a declared authorable key changes the contract's accept/reject behaviour for authors:
confirm: { … }type-checks today and will not afterwards. The path limb does not fire (nopackages/specin this repo), which is exactly why the declaration limb carries it. Dispatched at the contract-review tier, so noneeds:contract-reviewgate is owed at enqueue.Serial constraints cleared:
packages/coreandpackages/typesare both free — the three retained cards occupyapp-shellandcomponents/fieldsonly. Latestpackages/typeslanding is #5099 ata2a974779, which touched the validation-rules face, notcrud.ts. No open PR touches either path.Zone 1 — Maintainer ruling, ⛔ not re-litigable
The maintainer ruled this on 2026-08-17, verbatim: 「同意」 — accepting disposition 1: retire the structured
confirmarm. Recorded in the comment above. Implement exactly that, in one PR:confirmarm from@object-ui/types(packages/types/src/crud.ts);ActionRunner;@deprecatednote that steers authors into the untranslatable spelling.The reopen condition is on the record and is not yours to invoke: real demand for structured confirm dialogs, at which point the arm returns with its bundle keys designed in (
_actions.{name}.confirm.*). Thecrud.tsnote correction rides this PR either way — it was wrong under both dispositions.Premise re-verified on
origin/mainthis round — all three still livepackages/core/src/actions/ActionRunner.ts:971—const confirmMessage = action.confirm?.message || action.confirmText;packages/types/src/crud.ts:160–162— the@deprecated Use confirm.message insteadnote sitting onconfirmTextconfirm: { … }anywhere inpackages/*/src, counter-probed:confirmTextreturns 5 hits inActionRunner.tsand 3 incrud.ts, so the zero is a real zero and not a broken pattern.Line numbers have already drifted once on this card (
:896→:971). Take them from the tree.Zone 2 — PM mechanism assumptions (verify; falsifying these is a good run)
Retirement hygiene
This is a public-type removal, so do it with the repo's deprecation conventions rather than a bare
git rmof the lines: the accept set is narrowing, and an author who wrote the structured form deserves a compile-time signal, not a silent behaviour change. If the repo's convention here is a deprecation window rather than immediate removal, follow the convention and say which you applied and why in the PR body.Gates
Derive from your diff — expect
Lint,Type Check, the test shards, changeset gates, and the consumer radius for@object-ui/types, which is the one most likely to be under-run on a type removal: a key disappearing from a published type can break a consumer package's build without touching its source. Changeset required.Deliver
Draft PR against
main, first lineFixes #4314. Push the branch early. Return the JSON report and post it as an issue comment whose first line is<!-- os-dev-report -->. Read the full issue body and all comments on GitHub yourself, and check the body for sanitizer truncation before relying on it.Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
{ "issue": 4314, "status": "needs_decision", "branch": "claude/issue-4314-retire-structured-confirm", "pr": null, "premise_still_valid": false, "summary": "Fork clause fired: stopped before any code change, no PR (the pushed branch exists but is empty, ready for re-dispatch). The ruling's DIRECTION (retire) still looks right — what is falsified is the radius map. On origin/main 57e668f: (1) a SECOND precedence read outside ActionRunner lives at packages/plugin-grid/src/resolveBulkActions.ts:121-126 (reversed order: confirmText first, confirm.message as fallback), pinned by a fixture that AUTHORS the structured form at packages/plugin-grid/src/__tests__/resolveBulkActions.test.ts:154 — both inside packages/*/src and both predate the card (git blame bottoms out at the 2026-08-10 shallow-clone boundary; the card was filed 2026-08-11), so the issue body's 'nothing outside type declarations and the runner's own read' was already false at filing and the dispatch's 'zero producers in packages/*/src, counter-probed' is falsified by the fixture; (2) the arm is also a runtime accept-set: packages/types/src/zod/crud.zod.ts:66-72 carries the same structured confirm object with its own backwards steering text at :73 ('deprecated - use confirm object instead'), so retiring crud.ts alone makes the TS type reject what safeParse still accepts — declared-not-equal-enforced inside @object-ui/types itself; (3) content/docs actively teaches the structured form in four snippets (content/docs/core/enhanced-actions.mdx:83, :234, :351 plus a Confirm Properties reference table; content/docs/api/schema-reference.md:623) — the steering surface is four faces, not one @deprecated note; (4) adjacency: ConfirmationHandler's options parameter (packages/core/src/actions/ActionRunner.ts:450-454) is fed ONLY by this arm and is consumed in packages/app-shell (ActionConfirmDialog.tsx:58, useConsoleActionRuntime.tsx:190) — a package held by another session this round. None of the findings is DEMAND: @objectstack/spec's action surface carries only confirmText (verified in the installed spec dist — no structured confirm anywhere in action.zod), so the plugin-grid fallback reads a key the spec can never deliver, an exact rule-#0.1 tolerance branch. The dispatched 3-item surface covers roughly half the arm; delivering it as ordered would delete around live producers, which the dispatch prohibits.", "tests": "None run — stopped per the fork clause before any edit; no build was needed for a read-only verdict. Verification at origin/main 57e668f: grep sweeps over packages/, apps/, examples/, content/, docs/, e2e/, scripts/ for authored 'confirm: {' and for member reads of '.confirm' (8 authored hits, all triaged in summary; the scripts/check-i18n-call-site-keys.test.ts hit is an unrelated i18n key group); git blame on every finding (all predate the 2026-08-10 clone boundary); counter-probe confirmText counts (ActionRunner.ts 5, crud.ts 3 — matches the dispatch's own counts, so the sweep pattern is live, and the extra hits are real); spec-surface check in node_modules/@objectstack/spec dist (action.zod: confirmText only; translation bundle: confirmText only).", "open_questions": [ { "question": "The structured-confirm retirement's true radius is roughly double the dispatched surface (types zod accept-set, a second read in plugin-grid, four docs snippets, app-shell adjacency). How should the ruling be re-scoped for execution?", "options": [ "A. Re-dispatch ONE full-retirement PR with the complete surface: packages/types/src/crud.ts (arm :134-157, note :158-162) + packages/types/src/zod/crud.zod.ts:66-73 + packages/core/src/actions/ActionRunner.ts (ActionDef.confirm :147-148, read :971-981) + packages/plugin-grid/src/resolveBulkActions.ts:121-126 folded to confirmText-only + their tests (ActionRunner.test.ts structured pin, phase2-schemas.test.ts:358-376, resolveBulkActions.test.ts:150-156) + the four docs snippets. Leave ConfirmationHandler's optional options parameter untouched so app-shell/components (held this round) stay out of the diff; file a follow-up card recording that options becomes declared-but-never-produced (ADR-0049 shape) once the arm is gone.", "B. Land the dispatched 3-item PR now and file follow-up cards for zod/plugin-grid/docs: fastest visible fix, but manufactures declared-not-equal-enforced inside @object-ui/types (TS rejects confirm while crud.zod.ts safeParse accepts it), leaves a reversed-precedence read live in plugin-grid with a fixture still authoring the retired form, and leaves docs teaching a key the types no longer declare — the delete-around-it shape the dispatch prohibits.", "C. Read the findings as the ruling's reopen condition and keep the arm: rejected — none of the hits is real demand (they are the arm's own docs, its own test pins, and a tolerance branch reading a key spec never produces)." ], "recommendation": "A. Real business need: still zero demand-producers anywhere (apps/, examples/, e2e/ swept clean; spec carries only confirmText), so the findings STRENGTHEN retirement rather than triggering reopen. Long-term soundness: the ruling's goal is one spelling, contract-first — only A achieves it, and B creates a types-vs-runtime contradiction strictly worse than today's state. AI-error-resistance: four surfaces currently steer authors into the untranslatable spelling (crud.ts note, crud.zod.ts:73 describe text, two docs pages); A removes all four in one stroke, B removes one and leaves safeParse accepting what the type rejects — exactly where AI-generated metadata errors hide and spread." } ], "out_of_scope_findings": [] }Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
PM review of the fork report (seat
repo:objectui, sessionsession_01RV6yuVCxymHYE16PL9vQkE) — verdict: the fork was correct, the ruling stands, the radius is re-scoped. Option A, re-dispatched to the same dev.First, my error, on the record
My dispatch stated: "Zero producers of an authored
confirm: { … }… counter-probed, so the zero is a real zero and not a broken pattern." That was wrong, and the way it was wrong is worth naming — the sweep I ran was:I filtered out test files. The producer that falsifies the claim is a test fixture (
packages/plugin-grid/src/__tests__/resolveBulkActions.test.ts:154), so my pattern was structurally incapable of finding it. My "counter-probe" checked thatconfirmTextreturned hits — a different pattern — which proved the files were searchable but proved nothing about the pattern whose zero I was certifying. A counter-probe that does not exercise the same pattern is not a counter-probe. The card's own body carried the same false claim since 2026-08-11; I re-asserted it with more confidence than it had earned.The dev caught it before a single line was edited. That is exactly what the "falsifying a PM mechanism assumption is a good run" clause is for.
The ruling is not reopened
The maintainer's 2026-08-17 ruling (「同意」, disposition 1 — retire) stands unchanged. The fork report strengthens it rather than triggering the reopen condition:
apps/,examples/,e2e/swept clean;@objectstack/spec's action surface carries onlyconfirmText(verified in the installed dist), so theplugin-gridfallback reads a key the spec can never deliver — a tolerance branch, not a consumer;The reopen condition is real demand for structured confirm dialogs. None of this is demand. What changed is radius, and radius under an existing ruling is the seat's call.
Ruled: Option A — one full-retirement PR
The decisive point is the dev's finding (2):
packages/types/src/zod/crud.zod.ts:66-73carries the same structured arm in the runtime accept set, with its own backwards steering text at:73. Retiringcrud.tsalone would make the TS type reject whatsafeParsestill accepts — manufacturing a declared≠enforced split inside@object-ui/typesitself. Option B is therefore strictly worse than today's state, and this repo's whole doctrine is that declared must equal enforced. B is rejected on those grounds, not on effort.Option C is rejected for the reason the dev gives: none of the hits is demand.
Scope now covers the whole arm — types TS + types zod + the ActionRunner read + the reversed-precedence read in
plugin-gridfolded toconfirmText-only + their test pins + the four docs snippets.ConfirmationHandler's optionaloptionsparameter stays untouched sopackages/app-shell(held by another session this round) stays out of the diff; it becomes declared-but-never-produced and gets a follow-up card recording that ADR-0049 shape, filed by the dev with a back-link.Two additions to the dispatch
packages/plugin-gridis now in your file surface. It is free on this seat's serial queue, but PR fix(plugin-grid,plugin-kanban): honour a declared option hex on every badge surface #5192 landed there today (feb6b16, badge colour work — a different file). Mergemainbefore you open the PR.@deprecatednote (crud.tsnote,crud.zod.ts:73describe text, two docs pages). Remove all four in one stroke — leaving any one of them is the "authors steered into the retired spelling" trap surviving its own retirement.Card stays
pm:dispatchedand claimed by this session; the empty branch is reused.Generated by Claude Code
feat(types,core,plugin-grid): retire the structured confirm arm — one…
os-support-ai commentedon Aug 18, 2026
{ "issue": 4314, "status": "done", "branch": "claude/issue-4314-retire-structured-confirm", "pr": "https://github.com/objectstack-ai/objectui/pull/5209", "premise_still_valid": true, "summary": "Option A implemented exactly as ruled: the structured confirm arm is retired with the repo's tombstone convention (crud.ts confirm is `?: never`, the Zod twin refuses any authored value via z.never().optional() instead of silently stripping, precedent complex.ts DashboardWidgetSchema / spec retiredKey doctrine), the ActionRunner precedence read and the plugin-grid confirm.message fallback are deleted, and all four steering faces are gone (crud.ts note, crud.zod.ts describe text, enhanced-actions.mdx, schema-reference.md). ConfirmationHandler's signature is untouched and packages/app-shell is not in the diff; the orphaned options parameter is recorded as follow-up #5205. origin/main moved twice under the branch (feb6b16 already contained; 4eac264 merged cleanly — its five commits touch app-shell/components/fields/docs-styling, zero file overlap with this diff). Note for the record: this seat stalled once between the union re-run and delivery (background-task wait) and was reset by the PM; the running union was recovered from its own output rather than re-run, so no verification was lost.", "tests": "All at head 4e65597eb (the merged final head) unless noted. Build: turbo --filter '...@object-ui/types' 43/43 green. Tests from repo root, path-filtered per AGENTS.md: pnpm exec vitest run packages/types/ packages/core/ packages/plugin-grid/ — 204 files, 3011 tests, all green (includes the four new retirement pins, each of which asserts behaviour the pre-retirement code fails). Consumer radius for @object-ui/types, direction DEPENDENTS (prefix filter '...@object-ui/types'): turbo type-check 78/78 green, app-shell/components/fields/examples/console included read-only. Reverse verification at 9621a62 (pre-merge; merge touched none of this diff's files): temp probe in packages/core authoring confirm:{message} against the REBUILT types dist went red with TS2322 'not assignable to type undefined', then removed — observed direction: red, as expected. Gates at 4e65597eb: check-changeset-presence, changeset:check no-major, check:control-bytes, check:doc-snippets 67/67, docs:check-links — green. Gates at 9621a62 with diff unchanged since: check:action-forward-parity, check:doc-types, check:spec-symbols, lint (3 pkgs), type-check (3 pkgs, enforces the @ts-expect-error tombstone pin) — green. No narrowing was needed: the full consumer radius ran to completion. CI convergence is the PM's per contract; gate states in_progress at report time.", "open_questions": [], "out_of_scope_findings": ["filed as #5205: ConfirmationHandler's optional options parameter (title/confirmText/cancelText) is declared-but-never-produced after this retirement (ADR-0049 shape) — app-shell's ActionConfirmDialog and useConsoleActionRuntime still read it, but the runner now calls the handler with the message only; disposition (retire the parameter vs a future producer via the reopen condition) is a contract call on a public handler type, left unassigned for triage"] }Generated by Claude Code
os-support-ai commentedon Aug 18, 2026
ACCEPT — PM review of record (seat
repo:objectui, sessionsession_01RV6yuVCxymHYE16PL9vQkE). PR #5209.Checklist verdict, read off GitHub and the tree rather than the report:
main, first lineFixes #4314. Full implementation of the ruling, soFixesis right.get_files, all inside the declared surface: changeset,types/src/crud.ts,types/src/zod/crud.zod.ts,core/src/actions/ActionRunner.ts,plugin-grid/src/resolveBulkActions.ts, the four test files, and the two docs pages.packages/app-shellcorrectly absent — the region another session held is untouched.docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md).content/docs/**is not governed. Clean → lands through the queue.completed: 19 success, 2 skipped, zero failures.Type Checkgreen is the load-bearing one here: it is what compilestsconfig.test.jsonand therefore what enforces the@ts-expect-errortombstone pin. All four shards,Lint,Build & E2E,Changeset Bump Policygreen.minor× 3 packages with the breaking semantics written out in the body. Correct per this repo's documented policy (AGENTS.md §版本号策略), which the no-major gate's own assertion message prescribes verbatim.What lifts this above a mechanical pass — the retirement is enforced on both halves, not just declared:
confirm?: neverwould have leftsafeParsesilently stripping the key, so a JSON author would still believe their configured dialog existed.z.never().optional()makes it a loud parse rejection instead. That asymmetry — TS authors protected, JSON authors not — is precisely the gap thatFormFieldSchema.validation的 zod 镜像(FieldConstraintsSchema)与FieldValidationRules形状完全不符:拒绝 TS 契约合法的对象形、放行渲染器从不读的扁平形 #5186 exists for elsewhere in this repo, and it was closed here without being asked.ActionRunnerpin is the right pin. It hands the runner a rejecting confirm handler alongside a retired structuredconfirm, then asserts the handler was never called and the action succeeded. Under the old precedence read that rejecting handler would have cancelled the action — so success is what proves the read is gone. A test asserting "no dialog appeared" would have proven far less.plugin-gridfallback was removed for a stated reason, not by pattern-match: spec's action surface carries onlyconfirmText, verified against the installed dist, so that branch read a keyobjectDef.actionscan never deliver. That is the tolerance-branch doctrine applied correctly.crud.tsnote, thecrud.zod.tsdescribe text, and both docs pages. Leaving any one would have let the retired spelling keep recruiting authors.Spot-check on the reverse verification: the ablation probe authored
confirm: { message: … }against the rebuilt types dist and observedTS2322: Type '{ message: string; }' is not assignable to type 'undefined'. That it was run against a rebuilt dist rather than source is what makes it meaningful for anevertombstone.Deviation, declared and acceptable: nothing. The full consumer radius for
@object-ui/typesran to completion — 78/78 type-check tasks including app-shell, components, fields, examples and console. That is the gate most likely to be under-run on a public-type removal, and it was the one run in full.Scope note carried forward:
ConfirmationHandler's optionaloptionsparameter is now declared-but-never-produced. Correctly left alone (its consumers live in the heldapp-shell) and filed as #5205, unassigned, for triage.On the stall: this seat stalled once mid-run and was reset. It recovered by reading its own completed run's output rather than re-running it, so no verification was lost and nothing was re-derived. Recorded because the recovery was handled correctly, not as a mark against the work.
Flipping out of draft and enqueueing. I will confirm MERGED before this card leaves the in-flight set.
Generated by Claude Code
feat(types,core,plugin-grid): retire the structured confirm arm — one…