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
46 changes: 19 additions & 27 deletions packages/neo-tui/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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-<platform>-<arch>` 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-<platform>-<arch>` 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.
18 changes: 10 additions & 8 deletions packages/neo-tui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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` |
Expand All @@ -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.

Expand All @@ -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
Expand All @@ -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

Expand Down
57 changes: 57 additions & 0 deletions packages/neo-tui/docs/keymap-spec.md
Original file line number Diff line number Diff line change
@@ -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`.
54 changes: 54 additions & 0 deletions packages/neo-tui/docs/theme-spec.md
Original file line number Diff line number Diff line change
@@ -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`.