From 053d2282ebc9f5c0415a29fb8174f127a6a2ec51 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sun, 11 Oct 2026 21:29:08 -0400 Subject: [PATCH 1/5] test(session): Turbopack and the CLI watch an absent system path outside the root Gut-check item B4. The orchestrator watched the system's dependencies, asset sources and configuration inputs outside the root, but not the paths the system load found absent, so creating one did not reload the system until another edit or a restart. Red on 0e9cedac. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../turbopack-watcher-registration.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/extract/tests/session/turbopack-watcher-registration.test.ts b/packages/extract/tests/session/turbopack-watcher-registration.test.ts index 11c92a1f..0a1f8c0e 100644 --- a/packages/extract/tests/session/turbopack-watcher-registration.test.ts +++ b/packages/extract/tests/session/turbopack-watcher-registration.test.ts @@ -366,6 +366,50 @@ describe('startTurbopackWatcher OS registration', () => { } }); + // Contract: a path the system load found absent, outside the root's + // watch, is watched too; creating it reloads the system (spec §12.2 row 7). + test('an absent system path outside the root is watched, and creating it reloads', async () => { + const restoreGlobals = resetAnimusGlobals(); + try { + const root = createProject(); + const systemPath = join(root, 'src', 'system.ts'); + writeFileSync(systemPath, 'export const system = {};\n'); + const shared = makeTempRoot('animus-outside-missing-'); + const absent = join(shared, 'tokens.tsx'); + engine.loadSystemModule.mockReturnValue({ + ...SYSTEM_CONFIG, + dependencies: [systemPath], + missing: [absent], + }); + const session = new ExtractionSession({ system: './src/system.ts' }); + session.rootDir = root; + session.development = true; + const handle = startedHandle( + startTurbopackWatcher(session, root, { + debounceMs: 5, + watchFn: fakeWatch, + holdEvents: true, + }) + ); + await session.runFullPipeline(); + await handle.deliverHeldEvents(); + const watcher = () => watchers.find((w) => w.dir === shared && !w.closed); + expect(watcher()).toBeDefined(); + + const loads = engine.loadSystemModule.mock.calls.length; + writeFileSync(absent, 'export const t = 1;\n'); + watcher()!.listener!('rename', 'tokens.tsx'); + await vi.waitFor(() => + expect(engine.loadSystemModule.mock.calls.length).toBe(loads + 1) + ); + handle.close(); + await handle.settle(); + session.close(); + } finally { + restoreGlobals(); + } + }); + test('a batch that arrives after close() never enters the session', async () => { const root = createProject(); const entries: WatchChanges[] = []; From 485ff93c0adbfabdbd93756035fd47d1fff3c5c0 Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sun, 11 Oct 2026 21:31:18 -0400 Subject: [PATCH 2/5] refactor(session): one watch plan, registered by every host Gut check C ("one watch plan") and B4. Four hosts decided what to watch in four places and disagreed: Vite left out asset sources and config inputs, Next webpack config inputs, unplugin config inputs beyond the root tsconfig, and the Turbopack and CLI orchestrator the paths the system load found absent. The Session's watchPlan(covered) now returns one plan: the files whose change it acts on (the system module and the modules it read, asset sources, configuration inputs), the absent paths whose creation reloads the system (less those a host's recursive watch covers), and the canonical external roots. Hosts keep only registration: Vite's watcher, webpack's dependency kinds, Rollup's addWatchFile (with its analysed files, as Rollup watches only its graph), and the orchestrator's dependency watchers. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../extract/session/extraction-session.ts | 34 +++++++++++ packages/extract/session/index.ts | 1 + .../extract/session/turbopack-orchestrator.ts | 12 ++-- packages/next-plugin/src/plugin.ts | 18 +++--- packages/unplugin/src/core.ts | 28 +++++----- packages/vite-plugin/src/context.ts | 56 +++++++------------ .../tests/watch-registration.test.ts | 9 ++- 7 files changed, 88 insertions(+), 70 deletions(-) diff --git a/packages/extract/session/extraction-session.ts b/packages/extract/session/extraction-session.ts index 60190e13..d1ae5e22 100644 --- a/packages/extract/session/extraction-session.ts +++ b/packages/extract/session/extraction-session.ts @@ -369,6 +369,13 @@ export interface DeliveryIds { systemPropsModuleId: string | typeof SESSION_ARTIFACT; } +/** What a host watches for a Session (see `ExtractionSession.watchPlan`). */ +export interface WatchPlan { + files: string[]; + missing: string[]; + directories: string[]; +} + /** One published generation, as a host serving from memory reads it. */ export interface SessionPublication { manifest: ProjectManifest; @@ -648,6 +655,33 @@ export class ExtractionSession { // files. A failed reload keeps the last set — the stale config still serves. private systemDependencyKeys: Set = new Set(); + /** What a host watches for this Session, one list for every host: the + * files whose change it acts on (the system module and the modules it + * read, asset() sources, the configuration inputs), the paths the system + * load found absent, whose creation reloads it, and the external package + * roots, watched recursively. An absent path inside `covered` (roots the + * host already watches recursively) is left out: a host's recursive + * watch reports its creation, and watching an absent file inside a + * watched directory can stop that directory's events (inotify). */ + watchPlan(covered: readonly string[] = []): WatchPlan { + const system = + this.rootDir === null ? [] : [resolve(this.rootDir, this.options.system)]; + return { + files: [ + ...new Set([ + ...system, + ...this.systemDependencyPaths, + ...this.assetDependencyPaths, + ...this.configInputPaths, + ]), + ], + missing: this.systemMissingPaths.filter( + (path) => !covered.some((root) => isPathWithinRoot(root, path)) + ), + directories: [...this.externalWatchRoots], + }; + } + /** Whether `path` is the system module, one of its dependencies, or a * path its load found absent: a change to it reloads the system. */ isSystemDependency(path: string): boolean { diff --git a/packages/extract/session/index.ts b/packages/extract/session/index.ts index 12b37eb7..61771705 100644 --- a/packages/extract/session/index.ts +++ b/packages/extract/session/index.ts @@ -11,6 +11,7 @@ export type { SessionOutcome, DeliveryIds, SessionPublication, + WatchPlan, WatchChanges, } from './extraction-session'; export { diff --git a/packages/extract/session/turbopack-orchestrator.ts b/packages/extract/session/turbopack-orchestrator.ts index 057f5ccb..a899cfe0 100644 --- a/packages/extract/session/turbopack-orchestrator.ts +++ b/packages/extract/session/turbopack-orchestrator.ts @@ -286,16 +286,14 @@ export function startTurbopackWatcher( ); }; - /** Watches the system, asset and configuration dependencies no root - * watcher reports, by their directories, for those files only. */ + /** Watches the Session's plan (its files, and the absent paths whose + * creation reloads the system) where no root watcher reports them, by + * their directories, for those paths only. */ const syncDependencyWatchers = (): void => { if (closed) return; const wanted = new Map>(); - for (const path of [ - ...session.systemDependencyPaths, - ...session.assetDependencyPaths, - ...session.configInputPaths, - ]) { + const plan = session.watchPlan(); + for (const path of [...plan.files, ...plan.missing]) { if (watchedByRoot(path)) continue; const files = wanted.get(dirname(path)) ?? new Set(); files.add(path); diff --git a/packages/next-plugin/src/plugin.ts b/packages/next-plugin/src/plugin.ts index c2f39088..1b6a1cea 100644 --- a/packages/next-plugin/src/plugin.ts +++ b/packages/next-plugin/src/plugin.ts @@ -369,20 +369,18 @@ export class AnimusWebpackPlugin { }); compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation: Compilation) => { + // The Session's watch plan, as webpack's dependency kinds: a path the + // system load found absent reloads it when created. const registerSystemDependencies = () => { - for (const dep of this.session.systemDependencyPaths) { - if (existsSync(dep)) compilation.fileDependencies.add(dep); - else compilation.missingDependencies.add(dep); + const plan = this.session.watchPlan(); + for (const path of plan.files) { + if (existsSync(path)) compilation.fileDependencies.add(path); + else compilation.missingDependencies.add(path); } - // A path the system load found absent reloads it when created. - for (const path of this.session.systemMissingPaths) { + for (const path of plan.missing) { compilation.missingDependencies.add(path); } - for (const dep of this.session.assetDependencyPaths) { - if (existsSync(dep)) compilation.fileDependencies.add(dep); - else compilation.missingDependencies.add(dep); - } - for (const root of this.session.externalWatchRoots) { + for (const root of plan.directories) { compilation.contextDependencies.add(root); } }; diff --git a/packages/unplugin/src/core.ts b/packages/unplugin/src/core.ts index 81176f26..96ecb7ba 100644 --- a/packages/unplugin/src/core.ts +++ b/packages/unplugin/src/core.ts @@ -655,14 +655,11 @@ export const unpluginFactory: UnpluginFactory< try { await session.runFullPipeline(); } catch (error) { - // A failed load still names what to watch for the retry: the files - // it read and the paths it found absent. - state.watchPaths = [ - resolve(root, options.system), - ...session.systemDependencyPaths, - ...session.systemMissingPaths, - join(root, 'tsconfig.json'), - ]; + // A failed load still names what to watch for the retry: the + // Session's plan holds the files it read and the paths it found + // absent. + const plan = session.watchPlan(); + state.watchPaths = [...plan.files, ...plan.missing]; throw error; } state.sessionDir = getSessionArtifactDir(session.key); @@ -683,13 +680,14 @@ export const unpluginFactory: UnpluginFactory< state.analyzedFiles = [...analyzed.keys()].map((key) => resolve(root, key) ); - const watchPaths = new Set(state.analyzedFiles); - for (const dep of session.systemDependencyPaths) watchPaths.add(dep); - // Absent now; creating one rebuilds against a system that loads it. - for (const path of session.systemMissingPaths) watchPaths.add(path); - for (const dep of session.assetDependencyPaths) watchPaths.add(dep); - watchPaths.add(join(root, 'tsconfig.json')); - state.watchPaths = [...watchPaths]; + // Rollup watches only its module graph: the analysed files join the + // Session's plan (an absent path's creation rebuilds against a system + // that loads it). Its directories need no watch: Rollup's graph holds + // the kit sources an import reaches. + const plan = session.watchPlan(); + state.watchPaths = [ + ...new Set([...state.analyzedFiles, ...plan.files, ...plan.missing]), + ]; state.transformFile = engineApi(session.key).transformFile; }); } diff --git a/packages/vite-plugin/src/context.ts b/packages/vite-plugin/src/context.ts index cfeb52c5..4376f1b4 100644 --- a/packages/vite-plugin/src/context.ts +++ b/packages/vite-plugin/src/context.ts @@ -5,7 +5,6 @@ import { contentHash, DEFAULT_EXTENSIONS, findSheetAssetSpecifiers, - isPathWithinRoot, parseFilesJson, resolveAssetFile, resolveVerbosity, @@ -255,9 +254,8 @@ export class PluginContext { // eslint-disable-next-line @typescript-eslint/no-explicit-any devServer: any; - /** The Session's path lists already registered with Vite's watcher; the - * Session replaces a list when it changes. */ - private readonly watchedPathLists = new WeakSet(); + /** The paths already registered with Vite's watcher. */ + private readonly watchedPaths = new Set(); // Per-plugin-instance engine state: two differently-configured plugins in // one process must not share an engine. @@ -671,46 +669,32 @@ export class PluginContext { return this.session?.isSystemDependency(absFile) ?? false; } - /** Watch every loader-reported dependency path, the external package - * roots, and the paths the load found absent that no watched directory - * covers (creating one reloads the system). Vite hard-ignores - * node_modules, so dependencies installed there produce no events. A list - * already registered is not added again. */ + /** Registers the Session's watch plan with Vite's watcher, each path + * once. Vite also watches its packages' own directories beside the + * plan's canonical roots, and an absent path either covers (or the root + * does) is left to their watch: a watch on an absent file inside a + * watched directory stops that directory's events under inotify + * (chokidar's non-fsevents handler). Vite hard-ignores node_modules, so + * dependencies installed there produce no events. */ registerSystemWatchPaths(): void { const watcher = this.devServer?.watcher; const session = this.session; if (!watcher || !session) return; - for (const paths of [ - session.systemDependencyPaths, - session.systemMissingPaths, - session.externalPackageDirs, - ]) { - if (paths.length === 0 || this.watchedPathLists.has(paths)) continue; - this.watchedPathLists.add(paths); - const added = - paths === session.systemMissingPaths - ? this.uncoveredMissingPaths(paths, session.externalPackageDirs) - : paths; + const directories = [ + ...new Set([ + ...session.watchPlan().directories, + ...session.externalPackageDirs, + ]), + ]; + const covered = this.rootDir ? [this.rootDir, ...directories] : directories; + const plan = session.watchPlan(covered); + for (const paths of [plan.files, plan.missing, directories]) { + const added = paths.filter((path) => !this.watchedPaths.has(path)); + for (const path of added) this.watchedPaths.add(path); if (added.length > 0) watcher.add(added); } } - /** The absent paths no watched directory reports a creation for. Vite's - * root watch and a package root's watch already do, and a watch on an - * absent file inside a watched directory stops that directory's events - * under inotify (chokidar's non-fsevents handler). */ - private uncoveredMissingPaths( - paths: readonly string[], - packageDirs: readonly string[] - ): string[] { - const watchedDirs = this.rootDir - ? [this.rootDir, ...packageDirs] - : packageDirs; - return paths.filter( - (path) => !watchedDirs.some((dir) => isPathWithinRoot(dir, path)) - ); - } - /** Invalidate the component CSS and system-props modules, then reload. The * reload alone rescues nothing: Vite keeps serving its cached transform. * `devServer.moduleGraph` is the mixed back-compat graph on purpose — it diff --git a/packages/vite-plugin/tests/watch-registration.test.ts b/packages/vite-plugin/tests/watch-registration.test.ts index 31ec0187..d165de43 100644 --- a/packages/vite-plugin/tests/watch-registration.test.ts +++ b/packages/vite-plugin/tests/watch-registration.test.ts @@ -16,17 +16,22 @@ describe('registerSystemWatchPaths', () => { const ctx = new PluginContext({ system: './src/ds.ts' }); const add = vi.fn(); ctx.devServer = { watcher: { add } }; - // Registration reads only these three path lists of the Session. + // Registration reads the Session's watch plan, over these lists. ctx.session = Object.assign(Object.create(ExtractionSession.prototype), { + rootDir: null, + options: { system: './src/ds.ts' }, systemDependencyPaths: [], systemMissingPaths: [], + assetDependencyPaths: new Set(), + configInputPaths: [], + externalWatchRoots: [], externalPackageDirs: [], ...paths, }); return { ctx, add }; } - it('registers loader-reported system dependency paths, once per list', () => { + it('registers loader-reported system dependency paths, once each', () => { const { ctx, add } = contextWithWatcher({ systemDependencyPaths: ['/ws/tokens/src/theme.ts'], }); From 16365c67c3820c1ccc816ed491ffc1a21e3ea5ae Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sun, 11 Oct 2026 21:50:28 -0400 Subject: [PATCH 3/5] test(vite-plugin): a replacement dev server's watcher gets every path again spike-codex's #417 P1: one plugin instance reused with a replacement dev server kept its registration memo, so outside-root paths (an external palette module) were never added to the new server's watcher and their edits left stale CSS. Red on 485ff93c. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../tests/watch-registration.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/vite-plugin/tests/watch-registration.test.ts b/packages/vite-plugin/tests/watch-registration.test.ts index d165de43..cfd27f05 100644 --- a/packages/vite-plugin/tests/watch-registration.test.ts +++ b/packages/vite-plugin/tests/watch-registration.test.ts @@ -70,6 +70,25 @@ describe('registerSystemWatchPaths', () => { expect(add).toHaveBeenCalledWith(['/ws/ui-kit/src']); }); + // Contract: a reused plugin instance whose dev server is replaced + // registers every path with the new server's watcher; the old watcher's + // registrations do not carry over. + it("registers again with a replacement server's watcher", () => { + const { ctx, add } = contextWithWatcher({ + systemDependencyPaths: ['/ws/tokens/src/palette.ts'], + externalPackageDirs: ['/ws/ui-kit/src'], + }); + ctx.registerSystemWatchPaths(); + const addAgain = vi.fn(); + ctx.devServer = { watcher: { add: addAgain } }; + ctx.registerSystemWatchPaths(); + expect(add.mock.calls).toEqual([ + [['/ws/tokens/src/palette.ts']], + [['/ws/ui-kit/src']], + ]); + expect(addAgain.mock.calls).toEqual(add.mock.calls); + }); + it('no-ops without a dev server', () => { const { ctx } = contextWithWatcher({ externalPackageDirs: ['/ws/ui-kit/src'], From b178dcb2f63ac334f4d12963783d065a3cf766fb Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sun, 11 Oct 2026 21:50:46 -0400 Subject: [PATCH 4/5] fix(vite-plugin): register the watch plan once per watcher, not per context spike-codex's #417 P1 (silent stale CSS). The registration memo lived as long as the plugin context, so a reused plugin instance whose dev server was replaced skipped every path it had given the old watcher, and the new server never watched an outside-root system module. The memo is now keyed by watcher. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/vite-plugin/src/context.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/vite-plugin/src/context.ts b/packages/vite-plugin/src/context.ts index 4376f1b4..cb404628 100644 --- a/packages/vite-plugin/src/context.ts +++ b/packages/vite-plugin/src/context.ts @@ -254,8 +254,9 @@ export class PluginContext { // eslint-disable-next-line @typescript-eslint/no-explicit-any devServer: any; - /** The paths already registered with Vite's watcher. */ - private readonly watchedPaths = new Set(); + /** The paths already registered, per watcher: a reused plugin instance + * whose dev server is replaced registers everything with the new one. */ + private readonly watchedPaths = new WeakMap>(); // Per-plugin-instance engine state: two differently-configured plugins in // one process must not share an engine. @@ -670,7 +671,7 @@ export class PluginContext { } /** Registers the Session's watch plan with Vite's watcher, each path - * once. Vite also watches its packages' own directories beside the + * once per watcher. Vite also watches its packages' own directories beside the * plan's canonical roots, and an absent path either covers (or the root * does) is left to their watch: a watch on an absent file inside a * watched directory stops that directory's events under inotify @@ -688,9 +689,14 @@ export class PluginContext { ]; const covered = this.rootDir ? [this.rootDir, ...directories] : directories; const plan = session.watchPlan(covered); + let registered = this.watchedPaths.get(watcher); + if (registered === undefined) { + registered = new Set(); + this.watchedPaths.set(watcher, registered); + } for (const paths of [plan.files, plan.missing, directories]) { - const added = paths.filter((path) => !this.watchedPaths.has(path)); - for (const path of added) this.watchedPaths.add(path); + const added = paths.filter((path) => !registered.has(path)); + for (const path of added) registered.add(path); if (added.length > 0) watcher.add(added); } } From b4cabf8f18179d4130a4f9b1f1d1b9663a377fed Mon Sep 17 00:00:00 2001 From: codecaaron Date: Sun, 11 Oct 2026 21:54:51 -0400 Subject: [PATCH 5/5] test(vite-plugin): a replacement dev server reloads on an outside-root edit spike-codex's retained witness for the #417 P1, on a real Vite server: one plugin object, a first server closed, a second server started; editing a system module outside the root (a palette the theme imports) must reach the second server's served CSS. Red against 485ff93c's context (the edit never arrives); green with b178dcb2. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../tests/dev-server/replaced-server.test.ts | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 packages/vite-plugin/tests/dev-server/replaced-server.test.ts diff --git a/packages/vite-plugin/tests/dev-server/replaced-server.test.ts b/packages/vite-plugin/tests/dev-server/replaced-server.test.ts new file mode 100644 index 00000000..8386a2a7 --- /dev/null +++ b/packages/vite-plugin/tests/dev-server/replaced-server.test.ts @@ -0,0 +1,108 @@ +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'; +import { createServer as createNetServer } from 'net'; +import { tmpdir } from 'os'; +import { join, relative } from 'path'; +import { createServer } from 'vite-plus'; +import { afterEach, expect, test } from 'vitest'; + +import { animusExtract } from '../../src/index'; +import { createDevFixture, INITIAL_BRAND_HEX } from './fixture'; +import { until } from './scenario'; + +import type { AddressInfo } from 'net'; +import type { ViteDevServer } from 'vite-plus'; + +/** A free loopback port: each server's HMR socket needs its own. */ +function reserveHmrPort(): Promise { + return new Promise((resolve, reject) => { + const probe = createNetServer(); + probe.on('error', reject); + probe.listen(0, '127.0.0.1', () => { + // SAFETY: a listening net.Server bound to a numeric port and IP host + // reports a TCP AddressInfo, never a Unix-socket string. + const { port } = probe.address() as AddressInfo; + probe.close(() => resolve(port)); + }); + }); +} + +/** What each of the two servers is started with. */ +interface ReplacedServerConfig { + root: string; + configFile: false; + logLevel: 'silent'; + optimizeDeps: { noDiscovery: true; include: never[] }; + server: { middlewareMode: true; hmr: { port: number } }; + plugins: ReturnType[]; +} + +/** The plugin's resolved stylesheet id; `\0` is Vite's virtual marker. */ +const STYLES_ID = '\0virtual:animus/styles.css'; + +const cleanups: Array<() => Promise | void> = []; +afterEach(async () => { + for (const cleanup of cleanups.splice(0).reverse()) await cleanup(); +}); + +// Contract: a plugin instance reused with a replacement dev server watches +// the system's outside-root modules on the new server too, so their edits +// still reach the served CSS. +test("a replacement dev server watches the system's outside-root modules", async () => { + const fixture = createDevFixture(); + cleanups.push(() => fixture.dispose()); + const outside = mkdtempSync(join(tmpdir(), 'animus-outside-palette-')); + cleanups.push(() => rmSync(outside, { recursive: true, force: true })); + const palette = join(outside, 'palette.ts'); + writeFileSync(palette, `export const brand = '${INITIAL_BRAND_HEX}';\n`); + const themePath = join(fixture.root, 'src/theme.ts'); + const specifier = relative(join(fixture.root, 'src'), palette).replace( + /\.ts$/, + '' + ); + fixture.write( + 'src/theme.ts', + `import { brand } from '${specifier}';\n` + + readFileSync(themePath, 'utf8').replace(`'${INITIAL_BRAND_HEX}'`, 'brand') + ); + + // One plugin object, as a host that restarts its server keeps it. + const plugin = animusExtract({ system: './src/ds.ts' }); + const start = async (): Promise => { + // SAFETY: vite-plus runs Vite's server and consumes the same plugin hooks; + // only the nominal declarations differ. + const startServer = createServer as ( + config: ReplacedServerConfig + ) => Promise; + const server = await startServer({ + root: fixture.root, + configFile: false, + logLevel: 'silent', + optimizeDeps: { noDiscovery: true, include: [] }, + server: { middlewareMode: true, hmr: { port: await reserveHmrPort() } }, + plugins: [plugin], + }); + for (const url of ['/src/main.ts', '/src/Button.ts']) { + await server.environments.client.transformRequest(url); + } + return server; + }; + const staticCss = async (server: ViteDevServer): Promise => { + // As the adapter reads it: the graph node first, as import analysis + // registers it, then the module. + const environment = server.environments.client; + await environment.moduleGraph.ensureEntryFromUrl(STYLES_ID, false); + const result = await environment.transformRequest(STYLES_ID); + return result?.code ?? ''; + }; + + const first = await start(); + await first.close(); + const second = await start(); + cleanups.push(() => second.close()); + expect(await staticCss(second)).toContain(INITIAL_BRAND_HEX); + + writeFileSync(palette, `export const brand = '#ff0000';\n`); + await until(async () => (await staticCss(second)).includes('#ff0000'), { + what: 'the outside-root palette edit in the served CSS', + }); +}, 30_000);