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 +139 -0
- package/dist/index.js +65 -23
- package/package.json +1 -1
- package/rules/01-modes.md +0 -14
- package/rules/02-tokens.md +26 -17
- package/rules/03-patterns.md +0 -14
- package/rules/04-principles.md +0 -14
- package/rules/05-copy.md +0 -10
- package/templates/SKILL.md.tmpl +10 -0
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
|
|
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))
|
|
2414
|
-
|
|
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
|
-
|
|
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. \`--
|
|
3590
|
-
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
package/rules/02-tokens.md
CHANGED
|
@@ -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 |
|
|
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
|
|
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/03-patterns.md
CHANGED
|
@@ -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.
|
package/rules/04-principles.md
CHANGED
|
@@ -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.
|
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.
|