repoburg 1.3.188 → 1.3.196

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/agent-pane/.prettierignore +5 -0
  2. package/agent-pane/.prettierrc +8 -0
  3. package/agent-pane/backend/coverage/base.css +224 -0
  4. package/agent-pane/backend/coverage/block-navigation.js +87 -0
  5. package/agent-pane/backend/coverage/clover.xml +887 -0
  6. package/agent-pane/backend/coverage/coverage-final.json +10 -0
  7. package/agent-pane/backend/coverage/favicon.png +0 -0
  8. package/agent-pane/backend/coverage/index.html +176 -0
  9. package/agent-pane/backend/coverage/prettify.css +1 -0
  10. package/agent-pane/backend/coverage/prettify.js +2 -0
  11. package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
  12. package/agent-pane/backend/coverage/sorter.js +210 -0
  13. package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +1402 -0
  14. package/agent-pane/backend/coverage/src/codex/index.html +131 -0
  15. package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +331 -0
  16. package/agent-pane/backend/coverage/src/db/index.html +131 -0
  17. package/agent-pane/backend/coverage/src/db/index.ts.html +436 -0
  18. package/agent-pane/backend/coverage/src/db/schema.ts.html +175 -0
  19. package/agent-pane/backend/coverage/src/index.html +116 -0
  20. package/agent-pane/backend/coverage/src/index.ts.html +262 -0
  21. package/agent-pane/backend/coverage/src/routes/index.html +146 -0
  22. package/agent-pane/backend/coverage/src/routes/sessions.ts.html +991 -0
  23. package/agent-pane/backend/coverage/src/routes/turns.ts.html +256 -0
  24. package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +406 -0
  25. package/agent-pane/backend/coverage/src/ws/index.html +116 -0
  26. package/agent-pane/backend/coverage/src/ws/index.ts.html +238 -0
  27. package/agent-pane/backend/data.db +0 -0
  28. package/agent-pane/backend/data.db-shm +0 -0
  29. package/agent-pane/backend/data.db-wal +0 -0
  30. package/agent-pane/backend/dist/index.js +70869 -0
  31. package/agent-pane/backend/package-lock.json +4529 -1013
  32. package/agent-pane/backend/package.json +10 -6
  33. package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +163 -0
  34. package/agent-pane/backend/src/claude/claude-message-mapper.ts +154 -0
  35. package/agent-pane/backend/src/claude/claude-provider.test.ts +65 -0
  36. package/agent-pane/backend/src/claude/claude-provider.ts +482 -0
  37. package/agent-pane/backend/src/claude/claude-types.ts +33 -0
  38. package/agent-pane/backend/src/codex/app-server-client.test.ts +183 -0
  39. package/agent-pane/backend/src/codex/app-server-client.ts +225 -0
  40. package/agent-pane/backend/src/codex/app-server-types.ts +259 -0
  41. package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +56 -0
  42. package/agent-pane/backend/src/codex/codex-provider.test.ts +60 -0
  43. package/agent-pane/backend/src/codex/codex-provider.ts +521 -265
  44. package/agent-pane/backend/src/codex/item-mapper.test.ts +221 -0
  45. package/agent-pane/backend/src/codex/item-mapper.ts +35 -8
  46. package/agent-pane/backend/src/codex/shared-app-server.ts +25 -0
  47. package/agent-pane/backend/src/db/index.test.ts +125 -0
  48. package/agent-pane/backend/src/db/index.ts +39 -8
  49. package/agent-pane/backend/src/db/schema.ts +28 -74
  50. package/agent-pane/backend/src/index.ts +29 -2
  51. package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +29 -0
  52. package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +40 -0
  53. package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +16 -0
  54. package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +60 -0
  55. package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +34 -0
  56. package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +85 -0
  57. package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +48 -0
  58. package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +35 -0
  59. package/agent-pane/backend/src/integration/delta-streaming.test.ts +130 -0
  60. package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +272 -0
  61. package/agent-pane/backend/src/integration/helpers/test-app.ts +108 -0
  62. package/agent-pane/backend/src/integration/helpers/ws-capture.ts +64 -0
  63. package/agent-pane/backend/src/integration/item-lifecycle.test.ts +133 -0
  64. package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +102 -0
  65. package/agent-pane/backend/src/integration/missing-item-started.test.ts +91 -0
  66. package/agent-pane/backend/src/integration/multi-turn.test.ts +121 -0
  67. package/agent-pane/backend/src/integration/session-operations.test.ts +168 -0
  68. package/agent-pane/backend/src/integration/title-generation.test.ts +109 -0
  69. package/agent-pane/backend/src/integration/turn-cancel.test.ts +111 -0
  70. package/agent-pane/backend/src/integration/turn-failure.test.ts +97 -0
  71. package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +141 -0
  72. package/agent-pane/backend/src/providers/index.ts +14 -0
  73. package/agent-pane/backend/src/providers/registry.test.ts +92 -0
  74. package/agent-pane/backend/src/providers/registry.ts +43 -0
  75. package/agent-pane/backend/src/providers/runtime-provider.ts +25 -0
  76. package/agent-pane/backend/src/routes/files.ts +31 -0
  77. package/agent-pane/backend/src/routes/mcp.ts +33 -0
  78. package/agent-pane/backend/src/routes/models.test.ts +92 -0
  79. package/agent-pane/backend/src/routes/models.ts +16 -0
  80. package/agent-pane/backend/src/routes/plugins.ts +40 -0
  81. package/agent-pane/backend/src/routes/providers.test.ts +69 -0
  82. package/agent-pane/backend/src/routes/providers.ts +57 -0
  83. package/agent-pane/backend/src/routes/sessions.test.ts +424 -0
  84. package/agent-pane/backend/src/routes/sessions.ts +295 -42
  85. package/agent-pane/backend/src/routes/skills.test.ts +114 -0
  86. package/agent-pane/backend/src/routes/skills.ts +70 -0
  87. package/agent-pane/backend/src/routes/turns.test.ts +152 -0
  88. package/agent-pane/backend/src/routes/turns.ts +36 -16
  89. package/agent-pane/backend/src/routes/workspaces.test.ts +197 -0
  90. package/agent-pane/backend/src/routes/workspaces.ts +73 -55
  91. package/agent-pane/backend/src/ws/index.test.ts +107 -0
  92. package/agent-pane/backend/src/ws/index.ts +19 -11
  93. package/agent-pane/backend/tsconfig.json +4 -2
  94. package/agent-pane/backend/vitest.config.ts +20 -0
  95. package/agent-pane/eslint.config.js +33 -0
  96. package/agent-pane/frontend/coverage/base.css +224 -0
  97. package/agent-pane/frontend/coverage/block-navigation.js +87 -0
  98. package/agent-pane/frontend/coverage/clover.xml +2962 -0
  99. package/agent-pane/frontend/coverage/coverage-final.json +37 -0
  100. package/agent-pane/frontend/coverage/favicon.png +0 -0
  101. package/agent-pane/frontend/coverage/index.html +251 -0
  102. package/agent-pane/frontend/coverage/prettify.css +1 -0
  103. package/agent-pane/frontend/coverage/prettify.js +2 -0
  104. package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
  105. package/agent-pane/frontend/coverage/sorter.js +210 -0
  106. package/agent-pane/frontend/coverage/src/App.tsx.html +217 -0
  107. package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +223 -0
  108. package/agent-pane/frontend/coverage/src/api/client.ts.html +187 -0
  109. package/agent-pane/frontend/coverage/src/api/index.html +161 -0
  110. package/agent-pane/frontend/coverage/src/api/sessions.ts.html +316 -0
  111. package/agent-pane/frontend/coverage/src/api/websocket.ts.html +544 -0
  112. package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +199 -0
  113. package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +226 -0
  114. package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +382 -0
  115. package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +715 -0
  116. package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +160 -0
  117. package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +478 -0
  118. package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +472 -0
  119. package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +574 -0
  120. package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +205 -0
  121. package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +133 -0
  122. package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +217 -0
  123. package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +271 -0
  124. package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +109 -0
  125. package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +313 -0
  126. package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +1216 -0
  127. package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +1138 -0
  128. package/agent-pane/frontend/coverage/src/components/index.html +326 -0
  129. package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +247 -0
  130. package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +160 -0
  131. package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +427 -0
  132. package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +397 -0
  133. package/agent-pane/frontend/coverage/src/components/panels/index.html +161 -0
  134. package/agent-pane/frontend/coverage/src/constants/index.html +116 -0
  135. package/agent-pane/frontend/coverage/src/constants/models.ts.html +406 -0
  136. package/agent-pane/frontend/coverage/src/hooks/index.html +146 -0
  137. package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +124 -0
  138. package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +142 -0
  139. package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +187 -0
  140. package/agent-pane/frontend/coverage/src/index.html +146 -0
  141. package/agent-pane/frontend/coverage/src/main.tsx.html +115 -0
  142. package/agent-pane/frontend/coverage/src/store/index.html +116 -0
  143. package/agent-pane/frontend/coverage/src/store/store.ts.html +2404 -0
  144. package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +190 -0
  145. package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +367 -0
  146. package/agent-pane/frontend/coverage/src/test/index.html +131 -0
  147. package/agent-pane/frontend/coverage/src/types/index.html +116 -0
  148. package/agent-pane/frontend/coverage/src/types/index.ts.html +466 -0
  149. package/agent-pane/frontend/coverage/src/utils/changes.ts.html +202 -0
  150. package/agent-pane/frontend/coverage/src/utils/duration.ts.html +127 -0
  151. package/agent-pane/frontend/coverage/src/utils/index.html +131 -0
  152. package/agent-pane/frontend/dist/assets/index-Bg0PF2aV.css +1 -0
  153. package/agent-pane/frontend/dist/assets/index-BlybeX5e.js +281 -0
  154. package/agent-pane/frontend/dist/index.html +19 -0
  155. package/agent-pane/frontend/package-lock.json +2434 -213
  156. package/agent-pane/frontend/package.json +10 -3
  157. package/agent-pane/frontend/src/App.test.tsx +74 -0
  158. package/agent-pane/frontend/src/App.tsx +1 -1
  159. package/agent-pane/frontend/src/AppInitializer.test.tsx +142 -0
  160. package/agent-pane/frontend/src/AppInitializer.tsx +1 -1
  161. package/agent-pane/frontend/src/api/client.test.ts +126 -0
  162. package/agent-pane/frontend/src/api/client.ts +22 -29
  163. package/agent-pane/frontend/src/api/models.ts +7 -0
  164. package/agent-pane/frontend/src/api/providers.ts +12 -0
  165. package/agent-pane/frontend/src/api/sessions.test.ts +241 -0
  166. package/agent-pane/frontend/src/api/sessions.ts +43 -1
  167. package/agent-pane/frontend/src/api/skills.ts +12 -0
  168. package/agent-pane/frontend/src/api/websocket.test.ts +288 -0
  169. package/agent-pane/frontend/src/api/websocket.ts +27 -2
  170. package/agent-pane/frontend/src/api/workspaces.test.ts +112 -0
  171. package/agent-pane/frontend/src/api/workspaces.ts +17 -3
  172. package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +49 -0
  173. package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +2 -5
  174. package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +133 -0
  175. package/agent-pane/frontend/src/components/CommandInputBar.tsx +296 -17
  176. package/agent-pane/frontend/src/components/CommandPalette.test.tsx +230 -0
  177. package/agent-pane/frontend/src/components/CommandPalette.tsx +26 -23
  178. package/agent-pane/frontend/src/components/IdeWorkspace.tsx +1 -1
  179. package/agent-pane/frontend/src/components/ModelSelector.test.tsx +129 -0
  180. package/agent-pane/frontend/src/components/ModelSelector.tsx +191 -0
  181. package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +95 -0
  182. package/agent-pane/frontend/src/components/NewSessionDialog.tsx +58 -13
  183. package/agent-pane/frontend/src/components/RenderArea.test.tsx +160 -0
  184. package/agent-pane/frontend/src/components/RenderArea.tsx +18 -25
  185. package/agent-pane/frontend/src/components/RightPanel.test.tsx +43 -0
  186. package/agent-pane/frontend/src/components/RightPanel.tsx +1 -1
  187. package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +32 -0
  188. package/agent-pane/frontend/src/components/RuntimeIcon.tsx +1 -1
  189. package/agent-pane/frontend/src/components/SessionTabs.test.tsx +72 -0
  190. package/agent-pane/frontend/src/components/SessionTabs.tsx +12 -12
  191. package/agent-pane/frontend/src/components/StatusBar.test.tsx +56 -0
  192. package/agent-pane/frontend/src/components/StatusBar.tsx +25 -31
  193. package/agent-pane/frontend/src/components/StatusDot.test.tsx +25 -0
  194. package/agent-pane/frontend/src/components/StatusDot.tsx +2 -7
  195. package/agent-pane/frontend/src/components/Toast.test.tsx +79 -0
  196. package/agent-pane/frontend/src/components/Toast.tsx +2 -6
  197. package/agent-pane/frontend/src/components/TurnContent.test.tsx +287 -0
  198. package/agent-pane/frontend/src/components/TurnContent.tsx +107 -24
  199. package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +297 -0
  200. package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +33 -32
  201. package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +32 -0
  202. package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +63 -0
  203. package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +78 -0
  204. package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +96 -0
  205. package/agent-pane/frontend/src/components/input/SlashPopup.tsx +63 -0
  206. package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +61 -0
  207. package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +12 -39
  208. package/agent-pane/frontend/src/components/panels/LogsPanel.test.tsx +58 -0
  209. package/agent-pane/frontend/src/components/panels/LogsPanel.tsx +1 -1
  210. package/agent-pane/frontend/src/components/panels/MetricsPanel.test.tsx +71 -0
  211. package/agent-pane/frontend/src/components/panels/MetricsPanel.tsx +63 -31
  212. package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +83 -0
  213. package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +66 -7
  214. package/agent-pane/frontend/src/constants/models.test.ts +97 -0
  215. package/agent-pane/frontend/src/constants/models.ts +129 -0
  216. package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +42 -0
  217. package/agent-pane/frontend/src/hooks/useEscapeKey.ts +13 -0
  218. package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +92 -0
  219. package/agent-pane/frontend/src/hooks/useOutsideClick.ts +19 -0
  220. package/agent-pane/frontend/src/hooks/useProvider.ts +44 -0
  221. package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +114 -0
  222. package/agent-pane/frontend/src/hooks/useSessionStatus.ts +34 -0
  223. package/agent-pane/frontend/src/main.test.tsx +37 -0
  224. package/agent-pane/frontend/src/main.tsx +1 -1
  225. package/agent-pane/frontend/src/store/sessionSlice.ts +417 -0
  226. package/agent-pane/frontend/src/store/store.test.ts +1284 -0
  227. package/agent-pane/frontend/src/store/store.ts +16 -617
  228. package/agent-pane/frontend/src/store/uiSlice.ts +83 -0
  229. package/agent-pane/frontend/src/store/workspaceSlice.ts +77 -0
  230. package/agent-pane/frontend/src/store/wsSlice.ts +292 -0
  231. package/agent-pane/frontend/src/styles/app.css +113 -7
  232. package/agent-pane/frontend/src/test/component-helpers.ts +36 -0
  233. package/agent-pane/frontend/src/test/fixtures.ts +96 -0
  234. package/agent-pane/frontend/src/test-setup.ts +5 -0
  235. package/agent-pane/frontend/src/types/index.test.ts +128 -0
  236. package/agent-pane/frontend/src/types/index.ts +80 -104
  237. package/agent-pane/frontend/src/utils/changes.test.ts +81 -0
  238. package/agent-pane/frontend/src/utils/changes.ts +39 -0
  239. package/agent-pane/frontend/src/utils/duration.test.ts +71 -0
  240. package/agent-pane/frontend/src/utils/duration.ts +14 -0
  241. package/agent-pane/frontend/tsconfig.json +4 -1
  242. package/agent-pane/frontend/vite.config.ts +6 -0
  243. package/agent-pane/frontend/vitest.config.ts +29 -0
  244. package/agent-pane/package-lock.json +1527 -0
  245. package/agent-pane/package.json +19 -0
  246. package/agent-pane/shared/provider-types.ts +53 -0
  247. package/agent-pane/shared/types.ts +107 -0
  248. package/agentpane-branch-session.png +0 -0
  249. package/agentpane-expanded-all.png +0 -0
  250. package/agentpane-fixed-rendering.png +0 -0
  251. package/agentpane-focus-check.png +0 -0
  252. package/agentpane-initial.png +0 -0
  253. package/agentpane-mcp-detail.png +0 -0
  254. package/agentpane-no-focus-ring.png +0 -0
  255. package/agentpane-soft-focus.png +0 -0
  256. package/backend/dist/src/llm-provider/ollama-llm.provider.js +1 -0
  257. package/backend/dist/src/llm-provider/ollama-llm.provider.js.map +1 -1
  258. package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
  259. package/data.db +0 -0
  260. package/empty-state.png +0 -0
  261. package/frontend-home.png +0 -0
  262. package/model-selector-open.png +0 -0
  263. package/package.json +1 -1
  264. package/slash-popup-working.png +0 -0
  265. package/agent-pane/frontend/src/components/DiffViewer.tsx +0 -27
