Uh oh!
There was an error while loading. Please reload this page.
feat(core,types,components)!: retire ActionRunner's legacy onSuccess chained-callback channel (#5934) - #7065
Conversation
…chained-callback channel (objectui#5934) The maintainer ruled (2026-08-31, director batch #5 item #1) to retire the runner's pre-spec onSuccess meaning: ActionDef | ActionDef[], dispatched through executeChain. The spec strict-refuses a callback shape inside onSuccess at parse, so no validated metadata could ever reach the channel, and a producer census with a positive control found zero producers outside the channel's own pins. - core: ActionDef.onSuccess now derives the spec's { navigate, openIn } block (SpecActionInput['onSuccess']); handlePostExecution no longer falls through to executeChain; readOnSuccessNavigation is a shape guard on unparsed rows, not a discriminator. onFailure is untouched. - types: UIActionSchema declares onSuccess, derived from the spec, so the renderer view spells the key the four action surfaces forward. - components: the four renderers forward onSuccess uncast. - tests: the channel's pins now pin its absence; the spec-path pins are untouched and stay green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
…release model objectui's major is pinned to the @objectstack family major; the repo's own breaking changes ship as minor with the break spelled out in the changeset body (scripts/check-changeset-no-major.mjs). The BREAKING declaration in the body is unchanged and now names the model explicitly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB
✅ Console Performance Budget
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
Size Limits
|
os-sam
commented
Aug 31, 2026
Correcting a hold I placed on this PR in error. Auto-merge (squash) is now armed at I had marked this PR ready but deliberately withheld auto-merge, on the stated reason that it is a published-surface narrowing and therefore had to go through the contract-review chain. That reason does not apply to this card. I carried it over from #6881's ruling, which is a different card. The two rulings differ exactly here:
So this PR was waiting on a gate its ruling never prescribed. Holding it did not make anything safer; it just stalled a PR whose floor had already been cleared by the maintainer, in the very comment that authorised the work. For the record, the reason I noticed: #7066 carried State at arming: ⛔ #7066 remains held and unarmed — its ruling does prescribe the review chain, and that hold stands. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
os-sam
commented
Aug 31, 2026
Dequeued at 20:12:13Z with What I established
⛔ What I could NOT establish, stated as a gap rather than papered overI do not have the failing check's name or log. The ⛔ I am also not calling it a flake. "Flake" is not a root cause, and I have not earned that word here. What I didRe-enabled auto-merge (squash) on all three — one attempt, on the strength of the evidence above and because re-queueing is the remedy this repo's own dequeue notice prescribes for a grouped-queue failure. ⛔ Not a fix, because there is nothing here to fix that I can see; and ⛔ not an empty commit or a close/reopen to kick CI. If it dequeues a second time, that is real and it is this PR's to root-cause — not another re-queue. The next reader should pull the failing job's log directly from the merge-queue run rather than the workflow listing, which is where I ran out of reach. Context: the Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#5934
Implemented in session claude.ai/code/session_013hfmP9hoMd3dJwTh85J4yB (dev seat). Executes the verbatim maintainer ruling on the card (hotlong, 2026-08-31, director batch #5, item #1): 「退役
ActionRunner的传统onSuccess回调通道(ActionDef | ActionDef[]形),onSuccess收敛为 spec 声明的唯一语义{ navigate, openIn }」. All three must-answers were re-verified independently before any edit; both hold, so the BREAKING route applies (the ruling's downgrade clause was not triggered).Must-answer 1 — the member IS on the exported
ActionDef(verified, holds)Chain, measured on base
85b495795:packages/core/src/actions/ActionRunner.ts—export interface ActionDefspans lines 113 to 441 and contains line 324onSuccess?: ActionDef | ActionDef[](the card said line 323; one line of drift on current main, same member).src/actions/index.tsline 9export * from './ActionRunner.js'→src/index.tsline 45export * from './actions/index.js'→ package exports map.→dist/index.d.tsline 29 →dist/actions/index.d.tsline 8 →dist/actions/ActionRunner.d.tsline 104export interface ActionDef, line 295onSuccess?: ActionDef | ActionDef[].So the removal is a breaking change to the published surface of
@object-ui/core. Per this repo's release model (scripts/check-changeset-no-major.mjs: objectui's major is pinned to the@objectstackfamily major, and the repo's own breaking changes ship asminorwith the break spelled out in the changeset body), the changeset isminorand opens with the BREAKING declaration.Must-answer 2 — zero-producer census, re-run with a positive control
Procedure: repo-wide grep of every
onSuccessoccurrence outside dist (286 hits, 70 files), filtered to writer-shaped hits (onSuccess:with a value, excluding type declarations and property reads), then each writer classified by the ruling's discriminator — is the value an ActionDef-shaped object written onto an action object, as opposed to a React form callback on a different surface, a spec-shaped{ navigate, openIn }block, or a forward of a read value.Producers of the retired callback shape found: exactly three, all test fixtures of the channel itself or of its discrimination —
packages/core/src/actions/__tests__/ActionRunner.test.tslines 1028 and 1056 — the channel's own pins (rewritten by this PR to pin the absence);packages/app-shell/src/utils/__tests__/consoleServerAction.test.tsxline 415 — a negative fixture proving the shape is NOT mistaken for the declared hop (still passes unchanged, because that behavior survives retirement).Zero producers in app code, metadata, or examples. The many form-component
onSuccess:writers (handleFormSuccess, arrow functions) are React callbacks onObjectFormSchemaand friends — a different surface, excluded exactly as triage instructed.Positive control: the identical procedure run against
chain(adjacent member, certainly produced) reports non-zero producers —ActionRunner.test.tslines 970, 988, 1007 write ActionDef arrays onto action objects, among 50 writer-shaped hits. The instrument also distinguishes shapes on the same key: it found the spec-shapedonSuccess: { navigate: ... }producers (e.g.consoleServerAction.test.tsxline 390). The census is a live measurement, not a broken instrument reading zero everywhere.Also re-measured on the installed
@objectstack/spec17.2.0 pin, with a valid-fixture control: a bare valid action parses;onSuccess: { navigate: '/x', openIn: 'newTab' }parses;onSuccess: { navigate: '/x', bogus: 1 }is refused withunrecognized_keys;onSuccess: { type: 'notify' }andonSuccess: [{ type: 'cb1' }]are refused. The channel was unreachable from validated metadata, exactly as the card claimed.Published-surface delta, by specifier
@object-ui/core— BREAKING (minorbump by the pinned-major release model; the break is declared in the changeset body).import type { ActionDef } from '@object-ui/core':ActionDef['onSuccess']wasActionDef | ActionDef[](chained callbacks the runner dispatched throughexecuteChainon success). After: derived from the spec —SpecActionInput['onSuccess'], the closed strict block{ navigate: string, openIn?: 'self' | 'newTab' }, following the interface's own derivation doctrine (never hand-copied). Runtime: a callback-shaped value now gets NO reading — no handler dispatch, no navigation, the action's own result untouched. Before, it was executed as a chain. Consumers who drove the channel programmatically migrate tochain(unchanged, declared) or author the spec block.ActionDef['onFailure']is NOT changed — the spec declares no such key, so it has only ever had its one runner-native meaning; the ruling coversonSuccessalone.@object-ui/types— additive (minor).import type { UIActionSchema } from '@object-ui/types': the renderer view now declaresonSuccess, derived from the spec'sActionSchema.onSuccess. Before, the key reached renderers only through casts; nothing an existing consumer imported changes shape.@object-ui/components— patch, no API change. The four action renderers forwardonSuccesswithout theas anycasts the ruling ordered removed:action-button.tsx,action-group.tsx,action-icon.tsx,action-menu.tsx. Same key, same value, now type-checked. (Measured why the casts existed:UIActionSchemais a closed interface — the index signature atui-action.tsline 625 belongs toActionContext, not to it — so the read side needed the key declared; the types change above is the minimal enabler, and the write side is covered by the core retype.)Behaviour unchanged for every consumer that did not use the removed member
ActionRunner.onSuccessNavigation.test.ts(SPA hop, interpolation depth, openIn spelling isolation),action-onSuccess-forward.test.tsx,action-forward-precedence.test.tsx,consoleServerAction.test.tsx(declared-hop precedence, redirectUrl convention, legacy-shape non-confusion).365f96c29; the only later commit,309ca3ec8, changes a single.changeset/*.mdfile which none of those runs read (declared narrowing), and both changeset gates —node scripts/check-changeset-no-major.mjsandpnpm changeset:check— were re-run green at309ca3ec8: core actions suite + components action renderer suite + the whole@object-ui/typestest dir + the app-shell server-action test — 117 files, 1677 tests, all passing. Type-checks green for@object-ui/types,@object-ui/core,@object-ui/components,@object-ui/app-shell(after building the dependency closure). Gates:check:action-forward-parity,check:spec-symbols,check:control-bytesall green.pnpm exec eslinton every touched file: 0 errors (135 pre-existing warnings, allno-explicit-any/style classes already present in those files).check:readme-exportsis NOT MEASURED locally: it exits 1 in this worktree with 69 findings, every one of them "type entry dist/index.d.ts is not on disk — run pnpm build first" for packages outside this diff (app-shell, cli, plugin-ai, plugin-gantt, ...), zero findings for core/types/components READMEs. It needs the full workspace built, which CI does before running it.Reverse verification (both directions, from the committed state)
ActionRunner.ts(channel alive) while keeping the new pins — mutation proven on disk by marker count (theexecuteChainfall-through line: 0 before, 1 after mutation). Result: exactly the 3 retirement pins go red (3 failed, 91 passed), every spec-path pin stays green. Restoration proven three ways: emptygit diff HEADfor the path, marker count back to 0, andgit hash-objectof the working file equal to the HEAD blob hash. The pinned tests import the runner by relative path, so this leg is source-compiled — no stale-dist hazard.@object-ui/componentsimportingActionDeffrom@object-ui/core—onSuccess: { type: 'notify' }is refused (TS2353, and the error text quotes the new derived shape{ navigate: string; openIn?: "self" | "newTab" }, proving the REBUILT d.ts is what consumers resolve, not a cache); the array form is refused (TS2741, navigate missing); the spec block compiles. Zero non-probe errors; probe removed; tree clean.Retirement conventions
ADR-0087 registries /
retirementTombstone()were judged NOT applicable and here is why: that machinery retires authored spec metadata keys (parse-level tombstones so authoring a retired key is a loud rejection). This card retires a runtime channel on a published TypeScript type — the authored keyonSuccessis not retired, it converges on its declared spec meaning, which the spec already enforces at parse. The compile error for the callback shape (measured in the type leg above) is the analogous authoring-time loud rejection. The in-code documentation records the retirement at the declaration, the post-execution site, and the shape guard.Follow-on note: this is the sequential follow-on to #5493, which remains closed (landed via #6304) — its four forward sites are exactly the cast sites this PR un-casts. #5493 is not re-opened or otherwise affected here.
Generated by Claude Code