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
Studio Automations: a refused flow switch (409) shows in StudioRefusalStrip, which has no role=alert or aria-live, so the refusal is no longer announced to assistive tech — the retired Setup page announced it #29139
立卡门类别: ① 产品缺陷(用户可见的体验缺陷)— 具名落点 (ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, the shared StudioRefusalStrip block, data-testid="studio-refusal") + 复现; 所需通用块: StudioRefusalStrip itself (one fix covers every pillar that renders it).
reach: 公开入口实测一次错误结果 — in the console at 7fa9873, a 409 on Studio's flow switch renders in studio-refusal with no role and no aria-live (browser run, reproduced twice).
业务理由: an admin using a screen reader who flips a flow's switch in Studio and is refused hears nothing; the switch simply does not move. Before #28785 retired the Setup › Packaged automation page, the same refusal rendered in a role="alert" box, so it was announced (the checklist item automation.setup-packaged-automation-board acceptance[5] pins that property). Moving the switch to Studio (ADR-0146 D4) dropped that property. The strip is shared, so every Studio refusal it renders is silent to assistive tech today. Not doing it: the platform's write-feedback for refusals is visual-only in its main authoring surface.
动手的读者: the domain:ui seat; triage first touch confirms the lane, then os-dev.
查重: studio-refusal role alert → 13 hits, StudioRefusalStrip → 0 hits (incl. closed): none matching. Related, not the same: #28985 (flow node inspector controls disabled with no reason exposed to assistive tech). The re-point of the Setup item to Studio is #28579, which needs this decided for acceptance[5].
Reproduction (stock showcase, admin, console from this tree):
Open Studio › Automations, select a packaged flow, and flip its switch (auto-ledger-switch) under a condition the server refuses with 409 (the item's acceptance[5] admin cell stages one).
The 409 sentence appears verbatim in the element [data-testid="studio-refusal"].
Inspect it: no role, no aria-live, no aria-describedby link from the switch. A screen reader announces nothing.
Expected: the refusal is announced (role="alert" or a polite live region on the strip), as the Setup page did.
Claim: PM loop round 3 · 2026-11-02T08:45Z
Session: session_01QsR2jReFyNpMGkARoUHPD3 (card thread, os-dev); dispatched by seat session session_01QsR2jV4dWohojjbTJTQq5m
Account: zhuangjianguo
Branch: claude/project-thread-bjrk0p (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29139
Domain: domain:ui
Seat: domain:ui#3
File surface: ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the StudioRefusalStrip block only), its tests, the i18n packs only if a region label is added, 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: xhigh (p1)
Clause-②: no
Metadata-protocol: none
Responsibility: ui code: the shared StudioRefusalStrip renders refusals with no role or live region | the retired Setup page used role="alert"; nothing on the Studio path announces | every screen-reader admin refused a Studio write (measured at a public door, QA run #29124)
Thread-read: 6572480834
Premise: holds (at origin/main87d6baef: StudioDesignSurface.tsx:729 renders data-testid="studio-refusal" with no role and no aria-live)
Serial constraints cleared: seat 1's #29015 (PR #29132) edits StudioDesignSurface.tsx elsewhere; this edit stays in the strip block and merges main before pr_create. #29113 and #29148 wait on PR #29132, not on this card.
Card thread (os-dev) report, written 2026-11-02T09:49Z. Draft PR #29224 at head 0dffec2. Evidence files: /mnt/project-files/issue-29139/ (pr-body.md, live/before.json, live/after.json, strip screenshots).
{
"issue": 29139,
"status": "done",
"branch": "claude/project-thread-bjrk0p",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29224",
"session": "session_01QsR2jReFyNpMGkARoUHPD3",
"premise_still_valid": true,
"summary": "StudioRefusalStrip's sentence element (data-testid studio-refusal-message) now carries role=alert; nothing else in the strip moves (no string, prop or layout change), so every pillar that draws the strip announces its refusal. role=alert, not a polite region: the strip mounts already filled, and an alert is announced on insertion while a polite region inserted filled is not reliably announced; the refusal blocks the author's action. On the sentence, not the strip: the retired Setup page used a paragraph with role=alert holding the server sentence, acceptance[5] scores alert text == server sentence, and a strip-wide alert would also read Show me and Details and re-announce when Details opens. aria-describedby from the switch was measured and left as a finding (not local).",
"files_changed": [
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (StudioRefusalStrip block: role on the sentence + doc comment)",
"ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.refusalAnnounced-29139.test.tsx (new)",
"ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx (one 409 case + a request gate in the harness)",
".changeset/29139-studio-refusal-alert.md (@object-ui/app-shell patch, Clause-② no, Metadata-protocol none)"
],
"line_budget": "138 changed lines (+136 / -2), 4 files, delta to origin/main; under the 3000 human-merge threshold; no skills/** or governed surface",
"tests": "At 5c8cc565 and again at 3d6fa845: the two touched files 12/12 (3 new strip pins, 1 new 409 pin, 8 existing); the 24 test files that render or query the strip or an alert in Studio 125/125 (pnpm exec vitest run --maxWorkers=2 from ui/, under os-verify-lock, VERDICT command-exit 0). Ablation (scripts/ablation-replace.mjs, wrap mode, source import so no dist leg): role=alert dropped on the sentence, anchor x1 to x0, blob 5c1d6a63 to 0294a55a; 4 of 12 red (the 3 strip pins and the 409 pin), 8 green; restore proven blob == HEAD and git diff HEAD empty. Type-check: turbo type-check @object-ui/app-shell (tsc --noEmit and tsc -p tsconfig.test.json) green at 5c8cc565 and 3d6fa845. eslint on the 3 touched ui files: 0 errors (43 warnings, all on existing lines of StudioDesignSurface.tsx outside the strip). Live, the card's reproduction, MEASURED at 3d6fa845: fresh showcase (objectstack dev --seed-admin --fresh, private port) + Vite console on source, admin, Studio Automations showcase_notify_owner (read-only package, ledger switch), Space twice; the door answered 409 DELETE_RESTRICTED naming showcase_task_done_notify_owner both times. This branch: 0 alerts before; after, 1 role=alert inside studio-refusal whose text equals error.message byte for byte, aria snapshot '- alert: Flow showcase_notify_owner cannot be disabled ...', 2 alert insertions over 2 refusals. origin/main strip (file swapped in, restore proven): '- paragraph', 0 alerts, 0 insertions. Strip screenshots byte-identical on both legs (md5 50e5dd60).",
"gates": "Local subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 at 5c8cc565; all 34 ran, each exit 0 (exit codes captured before any pipe; type-check-debt and ui vitest scripts/ under the lock). Includes node scripts/check-sdui-manifest.mjs :: exit 0, its --self-test :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (169 files, 4900 tests, 2 skipped files), pnpm check:type-check-debt :: exit 0, pnpm check:query-options-erasure :: exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the exact body before pr_create: check-changeset-no-major exit 0, check-adr-0087-registration exit 0, check-partof-closing-keyword exit 0, check-closing-keyword-parity --body report mode binds only Fixes 29139. CI on 0dffec2a: Check PR Size success, filter success, the rest in_progress or queued at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"deviations": [
"The derived sweep ran at 5c8cc565 (the fix commit), not at the final head 0dffec2a: the two later commits are merges of origin/main (12 and 4 commits, clean, none under ui/packages/app-shell/src/views/studio-design) that leave the delta to main at the same 4 files. CI's Lint and Repo Gates runs every family on 0dffec2a.",
"Tests and type-check re-ran at 3d6fa845, not at 0dffec2a: the second merge brings 4 main commits outside ui/packages/app-shell (spec view default, runtime action context, cli and create-objectstack scaffolding, docs references). CI Test UI and Type Check UI run the head.",
"Not merged a third time right before pr_create: main had moved 2 commits (to 49aba82b) since the merge, neither under studio-design, git merge-tree clean. Merge on the seat's word or on a reported conflict.",
"No assignee and no reviewer request on the PR (dispatch contract and common rules 3 and 10), although the session harness asks for both through GitHub MCP writes; and no PR-activity subscription, per os-dev.md's override of the platform's subscription posture.",
"The harness attribution reminder asks for a model-named Co-Authored-By trailer; commits carry the model-free pair (objectstack pre-push check)."
],
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on 29139; no write tool",
"api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, run 41423510540, read-back identical 9793 bytes); this report through scripts/pm/post-stamped.mjs. git push x3 (not REST). REST reads only otherwise.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door measured once — Studio Automations on a fresh showcase, keyboard Space on the ledger switch (data-testid auto-ledger-switch); document.activeElement reads body after the toggle's answer, on origin/main's strip and on this branch alike (live/before.json and live/after.json, activeElement) · evidence: FlowLedgerSwitch draws the switch disabled while busy (disabled={busy || !!walledReason}), most likely dropping focus, and nothing restores it; a keyboard or screen-reader author must find the switch again after every flip · dedupe words: ledger switch focus lost, auto-ledger-switch focus, Studio switch focus body after toggle, FlowLedgerSwitch busy disabled focus · carrier: ui seat 3 to file (not filed by dev)",
"class: none (polish beyond the card's acceptance) · reach: n/a · aria-describedby from auto-ledger-switch to the refusal sentence not added: the strip has no id and is drawn from the pillar's shared error state (load, save, delete and toggle failures), so linking needs a strip id, a FlowLedgerSwitch prop and a pillar discriminator, three places outside the strip block; measured live, the switch's aria-describedby names only its hint id before and after the refusal · carrier: none — noted in the PR's Acceptance notes"
]
}
Landed: PR #29224 merged at 2026-11-02T10:50Z as 8a8a03d3. Fixes #29139 closed this card; pm:dispatched removed. Delivered: every Studio refusal strip's sentence is a role="alert", so a refused flow switch is announced verbatim. Follow-up filed: #29228 (focus drops after a ledger-switch flip). Nothing left on this card.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: automation.setup-packaged-automation-board
Triage:
domain:ui· p1 ·pm:queue· 2026-11-02T08:23Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Claim: PM loop round 3 · 2026-11-02T08:45Z
Session:
session_01QsR2jReFyNpMGkARoUHPD3(card thread, os-dev); dispatched by seat sessionsession_01QsR2jV4dWohojjbTJTQq5mAccount:
zhuangjianguoBranch:
claude/project-thread-bjrk0p(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29139Domain:
domain:uiSeat:
domain:ui#3File surface:
ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(theStudioRefusalStripblock only), its tests, the i18n packs only if a region label is added, 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: xhigh(p1)Clause-②: no
Metadata-protocol: none
Responsibility:
ui code: the shared StudioRefusalStrip renders refusals with no role or live region | the retired Setup page used role="alert"; nothing on the Studio path announces | every screen-reader admin refused a Studio write (measured at a public door, QA run #29124)Thread-read: 6572480834
Premise: holds (at
origin/main87d6baef:StudioDesignSurface.tsx:729rendersdata-testid="studio-refusal"with noroleand noaria-live)Serial constraints cleared: seat 1's #29015 (PR #29132) edits
StudioDesignSurface.tsxelsewhere; this edit stays in the strip block and merges main before pr_create. #29113 and #29148 wait on PR #29132, not on this card.objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jReFyNpMGkARoUHPD3
os-dev-report
Card thread (os-dev) report, written 2026-11-02T09:49Z. Draft PR #29224 at head 0dffec2. Evidence files: /mnt/project-files/issue-29139/ (pr-body.md, live/before.json, live/after.json, strip screenshots).
{ "issue": 29139, "status": "done", "branch": "claude/project-thread-bjrk0p", "pr": "https://github.com/objectstack-ai/objectstack/pull/29224", "session": "session_01QsR2jReFyNpMGkARoUHPD3", "premise_still_valid": true, "summary": "StudioRefusalStrip's sentence element (data-testid studio-refusal-message) now carries role=alert; nothing else in the strip moves (no string, prop or layout change), so every pillar that draws the strip announces its refusal. role=alert, not a polite region: the strip mounts already filled, and an alert is announced on insertion while a polite region inserted filled is not reliably announced; the refusal blocks the author's action. On the sentence, not the strip: the retired Setup page used a paragraph with role=alert holding the server sentence, acceptance[5] scores alert text == server sentence, and a strip-wide alert would also read Show me and Details and re-announce when Details opens. aria-describedby from the switch was measured and left as a finding (not local).", "files_changed": [ "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (StudioRefusalStrip block: role on the sentence + doc comment)", "ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.refusalAnnounced-29139.test.tsx (new)", "ui/packages/app-shell/src/views/studio-design/AutomationsPillar.ledgerSwitch-28148.test.tsx (one 409 case + a request gate in the harness)", ".changeset/29139-studio-refusal-alert.md (@object-ui/app-shell patch, Clause-② no, Metadata-protocol none)" ], "line_budget": "138 changed lines (+136 / -2), 4 files, delta to origin/main; under the 3000 human-merge threshold; no skills/** or governed surface", "tests": "At 5c8cc565 and again at 3d6fa845: the two touched files 12/12 (3 new strip pins, 1 new 409 pin, 8 existing); the 24 test files that render or query the strip or an alert in Studio 125/125 (pnpm exec vitest run --maxWorkers=2 from ui/, under os-verify-lock, VERDICT command-exit 0). Ablation (scripts/ablation-replace.mjs, wrap mode, source import so no dist leg): role=alert dropped on the sentence, anchor x1 to x0, blob 5c1d6a63 to 0294a55a; 4 of 12 red (the 3 strip pins and the 409 pin), 8 green; restore proven blob == HEAD and git diff HEAD empty. Type-check: turbo type-check @object-ui/app-shell (tsc --noEmit and tsc -p tsconfig.test.json) green at 5c8cc565 and 3d6fa845. eslint on the 3 touched ui files: 0 errors (43 warnings, all on existing lines of StudioDesignSurface.tsx outside the strip). Live, the card's reproduction, MEASURED at 3d6fa845: fresh showcase (objectstack dev --seed-admin --fresh, private port) + Vite console on source, admin, Studio Automations showcase_notify_owner (read-only package, ledger switch), Space twice; the door answered 409 DELETE_RESTRICTED naming showcase_task_done_notify_owner both times. This branch: 0 alerts before; after, 1 role=alert inside studio-refusal whose text equals error.message byte for byte, aria snapshot '- alert: Flow showcase_notify_owner cannot be disabled ...', 2 alert insertions over 2 refusals. origin/main strip (file swapped in, restore proven): '- paragraph', 0 alerts, 0 insertions. Strip screenshots byte-identical on both legs (md5 50e5dd60).", "gates": "Local subset: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 at 5c8cc565; all 34 ran, each exit 0 (exit codes captured before any pipe; type-check-debt and ui vitest scripts/ under the lock). Includes node scripts/check-sdui-manifest.mjs :: exit 0, its --self-test :: exit 0, pnpm --dir ui exec vitest run scripts/ :: exit 0 (169 files, 4900 tests, 2 skipped files), pnpm check:type-check-debt :: exit 0, pnpm check:query-options-erasure :: exit 0. --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the exact body before pr_create: check-changeset-no-major exit 0, check-adr-0087-registration exit 0, check-partof-closing-keyword exit 0, check-closing-keyword-parity --body report mode binds only Fixes 29139. CI on 0dffec2a: Check PR Size success, filter success, the rest in_progress or queued at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "deviations": [ "The derived sweep ran at 5c8cc565 (the fix commit), not at the final head 0dffec2a: the two later commits are merges of origin/main (12 and 4 commits, clean, none under ui/packages/app-shell/src/views/studio-design) that leave the delta to main at the same 4 files. CI's Lint and Repo Gates runs every family on 0dffec2a.", "Tests and type-check re-ran at 3d6fa845, not at 0dffec2a: the second merge brings 4 main commits outside ui/packages/app-shell (spec view default, runtime action context, cli and create-objectstack scaffolding, docs references). CI Test UI and Type Check UI run the head.", "Not merged a third time right before pr_create: main had moved 2 commits (to 49aba82b) since the merge, neither under studio-design, git merge-tree clean. Merge on the seat's word or on a reported conflict.", "No assignee and no reviewer request on the PR (dispatch contract and common rules 3 and 10), although the session harness asks for both through GitHub MCP writes; and no PR-activity subscription, per os-dev.md's override of the platform's subscription posture.", "The harness attribution reminder asks for a model-named Co-Authored-By trailer; commits carry the model-free pair (objectstack pre-push check)." ], "mcp_calls": "2 — mcp__github__issue_read (get, get_comments) on 29139; no write tool", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (fleet-write relay pr_create, run 41423510540, read-back identical 9793 bytes); this report through scripts/pm/post-stamped.mjs. git push x3 (not REST). REST reads only otherwise.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door measured once — Studio Automations on a fresh showcase, keyboard Space on the ledger switch (data-testid auto-ledger-switch); document.activeElement reads body after the toggle's answer, on origin/main's strip and on this branch alike (live/before.json and live/after.json, activeElement) · evidence: FlowLedgerSwitch draws the switch disabled while busy (disabled={busy || !!walledReason}), most likely dropping focus, and nothing restores it; a keyboard or screen-reader author must find the switch again after every flip · dedupe words: ledger switch focus lost, auto-ledger-switch focus, Studio switch focus body after toggle, FlowLedgerSwitch busy disabled focus · carrier: ui seat 3 to file (not filed by dev)", "class: none (polish beyond the card's acceptance) · reach: n/a · aria-describedby from auto-ledger-switch to the refusal sentence not added: the strip has no id and is drawn from the pillar's shared error state (load, save, delete and toggle failures), so linking needs a strip id, a FlowLedgerSwitch prop and a pillar discriminator, three places outside the strip block; measured live, the switch's aria-describedby names only its hint id before and after the refusal · carrier: none — noted in the PR's Acceptance notes" ] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m
Landed: PR #29224 merged at 2026-11-02T10:50Z as
8a8a03d3.Fixes #29139closed this card;pm:dispatchedremoved. Delivered: every Studio refusal strip's sentence is arole="alert", so a refused flow switch is announced verbatim. Follow-up filed: #29228 (focus drops after a ledger-switch flip). Nothing left on this card.