stitch2 0.23.0 → 0.24.1
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/README.md +2 -2
- package/config.schema.json +1 -1
- package/dist/canvas.html +13 -2
- package/dist/check.js +129 -15
- package/dist/config.d.ts +2 -1
- package/dist/typecheck.d.ts +2 -0
- package/package.json +1 -1
- package/skills/stitch2/SKILL.md +6 -1
- package/skills/stitch2/references/screens.md +4 -2
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ file that already exists. From there the agent spends its effort on the design.
|
|
|
59
59
|
icon-only controls without a name, more than five bottom-navigation destinations and controls without visible
|
|
60
60
|
focus. Motion: transitions whose length isn't one of DESIGN.md's motion tokens, and anything that still moves
|
|
61
61
|
with reduced motion on. Each screen is also run in long and right-to-left
|
|
62
|
-
pseudo-languages and your locales (see below).
|
|
62
|
+
pseudo-languages and your locales (see below), and, with `modes`, in each app state it is not drawn in.
|
|
63
63
|
- **States** (from `stitch2 check` on the component catalog): every control at rest, hovered, focused and
|
|
64
64
|
pressed, forced through Chromium so the components need nothing extra, side by side on one sheet; with
|
|
65
65
|
warnings for a control that gives no feedback and text that loses contrast in a state.
|
|
@@ -262,7 +262,7 @@ layout first, then a shorter natural word, never a smaller type size.
|
|
|
262
262
|
| `stitch2 init --name N --primary #hex [options]` | Writes a complete DESIGN.md from a few choices |
|
|
263
263
|
| `stitch2 extract <url or file…>` | Writes a DESIGN.md measured from an existing site or screens |
|
|
264
264
|
| `stitch2 canvas` | The canvas at http://localhost:4400 (`PORT` and `HOST` to change) |
|
|
265
|
-
| `stitch2 check [filter] [--shots] [--strict] [--locales l,…]` | All checks, in every theme and check language; `--shots` saves renders to `<design>/renders/` (`--light.png`, `--pseudo.png`…) |
|
|
265
|
+
| `stitch2 check [filter] [--shots] [--strict] [--locales l,…]` | All checks, in every theme, app state and check language; `--shots` saves renders to `<design>/renders/` (`--light.png`, `--pseudo.png`, `--auth-user.png`…). Reruns visit only the screens that changed; after a change to stitch2, DESIGN.md, the tokens, the components or the catalog every screen is visited again, which takes minutes on a large project. Progress is printed to stderr, and `checks.json` is saved as screens finish and when the run is stopped (Ctrl-C), so the next run continues where it left off |
|
|
266
266
|
| `stitch2 i18n [filter] [--locale l] [--suggest] [--json]` | Message keys the source or a locale lacks, and screen text with no key |
|
|
267
267
|
| `stitch2 consistency [kind…]` | The consistency report, `<design>/consistency/index.html` |
|
|
268
268
|
| `stitch2 type [filter] [--json]` | The type audit |
|
package/config.schema.json
CHANGED
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"modes": {
|
|
81
81
|
"type": "array",
|
|
82
|
-
"description": "App states every screen can be shown in, picked from the canvas sidebar: signed in or out, a plan, a role. The pick is an attribute on each screen's <html>, so the screens' own CSS and scripts answer to it. The dropdown also offers the screens as drawn, which leaves each one as its file has it.",
|
|
82
|
+
"description": "App states every screen can be shown in, picked from the canvas sidebar: signed in or out, a plan, a role. The pick is an attribute on each screen's <html>, so the screens' own CSS and scripts answer to it. The dropdown also offers the screens as drawn, which leaves each one as its file has it. stitch2 check also runs each screen in every value its file does not already have, one mode at a time, and tags what only shows there.",
|
|
83
83
|
"items": {
|
|
84
84
|
"type": "object",
|
|
85
85
|
"additionalProperties": false,
|
package/dist/canvas.html
CHANGED
|
@@ -563,7 +563,15 @@ function tree() {
|
|
|
563
563
|
item('Languages', 'screens × languages', docOpen('/languages'), () => openDoc('/languages'))
|
|
564
564
|
const all = rows().filter((r) => r.key !== '~components' && r.visible.length)
|
|
565
565
|
head('Screens')
|
|
566
|
-
|
|
566
|
+
// A rollup is green only when nothing is pending: like versionsOf, review outranks approved, and archived
|
|
567
|
+
// versions do not count.
|
|
568
|
+
const dotOf = (list) => {
|
|
569
|
+
const live = list.filter((s) => s.status !== 'archived')
|
|
570
|
+
if (live.some((s) => s.status === 'review')) return 'review'
|
|
571
|
+
if (live.some((s) => s.status !== 'approved')) return 'draft'
|
|
572
|
+
if (live.some((s) => s.status === 'approved')) return 'approved'
|
|
573
|
+
return 'draft'
|
|
574
|
+
}
|
|
567
575
|
const screenRow = (row, nested) => {
|
|
568
576
|
const name = document.createElement('div')
|
|
569
577
|
name.className = `screen-name${nested ? ' state' : ''}`
|
|
@@ -920,7 +928,10 @@ function inspector(f) {
|
|
|
920
928
|
li.querySelector('code').textContent = i.selector
|
|
921
929
|
li.onclick = () => {
|
|
922
930
|
const r = rangeOf(s)
|
|
923
|
-
|
|
931
|
+
// An issue found in another app state: show every screen in it, then find the element.
|
|
932
|
+
const [mode, value] = i.mode ? i.mode.split(':') : []
|
|
933
|
+
if (mode && modePick[mode] !== value) { setMode(mode, value); setTimeout(() => locate(f, i.selector), 600) }
|
|
934
|
+
else if (i.width && r && i.width !== s.width) { setWidth(i.width === r[0] ? 'narrow' : i.width === r[1] ? 'wide' : `at:${i.width}`); setTimeout(() => locate(f, i.selector), 900) }
|
|
924
935
|
else locate(f, i.selector)
|
|
925
936
|
}
|
|
926
937
|
ul.append(li)
|
package/dist/check.js
CHANGED
|
@@ -5,24 +5,30 @@
|
|
|
5
5
|
* the canvas shows as badges.
|
|
6
6
|
* Every screen is checked in its own theme, then again in each other theme DESIGN.md defines (designThemes):
|
|
7
7
|
* colours change there, so contrast is checked again, tagged with the theme.
|
|
8
|
+
* Then in each app state (config modes), one mode at a time and in the screen's own theme: a screen is checked with
|
|
9
|
+
* every value its own file does not already have, and what only shows there (a bar that overflows signed in, a control
|
|
10
|
+
* that crowds another) is tagged with the state.
|
|
8
11
|
* Then again in each check language (i18n.ts: the pseudo-languages and the config's): layout problems that
|
|
9
12
|
* show only there (a label running into its neighbour, text cut off, a control drawn backwards right to left),
|
|
10
13
|
* and keys a locale has no translation for, tagged with the locale. Last, each variant at both ends of the widths
|
|
11
14
|
* it covers (config widths: mobile 360–599, desktop 1024–1920 by default) and at every breakpoint inside them
|
|
12
15
|
* (config breakpoints, Tailwind's by default), tagged with the width.
|
|
13
16
|
* Usage: stitch2 check [path filter…] [--shots] [--strict] [--locales ru,ar | all | none]
|
|
14
|
-
* --shots also save page-tall renders to design/renders/ (one per theme, language and width)
|
|
17
|
+
* --shots also save page-tall renders to design/renders/ (one per theme, app state, language and width)
|
|
15
18
|
* --strict exit 1 when any screen has errors (for CI and agent loops)
|
|
16
19
|
* Screens run four at a time; reruns only visit screens that changed since design/checks.json (or
|
|
17
|
-
* everything DESIGN.md, the tokens, the components
|
|
20
|
+
* everything DESIGN.md, the tokens, the components, the catalog or stitch2 itself changed since: a first run after
|
|
21
|
+
* that visits every screen and takes minutes on a large project). Progress goes to stderr, one line per screen
|
|
22
|
+
* ("check: 37/588 …, about 14 min left"). design/checks.json is saved as screens finish and when the run is stopped
|
|
23
|
+
* (Ctrl-C, kill), so the next run continues with the screens that are left instead of starting over.
|
|
18
24
|
*/
|
|
19
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
20
26
|
import { join } from 'node:path';
|
|
21
27
|
import { fileURLToPath } from 'node:url';
|
|
22
28
|
import { chromium } from '@playwright/test';
|
|
23
29
|
import { fingerprintPaths } from './cache.js';
|
|
24
30
|
import { colourIssues } from './colours.js';
|
|
25
|
-
import { CONFIG_FILE, checkWidths, config } from './config.js';
|
|
31
|
+
import { CONFIG_FILE, checkWidths, config, modes } from './config.js';
|
|
26
32
|
import { collectComponents, collectOptions, consistencyIssues, reuseIssues, } from './consistency.js';
|
|
27
33
|
import { designThemes, motionDurations, typeLevels } from './design-md.js';
|
|
28
34
|
import { checkLocales } from './i18n.js';
|
|
@@ -40,6 +46,7 @@ const filters = args.filter((a, i) => !a.startsWith('--') && (localesFlag < 0 ||
|
|
|
40
46
|
const screens = listScreens().filter((s) => !filters.length || filters.some((f) => s.path.includes(f)));
|
|
41
47
|
const levels = typeLevels();
|
|
42
48
|
const themes = designThemes();
|
|
49
|
+
const appModes = modes();
|
|
43
50
|
const collect = collectOptions();
|
|
44
51
|
/** What the layout audit needs: the comfortable tap size and DESIGN.md's motion durations. */
|
|
45
52
|
const auditOpts = { comfortable: config.comfortableTarget, durations: (() => { try {
|
|
@@ -52,7 +59,8 @@ const server = await startServer(0);
|
|
|
52
59
|
/** The tallest render, in CSS px: a long landing page is shot whole (the canvas draws frames up to the same height). */
|
|
53
60
|
const MAX_RENDER_HEIGHT = 20000;
|
|
54
61
|
const fresh = async () => {
|
|
55
|
-
|
|
62
|
+
// Signals are handled below: a stopped run saves what it has before the browsers close.
|
|
63
|
+
const browser = await chromium.launch({ handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false });
|
|
56
64
|
const page = await browser.newPage({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 });
|
|
57
65
|
return { browser, page, visits: 0 };
|
|
58
66
|
};
|
|
@@ -89,6 +97,63 @@ for (const s of screens) {
|
|
|
89
97
|
components[s.path] = previous.components[s.path];
|
|
90
98
|
}
|
|
91
99
|
}
|
|
100
|
+
const exists = new Set(listScreens().map((s) => s.path));
|
|
101
|
+
const keep = (o) => Object.fromEntries(Object.entries(o).filter(([p]) => exists.has(p)));
|
|
102
|
+
const freshPreviousComponents = Object.fromEntries(Object.entries(previous.components ?? {})
|
|
103
|
+
.filter(([path]) => previous.cacheKeys?.[path]?.startsWith(`${currentDeps}:`)
|
|
104
|
+
&& previous.cacheKeys[path].endsWith(`:${fingerprintPaths([join(ROOT, path)])}`)));
|
|
105
|
+
// What this run has finished is saved while it runs, not only at the end: a run that is stopped, killed or crashes
|
|
106
|
+
// keeps its screens (their cache keys are current), and the next run visits only the rest. Written to a temporary
|
|
107
|
+
// file and renamed, so the canvas never reads half a file.
|
|
108
|
+
const writeChecks = (data) => {
|
|
109
|
+
const file = join(ROOT, 'checks.json');
|
|
110
|
+
writeFileSync(`${file}.tmp`, `${JSON.stringify({ checkedAt: new Date().toISOString(), ...data }, null, 2)}\n`);
|
|
111
|
+
renameSync(`${file}.tmp`, file);
|
|
112
|
+
};
|
|
113
|
+
/** Screens finished in this run. Catalogs wait for the end: their States sheet is only written then. */
|
|
114
|
+
const finished = [];
|
|
115
|
+
let lastSaved = Date.now();
|
|
116
|
+
const saveProgress = () => {
|
|
117
|
+
const keys = { ...previous.cacheKeys };
|
|
118
|
+
const rows = { ...previous.screens };
|
|
119
|
+
for (const p of finished) {
|
|
120
|
+
keys[p] = cacheKeys[p];
|
|
121
|
+
rows[p] = results[p];
|
|
122
|
+
}
|
|
123
|
+
writeChecks({ cacheKeys: keep(keys), screens: keep(rows), components: keep({ ...freshPreviousComponents, ...components }) });
|
|
124
|
+
lastSaved = Date.now();
|
|
125
|
+
};
|
|
126
|
+
const note = (line) => process.stderr.write(`check: ${line}\n`);
|
|
127
|
+
const duration = (seconds) => seconds < 90 ? `${Math.round(seconds)} s` : seconds < 5400 ? `${Math.round(seconds / 60)} min` : `${(seconds / 3600).toFixed(1)} h`;
|
|
128
|
+
const sharedChanged = !!previous.cacheKeys && screens.length > 0 && screens.every((s) => !previous.cacheKeys[s.path]?.startsWith(`${currentDeps}:`));
|
|
129
|
+
if (!todo.length)
|
|
130
|
+
note(`all ${screens.length} screens are unchanged since the last run`);
|
|
131
|
+
else if (todo.length < screens.length)
|
|
132
|
+
note(`visiting ${todo.length} of ${screens.length} screens; the others are unchanged since the last run`);
|
|
133
|
+
else
|
|
134
|
+
note(`visiting all ${screens.length} screens` +
|
|
135
|
+
(shots
|
|
136
|
+
? ' (--shots renders every one)'
|
|
137
|
+
: sharedChanged
|
|
138
|
+
? ' (DESIGN.md, the tokens, the components, the catalog or stitch2 itself changed, which rechecks every screen)'
|
|
139
|
+
: ''));
|
|
140
|
+
if (todo.length > 40)
|
|
141
|
+
note('this takes a while: the finished screens are kept, so stopping it (Ctrl-C) loses nothing');
|
|
142
|
+
let stopping = false;
|
|
143
|
+
let finalising = false;
|
|
144
|
+
for (const [signal, code] of [['SIGINT', 130], ['SIGTERM', 143], ['SIGHUP', 129]])
|
|
145
|
+
process.on(signal, () => {
|
|
146
|
+
// Everything is visited: the last write below is quick, and a partial save must not replace it.
|
|
147
|
+
if (finalising)
|
|
148
|
+
process.exit(code);
|
|
149
|
+
if (stopping)
|
|
150
|
+
return;
|
|
151
|
+
stopping = true;
|
|
152
|
+
saveProgress();
|
|
153
|
+
note(`stopped with ${finished.length} of ${todo.length} screens finished; they are kept, run it again to continue`);
|
|
154
|
+
const closed = Promise.all(pool.map((w) => w.browser.close().catch(() => { })));
|
|
155
|
+
void Promise.race([closed, new Promise((r) => setTimeout(r, 3000))]).finally(() => process.exit(code));
|
|
156
|
+
});
|
|
92
157
|
const checkOne = async (s, worker) => {
|
|
93
158
|
if (worker.visits > 0 && worker.visits % 20 === 0) {
|
|
94
159
|
// A check loads each screen several times (themes, languages, widths); a fresh browser keeps long
|
|
@@ -134,6 +199,23 @@ const checkOne = async (s, worker) => {
|
|
|
134
199
|
}
|
|
135
200
|
if (themes.length > 1)
|
|
136
201
|
await setTheme(page, base);
|
|
202
|
+
// The app states: each value the screen is not drawn in, one mode at a time, then the screen's own value again.
|
|
203
|
+
for (const mode of appModes) {
|
|
204
|
+
const drawn = await page.evaluate((attribute) => document.documentElement.getAttribute(attribute), mode.attribute);
|
|
205
|
+
for (const value of mode.values.filter((v) => v.value !== drawn)) {
|
|
206
|
+
await setAttribute(page, mode.attribute, value.value);
|
|
207
|
+
for (const i of await page.evaluate(audit, auditOpts)) {
|
|
208
|
+
const key = `${i.type} ${i.selector}`;
|
|
209
|
+
if (seen.has(key))
|
|
210
|
+
continue;
|
|
211
|
+
seen.add(key);
|
|
212
|
+
themed.push({ ...i, ...(i.type === 'contrast' ? { theme: base } : {}), mode: `${mode.name}:${value.value}`, message: `${value.label}: ${i.message}` });
|
|
213
|
+
}
|
|
214
|
+
if (shots)
|
|
215
|
+
await shoot(page, s, undefined, undefined, `${mode.name}-${value.value}`);
|
|
216
|
+
}
|
|
217
|
+
await setAttribute(page, mode.attribute, drawn);
|
|
218
|
+
}
|
|
137
219
|
const typed = typeIssues(levels, await page.evaluate(collectText));
|
|
138
220
|
if (shots)
|
|
139
221
|
await shoot(page, s);
|
|
@@ -227,16 +309,40 @@ const checkOne = async (s, worker) => {
|
|
|
227
309
|
message: `check crashed on this screen (${String(e).split('\n')[0]}): rerun to retry`,
|
|
228
310
|
},
|
|
229
311
|
];
|
|
230
|
-
|
|
312
|
+
if (!stopping)
|
|
313
|
+
console.error(`check: ${s.path} crashed (${String(e).split('\n')[0]}), recorded and continuing`);
|
|
231
314
|
}
|
|
232
315
|
};
|
|
233
316
|
let cursor = 0;
|
|
317
|
+
const startedAt = Date.now();
|
|
318
|
+
let completed = 0;
|
|
319
|
+
const progress = (path, seconds) => {
|
|
320
|
+
completed++;
|
|
321
|
+
const left = todo.length - completed;
|
|
322
|
+
const eta = completed >= 3 && left > 0 ? `, about ${duration(((Date.now() - startedAt) / 1000 / completed) * left)} left` : '';
|
|
323
|
+
note(`${completed}/${todo.length} ${path} (${duration(seconds)}${eta})`);
|
|
324
|
+
};
|
|
234
325
|
// One screen per worker at a time: workers pull from the queue, so a page is never shared.
|
|
235
326
|
const drive = async (worker) => {
|
|
236
|
-
while (cursor < todo.length)
|
|
237
|
-
|
|
327
|
+
while (cursor < todo.length && !stopping) {
|
|
328
|
+
const s = todo[cursor++];
|
|
329
|
+
const began = Date.now();
|
|
330
|
+
await checkOne(s, worker);
|
|
331
|
+
// A stopped run closes the browsers under the screens still being checked: those are not results.
|
|
332
|
+
if (stopping)
|
|
333
|
+
return;
|
|
334
|
+
if (s.kind !== 'components')
|
|
335
|
+
finished.push(s.path);
|
|
336
|
+
progress(s.path, (Date.now() - began) / 1000);
|
|
337
|
+
if (Date.now() - lastSaved > 15_000)
|
|
338
|
+
saveProgress();
|
|
339
|
+
}
|
|
238
340
|
};
|
|
239
341
|
await Promise.all(pool.map((w) => drive(w)));
|
|
342
|
+
// A stopped run has saved and is closing the browsers; the handler above ends the process.
|
|
343
|
+
if (stopping)
|
|
344
|
+
await new Promise(() => { });
|
|
345
|
+
finalising = true;
|
|
240
346
|
await Promise.all(pool.map(async (w) => {
|
|
241
347
|
await w.page.close().catch(() => { });
|
|
242
348
|
await w.browser.close().catch(() => { });
|
|
@@ -251,7 +357,16 @@ async function setTheme(page, theme) {
|
|
|
251
357
|
}, theme);
|
|
252
358
|
await page.waitForTimeout(150);
|
|
253
359
|
}
|
|
254
|
-
|
|
360
|
+
async function setAttribute(page, attribute, value) {
|
|
361
|
+
await page.evaluate(([name, to]) => {
|
|
362
|
+
if (to === null)
|
|
363
|
+
document.documentElement.removeAttribute(name);
|
|
364
|
+
else
|
|
365
|
+
document.documentElement.setAttribute(name, to);
|
|
366
|
+
}, [attribute, value]);
|
|
367
|
+
await page.waitForTimeout(150);
|
|
368
|
+
}
|
|
369
|
+
/** A page-tall render; renders in another theme, app state or language than the screen's own get a --<name> suffix. */
|
|
255
370
|
async function shoot(page, s, theme, locale, at) {
|
|
256
371
|
const height = await page.evaluate(() => document.documentElement.scrollHeight);
|
|
257
372
|
await page.setViewportSize({ width: s.width, height: Math.max(844, Math.min(height, MAX_RENDER_HEIGHT)) });
|
|
@@ -261,11 +376,6 @@ async function shoot(page, s, theme, locale, at) {
|
|
|
261
376
|
await page.screenshot({ path: join(ROOT, 'renders', name) });
|
|
262
377
|
await page.setViewportSize({ width: s.width, height: 844 });
|
|
263
378
|
}
|
|
264
|
-
const exists = new Set(listScreens().map((s) => s.path));
|
|
265
|
-
const keep = (o) => Object.fromEntries(Object.entries(o).filter(([p]) => exists.has(p)));
|
|
266
|
-
const freshPreviousComponents = Object.fromEntries(Object.entries(previous.components ?? {})
|
|
267
|
-
.filter(([path]) => previous.cacheKeys?.[path]?.startsWith(`${currentDeps}:`)
|
|
268
|
-
&& previous.cacheKeys[path].endsWith(`:${fingerprintPaths([join(ROOT, path)])}`)));
|
|
269
379
|
const allComponents = keep({ ...freshPreviousComponents, ...components });
|
|
270
380
|
// Archived versions are history: they do not vote on how a component should look.
|
|
271
381
|
const archived = new Set(listScreens()
|
|
@@ -282,7 +392,11 @@ for (const s of screens) {
|
|
|
282
392
|
for (const i of issues.filter((i) => i.severity === 'error'))
|
|
283
393
|
console.log(` ${i.type}: ${i.message}${i.text ? ` — “${i.text}”` : ''}`);
|
|
284
394
|
}
|
|
285
|
-
|
|
395
|
+
writeChecks({
|
|
396
|
+
cacheKeys: keep({ ...previous.cacheKeys, ...cacheKeys }),
|
|
397
|
+
screens: keep({ ...previous.screens, ...results }),
|
|
398
|
+
components: allComponents,
|
|
399
|
+
});
|
|
286
400
|
console.log(`\n${screens.length} screens checked, ${errors} errors. Results in design/checks.json (badges on the canvas).`);
|
|
287
401
|
if (strict && errors)
|
|
288
402
|
process.exit(1);
|
package/dist/config.d.ts
CHANGED
|
@@ -60,7 +60,8 @@ export interface LabConfig {
|
|
|
60
60
|
sandboxExclude?: string[];
|
|
61
61
|
/**
|
|
62
62
|
* App states shown across all screens (see ModeConfig). The canvas has a dropdown for each, with the screens as drawn
|
|
63
|
-
* first; picking a value sets its attribute on every screen, live ones and the pictures of the rest.
|
|
63
|
+
* first; picking a value sets its attribute on every screen, live ones and the pictures of the rest. stitch2 check also
|
|
64
|
+
* runs each screen in every value its file does not already have, one mode at a time.
|
|
64
65
|
*/
|
|
65
66
|
modes?: ModeConfig[];
|
|
66
67
|
/** The live app, for stitch2 compare (see compare.ts): its URL, a sign-in script and a route per screen. */
|
package/dist/typecheck.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface Issue {
|
|
|
15
15
|
locale?: string;
|
|
16
16
|
/** Set when the issue shows only at another window width: the edge of its variant's range (config widths). */
|
|
17
17
|
width?: number;
|
|
18
|
+
/** Set when the issue shows only in another app state (config modes): "name:value", as the canvas sidebar names it. */
|
|
19
|
+
mode?: string;
|
|
18
20
|
}
|
|
19
21
|
export interface TextItem {
|
|
20
22
|
selector: string;
|
package/package.json
CHANGED
package/skills/stitch2/SKILL.md
CHANGED
|
@@ -77,6 +77,8 @@ References, read before the first screen:
|
|
|
77
77
|
An issue tagged with a width ("at 1024px: …") breaks at an edge of the windows the variant covers: fix it with a
|
|
78
78
|
responsive rule in the screen, or a new variant when the layout's structure has to change (screens.md, Widths).
|
|
79
79
|
Widths include the breakpoints inside the range; `narrow-layout` is a phone column left in a wide window.
|
|
80
|
+
An issue tagged with an app state ("Signed in: …"; `modes` in the config, screens.md) shows only in that state: fix it in
|
|
81
|
+
the screen's rules for that state, and look at its render (`--auth-user.png`). Clicking it on the canvas shows the state.
|
|
80
82
|
Overlap checks compare the drawn fragments of wrapped inline controls, not the empty corners of their
|
|
81
83
|
bounding boxes. For fixed bars, the browser's paint order decides coverage: a menu painted in front of a
|
|
82
84
|
header is not hidden by it. Native modal dialogs and ARIA modals limit coverage and control comparisons
|
|
@@ -106,6 +108,9 @@ References, read before the first screen:
|
|
|
106
108
|
create or hide a `label-wraps` issue on the trigger. The actual painted control label is still checked.
|
|
107
109
|
Cached checks are invalidated when shared components, the catalog or the checker change. A filtered
|
|
108
110
|
rerun refreshes only those screens; it must not make unrelated screens' older checks appear current.
|
|
111
|
+
The first unfiltered run after such a change visits every screen and can take many minutes on a large project:
|
|
112
|
+
it prints `check: n/total …` with an estimate to stderr, so leave it running and read that, and do not start a
|
|
113
|
+
second `check` beside it. Stopping it (Ctrl-C, kill) keeps the finished screens; the next run visits only the rest.
|
|
109
114
|
Motion warnings: `motion-token` (a transition length DESIGN.md doesn't name) and `reduced-motion` (something
|
|
110
115
|
still moves when the person asked for less); see motion.md. On the catalog, `state-*` warnings come from the
|
|
111
116
|
States sheet (components.md, States).
|
|
@@ -127,7 +132,7 @@ References, read before the first screen:
|
|
|
127
132
|
|---|---|
|
|
128
133
|
| `setup [--name N --primary #hex] [options]` | Sets a project up in one step; keeps every file that exists |
|
|
129
134
|
| `canvas` | The canvas at http://localhost:4400: screens and versions, DESIGN.md, the consistency report, approval and notes |
|
|
130
|
-
| `check [filter] [--shots] [--strict] [--locales l,…]` | Layout, type, reuse and consistency checks, in every theme and check language, and at both ends of each variant's widths; `--shots` saves renders |
|
|
135
|
+
| `check [filter] [--shots] [--strict] [--locales l,…]` | Layout, type, reuse and consistency checks, in every theme, app state and check language, and at both ends of each variant's widths; `--shots` saves renders |
|
|
131
136
|
| `i18n [filter] [--locale l] [--suggest]` | Message keys screens use that the source or a locale lacks, and text with no key (stitch2-i18n) |
|
|
132
137
|
| `consistency [kind…]` | Every shared component cropped from every screen, next to the catalog's version |
|
|
133
138
|
| `type [filter] [--json]` | Every text style a screen uses and the type level it matches |
|
|
@@ -186,8 +186,10 @@ screen per state. Draw it once, let its CSS and scripts answer to an attribute o
|
|
|
186
186
|
The canvas sidebar then has a dropdown for each mode. A value sets the attribute (`data-auth="user"`; `attribute`
|
|
187
187
|
names another) on every screen, live frames and the pictures of the rest, and "As drawn" puts each screen back to what
|
|
188
188
|
its file says. `m` cycles the first mode. A screen is still written for the state it shows (a signed-in account page has
|
|
189
|
-
`data-auth="user"` in its file); modes show the other states of the same screen. `stitch2 check` runs
|
|
190
|
-
|
|
189
|
+
`data-auth="user"` in its file); modes show the other states of the same screen. `stitch2 check` also runs each screen in every
|
|
190
|
+
value its own file does not have, one mode at a time (modes are not combined), and tags what only shows there with the
|
|
191
|
+
state ("Signed in: …"); clicking such an issue on the canvas shows that state. With `--shots` each state is rendered too
|
|
192
|
+
(`<screen>--auth-user.png`): look at it before hand-over.
|
|
191
193
|
|
|
192
194
|
## The canvas
|
|
193
195
|
|