jig-ui 0.18.0 → 0.18.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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.1
4
+
5
+ Two fixes found by jig-site's fourth round: the probe misread a `<details>`
6
+ menu, and `A-05` missed an arrow that renders as an emoji.
7
+
8
+ ### Fixed
9
+
10
+ - **The probe reads a `<details>` menu the way the browser shows it.** On
11
+ jig-site's Reference it reported a working phone menu as broken, and
12
+ `jig verdicts` refused a correct `P-14` verdict on the strength of it. Four
13
+ defects, each now covered by a browser test:
14
+ - Where the banner's menu is hidden at a width, the probe took the next
15
+ disclosure in some other `<nav>` (a docs rail's section tree) as the menu,
16
+ and reported "a menu button beside links that already show". A menu the
17
+ page names, or one in its banner, is now the only candidate once it exists.
18
+ - A closed `<details>` hides its content with `content-visibility` in
19
+ Chromium, which still gives its links boxes, so the closed menu's links
20
+ counted as showing and opening it "showed no more links". Visibility now
21
+ also asks `checkVisibility()`.
22
+ - A label swapped with CSS ("Menu" to "Close") keeps both words in
23
+ `textContent`; the label is now read from the rendered text.
24
+ - A native `<summary>` has no `aria-expanded` attribute; its expanded state
25
+ is its `<details>`'s `open`, which is what assistive technology is given.
26
+
27
+ Probe files are now version 6, so earlier ones are re-recorded.
28
+
29
+ - **`A-05` catches an arrow that turns into an emoji.** ↔ ↕ ↖ ↗ ↘ ↙ ↩ ↪ ▶ ◀
30
+ are text by default but have an emoji form, and the detector skipped the
31
+ whole arrow block so prose arrows would not fire. jig-site marked its
32
+ external links with `<span aria-hidden="true">↗</span>`, which some platforms
33
+ draw as a colour emoji, and `check --all` and three critiques passed it. One
34
+ of these characters now fires when it stands alone as an icon (the whole
35
+ content of an element, or a pseudo-element's `content`) without U+FE0E. The
36
+ same arrow in a sentence still does not.
37
+
3
38
  ## 0.18.0
4
39
 
5
40
  A page's frame gets tokens, a project gets a place to record where its layout
package/dist/index.js CHANGED
@@ -2265,6 +2265,11 @@ function maskProseComments(raw) {
2265
2265
  // src/check/detectors/emoji-icon.ts
2266
2266
  var EMOJI_RE = /[\u{1F300}-\u{1FAFF}\u{1F000}-\u{1F2FF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE0F}]/gu;
2267
2267
  var TEXTUAL = /* @__PURE__ */ new Set(["\u2192", "\u2190", "\u2191", "\u2193", "\u2194", "\u21D2", "\u21D0", "\u2122", "\u2713", "\u2717", "\u2212", "\u2217", "\u2318", "\u2325", "\u23CE"]);
2268
+ var EMOJI_CAPABLE = "\\u2194-\\u2199\\u21A9\\u21AA\\u25B6\\u25C0";
2269
+ var LONE_GLYPH = new RegExp(
2270
+ `>\\s*([${EMOJI_CAPABLE}])(?!\\uFE0E)\\s*<|content\\s*:\\s*["']([${EMOJI_CAPABLE}])(?!\\uFE0E)["']`,
2271
+ "gu"
2272
+ );
2268
2273
  var emojiIcon = {
2269
2274
  name: "emoji-icon",
2270
2275
  appliesTo: (file) => isStyleBearing(file),
@@ -2289,6 +2294,22 @@ var emojiIcon = {
2289
2294
  )
2290
2295
  );
2291
2296
  }
2297
+ for (const m of masked.matchAll(LONE_GLYPH)) {
2298
+ const glyph = m[1] ?? m[2];
2299
+ const line = masked.slice(0, m.index + m[0].indexOf(glyph)).split("\n").length;
2300
+ if (seen.has(line)) continue;
2301
+ seen.add(line);
2302
+ findings.push(
2303
+ mkFinding(
2304
+ ctx,
2305
+ "emoji-icon",
2306
+ file,
2307
+ line,
2308
+ `A character with an emoji form used as an icon (${glyph}). Standing alone, it renders as a colour emoji on some platforms. Use an icon set, or words.`,
2309
+ lines[line - 1] ?? ""
2310
+ )
2311
+ );
2312
+ }
2292
2313
  return findings;
2293
2314
  }
2294
2315
  };
@@ -5676,10 +5697,13 @@ import { fileURLToPath as fileURLToPath2 } from "url";
5676
5697
  import { join as join33, relative as relative3, resolve as resolve4, sep as sep5 } from "path";
5677
5698
 
5678
5699
  // src/probe/script.ts
