crabidy/plan/search.md

7.1 KiB

Plan — search via creatable nodes

Ordered tasks for the implement stage. Inputs: architecture/search.md, stubs across all five crates, gates in quality/search.md. Tests: devenv shell -- cargo test -p crabidy-core -p tidaldy -p cbd-tui (11 failing at plan time = the target). Note: use a session-local CARGO_TARGET_DIR if target/ contains root-owned artifacts from the repo owner's builds.

1. Path segment encoding (crabidy-core)

  • Add the percent-encoding crate (workspace dependency; check the nixpkgs/devenv side is unaffected — pure Rust). Implement encode_segment (encode /, %, whitespace, controls and everything non-alphanumeric-unreserved via an AsciiSet) and decode_segment (lossy UTF-8 decode). Verify: the three crabidy-core tests pass (encode_segment_round_trips_arbitrary_text, encoded_segments_are_path_safe, decode_segment_is_lossy_not_panicky); gates "encode/decode are total", "only percent-encoding added".

2. Verify the search payload shape (tidaldy, live API)

  • Before typing the models: run the existing explorer helper (Client::search) once against the live API (needs the local tidal config; if unavailable, consult the response shapes used by other tdl clients and mark the gate as verified-by-proxy). Confirm whether search/tracks|artists|albums return the same Page<T> item shapes as the library endpoints; put any deviation into tidaldy::models. Verify: gate "models verified against the live API".

3. Typed search requests (tidaldy)

  • Implement search_tracks/search_artists/search_albums: single make_request with query, limit=SEARCH_RESULT_LIMIT, offset=0, decoding a Page<T>; no pagination loop. Verify: gate "first page only"; unit-testable only against live API (leave network test #[ignore] like the existing one).

4. Search subtree in the provider (tidaldy)

  • is_track_path: include TidalPath::SearchTrack; track_id_from_path already falls out (its match is on the parsed variant — extend it). Verify: search_track_paths_are_track_paths, track_id_is_extracted_from_search_track_paths pass.
  • get_lib_node Search arm: children = one entry per stored term (path join_path(path, encode_segment(term)), title = raw term, is_queable: false), node is_creatable: true. Do not require user_id for search paths (move the get_user_id gate into the arms that need it — search must work even if the user id is missing). Verify: gate "search node lists terms"; ignored network test extended by hand.
  • get_lib_node SearchTerm arm: decode the term; implicitly register unknown terms (stale-client recovery); fetch the three categories concurrently (tokio::join!); node = tracks from search_tracks (to_proto(path)), children = artists (Artist: <name>/tidal/artists/<id>, queueable) then albums (Album: <title>/tidal/artists/<artist-id>/<album-id>, queueable), is_queable: false, is_creatable: false. Verify: gates "term node" + "canonical children"; behavior exercised end-to-end in task 9.
  • create_lib_node: trim title → InvalidInput if empty; parent must parse to TidalPath::Search else NotSupported; store raw term idempotently (no duplicates, lock not held across await); return self.get_lib_node(term_path). Verify: gate "create semantics"; add non-network unit tests: create with bad parent / empty title on an offline client returns the right errors (no API call happens before validation).

5. Server plumbing (crabidy-server)

  • ProviderOrchestrator::create_lib_node: /tidal-prefixed parent → tidal_client.create_lib_node, anything else NotSupported (warn like the other routes). Verify: gate "orchestrator routing".
  • rpc.rs create_library_node: bounded(1) rendezvous with ProviderCommand::CreateLibraryNode; map errors NotSupportedfailed_precondition, InvalidInputinvalid_argument, rest → internal. Verify: gate "error mapping"; cargo check.

6. TUI input overlay (cbd-tui)

  • App::dispatch(LibraryCreateNode): open InputState { parent_path: library.path(), buffer: "" } only when library.is_creatable(). Verify: create_node_only_opens_input_on_creatable_nodes.
  • App::handle_input_key: Esc cancels; Enter trims + sends MessageFromUi::CreateNode (empty → just close); Backspace pops; KeyCode::Char(c) appends regardless of SHIFT; all else ignored. Verify: input_appends_and_backspace_pops, esc_cancels_without_sending, enter_submits_trimmed_title_and_closes, enter_on_empty_input_closes_without_sending.
  • Event loop (main.rs run_ui): when app.input.is_some(), route the key to app.handle_input_key(key) and skip bindings::lookup entirely. Verify: gate "input mode bypasses bindings" (read the loop; also confirm q cannot quit while typing).

7. TUI library rendering (cbd-tui)

  • Library::update: apply empty nodes when node.is_creatable (keep the skip for empty non-creatable nodes). Verify: empty_creatable_nodes_are_enterable.
  • Mark creatable children in the list (suffix marker, COLOR_SECONDARY) — requires keeping is_creatable on UiItem; hint in the pane title when the open node is creatable (e.g. search — % to add). Verify: gate "creatable marked in UI" (read render code).
  • Render the input overlay: bottom line of the library pane showing new node: <buffer>▏ while input.is_some(). Verify: gate "input overlay renders"; add a TestBackend test asserting the buffer text appears while open (mirror the help-modal test helpers).

8. TUI ↔ server wiring (cbd-tui)

  • RpcClient::create_library_node: send request, evict library_node_cache entry for parent_path, insert the returned node, return it. Verify: gate "cache eviction" (read; the cache is private — no test seam without refactoring, keep it a gate).
  • main.rs poll: CreateNode arm calls the client method; on success ReplaceLibraryNode(node); on failure log and leave the UI unchanged (no panic — replace the stub's todo!). Verify: gate "create failures surface as errors, never a panic".

9. End-to-end + gates sweep

  • Run the real stack (crabidy-server + cbd-tui, needs tidal login): enter /tidal/search, %, type a term, Enter; results appear; queue a track result; dive into an artist result. If no login is available, exercise create/list/error paths against the offline client instead and note it in the summary. Verify: architecture flow diagram matches reality.
  • Sweep: no todo!() left (workspace grep), fmt + clippy + tests green, every quality/search.md box checked, docs updated where behavior shifted. Append the outcome + deviations to plan/summary.md.