repoburg 1.3.202 → 1.3.204
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.
- package/agent-pane/backend/dist/index.js +2092 -32001
- package/agent-pane/backend/dist/server.js +45304 -0
- package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/daemon/dist/agentPane.js +128 -0
- package/daemon/dist/agentPane.spec.js +183 -0
- package/daemon/dist/index.js +28 -8
- package/daemon/package.json +1 -1
- package/package.json +15 -2
- package/agent-pane/.prettierignore +0 -5
- package/agent-pane/.prettierrc +0 -8
- package/agent-pane/backend/coverage/base.css +0 -224
- package/agent-pane/backend/coverage/block-navigation.js +0 -87
- package/agent-pane/backend/coverage/clover.xml +0 -887
- package/agent-pane/backend/coverage/coverage-final.json +0 -10
- package/agent-pane/backend/coverage/favicon.png +0 -0
- package/agent-pane/backend/coverage/index.html +0 -176
- package/agent-pane/backend/coverage/prettify.css +0 -1
- package/agent-pane/backend/coverage/prettify.js +0 -2
- package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/backend/coverage/sorter.js +0 -210
- package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +0 -1402
- package/agent-pane/backend/coverage/src/codex/index.html +0 -131
- package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +0 -331
- package/agent-pane/backend/coverage/src/db/index.html +0 -131
- package/agent-pane/backend/coverage/src/db/index.ts.html +0 -436
- package/agent-pane/backend/coverage/src/db/schema.ts.html +0 -175
- package/agent-pane/backend/coverage/src/index.html +0 -116
- package/agent-pane/backend/coverage/src/index.ts.html +0 -262
- package/agent-pane/backend/coverage/src/routes/index.html +0 -146
- package/agent-pane/backend/coverage/src/routes/sessions.ts.html +0 -991
- package/agent-pane/backend/coverage/src/routes/turns.ts.html +0 -256
- package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +0 -406
- package/agent-pane/backend/coverage/src/ws/index.html +0 -116
- package/agent-pane/backend/coverage/src/ws/index.ts.html +0 -238
- package/agent-pane/backend/data.db +0 -0
- package/agent-pane/backend/data.db-shm +0 -0
- package/agent-pane/backend/data.db-wal +0 -0
- package/agent-pane/backend/package-lock.json +0 -5945
- package/agent-pane/backend/package.json +0 -33
- package/agent-pane/backend/scripts/backfill-from-jsonl.ts +0 -206
- package/agent-pane/backend/scripts/import-session-from-jsonl.ts +0 -264
- package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +0 -271
- package/agent-pane/backend/src/claude/claude-message-mapper.ts +0 -259
- package/agent-pane/backend/src/claude/claude-provider.test.ts +0 -455
- package/agent-pane/backend/src/claude/claude-provider.ts +0 -1319
- package/agent-pane/backend/src/claude/claude-types.ts +0 -45
- package/agent-pane/backend/src/codex/app-server-client.test.ts +0 -183
- package/agent-pane/backend/src/codex/app-server-client.ts +0 -247
- package/agent-pane/backend/src/codex/app-server-types.ts +0 -259
- package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +0 -56
- package/agent-pane/backend/src/codex/codex-provider.test.ts +0 -60
- package/agent-pane/backend/src/codex/codex-provider.ts +0 -619
- package/agent-pane/backend/src/codex/item-mapper.test.ts +0 -221
- package/agent-pane/backend/src/codex/item-mapper.ts +0 -86
- package/agent-pane/backend/src/codex/shared-app-server.ts +0 -25
- package/agent-pane/backend/src/db/index.test.ts +0 -125
- package/agent-pane/backend/src/db/index.ts +0 -236
- package/agent-pane/backend/src/db/schema.ts +0 -34
- package/agent-pane/backend/src/index.ts +0 -89
- package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +0 -29
- package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +0 -40
- package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +0 -16
- package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +0 -60
- package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +0 -34
- package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +0 -85
- package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +0 -48
- package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +0 -35
- package/agent-pane/backend/src/integration/delta-streaming.test.ts +0 -130
- package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +0 -272
- package/agent-pane/backend/src/integration/helpers/test-app.ts +0 -110
- package/agent-pane/backend/src/integration/helpers/ws-capture.ts +0 -64
- package/agent-pane/backend/src/integration/item-lifecycle.test.ts +0 -133
- package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +0 -102
- package/agent-pane/backend/src/integration/missing-item-started.test.ts +0 -91
- package/agent-pane/backend/src/integration/multi-turn.test.ts +0 -121
- package/agent-pane/backend/src/integration/session-operations.test.ts +0 -175
- package/agent-pane/backend/src/integration/threads-claude-fork.test.ts +0 -241
- package/agent-pane/backend/src/integration/threads.test.ts +0 -309
- package/agent-pane/backend/src/integration/title-generation.test.ts +0 -109
- package/agent-pane/backend/src/integration/turn-cancel.test.ts +0 -111
- package/agent-pane/backend/src/integration/turn-failure.test.ts +0 -97
- package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +0 -141
- package/agent-pane/backend/src/providers/index.ts +0 -16
- package/agent-pane/backend/src/providers/registry.test.ts +0 -92
- package/agent-pane/backend/src/providers/registry.ts +0 -43
- package/agent-pane/backend/src/providers/runtime-provider.ts +0 -49
- package/agent-pane/backend/src/repoburg/action-mapping.test.ts +0 -122
- package/agent-pane/backend/src/repoburg/action-mapping.ts +0 -294
- package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +0 -271
- package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +0 -285
- package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +0 -356
- package/agent-pane/backend/src/repoburg/backend-resolver.ts +0 -261
- package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +0 -180
- package/agent-pane/backend/src/repoburg/repoburg-client.ts +0 -194
- package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +0 -780
- package/agent-pane/backend/src/repoburg/repoburg-provider.ts +0 -471
- package/agent-pane/backend/src/repoburg/turn-projector.test.ts +0 -621
- package/agent-pane/backend/src/repoburg/turn-projector.ts +0 -637
- package/agent-pane/backend/src/routes/files.ts +0 -31
- package/agent-pane/backend/src/routes/mcp.ts +0 -33
- package/agent-pane/backend/src/routes/models.test.ts +0 -92
- package/agent-pane/backend/src/routes/models.ts +0 -16
- package/agent-pane/backend/src/routes/plugins.ts +0 -40
- package/agent-pane/backend/src/routes/providers.test.ts +0 -86
- package/agent-pane/backend/src/routes/providers.ts +0 -86
- package/agent-pane/backend/src/routes/sessions.test.ts +0 -772
- package/agent-pane/backend/src/routes/sessions.ts +0 -565
- package/agent-pane/backend/src/routes/skills.test.ts +0 -114
- package/agent-pane/backend/src/routes/skills.ts +0 -70
- package/agent-pane/backend/src/routes/threads.ts +0 -323
- package/agent-pane/backend/src/routes/turns.test.ts +0 -196
- package/agent-pane/backend/src/routes/turns.ts +0 -103
- package/agent-pane/backend/src/routes/workspaces.test.ts +0 -197
- package/agent-pane/backend/src/routes/workspaces.ts +0 -128
- package/agent-pane/backend/src/ws/index.test.ts +0 -107
- package/agent-pane/backend/src/ws/index.ts +0 -51
- package/agent-pane/backend/tsconfig.json +0 -25
- package/agent-pane/backend/vitest.config.ts +0 -20
- package/agent-pane/eslint.config.js +0 -33
- package/agent-pane/frontend/coverage/base.css +0 -224
- package/agent-pane/frontend/coverage/block-navigation.js +0 -87
- package/agent-pane/frontend/coverage/clover.xml +0 -2962
- package/agent-pane/frontend/coverage/coverage-final.json +0 -37
- package/agent-pane/frontend/coverage/favicon.png +0 -0
- package/agent-pane/frontend/coverage/index.html +0 -251
- package/agent-pane/frontend/coverage/prettify.css +0 -1
- package/agent-pane/frontend/coverage/prettify.js +0 -2
- package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/frontend/coverage/sorter.js +0 -210
- package/agent-pane/frontend/coverage/src/App.tsx.html +0 -217
- package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +0 -223
- package/agent-pane/frontend/coverage/src/api/client.ts.html +0 -187
- package/agent-pane/frontend/coverage/src/api/index.html +0 -161
- package/agent-pane/frontend/coverage/src/api/sessions.ts.html +0 -316
- package/agent-pane/frontend/coverage/src/api/websocket.ts.html +0 -544
- package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +0 -199
- package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +0 -226
- package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +0 -382
- package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +0 -715
- package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +0 -160
- package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +0 -478
- package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +0 -472
- package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +0 -574
- package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +0 -205
- package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +0 -133
- package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +0 -217
- package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +0 -271
- package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +0 -109
- package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +0 -313
- package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +0 -1216
- package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +0 -1138
- package/agent-pane/frontend/coverage/src/components/index.html +0 -326
- package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +0 -247
- package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +0 -160
- package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +0 -427
- package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +0 -397
- package/agent-pane/frontend/coverage/src/components/panels/index.html +0 -161
- package/agent-pane/frontend/coverage/src/constants/index.html +0 -116
- package/agent-pane/frontend/coverage/src/constants/models.ts.html +0 -406
- package/agent-pane/frontend/coverage/src/hooks/index.html +0 -146
- package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +0 -124
- package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +0 -142
- package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +0 -187
- package/agent-pane/frontend/coverage/src/index.html +0 -146
- package/agent-pane/frontend/coverage/src/main.tsx.html +0 -115
- package/agent-pane/frontend/coverage/src/store/index.html +0 -116
- package/agent-pane/frontend/coverage/src/store/store.ts.html +0 -2404
- package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +0 -190
- package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +0 -367
- package/agent-pane/frontend/coverage/src/test/index.html +0 -131
- package/agent-pane/frontend/coverage/src/types/index.html +0 -116
- package/agent-pane/frontend/coverage/src/types/index.ts.html +0 -466
- package/agent-pane/frontend/coverage/src/utils/changes.ts.html +0 -202
- package/agent-pane/frontend/coverage/src/utils/duration.ts.html +0 -127
- package/agent-pane/frontend/coverage/src/utils/index.html +0 -131
- package/agent-pane/frontend/dist/assets/index-CJxD4YmJ.js +0 -370
- package/agent-pane/frontend/dist/assets/index-r-xwfOKQ.css +0 -1
- package/agent-pane/frontend/dist/index.html +0 -19
- package/agent-pane/frontend/index.html +0 -18
- package/agent-pane/frontend/package-lock.json +0 -4211
- package/agent-pane/frontend/package.json +0 -35
- package/agent-pane/frontend/src/App.test.tsx +0 -74
- package/agent-pane/frontend/src/App.tsx +0 -44
- package/agent-pane/frontend/src/AppInitializer.test.tsx +0 -142
- package/agent-pane/frontend/src/AppInitializer.tsx +0 -46
- package/agent-pane/frontend/src/__tests__/ThreadsPanel.test.tsx +0 -192
- package/agent-pane/frontend/src/api/client.test.ts +0 -126
- package/agent-pane/frontend/src/api/client.ts +0 -34
- package/agent-pane/frontend/src/api/models.ts +0 -7
- package/agent-pane/frontend/src/api/providers.ts +0 -17
- package/agent-pane/frontend/src/api/sessions.test.ts +0 -246
- package/agent-pane/frontend/src/api/sessions.ts +0 -97
- package/agent-pane/frontend/src/api/skills.ts +0 -12
- package/agent-pane/frontend/src/api/websocket.test.ts +0 -288
- package/agent-pane/frontend/src/api/websocket.ts +0 -240
- package/agent-pane/frontend/src/api/workspaces.test.ts +0 -112
- package/agent-pane/frontend/src/api/workspaces.ts +0 -38
- package/agent-pane/frontend/src/components/AskUserItem.test.tsx +0 -143
- package/agent-pane/frontend/src/components/AskUserItem.tsx +0 -207
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +0 -49
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +0 -56
- package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +0 -133
- package/agent-pane/frontend/src/components/CommandInputBar.tsx +0 -584
- package/agent-pane/frontend/src/components/CommandPalette.test.tsx +0 -226
- package/agent-pane/frontend/src/components/CommandPalette.tsx +0 -194
- package/agent-pane/frontend/src/components/CommentableText.test.tsx +0 -196
- package/agent-pane/frontend/src/components/CommentableText.tsx +0 -379
- package/agent-pane/frontend/src/components/IdeWorkspace.test.tsx +0 -39
- package/agent-pane/frontend/src/components/IdeWorkspace.tsx +0 -51
- package/agent-pane/frontend/src/components/ModelSelector.test.tsx +0 -129
- package/agent-pane/frontend/src/components/ModelSelector.tsx +0 -191
- package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +0 -95
- package/agent-pane/frontend/src/components/NewSessionDialog.tsx +0 -129
- package/agent-pane/frontend/src/components/RenderArea.test.tsx +0 -160
- package/agent-pane/frontend/src/components/RenderArea.tsx +0 -170
- package/agent-pane/frontend/src/components/ResizeHandle.tsx +0 -29
- package/agent-pane/frontend/src/components/RightPanel.test.tsx +0 -43
- package/agent-pane/frontend/src/components/RightPanel.tsx +0 -46
- package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +0 -32
- package/agent-pane/frontend/src/components/RuntimeIcon.tsx +0 -16
- package/agent-pane/frontend/src/components/SessionTabs.test.tsx +0 -72
- package/agent-pane/frontend/src/components/SessionTabs.tsx +0 -49
- package/agent-pane/frontend/src/components/StatusBar.test.tsx +0 -56
- package/agent-pane/frontend/src/components/StatusBar.tsx +0 -129
- package/agent-pane/frontend/src/components/StatusDot.test.tsx +0 -25
- package/agent-pane/frontend/src/components/StatusDot.tsx +0 -8
- package/agent-pane/frontend/src/components/Toast.test.tsx +0 -79
- package/agent-pane/frontend/src/components/Toast.tsx +0 -76
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +0 -377
- package/agent-pane/frontend/src/components/TurnContent.tsx +0 -594
- package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +0 -297
- package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +0 -351
- package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +0 -32
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +0 -63
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +0 -78
- package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +0 -96
- package/agent-pane/frontend/src/components/input/SlashPopup.tsx +0 -82
- package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +0 -61
- package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +0 -54
- package/agent-pane/frontend/src/components/panels/TasksPanel.test.tsx +0 -97
- package/agent-pane/frontend/src/components/panels/TasksPanel.tsx +0 -208
- package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +0 -382
- package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +0 -131
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +0 -131
- package/agent-pane/frontend/src/constants/models.test.ts +0 -97
- package/agent-pane/frontend/src/constants/models.ts +0 -166
- package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +0 -42
- package/agent-pane/frontend/src/hooks/useEscapeKey.ts +0 -13
- package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +0 -92
- package/agent-pane/frontend/src/hooks/useOutsideClick.ts +0 -19
- package/agent-pane/frontend/src/hooks/useProvider.ts +0 -44
- package/agent-pane/frontend/src/hooks/useResizable.test.ts +0 -97
- package/agent-pane/frontend/src/hooks/useResizable.ts +0 -75
- package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +0 -116
- package/agent-pane/frontend/src/hooks/useSessionStatus.ts +0 -34
- package/agent-pane/frontend/src/main.test.tsx +0 -37
- package/agent-pane/frontend/src/main.tsx +0 -10
- package/agent-pane/frontend/src/store/sessionSlice.ts +0 -440
- package/agent-pane/frontend/src/store/store.test.ts +0 -1338
- package/agent-pane/frontend/src/store/store.ts +0 -37
- package/agent-pane/frontend/src/store/threadSlice.ts +0 -136
- package/agent-pane/frontend/src/store/uiSlice.ts +0 -133
- package/agent-pane/frontend/src/store/workspaceSlice.ts +0 -77
- package/agent-pane/frontend/src/store/wsSlice.ts +0 -368
- package/agent-pane/frontend/src/styles/app.css +0 -627
- package/agent-pane/frontend/src/styles/tokens.css +0 -62
- package/agent-pane/frontend/src/test/component-helpers.ts +0 -38
- package/agent-pane/frontend/src/test/fixtures.ts +0 -101
- package/agent-pane/frontend/src/test-setup.ts +0 -5
- package/agent-pane/frontend/src/types/index.test.ts +0 -128
- package/agent-pane/frontend/src/types/index.ts +0 -134
- package/agent-pane/frontend/src/utils/changes.test.ts +0 -81
- package/agent-pane/frontend/src/utils/changes.ts +0 -39
- package/agent-pane/frontend/src/utils/duration.test.ts +0 -76
- package/agent-pane/frontend/src/utils/duration.ts +0 -14
- package/agent-pane/frontend/tsconfig.json +0 -25
- package/agent-pane/frontend/vite.config.ts +0 -22
- package/agent-pane/frontend/vitest.config.ts +0 -29
- package/agent-pane/package-lock.json +0 -1527
- package/agent-pane/package.json +0 -19
- package/agent-pane/shared/provider-types.ts +0 -67
- package/agent-pane/shared/types.ts +0 -184
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../store/store.ts';
|
|
2
|
-
import TurnContent from './TurnContent.tsx';
|
|
3
|
-
import { collectFileChanges } from '../utils/changes.ts';
|
|
4
|
-
import { memo, useEffect, useRef } from 'react';
|
|
5
|
-
|
|
6
|
-
function RenderAreaBase() {
|
|
7
|
-
const {
|
|
8
|
-
activeSessionId,
|
|
9
|
-
sessionDetails,
|
|
10
|
-
activeTurnIndex,
|
|
11
|
-
activeRightTab,
|
|
12
|
-
changesScope,
|
|
13
|
-
submittingPrompt,
|
|
14
|
-
loading,
|
|
15
|
-
selectedChangePath,
|
|
16
|
-
setSelectedChangePath,
|
|
17
|
-
} = useStore();
|
|
18
|
-
|
|
19
|
-
const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
|
|
20
|
-
const turns = detail?.turns ?? [];
|
|
21
|
-
const renderScrollRef = useRef<HTMLDivElement>(null);
|
|
22
|
-
|
|
23
|
-
// Auto-scroll to bottom when session changes or new turns are added
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (renderScrollRef.current) {
|
|
26
|
-
renderScrollRef.current.scrollTop = renderScrollRef.current.scrollHeight;
|
|
27
|
-
}
|
|
28
|
-
}, [activeSessionId, turns.length]);
|
|
29
|
-
|
|
30
|
-
// F4: Scroll to selected change path when it changes
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (!selectedChangePath || activeRightTab !== 'changes') return;
|
|
33
|
-
const timer = setTimeout(() => {
|
|
34
|
-
const sanitized = selectedChangePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
35
|
-
const el = document.getElementById(`diff-${sanitized}`);
|
|
36
|
-
if (el) {
|
|
37
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
38
|
-
el.classList.add('diff-highlight');
|
|
39
|
-
setTimeout(() => el.classList.remove('diff-highlight'), 1500);
|
|
40
|
-
}
|
|
41
|
-
setSelectedChangePath(null);
|
|
42
|
-
}, 50);
|
|
43
|
-
return () => clearTimeout(timer);
|
|
44
|
-
}, [selectedChangePath, activeRightTab, setSelectedChangePath]);
|
|
45
|
-
|
|
46
|
-
// When Changes tab is active, render file changes from items
|
|
47
|
-
if (activeRightTab === 'changes') {
|
|
48
|
-
if (!detail) {
|
|
49
|
-
return (
|
|
50
|
-
<div className="render-scroll">
|
|
51
|
-
<div className="empty-state">No session selected</div>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const deduped = collectFileChanges(turns, changesScope, activeTurnIndex);
|
|
57
|
-
|
|
58
|
-
if (deduped.length === 0) {
|
|
59
|
-
return (
|
|
60
|
-
<div className="render-scroll">
|
|
61
|
-
<div className="render-content">
|
|
62
|
-
<p style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>No file changes in this scope.</p>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<div className="render-scroll" ref={renderScrollRef}>
|
|
70
|
-
<div className="render-content">
|
|
71
|
-
<div style={{ marginBottom: '20px' }}>
|
|
72
|
-
<span style={{ fontSize: '16px', fontWeight: 600, color: 'var(--text)' }}>
|
|
73
|
-
{changesScope === 'session' ? 'All Session Changes' : 'Current Turn Changes'}
|
|
74
|
-
</span>
|
|
75
|
-
<span style={{ fontSize: '12px', color: 'var(--text-muted)', marginLeft: '8px' }}>
|
|
76
|
-
{deduped.length} file{deduped.length > 1 ? 's' : ''}
|
|
77
|
-
</span>
|
|
78
|
-
</div>
|
|
79
|
-
{deduped.map((c) => {
|
|
80
|
-
const fileName = c.path.split('/').pop() || c.path;
|
|
81
|
-
const dir = c.path.replace('/' + fileName, '');
|
|
82
|
-
const statusLabel = c.kind === 'add' ? 'Added' : c.kind === 'delete' ? 'Deleted' : 'Modified';
|
|
83
|
-
const statusClass = c.kind === 'add' ? 'fc-add' : c.kind === 'delete' ? 'fc-del' : 'fc-mod';
|
|
84
|
-
const statusChar = c.kind === 'add' ? 'A' : c.kind === 'delete' ? 'D' : 'M';
|
|
85
|
-
const sanitizedId = c.path.replace(/[^a-zA-Z0-9]/g, '-');
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div key={c.path}>
|
|
89
|
-
<div className="diff-file-header" id={`diff-${sanitizedId}`}>
|
|
90
|
-
<span className={`diff-file-status ${statusClass}`}>{statusChar}</span>
|
|
91
|
-
<span className="diff-file-name">
|
|
92
|
-
{dir && <span style={{ color: 'var(--text-muted)' }}>{dir}/</span>}
|
|
93
|
-
{fileName}
|
|
94
|
-
</span>
|
|
95
|
-
<span className="diff-file-stats">
|
|
96
|
-
<span className={`diff-file-badge ${statusClass}`}>{statusLabel}</span>
|
|
97
|
-
</span>
|
|
98
|
-
</div>
|
|
99
|
-
<p style={{ color: 'var(--text-muted)', fontSize: '12px', fontStyle: 'italic', padding: '8px 16px' }}>
|
|
100
|
-
Diff content not available (Codex SDK limitation).
|
|
101
|
-
</p>
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
})}
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// F2: Skeleton loading when fetching session detail for the first time
|
|
111
|
-
if (loading && !detail) {
|
|
112
|
-
return (
|
|
113
|
-
<div className="render-scroll">
|
|
114
|
-
<div className="skeleton-bars">
|
|
115
|
-
<div className="skeleton-bar" />
|
|
116
|
-
<div className="skeleton-bar" />
|
|
117
|
-
<div className="skeleton-bar" />
|
|
118
|
-
<div className="skeleton-bar" />
|
|
119
|
-
<div className="skeleton-bar" />
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!detail) {
|
|
126
|
-
return (
|
|
127
|
-
<div className="render-scroll">
|
|
128
|
-
<div className="empty-state">No session selected</div>
|
|
129
|
-
</div>
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Render ALL turns, scrollable
|
|
134
|
-
return (
|
|
135
|
-
<div className="render-scroll" ref={renderScrollRef}>
|
|
136
|
-
{/* Submitting prompt — shown at top while waiting for API response */}
|
|
137
|
-
{submittingPrompt && (
|
|
138
|
-
<div className="turn-block">
|
|
139
|
-
<div className="turn-group">
|
|
140
|
-
<div className="render-content">
|
|
141
|
-
<div className="turn-header">
|
|
142
|
-
<span className="turn-header-icon" style={{ background: 'var(--amber)', color: '#fff' }}>
|
|
143
|
-
U
|
|
144
|
-
</span>
|
|
145
|
-
<span className="turn-header-title">You</span>
|
|
146
|
-
</div>
|
|
147
|
-
<p className="user-message">{submittingPrompt}</p>
|
|
148
|
-
</div>
|
|
149
|
-
<div className="render-content">
|
|
150
|
-
<p style={{ color: 'var(--text-muted)', fontStyle: 'italic' }}>Sending…</p>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
)}
|
|
155
|
-
|
|
156
|
-
{turns.length === 0 && !submittingPrompt && (
|
|
157
|
-
<div className="empty-state">No turns yet. Send a message below to start.</div>
|
|
158
|
-
)}
|
|
159
|
-
|
|
160
|
-
{turns.filter((t) => !t.thread_id).map((turn, idx) => (
|
|
161
|
-
<div key={turn.id} id={`turn-${idx}`} className="turn-block">
|
|
162
|
-
<TurnContent turn={turn} runtimeType={detail.runtime_type} threads={detail.threads ?? []} />
|
|
163
|
-
</div>
|
|
164
|
-
))}
|
|
165
|
-
</div>
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const RenderArea = memo(RenderAreaBase);
|
|
170
|
-
export default RenderArea;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useResizable, type ResizeSide } from '../hooks/useResizable.ts';
|
|
2
|
-
|
|
3
|
-
interface ResizeHandleProps {
|
|
4
|
-
side: ResizeSide;
|
|
5
|
-
minWidth: number;
|
|
6
|
-
maxWidth: number;
|
|
7
|
-
getWidth: () => number;
|
|
8
|
-
setWidth: (w: number) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Thin draggable gutter rendered as its own CSS grid track. Sits between a
|
|
13
|
-
* sidebar and the center pane. Dragging adjusts the adjacent sidebar width.
|
|
14
|
-
*/
|
|
15
|
-
export default function ResizeHandle({ side, minWidth, maxWidth, getWidth, setWidth }: ResizeHandleProps) {
|
|
16
|
-
const { onPointerDown, dragging } = useResizable({ side, minWidth, maxWidth, getWidth, setWidth });
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div
|
|
20
|
-
className={`resize-handle ${side}${dragging ? ' dragging' : ''}`}
|
|
21
|
-
onPointerDown={onPointerDown}
|
|
22
|
-
// Gutter shouldn't catch content scroll / text selection; keyboard a11y is non-goal here.
|
|
23
|
-
role="separator"
|
|
24
|
-
aria-orientation="vertical"
|
|
25
|
-
aria-label={`Resize ${side} panel`}
|
|
26
|
-
data-resizing={dragging ? '' : undefined}
|
|
27
|
-
/>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import RightPanel from './RightPanel.tsx';
|
|
4
|
-
import { resetStore } from '../test/component-helpers.ts';
|
|
5
|
-
|
|
6
|
-
describe('RightPanel', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
resetStore();
|
|
9
|
-
});
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
cleanup();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('renders 4 tab buttons', () => {
|
|
15
|
-
render(<RightPanel />);
|
|
16
|
-
expect(screen.getByText('Turns')).toBeInTheDocument();
|
|
17
|
-
expect(screen.getByText('Changes')).toBeInTheDocument();
|
|
18
|
-
expect(screen.getByText('Tasks')).toBeInTheDocument();
|
|
19
|
-
expect(screen.getByText('Threads')).toBeInTheDocument();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('active tab has active class', () => {
|
|
23
|
-
resetStore({ activeRightTab: 'changes' });
|
|
24
|
-
render(<RightPanel />);
|
|
25
|
-
const changesTab = screen.getByText('Changes').closest('.right-tab');
|
|
26
|
-
expect(changesTab).toHaveClass('active');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('click on tab calls switchRightTab', () => {
|
|
30
|
-
const switchRightTab = vi.fn();
|
|
31
|
-
resetStore({ switchRightTab });
|
|
32
|
-
render(<RightPanel />);
|
|
33
|
-
fireEvent.click(screen.getByText('Changes'));
|
|
34
|
-
expect(switchRightTab).toHaveBeenCalledWith('changes');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('renders TurnsPanel when activeRightTab is turns', () => {
|
|
38
|
-
resetStore({ activeRightTab: 'turns' });
|
|
39
|
-
render(<RightPanel />);
|
|
40
|
-
// TurnsPanel renders "No turns yet" when no active session
|
|
41
|
-
expect(screen.getByText('No turns yet')).toBeInTheDocument();
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../store/store.ts';
|
|
2
|
-
import TurnsPanel from './panels/TurnsPanel.tsx';
|
|
3
|
-
import ChangesPanel from './panels/ChangesPanel.tsx';
|
|
4
|
-
import TasksPanel from './panels/TasksPanel.tsx';
|
|
5
|
-
import ThreadsPanel from './panels/ThreadsPanel.tsx';
|
|
6
|
-
import type { RightTab } from '../types/index.ts';
|
|
7
|
-
|
|
8
|
-
export default function RightPanel() {
|
|
9
|
-
const { activeRightTab, switchRightTab, activeSessionId, sessionDetails } = useStore();
|
|
10
|
-
|
|
11
|
-
// Badge: active background task count for the Tasks tab
|
|
12
|
-
const detail = activeSessionId ? sessionDetails[activeSessionId] : null;
|
|
13
|
-
const activeTaskCount = detail?.backgroundTasks?.length ?? 0;
|
|
14
|
-
const openThreadCount = detail?.threads?.filter((t) => t.status === 'open').length ?? 0;
|
|
15
|
-
|
|
16
|
-
const tabs: { id: RightTab; label: string; badge?: number }[] = [
|
|
17
|
-
{ id: 'turns', label: 'Turns' },
|
|
18
|
-
{ id: 'changes', label: 'Changes' },
|
|
19
|
-
{ id: 'tasks', label: 'Tasks', badge: activeTaskCount || undefined },
|
|
20
|
-
{ id: 'threads', label: 'Threads', badge: openThreadCount || undefined },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className="ide-right">
|
|
25
|
-
<div className="right-tabs">
|
|
26
|
-
{tabs.map((tab) => (
|
|
27
|
-
<div
|
|
28
|
-
key={tab.id}
|
|
29
|
-
className={`right-tab ${activeRightTab === tab.id ? 'active' : ''}`}
|
|
30
|
-
onClick={() => switchRightTab(tab.id)}
|
|
31
|
-
>
|
|
32
|
-
{tab.label}
|
|
33
|
-
{tab.badge ? <span className="right-tab-badge">{tab.badge}</span> : null}
|
|
34
|
-
</div>
|
|
35
|
-
))}
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<div className="right-content">
|
|
39
|
-
{activeRightTab === 'turns' && <TurnsPanel />}
|
|
40
|
-
{activeRightTab === 'changes' && <ChangesPanel />}
|
|
41
|
-
{activeRightTab === 'tasks' && <TasksPanel />}
|
|
42
|
-
{activeRightTab === 'threads' && <ThreadsPanel />}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import RuntimeIcon from './RuntimeIcon.tsx';
|
|
4
|
-
|
|
5
|
-
describe('RuntimeIcon', () => {
|
|
6
|
-
it('renders span with runtime-icon and iconClass', () => {
|
|
7
|
-
const { container } = render(<RuntimeIcon iconClass="ic-codex" iconText="CX" />);
|
|
8
|
-
const span = container.firstChild as HTMLElement;
|
|
9
|
-
expect(span.tagName).toBe('SPAN');
|
|
10
|
-
expect(span.className).toBe('runtime-icon ic-codex');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('renders iconText as child content', () => {
|
|
14
|
-
const { container } = render(<RuntimeIcon iconClass="ic-claude" iconText="CL" />);
|
|
15
|
-
const span = container.firstChild as HTMLElement;
|
|
16
|
-
expect(span.textContent).toBe('CL');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('does not set inline style when size is default (20)', () => {
|
|
20
|
-
const { container } = render(<RuntimeIcon iconClass="ic-codex" iconText="CX" />);
|
|
21
|
-
const span = container.firstChild as HTMLElement;
|
|
22
|
-
expect(span.style.cssText).toBe('');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('sets width, height, and fontSize inline style when size is custom', () => {
|
|
26
|
-
const { container } = render(<RuntimeIcon iconClass="ic-codex" iconText="CX" size={14} />);
|
|
27
|
-
const span = container.firstChild as HTMLElement;
|
|
28
|
-
expect(span.style.width).toBe('14px');
|
|
29
|
-
expect(span.style.height).toBe('14px');
|
|
30
|
-
expect(span.style.fontSize).toBe('6.3px'); // 14 * 0.45
|
|
31
|
-
});
|
|
32
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
interface Props {
|
|
2
|
-
iconClass: string;
|
|
3
|
-
iconText: string;
|
|
4
|
-
size?: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export default function RuntimeIcon({ iconClass, iconText, size = 20 }: Props) {
|
|
8
|
-
return (
|
|
9
|
-
<span
|
|
10
|
-
className={`runtime-icon ${iconClass}`}
|
|
11
|
-
style={size !== 20 ? { width: size, height: size, fontSize: size * 0.45 } : undefined}
|
|
12
|
-
>
|
|
13
|
-
{iconText}
|
|
14
|
-
</span>
|
|
15
|
-
);
|
|
16
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
-
import SessionTabs from './SessionTabs.tsx';
|
|
4
|
-
import { resetStore } from '../test/component-helpers.ts';
|
|
5
|
-
import { makeSession } from '../test/fixtures.ts';
|
|
6
|
-
|
|
7
|
-
describe('SessionTabs', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetStore();
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
cleanup();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('renders one tab per openTabs entry', () => {
|
|
16
|
-
const s1 = makeSession('s1', { title: 'Session 1' });
|
|
17
|
-
const s2 = makeSession('s2', { title: 'Session 2' });
|
|
18
|
-
resetStore({
|
|
19
|
-
openTabs: ['s1', 's2'],
|
|
20
|
-
sessions: [s1, s2],
|
|
21
|
-
activeSessionId: 's1',
|
|
22
|
-
});
|
|
23
|
-
render(<SessionTabs />);
|
|
24
|
-
expect(screen.getByText('Session 1')).toBeInTheDocument();
|
|
25
|
-
expect(screen.getByText('Session 2')).toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('active tab has active class', () => {
|
|
29
|
-
const s1 = makeSession('s1', { title: 'Tab One' });
|
|
30
|
-
resetStore({ openTabs: ['s1'], sessions: [s1], activeSessionId: 's1' });
|
|
31
|
-
render(<SessionTabs />);
|
|
32
|
-
const tab = screen.getByText('Tab One').closest('.ide-tab');
|
|
33
|
-
expect(tab).toHaveClass('active');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('click on tab calls selectSession', () => {
|
|
37
|
-
const selectSession = vi.fn().mockResolvedValue(undefined);
|
|
38
|
-
const s1 = makeSession('s1', { title: 'Tab One' });
|
|
39
|
-
const s2 = makeSession('s2', { title: 'Tab Two' });
|
|
40
|
-
resetStore({ openTabs: ['s1', 's2'], sessions: [s1, s2], activeSessionId: 's1', selectSession });
|
|
41
|
-
render(<SessionTabs />);
|
|
42
|
-
fireEvent.click(screen.getByText('Tab Two'));
|
|
43
|
-
expect(selectSession).toHaveBeenCalledWith('s2');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('close button calls closeTab with correct id', () => {
|
|
47
|
-
const closeTab = vi.fn();
|
|
48
|
-
const s1 = makeSession('s1', { title: 'Close Me' });
|
|
49
|
-
resetStore({ openTabs: ['s1'], sessions: [s1], activeSessionId: 's1', closeTab });
|
|
50
|
-
render(<SessionTabs />);
|
|
51
|
-
const closeBtn = screen.getByText('Close Me').closest('.ide-tab')!.querySelector('.ide-tab-close')!;
|
|
52
|
-
fireEvent.click(closeBtn);
|
|
53
|
-
expect(closeTab).toHaveBeenCalledWith('s1');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('plus button calls setShowNewSessionDialog(true)', () => {
|
|
57
|
-
const setShowNewSessionDialog = vi.fn();
|
|
58
|
-
resetStore({ openTabs: [], sessions: [], setShowNewSessionDialog });
|
|
59
|
-
render(<SessionTabs />);
|
|
60
|
-
const addBtn = document.querySelector('.ide-tab-add')!;
|
|
61
|
-
fireEvent.click(addBtn);
|
|
62
|
-
expect(setShowNewSessionDialog).toHaveBeenCalledWith(true);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('missing session in openTabs returns null for that tab', () => {
|
|
66
|
-
resetStore({ openTabs: ['s1'], sessions: [], activeSessionId: null });
|
|
67
|
-
render(<SessionTabs />);
|
|
68
|
-
// No tab content rendered — only the + button
|
|
69
|
-
expect(document.querySelectorAll('.ide-tab')).toHaveLength(0);
|
|
70
|
-
expect(document.querySelector('.ide-tab-add')).toBeInTheDocument();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../store/store.ts';
|
|
2
|
-
import StatusDot from './StatusDot.tsx';
|
|
3
|
-
import RuntimeIcon from './RuntimeIcon.tsx';
|
|
4
|
-
import { deriveSessionStatus } from '../hooks/useSessionStatus.ts';
|
|
5
|
-
import { X, Plus } from 'lucide-react';
|
|
6
|
-
|
|
7
|
-
export default function SessionTabs() {
|
|
8
|
-
const openTabs = useStore((s) => s.openTabs);
|
|
9
|
-
const activeSessionId = useStore((s) => s.activeSessionId);
|
|
10
|
-
const sessions = useStore((s) => s.sessions);
|
|
11
|
-
const selectSession = useStore((s) => s.selectSession);
|
|
12
|
-
const closeTab = useStore((s) => s.closeTab);
|
|
13
|
-
const setShowNewSessionDialog = useStore((s) => s.setShowNewSessionDialog);
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div className="ide-tabs">
|
|
17
|
-
{openTabs.map((sid) => {
|
|
18
|
-
const s = sessions.find((x) => x.id === sid);
|
|
19
|
-
if (!s) return null;
|
|
20
|
-
const status = deriveSessionStatus(s);
|
|
21
|
-
const isActive = activeSessionId === sid;
|
|
22
|
-
return (
|
|
23
|
-
<div
|
|
24
|
-
key={sid}
|
|
25
|
-
className={`ide-tab ${isActive ? 'active' : ''}`}
|
|
26
|
-
onClick={() => selectSession(sid)}
|
|
27
|
-
>
|
|
28
|
-
<RuntimeIcon iconClass={status.iconClass} iconText={status.iconText} size={14} />
|
|
29
|
-
{s.title || sid.slice(0, 8)}
|
|
30
|
-
{status.dot && <StatusDot status={status.dot} />}
|
|
31
|
-
<span
|
|
32
|
-
className="ide-tab-close"
|
|
33
|
-
onClick={(e) => {
|
|
34
|
-
e.stopPropagation();
|
|
35
|
-
closeTab(sid);
|
|
36
|
-
}}
|
|
37
|
-
>
|
|
38
|
-
<X size={12} />
|
|
39
|
-
</span>
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
})}
|
|
43
|
-
|
|
44
|
-
<div className="ide-tab-add" title="New Session" onClick={() => setShowNewSessionDialog(true)}>
|
|
45
|
-
<Plus size={14} />
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import { render, screen, cleanup } from '@testing-library/react';
|
|
3
|
-
import StatusBar from './StatusBar.tsx';
|
|
4
|
-
import { resetStore } from '../test/component-helpers.ts';
|
|
5
|
-
import { makeSession, makeDetail, makeTurn } from '../test/fixtures.ts';
|
|
6
|
-
|
|
7
|
-
describe('StatusBar', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
resetStore();
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
cleanup();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('shows AgentPane and Offline when no session', () => {
|
|
16
|
-
render(<StatusBar />);
|
|
17
|
-
expect(screen.getByText('AgentPane')).toBeInTheDocument();
|
|
18
|
-
expect(screen.getByText('Offline')).toBeInTheDocument();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('shows Live when wsConnected is true', () => {
|
|
22
|
-
resetStore({ wsConnected: true });
|
|
23
|
-
render(<StatusBar />);
|
|
24
|
-
expect(screen.getByText('Live')).toBeInTheDocument();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('shows Offline when wsConnected is false', () => {
|
|
28
|
-
resetStore({ wsConnected: false });
|
|
29
|
-
render(<StatusBar />);
|
|
30
|
-
expect(screen.getByText('Offline')).toBeInTheDocument();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('shows status label for active session', () => {
|
|
34
|
-
const session = makeSession('s1', { status: 'active', is_running: 1 });
|
|
35
|
-
resetStore({ activeSessionId: 's1', sessions: [session], sessionDetails: { s1: makeDetail('s1') } });
|
|
36
|
-
render(<StatusBar />);
|
|
37
|
-
expect(screen.getByText('Running')).toBeInTheDocument();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('shows duration for active session', () => {
|
|
41
|
-
const session = makeSession('s1', { status: 'active', is_running: 0 });
|
|
42
|
-
const t1 = makeTurn('t1', 's1', {
|
|
43
|
-
created_at: '2025-01-01T00:00:00Z',
|
|
44
|
-
updated_at: '2025-01-01T00:00:05Z',
|
|
45
|
-
});
|
|
46
|
-
const detail = makeDetail('s1', [t1]);
|
|
47
|
-
resetStore({
|
|
48
|
-
activeSessionId: 's1',
|
|
49
|
-
sessions: [session],
|
|
50
|
-
sessionDetails: { s1: detail },
|
|
51
|
-
});
|
|
52
|
-
render(<StatusBar />);
|
|
53
|
-
// Duration = 5 seconds between first turn created_at and last turn updated_at
|
|
54
|
-
expect(screen.getByText('5s')).toBeInTheDocument();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { useStore } from '../store/store.ts';
|
|
2
|
-
import StatusDot from './StatusDot.tsx';
|
|
3
|
-
import ModelSelector from './ModelSelector.tsx';
|
|
4
|
-
import { deriveSessionStatus } from '../hooks/useSessionStatus.ts';
|
|
5
|
-
import { calcSessionDuration } from '../utils/duration.ts';
|
|
6
|
-
import { Zap, Command, Terminal } from 'lucide-react';
|
|
7
|
-
import type { ContextUsage } from '../types/index.ts';
|
|
8
|
-
|
|
9
|
-
/** Format a token count as compact K/M (e.g. 30000 → "30K", 1500000 → "1.5M"). */
|
|
10
|
-
function formatTokens(n: number): string {
|
|
11
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n % 1_000_000 === 0 ? 0 : 1)}M`;
|
|
12
|
-
if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
|
|
13
|
-
return String(n);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default function StatusBar() {
|
|
17
|
-
const activeSessionId = useStore((s) => s.activeSessionId);
|
|
18
|
-
const session = useStore((s) =>
|
|
19
|
-
activeSessionId ? (s.sessions.find((x) => x.id === activeSessionId) ?? null) : null,
|
|
20
|
-
);
|
|
21
|
-
const detail = useStore((s) => (activeSessionId ? (s.sessionDetails[activeSessionId] ?? null) : null));
|
|
22
|
-
const wsConnected = useStore((s) => s.wsConnected);
|
|
23
|
-
const switchRightTab = useStore((s) => s.switchRightTab);
|
|
24
|
-
const contextUsage = detail?.contextUsage ?? null;
|
|
25
|
-
|
|
26
|
-
if (!session) {
|
|
27
|
-
return (
|
|
28
|
-
<div className="ide-statusbar">
|
|
29
|
-
<div className="sb-item">
|
|
30
|
-
<Zap size={14} /> AgentPane
|
|
31
|
-
</div>
|
|
32
|
-
<div className="sb-divider">|</div>
|
|
33
|
-
<div className="sb-item" title={wsConnected ? 'WebSocket connected' : 'WebSocket disconnected'}>
|
|
34
|
-
<span className={`status-dot ${wsConnected ? 'done' : 'error'}`} />
|
|
35
|
-
{wsConnected ? 'Live' : 'Offline'}
|
|
36
|
-
</div>
|
|
37
|
-
<div className="sb-spacer" />
|
|
38
|
-
<div className="sb-item">
|
|
39
|
-
<Command size={14} /> K
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const status = deriveSessionStatus(session);
|
|
46
|
-
const duration = calcSessionDuration(detail, status.isRunning);
|
|
47
|
-
|
|
48
|
-
// Count active background tasks from two sources:
|
|
49
|
-
// 1. backgroundTasks (from background_tasks_changed WS event — Monitor only)
|
|
50
|
-
// 2. in_progress background_task items in turns (from item:started/updated — both Monitor & Subagent)
|
|
51
|
-
// Deduplicate by task_id.
|
|
52
|
-
const bgTasks = detail?.backgroundTasks ?? [];
|
|
53
|
-
const activeTaskIds = new Set<string>();
|
|
54
|
-
const activeTaskTypes = new Map<string, string>();
|
|
55
|
-
|
|
56
|
-
for (const t of bgTasks) {
|
|
57
|
-
activeTaskIds.add(t.task_id);
|
|
58
|
-
activeTaskTypes.set(t.task_id, t.task_type);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
for (const turn of detail?.turns ?? []) {
|
|
62
|
-
for (const item of turn.items) {
|
|
63
|
-
if (item.type === 'background_task' && item.status === 'in_progress') {
|
|
64
|
-
const taskId = (item.data?.task_id as string) ?? item.external_item_id;
|
|
65
|
-
const taskType = (item.data?.task_type as string) ?? 'unknown';
|
|
66
|
-
activeTaskIds.add(taskId);
|
|
67
|
-
activeTaskTypes.set(taskId, taskType);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const monitorCount = [...activeTaskTypes.values()].filter((t) => t === 'local_bash').length;
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
<div className="ide-statusbar">
|
|
76
|
-
<div className="sb-item">
|
|
77
|
-
<Zap size={14} /> AgentPane
|
|
78
|
-
</div>
|
|
79
|
-
<div className="sb-divider">|</div>
|
|
80
|
-
<ModelSelector />
|
|
81
|
-
{monitorCount > 0 && (
|
|
82
|
-
<>
|
|
83
|
-
<div className="sb-divider">|</div>
|
|
84
|
-
<div
|
|
85
|
-
className="sb-item"
|
|
86
|
-
style={{ color: 'var(--amber)', cursor: 'pointer' }}
|
|
87
|
-
title={`${monitorCount} Monitor task${monitorCount > 1 ? 's' : ''} running — click to view`}
|
|
88
|
-
onClick={() => switchRightTab('tasks')}
|
|
89
|
-
>
|
|
90
|
-
<Terminal size={14} /> {monitorCount}
|
|
91
|
-
</div>
|
|
92
|
-
</>
|
|
93
|
-
)}
|
|
94
|
-
<div className="sb-spacer" />
|
|
95
|
-
<div className="sb-item" title={wsConnected ? 'WebSocket connected' : 'WebSocket disconnected'}>
|
|
96
|
-
<span className={`status-dot ${wsConnected ? 'done' : 'error'}`} />
|
|
97
|
-
{wsConnected ? 'Live' : 'Offline'}
|
|
98
|
-
</div>
|
|
99
|
-
<div className="sb-divider">|</div>
|
|
100
|
-
<div className="sb-item" style={session.status === 'failed' ? { color: 'var(--red)' } : undefined}>
|
|
101
|
-
<StatusDot status={status.dot} /> {status.label}
|
|
102
|
-
</div>
|
|
103
|
-
{contextUsage && <ContextBar usage={contextUsage} />}
|
|
104
|
-
<div className="sb-divider">|</div>
|
|
105
|
-
<div className="sb-item">{duration}</div>
|
|
106
|
-
<div className="sb-divider">|</div>
|
|
107
|
-
<div className="sb-item">
|
|
108
|
-
<Command size={14} /> K
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Compact context usage text — "%40 / 400K / 1M" format, hover tooltip. */
|
|
115
|
-
function ContextBar({ usage }: { usage: ContextUsage }) {
|
|
116
|
-
const colorVar = usage.percentage >= 85 ? 'var(--red)' : usage.percentage >= 60 ? 'var(--amber)' : 'var(--green)';
|
|
117
|
-
const tooltip = `${formatTokens(usage.totalTokens)} / ${formatTokens(usage.contextWindow)} tokens`;
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<>
|
|
121
|
-
<div className="sb-divider">|</div>
|
|
122
|
-
<div className="sb-context" title={tooltip}>
|
|
123
|
-
<span className="sb-context-text" style={{ color: colorVar }}>
|
|
124
|
-
{Math.round(usage.percentage)}% / {formatTokens(usage.totalTokens)} / {formatTokens(usage.contextWindow)}
|
|
125
|
-
</span>
|
|
126
|
-
</div>
|
|
127
|
-
</>
|
|
128
|
-
);
|
|
129
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import StatusDot from './StatusDot.tsx';
|
|
4
|
-
|
|
5
|
-
describe('StatusDot', () => {
|
|
6
|
-
it('renders span with status-dot and status class', () => {
|
|
7
|
-
const { container } = render(<StatusDot status="running" />);
|
|
8
|
-
const span = container.firstChild as HTMLElement;
|
|
9
|
-
expect(span.tagName).toBe('SPAN');
|
|
10
|
-
expect(span.className).toBe('status-dot running');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('does not set inline style when size is default (6)', () => {
|
|
14
|
-
const { container } = render(<StatusDot status="error" />);
|
|
15
|
-
const span = container.firstChild as HTMLElement;
|
|
16
|
-
expect(span.style.cssText).toBe('');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('sets width and height inline style when size is custom', () => {
|
|
20
|
-
const { container } = render(<StatusDot status="queued" size={10} />);
|
|
21
|
-
const span = container.firstChild as HTMLElement;
|
|
22
|
-
expect(span.style.width).toBe('10px');
|
|
23
|
-
expect(span.style.height).toBe('10px');
|
|
24
|
-
});
|
|
25
|
-
});
|