feat(maestro): add [tui] config section + crossterm workspace dep (NAP-277)

Schema-only — adds `TuiSection` with `leader`, `chord_timeout_ms`, and per-mode
keymap override maps; pulls crossterm into the workspace deps so the next
commit's input layer can consume it.

Field set matches `docs/superpowers/specs/2026-05-28-maestro-tui-design.md` §3.9.
This commit is contained in:
2026-05-28 19:47:05 +00:00
parent 92396a6bfe
commit ed128ec200
3 changed files with 38 additions and 0 deletions
+35
View File
@@ -27,6 +27,7 @@
//! URL derivation helper. Each binary applies its own env-var precedence and
//! defaults on top of [`MaestroConfigFile`].
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::path::{Path, PathBuf};
@@ -76,6 +77,8 @@ pub struct MaestroConfigFile {
pub registry: RegistrySection,
#[serde(default, skip_serializing_if = "WebSection::is_empty")]
pub web: WebSection,
#[serde(default, skip_serializing_if = "TuiSection::is_empty")]
pub tui: TuiSection,
}
/// Fields consumed by more than one binary (CLI + daemon, daemon + registry).
@@ -228,6 +231,38 @@ impl WebSection {
}
}
/// `[tui]` — `maestro tui` settings (NAP-275 epic, phase 2 NAP-277).
///
/// Leader key, chord timeout, and per-mode keymap overrides. The TUI ships
/// with a default keymap; entries in `keymap_normal` / `keymap_insert` are
/// applied **over** the defaults, so a partial override is enough.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TuiSection {
/// Canonical key form, e.g. `"ctrl-a"`. Default `ctrl-a`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub leader: Option<String>,
/// Maximum interval between chord keys before a held prefix is dropped.
/// Default `1000`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chord_timeout_ms: Option<u64>,
/// Normal-mode bindings: canonical input → action string. Applied over
/// the shipped defaults.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub keymap_normal: BTreeMap<String, String>,
/// Insert-mode bindings, same semantics as `keymap_normal`.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub keymap_insert: BTreeMap<String, String>,
}
impl TuiSection {
pub fn is_empty(&self) -> bool {
self.leader.is_none()
&& self.chord_timeout_ms.is_none()
&& self.keymap_normal.is_empty()
&& self.keymap_insert.is_empty()
}
}
impl MaestroConfigFile {
/// Strict load — error with [`ConfigError::MissingFile`] if absent.
pub fn load(path: &Path) -> Result<Self, ConfigError> {
+2
View File
@@ -9,4 +9,6 @@ description = "Terminal UI client over the local maestro daemon's `/api/*` surfa
[dependencies]
clap.workspace = true
crossterm.workspace = true
maestro-config.workspace = true
thiserror.workspace = true