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 +28 -0
- package/dist/index.js +13 -7
- package/examples/I-82.html +1 -1
- package/examples/K-132.html +1 -1
- package/examples/README.md +7 -3
- package/package.json +1 -1
- package/rules/02-tokens.md +14 -10
- package/tokens/mode.editorial.css +5 -0
- package/tokens/mode.operator.css +7 -0
- package/tokens/mode.product.css +9 -1
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
|
-
|
|
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
|
|
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 {
|
package/examples/I-82.html
CHANGED
|
@@ -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
|
|
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>
|
package/examples/K-132.html
CHANGED
|
@@ -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_
|
|
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>
|
package/examples/README.md
CHANGED
|
@@ -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
|
|
26
|
-
|
|
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.
|
|
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",
|
package/rules/02-tokens.md
CHANGED
|
@@ -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` |
|
|
198
|
-
| `--size-row-compact` |
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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) {
|
package/tokens/mode.operator.css
CHANGED
|
@@ -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) {
|
package/tokens/mode.product.css
CHANGED
|
@@ -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) {
|