# LAST_STATE — 2026-09-02 Session sign-off snapshot. Read this first when resuming the multistreaming work. ## 1. Done: multistreaming E2E test (podman) — all passing Tested the whole app in rootless podman: built `multistreaming:test` from `services/multistreaming/Dockerfile`, ran with `--network host`, `AUTH_MODE=local`, volume `ms-test-data`. - HTTP API smoke: health, auth config, providers, register/login/session, `/api/me`, `/api/state`. - RTMP ingest: published 12s `testsrc2` with host ffmpeg → feed went live in state, cleaned up on unpublish. - Passthrough fanout: `-c copy` child pushed to a local `ffmpeg -listen 1` RTMP sink on :1936; sink file probed as h264 1280×720, 12.004s — exact copy, 0 restarts. - Scene composition: grid scene + text overlay activated → passthrough suspended, composed child ran; sink file h264 1920×1080 + aac program audio; drawtext overlay confirmed rendered (white-glyph pixel analysis; fanout log shows the correct filter graph). - Persistence: accounts/rooms/feeds survived container recreation (volume). Scratch scripts: `/tmp/ms_test/` (passthrough.sh, scene.sh, verify-fix.sh, ids.txt…). Test recipe in Claude memory: `memory/multistreaming-podman-test.md`. Test container `ms-test` may still be running (panel :8080); teardown: `podman rm -f ms-test && podman volume rm -f ms-test-data`. ## 2. Done: Dockerfile font path fix (committed) `src/config.js` defaults `SCENE_FONT`/`SCENE_FONT_BOLD` to Debian's `/usr/share/fonts/truetype/dejavu/`, which does not exist in the Alpine image (fonts live in `/usr/share/fonts/dejavu/`). drawtext only worked via FFmpeg's fontconfig fallback. Fixed by setting the Alpine paths as ENV in `services/multistreaming/Dockerfile`. Rebuilt, re-verified (fanout log shows the correct fontfile, overlay still renders). ## 3. In progress: OBS-style rework of the web panel (`services/multistreaming/web`) Reference image: `reference1.png` (repo root) — an OBS Studio screenshot: left dock Scenes+Sources, center program canvas, right dock Audio Mixer + Scene Transitions + Controls, bottom status bar. (Image viewing is broken in this environment; the layout was decoded via OCR.) **User-approved target layout — variant B:** ``` ┌──────────┬──────────────────────┬───────────────┐ │ SCENES │ │ CONTROLS │ │ │ PROGRAM PREVIEW │ Start/Stop │ │ SOURCES │ (live feed) ├───────────────┤ │ (media) │ │ Rooms/Vaults │ │ ├──────────────────────┤ User/Members │ │ │ MIXER │ TRANSITIONS│ │ └──────────┴──────────────────────┴───────────────┘ ``` - **Left dock**: Scenes + Sources (media feeds) + Destinations (output routing) + Images. - **Center**: simulated 16:9 program canvas; beneath it Audio Mixer + Scene Transitions panels; OBS-style slim status bar at the bottom. - **Right sidebar**: Streaming Controls + app nav (rooms/vaults/user, moved from the left) + Members. The current left `AppSidebar` and right `RoomSidebar` are both absorbed into this one right sidebar. - **Simulated program canvas** (no real video pipeline): render from scene data — grid/PiP layout math mirroring backend `src/compose.js` (grid cell = floor(W/cols) × floor(H/rows), single live feed promoted fullscreen, PiP main fullscreen + 480×270 window top-right); slot boxes with feed names + LIVE badges; text/image overlays at their real coordinates scaled to the canvas (text: x,y,fontSize,color,bold; image: x,y,width,height,opacity, src `/api/images/:id/file`). Passthrough mode (no active scene): live feeds in a simple grid. Scene activate crossfades the canvas (CSS transition keyed on `activeSceneId`). **Design decisions already made:** 1. **Mixer**: active scene → one channel per scene-output with functional audio-routing control (Program/Silent/Feed via `api.updateSceneOutput`) + animated meter when `runtime.running`. Passthrough → channels = live feeds, decorative animated meters only (no real levels exist). 2. **Transitions**: "Fade" + duration slider (localStorage, client-side only) controlling the CSS crossfade of the simulated canvas on scene activate. Real video crossfade = future backend work. 3. **Controls**: Start Streaming (existing `GrantDialog` flow), **Stop Streaming** (wire the currently unused `api.stopStream` — zero call sites today), per-account grant/live status. No Studio Mode / Recording / Virtual Camera (no backend equivalents). 4. **Right sidebar**: Controls + NavVaults + NavRooms + NavUser + Members; renders vaults/rooms/user even without a room open (Controls/Members room-only). 5. **HomeView** unchanged, just rendered in the center column without a left sidebar. 6. Keep prop-drilling + 3s `api.state()` polling (src/lib/use-app.ts); no state rewrite. 7. SceneEditorDialog / ImageUploader / PromptDialogs stay dialogs; only mounting points move. **Current codebase facts (from exploration):** - Shell: `src/App.tsx` (SidebarProvider > AppSidebar + SidebarInset{header+RoomView|HomeView} + RoomSidebar; global dialogs at App level). No router; nav via useApp `screen/roomId`. - Components: `app-sidebar.tsx`, `room-sidebar.tsx`, `room-view.tsx` (stacked cards), `scene-panel.tsx`, `scene-editor.tsx` (606-line dialog), `dialogs.tsx` (GrantDialog 428-515), `image-upload.tsx`, `home-view.tsx`, `nav-*.tsx`, `theme-provider.tsx` (`useTheme` unused in UI). - shadcn ui/ has sidebar/card/dialog/select/switch/toggle-group/badge/tooltip etc. **Missing: slider** (build `ui/slider.tsx` from the `radix-ui` unified package already in dependencies — no new npm deps). - `lib/api.ts` has every endpoint needed; `lib/types.ts` covers all state shapes. - Dark theme tokens are neutral gray (`src/index.css`); OBS look is dark blue-gray — subtle token tint planned. **What remains:** implement the layout (no code written yet). The full implementation plan (component-by-component, canvas math, crossfade pattern, styling tokens, order, risks) is in `~/.claude/plans/starry-tickling-grove.md`.