Skip to content

Commit ac179e5

Browse files
authored
🤖 fix: add loading state to ProjectContext to fix Storybook race (#960)
The SingleProject story sometimes failed to show workspace creation controls because of a race between two async operations: 1. **ProjectContext** loads projects via `api.projects.list()` 2. **WorkspaceContext** loads workspaces via `api.workspace.list()` When `WorkspaceContext.loading` became `false`, App would render but projects might still be empty (size 0), causing the condition `projects.size === 1` to be false, so `creationProjectPath` was null. **Fix:** Add a `loading` state to ProjectContext and gate on it in AppLoaderInner alongside `workspaceContext.loading`. _Generated with `mux`_
1 parent 2c5f8b0 commit ac179e5

File tree

2 files changed

+13
-4
lines changed

2 files changed

+13
-4
lines changed

src/browser/components/AppLoader.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import App from "../App";
33
import { LoadingScreen } from "./LoadingScreen";
44
import { useWorkspaceStoreRaw } from "../stores/WorkspaceStore";
55
import { useGitStatusStoreRaw } from "../stores/GitStatusStore";
6-
import { ProjectProvider } from "../contexts/ProjectContext";
6+
import { ProjectProvider, useProjectContext } from "../contexts/ProjectContext";
77
import { APIProvider, useAPI, type APIClient } from "@/browser/contexts/API";
88
import { WorkspaceProvider, useWorkspaceContext } from "../contexts/WorkspaceContext";
99

@@ -41,6 +41,7 @@ export function AppLoader(props: AppLoaderProps) {
4141
*/
4242
function AppLoaderInner() {
4343
const workspaceContext = useWorkspaceContext();
44+
const projectContext = useProjectContext();
4445
const { api } = useAPI();
4546

4647
// Get store instances
@@ -72,8 +73,8 @@ function AppLoaderInner() {
7273
api,
7374
]);
7475

75-
// Show loading screen until stores are synced
76-
if (workspaceContext.loading || !storesSynced) {
76+
// Show loading screen until both projects and workspaces are loaded and stores synced
77+
if (projectContext.loading || workspaceContext.loading || !storesSynced) {
7778
return <LoadingScreen />;
7879
}
7980

src/browser/contexts/ProjectContext.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ interface WorkspaceModalState {
2525

2626
export interface ProjectContext {
2727
projects: Map<string, ProjectConfig>;
28+
/** True while initial project list is loading */
29+
loading: boolean;
2830
refreshProjects: () => Promise<void>;
2931
addProject: (normalizedPath: string, projectConfig: ProjectConfig) => void;
3032
removeProject: (path: string) => Promise<{ success: boolean; error?: string }>;
@@ -58,6 +60,7 @@ function deriveProjectName(projectPath: string): string {
5860
export function ProjectProvider(props: { children: ReactNode }) {
5961
const { api } = useAPI();
6062
const [projects, setProjects] = useState<Map<string, ProjectConfig>>(new Map());
63+
const [loading, setLoading] = useState(true);
6164
const [isProjectCreateModalOpen, setProjectCreateModalOpen] = useState(false);
6265
const [workspaceModalState, setWorkspaceModalState] = useState<WorkspaceModalState>({
6366
isOpen: false,
@@ -82,7 +85,10 @@ export function ProjectProvider(props: { children: ReactNode }) {
8285
}, [api]);
8386

8487
useEffect(() => {
85-
void refreshProjects();
88+
void (async () => {
89+
await refreshProjects();
90+
setLoading(false);
91+
})();
8692
}, [refreshProjects]);
8793

8894
const addProject = useCallback((normalizedPath: string, projectConfig: ProjectConfig) => {
@@ -224,6 +230,7 @@ export function ProjectProvider(props: { children: ReactNode }) {
224230
const value = useMemo<ProjectContext>(
225231
() => ({
226232
projects,
233+
loading,
227234
refreshProjects,
228235
addProject,
229236
removeProject,
@@ -239,6 +246,7 @@ export function ProjectProvider(props: { children: ReactNode }) {
239246
}),
240247
[
241248
projects,
249+
loading,
242250
refreshProjects,
243251
addProject,
244252
removeProject,

0 commit comments

Comments
 (0)