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 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 = 6;
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.9",
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. One declaration in the type layer, not a fix applied per heading.
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