jig-ui 0.17.1 → 0.17.2

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,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.2
4
+
5
+ Four defects found by building one page with the full loop.
6
+
7
+ ### Fixed
8
+
9
+ - **Every mode declares every token name.** `editorial` had no row heights or
10
+ `--font-numeric`, and `product` and `operator` had no ambient durations. A
11
+ project with two modes shares one alias block, so those aliases resolved to
12
+ nothing on the routes of the mode that lacked them; a real site's editorial
13
+ pages had three undefined utilities, and the probe failed them. Each mode now
14
+ states a value for each name (ambient motion is `0s` outside `editorial`),
15
+ and `check-tokens` Rule 14 fails a mode file that declares a name another
16
+ does not.
17
+ - **The gate no longer marks a critique as touched by writing its own lock.**
18
+ 0.17.1 judged only critiques touched this session, but a critique session
19
+ wrote a lock into every critique folder, and the next stop read that write as
20
+ a touch. Locks are now written only for the critiques in play, and the lock
21
+ file is ignored when dating a folder.
22
+ - **The probe does not take an in-page section list for the site menu.** A
23
+ `<summary>` in a `<nav>` inside `<main>`, `<article>` or `<aside>` is page
24
+ navigation; judging it as a menu failed a rule page's "More in this section"
25
+ disclosure for not behaving like one.
26
+ - **Every example fits 320 by 260.** The examples README promised about
27
+ 320 by 180; one figure was 363px wide and four were taller. The bound is now
28
+ stated as measured, the two outliers are trimmed, and a test renders all 286
29
+ figures in Chrome and fails any wider or taller.
30
+
3
31
  ## 0.17.1
4
32
 
5
33
  ### Fixed
package/dist/index.js CHANGED
@@ -5763,7 +5763,12 @@ var PROBE_SCRIPT = `(async () => {
5763
5763
  const navAtRest = navLinks();
5764
5764
  // The menu control: inside the header or navigation, or named as the menu.
5765
5765
  // Not any disclosure \u2014 an FAQ <summary> is not a menu.
5766
- const inChrome = (b) => !!b.closest('header, nav, [role=navigation], [role=banner]');
5766
+ // The site's navigation, not a page's own: a <nav> inside <main>, an
5767
+ // <article> or an <aside> is in-page navigation (a section list, a table of
5768
+ // contents), and its <summary> is a disclosure the reader opens, not the menu.
5769
+ // A rule page's "More in this section" list was judged a broken menu this way.
5770
+ const inChrome = (b) => !!b.closest('header, [role=banner]') ||
5771
+ (!!b.closest('nav, [role=navigation]') && !b.closest('main, article, aside, [role=main]'));
5767
5772
  const named = (b) => /\b(menu|navigation)\b/i.test((b.getAttribute('aria-label') || '') + ' ' + b.textContent);
5768
5773
  const toggle = [...document.querySelectorAll('button, summary, [role=button]')].find((b) => vis(b) &&
5769
5774
  (named(b) || (inChrome(b) && (b.tagName === 'SUMMARY' || b.hasAttribute('aria-expanded') || b.hasAttribute('aria-controls')))));
@@ -5928,7 +5933,7 @@ async function runProbe(opts) {
5928
5933
  fail(new Error(`${chrome} exited with code ${code} before it was ready.`));
5929
5934
  });
5930
5935
  });
5931
- return await evaluate(endpoint, opts.url, opts.width, opts.height ?? 900, timeoutMs);
5936
+ return await evaluate(endpoint, opts.url, opts.width, opts.height ?? 900, timeoutMs, opts.expression ?? PROBE_SCRIPT);
5932
5937
  } finally {
5933
5938
  const exited = child.exitCode !== null ? Promise.resolve() : new Promise((done) => child.once("exit", () => done()));
5934
5939
  child.kill();
@@ -5939,7 +5944,7 @@ async function runProbe(opts) {
5939
5944
  }
5940
5945
  }
5941
5946
  }
5942
- async function evaluate(endpoint, url, width, height, timeoutMs) {
5947
+ async function evaluate(endpoint, url, width, height, timeoutMs, expression) {
5943
5948
  const socket = new WebSocket(endpoint);
5944
5949
  let nextId = 1;
5945
5950
  const pending = /* @__PURE__ */ new Map();
@@ -5983,7 +5988,7 @@ async function evaluate(endpoint, url, width, height, timeoutMs) {
5983
5988
  ]);
5984
5989
  }
5985
5990
  await new Promise((done) => setTimeout(done, 250));
5986
- const result = await send("Runtime.evaluate", { expression: PROBE_SCRIPT, awaitPromise: true, returnByValue: true }, session);
5991
+ const result = await send("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true }, session);
5987
5992
  const value = result.result?.value;
5988
5993
  if (typeof value !== "string") {
5989
5994
  const description = result.exceptionDetails?.text;
@@ -6641,6 +6646,7 @@ function sessionStart(transcriptPath) {
6641
6646
  function newestMtime(dir) {
6642
6647
  let newest = 0;
6643
6648
  for (const name of readdirSync15(dir, { withFileTypes: true })) {
6649
+ if (name.name === LOCK) continue;
6644
6650
  const path = join37(dir, name.name);
6645
6651
  newest = Math.max(newest, name.isDirectory() ? newestMtime(path) : statSync4(path).mtimeMs);
6646
6652
  }
@@ -6743,7 +6749,7 @@ function gate(opts) {
6743
6749
  );
6744
6750
  }
6745
6751
  }
6746
- problems.push(...verdictGuard(root, command));
6752
+ problems.push(...verdictGuard(root, command, surfacesInPlay(root, command, opts.input.transcript_path)));
6747
6753
  const critiqueDir = join37(root, ".jig", "critique");
6748
6754
  if (existsSync24(critiqueDir)) {
6749
6755
  for (const surface of surfacesInPlay(root, command, opts.input.transcript_path)) {
@@ -6806,7 +6812,7 @@ ${readFileSync30(join37(dir, f), "utf8")}`;
6806
6812
  });
