From 6883d83ad1dd9b08305967c3ff050d767aa8a10c Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Wed, 24 Jun 2026 11:25:57 -0700 Subject: [PATCH] Fix overlay warning --- .../pushed-config-error-overlay/index.test.ts | 72 +++++++++++++++++++ .../src/pushed-config-error-overlay/index.ts | 5 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 packages/template/src/pushed-config-error-overlay/index.test.ts diff --git a/packages/template/src/pushed-config-error-overlay/index.test.ts b/packages/template/src/pushed-config-error-overlay/index.test.ts new file mode 100644 index 000000000..b59040f98 --- /dev/null +++ b/packages/template/src/pushed-config-error-overlay/index.test.ts @@ -0,0 +1,72 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { envVars } from "../generated/env"; +import { StackClientApp } from "../lib/hexclave-app"; +import { mountPushedConfigErrorOverlay } from "."; + +function createMockElement() { + return { + style: {}, + appendChild: () => {}, + addEventListener: () => {}, + setAttribute: () => {}, + replaceChildren: () => {}, + remove: () => {}, + }; +} + +describe("pushed config error overlay", () => { + afterEach(() => { + vi.unstubAllEnvs(); + vi.unstubAllGlobals(); + }); + + it("defers the first project refresh until after construction-time callers finish", async () => { + const app = new StackClientApp({ + baseUrl: "http://localhost:12345", + projectId: "00000000-0000-4000-8000-000000000000", + publishableClientKey: "stack-pk-test", + tokenStore: "memory", + redirectMethod: "none", + devTool: false, + }); + const getProject = vi.fn(async () => ({ + pushedConfigError: null, + configWarnings: [], + })); + Reflect.set(app, "getProject", getProject); + const appendChild = vi.fn(); + vi.stubEnv("NODE_ENV", "development"); + expect(Reflect.get(envVars, "NODE_ENV")).toBe("development"); + + vi.stubGlobal("window", { + "__hexclave-pushed-config-error-overlay": null, + location: { + href: "http://localhost:3000", + }, + }); + vi.stubGlobal("document", { + body: { + appendChild, + }, + createElement: () => createMockElement(), + createTextNode: () => createMockElement(), + }); + vi.stubGlobal("localStorage", { + getItem: () => null, + setItem: () => {}, + removeItem: () => {}, + }); + + const cleanup = mountPushedConfigErrorOverlay(app); + try { + expect(appendChild).toHaveBeenCalledOnce(); + expect(getProject).not.toHaveBeenCalled(); + + await Promise.resolve(); + + expect(getProject).toHaveBeenCalledOnce(); + } finally { + cleanup(); + } + }); +}); diff --git a/packages/template/src/pushed-config-error-overlay/index.ts b/packages/template/src/pushed-config-error-overlay/index.ts index 7167ac791..145d3a93b 100644 --- a/packages/template/src/pushed-config-error-overlay/index.ts +++ b/packages/template/src/pushed-config-error-overlay/index.ts @@ -528,7 +528,10 @@ export function mountPushedConfigErrorOverlay(app: StackClientApp): () => }); }; - refresh(); + // This is mounted from the base client-app constructor, which also runs + // before subclass field initializers. Defer the first app call so overridden + // methods like adminApp.getProject() can safely touch subclass caches. + queueMicrotask(refresh); const interval = setInterval(refresh, REFRESH_INTERVAL_MS); const cleanup = () => {