feat: add Ctrl-R (refresh) and Ctrl-S (save-local) keybindings to model selection - #27
Conversation
…el selection - Add ModelSelectionResult dataclass (models, save_local) as the return type of select_models, replacing the bare dict - Add _REFRESH and _SAVE_TOGGLE sentinel objects; key handlers exit the prompt_toolkit app with the sentinel, and the surrounding while loop interprets it (re-render vs. advance) - _inject_key_bindings registers Ctrl-R eagerly on every slot; Ctrl-S is only registered when save_local_allowed=True (omitted under --no-project-config) - _do_refresh implements the three Ctrl-R cases: normal re-fetch, YAML-pinned skip, and --dry-run skip; stale chosen values are reset with a stderr note - Build an instruction footer advertising active keys and the armed state - Extend ResolvedModels with save_local and dialog_shown fields (defaults keep existing test calls valid) - resolve_models forwards the new keyword params to select_models and sets result.save_local/dialog_shown when the dialog branch runs - run_claude computes effective_save_local (dialog result when dialog ran, CLI flag otherwise) and uses it in the persistence block - Update PRD 03, README.md, and docs/commands.md to document the keybindings - Add 17 new unit tests covering Ctrl-R and Ctrl-S behavior, including refresh callback invocation, stale-value reset, save_local toggle, and effective_save_local in run_claude Closes #26 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds interactive keybindings to the model-selection dialog to refresh models in-place (Ctrl‑R) and toggle saving the final selection to project config (Ctrl‑S), with corresponding plumbing through resolution/persistence and documentation updates.
Changes:
- Implement Ctrl‑R refresh and Ctrl‑S save-local toggle in
select_models, including footer instructions and a structuredModelSelectionResult. - Thread new dialog outcomes/flags through
resolve_modelsand computeeffective_save_localinrun_claude. - Add/expand unit tests and update user/PRD documentation for the sequential per-slot dialog design and keybindings.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/selection/test_resolver.py | Adds coverage for new ResolvedModels fields and verifies forwarding of keybinding params into the dialog. |
| tests/unit/selection/test_interactive_keybindings.py | New unit tests for Ctrl‑R/Ctrl‑S behavior, refresh edge cases, and footer instruction content. |
| tests/unit/commands/test_claude.py | Tests effective_save_local behavior (dialog-governed vs CLI-governed) and dry-run persistence behavior. |
| src/leitum/selection/resolver.py | Extends ResolvedModels with save_local/dialog_shown and forwards refresh/save parameters into interactive selection. |
| src/leitum/selection/interactive.py | Implements sentinels, keybinding injection, refresh handler, footer builder, and returns ModelSelectionResult. |
| src/leitum/commands/claude.py | Wires refresh callback into resolver, computes effective_save_local, and uses it in persistence + dry-run messaging. |
| README.md | Documents the new Ctrl‑R/Ctrl‑S shortcuts and save-local armed state indicator. |
| prd/03-selection-flows.md | Reconciles PRD with sequential per-slot dialog reality and documents the new keybindings and persistence resolution. |
| docs/commands.md | Adds command documentation describing the model-selection keybindings and footer format. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review on PR #27: give test helper dicts explicit key/value type parameters and fix the _run_select_models return annotation (it returns a (result, spy) tuple, not ModelSelectionResult | None), removing the stale type: ignore. All changed test files now pass mypy --strict individually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the Copilot review (commit 4104ef6). Context: the project's actual type gate is That said, the comments point at genuinely loose typing in the new tests, so I tightened them anyway:
Both changed test files now pass |
Address Copilot review on PR #27: the other keybinding tests mock questionary away, so the actual Ctrl-R/Ctrl-S registration in _inject_key_bindings was never executed under test — a regression could ship unnoticed. Add a focused test that builds a real prompt_toolkit KeyBindings, runs injection, and asserts Ctrl-R is always bound and Ctrl-S only when save_local_allowed, plus the two guard paths (missing .application, non-KeyBindings object) are no-ops. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the latest Copilot review (commit adf5918). The point is legitimate: the other keybinding tests patch Added
Suite: 178 -> 182 tests. |
Address Copilot review on PR #27: give test helper dicts explicit key/value type parameters and fix the _run_select_models return annotation (it returns a (result, spy) tuple, not ModelSelectionResult | None), removing the stale type: ignore. All changed test files now pass mypy --strict individually. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
--dry-run, prints a note to stderr and stays on the current list. Any already-chosen slot value no longer in the refreshed list is reset to "(do not set)" with a stderr note.leitum.yaml(equivalent to--save-local). Toggles an armed state shown in the instruction footer as[save→project ON]. Unavailable (unbound, hint omitted) under--no-project-config. The dialog's final armed state fully governs the target when the dialog was shown (effective_save_localinrun_claude).ModelSelectionResultdataclass replaces the bare dict return type ofselect_models.ResolvedModelsgainssave_localanddialog_shownfields (defaults keep all existing tests valid).README.md, anddocs/commands.mdupdated to document the implemented sequential per-slot dialog design and the new keybindings.Test plan
ruff format,ruff check,mypy --strict,pytestall pass (178 tests, 0 failures — verified before PR open)leitum claude→ Ctrl-R re-fetches; Ctrl-S shows[save→project ON]and writesleitum.yamlleitum --dry-run claude→ refresh skip note, nothing writtenleitum --no-project-config claude→ no Ctrl-S hint in footer, key unboundCloses #26