# Session handoff — Catacomb

> Working notes for continuing this work in another session/agent (Copilot, Codex, etc.).
> Updated 2026-06-19. Delete or update freely; this is not a tracked spec.

## TL;DR — where things are

- Project: **Catacomb** (crate/binary `catacomb`) — one Rust binary that is
  **both** an egui desktop GUI and an axum web server wrapping `yt-dlp`. See
  [CLAUDE.md](CLAUDE.md) for the authoritative architecture; [ROADMAP.md](ROADMAP.md)
  for the plan.
- Branch: `main`. Last commit `c12de9c` (command palette); `5f95bdb` and
  earlier are pushed, **`c12de9c` is committed but NOT yet pushed**. Remote:
  `https://codeberg.org/anassaeneroi/catacomb.git`.
- **Local checkout dir was renamed** `~/code/youtube-backup` → **`~/code/catacomb`**
  (code dir only; the library/backup dir at `/mnt/InannaBeloved/youtube-backup`
  is unchanged). Run the server with the right CWD, e.g.
  `env -C ~/code/catacomb ~/code/catacomb/target/release/catacomb --web 8081`.
- Only `HANDOFF.md` is uncommitted now.
- The project was **renamed yt-offline → Catacomb** (crate, binary, data
  paths with migration, UI, docs, repo URLs). See "The rename" below.
- A dev web server is usually run on **:8081** against the user's real library.
  It gets reaped between turns in this sandbox — just relaunch it (see gotchas).

## How to build / run / test

```bash
cargo build --release                 # ~1.5 min (opt-level=3 + thin LTO) → target/release/catacomb
cargo test --release                  # 128 unit + 11 integration (tests/api.rs); no network
./target/release/catacomb --web 8081  # headless web server (what the user uses)
./target/release/catacomb             # desktop GUI (default)
```

### CRITICAL gotchas

1. **The web SPA is one big embedded file** — `src/web_ui/index.html`, baked in
   at compile time via `include_str!`. **Editing it requires a `cargo build`** to
   take effect. A JS syntax error there will NOT be caught by `cargo build` (the
   HTML is just a string), so after every edit:
   ```bash
   awk '/<script>/{f=1;next}/<\/script>/{f=0}f' src/web_ui/index.html > /tmp/spa.js && node --check /tmp/spa.js
   ```
   `src/web_ui/login.html` is a second, separate embedded page (the login screen).
2. **DB location**: the server opens the DB at `channels_root.join("catacomb.db")`
   (`web.rs` ~L3127 and `app.rs` ~L395), i.e. **`<backup.directory>/catacomb.db`**.
   For this user that's `/mnt/InannaBeloved/youtube-backup/catacomb.db` (~64 MB,
   real data — do NOT clobber). `config.toml` is read from the **process CWD**.
   A stray 0-byte `catacomb.db` may sit in the repo dir; it's unused — ignore it.
3. **Offline-first**: this is a self-hosted, possibly-no-internet archiver.
   **Never load fonts/CSS/JS from a CDN.** UI fonts are embedded as base64 woff2
   (SIL OFL) directly in the `<style>` block. Keep it that way.
4. **All 10 themes** are driven by 7 CSS variables (`--bg --panel --card --accent
   --text --muted --border`) set per `.theme-*` class. Any new UI must use those
   vars (and the design-layer tokens below) so every theme inherits it.
5. **Background server reaping**: in the sandbox, `run_in_background` servers get
   killed between turns and shell `&`/`nohup` is unreliable. Just relaunch
   `./target/release/catacomb --web 8081`; `pkill -f "web 8081"` first.
6. **Verifying UI visually without a browser driver** (no puppeteer/playwright):
   extract the relevant CSS/JS from `index.html` into a `/tmp/*.html` harness with
   a mock payload and screenshot with headless chromium:
   ```bash
   chromium --headless=new --no-sandbox --disable-gpu --hide-scrollbars \
     --window-size=1100,1300 --virtual-time-budget=3000 \
     --screenshot=/tmp/out.png "file:///tmp/harness.html"
   ```
   `--virtual-time-budget` can capture entry animations mid-flight; add a "settle"
   override (disable transitions, force final state) for a clean final shot.

