cascivo 0.7.1 → 0.7.2

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.
@@ -25,6 +25,7 @@ var config_exports = /* @__PURE__ */ __exportAll({
25
25
  installHint: () => installHint,
26
26
  isPackageManager: () => isPackageManager,
27
27
  loadConfig: () => loadConfig,
28
+ pinSpecifier: () => pinSpecifier,
28
29
  resolveConfig: () => resolveConfig
29
30
  });
30
31
  /**
@@ -152,12 +153,34 @@ function detectPackageManager(cwd = process.cwd(), opts = {}) {
152
153
  }
153
154
  return "npm";
154
155
  }
156
+ /**
157
+ * Pin every `@cascivo/*` package to an explicit version specifier.
158
+ *
159
+ * A bare name lets the package manager reuse whatever it already has resolvable. In a pnpm
160
+ * workspace a sibling package's lockfile entry won and `@cascivo/i18n` resolved to **0.2.14**
161
+ * while latest was **0.16.0** — then cascivo warned the adopter about the version it had
162
+ * just installed itself. `add name@latest` (or the registry's known floor) makes the intent
163
+ * explicit instead of leaving it to resolution order.
164
+ *
165
+ * Non-cascivo packages (`@preact/signals-react`) keep their bare name: their version is the
166
+ * app's business, and cascivo has no floor to assert.
167
+ */
168
+ function pinSpecifier(pkg, floors = {}) {
169
+ if (!pkg.startsWith("@cascivo/") && pkg !== "cascivo") return pkg;
170
+ if (pkg.includes("@", 1)) return pkg;
171
+ const floor = floors[pkg];
172
+ if (floor?.startsWith(">=")) return `${pkg}@${floor.slice(2)} - x`;
173
+ return `${pkg}@latest`;
174
+ }
155
175
  /** The install subcommand each package manager uses to add dependencies. */
156
176
  function installCommand(pm, packages, opts = {}) {
177
+ const verb = pm === "npm" ? "install" : "add";
178
+ const devFlag = opts.dev ? [pm === "npm" ? "--save-dev" : "-D"] : [];
179
+ const specs = packages.map((p) => opts.pin && (p.startsWith("@cascivo/") || p === "cascivo") ? `${p}@${opts.pin}` : pinSpecifier(p, opts.floors ?? {}));
157
180
  return [pm, [
158
- pm === "npm" ? "install" : "add",
159
- ...opts.dev ? [pm === "npm" ? "--save-dev" : "-D"] : [],
160
- ...packages
181
+ verb,
182
+ ...devFlag,
183
+ ...specs
161
184
  ]];
162
185
  }
163
186
  /** Human-readable install command a user can copy-paste, e.g. `pnpm add -D cascivo`. */
