# Files changed · PR \#22267 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/22267/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[test\(web\): migrate browser tests to the disposable render helper \(8/8\)](https://git.cafe/pingdotgg/t3code/pull/22267.md)

Version: 9

Head commit: 4eb6aabc23d5760f2c10f40041157c8c908fb8eb

Base commit: ab423b24a8b52ca24e59975f15e917f35d37a68f

Comparison base: ab423b24a8b52ca24e59975f15e917f35d37a68f

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/hooks/useHtmlRenderTheme.browser.test.tsx

Status: modified; +15; −28

```diff
@@ -1,9 +1,9 @@
 import { HTML_RENDER_DEFAULT_FONTS, type HtmlRenderTheme } from "@t3tools/shared/htmlRender";
 import { BUILT_IN_THEME_IDS } from "@t3tools/shared/themePalettes";
-import { act, useEffect } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { useEffect } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { render } from "../../test/render";
 import {
   getStandardThemeColors,
   setEnvironmentThemes,
@@ -24,21 +24,12 @@
   useTheme: (() => selection) as never,
 }));
 
-let root: Root | undefined;
-
-beforeEach(() => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  selection.themeHalves = null;
+function setup(theme: ThemePreference, resolvedTheme: ThemeAppearance): Disposable {
+  Object.assign(selection, { theme, resolvedTheme, themeHalves: null });
-});
+  return { [Symbol.dispose]: () => setEnvironmentThemes([]) };
+}
 
+async function readRenderTheme(): Promise<HtmlRenderTheme> {
-afterEach(() => {
-  act(() => root?.unmount());
-  root = undefined;
-  setEnvironmentThemes([]);
-  vi.unstubAllGlobals();
-});
-
-function readRenderTheme(): HtmlRenderTheme {
   let theme: HtmlRenderTheme | undefined;
   function Probe() {
     const resolved = useHtmlRenderTheme();
@@ -47,9 +38,7 @@
     }, [resolved]);
     return null;
   }
-  act(() => root?.unmount());
+  await using _screen = await render(<Probe />);
-  root = createRoot(document.createElement("div"));
-  act(() => root!.render(<Probe />));
   if (!theme) throw new Error("The HTML render theme was not resolved.");
   return theme;
 }
@@ -58,11 +47,10 @@
   for (const appearance of ["light", "dark"] as const) {
     it.each([appearance, ...BUILT_IN_THEME_IDS])(
       `injects hex colors for %s in ${appearance} mode`,
+      async (preference) => {
+        using _selection = setup(preference, appearance);
-      (preference) => {
-        selection.theme = preference;
-        selection.resolvedTheme = appearance;
 
+        const theme = await readRenderTheme();
-        const theme = readRenderTheme();
 
         expect(theme.appearance).toBe(appearance);
         for (const [variable, value] of Object.entries(theme.variables)) {
@@ -76,7 +64,8 @@
     );
   }
 
-  it("preserves alpha when converting a published palette", () => {
+  it("preserves alpha when converting a published palette", async () => {
+    using _selection = setup("transparent", "dark");
     setEnvironmentThemes([
       {
         id: "transparent",
@@ -85,10 +74,8 @@
         colors: { ...getStandardThemeColors("dark"), accent: "oklch(0.5 0 0 / 0.5)" },
       },
     ]);
-    selection.theme = "transparent";
-    selection.resolvedTheme = "dark";
 
-    const theme = readRenderTheme();
+    const theme = await readRenderTheme();
 
     expect(theme.variables["--accent"]).toBe("#63636380");
     expect(theme.variables["--chart-1"]).toBe("#63636380");
```

## apps/web/src/hooks/useLiveRefresh.browser.test.tsx

Status: modified; +43; −49

```diff
@@ -1,87 +1,81 @@
-import { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, fakeTimers, render } from "../../test/render";
 import { LIVE_REFRESH_IDLE_AFTER_MS, useLiveRefresh } from "./useLiveRefresh";
 
+function setup() {
+  using stack = new DisposableStack();
+  stack.use(fakeTimers());
+  let visibility: DocumentVisibilityState = "visible";
+  stack.use(vi.spyOn(document, "visibilityState", "get").mockImplementation(() => visibility));
+  const environment = stack.move();
+  return {
-let root: Root;
+    refresh: vi.fn(),
-let visibility: DocumentVisibilityState;
+    setVisibility(next: DocumentVisibilityState) {
+      visibility = next;
+      document.dispatchEvent(new Event("visibilitychange"));
-const refresh = vi.fn();
-
-function setVisibility(next: DocumentVisibilityState) {
-  visibility = next;
-  document.dispatchEvent(new Event("visibilitychange"));
+    },
+    [Symbol.dispose]: () => environment.dispose(),
+  };
 }
 
-beforeEach(() => {
-  vi.useFakeTimers();
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  visibility = "visible";
-  vi.spyOn(document, "visibilityState", "get").mockImplementation(() => visibility);
-  refresh.mockReset();
-  root = createRoot(document.createElement("div"));
-});
-
-afterEach(() => {
-  act(() => root.unmount());
-  vi.restoreAllMocks();
-  vi.useRealTimers();
-  vi.unstubAllGlobals();
-});
-
 describe("live refresh cadence", () => {
+  it("polls every 45 seconds, pauses while hidden or busy, respects disabling, and cleans up", async () => {
+    using env = setup();
-  it("polls every 45 seconds, pauses while hidden or busy, respects disabling, and cleans up", () => {
+    const { refresh } = env;
     vi.setSystemTime(0);
     function Probe({ pending = false, enabled = true }) {
       useLiveRefresh(pending ? null : refresh, { intervalMs: 45_000, enabled });
       return null;
     }
-    act(() => root.render(<Probe />));
+    await using screen = await render(<Probe />);
-    act(() => vi.advanceTimersByTime(44_999));
+    await act(() => vi.advanceTimersByTime(44_999));
     expect(refresh).not.toHaveBeenCalled();
+    await act(() => vi.advanceTimersByTime(1));
-    act(() => vi.advanceTimersByTime(1));
     expect(refresh).toHaveBeenCalledTimes(1);
-    act(() => vi.advanceTimersByTime(45_000));
+    await act(() => vi.advanceTimersByTime(45_000));
     expect(refresh).toHaveBeenCalledTimes(2);
-    setVisibility("hidden");
+    env.setVisibility("hidden");
+    await act(() => vi.advanceTimersByTime(90_000));
-    act(() => vi.advanceTimersByTime(90_000));
     expect(refresh).toHaveBeenCalledTimes(2);
-    setVisibility("visible");
+    env.setVisibility("visible");
     window.dispatchEvent(new Event("focus"));
     expect(refresh).toHaveBeenCalledTimes(3);
-    act(() => root.render(<Probe pending />));
+    await screen.rerender(<Probe pending />);
+    await act(() => vi.advanceTimersByTime(45_000));
-    act(() => vi.advanceTimersByTime(45_000));
     expect(refresh).toHaveBeenCalledTimes(3);
+    await screen.rerender(<Probe />);
+    await act(() => vi.advanceTimersByTime(45_000));
-    act(() => root.render(<Probe />));
-    act(() => vi.advanceTimersByTime(45_000));
     expect(refresh).toHaveBeenCalledTimes(4);
-    act(() => root.render(<Probe enabled={false} />));
+    await screen.rerender(<Probe enabled={false} />);
+    await act(() => vi.advanceTimersByTime(90_000));
-    act(() => vi.advanceTimersByTime(90_000));
     window.dispatchEvent(new Event("focus"));
     expect(refresh).toHaveBeenCalledTimes(4);
-    act(() => root.render(null));
+    await screen.rerender(null);
-    act(() => vi.advanceTimersByTime(45_000));
+    await act(() => vi.advanceTimersByTime(45_000));
     expect(refresh).toHaveBeenCalledTimes(4);
   });
 
   it.each(["focus", "visibilitychange"])(
     "keeps an idle view paused after %s until input",
+    async (event) => {
-    (event) => {
+      using env = setup();
+      const { refresh } = env;
       vi.setSystemTime(1_000_000);
       function Probe() {
         useLiveRefresh(refresh, { intervalMs: 45_000 });
         return null;
       }
+      await using _screen = await render(<Probe />);
-      act(() => root.render(<Probe />));
+      await act(() => vi.advanceTimersByTime(LIVE_REFRESH_IDLE_AFTER_MS));
-      act(() => vi.advanceTimersByTime(LIVE_REFRESH_IDLE_AFTER_MS));
       refresh.mockClear();
-      act(() => (event === "focus" ? window : document).dispatchEvent(new Event(event)));
+      await act(() => (event === "focus" ? window : document).dispatchEvent(new Event(event)));
       expect(refresh).not.toHaveBeenCalled();
+      await act(() => vi.advanceTimersByTime(90_000));
-      act(() => vi.advanceTimersByTime(90_000));
       expect(refresh).not.toHaveBeenCalled();
+      await act(() => document.dispatchEvent(new Event("pointerdown")));
-      act(() => document.dispatchEvent(new Event("pointerdown")));
       expect(refresh).toHaveBeenCalledTimes(1);
-      act(() => vi.advanceTimersByTime(45_000));
+      await act(() => vi.advanceTimersByTime(45_000));
       expect(refresh).toHaveBeenCalledTimes(2);
     },
   );
```

