Repository navigation
Description
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: 373d72b15bbd491fa7efbb65c382ae3f
Branch: dev/3-p445-live-row-name
Base: 9863992
At: 2026-11-02T23:34:31Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
Credit: found by FlyBot Product (P-445 audit on live main).
Kind: bug (accessibility regression introduced by #2224)
Source: FlyBot Product audit P-445; code and docs re-read by Lead on live main 986399246ff8c26bd418978785ce1199dbd239ca.
Dedupe (searched before opening)
Open issues matching accessible name, screen reader, aria-label, wf-row, {count} running: none. aria-label matches only #1306 (workflow list filter, unrelated). #1446 (same-name workflows) is closed not_planned, so duplicate lane names stay legal and reachable — plugins/workflow/src/service.js:1181 only appends a copy suffix once, so two Projects can each hold a lane named 发布交付流水线.
What is wrong
The live-row counter added by #2224 is carried by an aria-label on the row button:
plugins/workflow/client.js:2212—'aria-label': (item.activeCount ?? 0) > 0 ? tr('{name} · {count} running', { name: item.name, count: item.activeCount }) : undefined
An aria-label replaces the accessible name computed from the row's content. The content carries what the reader needs to tell rows apart:
:2223— the Project name (.wf-proj),:2224— the preview (.wf-t2),:2222— theWorkflowbadge.
So on a lane with live runs the accessible name becomes Delivery · 1 running and drops Project and preview. Two lanes can legally share a name (see dedupe above); a reader then hears two identical buttons while the visible rows differ.
This is a regression: on base 2a0bbb24 the row had no aria-label, so its name came from the content (Project and preview included) and two same-named lanes were distinguishable.
Reproduction (readers, no browser needed)
Build a Section whose sidebar row list holds two lanes with the same name (Delivery), different projectId (p1, p2), one live run each (activeCount: 1) and different previews. Read each row button's accessible name:
SAME_ROW 0 "Delivery · 1 running"
SAME_ROW 1 "Delivery · 1 running"
while the visible text differs (DeliveryWorkflowAlphaRunning #2214 · step 2/31 vs DeliveryWorkflowBetaRunning #2219 · step 1/31).
Same fixture on base 2a0bbb24: aria-label is undefined, the name comes from the content — including Alpha/Beta and the progress step — so the two rows are distinguishable.
Expected
- Two rows that differ visibly must differ in their accessible name, and each name must carry its own Project.
- The counter must stay reachable: a single live row still names its count, and an idle row still draws no
aria-label, no dot and no digit — the#2224contract (docs/subsystems/workflow.md:44) must not regress. - A live row's name must not lose anything an idle row's name gives: the
Workflowbadge and the preview line are part of the name today on idle rows, so a live row that names Project but drops both is only half fixed.
What is not broken: the Project badge still paints. Product's probe on the same tree reads PROBE_LIVE_VISIBLE "Delivery LaneWorkflowAlpha等待任务2" (i.e. Alpha and the preview 等待任务 are in the visible text, same shape as the idle row PROBE_IDLE_VISIBLE "Delivery LaneWorkflowAlphaWaiting for a task"), against PROBE_LIVE_ARIA_LABEL "Delivery Lane · 2 running" and PROBE_LIVE_NAME_HAS_PROJECT false vs PROBE_IDLE_NAME_HAS_PROJECT true. So there is exactly one defect here — the row-level aria-label overriding the descendants (:2223 is .wf-proj and it is unconditional, live or idle) — not a missing badge.
Red-first (must be captured on live main before the fix)
- (a) the two same-named live rows' accessible names are not equal and each contains its own Project (today: equal, no Project);
- (b) a live row's name still contains the
Workflowbadge text and the preview line, as an idle row's does (today: neither); - (c) the reverse proofs must not turn red: a single live row's name still contains the count, and
activeCount: 0still yields noaria-label.
Existing tests this touches (do not delete an assertion)
#2222's own tests assert on the prop, not on a computed name:
plugins/workflow/test/client.test.js:655—String(row.props['aria-label'] ?? '').includes('3'), "the accessible name says how many runs are live";:681—...includes('2'), "the read-only row still names the live count".
Whichever mechanism is chosen, the count must stay asserted. If aria-label goes away, those two assertions have to be rewritten to the new carrier (a visually-hidden span inside the row) — replacing them with a weaker or deleted assertion would hide exactly the regression this issue is about.
Suggested fix (XS, pick one)
Preferred: drop the aria-label and put the count in a visually-hidden span that is not aria-hidden. This file has no visually-hidden rule yet (grep -n 'sr-only\|visually\|clip:' plugins/workflow/client.js finds nothing) — the plugin ships its own sheet (const CSS at :2326, the live styles at :2339-2341, injected at :1927), so the fix adds one .wf-sr rule there and uses it for the word running beside the visible digit. Today :2225-2227 carries the number inside wf-live-wrap with aria-hidden: true, so dropping the label alone would silence the count and reopen #2224's own bug — the span is what keeps it. This is the option that keeps all three pieces (Project, preview, Workflow word) because the name then comes from the content again, which is exactly the pre-#2224 shape; the two assertions below get rewritten to the new carrier.
The alternative — keep aria-label and append Project (and preview) — needs a hand-built string that reproduces the whole row (name, Workflow, Project-or-Unassigned, preview, count) and drifts from the visible text the moment any of those change; it also cannot name the preview without a second lookup. Take it only if a reviewer prefers one line over correctness.
Either way the same change must touch:
docs/subsystems/workflow.md:44— the sentence that says a live row "carries{name} · {count} runningas its accessible name";- a new
changelog.d/entry for this PR (the#2222entrychangelog.d/2222-list-live-count.fixed.mdis already on main and stays as history).
Scope
plugins/workflow/client.js (the row's accessible name), its test file, that one doc sentence and one changelog entry. No change to the counter's visibility, to the read-only lock, or to the row's click/context-menu behaviour.
Tier: T2 (plugins/workflow client surface).
Activity
HugoXK commentedon Nov 3, 2026
Release
Written-by: FlyBot Lead
Released: 373d72b15bbd491fa7efbb65c382ae3f
Reason: merged as 2ec6efb
At: 2026-11-03T00:17:55Z