## The rename (yt-offline → Catacomb)

Done and pushed across `404362b` (code), `3f61184` (docs), `5f95bdb` (repo URLs):

- **Crate/binary** `catacomb` (Cargo.toml, deb/rpm assets, PKGBUILD, package.sh,
  `catacomb.desktop`, launch.json, release CI, `tests/api.rs` → `CARGO_BIN_EXE_catacomb`).
- **Data paths**: DB `yt-offline.db` → `catacomb.db`; venv `~/.local/share/yt-offline`
  → `~/.local/share/catacomb`. `migrate_legacy_paths()` in `main.rs` adopts the old
  names on first run (renames DB + WAL/SHM sidecars + venv dir; best-effort, no-op
  once migrated). **Keep this function's OLD-name args as `yt-offline`** — a blunt
  rename sed will wrongly make both sides `catacomb` and break migration (happened
  once; watch for it).
- **Display**: window/tray/title/feed/login/SPA wordmarks → "Catacomb". Env
  override `YT_OFFLINE_RENDERER` → `CATACOMB_RENDERER`.
- Verified: builds, 128+11 tests pass, scratch-dir migration preserved bytes, and
  the live cutover migrated the real 64 MB DB + venv (server served the library).
- Repo URLs point at `…/anassaeneroi/catacomb` (Cargo.toml, PKGBUILD, package.sh).

## Settings flow (the easy thing to get wrong)

Adding a setting touches **five** places (grep an existing one like
`dedup_enabled` or `sponsorblock_mode` end-to-end first):

1. `config.rs` — field + `Default` + `default_with_dir()`.
2. `download_options.rs` — `Option<…>` per-channel override (None = use global).
3. `downloader.rs` — resolver merging global+override + a `pub` field on `Downloader`.
4. Both UIs: `app.rs` (egui Settings + channel-options dialog) AND
   `web_ui/index.html` (Settings modal + channel dialog) AND `web.rs`
   `SettingsPayload` (GET reads config, POST writes config + pushes to live `Downloader`).
5. Seed the `Downloader` field at construction AND on settings-save, in BOTH
   `app.rs` and `web.rs`.

## What this session shipped (committed, newest first)

- `5f95bdb` repo URLs → renamed Codeberg repo.
- `3f61184` / `404362b` **Rename → Catacomb** (see above).
- `6d2261b` **Login page reskin** — `src/web_ui/login.html`: serif wordmark +
  recording dot, aurora + grain, accent focus ring; system-serif (offline-safe).
- `2f95e7f` **Unified cinematic video player.** Replaced native `<video controls>`
  with ONE custom player for both direct + transcode. Custom scrubber
  (played/buffered/chapter-ticks/hover tooltip, drag-to-seek via pointer events,
  commits on release), speed popover (0.5–2×), persistent volume/speed/captions
  (localStorage `plPrefs`), CC toggle, PiP, fullscreen, auto-hiding controls +
  center flash, expanded keyboard (space/k j/l ←→ ↑↓ m c p f `<` `>` 0–9). All
  seeking routes through `playerSeek`/`effTime` (transcode reload-at-offset intact).
  CSS prefix `.pl-*`; JS around `playVideo`/`updateVctrl`/`plBindScrubber`.
- `816da05` **Persist login sessions in SQLite** — `sessions(token, issued_at)`
  table; map switched `Instant`→`u64`; insert/delete/clear + rehydrate at startup.
  Fixes "restart logs everyone out".
- `dd48e89` **api() 401 → reload to login** instead of a cryptic "error" toast.
- `615c088` **Maintenance modal → "diagnostics console"** (CSS `.mx-*`): pulsing
  health verdict + status tiles + instrument panels. Presentation only; handler
  ids/classes untouched.
