diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 1a013c4c4..e069196b0 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -123,8 +123,14 @@ runtime cost control, and shared AI transport surfaces. may persist a bounded local history of submitted prompt text and structured mentions for ArrowUp/ArrowDown recall, but that history must not persist or replay one-shot finding handoff, approval, autonomous-mode, or other scoped - send options. Scoped context replay remains owned by explicit session - handoff metadata or queued follow-up edit state. + send options. The referenced OpenCode source at fetched `origin/dev` commit + `ba57718b0516c7a8670d1e820b1a24146a8b8262` binds prompt history navigation + in `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx` to the + input cursor boundary: previous history is available at the start of the + current draft, and next history restores the saved draft at the end. Pulse's + Assistant drawer follows that draft-safe recall model rather than limiting + history recall to an empty composer. Scoped context replay remains owned by + explicit session handoff metadata or queued follow-up edit state. Failed-turn retry is part of that same local chat-runtime boundary: a retryable in-memory assistant error may replay the original user turn's structured mentions, finding id, approval override, handoff resources, diff --git a/frontend-modern/src/components/AI/Chat/__tests__/AIChat.test.tsx b/frontend-modern/src/components/AI/Chat/__tests__/AIChat.test.tsx index b8e0b2e4d..e8624184d 100644 --- a/frontend-modern/src/components/AI/Chat/__tests__/AIChat.test.tsx +++ b/frontend-modern/src/components/AI/Chat/__tests__/AIChat.test.tsx @@ -1332,7 +1332,7 @@ describe('AIChat', () => { await waitFor(() => expect(textarea.value).toBe('')); }); - it('does not replace a non-empty composer draft with prompt history', () => { + it('recalls prompt history from the start of a draft and restores the draft', async () => { localStorage.setItem( 'pulse:ai_chat_prompt_history', JSON.stringify([{ prompt: 'previous prompt', mentions: [] }]), @@ -1344,9 +1344,14 @@ describe('AIChat', () => { ) as HTMLTextAreaElement; fireEvent.input(textarea, { target: { value: 'draft prompt' } }); + textarea.setSelectionRange(0, 0); fireEvent.keyDown(textarea, { key: 'ArrowUp' }); - expect(textarea.value).toBe('draft prompt'); + await waitFor(() => expect(textarea.value).toBe('previous prompt')); + + fireEvent.keyDown(textarea, { key: 'ArrowDown' }); + + await waitFor(() => expect(textarea.value).toBe('draft prompt')); }); it('loads persisted prompt history for recall', async () => { diff --git a/frontend-modern/src/components/AI/Chat/index.tsx b/frontend-modern/src/components/AI/Chat/index.tsx index 7dfd808b1..ae21cfc23 100644 --- a/frontend-modern/src/components/AI/Chat/index.tsx +++ b/frontend-modern/src/components/AI/Chat/index.tsx @@ -592,7 +592,7 @@ export const AIChat: Component = (props) => { const cursor = textareaRef.selectionStart ?? text.length; const inHistory = promptHistoryIndex() >= 0; if (inHistory) return cursor === 0 || cursor === text.length; - if (direction === 'up') return cursor === 0 && text.length === 0; + if (direction === 'up') return cursor === 0; return false; };