fix(story-mode): what a non-technical reader would believe, from the cold-reader review

Review round 5 had an agent read only the comic of the nine real sessions
(20 turns), write down what it believed happened, and only then compare
with the transcripts. Three panels told it something false, one hid the
failure the user asked to see:

- A push that went through, followed by a check that failed in the same
  command, drew as a failed push; the output (`[new branch]`, `->`) now
  decides: "Claude saves the work in 2 commits and uploads it, but a step
  after that fails."
- A formatter's "would be reformatted" preview, printed before it fixed
  the file, made a clean run a failure: the last verdict wins.
- A blocked command ended in confetti and "Mission complete!": a run panel
  where everything failed says "Claude tries a command, and it is
  blocked.", and a turn where nothing worked ends "Turn over" / "Nothing
  Claude tried worked, so the job is not done." with a neutral robot.
- The loop stamped only the final verdict (POW ×2), so a first run that
  failed on purpose never showed. It now marks every test run red or green,
  and says it: "Fix and test: the first run fails, the second passes." /
  "Build and test, 4 rounds. A check fails once, then everything passes."

Also:
- Files written inside a check or a save reach the plain caption ("Claude
  writes 4 files; the tests pass.").
- Balloons keep the next sentence when the first reports a problem ("…left a
  stray file. I'll remove it…") or is very short ("Not done. The harness
  blocked sleep 65…"); Claude's last words keep a caveat that follows them
  ("Nothing is committed because this folder isn't a git repository.").
- The advisor never shares a panel with helpers that do work; ToolSearch
  reads "Claude gets the tools it needs ready."; Codeman's own case file is
  not drawn as a file Claude read; looking around shows what Claude meant
  (the call's description), not raw commands; a montage at the start of a
  turn reads "Claude gets started".
- Words: boards count tests ("19 tests"), "uploads it" replaces "sends it
  up", and the prompt balloon keeps 400 characters.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 12:32:40 +02:00
1 parent 0effbb9ce4
commit 814bec739d
14 files changed
+307 -109

No files matched your search

+53 -14
View File
@@ -1080,7 +1080,7 @@ const StoryArt = (() => {
const dots = Math.min(42, total);
const fail = bad ? Math.max(1, Math.round((bad / total) * dots)) : 0;
const fill = running ? (f * 0.6) % (dots + 6) : Math.min(dots, (f - 4) * 1.6);
b = board(ctx, 16, 106, `${total} checks`, dots, fail, fill);
b = board(ctx, 16, 106, `${total} tests`, dots, fail, fill);
} else {
b = verdict(ctx, 16, 106, 'checks', p.panel.ok, f);
}
@@ -1139,7 +1139,7 @@ const StoryArt = (() => {
if (total) {
const dots = Math.min(28, total);
const fail = bad ? Math.max(1, Math.round((bad / total) * dots)) : 0;
b = board(ctx, bx, 116, `${total} checks`, dots, fail, Math.min(dots, (f - 4) * 1.6));
b = board(ctx, bx, 116, `${total} tests`, dots, fail, Math.min(dots, (f - 4) * 1.6));
} else b = verdict(ctx, bx, 116, 'checks', p.panel.ok, f);
hit('output', bx, 116, b.w, b.h, 'See the test output');
}
@@ -1169,17 +1169,50 @@ const StoryArt = (() => {
ctx.closePath();
inked(ctx, C.yellow, 2);
ctx.restore();
// Rounds and verdict stack under the top corner, which the balloon may take.
sfx(
ctx,
`×${p.panel.rounds || 1}`,
W - 60,
214,
30,
C.yellow,
-0.1,
spring(f - 20, { damping: 9, stiffness: 160 })
);
// Every round's verdict, in order: a first run that failed (often the one you asked for) shows.
const marks = p.panel.roundsOk || [];
if (marks.length) {
const shown = marks.slice(-6);
const gap = 22;
const x0 = W - 60 - ((shown.length - 1) * gap) / 2;
shown.forEach((ok, k) => {
const pr = spring(f - 20 - k * 6, { damping: 10, stiffness: 160 });
if (pr <= 0.01) return;
const cx = x0 + k * gap;
ctx.save();
ctx.translate(cx, 214);
ctx.scale(pr, pr);
ctx.beginPath();
ctx.arc(0, 0, 9, 0, TAU);
inked(ctx, ok ? C.go : C.magenta, 2.5);
ctx.strokeStyle = C.balloon;
ctx.lineWidth = 2.5;
ctx.lineCap = 'round';
ctx.beginPath();
if (ok) {
ctx.moveTo(-4, 0);
ctx.lineTo(-1, 3.5);
ctx.lineTo(4.5, -3.5);
} else {
ctx.moveTo(-3.5, -3.5);
ctx.lineTo(3.5, 3.5);
ctx.moveTo(3.5, -3.5);
ctx.lineTo(-3.5, 3.5);
}
ctx.stroke();
ctx.restore();
});
} else
sfx(
ctx,
`×${p.panel.rounds || 1}`,
W - 60,
214,
30,
C.yellow,
-0.1,
spring(f - 20, { damping: 9, stiffness: 160 })
);
// The verdict stamp only where it clears the board (a narrow panel shows the board alone).
if (p.panel.ok !== undefined && W - 108 > bx + 131) {
const word = p.panel.ok ? 'POW!' : 'KRAK!';
@@ -1392,6 +1425,10 @@ const StoryArt = (() => {
}
return;
}
if (p.page.noneWorked) {
agent(ctx, ax, 250, { frame: f, scale: sc, mood: 'neutral', l: [-34, -48], r: [34, -48] });
return;
}
const colors = [C.cyan, C.magenta, C.yellow, C.go];
for (let i = 0; i < 42; i++) {
const x = rnd(i) * W;
@@ -2272,7 +2309,9 @@ Object.assign(CodemanApp.prototype, {
? 'Stopped!'
: page.superseded
? 'To be continued…'
: 'Mission complete!';
: page.noneWorked
? 'Turn over'
: 'Mission complete!';
const chips = rt.splash.querySelector('.story-chips');
const items = [];
if (page.durationMs !== undefined) items.push({ text: this._storyDuration(page.durationMs) });