isotone 1.0.10 → 1.1.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.
package/AGENTS.md CHANGED
@@ -35,11 +35,15 @@ they compare them:
35
35
  ```sh
36
36
  npx isotone variant new <id> --note <notePrefix>
37
37
  npx isotone variant add <id> <file...>
38
+ npx isotone variant add <id> --new <file>
38
39
  npx isotone variant path <id> <file>
39
40
  ```
40
41
 
41
- Edit the copied file. Use `--group` for alternatives of the same decision and
42
- scope them to the note's route. `variant use <id>` previews one; promote it only
42
+ Edit the copied file. `--new` starts a file the project does not have yet.
43
+ Where you can, copy only files that export components. A copied file that
44
+ exports a hook, helper or constant object makes every switch reload the page.
45
+ Use `--group` for alternatives of the same decision and scope them to the
46
+ note's route. `variant use <id>` previews one; promote it only
43
47
  when the reviewer chooses it. See [variants](docs/variants.md) for stale-file
44
48
  checks and promotion. A `[set-defaults]` note names the selected variants and
45
49
  knobs to make permanent. Removal requests remain notes until an agent changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.1.0
6
+
7
+ - Overlay variants switch in the browser in React projects on Vite 6 or later.
8
+ The plugin loads every version of an overridden file once, so a component
9
+ switch is a re-render, and a switch that changes a hook or other
10
+ non-component export reloads the page once. The pick lives in the tab's
11
+ `?v=`, so links reproduce it and two tabs can compare variants.
12
+ `variants: { live: false }` keeps whole-file HMR swaps.
13
+ - `isotone variant add <id> --new <file>` lets a variant add a file the
14
+ project does not have.
15
+
5
16
  ## 1.0.10
6
17
 
7
18
  - Hosted demo with a first-visit tour, and Enter finishes selecting (#15)
@@ -7,7 +7,7 @@ import { randomUUID } from "crypto";
7
7
 
8
8
  // package.json
9
9
  var name = "isotone";
10
- var version = "1.0.10";
10
+ var version = "1.1.0";
11
11
 
12
12
  // src/update.ts
13
13
  var UPDATE_ENDPOINT = "/api/iteration/update";
@@ -228,4 +228,4 @@ export {
228
228
  checkUpdate,
229
229
  startUpdateCheck
230
230
  };
231
- //# sourceMappingURL=chunk-IKTHHG7T.js.map
231
+ //# sourceMappingURL=chunk-B3JNZITE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/update.ts","../package.json"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport type { UpdateInfo } from './core/update';\nimport { name as packageName, version as currentVersion } from '../package.json';\n\nexport const UPDATE_ENDPOINT = '/api/iteration/update';\nconst REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;\nconst CHECK_TIMEOUT = 5_000;\nconst CACHE_TTL = 24 * 60 * 60 * 1000;\nconst CACHE_FILE = join(tmpdir(), `isotone-update-${process.getuid?.() ?? 'user'}-v1.json`);\n\ntype Manager = 'npm' | 'pnpm' | 'yarn';\nexport type { UpdateInfo } from './core/update';\n\ninterface Version { parts: bigint[]; prerelease: string[] }\nfunction parseVersion(value: string): Version {\n if (value.length > 256) throw new Error('Invalid version.');\n const match = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?(?:\\+([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?$/.exec(value);\n if (!match) throw new Error('Invalid version.');\n const prerelease = match[4]?.split('.') ?? [];\n if (prerelease.some((part) => /^0\\d+$/.test(part))) throw new Error('Invalid version.');\n return { parts: match.slice(1, 4).map(BigInt), prerelease };\n}\n\n/** SemVer precedence, including numeric prereleases and ignored build metadata. */\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a), right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left.parts[i] !== right.parts[i]) return left.parts[i] > right.parts[i] ? 1 : -1;\n }\n if (!left.prerelease.length || !right.prerelease.length) {\n return left.prerelease.length === right.prerelease.length ? 0 : left.prerelease.length ? -1 : 1;\n }\n for (let i = 0; i < Math.max(left.prerelease.length, right.prerelease.length); i++) {\n const x = left.prerelease[i], y = right.prerelease[i];\n if (x === y) continue;\n if (x === undefined || y === undefined) return x === undefined ? -1 : 1;\n const nx = /^\\d+$/.test(x), ny = /^\\d+$/.test(y);\n if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : -1;\n if (nx !== ny) return nx ? -1 : 1;\n return x > y ? 1 : -1;\n }\n return 0;\n}\n\nfunction manifest(directory: string): Record<string, unknown> | null {\n try {\n const value = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));\n return value && typeof value === 'object' && !Array.isArray(value) ? value : null;\n } catch { return null; }\n}\n\nfunction findProject(cwd: string): { directory: string; json: Record<string, unknown> | null } {\n let directory = resolve(cwd);\n while (true) {\n const json = manifest(directory);\n if (json) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return { directory: resolve(cwd), json: null };\n directory = dirname(directory);\n }\n}\n\nfunction dependencyOwner(project: ReturnType<typeof findProject>): ReturnType<typeof findProject> {\n let directory = project.directory;\n while (true) {\n const json = manifest(directory);\n if (['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some((key) => {\n const dependencies = json?.[key];\n return dependencies && typeof dependencies === 'object' && Object.hasOwn(dependencies, packageName);\n })) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return project;\n directory = dirname(directory);\n }\n}\n\nexport function upgradePlan(cwd: string, latest: string, override?: Manager): Pick<UpdateInfo, 'upgrade' | 'message'> {\n parseVersion(latest); // Only a validated version can enter a copied command.\n const project = dependencyOwner(findProject(cwd));\n if (project.json?.name === packageName) return { upgrade: null, message: 'This is the source checkout; update its source instead of adding itself as a dependency.' };\n let manager = override;\n let directory = project.directory;\n const locks = new Set<Manager>();\n while (!manager) {\n const declared = manifest(directory)?.packageManager;\n if (typeof declared === 'string') {\n const match = /^(npm|pnpm|yarn)@/.exec(declared);\n if (!match) return { upgrade: null, message: 'The declared package manager is not supported by this command. Upgrade with your project’s package manager.' };\n manager = match[1] as Manager;\n break;\n }\n if (['bun.lock', 'bun.lockb'].some((file) => existsSync(join(directory, file)))) return { upgrade: null, message: 'This project uses Bun. Upgrade with the project’s package manager.' };\n if (existsSync(join(directory, 'pnpm-lock.yaml'))) locks.add('pnpm');\n if (existsSync(join(directory, 'yarn.lock'))) locks.add('yarn');\n if (['package-lock.json', 'npm-shrinkwrap.json'].some((file) => existsSync(join(directory, file)))) locks.add('npm');\n if (locks.size || dirname(directory) === directory || existsSync(join(directory, '.git'))) break;\n directory = dirname(directory);\n }\n if (!manager && locks.size > 1) return { upgrade: null, message: 'Multiple package-manager lockfiles found. Pass --manager npm, pnpm or yarn.' };\n manager ??= [...locks][0] ?? 'npm';\n const production = project.json?.dependencies as Record<string, unknown> | undefined;\n const optional = project.json?.optionalDependencies as Record<string, unknown> | undefined;\n const peers = project.json?.peerDependencies as Record<string, unknown> | undefined;\n if (optional?.[packageName] || peers?.[packageName]) return { upgrade: null, message: 'Review this optional/peer dependency manually with your package manager.' };\n const dev = production?.[packageName] ? '' : ' -D';\n let workspace = manager === 'pnpm' && existsSync(join(project.directory, 'pnpm-workspace.yaml')) ? ' -w' : '';\n if (manager === 'yarn' && project.json?.workspaces) {\n const declared = project.json.packageManager;\n if (typeof declared !== 'string' || !/^yarn@\\d+\\./.test(declared)) return { upgrade: null, message: 'Declare the Yarn version in packageManager before updating a workspace-root dependency; Yarn Classic and modern Yarn use different root flags.' };\n if (declared.startsWith('yarn@1.')) workspace = ' -W';\n }\n const command = manager === 'npm'\n ? `npm install${dev} --save-exact ${packageName}@${latest}`\n : `${manager} add${dev}${workspace} --exact ${packageName}@${latest}`;\n return { upgrade: { manager, command, cwd: project.directory } };\n}\n\nexport async function fetchLatest(fetcher: typeof fetch = fetch, signal?: AbortSignal): Promise<string> {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n const timer = setTimeout(abort, CHECK_TIMEOUT);\n try {\n if (controller.signal.aborted) throw new Error('Update check cancelled.');\n const response = await fetcher(REGISTRY_URL, { signal: controller.signal, redirect: 'error', headers: { accept: 'application/json' } });\n if (!response.ok) throw new Error(`Registry returned HTTP ${response.status}.`);\n if (!response.body) throw new Error('Registry returned an empty response.');\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n if (bytes > 64 * 1024) {\n await reader.cancel();\n throw new Error('Registry response was too large.');\n }\n chunks.push(value);\n }\n } finally { reader.releaseLock(); }\n const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n if (typeof data.version !== 'string') throw new Error('Registry returned no version.');\n parseVersion(data.version);\n return data.version;\n } finally { clearTimeout(timer); signal?.removeEventListener('abort', abort); }\n}\n\nexport function updateInfo(latest: string, cwd: string, manager?: Manager): UpdateInfo {\n const order = compareVersions(latest, currentVersion);\n return {\n currentVersion, latestVersion: latest,\n status: order > 0 ? 'available' : order < 0 ? 'ahead' : 'current',\n ...(order > 0 ? upgradePlan(cwd, latest, manager) : { upgrade: null }),\n };\n}\n\nexport async function checkUpdate(cwd: string, manager?: Manager, fetcher?: typeof fetch, signal?: AbortSignal): Promise<UpdateInfo> {\n try { return updateInfo(await fetchLatest(fetcher, signal), cwd, manager); }\n catch { return { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null, message: 'Could not check npm for updates. The installed version remains usable; try again when online.' }; }\n}\n\ninterface CachedCheck { checkedAt: number; latestVersion: string | null }\nasync function readCache(file: string): Promise<CachedCheck | null> {\n try {\n if ((await stat(file)).size > 4096) return null;\n const data = JSON.parse(await readFile(file, 'utf8'));\n if (!Number.isFinite(data.checkedAt) || data.checkedAt > Date.now() || Date.now() - data.checkedAt >= CACHE_TTL) return null;\n if (data.latestVersion !== null) parseVersion(data.latestVersion);\n return data;\n } catch { return null; }\n}\n\nasync function saveCache(file: string, latestVersion: string | null): Promise<void> {\n const temporary = `${file}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, JSON.stringify({ checkedAt: Date.now(), latestVersion }), { mode: 0o600, flag: 'wx' });\n await rename(temporary, file);\n } finally { await rm(temporary, { force: true }); }\n}\n\n/** One bounded request a day, including failed attempts, shared across dev servers. */\nexport function startUpdateCheck(cwd: string, enabled = true, options: { cacheFile?: string; fetcher?: typeof fetch; env?: NodeJS.ProcessEnv } = {}): { getSnapshot(): UpdateInfo; ready: Promise<void>; dispose(): void } {\n const controller = new AbortController();\n const env = options.env ?? process.env;\n const off = !enabled || env.ISOTONE_UPDATE_CHECK === '0' || (!!env.CI && env.CI !== 'false' && env.CI !== '0') || env.NODE_ENV === 'test' || !!env.VITEST;\n let info: UpdateInfo = { currentVersion, latestVersion: null, status: off ? 'disabled' : 'checking', upgrade: null };\n const ready = off ? Promise.resolve() : (async () => {\n const file = options.cacheFile ?? CACHE_FILE;\n let locked = false;\n try {\n const cache = await readCache(file);\n if (cache) { info = cache.latestVersion ? updateInfo(cache.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await mkdir(dirname(file), { recursive: true });\n try { await (await open(`${file}.lock`, 'wx', 0o600)).close(); locked = true; }\n catch {\n // A concurrent server owns the request. Recover an abandoned lease next time.\n if (Date.now() - (await stat(`${file}.lock`)).mtimeMs > CHECK_TIMEOUT * 3) await rm(`${file}.lock`, { force: true });\n info = { ...info, status: 'unavailable' };\n return;\n }\n const refreshed = await readCache(file);\n if (refreshed) { info = refreshed.latestVersion ? updateInfo(refreshed.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await saveCache(file, null);\n info = await checkUpdate(cwd, undefined, options.fetcher, controller.signal);\n if (info.latestVersion) await saveCache(file, info.latestVersion);\n } catch { info = { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null }; }\n finally { if (locked) await rm(`${file}.lock`, { force: true }).catch(() => {}); }\n })();\n return { getSnapshot: () => info, ready, dispose: () => controller.abort() };\n}\n","{\n \"name\": \"isotone\",\n \"version\": \"1.0.10\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"description\": \"Design variants and feedback for your running web app. Switch between variants your coding agent builds, pin elements, and give the agent the context to make the change.\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"exports\": {\n \"./react\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./core\": {\n \"types\": \"./dist/core.d.ts\",\n \"import\": \"./dist/core.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\"\n },\n \"./serve\": {\n \"types\": \"./dist/serve.d.ts\",\n \"import\": \"./dist/serve.js\"\n },\n \"./vite\": {\n \"types\": \"./dist/vite-plugin.d.ts\",\n \"import\": \"./dist/vite-plugin.js\"\n },\n \"./cli\": {\n \"types\": \"./dist/cli.d.ts\",\n \"import\": \"./dist/cli.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"docs/assets/preview.png\",\n \"docs/integrations.md\",\n \"docs/migration.md\",\n \"docs/updates.md\",\n \"docs/variants.md\",\n \"docs/third-party-notices.md\",\n \"AGENTS.md\",\n \"CHANGELOG.md\"\n ],\n \"bin\": {\n \"isotone\": \"./dist/cli.js\"\n },\n \"scripts\": {\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"biome lint .\",\n \"lint:fix\": \"biome lint --write .\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"build\": \"tsup\",\n \"check\": \"pnpm typecheck && pnpm lint && pnpm build && pnpm test\",\n \"prepublishOnly\": \"pnpm check\",\n \"dev\": \"vite --config examples/vite.config.ts\",\n \"build:demo\": \"vite build --config examples/vite.config.ts --mode hosted\",\n \"check:package\": \"node scripts/check-package.mjs\"\n },\n \"peerDependencies\": {\n \"react\": \"^18 || ^19\",\n \"react-dom\": \"^18 || ^19\",\n \"vite\": \"^5 || ^6 || ^7 || ^8\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"2.5.7\",\n \"@types/node\": \"24.13.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"jsdom\": \"^30.0.1\",\n \"react\": \"^19\",\n \"react-dom\": \"^19\",\n \"tsup\": \"^8\",\n \"typescript\": \"^5\",\n \"vite\": \"^7\",\n \"vitest\": \"^4.1.10\",\n \"lucide-react\": \"^1.55.0\"\n },\n \"keywords\": [\n \"feedback\",\n \"review\",\n \"dev-tool\",\n \"vite-plugin\",\n \"coding-agent\",\n \"annotations\",\n \"design-review\",\n \"variants\",\n \"iteration\"\n ],\n \"sideEffects\": false,\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/aaroi/isotone.git\"\n },\n \"homepage\": \"https://github.com/aaroi/isotone#readme\",\n \"bugs\": \"https://github.com/aaroi/isotone/issues\",\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vite\": {\n \"optional\": true\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/update.ts","../package.json"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomUUID } from 'node:crypto';\nimport type { UpdateInfo } from './core/update';\nimport { name as packageName, version as currentVersion } from '../package.json';\n\nexport const UPDATE_ENDPOINT = '/api/iteration/update';\nconst REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;\nconst CHECK_TIMEOUT = 5_000;\nconst CACHE_TTL = 24 * 60 * 60 * 1000;\nconst CACHE_FILE = join(tmpdir(), `isotone-update-${process.getuid?.() ?? 'user'}-v1.json`);\n\ntype Manager = 'npm' | 'pnpm' | 'yarn';\nexport type { UpdateInfo } from './core/update';\n\ninterface Version { parts: bigint[]; prerelease: string[] }\nfunction parseVersion(value: string): Version {\n if (value.length > 256) throw new Error('Invalid version.');\n const match = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?(?:\\+([\\da-zA-Z-]+(?:\\.[\\da-zA-Z-]+)*))?$/.exec(value);\n if (!match) throw new Error('Invalid version.');\n const prerelease = match[4]?.split('.') ?? [];\n if (prerelease.some((part) => /^0\\d+$/.test(part))) throw new Error('Invalid version.');\n return { parts: match.slice(1, 4).map(BigInt), prerelease };\n}\n\n/** SemVer precedence, including numeric prereleases and ignored build metadata. */\nexport function compareVersions(a: string, b: string): number {\n const left = parseVersion(a), right = parseVersion(b);\n for (let i = 0; i < 3; i++) {\n if (left.parts[i] !== right.parts[i]) return left.parts[i] > right.parts[i] ? 1 : -1;\n }\n if (!left.prerelease.length || !right.prerelease.length) {\n return left.prerelease.length === right.prerelease.length ? 0 : left.prerelease.length ? -1 : 1;\n }\n for (let i = 0; i < Math.max(left.prerelease.length, right.prerelease.length); i++) {\n const x = left.prerelease[i], y = right.prerelease[i];\n if (x === y) continue;\n if (x === undefined || y === undefined) return x === undefined ? -1 : 1;\n const nx = /^\\d+$/.test(x), ny = /^\\d+$/.test(y);\n if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : -1;\n if (nx !== ny) return nx ? -1 : 1;\n return x > y ? 1 : -1;\n }\n return 0;\n}\n\nfunction manifest(directory: string): Record<string, unknown> | null {\n try {\n const value = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8'));\n return value && typeof value === 'object' && !Array.isArray(value) ? value : null;\n } catch { return null; }\n}\n\nfunction findProject(cwd: string): { directory: string; json: Record<string, unknown> | null } {\n let directory = resolve(cwd);\n while (true) {\n const json = manifest(directory);\n if (json) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return { directory: resolve(cwd), json: null };\n directory = dirname(directory);\n }\n}\n\nfunction dependencyOwner(project: ReturnType<typeof findProject>): ReturnType<typeof findProject> {\n let directory = project.directory;\n while (true) {\n const json = manifest(directory);\n if (['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'].some((key) => {\n const dependencies = json?.[key];\n return dependencies && typeof dependencies === 'object' && Object.hasOwn(dependencies, packageName);\n })) return { directory, json };\n if (dirname(directory) === directory || existsSync(join(directory, '.git'))) return project;\n directory = dirname(directory);\n }\n}\n\nexport function upgradePlan(cwd: string, latest: string, override?: Manager): Pick<UpdateInfo, 'upgrade' | 'message'> {\n parseVersion(latest); // Only a validated version can enter a copied command.\n const project = dependencyOwner(findProject(cwd));\n if (project.json?.name === packageName) return { upgrade: null, message: 'This is the source checkout; update its source instead of adding itself as a dependency.' };\n let manager = override;\n let directory = project.directory;\n const locks = new Set<Manager>();\n while (!manager) {\n const declared = manifest(directory)?.packageManager;\n if (typeof declared === 'string') {\n const match = /^(npm|pnpm|yarn)@/.exec(declared);\n if (!match) return { upgrade: null, message: 'The declared package manager is not supported by this command. Upgrade with your project’s package manager.' };\n manager = match[1] as Manager;\n break;\n }\n if (['bun.lock', 'bun.lockb'].some((file) => existsSync(join(directory, file)))) return { upgrade: null, message: 'This project uses Bun. Upgrade with the project’s package manager.' };\n if (existsSync(join(directory, 'pnpm-lock.yaml'))) locks.add('pnpm');\n if (existsSync(join(directory, 'yarn.lock'))) locks.add('yarn');\n if (['package-lock.json', 'npm-shrinkwrap.json'].some((file) => existsSync(join(directory, file)))) locks.add('npm');\n if (locks.size || dirname(directory) === directory || existsSync(join(directory, '.git'))) break;\n directory = dirname(directory);\n }\n if (!manager && locks.size > 1) return { upgrade: null, message: 'Multiple package-manager lockfiles found. Pass --manager npm, pnpm or yarn.' };\n manager ??= [...locks][0] ?? 'npm';\n const production = project.json?.dependencies as Record<string, unknown> | undefined;\n const optional = project.json?.optionalDependencies as Record<string, unknown> | undefined;\n const peers = project.json?.peerDependencies as Record<string, unknown> | undefined;\n if (optional?.[packageName] || peers?.[packageName]) return { upgrade: null, message: 'Review this optional/peer dependency manually with your package manager.' };\n const dev = production?.[packageName] ? '' : ' -D';\n let workspace = manager === 'pnpm' && existsSync(join(project.directory, 'pnpm-workspace.yaml')) ? ' -w' : '';\n if (manager === 'yarn' && project.json?.workspaces) {\n const declared = project.json.packageManager;\n if (typeof declared !== 'string' || !/^yarn@\\d+\\./.test(declared)) return { upgrade: null, message: 'Declare the Yarn version in packageManager before updating a workspace-root dependency; Yarn Classic and modern Yarn use different root flags.' };\n if (declared.startsWith('yarn@1.')) workspace = ' -W';\n }\n const command = manager === 'npm'\n ? `npm install${dev} --save-exact ${packageName}@${latest}`\n : `${manager} add${dev}${workspace} --exact ${packageName}@${latest}`;\n return { upgrade: { manager, command, cwd: project.directory } };\n}\n\nexport async function fetchLatest(fetcher: typeof fetch = fetch, signal?: AbortSignal): Promise<string> {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n const timer = setTimeout(abort, CHECK_TIMEOUT);\n try {\n if (controller.signal.aborted) throw new Error('Update check cancelled.');\n const response = await fetcher(REGISTRY_URL, { signal: controller.signal, redirect: 'error', headers: { accept: 'application/json' } });\n if (!response.ok) throw new Error(`Registry returned HTTP ${response.status}.`);\n if (!response.body) throw new Error('Registry returned an empty response.');\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n bytes += value.byteLength;\n if (bytes > 64 * 1024) {\n await reader.cancel();\n throw new Error('Registry response was too large.');\n }\n chunks.push(value);\n }\n } finally { reader.releaseLock(); }\n const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));\n if (typeof data.version !== 'string') throw new Error('Registry returned no version.');\n parseVersion(data.version);\n return data.version;\n } finally { clearTimeout(timer); signal?.removeEventListener('abort', abort); }\n}\n\nexport function updateInfo(latest: string, cwd: string, manager?: Manager): UpdateInfo {\n const order = compareVersions(latest, currentVersion);\n return {\n currentVersion, latestVersion: latest,\n status: order > 0 ? 'available' : order < 0 ? 'ahead' : 'current',\n ...(order > 0 ? upgradePlan(cwd, latest, manager) : { upgrade: null }),\n };\n}\n\nexport async function checkUpdate(cwd: string, manager?: Manager, fetcher?: typeof fetch, signal?: AbortSignal): Promise<UpdateInfo> {\n try { return updateInfo(await fetchLatest(fetcher, signal), cwd, manager); }\n catch { return { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null, message: 'Could not check npm for updates. The installed version remains usable; try again when online.' }; }\n}\n\ninterface CachedCheck { checkedAt: number; latestVersion: string | null }\nasync function readCache(file: string): Promise<CachedCheck | null> {\n try {\n if ((await stat(file)).size > 4096) return null;\n const data = JSON.parse(await readFile(file, 'utf8'));\n if (!Number.isFinite(data.checkedAt) || data.checkedAt > Date.now() || Date.now() - data.checkedAt >= CACHE_TTL) return null;\n if (data.latestVersion !== null) parseVersion(data.latestVersion);\n return data;\n } catch { return null; }\n}\n\nasync function saveCache(file: string, latestVersion: string | null): Promise<void> {\n const temporary = `${file}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporary, JSON.stringify({ checkedAt: Date.now(), latestVersion }), { mode: 0o600, flag: 'wx' });\n await rename(temporary, file);\n } finally { await rm(temporary, { force: true }); }\n}\n\n/** One bounded request a day, including failed attempts, shared across dev servers. */\nexport function startUpdateCheck(cwd: string, enabled = true, options: { cacheFile?: string; fetcher?: typeof fetch; env?: NodeJS.ProcessEnv } = {}): { getSnapshot(): UpdateInfo; ready: Promise<void>; dispose(): void } {\n const controller = new AbortController();\n const env = options.env ?? process.env;\n const off = !enabled || env.ISOTONE_UPDATE_CHECK === '0' || (!!env.CI && env.CI !== 'false' && env.CI !== '0') || env.NODE_ENV === 'test' || !!env.VITEST;\n let info: UpdateInfo = { currentVersion, latestVersion: null, status: off ? 'disabled' : 'checking', upgrade: null };\n const ready = off ? Promise.resolve() : (async () => {\n const file = options.cacheFile ?? CACHE_FILE;\n let locked = false;\n try {\n const cache = await readCache(file);\n if (cache) { info = cache.latestVersion ? updateInfo(cache.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await mkdir(dirname(file), { recursive: true });\n try { await (await open(`${file}.lock`, 'wx', 0o600)).close(); locked = true; }\n catch {\n // A concurrent server owns the request. Recover an abandoned lease next time.\n if (Date.now() - (await stat(`${file}.lock`)).mtimeMs > CHECK_TIMEOUT * 3) await rm(`${file}.lock`, { force: true });\n info = { ...info, status: 'unavailable' };\n return;\n }\n const refreshed = await readCache(file);\n if (refreshed) { info = refreshed.latestVersion ? updateInfo(refreshed.latestVersion, cwd) : { ...info, status: 'unavailable' }; return; }\n await saveCache(file, null);\n info = await checkUpdate(cwd, undefined, options.fetcher, controller.signal);\n if (info.latestVersion) await saveCache(file, info.latestVersion);\n } catch { info = { currentVersion, latestVersion: null, status: 'unavailable', upgrade: null }; }\n finally { if (locked) await rm(`${file}.lock`, { force: true }).catch(() => {}); }\n })();\n return { getSnapshot: () => info, ready, dispose: () => controller.abort() };\n}\n","{\n \"name\": \"isotone\",\n \"version\": \"1.1.0\",\n \"type\": \"module\",\n \"license\": \"MIT\",\n \"description\": \"Design variants and feedback for your running web app. Switch between variants your coding agent builds, pin elements, and give the agent the context to make the change.\",\n \"engines\": {\n \"node\": \">=20\"\n },\n \"exports\": {\n \"./react\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./core\": {\n \"types\": \"./dist/core.d.ts\",\n \"import\": \"./dist/core.js\"\n },\n \"./server\": {\n \"types\": \"./dist/server.d.ts\",\n \"import\": \"./dist/server.js\"\n },\n \"./serve\": {\n \"types\": \"./dist/serve.d.ts\",\n \"import\": \"./dist/serve.js\"\n },\n \"./vite\": {\n \"types\": \"./dist/vite-plugin.d.ts\",\n \"import\": \"./dist/vite-plugin.js\"\n },\n \"./cli\": {\n \"types\": \"./dist/cli.d.ts\",\n \"import\": \"./dist/cli.js\"\n }\n },\n \"files\": [\n \"dist\",\n \"docs/assets/preview.png\",\n \"docs/integrations.md\",\n \"docs/migration.md\",\n \"docs/updates.md\",\n \"docs/variants.md\",\n \"docs/third-party-notices.md\",\n \"AGENTS.md\",\n \"CHANGELOG.md\"\n ],\n \"bin\": {\n \"isotone\": \"./dist/cli.js\"\n },\n \"scripts\": {\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"biome lint .\",\n \"lint:fix\": \"biome lint --write .\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"build\": \"tsup\",\n \"check\": \"pnpm typecheck && pnpm lint && pnpm build && pnpm test\",\n \"prepublishOnly\": \"pnpm check\",\n \"dev\": \"vite --config examples/vite.config.ts\",\n \"build:demo\": \"vite build --config examples/vite.config.ts --mode hosted\",\n \"check:package\": \"node scripts/check-package.mjs\"\n },\n \"peerDependencies\": {\n \"react\": \"^18 || ^19\",\n \"react-dom\": \"^18 || ^19\",\n \"vite\": \"^5 || ^6 || ^7 || ^8\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"2.5.7\",\n \"@types/node\": \"24.13.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"jsdom\": \"^30.0.1\",\n \"react\": \"^19\",\n \"react-dom\": \"^19\",\n \"tsup\": \"^8\",\n \"typescript\": \"^5\",\n \"vite\": \"^7\",\n \"vitest\": \"^4.1.10\",\n \"lucide-react\": \"^1.55.0\"\n },\n \"keywords\": [\n \"feedback\",\n \"review\",\n \"dev-tool\",\n \"vite-plugin\",\n \"coding-agent\",\n \"annotations\",\n \"design-review\",\n \"variants\",\n \"iteration\"\n ],\n \"sideEffects\": false,\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/aaroi/isotone.git\"\n },\n \"homepage\": \"https://github.com/aaroi/isotone#readme\",\n \"bugs\": \"https://github.com/aaroi/isotone/issues\",\n \"peerDependenciesMeta\": {\n \"react\": {\n \"optional\": true\n },\n \"react-dom\": {\n \"optional\": true\n },\n \"vite\": {\n \"optional\": true\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  readBrowserStorage,
4
4
  request,
5
5
  writeBrowserStorage
6
- } from "./chunk-OSETWKGE.js";
6
+ } from "./chunk-PUXQGUMQ.js";
7
7
  import {
8
8
  MAX_FEEDBACK_LENGTH
9
9
  } from "./chunk-A4DX2V4C.js";