## apps/web/src/hooks/useLocalStorage.hook.browser.test.tsx

Status: modified; +32; −31

```diff
@@ -1,51 +1,52 @@
 import * as Schema from "effect/Schema";
-import { act, createElement, useEffect } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { useEffect } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, render } from "../../test/render";
 import { useLocalStorage } from "./useLocalStorage";
 
 const KEY = "corrupt-key";
-let root: Root;
-let current: ReturnType<typeof useLocalStorage<number, number>>;
+type StoredNumber = ReturnType<typeof useLocalStorage<number, number>>;
 
-function Probe() {
-  const result = useLocalStorage(KEY, 0, Schema.Number);
+async function renderWithCorruptValue() {
+  await using stack = new AsyncDisposableStack();
+  stack.use(vi.spyOn(console, "error").mockImplementation(() => {}));
+  localStorage.setItem(KEY, JSON.stringify("not a number"));
+  stack.defer(() => localStorage.clear());
+  let current: StoredNumber | undefined;
+  function Probe() {
+    const result = useLocalStorage(KEY, 0, Schema.Number);
+    useEffect(() => {
+      current = result;
+    });
+    return null;
+  }
+  stack.use(await render(<Probe />));
+  const environment = stack.move();
+  return {
+    get current() {
+      return current!;
+    },
+    [Symbol.asyncDispose]: () => environment.disposeAsync(),
+  };
-  useEffect(() => {
-    current = result;
-  });
-  return null;
 }
 
-beforeEach(async () => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  vi.spyOn(console, "error").mockImplementation(() => {});
-  localStorage.setItem(KEY, JSON.stringify("not a number"));
-  root = createRoot(document.createElement("div"));
-  await act(() => root.render(createElement(Probe)));
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  localStorage.clear();
-  vi.restoreAllMocks();
-  vi.unstubAllGlobals();
-});
-
 describe("useLocalStorage with a stored value that fails decode", () => {
   it("replaces it with a plain write", async () => {
-    expect(current[0]).toBe(0);
+    await using stored = await renderWithCorruptValue();
+    expect(stored.current[0]).toBe(0);
 
-    await act(() => current[1](42));
+    await act(() => stored.current[1](42));
 
     expect(localStorage.getItem(KEY)).toBe("42");
-    expect(current[0]).toBe(42);
+    expect(stored.current[0]).toBe(42);
   });
 
   it("runs an updater from the initial value", async () => {
+    await using stored = await renderWithCorruptValue();
+    await act(() => stored.current[1]((value) => value + 1));
-    await act(() => current[1]((value) => value + 1));
 
     expect(localStorage.getItem(KEY)).toBe("1");
+    expect(stored.current[0]).toBe(1);
-    expect(current[0]).toBe(1);
   });
 });
```

## apps/web/src/hooks/usePullRequestChecksRefresh.browser.test.tsx

Status: modified; +30; −45

```diff
@@ -1,32 +1,18 @@
 import type { PullRequestCheck } from "@t3tools/contracts";
-import { act } from "react";
+import { expect, it, vi } from "vite-plus/test";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
 
+import { act, fakeTimers, render } from "../../test/render";
 import { usePullRequestChecksRefresh } from "./usePullRequestChecksRefresh";
 
-let root: Root;
-let visibility: DocumentVisibilityState;
-
-beforeEach(() => {
-  vi.useFakeTimers();
-  vi.setSystemTime(1_000_000);
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  visibility = "visible";
-  vi.spyOn(document, "visibilityState", "get").mockImplementation(() => visibility);
-  root = createRoot(document.createElement("div"));
-});
-
-afterEach(() => {
-  act(() => root.unmount());
-  vi.restoreAllMocks();
-  vi.useRealTimers();
-  vi.unstubAllGlobals();
-});
-
 it.each(["pending", "action-required"] as const)(
   "polls quiet PRs each minute, speeds up for %s or missing checks, resumes from idle, and stops for closed PRs",
+  async (pendingStatus) => {
+    using _timers = fakeTimers();
+    vi.setSystemTime(1_000_000);
+    let visibility: DocumentVisibilityState = "visible";
+    using _visibility = vi
+      .spyOn(document, "visibilityState", "get")
+      .mockImplementation(() => visibility);
-  (pendingStatus) => {
     const refresh = vi.fn();
     function Probe({
       status = "success",
@@ -43,46 +29,45 @@
       });
       return null;
     }
+    await using screen = await render(<Probe />);
+    const update = (props: Parameters<typeof Probe>[0]) => screen.rerender(<Probe {...props} />);
-    const update = (props: Parameters<typeof Probe>[0]) =>
-      act(() => root.render(<Probe {...props} />));
     const advance = (ms: number) => act(() => vi.advanceTimersByTime(ms));
 
-    update({});
+    await advance(59_999);
-    advance(59_999);
     expect(refresh).toHaveBeenCalledTimes(0);
+    await advance(1);
-    advance(1);
     expect(refresh).toHaveBeenCalledTimes(1);
-    update({ status: pendingStatus });
-    advance(44_999);
+    await update({ status: pendingStatus });
+    await advance(44_999);
     expect(refresh).toHaveBeenCalledTimes(1);
+    await advance(1);
-    advance(1);
     expect(refresh).toHaveBeenCalledTimes(2);
+    await update({ status: "failure" });
-    update({ status: "failure" });
+    await advance(60_000);
-    advance(60_000);
     expect(refresh).toHaveBeenCalledTimes(3);
-    update({ status: null });
+    await update({ status: null });
     for (let tick = 0; tick < 3; tick++) {
-      advance(45_000);
-      update({ status: null });
+      await advance(45_000);
+      await update({ status: null });
     }
     expect(refresh).toHaveBeenCalledTimes(6);
-    advance(44_999);
+    await advance(44_999);
     expect(refresh).toHaveBeenCalledTimes(6);
+    await advance(1);
-    advance(1);
     expect(refresh).toHaveBeenCalledTimes(7);
+    await advance(6 * 60_000);
-    advance(6 * 60_000);
     const beforeResume = refresh.mock.calls.length;
-    act(() => document.dispatchEvent(new Event("pointerdown")));
+    await act(() => document.dispatchEvent(new Event("pointerdown")));
     expect(refresh).toHaveBeenCalledTimes(beforeResume + 1);
     visibility = "hidden";
-    act(() => document.dispatchEvent(new Event("visibilitychange")));
+    await act(() => document.dispatchEvent(new Event("visibilitychange")));
+    await advance(60_000);
-    advance(60_000);
     expect(refresh).toHaveBeenCalledTimes(beforeResume + 1);
     visibility = "visible";
-    act(() => document.dispatchEvent(new Event("visibilitychange")));
+    await act(() => document.dispatchEvent(new Event("visibilitychange")));
     expect(refresh).toHaveBeenCalledTimes(beforeResume + 2);
-    update({ enabled: false });
-    advance(120_000);
+    await update({ enabled: false });
+    await advance(120_000);
     expect(refresh).toHaveBeenCalledTimes(beforeResume + 2);
   },
 );
```

