remove: session operations center and progress panel

The bottom-right progress panel duplicated feedback already shown by
toasts. Remove it and the operation-tracking state to simplify the UI:

- Delete state/session-operations.tsx and session-progress-panel.tsx.
- Remove SessionOperationsProvider/SessionProgressPanel from AppShell.
- Remove startOperation/completeOperation calls from useInstanceActions
  and ToolStarter.
- Remove SessionOperationsProvider wrapper from DashboardPage.test.tsx.
- Remove .session-progress-panel CSS rules.
- Format use-events.test.ts mock to match project lint rules.

Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
This commit is contained in:
Developer
2026-06-13 09:50:14 +00:00
parent 43d65840ea
commit 61f9c52455
8 changed files with 28 additions and 561 deletions
+17 -9
View File
@@ -30,17 +30,25 @@ describe("useEvents", () => {
return EventSource.OPEN;
},
url: "http://localhost:8000/events/stream",
addEventListener: vi.fn((type: string, handler: (e: MessageEvent) => void) => {
if (!listeners.has(type)) listeners.set(type, new Set());
listeners.get(type)!.add(handler);
}),
removeEventListener: vi.fn((type: string, handler: (e: MessageEvent) => void) => {
listeners.get(type)?.delete(handler);
}),
addEventListener: vi.fn(
(type: string, handler: (e: MessageEvent) => void) => {
if (!listeners.has(type)) listeners.set(type, new Set());
listeners.get(type)!.add(handler);
},
),
removeEventListener: vi.fn(
(type: string, handler: (e: MessageEvent) => void) => {
listeners.get(type)?.delete(handler);
},
),
emit: (type: string, data: string) => {
listeners.get(type)?.forEach((handler) => handler({ data } as MessageEvent));
listeners
.get(type)
?.forEach((handler) => handler({ data } as MessageEvent));
},
} as unknown as EventSource & { emit: (type: string, data: string) => void };
} as unknown as EventSource & {
emit: (type: string, data: string) => void;
};
mockedCreateEventSource.mockReturnValue(mockEs);
mockedProbeEventStreamStatus.mockResolvedValue(null);
});
+5 -33
View File
@@ -8,7 +8,6 @@ import {
} from "../api/sessions";
import type { Session } from "../api/sessions";
import { useSessions } from "../state/sessions";
import { useSessionOperations } from "../state/session-operations";
interface UseInstanceActionsOptions {
onRefresh: () => Promise<void>;
@@ -33,7 +32,6 @@ export function useInstanceActions(
): UseInstanceActionsReturn {
const { onRefresh } = options;
const { removeSession } = useSessions();
const { startOperation, completeOperation } = useSessionOperations();
const [loadingSessionId, setLoadingSessionId] = useState<string | null>(null);
const [dirtyDeleteSession, setDirtyDeleteSession] = useState<Session | null>(
null,
@@ -66,7 +64,6 @@ export function useInstanceActions(
async (session: Session) => {
if (loadingSessionId === session.id) return;
setLoadingSessionId(session.id);
startOperation("start", session.id, session.display_name);
try {
await startInstance(
session.project_id,
@@ -74,20 +71,17 @@ export function useInstanceActions(
session.id,
);
await onRefresh();
} catch {
completeOperation(session.id, "start", "error");
} finally {
setLoadingSessionId(null);
}
},
[loadingSessionId, onRefresh, startOperation, completeOperation],
[loadingSessionId, onRefresh],
);
const handleStop = useCallback(
async (session: Session) => {
if (loadingSessionId === session.id) return;
setLoadingSessionId(session.id);
startOperation("stop", session.id, session.display_name);
try {
await stopInstance(
session.project_id,
@@ -95,20 +89,17 @@ export function useInstanceActions(
session.id,
);
await onRefresh();
} catch {
completeOperation(session.id, "stop", "error");
} finally {
setLoadingSessionId(null);
}
},
[loadingSessionId, onRefresh, startOperation, completeOperation],
[loadingSessionId, onRefresh],
);
const handleDelete = useCallback(
async (session: Session) => {
if (loadingSessionId === session.id) return;
setLoadingSessionId(session.id);
startOperation("delete", session.id, session.display_name);
try {
await deleteInstance(
session.project_id,
@@ -120,7 +111,6 @@ export function useInstanceActions(
removeSession(session.id);
await onRefresh();
} catch (error) {
completeOperation(session.id, "delete", "error");
const axiosError = error as {
response?: {
status?: number;
@@ -139,20 +129,13 @@ export function useInstanceActions(
setLoadingSessionId(null);
}
},
[
loadingSessionId,
onRefresh,
removeSession,
startOperation,
completeOperation,
],
[loadingSessionId, onRefresh, removeSession],
);
const handleForceDelete = useCallback(
async (session: Session) => {
if (loadingSessionId === session.id) return;
setLoadingSessionId(session.id);
startOperation("delete", session.id, session.display_name);
try {
await deleteInstance(
session.project_id,
@@ -164,26 +147,17 @@ export function useInstanceActions(
setDirtyDeleteFiles([]);
removeSession(session.id);
await onRefresh();
} catch {
completeOperation(session.id, "delete", "error");
} finally {
setLoadingSessionId(null);
}
},
[
loadingSessionId,
onRefresh,
removeSession,
startOperation,
completeOperation,
],
[loadingSessionId, onRefresh, removeSession],
);
const handleRecreateTunnel = useCallback(
async (session: Session) => {
if (loadingSessionId === session.id) return;
setLoadingSessionId(session.id);
startOperation("recreate-tunnel", session.id, session.display_name);
try {
await recreateInstanceTunnel(
session.project_id,
@@ -191,18 +165,16 @@ export function useInstanceActions(
session.id,
);
await onRefresh();
completeOperation(session.id, "recreate-tunnel", "success");
} catch (err) {
const message =
(err as { response?: { data?: { detail?: string } } })?.response?.data
?.detail || "Failed to recreate tunnel";
completeOperation(session.id, "recreate-tunnel", "error", message);
alert(message);
} finally {
setLoadingSessionId(null);
}
},
[loadingSessionId, onRefresh, startOperation, completeOperation],
[loadingSessionId, onRefresh],
);
const handleRename = useCallback(