# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Current state (read this first)

Active OBS Studio config dir for the **ophi118** Twitch streaming rig. It hosts: six scenes (Landing, Project Loading, Game, Desktop, Music Box, Good Bye), a Laravel webapp (`webapp/`) that serves the scene overlays over `http://127.0.0.1:1118/` under supervisord, an MPD-based audio architecture with `bridges/mpd-state.py` re-broadcasting MPD state as `mpd:state` events over OBS WebSocket, a Twitch chat bot that controls MPD via `!skip`/`!queue`/`!info`, and an optional Mattermost notifier that posts when the channel goes live. Two rigs share this codebase via a single git repo: **Ignia** (current) and **Midgolem** (deployment target).

## What this directory is

Active OBS Studio user config for the **Flatpak** install (`com.obsproject.Studio` from Flathub). Path: `~/.config/obs-studio/`.

**Why `~/.config/` and not `~/.var/app/com.obsproject.Studio/config/obs-studio/`?** The Flatpak ships with `filesystems=host` (full host access, see `flatpak info --show-permissions com.obsproject.Studio`). With that permission, Flatpak does **not** indirect XDG paths into the per-app private dir — OBS sees the real `$HOME/.config/` and uses `~/.config/obs-studio/` directly, like a native install would.

A ghost copy lives at `~/.var/app/com.obsproject.Studio/config/obs-studio/` — that's the path Flatpak *would* use if `filesystems=host` weren't set. **OBS does not read or write there.** Some files accumulated there from work that targeted the wrong path. Treat it as orphaned; don't edit it expecting OBS to pick changes up.

## How OBS is launched

```
flatpak run com.obsproject.Studio
```

Or via the app menu ("OBS Studio"). The native `obs` binary still exists from `pacman` but should be considered dead.

## Layout — what lives where

