Merge branch 'feat/cli-ux-progress'
This commit is contained in:
commit
27a1b806cd
44 changed files with 4995 additions and 106 deletions
|
|
@ -0,0 +1,168 @@
|
|||
# v0.9 Finalisation Design
|
||||
|
||||
## Goal
|
||||
|
||||
Ship a credible v0.9 release that improves operational trust and product completeness without widening provider scope prematurely. This slice hardens downloads, enforces integrity checks, makes registry mutation safer, finalises a provider-extensible search contract with GitHub search as the first implementation, and aligns docs with the actual supported provider surface.
|
||||
|
||||
## Release Positioning
|
||||
|
||||
v0.9 is a trust-and-discoverability release.
|
||||
|
||||
It is not the true v1.0 provider-completion release. `custom-json` is explicitly deferred to v1.0. Broader provider discovery remains phase 2 work.
|
||||
|
||||
## In Scope
|
||||
|
||||
1. Stream artifact downloads to disk instead of buffering whole payloads in memory.
|
||||
2. Add timeout and retry behavior to the download path.
|
||||
3. Enforce checksum verification when trusted metadata provides a checksum.
|
||||
4. Make registry mutation atomic and add advisory locking for mutating flows.
|
||||
5. Finalise `aim search <query>` with a provider-extensible search abstraction in `aim-core` and GitHub as the first remote provider.
|
||||
6. Update user-facing docs so supported providers and current search scope are described honestly.
|
||||
7. Add regression coverage for the above behaviors.
|
||||
|
||||
## Explicitly Out Of Scope
|
||||
|
||||
1. Implementing `custom-json`.
|
||||
2. Broad multi-provider remote search parity.
|
||||
3. Adding `info`, `show`, `dry-run`, rollback, or version pinning.
|
||||
4. Expanding GitLab or SourceForge install resolution beyond the currently defended contract.
|
||||
5. Reworking the CLI into an interactive picker or install-from-search workflow.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Download Hardening
|
||||
|
||||
The current add flow downloads into memory and only then stages the payload. v0.9 changes that boundary so the network layer streams into a staged file on disk. Payload validation and final commit remain owned by the install integration path, but the source of truth becomes a staged file rather than a `Vec<u8>` buffer.
|
||||
|
||||
This keeps memory usage effectively flat for large AppImages and makes retry or timeout policy attach naturally to the download operation.
|
||||
|
||||
### Integrity Enforcement
|
||||
|
||||
Checksum hints already exist in parsed metadata. v0.9 carries those hints through artifact selection and install execution so the staged payload can be verified before the final install commit. If a trusted checksum exists and validation fails, install must fail closed. If no checksum exists, install continues with no false claim of verification.
|
||||
|
||||
For v0.9, the only enforced trusted checksum contract is the existing electron-builder `sha512` field. That checksum must be treated as a base64-encoded SHA-512 digest of the raw payload bytes. Verification compares the base64 digest of the staged payload against the trimmed metadata value. A malformed trusted checksum is an install failure, not a warning.
|
||||
|
||||
### Registry Safety
|
||||
|
||||
Registry writes move to an atomic temp-file-and-rename pattern. Mutating commands also acquire an advisory registry lock so add, update, and remove cannot clobber each other through concurrent read-modify-write cycles.
|
||||
|
||||
The registry layer remains in `aim-core`; `aim-cli` should continue to orchestrate, not own persistence rules.
|
||||
|
||||
To avoid long lock retention, v0.9 does not hold the registry lock for network discovery, downloads, or desktop integration work. Instead, mutating flows acquire the exclusive advisory lock immediately before the registry transaction, reload the latest registry under lock, apply the final mutation by `stable_id`, save atomically, then release the lock. Read-only flows such as `list`, bare review, and `search` do not take the mutation lock.
|
||||
|
||||
### Search Architecture
|
||||
|
||||
Search becomes a first-class app flow in `aim-core`, not a CLI-only helper. The abstraction should be provider-extensible from the beginning, but only GitHub remote search is implemented in v0.9.
|
||||
|
||||
The stable shape is:
|
||||
|
||||
- `SearchQuery` for raw user intent and optional limits
|
||||
- `SearchProvider` trait for provider-specific search backends
|
||||
- `SearchResult` / `SearchResults` domain types for normalized output
|
||||
- `build_search_results(...)` app entry point that aggregates remote provider hits and local installed matches
|
||||
|
||||
GitHub search should provide install-ready queries that feed the existing add flow. Non-GitHub providers can implement the same contract later without changing the CLI surface.
|
||||
|
||||
For v0.9, GitHub search is repository search only. The normalized install-ready query should be the canonical `owner/repo` form so search results feed the existing add flow without introducing a parallel install path.
|
||||
|
||||
### CLI Surface
|
||||
|
||||
`aim search <query>` is added as a read-only command.
|
||||
|
||||
Output should distinguish:
|
||||
|
||||
- remote provider results
|
||||
- installed/local matches
|
||||
- warnings such as partial provider failure or rate limit degradation
|
||||
|
||||
Search does not install anything in v0.9. It is a discovery surface only.
|
||||
|
||||
The CLI contract should also be deterministic:
|
||||
|
||||
- default remote result limit is 10
|
||||
- GitHub remote hits preserve provider ranking order, with canonical locator as the stable tie-breaker when fixtures or adapters need explicit ordering
|
||||
- local installed matches use case-insensitive substring matching against `stable_id` and `display_name`
|
||||
- local matches render in a separate section and are sorted by exact match first, then prefix match, then substring match, with `stable_id` as the final tie-breaker
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Add / Install
|
||||
|
||||
1. Resolve query into source semantics.
|
||||
2. Discover release candidates and metadata as today.
|
||||
3. Select artifact and attach optional checksum hint.
|
||||
4. Stream artifact bytes into a staged file.
|
||||
5. Verify checksum if available.
|
||||
6. Validate payload shape.
|
||||
7. Commit staged payload into final location.
|
||||
8. Persist registry through the locked, atomic registry store.
|
||||
|
||||
If download, checksum verification, or payload validation fails, the staged file must be removed before returning the error.
|
||||
|
||||
### Search
|
||||
|
||||
1. Parse `aim search <query>`.
|
||||
2. Build `SearchQuery`.
|
||||
3. Run enabled providers, currently GitHub.
|
||||
4. Normalize remote hits into provider-neutral results.
|
||||
5. Derive local installed matches from the registry.
|
||||
6. Render a stable CLI summary.
|
||||
|
||||
Search warnings must preserve partial-failure explainability. For v0.9, a GitHub rate limit or transport failure should become a warning if any local results still exist, and a command failure only if the overall command would otherwise produce no meaningful result.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Download Failures
|
||||
|
||||
- Connection and HTTP failures should be retried according to policy.
|
||||
- Exhausted retries should surface as a clear install failure.
|
||||
- Timeout should be explicit rather than hanging indefinitely.
|
||||
- Partial staged payloads must be removed on failure.
|
||||
|
||||
### Checksum Failures
|
||||
|
||||
- A checksum mismatch is a hard error.
|
||||
- A malformed trusted checksum is a hard error.
|
||||
- Absence of checksum is not an error.
|
||||
- Search results must never imply verified integrity.
|
||||
|
||||
### Registry Lock Failures
|
||||
|
||||
- A second mutating process should fail cleanly with an explicit lock message or short wait policy.
|
||||
- Non-mutating flows like list and bare review should remain read-only and avoid unnecessary lock contention.
|
||||
- The registry mutation transaction must reload the latest registry state while holding the lock before applying the final mutation.
|
||||
|
||||
### Search Failures
|
||||
|
||||
- Provider failure should degrade to warnings when at least one provider succeeds.
|
||||
- Search should fail only when the overall operation cannot produce a meaningful result.
|
||||
- Search ordering, limit behavior, and installed-match rules must be stable across runs.
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. Unit tests for streaming or staged download helpers, retry policy, and checksum verification.
|
||||
2. Registry store tests for atomic write behavior and lock semantics.
|
||||
3. CLI command tests for `aim search --help` and search rendering.
|
||||
4. Fixture-backed GitHub search tests in `aim-core`.
|
||||
5. Install integration tests for checksum pass and checksum mismatch behavior.
|
||||
6. Focused regression tests to ensure current GitLab, SourceForge, and direct URL install semantics do not regress.
|
||||
7. Cleanup tests to ensure failed downloads or failed checksum validation do not leave staged payloads behind.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. Large artifact downloads no longer require full in-memory buffering.
|
||||
2. Download timeout and retry policy exist and are covered by tests.
|
||||
3. Trusted checksums are enforced before final install commit.
|
||||
4. Registry writes are atomic and mutating commands do not race each other silently.
|
||||
5. `aim search <query>` works end to end against GitHub fixtures.
|
||||
6. Search architecture allows additional providers to be added in phase 2 without changing the public CLI contract.
|
||||
7. README and related plan docs describe current provider and search scope honestly.
|
||||
8. Failed download or checksum paths do not leave orphaned staged files behind.
|
||||
|
||||
## v1.0 Follow-On
|
||||
|
||||
The true v1.0 track can build on this by:
|
||||
|
||||
1. implementing `custom-json`
|
||||
2. widening provider discovery beyond GitHub search
|
||||
3. expanding provider install semantics where justified by defended tests
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
# v0.9 Finalisation Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Finalise v0.9 by hardening artifact downloads, enforcing checksum verification, making registry mutation safe, and shipping provider-extensible search with GitHub as the first provider while keeping `custom-json` deferred to v1.0.
|
||||
|
||||
**Architecture:** Push download, integrity, and registry safety down into `aim-core`, keep `aim-cli` as a thin dispatch and rendering layer, and add a provider-neutral search app flow that can grow to additional providers in phase 2 without changing the CLI contract.
|
||||
|
||||
**Tech Stack:** Rust, Cargo workspace, `aim-core` app/source/registry modules, `aim-cli` command parsing and rendering, reqwest blocking client, existing fixture-backed GitHub tests, CLI integration tests.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Lock the v0.9 CLI and docs contract with failing tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/src/cli/args.rs`
|
||||
- Modify: `crates/aim-cli/tests/cli_commands.rs`
|
||||
- Modify: `README.md`
|
||||
- Modify: `.plans/008-v0-9-finalisation/2026-03-21-v0-9-finalisation-design.md`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Extend CLI help coverage so `--help` is expected to include `search`.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test cli_commands`
|
||||
Expected: FAIL because the CLI does not yet expose `search`.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add a `Search { query: String }` subcommand to the CLI args and update the README command/query documentation to reflect:
|
||||
|
||||
- `aim search <query>` exists in v0.9
|
||||
- SourceForge support is documented honestly
|
||||
- search is GitHub-backed first and provider-extensible, not multi-provider complete
|
||||
- `custom-json` is not presented as a v0.9 feature
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test cli_commands`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-cli/src/cli/args.rs crates/aim-cli/tests/cli_commands.rs README.md .plans/008-v0-9-finalisation/2026-03-21-v0-9-finalisation-design.md
|
||||
git commit -m "feat: add v0.9 search command contract"
|
||||
```
|
||||
|
||||
### Task 2: Add failing GitHub search tests and a provider-neutral search model
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/aim-core/src/app/search.rs`
|
||||
- Modify: `crates/aim-core/src/app/mod.rs`
|
||||
- Create: `crates/aim-core/src/domain/search.rs`
|
||||
- Modify: `crates/aim-core/src/domain/mod.rs`
|
||||
- Modify: `crates/aim-core/src/source/github.rs`
|
||||
- Create: `crates/aim-core/tests/search_github.rs`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add search tests that assert:
|
||||
|
||||
- GitHub fixtures can return normalized remote search hits
|
||||
- normalized results include provider id, display name, description, homepage/source locator, and install-ready query
|
||||
- the app-level search result type can also carry installed/local matches and warnings
|
||||
- default remote result limit is 10
|
||||
- the install-ready query is canonical `owner/repo`
|
||||
- remote hit ordering is stable under fixtures
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-core --test search_github`
|
||||
Expected: FAIL because no search domain or app flow exists yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add provider-neutral search domain types and the app-level search entry point in `aim-core`. Extend the GitHub source transport with the smallest search capability needed for fixture-backed repository search.
|
||||
|
||||
Keep the model narrow:
|
||||
|
||||
- one provider trait or equivalent provider entry shape
|
||||
- one normalized result type
|
||||
- no premature provider-specific knobs beyond what GitHub needs now
|
||||
|
||||
For v0.9, make the GitHub provider search repositories only. Preserve provider ranking order from fixtures or transport results, and use canonical locator as the deterministic tie-breaker when a secondary sort is needed.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-core --test search_github`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/search.rs crates/aim-core/src/app/mod.rs crates/aim-core/src/domain/search.rs crates/aim-core/src/domain/mod.rs crates/aim-core/src/source/github.rs crates/aim-core/tests/search_github.rs
|
||||
git commit -m "feat: add provider-neutral search core"
|
||||
```
|
||||
|
||||
### Task 3: Wire `aim search` through dispatch and rendering
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-cli/src/lib.rs`
|
||||
- Modify: `crates/aim-cli/src/ui/render.rs`
|
||||
- Create: `crates/aim-cli/tests/search_cli.rs`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add CLI integration tests that assert:
|
||||
|
||||
- `aim search bat` prints a `Search Results` heading
|
||||
- remote GitHub hits render with provider label and install-ready query
|
||||
- installed/local matches render in a separate section when the registry contains matching apps
|
||||
- installed/local matches use case-insensitive substring matching across `stable_id` and `display_name`
|
||||
- installed/local matches are sorted deterministically by exact match, prefix match, substring match, then `stable_id`
|
||||
- search remains read-only and does not mutate the registry
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_cli`
|
||||
Expected: FAIL because dispatch and render do not know about search yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Add search dispatch to `aim-cli`, call into the new `aim-core` search flow, load registry state for local-match context, and render a stable read-only summary.
|
||||
|
||||
Do not add interactive selection or install-from-search behavior.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-cli --test search_cli`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-cli/src/lib.rs crates/aim-cli/src/ui/render.rs crates/aim-cli/tests/search_cli.rs
|
||||
git commit -m "feat: wire search through cli"
|
||||
```
|
||||
|
||||
### Task 4: Add staged-download tests before changing the artifact pipeline
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-core/src/app/add.rs`
|
||||
- Modify: `crates/aim-core/src/integration/install.rs`
|
||||
- Modify: `crates/aim-core/tests/install_integration.rs`
|
||||
- Create: `crates/aim-core/tests/download_pipeline.rs`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add tests that assert:
|
||||
|
||||
- artifact download can stream into a staged path instead of returning full in-memory bytes
|
||||
- the staged file reaches full byte count and still emits progress
|
||||
- the install path can commit from a staged file source
|
||||
- a failed download attempt does not leave a partial staged payload behind
|
||||
|
||||
Use fixture or local test doubles rather than real network calls.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-core --test download_pipeline`
|
||||
Expected: FAIL because the current add flow still downloads into `Vec<u8>`.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Refactor the add/install boundary so downloads are streamed into a staged file and the install integration path commits from disk.
|
||||
|
||||
Keep existing operation stages and user-facing progress events intact.
|
||||
|
||||
Make cleanup deterministic: if streaming, payload validation, or post-download verification fails, the staged file must be removed before returning the error.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-core --test download_pipeline`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/add.rs crates/aim-core/src/integration/install.rs crates/aim-core/tests/install_integration.rs crates/aim-core/tests/download_pipeline.rs
|
||||
git commit -m "refactor: stream artifacts into staged payloads"
|
||||
```
|
||||
|
||||
### Task 5: Add retry and timeout policy to the download client
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-core/src/app/add.rs`
|
||||
- Modify: `crates/aim-core/src/source/github.rs`
|
||||
- Modify: `crates/aim-core/tests/download_pipeline.rs`
|
||||
- Modify: `crates/aim-core/tests/github_source_discovery.rs`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add focused tests that assert:
|
||||
|
||||
- the shared HTTP client is constructed with explicit timeout behavior
|
||||
- download retries transient failures according to policy
|
||||
- exhausted retries surface a clear failure
|
||||
- retry exhaustion does not leave a staged payload behind
|
||||
|
||||
Prefer test doubles around client-building or download helpers over brittle timing assertions.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-core --test download_pipeline`
|
||||
Expected: FAIL because timeout and retry policy are not represented yet.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Introduce a small shared download client or helper configuration that both GitHub discovery and artifact download can use. Add explicit timeout configuration and retry loops for transient failures.
|
||||
|
||||
Do not add resume support in this slice unless it falls out naturally from the refactor; timeout and retry are the required behaviors.
|
||||
|
||||
Make the timeout contract explicit in code and tests. The implementation does not need user-facing configurability in v0.9, but it must use fixed non-infinite defaults.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-core --test download_pipeline`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/add.rs crates/aim-core/src/source/github.rs crates/aim-core/tests/download_pipeline.rs crates/aim-core/tests/github_source_discovery.rs
|
||||
git commit -m "feat: add timeout and retry policy to downloads"
|
||||
```
|
||||
|
||||
### Task 6: Enforce checksum verification on install
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-core/src/app/add.rs`
|
||||
- Modify: `crates/aim-core/src/integration/install.rs`
|
||||
- Modify: `crates/aim-core/src/domain/update.rs`
|
||||
- Modify: `crates/aim-core/tests/install_integration.rs`
|
||||
- Create: `crates/aim-core/tests/checksum_verification.rs`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add tests that assert:
|
||||
|
||||
- installs with a valid checksum succeed
|
||||
- installs with a checksum mismatch fail before final payload commit
|
||||
- installs without a checksum still succeed
|
||||
- malformed trusted checksums fail before final payload commit
|
||||
- checksum failure does not leave a staged payload behind
|
||||
|
||||
Use fixture metadata with deterministic payload bytes.
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-core --test checksum_verification`
|
||||
Expected: FAIL because checksum hints are parsed but never enforced.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Thread checksum hints through artifact selection into install execution and verify the staged payload before the final rename. Surface a typed install failure for mismatch.
|
||||
|
||||
For v0.9, implement only the existing electron-builder checksum contract: compare the base64-encoded SHA-512 digest of the raw staged payload bytes against the trimmed `sha512` metadata value. Treat malformed trusted checksum input as an install failure.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-core --test checksum_verification`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/app/add.rs crates/aim-core/src/integration/install.rs crates/aim-core/src/domain/update.rs crates/aim-core/tests/install_integration.rs crates/aim-core/tests/checksum_verification.rs
|
||||
git commit -m "feat: verify artifact checksum before install"
|
||||
```
|
||||
|
||||
### Task 7: Make registry mutation atomic and locked
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aim-core/src/registry/store.rs`
|
||||
- Modify: `crates/aim-cli/src/lib.rs`
|
||||
- Create: `crates/aim-core/tests/registry_store.rs`
|
||||
- Modify: `Cargo.toml`
|
||||
- Modify: `crates/aim-core/Cargo.toml`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
Add registry store tests that assert:
|
||||
|
||||
- saves write through a temp file and leave the final registry valid
|
||||
- concurrent mutating access cannot silently race
|
||||
- lock acquisition failure surfaces a clear error
|
||||
- the locked mutation path reloads the latest registry before applying the final mutation
|
||||
- read-only flows do not require the mutation lock
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cargo test --package aim-core --test registry_store`
|
||||
Expected: FAIL because save is a direct `fs::write` with no lock semantics.
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
Implement atomic save and advisory locking in the registry store. Thread any needed lock lifecycle changes through the CLI mutating commands.
|
||||
|
||||
Keep read-only flows simple and avoid unnecessary lock retention.
|
||||
|
||||
The lock scope must be deterministic:
|
||||
|
||||
- do not hold the registry lock during network discovery, downloads, or desktop integration
|
||||
- acquire the exclusive lock immediately before the final registry transaction
|
||||
- reload the latest registry while the lock is held
|
||||
- apply the mutation by `stable_id`
|
||||
- save atomically, then release the lock
|
||||
|
||||
If a remove target disappears between pre-lock planning and the locked reload, fail cleanly instead of silently removing the wrong record.
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cargo test --package aim-core --test registry_store`
|
||||
Expected: PASS.
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add crates/aim-core/src/registry/store.rs crates/aim-cli/src/lib.rs crates/aim-core/tests/registry_store.rs Cargo.toml crates/aim-core/Cargo.toml
|
||||
git commit -m "feat: add atomic and locked registry mutation"
|
||||
```
|
||||
|
||||
### Task 8: Run provider regression coverage and final verification
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
- Modify: `.plans/008-v0-9-finalisation/2026-03-21-v0-9-finalisation-implementation-plan.md`
|
||||
|
||||
**Step 1: Add any missing regression expectations**
|
||||
|
||||
If provider or CLI regressions appear during execution, add the smallest missing focused tests in the existing suites:
|
||||
|
||||
- `crates/aim-core/tests/query_resolution.rs`
|
||||
- `crates/aim-core/tests/adapter_contract.rs`
|
||||
- `crates/aim-core/tests/install_integration.rs`
|
||||
- `crates/aim-core/tests/update_planning.rs`
|
||||
- `crates/aim-cli/tests/end_to_end_cli.rs`
|
||||
|
||||
**Step 2: Run focused verification**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo test --package aim-core --test search_github --test download_pipeline --test checksum_verification --test registry_store --test install_integration
|
||||
cargo test --package aim-cli --test cli_commands --test search_cli --test end_to_end_cli
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
**Step 3: Run full verification**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo test --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add README.md .plans/008-v0-9-finalisation/2026-03-21-v0-9-finalisation-implementation-plan.md crates/
|
||||
git commit -m "feat: finalize v0.9 reliability and search"
|
||||
```
|
||||
|
|
@ -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