stitch2 0.25.0 → 0.25.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.
Files changed (2) hide show
  1. package/dist/shots.js +46 -28
  2. package/package.json +1 -1
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-v2',
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
- const page = await (await browser).newPage({ viewport: { width: r.width, height: 844 }, deviceScaleFactor: 1 });
98
- try {
99
- const viewport = Math.min(4000, Math.max(200, Number(meta(readFileSync(join(ROOT, r.path), 'utf8'), tag('frame-height'))) || 844));
100
- await page.setViewportSize({ width: r.width, height: viewport });
101
- const query = new URLSearchParams({ canvas: String(viewport) });
102
- if (r.lang && r.lang !== sourceLocale())
103
- query.set('lang', r.lang);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitch2",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "description": "A design canvas, layout and type checks, component consistency and screen versioning for agent-built UI, on Google's DESIGN.md format.",
5
5
  "license": "MIT",
6
6
  "author": "ifokeev",