@@ -28,6 +28,7 @@ pub const KNOWN_KEYS: &[&str] = &[
2828 "agent.idle_lock_secs" ,
2929 "agent.session_keyring" ,
3030 "sync.interval_secs" ,
31+ "ui.reduced_motion" ,
3132] ;
3233
3334/// Accepted values for `clipboard.backend`.
@@ -45,6 +46,8 @@ pub struct Config {
4546 pub agent : AgentCfg ,
4647 /// Background-sync settings.
4748 pub sync : SyncCfg ,
49+ /// TUI rendering preferences.
50+ pub ui : UiCfg ,
4851 /// Registered account profile (written by `vault register`). Skipped from
4952 /// the file until something is set, so an unregistered config carries no
5053 /// empty `[account]` table.
@@ -86,6 +89,17 @@ pub struct SyncCfg {
8689 pub interval_secs : Option < u64 > ,
8790}
8891
92+ /// `[ui]` table — TUI-only rendering preferences (the agent never renders, so
93+ /// these are read by `vault-tui` directly, not relayed via `agent_args`).
94+ #[ derive( Clone , Debug , Default , PartialEq , Eq , Deserialize , Serialize ) ]
95+ #[ serde( default , deny_unknown_fields) ]
96+ pub struct UiCfg {
97+ /// Suppress animated TUI elements (spinner / lock-countdown) for
98+ /// accessibility. Reserved: the TUI has no animations yet, so this records
99+ /// the preference for when they land.
100+ pub reduced_motion : Option < bool > ,
101+ }
102+
89103/// `[account]` table — the registered account, written by `vault register`
90104/// and read by `login`/`unlock` to default `server`/`email`/`device_id`.
91105#[ derive( Clone , Debug , Default , PartialEq , Eq , Deserialize , Serialize ) ]
@@ -142,6 +156,12 @@ impl Config {
142156 self . sync . interval_secs
143157 }
144158
159+ /// Effective `ui.reduced_motion`, if set.
160+ #[ must_use]
161+ pub const fn reduced_motion ( & self ) -> Option < bool > {
162+ self . ui . reduced_motion
163+ }
164+
145165 /// The registered account profile.
146166 #[ must_use]
147167 pub const fn account ( & self ) -> & AccountCfg {
@@ -171,6 +191,7 @@ impl Config {
171191 "agent.idle_lock_secs" => Ok ( self . agent . idle_lock_secs . map ( |v| v. to_string ( ) ) ) ,
172192 "agent.session_keyring" => Ok ( self . agent . session_keyring . map ( |v| v. to_string ( ) ) ) ,
173193 "sync.interval_secs" => Ok ( self . sync . interval_secs . map ( |v| v. to_string ( ) ) ) ,
194+ "ui.reduced_motion" => Ok ( self . ui . reduced_motion . map ( |v| v. to_string ( ) ) ) ,
174195 other => Err ( other. to_owned ( ) ) ,
175196 }
176197 }
@@ -203,6 +224,10 @@ impl Config {
203224 self . sync . interval_secs = Some ( parse_u64 ( key, raw) ?) ;
204225 Ok ( ( ) )
205226 }
227+ "ui.reduced_motion" => {
228+ self . ui . reduced_motion = Some ( parse_bool ( key, raw) ?) ;
229+ Ok ( ( ) )
230+ }
206231 other => Err ( unknown_key ( other) ) ,
207232 }
208233 }
@@ -234,6 +259,10 @@ impl Config {
234259 self . sync . interval_secs = None ;
235260 Ok ( ( ) )
236261 }
262+ "ui.reduced_motion" => {
263+ self . ui . reduced_motion = None ;
264+ Ok ( ( ) )
265+ }
237266 other => Err ( unknown_key ( other) ) ,
238267 }
239268 }
@@ -524,6 +553,27 @@ mod tests {
524553 assert_eq ! ( c. clipboard_backend( ) , None ) ;
525554 }
526555
556+ #[ test]
557+ fn reduced_motion_round_trips_and_is_tui_only ( ) {
558+ let mut c = Config :: default ( ) ;
559+ assert_eq ! ( c. reduced_motion( ) , None ) ;
560+ c. set ( "ui.reduced_motion" , "true" ) . expect ( "set true" ) ;
561+ assert_eq ! ( c. reduced_motion( ) , Some ( true ) ) ;
562+ // It's a TUI-rendering preference — never relayed to the agent.
563+ assert ! (
564+ !agent_args( & c) . contains( & OsString :: from( "--reduced-motion" ) ) ,
565+ "ui.reduced_motion must not become an agent flag"
566+ ) ;
567+ // Survives a toml round-trip.
568+ let text = toml:: to_string_pretty ( & c) . expect ( "serialise" ) ;
569+ let back: Config = toml:: from_str ( & text) . expect ( "parse" ) ;
570+ assert_eq ! ( back. reduced_motion( ) , Some ( true ) ) ;
571+ // Non-boolean rejected; unset clears.
572+ assert ! ( c. set( "ui.reduced_motion" , "sometimes" ) . is_err( ) ) ;
573+ c. unset ( "ui.reduced_motion" ) . expect ( "unset" ) ;
574+ assert_eq ! ( c. reduced_motion( ) , None ) ;
575+ }
576+
527577 #[ test]
528578 fn sync_interval_round_trips ( ) {
529579 let mut c = Config :: default ( ) ;
0 commit comments