Commit graph

161 commits

Author SHA1 Message Date
Luna
be5634a2f9
docs: handoff — empty-library-at-launch findings, remote-editor brainstorm state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 03:52:22 -07:00
Luna
b74dc0924d
docs: retire player click-through watch-out (verified in daily use)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 03:35:59 -07:00
Luna
2c9031bcf3
fix: stop narrow windows shoving the library under the sidebar
At the ~1000px window minimum, the toolbar's right-aligned sort group
ran out of row width and overflowed leftward past the panel edge; egui
then advances the vertical cursor from the overflowed rect, so every
row below (view toggle, the whole list) shifted left and was clipped
under the sidebar.

The sort chips now right-align only when a font-metrics measurement
says they fit, and otherwise drop to their own wrapped row. The top
bar likewise wraps instead of pushing nav buttons off-screen, with the
right-aligned status label width-guarded the same way (truncating
fallback).

Verified via live GUI screenshots at 1000x700 and 1400x800; the wide
layout is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 03:31:56 -07:00
Luna
23ed80a3b7
perf: desktop row virtualization, phase 2
Render List/Card/Grid through ScrollArea::show_rows so only the visible
row range is laid out per frame. Fixed-size cells (allocate_ui_with_layout
+ set_min_size) enforce a uniform lattice; titles truncate to one line with
the full title on hover. List separators are painted hlines inside the
cell; Grid is a manual horizontal run of fixed cells per lattice row with
cols computed from available width before show_rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 02:52:04 -07:00
Luna
d4bed019b2
perf: runtime performance pass, phase 1 (DB pragmas, batched scan writes)
Executes docs/superpowers/specs/2026-07-07-runtime-performance-pass-design.md:

- database.rs: every pooled connection now runs WAL + synchronous=NORMAL
  + busy_timeout=5000 + foreign_keys=ON via with_init (in-memory pools
  get the latter two). Fixes the SQLITE_BUSY failure mode under the
  parallel scanner and makes FK cascades work on every connection, not
  just whichever one last ran the old one-shot pragma in delete_folder.
- database.rs: prepare_cached for the per-video info_cache queries; new
  info_cache_put_many bulk upsert (single transaction) replaces the
  per-row autocommitted info_cache_put, with a round-trip unit test.
- library.rs: enrich_with_cache collects cache misses and flushes them
  once per channel; title sort uses sort_by_cached_key.
- app.rs: Title/ChannelAsc sorts use sort_by_cached_key instead of
  allocating two lowercased strings per comparison.
- Cargo.toml: panic=abort in release (crash-hook still fires; Cargo
  forces unwind for test harnesses). PACKAGING.md documents the local
  RUSTFLAGS target-cpu=native opt-in; repo stays portable.
- .gitignore: catacomb.db-wal / catacomb.db-shm sidecars.

Verified: 146 tests pass (integration suite exercises the pragmas
end-to-end), x86_64-pc-windows-gnu check green, live smoke shows
journal_mode=wal + batched cache writes landing correct rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 01:22:40 -07:00
Luna
891b1e0d3c
feat(web): fix mobile scaling regressions and ship installable PWA
Mobile fixes (all in the embedded SPA):
- Move the mobile media queries to the bottom of the stylesheet — the
  cinematheque design layer was appended after them, so its base rules
  (header padding, video heights) silently overrode the phone layout by
  source order. A comment now pins the ordering invariant.
- The design layer's uiDrop entry animation (fill-mode:both) permanently
  overrode the sidebar's translateX(-100%), leaving the drawer stuck open
  over the content on phones; the mobile block now disables it.
- Lower the single-column breakpoint 380px -> 350px: it was catching
  360/375px phones and blowing every card up to full width.
- viewport-fit=cover so the env(safe-area-inset-*) paddings actually
  resolve on iPhones; notch-safe padding on header/content/modals.
