rainbowindex 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,169 @@ 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.5.0] - 2026-08-27
9
+
10
+ ### Added
11
+
12
+ - **`rainbowindex scan <file|glob…>`** — prints every class candidate the
13
+ scanner extracts from each file, with scanner warnings on stderr. Answers
14
+ "why doesn't my class generate?" directly: a class missing from the list
15
+ was never seen by the scanner (check the markup), while a class listed
16
+ there that still produces no rule fails later (check the build warnings).
17
+ - **RI-1411 — skipped over-long lines are now reported.** The scanner drops
18
+ lines above the minified-input guard, previously in silence. It now warns
19
+ once per file with the path and line count. Suppressed for `node_modules`
20
+ paths, where minified dists are the guard's intended target.
21
+ - **RI-1038 — uppercase `@utility` names warn.** The markup scanner only
22
+ matches lowercase tokens, so `@utility cardHeader` could never trigger
23
+ from `class="cardHeader"`. The utility still works through `@a`/`@apply`
24
+ and inline `@source`, so it is kept — but no longer silently unreachable.
25
+
26
+ ### Changed
27
+
28
+ - **Fallback stacks moved from preflight into `@font`.** The
29
+ `--sans-fallback` / `--serif-fallback` / `--mono-fallback` variables were
30
+ emitted into every project and referenced by nothing. A manual `@font`
31
+ slot that declares no fallbacks now gets the default system stack appended
32
+ to its `--font-<slot>` value instead (`sans: "Chartwell";` →
33
+ `"Chartwell", ui-sans-serif, system-ui, …`); a slot that declares its own
34
+ fallbacks is untouched. Font tokens no longer depend on a preflight
35
+ category that users can switch off.
36
+ - **Focus ring uses literal lengths.** `:focus-visible` drew its outline at
37
+ `var(--spacing)` (4px by default) and offset at half that, so changing the
38
+ spacing scale silently resized every focus ring. Now a flat 2px width and
39
+ 2px offset.
40
+ - **Placeholder color follows the text color** —
41
+ `color-mix(in oklab, currentColor 48%, transparent)` instead of a
42
+ hardcoded gray that ignored the theme in dark mode.
43
+ - **Scanner line-length guard raised from 2,000 to 10,000 characters.** Real
44
+ minified files run far above this, while hand-written long lines (inline
45
+ SVG path data, long attribute stacks) sit below it. `MAX_LINE_LENGTH` is
46
+ exported so tooling and tests derive from it instead of hardcoding.
47
+ - **Default source patterns scan every root HTML file** (`*.html`, not just
48
+ `index.html`), so Vite multi-page apps are covered without an explicit
49
+ `@source`. `dist`, `build`, and `public` remain excluded.
50
+
51
+ ### Removed
52
+
53
+ - **Preflight `select-reset`.** It styled rather than reset, and shipped
54
+ three defects: its chevron was a `currentColor` SVG in a `background-image`,
55
+ which resolves to black and disappears on dark backgrounds; the bare
56
+ `select` selector also hit `<select multiple>`, giving listboxes a floating
57
+ chevron and 2.5rem of padding; and `appearance: none` stripped the native
58
+ control on every platform. Style selects in your own CSS.
59
+ - **Preflight `:focus:not(:focus-visible) { outline: none }`** — no current
60
+ browser draws an outline for plain `:focus`, so the rule was dead.
61
+
62
+ ### Fixed
63
+
64
+ - **Quoted class attributes on over-long lines are no longer lost.** A
65
+ `className="…"` sharing a line with multi-KB inline SVG path data
66
+ generated nothing: the whole-file scan skipped the line for length, and
67
+ the attribute collector stripped the quotes and then searched only for
68
+ string literals *nested inside* the value, of which a plain quoted
69
+ attribute has none. Quoted attribute values are now tokenized directly, in
70
+ the one shared collector — so JSX, HTML, Vue, Svelte, and object-literal
71
+ (`{ className: "…" }`) syntax are all fixed together. Non-quoted
72
+ expression values keep their existing semantics.
73
+ - **List margins are reset.** `list-reset` removed bullets and padding while
74
+ `ol`, `ul`, and `menu` kept the browser's `margin-block: 1em`, leaving
75
+ unexplained gaps around navigation and menus.
76
+ - **`fieldset` and `legend` are reset** — their default margin and padding
77
+ survived while every other form control was flush.
78
+
79
+ ## [0.4.1] - 2026-08-25
80
+
81
+ ### Added
82
+
83
+ - **Automatic zero-CLS font fallbacks.** Any `@font` slot whose family is in
84
+ the built-in metrics table (~100 common Google + system families, generated
85
+ from `@capsizecss/metrics` at development time — no new runtime dependency)
86
+ now gets a metrics-adjusted local fallback `@font-face` automatically. The
87
+ fallback font is picked from the slot's stack, or by the web font's
88
+ category (Arial / Times New Roman / Courier New). Opt out with
89
+ `metrics: none;`, pick the matched local font with `metrics: "Segoe UI";`,
90
+ or keep full manual control with
91
+ `metrics: "Arial" <size-adjust> <ascent> <descent> <line-gap>;`.
92
+ - **`face:` entries** — local font files are now declared as repeatable
93
+ `face: <src> [{ overrides }]` entries, one grammar for every face:
94
+ `display: "Satoshi" { face: /Satoshi.woff2; face: /Satoshi-Italic.woff2 { style: italic; } }`.
95
+ - **Fallback stacks work with `from google`** —
96
+ `sans: "Inter", ui-sans-serif, sans-serif from google;` now parses as a
97
+ google slot with fallbacks (previously it silently became a manual stack).
98
+ - **@font now warns instead of ignoring silently**: unknown option keys
99
+ (RI-1217), `preload` on non-local slots where it can have no effect
100
+ (RI-1219), and partial or invalid `metrics` values (RI-1220).
101
+
102
+ ### Changed
103
+
104
+ - The `@font` parser was rebuilt on the shared entry scanner: one pass, no
105
+ regex preambles, no re-serialization. Public API (`parseFontBody`,
106
+ `parseNestedFontBlock`) is unchanged.
107
+ - **Incremental rebuilds only re-do changed work** — the scanner keeps a
108
+ per-file cache keyed on mtime + size, so a CLI watch or Vite HMR rebuild
109
+ re-reads and re-extracts only the files that actually changed instead of
110
+ the whole project. The CSS entry analysis is memoized on the entry text
111
+ (stable theme identity across rebuilds), Google-font resolution keeps its
112
+ identity when metadata is unchanged, and per-class compilation results
113
+ (rule, warnings, token usage) are replayed across rebuilds from a
114
+ theme-keyed cache. A one-file edit in a large project now costs
115
+ O(changed files) instead of O(project).
116
+ - **`ri()` conditional args hit the cache fast path** — falsy arguments
117
+ (`ri("flex", isActive && "bg-blue-500")`) no longer force re-tokenization
118
+ on every call: the raw-key cache skips falsy primitives, making cached
119
+ conditional-pattern calls ~4x faster.
120
+ - **Cheaper compile passes** — duplicate class names across `@apply` rules
121
+ resolve once per pass, the `@slot`/`@apply` walks no longer traverse the
122
+ generated utility CSS, custom `@utility` bodies parse once per body text
123
+ instead of once per variant form, and the editor candidate collector's
124
+ context assignment went from quadratic to a sorted sweep. As part of the
125
+ compile-result replay, engine-level compilation warnings are now
126
+ deduplicated (final project output already was).
127
+ - **Internal restructuring (behavior-preserving)** — the directive body
128
+ parsers were split by grammar family: `@color` and `@font` now own their
129
+ own modules, and the generic key-value/entry grammars sit together in the
130
+ directives foundation. The `parsers` import surface is unchanged.
131
+
132
+ ### Deprecated
133
+
134
+ - The older `@font` forms still parse and desugar into the new model, but
135
+ warn (RI-1218) and will be removed in a future release: `@face { src: …; }`
136
+ blocks and the `italic:` shorthand (use `face:`), `from "<path>"` (use
137
+ `face:`), `from system` (use bare `system`), the `fallback:` key (list
138
+ fallbacks in the slot preamble), the five-key metrics cluster
139
+ `metricsFallback`/`sizeAdjust`/`ascent`/`descent`/`lineGap` (use
140
+ `metrics:`), and the `unicodeRange` spelling (use `unicode-range`).
141
+
142
+ ### Removed
143
+
144
+ - **`@font` `subset:`** — the Google css2 API takes no subset hint, so the
145
+ key never affected the emitted URL. It now warns (RI-1218) and is ignored.
146
+ - **`FontSlot` / `FontFace` fields** — the five slot-level metrics fields
147
+ (`metricsFallback`, `sizeAdjust`, `ascent`, `descent`, `lineGap`) are
148
+ replaced by a single `metrics?: FontMetricsConfig | null`; slot-level
149
+ `preload` moved onto the faces; `FontFace.subset` is gone. Stylesheets are
150
+ unaffected — this only touches code importing those types directly.
151
+
152
+ ### Fixed
153
+
154
+ - **`ri()` stroke-width vs stroke-color conflict — for real this time.** The
155
+ v0.4.0 changelog described this fix, but the implementation did not ship in
156
+ that release: `ri("stroke-2 stroke-red-500")` still dropped `stroke-2`. The
157
+ `stroke` prefix is now actually width-vs-color dual-mode (mirroring
158
+ `border`/`outline`): decimal and non-color arbitrary values claim
159
+ `stroke-width`, color values claim `stroke`. Same-property conflicts
160
+ (`stroke-2` vs `stroke-4`, `stroke-red-500` vs `stroke-blue-500`) still
161
+ merge as before. The emission↔claim parity suite no longer carries a
162
+ stroke exception, and regression tests pin the merged output.
163
+ - **Directive rewriter no longer duplicates unclosed blocks** — an
164
+ unterminated `{` inside a `@color` body made the Vite-plugin pre-pass
165
+ re-emit the text before it (`@color { accent: 0.18 330 { inline` came out
166
+ with the entry doubled). The rewriter's two hand-rolled brace walks were
167
+ replaced by one shared walker built on the same brace matcher the directive
168
+ scan already uses, which also makes quotes and comments inside blocks
169
+ behave consistently across the rewrite passes.
170
+
8
171
  ## [0.4.0] - 2026-08-20
