diff --git a/packages/coding-agent/src/modes/neo-mode.ts b/packages/coding-agent/src/modes/neo-mode.ts index 1b7648f62..c5570e44e 100644 --- a/packages/coding-agent/src/modes/neo-mode.ts +++ b/packages/coding-agent/src/modes/neo-mode.ts @@ -106,9 +106,9 @@ export interface RunNeoModeOptions { /** * Launch the Rust TUI binary with stdio inherited so it owns the TTY. - * The Rust binary is expected to spawn `senpi --mode rpc` as its own child - * for the agent backend (T6 wires that up; until then the binary renders - * the demo state). + * The Rust binary spawns `senpi --mode rpc` as its own child for the agent + * backend. If no backend is configured, it runs offline (demo mode or an + * empty idle session). */ export async function runNeoMode(options: RunNeoModeOptions): Promise { const located = resolveBinaryPath(); diff --git a/packages/neo-tui/src/components/chat.rs b/packages/neo-tui/src/components/chat.rs index 415722951..9b4854b8b 100644 --- a/packages/neo-tui/src/components/chat.rs +++ b/packages/neo-tui/src/components/chat.rs @@ -538,6 +538,6 @@ pub fn sample() -> ChatState { }), }); state.message_ids.push(assistant.saturating_add(1)); - state.push_assistant("Mapped 12 module files. theme = JSON-driven semantic tokens. layout = pure compute. components = header / chat / input / footer. rpc = JSONL subprocess client (in progress). compositor = layered Component dispatch (stub). anim = spinners + scanners (stub).".into()); + state.push_assistant("Mapped the native TUI modules. theme = JSON-driven semantic tokens. layout = pure compute. components = header / chat / input / footer / overlays. rpc = JSONL subprocess client. compositor = Component event/render helpers. anim = spinners + scanners.".into()); state } diff --git a/packages/neo-tui/src/components/dialog.rs b/packages/neo-tui/src/components/dialog.rs index 9650c1f04..903844a34 100644 --- a/packages/neo-tui/src/components/dialog.rs +++ b/packages/neo-tui/src/components/dialog.rs @@ -1,2 +1,5 @@ -//! Dialog stack: command palette, model/theme/session pickers, help. -//! Implementation lands in T14. +//! Shared dialog component placeholder. +//! +//! The active modal UI lives in [`crate::overlay`]. This module is kept +//! for future reusable dialog chrome once multiple overlays share a +//! common component. diff --git a/packages/neo-tui/src/lib.rs b/packages/neo-tui/src/lib.rs index 06e846633..84dbc9069 100644 --- a/packages/neo-tui/src/lib.rs +++ b/packages/neo-tui/src/lib.rs @@ -1,7 +1,7 @@ //! `senpi-neo-tui` - native Rust + ratatui TUI for senpi. //! //! See [`README`](https://github.com/code-yeongyu/senpi/blob/main/packages/neo-tui/README.md) -//! and `plans/neo-tui.md` for the architecture and module layout. +//! and `packages/neo-tui/AGENTS.md` for the architecture and module layout. //! //! The crate exposes a thin library surface so integration tests and the //! offline faux backend can drive individual subsystems without diff --git a/packages/neo-tui/src/overlay/mod.rs b/packages/neo-tui/src/overlay/mod.rs index 7fb7c3635..ecf18c780 100644 --- a/packages/neo-tui/src/overlay/mod.rs +++ b/packages/neo-tui/src/overlay/mod.rs @@ -2,19 +2,21 @@ //! //! Mirrors the opencode dialog pattern (semi-transparent backdrop + //! centered floating panel) and the DeepSeek-TUI `ViewStack` pattern -//! (only the top overlay receives events). Three overlays ship: +//! (only the active overlay receives events). Shipped overlays are: //! //! - [`HelpOverlay`]: scrollable list of every keymap binding + //! `app.*`/`tui.*`/`neo.*` action, auto-generated from the resolved //! keymap so it never drifts. //! - [`SlashOverlay`]: grok-CLI-style `/` menu opened when `/` is -//! typed into an empty input buffer. +//! typed into an empty input buffer, or when `neo.slash.open` is +//! explicitly dispatched. //! - [`PaletteOverlay`]: opencode-style fuzzy command palette (Alt+P) //! matched via `nucleo-matcher`. +//! - [`ModelPickerOverlay`]: model selector. +//! - [`ThemePickerOverlay`]: bundled-theme selector. //! -//! All three render via `Clear` + a bordered `Block` centered on the -//! frame area. The compositor (in [`App`]) renders the chat scene -//! first, then the overlay on top. +//! Every overlay renders via `Clear` + a bordered `Block` centered on +//! the frame area after the base chat scene has been drawn. use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use nucleo_matcher::pattern::{CaseMatching, Normalization, Pattern}; diff --git a/packages/neo-tui/src/rpc/envelope.rs b/packages/neo-tui/src/rpc/envelope.rs index 71532038f..2154eafc8 100644 --- a/packages/neo-tui/src/rpc/envelope.rs +++ b/packages/neo-tui/src/rpc/envelope.rs @@ -1,9 +1,9 @@ //! Wire-level envelope for senpi `--mode rpc` traffic. //! //! See `packages/coding-agent/docs/rpc.md` for the canonical protocol. -//! -//! The full event taxonomy lands in T6. This stub captures the two top-level -//! kinds (`response`, `event`) plus the shapes needed by the RED tests in T2. +//! This module captures the two top-level inbound kinds (`response`, `event`). +//! Event payloads are decoded by [`crate::rpc::event`] after the envelope is +//! parsed. use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -45,8 +45,8 @@ pub enum EnvelopeError { Json(#[from] serde_json::Error), } -/// Parse a single JSONL line into an [`Envelope`]. T6 expands this with -/// streaming-friendly partial-line buffering. +/// Parse a single JSONL line into an [`Envelope`]. The transport layer owns +/// line buffering; this function parses one complete JSONL frame. pub fn parse_line(line: &str) -> Result { let trimmed = line.trim_end_matches(['\r', '\n']); let value: Envelope = serde_json::from_str(trimmed)?; diff --git a/packages/neo-tui/src/theme/mod.rs b/packages/neo-tui/src/theme/mod.rs index 9364d082a..52414cd55 100644 --- a/packages/neo-tui/src/theme/mod.rs +++ b/packages/neo-tui/src/theme/mod.rs @@ -251,8 +251,8 @@ impl ResolvedTheme { /// Look up a token. Returns `Color::Reset` for unknown tokens; this is /// deliberate so callers can render even on incomplete themes without - /// panicking. T7 logs missing tokens via `tracing` once the logger - /// initializes (T16). + /// panicking. Tests assert that bundled themes resolve every token in + /// [`Token::ALL`]. pub fn token(&self, token: Token) -> Color { self.colors.get(&token).copied().unwrap_or(Color::Reset) } diff --git a/packages/neo-tui/tests/app_loop.rs b/packages/neo-tui/tests/app_loop.rs index 67d19cc0d..021690f47 100644 --- a/packages/neo-tui/tests/app_loop.rs +++ b/packages/neo-tui/tests/app_loop.rs @@ -683,8 +683,8 @@ fn slash_overlay_filter_then_enter_dispatches_app_exit() { app.handle_key(ev(KeyCode::Char(ch), KeyModifiers::NONE)); } let action = app.handle_key(ev(KeyCode::Enter, KeyModifiers::NONE)); - // `/quit` maps to `app.exit`; with an empty buffer this resolves - // to AppAction::Quit per the legacy Ctrl+D semantics. + // `/quit` maps to `app.exit`; explicit exit actions always quit, + // regardless of the composer buffer state. assert_eq!(action, AppAction::Quit); } diff --git a/packages/neo-tui/tests/keymap.rs b/packages/neo-tui/tests/keymap.rs index 8e2c137da..fc2065e9e 100644 --- a/packages/neo-tui/tests/keymap.rs +++ b/packages/neo-tui/tests/keymap.rs @@ -13,7 +13,9 @@ //! neo-tui-keymap-parity.test.ts`) re-validates the same table against //! the actual `KEYBINDINGS` constant so we catch drift on both sides. //! -//! Strict `Action`-enum validation arrives with T8. +//! The Rust parser intentionally accepts arbitrary action ids; runtime +//! dispatch owns the distinction between implemented, context-scoped, and +//! visibly unimplemented actions. use senpi_neo_tui::keymap; @@ -210,9 +212,9 @@ fn default_keymap_binds_shift_enter_to_legacy_input_newline() { } #[test] -fn accepts_arbitrary_keys_until_t8_strictens() { - // Today the parser round-trips arbitrary string keys. T8 will reject - // unknown actions at merge time; that test lives alongside T8. +fn accepts_arbitrary_action_ids() { + // The parser round-trips arbitrary string keys so the JSON can mirror + // legacy senpi ids plus neo-only ids without a generated action enum. let bad = r#"{ "bindings": { "nonsense.action": ["alt+x"] } }"#; let spec = keymap::parse(bad).expect("parser accepts unknown action names today"); assert!(spec.bindings.contains_key("nonsense.action")); diff --git a/packages/neo-tui/tests/rpc_envelope.rs b/packages/neo-tui/tests/rpc_envelope.rs index ae8a99e68..b366a9a5b 100644 --- a/packages/neo-tui/tests/rpc_envelope.rs +++ b/packages/neo-tui/tests/rpc_envelope.rs @@ -1,5 +1,4 @@ -//! Contract tests for the RPC envelope. RED until T6 fills in the full -//! event taxonomy; today we lock the response / event discriminator. +//! Contract tests for the RPC envelope parser and response/event discriminator. use senpi_neo_tui::rpc::envelope::{self, Envelope}; diff --git a/packages/neo-tui/tests/scaffold.rs b/packages/neo-tui/tests/scaffold.rs index 1090bc90e..6b91c9b6c 100644 --- a/packages/neo-tui/tests/scaffold.rs +++ b/packages/neo-tui/tests/scaffold.rs @@ -1,7 +1,7 @@ //! Scaffold-level tests that lock the public surface in place. //! -//! These tests start as RED (they call `parse` stubs that error out) and -//! flip to GREEN as T6/T7/T8 land. +//! These tests lock the bundled theme/keymap assets and the initial RPC +//! envelope contract. use senpi_neo_tui::{ DEFAULT_DARK_THEME_JSON, DEFAULT_KEYMAP_JSON, VERSION, diff --git a/packages/neo-tui/tests/theme.rs b/packages/neo-tui/tests/theme.rs index 5de54fc31..3daf422bf 100644 --- a/packages/neo-tui/tests/theme.rs +++ b/packages/neo-tui/tests/theme.rs @@ -1,8 +1,7 @@ //! Contract tests for the theme system. //! -//! T2 locks the parse-time and resolve-error shape. T7 ships the full -//! resolver; this file gains additional happy-path assertions as the -//! resolver covers more tokens. +//! These tests lock parse errors, resolve errors, and bundled token +//! coverage for the JSON-driven theme system. use ratatui::style::Color; use senpi_neo_tui::theme::{self, Token};