@@ -370,4 +370,4 @@ export {
370
370
  isRouteVisible,
371
371
  createIterationStore
372
372
  };
373
- //# sourceMappingURL=chunk-OOFRFHCN.js.map
373
+ //# sourceMappingURL=chunk-FKEKBZ3E.js.map
@@ -380,6 +380,11 @@ function overlayHint(v) {
380
380
  if (v.missing.length) parts.push(`missing: ${v.missing.join(", ")}`);
381
381
  return parts.join("\n");
382
382
  }
383
+ function pageLiveStore() {
384
+ if (typeof window === "undefined") return null;
385
+ const store = window.__isotoneLive;
386
+ return store && store.version === 1 ? store : null;
387
+ }
383
388
  function createOverlayVariantsSource(cfg) {
384
389
  let list = null;
385
390
  let error = null;
@@ -387,10 +392,14 @@ function createOverlayVariantsSource(cfg) {
387
392
  let activation = null;
388
393
  let revision = 0;
389
394
  let refreshRevision = 0;
395
+ let reloadAfterPost = false;
390
396
  const listeners = /* @__PURE__ */ new Set();
391
397
  const emit = () => {
392
398
  for (const l of listeners) l();
393
399
  };
400
+ const live = cfg.live === void 0 ? pageLiveStore() : cfg.live;
401
+ live?.subscribe(emit);
402
+ const current = () => live?.active ?? intended ?? list?.active ?? [];
394
403
  const selects = () => list ? overlayAxes(list, cfg.getRoute()) : [];
395
404
  const knobAxes = () => knobsFor(list?.knobs, cfg.getRoute());
396
405
  const axes = () => [...selects(), ...knobAxes()];
@@ -413,7 +422,7 @@ function createOverlayVariantsSource(cfg) {
413
422
  }
414
423
  };
415
424
  try {
416
- return storage ? parse(readBrowserStorage(storage, KNOBS_STORAGE_KEY, (current, previous) => JSON.stringify({ ...parse(previous), ...parse(current) }))) : {};
425
+ return storage ? parse(readBrowserStorage(storage, KNOBS_STORAGE_KEY, (current2, previous) => JSON.stringify({ ...parse(previous), ...parse(current2) }))) : {};
417
426
  } catch {
418
427
  return {};
419
428
  }
@@ -448,8 +457,13 @@ function createOverlayVariantsSource(cfg) {
448
457
  if (sentRevision === revision) intended = null;
449
458
  emit();
450
459
  }
460
+ if (reloadAfterPost && live) {
461
+ reloadAfterPost = false;
462
+ live.reload();
463
+ }
451
464
  }