9
172
 
10
173
  ### Added
package/README.md CHANGED
@@ -211,7 +211,7 @@ Customization happens in your CSS input, not a JS config. The engine recognizes:
211
211
  | `@color` | Define color tokens. Supports generative (`chroma hue`), explicit (`oklch(...)`, `#rrggbb`), light/dark pairs, and aliases. |
212
212
  | `@spacing` | Set the spacing base unit. |
213
213
  | `@text` | Define text size tokens (`size, line-height`). |
214
- | `@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). |
215
215
  | `@rounded` | Border-radius tokens; modifier sets corner shape (`round`, `squircle`, `superellipse(N)`, etc). |
216
216
  | `@fluid` | Configure fluid type/spacing range. |
217
217
  | `@animate` | Register named animations with inline `@keyframes`. |
@@ -242,10 +242,11 @@ Example:
242
242
  }
243
243
 
244
244
  @font {
245
- sans: "Inter" from google { weight: 400 700; }
246
- display: "Satoshi" from "/fonts/Satoshi.woff2" {
245
+ sans: "Inter", ui-sans-serif, sans-serif from google { weight: 400 700; }
246
+ display: "Satoshi" {
247
247
  weight: 300 900;
248
- italic: "/fonts/Satoshi-Italic.woff2"; /* second face, font-style: italic */
248
+ face: /fonts/Satoshi.woff2;
249
+ face: /fonts/Satoshi-Italic.woff2 { style: italic; }
249
250
  }
250
251
  }
251
252
 
package/dist/browser.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  registerCustomTextSizes,
14
14
  registerCustomUtility,
15
15
  ri
16
- } from "./chunk-5Y7EXXLS.mjs";
16
+ } from "./chunk-KRZL4IDK.mjs";
17
17
 
18
18
  // src/entries/browser.ts
19
19
  function browserEntryUnavailable() {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  compileScannedProject
3
- } from "./chunk-4SVFFDS2.mjs";
3
+ } from "./chunk-DT5HYIM3.mjs";
4
4
  import {
5
5
  APPLY_ALIASES,
6
6
  DIRECTIVE_NAMES_SET,
@@ -20,7 +20,7 @@ import {
20
20
  resolveUtilityDeclarations,
21
21
  resolveVariant,
22
22
  splitSelectorList
23
- } from "./chunk-6DAHUFNU.mjs";
23
+ } from "./chunk-ZI5ZYNSU.mjs";
24
24
 
25
25
  // src/integrations/postcss/index.ts
26
26
  import postcss2 from "postcss";
@@ -102,6 +102,7 @@ function processApply(root, theme, warnings) {
102
102
  }
103
103
  const customVariantMap = new Map(theme.customVariants.map((cv) => [cv.name, cv]));
104
104
  const checkedApplyRoots = /* @__PURE__ */ new Set();
105
+ const resolveCache = /* @__PURE__ */ new Map();
105
106
  for (let depth = 0; depth < MAX_APPLY_DEPTH; depth++) {
106
107
  const applyNodes = [];
107
108
  const classListByNode = /* @__PURE__ */ new Map();
@@ -127,7 +128,8 @@ function processApply(root, theme, warnings) {
127
128
  warnings,
128
129
  customVariantMap,
129
130
  groupRoots,
130
- checkedApplyRoots
131
+ checkedApplyRoots,
132
+ resolveCache
131
133
  );
132
134
  }
133
135
  }
@@ -144,7 +146,7 @@ function processApply(root, theme, warnings) {
144
146
  }
145
147
  }
146
148
  }
147
- function expandApply(atRule, classNames, theme, warnings, customVariantMap, groupRoots, checkedApplyRoots) {
149
+ function expandApply(atRule, classNames, theme, warnings, customVariantMap, groupRoots, checkedApplyRoots, resolveCache) {
148
150
  const parentRule = atRule.parent;
149
151
  if (parentRule?.type !== "rule") {
150
152
  warnings.push("[RI-1006] @apply must be used inside a CSS rule, not at the top level.");
@@ -166,8 +168,23 @@ function expandApply(atRule, classNames, theme, warnings, customVariantMap, grou
166
168
  }
167
169
  const resolved = [];
168
170
  for (const className of classNames) {
169
- const r = resolveClassName(className, theme, warnings, customVariantMap, checkedApplyRoots);
170
- if (r) resolved.push(r);
171
+ let entry = resolveCache.get(className);
172
+ if (!entry) {
173
+ const entryWarnings = [];
174
+ entry = {
175
+ result: resolveClassName(
176
+ className,
177
+ theme,
178
+ entryWarnings,
179
+ customVariantMap,
180
+ checkedApplyRoots
181
+ ),
182
+ warnings: entryWarnings
183
+ };
184
+ resolveCache.set(className, entry);
185
+ }
186
+ warnings.push(...entry.warnings);
187
+ if (entry.result) resolved.push(entry.result);
171
188
  }
172
189
  const baseGroups = [];
173
190
  const baseNestedBlocks = [];
@@ -556,16 +573,16 @@ var rainbowindex = (options = {}) => {
556
573
  const compilationWarnings = compiled.warnings;
557
574
  const from = root.source?.input?.file ?? root.source?.input?.id ?? root.source?.input?.from ?? "<rainbowindex>";
558
575
  stripRIDirectiveNodes(root);
559
- if (compiled.sections.length > 0) {
560
- const generatedRoot = postcss2.parse(compiled.sections.join("\n\n"), { from });
561
- root.prepend(generatedRoot.nodes);
562
- }
563
576
  const slotWarnings = [];
564
577
  warnStandaloneSlots(root, slotWarnings);
565
578
  pushWarningsDeduped(compilationWarnings, slotWarnings, warningSeen);
566
579
  const applyWarnings = [];
567
580
  processApply(root, compiled.theme, applyWarnings);
568
581
  pushWarningsDeduped(compilationWarnings, applyWarnings, warningSeen);
582
+ if (compiled.sections.length > 0) {
583
+ const generatedRoot = postcss2.parse(compiled.sections.join("\n\n"), { from });
584
+ root.prepend(generatedRoot.nodes);
585
+ }
569
586
  const cssFnWarnings = [];
570
587
  processCSSFunctions(root, compiled.theme, cssFnWarnings);
571
588
  pushWarningsDeduped(compilationWarnings, cssFnWarnings, warningSeen);