@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.
- package/AGENTS.md +2 -3
- package/README.md +12 -13
- package/dist/probe_page.js +97 -302
- package/dist/src/cli.js +79376 -83650
- package/dist/src/client.d.ts +9 -63
- package/dist/src/client.js +2 -4
- package/docs/building_an_app.md +55 -57
- package/docs/cli_reference.md +13 -14
- package/docs/migration.md +64 -58
- package/package.json +1 -1
- package/docs/clauses.md +0 -135
package/dist/probe_page.js
CHANGED
|
@@ -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")
|
|
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(
|
|
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
|
|
698
|
-
var
|
|
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
|
|
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,
|
|
709
|
-
const head =
|
|
710
|
-
|
|
711
|
-
|
|
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.
|
|
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
|
|
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
|
|
748
|
-
if (
|
|
742
|
+
const header = within(surface.root, HEADER).find(onScreen);
|
|
743
|
+
if (header === void 0) return [];
|
|
749
744
|
const head = new Set(
|
|
750
|
-
within(
|
|
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
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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
|
|
757
|
+
function emptyBlocks(root = document.body) {
|
|
786
758
|
let total = 0;
|
|
787
759
|
const empty = surfaces(root).flatMap((surface) => {
|
|
788
|
-
const
|
|
789
|
-
total +=
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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-
|
|
805
|
-
const
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
1125
|
+
const headings = within(root, HEADING2).filter(
|
|
1181
1126
|
(el) => onScreen(el) && el.closest(NAVIGATION) === null && words3(el) !== ""
|
|
1182
1127
|
);
|
|
1183
|
-
const shallowest =
|
|
1184
|
-
const pageTitle =
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
1356
|
+
name: words4(headings[index]) || `column ${index + 1}`,
|
|
1412
1357
|
kind: "column",
|
|
1413
|
-
heading:
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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: "
|
|
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
|
|
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 =
|
|
1679
|
+
const title = headings[0];
|
|
1857
1680
|
const rank = (el) => Number(el.tagName.slice(1));
|
|
1858
|
-
const heading = title === void 0 ? void 0 :
|
|
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
|
|
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
|
-
|
|
1844
|
+
fact_repeats: {
|
|
2032
1845
|
law: "hierarchy.md",
|
|
2033
1846
|
section: "One fact, one place",
|
|
2034
|
-
fix: "
|
|
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
|
-
|
|
1849
|
+
empty_block: {
|
|
2037
1850
|
law: "hierarchy.md",
|
|
2038
|
-
section: "
|
|
2039
|
-
fix: "Draw
|
|
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: "
|
|
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
|
|
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
|
|
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: "
|
|
2288
|
-
...signature("
|
|
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
|
|
2297
|
-
if (
|
|
2097
|
+
const blocks = emptyBlocks(surface);
|
|
2098
|
+
if (blocks.empty.length > 0) {
|
|
2298
2099
|
findings.push({
|
|
2299
|
-
probe: "
|
|
2300
|
-
...signature("
|
|
2301
|
-
total:
|
|
2302
|
-
count:
|
|
2303
|
-
detail:
|
|
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() {
|