From 34a12163ae8d80ecc4456f7cbfac5542514a9011 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 2 Sep 2026 00:22:54 +0000 Subject: [PATCH] feat(workbench): migrate the Agent Document panel to @effect/atom-react atoms under a root RegistryProvider (#105 phase 1) Replaces RuntimeDocumentPanel's hand-rolled request union, AbortController, and stale-run guards with an Atom.family keyed by run id consuming the existing strictly-decoded agent-document-client output. Adds the Workbench browser-state convention to docs/effect-conventions.md, a disposal regression test (mount/unmount cycles interrupt in-flight loads and leak no subscriptions), and exact pins effect@4.0.0-rc.112 / @effect/atom-react@4.0.0-rc.112 / scheduler@0.27.0. --- .changeset/workbench-atom-pilot.md | 7 + docs/effect-conventions.md | 44 ++++- packages/workbench/package.json | 3 + packages/workbench/src/main.tsx | 3 +- packages/workbench/src/runtime-inspector.tsx | 51 ++--- packages/workbench/src/runtime-playground.tsx | 8 +- .../src/runtime/agent-document-atoms.ts | 32 +++ .../runtime-document-atoms-disposal.test.ts | 183 ++++++++++++++++++ .../workbench/tests/runtime-inspector.test.ts | 23 ++- pnpm-lock.yaml | 22 +++ rstest.integration-tests.ts | 1 + 11 files changed, 325 insertions(+), 52 deletions(-) create mode 100644 .changeset/workbench-atom-pilot.md create mode 100644 packages/workbench/src/runtime/agent-document-atoms.ts create mode 100644 packages/workbench/tests/runtime-document-atoms-disposal.test.ts diff --git a/.changeset/workbench-atom-pilot.md b/.changeset/workbench-atom-pilot.md new file mode 100644 index 000000000..ed1a66061 --- /dev/null +++ b/.changeset/workbench-atom-pilot.md @@ -0,0 +1,7 @@ +--- +"agent-bundle": patch +--- + +The Workbench Agent Document panel migrated its hand-rolled request state to +`@effect/atom-react` atoms keyed by run id under a root `RegistryProvider`, +keeping the strict zod decoding and imperative client lifecycles unchanged. diff --git a/docs/effect-conventions.md b/docs/effect-conventions.md index 8ad528ffd..76339176a 100644 --- a/docs/effect-conventions.md +++ b/docs/effect-conventions.md @@ -174,6 +174,37 @@ error channel, and crosses back to the public Promise API only through the runtime boundary. It adopts no unstable Effect modules and starts no fibers, timers, or workers between invocations. +## Workbench browser state (#105 phase 1) + +The Workbench (`packages/workbench`) is an internal React client, never a +published API surface; its dist ships inside the `agent-bundle` package as +static assets. + +Exact-pin `effect` + `@effect/atom-react` (synchronized with the repo's effect +pin, currently `4.0.0-rc.112`) are allowed there, but only in dedicated +browser-state modules (the first is `src/runtime/agent-document-atoms.ts`). +Atoms live in `effect/unstable/reactivity`; React bindings come from +`@effect/atom-react`. + +- No `Atom` or `AsyncResult` types in DTOs, public exports, or examples — + atoms consume the strictly-decoded outputs of the existing zod clients and + never replace wire contracts. +- One root `RegistryProvider` mounted in the app shell (`src/main.tsx`); the + module-level default registry is never used. +- Components never call `Effect.run*` — the registry owns effect execution; + components interact through the `@effect/atom-react` hooks only (rslint + `effect-boundary/no-ad-hoc-run` already enforces the run ban). +- Imperative clients (`ProjectClient`, `RuntimeClient`, `AgentDocumentClient`, + …) stay the lifecycle authorities; atoms are read-side caches over their + decoded outputs. +- Known caveat: `4.0.0-rc.112` has a stream-backed derived-atom disposal bug + (fixed upstream post-rc.112, unpublished) — no stream-backed derived atoms + in the Workbench until a re-pin past the fix; the root `RegistryProvider` + (not the default registry) avoids the reported React case. +- Every effect re-pin must bump `@effect/atom-react` to the same RC in the + same chore, re-run the Workbench disposal regression test, and re-measure + the Workbench production bundle (the rsbuild build emits the size table). + ## Banned modules and APIs - `Effect.runPromise` / `runSync` / `runFork` / `runCallback` (and `*With` / @@ -196,7 +227,7 @@ The #99 notice ledger also adopts none: it needs only stable `Effect` and | Module | Adopted in | Re-verify | | --- | --- | --- | -| — | — | — | +| `effect/unstable/reactivity` (+ `@effect/atom-react` bindings) | Workbench Agent Document panel (#105 phase 1) | re-pin bumps @effect/atom-react in lockstep; re-run disposal regression + bundle measurement; stream-backed derived atoms stay banned until the rc.112 disposal fix ships | ## Language service @@ -224,7 +255,10 @@ must not regress it: `pnpm bench:hook-cold-start -- --check`. ## Re-pin chore 1. Bump the exact `effect` version in `packages/rsc-runtime/package.json`. -2. `git subtree pull --prefix=repos/effect https://github.com/Effect-TS/effect.git main --squash`. -3. Re-read `repos/effect/LLMS.md` and refresh `agent-patterns/effect-*.md`. -4. Re-verify every unstable-module row and the language-service diagnostics. -5. Re-run the hook cold-start check. +2. Synchronize `@effect/atom-react` in `packages/workbench/package.json` to + the same RC; re-run the Workbench disposal regression test and production + bundle measurement (rsbuild size table). +3. `git subtree pull --prefix=repos/effect https://github.com/Effect-TS/effect.git main --squash`. +4. Re-read `repos/effect/LLMS.md` and refresh `agent-patterns/effect-*.md`. +5. Re-verify every unstable-module row and the language-service diagnostics. +6. Re-run the hook cold-start check. diff --git a/packages/workbench/package.json b/packages/workbench/package.json index c1da31f05..1b2738b9d 100644 --- a/packages/workbench/package.json +++ b/packages/workbench/package.json @@ -12,13 +12,16 @@ "typecheck": "tsc --project tsconfig.json" }, "dependencies": { + "@effect/atom-react": "4.0.0-rc.112", "@modelcontextprotocol/client": "2.0.0", "@modelcontextprotocol/ext-apps": "1.7.5", "@modelcontextprotocol/sdk": "1.30.0", + "effect": "4.0.0-rc.112", "react": "19.2.8", "react-dom": "19.2.8", "react-markdown": "10.1.0", "remark-gfm": "4.0.1", + "scheduler": "0.27.0", "shiki": "4.4.3", "zod": "4.4.3" }, diff --git a/packages/workbench/src/main.tsx b/packages/workbench/src/main.tsx index 22bb9ffc5..dd814a368 100644 --- a/packages/workbench/src/main.tsx +++ b/packages/workbench/src/main.tsx @@ -1,3 +1,4 @@ +import { RegistryProvider } from '@effect/atom-react'; import { type KeyboardEvent as ReactKeyboardEvent, type MutableRefObject, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; import type { Diagnostic } from '../../agent-bundle/src/contracts/diagnostics.ts'; @@ -1445,4 +1446,4 @@ const Workbench = () => { return
Loading project state…{runtimeError === undefined ? undefined :

Runtime capability issue: {runtimeError}

}
; }; -createRoot(document.getElementById('root')!).render(); +createRoot(document.getElementById('root')!).render(); diff --git a/packages/workbench/src/runtime-inspector.tsx b/packages/workbench/src/runtime-inspector.tsx index 66702ebe2..23f192d64 100644 --- a/packages/workbench/src/runtime-inspector.tsx +++ b/packages/workbench/src/runtime-inspector.tsx @@ -1,8 +1,11 @@ -import React, { useEffect, useRef, useState, type KeyboardEvent } from 'react'; +import { useAtomValue } from '@effect/atom-react'; +import { AsyncResult } from 'effect/unstable/reactivity'; +import React, { useRef, useState, type KeyboardEvent } from 'react'; import type { DevRuntimeDiagnostic, DevRuntimeRun, DevRuntimeStatus, DevRuntimeSurface, DevRuntimeTreeNode } from '../../agent-bundle/src/contracts/runtime.ts'; import { RuntimeEvidence } from './runtime-evidence.tsx'; import type { RuntimeInspectorTab } from './runtime-model.ts'; +import { agentDocumentEventsAtom, useAgentDocumentLoader } from './runtime/agent-document-atoms.ts'; import type { AgentRenderEvent } from './runtime/agent-document-client.ts'; import { AgentDocumentStage } from './runtime/agent-document-stage.tsx'; @@ -55,48 +58,26 @@ const resultDiagnostics = (run: DevRuntimeRun | undefined, status: DevRuntimeSta ...(run?.status === 'failed' ? run.diagnostics : []), ]; -type RuntimeDocumentState = - | Readonly<{ readonly phase: 'idle' }> - | Readonly<{ readonly phase: 'loading'; readonly runId: string }> - | Readonly<{ readonly events: readonly AgentRenderEvent[]; readonly phase: 'ready'; readonly runId: string }> - | Readonly<{ readonly message: string; readonly phase: 'error'; readonly runId: string }>; +const RuntimeDocumentResult = ({ runId }: Readonly<{ readonly runId: string }>): React.ReactNode => { + const result = useAtomValue(agentDocumentEventsAtom(runId)); + return AsyncResult.matchWithWaiting(result, { + onDefect: (error) =>

{error instanceof Error ? error.message : 'Agent Document request could not be completed.'}

, + onError: (message) =>

{message}

, + onSuccess: ({ value: events }) => , + onWaiting: () =>

Loading Agent Document…

, + }); +}; const RuntimeDocumentPanel = ({ loadDocumentEvents, run }: Pick): React.ReactNode => { - const [state, setState] = useState({ phase: 'idle' }); + const loaderReady = useAgentDocumentLoader(loadDocumentEvents); const selected = run?.status === 'succeeded' ? run.result : undefined; - const canLoad = run?.status === 'succeeded' && selected?.flight !== undefined && loadDocumentEvents !== undefined; - - useEffect(() => { - if (!canLoad || run === undefined || loadDocumentEvents === undefined) { - setState({ phase: 'idle' }); - return; - } - const controller = new AbortController(); - setState({ phase: 'loading', runId: run.id }); - void loadDocumentEvents(run.id, controller.signal).then( - (events) => { - if (!controller.signal.aborted) setState({ events, phase: 'ready', runId: run.id }); - }, - (error: unknown) => { - if (!controller.signal.aborted) { - setState({ - message: error instanceof Error ? error.message : 'Agent Document request could not be completed.', - phase: 'error', - runId: run.id, - }); - } - }, - ); - return () => controller.abort(); - }, [canLoad, loadDocumentEvents, run]); if (run === undefined) return

Select a runtime run to inspect its Agent Document.

; if (run.status !== 'succeeded') return

This run did not succeed, so it has no decodable Agent Document.

; if (selected?.flight === undefined) return

This run has no stored Flight payload to decode as an Agent Document.

; if (loadDocumentEvents === undefined) return

Agent Document loading is not available in this Workbench session.

; - if (state.phase === 'idle' || state.runId !== run.id || state.phase === 'loading') return

Loading Agent Document…

; - if (state.phase === 'error') return

{state.message}

; - return ; + if (!loaderReady) return

Loading Agent Document…

; + return ; }; export const RuntimeInspector = ({ loadDocumentEvents, onDownloadFlight, onTabChange, run, status, surface, tab, traceExpansion }: RuntimeInspectorProps): React.ReactNode => { diff --git a/packages/workbench/src/runtime-playground.tsx b/packages/workbench/src/runtime-playground.tsx index c31530a6d..b245828a7 100644 --- a/packages/workbench/src/runtime-playground.tsx +++ b/packages/workbench/src/runtime-playground.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import type { DevRuntimeInvocationRequest, @@ -440,6 +440,10 @@ const resetSeedLabel = (request: DevRuntimeStateResetRequest): string => export const RuntimePlayground = ({ controller, liveMcpPageAdapter = runtimePlaygroundLiveMcpPageAdapter, registerAppPreviewLifecycle, renderAppPreview }: RuntimePlaygroundProps): React.ReactNode => { const [model, setModel] = useState(controller.model); + const loadDocumentEvents = useCallback( + (runId: string, signal?: AbortSignal) => controller.readRunDocument(runId, signal), + [controller], + ); const cancelRef = useRef(null); const confirmRef = useRef(null); const invokingRef = useRef(null); @@ -655,7 +659,7 @@ export const RuntimePlayground = ({ controller, liveMcpPageAdapter = runtimePlay /> {flightDownloadError === undefined ? undefined :

{flightDownloadError}

} controller.readRunDocument(runId, signal)} + loadDocumentEvents={loadDocumentEvents} onDownloadFlight={downloadFlight} onTabChange={(tab) => controller.dispatch({ tab, type: 'selection.tab' })} run={run} diff --git a/packages/workbench/src/runtime/agent-document-atoms.ts b/packages/workbench/src/runtime/agent-document-atoms.ts new file mode 100644 index 000000000..e2abb8768 --- /dev/null +++ b/packages/workbench/src/runtime/agent-document-atoms.ts @@ -0,0 +1,32 @@ +import { useAtom } from '@effect/atom-react'; +import { Effect } from 'effect'; +import { Atom } from 'effect/unstable/reactivity'; +import { useEffect } from 'react'; + +import type { AgentRenderEvent } from './agent-document-client.ts'; + +type AgentDocumentLoader = (runId: string, signal?: AbortSignal) => Promise; + +export const agentDocumentLoaderAtom = Atom.make(undefined); + +export const agentDocumentEventsAtom = Atom.family((runId: string) => Atom.make((get) => { + const loader = get.once(agentDocumentLoaderAtom); + if (loader === undefined) { + return Effect.fail('Agent Document loading is not available in this Workbench session.'); + } + return Effect.tryPromise({ + catch: (error) => error instanceof Error ? error.message : 'Agent Document request could not be completed.', + try: (signal) => loader(runId, signal), + }); +})); + +export const useAgentDocumentLoader = (loader: AgentDocumentLoader | undefined): boolean => { + const [current, setCurrent] = useAtom(agentDocumentLoaderAtom); + useEffect(() => { + setCurrent((latest: AgentDocumentLoader | undefined) => latest === loader ? latest : loader); + return () => { + setCurrent((latest: AgentDocumentLoader | undefined) => latest === loader ? undefined : latest); + }; + }, [loader, setCurrent]); + return loader === undefined || current !== undefined; +}; diff --git a/packages/workbench/tests/runtime-document-atoms-disposal.test.ts b/packages/workbench/tests/runtime-document-atoms-disposal.test.ts new file mode 100644 index 000000000..fad6ffd2b --- /dev/null +++ b/packages/workbench/tests/runtime-document-atoms-disposal.test.ts @@ -0,0 +1,183 @@ +import { createServer } from 'node:http'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { join, normalize, relative } from 'node:path'; + +import { createRsbuild, type RsbuildConfig } from '@rsbuild/core'; +import { chromium } from 'playwright'; +import { describe, expect, it } from '@rstest/core'; + +import { createWorkbenchConfig } from '../rsbuild.config.ts'; + +declare global { + interface Window { + __runtimeDocumentAtoms: { + mount(mode: 'never' | 'resolving'): void; + readonly stats: { + neverAborted: number; + neverCalls: number; + resolvingCalls: number; + unmounts: number; + }; + unmount(): void; + }; + } +} + +const contentType = (path: string): string => path.endsWith('.css') + ? 'text/css' + : path.endsWith('.js') + ? 'text/javascript' + : 'text/html'; + +const startStaticServer = async (root: string) => { + const server = createServer((request, response) => { + const pathname = new URL(request.url ?? '/', 'http://127.0.0.1').pathname; + const file = pathname === '/' ? 'runtime-document-atoms-fixture.html' : pathname.slice(1); + const path = normalize(join(root, file)); + if (relative(root, path).startsWith('..')) { + response.writeHead(404).end(); + return; + } + void readFile(path).then((body) => response.writeHead(200, { 'content-type': contentType(path) }).end(body), () => response.writeHead(404).end()); + }); + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, '127.0.0.1', resolve); + }); + const address = server.address(); + if (address === null || typeof address === 'string') throw new Error('Runtime Document atom fixture did not expose a TCP address.'); + return { server, url: `http://127.0.0.1:${address.port}` }; +}; + +const fixtureSource = (root: string): string => ` + import { RegistryProvider } from '@effect/atom-react'; + import React, { useState } from 'react'; + import { createRoot } from 'react-dom/client'; + import { RuntimeInspector } from ${JSON.stringify(join(root, 'packages/workbench/src/runtime-inspector.tsx'))}; + + const run = { + completedAt: '2026-08-15T12:00:01.000Z', + id: 'run-document-disposal', + input: {}, + result: { + flight: { bytes: 24, preview: 'Flight payload', truncated: false }, + protocol: {}, + state: { identity: { stateStoreId: 'state-document', stateVersion: 1 } }, + trace: [], + tree: [], + }, + startedAt: '2026-08-15T12:00:00.000Z', + status: 'succeeded', + surfaceId: 'tool/document', + target: 'portable', + vector: { + providerSessionId: 'provider', + runtimeGenerationId: 'generation', + sourceRevision: 'source', + stateStoreId: 'state-document', + stateVersion: 1, + }, + } as const; + const agentDocument = { + root: { children: [{ kind: 'markdown', text: '# Disposal document' }], kind: 'result' }, + status: 'success', + version: 1, + } as const; + const stats = { neverAborted: 0, neverCalls: 0, resolvingCalls: 0, unmounts: 0 }; + const neverLoader = (_runId: string, signal?: AbortSignal) => { + stats.neverCalls += 1; + return new Promise(() => { + signal?.addEventListener('abort', () => { stats.neverAborted += 1; }, { once: true }); + }); + }; + const resolvingLoader = async () => { + stats.resolvingCalls += 1; + return [ + { document: agentDocument, sequence: 0, type: 'shell' }, + { document: agentDocument, sequence: 1, type: 'complete' }, + ] as const; + }; + + const Fixture = () => { + const [state, setState] = useState<{ mounted: boolean; mode: 'never' | 'resolving' }>({ + mode: 'never', + mounted: false, + }); + window.__runtimeDocumentAtoms = { + mount: (mode: 'never' | 'resolving') => { setState({ mode, mounted: true }); }, + stats, + unmount: () => { + stats.unmounts += 1; + setState((current) => ({ ...current, mounted: false })); + }, + }; + return state.mounted + ? + :

Inspector unmounted

; + }; + + createRoot(document.getElementById('root')!).render( + , + ); +`; + +describe('Runtime Document atoms', () => { + it('interrupts disposed requests and remains reusable across repeated mounts', async () => { + const root = process.cwd(); + const temp = await mkdtemp(join(root, 'packages/workbench/.runtime-document-atoms-')); + const entry = join(temp, 'runtime-document-atoms-fixture.tsx'); + const output = join(temp, 'dist'); + await writeFile(entry, fixtureSource(root)); + const config: RsbuildConfig = createWorkbenchConfig(); + config.source = { + define: { 'process.env.NODE_ENV': JSON.stringify('production') }, + entry: { 'runtime-document-atoms-fixture': entry }, + }; + config.output = { ...config.output, distPath: { root: output } }; + const rsbuild = await createRsbuild({ config }); + const buildResult = await rsbuild.build(); + await buildResult.close(); + const { server, url } = await startStaticServer(output); + const browser = await chromium.launch({ channel: 'chrome' }); + try { + const page = await browser.newPage(); + const errors: string[] = []; + page.on('pageerror', (error) => errors.push(error.stack ?? error.message)); + await page.goto(url, { timeout: 5_000, waitUntil: 'domcontentloaded' }); + await page.getByText('Inspector unmounted', { exact: true }).waitFor({ timeout: 5_000 }); + + for (let cycle = 1; cycle <= 5; cycle += 1) { + await page.evaluate(() => window.__runtimeDocumentAtoms.mount('never')); + await expect.poll(() => page.evaluate(() => window.__runtimeDocumentAtoms.stats.neverCalls)).toBe(cycle); + await page.evaluate(() => window.__runtimeDocumentAtoms.unmount()); + await expect.poll(() => page.evaluate(() => window.__runtimeDocumentAtoms.stats.neverAborted)).toBe(cycle); + } + const neverStats = await page.evaluate(() => window.__runtimeDocumentAtoms.stats); + expect(neverStats.neverAborted).toBe(neverStats.unmounts); + + for (let cycle = 1; cycle <= 5; cycle += 1) { + await page.evaluate(() => window.__runtimeDocumentAtoms.mount('resolving')); + await page.getByRole('heading', { name: 'Disposal document' }).waitFor({ timeout: 5_000 }); + expect(await page.evaluate(() => window.__runtimeDocumentAtoms.stats.resolvingCalls)).toBeLessThanOrEqual(cycle); + await page.evaluate(() => window.__runtimeDocumentAtoms.unmount()); + await page.getByText('Inspector unmounted', { exact: true }).waitFor({ timeout: 5_000 }); + } + expect(await page.evaluate(() => window.__runtimeDocumentAtoms.stats)).toEqual({ + neverAborted: 5, + neverCalls: 5, + resolvingCalls: 5, + unmounts: 10, + }); + expect(errors).toEqual([]); + } finally { + await browser.close(); + await new Promise((resolve, reject) => server.close((error) => error === undefined ? resolve() : reject(error))); + await rm(temp, { force: true, recursive: true }); + } + }, 60_000); +}); + diff --git a/packages/workbench/tests/runtime-inspector.test.ts b/packages/workbench/tests/runtime-inspector.test.ts index 690ddc5c0..69989377f 100644 --- a/packages/workbench/tests/runtime-inspector.test.ts +++ b/packages/workbench/tests/runtime-inspector.test.ts @@ -35,6 +35,7 @@ const startStaticServer = async (root: string) => { }; const fixtureSource = (root: string): string => ` + import { RegistryProvider } from '@effect/atom-react'; import React from 'react'; import { createRoot } from 'react-dom/client'; import { RuntimeInspector } from ${JSON.stringify(join(root, 'packages/workbench/src/runtime-inspector.tsx'))}; @@ -59,15 +60,19 @@ const fixtureSource = (root: string): string => ` root: { children: [{ kind: 'markdown', text: '# Customer document' }], kind: 'result' }, status: 'success', version: 1, } as const; - createRoot(document.getElementById('root')!).render( [ - { document: agentDocument, sequence: 0, type: 'shell' }, - { completed: 1, message: 'Loaded', sequence: 1, total: 1, type: 'progress' }, - { document: agentDocument, sequence: 2, type: 'complete' }, - ]} - run={run} - surface={surface} - />); + createRoot(document.getElementById('root')!).render( + + [ + { document: agentDocument, sequence: 0, type: 'shell' }, + { completed: 1, message: 'Loaded', sequence: 1, total: 1, type: 'progress' }, + { document: agentDocument, sequence: 2, type: 'complete' }, + ]} + run={run} + surface={surface} + /> + , + ); `; describe('Runtime inspector', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe5d89cd6..d805d3d28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -348,6 +348,9 @@ importers: packages/workbench: dependencies: + '@effect/atom-react': + specifier: 4.0.0-rc.112 + version: 4.0.0-rc.112(effect@4.0.0-rc.112)(react@19.2.8)(scheduler@0.27.0) '@modelcontextprotocol/client': specifier: 2.0.0 version: 2.0.0 @@ -357,6 +360,9 @@ importers: '@modelcontextprotocol/sdk': specifier: 1.30.0 version: 1.30.0(supports-color@7.2.0)(zod@4.4.3) + effect: + specifier: 4.0.0-rc.112 + version: 4.0.0-rc.112 react: specifier: 19.2.8 version: 19.2.8 @@ -369,6 +375,9 @@ importers: remark-gfm: specifier: 4.0.1 version: 4.0.1(supports-color@7.2.0) + scheduler: + specifier: 0.27.0 + version: 0.27.0 shiki: specifier: 4.4.3 version: 4.4.3 @@ -541,6 +550,13 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@effect/atom-react@4.0.0-rc.112': + resolution: {integrity: sha512-Ksf90KQa6D4UDnMj4M84arlVKNIuwxxF2GvgXBzuaYi2LsgririsUhKQ5SYaK1euf8oIYeamEuthoZSbUQmIhw==} + peerDependencies: + effect: ^4.0.0-rc.112 + react: '>=19.0.0 <20.0.0' + scheduler: '>=0.25.0 <0.28.0' + '@effect/language-service@0.87.2': resolution: {integrity: sha512-CfiSoaVQO8pZgaMZGw5VvMvRGybsJ2LaSDoLYaqiVGvo/YYPYVR2GzUKY0h1NtIweq/+SQ5XLrvtzPIttWQ0GQ==} hasBin: true @@ -2884,6 +2900,12 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@effect/atom-react@4.0.0-rc.112(effect@4.0.0-rc.112)(react@19.2.8)(scheduler@0.27.0)': + dependencies: + effect: 4.0.0-rc.112 + react: 19.2.8 + scheduler: 0.27.0 + '@effect/language-service@0.87.2': {} '@effect/tsgo-darwin-arm64@0.39.0': diff --git a/rstest.integration-tests.ts b/rstest.integration-tests.ts index 3314582c8..563f6c86d 100644 --- a/rstest.integration-tests.ts +++ b/rstest.integration-tests.ts @@ -65,6 +65,7 @@ export const integrationTestFiles: readonly string[] = [ 'packages/workbench/tests/playground-real.e2e.test.ts', 'packages/workbench/tests/rsbuild-closure.test.ts', 'packages/workbench/tests/rsbuild-workbench.test.ts', + 'packages/workbench/tests/runtime-document-atoms-disposal.test.ts', 'packages/workbench/tests/runtime-inspector.test.ts', 'packages/workbench/tests/runtime-consent-dialog.test.ts', 'packages/workbench/tests/runtime-playground-capture-cleanup.test.ts',