stitch2 0.15.0 → 0.15.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/README.md +1 -1
- package/dist/audit.js +29 -1
- package/dist/canvas.html +5 -1
- package/dist/check.js +3 -1
- package/package.json +1 -1
- package/skills/stitch2/references/principles.md +2 -0
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ file that already exists. From there the agent spends its effort on the design.
|
|
|
43
43
|
Pick mode (`P`) highlights any element of a screen and copies a reference with its source line, such as
|
|
44
44
|
`home mobile v1 (design/screens/home/mobile-v1.html:36) <gg-button> “Start Push”`; Shift-click collects
|
|
45
45
|
several to copy at once or add to the version's note.
|
|
46
|
-
- **Checks** (`stitch2 check`): content past the frame edge or out of its card, labels that wrap, text running into
|
|
46
|
+
- **Checks** (`stitch2 check`): content past the frame edge or out of its card, navigation whose links scroll or are cut (in any language), labels that wrap, text running into
|
|
47
47
|
other text, labels cut short with an ellipsis, content under fixed bars, fixed bars that overlap each other, tap
|
|
48
48
|
targets (WCAG 2.2), all of it again at both ends of the window widths each layout covers (360 and 599 px for a
|
|
49
49
|
phone, 1024 and 1920 for desktop, or your own ranges), text contrast in every theme, text that is not one of DESIGN.md's type levels, capitals without tracking,
|
package/dist/audit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Layout errors that mean the same bug wherever they appear; compare reports these for the app too. */
|
|
2
|
-
export const LAYOUT_ERRORS = ['overflow', 'fixed-overlap', 'control-overlap', 'text-overlap'];
|
|
2
|
+
export const LAYOUT_ERRORS = ['overflow', 'nav-overflow', 'fixed-overlap', 'control-overlap', 'text-overlap'];
|
|
3
3
|
/** Runs inside the page. Plain JS on purpose: Playwright sends its source to the browser. */
|
|
4
4
|
export async function audit(opts) {
|
|
5
5
|
const W = document.documentElement.clientWidth;
|
|
@@ -31,6 +31,10 @@ export async function audit(opts) {
|
|
|
31
31
|
const cs = getComputedStyle(el);
|
|
32
32
|
if (cs.display === 'none' || cs.visibility === 'hidden')
|
|
33
33
|
return false;
|
|
34
|
+
// A closed <details> shows only its summary (a menu's links until it opens).
|
|
35
|
+
const closed = el.closest('details:not([open])');
|
|
36
|
+
if (closed && !closed.querySelector(':scope > summary')?.contains(el))
|
|
37
|
+
return false;
|
|
34
38
|
const r = el.getBoundingClientRect();
|
|
35
39
|
return r.width > 0 && r.height > 0;
|
|
36
40
|
};
|
|
@@ -74,6 +78,30 @@ export async function audit(opts) {
|
|
|
74
78
|
else
|
|
75
79
|
push('error', 'overflow', el, `Sticks out past the screen edge (${Math.round(r.right - W)}px)`);
|
|
76
80
|
}
|
|
81
|
+
// 1b. Navigation fits: a nav's links never hide in a sideways scroll or run past its edge (in a longer language
|
|
82
|
+
// they go into a menu instead). Only chip rows scroll sideways.
|
|
83
|
+
for (const el of all) {
|
|
84
|
+
if (!el.matches('nav, [role=navigation]') && !(el.closest('header') && el.querySelector(':scope > a')))
|
|
85
|
+
continue;
|
|
86
|
+
const ox = getComputedStyle(el).overflowX;
|
|
87
|
+
if (ox === 'visible')
|
|
88
|
+
continue;
|
|
89
|
+
const box = el.getBoundingClientRect();
|
|
90
|
+
const cut = [...el.querySelectorAll('a')].filter((a) => {
|
|
91
|
+
if (!shown(a))
|
|
92
|
+
return false;
|
|
93
|
+
const r = a.getBoundingClientRect();
|
|
94
|
+
// Wholly outside (wrapped away into a menu's overflow) is a choice; partly outside is a cut link.
|
|
95
|
+
const inside = r.left >= box.left - 1 && r.right <= box.right + 1 && r.top >= box.top - 1 && r.bottom <= box.bottom + 1;
|
|
96
|
+
const outside = r.right <= box.left || r.left >= box.right || r.bottom <= box.top || r.top >= box.bottom;
|
|
97
|
+
return !inside && (!outside || ox === 'auto' || ox === 'scroll');
|
|
98
|
+
});
|
|
99
|
+
if (cut.length)
|
|
100
|
+
push('error', 'nav-overflow', el, `Navigation does not fit: ${cut.length} link${cut.length > 1 ? 's' : ''} cut off or scrolled out of view (${cut
|
|
101
|
+
.map(text)
|
|
102
|
+
.slice(0, 3)
|
|
103
|
+
.join(', ')})`);
|
|
104
|
+
}
|
|
77
105
|
// 2. Short labels (buttons, chips, tabs) stay on one line.
|
|
78
106
|
const isLabel = (el) => el.matches('button, a, label, [role=tab], [role=button], [role=switch], th') ||
|
|
79
107
|
/(^|\s)(chip|badge|pill|tag)|rounded-full/.test(String(el.className ?? ''));
|
package/dist/canvas.html
CHANGED
|
@@ -483,11 +483,15 @@ function zoomTo(f) {
|
|
|
483
483
|
apply()
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
/** The tallest a frame is drawn, in CSS px (the same limit as check's renders). */
|
|
487
|
+
const MAX_FRAME_HEIGHT = 20000
|
|
488
|
+
|
|
486
489
|
function measure(f) {
|
|
487
490
|
try {
|
|
488
491
|
const doc = f.iframe.contentDocument
|
|
489
492
|
const min = bigFrame(f.screen) ? 800 : 844
|
|
490
|
-
|
|
493
|
+
// As tall as the page, up to MAX_FRAME_HEIGHT: long landing pages are drawn whole.
|
|
494
|
+
const h = Math.min(MAX_FRAME_HEIGHT, Math.max(min, doc.documentElement.scrollHeight, doc.body?.scrollHeight ?? 0))
|
|
491
495
|
if (Math.abs(h - f.height) > 2) {
|
|
492
496
|
f.height = h
|
|
493
497
|
f.iframe.style.height = `${h}px`
|
package/dist/check.js
CHANGED
|
@@ -36,6 +36,8 @@ const levels = typeLevels();
|
|
|
36
36
|
const themes = designThemes();
|
|
37
37
|
const collect = collectOptions();
|
|
38
38
|
const server = await startServer(0);
|
|
39
|
+
/** The tallest render, in CSS px: a long landing page is shot whole (the canvas draws frames up to the same height). */
|
|
40
|
+
const MAX_RENDER_HEIGHT = 20000;
|
|
39
41
|
const browser = await chromium.launch();
|
|
40
42
|
const page = await browser.newPage({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 });
|
|
41
43
|
const results = {};
|
|
@@ -150,7 +152,7 @@ async function setTheme(theme) {
|
|
|
150
152
|
/** A page-tall render; renders in another theme or language than the screen's own get a --<theme> suffix. */
|
|
151
153
|
async function shoot(s, theme, locale, at) {
|
|
152
154
|
const height = await page.evaluate(() => document.documentElement.scrollHeight);
|
|
153
|
-
await page.setViewportSize({ width: s.width, height: Math.max(844, Math.min(height,
|
|
155
|
+
await page.setViewportSize({ width: s.width, height: Math.max(844, Math.min(height, MAX_RENDER_HEIGHT)) });
|
|
154
156
|
mkdirSync(join(ROOT, 'renders'), { recursive: true });
|
|
155
157
|
const suffix = theme ?? locale ?? at;
|
|
156
158
|
const name = s.path.replaceAll('/', '__').replace(/\.html$/, suffix ? `--${suffix}.png` : '.png');
|
package/package.json
CHANGED
|
@@ -68,6 +68,8 @@ short labels never wrap **(check)**.
|
|
|
68
68
|
- The primary action sits in the thumb zone (lower half or a docked bar); destructive actions far from it.
|
|
69
69
|
- Fixed bars never cover content: the scroll ends with enough bottom padding **(check)**.
|
|
70
70
|
- Nothing wider than the frame; only chip rows and tab strips scroll sideways **(check)**.
|
|
71
|
+
- Navigation fits in every language: a header shows the links that fit on one row and keeps all of them in a menu;
|
|
72
|
+
a link never scrolls sideways or gets cut **(check: nav-overflow)**.
|
|
71
73
|
|
|
72
74
|
## Review rubric (step 6)
|
|
73
75
|
|