feat: finalize search UX and release hardening
This commit is contained in:
parent
c63b2917da
commit
34f9543a78
44 changed files with 4983 additions and 94 deletions
|
|
@ -0,0 +1,137 @@
|
|||
# Search Interactive TUI Design
|
||||
|
||||
## Summary
|
||||
|
||||
This change upgrades `aim search <QUERY>` from a plain text summary into an interactive terminal search flow when stdout and stdin are attached to a TTY. The interactive flow will use `ratatui` for a scrollable result browser, numeric multi-select inspired by `paru`, and an explicit confirmation step before install handoff.
|
||||
|
||||
The repository does not currently contain a checked-in config file, but the product already has a user-facing `config.toml` used for themes outside the repository tree. This design extends that existing config contract rather than introducing a second settings path.
|
||||
|
||||
## Goals
|
||||
|
||||
- Make `aim search <QUERY>` interactive by default on TTY.
|
||||
- Render one result per row with clear columns for provider, repository, and install query.
|
||||
- Support bottom-to-top list orientation by default, with config control.
|
||||
- Support keyboard paging and numeric multi-select in the result browser.
|
||||
- Show a confirmation step before install handoff unless disabled in config.
|
||||
- Keep the search domain provider-extensible and avoid coupling core search ranking to terminal UI code.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No `--json` output in this slice.
|
||||
- No non-interactive rich formatting redesign beyond preserving a readable fallback.
|
||||
- No general settings overhaul beyond the minimum config foundation needed to read existing theme settings and the new search keys together.
|
||||
|
||||
## Recommended Approach
|
||||
|
||||
### Option 1: Extend `dialoguer`
|
||||
|
||||
This keeps dependencies smaller, but it does not fit the requested behavior well. Paging, bottom-to-top layout, dense row rendering, and `paru`-style numeric selection would have to be simulated awkwardly across prompt screens.
|
||||
|
||||
### Option 2: Add a small config layer plus a dedicated `ratatui` search flow
|
||||
|
||||
This is the recommended approach. It creates a minimal, reusable settings boundary in `aim-cli`, keeps the current `aim-core` search contract intact, and gives the terminal UI enough control to implement the requested interaction model without twisting `dialoguer` into a pseudo-TUI.
|
||||
|
||||
### Option 3: Keep search plain text and launch a second prompt-only selection phase
|
||||
|
||||
This is simpler to ship, but it falls short of the requested UX and would likely be replaced immediately. It also duplicates state between the renderer and the selection prompt.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Config
|
||||
|
||||
Add a lightweight config loader in `aim-cli` that reads the existing user `config.toml` location already used for theme settings. The loader should:
|
||||
|
||||
- tolerate a missing file by returning defaults
|
||||
- ignore unknown keys
|
||||
- treat malformed config as a CLI error with a clear path-aware message
|
||||
- expose a typed `CliConfig` model for UI code
|
||||
|
||||
The search section should be:
|
||||
|
||||
```toml
|
||||
[search]
|
||||
bottom_to_top = true
|
||||
skip_confirmation = false
|
||||
```
|
||||
|
||||
This keeps search-specific settings namespaced and avoids a flat `skip_search_confirmation` key that will not scale once more search settings exist.
|
||||
|
||||
### Dispatch Flow
|
||||
|
||||
`aim search <QUERY>` should continue to build `SearchResults` through `aim-core`. `aim-cli` then chooses one of two render paths:
|
||||
|
||||
- TTY path: launch the interactive search browser
|
||||
- non-TTY path: render the existing plain text summary
|
||||
|
||||
The interactive search browser should return one of three outcomes:
|
||||
|
||||
- cancelled
|
||||
- confirmed selection set
|
||||
- selection set that still requires explicit confirmation
|
||||
|
||||
Install execution is not part of this slice. The result of the search browser can remain a terminal-side selection artifact for now, but the code should be shaped so install handoff can be added without reworking the browser state machine.
|
||||
|
||||
### TUI Model
|
||||
|
||||
Add a dedicated module for interactive search state in `aim-cli`. It should own:
|
||||
|
||||
- the ordered result rows
|
||||
- the highlighted cursor row
|
||||
- the selected row indices
|
||||
- the current page and viewport
|
||||
- the row number buffer for `paru`-style typed numeric selection
|
||||
- the config-driven orientation flag
|
||||
- the confirmation mode state
|
||||
|
||||
Each visible row should stay one line tall. The row should include:
|
||||
|
||||
- numeric index
|
||||
- provider label
|
||||
- repository or package identity
|
||||
- install-ready query
|
||||
|
||||
Warnings and installed matches should remain accessible, but the main browser should prioritize remote installable hits. If installed matches are shown in the interactive view, they should render in a distinct section or with a clear marker so they are not confused with remote install targets.
|
||||
|
||||
### Key Handling
|
||||
|
||||
The search browser should support:
|
||||
|
||||
- `j` / `k` and arrow keys for movement
|
||||
- `Ctrl+d` / `Ctrl+u` or `PageDown` / `PageUp` for paging
|
||||
- `g` / `G` for jump to top or bottom
|
||||
- digit entry for numeric selection ranges and comma-separated values
|
||||
- `Space` to toggle the highlighted row
|
||||
- `Enter` to continue to confirmation
|
||||
- `Esc` or `q` to cancel
|
||||
|
||||
Numeric selection should accept the same grammar throughout the session, for example `1`, `1,4,7`, and `3-6`. Invalid tokens should not panic; they should produce a small inline validation message and preserve the current selection.
|
||||
|
||||
### Confirmation
|
||||
|
||||
After the user leaves the browser with at least one selection, `aim-cli` should show a confirmation step by default. That step should summarise the chosen items and require an explicit yes/no confirmation.
|
||||
|
||||
If `[search].skip_confirmation = true`, the browser should return the chosen set immediately after selection finalization.
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Missing or empty search results should not launch the browser; use the existing text renderer.
|
||||
- Non-TTY stdin or stdout should not attempt `ratatui` initialization.
|
||||
- Terminal initialization failure should fall back to plain text output rather than aborting the search command.
|
||||
- Config parse failure should abort with a clear message because silent misconfiguration would be hard to debug.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Follow TDD for each slice:
|
||||
|
||||
1. Add config parsing tests for defaults, valid search overrides, and malformed TOML.
|
||||
2. Add state-machine tests for numeric selection parsing, paging, orientation, and confirmation transitions.
|
||||
3. Add CLI tests covering TTY-gated fallback behavior and config-driven confirmation skipping.
|
||||
4. Add a focused rendering test for one-line row formatting to keep the table stable.
|
||||
|
||||
Avoid end-to-end terminal snapshot tests that depend on terminal escape sequences unless state-level coverage proves insufficient.
|
||||
|
||||
## Delivery Notes
|
||||
|
||||
- Keep `aim-core` unchanged unless a search-install handoff type is clearly needed.
|
||||
- Keep the existing plain text renderer for non-interactive contexts and future `--json` work.
|
||||
- Preserve current theme behavior and make the new config loader the shared entry point for both theme settings and search settings.
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
# Search Interactive TUI Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add config-backed interactive search to `aim search <QUERY>` with a `ratatui` browser, numeric multi-select, paging, and an optional confirmation skip.
|
||||
|
||||
**Architecture:** Keep `aim-core` responsible for search retrieval and ranking. Add a small config loader plus a `ratatui`-backed state machine in `aim-cli`, and gate the interactive path on TTY availability with a safe plain-text fallback.
|
||||
|
||||
**Tech Stack:** Rust, clap, serde, toml, ratatui, crossterm, assert_cmd
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add CLI config loading for search settings
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-cli/src/config.rs`
|
||||
- Modify: `crates/aim-cli/src/lib.rs`
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Test: `crates/aim-cli/tests/config_loading.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add tests covering:
|
||||
|
||||
- missing config returns defaults
|
||||
- valid `[search]` config overrides defaults
|
||||
- malformed TOML returns a path-aware error
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test config_loading`
|
||||
Expected: FAIL because `config.rs` and the config loader do not exist.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement a typed `CliConfig` with nested `SearchConfig` and the minimum loader API needed by `aim-cli`.
|
||||
|
||||
Defaults:
|
||||
|
||||
```rust
|
||||
SearchConfig {
|
||||
bottom_to_top: true,
|
||||
skip_confirmation: false,
|
||||
}
|
||||
```
|
||||
|
||||
The loader must tolerate a missing file and reject malformed TOML with the resolved path in the error.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test config_loading`
|
||||
Expected: PASS
|
||||
|
||||
### Task 2: Add a search browser state machine
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-cli/src/ui/search_browser.rs`
|
||||
- Modify: `crates/aim-cli/src/ui/mod.rs`
|
||||
- Test: `crates/aim-cli/tests/search_browser.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add state-level tests for:
|
||||
|
||||
- bottom-to-top ordering default
|
||||
- cursor movement and page movement
|
||||
- single index selection
|
||||
- comma-separated and range numeric selection
|
||||
- invalid numeric input preserving current selection
|
||||
- confirmation state transitions
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_browser`
|
||||
Expected: FAIL because the browser state module does not exist.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Build a pure Rust state model that does not require a live terminal to test. Keep terminal drawing and key-event adaptation separate from selection and pagination logic.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_browser`
|
||||
Expected: PASS
|
||||
|
||||
### Task 3: Wire `ratatui` and TTY-gated interactive search dispatch
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/Cargo.toml`
|
||||
- Modify: `crates/aim-cli/src/lib.rs`
|
||||
- Modify: `crates/aim-cli/src/ui/render.rs`
|
||||
- Modify: `crates/aim-cli/src/main.rs`
|
||||
- Modify: `crates/aim-cli/src/ui/prompt.rs`
|
||||
- Test: `crates/aim-cli/tests/search_cli.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add CLI coverage for:
|
||||
|
||||
- non-TTY search stays plain text
|
||||
- config skip confirmation changes the post-selection path
|
||||
- empty result sets do not launch the browser
|
||||
|
||||
Use deterministic seams rather than a full escape-sequence snapshot test.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_cli`
|
||||
Expected: FAIL because interactive search dispatch is not implemented.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add `ratatui` and `crossterm`, initialize the browser only when stdin and stdout are terminals, and fall back cleanly to the existing renderer if terminal setup fails or the result set is empty.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_cli`
|
||||
Expected: PASS
|
||||
|
||||
### Task 4: Add row rendering and confirmation summary coverage
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/src/ui/search_browser.rs`
|
||||
- Test: `crates/aim-cli/tests/ui_summary.rs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
Add focused tests for:
|
||||
|
||||
- one-line row formatting
|
||||
- provider and query columns remaining visible
|
||||
- confirmation summary content for multi-select
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test ui_summary`
|
||||
Expected: FAIL because the browser summaries are not rendered yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement the smallest formatting helpers needed to keep rows stable and the confirmation screen legible.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test ui_summary`
|
||||
Expected: PASS
|
||||
|
||||
### Task 5: Final verification
|
||||
|
||||
**Files:**
|
||||
- Modify as required by prior tasks only
|
||||
|
||||
**Step 1: Run focused CLI tests**
|
||||
|
||||
Run: `cargo test --package aim-cli --test config_loading --test search_browser --test search_cli --test ui_summary`
|
||||
Expected: PASS
|
||||
|
||||
**Step 2: Run workspace formatting**
|
||||
|
||||
Run: `cargo fmt --all`
|
||||
Expected: PASS
|
||||
|
||||
**Step 3: Run workspace linting and regression tests**
|
||||
|
||||
Run: `cargo test --workspace && cargo clippy --workspace --all-targets --all-features -- -D warnings`
|
||||
Expected: PASS
|
||||
Loading…
Add table
Add a link
Reference in a new issue