fix(story-mode): background commands, and Save image on a phone

From review round 5 (a phone with touch):

- Save image on a phone exported blank art for every panel not yet scrolled
  into view (the frame loop draws only what is on screen). It now draws
  every panel completely first, and the file name carries the turn number,
  so saves of different turns no longer overwrite each other.
- A test command sent to the background ("Command running in background
  with ID …") drew as "The tests pass." with POW before any test ran. It is
  now running: "Claude starts the tests in the background.", tagged "in the
  background" on the wire, and the final panel says a command is still
  running. Its task notification settles it from the exit code, and reading
  the job's output file that reports tests is a check ("The tests pass.",
  7 passed) instead of "Claude looks around".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 12:39:04 +02:00
1 parent 814bec739d
commit cdb7b5d55f
4 files changed
+112 -17

No files matched your search

+13 -5
View File
@@ -2370,7 +2370,7 @@ Object.assign(CodemanApp.prototype, {
else rt.pending = true;
},
_storyDrawPanel(rt) {
_storyDrawPanel(rt, full = false) {
const st = this._story;
if (!rt.panel || !rt.w || !rt.h) return;
const scene = StoryArt.SCENES[rt.kind];
@@ -2400,7 +2400,7 @@ Object.assign(CodemanApp.prototype, {
if (job.next < job.total) {
const b = job.canvas.getContext('2d');
// Without a frame loop (tests, a browser that never paints) the whole background at once.
const steps = st?.raf ? 1 : job.total - job.next;
const steps = st?.raf && !full ? 1 : job.total - job.next;
for (let i = 0; i < steps && b; i++, job.next++) {
const top = (StoryArt.VH / job.total) * job.next;
const bottom = top + StoryArt.VH / job.total;
@@ -2533,6 +2533,12 @@ Object.assign(CodemanApp.prototype, {
const page = st?.data?.page;
if (!page || !st.panels.length) return;
await document.fonts?.ready;
// Every panel finished, on screen or not: on a phone the frame loop draws only what was scrolled to.
for (const rt of st.panels) {
if (!rt.w) this._storySizePanel(rt);
this._storyDrawPanel(rt, true);
rt.pending = false;
}
const sheet = st.pageEl.getBoundingClientRect();
const head = 64;
const scale = Math.max(1, Math.min(2, 12000 / (sheet.height + head)));
@@ -2588,7 +2594,8 @@ Object.assign(CodemanApp.prototype, {
const a = document.createElement('a');
const name = (st.sessionLabel.textContent || 'session').replace(/[^A-Za-z0-9._-]+/g, '-').slice(0, 40);
a.href = URL.createObjectURL(blob);
a.download = `story-${name}-${(page.at || '').slice(0, 10) || 'turn'}.png`;
const turnNo = (st.data?.turns || []).findIndex((t) => t.id === page.id) + 1;
a.download = `story-${name}-${turnNo ? `turn-${turnNo}-` : ''}${(page.at || '').slice(0, 10) || 'turn'}.png`;
document.body.appendChild(a);
a.click();
a.remove();
@@ -2780,8 +2787,9 @@ Object.assign(CodemanApp.prototype, {
label.textContent = this._storyShortPath(ev.label);
if (label.textContent !== ev.label) label.title = ev.label;
const tag = document.createElement('i');
tag.className = `story-tag story-tag--${ev.ok === false ? 'bad' : ev.pending ? 'pending' : ev.intent}`;
tag.textContent = ev.ok === false ? 'error' : ev.pending ? 'running' : ev.intent;
const waiting = ev.pending || ev.running;
tag.className = `story-tag story-tag--${ev.ok === false ? 'bad' : waiting ? 'pending' : ev.intent}`;
tag.textContent = ev.ok === false ? 'error' : ev.running ? 'in the background' : waiting ? 'running' : ev.intent;
body.append(who, ' ', tool, ' ', label, ' ', tag);
li.append(t, body);
return li;