From 753d165a214b039e3516fcac281f5acb6dc88133 Mon Sep 17 00:00:00 2001 From: Naomi Carrigan Date: Wed, 21 Jan 2026 13:22:04 -0800 Subject: [PATCH] feat: input history (both text and commands) --- src/lib/components/InputBar.svelte | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/lib/components/InputBar.svelte b/src/lib/components/InputBar.svelte index 302d877..b19c8ba 100644 --- a/src/lib/components/InputBar.svelte +++ b/src/lib/components/InputBar.svelte @@ -23,6 +23,9 @@ type SlashCommand, } from "$lib/commands/slashCommands"; + const INPUT_HISTORY_KEY = "hikari-input-history"; + const MAX_HISTORY_SIZE = 100; + let inputValue = $state(""); let isSubmitting = $state(false); let isConnected = $state(false); @@ -31,6 +34,44 @@ let matchingCommands = $state([]); let selectedCommandIndex = $state(0); + // Input history state + let inputHistory = $state([]); + let historyIndex = $state(-1); + let tempInput = $state(""); + + // Load history from localStorage on init + function loadHistory(): string[] { + try { + const stored = localStorage.getItem(INPUT_HISTORY_KEY); + return stored ? JSON.parse(stored) : []; + } catch { + return []; + } + } + + function saveHistory(history: string[]) { + try { + localStorage.setItem(INPUT_HISTORY_KEY, JSON.stringify(history)); + } catch { + // Ignore storage errors + } + } + + function addToHistory(input: string) { + const trimmed = input.trim(); + if (!trimmed) return; + + // Don't add duplicates of the most recent entry + if (inputHistory.length > 0 && inputHistory[0] === trimmed) return; + + // Add to front of history + inputHistory = [trimmed, ...inputHistory.slice(0, MAX_HISTORY_SIZE - 1)]; + saveHistory(inputHistory); + } + + // Initialize history on mount + inputHistory = loadHistory(); + claudeStore.connectionStatus.subscribe((status) => { isConnected = status === "connected"; }); @@ -40,6 +81,10 @@ }); function handleInputChange() { + // Reset history navigation when user types + historyIndex = -1; + tempInput = ""; + if (isSlashCommand(inputValue)) { matchingCommands = getMatchingCommands(inputValue); showCommandMenu = matchingCommands.length > 0; @@ -76,6 +121,11 @@ // Check for slash commands first (these work even when disconnected) if (isSlashCommand(message)) { + // Add slash commands to history too + addToHistory(message); + historyIndex = -1; + tempInput = ""; + const wasCommand = await executeSlashCommand(); if (wasCommand) return; // If it started with / but wasn't a valid command, show error @@ -90,6 +140,11 @@ // Regular messages require connection if (!isConnected) return; + // Add to history before clearing + addToHistory(message); + historyIndex = -1; + tempInput = ""; + isSubmitting = true; inputValue = ""; @@ -222,6 +277,32 @@ User: ${formattedMessage}`; } } + // Handle input history navigation (when command menu is closed) + if (event.key === "ArrowUp" && inputHistory.length > 0) { + event.preventDefault(); + if (historyIndex === -1) { + // Save current input before navigating history + tempInput = inputValue; + } + if (historyIndex < inputHistory.length - 1) { + historyIndex++; + inputValue = inputHistory[historyIndex]; + } + return; + } + + if (event.key === "ArrowDown" && historyIndex >= 0) { + event.preventDefault(); + historyIndex--; + if (historyIndex === -1) { + // Restore the temp input when going back to current + inputValue = tempInput; + } else { + inputValue = inputHistory[historyIndex]; + } + return; + } + if (event.key === "Enter" && !event.shiftKey) { handleSubmit(event); }