jig-ui 0.18.9 → 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 +17 -0
- package/dist/index.js +33 -1
- package/package.json +1 -1
- package/rules/00-anti-patterns.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
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
|
+
|
|
3
20
|
## 0.18.9 (2026-09-25)
|
|
4
21
|
|
|
5
22
|
Each theme names its colour scheme, and no release summary names a rule by its
|
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
|
|