## apps/web/src/hooks/useResizableWidth.browser.test.tsx

Status: modified; +100; −84

```diff
@@ -1,16 +1,9 @@
-import { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import type { ReactNode } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, render, type Screen } from "../../test/render";
 import { RESIZABLE_WIDTH_PROPERTY, useResizableWidth } from "./useResizableWidth";
 
-let root: Root;
-let container: HTMLDivElement;
-let captured = false;
-let frame: FrameRequestCallback | undefined;
-const cancelAnimationFrame = vi.fn();
-let setItem: ReturnType<typeof vi.spyOn<Storage, "setItem">>;
-
 function Panel({
   edge = "left",
   maxWidth = 800,
@@ -35,97 +28,106 @@
   );
 }
 
-const host = () => container.querySelector<HTMLElement>("[data-testid=host]")!;
-const shownWidth = () => Number(container.querySelector("output")!.textContent);
-const liveWidth = () => host().style.getPropertyValue(RESIZABLE_WIDTH_PROPERTY);
-const bodyStyle = () => document.body.style;
-
-function handle() {
-  const element = container.querySelector<HTMLElement>("[data-testid=handle]")!;
-  // Synthetic pointers have no active pointer for the browser to capture.
-  element.setPointerCapture = () => {
-    captured = true;
-  };
-  element.hasPointerCapture = () => captured;
-  element.releasePointerCapture = () => {
+async function renderPanel() {
+  await using stack = new AsyncDisposableStack();
+  let captured = false;
+  let frame: FrameRequestCallback | undefined;
+  const cancelAnimationFrame = vi.fn();
+  stack.use(
+    vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
+      frame = callback;
+      return 42;
-    captured = false;
-  };
-  return element;
-}
-
-function pointer(
-  type: "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "lostpointercapture",
-  clientX = 100,
-) {
-  handle().dispatchEvent(
-    new PointerEvent(type, { bubbles: true, cancelable: true, button: 0, pointerId: 1, clientX }),
+    }),
   );
+  stack.use(vi.spyOn(window, "cancelAnimationFrame").mockImplementation(cancelAnimationFrame));
-}
+  const setItem = stack.use(vi.spyOn(Storage.prototype, "setItem"));
+  localStorage.clear();
+  stack.defer(() => localStorage.clear());
+  let screen: Screen = stack.use(await render(<Panel />));
+  const container = () => screen.container;
+  const host = () => container().querySelector<HTMLElement>("[data-testid=host]")!;
 
+  function handle() {
+    const element = container().querySelector<HTMLElement>("[data-testid=handle]")!;
+    // Synthetic pointers have no active pointer for the browser to capture.
+    element.setPointerCapture = () => {
+      captured = true;
+    };
+    element.hasPointerCapture = () => captured;
+    element.releasePointerCapture = () => {
+      captured = false;
+    };
+    return element;
-async function render(element = <Panel />) {
-  await act(() => root.render(element));
-}
+  }
 
+  const environment = stack.move();
+  return {
+    setItem,
+    cancelAnimationFrame,
+    get captured() {
+      return captured;
+    },
+    frame: () => frame?.(0),
+    host,
+    shownWidth: () => Number(container().querySelector("output")!.textContent),
+    liveWidth: () => host().style.getPropertyValue(RESIZABLE_WIDTH_PROPERTY),
+    pointer(
+      type: "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "lostpointercapture",
+      clientX = 100,
+    ) {
+      handle().dispatchEvent(
+        new PointerEvent(type, {
+          bubbles: true,
+          cancelable: true,
-async function mountFresh(element = <Panel />) {
-  root = createRoot(container);
-  await render(element);
+          button: 0,
+          pointerId: 1,
+          clientX,
+        }),
+      );
+    },
+    rerender: (element: ReactNode = <Panel />) => screen.rerender(element),
+    unmount: () => screen.unmount(),
+    async mountFresh(element: ReactNode = <Panel />) {
+      screen = environment.use(await render(element));
+    },
+    [Symbol.asyncDispose]: () => environment.disposeAsync(),
+  };
 }
 
-beforeEach(async () => {
-  localStorage.clear();
-  captured = false;
-  frame = undefined;
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
-    frame = callback;
-    return 42;
-  });
-  vi.spyOn(window, "cancelAnimationFrame").mockImplementation(cancelAnimationFrame);
-  setItem = vi.spyOn(Storage.prototype, "setItem");
-  container = document.createElement("div");
-  document.body.append(container);
-  await mountFresh();
+const bodyStyle = () => document.body.style;
-});
 
-afterEach(async () => {
-  await act(() => root.unmount());
-  container.remove();
-  localStorage.clear();
-  vi.restoreAllMocks();
-  vi.unstubAllGlobals();
-  cancelAnimationFrame.mockClear();
-});
-
 describe("panel resize cleanup", () => {
   it.each(["unmount", "lost capture", "blur", "cancel"])(
     "clears the cursor and pending resize after %s",
     async (reason) => {
+      await using panel = await renderPanel();
+      const { pointer, host, shownWidth, liveWidth, setItem, cancelAnimationFrame } = panel;
       await act(() => {
         pointer("pointerdown");
         pointer("pointermove", 50);
       });
       const resizedHost = host();
+      await act(() => panel.frame());
-      await act(() => frame?.(0));
       expect(liveWidth()).toBe("450px");
       expect(resizedHost.style.getPropertyValue("transition-property")).toBe("none");
       expect(shownWidth()).toBe(400);
       await act(() => pointer("pointermove", 25));
       expect(bodyStyle().cursor).toBe("col-resize");
       expect(bodyStyle().userSelect).toBe("none");
+      if (reason === "unmount") await panel.unmount();
       await act(() => {
-        if (reason === "unmount") root.unmount();
         if (reason === "lost capture") pointer("lostpointercapture");
         if (reason === "blur") window.dispatchEvent(new Event("blur"));
         if (reason === "cancel") pointer("pointercancel");
       });
       expect(bodyStyle().cursor).toBe("");
       expect(bodyStyle().userSelect).toBe("");
-      expect(captured).toBe(false);
+      expect(panel.captured).toBe(false);
       expect(resizedHost.style.getPropertyValue("transition-property")).toBe("");
       expect(cancelAnimationFrame).toHaveBeenCalledWith(42);
       if (reason === "unmount") {
         expect(setItem).not.toHaveBeenCalled();
-        await mountFresh();
+        await panel.mountFresh();
       } else {
         expect(shownWidth()).toBe(475);
         expect(setItem).toHaveBeenCalledExactlyOnceWith("test-panel-width", "475");
@@ -136,7 +138,9 @@
   it.each(["left", "right"] as const)(
     "uses the release position for a fast %s-edge drag",
     async (edge) => {
-      await render(<Panel edge={edge} />);
+      await using panel = await renderPanel();
+      const { pointer, shownWidth, setItem, cancelAnimationFrame } = panel;
+      await panel.rerender(<Panel edge={edge} />);
       await act(() => {
         pointer("pointerdown");
         pointer("pointermove", edge === "left" ? 50 : 150);
@@ -152,16 +156,18 @@
     [800, 450],
     [450, 800],
   ])("uses updated bounds during a drag from max %s to %s", async (initialMax, nextMax) => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth, liveWidth, setItem } = panel;
-    await render(<Panel maxWidth={initialMax} />);
+    await panel.rerender(<Panel maxWidth={initialMax} />);
     await act(() => {
       pointer("pointerdown");
       pointer("pointermove", -200);
     });
+    await act(() => panel.frame());
-    await act(() => frame?.(0));
     expect(liveWidth()).toBe(`${Math.min(700, initialMax)}px`);
-    await render(<Panel maxWidth={nextMax} />);
+    await panel.rerender(<Panel maxWidth={nextMax} />);
     await act(() => pointer("pointermove", -250));
-    await act(() => frame?.(0));
+    await act(() => panel.frame());
     expect(liveWidth()).toBe(`${Math.min(750, nextMax)}px`);
     await act(() => pointer("pointerup", -300));
     expect(shownWidth()).toBe(nextMax);
@@ -169,6 +175,8 @@
   });
 
   it("handles release before any move event", async () => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth, setItem } = panel;
     await act(() => {
       pointer("pointerdown");
       pointer("pointerup", 25);
@@ -178,6 +186,8 @@
   });
 
   it("clamps the release position to the panel bounds", async () => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth, setItem } = panel;
     await act(() => {
       pointer("pointerdown");
       pointer("pointerup", -1000);
@@ -187,6 +197,8 @@
   });
 
   it("saves the final width when release is followed by lost capture", async () => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth, setItem } = panel;
     await act(() => {
       pointer("pointerdown");
       pointer("pointermove", 50);
@@ -196,19 +208,21 @@
     expect(shownWidth()).toBe(450);
     expect(setItem).toHaveBeenCalledExactlyOnceWith("test-panel-width", "450");
     expect(bodyStyle().cursor).toBe("");
-    expect(captured).toBe(false);
+    expect(panel.captured).toBe(false);
   });
 });
 
 describe("panel width storage changes", () => {
   it("restores separate thread widths without remounting and retains them after reload", async () => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth } = panel;
     await act(() => {
       pointer("pointerdown");
       pointer("pointermove", 50);
       pointer("pointerup", 50);
     });
     expect(shownWidth()).toBe(450);
-    await render(<Panel storageKey="thread-b" />);
+    await panel.rerender(<Panel storageKey="thread-b" />);
     expect(shownWidth()).toBe(400);
     await act(() => {
       pointer("pointerdown");
@@ -216,35 +230,37 @@
       pointer("pointerup", -100);
     });
     expect(shownWidth()).toBe(600);
-    await render(<Panel />);
+    await panel.rerender(<Panel />);
     expect(shownWidth()).toBe(450);
-    await act(() => root.unmount());
-    await mountFresh(<Panel storageKey="thread-b" />);
+    await panel.unmount();
+    await panel.mountFresh(<Panel storageKey="thread-b" />);
     expect(shownWidth()).toBe(600);
   });
 
   it("cancels an unfinished drag on a thread switch without saving it to either thread", async () => {
+    await using panel = await renderPanel();
+    const { pointer, shownWidth, liveWidth, setItem } = panel;
     localStorage.setItem("thread-b", "650");
     setItem.mockClear();
     await act(() => {
       pointer("pointerdown");
       pointer("pointermove", 50);
     });
+    await act(() => panel.frame());
-    await act(() => frame?.(0));
     expect(liveWidth()).toBe("450px");
     await act(() => pointer("pointermove", 25));
-    await render(<Panel storageKey="thread-b" />);
+    await panel.rerender(<Panel storageKey="thread-b" />);
     expect(shownWidth()).toBe(650);
     expect(liveWidth()).toBe("650px");
+    expect(panel.captured).toBe(false);
-    expect(captured).toBe(false);
     expect(bodyStyle().cursor).toBe("");
     await act(() => {
+      panel.frame();
-      frame?.(0);
       pointer("pointerup", 25);
     });
     expect(shownWidth()).toBe(650);
     expect(setItem).not.toHaveBeenCalled();
-    await render(<Panel />);
+    await panel.rerender(<Panel />);
     expect(shownWidth()).toBe(400);
   });
 });
```

