ralph-review 0.2.5 → 0.2.6
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/package.json +1 -1
- package/src/lib/tui/dashboard/Dashboard.tsx +43 -2
- package/src/lib/tui/dashboard/dashboard-group-selection.ts +34 -0
- package/src/lib/tui/dashboard/dashboard-keyboard.ts +17 -1
- package/src/lib/tui/sessions/sidebar/SessionGroup.tsx +10 -10
- package/src/lib/tui/sessions/sidebar/SessionItem.tsx +3 -6
- package/src/lib/tui/sessions/sidebar/SessionSidebar.tsx +4 -3
- package/src/lib/tui/workspace/Workspace.tsx +3 -3
- package/src/lib/tui/workspace/use-workspace-state.ts +63 -16
- package/src/lib/tui/workspace/workspace-types.ts +1 -1
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@ import type { FocusedPane } from "@/lib/tui/workspace/workspace-types";
|
|
|
11
11
|
import { DashboardOverlays } from "./DashboardOverlays";
|
|
12
12
|
import { getPendingFixTarget } from "./dashboard-fix-state";
|
|
13
13
|
import { cycleDashboardFocus, cycleDashboardFocusReverse } from "./dashboard-focus";
|
|
14
|
+
import { resolveSelectedGroupPath, selectAdjacentGroupPath } from "./dashboard-group-selection";
|
|
14
15
|
import { isDashboardOverlayBlockingFocus } from "./dashboard-overlay-state";
|
|
15
16
|
import { Header } from "./Header";
|
|
16
17
|
import { StatusBar } from "./StatusBar";
|
|
@@ -21,7 +22,14 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
21
22
|
const renderer = useRenderer();
|
|
22
23
|
const resolvedUseWorkspaceState = deps?.useWorkspaceState ?? useWorkspaceState;
|
|
23
24
|
const ResolvedDashboardOverlays = deps?.DashboardOverlays ?? DashboardOverlays;
|
|
24
|
-
const
|
|
25
|
+
const [selectedGroupPath, setSelectedGroupPath] = useState<string>(projectPath);
|
|
26
|
+
const state = resolvedUseWorkspaceState(
|
|
27
|
+
projectPath,
|
|
28
|
+
branch,
|
|
29
|
+
refreshInterval,
|
|
30
|
+
undefined,
|
|
31
|
+
selectedGroupPath
|
|
32
|
+
);
|
|
25
33
|
const {
|
|
26
34
|
runError,
|
|
27
35
|
startupMode,
|
|
@@ -85,6 +93,15 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
85
93
|
setShowFixFindings(false);
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
const resolvedSelectedGroupPath = resolveSelectedGroupPath(
|
|
97
|
+
state.sessionGroups,
|
|
98
|
+
selectedGroupPath,
|
|
99
|
+
projectPath
|
|
100
|
+
);
|
|
101
|
+
if (resolvedSelectedGroupPath !== selectedGroupPath) {
|
|
102
|
+
setSelectedGroupPath(resolvedSelectedGroupPath);
|
|
103
|
+
}
|
|
104
|
+
|
|
88
105
|
const shutdown = useCallback(
|
|
89
106
|
async (after?: () => Promise<void>, exitCode: number = 0) => {
|
|
90
107
|
if (isExitingRef.current) {
|
|
@@ -124,6 +141,18 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
124
141
|
setFocusedPane((current) => cycleDashboardFocusReverse(current, outputVisible));
|
|
125
142
|
}, [outputVisible]);
|
|
126
143
|
|
|
144
|
+
const selectPrevGroup = useCallback(() => {
|
|
145
|
+
setSelectedGroupPath((current) =>
|
|
146
|
+
selectAdjacentGroupPath(state.sessionGroups, current, "prev")
|
|
147
|
+
);
|
|
148
|
+
}, [state.sessionGroups]);
|
|
149
|
+
|
|
150
|
+
const selectNextGroup = useCallback(() => {
|
|
151
|
+
setSelectedGroupPath((current) =>
|
|
152
|
+
selectAdjacentGroupPath(state.sessionGroups, current, "next")
|
|
153
|
+
);
|
|
154
|
+
}, [state.sessionGroups]);
|
|
155
|
+
|
|
127
156
|
const handleKeyboard = useCallback(
|
|
128
157
|
(key: { name: string }) => {
|
|
129
158
|
const action = resolveDashboardKeyAction({
|
|
@@ -137,6 +166,8 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
137
166
|
hasCurrentSession: Boolean(state.currentSession),
|
|
138
167
|
canFixPendingSession,
|
|
139
168
|
isRunSpawning: isStartupSpawning(),
|
|
169
|
+
sidebarFocused: focusedPane === "sidebar",
|
|
170
|
+
sessionGroupCount: state.sessionGroups.length,
|
|
140
171
|
});
|
|
141
172
|
|
|
142
173
|
switch (action) {
|
|
@@ -187,6 +218,12 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
187
218
|
clearRunError();
|
|
188
219
|
setShowReviewModeOverlay(true);
|
|
189
220
|
return;
|
|
221
|
+
case "select-prev-group":
|
|
222
|
+
selectPrevGroup();
|
|
223
|
+
return;
|
|
224
|
+
case "select-next-group":
|
|
225
|
+
selectNextGroup();
|
|
226
|
+
return;
|
|
190
227
|
}
|
|
191
228
|
},
|
|
192
229
|
[
|
|
@@ -194,7 +231,10 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
194
231
|
clearRunError,
|
|
195
232
|
cycleFocus,
|
|
196
233
|
cycleFocusReverse,
|
|
234
|
+
focusedPane,
|
|
197
235
|
isStartupSpawning,
|
|
236
|
+
selectNextGroup,
|
|
237
|
+
selectPrevGroup,
|
|
198
238
|
showFixFindings,
|
|
199
239
|
showHelp,
|
|
200
240
|
showReviewModeOverlay,
|
|
@@ -203,6 +243,7 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
203
243
|
shutdown,
|
|
204
244
|
state.allSessions,
|
|
205
245
|
state.currentSession,
|
|
246
|
+
state.sessionGroups.length,
|
|
206
247
|
stopSelectedSession,
|
|
207
248
|
]
|
|
208
249
|
);
|
|
@@ -238,7 +279,7 @@ export function Dashboard({ projectPath, branch, refreshInterval = 1000, deps }:
|
|
|
238
279
|
) : (
|
|
239
280
|
<Workspace
|
|
240
281
|
sessionGroups={state.sessionGroups}
|
|
241
|
-
|
|
282
|
+
selectedGroupPath={selectedGroupPath}
|
|
242
283
|
session={state.currentSession}
|
|
243
284
|
fixes={state.fixes}
|
|
244
285
|
skipped={state.skipped}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SessionGroupData } from "@/lib/tui/workspace/workspace-types";
|
|
2
|
+
|
|
3
|
+
export function selectAdjacentGroupPath(
|
|
4
|
+
groups: SessionGroupData[],
|
|
5
|
+
currentPath: string,
|
|
6
|
+
direction: "prev" | "next"
|
|
7
|
+
): string {
|
|
8
|
+
if (groups.length === 0) {
|
|
9
|
+
return currentPath;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const index = groups.findIndex((group) => group.projectPath === currentPath);
|
|
13
|
+
if (index < 0) {
|
|
14
|
+
return groups[0]?.projectPath ?? currentPath;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const delta = direction === "prev" ? -1 : 1;
|
|
18
|
+
const nextIndex = Math.min(groups.length - 1, Math.max(0, index + delta));
|
|
19
|
+
return groups[nextIndex]?.projectPath ?? currentPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveSelectedGroupPath(
|
|
23
|
+
groups: SessionGroupData[],
|
|
24
|
+
preferredPath: string,
|
|
25
|
+
fallbackPath: string
|
|
26
|
+
): string {
|
|
27
|
+
if (groups.some((group) => group.projectPath === preferredPath)) {
|
|
28
|
+
return preferredPath;
|
|
29
|
+
}
|
|
30
|
+
if (groups.some((group) => group.projectPath === fallbackPath)) {
|
|
31
|
+
return fallbackPath;
|
|
32
|
+
}
|
|
33
|
+
return groups[0]?.projectPath ?? fallbackPath;
|
|
34
|
+
}
|
|
@@ -17,7 +17,9 @@ export type DashboardKeyAction =
|
|
|
17
17
|
| "open-session"
|
|
18
18
|
| "stop-single-session"
|
|
19
19
|
| "open-stop-picker"
|
|
20
|
-
| "open-review-mode"
|
|
20
|
+
| "open-review-mode"
|
|
21
|
+
| "select-prev-group"
|
|
22
|
+
| "select-next-group";
|
|
21
23
|
|
|
22
24
|
interface ResolveDashboardCloseActionInput {
|
|
23
25
|
showStopPicker: boolean;
|
|
@@ -63,6 +65,8 @@ interface ResolveDashboardKeyActionInput extends ResolveDashboardCloseActionInpu
|
|
|
63
65
|
hasCurrentSession: boolean;
|
|
64
66
|
canFixPendingSession: boolean;
|
|
65
67
|
isRunSpawning: boolean;
|
|
68
|
+
sidebarFocused?: boolean;
|
|
69
|
+
sessionGroupCount?: number;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
export function resolveDashboardKeyAction({
|
|
@@ -76,6 +80,8 @@ export function resolveDashboardKeyAction({
|
|
|
76
80
|
hasCurrentSession,
|
|
77
81
|
canFixPendingSession,
|
|
78
82
|
isRunSpawning,
|
|
83
|
+
sidebarFocused = false,
|
|
84
|
+
sessionGroupCount = 0,
|
|
79
85
|
}: ResolveDashboardKeyActionInput): DashboardKeyAction {
|
|
80
86
|
if (keyName === "q" || keyName === "escape") {
|
|
81
87
|
return resolveDashboardCloseAction({
|
|
@@ -91,6 +97,16 @@ export function resolveDashboardKeyAction({
|
|
|
91
97
|
return "none";
|
|
92
98
|
}
|
|
93
99
|
|
|
100
|
+
if (sidebarFocused && sessionGroupCount >= 2) {
|
|
101
|
+
if (keyName === "up" || keyName === "k") {
|
|
102
|
+
return "select-prev-group";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (keyName === "down" || keyName === "j") {
|
|
106
|
+
return "select-next-group";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
94
110
|
if (keyName === "tab" || keyName === "right") {
|
|
95
111
|
return "cycle-focus";
|
|
96
112
|
}
|
|
@@ -4,16 +4,21 @@ import { SessionItem } from "./SessionItem";
|
|
|
4
4
|
|
|
5
5
|
interface SessionGroupProps {
|
|
6
6
|
group: SessionGroupData;
|
|
7
|
-
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
sidebarFocused?: boolean;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export function SessionGroup({ group,
|
|
11
|
+
export function SessionGroup({ group, isSelected, sidebarFocused = false }: SessionGroupProps) {
|
|
11
12
|
const icon = group.isCurrentProject ? "◆" : "○";
|
|
12
|
-
const
|
|
13
|
+
const baseNameColor = group.isCurrentProject ? TUI_COLORS.text.primary : TUI_COLORS.text.muted;
|
|
14
|
+
const nameColor = isSelected ? TUI_COLORS.text.primary : baseNameColor;
|
|
15
|
+
const headerBg = isSelected ? (sidebarFocused ? "#1e293b" : "#111827") : undefined;
|
|
16
|
+
const caret = isSelected ? "›" : " ";
|
|
13
17
|
|
|
14
18
|
return (
|
|
15
19
|
<box flexDirection="column">
|
|
16
|
-
<box flexDirection="row" gap={1} paddingLeft={1}>
|
|
20
|
+
<box flexDirection="row" gap={1} paddingLeft={1} paddingRight={1} backgroundColor={headerBg}>
|
|
21
|
+
<text fg={isSelected ? TUI_COLORS.accent.key : TUI_COLORS.text.dim}>{caret}</text>
|
|
17
22
|
<text fg={nameColor}>{icon}</text>
|
|
18
23
|
<text fg={nameColor} wrapMode="none">
|
|
19
24
|
<strong>{group.projectName}</strong>
|
|
@@ -23,12 +28,7 @@ export function SessionGroup({ group, selectedSessionId }: SessionGroupProps) {
|
|
|
23
28
|
)}
|
|
24
29
|
</box>
|
|
25
30
|
{group.sessions.map((session) => (
|
|
26
|
-
<SessionItem
|
|
27
|
-
key={session.sessionId}
|
|
28
|
-
session={session}
|
|
29
|
-
isSelected={session.sessionId === selectedSessionId}
|
|
30
|
-
projectName={group.projectName}
|
|
31
|
-
/>
|
|
31
|
+
<SessionItem key={session.sessionId} session={session} projectName={group.projectName} />
|
|
32
32
|
))}
|
|
33
33
|
{group.sessions.length === 0 && (
|
|
34
34
|
<text fg={TUI_COLORS.text.dim} paddingLeft={4}>
|
|
@@ -3,7 +3,6 @@ import { TUI_COLORS } from "@/lib/tui/shared/colors";
|
|
|
3
3
|
|
|
4
4
|
interface SessionItemProps {
|
|
5
5
|
session: ActiveSession;
|
|
6
|
-
isSelected: boolean;
|
|
7
6
|
projectName?: string;
|
|
8
7
|
}
|
|
9
8
|
|
|
@@ -32,16 +31,14 @@ function getStatusIcon(state: string): { icon: string; color: string } {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
export function SessionItem({ session,
|
|
34
|
+
export function SessionItem({ session, projectName }: SessionItemProps) {
|
|
36
35
|
const { icon, color } = getStatusIcon(session.state);
|
|
37
|
-
const bgColor = isSelected ? "#1e293b" : undefined;
|
|
38
|
-
const textColor = isSelected ? TUI_COLORS.text.primary : TUI_COLORS.text.muted;
|
|
39
36
|
const label = shortSessionLabel(session.sessionName, projectName);
|
|
40
37
|
|
|
41
38
|
return (
|
|
42
|
-
<box flexDirection="row" gap={1} paddingLeft={2} paddingRight={1}
|
|
39
|
+
<box flexDirection="row" gap={1} paddingLeft={2} paddingRight={1}>
|
|
43
40
|
<text fg={color}>{icon}</text>
|
|
44
|
-
<text fg={
|
|
41
|
+
<text fg={TUI_COLORS.text.muted} wrapMode="none" flexShrink={1}>
|
|
45
42
|
{label}
|
|
46
43
|
</text>
|
|
47
44
|
</box>
|
|
@@ -4,13 +4,13 @@ import { SessionGroup } from "./SessionGroup";
|
|
|
4
4
|
|
|
5
5
|
interface SessionSidebarProps {
|
|
6
6
|
groups: SessionGroupData[];
|
|
7
|
-
|
|
7
|
+
selectedGroupPath: string | null;
|
|
8
8
|
focused?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function SessionSidebar({
|
|
12
12
|
groups,
|
|
13
|
-
|
|
13
|
+
selectedGroupPath,
|
|
14
14
|
focused = false,
|
|
15
15
|
}: SessionSidebarProps) {
|
|
16
16
|
const borderColor = focused ? TUI_COLORS.ui.borderFocused : TUI_COLORS.ui.border;
|
|
@@ -38,7 +38,8 @@ export function SessionSidebar({
|
|
|
38
38
|
<SessionGroup
|
|
39
39
|
key={group.projectPath}
|
|
40
40
|
group={group}
|
|
41
|
-
|
|
41
|
+
isSelected={group.projectPath === selectedGroupPath}
|
|
42
|
+
sidebarFocused={focused}
|
|
42
43
|
/>
|
|
43
44
|
))
|
|
44
45
|
)}
|
|
@@ -7,7 +7,7 @@ import type { FocusedPane, SessionGroupData } from "./workspace-types";
|
|
|
7
7
|
|
|
8
8
|
interface WorkspaceProps extends DetailPaneProps {
|
|
9
9
|
sessionGroups: SessionGroupData[];
|
|
10
|
-
|
|
10
|
+
selectedGroupPath: string | null;
|
|
11
11
|
outputVisible: boolean;
|
|
12
12
|
focusedPane: FocusedPane;
|
|
13
13
|
overlayBlocked?: boolean;
|
|
@@ -15,7 +15,7 @@ interface WorkspaceProps extends DetailPaneProps {
|
|
|
15
15
|
|
|
16
16
|
export function Workspace({
|
|
17
17
|
sessionGroups,
|
|
18
|
-
|
|
18
|
+
selectedGroupPath,
|
|
19
19
|
outputVisible,
|
|
20
20
|
focusedPane,
|
|
21
21
|
overlayBlocked = false,
|
|
@@ -31,7 +31,7 @@ export function Workspace({
|
|
|
31
31
|
<box flexDirection="row" flexGrow={1} minHeight={0} gap={1} paddingLeft={1} paddingRight={1}>
|
|
32
32
|
<SessionSidebar
|
|
33
33
|
groups={sessionGroups}
|
|
34
|
-
|
|
34
|
+
selectedGroupPath={selectedGroupPath}
|
|
35
35
|
focused={sidebarFocused}
|
|
36
36
|
/>
|
|
37
37
|
<DetailPane {...detailPaneProps} focused={detailFocused} />
|
|
@@ -85,7 +85,7 @@ export function createInitialWorkspaceState(
|
|
|
85
85
|
sessionGroups: [],
|
|
86
86
|
allSessions: [],
|
|
87
87
|
projectSessions: [],
|
|
88
|
-
|
|
88
|
+
selectedGroupPath: null,
|
|
89
89
|
currentSession: null,
|
|
90
90
|
logEntries: [],
|
|
91
91
|
fixes: [],
|
|
@@ -121,6 +121,36 @@ export function createInitialWorkspaceState(
|
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function buildDetailResetState(selectedGroupPath: string): Partial<WorkspaceState> {
|
|
125
|
+
const initial = createInitialWorkspaceState();
|
|
126
|
+
return {
|
|
127
|
+
currentSession: initial.currentSession,
|
|
128
|
+
logEntries: initial.logEntries,
|
|
129
|
+
fixes: initial.fixes,
|
|
130
|
+
skipped: initial.skipped,
|
|
131
|
+
findings: initial.findings,
|
|
132
|
+
storedFindings: initial.storedFindings,
|
|
133
|
+
selectedFindingIds: initial.selectedFindingIds,
|
|
134
|
+
selectedFindings: initial.selectedFindings,
|
|
135
|
+
unselectedFindings: initial.unselectedFindings,
|
|
136
|
+
fixResults: initial.fixResults,
|
|
137
|
+
unresolvedSelectedFindings: initial.unresolvedSelectedFindings,
|
|
138
|
+
auditRegressionFindings: initial.auditRegressionFindings,
|
|
139
|
+
iterationFixes: initial.iterationFixes,
|
|
140
|
+
iterationSkipped: initial.iterationSkipped,
|
|
141
|
+
iterationFindings: initial.iterationFindings,
|
|
142
|
+
latestReviewIteration: initial.latestReviewIteration,
|
|
143
|
+
codexReviewText: initial.codexReviewText,
|
|
144
|
+
tmuxOutput: initial.tmuxOutput,
|
|
145
|
+
elapsed: initial.elapsed,
|
|
146
|
+
lastSessionStats: initial.lastSessionStats,
|
|
147
|
+
projectStats: initial.projectStats,
|
|
148
|
+
currentAgent: initial.currentAgent,
|
|
149
|
+
reviewOptions: initial.reviewOptions,
|
|
150
|
+
selectedGroupPath,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
function buildSessionGroups(
|
|
125
155
|
allSessions: ActiveSession[],
|
|
126
156
|
currentProjectPath: string
|
|
@@ -163,8 +193,10 @@ export function useWorkspaceState(
|
|
|
163
193
|
projectPath: string,
|
|
164
194
|
_branch?: string,
|
|
165
195
|
refreshInterval: number = DEFAULT_REFRESH_INTERVAL,
|
|
166
|
-
deps: WorkspaceStateDeps = defaultWorkspaceStateDeps
|
|
196
|
+
deps: WorkspaceStateDeps = defaultWorkspaceStateDeps,
|
|
197
|
+
selectedGroupPath?: string
|
|
167
198
|
): WorkspaceState {
|
|
199
|
+
const detailPath = selectedGroupPath ?? projectPath;
|
|
168
200
|
const [state, setState] = useState<WorkspaceState>(() => createInitialWorkspaceState());
|
|
169
201
|
|
|
170
202
|
const stateRef = useRef(state);
|
|
@@ -179,6 +211,23 @@ export function useWorkspaceState(
|
|
|
179
211
|
const lastLiveMetaRef = useRef<LiveRefreshMeta | null>(null);
|
|
180
212
|
const logIncrementalStateRef = useRef<LogIncrementalState | undefined>(undefined);
|
|
181
213
|
const lastLogSessionPathRef = useRef<string | null>(null);
|
|
214
|
+
const lastDetailPathRef = useRef<string>(detailPath);
|
|
215
|
+
|
|
216
|
+
const detailPathChanged = lastDetailPathRef.current !== detailPath;
|
|
217
|
+
if (detailPathChanged) {
|
|
218
|
+
lastDetailPathRef.current = detailPath;
|
|
219
|
+
logIncrementalStateRef.current = undefined;
|
|
220
|
+
lastLogSessionPathRef.current = null;
|
|
221
|
+
lastTmuxOutputRef.current = "";
|
|
222
|
+
lastTmuxSessionRef.current = null;
|
|
223
|
+
lastTmuxCaptureRef.current = 0;
|
|
224
|
+
tmuxCaptureIntervalRef.current = deps.tmuxCaptureMinIntervalMs;
|
|
225
|
+
lastLiveMetaRef.current = null;
|
|
226
|
+
setState((prev) => ({
|
|
227
|
+
...prev,
|
|
228
|
+
...buildDetailResetState(detailPath),
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
182
231
|
|
|
183
232
|
const refreshHeavy = useCallback(async () => {
|
|
184
233
|
if (isHeavyRefreshingRef.current) return;
|
|
@@ -190,19 +239,17 @@ export function useWorkspaceState(
|
|
|
190
239
|
deps.ensureGitRepositoryAsync(projectPath),
|
|
191
240
|
deps.listAllActiveSessions(),
|
|
192
241
|
deps.listProjectActiveSessions(undefined, projectPath),
|
|
193
|
-
deps.getLatestProjectActiveSession(undefined,
|
|
194
|
-
deps.getLatestProjectLogSession(undefined,
|
|
242
|
+
deps.getLatestProjectActiveSession(undefined, detailPath),
|
|
243
|
+
deps.getLatestProjectLogSession(undefined, detailPath),
|
|
195
244
|
loadWorkspaceConfigSafe(projectPath, deps.loadEffectiveConfig),
|
|
196
245
|
]);
|
|
197
246
|
|
|
198
247
|
const sessionGroups = buildSessionGroups(allSessions, projectPath);
|
|
199
248
|
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
? prevSelectedId
|
|
205
|
-
: (currentSession?.sessionId ?? allSessions[0]?.sessionId ?? null);
|
|
249
|
+
const selectedGroupExists = sessionGroups.some((group) => group.projectPath === detailPath);
|
|
250
|
+
const resolvedSelectedGroupPath = selectedGroupExists
|
|
251
|
+
? detailPath
|
|
252
|
+
: (sessionGroups[0]?.projectPath ?? projectPath);
|
|
206
253
|
|
|
207
254
|
let logEntries = stateRef.current.logEntries;
|
|
208
255
|
let nextLogIncrementalState = logIncrementalStateRef.current;
|
|
@@ -251,12 +298,12 @@ export function useWorkspaceState(
|
|
|
251
298
|
let projectStats: ProjectStats | null = null;
|
|
252
299
|
|
|
253
300
|
if (!currentSession) {
|
|
254
|
-
const projectLogSessions = await deps.listProjectLogSessions(undefined,
|
|
301
|
+
const projectLogSessions = await deps.listProjectLogSessions(undefined, detailPath);
|
|
255
302
|
const latestSession = projectLogSessions[0];
|
|
256
303
|
if (latestSession) {
|
|
257
304
|
lastSessionStats = await deps.computeSessionStats(latestSession);
|
|
258
305
|
projectStats = await deps.computeProjectStats(
|
|
259
|
-
deps.getProjectName(
|
|
306
|
+
deps.getProjectName(detailPath),
|
|
260
307
|
projectLogSessions
|
|
261
308
|
);
|
|
262
309
|
}
|
|
@@ -270,7 +317,7 @@ export function useWorkspaceState(
|
|
|
270
317
|
sessionGroups,
|
|
271
318
|
allSessions,
|
|
272
319
|
projectSessions,
|
|
273
|
-
|
|
320
|
+
selectedGroupPath: resolvedSelectedGroupPath,
|
|
274
321
|
currentSession,
|
|
275
322
|
logEntries,
|
|
276
323
|
fixes,
|
|
@@ -306,14 +353,14 @@ export function useWorkspaceState(
|
|
|
306
353
|
} finally {
|
|
307
354
|
isHeavyRefreshingRef.current = false;
|
|
308
355
|
}
|
|
309
|
-
}, [deps, projectPath]);
|
|
356
|
+
}, [deps, projectPath, detailPath]);
|
|
310
357
|
|
|
311
358
|
const refreshLive = useCallback(async () => {
|
|
312
359
|
if (isLiveRefreshingRef.current) return;
|
|
313
360
|
isLiveRefreshingRef.current = true;
|
|
314
361
|
|
|
315
362
|
try {
|
|
316
|
-
const currentSession = await deps.getLatestProjectActiveSession(undefined,
|
|
363
|
+
const currentSession = await deps.getLatestProjectActiveSession(undefined, detailPath);
|
|
317
364
|
|
|
318
365
|
let tmuxOutput = lastTmuxOutputRef.current;
|
|
319
366
|
const liveMeta = getLiveRefreshMeta(currentSession);
|
|
@@ -374,7 +421,7 @@ export function useWorkspaceState(
|
|
|
374
421
|
} finally {
|
|
375
422
|
isLiveRefreshingRef.current = false;
|
|
376
423
|
}
|
|
377
|
-
}, [deps,
|
|
424
|
+
}, [deps, detailPath]);
|
|
378
425
|
|
|
379
426
|
useEffect(() => {
|
|
380
427
|
void refreshHeavy();
|
|
@@ -30,7 +30,7 @@ export interface WorkspaceState {
|
|
|
30
30
|
sessionGroups: SessionGroupData[];
|
|
31
31
|
allSessions: ActiveSession[];
|
|
32
32
|
projectSessions: ActiveSession[];
|
|
33
|
-
|
|
33
|
+
selectedGroupPath: string | null;
|
|
34
34
|
currentSession: SessionState | null;
|
|
35
35
|
logEntries: LogEntry[];
|
|
36
36
|
fixes: FixEntry[];
|