jig-ui 0.17.0 → 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 +41 -0
- package/dist/index.js +48 -10
- 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/templates/COMMAND.md.tmpl +6 -0
- 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,46 @@
|
|
|
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
|
+
|
|
31
|
+
## 0.17.1
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **The gate judges the critiques a session touched, not every critique.**
|
|
36
|
+
A spec for one page could not finish because another page's critique
|
|
37
|
+
predated 0.17.0, whose new rules that critique had never judged, and a
|
|
38
|
+
record the project had set aside still failed for screenshots it no longer
|
|
39
|
+
had. The gate now checks a critique with a file changed since the session
|
|
40
|
+
began, and the current spec's surface after `critique`. A folder whose name
|
|
41
|
+
starts with `_` is set aside and never judged. With no transcript, as when
|
|
42
|
+
the gate is run by hand, it checks every critique as before.
|
|
43
|
+
|
|
3
44
|
## 0.17.0
|
|
4
45
|
|
|
5
46
|
Thirteen more tells of a generated page, ten of them caught by `check`, and a
|
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;
|
|
@@ -6159,7 +6164,7 @@ async function ensureProbes(opts) {
|
|
|
6159
6164
|
function critiquedSurfaces(projectRoot) {
|
|
6160
6165
|
const root = join32(projectRoot, ".jig", "critique");
|
|
6161
6166
|
if (!existsSync19(root)) return [];
|
|
6162
|
-
return readdirSync12(root).filter((surface) => existsSync19(join32(root, surface, "screen.json")) || existsSync19(join32(root, surface, "code.json")));
|
|
6167
|
+
return readdirSync12(root).filter((surface) => !surface.startsWith("_") && (existsSync19(join32(root, surface, "screen.json")) || existsSync19(join32(root, surface, "code.json"))));
|
|
6163
6168
|
}
|
|
6164
6169
|
|
|
6165
6170
|
// src/probe/check.ts
|
|
@@ -6575,7 +6580,7 @@ function verifyVerdicts(opts) {
|
|
|
6575
6580
|
}
|
|
6576
6581
|
|
|
6577
6582
|
// src/commands/gate.ts
|
|
6578
|
-
import { existsSync as existsSync24, mkdirSync as mkdirSync8, readdirSync as readdirSync15, readFileSync as readFileSync30, writeFileSync as writeFileSync7 } from "fs";
|
|
6583
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync8, readdirSync as readdirSync15, readFileSync as readFileSync30, statSync as statSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
6579
6584
|
import { createHash as createHash2 } from "crypto";
|
|
6580
6585
|
import { join as join37 } from "path";
|
|
6581
6586
|
var MAX_BLOCKS = 3;
|
|
@@ -6624,6 +6629,38 @@ function asksOwner(text) {
|
|
|
6624
6629
|
return /\?(?=[\s)\]"'*_]|$)/.test(prose);
|
|
6625
6630
|
}
|
|
6626
6631
|
var ASKS_THE_OWNER = /* @__PURE__ */ new Set(["decide", "spec", "mockup"]);
|
|
6632
|
+
function sessionStart(transcriptPath) {
|
|
6633
|
+
if (!transcriptPath || !existsSync24(transcriptPath)) return void 0;
|
|
6634
|
+
try {
|
|
6635
|
+
for (const line of readFileSync30(transcriptPath, "utf8").split("\n")) {
|
|
6636
|
+
const m = /"timestamp"\s*:\s*"([^"]+)"/.exec(line);
|
|
6637
|
+
if (m) {
|
|
6638
|
+
const t = Date.parse(m[1]);
|
|
6639
|
+
if (!Number.isNaN(t)) return t;
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6642
|
+
} catch {
|
|
6643
|
+
}
|
|
6644
|
+
return void 0;
|
|
6645
|
+
}
|
|
6646
|
+
function newestMtime(dir) {
|
|
6647
|
+
let newest = 0;
|
|
6648
|
+
for (const name of readdirSync15(dir, { withFileTypes: true })) {
|
|
6649
|
+
if (name.name === LOCK) continue;
|
|
6650
|
+
const path = join37(dir, name.name);
|
|
6651
|
+
newest = Math.max(newest, name.isDirectory() ? newestMtime(path) : statSync4(path).mtimeMs);
|
|
6652
|
+
}
|
|
6653
|
+
return newest;
|
|
6654
|
+
}
|
|
6655
|
+
function surfacesInPlay(root, command, transcriptPath) {
|
|
6656
|
+
const critiqueDir = join37(root, ".jig", "critique");
|
|
6657
|
+
if (!existsSync24(critiqueDir)) return [];
|
|
6658
|
+
const all = readdirSync15(critiqueDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("_") && !d.name.startsWith(".")).map((d) => d.name);
|
|
6659
|
+
const start = sessionStart(transcriptPath);
|
|
6660
|
+
if (start === void 0) return all;
|
|
6661
|
+
const current = command === "critique" ? /^\s*surface\s*:\s*(.+)$/im.exec(newestSpec(root)?.body.split(/^---\s*$/m)[1] ?? "")?.[1]?.trim().replace(/^["']|["']$/g, "") : void 0;
|
|
6662
|
+
return all.filter((s) => s === current || newestMtime(join37(critiqueDir, s)) >= start - 1e3);
|
|
6663
|
+
}
|
|
6627
6664
|
function commandProblems(root, command) {
|
|
6628
6665
|
const problems = [];
|
|
6629
6666
|
const spec = newestSpec(root);
|
|
@@ -6712,10 +6749,10 @@ function gate(opts) {
|
|
|
6712
6749
|
);
|
|
6713
6750
|
}
|
|
6714
6751
|
}
|
|
6715
|
-
problems.push(...verdictGuard(root, command));
|
|
6752
|
+
problems.push(...verdictGuard(root, command, surfacesInPlay(root, command, opts.input.transcript_path)));
|
|
6716
6753
|
const critiqueDir = join37(root, ".jig", "critique");
|
|
6717
6754
|
if (existsSync24(critiqueDir)) {
|
|
6718
|
-
for (const surface of
|
|
6755
|
+
for (const surface of surfacesInPlay(root, command, opts.input.transcript_path)) {
|
|
6719
6756
|
const dir = join37(critiqueDir, surface);
|
|
6720
6757
|
if (!existsSync24(join37(dir, "screen.json")) && !existsSync24(join37(dir, "code.json"))) continue;
|
|
6721
6758
|
const v = verifyVerdicts({ projectRoot: root, surface });
|
|
@@ -6775,16 +6812,17 @@ ${readFileSync30(join37(dir, f), "utf8")}`;
|
|
|
6775
6812
|
});
|
|
6776
6813
|
return parts.some(Boolean) ? checksum(parts.join("\n")) : void 0;
|
|
6777
6814
|
}
|
|
6778
|
-
function verdictGuard(root, command) {
|
|
6815
|
+
function verdictGuard(root, command, inPlay) {
|
|
6779
6816
|
const critiqueDir = join37(root, ".jig", "critique");
|
|
6780
6817
|
if (!existsSync24(critiqueDir)) return [];
|
|
6781
6818
|
const problems = [];
|
|
6782
6819
|
for (const surface of readdirSync15(critiqueDir)) {
|
|
6820
|
+
if (surface.startsWith("_")) continue;
|
|
6783
6821
|
const dir = join37(critiqueDir, surface);
|
|
6784
6822
|
const now = verdictChecksum(dir);
|
|
6785
6823
|
if (!now) continue;
|
|
6786
6824
|
const lockPath = join37(dir, LOCK);
|
|
6787
|
-
if (command === "critique") {
|
|
6825
|
+
if (command === "critique" && (!inPlay || inPlay.includes(surface))) {
|
|
6788
6826
|
try {
|
|
6789
6827
|
writeFileSync7(lockPath, JSON.stringify({ checksum: now }) + "\n", "utf8");
|
|
6790
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
|
|
@@ -1191,6 +1191,12 @@ blocking. It also refuses when a critique's verdict files changed after `critiqu
|
|
|
1191
1191
|
wrote them, in a session that did not run `critique`: a fix is judged by the next
|
|
1192
1192
|
review, never by the builder editing the last one.
|
|
1193
1193
|
|
|
1194
|
+
It judges the critiques this session touched: any with a file changed since the
|
|
1195
|
+
session began, and the current spec's surface when you ran `critique`. Another
|
|
1196
|
+
page's older critique does not hold you back. To set a critique aside for good,
|
|
1197
|
+
rename its folder with a leading underscore (`.jig/critique/_old-catalog`); the
|
|
1198
|
+
gate never judges it.
|
|
1199
|
+
|
|
1194
1200
|
A warning you are sure is right as it stands, because the detector guessed and the
|
|
1195
1201
|
guess is wrong here, is waived on its own line or the line above, in the file's
|
|
1196
1202
|
comment syntax: `jig-allow <ID>: <why>`. The reason is what the user reads, so write
|
|
@@ -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) {
|