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 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 `A-05` missed an arrow that renders as an emoji.
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 (`I-118`), with a detector.
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: H-47's
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 = 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.8",
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
 
@@ -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
@@ -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
+ }