@nodegx/mcp 0.2.1 → 0.2.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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodegx-mcp-bundles",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": true,
5
5
  "description": "Build metadata for the bundled CLIs beside it. See build-cloud-bundle.mjs: @noodl/platform reads it."
6
6
  }
@@ -551,7 +551,10 @@ var require_src = __commonJS({
551
551
  // two parts, a centred heading block and a toggle-and-price group ~260 px left of it.
552
552
  const byTop = [...inks].sort((a, b) => a.top - b.top);
553
553
  if (!byTop.every((k, i) => i === 0 || k.top >= byTop[i - 1].bottom - 2)) continue;
554
- if (byTop.some((k) => k.height > 240)) continue;
554
+ // Two or more tall parts, not one: RL1's waitlist is eyebrow, heading, line and a 290 px form card in ONE group,
555
+ // and "any part > 240" skipped it \u2014 Richard saw the eyebrow off the centred heading (s6). A page of sections
556
+ // has many tall children; a section with one card has one.
557
+ if (byTop.filter((k) => k.height > 240).length > 1) continue;
555
558
  const centre = pr.left + pr.width / 2;
556
559
  const offOf = (k) => k.left + k.width / 2 - centre;
557
560
  const centred = byTop.filter((k) => Math.abs(offOf(k)) <= 12 && k.width < pr.width * 0.9);
@@ -564,6 +567,47 @@ var require_src = __commonJS({
564
567
  }
565
568
  }
566
569
 
570
+ // P112-OWN019-LETTEREDGE \u2014 words hard against the edge of the box that frames them. Richard on TL2's hero: seven
571
+ // outlined day chips, each letter pressed to its chip's left edge. The chip Group centred its child the way CSS
572
+ // would, but a Text stretches to its Group's width and sets its letters left, so the centring moved nothing. The
573
+ // frame is the nearest of the text's first four ancestors with a border or a fill (a page-wide band is not a frame,
574
+ // nor an inline highlight); the reading is the gap between the words' own ink (a Range) and the frame's inner left
575
+ // and right edges. Every width: a chip is a chip on a phone too. Raw numbers here; the judgement is summarise's.
576
+ layout.edgeTexts = [];
577
+ const EDGE_TEXT_MAX_GAP = 3;
578
+ if (typeof document.createRange === 'function' && typeof document.createTreeWalker === 'function') {
579
+ const shown = new Set(visible);
580
+ const range = document.createRange();
581
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
582
+ const framed = (el) => {
583
+ const cs = styleOf(el);
584
+ if (cs.display === 'inline') return false;
585
+ const bg = cs.backgroundColor;
586
+ return (bg && bg !== 'transparent' && !/rgba\\([^)]*,\\s*0\\)$/.test(bg)) || cs.backgroundImage !== 'none' ||
587
+ parseFloat(cs.borderLeftWidth) > 0 || parseFloat(cs.borderRightWidth) > 0;
588
+ };
589
+ const frames = new Set();
590
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
591
+ const text = n.textContent.trim();
592
+ if (!text || !shown.has(n.parentElement)) continue;
593
+ let frame = null;
594
+ for (let e = n.parentElement, i = 0; e && e !== document.body && i < 4; e = e.parentElement, i++) {
595
+ if (framed(e)) { frame = e; break; }
596
+ }
597
+ if (!frame || frames.has(frame)) continue;
598
+ const fr = frame.getBoundingClientRect();
599
+ if (fr.width < 16 || fr.width >= vw * 0.98) continue;
600
+ range.selectNodeContents(n);
601
+ const r = range.getBoundingClientRect();
602
+ if (!(r.width > 0 && r.height > 0)) continue;
603
+ const fcs = styleOf(frame);
604
+ const left = r.left - (fr.left + (parseFloat(fcs.borderLeftWidth) || 0));
605
+ const right = fr.right - (parseFloat(fcs.borderRightWidth) || 0) - r.right;
606
+ frames.add(frame);
607
+ if (Math.min(left, right) < EDGE_TEXT_MAX_GAP) layout.edgeTexts.push({ cls: cls(frame), text: text.slice(0, 50), left: round(left), right: round(right), frameWidth: round(fr.width), frameHeight: round(fr.height) });
608
+ }
609
+ }
610
+
567
611
  const contentBottom = visible.reduce((lowest, el) => {
568
612
  const r = el.getBoundingClientRect();
569
613
  return r.width > 0 && r.height > 0 && r.bottom > lowest ? r.bottom : lowest;
@@ -718,7 +762,12 @@ var require_src = __commonJS({
718
762
  */
719
763
  RowGap: "row-gap",
720
764
  /** P112-OWN019-LAPTOPGAPS — a section that centres its heading and leaves another part off that centre (L1's price). */
721
- AlignmentDrift: "alignment-drift"
765
+ AlignmentDrift: "alignment-drift",
766
+ /**
767
+ * P112-OWN019-LETTEREDGE (Richard 2026-10-15, TL2's hero). Words within 3 px of the inner left or right edge of the
768
+ * box that frames them (a border or a fill) — a letter in an outlined chip set left because its Text stretched.
769
+ */
770
+ TextAgainstEdge: "text-against-edge"
722
771
  };
723
772
  var SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
724
773
  var ROW_GAP_MIN_TALLEST = 48;
@@ -939,7 +988,7 @@ var require_src = __commonJS({
939
988
  });
940
989
  }
941
990
  if (isDesktop && v.layout) {
942
- const gaps = (v.layout.rowGaps || []).filter((g) => g.tallest >= ROW_GAP_MIN_TALLEST && g.gap >= g.rowWidth * ROW_GAP_MIN_SHARE);
991
+ const gaps = (v.layout.rowGaps || []).filter((g) => (g.left || g.right) && g.tallest >= ROW_GAP_MIN_TALLEST && g.gap >= g.rowWidth * ROW_GAP_MIN_SHARE);
943
992
  const seen2 = /* @__PURE__ */ new Set();
944
993
  for (const g of gaps) {
945
994
  const key = `${g.left}|${g.right}`;
@@ -964,6 +1013,17 @@ var require_src = __commonJS({
964
1013
  });
965
1014
  }
966
1015
  }
1016
+ const edges = v.layout && v.layout.edgeTexts || [];
1017
+ if (edges.length) {
1018
+ const named = edges.slice(0, 3).map((e) => `"${e.text}" ${Math.max(0, Math.min(e.left, e.right))}px from the ${e.left <= e.right ? "left" : "right"} edge of its ${e.frameWidth}\xD7${e.frameHeight}px box`);
1019
+ add({
1020
+ code: RenderFinding.TextAgainstEdge,
1021
+ severity: "warning",
1022
+ viewport: name,
1023
+ message: `${plural(edges.length, "text sits", "texts sit")} hard against the edge of the outlined or filled box that frames ${edges.length === 1 ? "it" : "them"}: ${named.join("; ")}. Centring a Group does not centre a Text's letters \u2014 the Text stretches to the Group's width and sets them left \u2014 so give the Text textAlignX: center, or give the box padding. Look at this width's screenshot.`,
1024
+ evidence: edges.slice(0, 6)
1025
+ });
1026
+ }
967
1027
  if (v.consoleErrors && v.consoleErrors.length) {
968
1028
  add({
969
1029
  code: RenderFinding.ConsoleError,
@@ -2761,7 +2821,11 @@ async function runSteps(page, steps, { outDir, stubs = routeStubs(page), network
2761
2821
  } else if (verb === "watch") {
2762
2822
  const w = step.watch && typeof step.watch === "object" ? step.watch : {};
2763
2823
  if (w.look === void 0) throw new UsageError(`Step ${i + 1}: watch takes { look: T } or { look: [T, T, \u2026] }.`);
2764
- const forMs = Math.min(1e4, Math.max(100, Number(step.for_ms ?? 1500)));
2824
+ const unknown = Object.keys(w).filter((k) => !["look", "for_ms", "expect"].includes(k));
2825
+ if (unknown.length) throw new UsageError(`Step ${i + 1}: watch does not take ${unknown.map((k) => JSON.stringify(k)).join(", ")} \u2014 it takes look, for_ms and expect.`);
2826
+ if (step.expect !== void 0 && w.expect !== void 0) throw new UsageError(`Step ${i + 1}: give watch one expect, beside it or inside it, not both.`);
2827
+ const forMs = Math.min(1e4, Math.max(100, Number(step.for_ms ?? w.for_ms ?? 1500)));
2828
+ const expect = step.expect !== void 0 ? step.expect : w.expect;
2765
2829
  const many = Array.isArray(w.look);
2766
2830
  const targets = many ? w.look : [w.look];
2767
2831
  if (targets.length === 0 || targets.length > 8) throw new UsageError(`Step ${i + 1}: watch looks at 1 to 8 targets.`);
@@ -2782,7 +2846,7 @@ async function runSteps(page, steps, { outDir, stubs = routeStubs(page), network
2782
2846
  }
2783
2847
  const shown = (list) => list.map(({ from, until, look }) => ({ from, until, look }));
2784
2848
  row.value = many ? targets.map((t, k) => ({ target: t, states: shown(states[k]) })) : shown(states[0]);
2785
- const never = step.expect && typeof step.expect === "object" ? step.expect.never : void 0;
2849
+ const never = expect && typeof expect === "object" ? expect.never : void 0;
2786
2850
  if (never && typeof never === "object") {
2787
2851
  for (let k = 0; k < targets.length && row.ok; k++) {
2788
2852
  const hit = states[k].find((st) => st.look && Object.keys(never).every((key) => matches(st.look[key], never[key])));
@@ -551,7 +551,10 @@ var require_src = __commonJS({
551
551
  // two parts, a centred heading block and a toggle-and-price group ~260 px left of it.
552
552
  const byTop = [...inks].sort((a, b) => a.top - b.top);
553
553
  if (!byTop.every((k, i) => i === 0 || k.top >= byTop[i - 1].bottom - 2)) continue;
554
- if (byTop.some((k) => k.height > 240)) continue;
554
+ // Two or more tall parts, not one: RL1's waitlist is eyebrow, heading, line and a 290 px form card in ONE group,
555
+ // and "any part > 240" skipped it \u2014 Richard saw the eyebrow off the centred heading (s6). A page of sections
556
+ // has many tall children; a section with one card has one.
557
+ if (byTop.filter((k) => k.height > 240).length > 1) continue;
555
558
  const centre = pr.left + pr.width / 2;
556
559
  const offOf = (k) => k.left + k.width / 2 - centre;
557
560
  const centred = byTop.filter((k) => Math.abs(offOf(k)) <= 12 && k.width < pr.width * 0.9);
@@ -564,6 +567,47 @@ var require_src = __commonJS({
564
567
  }
565
568
  }
566
569
 
570
+ // P112-OWN019-LETTEREDGE \u2014 words hard against the edge of the box that frames them. Richard on TL2's hero: seven
571
+ // outlined day chips, each letter pressed to its chip's left edge. The chip Group centred its child the way CSS
572
+ // would, but a Text stretches to its Group's width and sets its letters left, so the centring moved nothing. The
573
+ // frame is the nearest of the text's first four ancestors with a border or a fill (a page-wide band is not a frame,
574
+ // nor an inline highlight); the reading is the gap between the words' own ink (a Range) and the frame's inner left
575
+ // and right edges. Every width: a chip is a chip on a phone too. Raw numbers here; the judgement is summarise's.
576
+ layout.edgeTexts = [];
577
+ const EDGE_TEXT_MAX_GAP = 3;
578
+ if (typeof document.createRange === 'function' && typeof document.createTreeWalker === 'function') {
579
+ const shown = new Set(visible);
580
+ const range = document.createRange();
581
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
582
+ const framed = (el) => {
583
+ const cs = styleOf(el);
584
+ if (cs.display === 'inline') return false;
585
+ const bg = cs.backgroundColor;
586
+ return (bg && bg !== 'transparent' && !/rgba\\([^)]*,\\s*0\\)$/.test(bg)) || cs.backgroundImage !== 'none' ||
587
+ parseFloat(cs.borderLeftWidth) > 0 || parseFloat(cs.borderRightWidth) > 0;
588
+ };
589
+ const frames = new Set();
590
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
591
+ const text = n.textContent.trim();
592
+ if (!text || !shown.has(n.parentElement)) continue;
593
+ let frame = null;
594
+ for (let e = n.parentElement, i = 0; e && e !== document.body && i < 4; e = e.parentElement, i++) {
595
+ if (framed(e)) { frame = e; break; }
596
+ }
597
+ if (!frame || frames.has(frame)) continue;
598
+ const fr = frame.getBoundingClientRect();
599
+ if (fr.width < 16 || fr.width >= vw * 0.98) continue;
600
+ range.selectNodeContents(n);
601
+ const r = range.getBoundingClientRect();
602
+ if (!(r.width > 0 && r.height > 0)) continue;
603
+ const fcs = styleOf(frame);
604
+ const left = r.left - (fr.left + (parseFloat(fcs.borderLeftWidth) || 0));
605
+ const right = fr.right - (parseFloat(fcs.borderRightWidth) || 0) - r.right;
606
+ frames.add(frame);
607
+ if (Math.min(left, right) < EDGE_TEXT_MAX_GAP) layout.edgeTexts.push({ cls: cls(frame), text: text.slice(0, 50), left: round(left), right: round(right), frameWidth: round(fr.width), frameHeight: round(fr.height) });
608
+ }
609
+ }
610
+
567
611
  const contentBottom = visible.reduce((lowest, el) => {
568
612
  const r = el.getBoundingClientRect();
569
613
  return r.width > 0 && r.height > 0 && r.bottom > lowest ? r.bottom : lowest;
@@ -718,7 +762,12 @@ var require_src = __commonJS({
718
762
  */
719
763
  RowGap: "row-gap",
720
764
  /** P112-OWN019-LAPTOPGAPS — a section that centres its heading and leaves another part off that centre (L1's price). */
721
- AlignmentDrift: "alignment-drift"
765
+ AlignmentDrift: "alignment-drift",
766
+ /**
767
+ * P112-OWN019-LETTEREDGE (Richard 2026-10-15, TL2's hero). Words within 3 px of the inner left or right edge of the
768
+ * box that frames them (a border or a fill) — a letter in an outlined chip set left because its Text stretched.
769
+ */
770
+ TextAgainstEdge: "text-against-edge"
722
771
  };
723
772
  var SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
724
773
  var ROW_GAP_MIN_TALLEST = 48;
@@ -939,7 +988,7 @@ var require_src = __commonJS({
939
988
  });
940
989
  }
941
990
  if (isDesktop && v.layout) {
942
- const gaps = (v.layout.rowGaps || []).filter((g) => g.tallest >= ROW_GAP_MIN_TALLEST && g.gap >= g.rowWidth * ROW_GAP_MIN_SHARE);
991
+ const gaps = (v.layout.rowGaps || []).filter((g) => (g.left || g.right) && g.tallest >= ROW_GAP_MIN_TALLEST && g.gap >= g.rowWidth * ROW_GAP_MIN_SHARE);
943
992
  const seen2 = /* @__PURE__ */ new Set();
944
993
  for (const g of gaps) {
945
994
  const key = `${g.left}|${g.right}`;
@@ -964,6 +1013,17 @@ var require_src = __commonJS({
964
1013
  });
965
1014
  }
966
1015
  }
1016
+ const edges = v.layout && v.layout.edgeTexts || [];
1017
+ if (edges.length) {
1018
+ const named = edges.slice(0, 3).map((e) => `"${e.text}" ${Math.max(0, Math.min(e.left, e.right))}px from the ${e.left <= e.right ? "left" : "right"} edge of its ${e.frameWidth}\xD7${e.frameHeight}px box`);
1019
+ add({
1020
+ code: RenderFinding.TextAgainstEdge,
1021
+ severity: "warning",
1022
+ viewport: name,
1023
+ message: `${plural(edges.length, "text sits", "texts sit")} hard against the edge of the outlined or filled box that frames ${edges.length === 1 ? "it" : "them"}: ${named.join("; ")}. Centring a Group does not centre a Text's letters \u2014 the Text stretches to the Group's width and sets them left \u2014 so give the Text textAlignX: center, or give the box padding. Look at this width's screenshot.`,
1024
+ evidence: edges.slice(0, 6)
1025
+ });
1026
+ }
967
1027
  if (v.consoleErrors && v.consoleErrors.length) {
968
1028
  add({
969
1029
  code: RenderFinding.ConsoleError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodegx/mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Model Context Protocol server exposing NodeGX (OpenNoodl) v2 projects to external AI agents.",
5
5
  "license": "GPL-3.0-only",
6
6
  "homepage": "https://nodegx.io",