cssints 0.34.0 → 0.35.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/dist/vite.mjs CHANGED
@@ -672,6 +672,7 @@ function cssints({ inner = false, exclude, devtools = false, opencode = false, e
672
672
  const siteOf = /* @__PURE__ */ new Map();
673
673
  let buildMap = false;
674
674
  const callsOf = /* @__PURE__ */ new Map();
675
+ const sheetEdits = {};
675
676
  const sheet = Effect.fnUntraced(function* (ids = [...usedBy.keys()]) {
676
677
  return yield* renderSheet(ids.flatMap((id) => usedBy.get(id)?.split("\n") ?? []));
677
678
  });
@@ -912,6 +913,7 @@ function cssints({ inner = false, exclude, devtools = false, opencode = false, e
912
913
  yield* drop(file);
913
914
  }
914
915
  yield* Effect.sync(() => forgetWarnings([file]));
916
+ yield* Effect.sync(() => sheetEdits.last = file);
915
917
  const graph = env.moduleGraph;
916
918
  const affected = [.../* @__PURE__ */ new Set([...depsOf.get(file) ?? [], ...inflight])].flatMap((id) => graph.getModuleById(id) ?? []);
917
919
  yield* Effect.sync(() => affected.forEach((m) => {
@@ -959,6 +961,9 @@ function cssints({ inner = false, exclude, devtools = false, opencode = false, e
959
961
  server = s;
960
962
  if (inner) return;
961
963
  evaluator.server = evaluator.needed ? void 0 : s;
964
+ s.environments.client.hot.on("cssints:edits:get", (_, client) => {
965
+ if (sheetEdits.measured) client.send("cssints:edits", sheetEdits.measured);
966
+ });
962
967
  s.environments.client.hot.on("cssints:sheet", (v) => {
963
968
  if (!s.environments.client.moduleGraph.getModuleById(SHEET)?.transformResult?.code.includes(v)) Effect.runSync(reloadSheet());
964
969
  });
@@ -983,6 +988,15 @@ function cssints({ inner = false, exclude, devtools = false, opencode = false, e
983
988
  if (id === SHEET) {
984
989
  if (build) return LAYERS;
985
990
  const code = runSync(hook("vite.load", id, sheet()));
991
+ if (this.environment.name === "client" && server) {
992
+ const classes = new Set([...usedBy.values()].flatMap((v) => v.split("\n"))).size;
993
+ sheetEdits.measured = {
994
+ file: sheetEdits.last ? relative(root, sheetEdits.last) : "",
995
+ bytes: code.length,
996
+ classes
997
+ };
998
+ server.environments.client.hot.send("cssints:edits", sheetEdits.measured);
999
+ }
986
1000
  return {
987
1001
  code,
988
1002
  map: sheetMap(code, siteOf)
package/docs/API.md CHANGED
@@ -4397,6 +4397,74 @@ TOKENS
4397
4397
  - **Its own styles do not touch yours.** The overlay is a custom element with a shadow root and its own cssints sheet, built with the package: the page's rules do not reach it, and its classes are not in your sheet or in the table.
4398
4398
  - **Limits.** The proximity of `@scope` is not ranked (no two looks set one property on one element), and a logical and a physical property (`margin-inline-start`, `margin-left`) are two properties, as `cn()` treats them; an element inside another shadow root is picked as its host; a page that Vite does not serve as HTML (server rendering) gets no entry.
4399
4399
 
4400
+ #### Vitals in the console (`@cssints/devtools/vitals`, 2026-11-07, cssints-0p4d)
4401
+
4402
+ The panel inspects an element; this entry inspects the page, and prints to the console rather than drawing a DOM panel —
4403
+ the only surface a page that draws to a canvas has. No dev server, no overlay chunk and no cssints dependency of its own: a
4404
+ plain ES module (about 30 kB, 9.5 kB gzip) any page imports in dev.
4405
+
4406
+ ```ts
4407
+ // the app's entry, dev only
4408
+ import { observe, vitals } from "@cssints/devtools/vitals";
4409
+ observe(); // long tasks and long animation frames are not buffered: observe before the work
4410
+
4411
+ // later, from the console:
4412
+ vitals(); // or window.vitals(): prints the report and returns it
4413
+ ```
4414
+
4415
+ ```text skip
4416
+ cssints vitals
4417
+ 265 ms · 214 nodes · 1 sheet · 380 classes
4418
+ vitals FCP 412 ms · LCP 1.20 s (h1.hero) · CLS 0.002
4419
+ INP 96 ms (button.primary) · 8+40+48 · TTFB 38 ms
4420
+ frames p50 16.7 ms · p95 33.4 ms · max 51.2 ms · 3 dropped ▁▂▃▅▇▆▃▂▁▂▃▅
4421
+ main 7 long tasks · 412 ms · max 120 ms · style+layout 41 ms · 0 errors · heap 42.1 MB
4422
+ sheet 64.4 kB · +29 B on src/registry/button/button.tsx · 3 loads · 81 B a class ▁▁█
4423
+ load 2 sheets · 1 render-blocking · 66 ms to last · slow.css 65 ms (327 B)
4424
+ history 12 loads · LCP 1.20 s (−180 ms) (−312 B) ▁▂▃▅▆▇▆▅
4425
+ CSS 41.2 kB · 380 rules · 1 902 decls · 0 blocked · specificity 0,3,2 (._1t8gqy5:hover)
4426
+ layer _.k ████████████████████ 22.4 kB
4427
+ tokens 14 colours · 2 fonts · 1 !important · custom props 12 declared / 9 used / 3 orphaned · keyframes 2 / 1 unused
4428
+ coverage 41 / 380 classes unused (11%) cs-Landing-hero …
4429
+ ```
4430
+
4431
+ - **Web vitals** come from [web-vitals](https://github.com/GoogleChrome/web-vitals) (MIT), the reference implementation:
4432
+ CLS is the largest session window of shifts (not their sum), INP is the ~98th percentile of interactions (not the worst),
4433
+ and each metric names its culprit — the LCP element and render delay, the interaction's target with input / processing /
4434
+ presentation, the frames it covered. `!` is needs-improvement and `!!` poor, the library's thresholds. It reads the same
4435
+ APIs, so CLS is Chromium only and the other four come from Chromium, Firefox and Safari. Long animation frames are ours,
4436
+ and `render` sums the style and layout time — the part CSS pays for.
4437
+ - **The sheet's numbers** come from the CSSOM, not a parser: bytes, rules, declarations, layer bytes, the worst
4438
+ specificity, colours, fonts, `!important`, custom properties declared / read / orphaned / undeclared, keyframes declared
4439
+ / used / unused, and coverage — the classes of the sheet that no element carries now. On a cssints sheet this should be
4440
+ clean by construction, so a drift names what bypassed the engine; it is Project Wallace's x-ray pointed at a generated
4441
+ sheet.
4442
+ - **The sheet's growth** is the dev server's measurement, over the HMR channel (`cssints:edits`; a late subscriber asks
4443
+ with `cssints:edits:get`): each load reports its size, its class count and the file that changed, so an edit reads
4444
+ `+29 B on src/registry/button/button.tsx`, with a sparkline of the last thirty loads and the bytes a class costs. Off a
4445
+ dev server the row is absent. These bytes are `renderSheet`'s, not the CSSOM's, so they differ a little from the `CSS`
4446
+ row.
4447
+ - **Long animation frames** also name their scripts: the top sources by script time (a source is the last two path
4448
+ segments), the invokers (`timer`, `message handler`, an event listener), and the _forced_ style and layout time — the
4449
+ read-after-write that makes a script pay for layout.
4450
+ - **The history** of the last twenty loads of a path lives in `sessionStorage` (`cssints-vitals`): LCP and the sheet's
4451
+ bytes as deltas against the previous load of the same page; `reset()` clears it, and a store that throws (a private
4452
+ window) is simply absent.
4453
+ - **The cost by call** needs the devtools table (`cssints({ devtools: true })`): bytes of each class at the call that made
4454
+ it, the total and the number of calls, and how many of the call's classes the page carries; without the table the row is
4455
+ absent.
4456
+ - **The CSS files' arrival** is the resource timeline: each `.css`, its duration, Chrome's `renderBlockingStatus`, the
4457
+ slowest and the last.
4458
+ - **Frames** are sampled by a rAF loop: p50 / p95 / max, the dropped frames, a sparkline and a heatmap of the timeline
4459
+ (green under 16.7 ms, amber to 33.4, orange to 50, red above).
4460
+ - **Limits.** Long animation frames are not buffered entries, so a report from a cold console sees only the work after
4461
+ `observe()`; the five vitals are buffered but arrive a task after the module loads, so the first report can be early.
4462
+ A cross-origin sheet throws on `cssRules` and is counted as `blocked`. Coverage asks the DOM now: a class used only in a
4463
+ state is used if an element carries it.
4464
+
4465
+ `report()` returns the numbers, `print(report)` prints them, `reset()` forgets the history and the sheet's measurements,
4466
+ `stop()` ends the frame loop.
4467
+
4400
4468
  ### Debugging: traces of the engine (`CSSINTS_TRACE`, cssints-r2qq)
4401
4469
 
4402
4470
  Opt-in. Set `CSSINTS_TRACE=otlp` (or `otlp:http://host:port`; the default is `http://localhost:4318`) before `vite dev` or `vite build`, and the plugin sends one trace per Vite hook to an OpenTelemetry backend over OTLP/HTTP (JSON). Unset, nothing is loaded and nothing runs: the hooks use Effect's own `runSync` and `runPromiseExit`, no tracer, no exporter, no HTTP client. The variable is read once, when the plugin loads.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cssints",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "description": "CSS in TypeScript: typed css.* calls compiled to atomic CSS at build time (a Vite plugin)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -72,7 +72,7 @@
72
72
  }
73
73
  },
74
74
  "dependencies": {
75
- "@cssints/css-grammar": "^0.34.0",
75
+ "@cssints/css-grammar": "^0.35.0",
76
76
  "culori": "^4.0.2",
77
77
  "effect": "4.0.6",
78
78
  "happy-dom": "^20.14.6",
@@ -114,7 +114,7 @@ shade: fn({
114
114
  - `value` runs at build time, once per call; the call is replaced by its CSS text. It throws for values the grammar
115
115
  cannot reject (units it cannot compute, a failed accessibility rule): `fluid` rejects `em` and fails WCAG 1.4.4.
116
116
  - What `value` returns must be a string or a Typed OM value (`cssints: tint() value must return a string or a Typed OM
117
- value`). A result that does not fit the property is that property's error at the same site.
117
+ value`). A result that does not fit the property is that property's error at the same site.
118
118
 
119
119
  ## Identity and HMR
120
120