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 }}
Repository navigation
List header: a single-record action (Reassign…) is live with no row selected, and a dead end where rows cannot be selected #27831
Filing gate: ③ triage-directed split. Triage on #27520 (6495077487) said: 「Each row is either taken, with its own card where it needs code」. This card is row 5.
Business reason: on a list, a record-scoped action in the header (Tasks → Reassign…) looks ready with nothing selected and explains itself only after a click. For a member whose grid has no row checkboxes, the button can never work.
Reader who acts: domain:ui (the console's list header actions).
Duplicate search: search_issues "list header single-record action enabled with nothing selected select a row first disabled tooltip" → #27604, #25883 and #23032, none of them this. New. The early refusal ("This action runs on a single record — select a row first.", refuseBeforeInput, objectui#13384) is prior work this card builds on, not a duplicate: it stops the dispatch, but the button still invites the click.
What happens (row 5 of #27520, 18.3.0 RC walkthrough at c4c515ad)
Admin and member, desktop, Tasks. Reassign… in the list header is enabled with nothing selected. Only after a click does it say "This action runs on a single record — select a row first." The member's grid has no row checkboxes, so for the member it is a dead end.
Expected (the row's own suggestion)
With no row selected, or more than one, a record-scoped header action is drawn disabled. Its tooltip is the same reason the early refusal already gives, in the session's language, from the existing keys.
With exactly one row selected it is enabled, and it behaves as today.
Where the grid offers no row selection at all, a record-scoped header action is not drawn.
The early refusal stays as the backstop for any path that still dispatches.
Acceptance
One measured repro on current main first. Name how the header knows an action is record-scoped (the same resolver refuseBeforeInput uses; ⛔ no second rule) and where the selection count lives.
Tests:
0 rows: disabled, with the reason as the tooltip.
1 row: enabled, and it dispatches.
2 rows: disabled, with the "exactly one row" reason.
No-selection grid: not drawn.
Control: a non-record-scoped header action (e.g. New) is unaffected.
An ablation.
No new i18n key expected. Reuse the refusal keys; if a new one is needed, it is Clause-②: yes (widening).
Claim: PM loop round 1
Session: session_01Utp6CbDAucNyQ4UtzCJKyh
Account: os-bill
Branch: claude/project-thread-j9w213 (this seat thread's designated push branch for its in-seat subagent dev, the recorded platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-27831
Domain: domain:ui
Seat: domain:ui#4
File surface: the list header action rendering (components / plugin-list / plugin-grid / app-shell as measured), tests beside, root .changeset/; @object-ui/coreactions/ActionRunner.ts (launchRefusal, subscribeContext) and @object-ui/typesui-action.ts (ActionContext.selectionAvailable) (amended 2026-10-30T02:56Z: the header asks the runner's own early refusal instead of restating its rule, which needs two public runner members and one optional context field); ⛔ useConsoleActionRuntime.tsx / RecordDetailView.tsxapiHandler (in-flight PR #27793) (stop on breach; explain in the report)
Container & model: S, mode:seat, model: TIER_DEFAULT (dispatch-gates --tier on fec60e00: no path-derived mandate), effort: medium
Clause-②: yes (widening) (amended 2026-10-30T02:56Z: the three additive members above)
Metadata-protocol: none
Responsibility: a record-scoped list header action is disabled with the early-refusal reason as tooltip unless exactly one row is selected, and not drawn where rows cannot be selected; same resolver as refuseBeforeInput
Thread-read: none
Premise: holds
Serial constraints cleared: in-flight: PR #27793 (api handlers, excluded)
Dispatched 2026-10-30T01:07Z to the seat's in-seat subagent on the seat thread's branch. ⛔ The subagent posts no claim and writes no assignee.
{
"issue": 27831,
"status": "done",
"branch": "claude/project-thread-j9w213",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27894",
"pr_number": 27894,
"head_sha": "d78f7954564325823c5853f19326730a6eafb0e2",
"session": "session_01Utp6CbDAucNyQ4UtzCJKyh",
"premise_still_valid": true,
"summary": "Measured on main fec60e00: a record-scoped list_toolbar action (the showcase Reassign shape) is enabled with 0 and 2 rows and drawn on a no-selection grid (new suite: 4 failed | 3 passed on base). The ObjectView header now draws every schema-driven action through ListHeaderActionBar, which asks the runner's new ActionRunner.launchRefusal (the same refuseBeforeInput execute asks, i.e. resolveServerActionRecordId, translated by errorKey) and sets disabled + disabledReason on a refused action, or leaves it out when the grid publishes selectionAvailable false. ObjectGrid publishes selectionAvailable beside selectedRecords (its selection-mode block moved, unchanged, above the early returns); ActionRunner.subscribeContext lets the header redraw. The early refusal stays as the backstop. No new i18n key.",
"files_changed": [
".changeset/27831-list-header-selection-gate.md",
"ui/packages/app-shell/src/views/ObjectView.headerSelectionGate-27831.test.tsx",
"ui/packages/app-shell/src/views/ObjectView.toolbarScriptSelection-12331.test.tsx",
"ui/packages/app-shell/src/views/objectViewHeaderChrome.tsx",
"ui/packages/app-shell/src/views/objectViewHeaderLaunchGate.tsx",
"ui/packages/app-shell/src/views/objectViewPageHeader.tsx",
"ui/packages/core/src/actions/ActionRunner.ts",
"ui/packages/core/src/actions/__tests__/ActionRunner.launchRefusal-27831.test.ts",
"ui/packages/plugin-grid/src/ObjectGrid.tsx",
"ui/packages/types/src/ui-action.ts"
],
"line_budget": "10 files, +699 / -95 vs merge-base (ObjectGrid.tsx +98/-86 is the moved selection-mode block plus a 13-line effect); under the 3000-line class (c) bound",
"tests": "Repro on base fec60e00 (five non-test sources restored from base, restored from HEAD after, git diff HEAD empty): ObjectView.headerSelectionGate-27831 = 4 failed | 3 passed (0 rows / 2 rows / zh not disabled; no-selection list still draws it). On HEAD: 7/7. Ablation via scripts/ablation-replace.mjs (launchRefusal call replaced by undefined; anchor 1->0, blob 3c7bbfc1872a->51a18c64a2c5): 4 failed | 3 passed, restored blob == HEAD, git diff HEAD empty; tests resolve @object-ui/* to src via ui/vitest.config.mts aliases, no dist leg. Pre-merge: vitest core/src/actions + plugin-grid 307 files / 2967 passed; 127 targeted app-shell/components/react/plugin-list files 1440 passed; type-check types/core/plugin-grid/app-shell exit 0. Final head d78f7954: 43 targeted files / 665 passed (27831, 12331, 12462, launchRefusal-27831, recordRowGuard-13384, flowRowGuard-13397, all action:* renderer suites); app-shell type-check exit 0; ui vitest scripts/ 169 passed | 2 skipped.",
"gates": "Local, final head d78f7954: dispatch-gates --commands derived 35 families, each run with exit captured before any pipe, all exit 0 (incl. node scripts/check-sdui-manifest.mjs and --self-test, check-changeset-no-major --base origin/main, check-adr-0087-registration, check:nul-bytes, check:type-check-debt, check:query-options-erasure, pnpm --dir ui exec vitest run scripts/); --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Extra: pnpm --dir ui check:new-line-citations exit 0 (0 new). CI on PR at report time: filter success, Check PR Size success, Governed Surface Queue Guard success, PR-body guards success; Lint/Type Check/Test Core/Build UI Deps/Live E2E UI/Check Changeset in_progress.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, PR #27894, read-back identical 11466/11466 bytes, assignees none); POST /repos/objectstack-ai/objectstack/issues/27831/comments (this report, via post-stamped). git push not counted.",
"deviations": [
"Clause-②: yes (widening) where the claim says no: two new public ActionRunner members (launchRefusal, subscribeContext) and ActionContext.selectionAvailable are needed for the header to ask the runner's private refusal and redraw on selection; the dispatch allows this arm. Changeset level patch, Metadata-protocol none.",
"Report comment: line 1 is the dispatch's signature line and os-dev-report is line 2 (os-dev asks for os-dev-report as the first line; the dispatch order names the signature first).",
"PR body line 2 is the dispatch-mandated claude-projects-attribution HTML comment (os-dev bans dev-authored HTML comments except the harness attribution line).",
"Commit trailers are the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line.",
"Local test scope narrowed and declared: app-shell has 1922 test files; ran the 127 that name list_toolbar / header chrome / selectedRecords / selectionAvailable / ObjectView plus action renderers, and core actions + plugin-grid in full; the full Test UI shards are CI's.",
"First local gate pass on the pre-merge head used a 280s per-command timeout: check:query-options-erasure and check:type-check-debt recorded exit 124 there, re-run without the timeout exit 0; the final-head pass has all 35 at exit 0."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — phone card view: ObjectGrid's card list draws no checkboxes while its selection mode stays on, so a record-scoped header action is drawn disabled with its reason instead of left out (PR Acceptance notes).",
"carrier: none · noted, not filed — EnvironmentListToolbar (sys_environment) draws its own bar from headerToolbarActions and is not routed through the gate (PR Acceptance notes).",
"carrier: none · noted, not filed — a ?runAction= deep link naming a record-scoped action on a no-selection list is consumed by useOfferedNavRunAction (reads the ungated list) while the button is not drawn; read from code, not measured (PR Acceptance notes)."
]
}
Activity
objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
Claim: PM loop round 1
Session:
session_01Utp6CbDAucNyQ4UtzCJKyhAccount:
os-billBranch:
claude/project-thread-j9w213(this seat thread's designated push branch for its in-seat subagent dev, the recorded platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27831Domain:
domain:uiSeat:
domain:ui#4File surface: the list header action rendering (components / plugin-list / plugin-grid / app-shell as measured), tests beside, root
.changeset/;@object-ui/coreactions/ActionRunner.ts(launchRefusal,subscribeContext) and@object-ui/typesui-action.ts(ActionContext.selectionAvailable) (amended 2026-10-30T02:56Z: the header asks the runner's own early refusal instead of restating its rule, which needs two public runner members and one optional context field); ⛔useConsoleActionRuntime.tsx/RecordDetailView.tsxapiHandler(in-flight PR #27793) (stop on breach; explain in the report)Container & model:
S,mode:seat,model: TIER_DEFAULT(dispatch-gates --tieronfec60e00: no path-derived mandate),effort: mediumClause-②: yes (widening) (amended 2026-10-30T02:56Z: the three additive members above)
Metadata-protocol: none
Responsibility: a record-scoped list header action is disabled with the early-refusal reason as tooltip unless exactly one row is selected, and not drawn where rows cannot be selected; same resolver as
refuseBeforeInputThread-read: none
Premise: holds
Serial constraints cleared: in-flight: PR #27793 (api handlers, excluded)
Dispatched 2026-10-30T01:07Z to the seat's in-seat subagent on the seat thread's branch. ⛔ The subagent posts no claim and writes no assignee.
Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
os-dev-report
{ "issue": 27831, "status": "done", "branch": "claude/project-thread-j9w213", "pr": "https://github.com/objectstack-ai/objectstack/pull/27894", "pr_number": 27894, "head_sha": "d78f7954564325823c5853f19326730a6eafb0e2", "session": "session_01Utp6CbDAucNyQ4UtzCJKyh", "premise_still_valid": true, "summary": "Measured on main fec60e00: a record-scoped list_toolbar action (the showcase Reassign shape) is enabled with 0 and 2 rows and drawn on a no-selection grid (new suite: 4 failed | 3 passed on base). The ObjectView header now draws every schema-driven action through ListHeaderActionBar, which asks the runner's new ActionRunner.launchRefusal (the same refuseBeforeInput execute asks, i.e. resolveServerActionRecordId, translated by errorKey) and sets disabled + disabledReason on a refused action, or leaves it out when the grid publishes selectionAvailable false. ObjectGrid publishes selectionAvailable beside selectedRecords (its selection-mode block moved, unchanged, above the early returns); ActionRunner.subscribeContext lets the header redraw. The early refusal stays as the backstop. No new i18n key.", "files_changed": [ ".changeset/27831-list-header-selection-gate.md", "ui/packages/app-shell/src/views/ObjectView.headerSelectionGate-27831.test.tsx", "ui/packages/app-shell/src/views/ObjectView.toolbarScriptSelection-12331.test.tsx", "ui/packages/app-shell/src/views/objectViewHeaderChrome.tsx", "ui/packages/app-shell/src/views/objectViewHeaderLaunchGate.tsx", "ui/packages/app-shell/src/views/objectViewPageHeader.tsx", "ui/packages/core/src/actions/ActionRunner.ts", "ui/packages/core/src/actions/__tests__/ActionRunner.launchRefusal-27831.test.ts", "ui/packages/plugin-grid/src/ObjectGrid.tsx", "ui/packages/types/src/ui-action.ts" ], "line_budget": "10 files, +699 / -95 vs merge-base (ObjectGrid.tsx +98/-86 is the moved selection-mode block plus a 13-line effect); under the 3000-line class (c) bound", "tests": "Repro on base fec60e00 (five non-test sources restored from base, restored from HEAD after, git diff HEAD empty): ObjectView.headerSelectionGate-27831 = 4 failed | 3 passed (0 rows / 2 rows / zh not disabled; no-selection list still draws it). On HEAD: 7/7. Ablation via scripts/ablation-replace.mjs (launchRefusal call replaced by undefined; anchor 1->0, blob 3c7bbfc1872a->51a18c64a2c5): 4 failed | 3 passed, restored blob == HEAD, git diff HEAD empty; tests resolve @object-ui/* to src via ui/vitest.config.mts aliases, no dist leg. Pre-merge: vitest core/src/actions + plugin-grid 307 files / 2967 passed; 127 targeted app-shell/components/react/plugin-list files 1440 passed; type-check types/core/plugin-grid/app-shell exit 0. Final head d78f7954: 43 targeted files / 665 passed (27831, 12331, 12462, launchRefusal-27831, recordRowGuard-13384, flowRowGuard-13397, all action:* renderer suites); app-shell type-check exit 0; ui vitest scripts/ 169 passed | 2 skipped.", "gates": "Local, final head d78f7954: dispatch-gates --commands derived 35 families, each run with exit captured before any pipe, all exit 0 (incl. node scripts/check-sdui-manifest.mjs and --self-test, check-changeset-no-major --base origin/main, check-adr-0087-registration, check:nul-bytes, check:type-check-debt, check:query-options-erasure, pnpm --dir ui exec vitest run scripts/); --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Extra: pnpm --dir ui check:new-line-citations exit 0 (0 new). CI on PR at report time: filter success, Check PR Size success, Governed Surface Queue Guard success, PR-body guards success; Lint/Type Check/Test Core/Build UI Deps/Live E2E UI/Check Changeset in_progress.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, PR #27894, read-back identical 11466/11466 bytes, assignees none); POST /repos/objectstack-ai/objectstack/issues/27831/comments (this report, via post-stamped). git push not counted.", "deviations": [ "Clause-②: yes (widening) where the claim says no: two new public ActionRunner members (launchRefusal, subscribeContext) and ActionContext.selectionAvailable are needed for the header to ask the runner's private refusal and redraw on selection; the dispatch allows this arm. Changeset level patch, Metadata-protocol none.", "Report comment: line 1 is the dispatch's signature line and os-dev-report is line 2 (os-dev asks for os-dev-report as the first line; the dispatch order names the signature first).", "PR body line 2 is the dispatch-mandated claude-projects-attribution HTML comment (os-dev bans dev-authored HTML comments except the harness attribution line).", "Commit trailers are the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line.", "Local test scope narrowed and declared: app-shell has 1922 test files; ran the 127 that name list_toolbar / header chrome / selectedRecords / selectionAvailable / ObjectView plus action renderers, and core actions + plugin-grid in full; the full Test UI shards are CI's.", "First local gate pass on the pre-merge head used a 280s per-command timeout: check:query-options-erasure and check:type-check-debt recorded exit 124 there, re-run without the timeout exit 0; the final-head pass has all 35 at exit 0." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — phone card view: ObjectGrid's card list draws no checkboxes while its selection mode stays on, so a record-scoped header action is drawn disabled with its reason instead of left out (PR Acceptance notes).", "carrier: none · noted, not filed — EnvironmentListToolbar (sys_environment) draws its own bar from headerToolbarActions and is not routed through the gate (PR Acceptance notes).", "carrier: none · noted, not filed — a ?runAction= deep link naming a record-scoped action on a no-selection list is consumed by useOfferedNavRunAction (reads the ungated list) while the button is not drawn; read from code, not measured (PR Acceptance notes)." ] }objectstack-fleet commentedon Oct 30, 2026
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
ACCEPT → PR #27894 comment 6514052604 · 2026-10-30T03:14Z
Generated by Claude Code