jig-ui 0.8.1 → 0.9.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 +124 -0
- package/README.md +4 -4
- package/dist/index.js +63 -22
- package/package.json +1 -1
- package/rules/00-anti-patterns.md +9 -1
- package/rules/02-tokens.md +25 -16
- package/rules.index.json +7 -1
- package/templates/SKILL.md.tmpl +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
One new rule and one amended correction, both from the same afternoon of
|
|
6
|
+
dogfooding and both about the same blind spot: H-47's correction always pointed
|
|
7
|
+
at a token, so an agent reading it literally always produced one.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`B-105` Monospace sized by a guessed ratio.** No rule in the corpus
|
|
12
|
+
mentioned monospace or inline `code` at all — `jig explain monospace` returned
|
|
13
|
+
nothing — while `brand.default.css` ships a `--font-mono` stack, so every Jig
|
|
14
|
+
project has the pairing and none had guidance on it.
|
|
15
|
+
|
|
16
|
+
Shrinking inline code by a ratio is right for faces drawn apart, where a mono
|
|
17
|
+
face often does sit larger at the same `font-size`. In a superfamily it is
|
|
18
|
+
wrong: IBM Plex Sans and IBM Plex Mono are both x-height 51.6 and cap-height
|
|
19
|
+
69.8 per 1000 units — identical — and mono is *narrower*. A `0.9em` there sets
|
|
20
|
+
code at x-height 46.8 inside text at 52, creating the mismatch it was meant to
|
|
21
|
+
remove. The rule asks for one measurement, once per project, when the brand
|
|
22
|
+
file is written.
|
|
23
|
+
|
|
24
|
+
It deliberately has no token. Inline `code` appears inside body text,
|
|
25
|
+
headings, table cells and captions; one multiplier has to be right for all
|
|
26
|
+
four, and a fixed token is worse — it collapses code in a heading to caption
|
|
27
|
+
size. Inheriting is correct in every host.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **`H-47`'s correction names a third branch.** It offered "reference the token"
|
|
32
|
+
or "a value that cannot be expressed as a token indicates a missing token".
|
|
33
|
+
Both end in a token. Twice in a row on Jig's own documentation site the right
|
|
34
|
+
answer was **deletion** — the `0.9em` above, and a `min-width` in `em` on a
|
|
35
|
+
table column that `max-content` measures for free. An agent following the text
|
|
36
|
+
as written invents `--text-code: 0.9em` and entrenches a value that should not
|
|
37
|
+
exist. The correction now says to check "should this value exist at all"
|
|
38
|
+
before minting a token.
|
|
39
|
+
|
|
40
|
+
`rules.index.json`'s `fix: token-substitute` on H-47 encoded the same
|
|
41
|
+
assumption and is now `token-substitute-or-remove`. Nothing consumes that
|
|
42
|
+
field yet, which is why it was worth correcting before something does.
|
|
43
|
+
|
|
44
|
+
- **Rule count is 105.** The attestation line and the README's counts move with
|
|
45
|
+
it.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## 0.8.2
|
|
49
|
+
|
|
50
|
+
Every fix here was found by a consumer using Jig rather than by Jig checking
|
|
51
|
+
itself: the documentation site was upgraded to 0.8.1 and then styled in
|
|
52
|
+
Tailwind, which is the first time Jig's Tailwind guidance had been followed
|
|
53
|
+
end to end by anything other than its own tests.
|
|
54
|
+
|
|
55
|
+
Three of the four are the same shape — two places answering one question, and
|
|
56
|
+
disagreeing without either knowing the other existed.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- **`H-47` read a value differently depending on how it was spelled.**
|
|
61
|
+
`00-anti-patterns.md:11` sets the scope of the whole rule file: "**Framework:**
|
|
62
|
+
agnostic. […] Where a utility-class framework is in use, translate — the rule
|
|
63
|
+
is about the resulting style, not the syntax." The detector was the exact
|
|
64
|
+
inverse, in both directions at once. Its CSS branch matched `px` alone and
|
|
65
|
+
excluded `0/1px/2px`; its Tailwind branch matched ten units and excluded
|
|
66
|
+
nothing. So `font-size: 0.9em` was silent while `text-[0.9em]` was an error,
|
|
67
|
+
and `p-[1px]` was an error while `padding: 1px` was not.
|
|
68
|
+
|
|
69
|
+
A project on plain CSS got a clean `check` for code a Tailwind project got
|
|
70
|
+
eight errors for. Found by converting a real stylesheet to utilities without
|
|
71
|
+
changing one computed value and watching the report go from `No findings` to
|
|
72
|
+
eight; the two declarations responsible had been in that file since it was
|
|
73
|
+
written and had never been flagged. Both branches now read one definition.
|
|
74
|
+
|
|
75
|
+
**This widens what `check` reports.** A stylesheet carrying `1rem` or `0.9em`
|
|
76
|
+
past the token layer was always an H-47 violation and is now reported as one,
|
|
77
|
+
so a repo that was clean under 0.8.1 can have findings here without its CSS
|
|
78
|
+
having moved.
|
|
79
|
+
|
|
80
|
+
- **The naming contract named namespaces Tailwind does not have.**
|
|
81
|
+
`02-tokens.md` opened with "an alias block can expose **any of them** as
|
|
82
|
+
Tailwind utilities" over a table of thirteen. Three generate nothing in
|
|
83
|
+
Tailwind v4 — `--duration-*`, `--measure-*` and `--focus-ring-*` — and
|
|
84
|
+
`--duration-*` shared a row with `--ease-*`, so only half of that row worked.
|
|
85
|
+
Aliasing one is accepted, emits the custom property, and produces no rule, so
|
|
86
|
+
the class lands on the element and does nothing: the same silent failure the
|
|
87
|
+
file warns about 190 lines later, reached from the opposite direction. The
|
|
88
|
+
table now carries a **Utility** column, and the three say how to be read from
|
|
89
|
+
a class instead — `max-w-(--measure-prose)`, which keeps the semantic token
|
|
90
|
+
and satisfies `H-47` without going through `@theme`.
|
|
91
|
+
|
|
92
|
+
`init`'s own generator was wrong in both directions. It correctly filtered
|
|
93
|
+
`--measure-*` and `--focus-ring-*`, and it also filtered `--size-*` and
|
|
94
|
+
`--border-width-*`, which both generate working utilities — `size-control`
|
|
95
|
+
sets width and height, `border-hairline` sets a border width. Jig was
|
|
96
|
+
withholding correct aliases for its own tokens, and a test asserted that as
|
|
97
|
+
correct, which is how it survived.
|
|
98
|
+
|
|
99
|
+
- **`check` did not say which files it had looked at.** It defaults to the
|
|
100
|
+
files changed since HEAD and falls back to the whole repo when that diff is
|
|
101
|
+
empty. Nothing in the output said so, so the same repo reported `files=31` on
|
|
102
|
+
a clean tree and `files=9` with nine files touched, minutes apart, with
|
|
103
|
+
nothing committed — and a consumer bisected it by reverting files one at a
|
|
104
|
+
time to find out why.
|
|
105
|
+
|
|
106
|
+
Worse, `mechanical=pass:0` on a dirty tree means "nothing in your diff
|
|
107
|
+
fired", not "the project is clean", and the skill tells an agent to run
|
|
108
|
+
`check` before finishing — exactly when the tree is dirty and the scope is
|
|
109
|
+
narrowest. The exempt line compounded it: `src/content/rules.ts (matches
|
|
110
|
+
nothing — check the path)` was printed for a path that exists, is tracked,
|
|
111
|
+
and had matched a file on the previous run. The glob was fine; it matched
|
|
112
|
+
nothing *within the narrowed set*, and "check the path" sends you to debug a
|
|
113
|
+
correct config.
|
|
114
|
+
|
|
115
|
+
The summary now names the scope, a narrowed run says a clean result is not a
|
|
116
|
+
clean project, the exempt note distinguishes "no such path" from "not in this
|
|
117
|
+
scan", and `--all`'s help says it widens the files rather than the rules —
|
|
118
|
+
which is how it had been read.
|
|
119
|
+
|
|
120
|
+
- **A flag written across two lines vanished from the metadata guard.**
|
|
121
|
+
`registeredFlags` read `src/index.ts` line by line and required the flag
|
|
122
|
+
string to sit on the same line as `.option(`, so reformatting one option to
|
|
123
|
+
fit a longer description dropped `--all` from the parsed set and from every
|
|
124
|
+
guard built on it. Caught by its own canary the moment an option wrapped.
|
|
125
|
+
|
|
126
|
+
|
|
3
127
|
## 0.8.1
|
|
4
128
|
|
|
5
129
|
Both fixes here are the same shape: 0.8.0 corrected the instance it was looking
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Installed as `npx jig-ui` — the bare name was taken on npm.
|
|
|
12
12
|
Jig is **a skill your coding agent reads**, and **a CLI you can run yourself**.
|
|
13
13
|
They are two halves of the same thing, and the split is not arbitrary:
|
|
14
14
|
|
|
15
|
-
- Of the
|
|
15
|
+
- Of the 105 rules, **7 can be decided by a machine** — a hard-coded colour, a
|
|
16
16
|
contrast ratio below the floor, a removed focus ring. The CLI decides those.
|
|
17
17
|
- The other **97 are judgment** — whether an empty state says anything useful,
|
|
18
18
|
whether a label reads as an instruction, whether motion earns its place. No
|
|
@@ -181,7 +181,7 @@ on the result — the CLI reports, the agent applies the judgment half.
|
|
|
181
181
|
| Slash command | Equivalent |
|
|
182
182
|
| --- | --- |
|
|
183
183
|
| `/jig init` | `jig init` — then states the mode it chose and what it wired |
|
|
184
|
-
| `/jig check` | `jig check` — then applies the
|
|
184
|
+
| `/jig check` | `jig check` — then applies the 95 judgment rules and reports both halves |
|
|
185
185
|
| `/jig explain C-19` | `jig explain C-19` — prints the rule as-is, without paraphrasing it |
|
|
186
186
|
| `/jig explain contrast` | `jig explain contrast` — every rule matching a word, when you do not have an id |
|
|
187
187
|
| `/jig install --agent cursor` | `jig install --agent cursor` |
|
|
@@ -262,7 +262,7 @@ In CI:
|
|
|
262
262
|
code — nothing model-dependent, no network. As a pre-commit hook, plain `check`
|
|
263
263
|
looks at changed files only.
|
|
264
264
|
|
|
265
|
-
What you will not get from the CLI alone is the other
|
|
265
|
+
What you will not get from the CLI alone is the other 95 rules. `check` says so
|
|
266
266
|
rather than letting a narrow pass read as a broad one.
|
|
267
267
|
|
|
268
268
|
## What `check` covers
|
|
@@ -330,7 +330,7 @@ treatment.
|
|
|
330
330
|
|
|
331
331
|
| File | Contents |
|
|
332
332
|
| --- | --- |
|
|
333
|
-
| `rules/00-anti-patterns.md` |
|
|
333
|
+
| `rules/00-anti-patterns.md` | 88 universal rules with corrections |
|
|
334
334
|
| `rules/01-modes.md` | `editorial` / `product` / `operator` profiles |
|
|
335
335
|
| `rules/02-tokens.md` | Token contract, naming, consumption |
|
|
336
336
|
| `rules/03-patterns.md` | Component anatomy and behaviour |
|
package/dist/index.js
CHANGED
|
@@ -1448,7 +1448,7 @@ function formatReport(findings, meta) {
|
|
|
1448
1448
|
if (notes > 0) summaryParts.push(plural(notes, "note"));
|
|
1449
1449
|
const rulesFired = new Set(findings.map((f) => f.ruleId)).size;
|
|
1450
1450
|
const scope = meta.totalSpecs ? `${meta.totalRules} rules (+ ${meta.totalSpecs} pattern and mode specs)` : `${meta.totalRules} rules`;
|
|
1451
|
-
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`;
|
|
1452
1452
|
lines.push(` ${summaryParts.join(", ")} \xB7 ${scope}${examined}, ${rulesFired} fired`);
|
|
1453
1453
|
if (meta.scanned !== void 0 && meta.withStyles === 0) {
|
|
1454
1454
|
lines.push("");
|
|
@@ -1456,11 +1456,17 @@ function formatReport(findings, meta) {
|
|
|
1456
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.`
|
|
1457
1457
|
);
|
|
1458
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
|
+
}
|
|
1459
1465
|
if (meta.exemptPatterns && meta.exemptPatterns.length > 0) {
|
|
1460
1466
|
const n = meta.exempt?.length ?? 0;
|
|
1461
1467
|
lines.push(` ${n} file(s) exempt via jig.config.json and not scanned:`);
|
|
1462
1468
|
for (const { pattern, count, tooBroad } of meta.exemptPatterns) {
|
|
1463
|
-
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" : ""}`;
|
|
1464
1470
|
lines.push(` ${pattern} (${note})`);
|
|
1465
1471
|
}
|
|
1466
1472
|
if (n > 0) {
|
|
@@ -2361,6 +2367,35 @@ var pureBlackWhite = {
|
|
|
2361
2367
|
}
|
|
2362
2368
|
};
|
|
2363
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
|
+
|
|
2364
2399
|
// src/check/tailwind.ts
|
|
2365
2400
|
var CLASS_ATTR = /\b(?:class|className)\s*=\s*/g;
|
|
2366
2401
|
function readBraced(source, open) {
|
|
@@ -2404,15 +2439,21 @@ function classAttributeValues(source) {
|
|
|
2404
2439
|
}
|
|
2405
2440
|
var ARBITRARY = /(?:^|\s)(?:[\w-]+:)*([a-z][\w-]*)-\[([^\]\s]+)\]/gi;
|
|
2406
2441
|
var COLOUR = /^(?:#[0-9a-f]{3,8}|(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\()/i;
|
|
2407
|
-
var
|
|
2442
|
+
var LENGTH_PARTS = /^(-?\d*\.?\d+)([a-z]+)$/i;
|
|
2408
2443
|
function arbitraryValues(classes) {
|
|
2409
2444
|
const out = [];
|
|
2410
2445
|
for (const m of classes.matchAll(ARBITRARY)) {
|
|
2411
2446
|
const [, utility, rawValue] = m;
|
|
2412
2447
|
const value = rawValue.replace(/_/g, " ");
|
|
2413
2448
|
if (/var\(|--/.test(value)) continue;
|
|
2414
|
-
if (COLOUR.test(value))
|
|
2415
|
-
|
|
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" });
|
|
2416
2457
|
}
|
|
2417
2458
|
return out;
|
|
2418
2459
|
}
|
|
@@ -2604,8 +2645,6 @@ var SPACING_PROPS = /* @__PURE__ */ new Set([
|
|
|
2604
2645
|
]);
|
|
2605
2646
|
var DECL_RE2 = /(?<![-\w])([a-zA-Z-]+)\s*:\s*([^;]+)(?:;|$)/g;
|
|
2606
2647
|
var COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgb|hsl)a?\([^)]*\)/;
|
|
2607
|
-
var PX_RE = /(-?\d*\.?\d+)px/g;
|
|
2608
|
-
var EXCLUDED_PX = /* @__PURE__ */ new Set([0, 1, 2]);
|
|
2609
2648
|
var KEYFRAME_STEP_RE = /^(from|to|\d+(\.\d+)?%)$/i;
|
|
2610
2649
|
var PRIMITIVE_RE = /var\(\s*(--(?:brand|error|warning|success|info)-(?:h|s|l|fill-a))\s*[,)]/g;
|
|
2611
2650
|
var TOKEN_LAYER_RE = /(^|\/)(\.jig\/tokens|jig)\/[^/]+\.css$/;
|
|
@@ -2662,16 +2701,7 @@ var hardcodedValue = {
|
|
|
2662
2701
|
continue;
|
|
2663
2702
|
}
|
|
2664
2703
|
if (SPACING_PROPS.has(prop)) {
|
|
2665
|
-
|
|
2666
|
-
let px;
|
|
2667
|
-
let flagged = false;
|
|
2668
|
-
while (px = PX_RE.exec(value)) {
|
|
2669
|
-
if (!EXCLUDED_PX.has(Math.abs(parseFloat(px[1])))) {
|
|
2670
|
-
flagged = true;
|
|
2671
|
-
break;
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
if (flagged) {
|
|
2704
|
+
if (hasHardCodedLength(value)) {
|
|
2675
2705
|
const line = lineOfOffset(block, m.index);
|
|
2676
2706
|
findings.push(
|
|
2677
2707
|
mkFinding(
|
|
@@ -3132,7 +3162,8 @@ function check(opts) {
|
|
|
3132
3162
|
scanned: files.length,
|
|
3133
3163
|
withStyles,
|
|
3134
3164
|
exempt,
|
|
3135
|
-
exemptPatterns: byPattern
|
|
3165
|
+
exemptPatterns: byPattern,
|
|
3166
|
+
scope: selection.mode
|
|
3136
3167
|
});
|
|
3137
3168
|
return { findings, report, hasError };
|
|
3138
3169
|
}
|
|
@@ -3547,7 +3578,9 @@ var TAILWIND_NAMESPACES = [
|
|
|
3547
3578
|
"--perspective-",
|
|
3548
3579
|
"--aspect-",
|
|
3549
3580
|
"--ease-",
|
|
3550
|
-
"--animate-"
|
|
3581
|
+
"--animate-",
|
|
3582
|
+
"--size-",
|
|
3583
|
+
"--border-width-"
|
|
3551
3584
|
];
|
|
3552
3585
|
function tailwindNamespaced(names) {
|
|
3553
3586
|
const kept = names.filter((n) => TAILWIND_NAMESPACES.some((ns) => n.startsWith(ns)));
|
|
@@ -3587,8 +3620,10 @@ function utilitiesBody(names, version2) {
|
|
|
3587
3620
|
order, so Jig's value always wins. Do not "fix" it \u2014 deleting the alias
|
|
3588
3621
|
removes the utility, deleting Jig's removes the value.
|
|
3589
3622
|
|
|
3590
|
-
Only tokens in a Tailwind namespace are listed. \`--
|
|
3591
|
-
|
|
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)\`.
|
|
3592
3627
|
|
|
3593
3628
|
Regenerate with \`jig init\` after the token layer changes; a token missing here
|
|
3594
3629
|
is a class that renders and matches nothing. */
|
|
@@ -4240,7 +4275,13 @@ program.command("update").description("Update vendored Jig rules, skipping files
|
|
|
4240
4275
|
process.exit(1);
|
|
4241
4276
|
}
|
|
4242
4277
|
});
|
|
4243
|
-
program.command("check").description("Check the repo against Jig's mechanical + hybrid rules.").option(
|
|
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) => {
|
|
4244
4285
|
const projectRoot = findProjectRoot(process.cwd());
|
|
4245
4286
|
try {
|
|
4246
4287
|
const result = check({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jig-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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",
|
|
@@ -144,6 +144,13 @@ Four workable treatments:
|
|
|
144
144
|
4. **Solid background behind the text** — the caption approach; most reliable, least subtle.
|
|
145
145
|
A text shadow may reinforce any of these but never substitutes for one. Verify against the worst image the slot will ever hold, not the one in the mockup.
|
|
146
146
|
|
|
147
|
+
### B-105 Monospace sized by a guessed ratio
|
|
148
|
+
❌ Inline `code` set to `0.9em` — or any fixed ratio — to stop it looking bigger than the text around it
|
|
149
|
+
✅ Measure both faces before correcting either. If they share an x-height, the ratio is the mismatch. Let code inherit its host's size.
|
|
150
|
+
The habit comes from pairings where it is true: a mono face drawn separately from the text face often does sit larger at the same `font-size`. In a superfamily it does not. IBM Plex Sans and IBM Plex Mono are both x-height 51.6 and cap-height 69.8 per 1000 units — identical — and mono is *narrower*, not larger. A `0.9em` there sets code at x-height 46.8 inside text at 52, creating the mismatch it was meant to remove.
|
|
151
|
+
A ratio is also the wrong shape of answer. Inline `code` appears inside body text, headings, table cells and captions; one multiplier has to be right for all of them, and a fixed token is worse still — it collapses code in a heading to caption size. Inheriting is correct in every host, which is why this rule has no token.
|
|
152
|
+
The measurement is one line in a browser: render `x` in both faces at the same size and compare the rendered heights, or read `sxHeight` from each font's `OS/2` table. Do it once per project when the brand file is written, not per component.
|
|
153
|
+
|
|
147
154
|
---
|
|
148
155
|
|
|
149
156
|
## C. Colour and contrast
|
|
@@ -485,7 +492,8 @@ Where people must *browse* to decide, split the list into two dependent fields
|
|
|
485
492
|
|
|
486
493
|
### H-47 Values hard-coded past the token layer
|
|
487
494
|
❌ A raw hex colour or pixel size written in component code
|
|
488
|
-
✅ Reference the token. Consume the semantic role (`--color-text-strong`), not the primitive (`--color-neutral-900`). A value that cannot be expressed as a token
|
|
495
|
+
✅ Reference the token. Consume the semantic role (`--color-text-strong`), not the primitive (`--color-neutral-900`). A value that cannot be expressed as a token is a missing token — or a value that should not exist at all. Check the second before minting the first.
|
|
496
|
+
Deletion is a real answer and the easy one to miss, because the correction points at a token and an agent reading it literally invents one. Both times this rule fired on Jig's own documentation site the fix was removal: `font-size: 0.9em` on inline `code`, where the two faces share vertical metrics and inheriting is correct (`B-105`); and a `min-width` in `em` on a table column that `max-content` measures for free. A token minted to satisfy a detector entrenches the value it was invented for.
|
|
489
497
|
|
|
490
498
|
### H-48 JavaScript for something CSS does
|
|
491
499
|
❌ Scroll listeners for sticky positioning; scripted accordions and dialogs that have native equivalents
|
package/rules/02-tokens.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
| `--
|
|
291
|
-
| `--
|
|
292
|
-
| `--
|
|
293
|
-
| `--
|
|
294
|
-
| `--
|
|
295
|
-
| `--
|
|
296
|
-
| `--
|
|
297
|
-
| `--
|
|
298
|
-
| `--
|
|
299
|
-
| `--
|
|
300
|
-
| `--
|
|
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.
|
package/rules.index.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"bucket": "mechanical",
|
|
50
50
|
"severity": "error",
|
|
51
51
|
"detector": "hardcoded-value",
|
|
52
|
-
"fix": "token-substitute",
|
|
52
|
+
"fix": "token-substitute-or-remove",
|
|
53
53
|
"since": "0.1.0"
|
|
54
54
|
},
|
|
55
55
|
{
|
|
@@ -187,6 +187,12 @@
|
|
|
187
187
|
"severity": "note",
|
|
188
188
|
"since": "0.1.0"
|
|
189
189
|
},
|
|
190
|
+
{
|
|
191
|
+
"id": "B-105",
|
|
192
|
+
"bucket": "judgment",
|
|
193
|
+
"severity": "note",
|
|
194
|
+
"since": "0.9.0"
|
|
195
|
+
},
|
|
190
196
|
{
|
|
191
197
|
"id": "C-66",
|
|
192
198
|
"bucket": "judgment",
|
package/templates/SKILL.md.tmpl
CHANGED
|
@@ -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.
|