@@ -1,40 +1,319 @@
1
- import { useState } from 'react';
2
- import { CornerDownLeft, Square } from 'lucide-react';
1
+ import { useRef, useState, useEffect } from 'react';
2
+ import { ArrowUp, Square } from 'lucide-react';
3
3
  import { useStore } from '../store/store.ts';
4
+ import SlashPopup, { type SlashCommand, filterCommands } from './input/SlashPopup.tsx';
5
+ import OptionSubPopup from './input/OptionSubPopup.tsx';
6
+ import SkillMentionPopup from './input/SkillMentionPopup.tsx';
7
+ import { useProvider } from '../hooks/useProvider.ts';
8
+ import type { SessionRuntimeConfig, Extension } from '../types/index.ts';
9
+
10
+ const MIN_HEIGHT = 32;
11
+ const MAX_HEIGHT = 200;
12
+
13
+ /** Parse runtime_config from the active session detail. */
14
+ function parseRuntimeConfig(json: string | null | undefined): SessionRuntimeConfig {
15
+ if (!json) return {};
16
+ try {
17
+ return JSON.parse(json) as SessionRuntimeConfig;
18
+ } catch {
19
+ return {};
20
+ }
21
+ }
4
22
 
5
23
  export default function CommandInputBar() {
6
24
  const [value, setValue] = useState('');
7
- const { submitPrompt, cancelTurn } = useStore();
25
+ const taRef = useRef<HTMLTextAreaElement>(null);
26
+ const { submitPrompt, cancelTurn, updateRuntimeConfig, setShowModelSelector } = useStore();
27
+ const activeSessionId = useStore((s) => s.activeSessionId);
8
28
  const detail = useStore((s) => (s.activeSessionId ? s.sessionDetails[s.activeSessionId] : null));
29
+ const restoredPrompt = useStore((s) => s.restoredPrompt);
9
30
  const isRunning = detail?.is_running === 1;
31
+ const noSession = !activeSessionId;
32
+
33
+ const { capabilities } = useProvider(detail?.runtime_type);
34
+
35
+ // Slash popup state
36
+ const [slashOpen, setSlashOpen] = useState(false);
37
+ const [slashFilter, setSlashFilter] = useState('');
38
+ const [selectedIndex, setSelectedIndex] = useState(0);
39
+ const [subPopup, setSubPopup] = useState<SlashCommand['subPopup']>(undefined);
40
+ const [skillPopup, setSkillPopup] = useState(false);
41
+ const [skillFilter, setSkillFilter] = useState('');
42
+ const [selectedSkills, setSelectedSkills] = useState<Extension[]>([]);
43
+
44
+ const commands = slashOpen ? filterCommands(slashFilter, capabilities) : [];
45
+
46
+ // Restore prompt after cancel
47
+ useEffect(() => {
48
+ if (restoredPrompt !== null) {
49
+ setValue(restoredPrompt);
50
+ requestAnimationFrame(() => {
51
+ if (taRef.current) {
52
+ taRef.current.style.height = 'auto';
53
+ taRef.current.style.height = Math.min(taRef.current.scrollHeight, MAX_HEIGHT) + 'px';
54
+ }
55
+ });
56
+ useStore.setState({ restoredPrompt: null });
57
+ }
58
+ }, [restoredPrompt]);
59
+
60
+ // ESC to cancel running turn (global — textarea is disabled while running)
61
+ useEffect(() => {
62
+ if (!isRunning) return;
63
+ function onKey(e: KeyboardEvent) {
64
+ if (e.key === 'Escape') {
65
+ e.preventDefault();
66
+ cancelTurn();
67
+ }
68
+ }
69
+ window.addEventListener('keydown', onKey);
70
+ return () => window.removeEventListener('keydown', onKey);
71
+ }, [isRunning, cancelTurn]);
72
+
73
+ // Close slash popup when session changes
74
+ useEffect(() => {
75
+ setSlashOpen(false);
76
+ setSubPopup(undefined);
77
+ setSkillPopup(false);
78
+ setSelectedSkills([]);
79
+ }, [activeSessionId]);
80
+
81
+ function autoResize() {
82
+ const el = taRef.current;
83
+ if (!el) return;
84
+ el.style.height = 'auto';
85
+ el.style.height = Math.min(el.scrollHeight, MAX_HEIGHT) + 'px';
86
+ }
10
87
 
11
88
  function handleSend() {
12
89
  const v = value.trim();
13
90
  if (!v || isRunning) return;
14
- setValue(''); // Clear input immediately for visual feedback
15
- submitPrompt(v);
91
+
92
+ // Build prompt with skill prefixes
93
+ let prompt = v;
94
+ if (selectedSkills.length > 0) {
95
+ const skillPrefix = selectedSkills.map((s) => `$${s.name}`).join(' ');
96
+ prompt = `${skillPrefix}\n\n${v}`;
97
+ }
98
+
99
+ setValue('');
100
+ setSelectedSkills([]);
101
+ if (taRef.current) taRef.current.style.height = MIN_HEIGHT + 'px';
102
+ setSlashOpen(false);
103
+ setSubPopup(undefined);
104
+ setSkillPopup(false);
105
+ submitPrompt(prompt);
106
+ }
107
+
108
+ function handleSlashCommand(cmd: SlashCommand) {
109
+ if (cmd.subPopup) {
110
+ setSubPopup(cmd.subPopup);
111
+ } else if (cmd.id === 'skills') {
112
+ setSkillPopup(true);
113
+ setSkillFilter('');
114
+ setSlashOpen(false);
115
+ setValue('');
116
+ } else if (cmd.id === 'model') {
117
+ setShowModelSelector(true);
118
+ setSlashOpen(false);
119
+ setValue('');
120
+ } else {
121
+ setSlashOpen(false);
122
+ setValue('');
123
+ }
124
+ }
125
+
126
+ function handleSkillSelect(skill: Extension) {
127
+ setSelectedSkills((prev) => [...prev, skill]);
128
+ setSkillPopup(false);
129
+ setValue('');
130
+ if (taRef.current) taRef.current.style.height = MIN_HEIGHT + 'px';
131
+ }
132
+
133
+ function handleOptionSelect(optionValue: string) {
134
+ const config = parseRuntimeConfig(detail?.runtime_config);
135
+
136
+ if (subPopup === 'sandbox') {
137
+ config.sandboxMode = optionValue as SessionRuntimeConfig['sandboxMode'];
138
+ } else if (subPopup === 'websearch') {
139
+ config.webSearchMode = optionValue as SessionRuntimeConfig['webSearchMode'];
140
+ } else if (subPopup === 'network') {
141
+ config.networkAccessEnabled = optionValue === 'true';
142
+ }
143
+
144
+ updateRuntimeConfig(config as Record<string, unknown>);
145
+ setSubPopup(undefined);
146
+ setSlashOpen(false);
147
+ setValue('');
148
+ if (taRef.current) taRef.current.style.height = MIN_HEIGHT + 'px';
149
+ }
150
+
151
+ function handleKeyDown(e: React.KeyboardEvent) {
152
+ // Slash popup keyboard navigation
153
+ if (slashOpen && !subPopup) {
154
+ if (e.key === 'ArrowDown') {
155
+ e.preventDefault();
156
+ setSelectedIndex((i) => Math.min(i + 1, commands.length - 1));
157
+ return;
158
+ }
159
+ if (e.key === 'ArrowUp') {
160
+ e.preventDefault();
161
+ setSelectedIndex((i) => Math.max(i - 1, 0));
162
+ return;
163
+ }
164
+ if (e.key === 'Enter') {
165
+ e.preventDefault();
166
+ if (commands[selectedIndex]) {
167
+ handleSlashCommand(commands[selectedIndex]);
168
+ }
169
+ return;
170
+ }
171
+ if (e.key === 'Escape') {
172
+ e.preventDefault();
173
+ setSlashOpen(false);
174
+ setValue('');
175
+ return;
176
+ }
177
+ }
178
+
179
+ if (subPopup && e.key === 'Escape') {
180
+ e.preventDefault();
181
+ setSubPopup(undefined);
182
+ return;
183
+ }
184
+
185
+ // Cmd/Ctrl+Enter to send
186
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && !isRunning) {
187
+ e.preventDefault();
188
+ handleSend();
189
+ return;
190
+ }
191
+
192
+ // ESC to cancel while running
193
+ if (e.key === 'Escape' && isRunning) {
194
+ e.preventDefault();
195
+ cancelTurn();
196
+ }
197
+ }
198
+
199
+ function handleChange(e: React.ChangeEvent<HTMLTextAreaElement>) {
200
+ const newValue = e.target.value;
201
+ setValue(newValue);
202
+ autoResize();
203
+
204
+ // Detect `/` at start of input → slash popup
205
+ if (newValue.startsWith('/') && !newValue.includes(' ') && !isRunning) {
206
+ setSlashOpen(true);
207
+ setSlashFilter(newValue.slice(1));
208
+ setSelectedIndex(0);
209
+ } else {
210
+ setSlashOpen(false);
211
+ setSubPopup(undefined);
212
+ }
213
+
214
+ // Detect `$` trigger anywhere → skill popup
215
+ if (newValue.startsWith('$') && !isRunning) {
216
+ setSkillPopup(true);
217
+ setSkillFilter(newValue.slice(1));
218
+ } else if (!newValue.startsWith('$')) {
219
+ setSkillPopup(false);
220
+ }
16
221
  }