## apps/web/src/hooks/useSettings.favorites.browser.test.tsx

Status: modified; +73; −51

```diff
@@ -13,10 +13,11 @@
 import * as Cause from "effect/Cause";
 import { AsyncResult, Atom, AtomRegistry } from "effect/reactivity";
 import { applyServerSettingsPatch } from "@t3tools/shared/serverSettings";
-import { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import type { ReactNode } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, render, type Screen } from "../../test/render";
+
 const state = vi.hoisted(() => ({
   settings: new Map<string, unknown>(),
   connections: new Map<
@@ -70,51 +71,39 @@
   favorites: [],
   providerModelPreferences: {},
 } satisfies ServerSettings;
-let registry: AtomRegistry.AtomRegistry;
-let root: Root;
-let container: HTMLDivElement;
-let durable: Map<EnvironmentId, ServerSettings>;
-let toggles: Array<() => Promise<void>>;
-let pendingClick: Promise<void> | undefined;
-let writes: Array<{
+interface Write {
   environmentId: EnvironmentId;
   patch: ServerSettingsPatch;
   finish: (success?: boolean) => void;
+}
-}>;
 
 const settingsAtom = (id: EnvironmentId) =>
   state.settings.get(id) as Atom.Writable<ServerSettings | null>;
 
+function FavoriteEditor({
+  environmentId,
+  toggles,
+  onClicked,
+}: {
+  environmentId: EnvironmentId;
+  toggles: Array<() => Promise<void>>;
-function FavoriteEditor({ environmentId }: { environmentId: EnvironmentId }) {
+  onClicked: (pending: Promise<void>) => void;
+}) {
   const toggle = useToggleEnvironmentModelFavorite(environmentId);
   const onClick = () => toggle(provider, model);
   toggles.push(onClick);
-  return <button onClick={() => void (pendingClick = onClick())}>Toggle favorite</button>;
-}
-
-function mount(environmentId = primaryId) {
-  return act(() =>
-    root.render(
-      <RegistryContext.Provider value={registry}>
-        <FavoriteEditor environmentId={environmentId} />
-      </RegistryContext.Provider>,
-    ),
-  );
-}
-
-function click(): Promise<void> {
-  container.querySelector("button")!.click();
-  return pendingClick!;
+  return <button onClick={() => onClicked(onClick())}>Toggle favorite</button>;
 }
 
+async function setup() {
+  await using stack = new AsyncDisposableStack();
+  const registry = stack.adopt(AtomRegistry.make(), (registry) => registry.dispose());
-beforeEach(() => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  registry = AtomRegistry.make();
   state.settings.clear();
   state.connections.clear();
-  durable = new Map();
+  const durable = new Map<EnvironmentId, ServerSettings>();
+  const toggles: Array<() => Promise<void>> = [];
+  const writes: Write[] = [];
+  let pendingClick: Promise<void> | undefined;
-  toggles = [];
-  writes = [];
   for (const id of [primaryId, remoteId]) {
     state.settings.set(id, Atom.make<ServerSettings | null>(initialSettings));
     durable.set(id, initialSettings);
@@ -143,22 +132,45 @@
         });
       }),
   );
-  container = document.createElement("div");
-  document.body.append(container);
-  root = createRoot(container);
-});
 
-afterEach(async () => {
-  await act(() => root.unmount());
-  container.remove();
-  registry.dispose();
-  vi.unstubAllGlobals();
-});
+  const environment = stack.move();
+  let screen: Screen | undefined;
+  const show = async (ui: ReactNode) => {
+    const tree = <RegistryContext.Provider value={registry}>{ui}</RegistryContext.Provider>;
+    if (screen) await screen.rerender(tree);
+    else screen = environment.use(await render(tree));
+  };
 
+  return {
+    registry,
+    durable,
+    toggles,
+    writes,
+    show,
+    mount: (environmentId = primaryId) =>
+      show(
+        <FavoriteEditor
+          environmentId={environmentId}
+          toggles={toggles}
+          onClicked={(pending) => {
+            pendingClick = pending;
+          }}
+        />,
+      ),
+    click(): Promise<void> {
+      screen!.container.querySelector("button")!.click();
+      return pendingClick!;
+    },
+    [Symbol.asyncDispose]: () => environment.disposeAsync(),
+  };
+}
+
 describe("environment favorite toggles", () => {
   it.each([false, true])(
     "toggles twice before any server echo when initially favorited=%s",
     async (initiallyFavorited) => {
+      await using fixture = await setup();
+      const { registry, durable, writes, mount, click } = fixture;
       const favorites = initiallyFavorited ? [{ provider, model }] : [];
       const settings = { ...initialSettings, favorites };
       registry.set(settingsAtom(primaryId), settings);
@@ -180,6 +192,8 @@
   );
 
   it("keeps the newest pending intent when an earlier write completes", async () => {
+    await using fixture = await setup();
+    const { durable, writes, mount, click } = fixture;
     await mount();
     const first = click();
     const second = click();
@@ -201,6 +215,8 @@
   });
 
   it("clears failed intent so a retry still adds the favorite", async () => {
+    await using fixture = await setup();
+    const { durable, writes, mount, click } = fixture;
     await mount();
     const failed = click();
     await act(async () => {});
@@ -214,6 +230,8 @@
   });
 
   it("toggles the acknowledged choice when the RPC reply precedes its server echo", async () => {
+    await using fixture = await setup();
+    const { registry, durable, writes, mount, click } = fixture;
     await mount();
     const first = click();
     await act(async () => {});
@@ -228,6 +246,8 @@
   });
 
   it("uses a later remote change instead of a cached acknowledged choice", async () => {
+    await using fixture = await setup();
+    const { registry, durable, writes, mount, click } = fixture;
     await mount();
     const first = click();
     await act(async () => {});
@@ -244,6 +264,8 @@
   });
 
   it("preserves an acknowledged add when a later remove fails before its echo", async () => {
+    await using fixture = await setup();
+    const { durable, writes, mount, click } = fixture;
     await mount();
     const first = click();
     await act(async () => {});
@@ -261,6 +283,8 @@
   });
 
   it("does not reuse another environment's pending intent for the same model", async () => {
+    await using fixture = await setup();
+    const { durable, writes, mount, click } = fixture;
     await mount();
     const primary = click();
     await mount(remoteId);
@@ -275,6 +299,8 @@
   });
 
   it("reads the latest server state even when the click callback predates its echo", async () => {
+    await using fixture = await setup();
+    const { registry, durable, toggles, writes, mount } = fixture;
     await mount();
     const previousClick = toggles.at(-1)!;
     const updated = { ...initialSettings, favorites: [{ provider, model }] };
@@ -297,6 +323,8 @@
   it.each(["succeeds", "fails"] as const)(
     "waits for connection and retries only after reconnect when the first save %s",
     async (outcome) => {
+      await using fixture = await setup();
+      const { registry, show } = fixture;
       const environmentId = EnvironmentId.make(`migration-${outcome}`);
       const otherId = EnvironmentId.make(`other-${outcome}`);
       for (const id of [environmentId, otherId]) {
@@ -326,13 +354,7 @@
         .mockReturnValueOnce(firstSave)
         .mockResolvedValue(AsyncResult.success(DEFAULT_SERVER_SETTINGS));
       const renderReader = (id = environmentId) =>
+        show(<ModelPreferencesReader key={id} environmentId={id} />);
-        act(() =>
-          root.render(
-            <RegistryContext.Provider value={registry}>
-              <ModelPreferencesReader key={id} environmentId={id} />
-            </RegistryContext.Provider>,
-          ),
-        );
       const connect = (id: EnvironmentId, generation: number) =>
         act(() =>
           registry.set(
```

## apps/web/src/hooks/useSettings.selector.browser.test.tsx

Status: modified; +26; −30

```diff
@@ -1,6 +1,7 @@
+import { createElement, Profiler, useEffect, type ReactNode } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
+
+import { act, render } from "../../test/render";
-import { act, createElement, Profiler, useEffect, type ReactNode } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
 
 vi.mock("~/localApi", () => ({
   ensureLocalApi: () => ({
@@ -15,11 +16,12 @@
   useClientSettings,
 } from "./useSettings";
 
+interface Counts {
+  readonly renders: { meter: number; grouping: number };
+  readonly grouping: unknown[];
+}
-const renders = { meter: 0, grouping: 0 };
-const seen = { grouping: [] as unknown[] };
-let root: Root;
 
-function counted(key: keyof typeof renders, child: ReactNode) {
+function counted(renders: Counts["renders"], key: keyof typeof renders, child: ReactNode) {
   return createElement(
     Profiler,
     {
@@ -37,43 +39,35 @@
   return createElement("span", null, String(enabled));
 }
 
-function GroupingReader() {
+function GroupingReader({ seen }: { seen: unknown[] }) {
   const grouping = useClientSettings((settings) => ({
     mode: settings.sidebarProjectGroupingMode,
     overrides: settings.sidebarProjectGroupingOverrides,
   }));
   useEffect(() => {
-    seen.grouping.push(grouping);
+    seen.push(grouping);
   });
   return null;
 }
 
-beforeEach(async () => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
+async function renderReaders() {
   await ensureClientSettingsHydrated();
-  renders.meter = 0;
-  renders.grouping = 0;
-  seen.grouping = [];
-  root = createRoot(document.createElement("div"));
-  await act(() =>
-    root.render(
-      createElement(
-        "div",
-        null,
-        counted("meter", createElement(MeterReader)),
-        counted("grouping", createElement(GroupingReader)),
-      ),
+  const counts: Counts = { renders: { meter: 0, grouping: 0 }, grouping: [] };
+  const screen = await render(
+    createElement(
+      "div",
+      null,
+      counted(counts.renders, "meter", createElement(MeterReader)),
+      counted(counts.renders, "grouping", createElement(GroupingReader, { seen: counts.grouping })),
     ),
   );
-});
-
-afterEach(async () => {
+  return { ...counts, [Symbol.asyncDispose]: () => screen[Symbol.asyncDispose]() };
-  await act(() => root.unmount());
-  vi.unstubAllGlobals();
-});
+}
 
 describe("useClientSettings(selector)", () => {
   it("re-renders only readers whose selected value changed", async () => {
+    await using readers = await renderReaders();
+    const { renders } = readers;
     const before = { ...renders };
 
     await act(() =>
@@ -87,11 +81,13 @@
   });
 
   it("keeps a structurally equal object selection", async () => {
+    await using readers = await renderReaders();
+    const { renders } = readers;
     const before = renders.grouping;
 
     await act(() => persistClientSettingsPatch({ sidebarProjectGroupingOverrides: {} }));
 
     expect(renders.grouping).toBe(before);
+    expect(new Set(readers.grouping).size).toBe(1);
-    expect(new Set(seen.grouping).size).toBe(1);
   });
 });
```

## apps/web/src/hooks/useSettings.sync.browser.test.tsx

Status: modified; +37; −45