6807
6813
  return parts.some(Boolean) ? checksum(parts.join("\n")) : void 0;
6808
6814
  }
6809
- function verdictGuard(root, command) {
6815
+ function verdictGuard(root, command, inPlay) {
6810
6816
  const critiqueDir = join37(root, ".jig", "critique");
6811
6817
  if (!existsSync24(critiqueDir)) return [];
6812
6818
  const problems = [];
@@ -6816,7 +6822,7 @@ function verdictGuard(root, command) {
6816
6822
  const now = verdictChecksum(dir);
6817
6823
  if (!now) continue;
6818
6824
  const lockPath = join37(dir, LOCK);
6819
- if (command === "critique") {
6825
+ if (command === "critique" && (!inPlay || inPlay.includes(surface))) {
6820
6826
  try {
6821
6827
  writeFileSync7(lockPath, JSON.stringify({ checksum: now }) + "\n", "utf8");
6822
6828
  } catch {
@@ -1,6 +1,6 @@
1
1
  <!-- I-82 · Uneven text length across parallel elements -->
2
2
  <figure data-example="dont">
3
- <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-wrap:nowrap;align-items:stretch"><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Search</b><div style="color:#5c5c5c;margin-top:4px;">Words.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Alerts</b><div style="color:#5c5c5c;margin-top:4px;">Saved questions that run every five minutes and message you when the answer changes.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Sharing</b><div style="color:#5c5c5c;margin-top:4px;">A link.</div></div></div></div>
3
+ <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-wrap:nowrap;align-items:stretch"><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Search</b><div style="color:#5c5c5c;margin-top:4px;">Words.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Alerts</b><div style="color:#5c5c5c;margin-top:4px;">Saved questions that run every five minutes and tell you on change.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Sharing</b><div style="color:#5c5c5c;margin-top:4px;">A link.</div></div></div></div>
4
4
  </figure>
5
5
  <figure data-example="do">
6
6
  <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-wrap:nowrap;align-items:stretch"><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Search</b><div style="color:#5c5c5c;margin-top:4px;">Ask in plain English.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Alerts</b><div style="color:#5c5c5c;margin-top:4px;">Hear when an answer changes.</div></div><div style="background:#ffffff;border:1px solid #d6d6d6;border-radius:8px;padding:12px;flex:1;min-width:0"><b>Sharing</b><div style="color:#5c5c5c;margin-top:4px;">Send any result by link.</div></div></div></div>
@@ -3,5 +3,5 @@
3
3
  <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;flex-direction:column;gap:0px;"><div style="font-size:12px;font-weight:600;margin-bottom:4px;">API key</div><div style="background:#ffffff;border:1px solid #a3a3a3;border-radius:6px;padding:7px 9px;font-size:13px;min-height:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">tw_live_8f2c91a7d44e0b6c3e</div></div><div style="font-size:11px;color:#5c5c5c;margin-top:8px">On screen, in a screenshot, in a screen share.</div></div>
4
4
  </figure>
5
5
  <figure data-example="do">
6
- <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;flex-direction:column;gap:0px;"><div style="font-size:12px;font-weight:600;margin-bottom:4px;">API key</div><div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-wrap:nowrap"><div style="background:#ffffff;border:1px solid #a3a3a3;border-radius:6px;padding:7px 9px;font-size:13px;min-height:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex:1">tw_live_••••••••••0b6c3e</div><span style="display:inline-block;align-self:flex-start;background:#ffffff;color:#1f1f1f;border:1px solid #d6d6d6;border-radius:6px;padding:6px 11px;font-weight:600;font-size:12px;">Copy</span><span style="display:inline-block;align-self:flex-start;background:#ffffff;color:#1f1f1f;border:1px solid #d6d6d6;border-radius:6px;padding:6px 11px;font-weight:600;font-size:12px;">Reveal</span></div></div></div>
6
+ <div style="font-family:system-ui,-apple-system,'Segoe UI',sans-serif;font-size:13px;line-height:1.45;color:#1f1f1f;background:#f5f5f4;padding:16px;min-height:176px;box-sizing:border-box;"><div style="display:flex;flex-direction:column;gap:0px;"><div style="font-size:12px;font-weight:600;margin-bottom:4px;">API key</div><div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-wrap:nowrap"><div style="background:#ffffff;border:1px solid #a3a3a3;border-radius:6px;padding:7px 9px;font-size:13px;min-height:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;flex:1;min-width:0">tw_live_••••6c3e</div><span style="display:inline-block;align-self:flex-start;background:#ffffff;color:#1f1f1f;border:1px solid #d6d6d6;border-radius:6px;padding:6px 11px;font-weight:600;font-size:12px;">Copy</span><span style="display:inline-block;align-self:flex-start;background:#ffffff;color:#1f1f1f;border:1px solid #d6d6d6;border-radius:6px;padding:6px 11px;font-weight:600;font-size:12px;">Reveal</span></div></div></div>
7
7
  </figure>
@@ -22,8 +22,11 @@ for them. The example is the picture beside the name.
22
22
  - **Self-contained.** Styles are `style` attributes on the elements. No `<style>`,
23
23
  `<script>`, `<link>`, event handler, or external URL. A consumer renders a figure
24
24
  in a sandboxed frame or an inert box, and it has to look the same in both.
25
- - **Small.** A figure is drawn for a box about 320 by 180 pixels. It exaggerates
26
- just enough to be recognised, and no more.
25
+ - **Small, and bounded.** A figure is drawn 320 pixels wide and is at most 260
26
+ tall; most are 176. Frame each one at 320 by 260, or size the frame to the
27
+ figure's measured height. A figure that shows content running off an edge
28
+ clips it inside itself, so nothing ever spills past 320. It exaggerates just
29
+ enough to be recognised, and no more.
27
30
  - **Raw values are fine here.** An example has to show violet, a glow, a 44px
28
31
  radius. It is a specimen of the failure, not a component, so the token rule
29
32
  that governs a real page does not apply inside it.
@@ -41,4 +44,5 @@ Where even that is impossible, the figure shows the code, set as code.
41
44
 
42
45
  `packages/cli/test/examples.test.ts` fails when a rule has no example, when a file
43
46
  has other than one `dont` and one `do`, or when a figure carries anything that
44
- would not render the same in a sandbox.
47
+ would not render the same in a sandbox. It also renders every figure in a real
48
+ browser and fails any wider than 320 pixels or taller than 260.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.17.1",
3
+ "version": "0.17.2",
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",
@@ -194,24 +194,28 @@ mode states its own value, because density is the thing a mode *is*.
194
194
  | --- | --- | --- | --- |
195
195
  | `--size-control` | 48px | 40px | 32px |
196
196
  | `--size-control-sm` | 40px | 32px | 28px |
197
- | `--size-row` | — | 48px | 36px |
198
- | `--size-row-compact` | — | — | 32px |
197
+ | `--size-row` | 56px | 48px | 36px |
198
+ | `--size-row-compact` | 48px | 40px | 32px |
199
199
  | `--size-icon` | 20px | 18px | 16px |
200
200
  | `--duration-fast` | 150ms | 100ms | 75ms |
201
201
  | `--duration-base` | 250ms | 150ms | 100ms |
202
202
  | `--duration-slow` | 300ms | 200ms | 120ms |
203
- | `--duration-ambient-fast` | 3s | — | — |
204
- | `--duration-ambient-base` | 4.7s | — | — |
205
- | `--duration-ambient-slow` | 7.1s | — | — |
203
+ | `--duration-ambient-fast` | 3s | 0s | 0s |
204
+ | `--duration-ambient-base` | 4.7s | 0s | 0s |
205
+ | `--duration-ambient-slow` | 7.1s | 0s | 0s |
206
206
  | `--measure-prose` | 68ch | 60ch | 72ch |
207
207
  | `--ease-out` | cubic-bezier(0.16, 1, 0.3, 1) | cubic-bezier(0.16, 1, 0.3, 1) | cubic-bezier(0.2, 0, 0, 1) |
208
208
  | `--ease-in-out` | cubic-bezier(0.65, 0, 0.35, 1) | cubic-bezier(0.65, 0, 0.35, 1) | cubic-bezier(0.4, 0, 0.2, 1) |
209
209
 
210
- A `—` means the mode does not define that token: `editorial` has no row
211
- heights because it has no dense record views, and `product` selects a single
212
- row height rather than a compact variant. A pattern that needs one in those
213
- modes is using the wrong mode, or the mode file needs the token added
214
- deliberately.
210
+ **Every mode declares every token name.** A project with two modes shares one
211
+ alias block (`utilities.css`) between them, and an alias for a token one mode
212
+ never declares points at nothing on that mode's routes: on a real site, three
213
+ utilities for row height and tabular figures were undefined on every editorial
214
+ page. So each mode states a value for each name, even where the value is only
215
+ a fallback: `editorial` rows are sized to its controls, though it rarely shows
216
+ dense records, and ambient motion is `0s` outside `editorial` (`P-13`), so a
217
+ shared class resolves and nothing loops. `check-tokens` fails a mode file that
218
+ declares a name another does not.
215
219
 
216
220
  **The ambient periods are a chord, not a ladder.** The three interaction
217
221
  durations are a scale — fast, base, slow, pick by weight of change. The three
@@ -75,6 +75,7 @@
75
75
  --grid-margin: var(--spacing-xxl); --grid-margin-sm: var(--spacing-s); /* 80 → 16 */
76
76
 
77
77
  --size-control: 48px; --size-control-sm: 40px;
78
+ --size-row: 56px; --size-row-compact: 48px;
78
79
  --size-touch-target: 48px; --size-icon: 20px;
79
80
 
80
81
  --border-width-control: var(--border-width-hairline);
@@ -102,6 +103,10 @@
102
103
  --duration-ambient-fast: 3s;
103
104
  --duration-ambient-base: 4.7s;
104
105
  --duration-ambient-slow: 7.1s;
106
+
107
+ /* Every mode declares every token name, so one alias block (utilities.css)
108
+ resolves on every surface. Tabular figures are harmless in prose. */
109
+ --font-numeric: var(--font-numeric-features);
105
110
  }
106
111
 
107
112
  @media (prefers-reduced-motion: reduce) {
@@ -83,6 +83,13 @@
83
83
  have anything moving on it that they did not cause. */
84
84
 
85
85
  --font-numeric: var(--font-numeric-features);
86
+
87
+ /* Every mode declares every token name, so one alias block (utilities.css)
88
+ resolves on every surface. Ambient motion is editorial's (P-13); here it
89
+ is declared still, so a shared class resolves and nothing loops. */
90
+ --duration-ambient-fast: 0s;
91
+ --duration-ambient-base: 0s;
92
+ --duration-ambient-slow: 0s;
86
93
  }
87
94
 
88
95
  @media (prefers-reduced-motion: reduce) {
@@ -71,7 +71,7 @@
71
71
  --grid-gutter: var(--spacing-m); --grid-gutter-sm: var(--spacing-s); /* 24 → 16 */
72
72
  --grid-margin: var(--spacing-l); --grid-margin-sm: var(--spacing-s); /* 32 → 16 */
73
73
 
74
- --size-control: 40px; --size-control-sm: 32px; --size-row: 48px;
74
+ --size-control: 40px; --size-control-sm: 32px; --size-row: 48px; --size-row-compact: 40px;
75
75
  --size-touch-target: 48px; --size-icon: 18px;
76
76
 
77
77
  --border-width-control: var(--border-width-hairline);
@@ -85,6 +85,14 @@
85
85
  /* No ambient duration tokens here, deliberately. Ambient motion is
86
86
  editorial only (P-13) — a surface someone works in all day must not
87
87
  have anything moving on it that they did not cause. */
88
+
89
+ /* Every mode declares every token name, so one alias block (utilities.css)
90
+ resolves on every surface. Ambient motion is editorial's (P-13); here it
91
+ is declared still, so a shared class resolves and nothing loops. */
92
+ --font-numeric: var(--font-numeric-features);
93
+ --duration-ambient-fast: 0s;
94
+ --duration-ambient-base: 0s;
95
+ --duration-ambient-slow: 0s;
88
96
  }
89
97
 
90
98
  @media (prefers-reduced-motion: reduce) {