Skip to content

action rows: follow @objectstack/spec retiring a page action row's own patch — the Undo snapshot must read the named action's patch, and the renderers and registry stop forwarding and publishing the row key #13022

Closed

Description

@objectstack-fleet

Filing gate ①: a follow-up that landed spec work owes (objectstack-ai/objectstack#24394, PR objectstack-ai/objectstack#24442). The reach is named code at the console pin and a pin test that reds on adoption. Filed by objectstack domain:spec seat 1 (seat post objectstack-ai/objectstack#6017) · os-warren · session session_01WnW7HvfnfHbLg1fpkHX3Mu. ⛔ Not a claim; grade and lane are this repository's triage.

Reader who acts: this repository's triage routes it to the seat that owns packages/core actions and the action renderers. It becomes due when objectui moves to an @objectstack/spec carrying objectstack PR #24442.

What changed upstream

@objectstack/spec 18 retires a page action row's own patch (action:button, action:icon, and the action:group / action:menu member) with a retiredKey() tombstone. A row's operation: 'update' runs the declared action its name names, and the platform action route writes THAT action's patch. The row's patch was never sent; this console read it only for its Undo snapshot. A row with operation: 'update' now also needs a name.

What objectui still does (read at the objectstack .objectui-sha pin 0707b4ae37)

  1. Undo snapshots the wrong fields. packages/core/src/utils/predicate-fields.ts:155 (undoableWrittenKeys) reads the row's patch. After the retirement no row carries one, so a row's snapshot covers only the collected params, not the named action's patch the route wrote. The snapshot must take the declaration's patch (ActionRunner.ts executeUpdateOperation, about :1761–:1802).
  2. The four renderers forward the row key. Examples: packages/components/src/renderers/action/action-button.tsx:323 (patch: schema.patch), and the same in action-icon.tsx, action-group.tsx and action-menu.tsx.
  3. The registry publishes a patch input on action:button / action:icon. objectstack's tracked sdui.manifest.json carries it, so its html-tier JSX gate still accepts a JSX patch until the pin bump regenerates the manifest.
  4. A pin reds on adoption. apps/console/src/__tests__/registry-inputs-spec-parity.test.ts:5239 ("the harvested tombstoned keys are recognised, not exempted — and not published either") will red once objectui adopts a spec with this tombstone while the registry still publishes the input.

Acceptance

  • Undo on an operation: 'update' row snapshots the fields the route writes: the named action's patch, plus the collected params.
  • No renderer forwards a row patch, and the registry publishes no patch input on the rows; the parity pin passes on the adopted spec.

Dedupe


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 + target:v18 · domain:ui · area:api · pm:queue · lands with #12971 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T05:55Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 19, 2026
  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 9 · on the maintainer's order, verbatim 「13022 12971 两个p1 插队,并发临时调到3,这两个处理完再下班」 (priority:p1; triage 6260698656)
    Session: session_0188TsgPV8kQXfuRuKmkj2ks
    Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13022-action-row-patch
    Worktree: objectui-issue-13022
    Domain: domain:ui
    Seat: domain:ui#1
    File surface:

    • packages/core/src/utils/predicate-fields.ts (undoableWrittenKeys) and packages/core/src/actions/ActionRunner.ts (executeUpdateOperation): the Undo snapshot reads the named action's patch, plus the collected params;
    • the patch forwarding in packages/components/src/renderers/action/action-button.tsx, action-icon.tsx, action-group.tsx and action-menu.tsx, and the tests that pin it (action-forward-precedence.test.tsx among them);
    • one changeset.
    • ⛔ Not in scope: the inputs blocks of action-button.tsx and action-icon.tsx (the published patch input), and registry-inputs-spec-parity.test.ts.
      • At the current pin the spec still declares the row patch, so the parity test's REVERSE direction requires the input.
      • Removing it is pin-coupled, and it rides objectui#12971 (dispatched in parallel on claude/issue-12971-pin-bump-main).

    Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus
    Clause-②: no
    Clause-② reason: no published key, input or schema moves here. The runner stopped posting a row patch in objectui#12941. This changes which fields the Undo snapshot captures, and drops a forwarded value no reader uses.
    Responsibility: the Undo snapshot regression traces to objectstack PR #24442 retiring the row patch, together with undoableWrittenKeys reading the row and not the declaration.
    Thread-read: 6260698656
    Serial constraints cleared: read at objectui main 47508f3b.

    • No open PR touches this surface.
    • action-button.tsx and action-icon.tsx are shared with objectui#12971 (its hunks are the inputs blocks, these are the forwarding lines).
    • Landing order: objectui#12971 lands first. This branch then merges main, re-runs its gates, and goes ready, so that both halves of this card's acceptance are on main when it closes.

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 13022,
      "status": "done",
      "branch": "claude/issue-13022-action-row-patch",
      "pr": "https://github.com/objectstack-ai/objectui/pull/13033",
      "session": "session_0188TsgPV8kQXfuRuKmkj2ks (parent's, mode:subagent)",
      "premise_still_valid": true,
      "summary": "The Undo snapshot of an operation: 'update' action now covers the patch of the declaration the action's name names, plus the collected params. The runner never covers a row's own patch. ActionRunner gains declaredUpdatePatch. It reads the declaration from the objectActions the host publishes beside objectName and objectFields, scoped to the context's own object. When no list was published for that object, or the list does not name the action, it reads the def's own patch: the spread-based hosts hand the declaration in whole. useConsoleActionRuntime and RecordDetailView now publish objectActions: objectDef.actions. action:button, action:icon, action:group and action:menu no longer forward patch; the request body is unchanged. check-action-forward-parity gets four JUSTIFIED entries, because the runner still reads action.patch off whole-dispatched declarations. Zone 2 item 2: the declaration was NOT in hand at the capture site. action:bar draws declarations through these same renderers, so dropping the forward needed the context thread. The call graph is host objectDef.actions, then the ActionProvider context, then new ActionRunner(context), then execute, then executeUpdateOperation, then declaredUpdatePatch, next to captureUpdateUndoData. Zone 1 premise, partly false: predicate-fields undoableWrittenKeys never reads a page row. Its inputs (rowActionDefs, bulkActionDefs, objectActions) are definitions applied whole, so only its doc changed. Item 3: a nameless update row is refused by createServerActionHandler before any request ('Action has no name — a server action must declare one', 0 requests, error toast, no Undo); unchanged. Draft PR #13033, assigned to sunhaolin. Per the dispatch it goes ready only after objectui#12971 lands and this branch merges main.",
      "tests": "Union after the final commit, HEAD a5cd587d (declared narrowing, run outside the verify lock: see deviations). pnpm exec vitest run --maxWorkers=2 over packages/core/src/actions/, predicate-fields.test.ts, packages/components/src/renderers/action/, all 12 useConsoleActionRuntime tests, 9 RecordDetailView action/undo tests, AppContent.undoFallbackObject-11080 and AppContent.undoRedoToast-11080, plugin-grid undoProjectedRow-10404, plugin-form affordanceGrantMap-12082, scripts/__tests__/check-action-forward-parity.test.ts and the -closing-keyword suite, and apps/console registry-inputs-spec-parity, public-block-binding-reach and public-contract: 'Test Files 102 passed (102) / Tests 1761 passed (1761)', exit 0. New pins: ActionRunner.declaredPatch-13022.test.ts (core, 7) and action-row-declared-patch-13022.test.tsx (components, 9; each of the four surfaces hands the runner operation and no patch, and the request body is the same with or without a row patch). Ablation 1, the snapshot change, on committed 9f41095f via objectstack scripts/ablation-replace.mjs (WRAP mode): anchor 'const patch = declaredUpdatePatch(action, this.context, objectName);' became 'const patch = patchMembers(action.patch);'. On disk: anchor x1 to x0, replacement x0 to x1, blob 79aef211708b to 1cb9d3ac99ea. Predicted red, observed red: 6 failed / 10 passed of 16, exactly the 3 core and 3 components Undo cases. Restore: blob 79aef211708b equals HEAD, git diff HEAD empty, rerun 16/16 passed. Ablation 2, the renderer forward: patch: schema.patch put back into action:button. The first attempt was refused by the tool (the replacement contained the anchor; no command ran; restored), and the re-run used a two-line anchor. On disk: anchor x1 to x0, blob 70dbbd6e83d5 to c825cec7358a. Observed red: 3 failed / 14 passed of 17 (the two button key-order pins and the button no-patch pin). The request-body pin stayed GREEN with the forward put back, which proves Zone 2 item 1 (no request byte moves). Restore: blob equals HEAD, git diff HEAD empty. No dist leg: the tests import ../ActionRunner relatively, and vitest aliases @object-ui/core to src.",
      "gates": "Local, every exit code captured before any pipe, on the final code. check:action-forward-parity=0 (26 justified omissions, 0 known gaps; it was 1 before the JUSTIFIED entries, naming patch on all four surfaces). check:new-line-citations=0 (VERDICT 0 new citations). check:test-path-roots=0. check:control-bytes=0. check:changeset-claims=0. changeset:check=0. node scripts/check-changeset-presence.mjs=0 (13 source files of 3 released packages, 1 changeset). check:handler-key-reads=0. check:pending-changeset-literals=0. check:spec-symbols=0. check:doc-fences=0. check:doc-json=0. check:doc-types=0. check:doc-example-ids=0. node scripts/check-governed-queue-guard.mjs --test (17 paths)=0, NOT GOVERNED. eslint as the package lint runs it, on the changed files: 0 errors, no warning in the new files and no added any. With --no-inline-config there are 6 pre-existing react-hooks/static-components errors on untouched lines, which the inline disables cover. Type-check: @object-ui/core type-check=0 (both programs; the --listFilesOnly count for the new pin is 1; it needed @object-ui/types built). @object-ui/components type-check=0 (--listFilesOnly lists all 4 changed test files; it needed the closure built: types, core, i18n, react, react-runtime, sdui-parser, data-objectstack). The README snippet compiles under tsc --strict --noUnusedLocals against core's built types (exit 0; the typo control exits 2). NOT MEASURED, reason: unbuilt closure (about 30 packages) and the lock was held for over an hour. That covers the @object-ui/app-shell type-check, whose diff is two context spreads. It also covers check:doc-snippets / check:doc-examples / check:readme-exports, which exited 2 'could not run' on the unbuilt tree, and their CI job builds the closure. objectui has no dispatch-gates.mjs, so this list was derived by hand from package.json against the diff, with no --ran reconciliation. PR #13033 check-runs on a5cd587d at report time, 35 in all. completed success: Changeset Bump Policy, Changeset Fixed Group Check, label. skipped: Test (coverage), Test (coverage shard N/8), dependabot. in_progress: Build & E2E, Build Docs, Bundle Analysis, Changeset Claim Re-read, Changeset Declaration, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, Lint, Live E2E (informational), Repo Gates, Test (dist pins), Test (shard 1/16 .. 16/16), Type Check. NOT MEASURED: the in_progress CI verdicts, because convergence belongs to the seat.",
      "mcp_calls": "0",
      "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, via scripts/pm/with-fleet.sh --via dispatch and via post-stamped.mjs), landing 3 platform writes as objectstack-fleet[bot]. pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /repos/objectstack-ai/objectui/issues/13033/assignees [sunhaolin]; read back as 11918 bytes sent = stored, identical to the end, draft true, assignee sunhaolin. This report: POST /repos/objectstack-ai/objectui/issues/13022/comments. Plus 3 git pushes (empty probe branch, implementation 9f41095f, docs a5cd587d), which are not REST. No label writes; the labels on #13033 are labeler.yml's path labels.",
      "open_questions": [
        {
          "question": "Residual direction. On a host that publishes no declarations for the dispatch's object, a row carries no patch, so its Undo snapshot is params-only. Two cases reach it: PageView's runtime binds no objectName, and a row whose objectName is another object. If the named declaration has a patch, that restore is partial. Keep it, refuse Undo there, or widen the lookup?",
          "options": [
            "A (this PR): published list first, else the def's own patch, else params-only, as today. No new surface.",
            "B: when no list is published for the object and the def carries no patch, offer no Undo. This closes the partial-restore hazard, but also drops Undo for every whole-dispatched param-only action on such hosts (related-list rows, hosts outside the console).",
            "C: resolve the declaration by the dispatch's object from every object the host holds (PageView passes objects), mirroring the route exactly. This is the widest fix, and it adds a host-to-runner lookup surface."
          ],
          "recommendation": "A, on the four axes. Business need, measured: no example app or producer authors an undoable update row on a page without a bound object, and the console record page, ObjectView and DeclaredActionsBar all publish. Long term: A keeps one documented two-source rule, while C is the faithful end state if a producer appears. Against AI mistakes: B is stricter, but it silences a working param-only Undo on hosts that dispatch whole declarations. Startup focus: A adds no surface. Revisit as C when a row with an undoable declaration is measured on an object-less page."
        }
      ],
      "out_of_scope_findings": [
        "carrier: objectui#12971 · noted, not filed. Between this PR and the pin bump, action:button / action:icon still publish a patch input (action-button-icon-inputs-11168 LEAF_KEYS, registry-inputs-spec-parity) that the block no longer forwards. The spec at next.3 still declares it, and the bump removes it. That half was left untouched per Zone 1 item 3."
      ],
      "deviations": [
        "The verify lock was held by objectui#12971's measure.sh (pid 3984: a repo-wide pnpm type-check plus a full vitest run at maxWorkers 3) for over an hour. Three attempts under OS_VERIFY_LOCK_SLOT=issue-13022-dev ended in VERDICT queue-timeout (exit 99). The holder was alive and progressing, not stuck. Per the queue clause, the checks were narrowed to the targeted files and builds listed under tests and gates and run outside the lock. The slot stays parked; it blocks nobody.",
        "The file surface is wider than the claim's list. packages/app-shell useConsoleActionRuntime.tsx and RecordDetailView.tsx carry the Zone 2 item 2 thread (objectActions in the runner context). scripts/check-action-forward-parity.mjs gained four JUSTIFIED entries, without which the gate reds on the ruled removal. packages/core/README.md and content/docs/guide/layout.md were updated per AGENTS.md #2. The changeset lists @object-ui/app-shell beside core and components.",
        "action-button-icon-inputs-11168.test.tsx: only its 'operation: update' case changed, because it pinned the forwarded patch. Its LEAF_KEYS input list was not touched (Zone 1 item 3).",
        "Zone 1 item 1 named predicate-fields undoableWrittenKeys as reading the row. As measured it reads declarations only, so it got a doc correction and no code change.",
        "Zone 2 item 3 was measured with a throwaway vitest probe file in the worktree. It was deleted before any commit, and git status was clean.",
        "Commit trailers use objectui AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named line. The PR footer is the session-URL form.",
        "Cleanup: the worktree node_modules was removed and git worktree remove succeeded, without --force, after confirming local HEAD = remote HEAD a5cd587d."
      ],
      "files_changed": [
        ".changeset/13022-action-row-declared-patch.md",
        "packages/core/src/actions/ActionRunner.ts",
        "packages/core/src/actions/__tests__/ActionRunner.declaredPatch-13022.test.ts",
        "packages/core/src/utils/predicate-fields.ts",
        "packages/core/README.md",
        "packages/components/src/renderers/action/action-button.tsx",
        "packages/components/src/renderers/action/action-icon.tsx",
        "packages/components/src/renderers/action/action-group.tsx",
        "packages/components/src/renderers/action/action-menu.tsx",
        "packages/components/src/renderers/action/__tests__/action-row-declared-patch-13022.test.tsx",
        "packages/components/src/renderers/action/__tests__/action-forward-precedence.test.tsx",
        "packages/components/src/renderers/action/__tests__/action-button-icon-inputs-11168.test.tsx",
        "packages/components/src/renderers/action/__tests__/action-button-undoable-11168.test.tsx",
        "packages/app-shell/src/hooks/useConsoleActionRuntime.tsx",
        "packages/app-shell/src/views/RecordDetailView.tsx",
        "scripts/check-action-forward-parity.mjs",
        "content/docs/guide/layout.md"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_0188TsgPV8kQXfuRuKmkj2ks · 2026-10-19T13:06Z

    PR #13033 merged through objectui's merge queue as 61ed5ada68. Its one parent is fe11eb12e5, and the merge is an ancestor of origin/main.

    probe value
    merge diff vs the reviewed diff (git patch-id --stable) identical to the ACCEPT head a5cd587dc1 (66181fe9; 17 files, +688 / −60)
    a model name in the merge commit's message 0
    CI on a5cd587dc1 green: 33 success, 3 skipped

    State change:

    • Fixes #13022 closed this card completed, and this act removes pm:dispatched.
    • An operation: 'update' action's Undo now covers the patch of the declaration its name names, read from the objectActions the console and the record page publish, plus the collected params. A page row's own patch is never read.
    • action:button, action:icon, action:group and action:menu no longer forward patch. The request body is unchanged.

    The other half of this card's acceptance is not landed yet. action:button and action:icon still publish the patch input, because the spec at the current pin still declares it. Removing it lands in objectui#12971's PR #13043, together with the pin that retires the key. That PR merges main after this landing (decision D3 = A, 6267645900; landing-order amendment on PR #13033).

    Records:

    • the claim 6261988109;
    • the dev's report 6263350045;
    • the ACCEPT 6263385634 (TIER_DEFAULT).

    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

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p1target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions