feat(03-01): add context switch overlay, hook, and store wiring

- Create ContextSwitchOverlay component with full-screen loading state
- Create useContextSwitch hook exposing switchContext/isContextSwitching/activeContextId
- Add ContextSlice to AppState type intersection
- Compose createContextSlice into store creation
This commit is contained in:
matt 2026-02-12 01:37:30 +00:00
parent f129715dc8
commit f01d545ee3
4 changed files with 70 additions and 0 deletions

View file

@ -0,0 +1,38 @@
/**
* ContextSwitchOverlay - Full-screen loading overlay during context switches.
*
* Displayed when isContextSwitching is true, preventing stale data flash
* during workspace transitions.
*/
import React from 'react';
import { useStore } from '@renderer/store';
export const ContextSwitchOverlay: React.FC = () => {
const isContextSwitching = useStore((state) => state.isContextSwitching);
const targetContextId = useStore((state) => state.targetContextId);
if (!isContextSwitching) {
return null;
}
// Format context label for display
const contextLabel =
targetContextId === 'local' ? 'Local' : (targetContextId?.replace(/^ssh-/, '') ?? 'Unknown');
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-surface">
<div className="flex flex-col items-center gap-4">
{/* Spinner */}
<div className="size-8 animate-spin rounded-full border-4 border-text border-t-transparent" />
{/* Text */}
<div className="flex flex-col items-center gap-1">
<p className="text-text">Switching to {contextLabel}...</p>
<p className="text-sm text-text-secondary">Loading workspace</p>
</div>
</div>
</div>
);
};

View file

@ -0,0 +1,28 @@
/**
* useContextSwitch - Hook for context switching actions.
*
* Thin wrapper exposing context switch functionality to components.
*/
import { useCallback } from 'react';
import { useStore } from '../store';
export function useContextSwitch() {
const switchContext = useStore((state) => state.switchContext);
const isContextSwitching = useStore((state) => state.isContextSwitching);
const activeContextId = useStore((state) => state.activeContextId);
const handleSwitch = useCallback(
async (targetContextId: string) => {
await switchContext(targetContextId);
},
[switchContext]
);
return {
switchContext: handleSwitch,
isContextSwitching,
activeContextId,
};
}

View file

@ -6,6 +6,7 @@ import { create } from 'zustand';
import { createConfigSlice } from './slices/configSlice';
import { createConnectionSlice } from './slices/connectionSlice';
import { createContextSlice } from './slices/contextSlice';
import { createConversationSlice } from './slices/conversationSlice';
import { createNotificationSlice } from './slices/notificationSlice';
import { createPaneSlice } from './slices/paneSlice';
@ -41,6 +42,7 @@ export const useStore = create<AppState>()((...args) => ({
...createNotificationSlice(...args),
...createConfigSlice(...args),
...createConnectionSlice(...args),
...createContextSlice(...args),
...createUpdateSlice(...args),
}));

View file

@ -5,6 +5,7 @@
import type { ConfigSlice } from './slices/configSlice';
import type { ConnectionSlice } from './slices/connectionSlice';
import type { ContextSlice } from './slices/contextSlice';
import type { ConversationSlice } from './slices/conversationSlice';
import type { NotificationSlice } from './slices/notificationSlice';
import type { PaneSlice } from './slices/paneSlice';
@ -88,4 +89,5 @@ export type AppState = ProjectSlice &
NotificationSlice &
ConfigSlice &
ConnectionSlice &
ContextSlice &
UpdateSlice;