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

4.2 KiB

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.

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.

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

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