Phase 1 of the App.tsx modularization epic #243: extract the Channels domain out of web/src/App.tsx into web/src/screens/ChannelsScreen.tsx with a colocated test file. Pure structural move — no API, route, CSS, or visual change. App.tsx retains only the import + the <ChannelsScreen /> dispatch clause.
Runs on top of merged PR #242 (#212 empty-lineup bare-create + #230 popstate guard), which #244 sequencing required to land first.
Changes
Move: 14 symbols (ChannelViewFilter → ChannelTableRow) → ChannelsScreen.tsx, zero-prop, mirroring the SchedulesScreen extraction. The Dashboard-owned progressFromNowPlaying is inlined into the moved progressFromChannelState (behavior-identical) so the screen has no import back into App.tsx.
Tests: 12 Channels behavior blocks → colocated ChannelsScreen.test.tsx with its own scoped window.fetch mock, rendering <ChannelsScreen /> directly (no mockDashboardApi). App.test.tsx keeps one thin nav-smoke test for the route. Three assertions adapted for direct-render (dropped App-TopBar heading assertion; refetch-count made relative; empty-lineup create-affordance count) — intent preserved.
## What
Phase 1 of the App.tsx modularization epic #243: extract the **Channels** domain out of `web/src/App.tsx` into `web/src/screens/ChannelsScreen.tsx` with a colocated test file. **Pure structural move** — no API, route, CSS, or visual change. `App.tsx` retains only the import + the `<ChannelsScreen />` dispatch clause.
Runs on top of merged PR #242 (#212 empty-lineup bare-create + #230 popstate guard), which #244 sequencing required to land first.
## Changes
- **Move**: 14 symbols (`ChannelViewFilter` → `ChannelTableRow`) → `ChannelsScreen.tsx`, zero-prop, mirroring the SchedulesScreen extraction. The Dashboard-owned `progressFromNowPlaying` is **inlined** into the moved `progressFromChannelState` (behavior-identical) so the screen has no import back into App.tsx.
- **Tests**: 12 Channels behavior blocks → colocated `ChannelsScreen.test.tsx` with its own scoped `window.fetch` mock, rendering `<ChannelsScreen />` directly (no `mockDashboardApi`). App.test.tsx keeps one thin nav-smoke test for the route. Three assertions adapted for direct-render (dropped App-TopBar heading assertion; refetch-count made relative; empty-lineup create-affordance count) — intent preserved.
- **App.tsx**: block deleted, import added, 12 now-dead imports pruned (shared `ChannelState`/`messageFromError`/`ApiError`/`useChannelsQuery` verified still used, kept).
- **Docs (same PR)**: spa-conventions §6 (extracted-screen own-fetch-mock convention), decisions.md (single-file rationale — no `web/src/channels/` sibling dir unlike Schedules; inlined helper; #238 deferral).
## Deliberately out of scope
- **#238** (TopBar `primaryAction` dead button) left exactly as-is — it is #238's owned bug; the shell/action redesign is epic phase 4 (#247).
- No `web/src/channels/` helper dir — Channels' pure logic is ~30 lines, no `itemRules.ts`-class layer (decisions.md).
## Verification
- web vitest **587 passed** (ChannelsScreen.test.tsx 12, App.test.tsx 116)
- eslint clean · tsc + vite build clean · `check:api` no drift
- Live E2E of the Channels screen to follow on this PR.
Closes #244.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Move the Channels domain verbatim out of web/src/App.tsx into
web/src/screens/ChannelsScreen.tsx (zero-prop, self-sufficient, mirroring the
SchedulesScreen extraction). Pure structural move: no API, route, CSS, or
visual change. App.tsx retains only the import + the <ChannelsScreen /> dispatch.
- 14 symbols moved (ChannelViewFilter → ChannelTableRow); the Dashboard-owned
progressFromNowPlaying is inlined into the moved progressFromChannelState so
the screen has no import back into App.tsx (behavior-identical).
- 12 Channels behavior tests moved to a colocated ChannelsScreen.test.tsx with
its own scoped fetch mock (renders <ChannelsScreen /> directly, no
mockDashboardApi); App.test.tsx keeps one nav-smoke test for the route.
- Pruned 12 now-dead App.tsx imports; shared symbols (ChannelState,
messageFromError, ApiError, useChannelsQuery) verified still used and kept.
- Docs: spa-conventions §6 (extracted-screen own-fetch-mock convention),
decisions.md (single-file rationale; no web/src/channels/ sibling dir, unlike
Schedules; inlined helper; #238 deferral).
Verified: web vitest 587 passed, eslint clean, tsc/vite build clean,
check:api no drift. #212 empty-lineup bare-create success+failure coverage
preserved. #238 TopBar dead-button left as-is (its owned bug; shell redesign
is epic phase 4 / #247).
refs #244#243
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
timothy
merged commit 533c6c1e9f into main2026-07-11 13:38:48 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Phase 1 of the App.tsx modularization epic #243: extract the Channels domain out of
web/src/App.tsxintoweb/src/screens/ChannelsScreen.tsxwith a colocated test file. Pure structural move — no API, route, CSS, or visual change.App.tsxretains only the import + the<ChannelsScreen />dispatch clause.Runs on top of merged PR #242 (#212 empty-lineup bare-create + #230 popstate guard), which #244 sequencing required to land first.
Changes
ChannelViewFilter→ChannelTableRow) →ChannelsScreen.tsx, zero-prop, mirroring the SchedulesScreen extraction. The Dashboard-ownedprogressFromNowPlayingis inlined into the movedprogressFromChannelState(behavior-identical) so the screen has no import back into App.tsx.ChannelsScreen.test.tsxwith its own scopedwindow.fetchmock, rendering<ChannelsScreen />directly (nomockDashboardApi). App.test.tsx keeps one thin nav-smoke test for the route. Three assertions adapted for direct-render (dropped App-TopBar heading assertion; refetch-count made relative; empty-lineup create-affordance count) — intent preserved.ChannelState/messageFromError/ApiError/useChannelsQueryverified still used, kept).web/src/channels/sibling dir unlike Schedules; inlined helper; #238 deferral).Deliberately out of scope
primaryActiondead button) left exactly as-is — it is #238's owned bug; the shell/action redesign is epic phase 4 (#247).web/src/channels/helper dir — Channels' pure logic is ~30 lines, noitemRules.ts-class layer (decisions.md).Verification
check:apino driftCloses #244.
🤖 Generated with Claude Code