Follow-up from the review of #1392 (raised by @cosarah).
Problem. #1392 adds per-course session bookkeeping to both classroom surfaces — app/classroom/[id]/page.tsx and components/classroom/ClassroomSurface.tsx — as parallel copies: the ownership sidecar fetch and noteStageGenerationOwnership, teardown on course change (aborting passes, clearing narration allocations, forgetting parked media allocations), and the resume gate. On the page the gate is re-composed inline as mayStartOwnerGeneration(isServerBackedMediaPersistence(), ownership) rather than read through useMayGenerateForStage.
Two ways this bites later: if the gate gains another input, the inline copy silently diverges and the visitor-must-never-spend invariant can reopen on one surface only; and the next per-course registry added to one file and forgotten in the other leaks the departing course's state.
Proposal. Extract one hook owning the per-course session (the pattern useNarrationAdoption already established): sidecar fetch + ownership recording + re-ask after transient failure, teardown of every per-course registry, and the resume gate read via useMayGenerateForStage. Both surfaces call it; the page's inline gate goes away.
Scope note. Pure refactor of two surfaces; deliberately kept out of #1392.
Related. The per-element document reload inside mutateDocument (one full-document GET per committed element, raised in the same review) is a separate trade-off — the reload under the per-stage lock is what makes each element's write-back independent, so a pass that dies half-way keeps what it committed. A "reuse the loaded document within a pass while keeping per-element commits" variant is worth exploring alongside this, since both touch the same per-course session code.
Follow-up from the review of #1392 (raised by @cosarah).
Problem. #1392 adds per-course session bookkeeping to both classroom surfaces —
app/classroom/[id]/page.tsxandcomponents/classroom/ClassroomSurface.tsx— as parallel copies: the ownership sidecar fetch andnoteStageGenerationOwnership, teardown on course change (aborting passes, clearing narration allocations, forgetting parked media allocations), and the resume gate. On the page the gate is re-composed inline asmayStartOwnerGeneration(isServerBackedMediaPersistence(), ownership)rather than read throughuseMayGenerateForStage.Two ways this bites later: if the gate gains another input, the inline copy silently diverges and the visitor-must-never-spend invariant can reopen on one surface only; and the next per-course registry added to one file and forgotten in the other leaks the departing course's state.
Proposal. Extract one hook owning the per-course session (the pattern
useNarrationAdoptionalready established): sidecar fetch + ownership recording + re-ask after transient failure, teardown of every per-course registry, and the resume gate read viauseMayGenerateForStage. Both surfaces call it; the page's inline gate goes away.Scope note. Pure refactor of two surfaces; deliberately kept out of #1392.
Related. The per-element document reload inside
mutateDocument(one full-document GET per committed element, raised in the same review) is a separate trade-off — the reload under the per-stage lock is what makes each element's write-back independent, so a pass that dies half-way keeps what it committed. A "reuse the loaded document within a pass while keeping per-element commits" variant is worth exploring alongside this, since both touch the same per-course session code.