5679
- var PROBE_VERSION = 5;
5700
+ var PROBE_VERSION = 6;
5680
5701
  var PROBE_SCRIPT = `(async () => {
5681
5702
  const doc = document.documentElement;
5682
- const vis = (e) => { const r = e.getBoundingClientRect(); const s = getComputedStyle(e); return r.width > 0 && r.height > 0 && s.visibility !== 'hidden' && s.display !== 'none'; };
5703
+ // checkVisibility also sees content a closed <details> hides: Chromium hides
5704
+ // it with content-visibility, which still gives its links real boxes, so a
5705
+ // closed menu counted as open and an opened one showed "no more links".
5706
+ const vis = (e) => { const r = e.getBoundingClientRect(); const s = getComputedStyle(e); return r.width > 0 && r.height > 0 && s.visibility !== 'hidden' && s.display !== 'none' && (!e.checkVisibility || e.checkVisibility()); };
5683
5707
  const probe = document.createElement('div');
5684
5708
  probe.style.all = 'initial';
5685
5709
  document.body.appendChild(probe);
@@ -5772,19 +5796,32 @@ var PROBE_SCRIPT = `(async () => {
5772
5796
  const inChrome = (b) => !!b.closest('header, [role=banner]') ||
5773
5797
  (!!b.closest('nav, [role=navigation]') && !b.closest('main, article, aside, [role=main]'));
5774
5798
  const named = (b) => /\b(menu|navigation)\b/i.test((b.getAttribute('aria-label') || '') + ' ' + b.textContent);
5775
- const toggle = [...document.querySelectorAll('button, summary, [role=button]')].find((b) => vis(b) &&
5776
- (named(b) || (inChrome(b) && (b.tagName === 'SUMMARY' || b.hasAttribute('aria-expanded') || b.hasAttribute('aria-controls')))));
5799
+ const candidates = [...document.querySelectorAll('button, summary, [role=button]')].filter((b) =>
5800
+ named(b) || (inChrome(b) && (b.tagName === 'SUMMARY' || b.hasAttribute('aria-expanded') || b.hasAttribute('aria-controls'))));
5801
+ // A menu the page names, or one in its banner, is the menu. Where it exists
5802
+ // but is hidden at this width, the links show instead and there is no menu
5803
+ // here; the next disclosure in some other <nav> (a docs rail's tree) is not
5804
+ // a stand-in for it.
5805
+ const primary = (b) => named(b) || !!b.closest('header, [role=banner]');
5806
+ const pool = candidates.some(primary) ? candidates.filter(primary) : candidates;
5807
+ const toggle = pool.find(vis);
5777
5808
  let menu = null;
5778
5809
  if (toggle) {
5779
- const name = () => ((toggle.getAttribute('aria-label') || '') + ' ' + (toggle.textContent || '')).replace(/\\s+/g, ' ').trim();
5810
+ // The label as rendered: a menu that swaps "Menu" for "Close" with CSS keeps
5811
+ // both words in its textContent at all times, so only innerText sees the swap.
5812
+ const name = () => ((toggle.getAttribute('aria-label') || '') + ' ' + (toggle.innerText || toggle.textContent || '')).replace(/\\s+/g, ' ').trim();
5813
+ // A native <summary> carries no aria-expanded attribute; the browser exposes
5814
+ // its <details>'s open state as the expanded state, which is what counts.
5815
+ const expanded = () => toggle.getAttribute('aria-expanded') ??
5816
+ (toggle.tagName === 'SUMMARY' && toggle.parentElement && toggle.parentElement.tagName === 'DETAILS' ? String(toggle.parentElement.open) : null);
5780
5817
  // Opening is judged by what becomes visible anywhere: a menu panel is often
5781
5818
  // a sibling of the header, not inside it (arm test 3, tw-1).
5782
5819
  const allLinks = () => [...document.querySelectorAll('a')].filter(vis).length;
5783
5820
  const controlled = () => { const id = toggle.getAttribute('aria-controls'); const el = id && document.getElementById(id); return el ? vis(el) : null; };
5784
- const before = { links: allLinks(), controlled: controlled(), expanded: toggle.getAttribute('aria-expanded'), name: name(), html: toggle.innerHTML };
5821
+ const before = { links: allLinks(), controlled: controlled(), expanded: expanded(), name: name(), html: toggle.innerHTML };
5785
5822
  toggle.click();
5786
5823
  await new Promise((r) => setTimeout(r, 350));
5787
- const after = { links: allLinks(), controlled: controlled(), expanded: toggle.getAttribute('aria-expanded'), name: name(), htmlChanged: toggle.innerHTML !== before.html };
5824
+ const after = { links: allLinks(), controlled: controlled(), expanded: expanded(), name: name(), htmlChanged: toggle.innerHTML !== before.html };
5788
5825
  toggle.focus();
5789
5826
  toggle.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
5790
5827
  document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
@@ -5794,7 +5831,7 @@ var PROBE_SCRIPT = `(async () => {
5794
5831
  expandedBefore: before.expanded, expandedAfter: after.expanded,
5795
5832
  opened: after.links > before.links || (before.controlled === false && after.controlled === true),
5796
5833
  labelChanged: after.name !== before.name || after.htmlChanged,
5797
- escapeCloses: toggle.getAttribute('aria-expanded') !== 'true' && allLinks() <= before.links,
5834
+ escapeCloses: expanded() !== 'true' && allLinks() <= before.links,
5798
5835
  focusReturned: document.activeElement === toggle,
5799
5836
  };
5800
5837
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "A design system for coding agents. 143 numbered UI rules, brand x mode design tokens, and an installer for Claude Code, Codex, Cursor and opencode.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",