@tenphi/tasty 2.6.2 → 2.6.3

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.
Files changed (44) hide show
  1. package/dist/async-storage-B7_o6FKt.js.map +1 -1
  2. package/dist/{collector-mnshMqSq.js → collector-Crs9kGJW.js} +3 -3
  3. package/dist/{collector-mnshMqSq.js.map → collector-Crs9kGJW.js.map} +1 -1
  4. package/dist/{config-r9Wc94ks.js → config-BaxtQMS3.js} +76 -43
  5. package/dist/{config-r9Wc94ks.js.map → config-BaxtQMS3.js.map} +1 -1
  6. package/dist/context-CkSg-kDT.js.map +1 -1
  7. package/dist/core/index.d.ts +1 -1
  8. package/dist/core/index.js +5 -5
  9. package/dist/{core-ZlQf3x-x.js → core-CmxaoZ-N.js} +5 -5
  10. package/dist/{core-ZlQf3x-x.js.map → core-CmxaoZ-N.js.map} +1 -1
  11. package/dist/{css-writer-Bkf5A_Sm.js → css-writer-CPy_cbFJ.js} +3 -3
  12. package/dist/{css-writer-Bkf5A_Sm.js.map → css-writer-CPy_cbFJ.js.map} +1 -1
  13. package/dist/format-global-rules-Dbc_1tc3.js.map +1 -1
  14. package/dist/{format-rules-Cy70prSz.js → format-rules-CPirO_Mj.js} +2 -2
  15. package/dist/{format-rules-Cy70prSz.js.map → format-rules-CPirO_Mj.js.map} +1 -1
  16. package/dist/{hydrate-2BQlSO9P.js → hydrate-CVn-A_3y.js} +2 -2
  17. package/dist/{hydrate-2BQlSO9P.js.map → hydrate-CVn-A_3y.js.map} +1 -1
  18. package/dist/{index-J7y6BV89.d.ts → index-B_k47mc_.d.ts} +40 -16
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +6 -6
  21. package/dist/index.js.map +1 -1
  22. package/dist/{keyframes-C15dNGU3.js → keyframes-vpzoVdUR.js} +2 -2
  23. package/dist/{keyframes-C15dNGU3.js.map → keyframes-vpzoVdUR.js.map} +1 -1
  24. package/dist/{merge-styles-Bl0X9hSR.js → merge-styles-BzQutdAK.js} +2 -2
  25. package/dist/{merge-styles-Bl0X9hSR.js.map → merge-styles-BzQutdAK.js.map} +1 -1
  26. package/dist/{resolve-recipes-D76rfxNo.js → resolve-recipes-DgH8A3Nn.js} +3 -3
  27. package/dist/{resolve-recipes-D76rfxNo.js.map → resolve-recipes-DgH8A3Nn.js.map} +1 -1
  28. package/dist/ssr/astro-client.js +1 -1
  29. package/dist/ssr/astro-client.js.map +1 -1
  30. package/dist/ssr/astro-middleware.js.map +1 -1
  31. package/dist/ssr/astro.js +3 -3
  32. package/dist/ssr/astro.js.map +1 -1
  33. package/dist/ssr/index.js +3 -3
  34. package/dist/ssr/next.js +4 -4
  35. package/dist/ssr/next.js.map +1 -1
  36. package/dist/static/index.js +1 -1
  37. package/dist/static/index.js.map +1 -1
  38. package/dist/static/inject.js.map +1 -1
  39. package/dist/zero/babel.js +4 -4
  40. package/dist/zero/babel.js.map +1 -1
  41. package/dist/zero/index.js +1 -1
  42. package/dist/zero/next.js.map +1 -1
  43. package/docs/pipeline.md +40 -14
  44. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { createJiti } from 'jiti';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n const jiti = createJiti(wpProjectDir, {\n moduleCache: false,\n });\n\n babelPluginOptions.config = () => {\n return jiti(wpAbsoluteConfigFile) as TastyZeroConfig;\n };\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"next.js","names":[],"sources":["../../src/zero/next.ts"],"sourcesContent":["/**\n * Next.js configuration wrapper for tasty-zero.\n *\n * Supports both webpack and Turbopack bundlers:\n * - **webpack**: Injects a babel-loader rule with the tasty-zero Babel plugin\n * via `webpack()` config hook. Config is passed as a jiti factory function.\n * - **Turbopack**: Adds a `turbopack.rules` entry with babel-loader and\n * JSON-serializable options (`configFile` path instead of a function).\n * The Babel plugin loads the config internally via jiti.\n *\n * The generated CSS is injected automatically — `@tenphi/tasty/static`\n * imports are replaced with an import of the output CSS file at build time.\n * No manual CSS import in layout files is needed.\n *\n * @example\n * ```javascript\n * // next.config.js\n * const { withTastyZero } = require('@tenphi/tasty/next');\n *\n * module.exports = withTastyZero({\n * output: 'public/tasty.css',\n * configFile: './app/tasty-zero.config.ts',\n * })({\n * // your Next.js config\n * });\n * ```\n */\n\nimport { createRequire } from 'module';\nimport * as path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { createJiti } from 'jiti';\n\nimport type { TastyZeroBabelOptions, TastyZeroConfig } from './babel';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Next.js types (inline to avoid requiring next as a dependency)\ninterface WebpackConfigContext {\n isServer: boolean;\n dev: boolean;\n buildId: string;\n dir: string;\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- webpack/Next.js config types are complex */\ninterface TurbopackLoaderItem {\n loader: string;\n options?: Record<string, unknown>;\n}\n\ninterface TurbopackRuleConfig {\n loaders: (string | TurbopackLoaderItem)[];\n as?: string;\n condition?: unknown;\n}\n\ninterface TurbopackConfig {\n rules?: Record<string, TurbopackRuleConfig | TurbopackRuleConfig[]>;\n [key: string]: unknown;\n}\n\ninterface NextConfig {\n webpack?: (config: any, context: WebpackConfigContext) => any;\n turbopack?: TurbopackConfig;\n [key: string]: unknown;\n}\n\nexport interface TastyZeroNextOptions {\n /**\n * Output path for CSS relative to project root.\n * @default 'public/tasty.css'\n */\n output?: string;\n\n /**\n * Whether to enable the plugin.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Tasty configuration for build-time processing.\n * For static configs that don't change during dev.\n *\n * For configs that depend on theme files, use `configFile` instead.\n */\n config?: TastyZeroConfig;\n\n /**\n * Path to a TypeScript/JavaScript module that exports the tasty zero config\n * as its default export. The module is re-evaluated on each\n * compilation, enabling hot reload when the file (or its imports) change.\n *\n * @example './app/tasty-zero.config.ts'\n */\n configFile?: string;\n\n /**\n * Extra file paths (relative to project root) that the config depends on.\n * When any of these files change, the Babel cache is invalidated and\n * the config is re-evaluated.\n *\n * The `configFile` itself is always tracked automatically.\n * Use this for transitive dependencies that aren't directly imported\n * by the config file, or when using `config` instead of `configFile`.\n *\n * @example ['./app/theme.ts']\n */\n configDeps?: string[];\n\n /**\n * Output mode for extracted CSS.\n *\n * - `'file'` (default): CSS is written to a single output file.\n * - `'inject'`: CSS is embedded inline in JS and injected at runtime.\n * No CSS file is written. Best for reusable components and extensions.\n *\n * When `mode` is `'inject'`, `output` is ignored.\n *\n * @default 'file'\n */\n mode?: 'file' | 'inject';\n}\n\n/**\n * Next.js configuration wrapper for tasty-zero.\n * Configures both webpack and Turbopack bundlers automatically.\n */\nexport function withTastyZero(options: TastyZeroNextOptions = {}) {\n const {\n output = 'public/tasty.css',\n enabled = true,\n config: tastyConfig,\n configFile,\n configDeps = [],\n mode,\n } = options;\n\n return (nextConfig: NextConfig = {}): NextConfig => {\n if (!enabled) {\n return nextConfig;\n }\n\n const projectDir = process.cwd();\n const absoluteOutput = path.resolve(projectDir, output);\n const babelPluginPath = path.resolve(__dirname, 'babel.js');\n\n const absoluteConfigFile = configFile\n ? path.resolve(projectDir, configFile)\n : undefined;\n\n const allDeps = [\n ...(absoluteConfigFile ? [absoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(projectDir, dep)),\n ];\n\n // --- Turbopack configuration ---\n // Turbopack loader options must be JSON-serializable (no functions).\n // The Babel plugin loads config internally via `configFile` path + jiti.\n const turbopackBabelOptions: Record<string, unknown> = {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [\n [\n babelPluginPath,\n {\n output: absoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n ...(absoluteConfigFile\n ? { configFile: absoluteConfigFile }\n : tastyConfig\n ? { config: tastyConfig }\n : {}),\n ...(allDeps.length > 0 ? { configDeps: allDeps } : {}),\n },\n ],\n ],\n };\n\n const existingTurbopack = nextConfig.turbopack || {};\n const existingRules = existingTurbopack.rules || {};\n\n const existingExperimental =\n (nextConfig.experimental as Record<string, unknown>) || {};\n\n return {\n ...nextConfig,\n\n experimental: {\n ...existingExperimental,\n turbopackUseBuiltinBabel: true,\n },\n\n turbopack: {\n ...existingTurbopack,\n rules: {\n ...existingRules,\n '*.{ts,tsx,js,jsx}': {\n condition: { not: 'foreign' },\n loaders: [\n {\n loader: 'babel-loader',\n options: turbopackBabelOptions,\n },\n ],\n },\n },\n },\n\n webpack(config: any, context: WebpackConfigContext) {\n const { dir } = context;\n\n const wpProjectDir = dir || projectDir;\n const wpAbsoluteOutput = path.resolve(wpProjectDir, output);\n const projectRequire = createRequire(\n path.resolve(wpProjectDir, 'package.json'),\n );\n\n const wpAbsoluteConfigFile = configFile\n ? path.resolve(wpProjectDir, configFile)\n : undefined;\n\n const wpAllDeps = [\n ...(wpAbsoluteConfigFile ? [wpAbsoluteConfigFile] : []),\n ...configDeps.map((dep) => path.resolve(wpProjectDir, dep)),\n ];\n\n const babelPluginOptions: TastyZeroBabelOptions = {\n output: wpAbsoluteOutput,\n injectImport: true,\n ...(mode ? { mode } : {}),\n };\n\n if (wpAbsoluteConfigFile) {\n const jiti = createJiti(wpProjectDir, {\n moduleCache: false,\n });\n\n babelPluginOptions.config = () => {\n return jiti(wpAbsoluteConfigFile) as TastyZeroConfig;\n };\n } else if (tastyConfig) {\n babelPluginOptions.config = tastyConfig;\n }\n\n if (wpAllDeps.length > 0) {\n babelPluginOptions.configDeps = wpAllDeps;\n }\n\n const babelPluginConfig = [babelPluginPath, babelPluginOptions];\n\n const existingRule = config.module?.rules?.find(\n (rule: any) =>\n rule.use?.loader === 'babel-loader' ||\n rule.use?.some?.((u: any) => u.loader === 'babel-loader'),\n );\n\n if (existingRule) {\n const babelUse = Array.isArray(existingRule.use)\n ? existingRule.use.find((u: any) => u.loader === 'babel-loader')\n : existingRule.use;\n\n if (babelUse?.options) {\n babelUse.options.plugins = babelUse.options.plugins || [];\n babelUse.options.plugins.push(babelPluginConfig);\n }\n } else {\n config.module = config.module || {};\n config.module.rules = config.module.rules || [];\n config.module.rules.push({\n test: /\\.(tsx?|jsx?)$/,\n exclude: /node_modules/,\n use: [\n {\n loader: projectRequire.resolve('babel-loader'),\n options: {\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: ['typescript', 'jsx', 'decorators-legacy'],\n },\n plugins: [babelPluginConfig],\n },\n },\n ],\n });\n }\n\n if (typeof nextConfig.webpack === 'function') {\n return nextConfig.webpack(config, context);\n }\n\n return config;\n },\n };\n };\n}\n"],"mappings":"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"}