```diff
@@ -9,10 +9,10 @@
 } from "@t3tools/contracts";
 import * as Cause from "effect/Cause";
 import { AsyncResult, Atom } from "effect/reactivity";
-import { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, render } from "../../test/render";
+
 type SessionResult = AsyncResult.AsyncResult<AuthSessionState, Error>;
 const state = vi.hoisted(() => ({
   sessions: new Map<EnvironmentId, Atom.Writable<SessionResult>>(),
@@ -67,34 +67,28 @@
     sessionCookieName: "t3_session",
   },
 });
-let root: Root;
+function SettingsEditor({ handlers }: { handlers: Array<() => void> }) {
-let container: HTMLDivElement;
-let handlers: Array<() => void> = [];
-
-function SettingsEditor() {
   const updateSettings = useUpdatePrimarySettings();
   const save = () => void updateSettings(patch);
   handlers.push(save);
   return <button onClick={save}>Save shared setting</button>;
-}
-
-function saveSharedSettings() {
-  container.querySelector("button")!.click();
 }
 
 async function mountEditor() {
+  const handlers: Array<() => void> = [];
+  const screen = await render(
+    <RegistryContext.Provider value={appAtomRegistry}>
+      <SettingsEditor handlers={handlers} />
+    </RegistryContext.Provider>,
-  await act(() =>
-    root.render(
-      <RegistryContext.Provider value={appAtomRegistry}>
-        <SettingsEditor />
-      </RegistryContext.Provider>,
-    ),
   );
+  return {
+    handlers,
+    saveSharedSettings: () => screen.container.querySelector("button")!.click(),
+    [Symbol.asyncDispose]: () => screen[Symbol.asyncDispose](),
+  };
 }
 
+function setup() {
-beforeEach(() => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  handlers = [];
   state.sessions.clear();
   state.sessions.set(
     primaryId,
@@ -113,19 +107,11 @@
   state.persist.mockReset();
   state.persist.mockResolvedValue(AsyncResult.success(DEFAULT_SERVER_SETTINGS));
   state.toast.mockReset();
-  container = document.createElement("div");
-  document.body.append(container);
-  root = createRoot(container);
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  container.remove();
-  vi.unstubAllGlobals();
+}
-});
 
 describe("shared settings writes", () => {
   it("waits for the remaining target before reporting a partial save", async () => {
+    setup();
     let finishRemote = () => {};
     const remote = new Promise((resolve) => {
       finishRemote = () => resolve(AsyncResult.success(DEFAULT_SERVER_SETTINGS));
@@ -134,8 +120,8 @@
       AsyncResult.failure(Cause.fail(new Error("Permission denied"))),
     );
     state.persist.mockReturnValueOnce(remote);
+    await using editor = await mountEditor();
+    await act(async () => editor.saveSharedSettings());
-    await mountEditor();
-    await act(async () => saveSharedSettings());
     expect(state.toast).not.toHaveBeenCalled();
     await act(async () => finishRemote());
     expect(state.toast).toHaveBeenCalledExactlyOnceWith({
@@ -146,12 +132,13 @@
   });
 
   it("keeps a single-target failure specific to that environment", async () => {
+    setup();
     appAtomRegistry.set(state.sessions.get(remoteId)!, AsyncResult.success(session([])));
     state.persist.mockResolvedValueOnce(
       AsyncResult.failure(Cause.fail(new Error("Permission denied"))),
     );
+    await using editor = await mountEditor();
+    await act(async () => editor.saveSharedSettings());
-    await mountEditor();
-    await act(async () => saveSharedSettings());
     expect(state.toast).toHaveBeenCalledExactlyOnceWith({
       type: "error",
       title: "Setting not saved",
@@ -160,12 +147,13 @@
   });
 
   it("names the failed environment when a server rejects a save after the grant check", async () => {
+    setup();
     state.persist.mockResolvedValueOnce(AsyncResult.success(DEFAULT_SERVER_SETTINGS));
     state.persist.mockResolvedValueOnce(
       AsyncResult.failure(Cause.fail(new Error("Permission denied"))),
     );
+    await using editor = await mountEditor();
+    await act(async () => editor.saveSharedSettings());
-    await mountEditor();
-    await act(async () => saveSharedSettings());
     expect(state.toast).toHaveBeenCalledExactlyOnceWith({
       type: "error",
       title: "Setting saved on some environments",
@@ -174,8 +162,9 @@
   });
 
   it("dispatches to the primary and connected remote before the remote grant finishes loading", async () => {
+    setup();
+    await using editor = await mountEditor();
+    editor.saveSharedSettings();
-    await mountEditor();
-    saveSharedSettings();
 
     expect(state.persist.mock.calls).toEqual([
       [{ environmentId: primaryId, input: { patch } }],
@@ -192,9 +181,10 @@
       () => AsyncResult.failure<AuthSessionState, Error>(Cause.fail(new Error("session rejected"))),
     ],
   ] as const)("skips a remote whose grant is %s", async (_label, result) => {
+    setup();
     appAtomRegistry.set(state.sessions.get(remoteId)!, result());
+    await using editor = await mountEditor();
+    editor.saveSharedSettings();
-    await mountEditor();
-    saveSharedSettings();
 
     expect(state.persist).toHaveBeenCalledExactlyOnceWith({
       environmentId: primaryId,
@@ -205,11 +195,12 @@
   it.each(["disconnected", "unsupported"] as const)(
     "skips a %s remote even with a cold grant",
     async (condition) => {
+      setup();
       const remote = state.environments[1]!;
       if (condition === "disconnected") remote.connection.phase = "disconnected";
       else remote.serverConfig.environment.capabilities.threadAutoSettlement = false;
+      await using editor = await mountEditor();
+      editor.saveSharedSettings();
-      await mountEditor();
-      saveSharedSettings();
 
       expect(state.persist).toHaveBeenCalledExactlyOnceWith({
         environmentId: primaryId,
@@ -221,13 +212,14 @@
   it.each(["primary", "hosted"] as const)(
     "rechecks a cold remote grant that resolves to denied after the %s handler renders",
     async (mode) => {
+      setup();
       if (mode === "hosted") {
         state.environments = state.environments.filter(
           (environment) => environment.environmentId !== primaryId,
         );
       }
+      await using editor = await mountEditor();
+      const previousUpdate = editor.handlers.at(-1)!;
-      await mountEditor();
-      const previousUpdate = handlers.at(-1)!;
       await act(() => {
         appAtomRegistry.set(state.sessions.get(remoteId)!, AsyncResult.success(session([])));
       });
```

## apps/web/src/hooks/useStableCallback.browser.test.tsx

Status: modified; +6; −20

```diff
@@ -1,32 +1,18 @@
-import { act } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { describe, expect, it } from "vite-plus/test";
 
+import { render } from "../../test/render";
 import { useStableCallback } from "./useStableCallback";
 
+function Probe({ label, handlers }: { label: string; handlers: Array<() => string> }) {
-let root: Root;
-let handlers: Array<() => string> = [];
-
-function Probe({ label }: { label: string }) {
   handlers.push(useStableCallback(() => label));
   return null;
 }
 
-beforeEach(() => {
-  handlers = [];
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  root = createRoot(document.createElement("div"));
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  vi.unstubAllGlobals();
-});
-
 describe("useStableCallback", () => {
   it("keeps one identity and calls the latest committed function", async () => {
-    await act(() => root.render(<Probe label="first" />));
-    await act(() => root.render(<Probe label="second" />));
+    const handlers: Array<() => string> = [];
+    await using screen = await render(<Probe label="first" handlers={handlers} />);
+    await screen.rerender(<Probe label="second" handlers={handlers} />);
 
     expect(new Set(handlers).size).toBe(1);
     expect(handlers[0]!()).toBe("second");
```

## apps/web/src/panelAnimations.browser.test.tsx

Status: modified; +29; −35

```diff
@@ -1,48 +1,42 @@
-import { act, useLayoutEffect } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { useLayoutEffect } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { act, render } from "../test/render";
 import { usePanelNavigationSuppression } from "./panelAnimations";
 
-let root: Root;
-let pendingFrames: FrameRequestCallback[] = [];
-let observed: boolean[] = [];
-
-function SuppressionProbe({ navigationKey }: { navigationKey: string }) {
+function SuppressionProbe({
+  navigationKey,
+  observed,
+}: {
+  navigationKey: string;
+  observed: boolean[];
+}) {
   const suppressed = usePanelNavigationSuppression(navigationKey);
   useLayoutEffect(() => {
     observed.push(suppressed);
-  }, [suppressed]);
+  }, [observed, suppressed]);
   return null;
 }
 
-beforeEach(() => {
-  pendingFrames = [];
-  observed = [];
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  // Frames are stepped by hand so each assertion lands between two paints.
-  vi.spyOn(window, "requestAnimationFrame").mockImplementation((callback) => {
-    pendingFrames.push(callback);
-    return pendingFrames.length;
-  });
-  vi.spyOn(window, "cancelAnimationFrame").mockImplementation(() => undefined);
-  root = createRoot(document.createElement("div"));
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  vi.restoreAllMocks();
-  vi.unstubAllGlobals();
-});
-
-async function paintPendingFrame() {
-  const callback = pendingFrames.shift();
-  await act(() => callback?.(0));
-}
-
 describe("usePanelNavigationSuppression", () => {
   it("suppresses initial and navigated panel state until each route has painted", async () => {
-    await act(() => root.render(<SuppressionProbe navigationKey="/thread/one" />));
+    const pendingFrames: FrameRequestCallback[] = [];
+    const observed: boolean[] = [];
+    // Frames are stepped by hand so each assertion lands between two paints.
+    using _requestFrame = vi
+      .spyOn(window, "requestAnimationFrame")
+      .mockImplementation((callback) => pendingFrames.push(callback));
+    using _cancelFrame = vi
+      .spyOn(window, "cancelAnimationFrame")
+      .mockImplementation(() => undefined);
+    const paintPendingFrame = async () => {
+      const callback = pendingFrames.shift();
+      await act(() => callback?.(0));
+    };
+
+    await using screen = await render(
+      <SuppressionProbe navigationKey="/thread/one" observed={observed} />,
+    );
     expect(observed.at(-1)).toBe(true);
 
     await paintPendingFrame();
@@ -50,7 +44,7 @@
     await paintPendingFrame();
     expect(observed.at(-1)).toBe(false);
 
-    await act(() => root.render(<SuppressionProbe navigationKey="/thread/two" />));
+    await screen.rerender(<SuppressionProbe navigationKey="/thread/two" observed={observed} />);
     expect(observed.at(-1)).toBe(true);
 
     await paintPendingFrame();
```

## apps/web/src/state/queries.threadSearch.browser.test.tsx

Status: modified; +14; −25

```diff
@@ -1,8 +1,7 @@
 import { EnvironmentId } from "@t3tools/contracts";
-import { act } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
 
+import { act, fakeTimers, render } from "../../test/render";
 import { useThreadSearch } from "./queries";
 
 const searchResults = vi.hoisted(() => ({
@@ -24,33 +23,23 @@
 }
 
 describe("useThreadSearch", () => {
-  let root: Root;
-  beforeEach(() => {
-    vi.useFakeTimers();
-    vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-    root = createRoot(document.createElement("div"));
-  });
-  afterEach(() => {
-    act(() => root.unmount());
-    vi.unstubAllGlobals();
-    vi.useRealTimers();
+  it("keeps the settled query and matches stable while typing, then reports the settled query", async () => {
+    using _timers = fakeTimers();
-  });
-
-  it("keeps the settled query and matches stable while typing, then reports the settled query", () => {
     const results: ThreadSearch[] = [];
+    const probe = (query: string) => (
+      <Probe query={query} onResult={(result) => results.push(result)} />
+    );
-    const render = (query: string) => {
+    await using screen = await render(probe(""));
+    const typing: ThreadSearch[] = [];
+    for (const query of ["li", "lin", "link "]) {
+      await screen.rerender(probe(query));
+      typing.push(results.at(-1)!);
+    }
-      const element = <Probe query={query} onResult={(result) => results.push(result)} />;
-      act(() => root.render(element));
-      return results.at(-1)!;
-    };
-
-    render("");
-    const typing = ["li", "lin", "link "].map(render);
     expect(typing.map((result) => result.query)).toEqual(["", "", ""]);
     expect(new Set(typing.map((result) => result.matches)).size).toBe(1);
     expect(typing.every((result) => result.isPending)).toBe(true);
 
-    act(() => {
+    await act(() => {
       vi.advanceTimersByTime(200);
     });
     const settled = results.at(-1)!;
```

## apps/web/src/state/terminalSessionAvailability.browser.test.tsx

Status: modified; +32; −34

```diff
@@ -1,8 +1,8 @@
 import { EnvironmentId, ThreadId, type TerminalSummary } from "@t3tools/contracts";
+import { useEffect } from "react";
+import { expect, it, vi } from "vite-plus/test";
-import { act, useEffect } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, expect, it, vi } from "vite-plus/test";
 
+import { render } from "../../test/render";
 import { useKnownTerminalSessions } from "./terminalSessions";
 
 const state = vi.hoisted(() => ({
@@ -23,8 +23,7 @@
       : { data: state.data, error: state.error }) as never,
 }));
 
-let root: Root;
-let current: ReturnType<typeof useKnownTerminalSessions>;
+type KnownSessions = ReturnType<typeof useKnownTerminalSessions>;
 const target = {
   environmentId: EnvironmentId.make("secondary"),
   threadId: ThreadId.make("thread"),
@@ -43,56 +42,55 @@
   updatedAt: "2026-09-05T00:00:00.000Z",
 };
 
-function Probe() {
+function Probe({ onSessions }: { onSessions: (sessions: KnownSessions) => void }) {
   const sessions = useKnownTerminalSessions(target);
   useEffect(() => {
-    current = sessions;
-  }, [sessions]);
+    onSessions(sessions);
+  }, [onSessions, sessions]);
   return null;
 }
 
+function setup() {
+  Object.assign(state, { canRead: true, data: null, error: null });
-function render() {
-  return act(() => root.render(<Probe />));
+  let current: KnownSessions | undefined;
+  const onSessions = (sessions: KnownSessions) => {
+    current = sessions;
+  };
+  return {
+    get current() {
+      return current;
+    },
+    element: () => <Probe onSessions={onSessions} />,
+  };
 }
 
-beforeEach(() => {
-  state.canRead = true;
-  state.data = null;
-  state.error = null;
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  root = createRoot(document.createElement("div"));
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  vi.unstubAllGlobals();
-});
-
 it("distinguishes missing metadata from an authoritative empty list across scope changes", async () => {
-  await render();
-  expect(current).toBeNull();
+  const probe = setup();
+  await using screen = await render(probe.element());
+  expect(probe.current).toBeNull();
 
   state.data = [terminal];
-  await render();
+  await screen.rerender(probe.element());
+  expect(probe.current?.[0]).toMatchObject({
-  expect(current?.[0]).toMatchObject({
     target: { environmentId: "secondary", terminalId: "term-1" },
     state: { hasRunningSubprocess: true },
   });
 
   state.canRead = false;
-  await render();
-  expect(current).toBeNull();
+  await screen.rerender(probe.element());
+  expect(probe.current).toBeNull();
 
   state.canRead = true;
   state.data = [];
-  await render();
-  expect(current).toEqual([]);
+  await screen.rerender(probe.element());
+  expect(probe.current).toEqual([]);
 });
 
 it("does not reuse cached terminal metadata after a subscription failure", async () => {
+  const probe = setup();
   state.data = [terminal];
-  await render();
+  await using screen = await render(probe.element());
   state.error = "Access denied";
-  await render();
-  expect(current).toBeNull();
+  await screen.rerender(probe.element());
+  expect(probe.current).toBeNull();
 });
```

## apps/web/src/state/usage.browser.test.tsx

Status: modified; +94; −88

