fix(story-mode): read right by a screen reader, and the last of review round 6

Accessibility (review6-c):
- A panel is named "Panel 2 of 7" and its caption is read once, as its
  content (it was the name and the content both).
- Balloons say who speaks, visually hidden ("Claude says: ", "You say: ",
  "Claude asks: ", "Notice: "); Save image leaves those words out.
- The overlay is named "Story Mode: <session>", the drawer by its kind and
  title ("Diff 2 files"), the final panel's chips by what they open, and
  the wire's panel labels are headings.
- While a file preview opened from a panel is up, the overlay is not
  aria-modal (the preview sits beside it), and is again once focus returns.
- The focus ring on a click region gets a light band under its dashes; on
  the loop's magenta halftone it vanished.

From review6-b:
- "Mission complete!" no longer shows while a helper or a background
  command is still out ("To be continued…").
- A background command's drawer says "Started in the background…", then
  "Finished in the background, exit code 0…", instead of the launch note
  meant for Claude ("do not poll that file").
- `sed -n '5,25p' README.md` is a printed file, offered in the drawer.
- A report turn no longer counts the helper it answers as one it started.
- A helper's `cd /work/x && ls` keeps its folder as ".".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 13:51:11 +02:00
1 parent 89c222086a
commit 6e45831909
7 files changed
+83 -17

No files matched your search

@@ -104,7 +104,7 @@
"filesChanged": 0,
"added": 0,
"removed": 0,
"helpers": 1,
"helpers": 0,
"helpersRunning": 0,
"panels": [
{
@@ -141,7 +141,7 @@
"kind": "complete",
"ok": null,
"story": "Done in 21 seconds.",
"director": "turn_duration · 21 s · 2 tool calls · 1 helper",
"director": "turn_duration · 21 s · 2 tool calls",
"balloon": "I built rename_by_date.sh, the helper agent wrote README.md, and the script passed every test I ran on sample files in samples/. Nothing is committed because this folder isn't a git repository, even though the session context said it was.",
"tests": null,
"roundsOk": null,
@@ -117,7 +117,7 @@
"filesChanged": 0,
"added": 0,
"removed": 0,
"helpers": 1,
"helpers": 0,
"helpersRunning": 0,
"panels": [
{
@@ -144,7 +144,7 @@
"kind": "complete",
"ok": null,
"story": "Done in 9 seconds.",
"director": "turn_duration · 9 s · 1 tool call · 1 helper",
"director": "turn_duration · 9 s · 1 tool call",
"balloon": "The background helper has finished README.md, and I've checked its work.",
"tests": null,
"roundsOk": null,
+8 -2
View File
@@ -151,9 +151,12 @@ describe('story-mode.js overlay', () => {
'save',
'complete',
]);
expect($('.story-panel[data-kind="mission"] .story-balloon')!.textContent).toBe(
expect($('.story-panel[data-kind="mission"] .story-balloon-text')!.textContent).toBe(
'The checkout button does nothing on phones. Can you fix it?'
);
// Who speaks, for a screen reader (visually hidden; the art shows it).
expect($('.story-panel[data-kind="mission"] .story-balloon .story-vh')!.textContent).toBe('You say: ');
expect($('.story-panel[data-kind="mission"]')!.getAttribute('aria-label')).toBe('Panel 1 of 7');
expect($('.story-panel[data-kind="mission"] .story-balloon')!.className).toContain('story-balloon--you');
expect($('.story-panel[data-kind="investigate"] .story-cap-story')!.textContent).toBe(
'Claude reads 2 files to find its way around.'
@@ -535,7 +538,8 @@ describe('story-mode.js overlay', () => {
await settle();
const balloon = $('.story-panel[data-kind="mission"] .story-balloon')!;
expect(balloon.className).toContain('story-balloon--note');
expect(balloon.textContent).toBe('Agent "Docs" finished');
expect(balloon.querySelector('.story-balloon-text')!.textContent).toBe('Agent "Docs" finished');
expect(balloon.querySelector('.story-vh')!.textContent).toBe('Notice: ');
});
it("the helper drawer fetches and lists the helper's own steps", async () => {
@@ -680,6 +684,8 @@ describe('story-mode.js overlay', () => {
await settle();
sizeAndDraw();
expect($('.story-panel[data-kind="complete"] .story-chips')!.textContent).toContain('1 helper, 1 still working');
// Work still out is not a finished mission.
expect($('.story-panel[data-kind="complete"] .story-splash-title')!.textContent).toBe('To be continued…');
$('.story-panel[data-kind="backup"] .story-hit')!.click();
await settle();
expect($('.story-drawer-sub')!.textContent).toContain('in the background');
+11
View File
@@ -735,6 +735,10 @@ describe('buildStoryTurns', () => {
const first = buildStoryPage(turns[0]);
expect(first.events[1]).toMatchObject({ intent: 'test', ok: true });
expect(first.events[1].running).toBeUndefined();
// Its drawer says what happened, not the launch note meant for Claude.
expect(first.events[1].output).toBe(
'Finished in the background, exit code 0. Its report is the turn it came back in.'
);
const second = buildStoryPage(turns[1]);
expect(second.panels.find((p) => p.kind === 'check')).toMatchObject({
caption: { story: 'The tests pass.' },
@@ -751,6 +755,7 @@ describe('buildStoryTurns', () => {
const page = buildStoryPage(alone);
expect(page.events[1]).toMatchObject({ running: true });
expect(page.events[1].ok).toBeUndefined();
expect(page.events[1].output).toBe('Started in the background. Its result comes back as a report of its own.');
expect(page.panels.find((p) => p.kind === 'check')!.caption.story).toBe(
'Claude starts the tests in the background.'
);
@@ -1033,6 +1038,12 @@ describe('buildStoryPage', () => {
expect(whole.hunks[0].atEnd).toBeUndefined();
});
it('sed -n prints part of a file, like head', () => {
expect(printedFiles("sed -n '5,25p' README.md")).toEqual(['README.md']);
expect(printedFiles('sed -n 10p src/a.py && ls')).toEqual(['src/a.py']);
expect(printedFiles("sed -i 's/a/b/' a.py")).toEqual([]);
});
it('files a command prints are on its event, resolved against its cwd, whatever it does', () => {
const lines = [
prompt('rewrite and test'),