crabidy/cbd-web
Test User 0d08c765a1 seek: put seek and track-skip on two keys, and make the web gauge clickable
Two physical keys now carry all four moves in both clients: `,`/`.` seek 15
seconds, and their shifted forms `<`/`>` skip a whole track. Self-teaching
(same key, shift = bigger jump), and `<`/`>` are the marks engraved on them.

The reason it is these keys and not control chords: they are plain printable
characters, so nothing a browser reserves can swallow them. Ctrl-n — the
long-standing next-track chord — cannot be claimed in a browser at all,
because Chrome and Firefox handle it as "new window" above the page where
preventDefault cannot reach (unlike Ctrl-f, Ctrl-p or Ctrl-b, which the
keydown handler does claim). So the web client had no working next-track key.
Ctrl-n/Ctrl-p stay bound as the terminal's primary chords; the web help
documents the pair that always works.

Click-to-seek on the web progress bar comes with it, and needed no new rpc:
the click maps the pointer's x within the gauge to a fraction of the duration
and sends target - position. Relative is right here even though the gesture
is absolute — the position it subtracts is the one drawn on the bar the user
just aimed at, at most one 250 ms tick old, far under one pixel of the bar.
That staleness is only fatal for a repeated key, which is why keys still send
a fixed step and let the server accumulate.

The arithmetic lives in state.rs as a pure function, so it is tested on the
native target rather than only in a browser: a click behind the playhead
seeks back, the edges are exactly the track's ends, a fraction outside [0, 1]
is clamped rather than extrapolated, and a duration of 0 (or a non-finite
fraction, meaning a zero-width element) declines instead of seeking somewhere
arbitrary. Geometry comes from current_target, since the click may land on
the fill rather than the track.

Also enables the web-sys DomRect feature, without which
Element::get_bounding_client_rect does not exist — caught only by the wasm
build, since cbd-web's `mod app` is cfg'd to wasm32 and native clippy never
sees it.

Verified: 119 cbd-tui, 24 cbd-web (3 new), workspace clippy clean under
-D warnings, fmt clean, wasm bundle and book build. Not exercised: an actual
click in a browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:29:30 +02:00
..
src seek: put seek and track-skip on two keys, and make the web gauge clickable 2026-07-26 14:29:30 +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: bring the book and every README up to date 2026-07-25 11:04:25 +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 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

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, 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, volume, shuffle/repeat, and a ? help overlay listing the keys. Keys mirror the TUI; every key also has a clickable control. 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).