```diff
@@ -4,10 +4,10 @@
   USAGE_CONTRACT_VERSION,
   type UsageProviderKind,
 } from "@t3tools/contracts";
-import { act, useLayoutEffect } from "react";
-import { createRoot, type Root } from "react-dom/client";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";
+import { useLayoutEffect } from "react";
+import { describe, expect, it, vi } from "vite-plus/test";
 
+import { render } from "../../test/render";
 import { useUsage, type EnvironmentUsageStatus, type UsageView } from "./usage";
 
 const testState = vi.hoisted(() => ({ environments: [] as EnvironmentUsageStatus[] }));
@@ -85,150 +85,156 @@
   };
 }
 
+interface ProbeProps {
+  selected: ReadonlySet<EnvironmentId> | null;
-let root: Root;
+  hidden?: ReadonlySet<UsageProviderKind>;
+  window?: typeof input;
+}
-let latest: UsageView;
 
 function Probe({
   selected,
   hidden,
   window = input,
-}: {
-  selected: ReadonlySet<EnvironmentId> | null;
+  onUsage,
+}: ProbeProps & { onUsage: (usage: UsageView) => void }) {
-  hidden?: ReadonlySet<UsageProviderKind>;
-  window?: typeof input;
-}) {
   const usage = useUsage(window, selected, hidden);
   useLayoutEffect(() => {
+    onUsage(usage);
-    latest = usage;
-  }, [usage]);
+  }, [onUsage, usage]);
   return null;
 }
 
+async function setup() {
+  testState.environments = [environment("a", 10), environment("b", 20), environment("slow", null)];
+  let latest: UsageView | undefined;
+  const onUsage = (usage: UsageView) => {
+    latest = usage;
+  };
+  const screen = await render(<Probe selected={null} onUsage={onUsage} />);
+  const show = (props: ProbeProps) => screen.rerender(<Probe {...props} onUsage={onUsage} />);
+  return {
+    get latest() {
+      return latest!;
+    },
+    show,
+    select: (...ids: string[]) =>
+      show({ selected: new Set(ids.map((id) => EnvironmentId.make(id))) }),
+    [Symbol.asyncDispose]: () => screen[Symbol.asyncDispose](),
+  };
-async function select(...ids: string[]) {
-  await act(() =>
-    root.render(<Probe selected={new Set(ids.map((id) => EnvironmentId.make(id)))} />),
-  );
 }
 
-beforeEach(async () => {
-  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
-  testState.environments = [environment("a", 10), environment("b", 20), environment("slow", null)];
-  root = createRoot(document.createElement("div"));
-  await act(() => root.render(<Probe selected={null} />));
-});
-
-afterEach(async () => {
-  await act(() => root.unmount());
-  vi.unstubAllGlobals();
-});
-
 describe("usage environment selection", () => {
   it("starts with all environments and adds results as they arrive", async () => {
+    await using screen = await setup();
-    expect(latest.merged.costUsd).toBe(30);
-    expect(latest.isPending).toBe(false);
-    expect(latest.isPartial).toBe(true);
+    expect(screen.latest.merged.costUsd).toBe(30);
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.isPartial).toBe(true);
 
     testState.environments = [...testState.environments.slice(0, 2), environment("slow", 40)];
+    await screen.show({ selected: null });
+    expect(screen.latest.merged.costUsd).toBe(70);
+    expect(screen.latest.isPartial).toBe(false);
-    await act(() => root.render(<Probe selected={null} />));
-    expect(latest.merged.costUsd).toBe(70);
-    expect(latest.isPartial).toBe(false);
   });
 
   it("excludes unselected usage and pending environments, then restores all", async () => {
+    await using screen = await setup();
-    await select("b");
+    await screen.select("b");
+    expect(screen.latest.merged.costUsd).toBe(20);
+    expect(screen.latest.merged.models.map((model) => model.model)).toEqual(["b"]);
-    expect(latest.merged.costUsd).toBe(20);
-    expect(latest.merged.models.map((model) => model.model)).toEqual(["b"]);
-    expect(latest.isPending).toBe(false);
-    expect(latest.isPartial).toBe(false);
-    expect(latest.environments).toHaveLength(3);
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.isPartial).toBe(false);
+    expect(screen.latest.environments).toHaveLength(3);
 
+    await screen.show({ selected: null });
+    expect(screen.latest.merged.costUsd).toBe(30);
+    expect(screen.latest.isPartial).toBe(true);
-    await act(() => root.render(<Probe selected={null} />));
-    expect(latest.merged.costUsd).toBe(30);
-    expect(latest.isPartial).toBe(true);
   });
 
   it("distinguishes a pending selection from an empty or failed selection", async () => {
-    await select("slow");
-    expect(latest.isPending).toBe(true);
+    await using screen = await setup();
+    await screen.select("slow");
-    expect(latest.merged.costUsd).toBe(0);
+    expect(screen.latest.isPending).toBe(true);
+    expect(screen.latest.merged.costUsd).toBe(0);
 
-    await select();
-    expect(latest.selectedEnvironments).toHaveLength(0);
+    await screen.select();
-    expect(latest.isPending).toBe(false);
+    expect(screen.latest.selectedEnvironments).toHaveLength(0);
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.isPartial).toBe(false);
-    expect(latest.isPartial).toBe(false);
 
     testState.environments = [{ ...environment("slow", null), isPending: false, error: "Offline" }];
-    await select("slow");
-    expect(latest.isPending).toBe(false);
+    await screen.select("slow");
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.isPartial).toBe(false);
-    expect(latest.isPartial).toBe(false);
   });
 
   it("deduplicates within the selection so an excluded owner cannot hide usage", async () => {
+    await using screen = await setup();
     testState.environments = [environment("a", 10, "shared"), environment("b", 20, "shared")];
+    await screen.show({ selected: null });
-    await act(() => root.render(<Probe selected={null} />));
-    expect(latest.merged.costUsd).toBe(10);
+    expect(screen.latest.merged.costUsd).toBe(10);
 
-    await select("b");
-    expect(latest.merged.costUsd).toBe(20);
+    await screen.select("b");
+    expect(screen.latest.merged.costUsd).toBe(20);
-    expect(latest.merged.duplicateSources).toEqual([]);
+    expect(screen.latest.merged.duplicateSources).toEqual([]);
   });
 
   it("keeps selected cached results visible during a refresh", async () => {
+    await using screen = await setup();
     testState.environments = [
       { ...environment("a", 10), isPending: true },
       environment("slow", null),
     ];
+    await screen.select("a");
+    expect(screen.latest.merged.costUsd).toBe(10);
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.isPartial).toBe(false);
-    await select("a");
-    expect(latest.merged.costUsd).toBe(10);
-    expect(latest.isPending).toBe(false);
-    expect(latest.isPartial).toBe(false);
   });
 
   it("shows the last answered usage until the next window answers, for the same selection", async () => {
+    await using screen = await setup();
     const selected = new Set([EnvironmentId.make("a")]);
+    await screen.show({ selected });
-    await act(() => root.render(<Probe selected={selected} />));
+    expect(screen.latest.shown?.merged.costUsd).toBe(10);
-    expect(latest.shown?.merged.costUsd).toBe(10);
 
     const nextWindow = { ...input, sinceDay: UsageDay.make("2026-08-28") };
     testState.environments = [environment("a", null)];
+    await screen.show({ selected, window: nextWindow });
+    expect(screen.latest.isPending).toBe(true);
-    await act(() => root.render(<Probe selected={selected} window={nextWindow} />));
-    expect(latest.isPending).toBe(true);
-    expect(latest.shown?.window).toBe(input);
+    expect(screen.latest.shown?.window).toBe(input);
+    expect(screen.latest.shown?.merged.costUsd).toBe(10);
-    expect(latest.shown?.merged.costUsd).toBe(10);
 
     testState.environments = [{ ...environment("a", null), isPending: false, error: "Offline" }];
-    await act(() => root.render(<Probe selected={selected} window={nextWindow} />));
+    await screen.show({ selected, window: nextWindow });
-    expect(latest.isPending).toBe(false);
+    expect(screen.latest.isPending).toBe(false);
+    expect(screen.latest.shown?.window).toBe(input);
+    expect(screen.latest.shown?.merged.costUsd).toBe(10);
-    expect(latest.shown?.window).toBe(input);
-    expect(latest.shown?.merged.costUsd).toBe(10);
 
     testState.environments = [environment("a", 30)];
-    await act(() => root.render(<Probe selected={selected} window={nextWindow} />));
+    await screen.show({ selected, window: nextWindow });
+    expect(screen.latest.shown?.window).toBe(nextWindow);
+    expect(screen.latest.shown?.merged.costUsd).toBe(30);
-    expect(latest.shown?.window).toBe(nextWindow);
-    expect(latest.shown?.merged.costUsd).toBe(30);
 
     testState.environments = [environment("a", null)];
+    await screen.show({ selected, hidden: new Set(["claude"]), window: input });
-    await act(() =>
+    expect(screen.latest.shown).toBeNull();
-      root.render(<Probe selected={selected} hidden={new Set(["claude"])} window={input} />),
-    );
-    expect(latest.shown).toBeNull();
 
     testState.environments = [environment("a", null)];
+    await screen.select("a");
+    expect(screen.latest.shown).toBeNull();
-    await select("a");
-    expect(latest.shown).toBeNull();
   });
 });
 
 describe("usage provider filter", () => {
   it("drops hidden providers from totals and sessions, then restores them", async () => {
+    await using screen = await setup();
     testState.environments = [environment("a", 10), environment("c", 5, "c", "claude")];
-    await act(() => root.render(<Probe selected={null} hidden={new Set(["codex"])} />));
-    expect(latest.merged.costUsd).toBe(5);
-    expect(latest.merged.sessions).toBe(1);
+    await screen.show({ selected: null, hidden: new Set(["codex"]) });
+    expect(screen.latest.merged.costUsd).toBe(5);
-    expect(latest.merged.providers.map((entry) => entry.provider)).toEqual(["claude"]);
+    expect(screen.latest.merged.sessions).toBe(1);
+    expect(screen.latest.merged.providers.map((entry) => entry.provider)).toEqual(["claude"]);
 
+    await screen.show({ selected: null });
+    expect(screen.latest.merged.costUsd).toBe(15);
+    expect(screen.latest.merged.sessions).toBe(2);
-    await act(() => root.render(<Probe selected={null} />));
-    expect(latest.merged.costUsd).toBe(15);
-    expect(latest.merged.sessions).toBe(2);
   });
 });
```
