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: 8a85ea8d0d52462688308708bb3f234c
Branch: dev/4-workflows-duplicate-sentence
Base: 90a5944
At: 2026-11-05T11:16:08Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
What happens
In a read-only window, Settings → Workflows → open any graph: the host's read-only sentence is drawn twice on the same screen. The Settings header draws it once (with a Reload, core/src/client/settings.js:262 → readOnlyLine(..., { className: 'bt-readonly-line-head' })), and the graph's edit bar draws it again, with no control beside it: core/src/client/workflows.js:600-603
locked
? h('span', { className: 'bt-wf-status', role: 'status' }, hostText(readOnly))
: opened?.source === 'own' ? h('button', ... Edit ...) : nullBoth copies carry role="status" and the same text, so a screen reader announces the sentence twice; visually the bar repeats what the header already says.
The repository already states the contract this breaks: core/test/settings-readonly-pages.test.js:241-249 asserts "the standing sentence stays in the dialog header, once per page". It does not catch this page for two reasons: PAGES (:200-208) lists seven pages and workflows is not among them, and the assertion (:245) counts only the bt-read-only class token while this second copy is bt-wf-status.
Reproduced on live main 90a594471b5886737b433e116f6397aa8ca2b6f2 with a throwaway probe (not committed): SettingsDialog({ page: 'workflows' }) header → sentences=1 / reload=1; WorkflowsView({ phase: 'ready', pack, opened: { source: 'own', id: 'w1' }, roster: locked }) edit bar → sentences=1 / reload=0, the two strings byte-identical to the host sentence; in a writable window the bar draws sentences=0 and the Edit button.
Expected
With a graph open in a read-only window the page draws the host's sentence exactly once, in the Settings header where the Reload already is; the edit bar draws neither the sentence nor an editing control, and keeps Back. A writable window is unchanged (sentence absent there too, Edit present). No second live region with the same text.
Red first
A clean 90a59447 tree carrying only the extended tests (new core/test/workflows-readonly-once.test.js plus the updated core/test/workflows-page.test.js):
not ok 43 - #2176: a read-only graph draws no editing control and leaves the host sentence to the header
the edit bar adds no second copy of the host sentence true !== false
not ok 48 - a read-only window draws the host sentence once, not twice, with a workflow graph open
the open graph does not repeat the sentence the header already carries 1 !== 0
# tests 50
# pass 48
# fail 2
The counts are by text block, not by class or role; a second assertion pins "the edit bar draws no role="status" node whose text is the host sentence", so a future re-classing cannot hide it.
Scope
core/src/client/workflows.js:600-603: the locked branch becomesnull(Back stays, Edit was already hidden); the now-unusedhostTextimport goes; the comment states that the sentence belongs to the Settings header, once per page. Do not add a second Reload here — the header's one already sits above the whole page, andWorkflowsViewis rendered only fromWorkflowsPage(:762), which is rendered only from the Settings dialog (settings.js:272), so no window is left with a sentence and no way to answer it.- Tests: the new
core/test/workflows-readonly-once.test.js(header sentence + edit bar = 1, was 2; writable window unchanged), andcore/test/workflows-page.test.js's workflow: a read-only window still offers Edit in Settings → Workflows #2176 assertion, which today requires the page to draw the sentence — it contradicts the "once per page" contract and must move with this change. docs/subsystems/core-client.md: the line saying the page draws that sentence where Edit was is now the opposite of the code (one line).- Client-bundle lane: regenerate
core/client.jsand the carriers. The only patch that carries these sources ispatches/ds-bot/0008-client-bundle.patch, with itsfiles/src/client/workflows.jsmirror and the two test mirrors; any other patch changing is a mismatch to report, not to keep.
Duplicate check
Searched open and closed issues on live main before opening: no open issue covers a duplicated read-only sentence. Closed and about other faces — #2181/#2187/#2200/#2205 (a read-only window still offered Edit/New/Rename/Delete, i.e. the control, not the sentence), #2199 (the sentence was missing across eight pages), #2212 (a refused dialog edit announced twice, a different element). #2521 is the opposite face: an overlay hiding the sidebar's Reload, not a repeated sentence.
Verification already done (bench, not pushed)
Branch dev/4-workflows-duplicate-sentence, commit 7c5f655b7789ade67fa347129599cf9f1d79c58f, tree 6878e85d4fdc3a1a004cb8422652f5beb64248ca, cut from 90a59447; 10 files, +350/-25. After the fix: the new test 3/3; 31 neighbouring suites 154/154; full core 2930/2930; build-client --check exit 0; ds-bot-patches check three ok lines including core/ equals ds-bot/ + patches/ds-bot/; canonical a no-op; arch-docs OK (10 changed file(s)); changelog changelog.d/2524-workflows-duplicate-sentence.fixed.md (changelog.mjs check: well formed). Generated set exactly 0008-client-bundle.patch 1/1, files/src/client/workflows.js 8/7, files/test/workflows-page.test.js 6/4, files/test/workflows-readonly-once.test.js 159/0.
These are local pre-rebase readings and are not carried over: the branch is rebased onto the lane main once it opens, generate runs exactly once there, and every number above is redone on that tip before a PR is opened.
Activity