452
465
  function activate(active) {
466
+ if (live?.select(active)) reloadAfterPost = true;
453
467
  intended = active;
454
468
  revision++;
455
469
  error = null;
@@ -494,7 +508,7 @@ function createOverlayVariantsSource(cfg) {
494
508
  },
495
509
  getAxes: axes,
496
510
  getSelections() {
497
- const active = new Set(intended ?? list?.active ?? []);
511
+ const active = new Set(current());
498
512
  const picks = Object.fromEntries(
499
513
  selects().map((axis) => [
500
514
  axis.id,
@@ -518,7 +532,7 @@ function createOverlayVariantsSource(cfg) {
518
532
  const axis = selects().find((a) => a.id === axisId);
519
533
  if (!axis || !isValidSelection(axis, value)) return;
520
534
  const members = new Set(axis.options.map((o) => o.id));
521
- const next = (intended ?? list?.active ?? []).filter((id) => !members.has(id));
535
+ const next = current().filter((id) => !members.has(id));
522
536
  if (value !== OVERLAY_DEFAULT_OPTION) next.push(value);
523
537
  activate(next);
524
538
  },
@@ -567,4 +581,4 @@ export {
567
581
  overlayAxes,
568
582
  createOverlayVariantsSource
569
583
  };
570
- //# sourceMappingURL=chunk-OSETWKGE.js.map
584
+ //# sourceMappingURL=chunk-PUXQGUMQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/browser-storage.ts","../src/core/request.ts","../src/core/variants.ts"],"sourcesContent":["/** Browser-only migration from the former package's default namespaces. */\nexport const FEEDBACK_STORAGE_KEY = 'isotone-feedback';\ntype BrowserStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;\n\nfunction legacyKey(key: string): string | undefined {\n if (key === FEEDBACK_STORAGE_KEY || key.startsWith(`${FEEDBACK_STORAGE_KEY}::draft::`))\n return `iso-iterate-feedback${key.slice(FEEDBACK_STORAGE_KEY.length)}`;\n if (key === 'isotone:knobs') return 'iso-iterate:knobs';\n return undefined;\n}\n\n/** A failed migration remains readable under its old key for the next visit. */\nexport function readBrowserStorage(\n storage: BrowserStorage,\n key: string,\n merge: (current: string | null, previous: string) => string = (current, previous) => current ?? previous,\n): string | null {\n let current: string | null = null;\n try { current = storage.getItem(key); }\n catch { return null; }\n const previousKey = legacyKey(key);\n if (!previousKey) return current;\n let previous: string | null;\n try { previous = storage.getItem(previousKey); }\n catch { return current; }\n if (previous === null) return current;\n const next = merge(current, previous);\n try { writeBrowserStorage(storage, key, next); }\n catch { /* Quota or access restrictions must not hide the previous value. */ }\n return next;\n}\n\nexport function writeBrowserStorage(storage: BrowserStorage, key: string, value: string): void {\n storage.setItem(key, value);\n const previousKey = legacyKey(key);\n if (!previousKey) return;\n try {\n if (storage.getItem(key) === value) storage.removeItem(previousKey);\n } catch { /* Keep the old value unless its replacement was verified. */ }\n}\n\n/** Explicitly clearing a value also clears its legacy fallback. */\nexport function removeBrowserStorage(storage: BrowserStorage, key: string): void {\n storage.removeItem(key);\n const previousKey = legacyKey(key);\n if (previousKey) storage.removeItem(previousKey);\n}\n","type RequestInput = Parameters<typeof fetch>[0];\n\nexport function request(input: RequestInput, init?: RequestInit): Promise<Response>;\nexport function request<T>(input: RequestInput, init: RequestInit | undefined, consume: (response: Response) => Promise<T>): Promise<T>;\n\n/** The deadline includes body consumption, not just receipt of the headers. */\nexport async function request<T>(input: RequestInput, init?: RequestInit, consume?: (response: Response) => Promise<T>): Promise<Response | T> {\n const native = (globalThis as { __isotoneFetch?: typeof fetch }).__isotoneFetch;\n const controller = new AbortController();\n const signal = init?.signal ?? (typeof Request !== 'undefined' && input instanceof Request ? input.signal : undefined);\n const abort = () => controller.abort(signal?.reason);\n if (signal?.aborted) abort();\n else signal?.addEventListener('abort', abort, { once: true });\n const timeout = setTimeout(() => controller.abort(new DOMException('The local server did not respond in time.', 'TimeoutError')), 5_000);\n let aborted: () => void = () => {};\n const cancelled = new Promise<never>((_, reject) => {\n aborted = () => reject(controller.signal.reason);\n if (controller.signal.aborted) aborted();\n else controller.signal.addEventListener('abort', aborted, { once: true });\n });\n try {\n return await Promise.race([cancelled, (async () => {\n if (controller.signal.aborted) throw controller.signal.reason;\n const response = await (native ?? globalThis.fetch)(input, { ...init, signal: controller.signal });\n return consume ? consume(response) : response;\n })()]);\n } finally {\n clearTimeout(timeout);\n signal?.removeEventListener('abort', abort);\n controller.signal.removeEventListener('abort', aborted);\n }\n}\n","/**\n * isotone — variants: the framework-free model.\n *\n * A variant axis is one tunable decision about a page: a discrete choice\n * (select), a continuous value (range) or a colour. The panel renders axes;\n * where their state lives is the host's business. Two hosts ship today:\n *\n * - A host with its own variant system (its own axes and\n * `useVariant` hooks) hands the panel a {@link VariantsSource} over that\n * state. The panel is then a view; the host still owns `?v=` sync,\n * defaults and promotion.\n * - A host with no variant system gets the overlay adapter: alternative\n * source files under `.isotone/variants/<id>/…` that the Vite plugin\n * swaps in for the real ones while a variant is active. The browser sees\n * one select axis per group of alternatives, on the route they belong to\n * ({@link overlayGroups}, {@link createOverlayVariantsSource}).\n *\n * Everything in this module is pure or DOM-free and runs in a `node` test.\n */\n\nimport type { IterationElement } from './index';\nimport { request } from './request';\nimport { readBrowserStorage, removeBrowserStorage, writeBrowserStorage } from './browser-storage';\n\n/* ── Axes ─────────────────────────────────────────────────────────────── */\n\nexport interface VariantOption {\n /** Stable short id, e.g. \"a\", \"b\". */\n id: string;\n /** Short label (1–2 words); it renders as a segment and truncates. */\n label: string;\n /** Optional preview image URL. When every option of an axis has one, the\n * panel renders the axis as a thumbnail grid. */\n image?: string;\n /** Optional secondary text shown as a tooltip on the option. */\n hint?: string;\n}\n\n/** Show an axis only while another axis holds (`equals`) or does not hold\n * (`not`) a value. Exactly one of the two should be set. */\nexport interface VariantAxisCondition {\n axis: string;\n equals?: string;\n not?: string;\n}\n\nexport interface SelectVariantAxis {\n kind?: 'select';\n id: string;\n label: string;\n options: VariantOption[];\n /** The primary option; what a host renders when nothing is selected. */\n defaultOption: string;\n showWhen?: VariantAxisCondition;\n /** Panel grouping: a hairline separates consecutive different sections. */\n section?: string;\n}\n\nexport interface RangeVariantAxis {\n kind: 'range';\n id: string;\n label: string;\n min: number;\n max: number;\n step: number;\n defaultValue: number;\n /** How the panel prints the value (\"px\", \"°\", \"em\"). Omitted: a 0–1 axis\n * prints as a percentage, anything else as the raw number. */\n unit?: string;\n showWhen?: VariantAxisCondition;\n section?: string;\n}\n\nexport interface ColorSwatch {\n id: string;\n label: string;\n /** Hex, e.g. \"#ea382e\". */\n value: string;\n}\n\nexport interface ColorVariantAxis {\n kind: 'color';\n id: string;\n label: string;\n /** Hex. */\n defaultColor: string;\n /** Quick-pick presets shown as swatches (include the default). */\n swatches: ColorSwatch[];\n showWhen?: VariantAxisCondition;\n section?: string;\n}\n\nexport type VariantAxis = SelectVariantAxis | RangeVariantAxis | ColorVariantAxis;\n\n/** Axis id → selected value. Ranges stringify their number. */\nexport type VariantSelections = Record<string, string>;\n\nexport interface VariantAction {\n axisId: string;\n action: 'remove-option' | 'remove-axis' | 'keep-default';\n optionId?: string;\n}\n\n/* ── Pure helpers ─────────────────────────────────────────────────────── */\n\n/** The primary value of an axis, as the selection string. */\nexport function axisDefault(axis: VariantAxis): string {\n if (axis.kind === 'range') return String(axis.defaultValue);\n if (axis.kind === 'color') return axis.defaultColor;\n return axis.defaultOption;\n}\n\n/** True when `value` is legal for the axis. */\nexport function isValidSelection(axis: VariantAxis, value: string): boolean {\n if (axis.kind === 'range') {\n const n = Number(value);\n return value.trim() !== '' && Number.isFinite(n) && n >= axis.min && n <= axis.max;\n }\n if (axis.kind === 'color') return /^#[0-9a-fA-F]{6}$/.test(value);\n return axis.options.some((o) => o.id === value);\n}\n\nexport function defaultSelections(axes: VariantAxis[]): VariantSelections {\n return Object.fromEntries(axes.map((a) => [a.id, axisDefault(a)]));\n}\n\n/** `\"headline:b,haze-speed:0.7\"` → validated selections. Unknown axes and\n * illegal values are dropped rather than failing the whole string. */\nexport function parseVariantParam(axes: VariantAxis[], raw: string): VariantSelections {\n const out = new Map<string, string>();\n const byId = new Map(axes.map((axis) => [axis.id, axis]));\n for (const pair of raw.split(',')) {\n const at = pair.indexOf(':');\n if (at < 0) continue;\n const axisId = pair.slice(0, at);\n const value = pair.slice(at + 1);\n const axis = byId.get(axisId);\n if (axis && isValidSelection(axis, value)) out.set(axisId, value);\n }\n return Object.fromEntries(out);\n}\n\n/** Non-default selections only, `\"\"` when everything is at its default. */\nexport function serializeVariantParam(\n axes: VariantAxis[],\n selections: VariantSelections,\n): string {\n return axes\n .filter((a) => selections[a.id] !== undefined && selections[a.id] !== axisDefault(a))\n .map((a) => `${a.id}:${selections[a.id]}`)\n .join(',');\n}\n\n/** Whether any axis differs from its default. */\nexport function isDirty(axes: VariantAxis[], selections: VariantSelections): boolean {\n return axes.some((a) => selections[a.id] !== undefined && selections[a.id] !== axisDefault(a));\n}\n\n/**\n * Whether an axis shows, given every axis and the current selections.\n * A hidden parent hides its children. Cycles are hidden, and long chains\n * are evaluated iteratively rather than consuming the call stack.\n */\nexport function isAxisVisible(\n axis: VariantAxis,\n axes: VariantAxis[],\n selections: VariantSelections,\n _depth = 0,\n): boolean {\n return visibilityById(axes.includes(axis) ? axes : [...axes, axis], selections).get(axis.id) ?? false;\n}\n\nexport function visibleAxes(axes: VariantAxis[], selections: VariantSelections): VariantAxis[] {\n const visibility = visibilityById(axes, selections);\n return axes.filter((axis) => visibility.get(axis.id));\n}\n\nfunction visibilityById(axes: VariantAxis[], selections: VariantSelections): Map<string, boolean> {\n const byId = new Map(axes.map((axis) => [axis.id, axis]));\n const visible = new Map<string, boolean>();\n for (const axis of axes) {\n const trail = new Set<string>();\n let current: VariantAxis | undefined = axis;\n let result = true;\n while (current) {\n const known = visible.get(current.id);\n if (known !== undefined) { result = known; break; }\n if (trail.has(current.id)) { result = false; break; }\n trail.add(current.id);\n const condition: VariantAxisCondition | undefined = current.showWhen;\n if (!condition) break;\n const parent = byId.get(condition.axis);\n const value = selections[condition.axis] ?? (parent ? axisDefault(parent) : undefined);\n if ((condition.equals !== undefined && value !== condition.equals) ||\n (condition.not !== undefined && value === condition.not)) { result = false; break; }\n current = parent;\n }\n for (const id of trail) visible.set(id, result);\n }\n return visible;\n}\n\n/** How the panel prints a range value: its unit when declared, a percentage\n * for a bare 0–1 axis, else the raw number. */\nexport function formatRangeValue(axis: RangeVariantAxis, value: string): string {\n if (axis.unit !== undefined) return `${value}${axis.unit}`;\n if (axis.min === 0 && axis.max === 1) return `${Math.round(Number(value) * 100)}%`;\n return value;\n}\n\n/* ── The source contract ──────────────────────────────────────────────── */\n\n/**\n * What the panel renders variants from. The host owns the state; the panel\n * reads it through these calls and re-renders on `subscribe` notifications.\n * Every method must be safe to call at any time after mount, including\n * before the host has any axes (return `[]`).\n */\nexport interface VariantsSource {\n /** The axes for the current page. May change over time (route change). */\n getAxes(): VariantAxis[];\n /** The current selections. Missing keys mean \"at default\". */\n getSelections(): VariantSelections;\n setSelection(axisId: string, value: string): void;\n /** Return every axis to its default. */\n reset(): void;\n /** Notify on any change to axes or selections. Returns an unsubscribe. */\n subscribe(listener: () => void): () => void;\n /** A recoverable transport error; notify subscribers when it changes. */\n getError?: () => string | null;\n /**\n * When present, the panel shows \"Save as defaults\" and calls this with the\n * current selections. A host that promotes by editing source (one that\n * files a `[set-defaults]` note for the agent) implements it; a host\n * whose variants are whole alternatives (the overlay adapter) leaves it\n * out and promotes from the CLI instead.\n */\n saveDefaults?: (selections: VariantSelections) => Promise<void> | void;\n /** Panel heading; defaults to \"Variants\". */\n title?: string;\n /** Optional durable host mutation. Reject to leave the UI unchanged. */\n manage?: (action: VariantAction) => Promise<void> | void;\n}\n\n/**\n * A `VariantsSource` over plain state, for hosts that just want to hand the\n * panel an axes array and be told when a selection changes. Handles\n * subscriptions, validation and defaults. `onChange` fires after every\n * selection or reset with the full selections.\n */\nexport function createVariantsSource(init: {\n axes: VariantAxis[];\n selections?: VariantSelections;\n onChange?: (selections: VariantSelections) => void;\n saveDefaults?: VariantsSource['saveDefaults'];\n title?: string;\n /** Persist edited definitions in a host's own storage, before notifying. */\n onManage?: (axes: VariantAxis[], selections: VariantSelections, action: VariantAction) => Promise<void> | void;\n}): VariantsSource & {\n /** Replace the axes (route change); selections not valid for the new set\n * are dropped. */\n setAxes(axes: VariantAxis[]): void;\n /** Replace the selections wholesale (URL hydration). */\n setSelections(selections: VariantSelections): void;\n} {\n let axes = init.axes;\n let selections = defaultSelections(axes);\n for (const axis of axes) {\n const value = init.selections?.[axis.id];\n if (value !== undefined && isValidSelection(axis, value)) selections[axis.id] = value;\n }\n const listeners = new Set<() => void>();\n const emit = () => {\n for (const l of listeners) l();\n };\n const changed = () => {\n init.onChange?.({ ...selections });\n emit();\n };\n return {\n title: init.title,\n saveDefaults: init.saveDefaults,\n getAxes: () => axes,\n getSelections: () => selections,\n async manage(action) {\n const axis = axes.find((a) => a.id === action.axisId);\n if (!axis) throw new Error('This selector is no longer available.');\n let next = [...axes];\n const picks = { ...selections };\n if (action.action === 'remove-option') {\n if (axis.kind === 'range') throw new Error('This selector has no removable options.');\n if (axis.kind === 'color') {\n const swatch = axis.swatches.find(s => s.id === action.optionId);\n if (!swatch || swatch.value.toLowerCase() === axis.defaultColor.toLowerCase()) throw new Error('Keep the default colour, or remove the whole selector.');\n const swatches = axis.swatches.filter(s => s.id !== action.optionId);\n if (picks[axis.id]?.toLowerCase() === swatch.value.toLowerCase()) picks[axis.id] = axis.defaultColor;\n next = next.map(a => a.id === axis.id ? { ...axis, swatches } : a);\n } else {\n if (action.optionId === axis.defaultOption) throw new Error('Keep the default option, or remove the whole selector.');\n if (!axis.options.some((o) => o.id === action.optionId)) throw new Error('This option is no longer available.');\n const options = axis.options.filter((o) => o.id !== action.optionId);\n if (picks[axis.id] === action.optionId) picks[axis.id] = axis.defaultOption;\n next = next.map((a) => a.id === axis.id ? { ...axis, options } : a);\n if (options.length === 1) next = next.filter((a) => a.id !== axis.id);\n }\n } else {\n next = next.filter((a) => a.id !== axis.id);\n if (action.action === 'keep-default') picks[axis.id] = axisDefault(axis);\n else delete picks[axis.id];\n }\n // A dependent control cannot outlive the decision that reveals it.\n const kept = new Set(next.map((a) => a.id));\n const removed = new Set(axes.filter((a) => !kept.has(a.id)).map((a) => a.id));\n const children = new Map<string, string[]>();\n for (const a of next) {\n if (!a.showWhen) continue;\n const dependents = children.get(a.showWhen.axis) ?? [];\n dependents.push(a.id);\n children.set(a.showWhen.axis, dependents);\n }\n // Set iteration includes newly added descendants and terminates on cycles.\n for (const id of removed) for (const child of children.get(id) ?? []) removed.add(child);\n next = next.filter((a) => !removed.has(a.id));\n for (const id of removed) if (id !== axis.id) delete picks[id];\n await init.onManage?.(next, picks, action);\n axes = next;\n selections = picks;\n changed();\n },\n setSelection(axisId, value) {\n const axis = axes.find((a) => a.id === axisId);\n if (!axis || !isValidSelection(axis, value)) return;\n if (selections[axisId] === value) return;\n selections = { ...selections, [axisId]: value };\n changed();\n },\n reset() {\n selections = defaultSelections(axes);\n changed();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n setAxes(next) {\n axes = next;\n const kept: VariantSelections = defaultSelections(next);\n for (const a of next) {\n const v = selections[a.id];\n if (v !== undefined && isValidSelection(a, v)) kept[a.id] = v;\n }\n selections = kept;\n emit();\n },\n setSelections(next) {\n selections = { ...defaultSelections(axes) };\n for (const a of axes) {\n const v = next[a.id];\n if (v !== undefined && isValidSelection(a, v)) selections[a.id] = v;\n }\n changed();\n },\n };\n}\n\n/* ── The overlay adapter's wire shape ─────────────────────────────── */\n\n/**\n * One overlay variant: a directory of alternative source files that the dev\n * server swaps in for the real ones while the variant is active. Stored as\n * `variant.json` in `.isotone/variants/<id>/`, with the files mirrored\n * under it at their repo-relative paths.\n */\nexport interface OverlayVariant {\n /** Directory name. Lowercase, `a-z0-9-` only. */\n id: string;\n /** Panel label. Defaults to the id. */\n label: string;\n /** The route the variant belongs to; `\"\"` (or absent) shows on every route.\n * Only a scope string, like a note's route. */\n route?: string;\n /** The element the variant was asked about — the handle, not a boundary;\n * the changed files may affect more than this element. */\n element?: IterationElement | null;\n /** The note id the variant answers, when it came from one. */\n note?: string;\n /** What the variant tries, in a sentence. */\n description?: string;\n /**\n * The decision this variant is one answer to. Variants in the same group\n * are alternatives of one element and share a selector in the panel;\n * activating one deactivates its siblings. Same shape as an id. Absent,\n * the group is inferred from file overlap (see {@link overlayGroups}).\n */\n group?: string;\n /** What the panel calls the group's selector. Any member may carry it. */\n groupLabel?: string;\n createdAt: string;\n /**\n * Repo-relative source path → what the override was derived from. `base`\n * is the git blob id of the real file when the override was made (`null`\n * when the file was not hashable), so a later change to the real file can\n * be detected and three-way merged into the override.\n */\n /** `added`: a file the project does not have; the variant introduces it. */\n files: Record<string, { base: string | null; added?: true }>;\n}\n\n/** An overlay variant as the endpoint lists it: the manifest plus what the\n * server computed about it. */\nexport interface OverlayVariantStatus extends OverlayVariant {\n /** Files whose real source has changed since the override was made. */\n stale: string[];\n /** Files the manifest names but the variant directory no longer holds. */\n missing: string[];\n}\n\n/**\n * A knob: a range or colour axis the host declares in `.isotone/knobs.json`\n * next to its overlay variants. The panel renders it as a slider (or a colour\n * pick) on the routes it names, and the value reaches the page as a CSS\n * custom property on `<html>`, `--knob-<id>`, with the axis unit appended\n * (`--knob-fade-from: 40%`). The host reads it with a fallback that is the\n * committed default: `var(--knob-fade-from, 0%)`. \"Save as defaults\" files\n * the moved knobs in the same `[set-defaults]` note as the variant picks, so\n * the agent writes the numbers into the source and the knob file.\n */\nexport type KnobAxis = (RangeVariantAxis | ColorVariantAxis) & {\n /** Route pattern(s) the knob belongs on, like a variant's `route`; absent means every route. */\n route?: string | string[];\n};\n\n/** `GET` body of the variants endpoint. */\nexport interface OverlayVariantsList {\n variants: OverlayVariantStatus[];\n /** The active variant ids, at most one per group; `[]` is the default. */\n active: string[];\n /** The host's knobs, every route; the client filters by route. */\n knobs?: KnobAxis[];\n}\n\n/** The custom property a knob writes: `--knob-<id>`. */\nexport function knobVar(id: string): string {\n return `--knob-${id}`;\n}\n\n/** The property's value: a range carries its unit, a colour is its hex. */\nexport function knobCssValue(axis: KnobAxis, value: string): string {\n return axis.kind === 'range' ? `${value}${axis.unit ?? ''}` : value;\n}\n\n/** The knobs that belong on `route`. */\nexport function knobsFor(knobs: KnobAxis[] | undefined, route: string): KnobAxis[] {\n return (knobs ?? []).filter((k) => {\n const patterns = k.route === undefined ? [undefined] : ([] as (string | undefined)[]).concat(k.route);\n return patterns.some((p) => variantAppliesTo(p, route));\n });\n}\n\n/** Write every knob's value onto the root element; missing values clear the property. */\nexport function applyKnobValues(\n knobs: KnobAxis[],\n values: VariantSelections,\n root: { style: { setProperty(name: string, value: string): void; removeProperty(name: string): void } },\n): void {\n for (const k of knobs) {\n const v = values[k.id];\n if (v === undefined || v === axisDefault(k)) root.style.removeProperty(knobVar(k.id));\n else root.style.setProperty(knobVar(k.id), knobCssValue(k, v));\n }\n}\n\n/** The knobs that sit off their default, with the value the panel showed. */\nexport function knobDelta(\n knobs: KnobAxis[],\n values: VariantSelections,\n): { axis: KnobAxis; value: string; shown: string }[] {\n return knobs.flatMap((k) => {\n const v = values[k.id];\n if (v === undefined || v === axisDefault(k)) return [];\n const shown = k.kind === 'range' ? formatRangeValue(k, v) : v;\n return [{ axis: k, value: v, shown }];\n });\n}\n\n/** The endpoint path the client calls. */\nexport const VARIANTS_ENDPOINT = '/api/iteration/variants';\n\n/** Where the browser keeps knob values between reloads. */\nexport const KNOBS_STORAGE_KEY = 'isotone:knobs';\n\n/** The option id that means \"no variant, the real files\". */\nexport const OVERLAY_DEFAULT_OPTION = 'default';\n\n/** The shape an explicit `group` has to take: an id, like a variant's. */\nexport const OVERLAY_GROUP_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;\n\n/** A set of alternatives to one decision, as the panel renders it. */\nexport interface OverlayGroup<T extends OverlayVariant = OverlayVariant> {\n /** The explicit `group`, else a slug of the sorted member ids. */\n id: string;\n label: string;\n /** In listing order. */\n members: T[];\n}\n\n/**\n * Partition variants into the decisions they answer. An explicit `group`\n * wins; the rest are joined by file overlap, because two copies of the same\n * file can never both be active, so variants sharing a file are alternatives\n * whether or not anyone said so. Overlap is transitive (connected\n * components): if A shares a file with B and B with C, all three are one\n * group even when A and C touch different files.\n *\n * The label is any member's `groupLabel`, else the basename of a file every\n * member overrides (the element's own file, in practice), else a lone\n * member's label, else the group id. Pure, so the panel, the CLI and the\n * tests all agree on the grouping.\n */\nexport function overlayGroups<T extends OverlayVariant>(variants: T[]): OverlayGroup<T>[] {\n const explicit = new Map<string, T[]>();\n const loose: T[] = [];\n for (const v of variants) {\n if (v.group) {\n const members = explicit.get(v.group) ?? [];\n members.push(v);\n explicit.set(v.group, members);\n } else {\n loose.push(v);\n }\n }\n\n // Union-find over the loose variants, keyed by index into `loose`.\n const parent = loose.map((_, i) => i);\n const find = (i: number): number => {\n while (parent[i] !== i) {\n parent[i] = parent[parent[i]];\n i = parent[i];\n }\n return i;\n };\n const owner = new Map<string, number>();\n loose.forEach((v, i) => {\n for (const file of Object.keys(v.files)) {\n const other = owner.get(file);\n if (other === undefined) owner.set(file, i);\n else parent[find(i)] = find(other);\n }\n });\n const inferred = new Map<number, T[]>();\n loose.forEach((v, i) => {\n const root = find(i);\n const members = inferred.get(root) ?? [];\n members.push(v);\n inferred.set(root, members);\n });\n\n // Emit in listing order of each group's first member, so a new variant\n // appends a selector instead of reshuffling the ones the reviewer knows.\n const groups: OverlayGroup<T>[] = [];\n const index = new Map(loose.map((variant, i) => [variant, i]));\n const seen = new Set<T[]>();\n for (const v of variants) {\n const members = v.group ? explicit.get(v.group) : inferred.get(find(index.get(v) ?? 0));\n if (!members) continue;\n if (seen.has(members)) continue;\n seen.add(members);\n const id = v.group ?? members.map((m) => m.id).sort().join('+');\n groups.push({ id, label: groupLabel(id, members), members });\n }\n return groups;\n}\n\nfunction groupLabel(id: string, members: OverlayVariant[]): string {\n const named = members.find((m) => m.groupLabel);\n if (named?.groupLabel) return named.groupLabel;\n const [first, ...rest] = members;\n const shared = Object.keys(first?.files ?? {}).find((file) =>\n rest.every((m) => Object.hasOwn(m.files, file)),\n );\n if (shared) return shared.slice(shared.lastIndexOf('/') + 1);\n if (members.length === 1 && first) return first.label || first.id;\n return id;\n}\n\n/**\n * The overlay adapter's axes for `route`: one select per group that has a\n * member on this route (a member made for it or for every route), with\n * Default plus those members. A selector belongs to the page its element is\n * on and nowhere else, active or not: an active variant\n * made for another route keeps applying, since the file swap is global, but\n * the place to switch it back is that route's page. Pure, so the panel and\n * the tests can build it from a listing.\n */\n/**\n * Does a variant scoped to `pattern` belong on `route`? No pattern means\n * everywhere; an exact route means that page; a pattern ending in `/*`\n * means the base and every page under it, so a variant made on one\n * detail page (`/items/42`) can be filed for `/items/*` and show on all.\n */\nexport function variantAppliesTo(pattern: string | undefined, route: string): boolean {\n if (!pattern) return true;\n if (pattern.endsWith('/*')) {\n const base = pattern.slice(0, -2);\n return route === base || route.startsWith(`${base}/`);\n }\n return pattern === route;\n}\n\nexport function overlayAxes(list: OverlayVariantsList, route: string): SelectVariantAxis[] {\n const axes: SelectVariantAxis[] = [];\n for (const group of overlayGroups(list.variants)) {\n const mine = group.members.filter((v) => variantAppliesTo(v.route, route));\n if (mine.length === 0) continue;\n axes.push({\n id: group.id,\n label: group.label,\n defaultOption: OVERLAY_DEFAULT_OPTION,\n options: [\n { id: OVERLAY_DEFAULT_OPTION, label: 'Default', hint: 'The committed source' },\n ...mine.map((v) => ({ id: v.id, label: v.label || v.id, hint: overlayHint(v) })),\n ],\n });\n }\n return axes;\n}\n\nfunction overlayHint(v: OverlayVariantStatus): string {\n const parts: string[] = [];\n if (v.description) parts.push(v.description);\n if (v.element) {\n parts.push(`↳ <${v.element.tag}>${v.element.text ? ` \"${v.element.text}\"` : ''}`);\n }\n const files = Object.keys(v.files);\n if (files.length) parts.push(files.join(', '));\n if (v.stale.length) parts.push(`stale: ${v.stale.join(', ')}`);\n if (v.missing.length) parts.push(`missing: ${v.missing.join(', ')}`);\n return parts.join('\\n');\n}\n\n/**\n * The tab's live variant store, which the Vite plugin creates on the page\n * in live mode (`window.__isotoneLive`). Only the part the panel uses.\n */\nexport interface LiveVariantSelection {\n readonly active: string[];\n /** Switch the tab; true when the page has to reload to show it. */\n select(next: string[]): boolean;\n subscribe(listener: () => void): () => void;\n reload(): void;\n}\n\nfunction pageLiveStore(): LiveVariantSelection | null {\n if (typeof window === 'undefined') return null;\n const store = (window as { __isotoneLive?: LiveVariantSelection & { version?: number } }).__isotoneLive;\n return store && store.version === 1 ? store : null;\n}\n\n/**\n * A `VariantsSource` over the overlay endpoint. One select axis per group on\n * the route; choosing an option replaces that group's active member and\n * posts the whole active set. In live mode the tab's store switches the page\n * at once (or asks for a reload), and the post makes the pick the server's\n * default; otherwise the server answers the post by hot-swapping the files.\n * `refresh()` re-reads the listing; the panel calls it when it opens.\n */\nexport function createOverlayVariantsSource(cfg: {\n /** Absolute or same-origin URL of the variants endpoint. */\n endpoint: string;\n getRoute: () => string;\n /** Where knob values live between reloads; omit for none (tests). Default: `localStorage`. */\n storage?: Pick<Storage, 'getItem' | 'setItem' | 'removeItem'> | null;\n /** The element knobs write their properties on. Default: `document.documentElement`. */\n knobRoot?: Parameters<typeof applyKnobValues>[2] | null;\n /** The tab's live store. Default: the one the Vite plugin put on the page, if any. */\n live?: LiveVariantSelection | null;\n}): VariantsSource & {\n refresh(): Promise<void>;\n readonly list: OverlayVariantsList | null;\n /** The knobs on the current route and their values, for the save note. */\n readonly knobs: { axes: KnobAxis[]; values: VariantSelections };\n} {\n let list: OverlayVariantsList | null = null;\n let error: string | null = null;\n let intended: string[] | null = null;\n let activation: Promise<void> | null = null;\n let revision = 0;\n let refreshRevision = 0;\n let reloadAfterPost = false;\n const listeners = new Set<() => void>();\n const emit = () => {\n for (const l of listeners) l();\n };\n const live = cfg.live === undefined ? pageLiveStore() : cfg.live;\n // The server's default can move the tab too (a CLI `variant use`).\n live?.subscribe(emit);\n const current = (): string[] => live?.active ?? intended ?? list?.active ?? [];\n const selects = (): SelectVariantAxis[] => (list ? overlayAxes(list, cfg.getRoute()) : []);\n const knobAxes = (): KnobAxis[] => knobsFor(list?.knobs, cfg.getRoute());\n const axes = (): VariantAxis[] => [...selects(), ...knobAxes()];\n\n // Knob values are review-time state, kept in the browser rather than on\n // the server: a slider is a look, not a file swap.\n const storage = (() => {\n if (cfg.storage !== undefined) return cfg.storage;\n try { return typeof localStorage === 'undefined' ? null : localStorage; }\n catch { return null; }\n })();\n const knobRoot =\n cfg.knobRoot === undefined\n ? typeof document === 'undefined'\n ? null\n : document.documentElement\n : cfg.knobRoot;\n let knobValues: VariantSelections = (() => {\n const parse = (raw: string | null): VariantSelections => {\n try {\n const parsed = raw ? (JSON.parse(raw) as unknown) : null;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === 'string')) : {};\n } catch { return {}; }\n };\n try {\n return storage ? parse(readBrowserStorage(storage, KNOBS_STORAGE_KEY, (current, previous) =>\n JSON.stringify({ ...parse(previous), ...parse(current) }))) : {};\n } catch {\n return {};\n }\n })();\n const persistKnobs = () => {\n try {\n if (!storage) return;\n if (Object.keys(knobValues).length === 0) removeBrowserStorage(storage, KNOBS_STORAGE_KEY);\n else writeBrowserStorage(storage, KNOBS_STORAGE_KEY, JSON.stringify(knobValues));\n } catch {\n // Storage full or blocked: the values still apply for this page view.\n }\n };\n const applyKnobs = () => {\n if (knobRoot) applyKnobValues(list?.knobs ?? [], knobValues, knobRoot);\n };\n async function drainActivations(): Promise<void> {\n while (intended !== null) {\n const active = intended;\n const sentRevision = revision;\n try {\n const res = await request(`${cfg.endpoint}/active`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ active }),\n });\n if (!res.ok) throw new Error(`Activating variants failed (${res.status}). Try again.`);\n if (list) list = { ...list, active };\n error = null;\n } catch (cause) {\n error = cause instanceof Error ? cause.message : 'Could not activate this variant. Try again.';\n }\n // Newer picks include the full intended set, including other groups.\n // Completing or failing an older request must not discard that intent.\n if (sentRevision === revision) intended = null;\n emit();\n }\n // The tab already shows the pick in its URL; a server render reads the\n // set just posted, so the reload waits for it.\n if (reloadAfterPost && live) {\n reloadAfterPost = false;\n live.reload();\n }\n }\n function activate(active: string[]): void {\n if (live?.select(active)) reloadAfterPost = true;\n intended = active;\n revision++;\n error = null;\n emit();\n scheduleActivation();\n }\n function scheduleActivation(): void {\n if (activation) return;\n activation = Promise.resolve().then(drainActivations).finally(() => {\n activation = null;\n if (intended !== null) scheduleActivation();\n });\n }\n return {\n title: 'Variants',\n getError: () => error,\n get list() {\n return list;\n },\n get knobs() {\n return { axes: knobAxes(), values: { ...knobValues } };\n },\n async refresh() {\n const refreshId = ++refreshRevision;\n const startedRevision = revision;\n const wasActivating = activation !== null;\n const next = await request(cfg.endpoint, undefined, async (res) => {\n if (!res.ok) throw new Error(`Loading variants failed (${res.status})`);\n return await res.json() as OverlayVariantsList;\n });\n if (refreshId !== refreshRevision) return;\n // An older GET must not roll back a successful POST that finished while\n // the listing was loading. Definitions can still refresh independently.\n const previousKnobs = list?.knobs ?? [];\n list = list && (wasActivating || activation !== null || startedRevision !== revision)\n ? { ...next, active: list.active } : next;\n if (knobRoot) applyKnobValues(previousKnobs, {}, knobRoot);\n const valid = new Map((list.knobs ?? []).map((knob) => [knob.id, knob]));\n knobValues = Object.fromEntries(Object.entries(knobValues).filter(([id, value]) => {\n const knob = valid.get(id);\n return knob && isValidSelection(knob, value);\n }));\n applyKnobs();\n emit();\n },\n getAxes: axes,\n getSelections() {\n const active = new Set(current());\n const picks = Object.fromEntries(\n selects().map((axis) => [\n axis.id,\n axis.options.find((o) => active.has(o.id))?.id ?? OVERLAY_DEFAULT_OPTION,\n ]),\n );\n for (const k of knobAxes()) picks[k.id] = knobValues[k.id] ?? axisDefault(k);\n return picks;\n },\n setSelection(axisId, value) {\n const knob = knobAxes().find((k) => k.id === axisId);\n if (knob) {\n if (!isValidSelection(knob, value)) return;\n if (value === axisDefault(knob)) delete knobValues[axisId];\n else knobValues = { ...knobValues, [axisId]: value };\n persistKnobs();\n applyKnobs();\n emit();\n return;\n }\n const axis = selects().find((a) => a.id === axisId);\n if (!axis || !isValidSelection(axis, value)) return;\n // The group's members leave the set and the pick joins it; the other\n // groups' picks ride along untouched.\n const members = new Set(axis.options.map((o) => o.id));\n const next = current().filter((id) => !members.has(id));\n if (value !== OVERLAY_DEFAULT_OPTION) next.push(value);\n activate(next);\n },\n reset() {\n knobValues = {};\n persistKnobs();\n applyKnobs();\n activate([]);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n"],"mappings":"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","names":["current"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  OVERLAY_GROUP_ID
3
- } from "./chunk-OSETWKGE.js";
3
+ } from "./chunk-PUXQGUMQ.js";
4
4
  import {
5
5
  ensureFileIgnored,
6
6
  withFileLock,
@@ -76,7 +76,7 @@ function isSafeManifestKey(key) {
76
76
  function isPlausibleVariant(v) {
77
77
  if (!v || typeof v !== "object" || Array.isArray(v)) return false;
78
78
  const o = v;
79
- return typeof o.id === "string" && typeof o.label === "string" && typeof o.createdAt === "string" && (o.route === void 0 || typeof o.route === "string") && (o.description === void 0 || typeof o.description === "string") && (o.group === void 0 || typeof o.group === "string" && OVERLAY_GROUP_ID.test(o.group)) && (o.groupLabel === void 0 || typeof o.groupLabel === "string") && !!o.files && typeof o.files === "object" && !Array.isArray(o.files) && Object.entries(o.files).every(([key, entry]) => isSafeManifestKey(key) && entry && typeof entry === "object" && "base" in entry && (entry.base === null || typeof entry.base === "string" && /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.base)));
79
+ return typeof o.id === "string" && typeof o.label === "string" && typeof o.createdAt === "string" && (o.route === void 0 || typeof o.route === "string") && (o.description === void 0 || typeof o.description === "string") && (o.group === void 0 || typeof o.group === "string" && OVERLAY_GROUP_ID.test(o.group)) && (o.groupLabel === void 0 || typeof o.groupLabel === "string") && !!o.files && typeof o.files === "object" && !Array.isArray(o.files) && Object.entries(o.files).every(([key, entry]) => isSafeManifestKey(key) && entry && typeof entry === "object" && "base" in entry && (entry.base === null || typeof entry.base === "string" && /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.base)) && (!("added" in entry) || entry.added === true));
80
80
  }
81
81
  function readVariant(opts, id) {
82
82
  if (!isValidVariantId(id)) return null;
@@ -100,6 +100,11 @@ function writeVariant(opts, v) {
100
100
  function isStale(base, currentHash) {
101
101
  return base === null || currentHash === null || currentHash !== base;
102
102
  }
103
+ function readVariants(opts) {
104
+ const dir = variantsDir(opts);
105
+ if (!existsSync(dir)) return [];
106
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => readVariant(opts, e.name)).filter((v) => v !== null).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
107
+ }
103
108
  function listVariants(opts) {
104
109
  const dir = variantsDir(opts);
105
110
  if (!existsSync(dir)) return [];
@@ -118,6 +123,10 @@ function listVariants(opts) {
118
123
  missing.push(file);
119
124
  continue;
120
125
  }
126
+ if (entry.added) {
127
+ if (existsSync(join(root, file))) stale.push(file);
128
+ continue;
129
+ }
121
130
  const hashKey = `${entry.base?.length}:${file}`;
122
131
  if (!hashes.has(hashKey)) hashes.set(hashKey, hashFile(join(root, file), entry.base));
123
132
  if (isStale(entry.base, hashes.get(hashKey) ?? null)) stale.push(file);
@@ -168,16 +177,24 @@ function setVariantGroup(opts, id, group, groupLabel) {
168
177
  return manifest;
169
178
  });
170
179
  }
171
- function addVariantFile(opts, id, file) {
180
+ function addVariantFile(opts, id, file, { create = false } = {}) {
172
181
  return mutateVariants(opts, () => {
173
182
  const manifest = readVariant(opts, id);
174
183
  if (!manifest) throw new Error(`No variant "${id}"`);
175
184
  const root = resolve(opts.root);
176
185
  const { abs: sourceAbs, key } = repoRelativeKey(root, file);
177
186
  if (Object.hasOwn(manifest.files, key)) return manifest;
178
- if (!existsSync(sourceAbs)) throw new Error(`"${file}" does not exist`);
179
187
  const destAbs = join(variantDir(opts, id), key);
180
188
  if (!staysInside(variantDir(opts, id), destAbs)) throw new Error("Override path escapes the variant directory");
189
+ if (create) {
190
+ if (existsSync(sourceAbs)) throw new Error(`"${file}" already exists; add it without --new`);
191
+ mkdirSync(dirname(destAbs), { recursive: true });
192
+ if (!existsSync(destAbs)) writeFileSync(destAbs, "");
193
+ manifest.files = { ...manifest.files, [key]: { base: null, added: true } };
194
+ writeVariant(opts, manifest);
195
+ return manifest;
196
+ }
197
+ if (!existsSync(sourceAbs)) throw new Error(`"${file}" does not exist. Use --new for a file the variant adds.`);
181
198
  mkdirSync(dirname(destAbs), { recursive: true });
182
199
  writeFileSync(destAbs, readFileSync(sourceAbs));
183
200
  manifest.files = { ...manifest.files, [key]: { base: blobOf(sourceAbs) } };
@@ -328,6 +345,10 @@ function syncVariant(opts, id) {
328
345
  for (const [file, entry] of Object.entries(manifest.files)) {
329
346
  const currentAbs = join(root, file);
330
347
  const overrideAbs = join(vDir, file);
348
+ if (entry.added) {
349
+ (existsSync(currentAbs) ? conflicts : unchanged).push(file);
350
+ continue;
351
+ }
331
352
  const currentHash = currentBlob(currentAbs);
332
353
  if (!isStale(entry.base, currentHash)) {
333
354
  unchanged.push(file);
@@ -510,6 +531,7 @@ export {
510
531
  variantsDir,
511
532
  variantDir,
512
533
  readVariant,
534
+ readVariants,
513
535
  listVariants,
514
536
  createVariant,
515
537
  setVariantGroup,
@@ -524,4 +546,4 @@ export {
524
546
  writeActive,
525
547
  variantsResponse
526
548
  };
527
- //# sourceMappingURL=chunk-UT2NLGBM.js.map
549
+ //# sourceMappingURL=chunk-TGAU3WTF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/variants-store.ts"],"sourcesContent":["/**\n * isotone — overlay variants store (node only).\n *\n * An overlay variant is a directory of alternative source files under\n * `.isotone/variants/<id>/`, mirrored at their repo-relative paths, plus\n * a `variant.json` manifest recording which git blob each override was made\n * from. That blob is the three-way merge base: when the real file moves on,\n * `syncVariant` diffs the override against what it started from and what\n * the file is now, the same way a branch rebases. Everything here is\n * synchronous and framework-free so the CLI and the Vite plugin (a\n * different owner) can both sit on top of it.\n */\nimport { execFileSync } from 'node:child_process';\nimport { createHash, randomUUID } from 'node:crypto';\nimport {\n existsSync,\n mkdirSync,\n lstatSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport type { IterationElement } from './core/index';\nimport {\n type ColorVariantAxis,\n type KnobAxis,\n OVERLAY_GROUP_ID,\n type OverlayVariant,\n type OverlayVariantStatus,\n type RangeVariantAxis,\n} from './core/variants';\nimport { ensureFileIgnored, type NotesRequest, type NotesResponse } from './store';\nimport { withFileLock, writeJson } from './file-store';\n\n/** Default location of the variants tree, relative to the project root. */\nexport const DEFAULT_VARIANTS_DIR = '.isotone/variants';\n\n/** Relative to root. Holds `{ \"active\": [\"<id>\", ...] }`, at most one id per\n * group. The Vite plugin keeps this out of git; nothing here assumes that. */\nexport const ACTIVE_FILE = '.isotone/active.json';\n\n/** Relative to root. A JSON array of {@link KnobAxis}; committed, since the\n * defaults in it are part of the design. Missing or malformed reads as none. */\nexport const KNOBS_FILE = '.isotone/knobs.json';\n\nexport interface VariantsStoreOptions {\n /** Project root: Vite's root or the CLI's cwd. Absolute. */\n root: string;\n /** Variants directory, relative to root or absolute. Default {@link DEFAULT_VARIANTS_DIR}. */\n dir?: string;\n}\n\ninterface ActiveFileShape {\n active: string[];\n}\n\n/** Agents in separate processes may edit the same manifest. Keep their\n * read-modify-write operations together, including promotion and removal. */\nfunction mutateVariants<T>(opts: VariantsStoreOptions, change: () => T): T {\n return withFileLock(join(variantsDir(opts), '.mutation'), change);\n}\n\n/* ── paths ────────────────────────────────────────────────────────────── */\n\nexport function variantsDir(opts: VariantsStoreOptions): string {\n const dir = opts.dir ?? DEFAULT_VARIANTS_DIR;\n return isAbsolute(dir) ? dir : resolve(opts.root, dir);\n}\n\nexport function variantDir(opts: VariantsStoreOptions, id: string): string {\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n return join(variantsDir(opts), id);\n}\n\nexport function isValidVariantId(id: string): boolean {\n return /^[a-z0-9][a-z0-9-]{0,63}$/.test(id);\n}\n\n/** POSIX-ify a path node gave us as platform-native (a no-op on POSIX,\n * where `sep` already is `/`; keeps manifest keys portable regardless). */\nfunction toPosixPath(p: string): string {\n return p.split(sep).join('/');\n}\n\n/** `relative()` puts you outside the base with a leading `..` (or, on a\n * different drive on Windows, an absolute path back out) — either way the\n * caller asked for something outside the tree we're allowed to touch. */\nfunction isEscaping(rel: string): boolean {\n return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);\n}\n\n/** Check existing ancestors too: a lexical child may be a symlink outside\n * the tree, including a not-yet-created file beneath a symlinked directory. */\nfunction staysInside(base: string, file: string): boolean {\n const canonical = (path: string): string => {\n let existing = resolve(path);\n while (dirname(existing) !== existing) {\n try { lstatSync(existing); break; } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n existing = dirname(existing);\n }\n }\n return resolve(realpathSync(existing), relative(existing, resolve(path)));\n };\n return !isEscaping(relative(canonical(base), canonical(file)));\n}\n\n/**\n * Resolve `file` (repo-relative, or absolute inside root) against `root`\n * and return both the absolute path and its POSIX repo-relative manifest\n * key. Throws for anything that escapes root, so every caller that takes a\n * user-supplied path gets the same guard.\n */\nfunction repoRelativeKey(root: string, file: string): { abs: string; key: string } {\n const abs = isAbsolute(file) ? resolve(file) : resolve(root, file);\n const rel = relative(root, abs);\n if (!rel || isEscaping(rel) || !isSafeManifestKey(toPosixPath(rel)) || !staysInside(root, abs)) {\n throw new Error(`\"${file}\" escapes the project root`);\n }\n return { abs, key: toPosixPath(rel) };\n}\n\n/* ── manifest read/write ──────────────────────────────────────────────── */\n\n/**\n * A manifest key is joined onto the variant dir and onto the project root,\n * so a key that climbs out of either (`../../x`, an absolute path, or a\n * backslash that Windows would read as a separator) would let a hand-edited\n * or checked-in variant.json reach files outside both trees. Such a manifest\n * is not a variant at all.\n */\nfunction isSafeManifestKey(key: string): boolean {\n if (!key || isAbsolute(key) || /[\\\\:\\0]/.test(key) || key === 'variant.json') return false;\n return !key.split('/').some((segment) => !segment || segment === '.' || segment === '..' || segment === '.git');\n}\n\nfunction isPlausibleVariant(v: unknown): v is OverlayVariant {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return false;\n const o = v as Record<string, unknown>;\n return (\n typeof o.id === 'string' &&\n typeof o.label === 'string' &&\n typeof o.createdAt === 'string' &&\n (o.route === undefined || typeof o.route === 'string') &&\n (o.description === undefined || typeof o.description === 'string') &&\n (o.group === undefined || (typeof o.group === 'string' && OVERLAY_GROUP_ID.test(o.group))) &&\n (o.groupLabel === undefined || typeof o.groupLabel === 'string') &&\n !!o.files &&\n typeof o.files === 'object' &&\n !Array.isArray(o.files) &&\n Object.entries(o.files).every(([key, entry]) => isSafeManifestKey(key) && entry && typeof entry === 'object' &&\n ('base' in entry) && (entry.base === null || (typeof entry.base === 'string' && /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.base))) &&\n (!('added' in entry) || entry.added === true))\n );\n}\n\nexport function readVariant(opts: VariantsStoreOptions, id: string): OverlayVariant | null {\n // An id that is not a valid directory name (`..`, a path with a slash)\n // would resolve to somewhere outside the variants dir; there is no\n // variant there by definition.\n if (!isValidVariantId(id)) return null;\n const file = join(variantDir(opts, id), 'variant.json');\n if (!existsSync(file)) return null;\n try {\n const data = JSON.parse(readFileSync(file, 'utf8'));\n const dir = variantDir(opts, id);\n return isPlausibleVariant(data) && data.id === id && staysInside(variantsDir(opts), file) &&\n Object.keys(data.files).every((key) => staysInside(opts.root, join(opts.root, key)) && staysInside(dir, join(dir, key))) ? data : null;\n } catch {\n // Junk directory: unreadable or not JSON. listVariants skips it.\n return null;\n }\n}\n\nexport function writeVariant(opts: VariantsStoreOptions, v: OverlayVariant): void {\n if (!isPlausibleVariant(v)) throw new Error('Invalid variant manifest');\n const dir = variantDir(opts, v.id);\n if (!staysInside(variantsDir(opts), dir)) throw new Error('Variant directory escapes the variants tree');\n mkdirSync(dir, { recursive: true });\n writeJson(join(dir, 'variant.json'), v);\n}\n\n/** Whether a tracked file has drifted from what its override was derived\n * from. `base: null` (never hashed, or the manifest was hand-edited to\n * simulate an unreachable blob) counts as stale too: there's nothing to\n * compare against, so we can't claim it's unchanged. */\nfunction isStale(base: string | null, currentHash: string | null): boolean {\n return base === null || currentHash === null || currentHash !== base;\n}\n\n/** Every valid manifest, oldest first, without the per-file status that\n * {@link listVariants} hashes for. */\nexport function readVariants(opts: VariantsStoreOptions): OverlayVariant[] {\n const dir = variantsDir(opts);\n if (!existsSync(dir)) return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => readVariant(opts, e.name))\n .filter((v): v is OverlayVariant => v !== null)\n .sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n}\n\nexport function listVariants(opts: VariantsStoreOptions): OverlayVariantStatus[] {\n const dir = variantsDir(opts);\n if (!existsSync(dir)) return [];\n const root = resolve(opts.root);\n const ids = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n\n const statuses: OverlayVariantStatus[] = [];\n const hashes = new Map<string, string | null>();\n for (const id of ids) {\n const manifest = readVariant(opts, id);\n if (!manifest) continue; // no manifest, or invalid JSON: not a variant\n const stale: string[] = [];\n const missing: string[] = [];\n for (const [file, entry] of Object.entries(manifest.files)) {\n const overrideAbs = join(dir, id, file);\n if (!existsSync(overrideAbs)) {\n missing.push(file);\n continue;\n }\n // A file the variant adds is current for as long as the project still\n // lacks it; once a real one appears, the two need a person.\n if (entry.added) {\n if (existsSync(join(root, file))) stale.push(file);\n continue;\n }\n const hashKey = `${entry.base?.length}:${file}`;\n if (!hashes.has(hashKey)) hashes.set(hashKey, hashFile(join(root, file), entry.base));\n if (isStale(entry.base, hashes.get(hashKey) ?? null)) stale.push(file);\n }\n statuses.push({ ...manifest, stale, missing });\n }\n statuses.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n return statuses;\n}\n\nexport function createVariant(\n opts: VariantsStoreOptions,\n input: {\n id: string;\n label?: string;\n route?: string;\n element?: IterationElement | null;\n note?: string;\n description?: string;\n group?: string;\n groupLabel?: string;\n },\n): OverlayVariant {\n return mutateVariants(opts, () => {\n if (!isValidVariantId(input.id)) {\n throw new Error(`Invalid variant id \"${input.id}\"`);\n }\n if (input.group !== undefined) assertGroupId(input.group);\n if (readVariant(opts, input.id)) {\n throw new Error(`Variant \"${input.id}\" already exists`);\n }\n const variant: OverlayVariant = {\n id: input.id,\n label: input.label || input.id,\n createdAt: new Date().toISOString(),\n files: {},\n ...(input.route !== undefined ? { route: input.route } : {}),\n ...(input.element !== undefined ? { element: input.element } : {}),\n ...(input.note !== undefined ? { note: input.note } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.group !== undefined ? { group: input.group } : {}),\n ...(input.groupLabel !== undefined ? { groupLabel: input.groupLabel } : {}),\n };\n writeVariant(opts, variant);\n return variant;\n });\n}\n\nfunction assertGroupId(group: string): void {\n if (!OVERLAY_GROUP_ID.test(group)) {\n throw new Error(`Invalid group id \"${group}\" (lowercase letters, digits and hyphens)`);\n }\n}\n\n/** Put an existing variant into a group, so two variants made separately\n * become alternatives of one decision. `groupLabel` is left as it was\n * unless given. */\nexport function setVariantGroup(\n opts: VariantsStoreOptions,\n id: string,\n group: string,\n groupLabel?: string,\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n assertGroupId(group);\n manifest.group = group;\n if (groupLabel !== undefined) manifest.groupLabel = groupLabel;\n writeVariant(opts, manifest);\n return manifest;\n });\n}\n\n/**\n * Copy a real file into the variant. With `create`, the file is one the\n * project does not have yet: the variant gets an empty file at that path,\n * and the plugin resolves imports of it while the variant is loaded.\n */\nexport function addVariantFile(\n opts: VariantsStoreOptions,\n id: string,\n file: string,\n { create = false }: { create?: boolean } = {},\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const { abs: sourceAbs, key } = repoRelativeKey(root, file);\n // Idempotent: a file already tracked keeps its override and its base\n // untouched. Re-adding is a no-op, not a refresh (sync is how you refresh).\n if (Object.hasOwn(manifest.files, key)) return manifest;\n const destAbs = join(variantDir(opts, id), key);\n if (!staysInside(variantDir(opts, id), destAbs)) throw new Error('Override path escapes the variant directory');\n if (create) {\n if (existsSync(sourceAbs)) throw new Error(`\"${file}\" already exists; add it without --new`);\n mkdirSync(dirname(destAbs), { recursive: true });\n if (!existsSync(destAbs)) writeFileSync(destAbs, '');\n manifest.files = { ...manifest.files, [key]: { base: null, added: true } };\n writeVariant(opts, manifest);\n return manifest;\n }\n if (!existsSync(sourceAbs)) throw new Error(`\"${file}\" does not exist. Use --new for a file the variant adds.`);\n mkdirSync(dirname(destAbs), { recursive: true });\n writeFileSync(destAbs, readFileSync(sourceAbs));\n manifest.files = { ...manifest.files, [key]: { base: blobOf(sourceAbs) } };\n writeVariant(opts, manifest);\n return manifest;\n });\n}\n\nexport function removeVariantFile(\n opts: VariantsStoreOptions,\n id: string,\n file: string,\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const { key } = repoRelativeKey(root, file);\n if (Object.hasOwn(manifest.files, key)) {\n delete manifest.files[key];\n rmSync(join(variantDir(opts, id), key), { force: true });\n writeVariant(opts, manifest);\n }\n return manifest;\n });\n}\n\n/** The marker `git merge-file` leaves at the top of a conflicted hunk, with\n * the label `syncVariant` gives the override side. */\nconst CONFLICT_MARKER = '<<<<<<< variant';\n\n/**\n * Land a variant: copy every override over its real file, then delete the\n * variant. Refuses when an override would silently discard work: a file\n * whose real source moved on since the override was made (run `syncVariant`\n * first) or an override still carrying merge conflict markers. `force`\n * skips both checks for the case where the reviewer has decided the\n * override wins as-is.\n */\nexport function promoteVariant(\n opts: VariantsStoreOptions,\n id: string,\n { force = false }: { force?: boolean } = {},\n): { files: string[] } {\n return mutateVariants(opts, () => {\n // Checked before the manifest read so the rmSync below can never be\n // handed a directory the id escaped into.\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const active = readActive(opts);\n const root = resolve(opts.root);\n const vDir = variantDir(opts, id);\n const missing = Object.keys(manifest.files).filter((key) => !existsSync(join(vDir, key)));\n if (missing.length) throw new Error(`\"${id}\" has missing overrides: ${missing.join(', ')}. Restore or remove them before promoting.`);\n if (!force) {\n const status = listVariants(opts).find((v) => v.id === id);\n if (status?.stale.length) {\n // A null base is stale by definition, but the file did not necessarily\n // change: there was simply no git to record what it looked like.\n const reasons = status.stale.map((file) =>\n manifest.files[file]?.base === null\n ? `no base recorded for ${file} (not a git repo when the override was made)`\n : `${file} changed since the override was made`,\n );\n throw new Error(\n `\"${id}\" is stale (${reasons.join('; ')}). Run \\`variant sync ${id}\\` first, or promote with --force.`,\n );\n }\n const conflicted = Object.keys(manifest.files).filter((key) => {\n const overrideAbs = join(vDir, key);\n return existsSync(overrideAbs) && readFileSync(overrideAbs, 'utf8').includes(CONFLICT_MARKER);\n });\n if (conflicted.length) {\n throw new Error(\n `\"${id}\" has unresolved conflict markers in ${conflicted.join(', ')}. Resolve them, or promote with --force.`,\n );\n }\n }\n const files: string[] = [];\n for (const key of Object.keys(manifest.files)) {\n const overrideAbs = join(vDir, key);\n const destAbs = join(root, key);\n mkdirSync(dirname(destAbs), { recursive: true });\n writeFileSync(destAbs, readFileSync(overrideAbs));\n files.push(key);\n }\n rmSync(vDir, { recursive: true, force: true });\n if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));\n return { files };\n });\n}\n\nexport function dropVariant(opts: VariantsStoreOptions, id: string): void {\n mutateVariants(opts, () => {\n // `variant drop ..` would otherwise resolve to the variants dir's parent\n // and rmSync it, recursively.\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n const active = readActive(opts);\n rmSync(variantDir(opts, id), { recursive: true, force: true });\n if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));\n });\n}\n\n/* ── git blob hashing ─────────────────────────────────────────────────── */\n\n/**\n * `git hash-object` looks up the repository from its cwd, not from the\n * path it's hashing, so every call here runs with cwd set to the file's own\n * directory — always inside the right repo regardless of where the caller's\n * process happens to be.\n */\nexport function blobOf(absPath: string): string | null {\n try {\n return execFileSync('git', ['hash-object', '--no-filters', '-w', absPath], {\n cwd: dirname(absPath),\n encoding: 'utf8',\n }).trim();\n } catch {\n return null;\n }\n}\n\nexport function currentBlob(absPath: string): string | null {\n try {\n return execFileSync('git', ['hash-object', '--no-filters', absPath], {\n cwd: dirname(absPath),\n encoding: 'utf8',\n }).trim();\n } catch {\n return null;\n }\n}\n\n/** Git blob hashing is just a header and the file bytes. Existing bases tell\n * us the repository's hash format, avoiding a process per file on every poll. */\nfunction hashFile(file: string, base: string | null): string | null {\n if (base === null) return null;\n try {\n const contents = readFileSync(file);\n return createHash(base.length === 64 ? 'sha256' : 'sha1')\n .update(`blob ${contents.length}\\0`).update(contents).digest('hex');\n } catch { return null; }\n}\n\nfunction catFile(cwd: string, hash: string): string | null {\n try {\n return execFileSync('git', ['cat-file', '-p', hash], { cwd, encoding: 'utf8' });\n } catch {\n return null; // the blob isn't reachable (gc'd, or never written)\n }\n}\n\n/** Runs `git merge-file -p <override> <base> <current>`, capturing stdout\n * whether it exits clean (0) or with conflict markers. execFileSync throws\n * on a nonzero exit, but the thrown error still carries stdout and the exit\n * status, so both are read from there. `status` is null when git never\n * ran to completion (killed by a signal, or not found). */\nfunction mergeFile(overrideAbs: string, baseAbs: string, currentAbs: string): {\n status: number | null;\n stdout: string;\n} {\n try {\n const stdout = execFileSync(\n 'git',\n [\n 'merge-file',\n '-p',\n '-L',\n 'variant',\n '-L',\n 'base',\n '-L',\n 'default',\n overrideAbs,\n baseAbs,\n currentAbs,\n ],\n { encoding: 'utf8' },\n );\n return { status: 0, stdout };\n } catch (err) {\n const e = err as { status?: number | null; stdout?: string };\n return { status: e.status ?? null, stdout: e.stdout ?? '' };\n }\n}\n\n/** `git merge-file` exits with the number of conflicts it left, so 1..127\n * is a conflicted merge whose stdout still carries the whole file (with\n * markers). 128 and up, or no status at all, is git failing outright. */\nfunction isConflictStatus(status: number | null): boolean {\n return status !== null && status >= 1 && status <= 127;\n}\n\nexport function syncVariant(\n opts: VariantsStoreOptions,\n id: string,\n): { merged: string[]; conflicts: string[]; unchanged: string[] } {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const vDir = variantDir(opts, id);\n\n const merged: string[] = [];\n const conflicts: string[] = [];\n const unchanged: string[] = [];\n let dirty = false;\n\n for (const [file, entry] of Object.entries(manifest.files)) {\n const currentAbs = join(root, file);\n const overrideAbs = join(vDir, file);\n if (entry.added) {\n // Nothing to merge into; a real file that appeared meanwhile is a\n // conflict for a person to settle.\n (existsSync(currentAbs) ? conflicts : unchanged).push(file);\n continue;\n }\n const currentHash = currentBlob(currentAbs);\n\n if (!isStale(entry.base, currentHash)) {\n unchanged.push(file);\n continue;\n }\n\n // No base to diff against (never hashed, or the blob is unreachable), or\n // no current file to merge into: nothing to three-way merge, so the\n // override is left as-is and flagged for a person to sort out.\n const baseContent = entry.base !== null ? catFile(dirname(currentAbs), entry.base) : null;\n if (baseContent === null || currentHash === null) {\n conflicts.push(file);\n continue;\n }\n\n const baseTmp = join(tmpdir(), `isotone-base-${randomUUID()}`);\n writeFileSync(baseTmp, baseContent);\n try {\n const result = mergeFile(overrideAbs, baseTmp, currentAbs);\n if (result.status === 0) {\n writeFileSync(overrideAbs, result.stdout);\n entry.base = currentHash;\n dirty = true;\n merged.push(file);\n } else if (isConflictStatus(result.status)) {\n // The markers go into the override for a person to resolve, and the\n // base advances anyway, the way git records a conflicted merge:\n // the current file is now what the override was reconciled against,\n // so resolving the markers is all that stands between it and\n // promote. Left behind, the base would make the resolved file read\n // as stale and a second sync would re-conflict the same hunks; the\n // marker check in promoteVariant is the guard that remains.\n writeFileSync(overrideAbs, result.stdout);\n entry.base = currentHash;\n dirty = true;\n conflicts.push(file);\n } else {\n // Git failed outright; its empty stdout must not replace the\n // override, and the base stays where it was.\n conflicts.push(file);\n }\n } finally {\n rmSync(baseTmp, { force: true });\n }\n }\n\n if (dirty) writeVariant(opts, manifest);\n return { merged, conflicts, unchanged };\n });\n}\n\n/* ── active variants / vite load() lookup ─────────────────────────── */\n\n/**\n * The override that answers for `absSourcePath` under the active set, or\n * `null` when the real file stands. The first active variant whose manifest\n * names the file wins; `readActive` and the endpoint keep the set free of\n * two variants over one file, so in practice there is only ever one.\n */\nexport function resolveOverride(\n opts: VariantsStoreOptions,\n active: string[],\n absSourcePath: string,\n): string | null {\n if (active.length === 0) return null;\n const root = resolve(opts.root);\n const relToRoot = relative(root, resolve(absSourcePath));\n if (isEscaping(relToRoot)) return null;\n const key = toPosixPath(relToRoot);\n for (const id of active) {\n const manifest = readVariant(opts, id);\n if (!manifest || !Object.hasOwn(manifest.files, key)) continue;\n const overrideAbs = join(variantDir(opts, id), key);\n if (existsSync(overrideAbs)) return overrideAbs;\n }\n return null;\n}\n\nexport function overrideSourcePath(\n opts: VariantsStoreOptions,\n absOverridePath: string,\n): { id: string; file: string } | null {\n const rel = relative(variantsDir(opts), resolve(absOverridePath));\n if (!rel || isEscaping(rel)) return null;\n const [id, ...rest] = toPosixPath(rel).split('/');\n if (!id || rest.length === 0) return null;\n return { id, file: rest.join('/') };\n}\n\n/**\n * Two active variants over the same file, as `[first, second, file]`, or\n * `null` when the set is consistent. `load` can only serve one copy of a\n * file, so such a set is refused by the endpoint and trimmed by\n * {@link readActive}. Ids without a manifest are skipped; the callers\n * check existence themselves.\n */\nexport function activeConflict(\n opts: VariantsStoreOptions,\n ids: string[],\n): [string, string, string] | null {\n const owner = new Map<string, string>();\n for (const id of ids) {\n const manifest = readVariant(opts, id);\n for (const file of Object.keys(manifest?.files ?? {})) {\n const first = owner.get(file);\n if (first !== undefined && first !== id) return [first, id, file];\n owner.set(file, id);\n }\n }\n return null;\n}\n\n/**\n * The active set, cleaned: ids whose variant is gone (dropped or promoted\n * since the file was written) are dropped, and of two variants over one\n * file the later one is dropped, so `load` never has to choose. The file's\n * older shape, one id or `null`, still reads.\n */\n/** The host's knobs, dropping entries that are not a usable axis. */\nexport function readKnobs(opts: VariantsStoreOptions): KnobAxis[] {\n const file = resolve(opts.root, KNOBS_FILE);\n if (!existsSync(file)) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(file, 'utf8'));\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const seen = new Set<string>();\n return parsed.filter((k): k is KnobAxis => {\n if (!k || typeof k !== 'object') return false;\n const a = k as Partial<KnobAxis>;\n if (typeof a.id !== 'string' || !isValidVariantId(a.id) || typeof a.label !== 'string') return false;\n if (a.route !== undefined && typeof a.route !== 'string' &&\n (!Array.isArray(a.route) || !a.route.every((route) => typeof route === 'string'))) return false;\n if (a.showWhen !== undefined && (!a.showWhen || typeof a.showWhen.axis !== 'string' ||\n (a.showWhen.equals !== undefined && typeof a.showWhen.equals !== 'string') ||\n (a.showWhen.not !== undefined && typeof a.showWhen.not !== 'string'))) return false;\n if (a.section !== undefined && typeof a.section !== 'string') return false;\n if (a.kind === 'range') {\n const { min, max, step, defaultValue } = a as Partial<RangeVariantAxis>;\n if (typeof min !== 'number' || typeof max !== 'number' || typeof step !== 'number' || typeof defaultValue !== 'number') return false;\n return [min, max, step, defaultValue, max - min].every(Number.isFinite) &&\n min < max && step > 0 && defaultValue >= min && defaultValue <= max &&\n (a.unit === undefined || typeof a.unit === 'string');\n }\n if (a.kind === 'color') {\n const c = a as Partial<ColorVariantAxis>;\n const hex = (value: unknown) => typeof value === 'string' && /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i.test(value);\n return hex(c.defaultColor) && Array.isArray(c.swatches) && c.swatches.every((s) =>\n s && typeof s.id === 'string' && typeof s.label === 'string' && hex(s.value));\n }\n return false;\n }).filter((axis) => {\n if (seen.has(axis.id)) return false;\n seen.add(axis.id);\n return true;\n });\n}\n\nexport function readActive(opts: VariantsStoreOptions): string[] {\n const file = join(resolve(opts.root), ACTIVE_FILE);\n if (!existsSync(file)) return [];\n let raw: unknown;\n try {\n raw = (JSON.parse(readFileSync(file, 'utf8')) as { active?: unknown }).active;\n } catch {\n return [];\n }\n const ids = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const kept: string[] = [];\n const seen = new Set<string>();\n const owner = new Set<string>();\n for (const id of ids) {\n if (typeof id !== 'string' || seen.has(id)) continue;\n seen.add(id);\n const manifest = readVariant(opts, id);\n if (!manifest) continue;\n const files = Object.keys(manifest.files);\n if (files.some((f) => owner.has(f))) continue;\n for (const f of files) owner.add(f);\n kept.push(id);\n }\n return kept;\n}\n\nexport function writeActive(opts: VariantsStoreOptions, ids: string[]): void {\n const file = join(resolve(opts.root), ACTIVE_FILE);\n mkdirSync(dirname(file), { recursive: true });\n const active = Array.from(new Set(ids));\n writeJson(file, { active } satisfies ActiveFileShape);\n // Which variants a machine is looking at is local state, never a commit;\n // the CLI writes this too, so the exclusion cannot live in the plugin alone.\n ensureFileIgnored(file);\n}\n\n/* ── the endpoint handler ─────────────────────────────────────────────── */\n\n/**\n * The transport-agnostic core of the variants endpoint, mirroring\n * {@link import('./store').notesResponse}. `req.url` is the path relative\n * to wherever the caller mounted `VARIANTS_ENDPOINT` (\"/\", \"/active\"), so\n * this function never needs to know the mount point.\n */\nexport function variantsResponse(opts: VariantsStoreOptions, req: NotesRequest): NotesResponse {\n const method = req.method ?? '';\n const path = (req.url ?? '/').split('?')[0] || '/';\n const json = (status: number, body: unknown): NotesResponse => ({\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },\n body,\n });\n\n if (method === 'GET' && path === '/') {\n return json(200, { variants: listVariants(opts), active: readActive(opts), knobs: readKnobs(opts) });\n }\n\n if (method === 'POST' && path === '/active') {\n const body = req.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return json(400, { error: 'body must be a JSON object' });\n }\n const raw = (body as { active?: unknown }).active;\n if (!Array.isArray(raw) || !raw.every((id) => typeof id === 'string')) {\n return json(400, { error: 'active must be an array of variant ids' });\n }\n const active = Array.from(new Set(raw as string[]));\n for (const id of active) {\n // Malformed, not merely unknown: a 404 would imply the shape was fine.\n if (!isValidVariantId(id)) return json(400, { error: `invalid variant id \"${id}\"` });\n }\n for (const id of active) {\n if (!readVariant(opts, id)) return json(404, { error: `unknown variant \"${id}\"` });\n }\n const conflict = activeConflict(opts, active);\n if (conflict) {\n const [a, b, file] = conflict;\n return json(409, {\n error: `\"${a}\" and \"${b}\" both override ${file}; only one can be active`,\n conflict,\n });\n }\n writeActive(opts, active);\n return json(200, { active });\n }\n\n return json(405, { error: 'method not allowed' });\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAS,oBAAoB;AAC7B,SAAS,YAAY,kBAAkB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,SAAS,YAAY,MAAM,UAAU,SAAS,WAAW;AAe3D,IAAM,uBAAuB;AAI7B,IAAM,cAAc;AAIpB,IAAM,aAAa;AAe1B,SAAS,eAAkB,MAA4B,QAAoB;AACzE,SAAO,aAAa,KAAK,YAAY,IAAI,GAAG,WAAW,GAAG,MAAM;AAClE;AAIO,SAAS,YAAY,MAAoC;AAC9D,QAAM,MAAM,KAAK,OAAO;AACxB,SAAO,WAAW,GAAG,IAAI,MAAM,QAAQ,KAAK,MAAM,GAAG;AACvD;AAEO,SAAS,WAAW,MAA4B,IAAoB;AACzE,MAAI,CAAC,iBAAiB,EAAE,EAAG,OAAM,IAAI,MAAM,uBAAuB,EAAE,GAAG;AACvE,SAAO,KAAK,YAAY,IAAI,GAAG,EAAE;AACnC;AAEO,SAAS,iBAAiB,IAAqB;AACpD,SAAO,4BAA4B,KAAK,EAAE;AAC5C;AAIA,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAC9B;AAKA,SAAS,WAAW,KAAsB;AACxC,SAAO,QAAQ,QAAQ,IAAI,WAAW,KAAK,GAAG,EAAE,KAAK,WAAW,GAAG;AACrE;AAIA,SAAS,YAAY,MAAc,MAAuB;AACxD,QAAM,YAAY,CAAC,SAAyB;AAC1C,QAAI,WAAW,QAAQ,IAAI;AAC3B,WAAO,QAAQ,QAAQ,MAAM,UAAU;AACrC,UAAI;AAAE,kBAAU,QAAQ;AAAG;AAAA,MAAO,SAAS,OAAO;AAChD,YAAK,MAAgC,SAAS,SAAU,OAAM;AAC9D,mBAAW,QAAQ,QAAQ;AAAA,MAC7B;AAAA,IACF;AACA,WAAO,QAAQ,aAAa,QAAQ,GAAG,SAAS,UAAU,QAAQ,IAAI,CAAC,CAAC;AAAA,EAC1E;AACA,SAAO,CAAC,WAAW,SAAS,UAAU,IAAI,GAAG,UAAU,IAAI,CAAC,CAAC;AAC/D;AAQA,SAAS,gBAAgB,MAAc,MAA4C;AACjF,QAAM,MAAM,WAAW,IAAI,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,IAAI;AACjE,QAAM,MAAM,SAAS,MAAM,GAAG;AAC9B,MAAI,CAAC,OAAO,WAAW,GAAG,KAAK,CAAC,kBAAkB,YAAY,GAAG,CAAC,KAAK,CAAC,YAAY,MAAM,GAAG,GAAG;AAC9F,UAAM,IAAI,MAAM,IAAI,IAAI,4BAA4B;AAAA,EACtD;AACA,SAAO,EAAE,KAAK,KAAK,YAAY,GAAG,EAAE;AACtC;AAWA,SAAS,kBAAkB,KAAsB;AAC/C,MAAI,CAAC,OAAO,WAAW,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,QAAQ,eAAgB,QAAO;AACrF,SAAO,CAAC,IAAI,MAAM,GAAG,EAAE,KAAK,CAAC,YAAY,CAAC,WAAW,YAAY,OAAO,YAAY,QAAQ,YAAY,MAAM;AAChH;AAEA,SAAS,mBAAmB,GAAiC;AAC3D,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC5D,QAAM,IAAI;AACV,SACE,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,UAAU,YACnB,OAAO,EAAE,cAAc,aACtB,EAAE,UAAU,UAAa,OAAO,EAAE,UAAU,cAC5C,EAAE,gBAAgB,UAAa,OAAO,EAAE,gBAAgB,cACxD,EAAE,UAAU,UAAc,OAAO,EAAE,UAAU,YAAY,iBAAiB,KAAK,EAAE,KAAK,OACtF,EAAE,eAAe,UAAa,OAAO,EAAE,eAAe,aACvD,CAAC,CAAC,EAAE,SACJ,OAAO,EAAE,UAAU,YACnB,CAAC,MAAM,QAAQ,EAAE,KAAK,KACtB,OAAO,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,kBAAkB,GAAG,KAAK,SAAS,OAAO,UAAU,YACjG,UAAU,UAAW,MAAM,SAAS,QAAS,OAAO,MAAM,SAAS,YAAY,kCAAkC,KAAK,MAAM,IAAI,OAChI,EAAE,WAAW,UAAU,MAAM,UAAU,KAAK;AAEnD;AAEO,SAAS,YAAY,MAA4B,IAAmC;AAIzF,MAAI,CAAC,iBAAiB,EAAE,EAAG,QAAO;AAClC,QAAM,OAAO,KAAK,WAAW,MAAM,EAAE,GAAG,cAAc;AACtD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAClD,UAAM,MAAM,WAAW,MAAM,EAAE;AAC/B,WAAO,mBAAmB,IAAI,KAAK,KAAK,OAAO,MAAM,YAAY,YAAY,IAAI,GAAG,IAAI,KACtF,OAAO,KAAK,KAAK,KAAK,EAAE,MAAM,CAAC,QAAQ,YAAY,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,CAAC,KAAK,YAAY,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC,IAAI,OAAO;AAAA,EACtI,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,MAA4B,GAAyB;AAChF,MAAI,CAAC,mBAAmB,CAAC,EAAG,OAAM,IAAI,MAAM,0BAA0B;AACtE,QAAM,MAAM,WAAW,MAAM,EAAE,EAAE;AACjC,MAAI,CAAC,YAAY,YAAY,IAAI,GAAG,GAAG,EAAG,OAAM,IAAI,MAAM,6CAA6C;AACvG,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,YAAU,KAAK,KAAK,cAAc,GAAG,CAAC;AACxC;AAMA,SAAS,QAAQ,MAAqB,aAAqC;AACzE,SAAO,SAAS,QAAQ,gBAAgB,QAAQ,gBAAgB;AAClE;AAIO,SAAS,aAAa,MAA8C;AACzE,QAAM,MAAM,YAAY,IAAI;AAC5B,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO,CAAC;AAC9B,SAAO,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,EAC5C,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,YAAY,MAAM,EAAE,IAAI,CAAC,EACpC,OAAO,CAAC,MAA2B,MAAM,IAAI,EAC7C,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC1D;AAEO,SAAS,aAAa,MAAoD;AAC/E,QAAM,MAAM,YAAY,IAAI;AAC5B,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO,CAAC;AAC9B,QAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,QAAM,MAAM,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,EACjD,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAC7B,IAAI,CAAC,MAAM,EAAE,IAAI;AAEpB,QAAM,WAAmC,CAAC;AAC1C,QAAM,SAAS,oBAAI,IAA2B;AAC9C,aAAW,MAAM,KAAK;AACpB,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU;AACf,UAAM,QAAkB,CAAC;AACzB,UAAM,UAAoB,CAAC;AAC3B,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,SAAS,KAAK,GAAG;AAC1D,YAAM,cAAc,KAAK,KAAK,IAAI,IAAI;AACtC,UAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,gBAAQ,KAAK,IAAI;AACjB;AAAA,MACF;AAGA,UAAI,MAAM,OAAO;AACf,YAAI,WAAW,KAAK,MAAM,IAAI,CAAC,EAAG,OAAM,KAAK,IAAI;AACjD;AAAA,MACF;AACA,YAAM,UAAU,GAAG,MAAM,MAAM,MAAM,IAAI,IAAI;AAC7C,UAAI,CAAC,OAAO,IAAI,OAAO,EAAG,QAAO,IAAI,SAAS,SAAS,KAAK,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;AACpF,UAAI,QAAQ,MAAM,MAAM,OAAO,IAAI,OAAO,KAAK,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,IACvE;AACA,aAAS,KAAK,EAAE,GAAG,UAAU,OAAO,QAAQ,CAAC;AAAA,EAC/C;AACA,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC9D,SAAO;AACT;AAEO,SAAS,cACd,MACA,OAUgB;AAChB,SAAO,eAAe,MAAM,MAAM;AAChC,QAAI,CAAC,iBAAiB,MAAM,EAAE,GAAG;AAC/B,YAAM,IAAI,MAAM,uBAAuB,MAAM,EAAE,GAAG;AAAA,IACpD;AACA,QAAI,MAAM,UAAU,OAAW,eAAc,MAAM,KAAK;AACxD,QAAI,YAAY,MAAM,MAAM,EAAE,GAAG;AAC/B,YAAM,IAAI,MAAM,YAAY,MAAM,EAAE,kBAAkB;AAAA,IACxD;AACA,UAAM,UAA0B;AAAA,MAC9B,IAAI,MAAM;AAAA,MACV,OAAO,MAAM,SAAS,MAAM;AAAA,MAC5B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,OAAO,CAAC;AAAA,MACR,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAC1D,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAChE,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,MACvD,GAAI,MAAM,gBAAgB,SAAY,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,MAC5E,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,MAC1D,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,IAC3E;AACA,iBAAa,MAAM,OAAO;AAC1B,WAAO;AAAA,EACT,CAAC;AACH;AAEA,SAAS,cAAc,OAAqB;AAC1C,MAAI,CAAC,iBAAiB,KAAK,KAAK,GAAG;AACjC,UAAM,IAAI,MAAM,qBAAqB,KAAK,2CAA2C;AAAA,EACvF;AACF;AAKO,SAAS,gBACd,MACA,IACA,OACA,YACgB;AAChB,SAAO,eAAe,MAAM,MAAM;AAChC,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,eAAe,EAAE,GAAG;AACnD,kBAAc,KAAK;AACnB,aAAS,QAAQ;AACjB,QAAI,eAAe,OAAW,UAAS,aAAa;AACpD,iBAAa,MAAM,QAAQ;AAC3B,WAAO;AAAA,EACT,CAAC;AACH;AAOO,SAAS,eACd,MACA,IACA,MACA,EAAE,SAAS,MAAM,IAA0B,CAAC,GAC5B;AAChB,SAAO,eAAe,MAAM,MAAM;AAChC,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,eAAe,EAAE,GAAG;AACnD,UAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,UAAM,EAAE,KAAK,WAAW,IAAI,IAAI,gBAAgB,MAAM,IAAI;AAG1D,QAAI,OAAO,OAAO,SAAS,OAAO,GAAG,EAAG,QAAO;AAC/C,UAAM,UAAU,KAAK,WAAW,MAAM,EAAE,GAAG,GAAG;AAC9C,QAAI,CAAC,YAAY,WAAW,MAAM,EAAE,GAAG,OAAO,EAAG,OAAM,IAAI,MAAM,6CAA6C;AAC9G,QAAI,QAAQ;AACV,UAAI,WAAW,SAAS,EAAG,OAAM,IAAI,MAAM,IAAI,IAAI,wCAAwC;AAC3F,gBAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,UAAI,CAAC,WAAW,OAAO,EAAG,eAAc,SAAS,EAAE;AACnD,eAAS,QAAQ,EAAE,GAAG,SAAS,OAAO,CAAC,GAAG,GAAG,EAAE,MAAM,MAAM,OAAO,KAAK,EAAE;AACzE,mBAAa,MAAM,QAAQ;AAC3B,aAAO;AAAA,IACT;AACA,QAAI,CAAC,WAAW,SAAS,EAAG,OAAM,IAAI,MAAM,IAAI,IAAI,0DAA0D;AAC9G,cAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,kBAAc,SAAS,aAAa,SAAS,CAAC;AAC9C,aAAS,QAAQ,EAAE,GAAG,SAAS,OAAO,CAAC,GAAG,GAAG,EAAE,MAAM,OAAO,SAAS,EAAE,EAAE;AACzE,iBAAa,MAAM,QAAQ;AAC3B,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,kBACd,MACA,IACA,MACgB;AAChB,SAAO,eAAe,MAAM,MAAM;AAChC,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,eAAe,EAAE,GAAG;AACnD,UAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,UAAM,EAAE,IAAI,IAAI,gBAAgB,MAAM,IAAI;AAC1C,QAAI,OAAO,OAAO,SAAS,OAAO,GAAG,GAAG;AACtC,aAAO,SAAS,MAAM,GAAG;AACzB,aAAO,KAAK,WAAW,MAAM,EAAE,GAAG,GAAG,GAAG,EAAE,OAAO,KAAK,CAAC;AACvD,mBAAa,MAAM,QAAQ;AAAA,IAC7B;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAIA,IAAM,kBAAkB;AAUjB,SAAS,eACd,MACA,IACA,EAAE,QAAQ,MAAM,IAAyB,CAAC,GACrB;AACrB,SAAO,eAAe,MAAM,MAAM;AAGhC,QAAI,CAAC,iBAAiB,EAAE,EAAG,OAAM,IAAI,MAAM,uBAAuB,EAAE,GAAG;AACvE,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,eAAe,EAAE,GAAG;AACnD,UAAM,SAAS,WAAW,IAAI;AAC9B,UAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,UAAM,OAAO,WAAW,MAAM,EAAE;AAChC,UAAM,UAAU,OAAO,KAAK,SAAS,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,WAAW,KAAK,MAAM,GAAG,CAAC,CAAC;AACxF,QAAI,QAAQ,OAAQ,OAAM,IAAI,MAAM,IAAI,EAAE,4BAA4B,QAAQ,KAAK,IAAI,CAAC,4CAA4C;AACpI,QAAI,CAAC,OAAO;AACV,YAAM,SAAS,aAAa,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACzD,UAAI,QAAQ,MAAM,QAAQ;AAGxB,cAAM,UAAU,OAAO,MAAM;AAAA,UAAI,CAAC,SAChC,SAAS,MAAM,IAAI,GAAG,SAAS,OAC3B,wBAAwB,IAAI,iDAC5B,GAAG,IAAI;AAAA,QACb;AACA,cAAM,IAAI;AAAA,UACR,IAAI,EAAE,eAAe,QAAQ,KAAK,IAAI,CAAC,yBAAyB,EAAE;AAAA,QACpE;AAAA,MACF;AACA,YAAM,aAAa,OAAO,KAAK,SAAS,KAAK,EAAE,OAAO,CAAC,QAAQ;AAC7D,cAAM,cAAc,KAAK,MAAM,GAAG;AAClC,eAAO,WAAW,WAAW,KAAK,aAAa,aAAa,MAAM,EAAE,SAAS,eAAe;AAAA,MAC9F,CAAC;AACD,UAAI,WAAW,QAAQ;AACrB,cAAM,IAAI;AAAA,UACR,IAAI,EAAE,wCAAwC,WAAW,KAAK,IAAI,CAAC;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAkB,CAAC;AACzB,eAAW,OAAO,OAAO,KAAK,SAAS,KAAK,GAAG;AAC7C,YAAM,cAAc,KAAK,MAAM,GAAG;AAClC,YAAM,UAAU,KAAK,MAAM,GAAG;AAC9B,gBAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,oBAAc,SAAS,aAAa,WAAW,CAAC;AAChD,YAAM,KAAK,GAAG;AAAA,IAChB;AACA,WAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC7C,QAAI,OAAO,SAAS,EAAE,EAAG,aAAY,MAAM,OAAO,OAAO,CAAC,MAAM,MAAM,EAAE,CAAC;AACzE,WAAO,EAAE,MAAM;AAAA,EACjB,CAAC;AACH;AAEO,SAAS,YAAY,MAA4B,IAAkB;AACxE,iBAAe,MAAM,MAAM;AAGzB,QAAI,CAAC,iBAAiB,EAAE,EAAG,OAAM,IAAI,MAAM,uBAAuB,EAAE,GAAG;AACvE,UAAM,SAAS,WAAW,IAAI;AAC9B,WAAO,WAAW,MAAM,EAAE,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC7D,QAAI,OAAO,SAAS,EAAE,EAAG,aAAY,MAAM,OAAO,OAAO,CAAC,MAAM,MAAM,EAAE,CAAC;AAAA,EAC3E,CAAC;AACH;AAUO,SAAS,OAAO,SAAgC;AACrD,MAAI;AACF,WAAO,aAAa,OAAO,CAAC,eAAe,gBAAgB,MAAM,OAAO,GAAG;AAAA,MACzE,KAAK,QAAQ,OAAO;AAAA,MACpB,UAAU;AAAA,IACZ,CAAC,EAAE,KAAK;AAAA,EACV,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,YAAY,SAAgC;AAC1D,MAAI;AACF,WAAO,aAAa,OAAO,CAAC,eAAe,gBAAgB,OAAO,GAAG;AAAA,MACnE,KAAK,QAAQ,OAAO;AAAA,MACpB,UAAU;AAAA,IACZ,CAAC,EAAE,KAAK;AAAA,EACV,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,SAAS,MAAc,MAAoC;AAClE,MAAI,SAAS,KAAM,QAAO;AAC1B,MAAI;AACF,UAAM,WAAW,aAAa,IAAI;AAClC,WAAO,WAAW,KAAK,WAAW,KAAK,WAAW,MAAM,EACrD,OAAO,QAAQ,SAAS,MAAM,IAAI,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK;AAAA,EACtE,QAAQ;AAAE,WAAO;AAAA,EAAM;AACzB;AAEA,SAAS,QAAQ,KAAa,MAA6B;AACzD,MAAI;AACF,WAAO,aAAa,OAAO,CAAC,YAAY,MAAM,IAAI,GAAG,EAAE,KAAK,UAAU,OAAO,CAAC;AAAA,EAChF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,SAAS,UAAU,aAAqB,SAAiB,YAGvD;AACA,MAAI;AACF,UAAM,SAAS;AAAA,MACb;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,EAAE,UAAU,OAAO;AAAA,IACrB;AACA,WAAO,EAAE,QAAQ,GAAG,OAAO;AAAA,EAC7B,SAAS,KAAK;AACZ,UAAM,IAAI;AACV,WAAO,EAAE,QAAQ,EAAE,UAAU,MAAM,QAAQ,EAAE,UAAU,GAAG;AAAA,EAC5D;AACF;AAKA,SAAS,iBAAiB,QAAgC;AACxD,SAAO,WAAW,QAAQ,UAAU,KAAK,UAAU;AACrD;AAEO,SAAS,YACd,MACA,IACgE;AAChE,SAAO,eAAe,MAAM,MAAM;AAChC,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU,OAAM,IAAI,MAAM,eAAe,EAAE,GAAG;AACnD,UAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,UAAM,OAAO,WAAW,MAAM,EAAE;AAEhC,UAAM,SAAmB,CAAC;AAC1B,UAAM,YAAsB,CAAC;AAC7B,UAAM,YAAsB,CAAC;AAC7B,QAAI,QAAQ;AAEZ,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,SAAS,KAAK,GAAG;AAC1D,YAAM,aAAa,KAAK,MAAM,IAAI;AAClC,YAAM,cAAc,KAAK,MAAM,IAAI;AACnC,UAAI,MAAM,OAAO;AAGf,SAAC,WAAW,UAAU,IAAI,YAAY,WAAW,KAAK,IAAI;AAC1D;AAAA,MACF;AACA,YAAM,cAAc,YAAY,UAAU;AAE1C,UAAI,CAAC,QAAQ,MAAM,MAAM,WAAW,GAAG;AACrC,kBAAU,KAAK,IAAI;AACnB;AAAA,MACF;AAKA,YAAM,cAAc,MAAM,SAAS,OAAO,QAAQ,QAAQ,UAAU,GAAG,MAAM,IAAI,IAAI;AACrF,UAAI,gBAAgB,QAAQ,gBAAgB,MAAM;AAChD,kBAAU,KAAK,IAAI;AACnB;AAAA,MACF;AAEA,YAAM,UAAU,KAAK,OAAO,GAAG,gBAAgB,WAAW,CAAC,EAAE;AAC7D,oBAAc,SAAS,WAAW;AAClC,UAAI;AACF,cAAM,SAAS,UAAU,aAAa,SAAS,UAAU;AACzD,YAAI,OAAO,WAAW,GAAG;AACvB,wBAAc,aAAa,OAAO,MAAM;AACxC,gBAAM,OAAO;AACb,kBAAQ;AACR,iBAAO,KAAK,IAAI;AAAA,QAClB,WAAW,iBAAiB,OAAO,MAAM,GAAG;AAQ1C,wBAAc,aAAa,OAAO,MAAM;AACxC,gBAAM,OAAO;AACb,kBAAQ;AACR,oBAAU,KAAK,IAAI;AAAA,QACrB,OAAO;AAGL,oBAAU,KAAK,IAAI;AAAA,QACrB;AAAA,MACF,UAAE;AACA,eAAO,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,MACjC;AAAA,IACF;AAEA,QAAI,MAAO,cAAa,MAAM,QAAQ;AACtC,WAAO,EAAE,QAAQ,WAAW,UAAU;AAAA,EACxC,CAAC;AACH;AAUO,SAAS,gBACd,MACA,QACA,eACe;AACf,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,QAAM,YAAY,SAAS,MAAM,QAAQ,aAAa,CAAC;AACvD,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,QAAM,MAAM,YAAY,SAAS;AACjC,aAAW,MAAM,QAAQ;AACvB,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,YAAY,CAAC,OAAO,OAAO,SAAS,OAAO,GAAG,EAAG;AACtD,UAAM,cAAc,KAAK,WAAW,MAAM,EAAE,GAAG,GAAG;AAClD,QAAI,WAAW,WAAW,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEO,SAAS,mBACd,MACA,iBACqC;AACrC,QAAM,MAAM,SAAS,YAAY,IAAI,GAAG,QAAQ,eAAe,CAAC;AAChE,MAAI,CAAC,OAAO,WAAW,GAAG,EAAG,QAAO;AACpC,QAAM,CAAC,IAAI,GAAG,IAAI,IAAI,YAAY,GAAG,EAAE,MAAM,GAAG;AAChD,MAAI,CAAC,MAAM,KAAK,WAAW,EAAG,QAAO;AACrC,SAAO,EAAE,IAAI,MAAM,KAAK,KAAK,GAAG,EAAE;AACpC;AASO,SAAS,eACd,MACA,KACiC;AACjC,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,MAAM,KAAK;AACpB,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,eAAW,QAAQ,OAAO,KAAK,UAAU,SAAS,CAAC,CAAC,GAAG;AACrD,YAAM,QAAQ,MAAM,IAAI,IAAI;AAC5B,UAAI,UAAU,UAAa,UAAU,GAAI,QAAO,CAAC,OAAO,IAAI,IAAI;AAChE,YAAM,IAAI,MAAM,EAAE;AAAA,IACpB;AAAA,EACF;AACA,SAAO;AACT;AASO,SAAS,UAAU,MAAwC;AAChE,QAAM,OAAO,QAAQ,KAAK,MAAM,UAAU;AAC1C,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAChD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO,CAAC;AACpC,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,OAAO,OAAO,CAAC,MAAqB;AACzC,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,OAAO,YAAY,CAAC,iBAAiB,EAAE,EAAE,KAAK,OAAO,EAAE,UAAU,SAAU,QAAO;AAC/F,QAAI,EAAE,UAAU,UAAa,OAAO,EAAE,UAAU,aAC7C,CAAC,MAAM,QAAQ,EAAE,KAAK,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC,UAAU,OAAO,UAAU,QAAQ,GAAI,QAAO;AAC5F,QAAI,EAAE,aAAa,WAAc,CAAC,EAAE,YAAY,OAAO,EAAE,SAAS,SAAS,YACxE,EAAE,SAAS,WAAW,UAAa,OAAO,EAAE,SAAS,WAAW,YAChE,EAAE,SAAS,QAAQ,UAAa,OAAO,EAAE,SAAS,QAAQ,UAAY,QAAO;AAChF,QAAI,EAAE,YAAY,UAAa,OAAO,EAAE,YAAY,SAAU,QAAO;AACrE,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,EAAE,KAAK,KAAK,MAAM,aAAa,IAAI;AACzC,UAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,YAAY,OAAO,SAAS,YAAY,OAAO,iBAAiB,SAAU,QAAO;AAC/H,aAAO,CAAC,KAAK,KAAK,MAAM,cAAc,MAAM,GAAG,EAAE,MAAM,OAAO,QAAQ,KACpE,MAAM,OAAO,OAAO,KAAK,gBAAgB,OAAO,gBAAgB,QAC/D,EAAE,SAAS,UAAa,OAAO,EAAE,SAAS;AAAA,IAC/C;AACA,QAAI,EAAE,SAAS,SAAS;AACtB,YAAM,IAAI;AACV,YAAM,MAAM,CAAC,UAAmB,OAAO,UAAU,YAAY,kCAAkC,KAAK,KAAK;AACzG,aAAO,IAAI,EAAE,YAAY,KAAK,MAAM,QAAQ,EAAE,QAAQ,KAAK,EAAE,SAAS,MAAM,CAAC,MAC3E,KAAK,OAAO,EAAE,OAAO,YAAY,OAAO,EAAE,UAAU,YAAY,IAAI,EAAE,KAAK,CAAC;AAAA,IAChF;AACA,WAAO;AAAA,EACT,CAAC,EAAE,OAAO,CAAC,SAAS;AAClB,QAAI,KAAK,IAAI,KAAK,EAAE,EAAG,QAAO;AAC9B,SAAK,IAAI,KAAK,EAAE;AAChB,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,WAAW,MAAsC;AAC/D,QAAM,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,WAAW;AACjD,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO,CAAC;AAC/B,MAAI;AACJ,MAAI;AACF,UAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC,EAA2B;AAAA,EACzE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,MAAM,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAI,CAAC;AAC1E,QAAM,OAAiB,CAAC;AACxB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,MAAM,KAAK;AACpB,QAAI,OAAO,OAAO,YAAY,KAAK,IAAI,EAAE,EAAG;AAC5C,SAAK,IAAI,EAAE;AACX,UAAM,WAAW,YAAY,MAAM,EAAE;AACrC,QAAI,CAAC,SAAU;AACf,UAAM,QAAQ,OAAO,KAAK,SAAS,KAAK;AACxC,QAAI,MAAM,KAAK,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC,EAAG;AACrC,eAAW,KAAK,MAAO,OAAM,IAAI,CAAC;AAClC,SAAK,KAAK,EAAE;AAAA,EACd;AACA,SAAO;AACT;AAEO,SAAS,YAAY,MAA4B,KAAqB;AAC3E,QAAM,OAAO,KAAK,QAAQ,KAAK,IAAI,GAAG,WAAW;AACjD,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,SAAS,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC;AACtC,YAAU,MAAM,EAAE,OAAO,CAA2B;AAGpD,oBAAkB,IAAI;AACxB;AAUO,SAAS,iBAAiB,MAA4B,KAAkC;AAC7F,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK;AAC/C,QAAM,OAAO,CAAC,QAAgB,UAAkC;AAAA,IAC9D;AAAA,IACA,SAAS,EAAE,gBAAgB,mCAAmC,iBAAiB,WAAW;AAAA,IAC1F;AAAA,EACF;AAEA,MAAI,WAAW,SAAS,SAAS,KAAK;AACpC,WAAO,KAAK,KAAK,EAAE,UAAU,aAAa,IAAI,GAAG,QAAQ,WAAW,IAAI,GAAG,OAAO,UAAU,IAAI,EAAE,CAAC;AAAA,EACrG;AAEA,MAAI,WAAW,UAAU,SAAS,WAAW;AAC3C,UAAM,OAAO,IAAI;AACjB,QAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;AACpE,aAAO,KAAK,KAAK,EAAE,OAAO,6BAA6B,CAAC;AAAA,IAC1D;AACA,UAAM,MAAO,KAA8B;AAC3C,QAAI,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,OAAO,OAAO,QAAQ,GAAG;AACrE,aAAO,KAAK,KAAK,EAAE,OAAO,yCAAyC,CAAC;AAAA,IACtE;AACA,UAAM,SAAS,MAAM,KAAK,IAAI,IAAI,GAAe,CAAC;AAClD,eAAW,MAAM,QAAQ;AAEvB,UAAI,CAAC,iBAAiB,EAAE,EAAG,QAAO,KAAK,KAAK,EAAE,OAAO,uBAAuB,EAAE,IAAI,CAAC;AAAA,IACrF;AACA,eAAW,MAAM,QAAQ;AACvB,UAAI,CAAC,YAAY,MAAM,EAAE,EAAG,QAAO,KAAK,KAAK,EAAE,OAAO,oBAAoB,EAAE,IAAI,CAAC;AAAA,IACnF;AACA,UAAM,WAAW,eAAe,MAAM,MAAM;AAC5C,QAAI,UAAU;AACZ,YAAM,CAAC,GAAG,GAAG,IAAI,IAAI;AACrB,aAAO,KAAK,KAAK;AAAA,QACf,OAAO,IAAI,CAAC,UAAU,CAAC,mBAAmB,IAAI;AAAA,QAC9C;AAAA,MACF,CAAC;AAAA,IACH;AACA,gBAAY,MAAM,MAAM;AACxB,WAAO,KAAK,KAAK,EAAE,OAAO,CAAC;AAAA,EAC7B;AAEA,SAAO,KAAK,KAAK,EAAE,OAAO,qBAAqB,CAAC;AAClD;","names":[]}