crabidy/cbd-web
Test User bb084dd62b queue, tui: dedup by title, and keep foreign writes off the screen
Four findings from a day's cbd.log and a 121-entry queue.

**Dedup by title.** The provider-item identity removed nothing from that
queue — 121 entries, 121 distinct ids — while three "Sink Into The Hips"
sat in it: two remixes and the album version, 171/189/228s. Those are
different recordings and merging them by default would silently discard a
version the user chose, so the default stands and `DedupQueue` gains a
`by_title` flag: lowercased (artist, title), survivor = the playing entry
else the *longest* take. Own key everywhere (`U`, `queue dedup --titles`)
because it throws recordings away. Duration-tolerant matching was the
third option and is not worth it: every same-title group in that queue
differed by tens of seconds, so a safe tolerance caught nothing.

**stderr no longer points at the terminal.** tracing goes to a file
because the TUI owns the screen, but fd 2 did not — and in the bundled
`cbd` the ALSA C library shares the process, so its "underrun occurred"
printed straight onto the interface, scrolled the terminal a line and
left the layout looking shifted (the queue appearing to bleed into the
now-playing pane; ratatui repaints only changed cells, so it persisted).
The message was lost too. One dup2 before the alternate screen sends fd 2
to `<log dir>/cbd.stderr.log`, so those diagnostics are kept instead.

**The spectrum flapped ~1/s during playback**, 3664 times in one log.
tokio's default MissedTickBehavior::Burst keeps the absolute schedule, so
once the per-tick FFT lateness reaches a whole period two ticks fire back
to back and the second necessarily sees no new frames — read as silence,
which zeroed the bars. Now `Delay`, plus a FlowDetector that wants two
consecutive empty ticks before declaring idle.

**The one ERROR in the log was a shutdown race**, mislabelled: "request
to server failed: sending on a closed channel" was the orchestrator's
send to the UI channel after the UI thread exited. It now reports the UI
closing at info and stops the loop instead of spinning on a stream nobody
reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 22:42:04 +02:00
..
src queue, tui: dedup by title, and keep foreign writes off the screen 2026-07-29 22:42:04 +02:00
.gitignore Add a Leptos web client served by the server 2026-07-21 22:43:06 +02:00
Cargo.toml seek: put seek and track-skip on two keys, and make the web gauge clickable 2026-07-26 14:29:30 +02:00
README.md docs: audit the book and the READMEs, and give Seek its role 2026-07-27 21:19:59 +02:00
Trunk.toml Add a Leptos web client served by the server 2026-07-21 22:43:06 +02:00
index.html Add a Leptos web client served by the server 2026-07-21 22:43:06 +02:00
style.css queue: de-duplicate and sort the queue, server-side 2026-07-29 21:50:17 +02:00

README.md

cbd-web — the browser client

A Leptos client-side WASM app with the same functionality as cbd-tui, served by crabidy-server itself.

How it works

  • Transport: gRPC-web (tonic-web-wasm-client) over the same generated client and proto types the TUI uses (crabidy-core). No second API surface — feature parity is structural. The server wraps its existing gRPC service in tonic-web, so the browser and the TUI hit identical /crabidy.v1.CrabidyService/… paths, and the same role auth layer gates both.
  • Serving: the built bundle (cbd-web/dist) is embedded into crabidy-server at compile time behind the default-on web-ui feature and served as the fallback route on port 50051. gRPC and static assets share one origin, so there is no CORS story.
  • Local-first: pure client-side rendering, every asset in the bundle (no CDN, no external fonts), library listings cached in memory like the TUI, credentials and theme in localStorage, and the update stream reconnects with backoff when the server disappears. There is no CRDT layer — this is a remote control for one live server state, not an offline-editing app (a deliberate departure from the web_client_example_workspace template that informed the toolchain).

Functionality

Everything the TUI does: browse the library (j/k/h/l, click), marks and visual mode (s, v/V) in both panes, the one-slot register (y yanks, d/c/C fill it as they remove, p/P paste), create/rename/ delete nodes (%/e/d), bookmark and capture (w/W, with live progress lines and skipped-track marking), the full queue and playback controls, seeking (,/. for 15 seconds, </> for a whole track, or a click on the progress bar), volume, shuffle/repeat, and a ? help overlay listing the keys. Keys mirror the TUI; every key also has a clickable control. The / live filter is the one thing that is TUI-only so far.

Two layout details earn their own note:

  • library/queue tabs in the top bar switch panes and show which one the keys go to — what Tab does, reachable by thumb.
  • Below 700px the panes cannot sit side by side, so only the focused pane is rendered. It is not collapsed to a strip: a strip's truncated rows still take taps, which sent them to the wrong pane.

A light/dark theme follows the OS and can be toggled (persisted). The accent color is the crab orange-red.

When the server requires credentials, a login form collects the role (owner / queue-owner / queue-appender) and password; they are stored in localStorage and sent as the gRPC-web authorization header on every request.

Building

The WASM toolchain (trunk, wasm-bindgen, the wasm32-unknown-unknown target) is provided by devenv. From the repo root:

devenv shell -- build-web        # release bundle → cbd-web/dist
cargo build -p crabidy-server    # embeds cbd-web/dist

build-web clears RUSTFLAGS first: the native toolchain sets the mold linker, which rust-lld (the wasm linker) cannot parse.

Building crabidy-server without a cbd-web/dist present is fine — it embeds a placeholder page telling you to run build-web. To drop the web client (and the tonic-web layer) entirely, build the server without its web-ui cargo feature — e.g. --no-default-features --features all-providers,opus,spectrum; see docs/src/build-features.md.

Dev loop

Run a server, then a live-reloading trunk server that proxies gRPC-web to it:

cargo run -p crabidy-server      # or `cbd`
devenv shell -- serve-web        # trunk serve on http://127.0.0.1:8080

Trunk.toml proxies /crabidy.v1.CrabidyService to 127.0.0.1:50051, so the app behaves as if served from the server.

Tests

The DOM-free logic (pane/selection state machines, the keymap, capture progress formatting) lives in src/state.rs and src/keymap.rs and is unit-tested on the native target:

cargo test -p cbd-web

Components in src/app.rs stay thin over that logic. The server-side serving and the gRPC-web + auth routing are tested in crabidy-server (src/web.rs, tests/web_server.rs).