@lotics/cli 0.242.0 → 0.243.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.
- package/dist/probe_page.js +462 -43
- package/dist/src/cli.js +9060 -8320
- package/docs/clauses.md +8 -3
- package/docs/cli_reference.md +1 -1
- package/package.json +1 -1
package/dist/probe_page.js
CHANGED
|
@@ -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
|
|
612
|
+
const said2 = [];
|
|
613
613
|
const walk = (node) => {
|
|
614
614
|
if (node === el) return;
|
|
615
615
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
616
|
-
|
|
616
|
+
said2.push(node.textContent || "");
|
|
617
617
|
return;
|
|
618
618
|
}
|
|
619
619
|
node.childNodes.forEach(walk);
|
|
620
620
|
};
|
|
621
621
|
walk(label);
|
|
622
|
-
return
|
|
622
|
+
return said2.join(" ").replace(/\s+/g, " ").trim();
|
|
623
623
|
}
|
|
624
624
|
function regionName(region) {
|
|
625
|
-
const
|
|
626
|
-
return
|
|
625
|
+
const said2 = stated(region);
|
|
626
|
+
return said2 !== "" ? said2 : words(region.querySelector(HEADING));
|
|
627
627
|
}
|
|
628
628
|
function nameOf(el) {
|
|
629
|
-
const
|
|
630
|
-
if (
|
|
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
|
|
693
|
-
return root.matches(selector) ? [root, ...
|
|
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
|
|
711
|
-
const
|
|
712
|
-
const called2 = (el) => el.matches(headings) ?
|
|
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) => `"${
|
|
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
|
|
825
|
+
const said2 = [];
|
|
757
826
|
const walk = (node) => {
|
|
758
827
|
if (node === value) return;
|
|
759
|
-
if (node.nodeType === Node.TEXT_NODE)
|
|
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
|
|
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
|
|
768
|
-
if (
|
|
769
|
-
const
|
|
770
|
-
if (
|
|
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 <
|
|
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,
|
|
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(
|
|
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
|
|
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(`"${
|
|
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) => `"${
|
|
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
|
|
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 &&
|
|
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 =
|
|
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 (
|
|
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(`"${
|
|
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) &&
|
|
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
|
|
1161
|
-
return
|
|
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
|
-
`"${
|
|
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,225 @@ 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 title of titles) {
|
|
1535
|
+
if (!title.contains(act) && (title.compareDocumentPosition(act) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) owner = title;
|
|
1536
|
+
}
|
|
1537
|
+
if (owner !== void 0 && !heading.contains(owner)) continue;
|
|
1538
|
+
total += 1;
|
|
1539
|
+
if (heading.contains(act)) continue;
|
|
1540
|
+
off.push(`"${words4(heading.querySelector(HEADINGS)) || words4(heading) || "(untitled)"}": "${name.slice(0, 40)}" drawn in the section's body`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
return { total, off };
|
|
1544
|
+
}
|
|
1545
|
+
var OF_TOTAL = /^\d[\d.,]*(?:\s*[–-]\s*\d[\d.,]*)?\s*(?:of|trên|\/)\s*(\d[\d.,]*)$/i;
|
|
1546
|
+
var FIGURE = /^\d{1,3}(?:[.,]\d{3})+$|^\d+$/;
|
|
1547
|
+
var figure = (text) => FIGURE.test(text) ? Number(text.replace(/[.,]/g, "")) : null;
|
|
1548
|
+
function statedCounts(root = document.body) {
|
|
1549
|
+
const stated2 = [];
|
|
1550
|
+
for (const el of within(root, "*")) {
|
|
1551
|
+
if (!onScreen(el) || el.closest('nav, [role="banner"]') !== null) continue;
|
|
1552
|
+
const text = words4(el);
|
|
1553
|
+
const total = OF_TOTAL.exec(text);
|
|
1554
|
+
if (total !== null && !Array.from(el.children).some((child) => OF_TOTAL.test(words4(child)))) {
|
|
1555
|
+
const value2 = figure(total[1]);
|
|
1556
|
+
if (value2 !== null) stated2.push({ text, value: value2 });
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
const holder = el.closest('[role="tab"], .lotics-count');
|
|
1560
|
+
if (el.childElementCount > 0 || holder === null) continue;
|
|
1561
|
+
const value = figure(text);
|
|
1562
|
+
if (value !== null) stated2.push({ text: words4(el.closest('[role="tab"]') ?? holder), value });
|
|
1563
|
+
}
|
|
1564
|
+
return stated2;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1279
1567
|
// src/probe_collectors/right_form.ts
|
|
1280
1568
|
var DAY_RUN_PART = ".lotics-timeline__dayhead";
|
|
1281
1569
|
var SIDE = 'aside, [role="complementary"], nav, [role="navigation"]';
|
|
@@ -1298,14 +1586,14 @@ var __loticsProbe = (() => {
|
|
|
1298
1586
|
const columnFloor = 3;
|
|
1299
1587
|
const twoTrackWidth = 1016;
|
|
1300
1588
|
const trackHeight = 240;
|
|
1301
|
-
const
|
|
1302
|
-
const
|
|
1303
|
-
const called2 = (el) =>
|
|
1589
|
+
const words5 = (el) => el === null ? "" : (el.textContent || "").trim().slice(0, 40);
|
|
1590
|
+
const surfaces2 = within(root, recordSurface).filter(onScreen);
|
|
1591
|
+
const called2 = (el) => words5(el.querySelector(".lotics-record-page__heading, h1, h2")) || "the record";
|
|
1304
1592
|
const readingInside = (side) => {
|
|
1305
1593
|
for (const kind of PARKED) {
|
|
1306
1594
|
for (const el of Array.from(side.querySelectorAll(kind.is))) {
|
|
1307
1595
|
if (!onScreen(el) || el.closest(IN_PAGE) !== null) continue;
|
|
1308
|
-
const named = nameOf(el).slice(0, 40) ||
|
|
1596
|
+
const named = nameOf(el).slice(0, 40) || words5(el);
|
|
1309
1597
|
return named === "" ? kind.called : `${kind.called} ("${named}")`;
|
|
1310
1598
|
}
|
|
1311
1599
|
}
|
|
@@ -1323,7 +1611,7 @@ var __loticsProbe = (() => {
|
|
|
1323
1611
|
twoOption += 1;
|
|
1324
1612
|
if (pairs.length < 3) {
|
|
1325
1613
|
const field = chooser.closest(".lotics-form-field");
|
|
1326
|
-
const named = nameOf(chooser).slice(0, 40) ||
|
|
1614
|
+
const named = nameOf(chooser).slice(0, 40) || words5(field === null ? null : field.querySelector(".lotics-form-field__label")) || "(unnamed)";
|
|
1327
1615
|
pairs.push(`"${named}": a two-option select \u2014 ${options.map((option) => `"${option}"`).join(" / ")}`);
|
|
1328
1616
|
}
|
|
1329
1617
|
}
|
|
@@ -1419,7 +1707,7 @@ var __loticsProbe = (() => {
|
|
|
1419
1707
|
let folds = 0;
|
|
1420
1708
|
let sections = 0;
|
|
1421
1709
|
const hidden = [];
|
|
1422
|
-
for (const surface of
|
|
1710
|
+
for (const surface of surfaces2) {
|
|
1423
1711
|
for (const fold of Array.from(surface.querySelectorAll(".lotics-accordion__trigger, details, [aria-expanded]"))) {
|
|
1424
1712
|
if (!onScreen(fold) || fold.closest('[role="complementary"]') !== null) continue;
|
|
1425
1713
|
if (fold.hasAttribute("aria-haspopup") || fold.matches(HOLDS_A_VALUE)) continue;
|
|
@@ -1444,7 +1732,7 @@ var __loticsProbe = (() => {
|
|
|
1444
1732
|
let split = 0;
|
|
1445
1733
|
const tracks = [];
|
|
1446
1734
|
if (window.innerWidth >= twoTrackWidth) {
|
|
1447
|
-
for (const surface of
|
|
1735
|
+
for (const surface of surfaces2) {
|
|
1448
1736
|
read += 1;
|
|
1449
1737
|
const box = surface.getBoundingClientRect();
|
|
1450
1738
|
const owners = [surface, ...Array.from(surface.querySelectorAll("*"))];
|
|
@@ -1659,6 +1947,46 @@ var __loticsProbe = (() => {
|
|
|
1659
1947
|
section: "9",
|
|
1660
1948
|
fix: "One rung per row: a mark's size follows what the row is ABOUT, so the supporting mark takes the smaller one."
|
|
1661
1949
|
},
|
|
1950
|
+
reserved_blank: {
|
|
1951
|
+
law: "reviewing.md",
|
|
1952
|
+
section: "8l",
|
|
1953
|
+
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."
|
|
1954
|
+
},
|
|
1955
|
+
shed_with_room: {
|
|
1956
|
+
law: "reviewing.md",
|
|
1957
|
+
section: "8m",
|
|
1958
|
+
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."
|
|
1959
|
+
},
|
|
1960
|
+
action_off_heading: {
|
|
1961
|
+
law: "reviewing.md",
|
|
1962
|
+
section: "8n",
|
|
1963
|
+
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."
|
|
1964
|
+
},
|
|
1965
|
+
partial_count: {
|
|
1966
|
+
law: "reviewing.md",
|
|
1967
|
+
section: "8o",
|
|
1968
|
+
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."
|
|
1969
|
+
},
|
|
1970
|
+
record_primary: {
|
|
1971
|
+
law: "hierarchy.md",
|
|
1972
|
+
section: "One primary act per surface",
|
|
1973
|
+
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."
|
|
1974
|
+
},
|
|
1975
|
+
brief_fold: {
|
|
1976
|
+
law: "hierarchy.md",
|
|
1977
|
+
section: "Depth \u2014 four surfaces, four jobs",
|
|
1978
|
+
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."
|
|
1979
|
+
},
|
|
1980
|
+
brief_repeats: {
|
|
1981
|
+
law: "hierarchy.md",
|
|
1982
|
+
section: "One fact, one place",
|
|
1983
|
+
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."
|
|
1984
|
+
},
|
|
1985
|
+
empty_tab: {
|
|
1986
|
+
law: "hierarchy.md",
|
|
1987
|
+
section: "Depth \u2014 four surfaces, four jobs",
|
|
1988
|
+
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."
|
|
1989
|
+
},
|
|
1662
1990
|
render: {
|
|
1663
1991
|
law: "reviewing.md",
|
|
1664
1992
|
section: "8",
|
|
@@ -1831,6 +2159,97 @@ var __loticsProbe = (() => {
|
|
|
1831
2159
|
detail: marks.mismatches.slice(0, 5).map((row) => `y=${row.y}: ${row.marks.join(", ")}`)
|
|
1832
2160
|
});
|
|
1833
2161
|
}
|
|
2162
|
+
const reserved = reservedBlanks(surface);
|
|
2163
|
+
if (reserved.blank.length > 0) {
|
|
2164
|
+
findings.push({
|
|
2165
|
+
probe: "reserved_blank",
|
|
2166
|
+
...signature("reserved_blank"),
|
|
2167
|
+
total: reserved.total,
|
|
2168
|
+
count: reserved.blank.length,
|
|
2169
|
+
detail: reserved.blank.slice(0, 5).map(
|
|
2170
|
+
(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`
|
|
2171
|
+
)
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
const shed = shedWithRoom(surface);
|
|
2175
|
+
if (shed.shed.length > 0) {
|
|
2176
|
+
findings.push({
|
|
2177
|
+
probe: "shed_with_room",
|
|
2178
|
+
...signature("shed_with_room"),
|
|
2179
|
+
total: shed.total,
|
|
2180
|
+
count: shed.shed.length,
|
|
2181
|
+
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`)
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
const adds = offHeadingActs(surface);
|
|
2185
|
+
if (adds.off.length > 0) {
|
|
2186
|
+
findings.push({
|
|
2187
|
+
probe: "action_off_heading",
|
|
2188
|
+
...signature("action_off_heading"),
|
|
2189
|
+
total: adds.total,
|
|
2190
|
+
count: adds.off.length,
|
|
2191
|
+
detail: adds.off.slice(0, 5)
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
const served = options.served;
|
|
2195
|
+
if (served !== void 0) {
|
|
2196
|
+
const stated2 = statedCounts(surface);
|
|
2197
|
+
const partial = stated2.flatMap((count) => {
|
|
2198
|
+
const read = served.reads.find(
|
|
2199
|
+
(one) => one.in_hand === count.value && one.total !== count.value && !served.counted.some((counted) => counted.alias === one.alias && counted.value === count.value)
|
|
2200
|
+
);
|
|
2201
|
+
return read === void 0 ? [] : [`"${count.text}" states ${count.value} \u2014 the rows in hand of "${read.alias}", which the server counts ${read.total}`];
|
|
2202
|
+
});
|
|
2203
|
+
if (partial.length > 0) {
|
|
2204
|
+
findings.push({
|
|
2205
|
+
probe: "partial_count",
|
|
2206
|
+
...signature("partial_count"),
|
|
2207
|
+
total: stated2.length,
|
|
2208
|
+
count: partial.length,
|
|
2209
|
+
detail: partial.slice(0, 5)
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
const primaries = headingPrimaries(surface);
|
|
2214
|
+
if (primaries.loud.length > 0) {
|
|
2215
|
+
findings.push({
|
|
2216
|
+
probe: "record_primary",
|
|
2217
|
+
...signature("record_primary"),
|
|
2218
|
+
total: primaries.total,
|
|
2219
|
+
count: primaries.loud.length,
|
|
2220
|
+
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`)
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
const folded = briefsBelowFold(surface);
|
|
2224
|
+
if (folded.below.length > 0) {
|
|
2225
|
+
findings.push({
|
|
2226
|
+
probe: "brief_fold",
|
|
2227
|
+
...signature("brief_fold"),
|
|
2228
|
+
total: folded.total,
|
|
2229
|
+
count: folded.below.length,
|
|
2230
|
+
detail: folded.below.slice(0, 5).map((one) => `"${one.record}": the brief ends at ${one.bottom}px, past the ${folded.fold}px first viewport`)
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
const repeats = briefRepeats(surface);
|
|
2234
|
+
if (repeats.repeated.length > 0) {
|
|
2235
|
+
findings.push({
|
|
2236
|
+
probe: "brief_repeats",
|
|
2237
|
+
...signature("brief_repeats"),
|
|
2238
|
+
total: repeats.total,
|
|
2239
|
+
count: repeats.repeated.length,
|
|
2240
|
+
detail: repeats.repeated.slice(0, 5).map((one) => `"${one.record}": the brief states "${one.value}", which the head already says`)
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
const tabs = emptyTabs(surface);
|
|
2244
|
+
if (tabs.empty.length > 0) {
|
|
2245
|
+
findings.push({
|
|
2246
|
+
probe: "empty_tab",
|
|
2247
|
+
...signature("empty_tab"),
|
|
2248
|
+
total: tabs.total,
|
|
2249
|
+
count: tabs.empty.length,
|
|
2250
|
+
detail: tabs.empty.slice(0, 5).map((one) => `"${one.record}": the tab "${one.tab}" holds nothing`)
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
1834
2253
|
findings.push(...censusFindings("clutter", clutterCensus(surface)));
|
|
1835
2254
|
findings.push(...censusFindings("right_form", rightFormCensus(surface)));
|
|
1836
2255
|
findings.push(...censusFindings("ink_and_air", [...inkAndAirCensus(surface), ...options.rings ?? []]));
|