Skip to content

Commit 1934495

Browse files
authored
feat: Add prompt history to task input and fix command center text selection (#1335)
## Problem Task input page has no prompt history navigation, and selecting text in the command center loses its highlight on selection. <img width="1466" height="470" alt="CleanShot 2026-03-26 at 09 40 37@2x" src="https://github.com/user-attachments/assets/4c7d48d5-dbd5-4a70-8a1a-14d2c4c842eb" /> ## Changes 1. Add persisted task input history store (last 50 prompts, deduped) 2. Generalize prompt history store to accept history arrays instead of task IDs 3. Wire up/down arrow navigation on the task input page via getPromptHistory 4. Update placeholder to mention ↑↓ for history 5. Fix command center click handler to skip focus steal when text is selected ## How did you test this? Manually
1 parent 12f76b0 commit 1934495

12 files changed

Lines changed: 112 additions & 50 deletions

File tree

apps/code/src/renderer/features/command-center/components/CommandCenterGrid.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ function GridCell({
6060

6161
const handleCellClick = useCallback(() => {
6262
setActiveTask(cell.taskId);
63+
const selection = window.getSelection();
64+
if (selection && !selection.isCollapsed) return;
6365
const actionSelector =
6466
cellRef.current?.querySelector<HTMLElement>("[tabindex='0']");
6567
actionSelector?.focus();
@@ -101,9 +103,7 @@ function GridCell({
101103
// biome-ignore lint/a11y/useKeyWithClickEvents lint/a11y/noStaticElementInteractions: click delegates focus to ActionSelector within
102104
<div
103105
ref={cellRef}
104-
className={`relative overflow-hidden bg-gray-1 focus-within:ring-2 focus-within:ring-accent-9 focus-within:ring-inset ${
105-
isActive ? "ring-2 ring-accent-9 ring-inset" : ""
106-
}`}
106+
className="relative overflow-hidden bg-gray-1"
107107
onClick={handleCellClick}
108108
onPointerDownCapture={handleCellPointerDownCapture}
109109
onFocusCapture={handleCellFocusCapture}
@@ -116,6 +116,9 @@ function GridCell({
116116
>
117117
<CommandCenterPanel cell={cell} isActiveSession={isActive} />
118118
</div>
119+
{isActive && (
120+
<div className="pointer-events-none absolute inset-0 border-2 border-accent-9" />
121+
)}
119122
{isDragActive && (
120123
// biome-ignore lint/a11y/noStaticElementInteractions: transparent overlay to capture drag events over session content
121124
<div

apps/code/src/renderer/features/command-center/components/CommandCenterView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useTaskViewed } from "@features/sidebar/hooks/useTaskViewed";
22
import { useSetHeaderContent } from "@hooks/useSetHeaderContent";
3-
import { SquaresFour } from "@phosphor-icons/react";
3+
import { Lightning } from "@phosphor-icons/react";
44
import { Box, Flex, Text } from "@radix-ui/themes";
55
import { useEffect, useMemo } from "react";
66
import { useCommandCenterData } from "../hooks/useCommandCenterData";
@@ -27,14 +27,14 @@ export function CommandCenterView() {
2727
const headerContent = useMemo(
2828
() => (
2929
<Flex align="center" gap="2" className="w-full min-w-0">
30-
<SquaresFour size={12} className="shrink-0 text-gray-10" />
30+
<Lightning size={12} className="shrink-0 text-gray-10" />
3131
<Text
3232
size="1"
3333
weight="medium"
3434
className="truncate whitespace-nowrap text-[13px]"
35-
title="Command center"
35+
title="ADHD Mode"
3636
>
37-
Command center
37+
ADHD Mode
3838
</Text>
3939
</Flex>
4040
),

apps/code/src/renderer/features/message-editor/components/MessageEditor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import "./message-editor.css";
22
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
33
import { BranchSelector } from "@features/git-interaction/components/BranchSelector";
44
import { useGitQueries } from "@features/git-interaction/hooks/useGitQueries";
5+
import { getUserPromptsForTask } from "@features/sessions/stores/sessionStore";
56
import { useConnectivity } from "@hooks/useConnectivity";
67
import { ArrowUp, Circle, Stop } from "@phosphor-icons/react";
78
import { Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
89
import { EditorContent } from "@tiptap/react";
910
import { hasOpenOverlay } from "@utils/overlay";
10-
import { forwardRef, useEffect, useImperativeHandle } from "react";
11+
import { forwardRef, useCallback, useEffect, useImperativeHandle } from "react";
1112
import { useHotkeys } from "react-hotkeys-hook";
1213
import { useDraftStore } from "../stores/draftStore";
1314
import { useTiptapEditor } from "../tiptap/useTiptapEditor";
@@ -167,6 +168,11 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
167168
const cloudDiffStats = context?.cloudDiffStats;
168169
const isSubmitDisabled = disabled || !isOnline;
169170

171+
const getPromptHistory = useCallback(
172+
() => getUserPromptsForTask(taskId),
173+
[taskId],
174+
);
175+
170176
const {
171177
editor,
172178
isReady,
@@ -192,6 +198,7 @@ export const MessageEditor = forwardRef<EditorHandle, MessageEditorProps>(
192198
isLoading,
193199
autoFocus,
194200
context: { taskId, repoPath },
201+
getPromptHistory,
195202
onSubmit,
196203
onBashCommand,
197204
onBashModeChange,

apps/code/src/renderer/features/message-editor/stores/promptHistoryStore.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import { getUserPromptsForTask } from "@features/sessions/stores/sessionStore";
21
import { create } from "zustand";
32

43
interface PromptHistoryStore {
54
index: number;
65
savedInput: string;
7-
navigateUp: (taskId: string, currentInput: string) => string | null;
8-
navigateDown: (taskId: string) => string | null;
6+
navigateUp: (history: string[], currentInput: string) => string | null;
7+
navigateDown: (history: string[]) => string | null;
98
reset: () => void;
109
}
1110

1211
export const usePromptHistoryStore = create<PromptHistoryStore>((set, get) => ({
1312
index: -1,
1413
savedInput: "",
1514

16-
navigateUp: (taskId, currentInput) => {
17-
const history = getUserPromptsForTask(taskId);
15+
navigateUp: (history, currentInput) => {
1816
if (history.length === 0) return null;
1917

2018
const { index } = get();
@@ -31,12 +29,10 @@ export const usePromptHistoryStore = create<PromptHistoryStore>((set, get) => ({
3129
return history[history.length - 1 - newIndex] ?? null;
3230
},
3331

34-
navigateDown: (taskId) => {
32+
navigateDown: (history) => {
3533
const { index, savedInput } = get();
3634
if (index === -1) return null;
3735

38-
const history = getUserPromptsForTask(taskId);
39-
4036
if (index > 0) {
4137
const newIndex = index - 1;
4238
set({ index: newIndex });
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { create } from "zustand";
2+
import { persist } from "zustand/middleware";
3+
4+
interface TaskInputHistoryState {
5+
prompts: string[];
6+
}
7+
8+
interface TaskInputHistoryActions {
9+
addPrompt: (prompt: string) => void;
10+
}
11+
12+
type TaskInputHistoryStore = TaskInputHistoryState & TaskInputHistoryActions;
13+
14+
const MAX_HISTORY = 50;
15+
16+
export const useTaskInputHistoryStore = create<TaskInputHistoryStore>()(
17+
persist(
18+
(set) => ({
19+
prompts: [],
20+
addPrompt: (prompt) =>
21+
set((state) => {
22+
const trimmed = prompt.trim();
23+
if (!trimmed) return state;
24+
const filtered = state.prompts.filter((p) => p !== trimmed);
25+
const updated = [...filtered, trimmed].slice(-MAX_HISTORY);
26+
return { prompts: updated };
27+
}),
28+
}),
29+
{
30+
name: "task-input-history",
31+
partialize: (state) => ({ prompts: state.prompts }),
32+
},
33+
),
34+
);

apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface UseTiptapEditorOptions {
2828
bashMode?: boolean;
2929
};
3030
clearOnSubmit?: boolean;
31+
getPromptHistory?: () => string[];
3132
onSubmit?: (text: string) => void;
3233
onBashCommand?: (command: string) => void;
3334
onBashModeChange?: (isBashMode: boolean) => void;
@@ -82,6 +83,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
8283
context,
8384
capabilities = {},
8485
clearOnSubmit = true,
86+
getPromptHistory,
8587
onSubmit,
8688
onBashCommand,
8789
onBashModeChange,
@@ -116,6 +118,9 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
116118
const submitDisabledRef = useRef(submitDisabled);
117119
submitDisabledRef.current = submitDisabled;
118120

121+
const getPromptHistoryRef = useRef(getPromptHistory);
122+
getPromptHistoryRef.current = getPromptHistory;
123+
119124
const prevBashModeRef = useRef(false);
120125
const prevIsEmptyRef = useRef(true);
121126
const submitRef = useRef<() => void>(() => {});
@@ -192,35 +197,38 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
192197
}
193198
}
194199

195-
if (
196-
taskId &&
197-
(event.key === "ArrowUp" || event.key === "ArrowDown")
198-
) {
200+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
201+
const historyGetter = getPromptHistoryRef.current;
202+
if (!taskId && !historyGetter) return false;
203+
199204
const currentText = view.state.doc.textContent;
200205
const isEmpty = !currentText.trim();
201206
const { from } = view.state.selection;
202207
const isAtStart = from === 1;
203208
const isAtEnd = from === view.state.doc.content.size - 1;
204209

205210
const forceNavigate = event.shiftKey;
211+
const history = historyGetter?.() ?? [];
206212

207213
if (
208214
event.key === "ArrowUp" &&
209215
(forceNavigate || isEmpty || isAtStart)
210216
) {
211-
const queuedContent =
212-
sessionStoreSetters.dequeueMessagesAsText(taskId);
213-
if (queuedContent !== null && queuedContent !== undefined) {
214-
event.preventDefault();
215-
view.dispatch(
216-
view.state.tr
217-
.delete(1, view.state.doc.content.size - 1)
218-
.insertText(queuedContent, 1),
219-
);
220-
return true;
217+
if (taskId) {
218+
const queuedContent =
219+
sessionStoreSetters.dequeueMessagesAsText(taskId);
220+
if (queuedContent !== null && queuedContent !== undefined) {
221+
event.preventDefault();
222+
view.dispatch(
223+
view.state.tr
224+
.delete(1, view.state.doc.content.size - 1)
225+
.insertText(queuedContent, 1),
226+
);
227+
return true;
228+
}
221229
}
222230

223-
const newText = historyActions.navigateUp(taskId, currentText);
231+
const newText = historyActions.navigateUp(history, currentText);
224232
if (newText !== null) {
225233
event.preventDefault();
226234
view.dispatch(
@@ -236,7 +244,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) {
236244
event.key === "ArrowDown" &&
237245
(forceNavigate || isEmpty || isAtEnd)
238246
) {
239-
const newText = historyActions.navigateDown(taskId);
247+
const newText = historyActions.navigateDown(history);
240248
if (newText !== null) {
241249
event.preventDefault();
242250
view.dispatch(

apps/code/src/renderer/features/sidebar/components/SidebarMenu.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,17 +210,17 @@ function SidebarMenuComponent() {
210210
</Box>
211211

212212
<Box mb="1">
213-
<CommandCenterItem
214-
isActive={sidebarData.isCommandCenterActive}
215-
onClick={handleCommandCenterClick}
216-
activeCount={commandCenterActiveCount}
213+
<SkillsItem
214+
isActive={sidebarData.isSkillsActive}
215+
onClick={handleSkillsClick}
217216
/>
218217
</Box>
219218

220219
<Box mb="2">
221-
<SkillsItem
222-
isActive={sidebarData.isSkillsActive}
223-
onClick={handleSkillsClick}
220+
<CommandCenterItem
221+
isActive={sidebarData.isCommandCenterActive}
222+
onClick={handleCommandCenterClick}
223+
activeCount={commandCenterActiveCount}
224224
/>
225225
</Box>
226226

apps/code/src/renderer/features/sidebar/components/items/CommandCenterItem.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SquaresFour } from "@phosphor-icons/react";
1+
import { Lightning } from "@phosphor-icons/react";
22
import { SidebarItem } from "../SidebarItem";
33

44
interface CommandCenterItemProps {
@@ -20,16 +20,16 @@ export function CommandCenterItem({
2020
return (
2121
<SidebarItem
2222
depth={0}
23-
icon={<SquaresFour size={16} weight={isActive ? "fill" : "regular"} />}
24-
label="Command center"
23+
icon={<Lightning size={16} weight={isActive ? "fill" : "regular"} />}
24+
label="ADHD Mode"
2525
isActive={isActive}
2626
onClick={onClick}
2727
endContent={
2828
activeCount && activeCount > 0 ? (
2929
<span
3030
className="inline-flex min-w-[16px] items-center justify-center rounded-full px-1 text-[11px] text-gray-11 leading-none"
3131
style={{ height: "16px" }}
32-
title={`${activeCount} active agents`}
32+
title={`${activeCount} active`}
3333
>
3434
{formatActiveCount(activeCount)}
3535
</span>

apps/code/src/renderer/features/sidebar/components/items/SkillsItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Lightning } from "@phosphor-icons/react";
1+
import { Lightbulb } from "@phosphor-icons/react";
22
import { SidebarItem } from "../SidebarItem";
33

44
interface SkillsItemProps {
@@ -10,7 +10,7 @@ export function SkillsItem({ isActive, onClick }: SkillsItemProps) {
1010
return (
1111
<SidebarItem
1212
depth={0}
13-
icon={<Lightning size={16} weight={isActive ? "fill" : "regular"} />}
13+
icon={<Lightbulb size={16} weight={isActive ? "fill" : "regular"} />}
1414
label="Skills"
1515
isActive={isActive}
1616
onClick={onClick}

apps/code/src/renderer/features/skills/components/SkillsView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ResizableSidebar } from "@components/ResizableSidebar";
22
import { useSetHeaderContent } from "@hooks/useSetHeaderContent";
3-
import { Lightning } from "@phosphor-icons/react";
3+
import { Lightbulb } from "@phosphor-icons/react";
44
import { Box, Flex, ScrollArea, Text } from "@radix-ui/themes";
55
import { useTRPC } from "@renderer/trpc";
66
import type { SkillInfo, SkillSource } from "@shared/types/skills";
@@ -60,7 +60,7 @@ export function SkillsView() {
6060
const headerContent = useMemo(
6161
() => (
6262
<Flex align="center" gap="2" className="w-full min-w-0">
63-
<Lightning size={12} className="shrink-0 text-gray-10" />
63+
<Lightbulb size={12} className="shrink-0 text-gray-10" />
6464
<Text
6565
size="1"
6666
weight="medium"
@@ -95,7 +95,7 @@ export function SkillsView() {
9595
className="py-12"
9696
>
9797
<Box className="rounded-lg border border-gray-6 border-dashed p-4">
98-
<Lightning size={24} className="text-gray-8" />
98+
<Lightbulb size={24} className="text-gray-8" />
9999
</Box>
100100
<Text size="2" className="text-[13px] text-gray-10">
101101
No skills found

0 commit comments

Comments
 (0)