# Implementation summaries ## node-editing (2026-07-20) Built per `plan/node-editing.md`: search-term nodes (created via `%`) are now modifiable — `e` opens the input overlay prefilled with the current title and renames (re-running the search; merge on title collision), `d` deletes without confirmation (documented decision, architecture/node-editing.md D4). Capabilities travel as `LibraryNodeChild.is_editable`/`is_deletable` (fields 5/6, child-only — no consumer for node-level copies), surfaced as a `[ed]` marker; two new rpcs `RenameLibraryNode` (returns the renamed node, TUI navigates into it) and `DeleteLibraryNode` (returns the refreshed parent). All 59 workspace tests green; every gate in `quality/node-editing.md` checked. Verified against the live Tidal API with a temporary ignored probe (removed after passing): create `beatles` → rename to `rolling stones` (in-place, 20 tracks / 40 children) → a track queued under the old term still resolved a stream URL → delete emptied the listing. The whole feature ran autonomously per standing instruction; decisions are recorded in `architecture/node-editing.md` (options + rationale per topic). ### Deviations from plan / architecture (node-editing) - **Self-rename bug caught by the gate tests**: the first `rename_search_term` implementation deleted a term renamed to itself (the merge branch removed the "old" slot). Fixed with an explicit `old != new` guard; the architecture text ("merge on collision") now implicitly means *distinct* titles. - **`pane_bindings_only_match_their_own_pane` (help-modal suite) updated**: it asserted plain `d` is unbound in the library — now it is `LibraryDeleteNode` by design; the test's queue-only example key moved to `c`. - **`delete_library_node` also maps `InvalidInput` → `invalid_argument`** although no provider raises it for delete today — keeps the error contract uniform across the three node-mutation rpcs. - **End-to-end check ran at the provider layer** (as with search): no interactive terminal/audio device in this environment; the gRPC handler and TUI layers above it are covered by unit tests and review. - **Environment note**: builds/tests again ran with a session-local `CARGO_TARGET_DIR` (owner-built artifacts in `target/`); no repo change. ## search (2026-07-20) Built per `plan/search.md`: `%` inside `/tidal/search` opens a one-line input; the term becomes a persistent (per-process) tree node holding Tidal search results — 20 tracks queueable in place, plus artist/album results as canonical `/tidal/artists/...` children. Creatable nodes carry an `is_creatable` flag end-to-end (proto → provider → TUI marker `[%]` + pane hint). All 48 workspace tests green; every gate in `quality/search.md` checked. Verified against the live Tidal API: payload shapes match the existing models (probe kept as the ignored `probe_search_shapes` test), and a full create→list→resolve-URL round trip succeeded (`beatles` → 20 tracks / 40 children, idempotent, playable stream URL from a search-track path). The whole feature ran autonomously on user instruction; decisions were taken without mid-stage confirmation and recorded in `architecture/search.md` (options + decision per topic). ### Deviations from plan / architecture (search) - **`Library::update` now concatenates tracks and children** (tracks first). The old code showed tracks *instead of* children, which would have hidden the artist/album results on term nodes — architecture assumed both would render. Existing nodes are unaffected (they only ever carry one kind). - **Search categories degrade independently**: a failing category logs and contributes nothing; only all three failing is a `FetchError`. The plan did not specify partial-failure behavior. - **`get_lib_node` no longer requires a user id up front** — the gate moved into the favorites arms (planned), which also means `create_lib_node` validation works fully offline (used by the new unit tests). - **End-to-end check ran at the provider layer** (temporary ignored test, removed after passing) rather than driving the full TUI + server — no interactive terminal/audio device in this environment. The gRPC handler and TUI layers above it are covered by unit tests and review. - **Environment note**: `target/` contains owner-built artifacts not writable by this agent's user; builds/tests ran with a session-local `CARGO_TARGET_DIR`. No repo change involved. ## help-modal (2026-07-20) Built per `plan/help-modal.md`: `app/bindings.rs` (declarative `BINDINGS` table + `lookup` + `key_label`), `app/help.rs` (overlay), the `App::dispatch`/`DispatchResult` seam, and the rewired event loop in `main.rs`. All 20 tests pass; every gate in `quality/help-modal.md` checked. ### Deviations from plan / architecture (help-modal) - **Two-column modal layout.** The architecture assumed a single-column list; the full table is ~50 rows and would not fit even a 100×40 frame. The modal renders Global in the left column and Library + Queue stacked in the right column, with the close keys as a footer line (`Close help: ?, Esc, q`) derived from the `Scope::Help` bindings instead of a fourth listed group. The open question "scroll vs truncate" stays resolved as truncate — but after the column split the content fits ~34×94, so truncation only kicks in on genuinely small terminals. - **`Scope` derives `Hash`** (not in the stub) so the chord-uniqueness test can use a `HashSet`. - **`QueueInsertHere` description reworded** to "Insert library selection after this track": `crabidy-server`'s `insert_tracks` splices at `position + 1`. Same check confirmed the planned "Queue selection after current track" wording for `LibraryQueueNext`. - **`main.rs`** passes `tx` to `App::new` without the now-unneeded clone; the `KeyCode`/`KeyModifiers`/`UiFocus`/`StatefulList` imports moved out with the old match.