@lotics/cli 0.197.0 → 0.204.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/README.md +20 -4
- package/dist/probe_page.js +217 -27
- package/dist/src/cli.js +7450 -967
- package/dist/src/client.d.ts +94 -12
- package/dist/src/client.js +57 -3
- package/docs/building_an_app.md +47 -13
- package/docs/cli_reference.md +16 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,12 @@ It creates no apps of its own: build one in the workspace afterwards
|
|
|
92
92
|
copied in — bound onto the tables the model just made — as part of the same run.
|
|
93
93
|
`setup` refuses a table name your workspace already has; `lotics scaffold apply model.json` is the
|
|
94
94
|
additive verb — it adopts that table, adds what the model declares beyond it, and deletes nothing.
|
|
95
|
+
Because it never renames, the file and the workspace drift: `lotics scaffold diff model.json`
|
|
96
|
+
prints exactly where and exits non-zero on any difference, and
|
|
97
|
+
`lotics field rename <table> <field> "<new label>"` moves a label on the platform, in the file
|
|
98
|
+
(`--model`) and in every app bound to it (`--apps`) at once. `lotics scaffold apply model.json
|
|
99
|
+
--documents` writes only the file cells of the model's rows, onto the records the first run made —
|
|
100
|
+
rows land only into empty tables, so it is the one way back for attachments that were missed.
|
|
95
101
|
|
|
96
102
|
The other direction, once a workspace works: `lotics scaffold export > model.json` prints its
|
|
97
103
|
tables (or only `--tables tbl_a,tbl_b`) as that same file, findings on stderr. It is a starting
|
|
@@ -163,7 +169,7 @@ lotics org use acme # switch active org by name (or org id)
|
|
|
163
169
|
lotics org use "Acme Corp" # names are case-insensitive
|
|
164
170
|
```
|
|
165
171
|
|
|
166
|
-
The org **id** is the identity; the name is
|
|
172
|
+
The org **id** is the identity; the name is the label the key was saved under, and it is yours — renaming the org in the app never moves it, so the scripts scoping by it keep running. `lotics auth whoami` learns the new name and records it beside the label; both resolve, and `lotics org` prints `label (server: name)` while they differ. A name matching two saved orgs is refused rather than guessed; use the id.
|
|
167
173
|
|
|
168
174
|
### Working in parallel (worktrees)
|
|
169
175
|
|
|
@@ -177,7 +183,7 @@ lotics workspace select wks_... # records the workspace in the local pin
|
|
|
177
183
|
|
|
178
184
|
Each worktree resolves independently; switching the global default in another shell leaves pinned worktrees untouched. `.lotics/` should be gitignored.
|
|
179
185
|
|
|
180
|
-
Every command that resolves a workspace names its target before it acts — `lotics →
|
|
186
|
+
Every command that resolves a workspace names its target before it acts — `lotics → Starters / Equipment & Projects` on **stderr**, so stdout stays clean for piping. Both halves are NAMES, since the line exists to be read before a destructive write; the workspace's name is cached beside its id the first time any command lists it, and until then the line prints the id and says the name is not known yet. When nothing in the directory or environment chose the org and it came from the machine-wide default, the line says so and prints the `--local` command to pin — that default is the one another shell can move between two of your commands. `lotics auth whoami` reports the same resolution on demand, including which source won.
|
|
181
187
|
|
|
182
188
|
### Resolution precedence
|
|
183
189
|
|
|
@@ -189,6 +195,8 @@ Every command that resolves a workspace names its target before it acts — `lot
|
|
|
189
195
|
|
|
190
196
|
`LOTICS_WORKSPACE` (or `--workspace <id>` / `-w`) overrides the workspace at any level. For ephemeral or CI use, set `LOTICS_API_KEY` instead of saving anything.
|
|
191
197
|
|
|
198
|
+
**`LOTICS_ORG` is resolved once, before any command runs.** A value matching no saved credential refuses every verb with one sentence — a read, a write, and a check that needs no credential alike — and the refusal lists the orgs this machine does hold, so it is answerable without another command. It refuses even when a key arrives another way: a variable that scopes the command must not go unread while `LOTICS_API_KEY` sends the write somewhere else. When the variable resolves AND a key is supplied, the key decides the org and the command says so. An org keeps the name it was saved under: a server-side rename never moves it, the new name resolves as well, and `lotics org` prints both when they differ.
|
|
199
|
+
|
|
192
200
|
### Diagnostics
|
|
193
201
|
|
|
194
202
|
Every request identifies the CLI (`user-agent: lotics-cli/<version> node/<v> <platform>`) and names the command that made it (`x-lotics-cli-command: app.workflow.set`), so a failure in the server's logs can be traced to the verb and version that produced it. Neither header carries arguments: the command chain stops before any id, path, `@file`, or JSON payload.
|
|
@@ -353,6 +361,13 @@ lotics app codegen # import { F, OPT } from "../.lotics/
|
|
|
353
361
|
# the code calls that nothing bound, undeclared capabilities, query drift. Exits 1 on what a deploy refuses, so CI can gate on it.
|
|
354
362
|
lotics app check
|
|
355
363
|
|
|
364
|
+
# --screens adds the rendered surface: each screen the app's navigation reaches,
|
|
365
|
+
# and the first record each one opens onto a PAGE, headless in Chrome at 1280
|
|
366
|
+
# and 375, measured against @lotics/ui docs/reviewing.md. --screen and --width
|
|
367
|
+
# narrow it while you iterate on one screen.
|
|
368
|
+
lotics app check --screens
|
|
369
|
+
lotics app check --screens --screen "Đơn hàng" --width 375
|
|
370
|
+
|
|
356
371
|
# Running an app's alias is a TOOL call, like every other tool. Exits non-zero
|
|
357
372
|
# when the RUN failed, not only when the call did.
|
|
358
373
|
lotics run run_app_workflow '{"app_id":"app_...","alias":"issueInvoice","inputs":{"record_id":"rec_..."}}'
|
|
@@ -365,7 +380,8 @@ lotics run run_app_workflow '{...}' --cleanup # also deletes created records (
|
|
|
365
380
|
# Edit workflow bodies as files. `app pull` writes src/workflows/<alias>.ts (the
|
|
366
381
|
# faithful server source, wrapped + referencing its .lotics/workflows/<alias>.globals.d.ts);
|
|
367
382
|
# edit the body, then push it back through set_app_workflow — the server verifies it
|
|
368
|
-
# (deploy
|
|
383
|
+
# (a deploy calls the same verb for every alias the project holds ahead of the app, so
|
|
384
|
+
# this is the one-alias spelling of it). `app workflow check` runs the server's own
|
|
369
385
|
# JS-subset parser + type-check locally, so a body that passes is one `set` will accept
|
|
370
386
|
# (`set` still adds name resolution + lint + structural checks, which need the workspace):
|
|
371
387
|
lotics app workflow pull # rewrite src/workflows/*.ts + globals from the server
|
|
@@ -394,7 +410,7 @@ LOTICS_UI_SRC=/abs/monorepo/packages/ui/src lotics app deploy -m "..." # warns
|
|
|
394
410
|
lotics app dev # unset ⇒ @lotics/ui resolves from node_modules again
|
|
395
411
|
```
|
|
396
412
|
|
|
397
|
-
`app codegen`
|
|
413
|
+
`app codegen` puts a table in `app_fields.ts` when a declared query reads it or a bound workflow writes it — the queries come from `package.json#lotics.queries`, the written tables from each binding's own `table_ids`, so a table only a workflow body touches is addressable by alias with nothing to declare.
|
|
398
414
|
|
|
399
415
|
## SDK
|
|
400
416
|
|
package/dist/probe_page.js
CHANGED
|
@@ -21,9 +21,12 @@ var __loticsProbe = (() => {
|
|
|
21
21
|
// src/probe_page.ts
|
|
22
22
|
var probe_page_exports = {};
|
|
23
23
|
__export(probe_page_exports, {
|
|
24
|
-
|
|
24
|
+
PROBES: () => PROBES,
|
|
25
|
+
openRecord: () => openRecord,
|
|
26
|
+
openScreen: () => openScreen,
|
|
27
|
+
recordRows: () => recordRows,
|
|
25
28
|
run: () => run,
|
|
26
|
-
|
|
29
|
+
screens: () => screens,
|
|
27
30
|
textLeafCount: () => textLeafCount
|
|
28
31
|
});
|
|
29
32
|
|
|
@@ -95,38 +98,107 @@ var __loticsProbe = (() => {
|
|
|
95
98
|
}
|
|
96
99
|
function reportedValues() {
|
|
97
100
|
const numeric = /^[-+−]?\d[\d.,]*\s?(?:%|[A-Za-z₫đ$€]{1,4})?$/;
|
|
101
|
+
const counted = /(?:%|[A-Za-z₫đ$€]{1,4})$/;
|
|
102
|
+
const delta = /^[↑↓→▲▼]\s*[-+−]?\d[\d.,]*\s?%$/;
|
|
98
103
|
const dateLike = /\d[/:-]\d/;
|
|
99
104
|
const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
|
|
100
105
|
const notAValue = 'a, button, [role="tab"], [role="button"], input, th, [role="columnheader"], nav';
|
|
101
106
|
const pictureMin = 32;
|
|
102
|
-
const
|
|
103
|
-
|
|
107
|
+
const nameReach = 360;
|
|
108
|
+
const values = [];
|
|
109
|
+
const labels = [];
|
|
110
|
+
let deltas = 0;
|
|
104
111
|
const walk = (node) => {
|
|
105
112
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
106
113
|
const text = (node.textContent || "").trim();
|
|
107
114
|
const parent = node.parentElement;
|
|
108
115
|
if (!text || !parent || skipTag.test(parent.tagName)) return;
|
|
109
116
|
if (typeof parent.checkVisibility === "function" && !parent.checkVisibility()) return;
|
|
110
|
-
|
|
117
|
+
const rect = parent.getBoundingClientRect();
|
|
118
|
+
if (rect.width === 0 && rect.height === 0) return;
|
|
119
|
+
if (delta.test(text)) {
|
|
120
|
+
deltas += 1;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const leaf = { text, left: Math.round(rect.left), right: Math.round(rect.right), top: Math.round(rect.top) };
|
|
124
|
+
if (!numeric.test(text) || dateLike.test(text)) {
|
|
125
|
+
labels.push(leaf);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
111
128
|
if (parent.closest(notAValue)) return;
|
|
112
|
-
|
|
113
|
-
if (samples.length < 6) samples.push(text);
|
|
129
|
+
values.push(leaf);
|
|
114
130
|
return;
|
|
115
131
|
}
|
|
116
132
|
node.childNodes.forEach(walk);
|
|
117
133
|
};
|
|
118
134
|
walk(document.body);
|
|
135
|
+
const clusters = [];
|
|
136
|
+
for (const leaf of [...values].sort((a, b) => a.left - b.left || a.top - b.top)) {
|
|
137
|
+
const cluster = clusters.find((c) => leaf.left < c.right && leaf.right > c.left);
|
|
138
|
+
if (cluster !== void 0) {
|
|
139
|
+
cluster.items.push(leaf);
|
|
140
|
+
cluster.left = Math.min(cluster.left, leaf.left);
|
|
141
|
+
cluster.right = Math.max(cluster.right, leaf.right);
|
|
142
|
+
} else {
|
|
143
|
+
clusters.push({ left: leaf.left, right: leaf.right, items: [leaf] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const columns = [];
|
|
147
|
+
let bare = 0;
|
|
148
|
+
let quantities = 0;
|
|
149
|
+
for (const cluster of clusters) {
|
|
150
|
+
const items = [...cluster.items].sort((a, b) => a.top - b.top);
|
|
151
|
+
const ordinal = items.length >= 3 && items.every((item, index) => item.text === String(index + 1));
|
|
152
|
+
if (ordinal) continue;
|
|
153
|
+
const top = items[0];
|
|
154
|
+
const above = labels.filter((label) => label.top < top.top && top.top - label.top <= nameReach && label.left < cluster.right && label.right > cluster.left).sort((a, b) => b.top - a.top)[0];
|
|
155
|
+
const beside = labels.filter((label) => Math.abs(label.top - top.top) <= 8 && label.right <= top.left).sort((a, b) => b.right - a.right)[0];
|
|
156
|
+
const scaled = items.filter((item) => counted.test(item.text)).length;
|
|
157
|
+
bare += items.length;
|
|
158
|
+
quantities += scaled;
|
|
159
|
+
columns.push({
|
|
160
|
+
slot: (above ?? beside)?.text.slice(0, 40) ?? "",
|
|
161
|
+
size: items.length,
|
|
162
|
+
quantities: scaled,
|
|
163
|
+
samples: items.slice(0, 4).map((item) => item.text)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
columns.sort((a, b) => b.size - a.size);
|
|
119
167
|
const drawn = Array.from(document.querySelectorAll('[role="meter"], [role="progressbar"], canvas')).filter((el) => {
|
|
120
168
|
const r = el.getBoundingClientRect();
|
|
121
169
|
return r.width > 0 && r.height > 0;
|
|
122
170
|
});
|
|
171
|
+
const reads = (el) => {
|
|
172
|
+
const role = el.getAttribute("role");
|
|
173
|
+
if (role !== "meter" && role !== "progressbar") return true;
|
|
174
|
+
const stated = (name) => {
|
|
175
|
+
const raw = el.getAttribute(name);
|
|
176
|
+
return raw === null || raw.trim() === "" ? Number.NaN : Number(raw);
|
|
177
|
+
};
|
|
178
|
+
const bound = (name, fallback) => {
|
|
179
|
+
const value = stated(name);
|
|
180
|
+
return Number.isFinite(value) ? value : fallback;
|
|
181
|
+
};
|
|
182
|
+
const now = stated("aria-valuenow");
|
|
183
|
+
if (!Number.isFinite(now)) return false;
|
|
184
|
+
const floor = bound("aria-valuemin", 0);
|
|
185
|
+
const top = bound("aria-valuemax", 100);
|
|
186
|
+
return top > floor && now < top;
|
|
187
|
+
};
|
|
123
188
|
const pictures = Array.from(document.querySelectorAll('[role="img"], svg, img')).filter((el) => {
|
|
124
189
|
if (el.getAttribute("aria-hidden") === "true") return false;
|
|
125
190
|
const r = el.getBoundingClientRect();
|
|
126
191
|
return r.width >= pictureMin && r.height >= pictureMin;
|
|
127
192
|
});
|
|
128
|
-
const encoded = drawn.length + pictures.length;
|
|
129
|
-
return {
|
|
193
|
+
const encoded = drawn.filter(reads).length + pictures.length + deltas;
|
|
194
|
+
return {
|
|
195
|
+
total: bare + encoded,
|
|
196
|
+
bare,
|
|
197
|
+
quantities,
|
|
198
|
+
encoded,
|
|
199
|
+
devices: drawn.length + pictures.length + deltas,
|
|
200
|
+
columns
|
|
201
|
+
};
|
|
130
202
|
}
|
|
131
203
|
function internalIds() {
|
|
132
204
|
const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
|
|
@@ -149,14 +221,21 @@ var __loticsProbe = (() => {
|
|
|
149
221
|
return { total, ids: [...ids] };
|
|
150
222
|
}
|
|
151
223
|
function tabStrips() {
|
|
152
|
-
const
|
|
224
|
+
const all = Array.from(document.querySelectorAll('[role="tablist"]')).filter((el) => {
|
|
153
225
|
const r = el.getBoundingClientRect();
|
|
154
226
|
return r.width > 0 && r.height > 0;
|
|
155
|
-
})
|
|
156
|
-
|
|
227
|
+
});
|
|
228
|
+
const counted = all.filter((el) => el.getAttribute("data-order") !== "sequence");
|
|
229
|
+
return {
|
|
230
|
+
total: all.length,
|
|
231
|
+
counted: counted.length,
|
|
232
|
+
strips: counted.map((el) => Array.from(el.querySelectorAll('[role="tab"]')).map((tab) => (tab.textContent || "").trim()))
|
|
233
|
+
};
|
|
157
234
|
}
|
|
158
235
|
function truncatedLeaves() {
|
|
159
236
|
const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
|
|
237
|
+
const clippedAway = /^inset\(\s*50%/;
|
|
238
|
+
const breakable = /[\t\n\v\f\r \u200b\u3000]/;
|
|
160
239
|
const cut = [];
|
|
161
240
|
let total = 0;
|
|
162
241
|
for (const el of Array.from(document.querySelectorAll("*"))) {
|
|
@@ -164,8 +243,17 @@ var __loticsProbe = (() => {
|
|
|
164
243
|
const text = (el.textContent || "").trim();
|
|
165
244
|
if (!text) continue;
|
|
166
245
|
if (typeof el.checkVisibility === "function" && !el.checkVisibility()) continue;
|
|
246
|
+
const style = getComputedStyle(el);
|
|
247
|
+
const box = el.getBoundingClientRect();
|
|
248
|
+
if (box.width <= 1 && box.height <= 1) continue;
|
|
249
|
+
if (clippedAway.test(style.getPropertyValue("clip-path"))) continue;
|
|
167
250
|
total += 1;
|
|
168
|
-
if (
|
|
251
|
+
if (style.overflow === "visible") continue;
|
|
252
|
+
const states = (property) => {
|
|
253
|
+
const value = style.getPropertyValue(property);
|
|
254
|
+
return value !== "" && value !== "none";
|
|
255
|
+
};
|
|
256
|
+
if ((states("-webkit-line-clamp") || states("line-clamp")) && breakable.test(text)) continue;
|
|
169
257
|
if (el.scrollWidth > el.clientWidth + 1) {
|
|
170
258
|
cut.push({ text: text.slice(0, 40), axis: "width", has: el.clientWidth, needs: el.scrollWidth });
|
|
171
259
|
} else if (el.scrollHeight > el.clientHeight + 1) {
|
|
@@ -224,6 +312,7 @@ var __loticsProbe = (() => {
|
|
|
224
312
|
if (depth === 0 || el.children.length === 0) return null;
|
|
225
313
|
return leafOf(el.children[0], depth - 1);
|
|
226
314
|
};
|
|
315
|
+
const sameColumn = 8;
|
|
227
316
|
let total = 0;
|
|
228
317
|
const overDemoted = [];
|
|
229
318
|
for (const el of Array.from(document.querySelectorAll("div"))) {
|
|
@@ -237,6 +326,7 @@ var __loticsProbe = (() => {
|
|
|
237
326
|
const a = first.getBoundingClientRect();
|
|
238
327
|
const b = second.getBoundingClientRect();
|
|
239
328
|
if (b.top < a.bottom - 2) continue;
|
|
329
|
+
if (Math.abs(b.left - a.left) > sameColumn) continue;
|
|
240
330
|
if (a.height === 0 || b.height === 0) continue;
|
|
241
331
|
total += 1;
|
|
242
332
|
if (px(supporting) < px(subject) && weight(subject) > weight(supporting)) {
|
|
@@ -301,9 +391,59 @@ var __loticsProbe = (() => {
|
|
|
301
391
|
}
|
|
302
392
|
|
|
303
393
|
// src/probe_page.ts
|
|
394
|
+
var PROBES = {
|
|
395
|
+
money_edges: {
|
|
396
|
+
section: "6",
|
|
397
|
+
fix: "One right edge per money column: give every figure in it the same alignment and the same unit."
|
|
398
|
+
},
|
|
399
|
+
reported_values: {
|
|
400
|
+
section: "9",
|
|
401
|
+
fix: "Encode the column the screen is ABOUT \u2014 a mark, a meter, a chart or a picture \u2014 and leave the rest as text; a meter whose maximum is the bound it alarms at reads the same on every row, so it encodes nothing."
|
|
402
|
+
},
|
|
403
|
+
internal_ids: {
|
|
404
|
+
section: "8",
|
|
405
|
+
fix: "A key is not a name: render the record's own label, and keep the id in the link."
|
|
406
|
+
},
|
|
407
|
+
tab_strips: {
|
|
408
|
+
section: "8a",
|
|
409
|
+
fix: "One navigation strip per screen. A second is a filter wearing navigation's clothes \u2014 draw it as one."
|
|
410
|
+
},
|
|
411
|
+
truncation: {
|
|
412
|
+
section: "8g",
|
|
413
|
+
fix: "Shed a trailing column, or widen this one until the value fits \u2014 a date, a code or a figure cut inside itself reads as a different one."
|
|
414
|
+
},
|
|
415
|
+
banned_glyph: {
|
|
416
|
+
section: "10",
|
|
417
|
+
fix: "No punctuation doing a word's job \u2014 separate the two facts with layout, or write the word."
|
|
418
|
+
},
|
|
419
|
+
compact_money_words: {
|
|
420
|
+
section: "10",
|
|
421
|
+
fix: "Abbreviate in the currency's own words: tr/t\u1EF7 beside \u20AB, K/M/B beside $ and \u20AC."
|
|
422
|
+
},
|
|
423
|
+
stacked_pairs: {
|
|
424
|
+
section: "1",
|
|
425
|
+
fix: "Demote once, not twice: either the size steps down or the subject gets the weight."
|
|
426
|
+
},
|
|
427
|
+
row_marks: {
|
|
428
|
+
section: "9",
|
|
429
|
+
fix: "One rung per row: a mark's size follows what the row is ABOUT, so the supporting mark takes the smaller one."
|
|
430
|
+
},
|
|
431
|
+
render: {
|
|
432
|
+
section: "8",
|
|
433
|
+
fix: "Open the screen and read it: nothing below this was measured, so a clean run over the rest means nothing."
|
|
434
|
+
},
|
|
435
|
+
settle: {
|
|
436
|
+
section: "9c",
|
|
437
|
+
fix: "A screen that never stops changing has a render that re-renders or a request that never lands \u2014 find which."
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
function signature(probe) {
|
|
441
|
+
return PROBES[probe];
|
|
442
|
+
}
|
|
304
443
|
var MONEY_EDGES_ALLOWED = 2;
|
|
305
444
|
var MONEY_COLUMN_MIN = 3;
|
|
306
445
|
var BARE_VALUES_FLOOR = 8;
|
|
446
|
+
var QUANTITY_COLUMN_MIN = 2;
|
|
307
447
|
function run(options) {
|
|
308
448
|
const findings = [];
|
|
309
449
|
const money = moneyColumns();
|
|
@@ -311,30 +451,37 @@ var __loticsProbe = (() => {
|
|
|
311
451
|
if (ragged.length > 0) {
|
|
312
452
|
findings.push({
|
|
313
453
|
probe: "money_edges",
|
|
454
|
+
...signature("money_edges"),
|
|
314
455
|
total: money.total,
|
|
315
456
|
count: ragged.length,
|
|
316
457
|
detail: ragged.slice(0, 5).map((column) => `x=${column.x}: ${column.edges.length} right edges over ${column.size} figures \u2014 ${column.samples.join(" | ")}`)
|
|
317
458
|
});
|
|
318
459
|
}
|
|
319
460
|
const values = reportedValues();
|
|
320
|
-
|
|
461
|
+
const measurable = values.columns.some((column) => column.quantities >= QUANTITY_COLUMN_MIN) || values.devices > 0;
|
|
462
|
+
if (values.bare >= BARE_VALUES_FLOOR && values.encoded === 0 && measurable) {
|
|
321
463
|
findings.push({
|
|
322
464
|
probe: "reported_values",
|
|
465
|
+
...signature("reported_values"),
|
|
323
466
|
total: values.total,
|
|
324
467
|
count: values.bare,
|
|
325
|
-
detail: [
|
|
468
|
+
detail: [
|
|
469
|
+
values.devices === 0 ? `${values.bare} values as bare text, nothing drawn as a mark, meter, chart or picture` : `${values.bare} values as bare text; ${values.devices} drawn, none of them reading a value`,
|
|
470
|
+
...values.columns.slice(0, 5).map((column) => ` ${column.slot === "" ? "(unnamed)" : column.slot}: ${column.size} \u2014 ${column.samples.join(", ")}`)
|
|
471
|
+
]
|
|
326
472
|
});
|
|
327
473
|
}
|
|
328
474
|
const ids = internalIds();
|
|
329
475
|
if (ids.ids.length > 0) {
|
|
330
|
-
findings.push({ probe: "internal_ids", total: ids.total, count: ids.ids.length, detail: ids.ids.slice(0, 5) });
|
|
476
|
+
findings.push({ probe: "internal_ids", ...signature("internal_ids"), total: ids.total, count: ids.ids.length, detail: ids.ids.slice(0, 5) });
|
|
331
477
|
}
|
|
332
478
|
const strips = tabStrips();
|
|
333
|
-
if (strips.
|
|
479
|
+
if (strips.counted > 1) {
|
|
334
480
|
findings.push({
|
|
335
481
|
probe: "tab_strips",
|
|
482
|
+
...signature("tab_strips"),
|
|
336
483
|
total: strips.total,
|
|
337
|
-
count: strips.
|
|
484
|
+
count: strips.counted,
|
|
338
485
|
detail: strips.strips.map((strip) => strip.join(" / "))
|
|
339
486
|
});
|
|
340
487
|
}
|
|
@@ -343,6 +490,7 @@ var __loticsProbe = (() => {
|
|
|
343
490
|
if (cut.cut.length > 0) {
|
|
344
491
|
findings.push({
|
|
345
492
|
probe: "truncation",
|
|
493
|
+
...signature("truncation"),
|
|
346
494
|
total: cut.total,
|
|
347
495
|
count: cut.cut.length,
|
|
348
496
|
detail: cut.cut.slice(0, 5).map((leaf) => `"${leaf.text}" needs ${leaf.needs}px of ${leaf.axis} in ${leaf.has}px`)
|
|
@@ -351,12 +499,13 @@ var __loticsProbe = (() => {
|
|
|
351
499
|
}
|
|
352
500
|
const glyphs = bannedGlyphs();
|
|
353
501
|
if (glyphs.found.length > 0) {
|
|
354
|
-
findings.push({ probe: "banned_glyph", total: glyphs.total, count: glyphs.found.length, detail: glyphs.found.slice(0, 5) });
|
|
502
|
+
findings.push({ probe: "banned_glyph", ...signature("banned_glyph"), total: glyphs.total, count: glyphs.found.length, detail: glyphs.found.slice(0, 5) });
|
|
355
503
|
}
|
|
356
504
|
const compact = compactMoneyWords();
|
|
357
505
|
if (compact.foreign.length > 0) {
|
|
358
506
|
findings.push({
|
|
359
507
|
probe: "compact_money_words",
|
|
508
|
+
...signature("compact_money_words"),
|
|
360
509
|
total: compact.total,
|
|
361
510
|
count: compact.foreign.length,
|
|
362
511
|
detail: compact.foreign.slice(0, 5).map((text) => `"${text}" abbreviates in the other currency's words`)
|
|
@@ -366,6 +515,7 @@ var __loticsProbe = (() => {
|
|
|
366
515
|
if (pairs.overDemoted.length > 0) {
|
|
367
516
|
findings.push({
|
|
368
517
|
probe: "stacked_pairs",
|
|
518
|
+
...signature("stacked_pairs"),
|
|
369
519
|
total: pairs.total,
|
|
370
520
|
count: pairs.overDemoted.length,
|
|
371
521
|
detail: pairs.overDemoted.slice(0, 5).map((pair) => `"${pair.subject}" ${pair.sizes[0]}px/${pair.weights[0]} over "${pair.supporting}" ${pair.sizes[1]}px/${pair.weights[1]}`)
|
|
@@ -375,6 +525,7 @@ var __loticsProbe = (() => {
|
|
|
375
525
|
if (marks.mismatches.length > 0) {
|
|
376
526
|
findings.push({
|
|
377
527
|
probe: "row_marks",
|
|
528
|
+
...signature("row_marks"),
|
|
378
529
|
total: marks.total,
|
|
379
530
|
count: marks.mismatches.length,
|
|
380
531
|
detail: marks.mismatches.slice(0, 5).map((row) => `y=${row.y}: ${row.marks.join(", ")}`)
|
|
@@ -386,8 +537,10 @@ var __loticsProbe = (() => {
|
|
|
386
537
|
leaves: textLeafCount(),
|
|
387
538
|
money: money.total,
|
|
388
539
|
bare: values.bare,
|
|
540
|
+
quantities: values.quantities,
|
|
389
541
|
encoded: values.encoded,
|
|
390
|
-
|
|
542
|
+
devices: values.devices,
|
|
543
|
+
strips: strips.counted
|
|
391
544
|
}
|
|
392
545
|
};
|
|
393
546
|
}
|
|
@@ -405,16 +558,53 @@ var __loticsProbe = (() => {
|
|
|
405
558
|
walk(document.body);
|
|
406
559
|
return count;
|
|
407
560
|
}
|
|
408
|
-
function
|
|
561
|
+
function navItems() {
|
|
562
|
+
const nav = document.querySelector("nav");
|
|
563
|
+
if (nav === null) return [];
|
|
564
|
+
return Array.from(nav.querySelectorAll('a[href], [role="link"]')).filter(
|
|
565
|
+
(item) => (item.textContent || "").trim() !== ""
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
function tabItems() {
|
|
409
569
|
const strip = document.querySelector('[role="tablist"]');
|
|
410
570
|
if (strip === null) return [];
|
|
411
|
-
return Array.from(strip.querySelectorAll('[role="tab"]'))
|
|
571
|
+
return Array.from(strip.querySelectorAll('[role="tab"]'));
|
|
412
572
|
}
|
|
413
|
-
function
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
573
|
+
function screens() {
|
|
574
|
+
const nav = navItems();
|
|
575
|
+
if (nav.length > 0) return { source: "nav", labels: nav.map((item) => (item.textContent || "").trim()) };
|
|
576
|
+
const tabs = tabItems();
|
|
577
|
+
if (tabs.length > 0) return { source: "tabs", labels: tabs.map((item) => (item.textContent || "").trim()) };
|
|
578
|
+
return { source: "root", labels: [] };
|
|
579
|
+
}
|
|
580
|
+
function openScreen(source, index) {
|
|
581
|
+
const items = source === "nav" ? navItems() : source === "tabs" ? tabItems() : [];
|
|
582
|
+
const item = items[index];
|
|
583
|
+
if (item === void 0) return false;
|
|
584
|
+
item.click();
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
function recordLinks() {
|
|
588
|
+
const here = location.pathname + location.search;
|
|
589
|
+
const doors = Array.from(document.querySelectorAll('a[href], [data-opens="page"]'));
|
|
590
|
+
return doors.filter((door) => {
|
|
591
|
+
if (door.closest('nav, [role="banner"], [role="tablist"]') !== null) return false;
|
|
592
|
+
if (doors.some((other) => other !== door && other.contains(door))) return false;
|
|
593
|
+
const rect = door.getBoundingClientRect();
|
|
594
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
595
|
+
const href = door.getAttribute("href");
|
|
596
|
+
if (href === null) return true;
|
|
597
|
+
if (href === "" || href.startsWith("http") || href.startsWith("#")) return false;
|
|
598
|
+
return href !== here;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
function recordRows() {
|
|
602
|
+
return recordLinks().map((door) => (door.textContent || "").trim() || door.getAttribute("href") || "");
|
|
603
|
+
}
|
|
604
|
+
function openRecord(index) {
|
|
605
|
+
const door = recordLinks()[index];
|
|
606
|
+
if (door === void 0) return false;
|
|
607
|
+
door.click();
|
|
418
608
|
return true;
|
|
419
609
|
}
|
|
420
610
|
return __toCommonJS(probe_page_exports);
|