@lotics/cli 0.246.0 → 0.251.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.
@@ -421,6 +421,15 @@ var __loticsProbe = (() => {
421
421
  };
422
422
  }
423
423
  var SINGLE_LINE_FIELDS = /* @__PURE__ */ new Set(["text", "search", "tel", "url", "email", "number"]);
424
+ function clippingAncestor(el, stop) {
425
+ for (let at = el.parentElement; at !== null; at = at.parentElement) {
426
+ const style = getComputedStyle(at);
427
+ const x = style.overflowX || style.overflow;
428
+ if (x === "hidden" || x === "clip") return at;
429
+ if (at === stop) return null;
430
+ }
431
+ return null;
432
+ }
424
433
  function truncatedLeaves(root = document.body) {
425
434
  const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
426
435
  const clippedAway2 = /^inset\(\s*50%/;
@@ -438,7 +447,16 @@ var __loticsProbe = (() => {
438
447
  if (box.width <= 1 && box.height <= 1) continue;
439
448
  if (clippedAway2.test(style.getPropertyValue("clip-path"))) continue;
440
449
  total += 1;
441
- if (style.overflow === "visible") continue;
450
+ if (style.overflow === "visible") {
451
+ const within2 = el.closest('.lotics-fact-groups, [role="listbox"]');
452
+ const clip = within2 === null ? null : clippingAncestor(el, within2);
453
+ if (clip !== null) {
454
+ const edge = clip.getBoundingClientRect();
455
+ const shown = Math.min(box.right, edge.right) - Math.max(box.left, edge.left);
456
+ if (box.right > edge.right + 1 || box.left < edge.left - 1) cut.push({ text: text.slice(0, 40), axis: "width", has: Math.max(0, Math.round(shown)), needs: Math.round(box.width) });
457
+ }
458
+ continue;
459
+ }
442
460
  const states = (property) => {
443
461
  const value = style.getPropertyValue(property);
444
462
  return value !== "" && value !== "none";
@@ -459,7 +477,7 @@ var __loticsProbe = (() => {
459
477
  const walk = (node) => {
460
478
  if (node.nodeType === Node.TEXT_NODE) {
461
479
  const parent = node.parentElement;
462
- if (!parent || skipTag.test(parent.tagName) || parent.closest("[data-content]") !== null) return;
480
+ if (!parent || skipTag.test(parent.tagName) || parent.tagName === "TEXTAREA" || parent.closest('[data-content], [contenteditable="true"]') !== null) return;
463
481
  const text = node.textContent || "";
464
482
  if (!text.trim()) return;
465
483
  total += 1;
@@ -694,22 +712,16 @@ var __loticsProbe = (() => {
694
712
  }
695
713
 
696
714
  // src/probe_collectors/anatomy.ts
697
- var BODY = ".lotics-record-page__body";
698
- var BRIEF = ".lotics-record-page__brief";
715
+ var FRAME = ".lotics-record-frame";
716
+ var HEADER = ".lotics-record-header";
717
+ var BLOCK = ".lotics-record-frame__block";
699
718
  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
- }
719
+ var FIELD = `${FRAME}__fields .lotics-form-field, .lotics-fact-groups__field`;
707
720
  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)" }];
721
+ return within(root, FRAME).filter(onScreen).map((frame) => {
722
+ const head = frame.closest(".lotics-drawer__popup") ?? frame;
723
+ const title = within(head, `${HEADER}__title`).find(onScreen);
724
+ return { frame, root: head, name: words2(title) || nameOf(head) || "(unnamed record)" };
713
725
  });
714
726
  }
715
727
  function words2(el) {
@@ -720,77 +732,38 @@ var __loticsProbe = (() => {
720
732
  function headingPrimaries(root = document.body) {
721
733
  const drawn = surfaces(root);
722
734
  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) }))
735
+ (surface) => within(surface.frame, PRIMARY).filter((act) => onScreen(act) && act.closest(BLOCK) !== null && act.closest(".lotics-section-heading") !== null).map((act) => ({ record: surface.name, act: nameOf(act) || words2(act) }))
724
736
  );
725
737
  return { total: drawn.length, loud };
726
738
  }
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 openPanel(body) {
736
- return within(body, '[role="tabpanel"]').find(onScreen);
737
- }
738
- function headings(el) {
739
- return within(el, ".lotics-section-heading").filter(onScreen).flatMap((heading) => {
740
- const title = within(heading, "h1, h2, h3, h4, h5, h6")[0];
741
- return title === void 0 ? [] : [words2(title)];
742
- });
743
- }
744
- function briefRepeats(root = document.body) {
739
+ function factRepeats(root = document.body) {
745
740
  let total = 0;
746
741
  const repeated = surfaces(root).flatMap((surface) => {
747
- const brief = within(surface.body, BRIEF).find(onScreen);
748
- if (brief === void 0) return [];
742
+ const header = within(surface.root, HEADER).find(onScreen);
743
+ if (header === void 0) return [];
749
744
  const head = new Set(
750
- 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)
745
+ within(header, "*").filter((el) => el.childElementCount === 0 && el.closest('button, [role="button"], [role="menu"]') === null && onScreen(el)).map((el) => said(words2(el))).filter((text) => text.length > 1)
751
746
  );
752
- const values = within(brief, ".lotics-record-facts__value").filter(onScreen);
753
- const parts = headings(brief);
754
- total += values.length + parts.length;
755
- const panel = openPanel(surface.body);
756
- const open = new Set(panel === void 0 ? [] : headings(panel).map(said));
757
- return [
758
- ...values.flatMap((value) => {
759
- const text = said(words2(value));
760
- return text !== "" && head.has(text) ? [{ record: surface.name, value: words2(value), where: "head" }] : [];
761
- }),
762
- ...parts.flatMap((part) => open.has(said(part)) ? [{ record: surface.name, value: part, where: "tab" }] : [])
763
- ];
764
- });
765
- return { total, repeated };
766
- }
767
- function actRepeats(root = document.body) {
768
- let total = 0;
769
- const repeated = surfaces(root).flatMap((surface) => {
770
- const panel = openPanel(surface.body);
771
- if (panel === void 0) return [];
772
- const verbs = new Set(
773
- within(surface.root, PRIMARY).filter((act) => !surface.body.contains(act) && onScreen(act)).map((act) => said(words2(act))).filter((text) => text !== "")
774
- );
775
- const waiting = within(panel, ".lotics-act-card").filter((card) => card.querySelector(".lotics-act-card__fold") === null && onScreen(card));
776
- const firsts = waiting.filter((card, at) => waiting.findIndex((other) => other.parentElement === card.parentElement) === at);
777
- total += firsts.length;
778
- return firsts.flatMap((card) => {
779
- const title = words2(card.querySelector(".lotics-act-card__title"));
780
- return title !== "" && verbs.has(said(title)) ? [{ record: surface.name, act: title }] : [];
747
+ const fields = within(surface.frame, FIELD).filter((field) => onScreen(field) && !header.contains(field));
748
+ total += fields.length;
749
+ return fields.flatMap((field) => {
750
+ const label = within(field, ".lotics-form-field__label")[0];
751
+ const value = within(field, "*").filter((el) => el.childElementCount === 0 && (label === void 0 || !label.contains(el)) && onScreen(el)).map((el) => words2(el)).join(" ").trim();
752
+ return value !== "" && head.has(said(value)) ? [{ record: surface.name, field: words2(label), value }] : [];
781
753
  });
782
754
  });
783
755
  return { total, repeated };
784
756
  }
785
- function emptyTabs(root = document.body) {
757
+ function emptyBlocks(root = document.body) {
786
758
  let total = 0;
787
759
  const empty = surfaces(root).flatMap((surface) => {
788
- const tabs = within(surface.body, '[role="tab"]').filter(onScreen);
789
- total += tabs.length;
790
- const open = tabs.find((tab) => tab.getAttribute("aria-selected") === "true");
791
- const panel = openPanel(surface.body);
792
- const bare = open !== void 0 && panel !== void 0 && within(panel, ".lotics-section").filter(onScreen).length === 0;
793
- return bare ? [{ record: surface.name, tab: words2(open).replace(/\d+$/, "").trim() }] : [];
760
+ const blocks = within(surface.frame, BLOCK).filter(onScreen);
761
+ total += blocks.length;
762
+ return blocks.flatMap((block) => {
763
+ const body = Array.from(block.children).filter((child) => !child.matches(".lotics-section-heading") && onScreen(child));
764
+ const drawn = body.some((child) => words2(child) !== "" || child.querySelector("img, svg, input, textarea, button, [contenteditable]") !== null);
765
+ return drawn ? [] : [{ record: surface.name, block: block.getAttribute("aria-label") ?? "" }];
766
+ });
794
767
  });
795
768
  return { total, empty };
796
769
  }
@@ -801,8 +774,8 @@ var __loticsProbe = (() => {
801
774
  function clutterCensus(root = document.body) {
802
775
  const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
803
776
  const chrome = 'nav, [role="banner"]';
804
- const recordSurface = '.lotics-record-page, [role="dialog"]';
805
- const headings2 = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
777
+ const recordSurface = '.lotics-record-frame, [role="dialog"]:not([data-side])';
778
+ const headings = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
806
779
  const noteFloor = 3;
807
780
  const formFloor = 3;
808
781
  const hueStep = 30;
@@ -811,7 +784,7 @@ var __loticsProbe = (() => {
811
784
  const sameEdge = 8;
812
785
  const words5 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
813
786
  const surfaces2 = within(root, recordSurface).filter(onScreen);
814
- const called2 = (el) => el.matches(headings2) ? words5(el) || "the section" : el.matches(recordSurface) ? words5(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record" : "the screen";
787
+ const called2 = (el) => el.matches(headings) ? words5(el) || "the section" : el.matches(recordSurface) ? words5(el.querySelector(".lotics-record-header__title, h1, h2")) || "the record" : "the screen";
815
788
  const rules = [];
816
789
  const primary = '[data-variant="primary"]';
817
790
  const acts = within(root, primary).filter(
@@ -824,7 +797,7 @@ var __loticsProbe = (() => {
824
797
  const surface = act.closest(recordSurface) ?? page;
825
798
  let titles = titled.get(surface);
826
799
  if (titles === void 0) {
827
- titles = within(surface, headings2).filter(onScreen);
800
+ titles = within(surface, headings).filter(onScreen);
828
801
  titled.set(surface, titles);
829
802
  }
830
803
  let owner = surface;
@@ -917,34 +890,6 @@ var __loticsProbe = (() => {
917
890
  count: twice,
918
891
  offenders: repeated
919
892
  });
920
- const absent = /^[—–-]$/;
921
- let facts = 0;
922
- let standing = 0;
923
- const open = [];
924
- for (const surface of surfaces2) {
925
- for (const block of within(surface, ".lotics-record-facts")) {
926
- const fold = block.querySelector(".lotics-record-facts__more");
927
- if (fold === null) continue;
928
- for (const band of Array.from(block.querySelectorAll(".lotics-record-facts__group"))) {
929
- if ((fold.compareDocumentPosition(band) & Node.DOCUMENT_POSITION_FOLLOWING) === 0) continue;
930
- for (const cell of Array.from(band.querySelectorAll(".lotics-record-facts__fact"))) {
931
- const value = cell.querySelector(".lotics-record-facts__value");
932
- if (value === null || !onScreen(cell)) continue;
933
- facts += 1;
934
- if (!absent.test((value.textContent || "").trim())) continue;
935
- standing += 1;
936
- if (open.length < 3) open.push(`"${words5(cell.querySelector(".lotics-record-facts__label")) || words5(cell)}" is unstated and drawn`);
937
- }
938
- }
939
- }
940
- }
941
- rules.push({
942
- law: "hierarchy.md",
943
- rule: "How a record derives them",
944
- total: facts,
945
- count: standing,
946
- offenders: open
947
- });
948
893
  let boxes = 0;
949
894
  let overReserved = 0;
950
895
  const reserved = [];
@@ -1111,7 +1056,7 @@ var __loticsProbe = (() => {
1111
1056
  var CONTROL = 'a[href], button, summary, [role="button"], [role="link"], [role="menuitem"], [role="tab"], [role="checkbox"], [role="switch"]';
1112
1057
  var NAVIGATION = 'nav, [role="navigation"], [role="banner"]';
1113
1058
  var EDITOR = 'input, textarea, select, [contenteditable=""], [contenteditable="true"], [contenteditable="plaintext-only"], [role="textbox"], [role="searchbox"], [role="combobox"], [role="spinbutton"]';
1114
- var FIELD = EDITOR + ', [role="checkbox"], [role="switch"], [role="radio"], [role="slider"]';
1059
+ var FIELD2 = EDITOR + ', [role="checkbox"], [role="switch"], [role="radio"], [role="slider"]';
1115
1060
  var GROUP = '[role="group"], fieldset';
1116
1061
  var TEXT_ENTRY = /* @__PURE__ */ new Set([
1117
1062
  "text",
@@ -1177,11 +1122,11 @@ var __loticsProbe = (() => {
1177
1122
  const tag = /^H([1-6])$/.exec(el.tagName);
1178
1123
  return tag === null ? 2 : Number(tag[1]);
1179
1124
  };
1180
- const headings2 = within(root, HEADING2).filter(
1125
+ const headings = within(root, HEADING2).filter(
1181
1126
  (el) => onScreen(el) && el.closest(NAVIGATION) === null && words3(el) !== ""
1182
1127
  );
1183
- const shallowest = headings2.reduce((top, el) => Math.min(top, rank(el)), Infinity);
1184
- const pageTitle = headings2.find((el) => rank(el) === shallowest);
1128
+ const shallowest = headings.reduce((top, el) => Math.min(top, rank(el)), Infinity);
1129
+ const pageTitle = headings.find((el) => rank(el) === shallowest);
1185
1130
  const headerRow = (heading) => {
1186
1131
  const own = words3(heading);
1187
1132
  let row = heading;
@@ -1195,7 +1140,7 @@ var __loticsProbe = (() => {
1195
1140
  let sections = 0;
1196
1141
  let badged = 0;
1197
1142
  const badges = [];
1198
- for (const heading of headings2) {
1143
+ for (const heading of headings) {
1199
1144
  if (heading === pageTitle) continue;
1200
1145
  sections += 1;
1201
1146
  const mark = graphicIn(headerRow(heading));
@@ -1213,7 +1158,7 @@ var __loticsProbe = (() => {
1213
1158
  offenders: badges
1214
1159
  });
1215
1160
  const editors = editorsIn(root);
1216
- const fieldsIn = (box) => within(box, FIELD).filter(onScreen);
1161
+ const fieldsIn = (box) => within(box, FIELD2).filter(onScreen);
1217
1162
  const labelsIn = (box) => within(box, "label").filter((el) => onScreen(el) && words3(el) !== "");
1218
1163
  const oneControl = (el) => el.matches(GROUP) || (el.getAttribute("aria-label") || "").trim() !== "" || (el.getAttribute("aria-labelledby") || "").trim() !== "";
1219
1164
  const around = (outer, inner) => outer.left <= inner.left + HAIR && outer.top <= inner.top + HAIR && outer.right + HAIR >= inner.right && outer.bottom + HAIR >= inner.bottom;
@@ -1399,18 +1344,18 @@ var __loticsProbe = (() => {
1399
1344
  const row = ".lotics-table__row";
1400
1345
  return within(root, table).filter((register) => onScreen(register) && !register.hasAttribute("data-stacked")).map((register) => {
1401
1346
  const rows = Array.from(register.querySelectorAll(row)).filter((one) => inside(one, register, table) && onScreen(one) && stored(one));
1402
- const headings2 = Array.from(register.querySelectorAll(".lotics-table__column")).filter((heading) => inside(heading, register, table));
1347
+ const headings = Array.from(register.querySelectorAll(".lotics-table__column")).filter((heading) => inside(heading, register, table));
1403
1348
  const cellsOf = (one) => {
1404
1349
  const box = Array.from(one.querySelectorAll(".lotics-table__cells")).find((cells) => inside(cells, one, row));
1405
1350
  return box === void 0 ? [] : Array.from(box.children).filter((cell) => cell.classList.contains("lotics-table__cell"));
1406
1351
  };
1407
1352
  const gutterOf = (one) => Array.from(one.querySelectorAll(".lotics-table__trailing")).find((gutter) => inside(gutter, one, row));
1408
1353
  const grid = rows.map(cellsOf);
1409
- const columns = Math.max(headings2.length, ...grid.map((cells) => cells.length), 0);
1354
+ const columns = Math.max(headings.length, ...grid.map((cells) => cells.length), 0);
1410
1355
  const slots = Array.from({ length: columns }, (_, index) => ({
1411
- name: words4(headings2[index]) || `column ${index + 1}`,
1356
+ name: words4(headings[index]) || `column ${index + 1}`,
1412
1357
  kind: "column",
1413
- heading: headings2[index],
1358
+ heading: headings[index],
1414
1359
  cells: grid.map((cells) => cells[index])
1415
1360
  }));
1416
1361
  const gutters = rows.map(gutterOf);
@@ -1421,19 +1366,19 @@ var __loticsProbe = (() => {
1421
1366
  function ariaRegisters(root) {
1422
1367
  const table = '[role="table"], [role="grid"]';
1423
1368
  return within(root, table).filter(onScreen).map((register) => {
1424
- const headings2 = Array.from(register.querySelectorAll('[role="columnheader"]')).filter((heading) => inside(heading, register, table)).map(words4);
1369
+ const headings = Array.from(register.querySelectorAll('[role="columnheader"]')).filter((heading) => inside(heading, register, table)).map(words4);
1425
1370
  const grid = Array.from(register.querySelectorAll('[role="row"]')).filter((one) => inside(one, register, table) && onScreen(one) && stored(one)).map((one) => ({
1426
1371
  row: one,
1427
1372
  cells: Array.from(one.querySelectorAll('[role="cell"], [role="gridcell"]')).filter(
1428
1373
  (cell) => inside(cell, one, '[role="row"]')
1429
1374
  )
1430
1375
  })).filter((one) => one.cells.length > 0);
1431
- const columns = Math.max(headings2.length, ...grid.map((one) => one.cells.length), 0);
1376
+ const columns = Math.max(headings.length, ...grid.map((one) => one.cells.length), 0);
1432
1377
  return {
1433
1378
  root: register,
1434
1379
  rows: grid.map((one) => one.row),
1435
1380
  slots: Array.from({ length: columns }, (_, index) => ({
1436
- name: headings2[index] || `column ${index + 1}`,
1381
+ name: headings[index] || `column ${index + 1}`,
1437
1382
  kind: "column",
1438
1383
  cells: grid.map((one) => one.cells[index])
1439
1384
  }))
@@ -1612,39 +1557,10 @@ var __loticsProbe = (() => {
1612
1557
 
1613
1558
  // src/probe_collectors/right_form.ts
1614
1559
  var DAY_RUN_PART = ".lotics-timeline__dayhead";
1615
- var SIDE = 'aside, [role="complementary"], nav, [role="navigation"]';
1616
- var IN_PAGE = 'button, summary, [role="button"], [role="link"], [role="tab"], [role="menuitem"], a[href^="#"]';
1617
- var HEADING3 = 'h1, h2, h3, h4, h5, h6, [role="heading"]';
1618
- var RECORD_ROWS = 'table, [role="table"], [role="grid"], [role="treegrid"], [role="row"], [role="list"], ul, ol';
1619
- var PARKED = [
1620
- {
1621
- is: 'input, textarea, select, [contenteditable=""], [contenteditable="true"], [role="textbox"], [role="searchbox"], [role="combobox"], [role="spinbutton"], [role="slider"], [role="checkbox"], [role="radio"], [role="switch"], form, [role="form"]',
1622
- called: "a field"
1623
- },
1624
- { is: 'table, [role="table"], [role="grid"], [role="treegrid"], [role="row"], [role="listbox"]', called: "a list of rows" },
1625
- { is: 'article, [role="article"], [role="feed"], [role="log"]', called: "a conversation" },
1626
- { is: 'img, svg, picture, canvas, [role="img"], [role="figure"]', called: "a picture" },
1627
- { is: "a[href]", called: "a link out of the page" }
1628
- ];
1629
1560
  function rightFormCensus(root = document.body) {
1630
1561
  const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
1631
- const recordSurface = '.lotics-record-page, [role="dialog"]';
1632
1562
  const columnFloor = 3;
1633
- const twoTrackWidth = 1016;
1634
- const trackHeight = 240;
1635
1563
  const words5 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
1636
- const surfaces2 = within(root, recordSurface).filter(onScreen);
1637
- const called2 = (el) => words5(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record";
1638
- const readingInside = (side) => {
1639
- for (const kind of PARKED) {
1640
- for (const el of Array.from(side.querySelectorAll(kind.is))) {
1641
- if (!onScreen(el) || el.closest(IN_PAGE) !== null) continue;
1642
- const named = nameOf(el).slice(0, 40) || words5(el);
1643
- return named === "" ? kind.called : `${kind.called} ("${named}")`;
1644
- }
1645
- }
1646
- return null;
1647
- };
1648
1564
  const rules = [];
1649
1565
  const choosers = within(root, 'select, [role="listbox"]').filter(onScreen);
1650
1566
  let twoOption = 0;
@@ -1663,7 +1579,7 @@ var __loticsProbe = (() => {
1663
1579
  }
1664
1580
  rules.push({
1665
1581
  law: "data_entry.md",
1666
- rule: "Choosing a CHOICE control \u2014 `choiceControl`, never taste",
1582
+ rule: "Choosing a CHOICE control \u2014 by the set, never taste",
1667
1583
  total: choosers.length,
1668
1584
  count: twoOption,
1669
1585
  offenders: pairs
@@ -1707,7 +1623,7 @@ var __loticsProbe = (() => {
1707
1623
  walk(root);
1708
1624
  }
1709
1625
  const dates = leaves.filter((leaf) => leaf.date);
1710
- const headings2 = leaves.filter((leaf) => !leaf.date);
1626
+ const headings = leaves.filter((leaf) => !leaf.date);
1711
1627
  const leads = (leaf) => !leaves.some(
1712
1628
  (other) => other.left < leaf.left && other.top < leaf.bottom && other.bottom > leaf.top
1713
1629
  );
@@ -1733,112 +1649,19 @@ var __loticsProbe = (() => {
1733
1649
  grouped += 1;
1734
1650
  if (runs.length < 3) {
1735
1651
  const top = Math.min(...column.items.map((leaf) => leaf.top));
1736
- const over = headings2.filter((label) => label.top < top && top - label.top <= 360 && label.left < column.right && label.right > column.left).sort((a, b) => b.top - a.top)[0];
1652
+ const over = headings.filter((label) => label.top < top && top - label.top <= 360 && label.left < column.right && label.right > column.left).sort((a, b) => b.top - a.top)[0];
1737
1653
  runs.push(
1738
1654
  `${over === void 0 ? "(unnamed)" : over.text}: ${column.items.length} rows over ${days.size} days, the date repeated per row`
1739
1655
  );
1740
1656
  }
1741
1657
  }
1742
1658
  rules.push({
1743
- law: "templates.md",
1659
+ law: "screen.md",
1744
1660
  rule: "Which device answers the question \u2192 Read what happened, or what is planned, in time",
1745
1661
  total: columns.length,
1746
1662
  count: grouped,
1747
1663
  offenders: runs
1748
1664
  });
1749
- const namesItself = (region) => {
1750
- const heading = Array.from(region.querySelectorAll(HEADING3)).find((el) => el.closest("summary") === null);
1751
- return heading !== void 0 || region.querySelector(RECORD_ROWS) !== null;
1752
- };
1753
- let folds = 0;
1754
- let sections = 0;
1755
- const hidden = [];
1756
- for (const surface of surfaces2) {
1757
- for (const fold of Array.from(surface.querySelectorAll(".lotics-accordion__trigger, details, [aria-expanded]"))) {
1758
- if (!onScreen(fold) || fold.closest('[role="complementary"]') !== null) continue;
1759
- if (fold.hasAttribute("aria-haspopup") || fold.matches(HOLDS_A_VALUE)) continue;
1760
- folds += 1;
1761
- const opened = fold.tagName === "DETAILS" ? [fold] : pointedAt(fold, "aria-controls");
1762
- if (!fold.matches(".lotics-accordion__trigger") && !opened.some(namesItself)) continue;
1763
- sections += 1;
1764
- if (hidden.length < 3) {
1765
- const named = nameOf(fold).slice(0, 40);
1766
- hidden.push(`${called2(surface)}: "${named === "" ? "(unnamed)" : named}" is a section behind a fold`);
1767
- }
1768
- }
1769
- }
1770
- rules.push({
1771
- law: "templates.md",
1772
- rule: "Which device answers the question \u2192 See what NAMES this record",
1773
- total: folds,
1774
- count: sections,
1775
- offenders: hidden
1776
- });
1777
- let read = 0;
1778
- let split = 0;
1779
- const tracks = [];
1780
- if (window.innerWidth >= twoTrackWidth) {
1781
- for (const surface of surfaces2) {
1782
- read += 1;
1783
- const box = surface.getBoundingClientRect();
1784
- const owners = [surface, ...Array.from(surface.querySelectorAll("*"))];
1785
- let found = null;
1786
- for (const owner of owners) {
1787
- const columnsHere = Array.from(owner.children).filter((child) => {
1788
- if (!onScreen(child)) return false;
1789
- if (child.matches(SIDE) || child.querySelector(SIDE) !== null) return false;
1790
- const rect = child.getBoundingClientRect();
1791
- return rect.height >= trackHeight && rect.height >= box.height * 0.5 && rect.width >= box.width * 0.2;
1792
- });
1793
- for (let a = 0; a < columnsHere.length && found === null; a++) {
1794
- for (let b = a + 1; b < columnsHere.length && found === null; b++) {
1795
- const left = columnsHere[a].getBoundingClientRect();
1796
- const right = columnsHere[b].getBoundingClientRect();
1797
- const apart = left.right <= right.left + 8 || right.right <= left.left + 8;
1798
- const overlap = Math.min(left.bottom, right.bottom) - Math.max(left.top, right.top);
1799
- const together = Math.max(left.right, right.right) - Math.min(left.left, right.left);
1800
- if (apart && overlap >= Math.min(left.height, right.height) * 0.5 && together >= box.width * 0.7) {
1801
- found = [columnsHere[a], columnsHere[b]];
1802
- }
1803
- }
1804
- }
1805
- if (found !== null) break;
1806
- }
1807
- if (found !== null) {
1808
- split += 1;
1809
- if (tracks.length < 3) {
1810
- const left = Math.round(found[0].getBoundingClientRect().width);
1811
- const right = Math.round(found[1].getBoundingClientRect().width);
1812
- tracks.push(`${called2(surface)}: two columns side by side, ${left}px and ${right}px`);
1813
- }
1814
- continue;
1815
- }
1816
- for (const side of Array.from(surface.querySelectorAll(SIDE))) {
1817
- if (!onScreen(side)) continue;
1818
- const rect = side.getBoundingClientRect();
1819
- if (rect.width > box.width * 0.6) continue;
1820
- const column = Array.from(surface.querySelectorAll("*")).some((other) => {
1821
- if (other === side || side.contains(other) || other.contains(side) || !onScreen(other)) return false;
1822
- const beside = other.getBoundingClientRect();
1823
- const apart = beside.left >= rect.right - 8 || beside.right <= rect.left + 8;
1824
- const overlap = Math.min(beside.bottom, rect.bottom) - Math.max(beside.top, rect.top);
1825
- return apart && beside.width >= box.width * 0.4 && overlap >= rect.height * 0.5;
1826
- });
1827
- if (!column) continue;
1828
- const parked = readingInside(side);
1829
- if (parked === null) continue;
1830
- split += 1;
1831
- if (tracks.length < 3) tracks.push(`${called2(surface)}: ${parked} beside the reading column`);
1832
- }
1833
- }
1834
- }
1835
- rules.push({
1836
- law: "hierarchy.md",
1837
- rule: "Depth \u2014 four surfaces, four jobs",
1838
- total: read,
1839
- count: split,
1840
- offenders: tracks
1841
- });
1842
1665
  return rules;
1843
1666
  }
1844
1667
 
@@ -1850,12 +1673,12 @@ var __loticsProbe = (() => {
1850
1673
  const navFirst = nav === null ? void 0 : Array.from(nav.querySelectorAll('a[href], [role="link"]')).filter(
1851
1674
  (item) => onScreen(item) && (item.textContent || "").trim() !== ""
1852
1675
  )[0];
1853
- const headings2 = within(root, "h1, h2, h3, h4, h5, h6").filter(
1676
+ const headings = within(root, "h1, h2, h3, h4, h5, h6").filter(
1854
1677
  (el) => onScreen(el) && el.closest('nav, [role="banner"]') === null && (el.textContent || "").trim() !== ""
1855
1678
  );
1856
- const title = headings2[0];
1679
+ const title = headings[0];
1857
1680
  const rank = (el) => Number(el.tagName.slice(1));
1858
- const heading = title === void 0 ? void 0 : headings2.slice(1).find((el) => rank(el) > rank(title));
1681
+ const heading = title === void 0 ? void 0 : headings.slice(1).find((el) => rank(el) > rank(title));
1859
1682
  const primaries = within(root, '[data-variant="primary"]').filter(
1860
1683
  (el) => onScreen(el) && el.closest('nav, [role="banner"]') === null
1861
1684
  );
@@ -2016,27 +1839,17 @@ var __loticsProbe = (() => {
2016
1839
  record_primary: {
2017
1840
  law: "hierarchy.md",
2018
1841
  section: "One primary act per surface",
2019
- 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."
2020
- },
2021
- brief_fold: {
2022
- law: "hierarchy.md",
2023
- section: "Depth \u2014 four surfaces, four jobs",
2024
- 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."
2025
- },
2026
- brief_repeats: {
2027
- law: "hierarchy.md",
2028
- section: "One fact, one place",
2029
- fix: "Take it out of the brief: the head already states the value, or the tab open at rest already draws the part, so the brief says it a second time."
1842
+ fix: "Draw a block's add secondary: the record's primaries are its acts \u2014 the next step at a section's foot."
2030
1843
  },
2031
- act_repeats: {
1844
+ fact_repeats: {
2032
1845
  law: "hierarchy.md",
2033
1846
  section: "One fact, one place",
2034
- fix: "Draw the head's act in its queue's card by its words and outcomes alone (`ActCard` `carried`): its verb, due day and refusal are the head's."
1847
+ fix: "Take the field out of the facts: the header already states it \u2014 its title, its status, the line under it or its figure."
2035
1848
  },
2036
- empty_tab: {
1849
+ empty_block: {
2037
1850
  law: "hierarchy.md",
2038
- section: "Depth \u2014 four surfaces, four jobs",
2039
- 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."
1851
+ section: "Whitespace is structure",
1852
+ fix: "Draw the block's body, or its empty line: a block under a heading that draws nothing is a press that leads nowhere."
2040
1853
  },
2041
1854
  render: {
2042
1855
  law: "reviewing.md",
@@ -2062,7 +1875,7 @@ var __loticsProbe = (() => {
2062
1875
  fix: "Move a fact down a tier, fold it, or take it off the screen \u2014 a budget failing is a tiering question, never a permission to shrink something."
2063
1876
  },
2064
1877
  right_form: {
2065
- law: "templates.md",
1878
+ law: "screen.md",
2066
1879
  section: "Which device answers the question",
2067
1880
  fix: "Draw what the JOB names: the device index gives the entry and the wrong device beside it, and a shape is picked by what the reader must do rather than by what the data is."
2068
1881
  },
@@ -2268,49 +2081,27 @@ var __loticsProbe = (() => {
2268
2081
  ...signature("record_primary"),
2269
2082
  total: primaries.total,
2270
2083
  count: primaries.loud.length,
2271
- 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`)
2084
+ detail: primaries.loud.slice(0, 5).map((one) => `"${one.record}": "${one.act}" is primary in a block's heading row, beside the record's own acts`)
2272
2085
  });
2273
2086
  }
2274
- const folded = briefsBelowFold(surface);
2275
- if (folded.below.length > 0) {
2276
- findings.push({
2277
- probe: "brief_fold",
2278
- ...signature("brief_fold"),
2279
- total: folded.total,
2280
- count: folded.below.length,
2281
- detail: folded.below.slice(0, 5).map((one) => `"${one.record}": the brief ends at ${one.bottom}px, past the ${folded.fold}px first viewport`)
2282
- });
2283
- }
2284
- const repeats = briefRepeats(surface);
2087
+ const repeats = factRepeats(surface);
2285
2088
  if (repeats.repeated.length > 0) {
2286
2089
  findings.push({
2287
- probe: "brief_repeats",
2288
- ...signature("brief_repeats"),
2090
+ probe: "fact_repeats",
2091
+ ...signature("fact_repeats"),
2289
2092
  total: repeats.total,
2290
2093
  count: repeats.repeated.length,
2291
- detail: repeats.repeated.slice(0, 5).map(
2292
- (one) => one.where === "head" ? `"${one.record}": the brief states "${one.value}", which the head already says` : `"${one.record}": the brief stands "${one.value}", which the tab open at rest draws too`
2293
- )
2094
+ detail: repeats.repeated.slice(0, 5).map((one) => `"${one.record}": the fact "${one.field}" reads "${one.value}", which the header already says`)
2294
2095
  });
2295
2096
  }
2296
- const acts = actRepeats(surface);
2297
- if (acts.repeated.length > 0) {
2097
+ const blocks = emptyBlocks(surface);
2098
+ if (blocks.empty.length > 0) {
2298
2099
  findings.push({
2299
- probe: "act_repeats",
2300
- ...signature("act_repeats"),
2301
- total: acts.total,
2302
- count: acts.repeated.length,
2303
- detail: acts.repeated.slice(0, 5).map((one) => `"${one.record}": the head's act "${one.act}" heads a card in the tab open at rest too`)
2304
- });
2305
- }
2306
- const tabs = emptyTabs(surface);
2307
- if (tabs.empty.length > 0) {
2308
- findings.push({
2309
- probe: "empty_tab",
2310
- ...signature("empty_tab"),
2311
- total: tabs.total,
2312
- count: tabs.empty.length,
2313
- detail: tabs.empty.slice(0, 5).map((one) => `"${one.record}": the tab "${one.tab}" holds nothing`)
2100
+ probe: "empty_block",
2101
+ ...signature("empty_block"),
2102
+ total: blocks.total,
2103
+ count: blocks.empty.length,
2104
+ detail: blocks.empty.slice(0, 5).map((one) => `"${one.record}": the block "${one.block}" draws nothing`)
2314
2105
  });
2315
2106
  }
2316
2107
  findings.push(...censusFindings("clutter", clutterCensus(surface)));
@@ -2395,8 +2186,8 @@ var __loticsProbe = (() => {
2395
2186
  function recordLinks() {
2396
2187
  const here = location.pathname + location.search;
2397
2188
  const doors2 = Array.from(
2398
- document.querySelectorAll('a[href], [data-opens="page"], button, [role="button"], [role="link"]')
2399
- ).filter((door) => door.matches('a[href], [data-opens="page"]') || isEmptyDoor(door));
2189
+ document.querySelectorAll('a[href], [data-opens="page"], [data-opens="event"], button, [role="button"], [role="link"]')
2190
+ ).filter((door) => door.matches('a[href], [data-opens="page"], [data-opens="event"]') || isEmptyDoor(door));
2400
2191
  return doors2.filter((door) => {
2401
2192
  if (door.closest('nav, [role="banner"], [role="tablist"]') !== null) return false;
2402
2193
  if (doors2.some((other) => other !== door && other.contains(door))) return false;
@@ -2496,6 +2287,9 @@ var __loticsProbe = (() => {
2496
2287
  const folds = Array.from(document.querySelectorAll('[aria-expanded="false"]')).filter(
2497
2288
  (el) => reachable(el) && el.getAttribute("aria-haspopup") === null
2498
2289
  );
2290
+ const tabs = Array.from(document.querySelectorAll('[role="tab"][aria-selected="false"]')).filter(
2291
+ (el) => onScreen(el) && el.closest('nav, [role="banner"]') === null && !open.some((overlay) => overlay.contains(el))
2292
+ );
2499
2293
  const acts = Array.from(
2500
2294
  document.querySelectorAll('[data-variant="primary"], [data-variant="secondary"]')
2501
2295
  ).filter((el) => {
@@ -2518,7 +2312,8 @@ var __loticsProbe = (() => {
2518
2312
  ...menus.slice(0, 1).map((el) => ({ kind: "menu", el })),
2519
2313
  ...folds.slice(0, 1).map((el) => ({ kind: "fold", el })),
2520
2314
  ...acts.map((el) => ({ kind: "dialog", el })),
2521
- ...parties.map((el) => ({ kind: "party", el }))
2315
+ ...parties.map((el) => ({ kind: "party", el })),
2316
+ ...tabs.map((el) => ({ kind: "tab", el }))
2522
2317
  ];
2523
2318
  }
2524
2319
  function doors() {