From 8f15dec296aed5c5b0e03a8ac324f8f3730786b0 Mon Sep 17 00:00:00 2001 From: changeroa Date: Mon, 25 May 2026 15:42:24 +0900 Subject: [PATCH] Document current neo TUI specs The Rust native TUI docs referenced spec files that were never checked in and still described copied dist assets, nextest-only testing, ColorSupport detection, and leader dispatch that do not match the implementation. Add the missing specs and update the package README/AGENTS guidance to reflect the current parser and build behavior.\n\nConstraint: Keep this PR documentation-only; do not add new runtime features for parsed-but-unused metadata.\nRejected: Implement leader-key dispatch and ColorSupport detection | out of scope for correcting documentation drift.\nConfidence: high\nScope-risk: narrow\nTested: git diff --check\nNot-tested: Runtime tests; documentation-only change --- packages/neo-tui/AGENTS.md | 46 ++++++++++------------ packages/neo-tui/README.md | 18 +++++---- packages/neo-tui/docs/keymap-spec.md | 57 ++++++++++++++++++++++++++++ packages/neo-tui/docs/theme-spec.md | 54 ++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 packages/neo-tui/docs/keymap-spec.md create mode 100644 packages/neo-tui/docs/theme-spec.md diff --git a/packages/neo-tui/AGENTS.md b/packages/neo-tui/AGENTS.md index 0c7657ff8..7f4f0619e 100644 --- a/packages/neo-tui/AGENTS.md +++ b/packages/neo-tui/AGENTS.md @@ -16,37 +16,31 @@ packages/neo-tui/ │ ├── themes/*.json # bundled themes │ └── keymaps/default.json # default keybindings ├── scripts/ -│ ├── build-binary.mjs # builds Rust + copies into coding-agent/dist -│ ├── qa.sh # tmux QA harness -│ └── qa-scenarios.json +│ ├── build-binary.mjs # builds/stages the release TUI binary +│ ├── capture-screenshots.sh # tmux + aha + Chrome screenshot capture +│ └── capture-theme-showcase.sh # bundled-theme screenshot matrix ├── src/ │ ├── main.rs # binary entry │ ├── lib.rs │ ├── app/ # run loop, state, action channel │ ├── rpc/ # RPC client (subprocess + JSONL codec) -│ ├── theme/ # JSON loader, tokens, ColorSupport -│ ├── keymap/ # configurable bindings + leader sequences +│ ├── theme/ # JSON loader, tokens, bundled theme registry +│ ├── keymap/ # configurable single-key bindings │ ├── layout/ # pure layout compute -│ ├── compositor/ # layered Component dispatch -│ ├── components/ # chat, input, header, footer, dialogs +│ ├── compositor/ # Component trait + event/render helpers +│ ├── components/ # chat, input, header, footer, lists, dialogs │ ├── anim/ # spinners, scanners, pulses │ ├── term/ # capability detection + OSC 52 clipboard │ └── bin/ │ └── senpi-neo-faux.rs # faux RPC backend for offline QA └── tests/ - ├── theme.rs - ├── keymap.rs - ├── rpc_envelope.rs - ├── rpc_client.rs - ├── layout.rs - ├── compositor.rs - ├── chat_snapshot.rs - ├── input_snapshot.rs - ├── fixtures/ - │ ├── themes/*.json - │ ├── keymaps/*.json - │ └── rpc/*.jsonl - └── snapshots/ + ├── app_loop.rs + ├── chat_view.rs + ├── editor.rs + ├── keymap*.rs + ├── overlay_pickers.rs + ├── rpc_*.rs + └── component/theme/text/layout integration tests ``` ## RULES @@ -64,11 +58,11 @@ packages/neo-tui/ ## TESTING ```bash -# Fast unit + snapshot tests -cargo nextest run --package senpi-neo-tui +# Fast Rust test suite +cargo test --package senpi-neo-tui -# Update snapshots after intentional changes: -INSTA_UPDATE=always cargo nextest run --package senpi-neo-tui +# Optional if cargo-nextest is installed: +# cargo nextest run --package senpi-neo-tui # Lint gate cargo clippy --package senpi-neo-tui --all-targets -- -D warnings @@ -92,7 +86,5 @@ cargo fmt --package senpi-neo-tui -- --check - `packages/coding-agent/src/cli/args.ts` parses `--neo`. - `packages/coding-agent/src/main.ts` dispatches to `runNeoMode`. - `packages/coding-agent/src/modes/neo-mode.ts` spawns `senpi-neo-tui`. -- `packages/coding-agent/dist/neo-tui-bin/senpi-neo-tui--` is the installed binary. -- `packages/coding-agent/dist/neo-tui-themes/*.json` are bundled themes. -- `packages/coding-agent/dist/neo-tui-keymap/default.json` is the default keymap. +- `packages/coding-agent/dist/neo-tui-bin/senpi-neo-tui--` is the only staged artifact; bundled themes and the default keymap are embedded in the Rust binary via `include_str!`. - The Rust binary spawns `senpi --mode rpc` as a child to drive the agent. diff --git a/packages/neo-tui/README.md b/packages/neo-tui/README.md index 8ce730587..2049aa41b 100644 --- a/packages/neo-tui/README.md +++ b/packages/neo-tui/README.md @@ -23,10 +23,12 @@ The crate ships two bins (`senpi-neo-tui`, the TUI itself; `senpi-neo-faux`, the cargo run --release --package senpi-neo-tui --bin senpi-neo-tui -- \ --demo --demo-seconds 5 -# Through the Node CLI (resolves the binary out of target/release): +# Through the Node CLI after `npm run build` has produced dist/cli.js +# (SENPI_NEO_TUI_DEV=1 makes it resolve target/release first): SENPI_NEO_TUI_DEV=1 node packages/coding-agent/dist/cli.js --neo -# Offline QA with the faux backend: +# Offline QA with the faux backend (build both bins first): +cargo build --release --package senpi-neo-tui --bins cargo run --release --package senpi-neo-tui --bin senpi-neo-tui -- \ --backend-bin ./target/release/senpi-neo-faux ``` @@ -62,7 +64,7 @@ Configurable in [`assets/keymaps/default.json`](./assets/keymaps/default.json). |--------|---------| | Insert newline in the composer | `Shift+Enter` (works inside tmux via xterm modifyOtherKeys mode 2) | | Submit the message | `Enter` | -| Recall previous / next prompt | `Up` / `Down` (when the composer is empty or on the first/last line) | +| Recall previous / next prompt | `Up` / `Down` (when the composer is empty, or while walking an active history cursor); otherwise moves the editor cursor | | Open slash command menu | `/` then type | | Open `@path` autocomplete | type `@` | | Cycle thinking level | `Shift+Tab` | @@ -75,7 +77,7 @@ Configurable in [`assets/keymaps/default.json`](./assets/keymaps/default.json). | Toggle animations | `Alt+A` | | Mouse wheel | scrolls the chat viewport | | Cancel current run | `Esc` | -| Quit | `Ctrl+D` | +| Delete forward / quit | `Ctrl+D` deletes forward while the composer has content; with an empty composer it quits. Explicit `app.exit` actions such as `/quit` always quit. | Full registry lives under the `bindings` map in the keymap JSON — every key is reassignable. @@ -96,8 +98,8 @@ Module layout matches the `Layout` section below; per-module roles and the testi - `app/` - main loop, state, action channel, RPC bridge - `rpc/` - subprocess RPC client speaking senpi `--mode rpc` (JSONL), with `Inbound::{Error, Disconnected, ParseError}` surfacing -- `theme/` - JSON theme loader, semantic tokens, `ColorSupport` detection -- `keymap/` - configurable bindings + leader-key sequences +- `theme/` - JSON theme loader, semantic tokens, bundled theme registry +- `keymap/` - configurable single-key bindings; `leader` metadata is parsed but not dispatched - `layout/` - pure layout computation - `compositor/` - layered `Component` dispatch + focus stack - `components/` - chat, input, header, footer, markdown, autocomplete, select_list, settings_list @@ -109,12 +111,12 @@ Module layout matches the `Layout` section below; per-module roles and the testi ## Tests ```bash -cargo nextest run --package senpi-neo-tui +cargo test --package senpi-neo-tui cargo clippy --package senpi-neo-tui --all-targets -- -D warnings cargo fmt --package senpi-neo-tui -- --check ``` -Snapshot updates: `INSTA_UPDATE=always cargo nextest run --package senpi-neo-tui`. +If `cargo-nextest` is installed locally, `cargo nextest run --package senpi-neo-tui` is also supported. There is no checked-in insta snapshot suite today. ## License diff --git a/packages/neo-tui/docs/keymap-spec.md b/packages/neo-tui/docs/keymap-spec.md new file mode 100644 index 000000000..f96eebe66 --- /dev/null +++ b/packages/neo-tui/docs/keymap-spec.md @@ -0,0 +1,57 @@ +# senpi-neo-tui keymap spec + +The default keymap is `packages/neo-tui/assets/keymaps/default.json` and is +compiled into the Rust binary with `include_str!`. Runtime dispatch is +implemented in `packages/neo-tui/src/keymap/mod.rs` and `packages/neo-tui/src/app/mod.rs`. + +## Top-level shape + +```json +{ + "$schema": "https://senpi.dev/neo-tui-keymap.json", + "version": 1, + "leader": "space", + "leaderTimeoutMs": 500, + "bindings": { + "tui.input.submit": ["enter"], + "neo.palette.open": ["alt+p"] + } +} +``` + +Only `bindings` affects dispatch today. `leader` and `leaderTimeoutMs` are +accepted by the parser for forward compatibility, but multi-stroke leader +sequences are not implemented. + +## Binding ids + +Binding ids are plain action strings. Existing namespaces are: + +- `tui.editor.*` and `tui.input.*` for input/editor behavior. +- `tui.select.*` for overlays and select-list behavior. +- `app.*` for legacy senpi app actions routed through the backend or app state. +- `neo.*` for Rust-native additions such as theme picker, palette, help, and + local history navigation. + +Focus decides precedence. Input focus tries `tui.editor.*`, then `tui.input.*`, +then `app.*`; dialog focus tries `tui.select.*` first; `neo.*` is considered +last so it does not shadow the legacy contract. + +## Chord grammar + +A chord is one keypress, optionally prefixed by modifiers joined with `+`. +Modifier order is irrelevant and matching is case-insensitive. + +Supported modifiers: + +- `ctrl` +- `alt`, `meta`, `opt`, `option` +- `shift` +- `super`, `cmd`, `command`, `win` + +Supported named keys include `enter`/`return`, `esc`/`escape`, `tab`, +`backtab`, `space`, `backspace`/`bs`, `delete`/`del`, `home`, `end`, +`pageup`/`pgup`, `pagedown`/`pgdn`, arrows, `insert`/`ins`, and `f1` through +`f12`. Single-character keys such as `/`, `?`, `]`, or `-` are also valid. + +Examples: `ctrl+c`, `shift+tab`, `alt+enter`, `ctrl+]`, `ctrl+-`, `alt+p`. diff --git a/packages/neo-tui/docs/theme-spec.md b/packages/neo-tui/docs/theme-spec.md new file mode 100644 index 000000000..30875973d --- /dev/null +++ b/packages/neo-tui/docs/theme-spec.md @@ -0,0 +1,54 @@ +# senpi-neo-tui theme spec + +Theme files are JSON documents loaded by `packages/neo-tui/src/theme/mod.rs`. +Bundled themes are compiled into the Rust binary with `include_str!`; the build +script does not copy theme JSON into `packages/coding-agent/dist`. + +## Top-level shape + +```json +{ + "$schema": "https://senpi.dev/neo-tui-theme.json", + "name": "senpi-neo-dark", + "type": "dark", + "defs": { + "espresso": "#1A1B26" + }, + "tokens": { + "background": "espresso", + "primary": "#FF9E64" + }, + "options": { + "thinkingOpacityPercent": 60, + "useNerdFonts": false, + "supportsTrueColor": true + } +} +``` + +| Field | Required | Runtime behavior | +| --- | --- | --- | +| `name` | yes | Stored on `ResolvedTheme.name`; also used as the current theme id in picker state. | +| `type` | no | `dark` or `light`; defaults to `dark`. | +| `defs` | no | Named color aliases. Values must be hex colors. | +| `tokens` | yes | Semantic token map. Each value is either a key from `defs` or a direct hex color. | +| `options.thinkingOpacityPercent` | no | Clamped to `0..=100` and resolved to a `0.0..=1.0` opacity. Defaults to `60`. | +| `options.useNerdFonts` | no | Parsed for schema compatibility; not currently used by render paths. | +| `options.supportsTrueColor` | no | Parsed as a theme hint; terminal true-color detection is not implemented here. | + +## Tokens + +The renderer expects every member of `Token::ALL` from +`packages/neo-tui/src/theme/mod.rs`. Missing or unparseable tokens fail theme +resolution in tests; direct runtime lookups fall back to `Color::Reset` instead +of panicking. + +Token names are camelCase. The bundled dark theme is the canonical example: +`packages/neo-tui/assets/themes/senpi-neo-dark.json`. + +## Bundled opencode themes + +`packages/neo-tui/src/theme/registry.rs` derives the 15 bundled opencode themes +from their upstream palette JSON. Both flat ids (`dracula`) and namespaced ids +(`opencode/dracula`) are accepted by `--theme`; `--list-themes` prints the flat +ids plus `senpi-neo-dark`.