The owner asked to merge the turn picker with the new input field. The prompts you already sent
(the picker and its arrows) now sit on the left of the bottom bar, the next message beside them,
the hint under the field; on a narrow screen the turns get their own row above it. The top bar
keeps the title, the reading levels, Save image and close, so a phone's bar is two rows. On a
touch screen the ready hint (Enter and Shift+Enter) is hidden: it means nothing on a touch
keyboard, and the hints that matter still show.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rows were re-fitted when the main area or the sheet's width changed, but
a change of words alone (the Director's cut toggled with the wire stacked
under the sheet, another reading level, a caption that grew, the fonts
arriving) leaves both alone while the final panel's in-flow words take
another height. The resize observer now watches the page's height too and
re-fits on the next frame: the same boxes give the same values and nothing
is written, so it settles (measured: 0 passes while idle, at most 2 per
change).
Bigger rows mean bigger panel backgrounds: 32 of them at 2560x1440 and a
2x screen came to 342 MB. The cache keeps at most 160 MB as well; a panel
that shows a background it lost keeps its own copy, only reuse is lost.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The comic sheet was capped at 1240px with empty margins beside it, the
Director's cut wire was a 320px box, and a short page ended half way down
the window. Now the stage takes the full width (16px gutters kept), the
wire is clamp(320px, 25vw, 460px) wide and runs the main area's whole
visible height, sticky, its list scrolling inside, and the rows grow to
fill the visible height.
Row heights come from a pure function next to storyLayoutSpans
(storyRowHeights): the visible height is shared evenly between the page's
rows for the active column count (6 over a 1000px sheet, 2 over 600px; one
column keeps its natural heights), never under 270px (a longer page keeps
270px rows and scrolls as before) and never over a cap: 540px (twice the
drawn size: past it a one-row page is mostly background and the text, which
stops growing at 1.45x, looks lost), and the height at which a row's
narrowest panel would get a scene stage under 280 units wide (the mission's
exclamation mark ran off the panel below that, which is where a span-2 panel
turns into a portrait). What a capped row cannot take goes to the rows that
can, mostly the final panel's full-width row; its scene keeps a stage wide
enough (440 units) to stand beside its words. When the final panel's
in-flow words make its row taller, the other rows give that height up, so
the page still ends at the bottom. A capped page leaves blank paper, never a
band of background: the sheet reaches the visible bottom.
The values are plain px written from JS (each panel's --story-panel-min,
the page's grid-template-rows and --story-row-h, which also feeds
grid-auto-rows, so there is one 270 default), and --story-panel-min is
registered as a <length>: _storySizePanel and _storyFitCaption read it with
parseFloat, which a raw calc() would have turned into NaN. The comic text
scales with --story-k (the shortest row over 270, at most 1.45), set on the
page; the you/notice balloons sit below the caption by the same factor.
It is recomputed by the existing ResizeObserver, which now also watches the
main area (window, drawer padding, a bar added later) and the sheet's width
(the Director's cut toggle), once per delivery, and on every render. It
reads boxes that its writes cannot change, and the main area keeps its
scrollbar gutter, so it never oscillates; panels still resize through
_storySizePanel, whose draws the frame loop rations. Main's bottom padding
is 16px.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Director's cut rebuilt the whole wire with replaceChildren on every
refresh (every 4 s poll and SSE burst of a running turn), so it never
followed new events and a rebuild could throw the reader's place, focus
and hover away.
Rows are now keyed by event id and headings by the row they head (a panel
that changes kind or number as the turn grows keeps its heading node).
Unchanged nodes are reused, a changed row is patched in place (a running
tag flips to its result on the same node), new rows are inserted in
order with a short transform and opacity entrance, and a new turn
replaces the list. _storyEventRow still builds every row.
While the wire follows, it glides to the newest row with a critically
damped spring on animation frames: a burst of updates moves the target
and the glide keeps its speed, so there is no stop and restart. Measured
against the browser's own smooth scrollTo on a growing transcript, the
spring moved a median 8 px a frame over ~25 frames with no stalls, while
scrollTo snapped at ~41 px a frame over ~10 frames and its read of the
new scrollHeight forced a layout inside the update (a 57 ms long task).
Scrolling up by hand (wheel, touch, scroll keys, the scrollbar) stops
following and shows a pill that counts the rows that came in since;
clicking it, or scrolling back to the bottom, follows again. The glide
only moves down and is never read as the reader scrolling up. A running
newest turn, and the Director's cut turned on during one, open at the
bottom, following; a finished turn opens at the top. Under
prefers-reduced-motion there is no entrance and the list jumps.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With three reading levels the third bar row (the levels and Save image)
is full at 390px, and a running turn's LIVE pill wrapped onto a fourth
row (the bar went from 170 to 222px). On a phone LIVE now sits on the
title row, where the session name gives way to it; the bar stays at
three rows either way.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The bar's two-button group becomes three, in reading order: Simple (new,
for a reader who has never seen a terminal), Advanced (the story view as
it was) and Director's cut (the wire and the tool captions, unchanged).
The Director's cut is the default, as the owner asked; the reader's own
pick is kept per device under codeman:story-level, and only a click
stores it, so opening Story Mode never writes the default back and
changing the default stays a one-line change. _storySetDirector(on)
stays as a shim (on: Director's cut, off: Advanced) beside the new
_storySetLevel(level); st.director stays a boolean for the wire and the
scenes, which see director false at Simple.
At Simple each panel shows caption.simple in the letter font, a size up
(calc(20px * var(--story-k, 1))). Claude's balloons are hidden (its
narration is often technical; the art carries the panel) while your own
prompt balloon stays. The final panel shows the plain summary instead of
Claude's last message, and its chips are the time, the files changed and
"Read Claude's message", which opens a new drawer view with Claude's
last words as it wrote them. Balloons are hidden with the hidden
attribute, so Save image (which skips hidden boxes) prints what the
screen shows. On a phone the three buttons and Save image share one row.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner wants to keep working from the comic view: "on the bottom have an
input field! so I can send more input in that view!". Story Mode gets a bar
across the bottom (the overlay's new last grid row): a field that grows from
one line to about five, a Send button and a one-line hint.
- It sends to the session the overlay shows (st.sessionId), never the active
tab, since Session Options opens Story for any session. Delivery reuses the
mobile composer's tested path, KeyboardAccessoryBar._sendComposedPrompt: one
bracketed paste on the durable exactly-once queue, then a separate delayed
Enter. Text plus a carriage return in one burst would be read as a paste by
Claude Code and strand the prompt.
- Enter sends, Shift+Enter adds a line, an IME composition and whitespace-only
text never send.
- While Claude waits on a question or a permission in the terminal (a pending
question on the newest page, or the permission/elicitation hook), sending is
off: the message would land in the selection dialog, whose Enter picks the
highlighted option. The field still takes a draft and the hint says why.
A session that is gone (not in app.sessions, or a 404 from the story
route), whose agent exited, or that Story cannot draw (a shell would run the
text) keeps its draft readable but sends nothing. A running turn may be
sent to: Claude Code queues the message.
- Drafts are kept per session in memory for the life of the page.
- After a send the field clears and keeps focus; a reader parked on an older
turn (or held there by a drawer) goes to the newest, since activity is
ignored while parked, and a refresh is scheduled.
- Escape in a field holding text steps out of it first (focus to Close), an
empty field closes one layer as before, and an Escape that cancels an IME
composition closes nothing.
- The bottom safe-area inset moves from the overlay onto the composer, so its
background reaches the edge; the drawer ends above the composer and toasts
sit above it (measured into --story-compose-h). Not part of Save image.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From a reader who has never written code:
- "a save your prompt here did not ask for" on a save that was step 3 of
the plan you approved ("Verify and commit"): a commit step in an
approved plan asks for the save. When Claude says a project rule asks
for its commits (read on its whole message), the line says "…; your
project's rules do".
- "a check after that fails" read as a failed test where it was `git
describe` finding no tags: "the version-tag lookup after that fails
(there are no tags yet)".
- The dry run you asked for never showed: the terminal card took the
last command's edge-case errors. A dry run in the panel is now what the
card shows, its "a -> b" lines, under "Claude does the dry run you
asked for: it shows what would happen, changing nothing."
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 11: a long caption ("Claude writes pyproject.toml
and changes test_csvstat.py; the tests pass, then Claude starts a git
repository and saves the work on a new branch …") covered the test
board, the POW and the code card's file name on a two-column panel.
A caption that reaches past the band above the art now makes its panel
taller by the difference (re-fitted on resize and on the Director's cut
toggle). Every scene keeps the panel's usual height as its stage and
stands at the bottom, the background filling the space above, the way
the final panel already did; Claude's balloon is placed against the
moved head. The browser test forces a long caption and checks the panel
grew and the caption ends above the art.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the fact-checker:
- A script created and then edited in the same panel showed "+95 -3":
a file created in the span counts what it ends with, never lines
removed from its own draft (+92), on the badge, the panel and the page.
- "Claude writes 4 files" for files an earlier panel wrote and this one
rewrote with here-documents: writing a file the turn already touched is
changing it.
- "Claude says: Compacted": a local command's output is the CLI's own,
a note, not Claude's words.
- "The CHANGELOG helper is still running" was dropped under "Mission
complete!": key sentences were read only in endings over 160
characters; a short ending is read too, and that one is a note.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From an engineering manager skimming titles and captions:
- "but a check after that fails" under "Mission complete!" where the
prompt said that step may fail now adds ", as you said it might".
- Saves no prompt of the turn asked for (a project rule, or Claude's own
idea) were indistinguishable from requested ones: the turn's first such
save carries a small line, "a save your prompt here did not ask for".
"save its log with tee" no longer counts as asking to save the work.
- The first save on a branch Claude created says which: "saves the work
on a new branch (feat/csvstat)".
- "Claude changes 3 files" hid an edit to your CLAUDE.md: a panel that
wrote some files and patched others says both ("Claude writes
README.md and .gitignore and changes CLAUDE.md."); a file created and
then edited in the same panel is written.
- A turn with a problem left and work still out read "Done for now";
the catch now wins the title.
- A helper's report that is only a path reads "reports back with the
file it wrote: README.md."
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the fact-checker:
- "+37" sat on mathx.py's card, which has 11 of those lines; with
several files the badge now counts the card in front (the Director's
cut keeps every file's count).
- A second opinion asked in the middle of a step's calls read "after
asking"; it now reads "along the way, asks a stronger AI model…".
- "Two places where I didn't follow your wording exactly" under
"Mission complete!": not following your words is a note ("Done, with a
note"), and the sentence counts as a word on what you asked.
- A test run that patched a file written a panel before said "Claude
writes" it: whole files are written, patched ones changed ("Claude
writes pyproject.toml and changes test_csvstat.py; …").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the person who asked and a fact-checker, reading fresh pages:
- "as planned" for failing first runs the prompt asked for ("it should
fail because mode is missing", "run them to watch them fail"): those
read "The first run fails on purpose, as you asked".
- A repository Claude started was told only on a save panel; the first
save inside a loop or a test run says it too ("Claude starts a git
repository and saves the work in 4 commits along the way.").
- `git push && git describe --tags; echo "exit=$?"` exits 0, so a
failure after the push went unsaid; what it printed after the push
(`fatal:`, `exit=128`) now counts. The commit subject stays on that
caption too.
- Tool-call counts no longer include second opinions.
- "a layout check" is "a formatting check"; "Turn over" (a game word)
is "Not done".
- "the last check finds nothing" told nothing: it now says what came
back empty ("the last one finds a git setting that is not set, which is
no error").
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 10: the chip "1 commit, copied to remote.git" was cut
off at the final panel's edge (on four of the eleven sessions), and so
would any ending longer than the panel's fixed 270 px. The final panel's
words now sit in flow, so its row grows (and a phone's 430 px is a
minimum). Its scene keeps the usual size, the panel's minimum height as
its stage, standing at the bottom with the background filling the rest;
otherwise a taller panel scaled Claude up with it. On a phone the words
end above Claude.
The browser test fills the final balloon with a long ending and checks
every chip, the balloon and the title lie inside the panel, at desktop
and phone width; it fails on the old layout.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From the person who asked for the work, reading fresh pages:
- "The result was 4 passed, 1 failed" under "Mission complete!" read as
a failing end. A test count is no failed step, and Claude's second
sentence is kept when it is the outcome ("All 5 tests now pass, and the
change is committed.").
- "Two places where I didn't follow your wording:" showed one. A list
intro that announces how many brings that many lines.
- "…would fix it for up to 10 files. Want me to add that?" never said
what "it" was. An offer that points back brings the start of its
paragraph ("Suffix swap: the two files with the same time would trade
their _1 suffix. …").
- "Decisions you may want to change" is something for you, like an offer.
- A second opinion whose call failed read as given; it now reads "after
trying to ask … (no answer came)".
- "but a step after that fails" after a push sounded as if the push
broke: when everything after it only looks (git log, status), it is "a
check after that fails".
- Tests run in a fresh clone read "The tests pass in a fresh copy made
from remote.git."
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 9 (a newcomer to the project):
- One or two files are named, not counted: "Claude writes .gitignore and
saves the work", "Claude changes rename_by_date.sh.", "Claude writes
the new files mathx.py and test_mathx.py." Three or more are counted.
- The first save in a folder Claude turned into a repository says so:
"Claude starts a git repository and saves the work: …". A `git init
--bare` (the push target) does not count, nor a folder that already
had commits.
- A push to a repository in a folder on this computer (`To ./origin.git`)
is a copy, not an upload: "then copies it to origin.git, a folder on
this computer", and the final chip reads "copied to origin.git".
- A tool lookup for plan mode reads "Claude gets ready to plan."
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 9: "Claude asks a stronger AI model for a second
opinion" stood as a whole panel on almost every page, and the reader
never learns what it said (the answer is encrypted). An advisor call now
rides on the panel of the step after it (or the last one, when nothing
follows), counted in `secondOpinion`: a small line under the caption,
"after asking a stronger AI model for a second opinion" ("then asks…"
when it came last), and "· after 1 second opinion" in the Director's
cut. Pages lose a panel of noise and keep room for the work.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 9:
- "another check" was too vague to act on. A check other than the tests
is named by its tool: a layout check (a formatter), a style check (a
linter), a type check, the build. "Fix and test, 2 rounds: a test fails
and Claude fixes it, then a layout check fails and Claude fixes that;
everything passes."
- A failure the prompt asked for reads "The first run fails on purpose,
as you asked; the second passes." ("The tests don't even load, as you
asked."); one Claude planned itself keeps "Test first: … as planned".
- A test run sent to the background that reported in a later turn reads
"Claude starts the tests in the background; later they pass.", not as
if they passed in that turn.
- The test board says "1 test" and, when nothing loaded, "1 error".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two fresh cold readers (the person who asked for the work, a newcomer to
the project) found the round-8 balloons still cut exactly where Claude
turns from describing to asking, offering or warning: "Want me to add
that?", "I can fix the number format whenever you want", the
http.server instruction, the fix that followed a reported bug.
Key sentences are now picked by kind, one of each, at most three, and
shown in the order Claude wrote them: a problem left (with the fix right
after it, when there is one, so it never reads as open), a question or
offer, how to use what was built, something not done or checked yet, a
step that failed, a word on what you asked for, a corrected warning. A
list intro is judged on its own words and brings its first items; a
pointer ("described below") is skipped; a short offer brings what
"that" was.
The final panel reads "Done, with a catch" for a problem left, "Done,
with a note" for something pending, and "Done, with a question" when
the next move is yours (`openNote` is now 'catch' | 'note'; `asks`).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
From review round 8: "Mission complete!" with confetti and cheering
helpers sat over balloons saying the page will not work as opened, a
helper's tests have not run, or a problem is left. The final panel is now
calm (no confetti, a neutral robot) whenever work was still out at the
turn's end, nothing worked, or Claude's last words name an open problem
(`page.openNote`: a key point about something wrong, not done, not
checked or not fixed); the last of these reads "Done, with a catch".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two agents read only the comic of the eleven recorded sessions (one who
has never written code, one engineering manager skimming), wrote down
what they believed, then compared with the transcripts. Both were misled
in the same places, and the cause was the same: the final panel kept
Claude's first sentence or two, and what a reader must not lose sat
further into its last message.
- Claude's last words now keep key sentences from anywhere in its final
message (read in full, past the label's clip): an open problem or a
quirk, something not yet done, checked or confirmed, work still out,
what you asked for or expected, a correction of an earlier warning
(with the sentence it refers to). A bare "I didn't" counts only when
nothing stronger is there; table rows, list intros and questions never.
"I built the pomodoro timer. Opening the page: it won't work if you
double-click index.html…"; "…That note makes the current behaviour
clear, but it doesn't fix it."; "As you expected, git describe --tags
then failed because the repo has no tags."
- A turn that ended with helpers or a background command still out says
so: "Done for now", and "Claude's part is done in 36 seconds. Its 2
helpers finished later and reported back." (`finishedLater`).
- A failure you asked for, or that Claude said it expected, reads as the
plan: "Test first: the first run fails as planned, the second
passes.", "The tests don't even load, as planned." A loop that failed
once on a test and once on another check says both, and the commits
made inside a loop are part of its story; the final panel counts the
turn's commits ("5 commits, uploaded").
- One or two files read are named ("Claude checks README.md." in a
turn a report started); a tool lookup reads "Claude gets the tools it
needs ready." again (it was overwritten) without its query; the
advisor is "a stronger AI model", and one that answered with an error
"gets no answer"; a commit message loses its `feat:` prefix in the
plain caption.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
From review round 7 (PR review):
- With no hook path (normal after a server restart), the transcript was
looked up by conversation id only; it now also tries the Codeman id and
a recovered session's `restored-<fragment>` transcript, as
last-response does, and keeps what it found briefly (a lookup reads
every project folder; a miss is kept only 4 s).
- A session Story Mode cannot draw is not polled, and a page with nothing
drawn yet is polled like a finished one (every 15 s), not every 4 s.
- Helper steps: one request per helper is drawn, the newest; a failed
refetch never writes over steps already shown.
- The Director's cut toggle, a skin change and the fonts arriving redraw
within the frame budget, not every panel in one task.
- A bare name under an `ls` header finds its folder in one pass over the
lines; a search per link over the text before it was quadratic (about
1.5 s for 700 links on agent output).
- A page of many questions still keeps to 12 panels: questions fold last.
- Opening Story Mode while it is open never starts a second runtime; the
status announcer carries data-i18n-skip; the turn key in the ETag is
clipped.
- The crafted-input timing test runs at sizes where the old code took 6
to 29 s and the new one under 10 ms, with a 1.5 s bound.
- Docs: `older`, `origin` (`scheduled`), which route answers a 500;
mobile.css's stale comment; an orphaned doc comment.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 7 (PR review):
- A helper's drawer froze while the helper worked: a background helper
writes only to its own transcript, so the parent page answered 304 and
the refresh returned before anything refreshed the drawer. An unchanged
page now still refreshes an open drawer's working helpers (ETag'd).
- A helper messaged twice read as superseded ("The trail stops here…"):
its one-page run took its ending from the first turn. It takes the last.
- More than 60 turns in a small file listed the newest 60 with no hint;
`older` is now set whenever the list leaves turns out.
- One /story request that never settled wedged the view, even across a
reopen. Requests carry a token and a 30 s timeout, and opening resets
the request state, with what was announced and shown.
- The background canvases (megabytes each at 2x) are released when Story
Mode closes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 7 (security):
- Eviction past the per-transcript caps dropped entries one shift() at
a time after the whole tail was parsed: quadratic and never yielding
(a 2 s event-loop stall on a 70 MB long turn, 51 s from 300k entries).
It is one splice, done at every 1 MB yield, so the peak stays near the
caps; and a window that saw a turn start which was then trimmed away no
longer widens to a bigger tail that would be trimmed the same.
- A FIFO or a device in place of a transcript or a helper's .meta.json
(stat said size 0, then open() waited for a writer) pinned a libuv
worker per request; four of them hang every fs, dns and crypto call on
the server. Files are now opened non-blocking, checked with the
handle's own stat (a regular file only) and read through that handle.
- The cache held 6 transcripts for everyone, so a seventh open view made
every poll a full re-read, and a 304 was decided only after reading. An
unchanged file is now a 304 from a stat alone, and the cache holds up
to 24 transcripts within 30 million characters.
- Story Mode is the first route to send a transcript's content to the
browser, so the hook-reported path is used only when it really lies
(symlinks resolved) in a Claude projects folder: the session's
relocated CLAUDE_CONFIG_DIR (now also used for the lookup by id) or the
server's. Anything else falls back to the lookup by conversation id.
Tests move their transcripts into the (temp) projects folder and cover a
path outside it, a symlink out of it, a FIFO transcript, a FIFO meta file
and a 100,000-entry tail.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
API reference: `roundsOk` carries `null` for a run still going, a
background command's `output`, script writes in `edits`, what `helpers`
counts, and the page text budget behind `truncated`.
src/web/CLAUDE.md: panels kept by identity, the balloon text span the
clamp must target, the drawer's unchanged-refresh skip and held turn, the
drawer under the bar and the inert page behind a full-width one, and text
tokens versus accents.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A sweep of the Story core over every local transcript (1,311 files,
3,855 turns: no errors, never more than 12 panels, the slowest page 12
ms) found pages up to 551 KB (157 KB gzipped), refetched every few
seconds while a long turn runs. The page text budget (outputs, hunks,
helper texts; the oldest events give theirs up first) goes from 400,000
to 120,000 characters: 94% of turns never reach it, and the longest page
is now 74 KB gzipped.
An edit whose lines the budget dropped now says so ("left out to keep a
long turn fast"), instead of the wording for a file a script wrote.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The speaker words added for screen readers became the balloon's first
child, and the clamp took that element instead of the text: long balloons
were no longer cut to fit and ran over the art (the loop's covered the
test count). The clamp now finds the text by its class.
The loop has no Claude to keep clear of on the left, so its balloon may
run wide and is clamped down to one line rather than over the board it
points at; on a 400 px panel "All 19 tests pass…" now ends above "19
tests".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
From review round 6 (accessibility): in the paper skin the accents were
used as text colours, and yellow, light green and pink on white fell to
1.42 to 4.48:1 (the "running" tag, the pencils' "Claude is working…",
diff markers, ok/error tags, +N/−N counts, "You approved it", LIVE); in
the dark skin the panel's LIVE tag (white on pink, 3.09) and the note
balloon's "Notice" label (2.74) were under too.
Text now takes its own tokens (--story-go-text, -bad-text, -wait-text,
-pencil-text, the LIVE tag's pair, -note-ink): the accents themselves in
the dark skin, darker ones in the paper skin. Fills and borders keep the
accents. Every pair measured 4.87:1 or better, the tinted diff rows
included.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 6:
- An open drawer was not tied to its turn: when the newest turn moved on,
a helper drawer went blank and a report drawer silently became the next
turn's prompt. The drawer now remembers its turn; when the newest turn
moves on, the page holds that turn (the picker lists the new one, and
the status line says so) until the drawer closes, then follows the
newest again. Picking a turn yourself wins.
- Previous on the second turn (or Next onto the last) disabled the
focused button and dropped focus to the page body. Focus moves to the
turn picker first.
- The drawer covered the right half of the bar at desktop widths; it now
starts under the bar there too. Where it covers the whole page (a
narrow window, a phone) the page behind it is inert, so Tab no longer
walks the panels it hides.
- A live loop shows the runs that finished, and the one still going as an
open circle (`roundsOk` carries `null` for it).
- The diff of a command that also printed something (`cat >> NOTES.md
<<EOF ... cat calc_stats.py`) shows what it printed, with "Open
calc_stats.py".
- "Red as expected: collection error." stays on its check, not the save
after it. Parked on an older turn, a new turn is announced. A new panel
is sized as it is inserted (no empty first frame). The turn picker is
44 px on touch screens, and the diff drawer no longer says "Live diff"
beside its LIVE tag.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 6:
- An open drawer rebuilt its body on every refresh, unchanged or not (11
rebuilds in 25 s, 7 identical), so a focused button inside it fell to
the page body and a text selection was cleared. A refresh that changes
nothing now keeps the nodes; a real change rebuilds and gives focus back
to the control that does the same thing. Helper steps are filled after
that decision, so a skipped rebuild costs no request and a helper still
working still refreshes its steps.
- When a fix folded into a loop under an open drawer, Escape left focus on
the body: the opener (`diff@fix:1`) was gone. Its replacement is found by
view and panel position (`diff@loop:1`).
- Panels are matched by identity (kind and first event), not position: a
fold into a montage near the start of a long live turn shifted every
later index and rebuilt all eleven panels with their entrances. Only the
montage is new now.
- An edit with no lines (a script, or a command Claude Code says changed
the file) says so: "What changed is not in the transcript: a script or
another program wrote this file."
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The final panel's chips were rebuilt on every redraw, so the chip that
opened a drawer was gone by the time it closed and focus fell to the
page body. Unchanged chips are now kept, and a trigger replaced anyway
hands focus to the control that opens the same thing.
- API reference: `older`, background Bash calls (`running`), `subject`,
append hunks (`atEnd`), `files` resolved against the command's folder,
test counts read before the output is cut, `roundsOk`, the `note`
speaker and `noneWorked`.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The rest of review round 5 (a phone with touch):
- Text appended with `cat >> f <<EOF` or `echo x >> f` was drawn as lines
1 to 8 of the file; where an append lands is not in the command, so the
diff now says "Added at the end of the file (8 lines)" with no numbers.
- A command that prints a file and does something else (`cat textstats.py
&& pytest`) kept no record of the file; it is now on the event,
resolved against the command's own folder, and the output drawer offers
"Open textstats.py". (A file rewritten by a python here-document still
cannot be diffed from the command alone; Claude Code's own "modified"
note, when it comes, is used.)
- A background job's report that starts a turn was a balloon credited to
"you" in the API (the page already drew it as a note); its speaker is
now "note".
- The output drawer tagged a command still running in the background
"ok"; it says "in the background".
- The Header buttons hint says that on a phone, Story Mode opens from
Session Options.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The bar on a phone took four rows (216 px of a 664 px screen) with the
close button alone on the last; it now takes three (170 px): title and
close, the turn, the view and Save image. The drawer starts exactly
under it and follows it when the phone turns (the bar is observed).
- A panel below the fold had no buttons until it was scrolled to (only
drawn panels get click regions), so Tab skipped it. A quiet frame now
draws one such panel, a band at a time; its entrance still plays when
it comes into view.
- "Fix and test: the first run fails, the second passes." was chosen by
rounds, so a loop with three runs (a failing test, then ruff, then a
pass) said it under ✗ ✗ ✓. It now needs exactly two runs; that loop
reads "Fix and test, 2 rounds: 2 runs fail, then it passes.", and the
corpus pins every loop's marks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 5 (a phone with touch):
- Touch screens: every control at least 44 px (prev/next, close, the
Story/Director's cut toggle, Save image, the final panel's chips, drawer
buttons and links), and the small type at 13 px.
- The drawer on a phone starts below the bar (it cut through it), closes by
dragging its head down, and keeps its head small in landscape.
- The turn picker says where you are ("Turn 2 of 5"), learns about new
turns while you look at an older one, and a turn you picked yourself is
not announced as news.
- A check whose command also wrote files offers "See the 2 files it wrote".
- Test counts are read from the whole output: a later command's lines had
pushed pytest's "5 passed" out of the kept tail.
- A save names its commit: "Claude saves the work: "feat: add wrap() for
modular wrapping into a range"." (from -m, -am, --message or the
here-document of git commit -F -; a trailer is never the subject).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
Stepping through 40 turns made 46 long tasks of 51 to 286 ms, enough for
the app's guard to drop the terminal's WebGL renderer for the rest of the
page's life. Drawing switched off made none: the cost was canvas raster at
paint time, which the per-frame JS budget never saw. Now:
- Panel backgrounds are shared between panels and turns (kind × size ×
palette), and a new one is built in four bands, one per frame (a wide
halftone at 2x was ~35 ms in one go; a band is ~8 ms).
- At most two panels are drawn per frame, the first one served rotating so
none starves; clocks still run for all.
- Measured on 20 turns stepped 40 times at 2x: 1 long task (52 ms), against
39; the settled overlay is still ~2% of a core.
Also from round 4:
- The turn picker says when older turns were not loaded (`older` in the
response: the transcript is longer than the part that was read).
- A compaction after the last turn closes that turn; a local command's
output is the final panel's balloon ("Compacted"), without its markdown
ticks, and a turn under a second reads "Done.".
- Exact durations keep their seconds past an hour, so the two captions never
disagree on the minute ("1 h 10 min." / "1 h 9 min 39 s").
- A turn that arrives already finished is announced as new too.
- Whole files written by here-documents read "Claude writes 3 files."; the
advisor's caption reads "a second opinion" instead of repeating its name.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 4 (a 110 MB real session, about 23 turns, read through
all four tail steps):
- A scheduled task's run was drawn inside the /compact turn before it (96
minutes and 237 tool calls credited to "You run /compact."): its prompt is
a meta line, and work after an ended turn was appended to that turn. The
scheduled prompt now starts a turn of origin `scheduled` ("A scheduled
task starts this turn."), and work that comes more than 5 s after a turn
ended starts a turn of its own ("Claude carries on"); a line Claude Code
flushed a moment late still joins.
- A turn with seven prompts added mid-turn drew 14 panels (the cap is 12):
missions were never folded. An added prompt may now fold into a montage,
as the last resort after quiet panels and work panels, and the montage
says so ("Claude keeps going (you add 2 things): ..."); the turn's own
prompt and a question to you are never folded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A turn with no end marker of its own (an abandoned or crashed turn) was
closed when the next prompt started and then drawn as "Mission complete!".
It now reads "To be continued…" / "The trail stops here: the next prompt
starts a new turn.", and the page carries `superseded`. The Director's cut
names what ended every turn: turn_duration, stop_hook_summary, a local
command, an interrupt, or the next prompt.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A report's turn links to the helper it answers: its drawer shows what the
helper did. Reports arrive HTML-escaped (`<a@b.c>`) and now read as
text.
- Screen readers: a polite status region says when a new turn starts and
when one finishes; the note is a status, the drawer a dialog named by its
title; "Open file" buttons name their file.
- Save image draws the balloons' tails (from their CSS triangles) and is
disabled with nothing to save.
- App toasts move to the bottom while Story Mode is open: at the top they
covered its bar and turn picker on phones.
- The bar names the session as its tab does; a click on the mission balloon
opens the prompt.
- A heading ends at its line (a plan's "# Plan: add wrap" no longer runs
into its next paragraph) and a short one ("## Summary") is dropped; the
plan you approved stamps "You approved it".
- "Claude's part is done in 3 minutes. A helper is still working."; the
helper steps say "still working" while the helper runs; the advisor's
labels call it a second opinion; a check run twice the same way reads once
with a count; the session folder reads "." in commands only, never in
prose; the loop's POW keeps off its board on a narrow panel; a shell
session's note says it runs a shell or another CLI.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 3:
- Keyboard: a file opened from a panel or drawer takes focus on the
preview's close button, Tab cycles inside the preview while it is open,
and Escape gives focus back to what opened it. Before, Tab walked out of
the overlay to the session tabs behind it. A panel rebuilt under focus (a
new turn, a fold into a loop) hands focus to its replacement.
- A commit at the end of a test one-liner is no longer lost: commits and
pushes are counted whatever intent wins, and the check says "The tests
pass, and Claude saves the work." (plus "1 commit" in the Director's cut).
- Files written by `printf/echo ... > f`, and files Claude Code says a
command changed ("This command modified 1 file you've previously read"),
count as changed files.
- A compaction says what it did ("automatic · 46.9k → 3.6k tokens · 24 s");
an automatic one reads "Claude's memory is full", and its panel opens.
- ToolSearch is looking around, not a terminal panel; EnterPlanMode reads
"Claude switches to plan mode"; the plan drawer shows the plan you
approved ("You approved the plan.").
- A finished newest page is checked every 15 s (a 304 when unchanged): a
local command like /compact raises no hook.
- Boards draw the real number of dots (5 tests, 5 dots); captions leave
here-document markers out.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
From review round 2 (a long turn, and a prompt added mid-turn):
- Commits fold last and a montage names them ("saves the work in 3
commits"); a save counts its commits ("saves the work in 6 commits").
Before, all three commits of a 16-step turn vanished into montages that
read "runs 2 commands".
- Folding a full page starts at the oldest pair and grows a montage already
there, and never takes the newest panel while another choice exists: a
live page no longer reshuffles panels the reader is watching.
- A check whose output reports problems ("1 file would be reformatted",
"Found 3 errors", "✖ 5 problems", `error TS…`) failed, though a trailing
`; ls` hid the exit code: "The tests pass, but another check fails."
- Shell structure is not a command (`for x in`, `do`, `done`, `if`), so a
`--version` probe in a loop reads as looking; `command -v` and
`git config --get` too.
- The montage draws only the kinds of steps it folded, with a box for
saves; the loop's arrow has a head and rests on a finished page; the
"MISSION COMPLETE!" title has room for its tilt; drawer commands keep
their line breaks.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Files a Bash call writes through here-documents (`cat > f <<'EOF'`, `tee`)
are whole-file edits on the event: in the diff drawer, on the code page,
in "N files changed". Before, a worker that wrote seven files that way
drew a page with zero files changed. Labels leave the bodies out (the wire
row was a wall of file text), and a relative target resolves against the
transcript line's cwd unless the command changed directory first.
- A caption names the part of a one-liner that decided it (`node --test`,
the commit), not its first line.
- The advisor (a server tool, answer encrypted in the transcript) is drawn:
"Claude asks the advisor for a second opinion." It is not counted as a
helper. Server web search maps to the web panel.
- Checks that all pass from the start read "Build and test, N rounds. Every
check passes.", not as fixing.
- pytest's collection errors are counted apart: "The tests don't even load."
- A result remark replaces a step announcement on its check.
- A turn still open when the next prompt starts has ended; an interrupted
turn is timed by its span.
- The mission drawer keeps prompts up to 4000 characters.
- Drawer: diff headers, titles and commands read relative to the session
folder (the folder itself reads "."); a bare name under an `ls dir:`
header links into that folder; the chosen answer is marked, not struck
through; boards count errors.
- While a question waits, the pencils say "Waiting for your answer…";
balloons start below the LIVE tag; the loop's balloon points at its
verdict, above the board.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>