@lotics/cli 0.190.0 → 0.191.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.
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __loticsProbe = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/probe_page.ts
22
+ var probe_page_exports = {};
23
+ __export(probe_page_exports, {
24
+ pressTab: () => pressTab,
25
+ run: () => run,
26
+ tabs: () => tabs,
27
+ textLeafCount: () => textLeafCount
28
+ });
29
+
30
+ // src/probe_collectors.ts
31
+ function moneyColumns() {
32
+ const money = /^[-+−]?\s?(?:(?:[$€]|USD|EUR|VND)\s?[\d.,]+\s?(?:[KMB]|tr|tỷ)?|[\d.,]+\s?(?:tr|tỷ)?\s?(?:₫|đ|VND|USD|EUR|[$€]))$/i;
33
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
34
+ const bandGap = 48;
35
+ const leaves = [];
36
+ const walk = (node) => {
37
+ if (node.nodeType === Node.TEXT_NODE) {
38
+ const text = (node.textContent || "").trim();
39
+ const parent = node.parentElement;
40
+ if (!text || !parent || skipTag.test(parent.tagName)) return;
41
+ if (typeof parent.checkVisibility === "function" && !parent.checkVisibility()) return;
42
+ if (!money.test(text)) return;
43
+ const range = document.createRange();
44
+ range.selectNodeContents(node);
45
+ let rect = range.getBoundingClientRect();
46
+ if (rect.width === 0 && rect.height === 0) rect = parent.getBoundingClientRect();
47
+ if (rect.width === 0 && rect.height === 0) return;
48
+ leaves.push({
49
+ text,
50
+ left: Math.round(rect.left),
51
+ right: Math.round(rect.right),
52
+ top: Math.round(rect.top),
53
+ bottom: Math.round(rect.bottom)
54
+ });
55
+ return;
56
+ }
57
+ node.childNodes.forEach(walk);
58
+ };
59
+ walk(document.body);
60
+ const bands = [];
61
+ for (const leaf of [...leaves].sort((a, b) => a.top - b.top)) {
62
+ const band = bands[bands.length - 1];
63
+ if (band !== void 0 && leaf.top - band.bottom <= bandGap) {
64
+ band.items.push(leaf);
65
+ band.bottom = Math.max(band.bottom, leaf.bottom);
66
+ } else {
67
+ bands.push({ bottom: leaf.bottom, items: [leaf] });
68
+ }
69
+ }
70
+ const columns = [];
71
+ for (const band of bands) {
72
+ const clusters = [];
73
+ for (const leaf of [...band.items].sort((a, b) => a.left - b.left)) {
74
+ const cluster = clusters[clusters.length - 1];
75
+ if (cluster !== void 0 && leaf.left < cluster.right) {
76
+ cluster.items.push(leaf);
77
+ cluster.right = Math.max(cluster.right, leaf.right);
78
+ } else {
79
+ clusters.push({ left: leaf.left, right: leaf.right, items: [leaf] });
80
+ }
81
+ }
82
+ for (const cluster of clusters) {
83
+ const firstByEdge = /* @__PURE__ */ new Map();
84
+ for (const item of cluster.items) if (!firstByEdge.has(item.right)) firstByEdge.set(item.right, item.text);
85
+ const edges = [...firstByEdge.entries()].sort((a, b) => a[0] - b[0]);
86
+ columns.push({
87
+ x: cluster.left,
88
+ size: cluster.items.length,
89
+ edges: edges.map(([edge]) => edge),
90
+ samples: edges.map(([, text]) => text)
91
+ });
92
+ }
93
+ }
94
+ return { total: leaves.length, columns };
95
+ }
96
+ function reportedValues() {
97
+ const numeric = /^[-+−]?\d[\d.,]*\s?(?:%|[A-Za-z₫đ$€]{1,4})?$/;
98
+ const dateLike = /\d[/:-]\d/;
99
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
100
+ const notAValue = 'a, button, [role="tab"], [role="button"], input, th, [role="columnheader"], nav';
101
+ const pictureMin = 32;
102
+ const samples = [];
103
+ let bare = 0;
104
+ const walk = (node) => {
105
+ if (node.nodeType === Node.TEXT_NODE) {
106
+ const text = (node.textContent || "").trim();
107
+ const parent = node.parentElement;
108
+ if (!text || !parent || skipTag.test(parent.tagName)) return;
109
+ if (typeof parent.checkVisibility === "function" && !parent.checkVisibility()) return;
110
+ if (!numeric.test(text) || dateLike.test(text)) return;
111
+ if (parent.closest(notAValue)) return;
112
+ bare += 1;
113
+ if (samples.length < 6) samples.push(text);
114
+ return;
115
+ }
116
+ node.childNodes.forEach(walk);
117
+ };
118
+ walk(document.body);
119
+ const drawn = Array.from(document.querySelectorAll('[role="meter"], [role="progressbar"], canvas')).filter((el) => {
120
+ const r = el.getBoundingClientRect();
121
+ return r.width > 0 && r.height > 0;
122
+ });
123
+ const pictures = Array.from(document.querySelectorAll('[role="img"], svg, img')).filter((el) => {
124
+ if (el.getAttribute("aria-hidden") === "true") return false;
125
+ const r = el.getBoundingClientRect();
126
+ return r.width >= pictureMin && r.height >= pictureMin;
127
+ });
128
+ const encoded = drawn.length + pictures.length;
129
+ return { total: bare + encoded, bare, encoded, samples };
130
+ }
131
+ function internalIds() {
132
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
133
+ const id = /\b(?:opt|fld|tbl|rec|wfl|mbr|app|wsp)_[A-Za-z0-9]{6,}\b/g;
134
+ const ids = /* @__PURE__ */ new Set();
135
+ let total = 0;
136
+ const walk = (node) => {
137
+ if (node.nodeType === Node.TEXT_NODE) {
138
+ const parent = node.parentElement;
139
+ if (!parent || skipTag.test(parent.tagName)) return;
140
+ const text = node.textContent || "";
141
+ if (!text.trim()) return;
142
+ total += 1;
143
+ for (const match of text.match(id) ?? []) ids.add(match);
144
+ return;
145
+ }
146
+ node.childNodes.forEach(walk);
147
+ };
148
+ walk(document.body);
149
+ return { total, ids: [...ids] };
150
+ }
151
+ function tabStrips() {
152
+ const strips = Array.from(document.querySelectorAll('[role="tablist"]')).filter((el) => {
153
+ const r = el.getBoundingClientRect();
154
+ return r.width > 0 && r.height > 0;
155
+ }).map((el) => Array.from(el.querySelectorAll('[role="tab"]')).map((tab) => (tab.textContent || "").trim()));
156
+ return { total: strips.length, strips };
157
+ }
158
+ function truncatedLeaves() {
159
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
160
+ const cut = [];
161
+ let total = 0;
162
+ for (const el of Array.from(document.querySelectorAll("*"))) {
163
+ if (skipTag.test(el.tagName) || el.children.length > 0) continue;
164
+ const text = (el.textContent || "").trim();
165
+ if (!text) continue;
166
+ if (typeof el.checkVisibility === "function" && !el.checkVisibility()) continue;
167
+ total += 1;
168
+ if (getComputedStyle(el).overflow === "visible") continue;
169
+ if (el.scrollWidth > el.clientWidth + 1) {
170
+ cut.push({ text: text.slice(0, 40), axis: "width", has: el.clientWidth, needs: el.scrollWidth });
171
+ } else if (el.scrollHeight > el.clientHeight + 1) {
172
+ cut.push({ text: text.slice(0, 40), axis: "height", has: el.clientHeight, needs: el.scrollHeight });
173
+ }
174
+ }
175
+ return { total, cut };
176
+ }
177
+ function bannedGlyphs() {
178
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
179
+ const found = [];
180
+ let total = 0;
181
+ const walk = (node) => {
182
+ if (node.nodeType === Node.TEXT_NODE) {
183
+ const parent = node.parentElement;
184
+ if (!parent || skipTag.test(parent.tagName)) return;
185
+ const text = node.textContent || "";
186
+ if (!text.trim()) return;
187
+ total += 1;
188
+ if (text.includes(" \xB7 ")) found.push(text.trim().slice(0, 40));
189
+ return;
190
+ }
191
+ node.childNodes.forEach(walk);
192
+ };
193
+ walk(document.body);
194
+ return { total, found };
195
+ }
196
+ function compactMoneyWords() {
197
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
198
+ const any = /\d[\d.,]*\s?(?:[KMB](?![a-z])|tr(?![a-z])|tỷ)/i;
199
+ const dongInEnglishWords = /\d[\d.,]*\s?[KMB]\s?(?:₫|đ|VND)|(?:₫|VND)\s?\d[\d.,]*\s?[KMB](?![a-z])/i;
200
+ const dollarInVietnameseWords = /[$€]\s?\d[\d.,]*\s?(?:tr(?![a-z])|tỷ)|\d[\d.,]*\s?(?:tr|tỷ)\s?(?:[$€]|USD|EUR)(?![a-z])/i;
201
+ const foreign = [];
202
+ let total = 0;
203
+ const walk = (node) => {
204
+ if (node.nodeType === Node.TEXT_NODE) {
205
+ const parent = node.parentElement;
206
+ if (!parent || skipTag.test(parent.tagName)) return;
207
+ const text = (node.textContent || "").trim();
208
+ if (!text) return;
209
+ if (any.test(text)) total += 1;
210
+ if (dongInEnglishWords.test(text) || dollarInVietnameseWords.test(text)) foreign.push(text.slice(0, 40));
211
+ return;
212
+ }
213
+ node.childNodes.forEach(walk);
214
+ };
215
+ walk(document.body);
216
+ return { total, foreign };
217
+ }
218
+ function scanStackedPairs() {
219
+ const px = (el) => parseFloat(getComputedStyle(el).fontSize);
220
+ const weight = (el) => parseInt(getComputedStyle(el).fontWeight, 10) || 400;
221
+ const isLeaf = (el) => el.children.length === 0 && (el.textContent || "").trim().length > 0;
222
+ const leafOf = (el, depth = 3) => {
223
+ if (isLeaf(el)) return el;
224
+ if (depth === 0 || el.children.length === 0) return null;
225
+ return leafOf(el.children[0], depth - 1);
226
+ };
227
+ let total = 0;
228
+ const overDemoted = [];
229
+ for (const el of Array.from(document.querySelectorAll("div"))) {
230
+ const kids = Array.from(el.children);
231
+ if (kids.length !== 2) continue;
232
+ const [first, second] = kids;
233
+ const subject = leafOf(first);
234
+ const supporting = leafOf(second);
235
+ if (!subject || !supporting) continue;
236
+ if (first.closest('[role="banner"]')) continue;
237
+ const a = first.getBoundingClientRect();
238
+ const b = second.getBoundingClientRect();
239
+ if (b.top < a.bottom - 2) continue;
240
+ if (a.height === 0 || b.height === 0) continue;
241
+ total += 1;
242
+ if (px(supporting) < px(subject) && weight(subject) > weight(supporting)) {
243
+ overDemoted.push({
244
+ subject: (subject.textContent || "").trim().slice(0, 60),
245
+ supporting: (supporting.textContent || "").trim().slice(0, 60),
246
+ sizes: [px(subject), px(supporting)],
247
+ weights: [weight(subject), weight(supporting)]
248
+ });
249
+ }
250
+ }
251
+ return { total, overDemoted };
252
+ }
253
+ function scanRowMarks() {
254
+ const marks = [];
255
+ for (const el of Array.from(document.querySelectorAll("div"))) {
256
+ const cs = getComputedStyle(el);
257
+ const r = el.getBoundingClientRect();
258
+ if (r.width < 12 || r.width > 56) continue;
259
+ if (Math.abs(r.width - r.height) > 1) continue;
260
+ if ((parseFloat(cs.borderTopLeftRadius) || 0) < 3) continue;
261
+ if (cs.backgroundColor === "rgba(0, 0, 0, 0)") continue;
262
+ const text = (el.textContent || "").trim();
263
+ if (text.length === 0 || text.length > 2) continue;
264
+ marks.push({ text, w: Math.round(r.width), y: Math.round(r.y), x: Math.round(r.x) });
265
+ }
266
+ const byRow = /* @__PURE__ */ new Map();
267
+ for (const m of marks) {
268
+ const band = Math.round(m.y / 20) * 20;
269
+ const list = byRow.get(band);
270
+ if (list) list.push(m);
271
+ else byRow.set(band, [m]);
272
+ }
273
+ const mismatches = [];
274
+ for (const [band, row] of byRow) {
275
+ const widths = new Set(row.map((m) => m.w));
276
+ if (widths.size <= 1) continue;
277
+ mismatches.push({ y: band, marks: row.sort((a, b) => a.x - b.x).map((m) => `${m.text}:${m.w}px`) });
278
+ }
279
+ return { total: marks.length, mismatches };
280
+ }
281
+
282
+ // src/probe_page.ts
283
+ var MONEY_EDGES_ALLOWED = 2;
284
+ var MONEY_COLUMN_MIN = 3;
285
+ var BARE_VALUES_FLOOR = 8;
286
+ function run(options) {
287
+ const findings = [];
288
+ const money = moneyColumns();
289
+ const ragged = money.columns.filter((column) => column.size >= MONEY_COLUMN_MIN && column.edges.length > MONEY_EDGES_ALLOWED);
290
+ if (ragged.length > 0) {
291
+ findings.push({
292
+ probe: "money_edges",
293
+ total: money.total,
294
+ count: ragged.length,
295
+ detail: ragged.slice(0, 5).map((column) => `x=${column.x}: ${column.edges.length} right edges over ${column.size} figures \u2014 ${column.samples.join(" | ")}`)
296
+ });
297
+ }
298
+ const values = reportedValues();
299
+ if (values.bare >= BARE_VALUES_FLOOR && values.encoded === 0) {
300
+ findings.push({
301
+ probe: "reported_values",
302
+ total: values.total,
303
+ count: values.bare,
304
+ detail: [`${values.bare} values as bare text, 0 as a mark, meter, chart or picture \u2014 e.g. ${values.samples.join(", ")}`]
305
+ });
306
+ }
307
+ const ids = internalIds();
308
+ if (ids.ids.length > 0) {
309
+ findings.push({ probe: "internal_ids", total: ids.total, count: ids.ids.length, detail: ids.ids.slice(0, 5) });
310
+ }
311
+ const strips = tabStrips();
312
+ if (strips.total > 1) {
313
+ findings.push({
314
+ probe: "tab_strips",
315
+ total: strips.total,
316
+ count: strips.total,
317
+ detail: strips.strips.map((strip) => strip.join(" / "))
318
+ });
319
+ }
320
+ if (options.truncation) {
321
+ const cut = truncatedLeaves();
322
+ if (cut.cut.length > 0) {
323
+ findings.push({
324
+ probe: "truncation",
325
+ total: cut.total,
326
+ count: cut.cut.length,
327
+ detail: cut.cut.slice(0, 5).map((leaf) => `"${leaf.text}" needs ${leaf.needs}px of ${leaf.axis} in ${leaf.has}px`)
328
+ });
329
+ }
330
+ }
331
+ const glyphs = bannedGlyphs();
332
+ if (glyphs.found.length > 0) {
333
+ findings.push({ probe: "banned_glyph", total: glyphs.total, count: glyphs.found.length, detail: glyphs.found.slice(0, 5) });
334
+ }
335
+ const compact = compactMoneyWords();
336
+ if (compact.foreign.length > 0) {
337
+ findings.push({
338
+ probe: "compact_money_words",
339
+ total: compact.total,
340
+ count: compact.foreign.length,
341
+ detail: compact.foreign.slice(0, 5).map((text) => `"${text}" abbreviates in the other currency's words`)
342
+ });
343
+ }
344
+ const pairs = scanStackedPairs();
345
+ if (pairs.overDemoted.length > 0) {
346
+ findings.push({
347
+ probe: "stacked_pairs",
348
+ total: pairs.total,
349
+ count: pairs.overDemoted.length,
350
+ 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]}`)
351
+ });
352
+ }
353
+ const marks = scanRowMarks();
354
+ if (marks.mismatches.length > 0) {
355
+ findings.push({
356
+ probe: "row_marks",
357
+ total: marks.total,
358
+ count: marks.mismatches.length,
359
+ detail: marks.mismatches.slice(0, 5).map((row) => `y=${row.y}: ${row.marks.join(", ")}`)
360
+ });
361
+ }
362
+ return {
363
+ findings,
364
+ census: {
365
+ leaves: textLeafCount(),
366
+ money: money.total,
367
+ bare: values.bare,
368
+ encoded: values.encoded,
369
+ strips: strips.total
370
+ }
371
+ };
372
+ }
373
+ function textLeafCount() {
374
+ const skipTag = /^(SCRIPT|STYLE|TITLE|META|LINK|NOSCRIPT|TEMPLATE)$/;
375
+ let count = 0;
376
+ const walk = (node) => {
377
+ if (node.nodeType === Node.TEXT_NODE) {
378
+ const parent = node.parentElement;
379
+ if (parent && !skipTag.test(parent.tagName) && (node.textContent || "").trim()) count += 1;
380
+ return;
381
+ }
382
+ node.childNodes.forEach(walk);
383
+ };
384
+ walk(document.body);
385
+ return count;
386
+ }
387
+ function tabs() {
388
+ const strip = document.querySelector('[role="tablist"]');
389
+ if (strip === null) return [];
390
+ return Array.from(strip.querySelectorAll('[role="tab"]')).map((tab) => (tab.textContent || "").trim());
391
+ }
392
+ function pressTab(index) {
393
+ const strip = document.querySelector('[role="tablist"]');
394
+ const tab = strip?.querySelectorAll('[role="tab"]')[index];
395
+ if (!(tab instanceof HTMLElement)) return false;
396
+ tab.click();
397
+ return true;
398
+ }
399
+ return __toCommonJS(probe_page_exports);
400
+ })();