package/docs/pipeline.md CHANGED
@@ -179,38 +179,64 @@ The condition tree representation enables:
179
179
 
180
180
  ### What It Does
181
181
 
182
- Collapses parsed entries that share the same value. Only **non-default** entries are merged — an entry with the default state (`''` → `TrueCondition`) is never merged with a non-default entry.
182
+ Collapses parsed entries that share the same value when doing so provably preserves the authored cascade. Only **non-default** entries are merged — an entry with the default state (`''` → `TrueCondition`) is never merged with a non-default entry.
183
183
 
184
184
  ### How It Works
185
185
 
186
- 1. Group entries by serialized value.
187
- 2. Within each group, split out default (TRUE) entries.
188
- 3. Keep default entries as-is; they must retain TRUE so they participate correctly in exclusive building.
189
- 4. Combine non-default entries into a single entry with condition `OR(e1.condition, e2.condition, …)`, simplified via `simplifyCondition`. The merged entry keeps the **highest** priority in the group.
190
- 5. Re-sort by priority (highest first).
186
+ Entries arrive sorted highest-priority-first. For each entry the pass walks already-emitted entries from most-recent backward, looking for the nearest same-value entry where merging is safe. If found, the two entries collapse into one (priority becomes the maximum of the pair, condition becomes the simplified `OR`). Default entries are emitted as singletons and never participate in a merge.
187
+
188
+ ### The Safety Condition
189
+
190
+ Merging two same-value entries with conditions `C_h` (higher priority) and `C_l` (lower priority) lifts the lower one up to `p_h` and changes the higher-priority "blocker" for every intermediate-priority entry from `!C_h` to `!(C_h | C_l) = !C_h & !C_l`. The added `!C_l` constraint can incorrectly block an intermediate entry that should have won.
191
+
192
+ The merge is safe iff for every entry `e_m` strictly between them in priority with a different value,
193
+
194
+ simplify(C_m & C_l & !C_h) = FALSE
195
+
196
+ i.e. there is no scenario where the intermediate state could have matched, the lower same-value entry would also have matched, and the higher one would not. This is the only way the merge could leak through and shadow the intermediate state.
191
197
 