@@ -168,4 +191,4 @@ function installHint(pm, packages, opts = {}) {
168
191
  //#endregion
169
192
  export { detectPackageManager as a, isPackageManager as c, config_exports as i, loadConfig as l, DEFAULT_CONFIG as n, installCommand as o, THEMES as r, installHint as s, CASCIVO_HOST as t, __exportAll as u };
170
193
 
171
- //# sourceMappingURL=config-C6GdrbvF.mjs.map
194
+ //# sourceMappingURL=config-D7ddWN_9.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-C6GdrbvF.mjs","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\n/**\n * Canonical (and only) host for hosted cascivo artifacts (registry.json, per-item\n * r/<name>.json, marketplace.json, /llms/*, /context/*). The legacy docs.cascivo.com\n * subdomain is retired and 301s here. Keep in sync with CASCIVO_HOST in packages/mcp.\n */\nexport const CASCIVO_HOST = 'https://cascivo.com'\n\n/** All first-party themes shipped by @cascivo/themes (selectable via data-theme). */\nexport const THEMES = [\n 'light',\n 'dark',\n 'warm',\n 'flat',\n 'minimal',\n 'midnight',\n 'pastel',\n 'brutalist',\n 'corporate',\n 'terminal',\n 'cyberpunk',\n 'arcade',\n] as const\n\nexport type ThemeName = (typeof THEMES)[number]\n\nexport type RegistryNamespaceConfig =\n | string\n | { url: string; headers?: Record<string, string>; params?: Record<string, string> }\n\nexport interface CascadeConfig {\n /** URL of the registry.json index. */\n registry: string\n /** Directory (relative to project root) where components are written. */\n outputDir: string\n /** Default theme imported by `cascade init`. */\n theme: ThemeName\n /** Namespace → registry URL template (with {name} placeholder) or auth config. */\n registries?: Record<string, RegistryNamespaceConfig>\n /** Whether to copy test files (*.contract.test.tsx) when adding components. */\n tests?: boolean\n}\n\nexport const DEFAULT_CONFIG: CascadeConfig = {\n // Canonical hosted registry index (served from the landing site, documented in\n // llms.txt). Prefer this over a branch's GitHub raw URL, which 404s for\n // unauthenticated/private-repo requests and breaks `cascivo list`/`add`.\n registry: `${CASCIVO_HOST}/registry.json`,\n outputDir: 'src/components/ui',\n theme: 'light',\n}\n\nconst CONFIG_FILES = ['cascivo.config.ts', 'cascivo.config.js', 'cascivo.config.mjs']\n\n/** Apply defaults over a (possibly partial) user config object. */\nexport function resolveConfig(partial: Partial<CascadeConfig> | null | undefined): CascadeConfig {\n return { ...DEFAULT_CONFIG, ...partial }\n}\n\n/** Let env vars override config — used by the MCP server to pass `outputDir`. */\nexport function applyEnvOverrides(\n config: CascadeConfig,\n env: NodeJS.ProcessEnv = process.env,\n): CascadeConfig {\n return {\n ...config,\n ...(env.CASCIVO_REGISTRY ? { registry: env.CASCIVO_REGISTRY } : {}),\n ...(env.CASCIVO_OUTPUT_DIR ? { outputDir: env.CASCIVO_OUTPUT_DIR } : {}),\n }\n}\n\n/**\n * Locate and load `cascivo.config.{ts,js,mjs}` from `cwd`. Falls back to the\n * default config when no file is found or the file cannot be loaded. Env vars\n * (`CASCIVO_REGISTRY`, `CASCIVO_OUTPUT_DIR`) take precedence.\n */\nexport async function loadConfig(cwd: string = process.cwd()): Promise<CascadeConfig> {\n for (const file of CONFIG_FILES) {\n const path = join(cwd, file)\n if (!existsSync(path)) continue\n try {\n const mod = (await import(pathToFileURL(path).href)) as {\n default?: Partial<CascadeConfig>\n }\n return applyEnvOverrides(resolveConfig(mod.default ?? (mod as Partial<CascadeConfig>)))\n } catch {\n // Unloadable config (e.g. unsupported TS syntax) — fall back to defaults.\n return applyEnvOverrides(resolveConfig(null))\n }\n }\n return applyEnvOverrides(resolveConfig(null))\n}\n\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm' | 'bun'\n\n/** Narrow an arbitrary string to a known PackageManager. */\nexport function isPackageManager(value: string | undefined): value is PackageManager {\n return value === 'pnpm' || value === 'yarn' || value === 'npm' || value === 'bun'\n}\n\n/**\n * Lock files in probe order. `bun.lock` (bun ≥ 1.2 text lockfile) sits beside the\n * legacy binary `bun.lockb`; `package-lock.json` is npm's marker so a walk that\n * reaches an npm workspace root still resolves to npm rather than the default.\n */\nconst PM_LOCKFILES: readonly [string, PackageManager][] = [\n ['pnpm-lock.yaml', 'pnpm'],\n ['yarn.lock', 'yarn'],\n ['bun.lockb', 'bun'],\n ['bun.lock', 'bun'],\n ['package-lock.json', 'npm'],\n]\n\n/** The package manager that invoked this process, from `npm_config_user_agent`. */\nfunction pmFromUserAgent(ua: string | undefined): PackageManager | undefined {\n if (!ua) return undefined\n const name = ua.split('/')[0]\n return isPackageManager(name) ? name : undefined\n}\n\n/** The `packageManager` corepack field of a package.json, if it names a known PM. */\nfunction pmFromPackageJson(dir: string): PackageManager | undefined {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {\n packageManager?: string\n }\n const name = pkg.packageManager?.split('@')[0]\n return isPackageManager(name) ? name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Detect the package manager in use. Precedence, highest first:\n * 1. explicit override (the `--package-manager`/`--pm` flag)\n * 2. `CASCIVO_PACKAGE_MANAGER` env var\n * 3. `npm_config_user_agent` (the PM that spawned the CLI, e.g. `pnpm dlx`)\n * 4. an upward walk from `cwd` for a lock file or `packageManager` field —\n * this is what makes detection work inside a workspace, where the lock\n * file lives at the repo root, not in the app subdirectory the user runs\n * the CLI from. The walk stops after a directory containing `.git`.\n * 5. default `npm`.\n */\nexport function detectPackageManager(\n cwd: string = process.cwd(),\n opts: { override?: string; env?: NodeJS.ProcessEnv } = {},\n): PackageManager {\n const env = opts.env ?? process.env\n\n if (isPackageManager(opts.override)) return opts.override\n\n const envPm = env.CASCIVO_PACKAGE_MANAGER\n if (isPackageManager(envPm)) return envPm\n\n const uaPm = pmFromUserAgent(env.npm_config_user_agent)\n if (uaPm) return uaPm\n\n let current = cwd\n for (;;) {\n for (const [file, pm] of PM_LOCKFILES) {\n if (existsSync(join(current, file))) return pm\n }\n const fromField = pmFromPackageJson(current)\n if (fromField) return fromField\n // A `.git` directory marks the repo root — do not walk past it.\n if (existsSync(join(current, '.git'))) break\n const parent = dirname(current)\n if (parent === current) break\n current = parent\n }\n\n return 'npm'\n}\n\n/** The install subcommand each package manager uses to add dependencies. */\nexport function installCommand(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean } = {},\n): [string, string[]] {\n const verb = pm === 'npm' ? 'install' : 'add'\n const devFlag = opts.dev ? [pm === 'npm' ? '--save-dev' : '-D'] : []\n return [pm, [verb, ...devFlag, ...packages]]\n}\n\n/** Human-readable install command a user can copy-paste, e.g. `pnpm add -D cascivo`. */\nexport function installHint(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean } = {},\n): string {\n const [cmd, args] = installCommand(pm, packages, opts)\n return `${cmd} ${args.join(' ')}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"config-D7ddWN_9.mjs","names":[],"sources":["../src/utils/config.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\n/**\n * Canonical (and only) host for hosted cascivo artifacts (registry.json, per-item\n * r/<name>.json, marketplace.json, /llms/*, /context/*). The legacy docs.cascivo.com\n * subdomain is retired and 301s here. Keep in sync with CASCIVO_HOST in packages/mcp.\n */\nexport const CASCIVO_HOST = 'https://cascivo.com'\n\n/** All first-party themes shipped by @cascivo/themes (selectable via data-theme). */\nexport const THEMES = [\n 'light',\n 'dark',\n 'warm',\n 'flat',\n 'minimal',\n 'midnight',\n 'pastel',\n 'brutalist',\n 'corporate',\n 'terminal',\n 'cyberpunk',\n 'arcade',\n] as const\n\nexport type ThemeName = (typeof THEMES)[number]\n\nexport type RegistryNamespaceConfig =\n | string\n | { url: string; headers?: Record<string, string>; params?: Record<string, string> }\n\nexport interface CascadeConfig {\n /** URL of the registry.json index. */\n registry: string\n /** Directory (relative to project root) where components are written. */\n outputDir: string\n /** Default theme imported by `cascade init`. */\n theme: ThemeName\n /** Namespace → registry URL template (with {name} placeholder) or auth config. */\n registries?: Record<string, RegistryNamespaceConfig>\n /** Whether to copy test files (*.contract.test.tsx) when adding components. */\n tests?: boolean\n}\n\nexport const DEFAULT_CONFIG: CascadeConfig = {\n // Canonical hosted registry index (served from the landing site, documented in\n // llms.txt). Prefer this over a branch's GitHub raw URL, which 404s for\n // unauthenticated/private-repo requests and breaks `cascivo list`/`add`.\n registry: `${CASCIVO_HOST}/registry.json`,\n outputDir: 'src/components/ui',\n theme: 'light',\n}\n\nconst CONFIG_FILES = ['cascivo.config.ts', 'cascivo.config.js', 'cascivo.config.mjs']\n\n/** Apply defaults over a (possibly partial) user config object. */\nexport function resolveConfig(partial: Partial<CascadeConfig> | null | undefined): CascadeConfig {\n return { ...DEFAULT_CONFIG, ...partial }\n}\n\n/** Let env vars override config — used by the MCP server to pass `outputDir`. */\nexport function applyEnvOverrides(\n config: CascadeConfig,\n env: NodeJS.ProcessEnv = process.env,\n): CascadeConfig {\n return {\n ...config,\n ...(env.CASCIVO_REGISTRY ? { registry: env.CASCIVO_REGISTRY } : {}),\n ...(env.CASCIVO_OUTPUT_DIR ? { outputDir: env.CASCIVO_OUTPUT_DIR } : {}),\n }\n}\n\n/**\n * Locate and load `cascivo.config.{ts,js,mjs}` from `cwd`. Falls back to the\n * default config when no file is found or the file cannot be loaded. Env vars\n * (`CASCIVO_REGISTRY`, `CASCIVO_OUTPUT_DIR`) take precedence.\n */\nexport async function loadConfig(cwd: string = process.cwd()): Promise<CascadeConfig> {\n for (const file of CONFIG_FILES) {\n const path = join(cwd, file)\n if (!existsSync(path)) continue\n try {\n const mod = (await import(pathToFileURL(path).href)) as {\n default?: Partial<CascadeConfig>\n }\n return applyEnvOverrides(resolveConfig(mod.default ?? (mod as Partial<CascadeConfig>)))\n } catch {\n // Unloadable config (e.g. unsupported TS syntax) — fall back to defaults.\n return applyEnvOverrides(resolveConfig(null))\n }\n }\n return applyEnvOverrides(resolveConfig(null))\n}\n\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm' | 'bun'\n\n/** Narrow an arbitrary string to a known PackageManager. */\nexport function isPackageManager(value: string | undefined): value is PackageManager {\n return value === 'pnpm' || value === 'yarn' || value === 'npm' || value === 'bun'\n}\n\n/**\n * Lock files in probe order. `bun.lock` (bun ≥ 1.2 text lockfile) sits beside the\n * legacy binary `bun.lockb`; `package-lock.json` is npm's marker so a walk that\n * reaches an npm workspace root still resolves to npm rather than the default.\n */\nconst PM_LOCKFILES: readonly [string, PackageManager][] = [\n ['pnpm-lock.yaml', 'pnpm'],\n ['yarn.lock', 'yarn'],\n ['bun.lockb', 'bun'],\n ['bun.lock', 'bun'],\n ['package-lock.json', 'npm'],\n]\n\n/** The package manager that invoked this process, from `npm_config_user_agent`. */\nfunction pmFromUserAgent(ua: string | undefined): PackageManager | undefined {\n if (!ua) return undefined\n const name = ua.split('/')[0]\n return isPackageManager(name) ? name : undefined\n}\n\n/** The `packageManager` corepack field of a package.json, if it names a known PM. */\nfunction pmFromPackageJson(dir: string): PackageManager | undefined {\n try {\n const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as {\n packageManager?: string\n }\n const name = pkg.packageManager?.split('@')[0]\n return isPackageManager(name) ? name : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Detect the package manager in use. Precedence, highest first:\n * 1. explicit override (the `--package-manager`/`--pm` flag)\n * 2. `CASCIVO_PACKAGE_MANAGER` env var\n * 3. `npm_config_user_agent` (the PM that spawned the CLI, e.g. `pnpm dlx`)\n * 4. an upward walk from `cwd` for a lock file or `packageManager` field —\n * this is what makes detection work inside a workspace, where the lock\n * file lives at the repo root, not in the app subdirectory the user runs\n * the CLI from. The walk stops after a directory containing `.git`.\n * 5. default `npm`.\n */\nexport function detectPackageManager(\n cwd: string = process.cwd(),\n opts: { override?: string; env?: NodeJS.ProcessEnv } = {},\n): PackageManager {\n const env = opts.env ?? process.env\n\n if (isPackageManager(opts.override)) return opts.override\n\n const envPm = env.CASCIVO_PACKAGE_MANAGER\n if (isPackageManager(envPm)) return envPm\n\n const uaPm = pmFromUserAgent(env.npm_config_user_agent)\n if (uaPm) return uaPm\n\n let current = cwd\n for (;;) {\n for (const [file, pm] of PM_LOCKFILES) {\n if (existsSync(join(current, file))) return pm\n }\n const fromField = pmFromPackageJson(current)\n if (fromField) return fromField\n // A `.git` directory marks the repo root — do not walk past it.\n if (existsSync(join(current, '.git'))) break\n const parent = dirname(current)\n if (parent === current) break\n current = parent\n }\n\n return 'npm'\n}\n\n/**\n * Pin every `@cascivo/*` package to an explicit version specifier.\n *\n * A bare name lets the package manager reuse whatever it already has resolvable. In a pnpm\n * workspace a sibling package's lockfile entry won and `@cascivo/i18n` resolved to **0.2.14**\n * while latest was **0.16.0** — then cascivo warned the adopter about the version it had\n * just installed itself. `add name@latest` (or the registry's known floor) makes the intent\n * explicit instead of leaving it to resolution order.\n *\n * Non-cascivo packages (`@preact/signals-react`) keep their bare name: their version is the\n * app's business, and cascivo has no floor to assert.\n */\nexport function pinSpecifier(pkg: string, floors: Record<string, string> = {}): string {\n if (!pkg.startsWith('@cascivo/') && pkg !== 'cascivo') return pkg\n if (pkg.includes('@', 1)) return pkg // already carries an explicit version\n const floor = floors[pkg]\n // `>=x.y.z` is not an installable specifier on its own; widen it to a range the package\n // manager understands, so a known floor is honoured rather than silently ignored.\n if (floor?.startsWith('>=')) return `${pkg}@${floor.slice(2)} - x`\n return `${pkg}@latest`\n}\n\n/** The install subcommand each package manager uses to add dependencies. */\nexport function installCommand(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean; floors?: Record<string, string>; pin?: string } = {},\n): [string, string[]] {\n const verb = pm === 'npm' ? 'install' : 'add'\n const devFlag = opts.dev ? [pm === 'npm' ? '--save-dev' : '-D'] : []\n const specs = packages.map((p) =>\n opts.pin && (p.startsWith('@cascivo/') || p === 'cascivo')\n ? `${p}@${opts.pin}`\n : pinSpecifier(p, opts.floors ?? {}),\n )\n return [pm, [verb, ...devFlag, ...specs]]\n}\n\n/** Human-readable install command a user can copy-paste, e.g. `pnpm add -D cascivo`. */\nexport function installHint(\n pm: PackageManager,\n packages: string[],\n opts: { dev?: boolean } = {},\n): string {\n const [cmd, args] = installCommand(pm, packages, opts)\n return `${cmd} ${args.join(' ')}`\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,MAAa,eAAe;;AAG5B,MAAa,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAqBA,MAAa,iBAAgC;CAI3C,UAAU,GAAG,aAAa;CAC1B,WAAW;CACX,OAAO;AACT;AAEA,MAAM,eAAe;CAAC;CAAqB;CAAqB;AAAoB;;AAGpF,SAAgB,cAAc,SAAmE;CAC/F,OAAO;EAAE,GAAG;EAAgB,GAAG;CAAQ;AACzC;;AAGA,SAAgB,kBACd,QACA,MAAyB,QAAQ,KAClB;CACf,OAAO;EACL,GAAG;EACH,GAAI,IAAI,mBAAmB,EAAE,UAAU,IAAI,iBAAiB,IAAI,CAAC;EACjE,GAAI,IAAI,qBAAqB,EAAE,WAAW,IAAI,mBAAmB,IAAI,CAAC;CACxE;AACF;;;;;;AAOA,eAAsB,WAAW,MAAc,QAAQ,IAAI,GAA2B;CACpF,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,OAAO,KAAK,KAAK,IAAI;EAC3B,IAAI,CAAC,WAAW,IAAI,GAAG;EACvB,IAAI;GACF,MAAM,MAAO,MAAM,OAAO,cAAc,IAAI,CAAC,CAAC;GAG9C,OAAO,kBAAkB,cAAc,IAAI,WAAY,GAA8B,CAAC;EACxF,QAAQ;GAEN,OAAO,kBAAkB,cAAc,IAAI,CAAC;EAC9C;CACF;CACA,OAAO,kBAAkB,cAAc,IAAI,CAAC;AAC9C;;AAKA,SAAgB,iBAAiB,OAAoD;CACnF,OAAO,UAAU,UAAU,UAAU,UAAU,UAAU,SAAS,UAAU;AAC9E;;;;;;AAOA,MAAM,eAAoD;CACxD,CAAC,kBAAkB,MAAM;CACzB,CAAC,aAAa,MAAM;CACpB,CAAC,aAAa,KAAK;CACnB,CAAC,YAAY,KAAK;CAClB,CAAC,qBAAqB,KAAK;AAC7B;;AAGA,SAAS,gBAAgB,IAAoD;CAC3E,IAAI,CAAC,IAAI,OAAO,KAAA;CAChB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC;CAC3B,OAAO,iBAAiB,IAAI,IAAI,OAAO,KAAA;AACzC;;AAGA,SAAS,kBAAkB,KAAyC;CAClE,IAAI;EAIF,MAAM,OAHM,KAAK,MAAM,aAAa,KAAK,KAAK,cAAc,GAAG,MAAM,CAGtD,CAAC,CAAC,gBAAgB,MAAM,GAAG,CAAC,CAAC;EAC5C,OAAO,iBAAiB,IAAI,IAAI,OAAO,KAAA;CACzC,QAAQ;EACN;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,qBACd,MAAc,QAAQ,IAAI,GAC1B,OAAuD,CAAC,GACxC;CAChB,MAAM,MAAM,KAAK,OAAO,QAAQ;CAEhC,IAAI,iBAAiB,KAAK,QAAQ,GAAG,OAAO,KAAK;CAEjD,MAAM,QAAQ,IAAI;CAClB,IAAI,iBAAiB,KAAK,GAAG,OAAO;CAEpC,MAAM,OAAO,gBAAgB,IAAI,qBAAqB;CACtD,IAAI,MAAM,OAAO;CAEjB,IAAI,UAAU;CACd,SAAS;EACP,KAAK,MAAM,CAAC,MAAM,OAAO,cACvB,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,GAAG,OAAO;EAE9C,MAAM,YAAY,kBAAkB,OAAO;EAC3C,IAAI,WAAW,OAAO;EAEtB,IAAI,WAAW,KAAK,SAAS,MAAM,CAAC,GAAG;EACvC,MAAM,SAAS,QAAQ,OAAO;EAC9B,IAAI,WAAW,SAAS;EACxB,UAAU;CACZ;CAEA,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAgB,aAAa,KAAa,SAAiC,CAAC,GAAW;CACrF,IAAI,CAAC,IAAI,WAAW,WAAW,KAAK,QAAQ,WAAW,OAAO;CAC9D,IAAI,IAAI,SAAS,KAAK,CAAC,GAAG,OAAO;CACjC,MAAM,QAAQ,OAAO;CAGrB,IAAI,OAAO,WAAW,IAAI,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,MAAM,CAAC,EAAE;CAC7D,OAAO,GAAG,IAAI;AAChB;;AAGA,SAAgB,eACd,IACA,UACA,OAAyE,CAAC,GACtD;CACpB,MAAM,OAAO,OAAO,QAAQ,YAAY;CACxC,MAAM,UAAU,KAAK,MAAM,CAAC,OAAO,QAAQ,eAAe,IAAI,IAAI,CAAC;CACnE,MAAM,QAAQ,SAAS,KAAK,MAC1B,KAAK,QAAQ,EAAE,WAAW,WAAW,KAAK,MAAM,aAC5C,GAAG,EAAE,GAAG,KAAK,QACb,aAAa,GAAG,KAAK,UAAU,CAAC,CAAC,CACvC;CACA,OAAO,CAAC,IAAI;EAAC;EAAM,GAAG;EAAS,GAAG;CAAK,CAAC;AAC1C;;AAGA,SAAgB,YACd,IACA,UACA,OAA0B,CAAC,GACnB;CACR,MAAM,CAAC,KAAK,QAAQ,eAAe,IAAI,UAAU,IAAI;CACrD,OAAO,GAAG,IAAI,GAAG,KAAK,KAAK,GAAG;AAChC"}
@@ -2,25 +2,28 @@ import { t as readFileSafe } from "./fs-m7ZvuBBm.mjs";
2
2
  import { c as findComponent, n as readLock, o as fetchRegistry, r as sha256 } from "./lock-CW8UuEPJ.mjs";
3
3
  import { t as checkPeerVersions } from "./peer-versions-Cep8Brn3.mjs";
4
4
  //#region src/commands/drift.ts
5
- /**
6
- * `cascivo doctor --drift` — compares installed components against the
7
- * registry. Two drift classes:
8
- *
9
- * 1. Local-edit drift: an installed file's content no longer matches what
10
- * was copied at install time (hand edits, or deleted after install).
11
- * 2. Peer-version drift: the currently-registered component source needs a
12
- * newer `@cascivo/*` peer package (per `peerVersions`) than what's
13
- * actually installed in node_modules — the dashboard-feedback failure
14
- * mode (DataTable referencing an i18n builtin key an older published
15
- * @cascivo/i18n build doesn't have).
16
- */
17
5
  async function runDoctorDrift(config, cwd = process.cwd()) {
18
6
  const lock = await readLock(cwd);
19
7
  if (!lock || Object.keys(lock.items).length === 0) {
20
8
  console.log("No installed components found in cascivo.lock.");
21
- return;
9
+ return {
10
+ ran: false,
11
+ reason: "no components installed (cascivo.lock is empty or missing)",
12
+ issues: 0
13
+ };
14
+ }
15
+ let registry;
16
+ try {
17
+ registry = await fetchRegistry(config.registry);
18
+ } catch (error) {
19
+ const reason = `could not reach the registry (${error instanceof Error ? error.message : String(error)})`;
20
+ console.log(`Drift check skipped: ${reason}`);
21
+ return {
22
+ ran: false,
23
+ reason,
24
+ issues: 0
25
+ };
22
26
  }
23
- const registry = await fetchRegistry(config.registry);
24
27
  let driftCount = 0;
25
28
  for (const [name, entry] of Object.entries(lock.items)) {
26
29
  const current = findComponent(registry, name);
@@ -50,8 +53,12 @@ async function runDoctorDrift(config, cwd = process.cwd()) {
50
53
  console.log(`\n${driftCount} drift issue(s) found.`);
51
54
  process.exitCode = 1;
52
55
  } else console.log("No drift detected — installed components match the registry.");
56
+ return {
57
+ ran: true,
58
+ issues: driftCount
59
+ };
53
60
  }
54
61
  //#endregion
55
62
  export { runDoctorDrift };
56
63
 
57
- //# sourceMappingURL=drift-qTzJ75Ds.mjs.map
64
+ //# sourceMappingURL=drift-B02BbFN_.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drift-B02BbFN_.mjs","names":[],"sources":["../src/commands/drift.ts"],"sourcesContent":["import type { CascadeConfig } from '../utils/config.js'\nimport { readFileSafe } from '../utils/fs.js'\nimport { readLock, sha256 } from '../utils/lock.js'\nimport { checkPeerVersions } from '../utils/peer-versions.js'\nimport { fetchRegistry, findComponent } from '../utils/registry.js'\n\n/**\n * `cascivo doctor --drift` — compares installed components against the\n * registry. Two drift classes:\n *\n * 1. Local-edit drift: an installed file's content no longer matches what\n * was copied at install time (hand edits, or deleted after install).\n * 2. Peer-version drift: the currently-registered component source needs a\n * newer `@cascivo/*` peer package (per `peerVersions`) than what's\n * actually installed in node_modules — the dashboard-feedback failure\n * mode (DataTable referencing an i18n builtin key an older published\n * @cascivo/i18n build doesn't have).\n */\n/**\n * What a drift run actually managed to do.\n *\n * `runDoctorDrift` used to return void, so the default `cascivo doctor` could not run it and\n * still say something honest. That is how \"No violations found.\" got printed by a run that\n * had never looked: the two were disjoint branches, and `--drift` reported five real issues\n * on the same project.\n */\nexport interface DriftOutcome {\n /** False when the check could not run at all (no lockfile, offline, unreachable registry). */\n ran: boolean\n /** Why it could not run — printed instead of an unqualified \"clean\". */\n reason?: string\n issues: number\n}\n\nexport async function runDoctorDrift(\n config: CascadeConfig,\n cwd: string = process.cwd(),\n): Promise<DriftOutcome> {\n const lock = await readLock(cwd)\n if (!lock || Object.keys(lock.items).length === 0) {\n console.log('No installed components found in cascivo.lock.')\n return {\n ran: false,\n reason: 'no components installed (cascivo.lock is empty or missing)',\n issues: 0,\n }\n }\n\n let registry: Awaited<ReturnType<typeof fetchRegistry>>\n try {\n registry = await fetchRegistry(config.registry)\n } catch (error) {\n const reason = `could not reach the registry (${error instanceof Error ? error.message : String(error)})`\n console.log(`Drift check skipped: ${reason}`)\n return { ran: false, reason, issues: 0 }\n }\n let driftCount = 0\n\n for (const [name, entry] of Object.entries(lock.items)) {\n const current = findComponent(registry, name)\n if (!current) continue\n\n for (const [path, lockedHash] of Object.entries(entry.files)) {\n const content = await readFileSafe(path)\n if (content === null) {\n console.log(`${name}: ${path} is missing (installed, then deleted)`)\n driftCount++\n continue\n }\n if (sha256(content) !== lockedHash) {\n console.log(`${name}: ${path} has local edits (differs from the version installed)`)\n driftCount++\n }\n }\n\n if (current.peerVersions) {\n const violations = await checkPeerVersions(cwd, current.peerVersions)\n for (const v of violations) {\n const installedDesc = v.installed ? `${v.installed} is installed` : 'it is not installed'\n console.log(`${name}: needs ${v.pkg} ${v.required}, but ${installedDesc}.`)\n driftCount++\n }\n }\n }\n\n if (driftCount > 0) {\n console.log(`\\n${driftCount} drift issue(s) found.`)\n process.exitCode = 1\n } else {\n console.log('No drift detected — installed components match the registry.')\n }\n return { ran: true, issues: driftCount }\n}\n"],"mappings":";;;;AAkCA,eAAsB,eACpB,QACA,MAAc,QAAQ,IAAI,GACH;CACvB,MAAM,OAAO,MAAM,SAAS,GAAG;CAC/B,IAAI,CAAC,QAAQ,OAAO,KAAK,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;EACjD,QAAQ,IAAI,gDAAgD;EAC5D,OAAO;GACL,KAAK;GACL,QAAQ;GACR,QAAQ;EACV;CACF;CAEA,IAAI;CACJ,IAAI;EACF,WAAW,MAAM,cAAc,OAAO,QAAQ;CAChD,SAAS,OAAO;EACd,MAAM,SAAS,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;EACvG,QAAQ,IAAI,wBAAwB,QAAQ;EAC5C,OAAO;GAAE,KAAK;GAAO;GAAQ,QAAQ;EAAE;CACzC;CACA,IAAI,aAAa;CAEjB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,KAAK,GAAG;EACtD,MAAM,UAAU,cAAc,UAAU,IAAI;EAC5C,IAAI,CAAC,SAAS;EAEd,KAAK,MAAM,CAAC,MAAM,eAAe,OAAO,QAAQ,MAAM,KAAK,GAAG;GAC5D,MAAM,UAAU,MAAM,aAAa,IAAI;GACvC,IAAI,YAAY,MAAM;IACpB,QAAQ,IAAI,GAAG,KAAK,IAAI,KAAK,sCAAsC;IACnE;IACA;GACF;GACA,IAAI,OAAO,OAAO,MAAM,YAAY;IAClC,QAAQ,IAAI,GAAG,KAAK,IAAI,KAAK,sDAAsD;IACnF;GACF;EACF;EAEA,IAAI,QAAQ,cAAc;GACxB,MAAM,aAAa,MAAM,kBAAkB,KAAK,QAAQ,YAAY;GACpE,KAAK,MAAM,KAAK,YAAY;IAC1B,MAAM,gBAAgB,EAAE,YAAY,GAAG,EAAE,UAAU,iBAAiB;IACpE,QAAQ,IAAI,GAAG,KAAK,UAAU,EAAE,IAAI,GAAG,EAAE,SAAS,QAAQ,cAAc,EAAE;IAC1E;GACF;EACF;CACF;CAEA,IAAI,aAAa,GAAG;EAClB,QAAQ,IAAI,KAAK,WAAW,uBAAuB;EACnD,QAAQ,WAAW;CACrB,OACE,QAAQ,IAAI,8DAA8D;CAE5E,OAAO;EAAE,KAAK;EAAM,QAAQ;CAAW;AACzC"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.16.0",
2
+ "version": "0.16.1",
3
3
  "tokens": [
4
4
  { "name": "--cascivo-gray-0", "resolvedDefault": "oklch(1 0 0)" },
5
5
  { "name": "--cascivo-gray-50", "resolvedDefault": "oklch(0.985 0.002 264)" },
@@ -92,6 +92,14 @@
92
92
  { "name": "--cascivo-text-2xl", "resolvedDefault": "1.5rem" },
93
93
  { "name": "--cascivo-text-3xl", "resolvedDefault": "1.875rem" },
94
94
  { "name": "--cascivo-text-4xl", "resolvedDefault": "2.25rem" },
95
+ { "name": "--cascivo-font-size-xs", "resolvedDefault": "0.75rem" },
96
+ { "name": "--cascivo-font-size-sm", "resolvedDefault": "0.875rem" },
97
+ { "name": "--cascivo-font-size-base", "resolvedDefault": "1rem" },
98
+ { "name": "--cascivo-font-size-lg", "resolvedDefault": "1.125rem" },
99
+ { "name": "--cascivo-font-size-xl", "resolvedDefault": "1.25rem" },
100
+ { "name": "--cascivo-font-size-2xl", "resolvedDefault": "1.5rem" },
101
+ { "name": "--cascivo-font-size-3xl", "resolvedDefault": "1.875rem" },
102
+ { "name": "--cascivo-font-size-4xl", "resolvedDefault": "2.25rem" },
95
103
  { "name": "--cascivo-font-normal", "resolvedDefault": "400" },
96
104
  { "name": "--cascivo-font-medium", "resolvedDefault": "500" },
97
105
  { "name": "--cascivo-font-semibold", "resolvedDefault": "600" },
@@ -1117,6 +1125,16 @@
1117
1125
  { "name": "className", "type": "string", "required": false }
1118
1126
  ]
1119
1127
  },
1128
+ {
1129
+ "name": "InfiniteScroll",
1130
+ "props": [
1131
+ { "name": "onLoadMore", "type": "() => Promise<unknown> | unknown", "required": true },
1132
+ { "name": "disabled", "type": "boolean", "required": false },
1133
+ { "name": "rootMargin", "type": "string", "required": false },
1134
+ { "name": "labels", "type": "{ loadMore?: string; loading?: string }", "required": false },
1135
+ { "name": "className", "type": "string", "required": false }
1136
+ ]
1137
+ },
1120
1138
  {
1121
1139
  "name": "InlineLoading",
1122
1140
  "props": [
@@ -1180,6 +1198,18 @@
1180
1198
  { "name": "labels", "type": "{ required?: string }", "required": false }
1181
1199
  ]
1182
1200
  },
1201
+ {
1202
+ "name": "LargeTitleHeader",
1203
+ "props": [
1204
+ { "name": "title", "type": "string", "required": true },
1205
+ { "name": "children", "type": "React.ReactNode", "required": true },
1206
+ { "name": "leading", "type": "React.ReactNode", "required": false },
1207
+ { "name": "actions", "type": "React.ReactNode", "required": false },
1208
+ { "name": "level", "type": "1 | 2 | 3", "required": false },
1209
+ { "name": "collapseDistance", "type": "number", "required": false },
1210
+ { "name": "className", "type": "string", "required": false }
1211
+ ]
1212
+ },
1183
1213
  {
1184
1214
  "name": "Link",
1185
1215
  "props": [
@@ -1531,6 +1561,20 @@
1531
1561
  { "name": "format", "type": "Intl.RelativeTimeFormatOptions", "required": false }
1532
1562
  ]
1533
1563
  },
1564
+ {
1565
+ "name": "ReorderList",
1566
+ "props": [
1567
+ { "name": "value", "type": "ReorderItem[]", "required": true },
1568
+ { "name": "onValueChange", "type": "(value: ReorderItem[]) => void", "required": true },
1569
+ { "name": "disabled", "type": "boolean", "required": false },
1570
+ {
1571
+ "name": "labels",
1572
+ "type": "{ handle?: string; grabbed?: string; moved?: string; dropped?: string; cancelled?: string }",
1573
+ "required": false
1574
+ },
1575
+ { "name": "className", "type": "string", "required": false }
1576
+ ]
1577
+ },
1534
1578
  {
1535
1579
  "name": "Resizable",
1536
1580
  "props": [
@@ -1984,10 +2028,38 @@
1984
2028
  { "name": "avatarProps", "type": "AvatarProps", "required": false }
1985
2029
  ]
1986
2030
  },
2031
+ {
2032
+ "name": "VirtualList",
2033
+ "props": [
2034
+ { "name": "items", "type": "Item[]", "required": true },
2035
+ { "name": "itemHeight", "type": "number", "required": true },
2036
+ { "name": "height", "type": "number", "required": true },
2037
+ {
2038
+ "name": "renderItem",
2039
+ "type": "(item: Item, index: number) => React.ReactNode",
2040
+ "required": true
2041
+ },
2042
+ { "name": "overscan", "type": "number", "required": false },
2043
+ { "name": "ariaLabel", "type": "string", "required": false },
2044
+ { "name": "className", "type": "string", "required": false }
2045
+ ]
2046
+ },
1987
2047
  {
1988
2048
  "name": "VisuallyHidden",
1989
2049
  "props": [{ "name": "children", "type": "ReactNode", "required": true }]
1990
2050
  },
2051
+ {
2052
+ "name": "WheelPicker",
2053
+ "props": [
2054
+ { "name": "options", "type": "WheelPickerOption[]", "required": true },
2055
+ { "name": "value", "type": "string", "required": true },
2056
+ { "name": "onValueChange", "type": "(value: string) => void", "required": true },
2057
+ { "name": "visibleCount", "type": "number", "required": false },
2058
+ { "name": "itemHeight", "type": "number", "required": false },
2059
+ { "name": "ariaLabel", "type": "string", "required": false },
2060
+ { "name": "className", "type": "string", "required": false }
2061
+ ]
2062
+ },
1991
2063
  {
1992
2064
  "name": "AppFrame",
1993
2065
  "props": [
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as detectPackageManager, c as isPackageManager, l as loadConfig, n as DEFAULT_CONFIG$1, o as installCommand, r as THEMES, s as installHint, t as CASCIVO_HOST, u as __exportAll } from "./config-C6GdrbvF.mjs";
2
+ import { a as detectPackageManager, c as isPackageManager, l as loadConfig, n as DEFAULT_CONFIG$1, o as installCommand, r as THEMES, s as installHint, t as CASCIVO_HOST, u as __exportAll } from "./config-D7ddWN_9.mjs";
3
3
  import { n as resolveOutputPath, r as writeFileSafe, t as readFileSafe } from "./fs-m7ZvuBBm.mjs";
4
4
  import { r as fetchTextRetry, t as fetchJson } from "./http-CJZ5W0Fa.mjs";
5
5
  import { a as writeLock, c as findComponent, i as updateLockEntry, n as readLock, o as fetchRegistry, r as sha256, s as fileName, t as createLock } from "./lock-CW8UuEPJ.mjs";
@@ -24,7 +24,11 @@ import { createInterface } from "node:readline/promises";
24
24
  function installPackages(packages, cwd = process.cwd(), opts = {}) {
25
25
  if (packages.length === 0) return true;
26
26
  const pm = opts.pm ?? detectPackageManager(cwd);
27
- const [cmd, args] = installCommand(pm, packages, { dev: opts.dev ?? false });
27
+ const [cmd, args] = installCommand(pm, packages, {
28
+ dev: opts.dev ?? false,
29
+ ...opts.floors ? { floors: opts.floors } : {},
30
+ ...opts.pin ? { pin: opts.pin } : {}
31
+ });
28
32
  console.log(`Installing ${packages.join(", ")} with ${cmd}…`);
29
33
  if (spawnSync(cmd, args, {
30
34
  cwd,
@@ -351,6 +355,17 @@ async function add(names, config, opts = {}) {
351
355
  }
352
356
  const { resolved, missing } = resolveBareClosure(registry, bareSpecs);
353
357
  for (const name of missing) console.error(`Component "${name}" not found in registry. Run "cascivo list".`);
358
+ const announced = /* @__PURE__ */ new Set();
359
+ for (const { entry, requested } of resolved) {
360
+ if (!requested || !entry.install || announced.has(entry.install)) continue;
361
+ announced.add(entry.install);
362
+ console.log(`\n"${entry.name}" ships in the ${entry.install} npm package — no source is copied into your project. Updates come from your package manager, not \`cascivo update\`.`);
363
+ }
364
+ for (const { entry, requested } of resolved) {
365
+ if (!requested || !entry.deprecated) continue;
366
+ const { replacement, since, note } = entry.deprecated;
367
+ console.warn(`\n⚠ "${entry.name}" is deprecated since ${since}. Use "${replacement}" instead:\n cascivo add ${replacement}\n` + (note ? ` ${note}\n` : "") + ` Adding "${entry.name}" anyway — it still works.\n`);
368
+ }
354
369
  const peerFloors = {};
355
370
  for (const { entry } of resolved) for (const [pkg, floor] of Object.entries(entry.peerVersions ?? {})) peerFloors[pkg] = floor;
356
371
  const npmPackages = /* @__PURE__ */ new Set();
@@ -499,11 +514,11 @@ function resolvePackageManagerFlag(args) {
499
514
  * and GETTING-STARTED.md tells adopters to pin exactly. Regenerate with `pnpm regen`.
500
515
  */
501
516
  const CASCIVO_VERSIONS = {
502
- "@cascivo/react": "0.16.0",
503
- "@cascivo/themes": "0.4.10",
504
- "@cascivo/charts": "0.16.0",
505
- "@cascivo/icons": "0.3.7",
506
- "@cascivo/eslint-config": "0.2.1"
517
+ "@cascivo/react": "0.16.1",
518
+ "@cascivo/themes": "0.4.11",
519
+ "@cascivo/charts": "0.16.1",
520
+ "@cascivo/icons": "0.3.8",
521
+ "@cascivo/eslint-config": "0.2.2"
507
522
  };
508
523
  /** `@cascivo/core`'s declared `@preact/signals-react` peer range. */
509
524
  const SIGNALS_PEER = ">=3.0.0";
@@ -667,7 +682,7 @@ function indexHtml(opts) {
667
682
  <title>${opts.name}</title>
668
683
  <style>
669
684
  @layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
670
- cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
685
+ cascivo.platform, cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
671
686
  /* cascivo.example is this app's own slot — above the component/blocks layers so your
672
687
  styles win, below cascivo.override which stays free for one-off hotfixes. The
673
688
  generated AGENTS.md tells the agent to write there, and this statement is what makes
@@ -816,7 +831,9 @@ import cascivo from '@cascivo/eslint-config'
816
831
 
817
832
  export default [
818
833
  js.configs.recommended,
819
- reactHooks.configs['recommended-latest'],
834
+ // NOTE the \`.flat\` — the plugin exports both \`configs['recommended-latest']\` (the legacy
835
+ // eslintrc shape, which applies NOTHING here and reports no error) and this one.
836
+ reactHooks.configs.flat['recommended-latest'],
820
837
  // Spread LAST — flat config is last-wins. This turns off \`react-hooks/immutability\`,
821
838
  // which reports cascivo's signal writes (\`signal.value = next\`) as errors.
822
839
  // See https://cascivo.com/docs/using-with-strict-eslint.md
@@ -880,7 +897,7 @@ This app's declared layer order (in \`index.html\`):
880
897
 
881
898
  \`\`\`css
882
899
  @layer vendor, cascivo.reset, cascivo.base, cascivo.tokens, cascivo.component,
883
- cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
900
+ cascivo.platform, cascivo.theme, cascivo.blocks, cascivo.example, cascivo.override;
884
901
  \`\`\`
885
902
 
886
903
  ### Worked example — nesting, not new layers
@@ -1011,7 +1028,7 @@ async function create(args, cwd = process.cwd()) {
1011
1028
  const templateSpec = flagValue(args, "template");
1012
1029
  if (templateSpec) {
1013
1030
  const { add } = await Promise.resolve().then(() => add_exports);
1014
- const { loadConfig } = await import("./config-C6GdrbvF.mjs").then((n) => n.i);
1031
+ const { loadConfig } = await import("./config-D7ddWN_9.mjs").then((n) => n.i);
1015
1032
  console.log(`\nInstalling template "${templateSpec}"…`);
1016
1033
  await add([templateSpec], await loadConfig(), {
1017
1034
  cwd: targetDir,
@@ -1030,6 +1047,7 @@ async function create(args, cwd = process.cwd()) {
1030
1047
  //#region src/commands/doctor.ts
1031
1048
  var doctor_exports = /* @__PURE__ */ __exportAll({
1032
1049
  checkDuplicateCore: () => checkDuplicateCore,
1050
+ checkFormatterIgnore: () => checkFormatterIgnore,
1033
1051
  checkProjectDependencies: () => checkProjectDependencies,
1034
1052
  checkSignalsCompat: () => checkSignalsCompat,
1035
1053
  checkSsrConfig: () => checkSsrConfig,
@@ -1250,6 +1268,38 @@ const VITE_CONFIG_FILES = [
1250
1268
  * cliff the 2026-07-20 report hit (blocker #1). A text match, not a gate. Returns
1251
1269
  * null when there's no Vite SSR framework or the config already handles it.
1252
1270
  */
1271
+ /**
1272
+ * Warn when the vendored components dir is not excluded from the project's formatter.
1273
+ *
1274
+ * Owning the code means your formatter rewrites it, and `cascivo update` then reports drift
1275
+ * on files you never edited. `cascivo init` writes the exclusion for you, but a project that
1276
+ * adopted cascivo before that existed — or that added Prettier afterwards — never got it.
1277
+ */
1278
+ function checkFormatterIgnore(cwd, outputDir) {
1279
+ const pairs = [{
1280
+ configs: [
1281
+ ".prettierrc",
1282
+ ".prettierrc.json",
1283
+ ".prettierrc.js",
1284
+ "prettier.config.js"
1285
+ ],
1286
+ ignore: ".prettierignore"
1287
+ }, {
1288
+ configs: [".oxfmtrc", ".oxfmtrc.json"],
1289
+ ignore: ".oxfmtignore"
1290
+ }];
1291
+ const dir = outputDir.replace(/\/+$/, "");
1292
+ for (const { configs, ignore } of pairs) {
1293
+ if (!configs.some((f) => existsSync(join(cwd, f)))) continue;
1294
+ let content = "";
1295
+ try {
1296
+ content = readFileSync(join(cwd, ignore), "utf8");
1297
+ } catch {}
1298
+ if (content.split("\n").some((l) => l.trim().replace(/\/+$/, "") === dir)) continue;
1299
+ return `"${dir}/" is not excluded from your formatter. Running it will rewrite code you own, and \`cascivo update\` will then report drift on files you never edited. Add "${dir}/" to ${ignore}.`;
1300
+ }
1301
+ return null;
1302
+ }
1253
1303
  function checkSsrConfig(cwd) {
1254
1304
  let deps = {};
1255
1305
  try {
@@ -1478,9 +1528,7 @@ async function generate(args, config) {
1478
1528
  }
1479
1529
  const { readFileSync } = await import("node:fs");
1480
1530
  const configJson = readFileSync(inputArg, "utf-8");
1481
- const viewConfig = JSON.parse(configJson);
1482
- await fetchRegistry(config.registry);
1483
- const tsx = generateTsx(viewConfig, /* @__PURE__ */ new Map(), componentsDirArg ?? config.outputDir ?? "./src/components/ui");
1531
+ const tsx = generateTsx(JSON.parse(configJson), /* @__PURE__ */ new Map(), componentsDirArg ?? config.outputDir ?? "./src/components/ui");
1484
1532
  const outPath = outArg ?? join(dirname(inputArg), `${basename(inputArg, ".json")}.tsx`);
1485
1533
  writeFileSync(outPath, tsx, "utf-8");
1486
1534
  console.log(`Generated ${outPath}`);
@@ -1570,6 +1618,85 @@ function hintEslintIfPresent(cwd) {
1570
1618
  console.log("\nESLint: copied cascivo source may trip a strict host config on stylistic rules.");
1571
1619
  console.log(" Scope them off your components dir — see docs/USING-WITH-STRICT-ESLINT.md");
1572
1620
  }
1621
+ /**
1622
+ * Write dependency entries into `package.json` so a failed install still leaves a
1623
+ * DECLARATIVE-complete project, one `install` away from working.
1624
+ *
1625
+ * The reported failure: one unrelated bad version range elsewhere in the adopter's
1626
+ * `package.json` made `pnpm add` exit non-zero. cascivo had already written
1627
+ * `cascivo.config.ts`, so the project claimed to be cascivo-configured with none of the
1628
+ * runtime present, and the printed advice ("install them yourself") was the same command
1629
+ * that had just failed. Recording the dependencies is the difference between "recoverable
1630
+ * with one command" and "figure out what was supposed to be here".
1631
+ *
1632
+ * Never overwrites an entry that already exists — the app's own pin wins.
1633
+ */
1634
+ function recordDependencies(cwd, packages, opts) {
1635
+ const pkgPath = join(cwd, "package.json");
1636
+ if (!existsSync(pkgPath)) return [];
1637
+ let pkg;
1638
+ try {
1639
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
1640
+ } catch {
1641
+ return [];
1642
+ }
1643
+ const field = opts.dev ? "devDependencies" : "dependencies";
1644
+ const deps = pkg[field] ??= {};
1645
+ const added = [];
1646
+ for (const name of packages) {
1647
+ if (deps[name] !== void 0) continue;
1648
+ deps[name] = "latest";
1649
+ added.push(name);
1650
+ }
1651
+ if (added.length === 0) return [];
1652
+ writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
1653
+ return added;
1654
+ }
1655
+ /** Formatter ignore files, in the order a project is likely to use them. */
1656
+ const FORMATTER_IGNORES = [{
1657
+ config: [
1658
+ ".prettierrc",
1659
+ ".prettierrc.json",
1660
+ ".prettierrc.js",
1661
+ "prettier.config.js"
1662
+ ],
1663
+ ignore: ".prettierignore"
1664
+ }, {
1665
+ config: [".oxfmtrc", ".oxfmtrc.json"],
1666
+ ignore: ".oxfmtignore"
1667
+ }];
1668
+ /**
1669
+ * Exclude the vendored components dir from the project's formatter.
1670
+ *
1671
+ * Owning the code means your formatter reformats it, and then `cascivo update` reports drift
1672
+ * on files you never touched. This ACTS rather than hints: the ESLint equivalent below only
1673
+ * prints a pointer, and an adopter ran `prettier --write .` before ever reading it.
1674
+ *
1675
+ * Idempotent, and never rewrites an existing line.
1676
+ */
1677
+ function ensureFormatterIgnore(cwd, outputDir) {
1678
+ const line = `${outputDir.replace(/\/+$/, "")}/`;
1679
+ for (const { config, ignore } of FORMATTER_IGNORES) {
1680
+ if (!(config.some((f) => existsSync(join(cwd, f))) || hasPrettierKeyInPackageJson(cwd, ignore))) continue;
1681
+ const path = join(cwd, ignore);
1682
+ const current = existsSync(path) ? readFileSync(path, "utf8") : "";
1683
+ if (current.split("\n").some((l) => l.trim() === line)) continue;
1684
+ const banner = "# cascivo: vendored component source — you own it, so do not reformat it\n";
1685
+ writeFileSync(path, current === "" ? banner + line + "\n" : `${current.replace(/\n*$/, "\n")}\n${banner}${line}\n`, "utf8");
1686
+ console.log(`\nAdded "${line}" to ${ignore} (so your formatter does not rewrite copied source).`);
1687
+ }
1688
+ }
1689
+ /** `prettier` key in package.json counts as a Prettier config — only relevant to .prettierignore. */
1690
+ function hasPrettierKeyInPackageJson(cwd, ignore) {
1691
+ if (ignore !== ".prettierignore") return false;
1692
+ const pkgPath = join(cwd, "package.json");
1693
+ if (!existsSync(pkgPath)) return false;
1694
+ try {
1695
+ return "prettier" in JSON.parse(readFileSync(pkgPath, "utf8"));
1696
+ } catch {
1697
+ return false;
1698
+ }
1699
+ }
1573
1700
  /** The "here's everything you need" summary, printed once at the end of init. */
1574
1701
  function printDependencySummary() {
1575
1702
  console.log("\nDependencies");
@@ -1607,13 +1734,36 @@ async function init(args = [], cwd = process.cwd()) {
1607
1734
  pm,
1608
1735
  dev: true
1609
1736
  });
1610
- if (!runtimeOk || !devOk) process.exitCode = 1;
1737
+ if (!runtimeOk || !devOk) {
1738
+ const recorded = [...runtimeOk ? [] : recordDependencies(cwd, RUNTIME_DEPS, { dev: false }), ...devOk ? [] : recordDependencies(cwd, DEV_DEPS, { dev: true })];
1739
+ console.error("\nInstall failed — cascivo.config.ts was written but the packages are not installed.");
1740
+ if (recorded.length > 0) {
1741
+ console.error(`Wrote ${recorded.length} dependency entries to package.json: ${recorded.join(", ")}`);
1742
+ console.error(`Recover with:\n ${pm} install`);
1743
+ } else console.error(`Recover with:\n ${installHint(pm, RUNTIME_DEPS)}\n ${installHint(pm, DEV_DEPS, { dev: true })}`);
1744
+ process.exitCode = 1;
1745
+ }
1611
1746
  }
1612
- console.log("\nImport the theme in your root CSS or entry file:");
1613
- console.log(` import '@cascivo/themes/${theme}.css'`);
1614
- console.log("Then set the theme on your root element:");
1747
+ console.log("\nStylesheets import these once, in this order, in your entry file:");
1748
+ console.log(` import '@cascivo/tokens' // primitive tokens — every --cascivo-* value`);
1749
+ console.log(` import '@cascivo/themes/${theme}.css'${" ".repeat(Math.max(1, 16 - theme.length))}// the ${theme} theme's semantic values`);
1750
+ console.log(` // …then your component CSS (\`cascivo add\` writes .module.css beside each component)`);
1751
+ console.log("\nThen set the theme on your root element:");
1615
1752
  console.log(` <html data-theme="${theme}">`);
1753
+ console.log("\nSwitching themes at runtime? Use a bundle instead of the single theme:");
1754
+ console.log(" import '@cascivo/themes/light-dark.css' // light + dark — the common case");
1755
+ console.log(" import '@cascivo/themes/all.css' // all twelve themes");
1756
+ console.log(" import { ThemeProvider } from '@cascivo/core'");
1757
+ console.log("\nIn YOUR components, when you read a signal during render:");
1758
+ console.log(" import { useSignals } from '@cascivo/core'");
1759
+ console.log(" function MyComponent() {");
1760
+ console.log(" useSignals() // ← first statement, or the component never re-renders");
1761
+ console.log(" return <span>{count.value}</span>");
1762
+ console.log(" }");
1763
+ console.log("\nAdding a chart later? Charts ship as an npm package with their own stylesheet:");
1764
+ console.log(" import '@cascivo/charts/styles.css' // `cascivo add <chart>` reminds you");
1616
1765
  printDependencySummary();
1766
+ ensureFormatterIgnore(cwd, DEFAULT_CONFIG$1.outputDir);
1617
1767
  hintEslintIfPresent(cwd);
1618
1768
  console.log("\nAdd components with: cascivo add <name>");
1619
1769
  }
@@ -1628,12 +1778,22 @@ const TYPE_LABELS = {
1628
1778
  flow: "Flow (npm: @cascivo/flow)",
1629
1779
  editor: "Editor (npm: @cascivo/editor)"
1630
1780
  };
1781
+ /**
1782
+ * Deprecation marker for the listing.
1783
+ *
1784
+ * Shown at DISCOVERY time on purpose. `overflow-menu` carried a `@deprecated` JSDoc in its
1785
+ * source for months, which an adopter only meets after they have already vendored the file —
1786
+ * and it pointed at an import path that cannot resolve on either install path.
1787
+ */
1788
+ function deprecationSuffix(c) {
1789
+ return c.deprecated ? ` ⚠ deprecated → ${c.deprecated.replacement}` : "";
1790
+ }
1631
1791
  /** Render a group of entries as an aligned text table (no section header). */
1632
1792
  function formatGroup(entries) {
1633
1793
  const rows = entries.map((c) => [
1634
1794
  c.name,
1635
1795
  c.category,
1636
- c.description
1796
+ c.description + deprecationSuffix(c)
1637
1797
  ]);
1638
1798
  const headers = [
1639
1799
  "Name",
@@ -2697,12 +2857,12 @@ async function run(args) {
2697
2857
  case "doctor": {
2698
2858
  const ci = rest.includes("--ci");
2699
2859
  if (rest.includes("--drift")) {
2700
- const { runDoctorDrift } = await import("./drift-qTzJ75Ds.mjs");
2860
+ const { runDoctorDrift } = await import("./drift-B02BbFN_.mjs");
2701
2861
  await runDoctorDrift(await loadConfig());
2702
2862
  } else {
2703
2863
  const cwd = process.cwd();
2704
2864
  const result = await runDoctor(cwd);
2705
- const { checkDuplicateCore, checkProjectDependencies, checkSignalsCompat, checkSsrConfig, detectInstallPath } = await Promise.resolve().then(() => doctor_exports);
2865
+ const { checkDuplicateCore, checkFormatterIgnore, checkProjectDependencies, checkSignalsCompat, checkSsrConfig, detectInstallPath } = await Promise.resolve().then(() => doctor_exports);
2706
2866
  const adopter = detectInstallPath(cwd) !== "unknown";
2707
2867
  const deps = adopter ? checkProjectDependencies(cwd) : [];
2708
2868
  const missingRequired = deps.filter((d) => d.required);
@@ -2710,15 +2870,23 @@ async function run(args) {
2710
2870
  const signalsError = signalsCompat?.severity === "error";
2711
2871
  const ssrHint = adopter ? checkSsrConfig(cwd) : null;
2712
2872
  const duplicateCore = adopter ? await checkDuplicateCore(cwd) : null;
2713
- if (result.passed && deps.length === 0 && signalsCompat === null && ssrHint === null && duplicateCore === null) console.log("No violations found.");
2873
+ const formatterHint = adopter ? checkFormatterIgnore(cwd, (await loadConfig()).outputDir) : null;
2874
+ const { runDoctorDrift } = await import("./drift-B02BbFN_.mjs");
2875
+ const driftOutcome = adopter ? await runDoctorDrift(await loadConfig(), cwd) : {
2876
+ ran: false,
2877
+ reason: "no cascivo install detected",
2878
+ issues: 0
2879
+ };
2880
+ if (result.passed && deps.length === 0 && signalsCompat === null && ssrHint === null && duplicateCore === null && formatterHint === null && driftOutcome.issues === 0) console.log(driftOutcome.ran ? "No violations found." : `No violations found (drift: not checked — ${driftOutcome.reason}).`);
2714
2881
  else {
2715
2882
  for (const v of result.violations) console.error(`[${v.rule}] ${v.detail}\n ${v.file}`);
2716
2883
  for (const d of missingRequired) console.error(d.kind === "forbidden" ? `[forbidden-dependency] ${d.package} must not be a direct dependency here. Remove it — ${d.hint}` : `[missing-dependency] ${d.package} is not in package.json. Install: ${d.hint}`);
2717
2884
  if (signalsCompat) (signalsError ? console.error : console.log)(`[${signalsError ? "signals-incompatible" : "signals-outdated"}] ${signalsCompat.detail} Upgrade: ${signalsCompat.hint}`);
2718
2885
  if (ssrHint) console.log(`[ssr-config] ${ssrHint}`);
2886
+ if (formatterHint) console.log(`[formatter-drift] ${formatterHint}`);
2719
2887
  if (duplicateCore) console.error(`[duplicate-core] More than one @cascivo/core is installed (root: ${duplicateCore.root ?? "none"}; ${duplicateCore.nested.join("; ")}). ` + duplicateCore.hint);
2720
2888
  for (const d of deps.filter((x) => !x.required)) console.log(`[optional] ${d.package} is not installed; add it when a component or chart needs it: ${d.hint}`);
2721
- if (ci && (result.violations.length > 0 || missingRequired.length > 0 || signalsError || duplicateCore !== null)) process.exitCode = 1;
2889
+ if (ci && (result.violations.length > 0 || missingRequired.length > 0 || signalsError || duplicateCore !== null || driftOutcome.issues > 0)) process.exitCode = 1;
2722
2890
  }
2723
2891
  }
2724
2892
  break;