17
222
 
223
+ const currentConfig = parseRuntimeConfig(detail?.runtime_config);
224
+
225
+ const sandboxOptions = [
226
+ { value: 'read-only', label: 'Read Only', description: 'Agent can only read files' },
227
+ { value: 'workspace-write', label: 'Workspace Write', description: 'Agent can read and write in workspace' },
228
+ { value: 'danger-full-access', label: 'Danger Full Access', description: 'Agent has unrestricted access' },
229
+ ];
230
+
231
+ const webSearchOptions = [
232
+ { value: 'disabled', label: 'Disabled', description: 'No web search' },
233
+ { value: 'cached', label: 'Cached', description: 'Use cached search results' },
234
+ { value: 'live', label: 'Live', description: 'Perform live web searches' },
235
+ ];
236
+
237
+ const networkOptions = [
238
+ { value: 'false', label: 'Disabled', description: 'No network access' },
239
+ { value: 'true', label: 'Enabled', description: 'Allow network access in sandbox' },
240
+ ];
241
+
18
242
  return (
19
243
  <div className="cmd-bar">
20
- <input
21
- className="cmd-input"
22
- placeholder={isRunning ? 'Agent is running…' : 'Type a message to the agent…'}
244
+ {slashOpen && !subPopup && (
245
+ <SlashPopup
246
+ commands={commands}
247
+ selectedIndex={selectedIndex}
248
+ onSelect={handleSlashCommand}
249
+ onHover={setSelectedIndex}
250
+ />
251
+ )}
252
+ {slashOpen && subPopup === 'sandbox' && (
253
+ <OptionSubPopup
254
+ title="Sandbox Mode"
255
+ options={sandboxOptions}
256
+ currentValue={currentConfig.sandboxMode}
257
+ onSelect={handleOptionSelect}
258
+ onClose={() => setSubPopup(undefined)}
259
+ />
260
+ )}
261
+ {slashOpen && subPopup === 'websearch' && (
262
+ <OptionSubPopup
263
+ title="Web Search"
264
+ options={webSearchOptions}
265
+ currentValue={currentConfig.webSearchMode}
266
+ onSelect={handleOptionSelect}
267
+ onClose={() => setSubPopup(undefined)}
268
+ />
269
+ )}
270
+ {slashOpen && subPopup === 'network' && (
271
+ <OptionSubPopup
272
+ title="Network Access"
273
+ options={networkOptions}
274
+ currentValue={currentConfig.networkAccessEnabled === true ? 'true' : 'false'}
275
+ onSelect={handleOptionSelect}
276
+ onClose={() => setSubPopup(undefined)}
277
+ />
278
+ )}
279
+ {skillPopup && (
280
+ <SkillMentionPopup
281
+ filter={skillFilter}
282
+ onSelect={handleSkillSelect}
283
+ onClose={() => { setSkillPopup(false); setValue(''); }}
284
+ />
285
+ )}
286
+ {selectedSkills.length > 0 && (
287
+ <div className="skill-badges">
288
+ {selectedSkills.map((s) => (
289
+ <span key={s.id} className="skill-badge">
290
+ ${s.name}
291
+ <button className="skill-badge-remove" onClick={() => setSelectedSkills((prev) => prev.filter((x) => x.id !== s.id))}>×</button>
292
+ </span>
293
+ ))}
294
+ </div>
295
+ )}
296
+ <textarea
297
+ ref={taRef}
298
+ className="cmd-textarea"
299
+ placeholder={
300
+ noSession ? 'Select or create a session…' : isRunning ? 'Agent is running…' : 'Type a message… Use / for commands'
301
+ }
23
302
  autoComplete="off"
24
303
  value={value}
25
- disabled={isRunning}
26
- onChange={(e) => setValue(e.target.value)}
27
- onKeyDown={(e) => {
28
- if (e.key === 'Enter' && !isRunning) handleSend();
29
- }}
304
+ disabled={isRunning || noSession}
305
+ rows={1}
306
+ style={{ minHeight: MIN_HEIGHT, maxHeight: MAX_HEIGHT }}
307
+ onChange={handleChange}
308
+ onKeyDown={handleKeyDown}
30
309
  />
31
310
  {isRunning ? (
32
- <button className="cmd-send" style={{ background: 'var(--red)' }} onClick={() => cancelTurn()}>
33
- <Square size={14} /> Cancel
311
+ <button className="cmd-stop-btn" onClick={() => cancelTurn()} title="Stop (Esc)">
312
+ <Square size={15} />
34
313
  </button>
35
314
  ) : (
36
- <button className="cmd-send" onClick={handleSend} disabled={!value.trim()}>
37
- <CornerDownLeft size={14} /> Send
315
+ <button className="cmd-send-btn" onClick={handleSend} disabled={!value.trim() || noSession} title="Send (⌘↵)">
316
+ <ArrowUp size={16} />
38
317
  </button>
39
318
  )}
40
319
  </div>
@@ -0,0 +1,230 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { render, screen, fireEvent, cleanup } from '@testing-library/react';
3
+ import CommandPalette from './CommandPalette.tsx';
4
+ import { resetStore } from '../test/component-helpers.ts';
5
+ import { makeSession, makeDetail } from '../test/fixtures.ts';
6
+
7
+ describe('CommandPalette', () => {
8
+ beforeEach(() => {
9
+ resetStore();
10
+ });
11
+ afterEach(() => {
12
+ cleanup();
13
+ });
14
+
15
+ it('returns null when show=false', () => {
16
+ const { container } = render(<CommandPalette />);
17
+ expect(container.firstChild).toBeNull();
18
+ });
19
+
20
+ it('renders search input when show=true', () => {
21
+ resetStore({ showCommandPalette: true });
22
+ render(<CommandPalette />);
23
+ expect(screen.getByPlaceholderText('Type a command…')).toBeInTheDocument();
24
+ });
25
+
26
+ it('shows all available commands when query is empty', () => {
27
+ resetStore({ showCommandPalette: true });
28
+ render(<CommandPalette />);
29
+ // "New Session" is always available
30
+ expect(screen.getByText('New Session')).toBeInTheDocument();
31
+ });
32
+
33
+ it('filters commands by query', () => {
34
+ resetStore({ showCommandPalette: true });
35
+ render(<CommandPalette />);
36
+ const input = screen.getByPlaceholderText('Type a command…');
37
+ fireEvent.change(input, { target: { value: 'new' } });
38
+ expect(screen.getByText('New Session')).toBeInTheDocument();
39
+ expect(screen.queryByText('Close Tab')).not.toBeInTheDocument();
40
+ });
41
+
42
+ it('shows "No matching commands" when filter empty', () => {
43
+ resetStore({ showCommandPalette: true });
44
+ render(<CommandPalette />);
45
+ const input = screen.getByPlaceholderText('Type a command…');
46
+ fireEvent.change(input, { target: { value: 'xyzzy' } });
47
+ expect(screen.getByText('No matching commands')).toBeInTheDocument();
48
+ });
49
+
50
+ it('ArrowDown moves selection down', () => {
51
+ // Need active session so multiple commands are available
52
+ const session = makeSession('s1');
53
+ const detail = makeDetail('s1', []);
54
+ resetStore({ showCommandPalette: true, activeSessionId: 's1', sessions: [session], sessionDetails: { s1: detail } });
55
+ render(<CommandPalette />);
56
+ const input = screen.getByPlaceholderText('Type a command…');
57
+ fireEvent.keyDown(input, { key: 'ArrowDown' });
58
+ const items = document.querySelectorAll('.palette-item');
59
+ expect(items[1]).toHaveClass('selected');
60
+ });
61
+
62
+ it('ArrowUp moves selection up', () => {
63
+ const session = makeSession('s1');
64
+ const detail = makeDetail('s1', []);
65
+ resetStore({ showCommandPalette: true, activeSessionId: 's1', sessions: [session], sessionDetails: { s1: detail } });
66
+ render(<CommandPalette />);
67
+ const input = screen.getByPlaceholderText('Type a command…');
68
+ // Move down twice, then up once
69
+ fireEvent.keyDown(input, { key: 'ArrowDown' });
70
+ fireEvent.keyDown(input, { key: 'ArrowDown' });
71
+ fireEvent.keyDown(input, { key: 'ArrowUp' });
72
+ const items = document.querySelectorAll('.palette-item');
73
+ expect(items[1]).toHaveClass('selected');
74
+ });
75
+
76
+ it('Enter executes selected command and closes palette', () => {
77
+ const setShowCommandPalette = vi.fn();
78
+ const setShowNewSessionDialog = vi.fn();
79
+ resetStore({ showCommandPalette: true, setShowCommandPalette, setShowNewSessionDialog });
80
+ render(<CommandPalette />);
81
+ const input = screen.getByPlaceholderText('Type a command…');
82
+ fireEvent.keyDown(input, { key: 'Enter' });
83
+ // First command is "New Session" — always available
84
+ expect(setShowNewSessionDialog).toHaveBeenCalledWith(true);
85
+ expect(setShowCommandPalette).toHaveBeenCalledWith(false);
86
+ });
87
+
88
+ it('ESC closes palette', () => {
89
+ const setShowCommandPalette = vi.fn();
90
+ resetStore({ showCommandPalette: true, setShowCommandPalette });
91
+ render(<CommandPalette />);
92
+ fireEvent.keyDown(screen.getByPlaceholderText('Type a command…'), { key: 'Escape' });
93
+ expect(setShowCommandPalette).toHaveBeenCalledWith(false);
94
+ });
95
+
96
+ it('click on command executes and closes', () => {
97
+ const setShowCommandPalette = vi.fn();
98
+ const setShowNewSessionDialog = vi.fn();
99
+ resetStore({ showCommandPalette: true, setShowCommandPalette, setShowNewSessionDialog });
100
+ render(<CommandPalette />);
101
+ fireEvent.click(screen.getByText('New Session'));
102
+ expect(setShowNewSessionDialog).toHaveBeenCalledWith(true);
103
+ expect(setShowCommandPalette).toHaveBeenCalledWith(false);
104
+ });
105
+
106
+ // --- command actions ---
107
+
108
+ it('Delete Session action calls deleteSession', () => {
109
+ const setShowCommandPalette = vi.fn();
110
+ const deleteSession = vi.fn().mockResolvedValue(undefined);
111
+ const session = makeSession('s1');
112
+ resetStore({
113
+ showCommandPalette: true,
114
+ activeSessionId: 's1',
115
+ sessions: [session],
116
+ setShowCommandPalette,
117
+ deleteSession,
118
+ });
119
+ render(<CommandPalette />);
120
+ fireEvent.click(screen.getByText('Delete Session'));
121
+ expect(deleteSession).toHaveBeenCalledWith('s1');
122
+ expect(setShowCommandPalette).toHaveBeenCalledWith(false);
123
+ });
124
+
125
+ it('Send Message action focuses .cmd-input element', () => {
126
+ // Stub a mock input element with focus
127
+ const mockFocus = vi.fn();
128
+ const mockInput = { focus: mockFocus } as unknown as HTMLInputElement;
129
+ const origQuery = document.querySelector;
130
+ document.querySelector = vi.fn((sel: string) =>
131
+ sel === '.cmd-input' ? mockInput : origQuery.call(document, sel),
132
+ ) as typeof document.querySelector;
133
+
134
+ const session = makeSession('s1');
135
+ const detail = makeDetail('s1', []);
136
+ resetStore({
137
+ showCommandPalette: true,
138
+ activeSessionId: 's1',
139
+ sessions: [session],
140
+ sessionDetails: { s1: detail },
141
+ });
142
+ render(<CommandPalette />);
143
+ fireEvent.click(screen.getByText('Send Message'));
144
+ expect(mockFocus).toHaveBeenCalled();
145
+ document.querySelector = origQuery;
146
+ });
147
+
148
+ it('Cancel Turn action calls cancelTurn when running', () => {
149
+ const setShowCommandPalette = vi.fn();
150
+ const cancelTurn = vi.fn().mockResolvedValue(undefined);
151
+ const session = makeSession('s1', { is_running: 1 });
152
+ const detail = makeDetail('s1', [], { is_running: 1 });
153
+ resetStore({
154
+ showCommandPalette: true,
155
+ activeSessionId: 's1',
156
+ sessions: [session],
157
+ sessionDetails: { s1: detail },
158
+ setShowCommandPalette,
159
+ cancelTurn,
160
+ });
161
+ render(<CommandPalette />);
162
+ fireEvent.click(screen.getByText('Cancel Turn'));
163
+ expect(cancelTurn).toHaveBeenCalled();
164
+ });
165
+
166
+ it('tab-switch commands call switchRightTab with correct tab', () => {
167
+ const switchRightTab = vi.fn();
168
+ const session = makeSession('s1');
169
+ const detail = makeDetail('s1', []);
170
+ resetStore({
171
+ showCommandPalette: true,
172
+ activeSessionId: 's1',
173
+ sessions: [session],
174
+ sessionDetails: { s1: detail },
175
+ switchRightTab,
176
+ });
177
+ render(<CommandPalette />);
178
+ fireEvent.click(screen.getByText('Switch to Turns Tab'));
179
+ expect(switchRightTab).toHaveBeenCalledWith('turns');
180
+ fireEvent.click(screen.getByText('Switch to Metrics Tab'));
181
+ expect(switchRightTab).toHaveBeenCalledWith('metrics');
182
+ fireEvent.click(screen.getByText('Switch to Changes Tab'));
183
+ expect(switchRightTab).toHaveBeenCalledWith('changes');
184
+ fireEvent.click(screen.getByText('Switch to Logs Tab'));
185
+ expect(switchRightTab).toHaveBeenCalledWith('logs');
186
+ });
187
+
188
+ it('Close Tab action calls closeTab', () => {
189
+ const setShowCommandPalette = vi.fn();
190
+ const closeTab = vi.fn().mockResolvedValue(undefined);
191
+ const session = makeSession('s1');
192
+ resetStore({
193
+ showCommandPalette: true,
194
+ activeSessionId: 's1',
195
+ sessions: [session],
196
+ setShowCommandPalette,
197
+ closeTab,
198
+ });
199
+ render(<CommandPalette />);
200
+ fireEvent.click(screen.getByText('Close Tab'));
201
+ expect(closeTab).toHaveBeenCalledWith('s1');
202
+ });
203
+
204
+ // --- UI interactions ---
205
+
206
+ it('overlay click closes palette', () => {
207
+ const setShowCommandPalette = vi.fn();
208
+ resetStore({ showCommandPalette: true, setShowCommandPalette });
209
+ render(<CommandPalette />);
210
+ fireEvent.click(document.querySelector('.palette-overlay')!);
211
+ expect(setShowCommandPalette).toHaveBeenCalledWith(false);
212
+ });
213
+
214
+ it('mouse enter on item sets selected class', () => {
215
+ const session = makeSession('s1');
216
+ const detail = makeDetail('s1', []);
217
+ resetStore({
218
+ showCommandPalette: true,
219
+ activeSessionId: 's1',
220
+ sessions: [session],
221
+ sessionDetails: { s1: detail },
222
+ });
223
+ render(<CommandPalette />);
224
+ const items = document.querySelectorAll('.palette-item');
225
+ // Hover over second item
226
+ fireEvent.mouseEnter(items[1]);
227
+ expect(items[1]).toHaveClass('selected');
228
+ expect(items[0]).not.toHaveClass('selected');
229
+ });
230
+ });
@@ -1,17 +1,6 @@
1
1
  import { useState, useEffect, useRef, useMemo } from 'react';
2
2
  import { useStore } from '../store/store.ts';
3
- import {
4
- Plus,
5
- Trash2,
6
- Send,
7
- Square,
8
- List,
9
- BarChart3,
10
- FileCode,
11
- ScrollText,
12
- X,
13
- CornerDownLeft,
14
- } from 'lucide-react';
3
+ import { Plus, Trash2, Send, Square, List, BarChart3, FileCode, ScrollText, X, CornerDownLeft } from 'lucide-react';
15
4
  import type { RightTab } from '../types/index.ts';
16
5
 
17
6
  interface Command {
@@ -51,7 +40,9 @@ export default function CommandPalette() {
51
40
  label: 'Delete Session',
52
41
  hint: 'Delete the active session',
53
42
  icon: Trash2,
54
- action: () => { if (store.activeSessionId) store.deleteSession(store.activeSessionId); },
43
+ action: () => {
44
+ if (store.activeSessionId) store.deleteSession(store.activeSessionId);
45
+ },
55
46
  available: !!store.activeSessionId,
56
47
  },
57
48
  {
@@ -59,7 +50,10 @@ export default function CommandPalette() {
59
50
  label: 'Send Message',
60
51
  hint: 'Focus the message input',
61
52
  icon: Send,
62
- action: () => { const el = document.querySelector<HTMLInputElement>('.cmd-input'); el?.focus(); },
53
+ action: () => {
54
+ const el = document.querySelector<HTMLInputElement>('.cmd-input');
55
+ el?.focus();
56
+ },
63
57
  available: !!store.activeSessionId && !isRunning,
64
58
  },
65
59
  {
@@ -107,7 +101,9 @@ export default function CommandPalette() {
107
101
  label: 'Close Tab',
108
102
  hint: 'Close the active session tab',
109
103
  icon: X,
110
- action: () => { if (store.activeSessionId) store.closeTab(store.activeSessionId); },
104
+ action: () => {
105
+ if (store.activeSessionId) store.closeTab(store.activeSessionId);
106
+ },
111
107
  available: !!store.activeSessionId,
112
108
  },
113
109
  ];
@@ -118,9 +114,7 @@ export default function CommandPalette() {
118
114
  const q = query.toLowerCase().trim();
119
115
  const available = commands.filter((c) => c.available);
120
116
  if (!q) return available;
121
- return available.filter((c) =>
122
- c.label.toLowerCase().includes(q) || c.hint.toLowerCase().includes(q),
123
- );
117
+ return available.filter((c) => c.label.toLowerCase().includes(q) || c.hint.toLowerCase().includes(q));
124
118
  }, [commands, query]);
125
119
 
126
120
  // Reset on open
@@ -163,7 +157,10 @@ export default function CommandPalette() {
163
157
  type="text"
164
158
  placeholder="Type a command…"
165
159
  value={query}
166
- onChange={(e) => { setQuery(e.target.value); setSelectedIdx(0); }}
160
+ onChange={(e) => {
161
+ setQuery(e.target.value);
162
+ setSelectedIdx(0);
163
+ }}
167
164
  onKeyDown={handleKeyDown}
168
165
  />
169
166
  <span className="palette-esc">ESC</span>
@@ -178,10 +175,15 @@ export default function CommandPalette() {
178
175
  <div
179
176
  key={cmd.id}
180
177
  className={`palette-item ${i === selectedIdx ? 'selected' : ''}`}
181
- onClick={() => { cmd.action(); setShow(false); }}
178
+ onClick={() => {
179
+ cmd.action();
180
+ setShow(false);
181
+ }}
182
182
  onMouseEnter={() => setSelectedIdx(i)}
183
183
  >
184
- <span className="palette-item-icon"><Icon size={16} /></span>
184
+ <span className="palette-item-icon">
185
+ <Icon size={16} />
186
+ </span>
185
187
  <span className="palette-item-label">{cmd.label}</span>
186
188
  <span className="palette-item-hint">{cmd.hint}</span>
187
189
  {i === selectedIdx && <CornerDownLeft size={12} className="palette-item-enter" />}
@@ -192,7 +194,8 @@ export default function CommandPalette() {
192
194
  )}
193
195
  <div className="palette-footer">
194
196
  <span className="palette-footer-hint">
195
- <span className="kbd">↑</span><span className="kbd">↓</span> Navigate
197
+ <span className="kbd">↑</span>
198
+ <span className="kbd">↓</span> Navigate
196
199
  </span>
197
200
  <span className="palette-footer-hint">
198
201
  <span className="kbd">↵</span> Select
@@ -204,4 +207,4 @@ export default function CommandPalette() {
204
207
  </div>
205
208
  </div>
206
209
  );
207
- }
210
+ }
@@ -22,4 +22,4 @@ export default function IdeWorkspace() {
22
22
  <StatusBar />
23
23
  </div>
24
24
  );
25
- }
25
+ }