@nodegx/mcp 0.2.0 → 0.2.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 (74) hide show
  1. package/dist/backend/cli.js +182 -182
  2. package/dist/cloud-bundle.cjs +747 -745
  3. package/dist/kit-extract.cjs +228 -228
  4. package/dist/kit-extract.inputs.json +113 -113
  5. package/dist/noodl-mcp.cjs +1705 -918
  6. package/dist/package.json +1 -1
  7. package/dist/render-harness/data/node-catalog-enriched.json +242 -9
  8. package/dist/render-harness/drive-steps.js +156 -16
  9. package/dist/render-harness/measure-from-disk.js +112 -2
  10. package/dist/render-harness/render-from-disk.js +136 -136
  11. package/dist/src/external/viewer/noodl.viewer.js +1 -1
  12. package/dist/starter-assets/noodl_modules/inter/Inter-Bold.woff2 +0 -0
  13. package/dist/starter-assets/noodl_modules/inter/Inter-Medium.woff2 +0 -0
  14. package/dist/starter-assets/noodl_modules/inter/Inter-Regular.woff2 +0 -0
  15. package/dist/starter-assets/noodl_modules/inter/Inter-SemiBold.woff2 +0 -0
  16. package/dist/starter-assets/noodl_modules/inter/LICENSE.txt +93 -0
  17. package/dist/starter-assets/noodl_modules/inter/manifest.json +7 -0
  18. package/dist/starter-assets/noodl_modules/inter/styles.css +49 -0
  19. package/dist/starter-assets/noodl_modules/lucide-icons/lucide.woff2 +0 -0
  20. package/dist/starter-assets/noodl_modules/lucide-icons/manifest.json +229 -0
  21. package/dist/starter-assets/noodl_modules/lucide-icons/styles.css +2007 -0
  22. package/dist/starter-assets/noodl_modules/starter-imagery/LICENCES.json +445 -0
  23. package/dist/starter-assets/noodl_modules/starter-imagery/animal-cat.webp +0 -0
  24. package/dist/starter-assets/noodl_modules/starter-imagery/animal-dog.webp +0 -0
  25. package/dist/starter-assets/noodl_modules/starter-imagery/animal-horse.webp +0 -0
  26. package/dist/starter-assets/noodl_modules/starter-imagery/animal-sheep.webp +0 -0
  27. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-1.webp +0 -0
  28. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-2.webp +0 -0
  29. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-3.webp +0 -0
  30. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-4.webp +0 -0
  31. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-5.webp +0 -0
  32. package/dist/starter-assets/noodl_modules/starter-imagery/avatar-6.webp +0 -0
  33. package/dist/starter-assets/noodl_modules/starter-imagery/food-bakery.webp +0 -0
  34. package/dist/starter-assets/noodl_modules/starter-imagery/food-board.webp +0 -0
  35. package/dist/starter-assets/noodl_modules/starter-imagery/food-bread.webp +0 -0
  36. package/dist/starter-assets/noodl_modules/starter-imagery/food-carrots.webp +0 -0
  37. package/dist/starter-assets/noodl_modules/starter-imagery/food-grocer.webp +0 -0
  38. package/dist/starter-assets/noodl_modules/starter-imagery/food-market.webp +0 -0
  39. package/dist/starter-assets/noodl_modules/starter-imagery/food-plate.webp +0 -0
  40. package/dist/starter-assets/noodl_modules/starter-imagery/ground-aurora.svg +28 -0
  41. package/dist/starter-assets/noodl_modules/starter-imagery/ground-canyon.webp +0 -0
  42. package/dist/starter-assets/noodl_modules/starter-imagery/ground-city-dusk.webp +0 -0
  43. package/dist/starter-assets/noodl_modules/starter-imagery/ground-coast.webp +0 -0
  44. package/dist/starter-assets/noodl_modules/starter-imagery/ground-dune.webp +0 -0
  45. package/dist/starter-assets/noodl_modules/starter-imagery/ground-forest-fog.webp +0 -0
  46. package/dist/starter-assets/noodl_modules/starter-imagery/ground-red-rock.webp +0 -0
  47. package/dist/starter-assets/noodl_modules/starter-imagery/ground-ridge.svg +28 -0
  48. package/dist/starter-assets/noodl_modules/starter-imagery/ground-shore.webp +0 -0
  49. package/dist/starter-assets/noodl_modules/starter-imagery/ground-skyline.webp +0 -0
  50. package/dist/starter-assets/noodl_modules/starter-imagery/manifest.json +4 -0
  51. package/dist/starter-assets/noodl_modules/starter-imagery/people-cafe.webp +0 -0
  52. package/dist/starter-assets/noodl_modules/starter-imagery/people-coffee-shop.webp +0 -0
  53. package/dist/starter-assets/noodl_modules/starter-imagery/people-desk.webp +0 -0
  54. package/dist/starter-assets/noodl_modules/starter-imagery/people-market.webp +0 -0
  55. package/dist/starter-assets/noodl_modules/starter-imagery/people-meeting.webp +0 -0
  56. package/dist/starter-assets/noodl_modules/starter-imagery/portrait.svg +11 -0
  57. package/dist/starter-assets/noodl_modules/starter-imagery/texture-brick.webp +0 -0
  58. package/dist/starter-assets/noodl_modules/starter-imagery/texture-coffee.webp +0 -0
  59. package/dist/starter-assets/noodl_modules/starter-imagery/texture-earth.webp +0 -0
  60. package/dist/starter-assets/noodl_modules/starter-imagery/texture-pebbles.webp +0 -0
  61. package/dist/starter-assets/noodl_modules/starter-imagery/texture-snow.webp +0 -0
  62. package/dist/starter-assets/noodl_modules/starter-imagery/texture-soil.webp +0 -0
  63. package/dist/starter-assets/noodl_modules/starter-imagery/tile-1.svg +17 -0
  64. package/dist/starter-assets/noodl_modules/starter-imagery/tile-2.svg +17 -0
  65. package/dist/starter-assets/noodl_modules/starter-imagery/tile-3.svg +17 -0
  66. package/dist/starter-assets/noodl_modules/starter-imagery/work-baker.webp +0 -0
  67. package/dist/starter-assets/noodl_modules/starter-imagery/work-carpenter.webp +0 -0
  68. package/dist/starter-assets/noodl_modules/starter-imagery/work-chef.webp +0 -0
  69. package/dist/starter-assets/noodl_modules/starter-imagery/work-hands-clay.webp +0 -0
  70. package/dist/starter-assets/noodl_modules/starter-imagery/work-leather-bench.webp +0 -0
  71. package/dist/starter-assets/noodl_modules/starter-imagery/work-machine-shop.webp +0 -0
  72. package/dist/starter-assets/noodl_modules/starter-imagery/work-potter.webp +0 -0
  73. package/dist/starter-assets/noodl_modules/starter-imagery/work-welder.webp +0 -0
  74. package/package.json +4 -3
