7.1 KiB
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-encodingcrate (workspace dependency; check the nixpkgs/devenv side is unaffected — pure Rust). Implementencode_segment(encode/,%, whitespace, controls and everything non-alphanumeric-unreserved via anAsciiSet) anddecode_segment(lossy UTF-8 decode). Verify: the threecrabidy-coretests 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 whethersearch/tracks|artists|albumsreturn the samePage<T>item shapes as the library endpoints; put any deviation intotidaldy::models. Verify: gate "models verified against the live API".
3. Typed search requests (tidaldy)
- Implement
search_tracks/search_artists/search_albums: singlemake_requestwithquery,limit=SEARCH_RESULT_LIMIT,offset=0, decoding aPage<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: includeTidalPath::SearchTrack;track_id_from_pathalready 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_pathspass.get_lib_nodeSearcharm: children = one entry per stored term (pathjoin_path(path, encode_segment(term)), title = raw term,is_queable: false), nodeis_creatable: true. Do not requireuser_idfor search paths (move theget_user_idgate 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_nodeSearchTermarm: decode the term; implicitly register unknown terms (stale-client recovery); fetch the three categories concurrently (tokio::join!); node = tracks fromsearch_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 →InvalidInputif empty; parent must parse toTidalPath::SearchelseNotSupported; store raw term idempotently (no duplicates, lock not held across await); returnself.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 elseNotSupported(warn like the other routes). Verify: gate "orchestrator routing".rpc.rs create_library_node: bounded(1) rendezvous withProviderCommand::CreateLibraryNode; map errorsNotSupported→failed_precondition,InvalidInput→invalid_argument, rest →internal. Verify: gate "error mapping";cargo check.
6. TUI input overlay (cbd-tui)
App::dispatch(LibraryCreateNode): openInputState { parent_path: library.path(), buffer: "" }only whenlibrary.is_creatable(). Verify:create_node_only_opens_input_on_creatable_nodes.App::handle_input_key: Esc cancels; Enter trims + sendsMessageFromUi::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): whenapp.input.is_some(), route the key toapp.handle_input_key(key)and skipbindings::lookupentirely. Verify: gate "input mode bypasses bindings" (read the loop; also confirmqcannot quit while typing).
7. TUI library rendering (cbd-tui)
Library::update: apply empty nodes whennode.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 keepingis_creatableonUiItem; 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>▏whileinput.is_some(). Verify: gate "input overlay renders"; add aTestBackendtest 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, evictlibrary_node_cacheentry forparent_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:CreateNodearm calls the client method; on successReplaceLibraryNode(node); on failure log and leave the UI unchanged (no panic — replace the stub'stodo!). 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, everyquality/search.mdbox checked, docs updated where behavior shifted. Append the outcome + deviations toplan/summary.md.