mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 10:39:32 +01:00
feat(story-mode): a run panel shows what its command printed
From review round 8: you asked to see a dry run, Claude's balloon said
"Here's the dry run you asked for", and the page showed nothing of it.
A run panel's terminal card now prints, dimmer under the commands, the
first lines its last command printed (the last ones when it failed:
that is where an error is).
The card is drawn on the canvas, so it goes into a saved image someone
may share: a line that looks like a secret (token, password, api key,
credential, inside identifiers too) is left out, addresses and long
token-like strings are masked, and Claude Code's own markers ("(Bash
completed with no output)", tool-error tags) are dropped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b19317175e
commit
e892278abc
17 files changed
+296
-7
No files matched your search
+25
-1
@@ -2164,6 +2164,19 @@ function montageSummary(evs: StoryEvent[]): string {
|
||||
return parts.length ? parts.join(', ') : 'a few more steps';
|
||||
}
|
||||
|
||||
/**
|
||||
* One output line as the terminal card may show it. The card is drawn on the canvas, so it goes into a
|
||||
* saved image someone may share: a line that looks like a secret is left out, addresses and long
|
||||
* token-like strings are masked, and Claude Code's own markers go.
|
||||
*/
|
||||
function printableLine(line: string): string | null {
|
||||
const text = line.replace(/<\/?[a-z_]+>/g, '').trim();
|
||||
if (!text || /^\(Bash completed with no output\)$/.test(text)) return null;
|
||||
// Anywhere in the line, inside identifiers too (`API_TOKEN=`, `db_password:`): losing a harmless line is cheap.
|
||||
if (/token|secret|passw(or)?d|api[_-]?key|authorization|bearer|private[_ -]?key|credential/i.test(text)) return null;
|
||||
return clip(text.replace(/[\w.+-]+@[\w-]+(\.[\w-]+)+/g, '…@…').replace(/[A-Za-z0-9+/=_-]{32,}/g, '…'), 90);
|
||||
}
|
||||
|
||||
/** You asked for a failing run first (test first), or Claude said the failure was expected. */
|
||||
const PLANNED_IN_PROMPT =
|
||||
/\b(test[- ]first|tdd|fail(s|ing)? (first|on purpose)|failing tests? first|on purpose|red first|before (the )?(code|module|function|implementation) exists)\b/i;
|
||||
@@ -2439,8 +2452,18 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'run':
|
||||
case 'run': {
|
||||
panel.notes = notesOf(evs);
|
||||
// What it printed, so a dry run or a listing you asked to see is on the page, not only in the drawer.
|
||||
const lastOut = [...evs].reverse().find((e) => e.output?.trim() && !e.running);
|
||||
if (lastOut?.output) {
|
||||
const lines = lastOut.output
|
||||
.split('\n')
|
||||
.map(printableLine)
|
||||
.filter((l): l is string => !!l);
|
||||
const picked = lastOut.ok === false ? lines.slice(-2) : lines.slice(0, 3);
|
||||
if (picked.length) panel.printed = picked;
|
||||
}
|
||||
story = evs.every((e) => e.running)
|
||||
? `Claude starts ${evs.length === 1 ? 'a command' : `${evs.length} commands`} in the background.`
|
||||
: errors && errors === evs.length
|
||||
@@ -2456,6 +2479,7 @@ function decorate(draft: DraftPanel, events: StoryEvent[], turn: StoryTurn, inde
|
||||
: 'Claude works in the terminal.';
|
||||
director = `${tools} · ${shortCommand(evs[0])}${errors ? ` · ${plural(errors, 'error')}` : ''}`;
|
||||
break;
|
||||
}
|
||||
case 'save': {
|
||||
panel.notes = notesOf(evs);
|
||||
const last = evs[evs.length - 1];
|
||||
|
||||
@@ -167,6 +167,8 @@ export interface StoryPanel {
|
||||
balloon?: { who: 'you' | 'claude' | 'note'; text: string };
|
||||
files?: string[];
|
||||
notes?: string[];
|
||||
/** What a run panel's last command printed, for its terminal card: the first lines, the last ones when it failed. */
|
||||
printed?: string[];
|
||||
helpers?: Array<{ name: string; type: string; task?: string; event: number }>;
|
||||
added?: number;
|
||||
removed?: number;
|
||||
|
||||
@@ -688,7 +688,8 @@ const StoryArt = (() => {
|
||||
}
|
||||
return { w, h };
|
||||
}
|
||||
function terminal(ctx, x, y, w, h, lines, f) {
|
||||
/** A terminal card: the commands (`› …`), then, dimmer, the first lines one of them printed. */
|
||||
function terminal(ctx, x, y, w, h, lines, f, printed = []) {
|
||||
rr(ctx, x, y, w, h, 7);
|
||||
inked(ctx, C.line, 2.5);
|
||||
ctx.fillStyle = C.balloonInk;
|
||||
@@ -708,10 +709,21 @@ const StoryArt = (() => {
|
||||
ctx.fillStyle = i === Math.min(rows, lines.length) - 1 ? C.yellow : C.balloon;
|
||||
ctx.fillText(text.slice(0, shown), x + 9, y + 30 + i * 14);
|
||||
});
|
||||
if (f % 30 < 15) {
|
||||
const n = Math.min(rows, lines.length);
|
||||
const n = Math.min(rows, lines.length);
|
||||
const out = printed.slice(0, Math.max(0, rows - n));
|
||||
out.forEach((line, i) => {
|
||||
const text = fitText(ctx, line, w - 18);
|
||||
const at = 8 + (n + i) * 12;
|
||||
const shown = Math.floor(interpolate(f, [at, at + 10], [0, text.length]));
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.72;
|
||||
ctx.fillStyle = C.balloon;
|
||||
ctx.fillRect(x + 9, y + 22 + n * 14, 6, 10);
|
||||
ctx.fillText(text.slice(0, shown), x + 9, y + 30 + (n + i) * 14);
|
||||
ctx.restore();
|
||||
});
|
||||
if (f % 30 < 15) {
|
||||
ctx.fillStyle = C.balloon;
|
||||
ctx.fillRect(x + 9, y + 22 + (n + out.length) * 14, 6, 10);
|
||||
}
|
||||
}
|
||||
function globe(ctx, cx, cy, r, f) {
|
||||
@@ -1248,7 +1260,7 @@ const StoryArt = (() => {
|
||||
draw(ctx, W, H, f, p, hit) {
|
||||
const tw = Math.min(W * 0.62, 260);
|
||||
const notes = p.panel.notes?.length ? p.panel.notes : ['…'];
|
||||
terminal(ctx, 14, 92, tw, 140, notes, f);
|
||||
terminal(ctx, 14, 92, tw, 140, notes.slice(0, p.panel.printed?.length ? 3 : 8), f, p.panel.printed || []);
|
||||
hit('output', 14, 92, tw, 140, 'See the commands and their output');
|
||||
agent(ctx, W - 48, 250, {
|
||||
frame: f,
|
||||
|
||||
Reference in new issue
Block a user