**OBS-owned (don't edit while OBS is running — see "OBS overwrites on exit" below):**
- `user.ini` / `global.ini` — selects active scene collection and profile, plus window state. `user.ini` is the live one (currently collection `Default Stream HUD`, profile `ophi118`).
- `basic/scenes/<name>.json` — the scene collection. Active: `Default_Stream_HUD.json`. Scenes inside: `Landing`, `Project Loading`, `Game`, `Desktop`, `Music Box`, `Good Bye` (Ctrl+M / Ctrl+, / Ctrl+. switch among Music Box / Desktop / Game).
- `basic/profiles/<name>/` — encoder, streaming service, recording settings. Active: `ophi118`.
- `plugin_config/<plugin-id>/` — per-plugin settings. `obs-websocket/config.json` holds the WebSocket password — secret.
- `plugin_manager/modules.json` — third-party plugins OBS loads.
- `logs/`, `profiler_data/`, `.sentinel/` — runtime state.

**Scene overlays (`webapp/` — Laravel app served on 127.0.0.1:1118 by supervisord):**
- `webapp/resources/views/overlays/landing.blade.php` — Fallout-style "PLEASE STAND BY" overlay + telemetry rotator.
- `webapp/resources/views/overlays/loading.blade.php` — terminal-style Project Loading overlay + countdown.
- `webapp/resources/views/overlays/game.blade.php` — Game HUD (camera-frame OBS-WS sync, status bar).
- `webapp/resources/views/overlays/desktop.blade.php` — Desktop HUD (camera + screen-capture frames).
- `webapp/resources/views/overlays/goodbye.blade.php` — sign-off terminal + "OFFLINE FOR" counter.
- `webapp/resources/views/overlays/music/box.blade.php` — full-screen Music Box terminal.
- `webapp/resources/views/overlays/music/widget.blade.php` — compact 549×880 widget (`bounds_type: 0`, no stretch).
- `webapp/resources/views/overlays/music/cover.blade.php` — album-art widget (`?bars=0` for compact mode).
- `webapp/resources/views/overlays/music/nc.blade.php` — ncurses-styled 2-pane Music Box.
- `webapp/resources/views/overlays/music/daemon.blade.php` — passive diagnostic; playback is handled by MPD + `bridges/mpd-state.py`.
- `webapp/public/css/hud.css` — extracted shared HUD chrome, scanlines, terminal styling, pulse keyframes.
- `webapp/public/js/{hud,crt-static,obs-ws-bootstrap,obs-ws-mini}.js` — shared JS; `obs-config.js` is gitignored, generated by `php artisan rig:setup`.
- Routes + controllers under `webapp/app/Http/Controllers/`. See `routes/web.php` for the URL → handler table.

**Artisan commands (replace the old bash helpers — see `archived/scripts/`):**
- `php artisan rig:setup` — generate `webapp/public/js/obs-config.js` + `OBS_WS_*` env from `plugin_config/obs-websocket/config.json`.
- `php artisan rig:telemetry [--collect]` — refresh hardware/OBS-profile snapshot at `webapp/storage/data/telemetry.json`.
- `php artisan rig:loading` — interactive manifest builder; updates Twitch channel via Helix (no Python shell-out).
- `php artisan rig:cmd <skip|prev|pause|resume>` — broadcast `mpd:cmd` over OBS WS (CLI mirror of `POST /cmd/{type}`).

**Audio + automation surfaces:**
- `bridges/mpd-state.py` — MPD → OBS WS state bridge (`obs-mpd-bridge.service`). Writes `bridges/cover.jpg`. Authoritative source of `mpd:state` events.
- `twitch-bot/bot.py` — Twitch chat bot for `!skip` / `!queue` / `!info`. Talks to MPD directly, not via OBS WS.
- `twitch-bot/{search-game,set-channel,_twitch}.py` — Helix helpers (still present; `rig:loading` no longer needs them — it uses Guzzle directly).
- `twitch-bot/.env*` — secrets (per-rig, never copy).

**Scripts (`.sh` lives only here):**
- `scripts/deploy-rig.sh` — idempotent bring-up for a fresh rig (now also installs the supervisord program + runs `composer install`).
- `scripts/obs-webapp.supervisord.conf` — supervisord program file; install to `/etc/supervisor.d/obs-webapp.conf`.
- `scripts/rewrite-scene-urls.py` — one-shot tool that rewrote `basic/scenes/Default_Stream_HUD.json` from `file://` to `http://127.0.0.1:1118/...` URLs during migration. Idempotent.
- `scripts/convert.sh`, `scripts/clean.sh` — yt-dlp `.webm` → AAC `.m4a` re-encode + audit.

**Reference / archive:** `archived/` (pre-migration scenes + bash scripts + `vendor/`, kept for rollback — see `archived/README.md`). `playlist/` (gitignored audio library). `profile/` (rig spec docs). `station/` (submodule for the public rig-spec page).

## How OBS reaches the overlays — supervisord + Laravel

OBS browser sources point at `http://127.0.0.1:1118/<route>` (rewritten from the old `file://...` URLs by `scripts/rewrite-scene-urls.py`). The Laravel app runs under supervisord:

```
sudo supervisorctl status obs-webapp     # → RUNNING
sudo supervisorctl restart obs-webapp    # after route / controller / .env changes
tail -F webapp/storage/logs/supervisord.{out,err}.log
```

The program file is `scripts/obs-webapp.supervisord.conf`; the installed copy is at `/etc/supervisor.d/obs-webapp.conf` (root-owned).

## Critical gotcha: OBS overwrites on exit

OBS rewrites `basic/scenes/<collection>.json`, `global.ini`, and `user.ini` on shutdown (and periodically). **Close OBS before editing these files**, or the next OBS exit will clobber the edit. After editing, re-launch OBS to load the change.

**Blade views under `webapp/resources/views/overlays/` are safe to edit while OBS is up** — CEF re-fetches on right-click source → **Refresh cache** in OBS, no restart needed. **Controller / route / `.env` changes need a supervisord restart** (`sudo supervisorctl restart obs-webapp`) — only then will the browser sources see the new behavior on next Refresh cache. CSS and JS under `webapp/public/` are pure static files; no restart needed, just Refresh cache.

**Detecting whether OBS is running** — the Flatpak's inner binary is just `obs`, not `flatpak run com.obsproject.Studio`. Use:
```bash
flatpak ps --columns=application | grep -qx com.obsproject.Studio && echo running   # canonical
pgrep -x obs                                                                          # also works
```
Do NOT use `pgrep -fa 'flatpak run com.obsproject.Studio'` — it almost never matches (the wrapper exits, the sandbox keeps running) and silently returns "off" while OBS is very much up. False-negatives here have led to JSON edits during live sessions that got clobbered on the next save.

OBS itself maintains `.bak` (previous save) and `.json.v1` (pre-version-migration) sidecars in `basic/scenes/`. Those are not git history — leave them alone unless explicitly recovering.

## Critical gotcha: don't restart pipewire-pulse while OBS is running

`systemctl --user restart pipewire-pulse.service` tears down every loopback / null-sink module, which silently detaches OBS's `pulse_output_capture` sources from their monitor devices. Even after the conf reloads and the sinks come back with the same names, OBS does **not** auto-rebind — meters go flat and stream audio cuts. A `SetInputSettings` round-trip over obs-websocket changes the stored settings but does NOT actually reattach the underlying audio stream; only an OBS process restart does.

If you need to apply a new conf in `~/.config/pipewire/pipewire-pulse.conf.d/`:
- **Preferred**: load the modules ad-hoc (`pactl load-module module-null-sink …` + `pactl load-module module-loopback …`) — the conf still takes over on the next pipewire-pulse start, no restart needed.
- **If unavoidable**: stop OBS first (`flatpak kill com.obsproject.Studio`), restart pipewire-pulse, then start OBS.

`scripts/deploy-rig.sh` phase 5 already guards against this: when OBS is running, it skips the restart and prints a warning.

## User-specific conventions

- Never add co-author tags to commit messages.
- On any path error, immediately run `pwd` to confirm location.
- `ssu` is available for git submodule management.
- Core plugins (user, blog, pages, payment) are reused across many projects — no breaking changes unless explicitly asked.

## Deeper context — read these when relevant

- **[docs/audio.md](docs/audio.md)** — PipeWire null-sink architecture, MPD config, OBS WebSocket bus + `mpd:state` payload, systemd user units. Read when editing `bridges/`, `twitch-bot/`, MPD config, pipewire confs, or working with WS custom events.
- **[docs/scenes-and-overlays.md](docs/scenes-and-overlays.md)** — Scene JSON structure, source ids, HUD browser-resolution constraints, camera-frame OBS-sync, plugin & Flatpak quirks. Read when editing `basic/scenes/`, building HUDs, or driving OBS programmatically.
- **[docs/deployment.md](docs/deployment.md)** — Deploy a fresh rig, Twitch identity & secrets, hardcoded asset paths, per-rig vs portable state, Flatpak permissions. Read when bringing up a new rig, rotating tokens, or copying state between Ignia and Midgolem.
- **[docs/history.md](docs/history.md)** — Migration history (linuxbrowser → CEF, browser-daemon → MPD bridge, Discord audio split, etc). Read when encountering pre-migration artifacts or auditing stale conventions.
- **[docs/cheatsheet.md](docs/cheatsheet.md)** — One-liners for common ops.
