From ade05607d2a5e5cff0d93d91e77a6fd79a04235b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 3 May 2026 20:15:19 +0700 Subject: [PATCH] fix(app): latch startup store readiness --- packages/app/src/app/_layout.tsx | 4 +++- .../src/hooks/use-latched-boolean.test.tsx | 22 +++++++++++++++++++ packages/app/src/hooks/use-latched-boolean.ts | 13 +++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages/app/src/hooks/use-latched-boolean.test.tsx create mode 100644 packages/app/src/hooks/use-latched-boolean.ts diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index 1042a1189..83505e681 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -60,6 +60,7 @@ import { UpdateCalloutSource } from "@/desktop/updates/update-callout-source"; import { useActiveWorktreeNewAction } from "@/hooks/use-active-worktree-new-action"; import { useFaviconStatus } from "@/hooks/use-favicon-status"; import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts"; +import { useLatchedBoolean } from "@/hooks/use-latched-boolean"; import { useOpenProject } from "@/hooks/use-open-project"; import { useAppSettings } from "@/hooks/use-settings"; import { useStableEvent } from "@/hooks/use-stable-event"; @@ -349,8 +350,9 @@ function HostRuntimeBootstrapProvider({ children }: { children: ReactNode }) { }, []); const splashError = !anyOnlineHostServerId ? daemonStartError : null; - const storeReady = + const isCurrentlyStoreReady = Boolean(anyOnlineHostServerId) || Boolean(splashError) || hasGivenUpWaitingForHost; + const storeReady = useLatchedBoolean(isCurrentlyStoreReady); const state = useMemo( () => ({ splashError, retry, hasGivenUpWaitingForHost, storeReady }), diff --git a/packages/app/src/hooks/use-latched-boolean.test.tsx b/packages/app/src/hooks/use-latched-boolean.test.tsx new file mode 100644 index 000000000..30d3ad9c3 --- /dev/null +++ b/packages/app/src/hooks/use-latched-boolean.test.tsx @@ -0,0 +1,22 @@ +/** + * @vitest-environment jsdom + */ +import { renderHook } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { useLatchedBoolean } from "./use-latched-boolean"; + +describe("useLatchedBoolean", () => { + it("stays true after the input first becomes true", () => { + const { result, rerender } = renderHook(({ value }) => useLatchedBoolean(value), { + initialProps: { value: false }, + }); + + expect(result.current).toBe(false); + + rerender({ value: true }); + expect(result.current).toBe(true); + + rerender({ value: false }); + expect(result.current).toBe(true); + }); +}); diff --git a/packages/app/src/hooks/use-latched-boolean.ts b/packages/app/src/hooks/use-latched-boolean.ts new file mode 100644 index 000000000..7008cfe31 --- /dev/null +++ b/packages/app/src/hooks/use-latched-boolean.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from "react"; + +export function useLatchedBoolean(value: boolean): boolean { + const [hasLatched, setHasLatched] = useState(value); + + useEffect(() => { + if (value) { + setHasLatched(true); + } + }, [value]); + + return hasLatched || value; +}