stitch2 0.15.1 → 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 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitch2",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
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",
@@ -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