rainbowindex 0.3.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,141 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.1] - 2026-08-25
9
+
10
+ ### Added
11
+
12
+ - **Automatic zero-CLS font fallbacks.** Any `@font` slot whose family is in
13
+ the built-in metrics table (~100 common Google + system families, generated
14
+ from `@capsizecss/metrics` at development time — no new runtime dependency)
15
+ now gets a metrics-adjusted local fallback `@font-face` automatically. The
16
+ fallback font is picked from the slot's stack, or by the web font's
17
+ category (Arial / Times New Roman / Courier New). Opt out with
18
+ `metrics: none;`, pick the matched local font with `metrics: "Segoe UI";`,
19
+ or keep full manual control with
20
+ `metrics: "Arial" <size-adjust> <ascent> <descent> <line-gap>;`.
21
+ - **`face:` entries** — local font files are now declared as repeatable
22
+ `face: <src> [{ overrides }]` entries, one grammar for every face:
23
+ `display: "Satoshi" { face: /Satoshi.woff2; face: /Satoshi-Italic.woff2 { style: italic; } }`.
24
+ - **Fallback stacks work with `from google`** —
25
+ `sans: "Inter", ui-sans-serif, sans-serif from google;` now parses as a
26
+ google slot with fallbacks (previously it silently became a manual stack).
27
+ - **@font now warns instead of ignoring silently**: unknown option keys
28
+ (RI-1217), `preload` on non-local slots where it can have no effect
29
+ (RI-1219), and partial or invalid `metrics` values (RI-1220).
30
+
31
+ ### Changed
32
+
33
+ - The `@font` parser was rebuilt on the shared entry scanner: one pass, no
34
+ regex preambles, no re-serialization. Public API (`parseFontBody`,
35
+ `parseNestedFontBlock`) is unchanged.
36
+ - **Incremental rebuilds only re-do changed work** — the scanner keeps a
37
+ per-file cache keyed on mtime + size, so a CLI watch or Vite HMR rebuild
38
+ re-reads and re-extracts only the files that actually changed instead of
39
+ the whole project. The CSS entry analysis is memoized on the entry text
40
+ (stable theme identity across rebuilds), Google-font resolution keeps its
41
+ identity when metadata is unchanged, and per-class compilation results
42
+ (rule, warnings, token usage) are replayed across rebuilds from a
43
+ theme-keyed cache. A one-file edit in a large project now costs
44
+ O(changed files) instead of O(project).
45
+ - **`ri()` conditional args hit the cache fast path** — falsy arguments
46
+ (`ri("flex", isActive && "bg-blue-500")`) no longer force re-tokenization
47
+ on every call: the raw-key cache skips falsy primitives, making cached
48
+ conditional-pattern calls ~4x faster.
49
+ - **Cheaper compile passes** — duplicate class names across `@apply` rules
50
+ resolve once per pass, the `@slot`/`@apply` walks no longer traverse the
51
+ generated utility CSS, custom `@utility` bodies parse once per body text
52
+ instead of once per variant form, and the editor candidate collector's
53
+ context assignment went from quadratic to a sorted sweep. As part of the
54
+ compile-result replay, engine-level compilation warnings are now
55
+ deduplicated (final project output already was).
56
+ - **Internal restructuring (behavior-preserving)** — the directive body
57
+ parsers were split by grammar family: `@color` and `@font` now own their
58
+ own modules, and the generic key-value/entry grammars sit together in the
59
+ directives foundation. The `parsers` import surface is unchanged.
60
+
61
+ ### Deprecated
62
+
63
+ - The older `@font` forms still parse and desugar into the new model, but
64
+ warn (RI-1218) and will be removed in a future release: `@face { src: …; }`
65
+ blocks and the `italic:` shorthand (use `face:`), `from "<path>"` (use
66
+ `face:`), `from system` (use bare `system`), the `fallback:` key (list
67
+ fallbacks in the slot preamble), the five-key metrics cluster
68
+ `metricsFallback`/`sizeAdjust`/`ascent`/`descent`/`lineGap` (use
69
+ `metrics:`), and the `unicodeRange` spelling (use `unicode-range`).
70
+
71
+ ### Removed
72
+
73
+ - **`@font` `subset:`** — the Google css2 API takes no subset hint, so the
74
+ key never affected the emitted URL. It now warns (RI-1218) and is ignored.
75
+ - **`FontSlot` / `FontFace` fields** — the five slot-level metrics fields
76
+ (`metricsFallback`, `sizeAdjust`, `ascent`, `descent`, `lineGap`) are
77
+ replaced by a single `metrics?: FontMetricsConfig | null`; slot-level
78
+ `preload` moved onto the faces; `FontFace.subset` is gone. Stylesheets are
79
+ unaffected — this only touches code importing those types directly.
80
+
81
+ ### Fixed
82
+
83
+ - **`ri()` stroke-width vs stroke-color conflict — for real this time.** The
84
+ v0.4.0 changelog described this fix, but the implementation did not ship in
85
+ that release: `ri("stroke-2 stroke-red-500")` still dropped `stroke-2`. The
86
+ `stroke` prefix is now actually width-vs-color dual-mode (mirroring
87
+ `border`/`outline`): decimal and non-color arbitrary values claim
88
+ `stroke-width`, color values claim `stroke`. Same-property conflicts
89
+ (`stroke-2` vs `stroke-4`, `stroke-red-500` vs `stroke-blue-500`) still
90
+ merge as before. The emission↔claim parity suite no longer carries a
91
+ stroke exception, and regression tests pin the merged output.
92
+ - **Directive rewriter no longer duplicates unclosed blocks** — an
93
+ unterminated `{` inside a `@color` body made the Vite-plugin pre-pass
94
+ re-emit the text before it (`@color { accent: 0.18 330 { inline` came out
95
+ with the entry doubled). The rewriter's two hand-rolled brace walks were
96
+ replaced by one shared walker built on the same brace matcher the directive
97
+ scan already uses, which also makes quotes and comments inside blocks
98
+ behave consistently across the rewrite passes.
99
+
100
+ ## [0.4.0] - 2026-08-20
101
+
102
+ ### Added
103
+
104
+ - **GitHub Packages publishing** — every release is now also published to
105
+ GitHub Packages as `@rainbowindex/rainbowindex` (that registry requires an
106
+ owner-scoped name). The README documents installing it under the usual
107
+ name via `pnpm add rainbowindex@npm:@rainbowindex/rainbowindex`, so all
108
+ documented imports and CLI invocations work unchanged.
109
+
110
+ ### Changed
111
+
112
+ - **Stricter CLI argument validation** — the CLI is now driven by a
113
+ declarative command table that knows which flags and positionals each
114
+ command accepts. Flags used under the wrong command, stray positional
115
+ arguments, and flags missing their value are rejected with an error
116
+ instead of being silently ignored.
117
+ - **Internal restructuring** (behavior-preserving, net ≈ −4,500 lines) —
118
+ the merge, scanner, and effects modules were split into focused modules;
119
+ the spacing/border prefix→property maps are now single-sourced from the
120
+ merge conflict table with a machine-checked parity test; `@apply` reuses
121
+ the engine's cascade ordering; the CLI and PostCSS builds share one
122
+ orchestration path with parallelized font fetches; and all internal
123
+ import cycles were broken. Public entry points are unchanged.
124
+
125
+ ### Fixed
126
+
127
+ - **`ri()` stroke-width vs stroke-color conflict** — width-shaped `stroke-*`
128
+ classes (`stroke-2`, `stroke-1.5`, `stroke-[3px]`) were claiming the
129
+ `stroke` color property in the merge conflict table, so
130
+ `ri("stroke-2 stroke-red-500")` dropped `stroke-2` even though the two
131
+ classes set different CSS properties. The `stroke` prefix is now
132
+ width-vs-color dual-mode (mirroring `border`/`outline`): decimal and
133
+ non-color arbitrary values claim `stroke-width`, color values claim
134
+ `stroke`. Same-property conflicts (`stroke-2` vs `stroke-4`,
135
+ `stroke-red-500` vs `stroke-blue-500`) still merge as before.
136
+ - **Watch mode double rebuild** — debouncing and rate-limiting now share a
137
+ single timer, so a burst of file changes landing inside the rate-limit
138
+ window schedules one rebuild instead of a redundant second one.
139
+ - **`@color` entries without a colon** — a colon-less fragment in an
140
+ `@color` block now produces a warning and is skipped on its own; it no
141
+ longer swallowed the entry that followed it.
142
+
8
143
  ## [0.3.0] - 2026-08-14
