jig-ui 0.25.0 → 0.26.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 +47 -8
- package/dist/index.js +25 -3
- package/package.json +1 -1
- package/rules/02-tokens.md +10 -0
- package/rules/03-patterns.md +12 -4
- package/tokens/brand.default.css +6 -2
- package/tokens/mode.operator.css +9 -0
- package/tokens/mode.product.css +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0 (2026-10-13)
|
|
4
|
+
|
|
5
|
+
An unbranded page that looks designed, tables that hold together on a phone and
|
|
6
|
+
on a touch screen, and two layout faults the render probe now measures.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **An unbranded page no longer reads as unstyled.** The default font stack put
|
|
11
|
+
`system-ui` first, and on Linux that resolves to the desktop's own font,
|
|
12
|
+
often DejaVu Sans, before any better face is tried. It now names each
|
|
13
|
+
platform's interface face in turn: San Francisco, Segoe UI, Roboto, then
|
|
14
|
+
Noto Sans, Helvetica and Arial. The `A-138` serif check knows these names,
|
|
15
|
+
so the stack is not read as a serif for ending in `sans-serif`.
|
|
16
|
+
- **A table stacked on a phone leads with what each row is.** `L-01`, step 6,
|
|
17
|
+
told an agent to put each column's name in front of every cell, and a member
|
|
18
|
+
list on a phone read like a form: "Member:", "Email:", "Role:", "Status:".
|
|
19
|
+
The row's own name now leads its block with no label, a value that says what
|
|
20
|
+
it is (an email, a role, a status) gets none, and the row's actions sit
|
|
21
|
+
together on one line where they fit, with no "Actions" label. In three runs,
|
|
22
|
+
every row kept its actions on one line at 390px.
|
|
23
|
+
- **A table's rows stay one height.** `operator`'s 36px row is below the 48px
|
|
24
|
+
touch target, and an agent met the floor with a control's own minimum
|
|
25
|
+
height, so rows with an action stood taller than rows without one. A control
|
|
26
|
+
in a row now takes the row's height. For a mouse a dense row is above WCAG
|
|
27
|
+
2.2's 24px minimum; for a finger it is not, so under a coarse pointer
|
|
28
|
+
`operator` and `product` raise their row tokens to the touch target, and
|
|
29
|
+
every row grows together.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **The render probe reports a page that scrolls sideways, with or without a
|
|
34
|
+
review.** A page wider than the screen was reported only when a critique had
|
|
35
|
+
called it clean; with no critique the measurement was dropped. Help articles
|
|
36
|
+
built without Jig ran 424 to 478px wide on a 360px phone, with nothing
|
|
37
|
+
reported. It is now a measured failure in every mode (`D-115`).
|
|
38
|
+
- **The render probe reports a table cell that has left its table.** An agent
|
|
39
|
+
set a cell to `display: flex` to line up a name and a tag; the cell stopped
|
|
40
|
+
taking its row's height, and every name drifted above its row's divider. Any
|
|
41
|
+
`td` or `th` that is not a table cell while its table is drawn as a table is
|
|
42
|
+
now a measured failure (`P-06`), which also says to put the flex on a wrapper
|
|
43
|
+
inside the cell. A table stacked on a phone is left out.
|
|
44
|
+
|
|
3
45
|
## 0.25.0 (2026-10-09)
|
|
4
46
|
|
|
5
47
|
Link previews, code and tables that reflow on an `editorial` phone, and the
|
|
@@ -339,8 +381,7 @@ and `jig ship` is where nothing is optional.
|
|
|
339
381
|
|
|
340
382
|
## 0.21.0 (2026-09-28)
|
|
341
383
|
|
|
342
|
-
Dev builds, so a fix is tried on a real project before it ships, and
|
|
343
|
-
fixes that trial on jig-site turned up.
|
|
384
|
+
Dev builds, so a fix is tried on a real project before it ships, and gate fixes that trial turned up.
|
|
344
385
|
|
|
345
386
|
### Added
|
|
346
387
|
|
|
@@ -622,7 +663,7 @@ The gate holds an HTML mockup that does not draw the page at every size.
|
|
|
622
663
|
|
|
623
664
|
## 0.18.4 (2026-09-25)
|
|
624
665
|
|
|
625
|
-
Every release summary is one or two lines
|
|
666
|
+
Every release summary is one or two lines.
|
|
626
667
|
|
|
627
668
|
### Changed
|
|
628
669
|
|
|
@@ -654,8 +695,7 @@ older probe file says it is older rather than forged.
|
|
|
654
695
|
|
|
655
696
|
## 0.18.2 (2026-09-25)
|
|
656
697
|
|
|
657
|
-
Every release in this changelog now carries its date and a one-line summary
|
|
658
|
-
for jig-site's Versions page.
|
|
698
|
+
Every release in this changelog now carries its date and a one-line summary.
|
|
659
699
|
|
|
660
700
|
### Changed
|
|
661
701
|
|
|
@@ -669,8 +709,7 @@ for jig-site's Versions page.
|
|
|
669
709
|
|
|
670
710
|
## 0.18.1 (2026-09-25)
|
|
671
711
|
|
|
672
|
-
Two fixes
|
|
673
|
-
menu, and the emoji rule missed an arrow that renders as an emoji.
|
|
712
|
+
Two fixes: the probe misread a `<details>` menu, and the emoji rule missed an arrow that renders as an emoji.
|
|
674
713
|
|
|
675
714
|
### Fixed
|
|
676
715
|
|
|
@@ -739,7 +778,7 @@ keyboard access a rule requires fits every script budget.
|
|
|
739
778
|
|
|
740
779
|
## 0.17.3 (2026-09-25)
|
|
741
780
|
|
|
742
|
-
Three defects found building
|
|
781
|
+
Three defects found building a site header with the full loop.
|
|
743
782
|
|
|
744
783
|
### Fixed
|
|
745
784
|
|
package/dist/index.js
CHANGED
|
@@ -4268,7 +4268,7 @@ var HEADLINE_SELECTOR = /\bh1\b|\bh2\b|hero|display|headline|title/i;
|
|
|
4268
4268
|
var SERIF = /font-family\s*:\s*([^;}]+)/i;
|
|
4269
4269
|
function serifFamily(value) {
|
|
4270
4270
|
const first = value.split(",")[0].trim().toLowerCase();
|
|
4271
|
-
if (/sans|mono|system-ui|ui-sans|inter\b|helvetica|arial/.test(first)) return false;
|
|
4271
|
+
if (/sans|mono|system-ui|ui-sans|-apple-system|blinkmacsystemfont|segoe|roboto|inter\b|helvetica|arial/.test(first)) return false;
|
|
4272
4272
|
return /serif|georgia|garamond|playfair|fraunces|lora|merriweather|instrument serif|dm serif|cormorant|newsreader|times|baskerville|bodoni|caslon|libre caslon|source serif|pt serif|crimson/.test(value.toLowerCase());
|
|
4273
4273
|
}
|
|
4274
4274
|
var italicSerifDisplay = {
|
|
@@ -6332,6 +6332,19 @@ var PROBE_SCRIPT = `(async () => {
|
|
|
6332
6332
|
if ((ox !== 'auto' && ox !== 'scroll') || el.scrollWidth <= el.clientWidth + 1 || !vis(el)) continue;
|
|
6333
6333
|
sidewaysBoxes.push({ box: el.tagName.toLowerCase() + (el.classList[0] ? '.' + el.classList[0] : ''), text: (el.innerText || '').trim().replace(/\\s+/g, ' ').slice(0, 40), scrollWidth: el.scrollWidth, clientWidth: el.clientWidth });
|
|
6334
6334
|
}
|
|
6335
|
+
// A table cell given its own display (flex, grid, block) while its table is
|
|
6336
|
+
// still drawn as a table stops being a cell: it no longer takes its row's
|
|
6337
|
+
// height, and its border no longer meets the row's. A team table set its
|
|
6338
|
+
// name cells to flex, to line up a name and a tag, and every name drifted
|
|
6339
|
+
// above its row. A stacked table (the table itself set to block) is left out.
|
|
6340
|
+
const brokenCells = [];
|
|
6341
|
+
for (const cell of [...document.querySelectorAll('td, th')].slice(0, 2000)) {
|
|
6342
|
+
if (brokenCells.length >= 5) break;
|
|
6343
|
+
const table = cell.closest('table');
|
|
6344
|
+
const display = getComputedStyle(cell).display;
|
|
6345
|
+
if (!table || getComputedStyle(table).display !== 'table' || display === 'table-cell' || display === 'none' || !vis(cell)) continue;
|
|
6346
|
+
brokenCells.push({ cell: cell.tagName.toLowerCase() + (cell.classList[0] ? '.' + cell.classList[0] : ''), text: (cell.innerText || '').trim().replace(/\\s+/g, ' ').slice(0, 40), display });
|
|
6347
|
+
}
|
|
6335
6348
|
return JSON.stringify({
|
|
6336
6349
|
jigProbe: ${PROBE_VERSION},
|
|
6337
6350
|
url: location.href,
|
|
@@ -6340,6 +6353,7 @@ var PROBE_SCRIPT = `(async () => {
|
|
|
6340
6353
|
clientWidth: doc.clientWidth,
|
|
6341
6354
|
sidewaysScroll: doc.scrollWidth > doc.clientWidth,
|
|
6342
6355
|
sidewaysBoxes,
|
|
6356
|
+
brokenCells,
|
|
6343
6357
|
bodyFont: getComputedStyle(document.body).fontFamily,
|
|
6344
6358
|
defaultFont: getComputedStyle(document.body).fontFamily === defaultFont,
|
|
6345
6359
|
unresolvedTokens: [...unresolved],
|
|
@@ -6811,13 +6825,21 @@ function probeCheck(probes, verdictOf, indexable = true, mode) {
|
|
|
6811
6825
|
const clean = (id) => ["ok", "n/a"].includes(verdictOf(id) ?? "");
|
|
6812
6826
|
const at = (p) => `probe-${p.width}.json`;
|
|
6813
6827
|
for (const p of probes) {
|
|
6814
|
-
if (p.sidewaysScroll
|
|
6815
|
-
|
|
6828
|
+
if (p.sidewaysScroll) {
|
|
6829
|
+
if (clean("D-115")) {
|
|
6830
|
+
errors.push(`D-115 is "${verdictOf("D-115")}", but ${at(p)} measured the page ${p.scrollWidth}px wide in a ${p.clientWidth}px viewport \u2014 it scrolls sideways.`);
|
|
6831
|
+
} else {
|
|
6832
|
+
failures.push(`${at(p)}: the page is ${p.scrollWidth}px wide in a ${p.clientWidth}px viewport, so the whole page scrolls sideways (D-115). Find what is wider than the screen and let it wrap, stack, or scroll inside its own box (L-01, step 6).`);
|
|
6833
|
+
}
|
|
6816
6834
|
}
|
|
6817
6835
|
if (mode === "editorial" && p.width <= 480 && p.sidewaysBoxes?.length) {
|
|
6818
6836
|
const shown = p.sidewaysBoxes.map((b) => `${b.box}${b.text ? ` ("${b.text}")` : ""}, ${b.scrollWidth}px in ${b.clientWidth}px`).join("; ");
|
|
6819
6837
|
failures.push(`${at(p)}: ${p.sidewaysBoxes.length === 1 ? "a box scrolls" : `${p.sidewaysBoxes.length} boxes scroll`} sideways on a phone: ${shown}. \`editorial\` allows no scrolling region on mobile (M-01): a code block wraps its long lines, keeping its line breaks and indents, and a wide table stacks its rows (L-01, step 6).`);
|
|
6820
6838
|
}
|
|
6839
|
+
if (p.brokenCells?.length) {
|
|
6840
|
+
const shown = p.brokenCells.map((c) => `${c.cell}${c.text ? ` ("${c.text}")` : ""} is display: ${c.display}`).join("; ");
|
|
6841
|
+
failures.push(`${at(p)}: ${p.brokenCells.length === 1 ? "a table cell has" : `${p.brokenCells.length} table cells have`} left the table: ${shown}. A cell given its own display stops taking its row's height, so its border and its text drift from the row's. Keep the cell a cell and put the flex on a wrapper inside it (P-06).`);
|
|
6842
|
+
}
|
|
6821
6843
|
if (p.defaultFont) {
|
|
6822
6844
|
failures.push(`${at(p)}: the page renders in the browser's default font (${p.bodyFont ?? "unknown"}) \u2014 its styles are not applying. No review of this page can pass until they do.`);
|
|
6823
6845
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jig-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "A design system for coding agents. 160 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/02-tokens.md
CHANGED
|
@@ -262,6 +262,16 @@ It is an accessibility floor, so it is excluded from the table above — there i
|
|
|
262
262
|
nothing per-mode about it to resolve. The same is true of `--focus-ring-width`
|
|
263
263
|
and `--focus-ring-offset`, which live in the brand file for that reason.
|
|
264
264
|
|
|
265
|
+
**Inside a table row, the row is the target, and every row is one height.** A
|
|
266
|
+
dense row is shorter than the floor (`operator`'s 36px), and a control that
|
|
267
|
+
sets its own `min-height` to the floor stretches the rows that hold it and not
|
|
268
|
+
the rest: in a run, an invoices table whose "Mark paid" rows stood 48px tall
|
|
269
|
+
among 36px ones. So a control in a row takes the row's height and never its
|
|
270
|
+
own. For a mouse, a dense row is above WCAG 2.2's 24px minimum; for a finger it
|
|
271
|
+
is not, so under `@media (pointer: coarse)` the mode files raise `--size-row`
|
|
272
|
+
(and `--size-row-compact`) to `--size-touch-target`, and every row grows
|
|
273
|
+
together.
|
|
274
|
+
|
|
265
275
|
**Layout sizes.** Three tokens size a page's frame, where the others size what
|
|
266
276
|
is inside it. A real site found none of them and made up its own three:
|
|
267
277
|
|
package/rules/03-patterns.md
CHANGED
|
@@ -285,6 +285,7 @@ Primarily an `operator` pattern; the rules apply wherever a table appears.
|
|
|
285
285
|
**Rules**
|
|
286
286
|
- Row identity is stable across refresh, sort and filter. A row that moves under a click causes the wrong record to be acted on — the most damaging bug this pattern has.
|
|
287
287
|
- Numeric columns are right-aligned and use tabular figures (`--font-numeric`). Text columns are start-aligned. Never centre either.
|
|
288
|
+
- A cell stays a table cell. To line up what is inside it (a name and a tag, an icon and a label), put `display: flex` on a wrapper inside the cell, never on the `<td>` or `<th>` itself: a cell given its own display stops taking its row's height, so its text and its border drift from the rest of the row. A table stacked on a phone, the whole table set to `block`, is the exception (`L-01`, step 6). The render probe reports a cell that has left its table.
|
|
288
289
|
- Column headers state units. "Weight" is ambiguous; "Weight (kg)" is not.
|
|
289
290
|
- Truncation is resolvable by click or expand, never by hover alone (`E-31`, `01-modes.md`).
|
|
290
291
|
- Sort state is visible in the header and reflected in the URL, so a view can be shared.
|
|
@@ -572,10 +573,17 @@ everything else follows from.
|
|
|
572
573
|
- **A code block** keeps its line breaks and indents and wraps a long line
|
|
573
574
|
(`white-space: pre-wrap`, `overflow-wrap: anywhere`). What is copied is
|
|
574
575
|
the source as written, not the wrapped lines.
|
|
575
|
-
- **A table too wide to fit** stacks its rows: each row a block,
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
576
|
+
- **A table too wide to fit** stacks its rows: each row a block, led by the
|
|
577
|
+
cell that names the row (a person, an invoice, a rule) as the block's own
|
|
578
|
+
heading, with no label in front of it. The other cells follow beneath it,
|
|
579
|
+
and a cell carries its column's name only where its value does not say
|
|
580
|
+
what it is: a bare "Yes", a number, a date. An email address, a role, a
|
|
581
|
+
status badge or a price says what it is, so it gets no "Email:", "Role:"
|
|
582
|
+
or "Status:". The row's actions sit together at the block's end, side by
|
|
583
|
+
side where they fit, with no "Actions" label. It stays a `<table>` with
|
|
584
|
+
its header row, so a screen reader still reads it as one. Where it fits,
|
|
585
|
+
it stays a table: the switch is the width at which its columns stop
|
|
586
|
+
fitting.
|
|
579
587
|
- **A long identifier** (a URL, a hash, a file path) breaks where it must
|
|
580
588
|
(`overflow-wrap: anywhere`).
|
|
581
589
|
6. **Each transition happens where the content needs it.** The width at which
|
package/tokens/brand.default.css
CHANGED
|
@@ -117,9 +117,13 @@
|
|
|
117
117
|
--focus-ring-offset: 2px;
|
|
118
118
|
|
|
119
119
|
/* ================= TYPE ================= */
|
|
120
|
-
|
|
120
|
+
/* Each platform's own interface face, named one by one: San Francisco,
|
|
121
|
+
Segoe UI, Roboto, then Noto Sans and Helvetica/Arial for the rest.
|
|
122
|
+
`system-ui` is left out on purpose: on Linux it resolves to the desktop's
|
|
123
|
+
own font, often DejaVu Sans, before any face below it is tried. */
|
|
124
|
+
--font-text: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", "Helvetica Neue", Arial, sans-serif;
|
|
121
125
|
--font-display: var(--font-text);
|
|
122
|
-
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", monospace;
|
|
126
|
+
--font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "Cascadia Code", "Liberation Mono", monospace;
|
|
123
127
|
--font-numeric-features: "tnum" 1, "lnum" 1;
|
|
124
128
|
|
|
125
129
|
/* ================= RADIUS — four, by element size ================= */
|
package/tokens/mode.operator.css
CHANGED
|
@@ -102,3 +102,12 @@
|
|
|
102
102
|
@media (prefers-reduced-motion: reduce) {
|
|
103
103
|
:root { --duration-fast: 1ms; --duration-base: 1ms; --duration-slow: 1ms; }
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/* On a touch screen a table row is a finger's target. A dense row (36px, 32px
|
|
107
|
+
compact) is above WCAG 2.2's 24px minimum for a mouse, and below the 48px a
|
|
108
|
+
finger needs, so under a coarse pointer every row rises to the floor. A
|
|
109
|
+
control inside a row never sets its own height to reach it: every row in a
|
|
110
|
+
table is one height, and here the tokens make that height the right one. */
|
|
111
|
+
@media (pointer: coarse) {
|
|
112
|
+
:root { --size-row: var(--size-touch-target); --size-row-compact: var(--size-touch-target); }
|
|
113
|
+
}
|
package/tokens/mode.product.css
CHANGED
|
@@ -105,3 +105,10 @@
|
|
|
105
105
|
@media (prefers-reduced-motion: reduce) {
|
|
106
106
|
:root { --duration-fast: 1ms; --duration-base: 1ms; --duration-slow: 1ms; }
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
/* On a touch screen a table row is a finger's target: a compact row (40px)
|
|
110
|
+
rises to the 48px floor under a coarse pointer, as `operator`'s rows do. A
|
|
111
|
+
control inside a row never sets its own height to reach it. */
|
|
112
|
+
@media (pointer: coarse) {
|
|
113
|
+
:root { --size-row-compact: var(--size-touch-target); }
|
|
114
|
+
}
|