- `6f61821` **Stats modal → "observatory" dashboard** (CSS `.sx-*`): count-up
  metrics, self-drawing SVG area chart, growing histogram, ranked leaderboard.
- `c8cb700` **"Cinematheque" reskin**: embedded Instrument Serif + Hanken Grotesk
  (base64 OFL), accent aurora + film-grain, cinematic card hover, recording dot.
- `983864f` macOS osxcross packaging; `207013e` Windows shippable .zip + CI;
  `8b25787` library sorting (download-date + grouped options).

### Design-layer conventions (web UI)

- CSS is appended near the end of the `<style>` block, AFTER the functional rules,
  so equal-specificity overrides win by source order. Prefixes: `.sx-*` = stats
  observatory, `.mx-*` = maintenance console, `.pl-*` = player.
- Reveal animations gate behind a class added post-layout (e.g. `.sx-go`) so they
  play once per open, not on re-render. All honor `prefers-reduced-motion` (global
  rule at the end of `<style>`).
- Fonts: `--font-display` (Instrument Serif), `--font-body` (Hanken Grotesk).
  Extra tokens: `--radius`, `--radius-sm`, `--ring`, `--glow`, `--shadow`, `--hair`.

## Command-palette search overlay — SHIPPED (`c12de9c`)

`openSearch()` is now a `.cp-*` command palette (CSS `.cp-*`; JS `openSearch`,
`runCommandSearch`, `renderCommandPalette`, `cpKeydown`, `updateSelected`,
`scrollIntoSelected`, `cpSelect`, `cpOpenRecent`, `cpClearRecents`,
`closeCommandPalette`, `ftsSnippet`). Blurred backdrop, centered input, results
grouped by channel with highlighted FTS snippets (`char(2)`/`char(3)` → `<mark>`
via `ftsSnippet`), ↑↓ nav, Enter to open (`cpSelect`→`selectVideo`), Esc to
close, recents + quick-actions in `localStorage['cp-recents']`. Debounced
queries to `/api/search?limit=60&q=…` with a `cpSeq` guard against stale
responses. Upload date is looked up client-side via `findVideo` (SearchHit has
no date). Triggers: 🔍 header button + `f` hotkey. Verified by build + headless
harness screenshot. Built into the running `:8081` binary.

## Suggested next steps (pick up here)

1. **Push** — `c12de9c` (command palette) is committed but not yet pushed.
2. Any rough edges from real daily use (the player + palette especially — both
   verified via harness screenshots, not live click-through).
3. A fresh web-UI surface to reskin/upgrade, or a roadmap item below.

Roadmap "surpass" items still open (see [ROADMAP.md](ROADMAP.md) §3):

- **3.1 macOS binary** — osxcross scaffolding done (`scripts/package.sh mac`);
  needs the toolchain + SDK installed, then verify. Windows already ships.
- **3.2 Android client** (big), **3.8 plugin/scripting hooks** (architectural).
- Phase 4 blue-sky: AI transcript summarisation (FTS transcript index already
  exists), TV-mode layout, multi-user accounts.
- Federation follow-up (3.5): in-UI "add remote" editor (peers are config-only).

## Watch-outs / open questions

- The video player's seek/speed/captions are verified by code-path review +
  static chrome screenshot, NOT live playback (no headless video). Worth a real
  click-through in the app.
- After the rename + session-persistence work, the user's existing browser cookie
  predates both, so they must **log in once more**; logins after that survive
  restarts. A download password may or may not be set (toggled during testing) —
  that's user-driven, not a bug.
- The user's local `config.toml` `source_url` still points at the old
  `…/yt-offline` repo (gitignored, user-specific) — change it in Settings if the
  UI "source" link should match the renamed repo.
- Don't commit `cookies.txt`, `config.toml`, or `catacomb.db` (all gitignored;
  contain creds / the Argon2 password hash).
