Repository navigation
Description
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Filing gate: ① user-visible UX defect, stable on screen, landing named (reach: Studio Automations on a fresh showcase, keyboard Space on the flow ledger switch, measured once by the #29139 os-dev).
Business reason: an author who works by keyboard or screen reader turns a package flow on or off for this deployment from Studio's Automations list. After every flip, focus drops to the page body, so they must tab back from the top of the page to find the switch again, and a screen reader announces nothing about where they are. Mouse users don't notice; keyboard users pay on every toggle.
Reader who acts: the ui lane (ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx). Keep focus on the switch across the busy state (for example aria-disabled plus a guarded handler instead of disabled while busy, or restore focus when the answer lands).
Dedupe: is:issue ledger switch focus 126 hits, is:issue auto-ledger-switch 531, is:issue FlowLedgerSwitch 6, is:issue toggle focus lost keyboard Studio 8 (QA runs, epics, #29139 and the closed #28148 / #28570 / #28578, other subjects). No card covers focus on the ledger switch.
Source: found by the #29139 os-dev (PR #29224), not in that card's scope.
What happens
On Studio → Automations, focus the ledger switch (data-testid="auto-ledger-switch") and press Space. When the toggle's answer lands, document.activeElement is <body>. This happens on origin/main and on PR #29224's branch alike, for a successful toggle and a refused (409) one.
Mechanism (read at origin/main 49aba82)
FlowLedgerSwitch.tsx:61 renders the switch with disabled={busy || !!walledReason}. A focused control that becomes disabled loses focus to the body, and nothing returns it when busy clears.
Acceptance
- After a keyboard toggle (success or refusal), focus is on the switch.
- The switch still cannot be toggled twice while a request is in flight.
- A test pins
document.activeElementafter the answer; an ablation (back todisabledwhile busy) goes red.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p3 ·
pm:queue· 2026-11-02T11:03Z⛔ Not a claim.
<body>after toggling the Studio flow ledger switch. An accessibility defect on one control, p3.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Claim: PM loop round 15 · 2026-11-02T11:44Z
Session:
session_01QsR2jTE8TKgsLoAjojtZ4P(card thread, os-dev); dispatched by seat sessionsession_01QsR2jV4dWohojjbTJTQq5mAccount:
zhuangjianguoBranch:
claude/project-thread-k49p7x(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29228Domain:
domain:uiSeat:
domain:ui#3File surface:
ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx, its call site inStudioDesignSurface.tsxonly if focus restore needs the host, their tests, and a changeset (stop on breach; explain in the report)Container & model: S,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
open — recorded at reviewThread-read: 6575537856
Premise: holds (at
origin/maina19759ac:FlowLedgerSwitch.tsx:61rendersdisabled={busy || !!walledReason}, so a focused switch that turns busy loses focus to<body>and nothing returns it whenbusyclears; measured once by the #29139 os-dev on PR #29224's branch and on main, for a success and a 409 refusal).Serial constraints cleared: PR #29224 (#29139) landed as
8a8a03d3. No in-flight claim of seats 1-5 or open PR namesFlowLedgerSwitch.tsx.objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jTE8TKgsLoAjojtZ4P
os-dev-report
Card thread (os-dev) report, written 2026-11-02T13:25Z. Draft PR #29375 at head 9dfcb64. Evidence files: the card thread's project folder issue-29228/ (pr-body.md, live/before.json, live/after.json, live/after-restored.json, live/definition-switch.json, both probe scripts).
{ "issue": 29228, "status": "done", "branch": "claude/project-thread-k49p7x", "pr": "https://github.com/objectstack-ai/objectstack/pull/29375", "session": "session_01QsR2jTE8TKgsLoAjojtZ4P", "premise_still_valid": true, "summary": "FlowLedgerSwitch: while busy the switch is aria-disabled (not disabled) and its onClick refuses the press while busy; disabled stays for the walled case only, so a walled switch is unchanged. Chosen over focus restore because focus never leaves (restore still drops to body for the whole flight), it needs no ref plumbing from the pillar, and the double-toggle guard sits in the component (toggleLedger has no busy check of its own). In-flight dimming moved to aria-disabled:opacity-50. StudioDesignSurface.tsx not touched. Live in Chromium 153 before and after: main drops focus to body in flight and after the answer on an accepted flip and a 409 flip; this branch keeps it on the switch, one request per leg.", "files_changed": [ "ui/packages/app-shell/src/views/studio-design/FlowLedgerSwitch.tsx (busy: aria-disabled + guarded onClick; disabled only when walled; doc comment)", "ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx (new describe: keyboard focus across an accepted and a 409 flip, second Space in flight, walled case; runFocusFixup; header bullet)", ".changeset/29228-ledger-switch-keeps-focus.md (@object-ui/app-shell patch, Clause-② no, Metadata-protocol none)" ], "line_budget": "105 changed lines (+101 / -4), 3 files, delta to origin/main; under the 3000 human-merge threshold; no skills/** and no governed surface", "tests": "At 9dfcb649: AutomationsPillar.ledgerSwitch-28148 12/12 and AutomationsPillar.customizeFlow-28570 10/10 (pnpm exec vitest run --maxWorkers=2 from ui/, under os-verify-lock, VERDICT command-exit 0). happy-dom keeps focus on a control that turns disabled (probed: blur() is a no-op on a disabled button), so the focus pins run a fixup (MutationObserver on the switch's disabled attribute, focus to body) that does what Chromium 153 was measured to do. Ablation 1 (scripts/ablation-replace.mjs, wrap mode, source import, no dist leg): disabled={busy || !!walledReason} back, anchor x1 to x0, blob d907dede to 2420b809: 2/12 red, both at expect(document.activeElement).toBe(sw) (received body); restored, blob == HEAD, git diff HEAD empty. Ablation 2: the busy guard dropped, blob d907dede to e0ae1470: 2/12 red at the request count (2, expected 1); restored, same proof. Live: see pr-body Verification (before.json, after.json, after-restored.json).", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 34; all 34 run at 951c8599, each exit 0 (incl. check-sdui-manifest and pnpm --dir ui exec vitest run scripts/ 169 files 4903 tests); --ran: 34 derived, 34 run, 0 NOT-MEASURED. After two merges of main, 31 re-run at 9dfcb649, each exit 0; check-comment-mask-corpus, check:query-options-erasure and the ui scripts/ suite not re-run at 9dfcb649 (no merged commit touches the three diff files). app-shell type-check (tsc --noEmit + tsc -p tsconfig.test.json) green at 951c8599; eslint on the two ui files exit 0. PR body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword, each exit 0. CI check-runs: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "3 — mcp__github__issue_read x2 (card body, card comments), mcp__claude-code-remote__get_session x1; no MCP write", "api_writes": "2 — fleet-write relay pr_create (run 41452744162, PR #29375, body read back byte-identical, no assignees, no labels); post-stamped os-dev-report comment on #29228. git push is not a REST write.", "open_questions": [], "security_findings": "withheld", "out_of_scope_findings": [ "class: a · reach: live in Chromium 153 on a fresh showcase: a scratch writable package (POST /api/v1/packages) with one flow (draft PUT), Studio Automations, the flow's DEFINITION switch focused, Space, draft save PUT 200, document.activeElement = BODY afterwards (live/definition-switch.json) · evidence: StudioDesignSurface.tsx draws it with disabled={!isEditable || !!saving || readOnly}, toggleEnabled sets saving='draft' for the save; same shape as this card's defect, outside its file surface · dedupe words: definition switch focus, auto flow switch keyboard focus, saving disabled focus Studio Automations, role=switch focus body" ] }Deviations: none on scope. One procedural note: in the live before leg the swap's restore trap ran from the scratch directory and failed (not a git repo); the file was restored by hand right after with git checkout HEAD, and blob == HEAD (d907dede) with an empty git diff HEAD was proven before anything else ran. A re-probe after the restore read the fixed behaviour on both legs.
objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Landed: PR #29375 merged at 2026-11-02T14:05Z as
64ba32b8.Fixes #29228closed this card;pm:dispatchedremoved. Delivered: the flow ledger switch keeps keyboard focus across a toggle (aria-disabledplus a guarded handler while busy,disabledonly when walled), and a second Space in flight sends no second request. Follow-up filed: #29376 (the writable flow's definition switch). Nothing left on this card.