1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Terminal user interface for evault.
//!
//! This crate ships the interactive front-end. It is intentionally
//! decoupled from the storage layer: the dashboard reads rows from a
//! caller-supplied [`VarProvider`], so any backend
//! ([`evault_core::service::RegistryService`], an in-memory test stub,
//! or a remote facade) can be wired in without the TUI knowing.
//!
//! # Architecture
//!
//! Three layers compose the UI:
//!
//! 1. [`Action`] — high-level intent derived from a key event.
//! Translating keys to actions in one place keeps view code free of
//! crossterm internals and gives a single auditable mapping table.
//! 2. [`AppState`] — observable, deterministic state machine. Pure
//! functions of `(state, action) -> state'`; no I/O. Drives all
//! rendering.
//! 3. [`run_tui`] — terminal lifecycle (raw mode, alternate screen,
//! panic-safe restore) plus the event loop that pumps keys into the
//! state and renders the result.
//!
//! # Examples
//!
//! Drive the state machine programmatically (no terminal needed):
//!
//! ```
//! use evault_core::model::VarId;
//! use evault_tui::{Action, AppState, ProviderError, VarProvider, VarSummary};
//! use secrecy::SecretString;
//!
//! struct Empty;
//! impl VarProvider for Empty {
//! fn list(&self) -> Result<Vec<VarSummary>, ProviderError> { Ok(Vec::new()) }
//! fn get_value(&self, _id: VarId) -> Result<Option<SecretString>, ProviderError> {
//! unimplemented!()
//! }
//! }
//!
//! let mut app = AppState::new();
//! app.refresh(&Empty).unwrap();
//! app.apply(Action::ToggleHelp);
//! assert!(app.help_visible());
//! app.apply(Action::Dismiss);
//! assert!(!app.help_visible());
//! ```
//!
//! [`run_tui`] additionally requires its backend to implement
//! [`VarMutator`] for the delete flow:
//!
//! ```no_run
//! use std::path::PathBuf;
//! use evault_core::model::VarId;
//! use evault_tui::{run_tui, ProviderError, VarDraft, VarMutator, VarProvider, VarSummary};
//! use secrecy::SecretString;
//!
//! struct EmptyBackend;
//! impl VarProvider for EmptyBackend {
//! fn list(&self) -> Result<Vec<VarSummary>, ProviderError> { Ok(Vec::new()) }
//! fn get_value(&self, _id: VarId) -> Result<Option<SecretString>, ProviderError> {
//! Ok(None)
//! }
//! }
//! impl VarMutator for EmptyBackend {
//! fn delete(&self, _id: VarId) -> Result<(), ProviderError> { Ok(()) }
//! fn create(&self, _draft: VarDraft) -> Result<VarId, ProviderError> {
//! Ok(VarId::new_v4())
//! }
//! fn update_value(&self, _id: VarId, _value: SecretString) -> Result<(), ProviderError> {
//! Ok(())
//! }
//! fn link_to_project(
//! &self,
//! _var_id: VarId,
//! _var_name: String,
//! _project_path: PathBuf,
//! _profile: String,
//! _materialize: bool,
//! ) -> Result<(), ProviderError> { Ok(()) }
//! fn run_in_project(
//! &self,
//! _project_path: PathBuf,
//! _profile: String,
//! _program: String,
//! _args: Vec<String>,
//! ) -> Result<Option<i32>, ProviderError> { Ok(Some(0)) }
//! }
//! run_tui(EmptyBackend).unwrap();
//! ```
pub use ;
pub use TuiError;
pub use Action;
pub use ;
pub use run_tui;
pub use Theme;