stitch2 0.25.0 → 0.26.0
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 +1 -1
- package/dist/check.js +9 -1
- package/dist/shots.js +46 -28
- package/package.json +1 -1
- package/skills/stitch2/SKILL.md +2 -2
- package/skills/stitch2-app-sync/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -283,7 +283,7 @@ layout first, then a shorter natural word, never a smaller type size.
|
|
|
283
283
|
| `stitch2 init --name N --primary #hex [options]` | Writes a complete DESIGN.md from a few choices |
|
|
284
284
|
| `stitch2 extract <url or file…>` | Writes a DESIGN.md measured from an existing site or screens |
|
|
285
285
|
| `stitch2 canvas` | The canvas at http://localhost:4400 (`PORT` and `HOST` to change) |
|
|
286
|
-
| `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 |
|
|
286
|
+
| `stitch2 check [filter] [--shots] [--strict] [--archived] [--locales l,…]` | All checks, in every theme, app state and check language, on every version that is not archived (`--archived` adds the outdated ones); `--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 |
|
|
287
287
|
| `stitch2 i18n [filter] [--locale l] [--suggest] [--json]` | Message keys the source or a locale lacks, and screen text with no key |
|
|
288
288
|
| `stitch2 consistency [kind…]` | The consistency report, `<design>/consistency/index.html` |
|
|
289
289
|
| `stitch2 type [filter] [--json]` | The type audit |
|
package/dist/check.js
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* Usage: stitch2 check [path filter…] [--shots] [--strict] [--locales ru,ar | all | none]
|
|
17
17
|
* --shots also save page-tall renders to design/renders/ (one per theme, app state, language and width)
|
|
18
18
|
* --strict exit 1 when any screen has errors (for CI and agent loops)
|
|
19
|
+
* --archived also check archived versions, which are left out otherwise: they are outdated (superseded or rejected),
|
|
20
|
+
* hidden on the canvas, and nothing is built from them, so checking them costs time and reports errors in history
|
|
19
21
|
* Screens run four at a time; reruns only visit screens that changed since design/checks.json (or
|
|
20
22
|
* everything DESIGN.md, the tokens, the components, the catalog or stitch2 itself changed since: a first run after
|
|
21
23
|
* that visits every screen and takes minutes on a large project). Progress goes to stderr, one line per screen
|
|
@@ -43,7 +45,13 @@ const strict = args.includes('--strict');
|
|
|
43
45
|
const localesFlag = args.indexOf('--locales');
|
|
44
46
|
const locales = checkLocales(localesFlag >= 0 ? args[localesFlag + 1] : undefined);
|
|
45
47
|
const filters = args.filter((a, i) => !a.startsWith('--') && (localesFlag < 0 || i !== localesFlag + 1));
|
|
46
|
-
const
|
|
48
|
+
const matching = listScreens().filter((s) => !filters.length || filters.some((f) => s.path.includes(f)));
|
|
49
|
+
const screens = args.includes('--archived') ? matching : matching.filter((s) => s.status !== 'archived');
|
|
50
|
+
const archivedLeftOut = matching.length - screens.length;
|
|
51
|
+
if (archivedLeftOut)
|
|
52
|
+
console.log(`${archivedLeftOut} archived screen${archivedLeftOut === 1 ? ' is' : 's are'} not checked (--archived includes ${archivedLeftOut === 1 ? 'it' : 'them'}).`);
|
|
53
|
+
if (!screens.length && matching.length)
|
|
54
|
+
process.exit(0);
|
|
47
55
|
const levels = typeLevels();
|
|
48
56
|
const themes = designThemes();
|
|
49
57
|
const appModes = modes();
|
package/dist/shots.js
CHANGED
|
@@ -13,6 +13,15 @@ import { messagesFile, sourceLocale } from './i18n.js';
|
|
|
13
13
|
import { ROOT, meta, tag } from './screens.js';
|
|
14
14
|
/** The tallest picture, in CSS px: the same limit as the canvas frames and check's renders. */
|
|
15
15
|
const MAX_HEIGHT = 20000;
|
|
16
|
+
/**
|
|
17
|
+
* Pictures are drawn at two pixels for each CSS pixel and kept at a high JPEG quality. At one pixel, a laptop screen
|
|
18
|
+
* (two device pixels for each CSS pixel) shows a picture stretched to twice its size: thin light text on a dark page
|
|
19
|
+
* comes out soft, grainy and low in contrast next to the live page. Chromium cannot draw a picture much taller than
|
|
20
|
+
* MAX_DEVICE_PX device pixels, so a page too tall for that is drawn at one pixel for each CSS pixel.
|
|
21
|
+
*/
|
|
22
|
+
const SCALE = 2;
|
|
23
|
+
const MAX_DEVICE_PX = 16000;
|
|
24
|
+
const QUALITY = 92;
|
|
16
25
|
const DIR = join(tmpdir(), 'stitch2-shots');
|
|
17
26
|
/** Folders under the design directory that hold outputs, not inputs: they never change a picture. */
|
|
18
27
|
const OUTPUTS = new Set(['renders', 'consistency', 'compare', 'states', 'node_modules', '.git']);
|
|
@@ -53,7 +62,7 @@ function keyOf(r) {
|
|
|
53
62
|
return null;
|
|
54
63
|
const messages = r.lang && r.lang !== sourceLocale() ? messagesFile(r.lang) : undefined;
|
|
55
64
|
const parts = [
|
|
56
|
-
'viewport-
|
|
65
|
+
'viewport-v3',
|
|
57
66
|
r.path,
|
|
58
67
|
statSync(file).mtimeMs,
|
|
59
68
|
sharedStamp(),
|
|
@@ -94,39 +103,48 @@ export function shot(base, r) {
|
|
|
94
103
|
return inFlight;
|
|
95
104
|
const job = slot(async () => {
|
|
96
105
|
browser ??= launch();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await page.goto(`${base}/${r.path}?${query}`, { waitUntil: 'networkidle', timeout: 30_000 }).catch(() => { });
|
|
105
|
-
if (r.theme)
|
|
106
|
-
await page.evaluate((t) => { document.documentElement.dataset.theme = t; }, r.theme);
|
|
107
|
-
const attributes = Object.entries(r.modes ?? {});
|
|
108
|
-
if (attributes.length)
|
|
109
|
-
await page.evaluate((list) => { for (const [name, value] of list)
|
|
110
|
-
document.documentElement.setAttribute(name, value); }, attributes);
|
|
111
|
-
await page.evaluate(() => document.fonts.ready);
|
|
112
|
-
// Tailwind's browser build and the components settle after load.
|
|
113
|
-
await page.waitForTimeout(400);
|
|
114
|
-
const height = await page.evaluate(() => Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0));
|
|
115
|
-
await page.setViewportSize({ width: r.width, height: Math.min(MAX_HEIGHT, Math.max(844, height)) });
|
|
116
|
-
const jpg = await page.screenshot({ type: 'jpeg', quality: 80 });
|
|
117
|
-
mkdirSync(DIR, { recursive: true });
|
|
118
|
-
writeFileSync(file, jpg);
|
|
119
|
-
return jpg;
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
await page.close();
|
|
123
|
-
}
|
|
106
|
+
// A page too tall for the sharp scale is drawn again at one pixel for each CSS pixel.
|
|
107
|
+
const jpg = (await draw(base, r, SCALE)) ?? (await draw(base, r, 1));
|
|
108
|
+
if (!jpg)
|
|
109
|
+
throw new Error('no picture of ' + r.path);
|
|
110
|
+
mkdirSync(DIR, { recursive: true });
|
|
111
|
+
writeFileSync(file, jpg);
|
|
112
|
+
return jpg;
|
|
124
113
|
});
|
|
125
114
|
pending.set(key, job);
|
|
126
115
|
const done = () => pending.delete(key);
|
|
127
116
|
job.then(done, done);
|
|
128
117
|
return job;
|
|
129
118
|
}
|
|
119
|
+
/** One picture of a screen at the given scale; null when the page is too tall to be drawn at that scale. */
|
|
120
|
+
async function draw(base, r, scale) {
|
|
121
|
+
const page = await (await browser).newPage({ viewport: { width: r.width, height: 844 }, deviceScaleFactor: scale });
|
|
122
|
+
try {
|
|
123
|
+
const viewport = Math.min(4000, Math.max(200, Number(meta(readFileSync(join(ROOT, r.path), 'utf8'), tag('frame-height'))) || 844));
|
|
124
|
+
await page.setViewportSize({ width: r.width, height: viewport });
|
|
125
|
+
const query = new URLSearchParams({ canvas: String(viewport) });
|
|
126
|
+
if (r.lang && r.lang !== sourceLocale())
|
|
127
|
+
query.set('lang', r.lang);
|
|
128
|
+
await page.goto(`${base}/${r.path}?${query}`, { waitUntil: 'networkidle', timeout: 30_000 }).catch(() => { });
|
|
129
|
+
if (r.theme)
|
|
130
|
+
await page.evaluate((t) => { document.documentElement.dataset.theme = t; }, r.theme);
|
|
131
|
+
const attributes = Object.entries(r.modes ?? {});
|
|
132
|
+
if (attributes.length)
|
|
133
|
+
await page.evaluate((list) => { for (const [name, value] of list)
|
|
134
|
+
document.documentElement.setAttribute(name, value); }, attributes);
|
|
135
|
+
await page.evaluate(() => document.fonts.ready);
|
|
136
|
+
// Tailwind's browser build and the components settle after load.
|
|
137
|
+
await page.waitForTimeout(400);
|
|
138
|
+
const height = Math.min(MAX_HEIGHT, Math.max(844, await page.evaluate(() => Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0))));
|
|
139
|
+
if (scale > 1 && height * scale > MAX_DEVICE_PX)
|
|
140
|
+
return null;
|
|
141
|
+
await page.setViewportSize({ width: r.width, height });
|
|
142
|
+
return await page.screenshot({ type: 'jpeg', quality: QUALITY });
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await page.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
130
148
|
export async function closeShots() {
|
|
131
149
|
const b = await browser?.catch(() => undefined);
|
|
132
150
|
browser = undefined;
|
package/package.json
CHANGED
package/skills/stitch2/SKILL.md
CHANGED
|
@@ -53,7 +53,7 @@ References, read before the first screen:
|
|
|
53
53
|
**stitch2-i18n**). Write the text as **stitch2-copy** says (the reader's outcome, plain words, the product's terms),
|
|
54
54
|
and translate it with **stitch2-translate**. Never add classes to a component to change how it looks, and never write a colour value (hex,
|
|
55
55
|
`rgb()`, `hsl()`): only DESIGN.md's colours, as token classes or `var(--<prefix>-<name>)`.
|
|
56
|
-
5. **Check** `stitch2 check <path> --shots --strict
|
|
56
|
+
5. **Check** `stitch2 check <path> --shots --strict` (archived versions are outdated and are not visited; `--archived` includes them). Fix every error (overflow, wrapped labels, content under
|
|
57
57
|
fixed bars, fixed bars overlapping each other, tap targets, contrast, cramped text), a hand-built copy of a
|
|
58
58
|
catalog component (`reuse`: use the component or extend it, never rebuild it), and every warning about type
|
|
59
59
|
(`type-*`), components that differ from the catalog (`consistency`) and colour values (`color`).
|
|
@@ -132,7 +132,7 @@ References, read before the first screen:
|
|
|
132
132
|
|---|---|
|
|
133
133
|
| `setup [--name N --primary #hex] [options]` | Sets a project up in one step; keeps every file that exists |
|
|
134
134
|
| `canvas` | The canvas at http://localhost:4400: screens and versions, DESIGN.md, the consistency report, approval and notes |
|
|
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 |
|
|
135
|
+
| `check [filter] [--shots] [--strict] [--archived] [--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; archived versions are left out unless `--archived` asks for them |
|
|
136
136
|
| `i18n [filter] [--locale l] [--suggest]` | Message keys screens use that the source or a locale lacks, and text with no key (stitch2-i18n) |
|
|
137
137
|
| `consistency [kind…]` | Every shared component cropped from every screen, next to the catalog's version |
|
|
138
138
|
| `type [filter] [--json]` | Every text style a screen uses and the type level it matches |
|
|
@@ -82,7 +82,7 @@ Read the findings as:
|
|
|
82
82
|
## Before you call it done
|
|
83
83
|
|
|
84
84
|
1. Every screen you implemented is approved, and `stitch2 screens --strict` passes: one current version each.
|
|
85
|
-
2. `stitch2 check --strict` passes on the screens you touched (archived screens
|
|
85
|
+
2. `stitch2 check --strict` passes on the screens you touched (it leaves archived screens out).
|
|
86
86
|
3. `stitch2 compare` has no errors for the screens you touched; warnings are explained (a kept feature) or fixed.
|
|
87
87
|
4. `stitch2 look` and `stitch2 geometry` report nothing on the screens you touched, except differences you can
|
|
88
88
|
name as data.
|