jig-ui 0.8.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,144 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.2
4
+
5
+ Every fix here was found by a consumer using Jig rather than by Jig checking
6
+ itself: the documentation site was upgraded to 0.8.1 and then styled in
7
+ Tailwind, which is the first time Jig's Tailwind guidance had been followed
8
+ end to end by anything other than its own tests.
9
+
10
+ Three of the four are the same shape — two places answering one question, and
11
+ disagreeing without either knowing the other existed.
12
+
13
+ ### Fixed
14
+
15
+ - **`H-47` read a value differently depending on how it was spelled.**
16
+ `00-anti-patterns.md:11` sets the scope of the whole rule file: "**Framework:**
17
+ agnostic. […] Where a utility-class framework is in use, translate — the rule
18
+ is about the resulting style, not the syntax." The detector was the exact
19
+ inverse, in both directions at once. Its CSS branch matched `px` alone and
20
+ excluded `0/1px/2px`; its Tailwind branch matched ten units and excluded
21
+ nothing. So `font-size: 0.9em` was silent while `text-[0.9em]` was an error,
22
+ and `p-[1px]` was an error while `padding: 1px` was not.
23
+
24
+ A project on plain CSS got a clean `check` for code a Tailwind project got
25
+ eight errors for. Found by converting a real stylesheet to utilities without
26
+ changing one computed value and watching the report go from `No findings` to
27
+ eight; the two declarations responsible had been in that file since it was
28
+ written and had never been flagged. Both branches now read one definition.
29
+
30
+ **This widens what `check` reports.** A stylesheet carrying `1rem` or `0.9em`
31
+ past the token layer was always an H-47 violation and is now reported as one,
32
+ so a repo that was clean under 0.8.1 can have findings here without its CSS
33
+ having moved.
34
+
35
+ - **The naming contract named namespaces Tailwind does not have.**
36
+ `02-tokens.md` opened with "an alias block can expose **any of them** as
37
+ Tailwind utilities" over a table of thirteen. Three generate nothing in
38
+ Tailwind v4 — `--duration-*`, `--measure-*` and `--focus-ring-*` — and
39
+ `--duration-*` shared a row with `--ease-*`, so only half of that row worked.
40
+ Aliasing one is accepted, emits the custom property, and produces no rule, so
41
+ the class lands on the element and does nothing: the same silent failure the
42
+ file warns about 190 lines later, reached from the opposite direction. The
43
+ table now carries a **Utility** column, and the three say how to be read from
44
+ a class instead — `max-w-(--measure-prose)`, which keeps the semantic token
45
+ and satisfies `H-47` without going through `@theme`.
46
+
47
+ `init`'s own generator was wrong in both directions. It correctly filtered
48
+ `--measure-*` and `--focus-ring-*`, and it also filtered `--size-*` and
49
+ `--border-width-*`, which both generate working utilities — `size-control`
50
+ sets width and height, `border-hairline` sets a border width. Jig was
51
+ withholding correct aliases for its own tokens, and a test asserted that as
52
+ correct, which is how it survived.
53
+
54
+ - **`check` did not say which files it had looked at.** It defaults to the
55
+ files changed since HEAD and falls back to the whole repo when that diff is
56
+ empty. Nothing in the output said so, so the same repo reported `files=31` on
57
+ a clean tree and `files=9` with nine files touched, minutes apart, with
58
+ nothing committed — and a consumer bisected it by reverting files one at a
59
+ time to find out why.
60
+
61
+ Worse, `mechanical=pass:0` on a dirty tree means "nothing in your diff
62
+ fired", not "the project is clean", and the skill tells an agent to run
63
+ `check` before finishing — exactly when the tree is dirty and the scope is
64
+ narrowest. The exempt line compounded it: `src/content/rules.ts (matches
65
+ nothing — check the path)` was printed for a path that exists, is tracked,
66
+ and had matched a file on the previous run. The glob was fine; it matched
67
+ nothing *within the narrowed set*, and "check the path" sends you to debug a
68
+ correct config.
69
+
70
+ The summary now names the scope, a narrowed run says a clean result is not a
71
+ clean project, the exempt note distinguishes "no such path" from "not in this
72
+ scan", and `--all`'s help says it widens the files rather than the rules —
73
+ which is how it had been read.
74
+
75
+ - **A flag written across two lines vanished from the metadata guard.**
76
+ `registeredFlags` read `src/index.ts` line by line and required the flag
77
+ string to sit on the same line as `.option(`, so reformatting one option to
78
+ fit a longer description dropped `--all` from the parsed set and from every
79
+ guard built on it. Caught by its own canary the moment an option wrapped.
80
+
81
+
82
+ ## 0.8.1
83
+
84
+ Both fixes here are the same shape: 0.8.0 corrected the instance it was looking
85
+ at and left the class alone, and in each case the commit message claimed a
86
+ verification that had only checked the file it had just edited. Both were found
87
+ by inspecting the published tarball rather than the working tree.
88
+
89
+ ### Fixed
90
+
91
+ - **Four rule files still shipped author notes.** 0.7.x removed the section
92
+ headed "Notes for the author (not for the agent)" from `00-anti-patterns.md`
93
+ after an agent read it as "license to go tighter than the shared default" and
94
+ halved the radius scale on that authority. `01-modes.md`, `03-patterns.md`,
95
+ `04-principles.md` and `05-copy.md` carried a section under the same heading
96
+ and kept shipping it to every agent that installed Jig.
97
+
98
+ `01-modes.md` was the worst of them: it told the reader to **"Change them in
99
+ `tokens/mode.*.css`"** and closed with **"it is your call"** — an invitation
100
+ to edit the token layer, which is the one thing Jig's architecture reserves
101
+ for a human. `03-patterns.md` described navigation and cards as "deliberately
102
+ absent… add them once you have built enough". `05-copy.md` pointed at
103
+ `03-brand.md`, which does not exist. The content moves to
104
+ `docs/house-positions.md` unchanged; only its audience changes.
105
+
106
+ - **`02-tokens.md` contradicted its own Tailwind fix 352 lines earlier.** The
107
+ compatibility table near the top still read *"Tailwind v4 | Wrap in
108
+ `@theme { }`"* — the exact instruction the section below it retracts with
109
+ *"Earlier versions of this file told you to, and Tailwind rejects it
110
+ outright."* The table is the part an agent reads first, so the correction
111
+ shipped underneath the error it corrected. A second, softer restatement
112
+ ("the same file can be wrapped in `@theme`") is gone too. `@theme` now first
113
+ appears in the section that explains it correctly.
114
+
115
+ - **`jig explain` rendered a dangling `---` in 14 of the 15 pattern and mode
116
+ specs.** `parse.ts` has always dropped bare separator lines; `specs.ts` is a
117
+ separate code path and never did, so every `P-` and `M-` spec that is
118
+ followed by a separator in the source carried it into the rendered body,
119
+ between the last paragraph and the footer. Spotted on `P-12`, but it was
120
+ never about `P-12`. Table separators (`| --- |`) are untouched.
121
+
122
+ - **`jig init`'s refusal without a terminal offered a way out that does not
123
+ work.** The message ended *"(To choose the mode without a terminal, write
124
+ jig.config.json first — init honours it.)"* The guard runs before any config
125
+ is read, so a config alone still exits 1. The sentence was true about mode
126
+ selection and false in a paragraph about not having a terminal, so it read as
127
+ a third alternative when it is a modifier on the first — a cold agent
128
+ followed it literally, hit the identical error, and allocated a
129
+ pseudo-terminal with Python's `pty` to get past it. It now says a config does
130
+ not replace `--yes`, and what the two do together. Nothing covered this path;
131
+ three tests now do.
132
+
133
+ ### Added
134
+
135
+ - **`check-tokens` rule 13 — no shipped rule file addresses the author.** The
136
+ guard that should have existed for the 0.7.x fix. It reads `rules/` from disk
137
+ rather than a hardcoded list, so a rule file added later cannot escape it the
138
+ way those four did, and it checks the second-person tells ("your taste", "it
139
+ is your call", "my inclination is") as well as the heading, because a rename
140
+ would otherwise defeat it. Each tell is verified to fire on reintroduction.
141
+
3
142
  ## 0.8.0
4
143
 
5
144
  Everything here was found by handing Jig to agents that had never seen it and
package/dist/index.js CHANGED
@@ -1125,6 +1125,7 @@ function parseSpecs(markdown, sourceFile) {
1125
1125
  current = null;
1126
1126
  continue;
1127
1127
  }
1128
+ if (current && /^-{3,}$/.test(line.trim())) continue;
1128
1129
  if (current) body.push(line);
1129
1130
  }
