@dxos/ui-theme 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ThemePlugin.cjs","names":[],"sources":["../../src/plugins/ThemePlugin.ts"],"sourcesContent":["//\n// Copyright 2022 DXOS.org\n//\n\n/* eslint-disable no-console */\n\nimport tailwindcssPostcss from '@tailwindcss/postcss';\nimport tailwindcssVite from '@tailwindcss/vite';\nimport autoprefixer from 'autoprefixer';\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport postcssImport from 'postcss-import';\nimport postcssNesting from 'postcss-nesting';\nimport { type HtmlTagDescriptor, type Plugin, type UserConfig } from 'vite';\n\n/**\n * CSS cascade layer order.\n * Must be established before any stylesheets load so that Tailwind's own @layer declarations don't override our ordering. Exported so consuming\n */\nexport const LAYER_ORDER = [\n 'properties',\n 'theme',\n 'dx-tokens',\n 'user-tokens',\n 'base',\n 'tw-base',\n 'dx-base',\n 'components',\n 'tw-components',\n 'dx-components',\n 'utilities',\n] as const;\n\n// Package root relative to this module's built location, `dist/plugin/ThemePlugin.{mjs,cjs}`.\n// Tied to the output depth: the two-pass vite build in `vite.plugin.config.ts` emits flat into\n// `dist/plugin`, where the retired pipeline nested a platform slug and a mirrored source tree.\nconst ROOT = '../../';\n\n/**\n * Disable `@tailwindcss/vite`'s `hotUpdate` hook under Vite's full-bundle dev mode.\n *\n * `vite dev --experimentalBundle` routes `hotUpdate` through Rolldown's plugin bridge, whose\n * options object carries no `server` and whose context carries no `environment` — so the hook\n * throws on its first dereference and takes the whole rebuild with it (`TypeError: Cannot read\n * properties of undefined (reading 'environments')`, then `(reading 'name')` once `server` is\n * supplied). Its job is to invalidate the generated CSS in Vite's module graph when a scanned\n * source file changes, and that graph is not what serves CSS under bundled dev, so there is\n * nothing to salvage by filling the gaps in: skipping it keeps HMR working for everything else,\n * at the cost of needing a server restart before a NEWLY USED utility class is generated.\n */\nconst skipHotUpdateInBundledDev = (plugin: Plugin): Plugin => {\n const hotUpdate = plugin.hotUpdate;\n const handler = typeof hotUpdate === 'function' ? hotUpdate : hotUpdate?.handler;\n if (!handler) {\n return plugin;\n }\n\n // Chained rather than assigned: only `@tailwindcss/vite:generate:serve` declares `hotUpdate` and\n // it declares no `configResolved` today, but this package publishes against a caret range where\n // a later minor could add one, and a clobbered hook would fail silently.\n const configResolved = plugin.configResolved;\n const inheritedConfigResolved = typeof configResolved === 'function' ? configResolved : configResolved?.handler;\n\n let bundledDev = false;\n return {\n ...plugin,\n configResolved(config) {\n bundledDev = config.experimental.bundledDev === true;\n return inheritedConfigResolved?.call(this, config);\n },\n hotUpdate: {\n ...(typeof hotUpdate === 'object' ? hotUpdate : {}),\n handler(context) {\n if (bundledDev) {\n return;\n }\n return handler.call(this, context);\n },\n },\n };\n};\n\nexport type ThemePluginOptions = {\n srcCssPath?: string;\n /** Extra Tailwind scan globs (absolute, or relative to the Vite root) for a consumer of the published package. */\n content?: string[];\n virtualFileId?: string;\n verbose?: boolean;\n};\n\nconst toCssString = (path: string): string => JSON.stringify(path.split(sep).join('/'));\n\n/**\n * Writes the theme entry for a consumer of the published package and returns its path.\n * `workspace.css` cannot serve them: its scan paths are relative to this package, which from inside\n * `node_modules` reaches every installed package, and Tailwind walks all of it before answering.\n */\nconst writeConsumerTheme = ({\n packageRoot,\n root,\n outPath,\n content,\n}: {\n packageRoot: string;\n root: string;\n outPath: string;\n content: string[];\n}): string => {\n // The scope this package was installed into, and the consumer's own, which differ under pnpm's isolated layout.\n const scopeDirs = new Set([dirname(packageRoot), resolve(root, 'node_modules/@dxos')]);\n const sources = [\n // Tailwind skips `node_modules` below an explicit base, so this covers only the consumer's own files.\n join(root, '**/*.{ts,tsx,js,jsx,html}'),\n ...[...scopeDirs].map((scopeDir) => join(scopeDir, '*/dist/lib/**/*.mjs')),\n ...content.map((glob) => resolve(root, glob)),\n ];\n const css = [\n `@import ${toCssString(join(packageRoot, 'src/main.css'))};`,\n ...sources.map((source) => `@source ${toCssString(source)};`),\n '',\n ].join('\\n');\n\n // Rewriting identical content would bump the mtime, which Tailwind reads as a reason to rebuild.\n if (!existsSync(outPath) || readFileSync(outPath, 'utf-8') !== css) {\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, css);\n }\n\n return outPath;\n};\n\n/**\n * Vite plugin to configure theme.\n * Returns the official Tailwind Vite plugin (persistent incremental scanner) alongside the theme plugin.\n */\nexport const ThemePlugin = (options: ThemePluginOptions): Plugin[] => {\n // `src` is published too, so only the location tells a workspace checkout from an installed copy.\n const packageRoot = realpathSync(resolve(import.meta.dirname, ROOT));\n const isMonorepo = !packageRoot.split(sep).includes('node_modules');\n\n // Static assets shipped via \"files\": [\"src\"] in package.json.\n // Both monorepo and installed package resolve to the same src/plugins/ directory.\n const pluginsDir = resolve(import.meta.dirname, ROOT, 'src/plugins');\n const darkModeScriptPath = resolve(pluginsDir, 'dark-mode.ts');\n const mainCssPath = resolve(pluginsDir, 'main.css');\n\n const config = {\n // Installed copies get theirs from `configResolved`, which knows the consumer's root.\n srcCssPath: options.srcCssPath ?? (isMonorepo ? resolve(packageRoot, 'src/workspace.css') : ''),\n virtualFileId: options.virtualFileId ?? '@dxos-theme',\n verbose: options.verbose,\n };\n\n // Set under `vite dev --experimentalBundle`; see the guard in `hotUpdate`.\n let bundledDev = false;\n\n // Trailing-edge debounce handle for theme CSS reloads (see `hotUpdate`).\n let themeReloadTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Under Vitest there is no HMR, and a live watcher leaks per-file `fs_event` handles: Tailwind's\n // `@source` scan registers every scanned source file as a Vite watch dependency, and those handles\n // are never released on close, hanging single-pass `vitest run` teardown. A non-null `server.watch`\n // here also overrides the `watch: null` set by the test configs, so gate it: disable the watcher\n // entirely under Vitest (`VITEST` is exported to the whole process tree), keep the HMR-ignore\n // patterns for interactive `storybook dev` / `vite dev` (where `VITEST` is unset).\n const isVitest = process.env.VITEST === 'true';\n\n const themePlugin: Plugin = {\n name: 'vite-plugin-dxos-ui-theme',\n configResolved: (resolved) => {\n bundledDev = resolved.experimental.bundledDev === true;\n if (!config.srcCssPath) {\n config.srcCssPath = writeConsumerTheme({\n packageRoot,\n root: resolved.root,\n outPath: resolve(resolved.cacheDir, '..', '.dxos-ui-theme.css'),\n content: options.content ?? [],\n });\n }\n\n if (process.env.DEBUG || options.verbose) {\n console.log('ThemePlugin:\\n', JSON.stringify(config, null, 2));\n }\n },\n config: (): UserConfig => {\n return {\n server: {\n watch: isVitest\n ? null\n : {\n // Stop build outputs from driving HMR — they are the root of the\n // `main.css` HMR storm.\n //\n // Tailwind's `@source` scanning (see `src/main.css`) registers its\n // scanned source files as Vite watch dependencies of the compiled\n // theme CSS. Tailwind's own scanner respects `.gitignore` (and the\n // `@source not` directives), so it never *scans* `dist/`. BUT the\n // scanner hands Vite a coarse `dir-dependency` glob — e.g.\n // `{**/*.html,**/*.ts,**/*.tsx}` — and Vite re-expands that glob\n // itself, ignoring only `node_modules` (not `.gitignore`, not the\n // `@source not` negations). The re-expansion therefore sweeps in\n // every `packages/*/dist/**/*.d.ts` (`.d.ts` matches `**/*.ts`),\n // making each emitted declaration file a watch-dependency of\n // `main.css`. A single package rebuild emits dozens of `.d.ts` in a\n // tight burst, and each write re-invalidates the theme — 40+ HMR\n // pings for `main.css` in one second, repeating on every rebuild.\n //\n // Ignoring build outputs in the watcher is also semantically\n // correct: in dev the workspace resolves `@dxos/*` via the `source`\n // export condition (see `vite-plugin-import-source`), so `dist/`\n // is never consumed at runtime and its churn should never trigger\n // HMR. Vite concatenates these patterns with its built-in ignores\n // (`**/node_modules/**`, `**/.git/**`, …), so this is purely\n // additive.\n //\n // `<root>/.claude/**` covers agent worktrees checked out under the\n // repo root (`.claude/worktrees/<name>/packages/**`): they are full\n // source copies, so the glob re-expansion above sweeps them in and\n // every agent-side edit burst or checkout invalidates the theme in\n // the user's dev server. The pattern is anchored at the resolved\n // repo root (not `**/.claude/**`) because chokidar matches against\n // absolute paths — a bare pattern would match *everything* when the\n // dev server itself runs from inside a worktree whose path contains\n // a `.claude` segment. `*.log` covers runtime log sinks (e.g.\n // vite-plugin-log's `app.log` in the app root), which are appended\n // continuously at runtime and must never feed back into the\n // watcher.\n ignored: ['**/dist/**', '**/out/**', '**/*.log', `${resolve(import.meta.dirname, ROOT, '.claude')}/**`],\n },\n },\n css: {\n postcss: {\n plugins: [\n // Handles @import statements in CSS.\n postcssImport(),\n // Processes CSS nesting syntax.\n postcssNesting(),\n // Resolves @reference/@apply in `.pcss` files (e.g. lit-grid, lit-ui), which the\n // @tailwindcss/vite plugin skips — its transform filter only matches `.css`.\n // Theme `.css` files are compiled by @tailwindcss/vite first (enforce: 'pre'),\n // so this plugin's quick-bail check passes them through untouched.\n tailwindcssPostcss(),\n // Adds vendor prefixes.\n autoprefixer,\n ],\n },\n },\n };\n },\n resolveId: (id) => {\n if (id === config.virtualFileId) {\n return config.srcCssPath;\n }\n },\n hotUpdate({ type, file, modules }) {\n // Everything below is an optimization of Vite's per-module dev graph, which full-bundle dev\n // mode does not have — and Rolldown's plugin bridge passes no `environment` there, so the\n // first dereference would throw and fail the rebuild instead of updating the page.\n if (bundledDev) {\n return;\n }\n\n // Direct edits to CSS (the theme source or its imports) keep Vite's\n // default immediate update for instant feedback while authoring styles.\n if (this.environment.name !== 'client' || type !== 'update' || file.endsWith('.css')) {\n return;\n }\n\n // Every content file Tailwind scans is registered as a dependency of the\n // theme CSS — Vite models it as a file-only entry node whose importer is\n // `main.css` — so each source-file save invalidates `main.css` and\n // re-runs the monorepo-wide Tailwind scan. During an edit wave that\n // serializes one full scan per save. Drop the theme-dep entries from the\n // update (the changed module itself still hot-updates immediately) and\n // reload the theme CSS once on the trailing edge of a quiet window, so a\n // wave costs at most one scan.\n const isThemeDep = (mod: (typeof modules)[number]): boolean =>\n mod.file === config.srcCssPath ||\n (mod.id === null &&\n mod.importers.size > 0 &&\n [...mod.importers].every((importer) => importer.file === config.srcCssPath));\n if (!modules.some(isThemeDep)) {\n return;\n }\n\n const environment = this.environment;\n clearTimeout(themeReloadTimer);\n themeReloadTimer = setTimeout(() => {\n for (const mod of environment.moduleGraph.getModulesByFile(config.srcCssPath) ?? []) {\n environment.reloadModule(mod).catch(() => {\n // Server may be mid-restart; the next edit reschedules the reload.\n });\n }\n }, 300);\n\n return modules.filter((mod) => !isThemeDep(mod));\n },\n transformIndexHtml: () => {\n // Apply .dark class to <html> synchronously before any scripts run, so that\n // the critical CSS html.dark rules apply on the very first paint.\n const darkModeTag: HtmlTagDescriptor = {\n tag: 'script',\n attrs: { 'data-dxos-theme': '' },\n injectTo: 'head-prepend',\n children: readFileSync(darkModeScriptPath, 'utf-8'),\n };\n\n // Establish cascade layer order before any stylesheet loads.\n const layersTag: HtmlTagDescriptor = {\n tag: 'style',\n attrs: { 'data-dxos-layers': '' },\n children: `@layer ${LAYER_ORDER.join(', ')};`,\n injectTo: 'head-prepend',\n };\n\n // Critical styles: font sizing, overscroll, color fallbacks.\n // Loaded from critical.css to keep styles maintainable and out of index.html.\n const criticalTag: HtmlTagDescriptor = {\n tag: 'style',\n attrs: { 'data-dxos-critical': '' },\n injectTo: 'head-prepend',\n children: readFileSync(mainCssPath, 'utf-8'),\n };\n\n return [darkModeTag, layersTag, criticalTag];\n },\n };\n\n // The Tailwind Vite plugins are `enforce: 'pre'`, so they compile theme CSS (resolving\n // `@import 'tailwindcss'`, @source, @plugin, @theme) before the postcss chain runs —\n // postcss-import never sees the raw Tailwind directives. Scan roots come from the @source\n // directives in main.css (relative to that file); no project-root base is needed.\n return [...tailwindcssVite().map(skipHotUpdateInBundledDev), themePlugin];\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ThemePlugin.cjs","names":["sep","dirname","resolve","join","existsSync","readFileSync","realpathSync","postcssImport","postcssNesting","tailwindcssPostcss","tailwindcssVite"],"sources":["../../src/plugins/ThemePlugin.ts"],"sourcesContent":["//\n// Copyright 2022 DXOS.org\n//\n\n/* eslint-disable no-console */\n\nimport tailwindcssPostcss from '@tailwindcss/postcss';\nimport tailwindcssVite from '@tailwindcss/vite';\nimport autoprefixer from 'autoprefixer';\nimport { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';\nimport { dirname, join, resolve, sep } from 'node:path';\nimport postcssImport from 'postcss-import';\nimport postcssNesting from 'postcss-nesting';\nimport { type HtmlTagDescriptor, type Plugin, type UserConfig } from 'vite';\n\n/**\n * CSS cascade layer order.\n * Must be established before any stylesheets load so that Tailwind's own @layer declarations don't override our ordering. Exported so consuming\n */\nexport const LAYER_ORDER = [\n 'properties',\n 'theme',\n 'dx-tokens',\n 'user-tokens',\n 'base',\n 'tw-base',\n 'dx-base',\n 'components',\n 'tw-components',\n 'dx-components',\n 'utilities',\n] as const;\n\n// Package root relative to this module's built location, `dist/plugin/ThemePlugin.{mjs,cjs}`.\n// Tied to the output depth: the two-pass vite build in `vite.plugin.config.ts` emits flat into\n// `dist/plugin`, where the retired pipeline nested a platform slug and a mirrored source tree.\nconst ROOT = '../../';\n\n/**\n * Disable `@tailwindcss/vite`'s `hotUpdate` hook under Vite's full-bundle dev mode.\n *\n * `vite dev --experimentalBundle` routes `hotUpdate` through Rolldown's plugin bridge, whose\n * options object carries no `server` and whose context carries no `environment` — so the hook\n * throws on its first dereference and takes the whole rebuild with it (`TypeError: Cannot read\n * properties of undefined (reading 'environments')`, then `(reading 'name')` once `server` is\n * supplied). Its job is to invalidate the generated CSS in Vite's module graph when a scanned\n * source file changes, and that graph is not what serves CSS under bundled dev, so there is\n * nothing to salvage by filling the gaps in: skipping it keeps HMR working for everything else,\n * at the cost of needing a server restart before a NEWLY USED utility class is generated.\n */\nconst skipHotUpdateInBundledDev = (plugin: Plugin): Plugin => {\n const hotUpdate = plugin.hotUpdate;\n const handler = typeof hotUpdate === 'function' ? hotUpdate : hotUpdate?.handler;\n if (!handler) {\n return plugin;\n }\n\n // Chained rather than assigned: only `@tailwindcss/vite:generate:serve` declares `hotUpdate` and\n // it declares no `configResolved` today, but this package publishes against a caret range where\n // a later minor could add one, and a clobbered hook would fail silently.\n const configResolved = plugin.configResolved;\n const inheritedConfigResolved = typeof configResolved === 'function' ? configResolved : configResolved?.handler;\n\n let bundledDev = false;\n return {\n ...plugin,\n configResolved(config) {\n bundledDev = config.experimental.bundledDev === true;\n return inheritedConfigResolved?.call(this, config);\n },\n hotUpdate: {\n ...(typeof hotUpdate === 'object' ? hotUpdate : {}),\n handler(context) {\n if (bundledDev) {\n return;\n }\n return handler.call(this, context);\n },\n },\n };\n};\n\nexport type ThemePluginOptions = {\n srcCssPath?: string;\n /** Extra Tailwind scan globs (absolute, or relative to the Vite root) for a consumer of the published package. */\n content?: string[];\n virtualFileId?: string;\n verbose?: boolean;\n};\n\nconst toCssString = (path: string): string => JSON.stringify(path.split(sep).join('/'));\n\n/**\n * Writes the theme entry for a consumer of the published package and returns its path.\n * `workspace.css` cannot serve them: its scan paths are relative to this package, which from inside\n * `node_modules` reaches every installed package, and Tailwind walks all of it before answering.\n */\nconst writeConsumerTheme = ({\n packageRoot,\n root,\n outPath,\n content,\n}: {\n packageRoot: string;\n root: string;\n outPath: string;\n content: string[];\n}): string => {\n // The scope this package was installed into, and the consumer's own, which differ under pnpm's isolated layout.\n const scopeDirs = new Set([dirname(packageRoot), resolve(root, 'node_modules/@dxos')]);\n const sources = [\n // Tailwind skips `node_modules` below an explicit base, so this covers only the consumer's own files.\n join(root, '**/*.{ts,tsx,js,jsx,html}'),\n ...[...scopeDirs].map((scopeDir) => join(scopeDir, '*/dist/lib/**/*.mjs')),\n ...content.map((glob) => resolve(root, glob)),\n ];\n const css = [\n `@import ${toCssString(join(packageRoot, 'src/main.css'))};`,\n ...sources.map((source) => `@source ${toCssString(source)};`),\n '',\n ].join('\\n');\n\n // Rewriting identical content would bump the mtime, which Tailwind reads as a reason to rebuild.\n if (!existsSync(outPath) || readFileSync(outPath, 'utf-8') !== css) {\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, css);\n }\n\n return outPath;\n};\n\n/**\n * Vite plugin to configure theme.\n * Returns the official Tailwind Vite plugin (persistent incremental scanner) alongside the theme plugin.\n */\nexport const ThemePlugin = (options: ThemePluginOptions): Plugin[] => {\n // `src` is published too, so only the location tells a workspace checkout from an installed copy.\n const packageRoot = realpathSync(resolve(import.meta.dirname, ROOT));\n const isMonorepo = !packageRoot.split(sep).includes('node_modules');\n\n // Static assets shipped via \"files\": [\"src\"] in package.json.\n // Both monorepo and installed package resolve to the same src/plugins/ directory.\n const pluginsDir = resolve(import.meta.dirname, ROOT, 'src/plugins');\n const darkModeScriptPath = resolve(pluginsDir, 'dark-mode.ts');\n const mainCssPath = resolve(pluginsDir, 'main.css');\n\n const config = {\n // Installed copies get theirs from `configResolved`, which knows the consumer's root.\n srcCssPath: options.srcCssPath ?? (isMonorepo ? resolve(packageRoot, 'src/workspace.css') : ''),\n virtualFileId: options.virtualFileId ?? '@dxos-theme',\n verbose: options.verbose,\n };\n\n // Set under `vite dev --experimentalBundle`; see the guard in `hotUpdate`.\n let bundledDev = false;\n\n // Trailing-edge debounce handle for theme CSS reloads (see `hotUpdate`).\n let themeReloadTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Under Vitest there is no HMR, and a live watcher leaks per-file `fs_event` handles: Tailwind's\n // `@source` scan registers every scanned source file as a Vite watch dependency, and those handles\n // are never released on close, hanging single-pass `vitest run` teardown. A non-null `server.watch`\n // here also overrides the `watch: null` set by the test configs, so gate it: disable the watcher\n // entirely under Vitest (`VITEST` is exported to the whole process tree), keep the HMR-ignore\n // patterns for interactive `storybook dev` / `vite dev` (where `VITEST` is unset).\n const isVitest = process.env.VITEST === 'true';\n\n const themePlugin: Plugin = {\n name: 'vite-plugin-dxos-ui-theme',\n configResolved: (resolved) => {\n bundledDev = resolved.experimental.bundledDev === true;\n if (!config.srcCssPath) {\n config.srcCssPath = writeConsumerTheme({\n packageRoot,\n root: resolved.root,\n outPath: resolve(resolved.cacheDir, '..', '.dxos-ui-theme.css'),\n content: options.content ?? [],\n });\n }\n\n if (process.env.DEBUG || options.verbose) {\n console.log('ThemePlugin:\\n', JSON.stringify(config, null, 2));\n }\n },\n config: (): UserConfig => {\n return {\n server: {\n watch: isVitest\n ? null\n : {\n // Stop build outputs from driving HMR — they are the root of the\n // `main.css` HMR storm.\n //\n // Tailwind's `@source` scanning (see `src/main.css`) registers its\n // scanned source files as Vite watch dependencies of the compiled\n // theme CSS. Tailwind's own scanner respects `.gitignore` (and the\n // `@source not` directives), so it never *scans* `dist/`. BUT the\n // scanner hands Vite a coarse `dir-dependency` glob — e.g.\n // `{**/*.html,**/*.ts,**/*.tsx}` — and Vite re-expands that glob\n // itself, ignoring only `node_modules` (not `.gitignore`, not the\n // `@source not` negations). The re-expansion therefore sweeps in\n // every `packages/*/dist/**/*.d.ts` (`.d.ts` matches `**/*.ts`),\n // making each emitted declaration file a watch-dependency of\n // `main.css`. A single package rebuild emits dozens of `.d.ts` in a\n // tight burst, and each write re-invalidates the theme — 40+ HMR\n // pings for `main.css` in one second, repeating on every rebuild.\n //\n // Ignoring build outputs in the watcher is also semantically\n // correct: in dev the workspace resolves `@dxos/*` via the `source`\n // export condition (see `vite-plugin-import-source`), so `dist/`\n // is never consumed at runtime and its churn should never trigger\n // HMR. Vite concatenates these patterns with its built-in ignores\n // (`**/node_modules/**`, `**/.git/**`, …), so this is purely\n // additive.\n //\n // `<root>/.claude/**` covers agent worktrees checked out under the\n // repo root (`.claude/worktrees/<name>/packages/**`): they are full\n // source copies, so the glob re-expansion above sweeps them in and\n // every agent-side edit burst or checkout invalidates the theme in\n // the user's dev server. The pattern is anchored at the resolved\n // repo root (not `**/.claude/**`) because chokidar matches against\n // absolute paths — a bare pattern would match *everything* when the\n // dev server itself runs from inside a worktree whose path contains\n // a `.claude` segment. `*.log` covers runtime log sinks (e.g.\n // vite-plugin-log's `app.log` in the app root), which are appended\n // continuously at runtime and must never feed back into the\n // watcher.\n ignored: ['**/dist/**', '**/out/**', '**/*.log', `${resolve(import.meta.dirname, ROOT, '.claude')}/**`],\n },\n },\n css: {\n postcss: {\n plugins: [\n // Handles @import statements in CSS.\n postcssImport(),\n // Processes CSS nesting syntax.\n postcssNesting(),\n // Resolves @reference/@apply in `.pcss` files (e.g. lit-grid, lit-ui), which the\n // @tailwindcss/vite plugin skips — its transform filter only matches `.css`.\n // Theme `.css` files are compiled by @tailwindcss/vite first (enforce: 'pre'),\n // so this plugin's quick-bail check passes them through untouched.\n tailwindcssPostcss(),\n // Adds vendor prefixes.\n autoprefixer,\n ],\n },\n },\n };\n },\n resolveId: (id) => {\n if (id === config.virtualFileId) {\n return config.srcCssPath;\n }\n },\n hotUpdate({ type, file, modules }) {\n // Everything below is an optimization of Vite's per-module dev graph, which full-bundle dev\n // mode does not have — and Rolldown's plugin bridge passes no `environment` there, so the\n // first dereference would throw and fail the rebuild instead of updating the page.\n if (bundledDev) {\n return;\n }\n\n // Direct edits to CSS (the theme source or its imports) keep Vite's\n // default immediate update for instant feedback while authoring styles.\n if (this.environment.name !== 'client' || type !== 'update' || file.endsWith('.css')) {\n return;\n }\n\n // Every content file Tailwind scans is registered as a dependency of the\n // theme CSS — Vite models it as a file-only entry node whose importer is\n // `main.css` — so each source-file save invalidates `main.css` and\n // re-runs the monorepo-wide Tailwind scan. During an edit wave that\n // serializes one full scan per save. Drop the theme-dep entries from the\n // update (the changed module itself still hot-updates immediately) and\n // reload the theme CSS once on the trailing edge of a quiet window, so a\n // wave costs at most one scan.\n const isThemeDep = (mod: (typeof modules)[number]): boolean =>\n mod.file === config.srcCssPath ||\n (mod.id === null &&\n mod.importers.size > 0 &&\n [...mod.importers].every((importer) => importer.file === config.srcCssPath));\n if (!modules.some(isThemeDep)) {\n return;\n }\n\n const environment = this.environment;\n clearTimeout(themeReloadTimer);\n themeReloadTimer = setTimeout(() => {\n for (const mod of environment.moduleGraph.getModulesByFile(config.srcCssPath) ?? []) {\n environment.reloadModule(mod).catch(() => {\n // Server may be mid-restart; the next edit reschedules the reload.\n });\n }\n }, 300);\n\n return modules.filter((mod) => !isThemeDep(mod));\n },\n transformIndexHtml: () => {\n // Apply .dark class to <html> synchronously before any scripts run, so that\n // the critical CSS html.dark rules apply on the very first paint.\n const darkModeTag: HtmlTagDescriptor = {\n tag: 'script',\n attrs: { 'data-dxos-theme': '' },\n injectTo: 'head-prepend',\n children: readFileSync(darkModeScriptPath, 'utf-8'),\n };\n\n // Establish cascade layer order before any stylesheet loads.\n const layersTag: HtmlTagDescriptor = {\n tag: 'style',\n attrs: { 'data-dxos-layers': '' },\n children: `@layer ${LAYER_ORDER.join(', ')};`,\n injectTo: 'head-prepend',\n };\n\n // Critical styles: font sizing, overscroll, color fallbacks.\n // Loaded from critical.css to keep styles maintainable and out of index.html.\n const criticalTag: HtmlTagDescriptor = {\n tag: 'style',\n attrs: { 'data-dxos-critical': '' },\n injectTo: 'head-prepend',\n children: readFileSync(mainCssPath, 'utf-8'),\n };\n\n return [darkModeTag, layersTag, criticalTag];\n },\n };\n\n // The Tailwind Vite plugins are `enforce: 'pre'`, so they compile theme CSS (resolving\n // `@import 'tailwindcss'`, @source, @plugin, @theme) before the postcss chain runs —\n // postcss-import never sees the raw Tailwind directives. Scan roots come from the @source\n // directives in main.css (relative to that file); no project-root base is needed.\n return [...tailwindcssVite().map(skipHotUpdateInBundledDev), themePlugin];\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,IAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAKA,IAAM,OAAO;;;;;;;;;;;;;AAcb,IAAM,6BAA6B,WAA2B;CAC5D,MAAM,YAAY,OAAO;CACzB,MAAM,UAAU,OAAO,cAAc,aAAa,YAAY,WAAW;CACzE,IAAI,CAAC,SACH,OAAO;CAMT,MAAM,iBAAiB,OAAO;CAC9B,MAAM,0BAA0B,OAAO,mBAAmB,aAAa,iBAAiB,gBAAgB;CAExG,IAAI,aAAa;CACjB,OAAO;EACL,GAAG;EACH,eAAe,QAAQ;GACrB,aAAa,OAAO,aAAa,eAAe;GAChD,OAAO,yBAAyB,KAAK,MAAM,MAAM;EACnD;EACA,WAAW;GACT,GAAI,OAAO,cAAc,WAAW,YAAY,CAAC;GACjD,QAAQ,SAAS;IACf,IAAI,YACF;IAEF,OAAO,QAAQ,KAAK,MAAM,OAAO;GACnC;EACF;CACF;AACF;AAUA,IAAM,eAAe,SAAyB,KAAK,UAAU,KAAK,MAAMA,UAAAA,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC;;;;;;AAOtF,IAAM,sBAAsB,EAC1B,aACA,MACA,SACA,cAMY;CAEZ,MAAM,4BAAY,IAAI,IAAI,EAAA,GAACC,UAAAA,QAAAA,CAAQ,WAAW,IAAA,GAAGC,UAAAA,QAAAA,CAAQ,MAAM,oBAAoB,CAAC,CAAC;CACrF,MAAM,UAAU;GAEdC,GAAAA,UAAAA,KAAAA,CAAK,MAAM,2BAA2B;EACtC,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,KAAK,cAAA,GAAaA,UAAAA,KAAAA,CAAK,UAAU,qBAAqB,CAAC;EACzE,GAAG,QAAQ,KAAK,UAAA,GAASD,UAAAA,QAAAA,CAAQ,MAAM,IAAI,CAAC;CAC9C;CACA,MAAM,MAAM;EACV,WAAW,aAAA,GAAYC,UAAAA,KAAAA,CAAK,aAAa,cAAc,CAAC,EAAE;EAC1D,GAAG,QAAQ,KAAK,WAAW,WAAW,YAAY,MAAM,EAAE,EAAE;EAC5D;CACF,CAAC,CAAC,KAAK,IAAI;CAGX,IAAI,EAAA,GAACC,QAAAA,WAAAA,CAAW,OAAO,MAAA,GAAKC,QAAAA,aAAAA,CAAa,SAAS,OAAO,MAAM,KAAK;EAClE,CAAA,GAAA,QAAA,UAAA,EAAA,GAAUJ,UAAAA,QAAAA,CAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;EAC/C,CAAA,GAAA,QAAA,cAAA,CAAc,SAAS,GAAG;CAC5B;CAEA,OAAO;AACT;;;;;AAMA,IAAa,eAAe,YAA0C;CAEpE,MAAM,eAAA,GAAcK,QAAAA,aAAAA,EAAAA,GAAaJ,UAAAA,QAAAA,CAAAA,WAA6B,IAAI,CAAC;CACnE,MAAM,aAAa,CAAC,YAAY,MAAMF,UAAAA,GAAG,CAAC,CAAC,SAAS,cAAc;CAIlE,MAAM,cAAA,GAAaE,UAAAA,QAAAA,CAAAA,WAA6B,MAAM,aAAa;CACnE,MAAM,sBAAA,GAAqBA,UAAAA,QAAAA,CAAQ,YAAY,cAAc;CAC7D,MAAM,eAAA,GAAcA,UAAAA,QAAAA,CAAQ,YAAY,UAAU;CAElD,MAAM,SAAS;EAEb,YAAY,QAAQ,eAAe,cAAA,GAAaA,UAAAA,QAAAA,CAAQ,aAAa,mBAAmB,IAAI;EAC5F,eAAe,QAAQ,iBAAiB;EACxC,SAAS,QAAQ;CACnB;CAGA,IAAI,aAAa;CAGjB,IAAI;CAQJ,MAAM,WAAW,QAAQ,IAAI,WAAW;CAExC,MAAM,cAAsB;EAC1B,MAAM;EACN,iBAAiB,aAAa;GAC5B,aAAa,SAAS,aAAa,eAAe;GAClD,IAAI,CAAC,OAAO,YACV,OAAO,aAAa,mBAAmB;IACrC;IACA,MAAM,SAAS;IACf,UAAA,GAASA,UAAAA,QAAAA,CAAQ,SAAS,UAAU,MAAM,oBAAoB;IAC9D,SAAS,QAAQ,WAAW,CAAC;GAC/B,CAAC;GAGH,IAAI,QAAQ,IAAI,SAAS,QAAQ,SAC/B,QAAQ,IAAI,kBAAkB,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;EAEjE;EACA,cAA0B;GACxB,OAAO;IACL,QAAQ,EACN,OAAO,WACH,OACA,EAsCE,SAAS;KAAC;KAAc;KAAa;KAAY,IAAA,GAAGA,UAAAA,QAAAA,CAAAA,WAA6B,MAAM,SAAS,EAAE;IAAI,EACxG,EACN;IACA,KAAK,EACH,SAAS,EACP,SAAS;MAEPK,GAAAA,eAAAA,QAAAA,CAAc;MAEdC,GAAAA,gBAAAA,QAAAA,CAAe;MAKfC,GAAAA,qBAAAA,QAAAA,CAAmB;KAEnB,aAAA;IACF,EACF,EACF;GACF;EACF;EACA,YAAY,OAAO;GACjB,IAAI,OAAO,OAAO,eAChB,OAAO,OAAO;EAElB;EACA,UAAU,EAAE,MAAM,MAAM,WAAW;GAIjC,IAAI,YACF;GAKF,IAAI,KAAK,YAAY,SAAS,YAAY,SAAS,YAAY,KAAK,SAAS,MAAM,GACjF;GAWF,MAAM,cAAc,QAClB,IAAI,SAAS,OAAO,cACnB,IAAI,OAAO,QACV,IAAI,UAAU,OAAO,KACrB,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC,OAAO,aAAa,SAAS,SAAS,OAAO,UAAU;GAC9E,IAAI,CAAC,QAAQ,KAAK,UAAU,GAC1B;GAGF,MAAM,cAAc,KAAK;GACzB,aAAa,gBAAgB;GAC7B,mBAAmB,iBAAiB;IAClC,KAAK,MAAM,OAAO,YAAY,YAAY,iBAAiB,OAAO,UAAU,KAAK,CAAC,GAChF,YAAY,aAAa,GAAG,CAAC,CAAC,YAAY,CAE1C,CAAC;GAEL,GAAG,GAAG;GAEN,OAAO,QAAQ,QAAQ,QAAQ,CAAC,WAAW,GAAG,CAAC;EACjD;EACA,0BAA0B;GA2BxB,OAAO;IAAC;KAvBN,KAAK;KACL,OAAO,EAAE,mBAAmB,GAAG;KAC/B,UAAU;KACV,WAAA,GAAUJ,QAAAA,aAAAA,CAAa,oBAAoB,OAAO;IAoB5C;IAAa;KAfnB,KAAK;KACL,OAAO,EAAE,oBAAoB,GAAG;KAChC,UAAU,UAAU,YAAY,KAAK,IAAI,EAAE;KAC3C,UAAU;IAYS;IAAW;KAN9B,KAAK;KACL,OAAO,EAAE,sBAAsB,GAAG;KAClC,UAAU;KACV,WAAA,GAAUA,QAAAA,aAAAA,CAAa,aAAa,OAAO;IAGb;GAAW;EAC7C;CACF;CAMA,OAAO,CAAC,IAAA,GAAGK,kBAAAA,QAAAA,CAAgB,CAAC,CAAC,IAAI,yBAAyB,GAAG,WAAW;AAC1E"}