192
198
  ### Why
193
199
 
194
- Without this, a value map like `{ '@dark': 'red', '@dark & @hc': 'red' }` would create two separate entries that later produce two CSS rules with identical output. Merging before exclusive building keeps the exclusive condition algebra small and avoids duplicate CSS.
200
+ Without this pass a value map like `{ '@dark': 'red', '@dark & @hc': 'red' }` would create two separate entries that later produce two CSS rules with identical output. Merging before exclusive building keeps the exclusive condition algebra small and avoids duplicate CSS. The safety check ensures we never break the cascade in service of this optimization.
195
201
 
196
- **Why defaults are kept separate:** merging `TRUE | X` collapses to `TRUE`, destroying X's participation in the exclusive cascade. Intermediate-priority states would then lose their `:not(X)` negation, producing overlapping CSS rules. See `exclusive.ts:140-160` for the rationale.
202
+ **Why defaults are kept separate:** merging `TRUE | X` collapses to `TRUE`, destroying X's participation in the exclusive cascade. Intermediate-priority states would then lose their `:not(X)` negation, producing overlapping CSS rules.
197
203
 
198
- ### Example
204
+ ### Example — Safe merge (still collapses)
199
205
 
200
206
  ```typescript
201
- // Input entries (highest priority first)
207
+ // { '@dark & @hc': 'red', '@dark': 'red' }
208
+ // Input entries (highest priority first), no intermediate different-value entry
202
209
  [
203
- { stateKey: '@dark & @hc', value: 'red', condition: dark & hc },
204
- { stateKey: '@dark', value: 'red', condition: dark },
210
+ { stateKey: '@dark & @hc', value: 'red', condition: dark & hc, priority: 1 },
211
+ { stateKey: '@dark', value: 'red', condition: dark, priority: 0 },
205
212
  ]
206
213
 
207
- // Output: one merged entry
214
+ // Safe: no intermediates. Output: one merged entry
208
215
  [
209
216
  { stateKey: '@dark & @hc | @dark', value: 'red',
210
- condition: simplify((dark & hc) | dark) = dark }
217
+ condition: simplify((dark & hc) | dark) = dark, priority: 1 }
211
218
  ]
212
219
  ```
