Files
amc/plans/input-history.md
teernisse c5b1fb3a80 chore(plans): add input-history and model-selection plans
plans/input-history.md:
- Implementation plan for shell-style up/down arrow message history
  in SimpleInput, deriving history from session log conversation data
- Covers prop threading, history derivation, navigation state,
  keybinding details, modal parity, and test cases

plans/model-selection.md:
- Three-phase plan for model visibility and control: display current
  model, model picker at spawn, mid-session model switching via Zellij

plans/PLAN-tool-result-display.md:
- Updates to tool result display plan (pre-existing changes)

plans/subagent-visibility.md:
- Updates to subagent visibility plan (pre-existing changes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 14:51:36 -05:00

97 lines
4.2 KiB
Markdown

# Input History (Up/Down Arrow)
## Summary
Add shell-style up/down arrow navigation through past messages in SimpleInput. History is derived from the conversation data already parsed from session logs -- no new state management, no server changes.
## How It Works Today
1. Server parses JSONL session logs, extracts user messages with `role: "user"` (`conversation.py:57-66`)
2. App.js stores parsed conversations in `conversations` state, refreshed via SSE on `conversation_mtime_ns` change
3. SessionCard receives `conversation` as a prop but does **not** pass it to SimpleInput
4. SimpleInput has no awareness of past messages
## Step 1: Pipe Conversation to SimpleInput
Pass the conversation array from SessionCard into SimpleInput so it can derive history.
- `SessionCard.js:165-169` -- add `conversation` prop to SimpleInput
- Same for the QuestionBlock path if freeform input is used there (line 162) -- skip for now, QuestionBlock is option-based
**Files**: `dashboard/components/SessionCard.js`
## Step 2: Derive User Message History
Inside SimpleInput, filter conversation to user messages only.
```js
const userHistory = useMemo(
() => (conversation || []).filter(m => m.role === 'user').map(m => m.content),
[conversation]
);
```
This updates automatically whenever the session log changes (SSE triggers conversation refresh, new prop flows down).
**Files**: `dashboard/components/SimpleInput.js`
## Step 3: History Navigation State
Add refs for tracking position in history and preserving the draft.
```js
const historyIndexRef = useRef(-1); // -1 = not browsing
const draftRef = useRef(''); // saves in-progress text before browsing
```
Use refs (not state) because index changes don't need re-renders -- only `setText` triggers the visual update.
**Files**: `dashboard/components/SimpleInput.js`
## Step 4: ArrowUp/ArrowDown Keybinding
In the `onKeyDown` handler (after the autocomplete block, before Enter-to-submit), add history navigation:
- **ArrowUp**: only when autocomplete is closed AND cursor is at position 0 (prevents hijacking multiline cursor movement). On first press, save current text to `draftRef`. Walk backward through `userHistory`. Call `setText()` with the history entry.
- **ArrowDown**: walk forward through history. If past the newest entry, restore `draftRef` and reset index to -1.
- **Reset on submit**: set `historyIndexRef.current = -1` in `handleSubmit` after successful send.
- **Reset on manual edit**: in `onInput`, reset `historyIndexRef.current = -1` so typing after browsing exits history mode.
### Cursor position check
```js
const atStart = e.target.selectionStart === 0 && e.target.selectionEnd === 0;
```
Only intercept ArrowUp when `atStart` is true. This lets multiline text cursor movement work normally. ArrowDown can use similar logic (check if cursor is at end of text) or always navigate history when `historyIndexRef.current !== -1` (already browsing).
**Files**: `dashboard/components/SimpleInput.js`
## Step 5: Modal Parity
The Modal (`Modal.js:71`) also renders SimpleInput with `onRespond`. Verify it passes `conversation` through. The same SessionCard is used in enlarged mode, so this should work automatically if Step 1 is done correctly.
**Files**: `dashboard/components/Modal.js` (verify, likely no change needed)
## Non-Goals
- No localStorage persistence -- history comes from session logs which survive across page reloads
- No server changes -- conversation parsing already extracts what we need
- No new API endpoints
- No changes to QuestionBlock (option-based, not free-text history)
## Test Cases
| Scenario | Expected |
|----------|----------|
| Press up with empty input | Fills with most recent user message |
| Press up multiple times | Walks backward through user messages |
| Press down after browsing up | Walks forward; past newest restores draft |
| Press up with text in input | Saves text as draft, shows history |
| Press down past end | Restores saved draft |
| Type after browsing | Exits history mode (index resets) |
| Submit after browsing | Sends displayed text, resets index |
| Up arrow in multiline text (cursor not at pos 0) | Normal cursor movement, no history |
| New message arrives via SSE | userHistory updates, no index disruption |
| Session with no prior messages | Up arrow does nothing |