From 61e1ad64545641a0b7ab0f2e82d1836863a3c0c2 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Sun, 18 Oct 2026 14:52:20 +0200 Subject: [PATCH] test(story-mode): two more real sessions in the corpus; background runs and script writes told right Round 6's two sessions join the corpus (sanitized like the others, and checked to draw the same pages as the originals): tests that do not load, a fix-fail-fix-pass loop, a commit then a push in its own command, a push with a failing step after it, an append; two helpers and a command in the background whose reports come back as turns of their own. Replaying them showed two captions to fix: - A test command that ran in the background and reported back read "The tests pass.", as if they ran during that turn: "The tests run in the background and pass." (events keep `background`). - A python script that rewrote README.md drew as "Claude works in the terminal." beside a separate change panel. A plain command that writes project files (a script fed by a here-document, or one Claude Code says changed files) is an edit: "Claude changes 3 files." Co-Authored-By: Claude Opus 5.5 (1M context) --- src/story-mode.ts | 10 +- src/types/story.ts | 2 + src/web/CLAUDE.md | 2 +- .../background-helpers-reports.expected.json | 449 ++++++++++++++++++ .../background-helpers-reports.jsonl | 81 ++++ .../errors-push-append.expected.json | 328 +++++++++++++ .../story-corpus/errors-push-append.jsonl | 81 ++++ test/story-corpus.test.ts | 33 +- test/story-mode.test.ts | 12 + 9 files changed, 995 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/story-corpus/background-helpers-reports.expected.json create mode 100644 test/fixtures/story-corpus/background-helpers-reports.jsonl create mode 100644 test/fixtures/story-corpus/errors-push-append.expected.json create mode 100644 test/fixtures/story-corpus/errors-push-append.jsonl diff --git a/src/story-mode.ts b/src/story-mode.ts index 97374319..bef315b2 100644 --- a/src/story-mode.ts +++ b/src/story-mode.ts @@ -951,6 +951,8 @@ function reduceInput(name: string, input: unknown, cwd?: string): ReducedInput { input.headline = clip(segment, 200); const writes = heredocWrites(full, cwd); if (writes.length) input.writes = writes; + // A script or command that writes project files is an edit, whatever it was run as. + if (writes.length && (intent === 'run' || intent === 'look')) input.intent = 'edit'; // Files it prints, whatever else it does (`cat a.py && pytest`): the drawer offers them. A // relative name resolves against the line's cwd (printedFiles gives none after a `cd`). const reads = printedFiles(full).map((f) => @@ -1522,6 +1524,7 @@ function applyResult(ev: StoryEvent, r: Extract): v if (r.background) { // Started, not done: its task notification says how it ended. ev.running = true; + ev.background = true; ev.ok = undefined; } // Reading a background job's output file that reports tests is a look at test results. @@ -1533,7 +1536,9 @@ function applyResult(ev: StoryEvent, r: Extract): v ) ev.intent = 'test'; if (r.modified?.length) { - // Changed, but no diff to show: the file is listed, its lines unknown. + // Changed, but no diff to show: the file is listed, its lines unknown. A plain command that did + // it (a script, a formatter) edited files. + if (ev.intent === 'run' || ev.intent === 'look') ev.intent = 'edit'; const known = new Set((ev.edits ?? []).map((d) => d.file)); const more = r.modified .filter((f) => !known.has(f)) @@ -2271,6 +2276,9 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde const failedFirst = checks.slice(0, -1).some((e) => passed(e) === false); if (lastCheck?.running) story = isTest ? 'Claude starts the tests in the background.' : 'Claude starts the checks in the background.'; + // Finished later, in the background: not tests that ran during this turn. + else if (lastCheck?.background && ok !== undefined) + story = `The ${isTest ? 'tests' : 'checks'} run in the background and ${ok ? 'pass' : 'fail'}.`; else if (ok === undefined) story = isTest ? 'Claude runs the tests.' : 'Claude runs the checks.'; else if (!ok && tests && !tests.passed && !tests.failed && tests.errors) story = "The tests don't even load."; else if (!ok && tests && tests.passed && !tests.failed && !tests.errors) diff --git a/src/types/story.ts b/src/types/story.ts index 8f51866c..de980a61 100644 --- a/src/types/story.ts +++ b/src/types/story.ts @@ -128,6 +128,8 @@ export interface StoryEvent { description?: string; /** A command sent to the background ("Command running in background") that has not reported back. */ running?: boolean; + /** A command that ran in the background (its result came back later, as a report of its own). */ + background?: boolean; /** A save that ships the work: `gh pr create` (`pr`) or `git push` (`push`). */ ships?: 'pr' | 'push'; /** How many commits a Bash call makes (`git commit` segments). */ diff --git a/src/web/CLAUDE.md b/src/web/CLAUDE.md index 2313fbd3..6d5e00c1 100644 --- a/src/web/CLAUDE.md +++ b/src/web/CLAUDE.md @@ -54,7 +54,7 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L **Settings surface** (`#appSettingsModal` + `#sessionOptionsModal` + `#createCaseModal`): one `set-*` language shared through a single `:is(...)` id scope in styles.css. App Settings' rail is a table of contents over ONE scrolling document (`switchSettingsTab` scrolls); Session Options and Add Case really switch (`switchOptionsTab` / `switchCaseModalTab`), and their larger per-modal size blocks are the design, not drift. ⚠️ **The load/save contract is `getElementById` by id**: renaming or dropping a control id silently stops it loading or saving. ⚠️ The Session Options "Session" entry still keys off `context` (label-only rename). ⚠️ Add Case keeps its legacy `.form-row` markup via an adapter; every `
` there needs `.set-adv-chev` plus both marker suppressions. ⚠️ Model cards and the effort segment are views over hidden `