- 16px inputs on small screens (kills iOS Safari's focus auto-zoom),
  same for the login page's password field.
- Mobile rules for the design-layer components that had none: full-height
  command-palette sheet, clamped stats numerals, coarse-pointer touch
  targets for the custom player (taller scrub track, always-visible
  thumb, no volume slider).
- CSP font-src now allows data: — the embedded base64 woff2 fonts were
  being blocked and silently fell back to system faces.

PWA:
- manifest.webmanifest, minimal service worker, and a new Catacomb arch
  icon set (SVG source + rendered PNGs incl. maskable + apple-touch),
  all include_str!/include_bytes!-embedded like the HTML.
- sw.js only intercepts GET navigations to "/" (network-first, cached
  offline fallback) and the static assets; /api, /ws, /files,
  /music-files, /feed are never touched, and it's served no-store so
  binary upgrades keep propagating.
- Routes + auth_middleware allowlist so the browser can fetch the
  statics pre-login; theme-color meta tracks the active theme's panel.
- tests/api.rs covers the new endpoints incl. the ungated-when-password
  invariant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 00:04:17 -07:00
Luna
52c561b47b feat(web): enhance layout and responsiveness of UI components 2026-07-04 02:54:00 -07:00
Luna
3a55ff5b8a feat(android): Compose app with bundled yt-dlp, theme pack, nav & progress
Turn android/ into a real Gradle app (Kotlin + Jetpack Compose + Material3)
that bundles an on-device yt-dlp engine and reuses the Phase-4 Rust JNI core.

- Bundled engine: youtubedl-android (Python + yt-dlp + ffmpeg + aria2c) via
  Engine.kt; initialises off the main thread on first launch. Requires legacy
  jniLib packaging so the bundled .zip.so payloads extract to disk.
- Intuitive navigation: bottom NavigationBar — Download / Files / Settings.
- Settings section: dedicated screen with a live theme picker, default-quality
  chips, engine info, and a Rust-core demo.
- All 19 desktop themes ported from src/theme.rs into Theme.kt as Material3
  colour schemes, shown as tappable swatches; selection persisted (Prefs.kt).
- Download screen: live platform detection through the Rust core as you type,
  plus an improved animated determinate progress indicator (%/ETA/status/
  success-error) and a scrollable log.

Toolchain pinned for reproducibility: Gradle 8.9 wrapper, AGP 8.5.2, Kotlin
1.9.24, Compose BOM 2024.06.00, compileSdk 34, minSdk 24, ABIs arm64-v8a +
x86_64. build-apk.sh runs Gradle on a JDK <= 21 (system JDK may break R8) and
builds the Rust libs first.

Verified on an Android 14 x86_64 emulator: installs, launches, engine reports
"yt-dlp ready", Rust .so loads, theme switching re-themes live and persists
across restart, all screens render. (A real YouTube download still depends on
the anti-bot/POT question — Phases 2-3, device-only.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-04 02:54:00 -07:00
Luna
1790fd1a22 feat(android): installable demo APK exercising the Rust JNI core
Add android/demo — a minimal, dependency-free app (plain Android Views,
no Gradle/AGP/AndroidX/Compose) that calls the Phase-4 JNI core on-device
and displays the results. build-apk.sh drives the SDK build-tools directly
(aapt2 -> javac -> d8 -> zip -> zipalign -> apksigner), compiling against
android.jar alone so it needs no network and tolerates a very new system
JDK by routing the SDK tools through a JDK <= 21.

The app has three buttons -> platformFromUrl/platformDirName, classifyError,
and vttParse, each calling libcatacomb_core.so and showing the JSON.

Verified on an Android 14 (API 34) x86_64 emulator: installs, launches,
loads the .so, and returns correct JSON on-device
({"dir_name":"channels","display_name":"YouTube","icon":"..."} for a
YouTube URL). Output APK (out/, git-ignored): catacomb-spike-debug.apk,
v2+v3 signed, minSdk 24, native code for arm64-v8a + x86_64.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-04 02:54:00 -07:00
Luna
9a613239f2 feat(android): Phase 4 — Rust core to Android .so via JNI
Implement the shared-Rust-core leg of the Android Stage-1 prototype
(docs/superpowers/specs/2026-06-27-android-stage1-prototype-plan.md).

android/rust/catacomb_core is a cdylib that reuses the pure desktop
modules (vtt, error_class, platform) verbatim via #[path] includes — no
logic fork — and exposes them to Kotlin over JNI as String-in/String-out
entry points:

  RustCore.vttParse(vtt)        -> JSON [{start,text},...]
  RustCore.classifyError(log)   -> JSON {class,label,hint}
  RustCore.platformFromUrl(url) -> JSON {dir_name,display_name,icon}
  RustCore.platformDirName(url) -> folder name

Built with panic=abort + per-entry catch_unwind so a Rust panic can never
unwind into the JVM. build.sh locates the NDK portably and cross-compiles
to arm64-v8a + x86_64, depositing the .so into app/src/main/jniLibs/.

Verified: cargo test (38 pass), llvm-readelf confirms all 4 JNI symbols
exported in the arm64 .so, and an end-to-end host-JVM round-trip
(System.load + call each native method) returns correct JSON.

Phases 2/3/5 (WebView-BotGuard anti-bot) remain device-only and untouched.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-04 02:54:00 -07:00
Luna
f3e6d3b5b7 feat(app): add egui context for background thread repaint requests 2026-07-04 02:54:00 -07:00
Luna
4de452c503 feat(tray): integrate egui context for system tray events to ensure immediate UI updates 2026-07-04 02:54:00 -07:00
Luna
f185e7062f Trigger mirror sync 20260629 2026-06-29 11:19:54 -07:00
Luna
cbade2cfd7 feat: desktop visual refresh + 12-theme pack
- 12 new themes (neon/goth/dev/cozy) + hand-tuned Dark/Light (19 total)
- theme-aware semantic accents replacing hardcoded selection rings
- List/Card/Grid video-list modes with global default + per-view override
- unified placeholder thumbnails + typography/spacing refresh
2026-06-28 00:57:28 -07:00
Luna
5091d0de96 fix(app): Grid mode no longer paints thumbnail twice; drop unused ThemeAccents::LEGACY 2026-06-27 19:30:02 -07:00
Luna
29d449eb9b style(app): bump base spacing + card title size for legibility 2026-06-27 19:21:10 -07:00
Luna
5aaa760ab4 feat(app): Settings picker for default view mode 2026-06-27 19:19:12 -07:00
Luna
4b011f8584 feat(app): view-mode toolbar toggle with per-view override 2026-06-27 19:17:00 -07:00
Luna
ada5b6d802 feat(app): List/Card/Grid video list render modes 2026-06-27 19:14:45 -07:00
Luna
91f3d8500d feat(app): unified theme-tinted placeholder thumbnails 2026-06-27 10:58:29 -07:00
Luna
0336615fa3 feat(app): ViewMode enum + global default + per-view overrides 2026-06-27 10:55:53 -07:00
Luna
1c858a3271 feat(app): use theme-aware accent rings for select/play/bulk 2026-06-27 10:51:03 -07:00
Luna
5fb8cc7036 feat(app): wire ThemeAccents snapshot, recompute on theme change 2026-06-27 10:48:27 -07:00
Luna
bb85d1fac0 feat(config): add [ui] default_view_mode field 2026-06-27 10:45:05 -07:00
Luna
0a58f8ae40 feat(theme): add 12 new themes (neon, goth, dev, cozy) 2026-06-27 10:42:43 -07:00
Luna
01c258e6d2 feat(theme): hand-tune default Dark/Light themes 2026-06-27 10:39:04 -07:00
Luna
ee1a02e20d feat(theme): add ThemeAccents struct + accents_for() lookup 2026-06-27 10:33:58 -07:00
Luna
4effd7b061 chore: gitignore .worktrees/ for isolated workspaces 2026-06-27 10:26:02 -07:00
Luna
98e8738a30 docs(plan): desktop visual refresh implementation plan 2026-06-27 10:20:21 -07:00
Luna
9aaaee0151 docs(book): add public Theming & appearance page 2026-06-27 10:13:06 -07:00
Luna
300820975c docs(design): desktop visual refresh + theme pack spec 2026-06-27 10:11:11 -07:00
Luna
c76ad1c467 docs(android-spike): gitignore local tooling/session files; add Stage-1 prototype plan 2026-06-27 09:46:25 -07:00
Luna
bd0ebfec1f docs(android-spike): synthesis — GO, with Stage-1 prototype recommended
All four questions answered: Q1-3 RISKY (credible paths), Q4 PROVEN. WebView-
BotGuard POT (YTDLnis-proven) collapses the JS-runtime problem; HLahwani/yt-
dlp-android solves Q1+Q2+Q3 together. One open question remains: does WebView-
POT recover anti-bot WITHOUT curl_cffi (broken on Android)? The Stage-1
prototype tests exactly that. Fallback: client-to-server app against existing web
API. Update ROADMAP 3.2 with pointer to findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 08:39:05 -07:00
Luna
dcdf35889e docs(android-spike): Q4 Rust-core-via-JNI findings + verdict (PROVEN)
Real build-proof run locally: vtt.rs cross-compiles to aarch64 + x86_64 Android
.so (NDK r26d, exported C-ABI symbol). cargo-ndk + uniffi recommended; ~5 days
to reuse 5-6 pure modules. On-device run-proof blocked by sandbox emulator
reaping, downgraded to symbol-export check per plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 02:40:13 -07:00
Luna
11b6679b29 docs(android-spike): Q3 POT/Proof-of-Origin findings + verdict (RISKY)
The pivotal gate does NOT block: POT is required on mobile, but WebView-based
BotGuard token generation (proven in YTDLnis) is the durable path and collapses
the JS-runtime + POT problems into one, killing the Deno/Node sidecar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 02:33:23 -07:00
Luna
0021dd7516 docs(android-spike): Q2 JS-runtime findings + verdict (RISKY)
External JS runtime now a hard yt-dlp dependency (Python jsinterp deprecated
for YouTube 2025.11.12); QuickJS-ng via NDK or HLahwani/yt-dlp-android is the
path. Also fixes a markdown line starting with '#'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 02:28:28 -07:00
Luna
d739fd76ee docs(android-spike): Q1 yt-dlp-on-Android findings + verdict (RISKY)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 01:56:20 -07:00
Luna
1fd169954b docs(android-spike): scaffold feasibility findings doc
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:28:01 -07:00
Luna
6cfc09b17a docs: implementation plan for Android engine feasibility research spike
Six tasks (Q1–Q4 research + emulator repros, plus scaffold and go/no-go
synthesis) against the approved spec. Research-spike shape: investigate →
reproduce-where-cheap → record evidence → verdict, not TDD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:24:54 -07:00
Luna
541324f7c2 docs: spec for Android engine feasibility research spike (roadmap 3.2)
First sub-project of the Android client: a research spike to decide go/no-go
on a standalone on-device download engine (yt-dlp + JS runtime + POT) before
building any UI. Four research questions, "reproduce where cheap" on the
emulator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 00:21:56 -07:00
Luna
5dd1284310 docs: refresh HANDOFF with rename + push-status notes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:25:57 -07:00
Luna
c12de9c645 web UI: command-palette search overlay (f / 🔍)
Replace the old search modal with a keyboard-driven command palette: blurred
backdrop, centered input, results grouped by channel with highlighted FTS
snippets (char(2)/char(3) → <mark>), ↑↓ navigation, Enter to open, Esc to
close, recent searches + quick actions (Downloads/Stats/Health/Shortcuts) in
localStorage. Debounced queries to /api/search with a sequence guard so stale
responses don't clobber newer ones. Upload date is looked up client-side from
the loaded library (SearchHit has no date field). Opened by the 🔍 header
button and the `f` hotkey.

Verified: node --check passes, release builds clean, and a headless harness
screenshot confirms grouped results, highlighted matches, and selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:48:37 -07:00
Luna
5f95bdb479 Point repo URLs at the renamed Codeberg repo (catacomb)
The Codeberg repo was renamed yt-offline → catacomb. Update repository/homepage
(Cargo.toml), url + git source (PKGBUILD), and the Windows-zip README Source
line (package.sh) to the new path. The PKGBUILD `$pkgname::` checkout-dir prefix
is no longer needed now that the repo basename matches pkgname.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 19:34:48 -07:00
Luna
3f611849af docs: rename yt-offline → Catacomb across README, ROADMAP, guides, packaging
Prose + commands updated to the new name: brand reads "Catacomb", while
binary/path/package references are the lowercase `catacomb` (commands, deb/rpm
names, ~/.local/share/catacomb, catacomb.db, catacomb.desktop). Codeberg repo
URLs left pointing at the existing repo. Also folds in the pending doc edits
from earlier in the session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 03:34:18 -07:00
Luna
404362bfb0 Rename yt-offline → Catacomb (crate, binary, data paths, UI)
Full product rename with a one-time data migration so existing installs keep
their library + bundled toolchain:

- Crate/binary: `yt-offline` → `catacomb` (Cargo.toml, deb/rpm assets, PKGBUILD,
  package.sh, the desktop file → catacomb.desktop, launch.json, release CI,
  tests/api.rs CARGO_BIN_EXE_catacomb). Codeberg repo URLs left as-is (real
  addresses); PKGBUILD pins the checkout dir via `$pkgname::`.
- 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 + the venv dir; best-effort,
  no-op once migrated). Crash log + restore-temp + IPC socket names follow suit.
- Display: window/app title, tray, web page titles, feed titles, login + SPA
  wordmarks → "Catacomb". Env override `YT_OFFLINE_RENDERER` → `CATACOMB_RENDERER`.

Verified: builds clean as target/release/catacomb, 128 unit + 11 integration
tests pass, and a scratch-dir run confirms the DB (+wal) and venv are migrated
with bytes preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 03:11:07 -07:00
Luna
6d2261b032 web UI: reskin the login page to match the cinematheque aesthetic
The login page was the last surface still on the old flat style. Give it the
same identity as the rest of the UI: serif wordmark with the glowing crimson
"recording" dot, an italic "your private archive" tagline, accent aurora +
film-grain atmosphere on a gradient card, a themed focus ring and accent
button, and a soft entrance. Login JS is unchanged; honours reduced-motion.
Standalone page, so the display font is a system serif stack (no CDN, no
duplicating the SPA's embedded woff2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 02:38:47 -07:00
Luna
2f95e7f09f web UI: unified cinematic video player (speed, scrubber, captions, PiP)
Replace the browser's native <video controls> (used for direct /files/
playback) with one custom player for both direct and transcode streams, so
the chrome matches the reskin and gains features it never had. All seeking
still routes through playerSeek/effTime, so the transcode reload-at-offset
path is unchanged.

- Custom scrubber: accent played fill + buffered fill + chapter tick markers
  + a hover time tooltip; click/drag to seek (pointer events, works in both
  modes — commits the seek on release so transcode only reloads once).
- Playback speed popover (0.5–2×) and persistent volume + speed + captions
  across videos (localStorage).
- Captions (CC) toggle for overlaid <track> subtitles (off by default; the
  searchable 📄 transcript pane is unchanged), PiP, fullscreen.
- Auto-hiding controls + cursor after idle while playing; a center play/pause
  flash; gradient scrim.
- Expanded keyboard: space/k, j/l ±10, ←/→ ±5, ↑/↓ volume, m, c, p, f,
  < > speed, 0–9 seek-to-percent — now active for direct videos too (they
  previously relied on native controls).

Verified: node --check passes, release builds clean, live 8081 serves it, and
a headless screenshot of the control chrome (extracted CSS) renders correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:59:02 -07:00
Luna
816da05962 web: persist login sessions in SQLite so a restart doesn't log everyone out
Sessions were an in-memory HashMap seeded empty at startup, so every restart
or upgrade invalidated every browser's cookie (the cause of the recent
"settings save → error" report: a restart out from under a logged-in tab).

