From 77e33f8845f50d95489b1ec137c7c8ebad16cc2d Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 26 Sep 2026 20:34:30 +0200 Subject: [PATCH] fix(app): keep host connection statuses live under the React Compiler The hook read a store version only as a memo trigger (void version); the compiler dropped it, so pages that mounted before a host came online kept showing it as connecting. --- packages/app/e2e/browser/usage-screen.spec.ts | 24 +++++++++++++++++++ packages/app/src/runtime/host-runtime.ts | 23 +++++++++--------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/app/e2e/browser/usage-screen.spec.ts b/packages/app/e2e/browser/usage-screen.spec.ts index cc180c4706..3ffa9a65e0 100644 --- a/packages/app/e2e/browser/usage-screen.spec.ts +++ b/packages/app/e2e/browser/usage-screen.spec.ts @@ -40,6 +40,30 @@ test.describe("usage screen", () => { await expect(group.getByText("Unavailable", { exact: true })).toBeVisible(); }); + test("shows the host once it connects after a cold load on a phone", async ({ page }) => { + test.setTimeout(120_000); + const serverId = getServerId(); + const usage = await installUsageReportsFixture(page, { + lists: [ + [ + { + sourceId: "alpha", + sourceLabel: "Alpha plan", + report: { account: { key: "a" }, status: "available", windows: [] }, + }, + ], + ], + }); + + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/usage"); + await usage.waitForListRequests(1); + + await expect( + page.getByTestId(`usage-host-${serverId}`).getByText("Alpha plan", { exact: true }), + ).toBeVisible({ timeout: 10_000 }); + }); + test("tells the user to update a host without usage sources", async ({ page }) => { test.setTimeout(120_000); const serverId = getServerId(); diff --git a/packages/app/src/runtime/host-runtime.ts b/packages/app/src/runtime/host-runtime.ts index 83cc37e903..2cea05144d 100644 --- a/packages/app/src/runtime/host-runtime.ts +++ b/packages/app/src/runtime/host-runtime.ts @@ -2653,21 +2653,22 @@ export function useHostRuntimeConnectionStatuses( serverIds: readonly string[], ): ReadonlyMap { const store = getHostRuntimeStore(); - const version = useSyncExternalStore( + // The snapshot is the statuses themselves, joined into a string so React compares by + // value. A version counter read only for reactivity is dropped by the React Compiler. + const readStatuses = () => + serverIds + .map((serverId) => store.getSnapshot(serverId)?.connectionStatus ?? "connecting") + .join("\n"); + const statuses = useSyncExternalStore( (onStoreChange) => store.subscribeAll(onStoreChange), - () => store.getVersion(), - () => store.getVersion(), + readStatuses, + readStatuses, ); return useMemo(() => { - // The aggregate version is the reactivity trigger; re-read snapshots on every host tick. - void version; - const entries: Array<[string, HostRuntimeConnectionStatus]> = serverIds.map((serverId) => [ - serverId, - store.getSnapshot(serverId)?.connectionStatus ?? "connecting", - ]); - return new Map(entries); - }, [serverIds, store, version]); + const values = statuses.split("\n") as HostRuntimeConnectionStatus[]; + return new Map(serverIds.map((serverId, index) => [serverId, values[index]])); + }, [serverIds, statuses]); } export function useHostRuntimeLastError(serverId: string): string | null {