Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<style>
.terminal-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-top: 18px; }
@media (max-width: 1100px) { .terminal-grid { grid-template-columns: 1fr; } }
.terminal-card { cursor: pointer; }
.terminal { background: var(--color-background); border: 1px solid var(--color-border); border-radius: 10px; overflow: hidden; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.45; }
.terminal-top { padding: 8px 10px; border-bottom: 1px solid var(--color-border); color: var(--color-text-muted); }
.terminal-body { padding: 12px; min-height: 430px; }
.user-line { color: var(--color-accent); margin-bottom: 12px; }
.assistant { color: var(--color-text); margin-bottom: 12px; }
.dim { color: var(--color-text-muted); }
.tool { color: var(--color-text-muted); margin: 8px 0; }
.thinking { border-left: 2px solid var(--color-border); padding: 7px 0 7px 10px; margin: 10px 0; color: var(--color-text-muted); background: var(--color-surface); }
.thinking-title { color: var(--color-text); font-weight: 600; margin-bottom: 5px; }
.hidden { color: var(--color-text-muted); border-top: 1px dashed var(--color-border); margin-top: 6px; padding-top: 5px; }
.done { color: var(--color-text-muted); margin-top: 10px; }
.popup-preview { position: relative; }
.overlay { position: absolute; inset: 62px 18px 88px 18px; border: 1px solid var(--color-border); border-radius: 10px; background: var(--color-surface); padding: 12px; }
.dock { margin-top: 16px; border: 1px solid var(--color-border); border-radius: 8px; color: var(--color-text-muted); padding: 8px; }
</style>

<h2>Thinking Trail UI Preview</h2>
<p class="subtitle">Click the direction that feels best. I recommend A for the branch: it proves the feature without adding overlay mechanics.</p>

<div class="terminal-grid">
<div class="card terminal-card" data-choice="a-inline-collapsed" onclick="toggleSelect(this)">
<div class="card-body">
<h3>A. Inline, Collapsed By Default</h3>
<p>Shows a small live trail near the frontier line, capped so long reasoning does not dominate the thread.</p>
<div class="terminal">
<div class="terminal-top">ShellPilot app pane</div>
<div class="terminal-body">
<div class="user-line">❯ inspect the failing tests</div>
<div class="thinking">
<div class="thinking-title">thinking · 1.1k reasoning</div>
<div>I need to find the failing test path and compare it with the recent UI branch changes...</div>
<div>The likely place is the AppUI state machine around turn finalization...</div>
<div class="hidden">... 18 hidden lines · press t to expand later</div>
</div>
<div class="tool">⏺ read_file(path='tests/test_app_ui.py')</div>
<div class="assistant">The failure is in the turn indicator expectations. The new trail needs to clear with the indicator on cancel.</div>
<div class="done">✓ done · 7s · 1.1k reasoning · 240 total</div>
<div class="dock">╭ type a message<br>╰────────────────────────</div>
</div>
</div>
</div>
</div>

<div class="card terminal-card" data-choice="b-inline-full" onclick="toggleSelect(this)">
<div class="card-body">
<h3>B. Inline, Normally Visible</h3>
<p>Most transparent, but it can quickly bury tool calls and answers when the model reasons for a while.</p>
<div class="terminal">
<div class="terminal-top">ShellPilot app pane</div>
<div class="terminal-body">
<div class="user-line">❯ inspect the failing tests</div>
<div class="thinking">
<div class="thinking-title">thinking trail</div>
<div>I need to map the current runtime callbacks.</div>
<div>The model streams thinking before content.</div>
<div>AppUI stores only the count today.</div>
<div>If I append every fragment directly, the pane may churn and grow quickly.</div>
<div>I should cap render height and keep raw accumulation separate...</div>
<div>Now check cancel behavior and stale indicators...</div>
</div>
<div class="tool">⏺ read_file(path='shellpilot/cli/app_ui.py')</div>
<div class="done">✓ done · 7s · 1.1k reasoning · 240 total</div>
</div>
</div>
</div>
</div>

<div class="card terminal-card" data-choice="c-popup" onclick="toggleSelect(this)">
<div class="card-body">
<h3>C. Toggleable Popup</h3>
<p>Keeps the thread clean, but needs new focus, scrolling, dismissal, and keyboard handling.</p>
<div class="terminal popup-preview">
<div class="terminal-top">ShellPilot app pane</div>
<div class="terminal-body">
<div class="user-line">❯ inspect the failing tests</div>
<div class="assistant dim">thinking · 1.1k reasoning · press t to view</div>
<div class="tool">⏺ read_file(path='tests/test_app_ui.py')</div>
<div class="assistant">The failure is in the turn indicator expectations...</div>
<div class="overlay">
<div class="thinking-title">Thinking Trail</div>
<div>I need to find the failing test path...</div>
<div>The app has no transcript popup primitive today...</div>
<div>Implementing this means overlay focus and scroll state...</div>
</div>
<div class="dock">╭ overlay open: Esc closes<br>╰────────────────────────</div>
</div>
</div>
</div>
</div>
</div>

