git:20260610.0b6976a to git:20260711.399c75f

36 added, 72 removed. Audit A to A.

# packages/coding-agent/src/modes/interactive
- The TUI mode. `interactive-mode.ts` is the orchestrator; `components/` holds the 37 in-tree TUI modules. Most fork-modified UI changes live here.
+ Interactive mode orchestrates the `senpi` TUI. `interactive-mode.ts` owns startup, session events, key dispatch, overlays, status, and command UI; `components/` owns rendering units.
## STRUCTURE
- ```
- interactive/
- ├── interactive-mode.ts # InteractiveMode class — startup, key dispatch, status, command bar
- ├── startup-tools.ts # fd/rg presence probe (non-blocking startup; fork-introduced)
- ├── working-status.ts # Working row text frames (shimmer animation, active-tool label)
- ├── session-info-format.ts # formatSessionInfo() — session name/cost/token stats text
- ├── theme/ # JSON theme files (copied to dist/theme/ on build)
- ├── assets/ # PNG splash/branding (copied to dist/assets/ on build)
- ├── components/ # 37 modules — see below
- └── changes.md # Fork-tracked: non-blocking tool discovery, favorite-model cycling, builtin display paths, disabled startup update checks
- ```
-
- ## COMPONENTS
-
- ```
- components/
- ├── armin.ts, daxnuts.ts, earendil-announcement.ts # Branded ASCII / mascot frames
- ├── assistant-message.ts # Streaming assistant message render — has memoization cache (TUI flicker budget)
- ├── tool-execution.ts # Tool-execution streaming render — has memoization cache
- ├── bash-execution.ts # Bash-tool dedicated render (real-time stdout panel)
- ├── user-message.ts / user-message-selector.ts # User input render + history selector
- ├── compaction-summary-message.ts # Inline compaction summary card
- ├── branch-summary-message.ts # Branch-summarization output card
- ├── skill-invocation-message.ts # Skill-call render
- ├── custom-message.ts # Extension-injected message renderer
- ├── diff.ts # In-line diff viewer
- ├── footer.ts # Status footer (model, cost, tokens, mode); tests in test/footer-*.test.ts
- ├── countdown-timer.ts # Compaction / startup timer
- ├── bordered-loader.ts / dynamic-border.ts # Animated borders
- ├── keybinding-hints.ts # Inline cheat-sheet
- ├── login-dialog.ts / oauth-selector.ts # Auth flows
- ├── model-selector.ts # Ctrl+P / `/model`
- ├── model-favorites.ts # Favorite-model id helpers (toggle/filter) used by the model selectors
- ├── favorite-models-selector.ts # `/favorite-models` (fork-introduced)
- ├── thinking-selector.ts # Thinking level toggle
- ├── trust-selector.ts # Project trust prompt (trust level + auto-updates options)
- ├── extension-selector.ts / extension-editor.ts / extension-input.ts # `/extensions` UI
- ├── settings-selector.ts # `/settings`
- ├── theme-selector.ts # `/theme`
- ├── show-images-selector.ts # Image attachment toggle
- ├── session-selector.ts / session-selector-search.ts # `/sessions`
- ├── config-selector.ts # Config picker (used during onboarding)
- ├── tree-selector.ts # File-tree picker (Tab completion)
- ├── custom-editor.ts # Multi-line editor (wraps tui editor-component)
- ├── visual-truncate.ts # Visual width truncation helpers
- └── index.ts # Component index
+ ```text
+ interactive-mode.ts Main lifecycle and event-to-UI coordinator
+ startup-tools.ts Non-blocking fd/rg capability probe
+ working-status.ts Animated working text/frames
+ session-info-format.ts Session/cost/token summaries
+ components/ Messages, tools, footer, selectors, dialogs, editor
+ theme/ JSON themes copied into builds
+ assets/ Branding assets copied into builds
+ changes.md Fork-specific interactive behavior
```
## WHERE TO LOOK
| Task | File |
- |------|------|
- | Startup behavior | `interactive-mode.ts` `init()` (fork: `startup-tools.ts` instead of awaiting fd/rg downloads) |
- | Streaming render perf | `components/assistant-message.ts` + `components/tool-execution.ts` memoization caches |
- | Working row animation | `interactive-mode.ts` `getWorkingIndicatorOptions()` + `working-status.ts`; default normal TUI uses `•/◦` frames and animated text |
- | Add a slash-command UI | corresponding component + `pi.registerCommand()` from a builtin extension |
- | Footer field | `components/footer.ts` (token format / width tested) |
- | Theme | `theme/<name>.json` + `theme-selector.ts` |
- | Favorite-model cycling (Ctrl+P) | `interactive-mode.ts` (cycles only `favoriteModels` setting; reports missing) |
- | Show extensions in startup banner | `interactive-mode.ts` `formatDisplayPath()` (renders `builtin/<name>`) |
+ |---|---|
+ | Startup and shutdown | `interactive-mode.ts` |
+ | Streaming assistant render | `components/assistant-message.ts` |
+ | Streaming tool render | `components/tool-execution.ts` |
+ | Working animation | `working-status.ts` and `interactive-mode.ts` |
+ | Footer/status | `components/footer.ts` |
+ | Model and favorites UI | selector components plus `interactive-mode.ts` |
+ | Theme behavior | `theme/` and `components/theme-selector.ts` |
- ## CONVENTIONS
+ ## INVARIANTS
- - **Component memoization for streaming**: `assistant-message.ts` and `tool-execution.ts` cache rendered trees keyed by stream state. Bypassing this causes the TUI flicker-budget test (`packages/tui/test/tui-render.test.ts`) to fail.
- - **All keybindings via `core/keybindings.ts`** — no inline keystroke literals.
- - **Theme files**: name them `<theme>.json` under `theme/`; `copy-assets` copies them into `dist/`.
- - **Working row**: preserve the default `•/◦` frames and `messageFormatter` suffix `Working (Xs • esc to interrupt)`. Plain `• Working` in a global install means the packaged `pi-tui` runtime is stale.
- - **Component file naming**: `<kebab-name>.ts`. Class default-exported; tag exported.
- - **Selectors return `Promise<T | null>`** — null = canceled.
+ - Preserve memoization in high-frequency assistant/tool renderers; bypassing it causes flicker and excess redraws.
+ - Startup tool discovery remains non-blocking. Version and package-update checks start asynchronously after the first frame; never await them during startup.
+ - Preserve the animated Working row, elapsed time, active-tool label, and interrupt hint.
+ - All keys route through `../../core/keybindings.ts`; no inline key literals.
+ - Components return styled text through TUI helpers. Arbitrary ANSI styling/output escapes are forbidden; preserve only established terminal-protocol markers such as the OSC 133 zones in `components/assistant-message.ts`.
+ - Themes remain JSON assets and are copied by package build scripts; do not symlink them.
+ - Selectors resolve `Promise<T | null>` where `null` means canceled.
## ANTI-PATTERNS
- - Removing the fork's non-blocking startup probe in `startup-tools.ts` — re-introduces several seconds of "fd/rg downloading…" before the first frame.
- - Re-enabling startup version checks (npm registry / package updates) — fork-disabled per `changes.md` to keep startup deterministic.
- - Collapsing the normal TUI Working row to a static bullet or publishing against upstream `@earendil-works/pi-tui` — removes the visible working animation in global installs.
- - Bypassing component memoization for streaming renderers — measurable flicker regression.
- - Embedding ANSI escapes directly — use `core/tools/render-utils.ts`.
+ - Recomputing complete message trees for every streaming delta.
+ - Blocking the first frame on tool downloads or update checks.
+ - Replacing the working animation with a static indicator.
+ - Writing directly to stdout from components.
+ - Adding UI behavior without width, theme, and cancellation states.
- ## NOTES
+ ## VALIDATION
- - The fork's `changes.md` lists interactive TUI deltas including normal Working animation, non-blocking startup tools, favorite-model cycling, builtin display paths, and disabled startup update checks. Preserve them on upstream rebases.
- - Splash assets (`assets/*.png`) are copied to dist by `copy-assets` script; do not symlink.
- - The `armin`, `daxnuts`, and `earendil-announcement` components are branded greeters — keep them harmlessly hidden behind branded settings.
+ - Run focused component/interactive tests from `packages/coding-agent`.
+ - Run `packages/tui/test/tui-render.test.ts` when render frequency or memoization changes.
+ - Every UI change requires root `npm run check`, `senpi-qa` TUI smoke evidence, and visual inspection across relevant terminal sizes.
+ - Record fork-visible changes in `changes.md` and preserve them during upstream merges.