Mirror sessions to a new `sessions(token, issued_at)` table:
- store issued-at as a UNIX timestamp (the map switches from Instant→u64 so it
  can round-trip the DB; is_authed prunes by wall-clock age vs SESSION_TTL),
- insert on login, delete on logout, clear on password change,
- rehydrate the map at startup via load_sessions(), which also prunes rows
  past the TTL. DB writes are best-effort — a failure just means that token
  won't survive a restart, not that login breaks.

Verified end-to-end: set a password, log in, restart the server against the
same DB, and the original cookie still authenticates (200) while a cookieless
request is still 401. 128 unit + 11 integration tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 19:18:23 -07:00
Luna
dd48e899fa web UI: on 401, reload to the login page instead of a cryptic error toast
Sessions are in-memory, so a server restart (or a session TTL expiry) leaves
a still-open SPA tab holding a stale cookie: cached views keep rendering, but
every mutating call 401s and surfaced only as an "authentication required"
toast (e.g. saving Settings) — a confusing dead end.

Make api() treat 401 as "session gone": flash a notice and location.reload(),
which the server answers with the login page. No reload loop (the login page
isn't the SPA), and authed sessions are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 18:09:49 -07:00
Luna
615c088b43 web UI: maintenance modal → "diagnostics console"
Reskin the Library-health modal into a systems-diagnostics readout, sibling
to the stats observatory and sharing its sx-* section language. Presentation
only — every id/class/handler the logic depends on (dup-chk, sim-chk, at-chk,
dedup-area, autotag-area, the async polling) is untouched.

- A pulsing health verdict ("Healthy" / "N items to review") whose count and
  status colour update live as the async subsystems resolve.
- Four status tiles (Duplicate IDs, Missing assets, Similar content, Unfiled
  groups) with health-coloured dots + top rules; sim/at start pending and are
  filled by their render functions.
- Sections become instrument panels: duplicate/similar groups as cards with
  KEEP/REMOVE pills, missing-asset rows, a gradient dedup progress bar, and
  auto-tag groups with confidence dots. Modal widened to 920px.
- Status colour flows through --mx-c off a data-status attribute, scoped to
  .mx-body so it can't leak onto other nodes; pulse honours reduced-motion.

Verified: JS passes node --check, release builds clean, live 8081 serves it,
and a headless screenshot of a harness running the extracted CSS+JS against a
mock report confirms the verdict math, tiles, pills and panels all render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:39:20 -07:00