The reading of a folder belongs to the tab, not to the catalogue screen: it was
made to survive the hand-over to the studio in 753eea0, and it does. What was
missing was any sign of it up there — a visitor who hands a half-read roll to the
studio saw a page that said nothing about the frames still landing, and had no
way to tell a reading in flight from one that had quietly died. The one thing
that did say so, the toolbar line, lived on the screen they had just left.
The header now carries it, immediately left of the way back into the catalogue,
where the two belong together: the ring the catalogue already uses for a roll in
hand, and the count the toolbar states, "4/12", whose title is the same
"Scanning 4/12 — 0 new…" line in the visitor's language. It reads the session
through scanSession() and watches it with watchScan() exactly as the catalogue
does, so a pass that moves the progress moves the header too — the session object
outlives them both, only its progress is replaced each pass. No new state is
introduced, no new copy: the markup borrows .lib-spin and the lib.scanning key
the catalogue already had.
Verified: tsc --noEmit and vite build clean; scripts/scan-nav-check.mjs grew one
step at the first hand-over, "the studio header shows the reading the tab is
doing", which waits for [data-key="studio-scan-count"] mid-scan and matches it
against \d+/\d+ — it passes with 4/12, the same reading the toolbar shows at that
moment; the other 18 steps of that check, the 52 steps of library-check.mjs and
roll-walk-check.mjs all still pass.
ponytail: the header states progress, it does not offer to stop the scan; the
catalogue's own STOP stays the one place that ends a reading. Add a control here
when someone asks to stop a roll from the studio.
Co-authored-by: PenguinHarness <noreply@penguin.local>
Two holes left by the last change. A frame is opened with `window.location.href`
rather than a link, so the studio was still handed a fresh page and the scan with
it — every route out of the catalogue now goes through `go()`, which pushes the
address instead of reloading while a scan is in flight. And a row counted what was
filed away rather than what had been read, so it sat at zero for the whole of a
first scan: the catalogue's frames only reach IndexedDB in one batch at the end.
`ScanProgress.counts` carries the frames the scan has reached, per row, off the
scan's own bookkeeping, and the column reads it while the scan runs — the row
under the reader's eye moves as the roll is read, and the toolbar's line stays the
whole picture. A re-read counts from the start, which is what it is doing.
The regression check grows the two: a frame opened mid-scan from the catalogue
(of a roll it already holds) has to stay in the page, and the row has to count.
The catalogue hands the visitor to the studio with a plain `<a href>`, and the
app has no router: following it threw the page away, so a scan that was halfway
through a roll died with it. A scan belongs to the tab, not to the screen that
started it.
The scan now lives outside the component (`scanSession`/`startScan`/`stopScan`,
watched by whoever is up), so the catalogue can unmount and come back to a
reading that never stopped; a screen that returns joins it and sees the toolbar
line, the stop button and the rows filling in. The internal links are taken over
for a history push *only while a scan is in flight* — everywhere else the
browser navigates exactly as before, so the landing-to-studio flow is untouched.
`scripts/scan-nav-check.mjs` is the regression: a roll read one frame at a time
off a slow server, handed to the studio mid-scan, back to the catalogue, and the
whole roll read to the end with the studio up, counting documents along the way.