9
144
 
10
145
  ### Added — editor tooling API (phase 2)
package/README.md CHANGED
@@ -28,6 +28,28 @@ pnpm add rainbowindex
28
28
 
29
29
  Requires Node `>=20.19`. The package is **ESM-only** — there is no CommonJS build, so `require("rainbowindex")` is only supported on runtimes that can `require()` ES modules (Node 20.19+); use `import` otherwise (e.g. an ESM `postcss.config.js`/`.mjs`). `postcss` is a required peer dependency, `vite` an optional one; `lightningcss`, `chokidar`, and `tinyglobby` are bundled as direct deps.
30
30
 
31
+ ### From GitHub Packages
32
+
33
+ Every release is also published to [GitHub Packages](https://github.com/rainbowindex/rainbowindex/pkgs/npm/rainbowindex) as `@rainbowindex/rainbowindex` — the same build under an owner-scoped name, which that registry requires. Point the scope at the registry in your project's `.npmrc`:
34
+
35
+ ```ini
36
+ @rainbowindex:registry=https://npm.pkg.github.com
37
+ ```
38
+
39
+ GitHub Packages authenticates every read, including public ones, so put a [personal access token (classic)](https://github.com/settings/tokens) with the `read:packages` scope in your `~/.npmrc`:
40
+
41
+ ```ini
42
+ //npm.pkg.github.com/:_authToken=YOUR_TOKEN
43
+ ```
44
+
45
+ Then install under the alias `rainbowindex`, so every import, `@import`, and CLI invocation in the docs below works unchanged:
46
+
47
+ ```sh
48
+ pnpm add rainbowindex@npm:@rainbowindex/rainbowindex
49
+ ```
50
+
51
+ Without the alias — `pnpm add @rainbowindex/rainbowindex` — the package resolves under its scoped name, and specifiers become `@rainbowindex/rainbowindex`, `@rainbowindex/rainbowindex/vite`, and so on.
52
+
31
53
  ## Quick start (Vite)
32
54
 
33
55
  The fastest path is the Vite plugin, which auto-injects PostCSS config and discovers your CSS entry on first dev-server listen.
@@ -189,7 +211,7 @@ Customization happens in your CSS input, not a JS config. The engine recognizes:
189
211
  | `@color` | Define color tokens. Supports generative (`chroma hue`), explicit (`oklch(...)`, `#rrggbb`), light/dark pairs, and aliases. |
190
212
  | `@spacing` | Set the spacing base unit. |
191
213
  | `@text` | Define text size tokens (`size, line-height`). |
192
- | `@font`, `@font-face` | Register font families inside a single `@font { … }` block. One slot can own multiple `@face` faces (e.g. upright + italic) or use the `italic:` shorthand. |
214
+ | `@font`, `@font-face` | Register font families inside a single `@font { … }` block. Local files are repeatable `face:` entries (e.g. upright + italic). Known families get an automatic zero-CLS metrics fallback (`metrics: none` opts out). |
193
215
  | `@rounded` | Border-radius tokens; modifier sets corner shape (`round`, `squircle`, `superellipse(N)`, etc). |
194
216
  | `@fluid` | Configure fluid type/spacing range. |
195
217
  | `@animate` | Register named animations with inline `@keyframes`. |
@@ -220,10 +242,11 @@ Example:
220
242
  }
221
243
 
222
244
  @font {
223
- sans: "Inter" from google { weight: 400 700; }
224
- display: "Satoshi" from "/fonts/Satoshi.woff2" {
245
+ sans: "Inter", ui-sans-serif, sans-serif from google { weight: 400 700; }
246
+ display: "Satoshi" {
225
247
  weight: 300 900;
226
- italic: "/fonts/Satoshi-Italic.woff2"; /* second face, font-style: italic */
248
+ face: /fonts/Satoshi.woff2;
249
+ face: /fonts/Satoshi-Italic.woff2 { style: italic; }
227
250
  }
228
251
  }
229
252
 
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { C as ColorDefinition, a as CompilationContext, b as CompilationSnapshot, D as DEFAULT_TEXT_SIZES, F as FluidConfig, T as TextSize, c as Theme, d as createCompilationContext, e as createRi, f as defaultTheme, g as finalizeCompilationContext, r as registerColorNames, h as registerCustomFontFamilies, i as registerCustomTextSizes, j as registerCustomUtility, k as ri } from './index-CfDtWufj.js';
2
- export { s as safelist } from './safelist-CH3_PywB.js';
1
+ export { C as ColorDefinition, a as CompilationContext, b as CompilationSnapshot, F as FluidConfig, T as TextSize, c as Theme, d as createCompilationContext, e as defaultTheme, f as finalizeCompilationContext, r as registerColorNames, g as registerCustomFontFamilies, h as registerCustomTextSizes, i as registerCustomUtility } from './context-ruu2x_jR.js';
2
+ export { D as DEFAULT_TEXT_SIZES, c as createRi, r as ri, s as safelist } from './safelist-D9-Plqta.js';
3
3
 
4
4
  declare function browserEntryUnavailable(): never;
5
5
 
package/dist/browser.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  registerCustomTextSizes,
14
14
  registerCustomUtility,
15
15
  ri
16
- } from "./chunk-SOMDX7V6.mjs";
16
+ } from "./chunk-KRZL4IDK.mjs";
17
17
 
18
18
  // src/entries/browser.ts
19
19
  function browserEntryUnavailable() {
@@ -0,0 +1,19 @@
1
+ // src/scanner/source-files.ts
2
+ var SOURCE_FILE_EXTENSIONS = Object.freeze([
3
+ "html",
4
+ "js",
5
+ "jsx",
6
+ "ts",
7
+ "tsx",
8
+ "md",
9
+ "mdx",
10
+ "vue",
11
+ "svelte"
12
+ ]);
13
+ function isSourceFile(file) {
14
+ return SOURCE_FILE_EXTENSIONS.some((ext) => file.endsWith(`.${ext}`));
15
+ }
16
+
17
+ export {
18
+ isSourceFile
19
+ };