1130
1131
  push();
@@ -1447,7 +1448,7 @@ function formatReport(findings, meta) {
1447
1448
  if (notes > 0) summaryParts.push(plural(notes, "note"));
1448
1449
  const rulesFired = new Set(findings.map((f) => f.ruleId)).size;
1449
1450
  const scope = meta.totalSpecs ? `${meta.totalRules} rules (+ ${meta.totalSpecs} pattern and mode specs)` : `${meta.totalRules} rules`;
1450
- const examined = meta.scanned === void 0 ? "" : ` \xB7 ${plural(meta.scanned, "file")}, ${meta.withStyles ?? 0} with styles`;
1451
+ const examined = meta.scanned === void 0 ? "" : ` \xB7 ${plural(meta.scanned, "file")}${meta.scope === "changed" ? " changed since HEAD" : ""}, ${meta.withStyles ?? 0} with styles`;
1451
1452
  lines.push(` ${summaryParts.join(", ")} \xB7 ${scope}${examined}, ${rulesFired} fired`);
1452
1453
  if (meta.scanned !== void 0 && meta.withStyles === 0) {
1453
1454
  lines.push("");
@@ -1455,11 +1456,17 @@ function formatReport(findings, meta) {
1455
1456
  ` No file carried a style region, so the detectors examined nothing. This is not a pass \u2014 a project with no styling and a project with clean styling report the same findings, and only one of them has been checked.`
1456
1457
  );
1457
1458
  }
1459
+ if (meta.scope === "changed") {
1460
+ lines.push("");
1461
+ lines.push(
1462
+ ` Scope: files changed since HEAD, not the whole project \u2014 this is the inner-loop default. A clean result here means nothing in your diff fired, not that the project is clean. Run 'jig check --all' for that.`
1463
+ );
1464
+ }
1458
1465
  if (meta.exemptPatterns && meta.exemptPatterns.length > 0) {
1459
1466
  const n = meta.exempt?.length ?? 0;
1460
1467
  lines.push(` ${n} file(s) exempt via jig.config.json and not scanned:`);
1461
1468
  for (const { pattern, count, tooBroad } of meta.exemptPatterns) {
1462
- const note = count === 0 ? "matches nothing \u2014 check the path" : tooBroad ? `${count} files \u2014 likely too broad, review it` : `${count} file${count > 1 ? "s" : ""}`;
1469
+ const note = count === 0 ? meta.scope === "changed" ? "matches nothing among the changed files \u2014 not necessarily wrong" : "matches nothing \u2014 check the path" : tooBroad ? `${count} files \u2014 likely too broad, review it` : `${count} file${count > 1 ? "s" : ""}`;
1463
1470
  lines.push(` ${pattern} (${note})`);
1464
1471
  }
1465
1472
  if (n > 0) {
@@ -2360,6 +2367,35 @@ var pureBlackWhite = {
2360
2367
  }
2361
2368
  };
2362
2369
 
2370
+ // src/check/length.ts
2371
+ var LENGTH_UNITS = [
2372
+ "px",
2373
+ "rem",
2374
+ "em",
2375
+ "pt",
2376
+ "vh",
2377
+ "vw",
2378
+ "vmin",
2379
+ "vmax",
2380
+ "ch",
2381
+ "ex"
2382
+ ];
2383
+ var UNITS = LENGTH_UNITS.join("|");
2384
+ var LENGTH_RE = new RegExp(`^-?\\d*\\.?\\d+(?:${UNITS})$`, "i");
2385
+ var LENGTH_SCAN_RE = new RegExp(`(-?\\d*\\.?\\d+)(${UNITS})`, "gi");
2386
+ function isExcludedLength(value, unit) {
2387
+ if (value === 0) return true;
2388
+ return unit.toLowerCase() === "px" && (Math.abs(value) === 1 || Math.abs(value) === 2);
2389
+ }
2390
+ function hasHardCodedLength(value) {
2391
+ LENGTH_SCAN_RE.lastIndex = 0;
2392
+ let m;
2393
+ while (m = LENGTH_SCAN_RE.exec(value)) {
2394
+ if (!isExcludedLength(parseFloat(m[1]), m[2])) return true;
2395
+ }
2396
+ return false;
2397
+ }
2398
+
2363
2399
  // src/check/tailwind.ts
2364
2400
  var CLASS_ATTR = /\b(?:class|className)\s*=\s*/g;
2365
2401
  function readBraced(source, open) {
@@ -2403,15 +2439,21 @@ function classAttributeValues(source) {
2403
2439
  }
2404
2440
  var ARBITRARY = /(?:^|\s)(?:[\w-]+:)*([a-z][\w-]*)-\[([^\]\s]+)\]/gi;
2405
2441
  var COLOUR = /^(?:#[0-9a-f]{3,8}|(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\()/i;
2406
- var LENGTH = /^-?\d*\.?\d+(?:px|rem|em|pt|vh|vw|vmin|vmax|ch|ex)$/i;
2442
+ var LENGTH_PARTS = /^(-?\d*\.?\d+)([a-z]+)$/i;
2407
2443
  function arbitraryValues(classes) {
2408
2444
  const out = [];
2409
2445
  for (const m of classes.matchAll(ARBITRARY)) {
2410
2446
  const [, utility, rawValue] = m;
2411
2447
  const value = rawValue.replace(/_/g, " ");
2412
2448
  if (/var\(|--/.test(value)) continue;
2413
- if (COLOUR.test(value)) out.push({ utility, value, kind: "colour" });
2414
- else if (LENGTH.test(value)) out.push({ utility, value, kind: "length" });
2449
+ if (COLOUR.test(value)) {
2450
+ out.push({ utility, value, kind: "colour" });
2451
+ continue;
2452
+ }
2453
+ if (!LENGTH_RE.test(value)) continue;
2454
+ const parts = LENGTH_PARTS.exec(value);
2455
+ if (isExcludedLength(parseFloat(parts[1]), parts[2])) continue;
2456
+ out.push({ utility, value, kind: "length" });
2415
2457
  }
2416
2458
  return out;
2417
2459
  }
@@ -2603,8 +2645,6 @@ var SPACING_PROPS = /* @__PURE__ */ new Set([
2603
2645
  ]);
2604
2646
  var DECL_RE2 = /(?<![-\w])([a-zA-Z-]+)\s*:\s*([^;]+)(?:;|$)/g;
2605
2647
  var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgb|hsl)a?\([^)]*\)/;
2606
- var PX_RE = /(-?\d*\.?\d+)px/g;
2607
- var EXCLUDED_PX = /* @__PURE__ */ new Set([0, 1, 2]);
2608
2648
  var KEYFRAME_STEP_RE = /^(from|to|\d+(\.\d+)?%)$/i;
2609
2649
  var PRIMITIVE_RE = /var\(\s*(--(?:brand|error|warning|success|info)-(?:h|s|l|fill-a))\s*[,)]/g;
2610
2650
  var TOKEN_LAYER_RE = /(^|\/)(\.jig\/tokens|jig)\/[^/]+\.css$/;
@@ -2661,16 +2701,7 @@ var hardcodedValue = {
2661
2701
  continue;
2662
2702
  }
2663
2703
  if (SPACING_PROPS.has(prop)) {
2664
- PX_RE.lastIndex = 0;
2665
- let px;
2666
- let flagged = false;
2667
- while (px = PX_RE.exec(value)) {
2668
- if (!EXCLUDED_PX.has(Math.abs(parseFloat(px[1])))) {
2669
- flagged = true;
2670
- break;
2671
- }
2672
- }
2673
- if (flagged) {
2704
+ if (hasHardCodedLength(value)) {
2674
2705
  const line = lineOfOffset(block, m.index);
2675
2706
  findings.push(
2676
2707
  mkFinding(
@@ -3131,7 +3162,8 @@ function check(opts) {
3131
3162
  scanned: files.length,
3132
3163
  withStyles,
3133
3164
  exempt,
3134
- exemptPatterns: byPattern
3165
+ exemptPatterns: byPattern,
3166
+ scope: selection.mode
3135
3167
  });
3136
3168
  return { findings, report, hasError };
3137
3169
  }
@@ -3546,7 +3578,9 @@ var TAILWIND_NAMESPACES = [
3546
3578
  "--perspective-",
3547
3579
  "--aspect-",
3548
3580
  "--ease-",
3549
- "--animate-"
3581
+ "--animate-",
3582
+ "--size-",
3583
+ "--border-width-"
3550
3584
  ];
3551
3585
  function tailwindNamespaced(names) {
3552
3586
  const kept = names.filter((n) => TAILWIND_NAMESPACES.some((ns) => n.startsWith(ns)));
@@ -3586,8 +3620,10 @@ function utilitiesBody(names, version2) {
3586
3620
  order, so Jig's value always wins. Do not "fix" it \u2014 deleting the alias
3587
3621
  removes the utility, deleting Jig's removes the value.
3588
3622
 
3589
- Only tokens in a Tailwind namespace are listed. \`--size-*\`, \`--measure-*\`,
3590
- \`--focus-ring-*\` and \`--border-width-*\` have none, so they stay \`var()\`-only.
3623
+ Only tokens in a Tailwind namespace are listed. \`--measure-*\`, \`--focus-ring-*\`
3624
+ and \`--duration-*\` have none, so they stay \`var()\`-only \u2014 read them from a class
3625
+ with Tailwind's custom-property form instead: \`max-w-(--measure-prose)\`,
3626
+ \`duration-(--duration-fast)\`.
3591
3627
 
3592
3628
  Regenerate with \`jig init\` after the token layer changes; a token missing here
3593
3629
  is a class that renders and matches nothing. */
@@ -3782,7 +3818,7 @@ async function init(opts) {
3782
3818
  const prompt = opts.prompt ?? defaultPrompt;
3783
3819
  if (!opts.yes && !opts.prompt && !process.stdin.isTTY) {
3784
3820
  throw new Error(
3785
- "'jig init' asks questions and stdin is not a terminal, so it cannot. Re-run with --yes to accept the derived defaults, or run it in a terminal. (To choose the mode without a terminal, write jig.config.json first \u2014 init honours it.)"
3821
+ "'jig init' asks questions and stdin is not a terminal, so it cannot. Re-run with --yes to accept the derived defaults, or run it in a terminal. A jig.config.json does not replace --yes, because this check runs before it is read. With both, init takes the mode from the config instead of deriving it."
3786
3822
  );
3787
3823
  }
3788
3824
  const migrateLegacy = async (report, describe) => {
@@ -4239,7 +4275,13 @@ program.command("update").description("Update vendored Jig rules, skipping files
4239
4275
  process.exit(1);
4240
4276
  }
4241
4277
  });
4242
- program.command("check").description("Check the repo against Jig's mechanical + hybrid rules.").option("--all", "check the whole repo instead of just changed files", false).option("--ci", "mechanical bucket only; exits non-zero on any error, deterministic", false).option("--json", "emit findings as JSON", false).action((opts) => {
4278
+ program.command("check").description("Check the repo against Jig's mechanical + hybrid rules.").option(
4279
+ "--all",
4280
+ // Names the files, because the default scope surprised a user who read
4281
+ // this as widening the RULES and attested a clean diff as a clean project.
4282
+ "scan every file in the repo, not just those changed since HEAD (same rules either way)",
4283
+ false
4284
+ ).option("--ci", "mechanical bucket only; exits non-zero on any error, deterministic", false).option("--json", "emit findings as JSON", false).action((opts) => {
4243
4285
  const projectRoot = findProjectRoot(process.cwd());
4244
4286
  try {
4245
4287
  const result = check({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "A design system for coding agents. 104 numbered UI rules, brand x mode design tokens, and an installer for Claude Code, Codex, Cursor and opencode.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/rules/01-modes.md CHANGED
@@ -205,17 +205,3 @@ Per project, one file supplying:
205
205
  - **Voice** — sentence case or title case, contraction policy, error-message tone.
206
206
 
207
207
  Default when no brand is supplied: warm neutral ramp anchored on `--color-bg-base` (`oklch(0.980 0.004 95)`, a warm off-white), no accent, 8px base radius (`--radius-sm`), border-led elevation. Greyscale output plus a stated question beats an invented purple (`A-01`).
208
-
209
- ---
210
-
211
- ## Notes for the author (not for the agent)
212
-
213
- **Decided, not derived.** These numbers are internally consistent and defensible, but several are judgement calls that should be tuned once you have run real work through them: the operator row height, the three section-rhythm values, and the motion durations. Change them in `tokens/mode.*.css`, never at the call site — this file describes them, `02-tokens.md` resolves them, and neither is where they live.
214
-
215
- **Where your taste is recorded here:**
216
- - The zero-JS default in `editorial` — a stronger position than most systems take, and consistent with your writing on JS-dependent forms.
217
- - Absolute-first timestamps in `operator` — that is the procurement instinct: the record is evidence before it is a convenience.
218
- - Typed confirmation for destructive operator actions, and no hover-hidden information in all-day tools.
219
- - Border-led elevation as the unbranded default.
220
-
221
- **Open question worth resolving before tokens.** `product` is currently defined as the midpoint of the other two, which is how it earns its place, but it is also the mode that most often needs to lean. A customer dashboard leans editorial; a billing admin screen leans operator. Consider whether `product` needs a documented `dense` variant, or whether such surfaces should simply be declared `operator`. My inclination is the latter — three modes you apply confidently beat five you deliberate over — but it is your call, and it affects how many token sets `02` has to emit.
@@ -48,7 +48,7 @@ They are the only token format every web framework consumes natively with no bui
48
48
  | Consumer | Usage |
49
49
  | --- | --- |
50
50
  | Plain CSS / any framework | `color: var(--color-text-strong)` |
51
- | Tailwind v4 | Wrap in `@theme { }` — generates utilities automatically |
51
+ | Tailwind v4 | `@import` the barrel flat, alongside `@import "tailwindcss"` — see [Colour architecture](#colour-architecture). Utility classes are opt-in and need an alias block |
52
52
  | CSS-in-JS (styled-components, emotion) | `color: var(--color-text-strong)` inside template literals |
53
53
  | Vue / Svelte / Angular | Identical to plain CSS, scoped or global |
54
54
  | React inline styles | `style={{ color: 'var(--color-text-strong)' }}` |
@@ -281,23 +281,32 @@ Resist per-component tokens (`--button-bg`). They multiply fast and rarely earn
281
281
 
282
282
  ## Naming contract
283
283
 
284
- Names align to Tailwind v4's theme namespaces. This is free for other frameworks — they are ordinary custom properties — and means the same file can be wrapped in `@theme` to generate utilities without any framework taking a dependency on Tailwind.
284
+ Names align to Tailwind v4's theme namespaces. This is free for other frameworks — they are ordinary custom properties — and means most of them can be exposed as Tailwind utilities through an alias block, without any framework taking a dependency on Tailwind.
285
285
 
286
- | Namespace | Holds | Layer |
287
- | --- | --- | --- |
288
- | `--color-*` | All colour | brand |
289
- | `--font-*` | Font families | brand |
290
- | `--text-*` | Font sizes | mode |
291
- | `--leading-*` | Line heights | mode |
292
- | `--tracking-*` | Letter spacing | mode |
293
- | `--spacing-*` | Spacing values | mode |
294
- | `--radius-*` | Corner radii | brand scale, mode selection |
295
- | `--border-width-*` | Stroke widths | brand options, mode selection |
296
- | `--focus-ring-*` | Focus indicator geometry | brand — an accessibility floor, so not mode-negotiable |
297
- | `--shadow-*` | Elevation | brand |
298
- | `--duration-*`, `--ease-*` | Motion | mode |
299
- | `--size-*` | Control and row heights | mode |
300
- | `--measure-*` | Line length caps | mode |
286
+ **Most, not all.** Three of the namespaces below are Jig's own and have no utility behind them in Tailwind v4 — the **Utility** column says which. Aliasing one is accepted silently, emits the custom property, and generates no rule, so the class lands on the element and does nothing. That is the same silent failure this file warns about under [Optional: Tailwind utility classes](#optional-tailwind-utility-classes), reached from the other direction: there the alias is missing, here the alias is present and the utility does not exist.
287
+
288
+ | Namespace | Holds | Layer | Utility |
289
+ | --- | --- | --- | --- |
290
+ | `--color-*` | All colour | brand | yes |
291
+ | `--font-*` | Font families | brand | yes |
292
+ | `--text-*` | Font sizes | mode | yes |
293
+ | `--leading-*` | Line heights | mode | yes |
294
+ | `--tracking-*` | Letter spacing | mode | yes |
295
+ | `--spacing-*` | Spacing values | mode | yes |
296
+ | `--radius-*` | Corner radii | brand scale, mode selection | yes |
297
+ | `--border-width-*` | Stroke widths | brand options, mode selection | yes |
298
+ | `--focus-ring-*` | Focus indicator geometry | brand — an accessibility floor, so not mode-negotiable | **no** |
299
+ | `--shadow-*` | Elevation | brand | yes |
300
+ | `--ease-*` | Motion easing | mode | yes |
301
+ | `--duration-*` | Motion duration | mode | **no** |
302
+ | `--size-*` | Control and row heights | mode | yes |
303
+ | `--measure-*` | Line length caps | mode | **no** |
304
+
305
+ `--duration-*` and `--ease-*` had one row between them and only one half of it works, which is why they are separated here.
306
+
307
+ **Reading a `no` token from a class.** Use Tailwind's custom-property value form, which needs no alias and keeps the semantic name: `max-w-(--measure-prose)`, `duration-(--duration-fast)`, `outline-(--focus-ring-width)`. That satisfies `H-47` — the value is still the token — it simply does not route through `@theme`. The same form covers any token a mode file adds that has no namespace at all, such as the `--grid-*` values.
308
+
309
+ Verified by compiling one alias per namespace against `tailwindcss@4.3.3` and reading the output. Probe each namespace with a *distinct* token name if you re-check this: `text-*`, `border-*`, `outline-*` and `max-w-*` each read more than one namespace, so a shared suffix makes a colour alias look like proof that four other namespaces work.
301
310
 
302
311
  **Rules**
303
312
  1. Semantic names only at the point of use. `--color-text-strong`, not `--color-neutral-900`, in component code. Primitives exist to build semantics, not to be consumed directly.
@@ -510,17 +510,3 @@ Before writing a new component, check whether it is a composite of things that a
510
510
  A new pattern earns a place here when it has been built three times. Before then it is a component, not a pattern.
511
511
 
512
512
  Each entry states: anatomy in order, complete state list, rules that are decidable, and mode variance. If a rule cannot be checked by looking at the output, it belongs in `04-principles.md`.
513
-
514
- ---
515
-
516
- ## Notes for the author (not for the agent)
517
-
518
- **Where your taste is recorded here:**
519
- - `P-01` — the whole feedback table is a position. Toasts are over-used because they are easy to build and require no layout decisions; treating them as the narrowest case rather than the default is deliberate.
520
- - `P-03` help-text-before-control. Contested — many systems put it after. Placing it before means it is read before the user commits to typing, which matters more in forms people fill once.
521
- - `P-04` one-column forms, and the no-JS baseline for the primary action.
522
- - `P-06` stable row identity, absolute timestamps, no hover-only truncation. The procurement instinct again: the record is evidence before it is a convenience.
523
-
524
- **Deliberately absent.** Navigation, cards, tabs, and toasts-as-a-component. Navigation and cards vary too much by project to have decidable rules yet — they would produce prose, not constraints. Add them once you have built enough to see the invariant.
525
-
526
- **Worth testing before extending.** These 12 cover most of what generated UI gets wrong. Point an agent at a form and a table with `00`, `01`, `02` and `03` loaded, and compare against the same task with nothing loaded. If `P-03` and `P-05` do not visibly change the output, the rules are not decidable enough and the fix is more specificity, not more patterns.
@@ -138,17 +138,3 @@ An invented accent, a decorative animation, a gradient filling an empty space
138
138
  A codebase with one consistent approach is more maintainable than one with a better approach applied to 30% of it. Note the divergence, raise it, change it deliberately as its own work — not silently, mid-task.
139
139
 
140
140
  **This tiebreaker outranks the other six.** It does not outrank Part 1: a local convention creating a genuine accessibility risk is a defect to raise, not a convention to match.
141
-
142
- ---
143
-
144
- ## Notes for the author (not for the agent)
145
-
146
- **What changed in v0.2.** Part 1 did not exist. The file was adjudicative only — seven tiebreakers that fire when rules collide, with no method for producing a rule not yet written. That meant the system handed an agent 51 known failures and no way to recognise the 52nd. The four frames are that method.
147
-
148
- Frame 3 is the most immediately useful, because it is the only idea here that produces a number. Everything else in this system is checked by inspection; interaction cost is checked by counting, which makes it the one principle an agent can be held to objectively.
149
-
150
- Frames 1 and 2 are close to reasoning already embedded in `00` — the risk frame is *why* most of those rules exist, and the rationale requirement is the decidability test that let them in. Stating them explicitly means the next rule can be derived rather than remembered.
151
-
152
- **Tiebreaker 7 remains the one to argue about**, and now has a stated ceiling: it loses to Frame 1. Without that boundary, "match the codebase" would license inheriting anything.
153
-
154
- Tiebreakers 1 and 2 are the same instinct from two directions, and both come from outside software — a document that looks wrong gets marked and filed, never destroyed.
package/rules/05-copy.md CHANGED
@@ -141,13 +141,3 @@ See `P-01` for *where* the message goes and `F-37` for field-level validation te
141
141
  6. Numerals as figures, formatted consistently? (`I-83`)
142
142
  7. One word per concept across the whole product? (`I-87`)
143
143
  8. Every error saying what happened and what to do next? (`I-90`)
144
-
145
- ---
146
-
147
- ## Notes for the author (not for the agent)
148
-
149
- **Where your taste is recorded here:** the ban on apology words in errors, and `I-90`'s requirement that the heading and button work without the body text. Both come from the same instinct as the rest of the system — the person reading is trying to get something done, and the interface should not make them wade.
150
-
151
- `I-87` is the rule most likely to need a project-specific companion. A term list belongs in the brand file's voice section, not here; this rule only says that one must exist and be followed.
152
-
153
- Deliberately absent: tone-of-voice guidance beyond plain language. Tone is a brand decision and varies per client, so it belongs in `03-brand.md` when that file exists.
@@ -89,3 +89,13 @@ check that never inspected anything.
89
89
 
90
90
  A skipped check must say `skipped` and give the reason. Do not report `ran` for
91
91
  a check you did not perform.
92
+
93
+ **`files=` and `styled=` describe a scope, and the scope is not fixed.**
94
+ `jig check` defaults to the files changed since HEAD and falls back to the
95
+ whole repo only when that diff is empty. So the same project reports a
96
+ different `files=` before and after a commit with nothing else having moved,
97
+ and two `JIG_CHECK:` lines are comparable only when both runs had the same
98
+ scope. Run `jig check --all` for a number that describes the project rather
99
+ than your diff — `--all` widens the files, not the rules. A narrowed run says
100
+ so in its own output; attest from what it printed, not from what you assumed
101
+ it looked at.