213
220
 
221
+ The compound dark/HC dedup pattern `{ '': light, '@dark': dark, '@hc': hc, '@dark & @hc': dark }` also collapses cleanly because `@hc & @dark & !(@dark & @hc)` simplifies to FALSE — the intermediate `@hc` is structurally blocked by the contradiction.
222
+
223
+ ### Example — Unsafe merge (must NOT collapse)
224
+
225
+ ```typescript
226
+ // { hovered: 'red', pressed: 'blue', disabled: 'red' }
227
+ // Authored cascade: disabled > pressed > hovered.
228
+ // Merging hovered (priority 0) with disabled (priority 2) would lift
229
+ // hovered to priority 2 and rewrite `pressed`'s exclusive from
230
+ // `pressed & !disabled` to `pressed & !disabled & !hovered`, making
231
+ // `pressed + hovered` resolve to red instead of blue.
232
+
233
+ // `simplify(pressed & hovered & !disabled)` is not FALSE — three
234
+ // independent modifiers can all be active — so the entries are kept
235
+ // separate. Stage 6 `mergeByValue` will later combine the two red
236
+ // CSS rules into one selector group, but only after the cascade is
237
+ // correctly resolved.
238
+ ```
239
+
214
240
  ---
215
241
 
216
242
  ## Stage 2a: Expand User OR Branches
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/tasty",
3
- "version": "2.6.2",
3
+ "version": "2.6.3",
4
4
  "description": "A design-system-integrated styling system and DSL for concise, state-aware UI styling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -147,7 +147,7 @@
147
147
  "prettier": "^3.8.1",
148
148
  "react": "^19.0.0",
149
149
  "size-limit": "^12.0.0",
150
- "tsdown": "^0.21.7",
150
+ "tsdown": "^0.22.0",
151
151
  "typescript": "^6.0.2",
152
152
  "typescript-eslint": "^8.56.0",
153
153
  "vitest": "^4.0.18"