<div class="section">
<h3>What I Want To Learn From This Preview</h3>
<p>Does option A feel useful enough, or does even the capped inline trail still add too much noise? If A feels close, the next design choice is whether finished turns keep only the summary line or keep a collapsed trail marker in history.</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"reason":"owner process exited","timestamp":1782645338050}
3 changes: 3 additions & 0 deletions .superpowers/brainstorm/44142-1782645277/state/server.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{"type":"server-started","port":60095,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:60095","screen_dir":"/Users/lavin/Projects/ShellPilot/.superpowers/brainstorm/44142-1782645277/content","state_dir":"/Users/lavin/Projects/ShellPilot/.superpowers/brainstorm/44142-1782645277/state"}
fs.watch error: EMFILE: too many open files, watch
{"type":"server-stopped","reason":"owner process exited"}
1 change: 1 addition & 0 deletions .superpowers/brainstorm/44142-1782645277/state/server.pid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
44151
14 changes: 13 additions & 1 deletion docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2893,7 +2893,19 @@ A typed `/...` or `!...` line is a harness control, not a model turn. The dock's

**One dispatcher, two consoles, a confirm safety net.** Both the loop and terminal paths drive the app's single `SlashDispatcher`. The injected `dispatch(line, console)` closure swaps the dispatcher's console per call: the loop path passes the capturing console; the terminal path passes the real console. It also swaps the confirm — the loop path uses `_decline` (always declines, never `input()`), so a slash form misclassified as fast can never block the event loop; the terminal path uses the real blocking `_default_confirm`. Both are restored in `finally`.

**`!` / `/shell` and the busy guard.** `!<cmd>` / bare `!` / `/shell` always run on the real terminal (`run_in_terminal` → `run_manual_command` / `manual_shell_loop`, at the live workspace so a prior `/cwd set` is honoured). A slash submitted while a turn is in flight is rejected with a status line (`is_busy` reads `TurnRunner.busy` on the loop thread) — single model, single turn. The default (non-app) REPL is untouched: it routes slash/`!`/manual-shell exactly as before.
**`!` / `/shell` and the busy guard.** `!<cmd>` / bare `!` / `/shell` always run on the real terminal (`run_in_terminal` → `run_manual_command` / `manual_shell_loop`, at the live workspace so a prior `/cwd set` is honoured). A slash submitted from the dock while a turn is in flight is now QUEUED (the §31.18 one-message queue), not rejected: it is staged and fired through the router at turn end. The `SlashRouter`'s own `is_busy` guard (`route()` shows a "Busy" status when a turn is in flight) is therefore defense-in-depth — the dock stages before `route()` is reached, so a queued slash always runs `route()` from idle — but the guard stays as a fail-safe for any non-dock caller. The default (non-app) REPL is untouched: it routes slash/`!`/manual-shell exactly as before.

### 31.18 Input-dock polish (v2)

The dock gains four refinements; each is additive and a default (non-app) session is byte-identical, since the new `build_app` parameters all default to absent.

**One-message queue.** A normal-line submit while a turn is in flight is staged, not dropped — `build_app` holds a single loop-thread slot (`pending`). The submit keybinding keeps its order: approval-gate check, then `/exit` quit, then the empty check, then — when `is_busy()` is true — it stages the line (a second submit replaces the first; one slot) and returns; otherwise it dispatches immediately. A faint `ConditionalContainer` **chip** (`⏳ queued: …` in unicode; the `queued:` label alone, no glyph, in ASCII) renders just above the dock border while a line is staged; the preview is `_sanitize_line`-stripped and single-lined (user-controlled text). The slot **fires at turn end**: `TurnRunner.on_idle` (set after construction, like `.app`/`.conversation`) is invoked inside `_mark_done` after `_busy` clears, and `build_app` registers `_fire_pending` through `register_idle`; `_fire_pending` clears the slot *before* dispatching, so the fired turn's own end finds an empty slot — no loop. Firing reuses the same dispatch routing as a live submit (slash/`!` → `on_slash`, normal → echo + `on_submit`). A Ctrl-C that **cancels** the turn also DRAINS the slot (the `c-c` handler clears `pending` on the loop thread before the worker's `_mark_done` schedules `on_idle`), so "stop everything" never leaves a staged follow-up to fire after the abort.

**Up-arrow recall.** Pressing Up in an *empty* dock with a line staged pulls it back into the box (cursor at end) and clears the slot, so the user can edit, clear, or re-send it; the chip disappears. The keybinding is filtered on `empty dock AND something staged`, so a non-empty box or an empty slot leaves the default Up (cursor/history) untouched.

**Mouse-wheel scroll.** The chat pane is cursorless-with-an-exposed-cursor-line (§31, `pane_scroll["line"]`, `None` = follow the bottom); the Window's own `vertical_scroll` is re-derived from that cursor each render, so a wheel nudge to `vertical_scroll` is lost. A `FormattedTextControl` subclass intercepts `SCROLL_UP`/`SCROLL_DOWN` at the control and folds them into the **same** cursor-line model as PageUp/PageDown (three lines per notch via `_scroll_up`/`_scroll_down`), so wheel and keyboard share one scroll/auto-follow behaviour; any other mouse event delegates to the base handler.

**Live status values.** The status bar reads its dir/model/profile/locality/ctx through an optional `status_fn` callable evaluated per render, so `/model use` (the cloud indicator!), `/profile use`, `/cwd set`, and context growth reflect immediately. The cloud bit stays the real `is_egressing` signal — now live, never model output, still unspoofable (§15.2). The git branch segment stays build-time (re-reading `.git/HEAD` every render would be wasteful; a mid-session `/cwd set` does not refresh it). With `status_fn` absent the bar uses the build-time params, unchanged.

## 32. Model Selection And Preload

Expand Down
Loading