Uh oh!
There was an error while loading. Please reload this page.
retire(core): drop the legacy params.newTab fallback on url actions (#4097) - #4262
Merged
Merged
Conversation
…#4097) Executes the objectstack#6828 maintainer ruling of 2026-08-10 (contract half shipped in objectstack PR #7375): the url-side readings of an object-form `params` are RETIRED, not renamed. `openIn: 'new-tab'` is the sanctioned spelling and already outranked the fallback. The fallback could only ever fire on an object-form `params`, which the spec has always refused (`params` is `z.array(ActionParamSchema)`), so no validated stack could reach it. Removing it also closes the collision hazard where a params dialog declaring a field named `newTab` had the user's own input silently steering navigation. The `interpolateTarget` ${param.X} scope half of the card is NOT removed — four internal callers synthesize a non-array `params` as the runtime value bag it reads. See the PR body and #4097 for the measurement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 11, 2026 09:55
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4097
Executes the consumer half of the objectstack#6828 maintainer ruling (2026-08-10) — but only one of the card's two removals. The card's own binding measurement clause fired: the other half is not dead, and its retirement needs a decision. Details below.
What this PR removes
ActionRunner.navigateToread a legacyparams.newTabescape hatch, belowopenInand above the external-URL heuristic:That read is gone.
openIn: 'self' | 'new-tab', the legacynavigate.newTabmodifier on thenavigationshape, and the external/relative default are all unchanged.Nothing that ever validated can regress.
paramsis declaredz.array(ActionParamSchema), so an object-formparamshas always failed the props parse — the fallback could only fire on a stack the spec refuses. Removing it also closes a collision hazard: a params dialog declaring a field namednewTabhad the user's own collected input silently steering navigation.The internal-producer sweep (the card's binding measurement clause)
The card asked whether any objectui-INTERNAL caller synthesizes a non-array
params— the corpus grep behind the ruling covered authored metadata only. It does. Eight sites, in four packages:packages/core/src/actions/ActionRunner.ts:836action.params = { ...priorParams, ...collected }— the params dialog's collected values, written back onto the actionpackages/plugin-grid/src/ObjectGrid.tsx:2076params: { ...params, _selectedIds: ids }— aggregate bulk dispatchpackages/plugin-grid/src/ObjectGrid.tsx:1721dispatch.params = { _rowRecord: r }packages/components/src/renderers/layout/containers.tsx:1287/:1289dispatch.params = { _rowRecord: record }/{ ...rawParams, _rowRecord: record }packages/app-shell/src/hooks/useNavActionDispatch.ts:93dispatch.params = { ...actionDef.params }— nav-item value bagpackages/components/src/renderers/action/action-button.tsx:94Array.isArray(schema.params) ? { actionParams } : { params }packages/components/src/renderers/action/action-group.tsx:251,action-icon.tsx:83,action-menu.tsx:233action.paramsasRecordEffect on the two halves of the card, which is opposite for each:
params.newTab(removed here). None of these bags ever carries a navigation directive — they carry collected dialog values,_selectedIds,_rowRecord. Sites 5–7 copy an authored objectparamsthrough, and that shape is spec-refused. Soparams.newTabis genuinely unreachable from any legitimate producer. Removal is safe.${param.X}interpolation scope (NOT removed — see below). Sites 1–4 exist precisely to feed it. It is the live read point for runtime-injected param values.Why the interpolation-scope half is not in this PR
interpolateTarget's non-arrayparamsbranch is live, not dead vocabulary. Three independent confirmations, all already in the repo or in the shipped contract:@objectstack/spec@17.0.0-rc.6, names the mechanism as surviving: "${param.X}interpolates a value collected by the params dialog".params—ActionRunner.resultDialog.test.ts:238(${param._selectedIds}injected by an aggregate bulk dispatch) andActionRunner.test.ts:660(${param.owner}on thenavigationalias). Deleting the branch turns both red.ActionRunner.bodyExtra.test.ts:190already documents the host-stashed bag as legitimate and explicitly not an authored payload.The branch cannot distinguish an authored object from a runtime-injected one, because ActionRunner writes the collected values into the same
action.paramsfield the authored shape would occupy. Separating them needs a non-authorable channel for injected param VALUES threaded through all four packages above — and it would also have to moveexecuteAPI's body base (asRecord(action.params)),executeModal'sparams?.schema, andserverActionHandler. That is a cross-package runtime-contract change, not a retirement, and the ruling supplies no spelling to move to: its sanctioned spellings (targetinterpolation,openIn) are authoring spellings, while these are internal injection channels that never pass through the spec parse.Per the card's clause — "any producer found must move to the sanctioned spelling in this change, or if that move is non-trivial, STOP and report" — that half is reported for a ruling rather than guessed at.
Part of #4097, notFixes, for exactly this reason.Pins
New file
packages/core/src/actions/__tests__/ActionRunner.urlParamsRetirement.test.ts, 10 cases:openIn: 'new-tab'opens a new tab;openIn: 'self'keeps an external url in the same tab.params: { newTab: true }gets no new tab; and it cannot overrideopenIn: 'self'.navigate.newTabescape hatch survives.${param.X}interpolates values the params dialog collected (URL-encoded), and degrades an uncollected token to empty string.${ctx.recordId}/${ctx.user.id}/${ctx.selection.ids}untouched.Verification
ActionRunner.tsonly, restore after): predicted and measured 1 red / 9 green — only the negative retirement pin goes red; every positive and control stays green through the revert, which is the proof they pin live mechanisms this change does not alter.vitest packages/core/ packages/components/src/renderers/action/ packages/app-shell/src/hooks/ packages/plugin-grid/src— 165 files / 2636 tests pass.pnpm --filter @object-ui/core type-check— clean (tsc --noEmit+ typetests).eslint --no-inline-configon changed files — 0 errors (25 pre-existingno-explicit-anywarnings, none in the new file).check:control-bytes— OK (3977 files); plus a targeted control-byte self-scan of the changed files, clean.check:action-forward-parity(ci(gate): diff each action renderer's forward whitelist against the keys the runtime reads (#4050, #4192) #4207) — green, and the delta is ZERO. Measured on both sides of the revert: identical "41 runtime-read keys from 3 consumers; 19 justified omissions, 7 known gaps" and identical per-surface owes/forwards. Expected: the gate extracts property accesses bound to the ActionDef parameter, soaction.params?.newTabcontributedparams, andparamsis still read at four other sites.Changeset:
@object-ui/corepatch.Generated by Claude Code