Each scene now shows who is in it as they look IN THAT SCENE, with one box:
'Change how Stewart looks from this scene on'. Cast cards show every look
front, side and back. Every picture opens large. The storyboard waits until
all pictures exist. A box for the author's notes on the script as a whole,
and Revise is always on offer.
A dollar sign before a quote is a substitution token to patch_git_file, so a
price label spliced the rest of the page in after itself. trailer.html carried
33 KB of raw JavaScript after its closing tag, which rendered as garbled text
in the page body; script.html carried 65 KB, commented out. The copy above the
first closing tag was verified complete: it parses, and it defines every
function and calls every action the removed text did.
A dollar sign before a quote is a substitution token to patch_git_file, so a
price label spliced the rest of the page in after itself. trailer.html carried
33 KB of raw JavaScript after its closing tag, which rendered as garbled text
in the page body; script.html carried 65 KB, commented out. The copy above the
first closing tag was verified complete: it parses, and it defines every
function and calls every action the removed text did.
The `download` attribute is ignored by every browser when the href is
cross-origin, and the file we redirect to is signed with an inline
disposition - so clicking Download opened the mp4 in a tab instead of
saving it. ?dl=1 asks the worker to serve it as an attachment.
Six URLs are baked into this page as static HTML, so they never pass through
the API where the rewrite happens - the videos and both photos have been
dead for as long as api.corben.world/files has been returning 500. The
worker signs and redirects, which works regardless of that proxy's state.
Third occurrence of this shape after cuts and the storyboard reel: a flag
meaning "work is happening" set before an in-memory thread starts, never
cleared when a deploy kills it, and a page that then hides the buttons for
ever. Anything older than 25 minutes is treated as gone.
A literal $' in replacement text is consumed as a special pattern by the
patch API, which truncated the block mid-string and swallowed the
if (unfilmed) line with it. BROWSER_TESTS.md records this exact hazard.
The price is now built by concatenating the dollar sign separately so no
$-quote sequence appears in patch text at all.
Separate from filming on purpose: a re-film decision wants the audio and
picture verdicts together, and an existing film can be checked without
paying to shoot it again.
Every clip has been transcribed and compared with its script since the first
film. Nothing looked at the picture, which is where this engine actually
fails - two identical photographs on a mantel, half a car, a person nobody
asked for. This renders the visual verdict the same way, and lists each
fault with the wording change that would have prevented it.
The page polls only while it believes something is working, and preparing
the reel was not on that list - so the progress froze at whatever the last
manual refresh had seen (5/20) while the job ran happily to completion.
Also: treat a reel job older than 15 minutes as gone, the way cuts already
are, so a worker restart cannot leave the button disabled for ever; and
start playing automatically once preparation finishes, since "Prepare and
play" promised both.
The estimate was cast x 3 + shots x 2. It counted characters who appear in
no shot, whose sheets are skipped, and knew nothing about the per-day
wardrobe sheets a multi-day story needs. On the Memory Road board that is
73 quoted against 82 drawn.
Reviewing meant scrolling to a shot, pressing play, listening, scrolling to
the next - which destroys exactly the flow a trailer is made of, so faults of
sequence were the hardest to notice and the ones we kept shipping.
This plays the whole thing: opening frame, then closing frame, while the
shot's lines are spoken, then on to the next. Stop, or jump to any shot to
leave a note. It is the trailer before it is filmed, for cents of speech
rather than dollars of video.
"Claude Opus 5 ★75" is unreadable when the model's own name ends in a digit -
it was read as a score of 5. The score now sits in its own bordered pill.
cuts_state is set before the thread starts and cleared when it finishes, so
a deploy in between left the page waiting forever on work that was no longer
running, with the retry button hidden. Fifteen minutes is longer than the
work can legitimately take.
A cut may reorder as well as drop, which changes what it is - worth saying
rather than leaving the reader to spot it in the id list.
The views table already records how far every viewer of a share page got.
Once each published cut has its own page, "which version holds people" is a
query, and this is where the answer is shown.
playCut steps one video element through the cut's clips. No render, no
upload, no cost - which is the whole reason ten proposals are worth looking
at rather than one.
A cut is an ordered list of shot ids, so previewing one costs nothing: the
page plays that cut's clips back to back. That is what makes asking for ten
proposals reasonable. Only a cut somebody chooses gets joined into a real
mp4 with its own share page, which is also what makes the watch-through
numbers comparable - one page per version.
Revising was reachable only after readers had reviewed it or the author had
left a note. Being ninety seconds over budget is a reason to revise on its
own, and the button now says how much it will cut.
The runtime in the header is the sum of the edit_seconds the writer typed,
which is a guess made from a word count. timingHtml renders what the lines
actually take when the cast speaks them, and how far over the target that
puts the trailer - which is the number a revision has to cut to.
Shots sharing a scene are drawn from one frame of that place. When the
grouping is wrong the symptom is a set that drifts, and until now the
slug doing the grouping was not on the page at all.
The writer now reads the book and commits to what the story IS before writing
a single shot - premise, protagonist, the emotional engine stated as a moment,
what a viewer must understand, which moments have to be SHOWN rather than
narrated, and how it ends so it knows what it is withholding.
That document is worth more to an author than the script is. It is where a
misreading of the book is visible and cheap to fix; by the time it is
twenty-six shots and a storyboard it is neither. So it sits above the shots,
collapsed, and the two lists that decide whether the trailer works - what a
viewer must understand, and what must be dramatised - are open by default.
"What a viewer must understand" is also literally the checklist the
comprehension test measures against, so the two panels now read as a pair: here
is what it must convey, and here is what three readers actually took away.
The review panel rendered the old critique shape - errors, suggestions,
strengths. The comprehension test produces something else: three summaries
written by readers who saw only the script, and the writer's own list of what
the trailer failed to convey.
The gap list leads, because that is what the revision consumes and what the
author needs to decide about. Each reader's summary is then shown verbatim,
with what they were still asking and whether they would watch the film -
reading three strangers describe your trailer badly is the most persuasive
evidence there is that it does not tell the story.
"Got wrong" is called out separately from "missed". A trailer that leaves
something out is incomplete; one that leads a viewer to the wrong conclusion is
worse, and they are different problems to fix.
The trailer was all-or-nothing. If one shot was wrong - a line cut short, a
character duplicated, a scene missing from the front - the only lever was to
film all twenty-four again.
Every shot now carries its own controls: describe a change and have the writer
turn it into a proper shot, draw its two frames, film just that one, watch the
clip that came back, and read the machine check that compared what it says with
what the script says. "Add a scene here" sits between shots, because the note
that prompted this was that the trailer needed an opening the writer never
thought to include.
Assemble is separate and cheap - it joins whatever has been filmed - so
re-cutting after changing one shot costs nothing but the one shot.
Prices are on the buttons because they differ by two orders of magnitude:
rewriting a shot is cents, drawing it is fourteen, filming it is about a
dollar.
The four scripts had no next step. Everything after "read them" lived in the
API and nothing on the page reached it.
The choice of head writer is the author's, and it is a judgement about concept
rather than a score, so it is a button on each tab rather than a leaderboard.
Naming the lead first also changes what the reviews are for: three models
arguing about how to fix THIS script is a more useful argument than three
models arguing about whose is better.
Reviews are split the way they come back - errors are things that are wrong
and are worth fixing, suggestions are matters of judgement, and "keeps" is
what a revision must not quietly remove. The author's shot notes outrank all
of it and the revise button says so.
Film is priced and gated on having a storyboard, because every shot needs an
opening frame to film from.
Once you navigated away from a trailer, the only route back to it was the URL.
The page offered a way to start work and no way to return to it, which meant
every finished run was effectively lost the moment you closed the tab.
The list leads with what someone actually wants to know at a glance - is it
finished, did every writer survive, has anything been drawn, what has it cost -
rather than with an id and a timestamp. A run still working shows what it is
doing and keeps polling, so this is also the page you land on to check
progress.
trailer.html gets a link back, so the two pages are a loop rather than a
one-way trip.
Uploading "Memory Road interior 12-28.pdf" produced a book, a run and a
storyboard all headed "Memory Road interior 12-28". The title is derived from
the filename because that is the only thing an upload carries, and publishers
name files after print runs.
Guessing harder is the wrong fix - stripping "interior 12-28" would be a rule
that works on one file. Letting the author type the title takes eight lines and
is right for every file. The author field goes in at the same time; it costs
nothing and a trailer should know whose book it is.
The draw button is live and priced from what that writer actually wrote -
three sheets a character plus an opening and a closing frame a shot - so a
14-shot trailer costs $3.33 and a 30-shot one costs more, rather than both
being quoted an average.
Cast sheets render as head/front/side, because they are the thing that stops a
face drifting between shots and the author should be able to reject a face
before seventy pictures are drawn from it.
Progress is per picture rather than a spinner: drawing takes minutes and a
spinner that might mean "working" or "hung" is worse than a count.
"Drop the manuscript here or click to choose a file" is a drop zone pretending
to be a button - it reads as an instruction rather than a control, and on a
laptop nobody drags a file from anywhere. There is now an actual button. The
zone still accepts a drop, because that costs nothing once the button exists.
The paste box is gone. It was there for a manuscript that would not upload
cleanly, but a book is 100,000 words and nobody pastes that; keeping it meant
offering a route that only works for the case it was not for. Word and PDF
cover it, and a PDF that turns out to be a scan is refused with the reason.
Takes a .docx, a text .pdf or a .txt up to 6MB, or pasted text for anything
that will not upload cleanly. The upload itself only stores the file and writes
a row - extraction runs afterwards on the server, because ptl_api is
synchronous with a five second budget and parsing a novel is not a five second
job. The page polls until the book says ready, or says why it could not be
read.
readFileB64 lives here rather than in app.js: app.js's resizeImage goes through
an <img> and a canvas, which is right for a phone photo and fatal for a .docx,
and this is the only page that uploads a document.
Reading a trailer tells you what happens; hearing it tells you the line is
four seconds too long and the grandmother sounds thirty. So the play button
ships before the draw button - speaking a whole trailer costs fractions of a
cent against five dollars to draw it - and it prints the spoken length against
the length the writer planned, which is the most common fault in these scripts.
The note box saves as you stop typing. A note you have to remember to save is
a note that gets lost when you switch tabs.
The draw button is priced from what the writer actually wrote - three sheets a
character plus an opening and a closing frame a shot - rather than from an
estimate, and is disabled until that stage lands.
The page has carried a commented-out copy of its own tail since a bad patch
spliced it in. It is inert, but 66KB of duplicated source in a file that gets
patched regularly is a hazard worth removing. Deleted in parts because the
patch API can only remove text by quoting all of it.
The previous repair re-inserted "var price = ..." at the point it replaced,
which landed between "var head = '...'" and its continuation. Semicolon
insertion then ended the head statement early, so the price and everything
after it fell out of the string and head rendered as a bare opening div.
var price is already declared above; the continuation line just needs to follow
the head assignment directly.
My previous patch put a dollar sign next to a quote in the REPLACEMENT text.
That is a substitution token, so the whole rest of the file was spliced in and
script.html went from 82KB to 148KB with render() cut in half. Writing it as a
$ escape did not avoid it either - that is decoded before the substitution
runs and springs exactly the same trap.
- The broken line is restored, with the currency sign as the HTML entity $
which contains no dollar character at all.
- The spliced duplicate is commented out to end of file. The live copy is the
one above it, so every future anchor still matches the live code first.
- Price now comes from the fresh quote get_project returns rather than the
price_usd stamped on the row at creation, which ignored the markup change.
Patch order matters here: the corrected line itself contains the fragment text,
so the fragment is dealt with first.
price_usd is written onto the project row when it is created, so changing the
markup constant left every existing project quoting the old number - the page
still said 40.85 after the server had recomputed it to 8.13.
get_project already returns a fresh quote. Use it, and fall back to the stored
value only for a response that predates it.
Making step 3 clickable was not enough: render clamped VIEW back down to the
project's stage on every draw, so the click set VIEW = 3 and the redraw
immediately put it back to 2. The Filming panel stayed unreachable.
- "off the clock" fired on ANY shot not within 1.2s of the clip length, so a
7.0s line in a 10s shot was flagged alongside a genuine 11.4s overrun. Under
is fine - the engine finishes early and waits. Only an overrun gets cut off
mid-word, and only that is now flagged, as "runs long".
- A cutaway could be made or remade but never removed, and the subject is the
writer's guess so it is often wrong. There is now a Remove button.
- The Filming step was disabled until the project was already filming, so the
only way to start was a button at the bottom of the Layout page. Approved now
reaches step 3.
- "Cutting to the clock ... rewrites how the lines are divided" is no longer
true - it measures and no longer moves boundaries, because Setup shots does
the division locally. Copy corrected.
- "speaking the words costs about a hundredth of a cent" was wrong by three
orders of magnitude; a measured run is about 16 cents.
The one button did two unrelated jobs and showed the result of neither.
- Setup shots is now local division only: Python at the engine's own measured
speaking rate, ~500ms, free, no LLM and no TTS. Answers "divide with local
code, because each voice has a different cadence".
- Script audio is a separate button that reads it aloud AND SHOWS THE PLAYER.
The audio was always being made and uploaded, then rendered into #alignout,
which only exists on the Layout view - so pressing the button on the Words
view produced fifteen seconds of nothing.
- The voice is now named. With an Omni engine the film ships Omni's own native
voice, so the ElevenLabs read-through is a stand-in and says so.
- Rewrites are no longer capped at three. The real cost from OpenRouter is
shown beside the buttons, added to the bill at cost with no markup.
- "Approve and lock it" now explains what it does.
A measured run of the 80s NIMBY script came back at $0.1589 for 946 characters
- the ElevenLabs rate of $22 per 131,000 - so the copy understated it by more
than tenfold. Compute it from the words actually in the box instead.