Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

ActionRunner's non-array params reads are dead vocabulary — the url interpolation scope and params.newTab were retired by the spec ruling on objectstack#6828 (finding) #4097

Closed

Description

@os-zhuang

Filed unassigned per contract-first: the contract half landed in objectstack, this is the consumer half. Not a live defect — a removal that is now authorized and was not before.

The ruling

objectstack maintainer ruling, 2026-08-10 (objectstack-ai/objectstack#6828, comment 5237221393), verbatim:

A key with three meanings and no authorized spelling for the third is the de-facto-contract shape AGENTS.md #0.1 forbids; the schema already refuses it and there is no live victim, so retirement costs nothing. Interpolation scope + newTab on a type:'url' inline action either gets its own properly-named key via a spec proposal (if someone demonstrates pull) or stays out of the vocabulary. Document the refusal in the error guidance so authors reaching for it learn the sanctioned spellings.

So object-form params is not getting a spelling on a type:'url' action. It never had one — packages/spec/src/ui/action.zod.ts has always declared params: z.array(ActionParamSchema), so the shape fails the props parse — but until this ruling the question "should it get a key?" was open. It is now closed the other way.

What that makes dead in this repo

Both reads are in packages/core/src/actions/ActionRunner.ts:

  1. interpolateTarget — the non-array params interpolation scope. It takes a non-array params as the ${param.X} substitution scope for the target URL (around :1091 as measured on objectui main for objectstack#6828 on 2026-08-09 — re-anchor by content, not line). The ${param.X} mechanism itself is live and stays: it resolves values the params dialog collected. What is dead is the branch that accepts a statically authored object as that scope — no author can produce one, because the spec refuses the shape at parse time.
  2. executeUrl — the legacy params.newTab escape hatch (around :1133-1136, same caveat), read below openIn in priority. openIn: 'self' | 'new-tab' is the declared key and already wins; the fallback can only fire on a shape that cannot be authored. Superseded in practice by URL action 无法声明式"新标签页打开";params 字段语义重载导致 newTab 被当成用户输入项弹框 #2043's fix, and now formally retired rather than merely outranked.

Why it is safe to remove

  • Nothing authors it. objectstack#6828 grepped origin/main in all three repos for inline-action sites (objectstack examples, cloud service-tenant pages, objectui). Every type:'url' inline action is target-only; the only object-form params anywhere was the showcase's api submit button, which objectstack#5777 migrated to bodyExtra.
  • The spec refuses it and now says so. The refusal message used to prescribe bodyExtra for every action type, which for a url action was a wrong instruction (an api request-body key is neither an interpolation scope nor a new-tab control). The objectstack PR for fix(data-objectstack): lower rule-shaped filter arrays on aggregate()'s analytics path #6828 branches that guidance: api ⇒ bodyExtra; url ⇒ put static values in the target string, and use openIn: 'new-tab' — naming both retired readings as retired, not renamed.
  • Removal cannot regress a validated stack, because a stack carrying either shape never validated.

What a fix looks like

  • Drop the non-array branch from interpolateTarget, keeping the dialog-collected ${param.X} scope and ${ctx.X} untouched.
  • Drop the params.newTab fallback from executeUrl, leaving openIn plus the existing external/relative-URL default.
  • Pin the sanctioned spellings so the removal cannot quietly come back: openIn: 'new-tab' opens a new tab, and a url action whose target carries ${param.X} interpolates from the collected dialog values.

Worth measuring before deleting: whether any objectui-internal caller synthesizes a non-array params itself (a renderer or console shim constructing an action object rather than parsing an authored one). The corpus grep above covered authored metadata; a synthesized caller inside this repo would not appear in it, and would have to move to the sanctioned spelling in the same change.

Related: objectstack-ai/objectstack#6828 (this card's contract half — the ruling and the error guidance), objectstack-ai/objectstack#5777 (the type:'api' half, ruled 2026-08-06 → bodyExtra), #2043 (the original newTab-in-params overload that produced openIn), #2997 (element:button's inline action shape).

Activity

  1. claude commented on Aug 10, 2026

    @claude
    Contributor

    Triage: pm:queue.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Contributor

    CLAIM (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3) — executing the objectstack#6828 ruling's consumer half; dispatching now.

    • Branch claude/issue-4097-retire-params-reads, worktree objectui-issue-4097 (S/M container)
    • Scope = the card's fix shape: drop interpolateTarget's non-array-params scope branch (the dialog-collected ${param.X} scope and ${ctx.X} stay), drop executeUrl's legacy params.newTab fallback (openIn + the existing default stay), pin the sanctioned spellings so the removal cannot quietly return. The card's own measurement clause is binding: before deleting, sweep for objectui-INTERNAL synthesized non-array params producers (the corpus grep covered authored metadata only) — any found must move to the sanctioned spelling in the same change or the removal stops.

    Generated by Claude Code

  4. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Contributor

    Delegated ruling on the open question: B — the objectui half is discharged (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3; veto window open) — and ACCEPT of PR #4262 as the card's shipped half.

    The measurement clause did exactly what it was for: the sweep found eight internal producers and proved the card's premise HALF-false — interpolateTarget's non-array scope is not dead authored vocabulary but the live read point of an internal runtime value-bag channel (_selectedIds, _rowRecord, dialog-collected values), confirmed three independent ways including the spec's own refusal message naming the mechanism as surviving. The #6828 ruling's target was the AUTHORING vocabulary — "a key with three meanings and no authorized spelling for the third" — and that is now fully handled: the spec refuses the third meaning at parse with guidance (upstream, shipped), and PR #4262 removes the newTab runtime read (with the bonus of closing the dialog-field-named-newTab navigation-hijack hazard). What remains is a channel no author can reach or observe; option A would spend a four-package runtime-contract migration to remove an already-unreachable shape — plumbing expansion with zero pull. Option C's heuristic can misfire on legitimate collected bags; declined.

    Residual scope (this card stays open, re-scoped to one S-rider): the in-code documentation that makes ruling B discoverable — a comment block at interpolateTarget's non-array branch naming it an INTERNAL runtime value-bag channel (not an authoring surface), citing #6828 + this ruling, and flagging the one hypothetical A named (a future spec loosening would silently re-admit an authored scope — the upstream refusal pin and the two local pins are the guards). It rides the NEXT card touching ActionRunner.ts (#4046 is the queued candidate) rather than minting a comment-only PR. PR #4262 lands now as Part of #4097.


    Generated by Claude Code

  5. yinlianghui commented on Aug 11, 2026

    @yinlianghui
    Contributor

    CLOSED — 全部两半均已落地 (session session_017Qqyix2QcnpUC9XeYVDzx3)。

    Ruling B 全文与否决窗口记录在本卡此前的裁决评论;spec 侧词汇状态归 objectstack#6828 线,不在本卡追踪。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions