@lotics/cli 0.242.0 → 0.244.0

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.
@@ -459,7 +459,7 @@ var __loticsProbe = (() => {
459
459
  const walk = (node) => {
460
460
  if (node.nodeType === Node.TEXT_NODE) {
461
461
  const parent = node.parentElement;
462
- if (!parent || skipTag.test(parent.tagName)) return;
462
+ if (!parent || skipTag.test(parent.tagName) || parent.closest("[data-content]") !== null) return;
463
463
  const text = node.textContent || "";
464
464
  if (!text.trim()) return;
465
465
  total += 1;
@@ -609,25 +609,25 @@ var __loticsProbe = (() => {
609
609
  const explicit = id === "" ? void 0 : Array.from(el.ownerDocument.querySelectorAll("label[for]")).find((label2) => label2.getAttribute("for") === id);
610
610
  const label = explicit ?? el.closest("label");
611
611
  if (label === null || label === void 0) return "";
612
- const said = [];
612
+ const said2 = [];
613
613
  const walk = (node) => {
614
614
  if (node === el) return;
615
615
  if (node.nodeType === Node.TEXT_NODE) {
616
- said.push(node.textContent || "");
616
+ said2.push(node.textContent || "");
617
617
  return;
618
618
  }
619
619
  node.childNodes.forEach(walk);
620
620
  };
621
621
  walk(label);
622
- return said.join(" ").replace(/\s+/g, " ").trim();
622
+ return said2.join(" ").replace(/\s+/g, " ").trim();
623
623
  }
624
624
  function regionName(region) {
625
- const said = stated(region);
626
- return said !== "" ? said : words(region.querySelector(HEADING));
625
+ const said2 = stated(region);
626
+ return said2 !== "" ? said2 : words(region.querySelector(HEADING));
627
627
  }
628
628
  function nameOf(el) {
629
- const said = stated(el);
630
- if (said !== "") return said;
629
+ const said2 = stated(el);
630
+ if (said2 !== "") return said2;
631
631
  if (el.tagName === "DETAILS") {
632
632
  const summary = words(el.querySelector("summary"));
633
633
  if (summary !== "") return summary;
@@ -689,8 +689,77 @@ var __loticsProbe = (() => {
689
689
  return inner[inner.length - 1] ?? document.body;
690
690
  }
691
691
  function within(root, selector) {
692
- const inside = Array.from(root.querySelectorAll(selector));
693
- return root.matches(selector) ? [root, ...inside] : inside;
692
+ const inside2 = Array.from(root.querySelectorAll(selector));
693
+ return root.matches(selector) ? [root, ...inside2] : inside2;
694
+ }
695
+
696
+ // src/probe_collectors/anatomy.ts
697
+ var BODY = ".lotics-record-page__body";
698
+ var BRIEF = ".lotics-record-page__brief";
699
+ var PRIMARY = '.lotics-button[data-variant="primary"]';
700
+ var FIRST_VIEWPORTS = [
701
+ { width: 1280, height: 800 },
702
+ { width: 375, height: 667 }
703
+ ];
704
+ function firstViewport(width) {
705
+ return FIRST_VIEWPORTS.reduce((near, one) => Math.abs(one.width - width) < Math.abs(near.width - width) ? one : near);
706
+ }
707
+ function surfaces(root) {
708
+ return within(root, BODY).filter(onScreen).flatMap((body) => {
709
+ const head = body.closest(".lotics-drawer__popup, .lotics-record-page");
710
+ if (head === null) return [];
711
+ const title = within(head, "h1, h2").find((one) => !body.contains(one) && onScreen(one));
712
+ return [{ body, root: head, name: words2(title) || nameOf(head) || "(unnamed record)" }];
713
+ });
714
+ }
715
+ function words2(el) {
716
+ if (el === null || el === void 0) return "";
717
+ return (el.textContent || "").replace(/\s+/g, " ").trim();
718
+ }
719
+ var said = (text) => text.toLowerCase().replace(/\s+/g, " ").replace(/:$/, "").trim();
720
+ function headingPrimaries(root = document.body) {
721
+ const drawn = surfaces(root);
722
+ const loud = drawn.flatMap(
723
+ (surface) => within(surface.body, PRIMARY).filter((act) => onScreen(act) && act.closest(".lotics-section-heading") !== null).map((act) => ({ record: surface.name, act: nameOf(act) || words2(act) }))
724
+ );
725
+ return { total: drawn.length, loud };
726
+ }
727
+ function briefsBelowFold(root = document.body, width = window.innerWidth) {
728
+ const fold = firstViewport(width).height;
729
+ const briefs = surfaces(root).flatMap((surface) => {
730
+ const brief = within(surface.body, BRIEF).find(onScreen);
731
+ return brief === void 0 ? [] : [{ record: surface.name, bottom: Math.round(brief.getBoundingClientRect().bottom) }];
732
+ });
733
+ return { total: briefs.length, fold, below: briefs.filter((one) => one.bottom > fold) };
734
+ }
735
+ function briefRepeats(root = document.body) {
736
+ let total = 0;
737
+ const repeated = surfaces(root).flatMap((surface) => {
738
+ const brief = within(surface.body, BRIEF).find(onScreen);
739
+ if (brief === void 0) return [];
740
+ const head = new Set(
741
+ within(surface.root, "*").filter((el) => el.childElementCount === 0 && !surface.body.contains(el) && el.closest('button, [role="button"], [role="tab"]') === null && onScreen(el)).map((el) => said(words2(el))).filter((text) => text.length > 1)
742
+ );
743
+ const values = within(brief, ".lotics-record-facts__value").filter(onScreen);
744
+ total += values.length;
745
+ return values.flatMap((value) => {
746
+ const text = said(words2(value));
747
+ return text !== "" && head.has(text) ? [{ record: surface.name, value: words2(value), where: "head" }] : [];
748
+ });
749
+ });
750
+ return { total, repeated };
751
+ }
752
+ function emptyTabs(root = document.body) {
753
+ let total = 0;
754
+ const empty = surfaces(root).flatMap((surface) => {
755
+ const tabs = within(surface.body, '[role="tab"]').filter(onScreen);
756
+ total += tabs.length;
757
+ const open = tabs.find((tab) => tab.getAttribute("aria-selected") === "true");
758
+ const panel = within(surface.body, '[role="tabpanel"]').find(onScreen);
759
+ const bare = open !== void 0 && panel !== void 0 && within(panel, ".lotics-section").filter(onScreen).length === 0;
760
+ return bare ? [{ record: surface.name, tab: words2(open).replace(/\d+$/, "").trim() }] : [];
761
+ });
762
+ return { total, empty };
694
763
  }
695
764
 
696
765
  // src/probe_collectors/clutter.ts
@@ -707,9 +776,9 @@ var __loticsProbe = (() => {
707
776
  const chromaFloor = 0.15;
708
777
  const sameColour = 24;
709
778
  const sameEdge = 8;
710
- const words3 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
711
- const surfaces = within(root, recordSurface).filter(onScreen);
712
- const called2 = (el) => el.matches(headings) ? words3(el) || "the section" : el.matches(recordSurface) ? words3(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record" : "the screen";
779
+ const words5 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
780
+ const surfaces2 = within(root, recordSurface).filter(onScreen);
781
+ const called2 = (el) => el.matches(headings) ? words5(el) || "the section" : el.matches(recordSurface) ? words5(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record" : "the screen";
713
782
  const rules = [];
714
783
  const primary = '[data-variant="primary"]';
715
784
  const acts = within(root, primary).filter(
@@ -739,7 +808,7 @@ var __loticsProbe = (() => {
739
808
  for (const [owner, run2] of held) {
740
809
  if (run2.length <= 1) continue;
741
810
  surplusActs += run2.length - 1;
742
- if (loud.length < 3) loud.push(`${called2(owner)}: ${run2.length} primary acts \u2014 ${run2.map((act) => `"${words3(act)}"`).join(", ")}`);
811
+ if (loud.length < 3) loud.push(`${called2(owner)}: ${run2.length} primary acts \u2014 ${run2.map((act) => `"${words5(act)}"`).join(", ")}`);
743
812
  }
744
813
  rules.push({
745
814
  law: "hierarchy.md",
@@ -753,27 +822,27 @@ var __loticsProbe = (() => {
753
822
  const coded = /^(?=[^0-9]*[0-9])(?=[^A-Z]*[A-Z])[A-Z0-9][A-Z0-9/_.-]{4,}$/;
754
823
  const trim = /^[\s·•|,:;–—-]+|[\s·•|,:;–—-]+$/g;
755
824
  const beside = (el, value) => {
756
- const said = [];
825
+ const said2 = [];
757
826
  const walk = (node) => {
758
827
  if (node === value) return;
759
- if (node.nodeType === Node.TEXT_NODE) said.push(node.textContent || "");
828
+ if (node.nodeType === Node.TEXT_NODE) said2.push(node.textContent || "");
760
829
  else node.childNodes.forEach(walk);
761
830
  };
762
831
  walk(el);
763
- return said.join(" ").replace(/\s+/g, " ").replace(trim, "");
832
+ return said2.join(" ").replace(/\s+/g, " ").replace(trim, "");
764
833
  };
765
834
  const named = (value, surface) => {
766
835
  for (let up = value; up !== null && up !== surface; up = up.parentElement) {
767
- const said = stated(up);
768
- if (said !== "") return said.replace(trim, "");
769
- const words4 = beside(up, value);
770
- if (words4 !== "") return words4.slice(0, 40);
836
+ const said2 = stated(up);
837
+ if (said2 !== "") return said2.replace(trim, "");
838
+ const words6 = beside(up, value);
839
+ if (words6 !== "") return words6.slice(0, 40);
771
840
  }
772
841
  return "";
773
842
  };
774
843
  const places = /* @__PURE__ */ new Map();
775
844
  const shown = /* @__PURE__ */ new Map();
776
- for (let index = 0; index < surfaces.length; index++) {
845
+ for (let index = 0; index < surfaces2.length; index++) {
777
846
  const walk = (node) => {
778
847
  if (node.nodeType === Node.TEXT_NODE) {
779
848
  const text = (node.textContent || "").trim();
@@ -781,7 +850,7 @@ var __loticsProbe = (() => {
781
850
  if (text === "" || parent === null || skipTag.test(parent.tagName)) return;
782
851
  if (!onScreen(parent)) return;
783
852
  if (!money.test(text) && !dated.test(text) && !coded.test(text)) return;
784
- const label = named(parent, surfaces[index]);
853
+ const label = named(parent, surfaces2[index]);
785
854
  const key = `${index}\0${label.toLowerCase()}\0${text}`;
786
855
  shown.set(key, { label, text });
787
856
  const edges = places.get(key);
@@ -792,7 +861,7 @@ var __loticsProbe = (() => {
792
861
  }
793
862
  node.childNodes.forEach(walk);
794
863
  };
795
- walk(surfaces[index]);
864
+ walk(surfaces2[index]);
796
865
  }
797
866
  let twice = 0;
798
867
  const repeated = [];
@@ -819,7 +888,7 @@ var __loticsProbe = (() => {
819
888
  let facts = 0;
820
889
  let standing = 0;
821
890
  const open = [];
822
- for (const surface of surfaces) {
891
+ for (const surface of surfaces2) {
823
892
  for (const block of within(surface, ".lotics-record-facts")) {
824
893
  const fold = block.querySelector(".lotics-record-facts__more");
825
894
  if (fold === null) continue;
@@ -831,7 +900,7 @@ var __loticsProbe = (() => {
831
900
  facts += 1;
832
901
  if (!absent.test((value.textContent || "").trim())) continue;
833
902
  standing += 1;
834
- if (open.length < 3) open.push(`"${words3(cell.querySelector(".lotics-record-facts__label")) || words3(cell)}" is unstated and drawn`);
903
+ if (open.length < 3) open.push(`"${words5(cell.querySelector(".lotics-record-facts__label")) || words5(cell)}" is unstated and drawn`);
835
904
  }
836
905
  }
837
906
  }
@@ -930,7 +999,7 @@ var __loticsProbe = (() => {
930
999
  overMarked += carried.length;
931
1000
  if (marks.length < 3) {
932
1001
  marks.push(
933
- `${carried.length} of ${run2.length} fields marked \u2014 ` + carried.slice(0, 3).map((field) => `"${words3(field.querySelector(".lotics-form-field__label")) || words3(field)}"`).join(", ")
1002
+ `${carried.length} of ${run2.length} fields marked \u2014 ` + carried.slice(0, 3).map((field) => `"${words5(field.querySelector(".lotics-form-field__label")) || words5(field)}"`).join(", ")
934
1003
  );
935
1004
  }
936
1005
  }
@@ -1027,7 +1096,7 @@ var __loticsProbe = (() => {
1027
1096
  ]);
1028
1097
  var EDGES = ["top", "right", "bottom", "left"];
1029
1098
  var HAIR = 0.5;
1030
- var words2 = (el) => el === null ? "" : (el.textContent || "").replace(/\s+/g, " ").trim();
1099
+ var words3 = (el) => el === null ? "" : (el.textContent || "").replace(/\s+/g, " ").trim();
1031
1100
  var called = (el) => {
1032
1101
  const named = nameOf(el).slice(0, 40);
1033
1102
  return named === "" ? "(unnamed)" : named;
@@ -1076,15 +1145,15 @@ var __loticsProbe = (() => {
1076
1145
  return tag === null ? 2 : Number(tag[1]);
1077
1146
  };
1078
1147
  const headings = within(root, HEADING2).filter(
1079
- (el) => onScreen(el) && el.closest(NAVIGATION) === null && words2(el) !== ""
1148
+ (el) => onScreen(el) && el.closest(NAVIGATION) === null && words3(el) !== ""
1080
1149
  );
1081
1150
  const shallowest = headings.reduce((top, el) => Math.min(top, rank(el)), Infinity);
1082
1151
  const pageTitle = headings.find((el) => rank(el) === shallowest);
1083
1152
  const headerRow = (heading) => {
1084
- const own = words2(heading);
1153
+ const own = words3(heading);
1085
1154
  let row = heading;
1086
1155
  for (let up = heading.parentElement; up !== null && up !== root; up = up.parentElement) {
1087
- if (words2(up) !== own) break;
1156
+ if (words3(up) !== own) break;
1088
1157
  row = up;
1089
1158
  }
1090
1159
  return row;
@@ -1100,7 +1169,7 @@ var __loticsProbe = (() => {
1100
1169
  if (mark === null) continue;
1101
1170
  badged += 1;
1102
1171
  if (badges.length < 3) {
1103
- badges.push(`"${words2(heading).slice(0, 40)}": the heading row draws <${mark.tagName.toLowerCase()}>`);
1172
+ badges.push(`"${words3(heading).slice(0, 40)}": the heading row draws <${mark.tagName.toLowerCase()}>`);
1104
1173
  }
1105
1174
  }
1106
1175
  rules.push({
@@ -1112,7 +1181,7 @@ var __loticsProbe = (() => {
1112
1181
  });
1113
1182
  const editors = editorsIn(root);
1114
1183
  const fieldsIn = (box) => within(box, FIELD).filter(onScreen);
1115
- const labelsIn = (box) => within(box, "label").filter((el) => onScreen(el) && words2(el) !== "");
1184
+ const labelsIn = (box) => within(box, "label").filter((el) => onScreen(el) && words3(el) !== "");
1116
1185
  const oneControl = (el) => el.matches(GROUP) || (el.getAttribute("aria-label") || "").trim() !== "" || (el.getAttribute("aria-labelledby") || "").trim() !== "";
1117
1186
  const around = (outer, inner) => outer.left <= inner.left + HAIR && outer.top <= inner.top + HAIR && outer.right + HAIR >= inner.right && outer.bottom + HAIR >= inner.bottom;
1118
1187
  let open = 0;
@@ -1157,8 +1226,8 @@ var __loticsProbe = (() => {
1157
1226
  const first = drawn(group)[0];
1158
1227
  if (first === void 0) return null;
1159
1228
  if (first.matches(HEADING2)) return first;
1160
- const inside = first.querySelector(HEADING2);
1161
- return inside !== null && onScreen(inside) && words2(inside) !== "" ? inside : null;
1229
+ const inside2 = first.querySelector(HEADING2);
1230
+ return inside2 !== null && onScreen(inside2) && words3(inside2) !== "" ? inside2 : null;
1162
1231
  };
1163
1232
  const rowGapOf = (group) => {
1164
1233
  const rows = drawn(group).slice(1);
@@ -1191,7 +1260,7 @@ var __loticsProbe = (() => {
1191
1260
  level += 1;
1192
1261
  if (merged.length < 3) {
1193
1262
  merged.push(
1194
- `"${words2(caption).slice(0, 40)}": ${Math.round(above)}px above the caption, ${Math.round(gap)}px between the rows it follows`
1263
+ `"${words3(caption).slice(0, 40)}": ${Math.round(above)}px above the caption, ${Math.round(gap)}px between the rows it follows`
1195
1264
  );
1196
1265
  }
1197
1266
  }
@@ -1276,6 +1345,232 @@ var __loticsProbe = (() => {
1276
1345
  ];
1277
1346
  }
1278
1347
 
1348
+ // src/probe_collectors/layout.ts
1349
+ var RESERVED_MIN_WIDTH = 40;
1350
+ var RESERVED_BLANK_SHARE = 0.5;
1351
+ var CONTROL2 = 'button, [role="button"], a[href], [role="link"], input, select, textarea, [role="checkbox"], [role="switch"]';
1352
+ function words4(el) {
1353
+ if (el === null || el === void 0) return "";
1354
+ const runs = [];
1355
+ const walk = (node) => {
1356
+ if (node.nodeType === Node.TEXT_NODE) runs.push(node.textContent || "");
1357
+ else node.childNodes.forEach(walk);
1358
+ };
1359
+ walk(el);
1360
+ return runs.join(" ").replace(/\s+/g, " ").trim().slice(0, 40);
1361
+ }
1362
+ var inside = (el, root, selector) => el.closest(selector) === root;
1363
+ var stored = (row) => !row.hasAttribute("data-ghost");
1364
+ function kitRegisters(root) {
1365
+ const table = ".lotics-table";
1366
+ const row = ".lotics-table__row";
1367
+ return within(root, table).filter((register) => onScreen(register) && !register.hasAttribute("data-stacked")).map((register) => {
1368
+ const rows = Array.from(register.querySelectorAll(row)).filter((one) => inside(one, register, table) && onScreen(one) && stored(one));
1369
+ const headings = Array.from(register.querySelectorAll(".lotics-table__column")).filter((heading) => inside(heading, register, table));
1370
+ const cellsOf = (one) => {
1371
+ const box = Array.from(one.querySelectorAll(".lotics-table__cells")).find((cells) => inside(cells, one, row));
1372
+ return box === void 0 ? [] : Array.from(box.children).filter((cell) => cell.classList.contains("lotics-table__cell"));
1373
+ };
1374
+ const gutterOf = (one) => Array.from(one.querySelectorAll(".lotics-table__trailing")).find((gutter) => inside(gutter, one, row));
1375
+ const grid = rows.map(cellsOf);
1376
+ const columns = Math.max(headings.length, ...grid.map((cells) => cells.length), 0);
1377
+ const slots = Array.from({ length: columns }, (_, index) => ({
1378
+ name: words4(headings[index]) || `column ${index + 1}`,
1379
+ kind: "column",
1380
+ heading: headings[index],
1381
+ cells: grid.map((cells) => cells[index])
1382
+ }));
1383
+ const gutters = rows.map(gutterOf);
1384
+ if (gutters.some((gutter) => gutter !== void 0)) slots.push({ name: "the row's acts", kind: "gutter", cells: gutters });
1385
+ return { root: register, rows, slots };
1386
+ });
1387
+ }
1388
+ function ariaRegisters(root) {
1389
+ const table = '[role="table"], [role="grid"]';
1390
+ return within(root, table).filter(onScreen).map((register) => {
1391
+ const headings = Array.from(register.querySelectorAll('[role="columnheader"]')).filter((heading) => inside(heading, register, table)).map(words4);
1392
+ const grid = Array.from(register.querySelectorAll('[role="row"]')).filter((one) => inside(one, register, table) && onScreen(one) && stored(one)).map((one) => ({
1393
+ row: one,
1394
+ cells: Array.from(one.querySelectorAll('[role="cell"], [role="gridcell"]')).filter(
1395
+ (cell) => inside(cell, one, '[role="row"]')
1396
+ )
1397
+ })).filter((one) => one.cells.length > 0);
1398
+ const columns = Math.max(headings.length, ...grid.map((one) => one.cells.length), 0);
1399
+ return {
1400
+ root: register,
1401
+ rows: grid.map((one) => one.row),
1402
+ slots: Array.from({ length: columns }, (_, index) => ({
1403
+ name: headings[index] || `column ${index + 1}`,
1404
+ kind: "column",
1405
+ cells: grid.map((one) => one.cells[index])
1406
+ }))
1407
+ };
1408
+ });
1409
+ }
1410
+ function inkSpan(cell) {
1411
+ const box = cell.getBoundingClientRect();
1412
+ let left = Number.POSITIVE_INFINITY;
1413
+ let right = Number.NEGATIVE_INFINITY;
1414
+ const take = (rect) => {
1415
+ if (rect.width <= 0 || rect.height <= 0) return;
1416
+ left = Math.min(left, Math.max(rect.left, box.left));
1417
+ right = Math.max(right, Math.min(rect.right, box.right));
1418
+ };
1419
+ const walk = (node) => {
1420
+ if (node.nodeType === Node.TEXT_NODE) {
1421
+ const parent = node.parentElement;
1422
+ if (parent === null || (node.textContent || "").trim() === "" || !onScreen(parent)) return;
1423
+ const range = document.createRange();
1424
+ range.selectNodeContents(node);
1425
+ take(range.getBoundingClientRect());
1426
+ return;
1427
+ }
1428
+ node.childNodes.forEach(walk);
1429
+ };
1430
+ walk(cell);
1431
+ for (const drawn of within(cell, DRAWS)) if (onScreen(drawn)) take(drawn.getBoundingClientRect());
1432
+ return right > left ? { left, right } : null;
1433
+ }
1434
+ var laidControls = (cell) => within(cell, CONTROL2).filter((control) => {
1435
+ const rect = control.getBoundingClientRect();
1436
+ const laid = typeof control.checkVisibility !== "function" || control.checkVisibility();
1437
+ return laid && control.closest("[inert]") === null && rect.width > 0 && rect.height > 0;
1438
+ });
1439
+ function occupied(cell) {
1440
+ if (cell === void 0) return 0;
1441
+ const box = cell.getBoundingClientRect();
1442
+ const ink = inkSpan(cell);
1443
+ const spans = [
1444
+ ...ink === null ? [] : [ink],
1445
+ ...laidControls(cell).map((control) => {
1446
+ const rect = control.getBoundingClientRect();
1447
+ return { left: Math.max(rect.left, box.left), right: Math.min(rect.right, box.right) };
1448
+ })
1449
+ ].filter((span) => span.right > span.left);
1450
+ if (spans.length === 0) return 0;
1451
+ return Math.max(...spans.map((span) => span.right)) - Math.min(...spans.map((span) => span.left));
1452
+ }
1453
+ var leavesRoom = (slot, cell, width) => slot.kind === "column" ? occupied(cell) === 0 : width - occupied(cell) >= RESERVED_MIN_WIDTH;
1454
+ var widthOf = (slot) => Math.round(Math.max(0, ...slot.cells.map((cell) => cell === void 0 ? 0 : cell.getBoundingClientRect().width)));
1455
+ function reservedBlanks(root = document.body) {
1456
+ let total = 0;
1457
+ const found = [];
1458
+ for (const register of [...kitRegisters(root), ...ariaRegisters(root)]) {
1459
+ if (register.rows.length === 0) continue;
1460
+ for (const slot of register.slots) {
1461
+ const width = widthOf(slot);
1462
+ if (width < RESERVED_MIN_WIDTH) continue;
1463
+ total += 1;
1464
+ const empty = slot.cells.filter((cell) => leavesRoom(slot, cell, width)).length;
1465
+ if (empty <= register.rows.length * RESERVED_BLANK_SHARE) continue;
1466
+ found.push({ slot: slot.name, kind: slot.kind, width, blank: empty, rows: register.rows.length });
1467
+ }
1468
+ }
1469
+ return { total, blank: found };
1470
+ }
1471
+ function shedOf(register) {
1472
+ const said2 = register.getAttribute("data-shed");
1473
+ if (said2 === null) return [];
1474
+ const parsed = JSON.parse(said2);
1475
+ if (!Array.isArray(parsed)) throw new Error(`data-shed is not a list: ${said2.slice(0, 80)}`);
1476
+ return parsed.map((entry) => {
1477
+ const label = entry !== null && typeof entry === "object" ? Reflect.get(entry, "label") : void 0;
1478
+ const width = entry !== null && typeof entry === "object" ? Reflect.get(entry, "width") : void 0;
1479
+ if (typeof label !== "string" || typeof width !== "number") throw new Error(`data-shed names a column without a label and a width: ${said2.slice(0, 80)}`);
1480
+ return { label, width };
1481
+ });
1482
+ }
1483
+ function spare(slot) {
1484
+ const width = widthOf(slot);
1485
+ if (slot.kind === "gutter") {
1486
+ const used = slot.cells.map(occupied).sort((a, b) => a - b);
1487
+ return Math.max(0, width - (used[Math.floor(used.length * RESERVED_BLANK_SHARE)] ?? 0));
1488
+ }
1489
+ if (slot.heading?.hasAttribute("data-fixed") === true || slot.cells.some((cell) => cell?.hasAttribute("data-fixed") === true)) return 0;
1490
+ const heading = slot.heading === void 0 ? null : inkSpan(slot.heading);
1491
+ const drawn = Math.max(
1492
+ heading === null ? 0 : heading.right - heading.left,
1493
+ ...slot.cells.map((cell) => {
1494
+ const span = cell === void 0 ? null : inkSpan(cell);
1495
+ return span === null ? 0 : span.right - span.left;
1496
+ })
1497
+ );
1498
+ return Math.max(0, width - drawn);
1499
+ }
1500
+ function slotGap(register) {
1501
+ const boxes = register.slots.map((slot) => slot.cells[0]).filter((cell) => cell !== void 0).map((cell) => cell.getBoundingClientRect()).sort((a, b) => a.left - b.left);
1502
+ return boxes.length < 2 ? 0 : Math.max(0, Math.round(boxes[1].left - boxes[0].right));
1503
+ }
1504
+ function shedWithRoom(root = document.body) {
1505
+ let total = 0;
1506
+ const found = [];
1507
+ for (const register of kitRegisters(root)) {
1508
+ const shed = shedOf(register.root);
1509
+ if (shed.length === 0 || register.rows.length === 0) continue;
1510
+ total += shed.length;
1511
+ const unused = Math.round(register.slots.reduce((sum, slot) => sum + spare(slot), 0));
1512
+ const gap = slotGap(register);
1513
+ for (const column of shed) if (column.width + gap <= unused) found.push({ ...column, gap, unused });
1514
+ }
1515
+ return { total, shed: found };
1516
+ }
1517
+ var CREATE = "[data-create]";
1518
+ var ITEM = '.lotics-row, .lotics-table__row, .lotics-table__block, .lotics-timeline__day, .lotics-region-state, [role="row"], [role="listitem"], li';
1519
+ var HEADINGS = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
1520
+ function offHeadingActs(root = document.body) {
1521
+ const section = ".lotics-section";
1522
+ let total = 0;
1523
+ const off = [];
1524
+ for (const one of within(root, section).filter(onScreen)) {
1525
+ const heading = Array.from(one.children).find((child) => child.classList.contains("lotics-section-heading"));
1526
+ if (heading === void 0) continue;
1527
+ const titles = within(one, HEADINGS).filter((title) => inside(title, one, section) && onScreen(title));
1528
+ for (const act of within(one, CREATE)) {
1529
+ if (!onScreen(act) || !inside(act, one, section)) continue;
1530
+ const name = nameOf(act) || words4(act);
1531
+ const item = act.closest(ITEM);
1532
+ if (item !== null && one.contains(item)) continue;
1533
+ let owner;
1534
+ for (const title2 of titles) {
1535
+ if (!title2.contains(act) && (title2.compareDocumentPosition(act) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) owner = title2;
1536
+ }
1537
+ if (owner !== void 0 && !heading.contains(owner)) continue;
1538
+ total += 1;
1539
+ const said2 = `"${words4(heading.querySelector(HEADINGS)) || words4(heading) || "(untitled)"}": "${name.slice(0, 40)}"`;
1540
+ if (!heading.contains(act)) {
1541
+ off.push(`${said2} drawn in the section's body`);
1542
+ continue;
1543
+ }
1544
+ const title = heading.querySelector(HEADINGS);
1545
+ if (title !== null && !title.contains(act) && act.getBoundingClientRect().top >= title.getBoundingClientRect().bottom) {
1546
+ off.push(`${said2} wrapped under the heading`);
1547
+ }
1548
+ }
1549
+ }
1550
+ return { total, off };
1551
+ }
1552
+ var OF_TOTAL = /^\d[\d.,]*(?:\s*[–-]\s*\d[\d.,]*)?\s*(?:of|trên|\/)\s*(\d[\d.,]*)$/i;
1553
+ var FIGURE = /^\d{1,3}(?:[.,]\d{3})+$|^\d+$/;
1554
+ var figure = (text) => FIGURE.test(text) ? Number(text.replace(/[.,]/g, "")) : null;
1555
+ function statedCounts(root = document.body) {
1556
+ const stated2 = [];
1557
+ for (const el of within(root, "*")) {
1558
+ if (!onScreen(el) || el.closest('nav, [role="banner"]') !== null) continue;
1559
+ const text = words4(el);
1560
+ const total = OF_TOTAL.exec(text);
1561
+ if (total !== null && !Array.from(el.children).some((child) => OF_TOTAL.test(words4(child)))) {
1562
+ const value2 = figure(total[1]);
1563
+ if (value2 !== null) stated2.push({ text, value: value2 });
1564
+ continue;
1565
+ }
1566
+ const holder = el.closest('[role="tab"], .lotics-count');
1567
+ if (el.childElementCount > 0 || holder === null) continue;
1568
+ const value = figure(text);
1569
+ if (value !== null) stated2.push({ text: words4(el.closest('[role="tab"]') ?? holder), value });
1570
+ }
1571
+ return stated2;
1572
+ }
1573
+
1279
1574
  // src/probe_collectors/right_form.ts
1280
1575
  var DAY_RUN_PART = ".lotics-timeline__dayhead";
1281
1576
  var SIDE = 'aside, [role="complementary"], nav, [role="navigation"]';
@@ -1298,14 +1593,14 @@ var __loticsProbe = (() => {
1298
1593
  const columnFloor = 3;
1299
1594
  const twoTrackWidth = 1016;
1300
1595
  const trackHeight = 240;
1301
- const words3 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
1302
- const surfaces = within(root, recordSurface).filter(onScreen);
1303
- const called2 = (el) => words3(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record";
1596
+ const words5 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
1597
+ const surfaces2 = within(root, recordSurface).filter(onScreen);
1598
+ const called2 = (el) => words5(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record";
1304
1599
  const readingInside = (side) => {
1305
1600
  for (const kind of PARKED) {
1306
1601
  for (const el of Array.from(side.querySelectorAll(kind.is))) {
1307
1602
  if (!onScreen(el) || el.closest(IN_PAGE) !== null) continue;
1308
- const named = nameOf(el).slice(0, 40) || words3(el);
1603
+ const named = nameOf(el).slice(0, 40) || words5(el);
1309
1604
  return named === "" ? kind.called : `${kind.called} ("${named}")`;
1310
1605
  }
1311
1606
  }
@@ -1323,7 +1618,7 @@ var __loticsProbe = (() => {
1323
1618
  twoOption += 1;
1324
1619
  if (pairs.length < 3) {
1325
1620
  const field = chooser.closest(".lotics-form-field");
1326
- const named = nameOf(chooser).slice(0, 40) || words3(field === null ? null : field.querySelector(".lotics-form-field__label")) || "(unnamed)";
1621
+ const named = nameOf(chooser).slice(0, 40) || words5(field === null ? null : field.querySelector(".lotics-form-field__label")) || "(unnamed)";
1327
1622
  pairs.push(`"${named}": a two-option select \u2014 ${options.map((option) => `"${option}"`).join(" / ")}`);
1328
1623
  }
1329
1624
  }
@@ -1419,7 +1714,7 @@ var __loticsProbe = (() => {
1419
1714
  let folds = 0;
1420
1715
  let sections = 0;
1421
1716
  const hidden = [];
1422
- for (const surface of surfaces) {
1717
+ for (const surface of surfaces2) {
1423
1718
  for (const fold of Array.from(surface.querySelectorAll(".lotics-accordion__trigger, details, [aria-expanded]"))) {
1424
1719
  if (!onScreen(fold) || fold.closest('[role="complementary"]') !== null) continue;
1425
1720
  if (fold.hasAttribute("aria-haspopup") || fold.matches(HOLDS_A_VALUE)) continue;
@@ -1444,7 +1739,7 @@ var __loticsProbe = (() => {
1444
1739
  let split = 0;
1445
1740
  const tracks = [];
1446
1741
  if (window.innerWidth >= twoTrackWidth) {
1447
- for (const surface of surfaces) {
1742
+ for (const surface of surfaces2) {
1448
1743
  read += 1;
1449
1744
  const box = surface.getBoundingClientRect();
1450
1745
  const owners = [surface, ...Array.from(surface.querySelectorAll("*"))];
@@ -1659,6 +1954,46 @@ var __loticsProbe = (() => {
1659
1954
  section: "9",
1660
1955
  fix: "One rung per row: a mark's size follows what the row is ABOUT, so the supporting mark takes the smaller one."
1661
1956
  },
1957
+ reserved_blank: {
1958
+ law: "reviewing.md",
1959
+ section: "8l",
1960
+ fix: "Give the room back: a column or row-act gutter most rows leave empty goes, and what the few rows hold rides those rows instead."
1961
+ },
1962
+ shed_with_room: {
1963
+ law: "reviewing.md",
1964
+ section: "8m",
1965
+ fix: "Seat the column the width can hold: reclaim what a slot reserves beyond what it must seat \u2014 a flexible column wider than its ink, a gutter held for acts most rows lack \u2014 before the fit gives a reading up."
1966
+ },
1967
+ action_off_heading: {
1968
+ law: "reviewing.md",
1969
+ section: "8n",
1970
+ fix: "Mount the section's add in its heading row (`RecordSection.action`, a `SectionHeadingAdd`), and let what it opens stand in the body: one place for a section's add, whatever its body draws."
1971
+ },
1972
+ partial_count: {
1973
+ law: "reviewing.md",
1974
+ section: "8o",
1975
+ fix: "State the server's count of the set, never the rows a page loaded: count the read (`useCount`) and step or badge against that total."
1976
+ },
1977
+ record_primary: {
1978
+ law: "hierarchy.md",
1979
+ section: "One primary act per surface",
1980
+ fix: "Draw a section's add secondary (`SectionHeadingAdd`): the record's one primary act is its head's \u2014 the next queued act, else the step its stage leads with."
1981
+ },
1982
+ brief_fold: {
1983
+ law: "hierarchy.md",
1984
+ section: "Depth \u2014 four surfaces, four jobs",
1985
+ fix: "Keep in the brief only what the head's act needs \u2014 the newest entry, the words to say, the one band placed first \u2014 and move the rest to a tab, until its foot is in the first viewport unscrolled."
1986
+ },
1987
+ brief_repeats: {
1988
+ law: "hierarchy.md",
1989
+ section: "One fact, one place",
1990
+ fix: "Take the value out of the brief's band: the head already states it, so the band read under it is the same fact twice."
1991
+ },
1992
+ empty_tab: {
1993
+ law: "hierarchy.md",
1994
+ section: "Depth \u2014 four surfaces, four jobs",
1995
+ fix: "Draw no tab over nothing: a tab is drawn only over a part with a body, so a part with no rows passes none."
1996
+ },
1662
1997
  render: {
1663
1998
  law: "reviewing.md",
1664
1999
  section: "8",
@@ -1831,6 +2166,97 @@ var __loticsProbe = (() => {
1831
2166
  detail: marks.mismatches.slice(0, 5).map((row) => `y=${row.y}: ${row.marks.join(", ")}`)
1832
2167
  });
1833
2168
  }
2169
+ const reserved = reservedBlanks(surface);
2170
+ if (reserved.blank.length > 0) {
2171
+ findings.push({
2172
+ probe: "reserved_blank",
2173
+ ...signature("reserved_blank"),
2174
+ total: reserved.total,
2175
+ count: reserved.blank.length,
2176
+ detail: reserved.blank.slice(0, 5).map(
2177
+ (slot) => `${slot.slot}: ${slot.width}px, ${slot.kind === "gutter" ? `${RESERVED_MIN_WIDTH}px or more beside the acts` : "blank"} on ${slot.blank} of ${slot.rows} rows`
2178
+ )
2179
+ });
2180
+ }
2181
+ const shed = shedWithRoom(surface);
2182
+ if (shed.shed.length > 0) {
2183
+ findings.push({
2184
+ probe: "shed_with_room",
2185
+ ...signature("shed_with_room"),
2186
+ total: shed.total,
2187
+ count: shed.shed.length,
2188
+ detail: shed.shed.slice(0, 5).map((column) => `"${column.label}" (${column.width}px + ${column.gap}px gap) shed beside ${column.unused}px the slots hold beyond what they seat`)
2189
+ });
2190
+ }
2191
+ const adds = offHeadingActs(surface);
2192
+ if (adds.off.length > 0) {
2193
+ findings.push({
2194
+ probe: "action_off_heading",
2195
+ ...signature("action_off_heading"),
2196
+ total: adds.total,
2197
+ count: adds.off.length,
2198
+ detail: adds.off.slice(0, 5)
2199
+ });
2200
+ }
2201
+ const served = options.served;
2202
+ if (served !== void 0) {
2203
+ const stated2 = statedCounts(surface);
2204
+ const partial = stated2.flatMap((count) => {
2205
+ const read = served.reads.find(
2206
+ (one) => one.in_hand === count.value && one.total !== count.value && !served.counted.some((counted) => counted.alias === one.alias && counted.value === count.value)
2207
+ );
2208
+ return read === void 0 ? [] : [`"${count.text}" states ${count.value} \u2014 the rows in hand of "${read.alias}", which the server counts ${read.total}`];
2209
+ });
2210
+ if (partial.length > 0) {
2211
+ findings.push({
2212
+ probe: "partial_count",
2213
+ ...signature("partial_count"),
2214
+ total: stated2.length,
2215
+ count: partial.length,
2216
+ detail: partial.slice(0, 5)
2217
+ });
2218
+ }
2219
+ }
2220
+ const primaries = headingPrimaries(surface);
2221
+ if (primaries.loud.length > 0) {
2222
+ findings.push({
2223
+ probe: "record_primary",
2224
+ ...signature("record_primary"),
2225
+ total: primaries.total,
2226
+ count: primaries.loud.length,
2227
+ detail: primaries.loud.slice(0, 5).map((one) => `"${one.record}": "${one.act}" is primary in a section's heading row, beside the head's act`)
2228
+ });
2229
+ }
2230
+ const folded = briefsBelowFold(surface);
2231
+ if (folded.below.length > 0) {
2232
+ findings.push({
2233
+ probe: "brief_fold",
2234
+ ...signature("brief_fold"),
2235
+ total: folded.total,
2236
+ count: folded.below.length,
2237
+ detail: folded.below.slice(0, 5).map((one) => `"${one.record}": the brief ends at ${one.bottom}px, past the ${folded.fold}px first viewport`)
2238
+ });
2239
+ }
2240
+ const repeats = briefRepeats(surface);
2241
+ if (repeats.repeated.length > 0) {
2242
+ findings.push({
2243
+ probe: "brief_repeats",
2244
+ ...signature("brief_repeats"),
2245
+ total: repeats.total,
2246
+ count: repeats.repeated.length,
2247
+ detail: repeats.repeated.slice(0, 5).map((one) => `"${one.record}": the brief states "${one.value}", which the head already says`)
2248
+ });
2249
+ }
2250
+ const tabs = emptyTabs(surface);
2251
+ if (tabs.empty.length > 0) {
2252
+ findings.push({
2253
+ probe: "empty_tab",
2254
+ ...signature("empty_tab"),
2255
+ total: tabs.total,
2256
+ count: tabs.empty.length,
2257
+ detail: tabs.empty.slice(0, 5).map((one) => `"${one.record}": the tab "${one.tab}" holds nothing`)
2258
+ });
2259
+ }
1834
2260
  findings.push(...censusFindings("clutter", clutterCensus(surface)));
1835
2261
  findings.push(...censusFindings("right_form", rightFormCensus(surface)));
1836
2262
  findings.push(...censusFindings("ink_and_air", [...inkAndAirCensus(surface), ...options.rings ?? []]));