docs: audit the book and the READMEs, and give Seek its role
Sweep of docs/ and every README against the code, after several features landed since the last one. Mechanical checks, which find what reading does not: SUMMARY covers every page and no more; every relative link resolves (docs/src/providers.md pointed a directory above the book, twice); every #anchor matches a real heading; every provider crate's Settings field is documented in both its README and its book page; the feature table matches Cargo.toml, which it did not -- `rss` was missing and "seven provider features" is now eight. Prose that predated recent work: the root README's client-config sample knew only `spectrum` and still claimed every option has a flag; its spectrum and web-client sections predated the colors, segments, shadows, pane tabs, register and seek; cbd-web/README.md likewise; intro.md's provider tree was missing /rss; clients.md omitted volume and mute from what the update stream carries; rssdy/README.md did not mention that only audio enclosures become episodes. The audit also found a defect the docs were right about: `Seek` was never added to `minimum_role`, so it fell through to the owner-only default while auth.md and architecture/roles-auth.md both promise a queue-owner may control playback. With auth configured a queue-owner could play, skip and change the volume, but got PermissionDenied on `,`/`.`. Seek now sits with the other playback verbs. The test meant to prevent that -- "a new RPC must be added to exactly one list" -- compared the role lists against a hardcoded 24, so a 25th method kept the suite green. It now reads the method names out of crabidy.proto and compares sets: a count copied out of a file is not a check against it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d8bd06219c
commit
87b98193d8
50
README.md
50
README.md
|
|
@ -137,13 +137,26 @@ password = ""
|
|||
|
||||
# Show the frequency-spectrum bars under the track progress. Default true.
|
||||
spectrum = true
|
||||
|
||||
# How those bars look. Defaults shown; a color is a "#rrggbb" triple, a
|
||||
# color name, or a 0-255 palette index, and an unusable value warns and
|
||||
# falls back. See docs/src/clients/tui.md for what each one does.
|
||||
spectrum_color = "#bf616a" # the bars (the queue's playing-track red)
|
||||
spectrum_gradient = true # shade them by height
|
||||
spectrum_top_color = "#b48ead" # what the shading reaches at the top
|
||||
spectrum_peak_color = "#81a1c1" # peak-hold shadows; "none" draws none
|
||||
spectrum_peak_fill = true # false: a thin rule at the peak instead
|
||||
spectrum_peak_fall = 4.0 # seconds for a full-scale shadow to fall
|
||||
spectrum_bar_width = 1 # least bar width, in cells
|
||||
spectrum_bar_gap = 1 # seam dividing two bars, in cells
|
||||
spectrum_row_gap = 1 # line dividing two rows, in eighths
|
||||
```
|
||||
|
||||
Every option is also available as a command-line flag before the
|
||||
subcommand (`cbd-tui --address ... --user owner`, `cbd --spectrum
|
||||
false`); a flag overrides the file value. To write the credentials into
|
||||
the config once, use the `auth` subcommand (see below) instead of
|
||||
editing the file by hand:
|
||||
Every option except the `spectrum_*` appearance settings is also available
|
||||
as a command-line flag before the subcommand (`cbd-tui --address ... --user
|
||||
owner`, `cbd --spectrum false`); a flag overrides the file value. To write
|
||||
the credentials into the config once, use the `auth` subcommand (see below)
|
||||
instead of editing the file by hand:
|
||||
|
||||
```sh
|
||||
cbd-tui auth owner 'my-password' # sets user + password
|
||||
|
|
@ -361,15 +374,32 @@ Toggle it at runtime with `f`, or set the startup default with
|
|||
`spectrum = false` in the client config. Servers built without the
|
||||
`spectrum` feature simply never send bars.
|
||||
|
||||
The bars are shaded by height — red at the floor reaching purple at the top
|
||||
— and divided into segments by a dim seam between bars and a thin line
|
||||
between value rows. Each bar trails a blue peak-hold shadow marking where it
|
||||
lately reached, falling away over a few seconds. Every part of that is
|
||||
configurable, including turning it all off; see
|
||||
[docs/src/clients/tui.md](docs/src/clients/tui.md).
|
||||
|
||||
`,` and `.` seek 15 seconds inside the playing track; `<` and `>` (or
|
||||
`Ctrl-p`/`Ctrl-n`) skip a whole track. `K`/`J` change the volume and `m`
|
||||
mutes; the now-playing pane shows the server's level, which tops out at
|
||||
110%.
|
||||
|
||||
## Web client
|
||||
|
||||
`crabidy-server` serves a browser client with the same functionality as
|
||||
the TUI at its own address (`http://<server>:50051/`) — same navigation,
|
||||
same keys (`j`/`k`/`h`/`l`, `%`, `e`, `d`, `w`, `W`, queue and playback
|
||||
controls, `?` for help), plus clickable equivalents and a light/dark
|
||||
theme toggle. It talks gRPC-web to the same service the TUI uses, so it
|
||||
honors the same `[auth]` roles (it shows a login form when the server
|
||||
requires credentials).
|
||||
same keys (`j`/`k`/`h`/`l`, `%`, `e`, `d`, `w`, `W`, marks and visual mode,
|
||||
the `y`/`d`/`p` register, `,`/`.` to seek, queue and playback controls, `?`
|
||||
for help), plus clickable equivalents for all of it: the progress bar seeks
|
||||
where you click, and `library`/`queue` tabs in the top bar switch panes
|
||||
without a keyboard. On a phone the two panes cannot sit side by side, so
|
||||
only the focused one is shown and those tabs are the way between them.
|
||||
There is a light/dark theme toggle. It talks gRPC-web to the same service
|
||||
the TUI uses, so it honors the same `[auth]` roles (it shows a login form
|
||||
when the server requires credentials). The `/` live filter is TUI-only for
|
||||
now.
|
||||
|
||||
It is compiled to a WASM bundle and embedded into the server binary,
|
||||
behind the default-on `web-ui` cargo feature. A plain `cargo build`
|
||||
|
|
|
|||
|
|
@ -25,12 +25,25 @@ functionality as `cbd-tui`, served by `crabidy-server` itself.
|
|||
|
||||
## 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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ pub fn minimum_role(grpc_path: &str) -> Role {
|
|||
// Every other queue and playback verb.
|
||||
"Queue" | "Replace" | "Remove" | "Insert" | "ClearQueue" | "SetCurrent"
|
||||
| "ToggleShuffle" | "ToggleRepeat" | "TogglePlay" | "Stop" | "ChangeVolume"
|
||||
| "ToggleMute" | "Next" | "Prev" | "RestartTrack" => Role::QueueOwner,
|
||||
| "ToggleMute" | "Next" | "Prev" | "RestartTrack" | "Seek" => Role::QueueOwner,
|
||||
// Library writes (CaptureLibraryNode, SaveQueue,
|
||||
// RenameLibraryNode, DeleteLibraryNode) and anything unmapped.
|
||||
_ => Role::Owner,
|
||||
|
|
@ -84,7 +84,7 @@ pub fn minimum_role(grpc_path: &str) -> Role {
|
|||
|
||||
/// Hashes a password into the PHC string `crabidy-server.toml` expects
|
||||
/// (argon2id, default parameters, fresh random salt). Backs the
|
||||
/// `crabidy-server hash-password` helper.
|
||||
/// `crabidy-server guard` subcommand.
|
||||
pub fn hash_password(password: &str) -> Result<String, String> {
|
||||
use argon2::password_hash::{rand_core::OsRng, SaltString};
|
||||
use argon2::PasswordHasher;
|
||||
|
|
@ -355,6 +355,7 @@ mod tests {
|
|||
"Next",
|
||||
"Prev",
|
||||
"RestartTrack",
|
||||
"Seek",
|
||||
];
|
||||
let owner = [
|
||||
"CaptureLibraryNode",
|
||||
|
|
@ -362,10 +363,28 @@ mod tests {
|
|||
"RenameLibraryNode",
|
||||
"DeleteLibraryNode",
|
||||
];
|
||||
// The full service, from crabidy.proto — 24 methods. A new RPC
|
||||
// must be added to exactly one list (and the layer keeps it
|
||||
// owner-only until then).
|
||||
assert_eq!(appender.len() + queue_owner.len() + owner.len(), 24);
|
||||
// Every RPC must appear in exactly one list above. Taken from the
|
||||
// proto itself rather than a count copied out of it: a hardcoded
|
||||
// total let `Seek` be added to the service and land on the
|
||||
// owner-only fallthrough with the suite still green, which is the
|
||||
// one thing this test exists to prevent.
|
||||
let proto = include_str!("../../crabidy-core/crabidy/v1/crabidy.proto");
|
||||
let service: std::collections::BTreeSet<&str> = proto
|
||||
.lines()
|
||||
.filter_map(|line| line.trim().strip_prefix("rpc "))
|
||||
.filter_map(|rest| rest.split('(').next())
|
||||
.map(str::trim)
|
||||
.collect();
|
||||
let mapped: std::collections::BTreeSet<&str> = appender
|
||||
.iter()
|
||||
.chain(queue_owner.iter())
|
||||
.chain(owner.iter())
|
||||
.copied()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
mapped, service,
|
||||
"every RPC of the service needs a role (left: mapped, right: the proto)"
|
||||
);
|
||||
for method in appender {
|
||||
let path = format!("{SERVICE_PREFIX}{method}");
|
||||
assert_eq!(minimum_role(&path), Role::QueueAppender, "{method}");
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ below it (owner ⊃ queue-owner ⊃ queue-appender):
|
|||
- **owner** — the normal user: everything, including all library
|
||||
writes.
|
||||
- **queue-owner** — anything on the queue and playback (append, remove,
|
||||
reorder, clear, shuffle, repeat, play/stop, next/prev, volume, mute,
|
||||
…), but **no library writes**: no bookmarks (`w`), no captures (`W`),
|
||||
reorder, clear, shuffle, repeat, play/stop, next/prev, seek, volume,
|
||||
mute), but **no library writes**: no bookmarks (`w`), no captures (`W`),
|
||||
no saving, renaming, or deleting.
|
||||
- **queue-appender** — may browse and search the library and **append**
|
||||
tracks to the queue; nothing else. (Searching is allowed because
|
||||
|
|
|
|||
|
|
@ -36,12 +36,13 @@ The same list goes into the server's startup log, so a support question
|
|||
| `abs` | the `/abs` audiobookshelf provider |
|
||||
| `soundcloud` | the `/soundcloud` provider |
|
||||
| `jamendo` | the `/jamendo` provider |
|
||||
| `rss` | the `/rss` podcast-subscription provider |
|
||||
| `fs` | local files **and persistent state** — see below |
|
||||
| `opus` | Ogg-Opus decoding (`symphonia` + a bundled libopus C build) |
|
||||
| `spectrum` | the server-side FFT feeding clients' spectrum bars |
|
||||
| `web-ui` | the embedded web client (`tonic-web` + the wasm bundle) |
|
||||
|
||||
Plus two conveniences: `all-providers` enables the seven provider features
|
||||
Plus two conveniences: `all-providers` enables the eight provider features
|
||||
at once, and `cbd` (the bundle) mirrors every feature above and adds
|
||||
`notifications` for the TUI's desktop "now playing" popups (`notify-rust`,
|
||||
which on Linux pulls a D-Bus stack).
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ A client is anything that drives the server. Every client speaks the
|
|||
same gRPC service (see [Architecture](./architecture.md)): it sends
|
||||
**commands** (browse the library, change the queue, control playback)
|
||||
and subscribes to the **update stream** that pushes the current queue,
|
||||
play state, track position, capture progress, and the frequency
|
||||
spectrum as they change. Nothing is polled — each client redraws from
|
||||
play state, track position, output level and mute, capture progress, and
|
||||
the frequency spectrum as they change. Nothing is polled — each client redraws from
|
||||
the pushed updates, so several clients driving one server always agree
|
||||
on what is playing.
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ segment is a **provider** mounted as a subtree:
|
|||
├── fs a local music folder
|
||||
├── fyyd podcast search
|
||||
├── jamendo Creative-Commons music
|
||||
├── rss podcast subscriptions
|
||||
├── soundcloud SoundCloud search, likes, and playlists
|
||||
├── tidal Tidal streaming
|
||||
├── youtube YouTube search & playlists
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ independent things decide it:
|
|||
|
||||
1. **Was it built in?** Every provider sits behind a Cargo feature (all on by
|
||||
default). A binary built without one can never mount it — see [Tailored
|
||||
builds](../build-features.md), and `crabidy-server features` to see what a
|
||||
builds](./build-features.md), and `crabidy-server features` to see what a
|
||||
binary has.
|
||||
2. **Is it enabled?** The `providers` list in `crabidy-server.toml` turns
|
||||
compiled-in providers on and off without a rebuild — see
|
||||
[Configuration](../config.md#enabling-and-disabling-providers).
|
||||
[Configuration](./config.md#enabling-and-disabling-providers).
|
||||
3. **Did it initialize?** Only a client that came up successfully is mounted.
|
||||
|
||||
```admonish note
|
||||
|
|
|
|||
|
|
@ -1774,3 +1774,41 @@ would divide by zero and freeze every shadow on screen.
|
|||
Seven appearance keys is a lot of config surface, justified by these being
|
||||
pure appearance with no right answer: each one is a question that came back
|
||||
with a different answer than the default assumed.
|
||||
|
||||
## Documentation audit (2026-07-27)
|
||||
|
||||
A sweep of `docs/` and every README against the code, prompted by several
|
||||
features landing after the last one. Mechanical checks first — they find what
|
||||
reading cannot:
|
||||
|
||||
- every `docs/src/**.md` appears in `SUMMARY.md` and vice versa;
|
||||
- every relative link in the book and the READMEs resolves (two did not:
|
||||
`docs/src/providers.md` pointed at `../build-features.md` and `../config.md`,
|
||||
which from `src/providers.md` lands a directory above the book);
|
||||
- every `#anchor` in an internal link matches a heading that exists;
|
||||
- every provider crate's `Settings` field appears in its README and its book
|
||||
page (all did, except Tidal's endpoint and token fields, which its page
|
||||
deliberately delegates to `tidaldy/README.md`);
|
||||
- the `crabidy-server` feature table against `Cargo.toml` (`rss` was missing,
|
||||
and "seven provider features" had become eight).
|
||||
|
||||
Stale prose, all of it from work that landed after the previous sweep: the
|
||||
root README's client-config sample knew only `spectrum`, still claimed every
|
||||
option has a command-line flag, and described the spectrum before it had
|
||||
colors, segments or shadows; its web section predated the pane tabs, the
|
||||
register and seek; `cbd-web/README.md` predated the same; `docs/src/intro.md`'s
|
||||
provider tree was missing `/rss`; `docs/src/clients.md` listed what the update
|
||||
stream carries without volume and mute.
|
||||
|
||||
The audit also turned up a **defect the docs were right about**: `Seek` was
|
||||
never added to `minimum_role`, so it fell through to the owner-only default
|
||||
while `auth.md` and `architecture/roles-auth.md` both promise a queue-owner
|
||||
may control playback. With auth configured, a queue-owner could play, skip and
|
||||
change the volume but got `PermissionDenied` on `,`/`.`.
|
||||
|
||||
The test meant to prevent exactly that — "a new RPC must be added to exactly
|
||||
one list" — compared the three role lists against a hardcoded 24. `Seek` made
|
||||
the service 25 methods and the suite stayed green. It now reads the method
|
||||
names out of `crabidy.proto` with `include_str!` and compares sets, so the
|
||||
next RPC cannot be forgotten. A count copied out of a file is not a check
|
||||
against that file.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ A subscription is queueable and downloadable, so you can queue or `W`-capture
|
|||
a whole feed. Episodes stream directly from the enclosure URL — no sidecar,
|
||||
no helper binary.
|
||||
|
||||
Only **audio** enclosures become episodes: an item is taken when its
|
||||
enclosure type says audio, or when the type is missing or generic and the URL
|
||||
looks like audio (including extensionless `/feed/mp3`-style URLs). Video and
|
||||
image enclosures are skipped, so a show that publishes both plays its audio
|
||||
and a blog feed does not list its featured images as tracks. See
|
||||
`docs/src/providers/rss.md` for the exact rule.
|
||||
|
||||
**Nothing is cached.** Every visit to a subscription fetches the feed, so an
|
||||
episode published a minute ago is there. One memo exists purely so that
|
||||
listing a feed and then queueing its 40 episodes costs one fetch rather than
|
||||
|
|
|
|||
Loading…
Reference in New Issue