jig-ui 0.18.8 → 0.18.10
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 +43 -4
- package/dist/index.js +33 -1
- package/package.json +1 -1
- package/rules/00-anti-patterns.md +2 -2
- package/rules/02-tokens.md +2 -0
- package/tokens/brand.default.css +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.10 (2026-09-26)
|
|
4
|
+
|
|
5
|
+
The rule against a stranded word holds in every browser, and the probe checks
|
|
6
|
+
it where `text-wrap: pretty` is not supported.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **`B-106` holds in every browser, and the probe checks it there.** The rule's
|
|
11
|
+
correction was `text-wrap: balance` and `pretty`, which Firefox and Safari
|
|
12
|
+
before 26 ignore. jig-site's Versions page did exactly that, passed its
|
|
13
|
+
critique in Chromium, and stranded "back." under 0.6.0 on the owner's phone.
|
|
14
|
+
The correction now adds a fallback: join each block's last two words with a
|
|
15
|
+
non-breaking space, in one function in the template layer. The render probe
|
|
16
|
+
(version 7) also lays the page out without `text-wrap: pretty` and records
|
|
17
|
+
every block that ends on one word, and `jig verdicts` refuses a `B-106` "ok"
|
|
18
|
+
that it contradicts.
|
|
19
|
+
|
|
20
|
+
## 0.18.9 (2026-09-25)
|
|
21
|
+
|
|
22
|
+
Each theme names its colour scheme, and no release summary names a rule by its
|
|
23
|
+
id.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **No release summary names a rule by its id.** jig-site's Versions page shows
|
|
28
|
+
each summary, and the site keeps rule ids out of its interface. Three named a
|
|
29
|
+
rule as `A-05`, `I-118` or `H-47`; they now name it in words. A test keeps ids
|
|
30
|
+
out of summaries.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **Each theme names its `color-scheme`.** The default brand file's `:root`
|
|
35
|
+
said `color-scheme: light dark`, which follows the device, and its dark blocks
|
|
36
|
+
set tokens only. jig-site added a theme toggle and a reader who chose the
|
|
37
|
+
other theme got the right page inside the device's scrollbars and form
|
|
38
|
+
controls. Both dark blocks now set `color-scheme: dark`, `[data-theme="light"]`
|
|
39
|
+
sets `light`, and `T-09` says why. A project whose brand file predates this
|
|
40
|
+
adds the same three declarations.
|
|
41
|
+
|
|
3
42
|
## 0.18.8 (2026-09-25)
|
|
4
43
|
|
|
5
44
|
Two runs writing one manifest no longer lose an entry.
|
|
@@ -121,7 +160,7 @@ for jig-site's Versions page.
|
|
|
121
160
|
## 0.18.1 (2026-09-25)
|
|
122
161
|
|
|
123
162
|
Two fixes found by jig-site's fourth round: the probe misread a `<details>`
|
|
124
|
-
menu, and
|
|
163
|
+
menu, and the emoji rule missed an arrow that renders as an emoji.
|
|
125
164
|
|
|
126
165
|
### Fixed
|
|
127
166
|
|
|
@@ -539,7 +578,7 @@ Meaning before presentation, and one fewer step anyone can skip.
|
|
|
539
578
|
|
|
540
579
|
## 0.12.0 (2026-09-18)
|
|
541
580
|
|
|
542
|
-
One new rule: no em dashes in interface text
|
|
581
|
+
One new rule: no em dashes in interface text, with a detector.
|
|
543
582
|
|
|
544
583
|
### Added
|
|
545
584
|
|
|
@@ -666,8 +705,8 @@ runs with Haiku.
|
|
|
666
705
|
|
|
667
706
|
## 0.9.0 (2026-09-13)
|
|
668
707
|
|
|
669
|
-
One new rule and one amended correction, both about the same blind spot:
|
|
670
|
-
correction always pointed an agent at a token.
|
|
708
|
+
One new rule and one amended correction, both about the same blind spot: a
|
|
709
|
+
rule's correction always pointed an agent at a token.
|
|
671
710
|
|
|
672
711
|
### Added
|
|
673
712
|
|
package/dist/index.js
CHANGED
|
@@ -5749,7 +5749,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
5749
5749
|
import { join as join33, relative as relative3, resolve as resolve4, sep as sep5 } from "path";
|
|
5750
5750
|
|
|
5751
5751
|
// src/probe/script.ts
|
|
5752
|
-
var PROBE_VERSION =
|
|
5752
|
+
var PROBE_VERSION = 7;
|
|
5753
5753
|
var PROBE_SCRIPT = `(async () => {
|
|
5754
5754
|
const doc = document.documentElement;
|
|
5755
5755
|
// checkVisibility also sees content a closed <details> hides: Chromium hides
|
|
@@ -5839,6 +5839,32 @@ var PROBE_SCRIPT = `(async () => {
|
|
|
5839
5839
|
const navLinks = () => [...document.querySelectorAll('nav a, header a, [role=navigation] a')].filter(vis).length;
|
|
5840
5840
|
// Measured before anything is clicked: what a reader sees on arrival.
|
|
5841
5841
|
const navAtRest = navLinks();
|
|
5842
|
+
// B-106 in every browser. \`text-wrap: pretty\` moves a lone last word up in
|
|
5843
|
+
// Chromium and does nothing in Firefox or in Safari before 26, so the page is
|
|
5844
|
+
// also measured as those lay it out: wrap forced plain, and the last word of
|
|
5845
|
+
// each block compared with the word before it. jig-site's Versions page was
|
|
5846
|
+
// clean in this browser and stranded "back." on an iPhone.
|
|
5847
|
+
const plainWrap = document.createElement('style');
|
|
5848
|
+
plainWrap.textContent = '*{text-wrap:wrap !important}';
|
|
5849
|
+
document.head.appendChild(plainWrap);
|
|
5850
|
+
const stranded = [];
|
|
5851
|
+
for (const el of document.querySelectorAll('h1,h2,h3,h4,h5,h6,p,li,dd,dt,figcaption,blockquote,td,th,caption,summary')) {
|
|
5852
|
+
if (!vis(el) || el.closest('pre, script, style, [aria-hidden="true"]')) continue;
|
|
5853
|
+
if (el.querySelector('p,li,dd,dt,blockquote,h1,h2,h3,h4,h5,h6')) continue;
|
|
5854
|
+
const words = [];
|
|
5855
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
5856
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
5857
|
+
for (const m of n.data.matchAll(/\\S+/g)) words.push([n, m.index, m.index + m[0].length, m[0]]);
|
|
5858
|
+
}
|
|
5859
|
+
if (words.length < 3) continue;
|
|
5860
|
+
const top = (w) => { const r = document.createRange(); r.setStart(w[0], w[1]); r.setEnd(w[0], w[2]); const rs = r.getClientRects(); return rs.length ? rs[rs.length - 1].top : null; };
|
|
5861
|
+
const last = words[words.length - 1];
|
|
5862
|
+
const a = top(words[words.length - 2]), b = top(last);
|
|
5863
|
+
if (/[\\p{L}\\p{N}]/u.test(last[3]) && a !== null && b !== null && b - a > 2) {
|
|
5864
|
+
stranded.push({ word: last[3], text: (el.innerText || '').trim().replace(/\\s+/g, ' ').slice(0, 40) });
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
plainWrap.remove();
|
|
5842
5868
|
// The menu control: inside the header or navigation, or named as the menu.
|
|
5843
5869
|
// Not any disclosure \u2014 an FAQ <summary> is not a menu.
|
|
5844
5870
|
// The site's navigation, not a page's own: a <nav> inside <main>, an
|
|
@@ -5901,6 +5927,8 @@ var PROBE_SCRIPT = `(async () => {
|
|
|
5901
5927
|
emDashes: [...new Set((ownText.match(/[^.!?\\n]{0,28}\u2014[^.!?\\n]{0,28}/g) || []).map((t) => t.trim()))].slice(0, 5),
|
|
5902
5928
|
brokenImages: [...document.images].filter((i) => i.complete && i.naturalWidth === 0).length,
|
|
5903
5929
|
navLinksVisible: navAtRest,
|
|
5930
|
+
strandedCount: stranded.length,
|
|
5931
|
+
strandedWords: stranded.slice(0, 6),
|
|
5904
5932
|
head: {
|
|
5905
5933
|
title: (document.title || '').trim(),
|
|
5906
5934
|
description: (document.querySelector('meta[name=description]') || {}).content || '',
|
|
@@ -6352,6 +6380,10 @@ function probeContradictions(probes, verdictOf, indexable = true) {
|
|
|
6352
6380
|
if (p.emDashes?.length) {
|
|
6353
6381
|
errors.push(`${at(p)}: the rendered page shows an em dash in ${p.emDashes.map((t) => `"${t}"`).join(", ")} (I-118) \u2014 use a full stop, a comma, a colon, or a second element.`);
|
|
6354
6382
|
}
|
|
6383
|
+
if (clean("B-106") && p.strandedCount) {
|
|
6384
|
+
const shown = (p.strandedWords ?? []).map((w) => `"${w.word}" (in "${w.text}\u2026")`).join(", ");
|
|
6385
|
+
errors.push(`B-106 is "${verdictOf("B-106")}", but ${at(p)} found ${p.strandedCount} block(s) ending on one word where \`text-wrap: pretty\` is not supported (Firefox, Safari before 26): ${shown}. B-106 says what holds in every browser.`);
|
|
6386
|
+
}
|
|
6355
6387
|
const line = p.longestLine;
|
|
6356
6388
|
if (line && line.chars > 90 && clean("B-11")) {
|
|
6357
6389
|
errors.push(`B-11 is "${verdictOf("B-11")}", but ${at(p)} measured a line of about ${line.chars} characters ("${line.text}\u2026") \u2014 past every mode's measure. Cap prose at \`--measure-prose\`.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jig-ui",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.10",
|
|
4
4
|
"description": "A design system for coding agents. 143 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",
|
|
@@ -210,9 +210,9 @@ The measurement is one line in a browser: render `x` in both faces at the same s
|
|
|
210
210
|
|
|
211
211
|
### B-106 A word stranded on its own line
|
|
212
212
|
❌ A heading that wraps to leave one word alone on the last line, or a paragraph ending on a single short word
|
|
213
|
-
✅ `text-wrap: balance` on headings and short blocks, `text-wrap: pretty` on body copy
|
|
213
|
+
✅ `text-wrap: balance` on headings and short blocks, `text-wrap: pretty` on body copy: one declaration in the type layer. Both do nothing in Firefox and in Safari before 26, so where the text is rendered, join each block's last two words with a non-breaking space (U+00A0): one function in the template layer that every heading and block of text passes through, never a space typed into content.
|
|
214
214
|
The eye reads a block's shape before it reads the words. A heading whose last line holds one word reads as a mistake to someone who could not name what is wrong with it — the silhouette says unfinished, and that impression lands before the sentence does.
|
|
215
|
-
This is invisible in the source. The same heading breaks cleanly at 1280px and badly at 900px: where a line breaks depends on the box, the face and the string together, and none of the three is decidable from the others. It is judged on the rendered page at more than one width, which is why it carries `pass: screen
|
|
215
|
+
This is invisible in the source. The same heading breaks cleanly at 1280px and badly at 900px: where a line breaks depends on the box, the face and the string together, and none of the three is decidable from the others. It is judged on the rendered page at more than one width, which is why it carries `pass: screen`, and the render probe also lays the page out without `text-wrap: pretty`, as those browsers do, because a page can be clean in one browser and stranded in the reader's.
|
|
216
216
|
A manual break is not the fix. `<br>` placed by eye is correct at exactly one viewport width and wrong at the next, and it survives into every layout the component is later used in.
|
|
217
217
|
Ragged-right is not the failure — that is correct, and `B-12` requires it. The failure is a *stranded* word, not an uneven edge. Do not chase every short last line; chase the one that is alone.
|
|
218
218
|
|
package/rules/02-tokens.md
CHANGED
|
@@ -442,6 +442,8 @@ These thresholds are APCA's own and do not line up with WCAG's large-text defini
|
|
|
442
442
|
|
|
443
443
|
Not an inversion (`C-21`). Each brand file supplies a dark block under `@media (prefers-color-scheme: dark)` and `[data-theme="dark"]`, remapping semantics only. Mode files are theme-independent — density does not change with colour scheme.
|
|
444
444
|
|
|
445
|
+
Each theme also names its `color-scheme`: `dark` in both dark blocks, and `light` under `[data-theme="light"]`. The browser's own parts, its scrollbars, form controls and the canvas behind the page, follow `color-scheme` and not the tokens, so a site with a theme toggle otherwise draws the chosen theme inside the device's scrollbars.
|
|
446
|
+
|
|
445
447
|
In dark, elevated surfaces get **lighter**, not shadowed. Border-led elevation survives the switch; shadow-led does not, which is one reason border-led is the unbranded default.
|
|
446
448
|
|
|
447
449
|
## T-10 · Consuming
|
package/tokens/brand.default.css
CHANGED
|
@@ -154,6 +154,7 @@
|
|
|
154
154
|
is the answer to it. */
|
|
155
155
|
@media (prefers-color-scheme: dark) {
|
|
156
156
|
:root:not([data-theme="light"]) {
|
|
157
|
+
color-scheme: dark;
|
|
157
158
|
|
|
158
159
|
/* Three solid levels. In dark, DEPTH COMES FROM BACKGROUND, NOT SHADOW —
|
|
159
160
|
shadows are nearly invisible on a dark surface. Never pure black. */
|
|
@@ -186,6 +187,7 @@
|
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
:root[data-theme="dark"] {
|
|
190
|
+
color-scheme: dark;
|
|
189
191
|
|
|
190
192
|
/* Three solid levels. In dark, DEPTH COMES FROM BACKGROUND, NOT SHADOW —
|
|
191
193
|
shadows are nearly invisible on a dark surface. Never pure black. */
|
|
@@ -215,3 +217,11 @@
|
|
|
215
217
|
--shadow-raised: none;
|
|
216
218
|
--shadow-overlay: 0 8px 24px -8px rgb(0 0 0 / 60%);
|
|
217
219
|
}
|
|
220
|
+
|
|
221
|
+
/* The browser's own parts (scrollbars, form controls, the canvas behind the
|
|
222
|
+
page) follow `color-scheme`, not the tokens. `:root` says `light dark`, which
|
|
223
|
+
follows the device, so a reader who chose the other theme got the right page
|
|
224
|
+
inside the wrong scrollbars (jig-site, DRIFT 48). Each theme names its own. */
|
|
225
|
+
:root[data-theme="light"] {
|
|
226
|
+
color-scheme: light;
|
|
227
|
+
}
|