@@ -492,6 +492,78 @@ var require_src = __commonJS({
492
492
  // viewport height with content laid out past it is clipped; a page that is
493
493
  // simply short has nothing past it. That difference is the whole of
494
494
  // \`clipped-page\`, and neither number alone can tell them apart.
495
+ // P112-OWN019-LAPTOPGAPS (R39) \u2014 where the INK is, not where the boxes are. Richard on RL1's laptop page: a
496
+ // feature number in a Columns "1 4" row had a fifth of the page to itself, ~215 px of nothing before its title;
497
+ // L1's pricing toggle and price sat ~260 px left of their centred heading. Every box there is well-formed \u2014 the
498
+ // number's column box abuts the title's \u2014 so the measure is each child's ink: the union of its shown text runs
499
+ // (a Range, so a full-width text block counts only as far as its words reach) and its painted boxes. Desktop only:
500
+ // that is where a row has room to open a hole. Raw numbers here; the judgement is summarise's.
501
+ const layout = { rowGaps: [], drifts: [] };
502
+ // (The API guard is for the fake DOMs the unit suites hand this expression; every browser has both.)
503
+ if (vw >= 1024 && typeof document.createRange === 'function' && typeof document.createTreeWalker === 'function') {
504
+ const shown = new Set(visible);
505
+ const textInk = [];
506
+ const range = document.createRange();
507
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
508
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
509
+ if (!n.textContent.trim() || !shown.has(n.parentElement)) continue;
510
+ range.selectNodeContents(n);
511
+ const r = range.getBoundingClientRect();
512
+ if (r.width > 0 && r.height > 0) textInk.push({ node: n, r });
513
+ }
514
+ const PAINTED_TAGS = ['IMG', 'SVG', 'CANVAS', 'VIDEO', 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'svg'];
515
+ const paintedInk = visible
516
+ .filter((el) => {
517
+ if (PAINTED_TAGS.includes(el.tagName)) return true;
518
+ const cs = styleOf(el);
519
+ const bg = cs.backgroundColor;
520
+ return (bg && bg !== 'transparent' && !/rgba\\([^)]*,\\s*0\\)$/.test(bg)) || cs.backgroundImage !== 'none' || parseFloat(cs.borderTopWidth) > 0;
521
+ })
522
+ .map((el) => ({ el, r: el.getBoundingClientRect() }))
523
+ .filter((p) => p.r.width > 0 && p.r.height > 0 && p.r.width < vw * 0.98);
524
+ const inkOf = (el) => {
525
+ let l = Infinity, t = Infinity, rr = -Infinity, b = -Infinity, text = '';
526
+ const take = (r) => { l = Math.min(l, r.left); t = Math.min(t, r.top); rr = Math.max(rr, r.right); b = Math.max(b, r.bottom); };
527
+ for (const x of textInk) if (el.contains(x.node)) { take(x.r); if (!text) text = x.node.textContent.trim().slice(0, 50); }
528
+ for (const p of paintedInk) if (el === p.el || el.contains(p.el)) take(p.r);
529
+ return l === Infinity ? null : { left: l, top: t, right: rr, bottom: b, width: rr - l, height: b - t, text };
530
+ };
531
+ const ROW_GAP_MIN_PX = 160;
532
+ for (const parent of visible) {
533
+ const kids = [...parent.children].filter((k) => shown.has(k));
534
+ if (kids.length < 2) continue;
535
+ const pr = parent.getBoundingClientRect();
536
+ if (pr.width < 600) continue;
537
+ const inks = kids.map(inkOf).filter(Boolean);
538
+ if (inks.length < 2) continue;
539
+ // Side by side: consecutive by left edge, overlapping vertically, the second starting after the first ends.
540
+ const byLeft = [...inks].sort((a, b) => a.left - b.left);
541
+ for (let i = 1; i < byLeft.length; i++) {
542
+ const a = byLeft[i - 1], b = byLeft[i];
543
+ const overlap = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top);
544
+ const gap = b.left - a.right;
545
+ if (overlap <= 0 || gap < ROW_GAP_MIN_PX) continue;
546
+ layout.rowGaps.push({ cls: cls(parent), gap: round(gap), rowWidth: round(pr.width), tallest: round(Math.max(a.height, b.height)), left: a.text, right: b.text });
547
+ }
548
+ // Stacked, each starting below the one before, and the parts of ONE section: every part under 240 px tall (a
549
+ // page whose children are whole sections compared section against section \u2014 measured on L1 and GL1, s5). The
550
+ // section's axis is set by a centred part among its first two (its heading, usually); L1's pricing is just
551
+ // two parts, a centred heading block and a toggle-and-price group ~260 px left of it.
552
+ const byTop = [...inks].sort((a, b) => a.top - b.top);
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;
555
+ const centre = pr.left + pr.width / 2;
556
+ const offOf = (k) => k.left + k.width / 2 - centre;
557
+ const centred = byTop.filter((k) => Math.abs(offOf(k)) <= 12 && k.width < pr.width * 0.9);
558
+ if (!centred.some((k) => byTop.indexOf(k) < 2)) continue;
559
+ for (const k of byTop) {
560
+ if (Math.abs(offOf(k)) > 40 && k.width < pr.width * 0.8) {
561
+ layout.drifts.push({ cls: cls(parent), offBy: round(offOf(k)), sectionWidth: round(pr.width), text: k.text, centredText: centred[0].text });
562
+ }
563
+ }
564
+ }
565
+ }
566
+
495
567
  const contentBottom = visible.reduce((lowest, el) => {
496
568
  const r = el.getBoundingClientRect();
497
569
  return r.width > 0 && r.height > 0 && r.bottom > lowest ? r.bottom : lowest;
@@ -499,6 +571,7 @@ var require_src = __commonJS({
499
571
 
500
572
  return {
501
573
  lists,
574
+ layout,
502
575
  // FLD-012 AC3 \u2014 the SIZE of the population every rule below is computed over, and the number of
503
576
  // elements the visibility correction took out of it. Reported directly because a rule's finding
504
577
  // count can move for several reasons at once, and 'the fix changed what is measured' is a
@@ -637,9 +710,19 @@ var require_src = __commonJS({
637
710
  */
638
711
  SingleGround: "single-ground",
639
712
  NoImagery: "no-imagery",
640
- NoDisplayType: "no-display-type"
713
+ NoDisplayType: "no-display-type",
714
+ /**
715
+ * P112-OWN019-LAPTOPGAPS (R39, Richard 2026-10-14). On a laptop, two things side by side in one row with a hole
716
+ * between their ink — RL1's feature number given a fifth of the page by a Columns "1 4" row, L1's text and card
717
+ * pushed to the row's two edges. A warning: a hole can be meant, and the screenshots are the arbiter.
718
+ */
719
+ RowGap: "row-gap",
720
+ /** P112-OWN019-LAPTOPGAPS — a section that centres its heading and leaves another part off that centre (L1's price). */
721
+ AlignmentDrift: "alignment-drift"
641
722
  };
642
723
  var SEVERITY_ORDER = { error: 0, warning: 1, info: 2 };
724
+ var ROW_GAP_MIN_TALLEST = 48;
725
+ var ROW_GAP_MIN_SHARE = 0.12;
643
726
  var POVERTY_FINDINGS = [RenderFinding.SingleGround, RenderFinding.NoImagery, RenderFinding.NoDisplayType];
644
727
  function isPovertyFinding(finding) {
645
728
  return Boolean(finding) && POVERTY_FINDINGS.indexOf(finding.code) !== -1;
@@ -855,6 +938,32 @@ var require_src = __commonJS({
855
938
  evidence: v.grounds.values
856
939
  });
857
940
  }
941
+ 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);
943
+ const seen2 = /* @__PURE__ */ new Set();
944
+ for (const g of gaps) {
945
+ const key = `${g.left}|${g.right}`;
946
+ if (seen2.has(key) || seen2.size >= 3) continue;
947
+ seen2.add(key);
948
+ add({
949
+ code: RenderFinding.RowGap,
950
+ severity: "warning",
951
+ viewport: name,
952
+ message: `"${g.left}" and "${g.right}" sit side by side with ${g.gap}px of empty space between them (${Math.round(g.gap / g.rowWidth * 100)}% of the ${g.rowWidth}px row). A label or number beside text reads as one thing at its own width with a small gap \u2014 a row Group, the label at its natural width and the text growing \u2014 not a column given a share of the page (Columns widths are proportions); two parts pushed to a wide row's edges leave the same hole: give the row a max width and a gap. Look at this width's screenshot.`,
953
+ evidence: g
954
+ });
955
+ }
956
+ const drifts = (v.layout.drifts || []).slice(0, 3);
957
+ for (const d of drifts) {
958
+ add({
959
+ code: RenderFinding.AlignmentDrift,
960
+ severity: "warning",
961
+ viewport: name,
962
+ message: `This section centres "${d.centredText}", but "${d.text}" sits ${Math.abs(d.offBy)}px ${d.offBy < 0 ? "left" : "right"} of that centre. One section, one alignment: centre the rest too, or align everything to the same left edge. Look at this width's screenshot.`,
963
+ evidence: d
964
+ });
965
+ }
966
+ }
858
967
  if (v.consoleErrors && v.consoleErrors.length) {
859
968
  add({
860
969
  code: RenderFinding.ConsoleError,
@@ -964,6 +1073,7 @@ var require_render_report = __commonJS({
964
1073
  var WS_MODULE = HARNESS_PATHS.WS_MODULE.path;
965
1074
  var {
966
1075
  DEFAULT_VIEWPORTS,
1076
+ NAMED_VIEWPORTS,
967
1077
  DESKTOP_WIDTH,
968
1078
  RenderFinding,
969
1079
  SEVERITY_ORDER,
@@ -1499,7 +1609,7 @@ var require_render_report = __commonJS({
1499
1609
  }
1500
1610
  function parseViewports2(spec) {
1501
1611
  if (!spec) return DEFAULT_VIEWPORTS;
1502
- const known = new Map(DEFAULT_VIEWPORTS.map((v) => [v.name, v]));
1612
+ const known = new Map(NAMED_VIEWPORTS.map((v) => [v.name, v]));
1503
1613
  return spec.split(",").map((token) => {
1504
1614
  const trimmed = token.trim();
1505
1615
  if (known.has(trimmed)) return known.get(trimmed);