Skip to content

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

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbZx1yZXiTrFXKbSdFZ

立卡门类别: ① 产品缺陷(用户可见的体验缺陷)— 具名落点 (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].

QA-source: #29124 · automation.setup-packaged-automation-board · acceptance[5]

Reproduction (stock showcase, admin, console from this tree):

  1. 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).
  2. The 409 sentence appears verbatim in the element [data-testid="studio-refusal"].
  3. 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.
  • Actual: visual only.

Full evidence: QA run record #29124.


Generated by Claude Code

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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/main 87d6baef: 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.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
      ]
    }
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    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.

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions