Commit graph

124 commits

Author SHA1 Message Date
Luna
db3fa5f86c
feat(icon): coffin + play app icon
Replace the crypt-archway icon with a goth coffin whose play wedge is
punched out as negative space, in the same default palette. Regenerate
all derived PNGs (web icon-512/192, apple-touch-icon, and the 256px
icon.png used for the desktop window/tray + Linux/AppImage/macOS packaging).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019d9sNr6GAtXoVpLJsnh19L
2026-07-22 04:02:42 -07:00
Luna
c06d28cef5
fix(web-ui): add the 11 themes missing from the web picker
The web UI kept its own hardcoded theme list (JS THEMES array + .theme-*
CSS blocks) separate from the desktop theme::THEMES, and had drifted: the
newer palettes (cemetery-moss, vampire, witching-hour, cyberpunk, synthwave,
vaporwave, gruvbox, tokyo-night, paper, honey, candlelight) were never
mirrored into the SPA, so the web only offered 10 of them.

Port those palettes to CSS variables and add them to the picker. A new
unit test (web_ui_offers_every_desktop_theme) pins the web list to
theme::THEMES so this can't silently drift again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 06:21:10 -07:00
Luna
7943388f5f
feat(desktop): PeerTube browse + per-video archive
Two-level lazy nav in the Remotes screen (channels → paged videos),
on-demand Play via mpv (HLS-only noted), one-click Archive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 05:05:24 -07:00
Luna
a77a8665c0
feat(web-ui): PeerTube browse view + per-video archive
Two-level lazy nav (channels → paged videos), on-demand Play (HLS-only
disabled), one-click Archive to the local library.

Channel handles/names are peer-controlled, so they're rendered via esc()'d
data-* attributes with JS-wired click handlers rather than interpolated into
inline onclick JS (a handle containing a single quote would otherwise break
out of the attribute — stored XSS from a hostile peer).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 05:05:19 -07:00
Luna
f102f33870
feat(web): PeerTube browse + archive endpoints (kind-guarded)
GET channels / channel videos (paged) / video media (204 = HLS-only);
POST archive routes watch_url through the shared downloader (lands in Other).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 09:48:51 -07:00
Luna
095f1d48b0
feat(remote): PeerTube browse passthroughs on RemoteClientKind
pt_channels/pt_channel_videos/pt_video_media/pt_watch_url dispatch to the
PeerTubeClient; Err for a catacomb remote. RemoteChannelInfo now Serialize.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 05:33:06 -07:00
Luna
b9271e4ab1
feat(desktop): federation peers editor (add/edit/remove/test, kind-aware)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 01:13:11 -07:00
Luna
476efcf129
feat(web-ui): federation peers editor (add/edit/remove/test, kind-aware)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:58:01 -07:00
Luna
a3d133562e
feat(web): PUT /api/remotes + test endpoint, kind-aware, live-apply
URL-keyed write-only password merge; whole-list replace rebuilds the live
RwLock client list; POST /api/remotes/test checks reachability per kind. GET
/api/remotes now reports kind + has_password (never plaintext). Integration
test covers the PUT/GET round-trip, removal, and password preservation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 07:00:36 -07:00
Luna
db0c74ffca
feat(remote): RemoteClientKind enum + kind-aware live remotes list
Both front-ends hold Vec<Arc<RemoteClientKind>> (web behind RwLock). Catacomb
browse unchanged; PeerTube remotes report a phase-3 stopgap when browsed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 06:56:26 -07:00
Luna
00ae19e666
feat(peertube): OAuth2 + list channels / videos / media over REST
Completes the phase-1 backend client: password-grant OAuth with refresh-token
renewal (falling back to a full re-grant), anonymous public browsing when no
creds are set, list_channels, paginated channel_videos, and on-demand
video_media (direct MP4, None for HLS-only). Wires the tested pure mapping fns
to the API. The client is dead-code-allowed until the editor (phase 2) and
browse UI (phase 3) construct it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 06:18:13 -07:00
Luna
f7f36d9bca
feat(peertube): pure JSON mapping (channels, videos, media, tokens)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 04:46:07 -07:00
Luna
e69ebc7a75
feat(peertube): module skeleton + URL/target parsing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 04:42:08 -07:00
Luna
bf3f2b0030
feat(config): RemoteKind + username for PeerTube remotes
Non-breaking: existing [[remote]] entries default to kind=catacomb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 04:39:44 -07:00
Luna
3e6bcfb564
feat(desktop): instant startup from persisted library snapshot
Seed self.library from load_library_snapshot before spawning the scan thread,
so a warm launch renders the library immediately (status '… refreshing…')
instead of a blank/scanning window. Write the snapshot after every scan (the
startup thread and rescan). The background scan is unchanged and still swaps
in authoritative data; the snapshot only ever shows briefly-stale state.

Verified live against the real 5574-video library: second launch renders the
full list within 2s (well under the ~64s cold scan) with the refreshing
status; deleting the snapshot row falls back to the scanning spinner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 21:19:13 -07:00
Luna
5a55c15209
feat(db): persist library snapshot as JSON blob keyed by root
Add serde derives to the library structs and a library_snapshot table with
save_library_snapshot/load_library_snapshot. Load returns None on a missing
row or unparsable JSON (with #[serde(default)] fields tolerating struct
evolution). Groundwork for desktop instant startup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 19:20:39 -07:00
Luna
843fee58ed
fix: show scanning state instead of empty library on cold launch
The desktop GUI's startup library scan runs on a background thread; on a
cold filesystem cache a large library on encrypted/compressed storage takes
~60s to walk + stat, during which the content area rendered an empty video
list. That looked like a broken/empty library and prompted a needless Rescan
(which only appeared to help because the OS cache was warm by then).

Render a centered spinner + "Scanning library…" while the initial scan is
in flight (library_load_rx still open and library empty). The scan/drain
pipeline is unchanged; this is display-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 17:40:02 -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
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
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
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
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
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
Luna
6f61821d1e web UI: stats modal → maximalist "observatory" data dashboard
Rebuild the Library statistics modal from plain tiles + flat bars + tables
into a real data dashboard, driven by the same /api/stats payload and the
theme variables (so all 10 themes inherit it).

- Metric cards with giant serif numbers that count up from zero on open
  (size/runtime spin up through their units), an accent top-rule, and a
  glowing hero tile.
- A self-drawing SVG area chart for downloads-per-week: Catmull-Rom-smoothed
  line that animates in via stroke-dashoffset, a gradient area fill,
  gridlines, a date axis, and hover dots with tooltips.
- A growing-column histogram for videos-by-upload-year with value labels.
- A ranked channel leaderboard with animated meter fills, gold/silver/bronze
  ranks, and a Size/Count segmented toggle that re-animates on switch.
- Reveal animations gate behind .sx-go (added post-layout) so they play once
  per open, not on re-render; honours prefers-reduced-motion; modal widened
  to 1040px.

Verified: JS passes node --check, release builds clean, the live 8081 server
serves it, and a headless screenshot of a harness running the *extracted*
dashboard CSS+JS against a mock payload confirms the chart math, count-up,
histogram and leaderboard all render correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:29:24 -07:00
Luna
c8cb700f7e web UI: "cinematheque" reskin — embedded editorial fonts + atmosphere + motion
A presentational redesign of the web SPA, appended as a design layer so it
overrides by source order while every colour still flows through the existing
7 per-theme CSS variables — so all 10 themes inherit it and no element
id/class the JS relies on changes.

- Typography: embed Instrument Serif (display) + Hanken Grotesk (body) as
  base64 woff2 (SIL OFL) — offline-safe, no CDN, no privacy leak. Serif
  wordmark/headers/empty-state; clean grotesque for UI.
- Identity: a glowing crimson "recording" dot before the wordmark; a soft
  accent aurora + fine SVG film-grain fixed behind content (negative z, so
  it never sits under text).
- Depth & motion: translucent blurred masthead with a hairline accent
  underglow; cinematic card hover (lift + accent ring + thumbnail zoom +
  shadow); one-time staggered shell reveal; themed focus rings; thin themed
  scrollbars; restrained button hover (no full crimson flood). Honours
  prefers-reduced-motion.

Verified: JS still passes node --check, release builds clean, and a headless
chromium screenshot of the live 8081 server confirms it renders (fonts +
atmosphere + masthead) without breaking the existing layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:34:57 -07:00
Luna
207013e957 Windows: shippable cross-compiled .zip + console fix + release CI
The tree already links a working x86_64-pc-windows-gnu exe; turn that into
a shipped artifact:

- scripts/package.sh gains a `win` target (and `all` picks it up when the
  mingw toolchain is present) that cross-compiles and zips the exe +
  LICENSE + a README listing the yt-dlp/ffmpeg/mpv PATH deps.
- Forgejo release workflow installs mingw-w64 + zip + the rust target, so
  a tag push produces yt-offline-<ver>-x86_64-windows.zip from the same
  Linux container — no Windows runner needed.
- main.rs: attach_windows_console() (cfg(windows), windows-sys
  AttachConsole) reattaches a release build to the launching terminal so
  --web/CLI output is visible, while a double-click stays windowless
  (release sets windows_subsystem = "windows").
- ROADMAP 3.1 updated: Windows ships; macOS deferred (needs a Mac runner).

Native Linux build + 128 unit / 11 integration tests still green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 02:31:38 -07:00
Luna
8b2578783a Library sorting: download-date sort + grouped sort options (both UIs)
Add "download date" sorting (file mtime, distinct from upload date) plus a
broader set of options. Web select is now grouped via <optgroup>; desktop
gains SortMode::DownloadDesc/DownloadAsc/ChannelAsc with matching arms and
toolbar entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:15:27 -07:00
Luna
015d03751c Library scan: leave one core free at startup
The startup channel scan used available_parallelism() (every core), which
could briefly peg the whole box on a cold/large library. Cap it at cores-1
so the machine stays responsive; the scan is disk-I/O-bound and runs as a
background task, so one fewer thread costs little. Mirrors the headroom the
fingerprint pass and thumbnail pool already leave.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 18:58:56 -07:00
Luna
5ffdb179f7 Download modal: diff-aware repaint + Retry-all
- Patch progress/speed in place on each tick instead of rebuilding the whole
  modal body, so open per-job logs no longer collapse and the bar doesn't
  flicker while downloading. Full rebuild only on a structural change
  (job added/removed, state or retryability changed, queue length changed).
- "↻ Retry all failed" header button that re-issues every retryable failed
  job (re-snapshots between retries since indices shift).

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