mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-06 12:52:04 +00:00
tui2: complete parity phases with settings/relay tools and CI smoke
This commit is contained in:
1 parent
176d12d9ae
commit
367cad6a04
13 files changed
+3435
-10
No files matched your search
@@ -30,6 +30,8 @@ jobs:
|
||||
run: |
|
||||
poetry run pip-audit || echo "::warning::pip-audit found vulnerabilities"
|
||||
shell: bash
|
||||
- name: TUI v2 check smoke
|
||||
run: poetry run seedpass tui2 --check
|
||||
- name: Run deterministic regression tests
|
||||
run: poetry run pytest -q src/tests/test_deterministic_artifact_regression.py
|
||||
- name: Run tests with coverage
|
||||
|
||||
@@ -25,6 +25,7 @@ This directory contains SeedPass project documentation.
|
||||
- `entry_graph.md`
|
||||
- `tui_v2_plan.md`
|
||||
- `tui_v2_parity_checklist.md`
|
||||
- `tui_v2_parity_backlog.md`
|
||||
- `tui_v2_cutover_decision.md`
|
||||
- `tui_v2_large_vault_validation.md`
|
||||
- `security.md`
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# TUI v2 Parity Backlog
|
||||
|
||||
Status: Active tracking on `beta` (updated 2026-03-01).
|
||||
|
||||
This document captures parity learnings and the remaining implementation queue
|
||||
for Textual TUI v2 versus legacy interactive TUI.
|
||||
|
||||
Linked plans:
|
||||
- `docs/tui_v2_plan.md`
|
||||
- `docs/tui_v2_parity_checklist.md`
|
||||
- `docs/tui_v2_cutover_decision.md`
|
||||
|
||||
## What We Learned
|
||||
|
||||
1. Core shell workflows are stable in TUI v2:
|
||||
- list/search/filter/detail
|
||||
- archive/restore
|
||||
- document edit save/cancel
|
||||
- graph links + relation filtering + neighbor traversal
|
||||
- retry/error-recovery flows
|
||||
|
||||
2. Security-sensitive parity has improved:
|
||||
- reveal/QR for `password`, `totp`, `seed`, `managed_account`, `ssh`, `pgp`, `nostr`
|
||||
- high-risk confirm gates (`reveal confirm`, `qr private confirm`)
|
||||
- secret-mode clipboard-aware reveal behavior
|
||||
|
||||
3. Scale validation now exists for KB-like workloads:
|
||||
- core sort/tag/search + high-degree graph link stress
|
||||
- TUI large-index navigation stress
|
||||
|
||||
4. Remaining parity is mostly feature-surface breadth, not stability:
|
||||
- add-entry creation flows in v2
|
||||
- legacy retrieve/action micro-workflows
|
||||
- dedicated 2FA board
|
||||
- settings/profile/nostr operational menus
|
||||
|
||||
## Active Build Queue
|
||||
|
||||
### Phase A: Add Entry in TUI v2
|
||||
- [x] Add password creation flow
|
||||
- [x] Add TOTP creation flow
|
||||
- [x] Add key/value creation flow
|
||||
- [x] Add document creation flow
|
||||
- [x] Add SSH/PGP/Nostr/Seed/Managed Account creation flows
|
||||
- [x] Add validation and post-create focus/selection behavior
|
||||
|
||||
### Phase B: Retrieve/Action Parity
|
||||
- [x] Add note from selected entry
|
||||
- [x] Add custom field / hidden field actions
|
||||
- [x] Edit tags action parity
|
||||
- [x] Field-level edit parity for non-document kinds
|
||||
- [x] Document export action
|
||||
- [x] Nostr QR submenu parity (public/private explicit flows)
|
||||
|
||||
### Phase C: 2FA Board Parity
|
||||
- [x] Dedicated 2FA list board view
|
||||
- [x] rolling code refresh + remaining timer UI
|
||||
- [x] secret-mode clipboard behavior in board view
|
||||
- [x] deterministic/imported split parity and display semantics
|
||||
|
||||
### Phase D: Settings/Profile/Nostr Parity
|
||||
- [x] profile switch/add/remove/list/rename
|
||||
- [x] secret mode / quick unlock / offline toggles
|
||||
- [x] inactivity timeout + KDF settings
|
||||
- [x] relay view/add/remove/reset + sync controls
|
||||
- [x] checksum verify/generate, import/export db, export totp, parent seed backup/reveal
|
||||
|
||||
### Phase E: Release/CI Closure
|
||||
- [x] Add CI smoke step for `seedpass tui2 --check`
|
||||
- [x] Extend docs/help parity after each feature landing
|
||||
- [x] Keep `legacy` fallback path until parity checklist has no high-severity gaps
|
||||
|
||||
Release note:
|
||||
- CI now runs a dedicated `tui2 --check` smoke gate via `scripts/tui2_check_smoke.sh`
|
||||
(invoked by `scripts/run_ci_tests.sh`) and in Poetry workflow (`tests.yml`).
|
||||
- Legacy fallback command/flag paths remain available (`seedpass legacy`,
|
||||
`seedpass --legacy-tui`, and `tui2` fallback toggle) while cutover hardening
|
||||
continues.
|
||||
|
||||
## Execution Notes
|
||||
|
||||
- Implement in small vertical slices and check boxes as each slice lands with
|
||||
tests.
|
||||
- Each parity slice must include:
|
||||
1) command/keybind discoverability,
|
||||
2) service-layer coverage,
|
||||
3) Textual interaction tests.
|
||||
@@ -54,3 +54,18 @@ This checklist tracks readiness to switch default interactive mode from legacy T
|
||||
- standard: `10,000` rows, stress: `50,000` rows (`--stress`)
|
||||
- [x] Confirm help text and docs match shipped keybindings and palette commands.
|
||||
- Updated `seedpass tui2 --help`, capabilities hints, and docs discoverability commands.
|
||||
|
||||
## Ongoing Tracking
|
||||
|
||||
- Active parity continuation and checkoff queue:
|
||||
- `docs/tui_v2_parity_backlog.md`
|
||||
|
||||
## Phase E Closure Notes
|
||||
|
||||
- CI smoke now explicitly validates `seedpass tui2 --check` output shape:
|
||||
- `scripts/tui2_check_smoke.sh` (called from `scripts/run_ci_tests.sh`)
|
||||
- `.github/workflows/tests.yml` (`poetry run seedpass tui2 --check`)
|
||||
- Legacy fallback paths remain intentionally available during hardening:
|
||||
- `seedpass --legacy-tui`
|
||||
- `seedpass legacy`
|
||||
- `seedpass tui2 --fallback-legacy`
|
||||
+2
-1
@@ -38,7 +38,7 @@ Goal: Introduce a modern, modular TUI (`seedpass tui2`) without rewriting core l
|
||||
- [x] Add plan doc.
|
||||
- [x] Add `seedpass tui2` command.
|
||||
- [x] Add runtime check utility and placeholder launcher.
|
||||
- [ ] Add CI smoke command for `seedpass tui2 --check`.
|
||||
- [x] Add CI smoke command for `seedpass tui2 --check`.
|
||||
|
||||
### Phase 1: Read-only shell
|
||||
- [x] Textual app skeleton with 3-pane layout:
|
||||
@@ -94,3 +94,4 @@ Goal: Introduce a modern, modular TUI (`seedpass tui2`) without rewriting core l
|
||||
- `seedpass/tui_v2/`
|
||||
- Docs updates:
|
||||
- this file listed in docs index and discoverability nav.
|
||||
- parity continuation tracker: `docs/tui_v2_parity_backlog.md`
|
||||
@@ -300,3 +300,231 @@
|
||||
- Added docs `docs/kb_scale_validation.md` with exact commands for standard and `--stress` runs.
|
||||
- Updated launch documentation to clearly show all interactive launch modes:
|
||||
- default (`seedpass`), explicit v2 (`seedpass tui2`), and legacy (`seedpass legacy` / `seedpass --legacy-tui`).
|
||||
|
||||
## 2026-03-01 TUI v2 parity backlog prioritization
|
||||
- TORCH latest daily/weekly memory snapshots are mostly scheduler metadata; TUI parity priorities should continue to be driven by `docs/tui_v2_plan.md`, `docs/tui_v2_parity_checklist.md`, and direct legacy-vs-v2 code diff.
|
||||
- Highest-impact remaining parity gaps identified:
|
||||
1) Add-entry workflows for all kinds in TUI v2
|
||||
2) Retrieve/action parity (notes/custom fields/tags/edit field-level variants/document export/QR submenus)
|
||||
3) 2FA codes live board parity with timer + secret-mode clipboard behavior
|
||||
4) Settings/profile/nostr operational parity in TUI v2
|
||||
5) CI smoke gate for `seedpass tui2 --check` still marked open in plan
|
||||
|
||||
## 2026-03-01 TUI v2 parity tracking + Phase A add-entry slice
|
||||
- Added a dedicated continuation tracker `docs/tui_v2_parity_backlog.md` and linked it from:
|
||||
- `docs/tui_v2_plan.md`
|
||||
- `docs/tui_v2_parity_checklist.md`
|
||||
- `docs/README.md`
|
||||
- Landed TUI v2 command-palette add-entry flows in `src/seedpass/tui_v2/app.py`:
|
||||
- `add-password <label> <length> [username] [url]`
|
||||
- `add-totp <label> [period] [digits] [secret]`
|
||||
- `add-key-value <label> <key> <value>`
|
||||
- `add-document <label> <file_type> <content>`
|
||||
- Added usage validation and post-create behavior:
|
||||
- strict argument/typing checks with status feedback
|
||||
- refresh + selection focus to created entry (or nearest match for TOTP)
|
||||
- retry wiring through existing `x` recovery path on service failures
|
||||
- Extended Textual tests:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py` now includes add-command success and validation-error scenarios.
|
||||
- `src/tests/test_tui_v2_action_matrix.py` test service now implements add methods and exercises add-command guards/success paths.
|
||||
- Validation run:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
|
||||
- Result: `24 passed`.
|
||||
- Updated backlog checkoffs (Phase A): password/TOTP/key-value/document creation + validation/post-create focus are now marked complete; remaining work is SSH/PGP/Nostr/Seed/Managed Account creation flows and downstream parity phases.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase A completion (remaining add kinds)
|
||||
- Extended TUI v2 command palette add workflows in `src/seedpass/tui_v2/app.py` for remaining entry kinds:
|
||||
- `add-ssh <label> [index]`
|
||||
- `add-pgp <label> [index] [key_type] [user_id]`
|
||||
- `add-nostr <label> [index]`
|
||||
- `add-seed <label> [words] [index]`
|
||||
- `add-managed-account <label> [index]`
|
||||
- Added argument validation and status hints for all new commands, including integer checks for index/words arguments and retry integration on service failures.
|
||||
- Updated discoverability text in palette/help/filter hints to include the new add command set.
|
||||
- Expanded test doubles and coverage:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py`: success + validation coverage for all add commands (existing and new).
|
||||
- `src/tests/test_tui_v2_action_matrix.py`: guard paths and success paths for new add commands.
|
||||
- Validation run repeated after formatting:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
|
||||
- Result: `24 passed`.
|
||||
- Backlog update:
|
||||
- `docs/tui_v2_parity_backlog.md` Phase A is now fully checked off.
|
||||
- Next priority to start: Phase B retrieve/action parity (notes/custom fields/tags/field-level edits/document export/nostr QR submenu behaviors).
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase B slice: notes/tags/custom fields/document export
|
||||
- Added retrieve/action parity commands to TUI v2 palette in `src/seedpass/tui_v2/app.py`:
|
||||
- `notes-set <text>` / `notes-clear`
|
||||
- `tag-add <tag>` / `tag-rm <tag>` / `tags-set <comma-list>` / `tags-clear`
|
||||
- `field-add <label> <value> [hidden-token]` / `field-rm <label>`
|
||||
- `doc-export [output_path]` (selected document only)
|
||||
- Introduced helper paths for selected-entry mutation:
|
||||
- `_selected_entry_payload()` for reliable selected entry fetch/guarding
|
||||
- `_apply_selected_modify(...)` to centralize modify->reload->reselect->retry behavior
|
||||
- Added/extended tests:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py` adds success + validation coverage for notes/tags/custom fields/doc export.
|
||||
- `src/tests/test_tui_v2_action_matrix.py` now exercises new command guards and paths.
|
||||
- Fake services in both test files now implement `export_document_file(...)`.
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
|
||||
- Result: `26 passed`.
|
||||
- UI-status formatting note for Textual markup:
|
||||
- Bracketed usage strings like `[hidden]`/`[output_path]` get treated as markup and partially disappear in status rendering.
|
||||
- Switched those usage strings to plain text `(optional: ...)` format.
|
||||
- Backlog updates in `docs/tui_v2_parity_backlog.md`:
|
||||
- Checked off Phase B items for notes, tags, custom fields, document export, and Nostr public/private QR submenu parity.
|
||||
- Remaining Phase B gap: field-level edit parity for non-document kinds.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase B slice: field-level edits for non-document kinds
|
||||
- Added palette commands in `src/seedpass/tui_v2/app.py`:
|
||||
- `set-field <name> <value>` (alias `field-set`)
|
||||
- `clear-field <name>` (alias `field-clear`)
|
||||
- Implemented kind-aware field edit allowlists and validation:
|
||||
- password: `label/notes/username/url/length`
|
||||
- totp: `label/notes/period/digits`
|
||||
- key_value: `label/notes/key/value`
|
||||
- document: `label/notes/file_type/content`
|
||||
- other kinds: `label/notes`
|
||||
- Integer coercion/validation added for `length/period/digits` with explicit status errors.
|
||||
- Added shared selected-entry modify helpers to keep behavior deterministic and consistent:
|
||||
- `_selected_entry_payload()`
|
||||
- `_apply_selected_modify(...)`
|
||||
- Expanded tests:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py` adds success + validation tests for set/clear field workflows across password/totp/key_value.
|
||||
- `src/tests/test_tui_v2_action_matrix.py` includes command guard and action-path coverage for new commands.
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
|
||||
- Result: `28 passed`.
|
||||
- Backlog update:
|
||||
- `docs/tui_v2_parity_backlog.md` now marks Phase B field-level edit parity complete.
|
||||
- Phase B is effectively complete in tracker and next focus should move to Phase C (dedicated 2FA board parity).
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase C: dedicated 2FA board
|
||||
- Added dedicated 2FA board mode in `src/seedpass/tui_v2/app.py`:
|
||||
- keybind: `6` (`action_toggle_totp_board`)
|
||||
- palette commands: `2fa-board`, `2fa-hide`, `2fa-refresh`, `2fa-copy <entry_id>`
|
||||
- Added right-pane board widget `#totp-board` and visibility switching helpers.
|
||||
- Added live refresh/timer behavior:
|
||||
- app interval tick (`set_interval(1.0, _tick_totp_board)`) updates board while visible
|
||||
- per-row remaining seconds (`rem`) based on each TOTP period
|
||||
- Added board display semantics for deterministic/imported split:
|
||||
- source column (`det` / `imp`) from `deterministic` flag fallback to secret presence
|
||||
- Added secret-mode behavior for board:
|
||||
- board masks visible codes as `******` when secret mode is enabled
|
||||
- `2fa-copy` copies actual code to clipboard and reports clear-delay status
|
||||
- Updated help/filter/palette discoverability text with new 2FA board commands.
|
||||
- Test coverage added/expanded:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py`:
|
||||
- `test_tui2_textual_2fa_board_view_timer_and_copy`
|
||||
- `test_tui2_textual_2fa_board_secret_mode_and_validation`
|
||||
- `src/tests/test_tui_v2_action_matrix.py` command matrix includes 2FA command guards/actions
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py`
|
||||
- Result: `30 passed`.
|
||||
- Backlog update: all Phase C checkbox items now marked complete in `docs/tui_v2_parity_backlog.md`.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase D slice: profile + settings palette plumbing
|
||||
- Extended `launch_tui2(...)` to accept optional `profile_service_factory` and `config_service_factory`, in addition to entry service factory.
|
||||
- Updated CLI preflight wiring (`src/seedpass/cli/__init__.py`) so TUI v2 receives entry/profile/config services from a single initialized manager when available, while preserving existing monkeypatch compatibility in CLI tests.
|
||||
- Added new TUI v2 palette commands in `src/seedpass/tui_v2/app.py`:
|
||||
- `profiles-list`
|
||||
- `profile-switch <fingerprint> [password]`
|
||||
- `setting-secret <on|off> [delay]`
|
||||
- `setting-offline <on|off>`
|
||||
- `setting-quick-unlock <on|off>`
|
||||
- Added toggle parsing helper for robust boolean token handling (`on/off/true/false/...`).
|
||||
- Added Textual interaction test fakes for profile/config services and new command coverage:
|
||||
- `test_tui2_textual_profiles_and_settings_palette_commands`
|
||||
- `test_tui2_textual_profiles_and_settings_palette_validation`
|
||||
- Expanded action-matrix command coverage to include new profile/settings command guard paths.
|
||||
- Validation run:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
|
||||
- Result: `86 passed`.
|
||||
- Backlog update (`docs/tui_v2_parity_backlog.md`):
|
||||
- marked `secret mode / quick unlock / offline toggles` complete.
|
||||
- profile switch/add/remove/list/rename remains open because only list/switch command-path parity is currently implemented; add/remove/rename UX flows still pending.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase D slice: profile lifecycle + relay/sync command paths
|
||||
- Extended TUI launch wiring to accept optional `nostr_service_factory` and `sync_service_factory` in `launch_tui2(...)`.
|
||||
- Updated CLI preflight service bundle (`src/seedpass/cli/__init__.py`) to pass entry/profile/config/nostr/sync services from a shared manager when available, while preserving monkeypatch compatibility in CLI tests.
|
||||
- Added new palette commands in `src/seedpass/tui_v2/app.py`:
|
||||
- profile lifecycle: `profile-add`, `profile-remove <fingerprint>` (plus existing `profiles-list`, `profile-switch`)
|
||||
- relays/sync: `relay-list`, `relay-add <url>`, `relay-rm <index>`, `sync-now`, `sync-bg`
|
||||
- Added Textual interaction fakes + coverage for new service surfaces:
|
||||
- fake profile service now supports add/remove
|
||||
- new fake nostr service for relay list/add/remove
|
||||
- new fake sync service for sync-now/bg paths
|
||||
- Expanded command matrix coverage to include new profile/relay/sync guard-path commands.
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
|
||||
- Result: `86 passed`.
|
||||
- Backlog status update:
|
||||
- Phase D rows for profile and relay/sync remain open because profile rename and relay reset parity are still missing.
|
||||
- Added explicit progress note in `docs/tui_v2_parity_backlog.md` documenting currently landed command coverage.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase D completion slice: profile rename + relay reset
|
||||
- Service-layer additions in `src/seedpass/core/api.py`:
|
||||
- `ProfileService.rename_profile(fingerprint, name)` using fingerprint manager naming support.
|
||||
- `NostrService.reset_relays()` to restore default relays, persist config/state, and update active Nostr client relay list.
|
||||
- TUI palette additions in `src/seedpass/tui_v2/app.py`:
|
||||
- `profile-rename <fingerprint> <name>`
|
||||
- `relay-reset`
|
||||
- Existing profile/relay command set now covers:
|
||||
- profiles: list/switch/add/remove/rename
|
||||
- relays/sync: list/add/remove/reset + sync-now/sync-bg
|
||||
- Test updates:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py` fakes now implement `rename_profile` and `reset_relays`; success/validation tests assert both command paths.
|
||||
- `src/tests/test_tui_v2_action_matrix.py` matrix guard coverage includes `profile-rename` and `relay-reset`.
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
|
||||
- Result: `86 passed`.
|
||||
- Backlog update:
|
||||
- `docs/tui_v2_parity_backlog.md` now marks both `profile switch/add/remove/list/rename` and `relay view/add/remove/reset + sync controls` complete.
|
||||
- Remaining Phase D open items are `inactivity timeout + KDF settings` and checksum/import-export utility actions.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase D completion slice: inactivity/KDF + checksum/export utilities
|
||||
- Extended TUI launch dependency injection to include optional `utility_service_factory` and `vault_service_factory`.
|
||||
- CLI preflight bundle (`src/seedpass/cli/__init__.py`) now provides utility/vault services to TUI v2 when manager-backed services are available.
|
||||
- Added new settings commands in `src/seedpass/tui_v2/app.py`:
|
||||
- `setting-timeout <seconds>`
|
||||
- `setting-kdf-iterations <n>`
|
||||
- `setting-kdf-mode <mode>`
|
||||
- Added utility/export commands:
|
||||
- `checksum-verify`
|
||||
- `checksum-update`
|
||||
- `db-export <path>`
|
||||
- `db-import <path>`
|
||||
- `totp-export <path>`
|
||||
- `parent-seed-backup [path] [password]`
|
||||
- Added support commands to existing profile/relay set:
|
||||
- `profile-rename <fingerprint> <name>` and `relay-reset` (wired to new service methods).
|
||||
- Service-layer additions in `src/seedpass/core/api.py`:
|
||||
- `ProfileService.rename_profile(...)`
|
||||
- `NostrService.reset_relays()`
|
||||
- Test coverage updates:
|
||||
- `src/tests/test_tui_v2_textual_interactions.py` now has fakes and assertions for all new settings/checksum/export/profile-rename/relay-reset commands.
|
||||
- `src/tests/test_tui_v2_action_matrix.py` command matrix includes guard path invocations for the new commands.
|
||||
- Validation:
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py`
|
||||
- Result: `86 passed`.
|
||||
- Backlog update:
|
||||
- `docs/tui_v2_parity_backlog.md` now marks all Phase D items complete.
|
||||
- Remaining backlog focus shifts to Phase E release/CI closure work.
|
||||
|
||||
## 2026-03-01 TUI v2 parity Phase E: CI smoke + docs/help closure
|
||||
- Added dedicated CI smoke script for TUI v2 runtime diagnostics:
|
||||
- `scripts/tui2_check_smoke.sh`
|
||||
- Executes `seedpass tui2 --check` when CLI binary is available, otherwise falls back to Typer `CliRunner` invocation.
|
||||
- Validates JSON output keys (`status`, `backend`, `textual_available`, `message`) and backend value (`textual`).
|
||||
- Includes `python`/`python3` fallback handling for portability.
|
||||
- Wired CI smoke gate into default CI test runner:
|
||||
- `scripts/run_ci_tests.sh` now runs `./scripts/tui2_check_smoke.sh` before determinism/full test suite.
|
||||
- Added explicit workflow smoke step for Poetry matrix:
|
||||
- `.github/workflows/tests.yml` includes `poetry run seedpass tui2 --check`.
|
||||
- Phase E docs/status updates:
|
||||
- `docs/tui_v2_parity_backlog.md` marks all Phase E closure items complete and includes release notes.
|
||||
- `docs/tui_v2_plan.md` marks the CI smoke command item complete in Phase 0.
|
||||
- `docs/tui_v2_parity_checklist.md` adds closure notes for CI smoke and retained legacy fallback paths.
|
||||
- Validation run:
|
||||
- `scripts/tui2_check_smoke.sh` => `tui2 --check smoke: ok`
|
||||
- `bash -n scripts/tui2_check_smoke.sh scripts/run_ci_tests.sh` => clean
|
||||
- `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py` => `86 passed`.
|
||||
@@ -3,6 +3,8 @@ set -eo pipefail
|
||||
|
||||
mkdir -p artifacts/coverage
|
||||
|
||||
./scripts/tui2_check_smoke.sh
|
||||
|
||||
if [[ "${DETERMINISM_GATE:-1}" == "1" ]]; then
|
||||
./scripts/run_determinism_tests.sh
|
||||
fi
|
||||
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# CI smoke gate for TUI v2 runtime diagnostics.
|
||||
# Prefer the real CLI command when available; fall back to Typer CliRunner.
|
||||
|
||||
if command -v seedpass >/dev/null 2>&1; then
|
||||
raw_output="$(seedpass tui2 --check)"
|
||||
else
|
||||
py_bin="python"
|
||||
if ! command -v "$py_bin" >/dev/null 2>&1; then
|
||||
py_bin="python3"
|
||||
fi
|
||||
raw_output="$("$py_bin" - <<'PY'
|
||||
import json
|
||||
from typer.testing import CliRunner
|
||||
from seedpass.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["tui2", "--check"])
|
||||
if result.exit_code != 0:
|
||||
raise SystemExit(result.exit_code)
|
||||
print(result.stdout.strip())
|
||||
PY
|
||||
)"
|
||||
fi
|
||||
|
||||
py_bin="python"
|
||||
if ! command -v "$py_bin" >/dev/null 2>&1; then
|
||||
py_bin="python3"
|
||||
fi
|
||||
|
||||
"$py_bin" - <<'PY' "$raw_output"
|
||||
import json
|
||||
import sys
|
||||
|
||||
text = sys.argv[1].strip()
|
||||
data = json.loads(text)
|
||||
required = {"status", "backend", "textual_available", "message"}
|
||||
missing = sorted(required - set(data))
|
||||
if missing:
|
||||
raise SystemExit(f"tui2 --check missing keys: {', '.join(missing)}")
|
||||
if data.get("backend") != "textual":
|
||||
raise SystemExit("tui2 --check backend mismatch")
|
||||
print("tui2 --check smoke: ok")
|
||||
PY
|
||||
@@ -84,14 +84,33 @@ def _gui_backend_available() -> bool:
|
||||
|
||||
def _prime_tui2_service(
|
||||
ctx: typer.Context,
|
||||
) -> tuple[Optional[object], Optional[Exception]]:
|
||||
) -> tuple[Optional[dict[str, object]], Optional[Exception]]:
|
||||
"""Initialize entry service before Textual mounts.
|
||||
|
||||
PasswordManager initialization may prompt for credentials. Doing that before
|
||||
entering Textual prevents hidden prompt stalls that appear as a blank screen.
|
||||
"""
|
||||
try:
|
||||
return _get_entry_service(ctx), None
|
||||
from seedpass.core.api import (
|
||||
ProfileService,
|
||||
ConfigService,
|
||||
NostrService,
|
||||
SyncService,
|
||||
UtilityService,
|
||||
VaultService,
|
||||
)
|
||||
|
||||
entry_service = _get_entry_service(ctx)
|
||||
pm = getattr(entry_service, "_manager", None)
|
||||
return {
|
||||
"entry": entry_service,
|
||||
"profile": ProfileService(pm) if pm is not None else None,
|
||||
"config": ConfigService(pm) if pm is not None else None,
|
||||
"nostr": NostrService(pm) if pm is not None else None,
|
||||
"sync": SyncService(pm) if pm is not None else None,
|
||||
"utility": UtilityService(pm) if pm is not None else None,
|
||||
"vault": VaultService(pm) if pm is not None else None,
|
||||
}, None
|
||||
except Exception as exc: # pragma: no cover - defensive runtime path
|
||||
return None, exc
|
||||
|
||||
@@ -126,7 +145,7 @@ def main(
|
||||
if legacy_tui:
|
||||
raise _launch_legacy_tui(fingerprint=fingerprint)
|
||||
|
||||
service, preflight_error = _prime_tui2_service(ctx)
|
||||
services, preflight_error = _prime_tui2_service(ctx)
|
||||
if preflight_error is not None:
|
||||
typer.echo(
|
||||
"TUI v2 preflight failed; falling back to legacy TUI. "
|
||||
@@ -136,7 +155,13 @@ def main(
|
||||
|
||||
launched = launch_tui2(
|
||||
fingerprint=fingerprint,
|
||||
entry_service_factory=lambda: service,
|
||||
entry_service_factory=lambda: services["entry"],
|
||||
profile_service_factory=lambda: services["profile"],
|
||||
config_service_factory=lambda: services["config"],
|
||||
nostr_service_factory=lambda: services["nostr"],
|
||||
sync_service_factory=lambda: services["sync"],
|
||||
utility_service_factory=lambda: services["utility"],
|
||||
vault_service_factory=lambda: services["vault"],
|
||||
)
|
||||
if launched:
|
||||
return
|
||||
@@ -176,7 +201,7 @@ def tui2(
|
||||
return
|
||||
|
||||
fingerprint = (ctx.obj or {}).get("fingerprint")
|
||||
service, preflight_error = _prime_tui2_service(ctx)
|
||||
services, preflight_error = _prime_tui2_service(ctx)
|
||||
if preflight_error is not None:
|
||||
if fallback_legacy:
|
||||
typer.echo(
|
||||
@@ -192,7 +217,13 @@ def tui2(
|
||||
|
||||
launched = launch_tui2(
|
||||
fingerprint=fingerprint,
|
||||
entry_service_factory=lambda: service,
|
||||
entry_service_factory=lambda: services["entry"],
|
||||
profile_service_factory=lambda: services["profile"],
|
||||
config_service_factory=lambda: services["config"],
|
||||
nostr_service_factory=lambda: services["nostr"],
|
||||
sync_service_factory=lambda: services["sync"],
|
||||
utility_service_factory=lambda: services["utility"],
|
||||
vault_service_factory=lambda: services["vault"],
|
||||
)
|
||||
if launched:
|
||||
return
|
||||
|
||||
@@ -224,6 +224,14 @@ class ProfileService:
|
||||
with self._lock:
|
||||
self._manager.select_fingerprint(req.fingerprint, password=req.password)
|
||||
|
||||
def rename_profile(self, fingerprint: str, name: str | None) -> None:
|
||||
"""Set or clear a display name for ``fingerprint``."""
|
||||
|
||||
with self._lock:
|
||||
ok = self._manager.fingerprint_manager.set_name(fingerprint, name)
|
||||
if not ok:
|
||||
raise ValueError("profile not found")
|
||||
|
||||
|
||||
class SyncService:
|
||||
"""Thread-safe wrapper around vault synchronization."""
|
||||
@@ -889,3 +897,15 @@ class NostrService:
|
||||
self._manager.nostr_client.relays = (
|
||||
self._manager.state_manager.list_relays()
|
||||
)
|
||||
|
||||
def reset_relays(self) -> list[str]:
|
||||
with self._lock:
|
||||
from nostr.client import DEFAULT_RELAYS
|
||||
|
||||
relays = list(DEFAULT_RELAYS)
|
||||
self._manager.config_manager.set_relays(relays, require_pin=False)
|
||||
state = self._manager.state_manager._load()
|
||||
state["relays"] = relays
|
||||
self._manager.state_manager._save(state)
|
||||
self._manager.nostr_client.relays = list(relays)
|
||||
return relays
|
||||
+1644
-1
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
@@ -39,6 +40,9 @@ class MatrixEntryService:
|
||||
self.clipboard_delay = 30
|
||||
self.clipboard_values: list[str] = []
|
||||
|
||||
def _next_id(self) -> int:
|
||||
return (max(self._entries.keys()) + 1) if self._entries else 1
|
||||
|
||||
def search_entries(self, query: str, kinds: list[str] | None = None):
|
||||
if self.fail_search:
|
||||
raise RuntimeError("search failed")
|
||||
@@ -69,6 +73,198 @@ class MatrixEntryService:
|
||||
return {}
|
||||
return dict(self._entries[int(entry_id)])
|
||||
|
||||
def add_entry(
|
||||
self,
|
||||
label: str,
|
||||
length: int,
|
||||
username: str | None = None,
|
||||
url: str | None = None,
|
||||
) -> int:
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "password",
|
||||
"label": label,
|
||||
"length": int(length),
|
||||
"username": username,
|
||||
"url": url,
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_totp(
|
||||
self,
|
||||
label: str,
|
||||
*,
|
||||
index: int | None = None,
|
||||
secret: str | None = None,
|
||||
period: int = 30,
|
||||
digits: int = 6,
|
||||
deterministic: bool = False,
|
||||
) -> str:
|
||||
_ = (index, deterministic)
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "totp",
|
||||
"label": label,
|
||||
"secret": secret or "JBSWY3DPEHPK3PXP",
|
||||
"period": int(period),
|
||||
"digits": int(digits),
|
||||
"archived": False,
|
||||
}
|
||||
return f"otpauth://totp/{label}"
|
||||
|
||||
def add_key_value(
|
||||
self, label: str, key: str, value: str, *, notes: str = ""
|
||||
) -> int:
|
||||
_ = notes
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "key_value",
|
||||
"label": label,
|
||||
"key": key,
|
||||
"value": value,
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_document(
|
||||
self,
|
||||
label: str,
|
||||
content: str,
|
||||
*,
|
||||
file_type: str = "txt",
|
||||
notes: str = "",
|
||||
tags: list[str] | None = None,
|
||||
archived: bool = False,
|
||||
) -> int:
|
||||
_ = notes
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "document",
|
||||
"label": label,
|
||||
"content": content,
|
||||
"file_type": file_type,
|
||||
"tags": tags or [],
|
||||
"archived": bool(archived),
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_ssh_key(
|
||||
self, label: str, *, index: int | None = None, notes: str = ""
|
||||
) -> int:
|
||||
_ = (index, notes)
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "ssh",
|
||||
"label": label,
|
||||
"private_key": f"SSH_PRIVATE_{entry_id}",
|
||||
"public_key": f"ssh-ed25519 AAAA-{entry_id}",
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_pgp_key(
|
||||
self,
|
||||
label: str,
|
||||
*,
|
||||
index: int | None = None,
|
||||
key_type: str = "ed25519",
|
||||
user_id: str = "",
|
||||
notes: str = "",
|
||||
) -> int:
|
||||
_ = (index, key_type, user_id, notes)
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "pgp",
|
||||
"label": label,
|
||||
"private_key": "-----BEGIN PGP PRIVATE KEY BLOCK-----",
|
||||
"fingerprint": f"FPR-{entry_id}",
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_nostr_key(
|
||||
self, label: str, *, index: int | None = None, notes: str = ""
|
||||
) -> int:
|
||||
_ = (index, notes)
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "nostr",
|
||||
"label": label,
|
||||
"npub": f"npub{entry_id}",
|
||||
"nsec": f"nsec{entry_id}",
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_seed(
|
||||
self,
|
||||
label: str,
|
||||
*,
|
||||
index: int | None = None,
|
||||
words: int = 24,
|
||||
notes: str = "",
|
||||
) -> int:
|
||||
_ = (index, notes)
|
||||
word_count = max(12, int(words))
|
||||
phrase = " ".join(["abandon"] * (word_count - 1) + ["about"])
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "seed",
|
||||
"label": label,
|
||||
"seed_phrase": phrase,
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def add_managed_account(
|
||||
self, label: str, *, index: int | None = None, notes: str = ""
|
||||
) -> int:
|
||||
_ = (index, notes)
|
||||
entry_id = self._next_id()
|
||||
self._entries[entry_id] = {
|
||||
"id": entry_id,
|
||||
"kind": "managed_account",
|
||||
"label": label,
|
||||
"seed_phrase": "legal winner thank year wave sausage worth useful legal winner thank yellow",
|
||||
"archived": False,
|
||||
}
|
||||
return entry_id
|
||||
|
||||
def export_document_file(
|
||||
self,
|
||||
entry_id: int,
|
||||
output_path: str | Path | None = None,
|
||||
*,
|
||||
overwrite: bool = False,
|
||||
) -> Path:
|
||||
_ = overwrite
|
||||
entry = self._entries[int(entry_id)]
|
||||
kind = str(entry.get("kind", ""))
|
||||
if kind != "document":
|
||||
raise ValueError("Entry is not a document entry")
|
||||
name = str(entry.get("label", f"document-{entry_id}")).replace(" ", "_")
|
||||
ext = str(entry.get("file_type", "txt")).lstrip(".") or "txt"
|
||||
if output_path is None:
|
||||
dest = Path.cwd() / f"{name}.{ext}"
|
||||
else:
|
||||
raw = Path(output_path)
|
||||
if raw.suffix:
|
||||
dest = raw
|
||||
else:
|
||||
dest = raw / f"{name}.{ext}"
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
dest.write_text(str(entry.get("content", "")), encoding="utf-8")
|
||||
return dest
|
||||
|
||||
def modify_entry(self, entry_id: int, **kwargs) -> None:
|
||||
if self.fail_modify:
|
||||
raise RuntimeError("modify failed")
|
||||
@@ -289,6 +485,61 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
|
||||
app._run_palette_command("link-add no")
|
||||
app._run_palette_command("link-rm")
|
||||
app._run_palette_command("link-rm no")
|
||||
app._run_palette_command("add-password")
|
||||
app._run_palette_command("add-password Site nope")
|
||||
app._run_palette_command("add-totp")
|
||||
app._run_palette_command("add-totp Auth bad")
|
||||
app._run_palette_command("add-key-value")
|
||||
app._run_palette_command("add-document")
|
||||
app._run_palette_command("add-ssh")
|
||||
app._run_palette_command("add-ssh Host nope")
|
||||
app._run_palette_command("add-pgp")
|
||||
app._run_palette_command("add-pgp Pgp nope")
|
||||
app._run_palette_command("add-nostr")
|
||||
app._run_palette_command("add-nostr Nostr nope")
|
||||
app._run_palette_command("add-seed")
|
||||
app._run_palette_command("add-seed Seed nope")
|
||||
app._run_palette_command("add-managed-account")
|
||||
app._run_palette_command("add-managed-account Managed nope")
|
||||
app._run_palette_command("notes-set")
|
||||
app._run_palette_command("notes-clear nope")
|
||||
app._run_palette_command("tag-add")
|
||||
app._run_palette_command("tag-rm")
|
||||
app._run_palette_command("tags-set")
|
||||
app._run_palette_command("tags-clear nope")
|
||||
app._run_palette_command("field-add")
|
||||
app._run_palette_command("field-rm")
|
||||
app._run_palette_command("set-field")
|
||||
app._run_palette_command("clear-field")
|
||||
app._run_palette_command("2fa-board nope")
|
||||
app._run_palette_command("2fa-hide nope")
|
||||
app._run_palette_command("2fa-refresh nope")
|
||||
app._run_palette_command("2fa-copy")
|
||||
app._run_palette_command("2fa-copy nope")
|
||||
app._run_palette_command("profiles-list")
|
||||
app._run_palette_command("profile-switch")
|
||||
app._run_palette_command("profile-add")
|
||||
app._run_palette_command("profile-remove")
|
||||
app._run_palette_command("profile-rename")
|
||||
app._run_palette_command("setting-secret on")
|
||||
app._run_palette_command("setting-offline on")
|
||||
app._run_palette_command("setting-quick-unlock on")
|
||||
app._run_palette_command("setting-timeout 300")
|
||||
app._run_palette_command("setting-kdf-iterations 100000")
|
||||
app._run_palette_command("setting-kdf-mode pbkdf2")
|
||||
app._run_palette_command("relay-list")
|
||||
app._run_palette_command("relay-add wss://relay.example")
|
||||
app._run_palette_command("relay-rm 1")
|
||||
app._run_palette_command("relay-reset")
|
||||
app._run_palette_command("sync-now")
|
||||
app._run_palette_command("sync-bg")
|
||||
app._run_palette_command("checksum-verify")
|
||||
app._run_palette_command("checksum-update")
|
||||
app._run_palette_command("db-export /tmp/seedpass-db.enc")
|
||||
app._run_palette_command("db-import /tmp/seedpass-db.enc")
|
||||
app._run_palette_command("totp-export /tmp/seedpass-totp.json")
|
||||
app._run_palette_command("parent-seed-backup")
|
||||
app._run_palette_command("doc-export one two")
|
||||
app._run_palette_command("filter")
|
||||
app._run_palette_command("unknown-cmd")
|
||||
await pilot.pause()
|
||||
@@ -301,6 +552,29 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
|
||||
app._run_palette_command("page 2")
|
||||
app._run_palette_command("open 1")
|
||||
app._run_palette_command("jump 2")
|
||||
app._run_palette_command("add-password NewLogin 18 user https://seedpass.dev")
|
||||
app._run_palette_command("add-totp NewAuth 30 6 JBSWY3DPEHPK3PXP")
|
||||
app._run_palette_command("add-key-value Env API_KEY value")
|
||||
app._run_palette_command("add-document Runbook md body")
|
||||
app._run_palette_command("add-ssh Host 10")
|
||||
app._run_palette_command("add-pgp Pgp 11 ed25519 [email protected]")
|
||||
app._run_palette_command("add-nostr Nostr 12")
|
||||
app._run_palette_command("add-seed Seed 24 13")
|
||||
app._run_palette_command("add-managed-account Managed 14")
|
||||
app._run_palette_command('notes-set "matrix note"')
|
||||
app._run_palette_command("tag-add matrix")
|
||||
app._run_palette_command("tag-rm matrix")
|
||||
app._run_palette_command("tags-set m1, m2")
|
||||
app._run_palette_command("tags-clear")
|
||||
app._run_palette_command("field-add scope admin hidden")
|
||||
app._run_palette_command("field-rm scope")
|
||||
app._run_palette_command("set-field notes matrix-updated")
|
||||
app._run_palette_command("clear-field notes")
|
||||
app._run_palette_command("2fa-board")
|
||||
app._run_palette_command("2fa-copy 2")
|
||||
app._run_palette_command("2fa-refresh")
|
||||
app._run_palette_command("2fa-hide")
|
||||
app._run_palette_command("doc-export /tmp/seedpass-matrix-export")
|
||||
await pilot.pause()
|
||||
|
||||
app._run_palette_command("open 2")
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user