# Quality gates — search via creatable nodes Checklist for the `implement` stage. Automatic tests live in `crabidy-core/src/lib.rs`, `tidaldy/src/lib.rs`, and `cbd-tui/src/app/{bindings,mod}.rs` test modules; run with `devenv shell -- cargo test --workspace` (network-dependent tidaldy tests stay `#[ignore]`). ## Contract & wire - [x] Proto changes are additive only: existing field numbers untouched; `is_creatable` fields 7 (`LibraryNode`) / 4 (`LibraryNodeChild`); `CreateLibraryNode` rpc present. Old clients keep working against the new server. - [x] `rpc.rs` (server) maps errors per the documented contract: `NotSupported` → `failed_precondition`, `InvalidInput` → `invalid_argument`, everything else → `internal`. No `color-eyre` report or debug formatting of internals leaks into `Status` messages. - [x] `ProviderOrchestrator::create_lib_node` routes `/tidal`-prefixed parents to the Tidal client and answers anything else (including the synthetic root `/`) with `NotSupported` — same prefix discipline as `get_lib_node`. - [x] The `CreateLibraryNode` command follows the existing bounded(1)-reply rendezvous pattern; no new unbounded channels anywhere in the feature. ## Provider semantics (tidaldy) - [x] `create_lib_node` trims the title; empty/whitespace → `InvalidInput`; parent other than `/tidal/search` → `NotSupported`; idempotent (an existing term returns its node, the term list holds no duplicates). - [x] `search_terms` stores **raw** terms; the path segment is `encode_segment(term)`; the node `title` shows the raw term. The lock is never held across an `await`. - [x] `get_lib_node(/tidal/search)` lists created terms as children (decoded titles) and sets `is_creatable: true` on the node; an unknown term path under search is implicitly (re)created, not an error — survives server restarts with stale client caches. - [x] The term node sets `is_queable: false` (see architecture/search.md: `resolve_tracks` would otherwise sweep every artist/album result into the queue). Its `tracks` are the track results with paths `/tidal/search//`; artist/album results are children with **canonical** `/tidal/artists/...` paths. - [x] `search_tracks/artists/albums` fetch a single page with `limit=SEARCH_RESULT_LIMIT` — they must NOT use `make_paginated_request`'s fetch-everything loop. - [x] `get_urls_for_track` and `get_metadata_for_track` work for `SearchTrack` paths (via `track_id_from_path`). - [x] The response models were verified against the live API (explorer request) before the typed methods were finalized; deviations live in `tidaldy::models`, not in ad-hoc serde attributes inline. ## No panics on user input (hard rule) - [x] All `todo!()` stubs from api-design are gone (grep the workspace). - [x] `encode_segment`/`decode_segment` are total: any client-supplied segment decodes without panicking (lossy, not `unwrap`). - [x] Search/create failures (network, auth, malformed) surface as `ProviderError`/`Status`, never a panic; the TUI's create path handles an error reply without crashing the orchestrate task. ## TUI behavior - [x] While `app.input.is_some()`, the event loop routes keys exclusively to `handle_input_key` — the bindings table (including `q`/quit and `?`) is unreachable; `%` while the overlay is already open just inserts the character. - [x] `Library::update` applies nodes with no children and no tracks when `is_creatable` (empty search node is enterable); genuinely empty non-creatable nodes keep the existing skip behavior. - [x] Creatable children are visibly marked in the library list and the pane title hints at `%` when the open node is creatable. - [x] The input overlay renders inside the library pane (bottom line) with the typed buffer visible; it disappears on cancel/submit. - [x] `RpcClient::create_library_node` evicts the parent's cache entry and caches the created node (stale `/tidal/search` listings would otherwise hide new terms). - [x] On successful create the library navigates into the returned node (`ReplaceLibraryNode`); on failure the library stays where it was. ## Code quality - [x] Public items added in all five crates have doc comments matching final behavior; the `%` binding appears in the help modal automatically. - [x] New dependency: only `percent-encoding` (workspace-managed version); nothing else added. - [x] `devenv shell -- cargo fmt --check`, `cargo clippy --workspace` (no new warnings), `cargo test --workspace` all pass.