@lotics/cli 0.189.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.
- package/AGENTS.md +11 -6
- package/README.md +13 -3
- package/dist/probe_page.js +400 -0
- package/dist/src/cli.js +1507 -490
- package/dist/src/client.d.ts +35 -0
- package/dist/src/client.js +15 -0
- package/docs/building_an_app.md +14 -5
- package/docs/cli_reference.md +8 -7
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ conventions are, and where the traps are.
|
|
|
8
8
|
| `lotics --help` | The verb inventory (§ COMMANDS) and global flags. The verb LIST is generated and never stale; the prose beside each verb is hand-written, so where it disagrees with `docs/cli_reference.md`, the reference wins. |
|
|
9
9
|
| `lotics tools` · `lotics tools <name>` | The agent tool registry and one tool's full JSON Schema. |
|
|
10
10
|
| `lotics docs` · `lotics docs <area>` | Every reference the packages installed beside the project actually ship — `@lotics/app-sdk`, `@lotics/ui` and the document engines each carry their own, and this index only covers THIS package. Discovered by looking, not by a list, so it reports the installed VERSION of each: a doc always describes the code that is really there. |
|
|
11
|
-
| `lotics scaffold docs` | How to write a `model.json` — the file a workspace is built from. Two forms: `{"from": "<preset-slug>", "variants", "rename", "entities", "rows", "apply"}`, which names a preset by slug and carries only what this business differs by, and the full one, spelled out, for when no preset is the trade: every top-level key, every field type with the config it needs, the row format, the rules, and a worked example. Offline, and not part of `lotics docs`. It also covers the `apply` list — published packages copied in after the model's own tables, each with an optional `bind` onto them — and the `preset` block a published model carries. `lotics scaffold check` then proves the file — offline, except for the one read a `from` file's preset needs — including every branch of a preset merged onto its base; `lotics setup` applies it and refuses a table name the workspace already has, `lotics scaffold apply` adopts that table and adds what is missing. |
|
|
11
|
+
| `lotics scaffold docs` | How to write a `model.json` — the file a workspace is built from. Two forms: `{"from": "<preset-slug>", "variants", "rename", "entities", "rows", "apply"}`, which names a preset by slug and carries only what this business differs by, and the full one, spelled out, for when no preset is the trade: every top-level key, every field type with the config it needs, the row format, the rules, and a worked example. Offline, and not part of `lotics docs`. It also covers the `apply` list — published packages copied in after the model's own tables, each with an optional `bind` onto them — and the `preset` block a published model carries. `lotics scaffold check` then proves the file — offline, except for the one read a `from` file's preset needs — including every branch of a preset merged onto its base; `lotics setup` applies it and refuses a table name the workspace already has, `lotics scaffold apply` adopts that table and adds what is missing. `lotics scaffold export` goes the other way — a workspace printed as one of these files, to edit into another business's model; a starting point, never a source of truth. |
|
|
12
12
|
| [docs/building_an_app.md](./docs/building_an_app.md) | The SEQUENCE — scaffold, model, types, queries, workflows, screens, ship — and the deploy-free inner loop. The other references describe contracts; this one is the order they go in and why. Read it once before starting an app. |
|
|
13
13
|
| [docs/cli_reference.md](./docs/cli_reference.md) | Per-command contracts, flags, exit codes, and gotchas — the detail `--help` compresses. Read it before hand-building a `set_app_*` payload: several tools REPLACE rather than patch, and a CLI verb already owns the safe assembly. |
|
|
14
14
|
| [docs/data_model.md](./docs/data_model.md) | How tables RELATE — one entity per table and the NAME-OVERLAP probe that says when a split has broken, one vocabulary wherever values are copied between tables, a copy boundary that accounts for every source field, provenance as a link rather than a flag, a declared natural key so find-or-create never compares rendered text, and why derived DEPTH costs more than row count. Separate from building_an_app because every workspace starts with tables and many never get an app. The within-table half (one fact, one column) is stated at `create_table` / `update_table`, where you meet it while deciding. |
|
|
@@ -42,11 +42,16 @@ whether something exists, read `lotics --help` § COMMANDS — the whole section
|
|
|
42
42
|
from a shell serving many. Every command that resolves a workspace echoes its target to **stderr**
|
|
43
43
|
(`lotics → <org> / <workspace>`); read it back before trusting a write. `file preview <fil_…>` is
|
|
44
44
|
the one credentialed command with no echo. Resolution precedence is in README § Organizations.
|
|
45
|
-
- **A machine with no key can still sign in
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
- **A machine with no key can still sign in, and the sign-in never blocks you.** `lotics auth
|
|
46
|
+
login <email>` prints the page a person opens (also mailed) and the code that page must show,
|
|
47
|
+
records the request, and EXITS. They press Confirm whenever they get to it; the next command that
|
|
48
|
+
NEEDS a credential collects the key before doing its own work, so "sign in" costs you one command
|
|
49
|
+
and then re-running what you wanted. Never wrap it in a timeout waiting for a human — `--wait`
|
|
50
|
+
exists if you really want one blocking command, and killing that one is safe too (the request
|
|
51
|
+
survives and the next command claims it). A command run before Confirm exits 1 naming the page and
|
|
52
|
+
code again; once the 15 minutes are up it says to ask again. `lotics setup` falls into the same
|
|
53
|
+
flow by itself when the email it was given already has an account: it stops having created
|
|
54
|
+
nothing, and the SAME command run again carries on.
|
|
50
55
|
- **Large payloads bypass `ARG_MAX`** — `lotics run <tool> @args.json` or piped stdin. A leading `@` is
|
|
51
56
|
unambiguously a file path (JSON args start with `{`).
|
|
52
57
|
- **stdout is the payload, stderr is the narration.** Progress, status lines, and the target echo go to
|
package/README.md
CHANGED
|
@@ -91,6 +91,10 @@ copied in — bound onto the tables the model just made — as part of the same
|
|
|
91
91
|
`setup` refuses a table name your workspace already has; `lotics scaffold apply model.json` is the
|
|
92
92
|
additive verb — it adopts that table, adds what the model declares beyond it, and deletes nothing.
|
|
93
93
|
|
|
94
|
+
The other direction, once a workspace works: `lotics scaffold export > model.json` prints its
|
|
95
|
+
tables (or only `--tables tbl_a,tbl_b`) as that same file, findings on stderr. It is a starting
|
|
96
|
+
point for the next business, never a source of truth — the labels are this one's.
|
|
97
|
+
|
|
94
98
|
## Install
|
|
95
99
|
|
|
96
100
|
```bash
|
|
@@ -118,13 +122,19 @@ lotics auth signup # interactive prompts
|
|
|
118
122
|
lotics auth signup a@b.com --name "Agent" # non-interactive
|
|
119
123
|
```
|
|
120
124
|
|
|
121
|
-
**`lotics auth login`** — Signs in an account that already exists, on a machine that holds no key.
|
|
125
|
+
**`lotics auth login`** — Signs in an account that already exists, on a machine that holds no key. **It does not wait for you.** It prints the page to open (also mailed to you) and the code that page must show, then exits. Sign in there if asked, check the code, press Confirm — and run whatever you wanted to run: the next command that needs a credential collects the key before doing its own work.
|
|
122
126
|
|
|
123
127
|
```bash
|
|
124
|
-
lotics auth login a@b.com #
|
|
125
|
-
|
|
128
|
+
lotics auth login a@b.com # prints the page + code, exits — nothing to keep open
|
|
129
|
+
# …press Confirm in the browser…
|
|
130
|
+
lotics auth whoami # picks up the key, then answers
|
|
131
|
+
|
|
132
|
+
lotics auth login a@b.com --wait # one command instead: holds the terminal until Confirm
|
|
133
|
+
lotics auth login a@b.com --local # pins this directory to that org (implies --wait)
|
|
126
134
|
```
|
|
127
135
|
|
|
136
|
+
A command run before you press Confirm names the page and the code again and exits 1; the request is good for 15 minutes, after which the next command says to ask again. Re-running `auth login` is always safe — it replaces the request, so only the newest code is the live one.
|
|
137
|
+
|
|
128
138
|
**`lotics auth web`** — Send a magic link email to access the web app (requires prior signup or setup).
|
|
129
139
|
|
|
130
140
|
```bash
|
|
@@ -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
|
+
})();
|