Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/coding-agent/src/modes/neo-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> {
const located = resolveBinaryPath();
Expand Down
2 changes: 1 addition & 1 deletion packages/neo-tui/src/components/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
7 changes: 5 additions & 2 deletions packages/neo-tui/src/components/dialog.rs
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion packages/neo-tui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down
12 changes: 7 additions & 5 deletions packages/neo-tui/src/overlay/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
10 changes: 5 additions & 5 deletions packages/neo-tui/src/rpc/envelope.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Envelope, EnvelopeError> {
let trimmed = line.trim_end_matches(['\r', '\n']);
let value: Envelope = serde_json::from_str(trimmed)?;
Expand Down
4 changes: 2 additions & 2 deletions packages/neo-tui/src/theme/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/neo-tui/tests/app_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
10 changes: 6 additions & 4 deletions packages/neo-tui/tests/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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"));
Expand Down
3 changes: 1 addition & 2 deletions packages/neo-tui/tests/rpc_envelope.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down
4 changes: 2 additions & 2 deletions packages/neo-tui/tests/scaffold.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
5 changes: 2 additions & 3 deletions packages/neo-tui/tests/theme.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down