The existing coverage moves with the constant: the review test asserts
against review.MAX_SLIDES + 1, and the browser suite already checks the slide
count the form sends. Removed so the change is exactly the four lines.
The caps were raised to the schema's own limit, so the suite asserts it where a
person would: the form's max is 60, and 60 typed reaches the route as '60'
rather than being clamped back. 36 passed on the rebuilt stack.
The page polls the job list as soon as it opens, so a stub that finished the
job on the second poll had already finished it before Generate was pressed —
and there was no in-flight to landed transition left to announce, which is the
behaviour these two specs exist to pin. The job now stays running until the
click is recorded, and the run is green: 34 passed.
Two faults kept every spec in this file from running, both in the same code
path and both from the SSO-only change. The fixtures minted a session by
shelling out to `docker compose exec`, which cannot work from inside the
Playwright container — it has no docker CLI and no socket — so all seventeen
specs (both viewports) died at the auth fixture before touching the page. The
harness now mints both sessions on the host and passes them in, and the seed's
connection banner is no longer mistaken for the token: it prints before it,
so the token is the last line. The host-side docker path stays as the fallback
for `npx playwright test` run directly on the host.
The specs then move to what generation is now: the click is answered with a
job, so the assertions follow the job list. A generation in flight is listed
and survives a reload, a job that lands reloads the library and says what it
was written from, a job that fails says why, and what was searched for is
reported when the job lands rather than when the button is pressed.
Topic is one line and Instructions say how; neither is the place for a list
of twelve topics or a case to build around. Details is: optional,
collapsed by default, up to 12,000 characters, quoted into the prompt as the
author's own account of what the resource must cover — material, not
commands, in both the deck and the markdown shapes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
The unit tests for this feature read source files and assert patterns. They
prove the code says the right thing, not that the screen does it, and nothing
exercised the browser at all — so a mismatch between what the form sends and
what the route reads passed all of them.
Three real bugs shipped through that gap in one session: a modification that
updated the markdown but not the deck, generation that failed whenever the slide
reviewer was off, and a figure generated for a slide that never referenced it.
Every one was found by driving the running server by hand.
So these assert the request bodies, not only the rendering: that Generate sends
topic, kind, slideCount, refinement, model and all four options as the strings
the route compares against; that unticking the library sends 'false' rather than
omitting the field, which the route would read as on; and that Modify posts to
the right resource with every source option. Plus the screen's own behaviour —
availability gating on both cards, the illustration hint switching on and
staying off once overruled, the bounded searchable library, the two different
empty states, an article never being offered as slides, a local refusal that
spends no round trip, and a refused modification surfacing its reason. Fourteen
tests, both viewports.
The API is stubbed. This is the contract between the screen and the route, and
stubbing keeps it fast, free and deterministic.
Proven to catch regressions rather than merely pass: renaming useCorpus in the
form failed two tests, breaking the availability gating failed one, and
truncating the modify picker failed another.
Two flakes of my own were fixed rather than retried. openTab slept 400ms for the
library and picker instead of waiting for them, which made Modify report
"nothing to modify yet" under load. And the console-error guard failed on
net::ERR_ABORTED and net::ERR_NETWORK_CHANGED — a request in flight when the
context closes, and the host network reconfiguring under a browser that runs on
it. Both are the harness, not the page: anything genuinely failing carries a
status code and is still caught. Five consecutive clean full runs after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU