Skip to content

workflow: a refused dialog edit is announced twice — the page keeps its own role="alert" copy beside the dialog's #2202

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: c72b3131ad0140a8b3dfd681f98e9d00
Branch: dev/1-single-refusal-alert
Base: 77a6b4d
At: 2026-11-02T16:26:46Z
Owner: Dev-1
Tier: T2

What happens today

A refused Rename / Delete / Create now keeps its dialog and shows the host's sentence inside it (#2193, fixed in the PR that this issue follows). But the page keeps its own copy of the same sentence in a page-level role="alert", so one refusal produces two live-region announcements of the same words.

Measured by FlyBot Test on 3f8e951312ecf1bb9a3c28b30a3358d6df14861d: RENAME_ALL=2, BEFORE_REOPEN_ALL=2, with one of each pair living in the sidebar's .wf-err. role="alert" is a live region — a visual mask does not hide it from the accessibility tree, so a screen-reader user hears the same refusal twice.

This is not the same as "the old node is still drawn but covered": the covered copy is still in the DOM and still announced. The write target of #2193 was "readable inside the dialog"; this issue is "exactly once, in the accessibility tree".

Expected behaviour

One refusal is announced once. Either the page-level copy stops rendering for a refusal the dialog is showing, or it stays in the DOM with aria-hidden="true" — decided by whichever keeps the page's own error reporting intact for refusals that have no dialog. Visual masking is not an answer.

Scope

  • plugins/workflow/client.js — the page-level wf-err (the one around :2205-2206 at the tip) and the dialog's copies (:1394, :1408, :1516, :1659) must not both announce the same refusal. The existing refusal and success paths are otherwise unchanged, and a refusal that has no dialog still reports as it does today.
  • plugins/workflow/test/client.test.js — the red-first test below.
  • docs/subsystems/workflow.md and changelog.d/<this issue>-single-refusal-alert.fixed.md.

Reproduction (red first)

Count role="alert" over the whole rendered tree, not inside the dialog. At the tip, opening Delete and letting the host refuse yields two nodes carrying the same sentence; the test asserts exactly one.

The shape matters and is part of the acceptance: the existing assertions introduced by #2193 all search inside role="dialog", which is precisely why the duplicate was not caught. This test must use the whole-tree helper (alertsOf(settings)-style) and assert:

  1. the whole tree carries exactly one role="alert" for the refusal;
  2. its text is the host's sentence verbatim;
  3. it is inside the open dialog (the dialog is where the action happened).

A second test covers the case that must not regress: a refusal with no dialog open still produces exactly one announcement, as it does today.

Three criteria (binding)

  1. In the whole tree — not only inside the dialog — a refusal may carry only one role="alert".
  2. A node that is under a mask must be aria-hidden="true" or not rendered at all; visual occlusion alone is not acceptable.
  3. The red-first assertion counts alerts over the whole tree shape, never findAll(dialog, …).

Prior art / duplicates

Searched before opening (announced twice, duplicate role=alert, wf-err alert screen reader):

  • #2193 — the refusal being invisible inside the dialog; this issue is the opposite face of the same refusal (announced twice), filed while the fix was in review, per the rule that a known gap lives in the issue thread rather than in a pull-request body edited after CI started.
  • #1521 / #1522 — a stage control reporting silently; different surface, closed.
  • #2187 — the read-only edit surface; unrelated.

No open or closed issue counts role="alert" over the whole tree for a refused Workflow edit.

Acceptance

  1. One refusal, one role="alert" in the whole tree, carrying the host's sentence verbatim.
  2. The covered page-level node is either absent or aria-hidden="true"; the test proves it through the accessibility-relevant attribute, not through geometry.
  3. A refusal without a dialog still announces exactly once.
  4. The new test fails before the fix and passes after; plugins/workflow suite green, npm run verify green, arch-docs: OK.

Activity

  1. HugoXK commented on Nov 2, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: c72b3131ad0140a8b3dfd681f98e9d00
    Reason: merged via PR #2212 (2a0bbb2)
    At: 2026-11-02T19:46:24Z

  2. added a commit that references this issue on Nov 2, 2026
    2a0bbb2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions