---
key: spa.playouts-screen-extraction
title: '2026-07-14 — Playouts screen extraction (#245): screen-owned route wrapper (epic #243 phase 2)'
status: active
since: '2026-07-14'
supersedes: none
superseded-by: none
rule: The Playouts domain (including its unguarded `PlayoutsRouteScreen` route wrapper with local pathname/popstate state) moved as one unit into `web/src/screens/PlayoutsScreen.tsx`, keeping its screen-specific sub-path route ownership colocated with the base screen; a pure structural move with no API/route/CSS/behavior change.
signals: 'screen extraction, route wrapper ownership, sub-path routing · paths: `web/src/screens/PlayoutsScreen.tsx` · issues: #245, #243'
mechanics: '`docs/spa-conventions.md` §2'
---

Second bounded extraction under the App.tsx modularization epic (#243): the Playouts domain moved from
`web/src/App.tsx` into `web/src/screens/PlayoutsScreen.tsx`, including its loading/error/empty states,
dialogs, mutations, timeline/filter helpers, and the existing `PlayoutsRouteScreen`. The dedicated
`PlayoutScheduleEditors.tsx` modules remain separate. This is a pure structural move: no API, route, CSS,
or runtime behavior changed; `App.tsx` retains only the import and `<PlayoutsRouteScreen />` dispatch.

**The unguarded route wrapper moves with the screen.** Playouts owns two sibling sub-path editors and,
per `spa-conventions.md` §2, must keep its local pathname state plus `popstate` listener because App's
allow-sub-path route object is reference-stable. Colocating the wrapper keeps that screen-specific route
ownership beside the base screen while App-level tests retain the cross-route navigation assertions.

**Temporal mutation behavior stays verbatim.** `mutatingRef`, `runMutation`, and the explicit
`query.refresh()` after a 409 moved as one unit. The extraction deliberately does not add mount/current
guards to these pre-existing promise completions; changing those semantics belongs to a separate issue.
Detailed behavior tests now render `PlayoutsScreen` directly with a scoped fetch mock, including the
zero-playout Add Playout affordance, lock/409 handling, refresh/poll ownership, action and kind gates, and
dialog flows. Refs #245 #243.
