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