@wenathlan/extension 1.1.33 → 1.1.35
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 +8 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +483 -5
- package/dist/index.js.map +3 -3
- package/dist/memory.d.ts +101 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +12 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +57 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +320 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1492 -14
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +1255 -6
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +16 -5
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +379 -10
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/dist/style.css +2 -1
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,15 @@ var planroot = document.querySelector("#plan");
|
|
|
7
7
|
var auditroot = document.querySelector("#audit");
|
|
8
8
|
var diagnosticroot = document.querySelector("#diagnostics");
|
|
9
9
|
var maproot = document.querySelector("#map");
|
|
10
|
+
var a11yroot = document.querySelector("#a11y");
|
|
11
|
+
var readerroot = document.querySelector("#reader");
|
|
12
|
+
var detectionsroot = document.querySelector("#detections");
|
|
13
|
+
var streamroot = document.querySelector("#stream");
|
|
14
|
+
var diffsroot = document.querySelector("#diffs");
|
|
15
|
+
var bannersroot = document.querySelector("#banners");
|
|
16
|
+
var selectorsroot = document.querySelector("#selectors");
|
|
17
|
+
var trailroot = document.querySelector("#trail");
|
|
18
|
+
var navigationroot = document.querySelector("#navigation");
|
|
10
19
|
var statusnode = document.querySelector("#status");
|
|
11
20
|
var progressnode = document.querySelector("#planprogress");
|
|
12
21
|
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
@@ -39,7 +48,7 @@ function button(label, action, disabled = false) {
|
|
|
39
48
|
element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
40
49
|
return element;
|
|
41
50
|
}
|
|
42
|
-
var previewkinds = ["focus", "inspect", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "clickpoint", "clicktext", "clickaria", "clickname", "resolvexpath"];
|
|
51
|
+
var previewkinds = ["focus", "inspect", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "clickpoint", "clicktext", "clickaria", "clickname", "resolvexpath", "deriveselector", "fingerprintsection"];
|
|
43
52
|
var topictags = [
|
|
44
53
|
{ topic: "pointer", kinds: ["movepointer", "clickpoint", "shiftclick", "clicktext", "clickaria", "clickname", "pierceshadow"] },
|
|
45
54
|
{ topic: "typing", kinds: ["typetime", "appendtext", "setvalue", "typeedit", "submitsearch"] },
|
|
@@ -48,7 +57,11 @@ var topictags = [
|
|
|
48
57
|
{ topic: "dialogs", kinds: ["dismissdialog"] },
|
|
49
58
|
{ topic: "frames", kinds: ["enterframe"] },
|
|
50
59
|
{ topic: "retry", kinds: ["retryaction"] },
|
|
51
|
-
{ topic: "reads", kinds: ["mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"] }
|
|
60
|
+
{ topic: "reads", kinds: ["mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"] },
|
|
61
|
+
{ topic: "observation", kinds: ["a11ytree", "readvisible", "readertree", "readoutline", "readselection", "readopengraph", "readlang", "detectlanguage", "listshadow", "listframes", "readscrollpos"] },
|
|
62
|
+
{ topic: "detection", kinds: ["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection"] },
|
|
63
|
+
{ topic: "watch", kinds: ["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"] },
|
|
64
|
+
{ topic: "navigation", kinds: ["openlink", "openprivate", "reloadcache", "stopnav", "waitload", "waiturl", "followlink", "spanav", "spawait", "rewritequery", "setfragment", "navlist", "navprofile", "detecthttp", "readredirects", "readfinalurl", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "trailaudit", "pausenav", "navintent", "navrate", "openclipboard", "checksafe", "batchopen"] }
|
|
52
65
|
];
|
|
53
66
|
function steptopic(kind) {
|
|
54
67
|
return topictags.find((tag) => tag.kinds.includes(kind))?.topic;
|
|
@@ -91,6 +104,41 @@ function retrydetail(step, retries) {
|
|
|
91
104
|
node.textContent = `retry timeline: ${latest.attempts} attempt${latest.attempts === 1 ? "" : "s"} \xB7 ${latest.movement.toFixed(1)} px movement \xB7 ${latest.ok ? "succeeded" : "failed"}`;
|
|
92
105
|
return node;
|
|
93
106
|
}
|
|
107
|
+
function quietdetail(step, outcomes) {
|
|
108
|
+
if (step.kind !== "waitquiet") return null;
|
|
109
|
+
const latest = [...outcomes].reverse().find((outcome) => outcome.stepid === step.id);
|
|
110
|
+
if (!latest) return null;
|
|
111
|
+
const samples = Array.isArray(latest.details?.samples) ? latest.details?.samples : [];
|
|
112
|
+
const idle = typeof latest.details?.idle === "number" ? latest.details?.idle : 0;
|
|
113
|
+
const last = samples[samples.length - 1];
|
|
114
|
+
const node = document.createElement("p");
|
|
115
|
+
node.className = "timeline";
|
|
116
|
+
node.textContent = `network quiet: ${samples.length} sample${samples.length === 1 ? "" : "s"} \xB7 quiet for ${Math.round(last?.quietfor ?? 0)} ms \xB7 idle threshold ${idle} ms \xB7 ${latest.ok ? "quiet reached" : "still busy"}`;
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
119
|
+
function navlistdetail(step, progress, outcomes) {
|
|
120
|
+
if (step.kind !== "navlist") return null;
|
|
121
|
+
const entries = (progress?.outcomes ?? []).filter((outcome) => outcome.stepid === step.id && outcome.details?.naventry !== void 0).map((outcome) => outcome.details?.naventry);
|
|
122
|
+
const total = entries.length > 0 ? Math.max(...entries.map((entry) => entry.index)) + 1 : 0;
|
|
123
|
+
const latestoutcome = [...outcomes].reverse().find((outcome) => outcome.stepid === step.id);
|
|
124
|
+
const remaining = typeof latestoutcome?.details?.remaining === "number" ? latestoutcome.details?.remaining : 0;
|
|
125
|
+
const current = entries[entries.length - 1];
|
|
126
|
+
const node = document.createElement("p");
|
|
127
|
+
node.className = "timeline";
|
|
128
|
+
node.textContent = entries.length === 0 ? "navigation list: no entry completed yet" : `navigation list: ${entries.filter((entry) => entry.ok).length} of ${total} entries completed \xB7 current url ${current?.url ?? ""} \xB7 ${remaining} remaining`;
|
|
129
|
+
return node;
|
|
130
|
+
}
|
|
131
|
+
function redirectdetail(step, outcomes) {
|
|
132
|
+
if (!["navigate", "followlink", "spanav", "navlist", "openlink", "reloadcache"].includes(step.kind)) return null;
|
|
133
|
+
const latest = [...outcomes].reverse().find((outcome) => outcome.stepid === step.id && outcome.details?.hops !== void 0);
|
|
134
|
+
if (!latest) return null;
|
|
135
|
+
const hops = typeof latest.details?.hops === "number" ? latest.details?.hops : 0;
|
|
136
|
+
const final = typeof latest.details?.finalurl === "string" ? latest.details?.finalurl : "";
|
|
137
|
+
const node = document.createElement("p");
|
|
138
|
+
node.className = "timeline";
|
|
139
|
+
node.textContent = `redirects: ${Math.max(0, hops - 1)} hop${hops - 1 === 1 ? "" : "s"} \xB7 final url ${final || "unknown"}`;
|
|
140
|
+
return node;
|
|
141
|
+
}
|
|
94
142
|
function renderpreview(step) {
|
|
95
143
|
const preview = previews.get(step.id);
|
|
96
144
|
if (!preview) return null;
|
|
@@ -116,14 +164,18 @@ function renderpreview(step) {
|
|
|
116
164
|
}
|
|
117
165
|
return node;
|
|
118
166
|
}
|
|
119
|
-
function stepitem(plan, step, completed, outcomes, retries) {
|
|
167
|
+
function stepitem(plan, step, completed, outcomes, retries, progress) {
|
|
120
168
|
const item = document.createElement("li");
|
|
121
169
|
const done = completed.includes(step.id);
|
|
122
170
|
item.textContent = `${done ? "\u2713" : ""} ${step.summary}${holddetail(step)}`;
|
|
123
171
|
const outcome = renderoutcome(step, outcomes);
|
|
124
172
|
if (outcome) item.append(outcome);
|
|
125
|
-
const timeline = step.kind === "retryaction" ? retrydetail(step, retries) :
|
|
173
|
+
const timeline = step.kind === "retryaction" ? retrydetail(step, retries) : quietdetail(step, outcomes);
|
|
126
174
|
if (timeline) item.append(timeline);
|
|
175
|
+
const navlist = navlistdetail(step, progress, outcomes);
|
|
176
|
+
if (navlist) item.append(navlist);
|
|
177
|
+
const redirects = redirectdetail(step, outcomes);
|
|
178
|
+
if (redirects) item.append(redirects);
|
|
127
179
|
const preview = renderpreview(step);
|
|
128
180
|
if (preview) item.append(preview);
|
|
129
181
|
const hastarget = Boolean(step.target) || options(step).targetref !== void 0;
|
|
@@ -140,7 +192,7 @@ function stepitem(plan, step, completed, outcomes, retries) {
|
|
|
140
192
|
}));
|
|
141
193
|
return item;
|
|
142
194
|
}
|
|
143
|
-
function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
195
|
+
function steplist(plan, steps, completed, outcomes, retries, risk, progress) {
|
|
144
196
|
const group = steps.filter((step) => step.risk === risk);
|
|
145
197
|
if (group.length === 0) return null;
|
|
146
198
|
const section = document.createElement("section");
|
|
@@ -150,7 +202,7 @@ function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
|
150
202
|
const general = group.filter((step) => steptopic(step.kind) === void 0);
|
|
151
203
|
if (general.length > 0) {
|
|
152
204
|
const list = document.createElement("ol");
|
|
153
|
-
for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries));
|
|
205
|
+
for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries, progress));
|
|
154
206
|
section.append(list);
|
|
155
207
|
}
|
|
156
208
|
for (const tag of topictags) {
|
|
@@ -160,7 +212,7 @@ function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
|
160
212
|
row.textContent = `${tag.topic} steps`;
|
|
161
213
|
section.append(row);
|
|
162
214
|
const list = document.createElement("ol");
|
|
163
|
-
for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries));
|
|
215
|
+
for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries, progress));
|
|
164
216
|
section.append(list);
|
|
165
217
|
}
|
|
166
218
|
return section;
|
|
@@ -178,9 +230,9 @@ function renderplan(plan, progress, outcomes = [], retries = []) {
|
|
|
178
230
|
planroot.append(title);
|
|
179
231
|
const completed = progress?.planid === plan.id ? progress.completedsteps : [];
|
|
180
232
|
renderprogress(plan, completed);
|
|
181
|
-
const sensitive = steplist(plan, plan.steps, completed, outcomes, retries, "sensitive");
|
|
182
|
-
const interaction = steplist(plan, plan.steps, completed, outcomes, retries, "interaction");
|
|
183
|
-
const read = steplist(plan, plan.steps, completed, outcomes, retries, "read");
|
|
233
|
+
const sensitive = steplist(plan, plan.steps, completed, outcomes, retries, "sensitive", progress);
|
|
234
|
+
const interaction = steplist(plan, plan.steps, completed, outcomes, retries, "interaction", progress);
|
|
235
|
+
const read = steplist(plan, plan.steps, completed, outcomes, retries, "read", progress);
|
|
184
236
|
for (const group of [sensitive, interaction, read]) if (group) planroot.append(group);
|
|
185
237
|
if (plan.state === "pending") {
|
|
186
238
|
planroot.append(button("Approve reviewed plan", async () => {
|
|
@@ -219,6 +271,314 @@ function rendermap(map) {
|
|
|
219
271
|
maproot.append(item);
|
|
220
272
|
}
|
|
221
273
|
}
|
|
274
|
+
function rendera11y(capture) {
|
|
275
|
+
if (!a11yroot) return;
|
|
276
|
+
a11yroot.replaceChildren();
|
|
277
|
+
if (!capture) {
|
|
278
|
+
a11yroot.textContent = "Run an a11ytree step to capture the accessibility tree beside the dom snapshot.";
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const lines = [];
|
|
282
|
+
const walk = (node, depth) => {
|
|
283
|
+
if (lines.length >= 80) return;
|
|
284
|
+
const states = node.states.length > 0 ? ` [${node.states.join(", ")}]` : "";
|
|
285
|
+
const value = node.value !== void 0 ? ` = ${node.value}` : "";
|
|
286
|
+
lines.push(`${"\xB7 ".repeat(depth)}${node.role}: ${node.name || "(unnamed)"}${states}${value}`);
|
|
287
|
+
for (const child of node.children) walk(child, depth + 1);
|
|
288
|
+
};
|
|
289
|
+
walk(capture.tree, 0);
|
|
290
|
+
const payload = document.createElement("pre");
|
|
291
|
+
payload.textContent = lines.join("\n");
|
|
292
|
+
a11yroot.append(payload);
|
|
293
|
+
}
|
|
294
|
+
function renderreader(capture) {
|
|
295
|
+
if (!readerroot) return;
|
|
296
|
+
readerroot.replaceChildren();
|
|
297
|
+
if (!capture) {
|
|
298
|
+
readerroot.textContent = "Run a readertree step to extract the reader view.";
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const title = document.createElement("p");
|
|
302
|
+
title.textContent = `${capture.article.title || "Untitled"}${capture.article.byline ? ` \xB7 ${capture.article.byline}` : ""} \xB7 ${capture.article.words} words \xB7 ${capture.article.blocks.length} blocks`;
|
|
303
|
+
readerroot.append(title);
|
|
304
|
+
for (const block of capture.article.blocks.slice(0, 40)) {
|
|
305
|
+
const line = document.createElement("p");
|
|
306
|
+
line.className = /^h\d$/.test(block.kind) ? "readerblock heading" : "readerblock";
|
|
307
|
+
line.textContent = `${block.kind}: ${block.text.slice(0, 200)}`;
|
|
308
|
+
readerroot.append(line);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function renderdetections(plan, outcomes) {
|
|
312
|
+
if (!detectionsroot) return;
|
|
313
|
+
detectionsroot.replaceChildren();
|
|
314
|
+
if (!plan) {
|
|
315
|
+
detectionsroot.textContent = "Detection steps list their detected lists, tables and pagination shapes here as suggestions.";
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const kindof = (stepid) => plan.steps.find((step) => step.id === stepid)?.kind;
|
|
319
|
+
const latest = (kind) => [...outcomes].reverse().find((outcome) => outcome.ok && kindof(outcome.stepid) === kind);
|
|
320
|
+
let shown = 0;
|
|
321
|
+
const listoutcome = latest("detectlists");
|
|
322
|
+
const lists = Array.isArray(listoutcome?.details?.lists) ? listoutcome?.details?.lists : [];
|
|
323
|
+
for (const pattern of lists.slice(0, 6)) {
|
|
324
|
+
const item = document.createElement("li");
|
|
325
|
+
const pick = document.createElement("button");
|
|
326
|
+
pick.type = "button";
|
|
327
|
+
pick.textContent = `list of ${pattern.repeat} items \xB7 ${pattern.itemselector}`;
|
|
328
|
+
pick.addEventListener("click", () => pickhint(`target hint: ${pattern.itemselector} (repeated list item of ${pattern.container})`));
|
|
329
|
+
item.append(pick);
|
|
330
|
+
detectionsroot.append(item);
|
|
331
|
+
shown += 1;
|
|
332
|
+
}
|
|
333
|
+
const tableoutcome = latest("detecttables");
|
|
334
|
+
const tables = Array.isArray(tableoutcome?.details?.tables) ? tableoutcome?.details?.tables : [];
|
|
335
|
+
for (const table of tables.slice(0, 6)) {
|
|
336
|
+
const item = document.createElement("li");
|
|
337
|
+
const pick = document.createElement("button");
|
|
338
|
+
pick.type = "button";
|
|
339
|
+
pick.textContent = `table of ${table.rows} rows \xB7 ${table.columns.length} columns \xB7 ${table.selector}`;
|
|
340
|
+
pick.addEventListener("click", () => pickhint(`target hint: ${table.selector} (detected data table)`));
|
|
341
|
+
item.append(pick);
|
|
342
|
+
detectionsroot.append(item);
|
|
343
|
+
shown += 1;
|
|
344
|
+
}
|
|
345
|
+
const paginationoutcome = latest("countpages");
|
|
346
|
+
if (paginationoutcome) {
|
|
347
|
+
const item = document.createElement("li");
|
|
348
|
+
const current = typeof paginationoutcome.details?.current === "number" ? paginationoutcome.details?.current : 0;
|
|
349
|
+
const total = typeof paginationoutcome.details?.total === "number" ? paginationoutcome.details?.total : 0;
|
|
350
|
+
const text = document.createElement("span");
|
|
351
|
+
text.textContent = `pagination: current page ${current} \xB7 estimated total ${total}`;
|
|
352
|
+
item.append(text);
|
|
353
|
+
detectionsroot.append(item);
|
|
354
|
+
shown += 1;
|
|
355
|
+
}
|
|
356
|
+
if (shown === 0) detectionsroot.textContent = "Detected lists, tables and pagination shapes appear here as plan suggestions.";
|
|
357
|
+
}
|
|
358
|
+
function renderstream(mutationevents, focusevents) {
|
|
359
|
+
if (!streamroot) return;
|
|
360
|
+
streamroot.replaceChildren();
|
|
361
|
+
if (mutationevents.length === 0 && focusevents.length === 0) {
|
|
362
|
+
streamroot.textContent = "Watched steps stream their mutation and focus events here.";
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
for (const event of mutationevents.slice(0, 6)) {
|
|
366
|
+
const item = document.createElement("li");
|
|
367
|
+
item.textContent = `${new Date(event.at).toLocaleTimeString()} \xB7 mutation ${event.event} \xB7 ${event.targetpath}`;
|
|
368
|
+
streamroot.append(item);
|
|
369
|
+
}
|
|
370
|
+
for (const event of focusevents.slice(0, 6)) {
|
|
371
|
+
const item = document.createElement("li");
|
|
372
|
+
item.textContent = `${new Date(event.at).toLocaleTimeString()} \xB7 focus ${event.kind} \xB7 ${event.targetpath}`;
|
|
373
|
+
streamroot.append(item);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function renderdiffs(diffs) {
|
|
377
|
+
if (!diffsroot) return;
|
|
378
|
+
diffsroot.replaceChildren();
|
|
379
|
+
const latest = diffs[0];
|
|
380
|
+
if (!latest) {
|
|
381
|
+
diffsroot.textContent = "Diff two captured observation versions with a diffsnapshots step.";
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const heading = document.createElement("p");
|
|
385
|
+
heading.textContent = `version ${latest.baseversion} \u2192 ${latest.targetversion}: ${latest.added.length} added \xB7 ${latest.removed.length} removed \xB7 ${latest.changed.length} changed`;
|
|
386
|
+
diffsroot.append(heading);
|
|
387
|
+
const list = document.createElement("ul");
|
|
388
|
+
for (const entry of [...latest.added, ...latest.removed, ...latest.changed].slice(0, 12)) {
|
|
389
|
+
const item = document.createElement("li");
|
|
390
|
+
item.className = `diffrow ${entry.kind}`;
|
|
391
|
+
item.textContent = `${entry.kind} \xB7 ${entry.selector} \xB7 ${entry.summary}`;
|
|
392
|
+
list.append(item);
|
|
393
|
+
}
|
|
394
|
+
diffsroot.append(list);
|
|
395
|
+
}
|
|
396
|
+
function renderbanners(banners) {
|
|
397
|
+
if (!bannersroot) return;
|
|
398
|
+
bannersroot.replaceChildren();
|
|
399
|
+
if (banners.length === 0) {
|
|
400
|
+
bannersroot.textContent = "No consent banner has been observed yet.";
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
for (const banner of banners.slice(0, 3)) {
|
|
404
|
+
const card = document.createElement("div");
|
|
405
|
+
card.className = "bannercard";
|
|
406
|
+
const title = document.createElement("p");
|
|
407
|
+
title.textContent = `${banner.kind} banner detected \u2014 review it before any interaction.`;
|
|
408
|
+
const text = document.createElement("p");
|
|
409
|
+
text.textContent = banner.text.slice(0, 160) || "(no banner text)";
|
|
410
|
+
const controls = document.createElement("p");
|
|
411
|
+
controls.textContent = `controls: ${banner.controls.length > 0 ? banner.controls.join(", ") : "none"}`;
|
|
412
|
+
card.append(title, text, controls);
|
|
413
|
+
bannersroot.append(card);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function renderselectors(selectors) {
|
|
417
|
+
if (!selectorsroot) return;
|
|
418
|
+
selectorsroot.replaceChildren();
|
|
419
|
+
if (selectors.length === 0) {
|
|
420
|
+
selectorsroot.textContent = "Run a deriveselector step to rank stable selectors.";
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
for (const record of selectors.slice(0, 8)) {
|
|
424
|
+
const item = document.createElement("li");
|
|
425
|
+
const pick = document.createElement("button");
|
|
426
|
+
pick.type = "button";
|
|
427
|
+
pick.textContent = `${record.selector} (${record.strategy} \xB7 stability ${record.score})`;
|
|
428
|
+
pick.addEventListener("click", () => pickhint(`target hint: ${record.selector} (derived ${record.strategy} selector)`));
|
|
429
|
+
item.append(pick);
|
|
430
|
+
selectorsroot.append(item);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function rendertrail(trail) {
|
|
434
|
+
if (!trailroot) return;
|
|
435
|
+
trailroot.replaceChildren();
|
|
436
|
+
if (trail.length === 0) {
|
|
437
|
+
trailroot.textContent = "No page has been visited inside a reviewed navigation step yet.";
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
for (const entry of [...trail].reverse().slice(0, 12)) {
|
|
441
|
+
const item = document.createElement("li");
|
|
442
|
+
item.textContent = `${new Date(entry.at).toLocaleTimeString()} \xB7 ${entry.url}${entry.title ? ` \xB7 ${entry.title}` : ""}${entry.stepid ? ` \xB7 step ${entry.stepid}` : ""}`;
|
|
443
|
+
trailroot.append(item);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function rendernavigation(context) {
|
|
447
|
+
if (!navigationroot) return;
|
|
448
|
+
navigationroot.replaceChildren();
|
|
449
|
+
const paused = document.createElement("p");
|
|
450
|
+
if (context.navcontrol?.pausedat) {
|
|
451
|
+
paused.className = "bannercard";
|
|
452
|
+
paused.textContent = `Navigation is paused while ${context.navcontrol.reason ?? "a consent prompt is open"}; reviewed navigation steps are blocked until it resumes.`;
|
|
453
|
+
} else {
|
|
454
|
+
paused.textContent = "Navigation is live; no consent prompt holds it.";
|
|
455
|
+
}
|
|
456
|
+
navigationroot.append(paused);
|
|
457
|
+
const rates = context.ratestates ?? [];
|
|
458
|
+
if (rates.length > 0) {
|
|
459
|
+
const heading = document.createElement("p");
|
|
460
|
+
heading.textContent = "rate limit windows per domain:";
|
|
461
|
+
navigationroot.append(heading);
|
|
462
|
+
const list = document.createElement("ul");
|
|
463
|
+
for (const state of rates.slice(0, 6)) {
|
|
464
|
+
const item = document.createElement("li");
|
|
465
|
+
item.textContent = `${state.domain}: ${state.count} of ${state.limit.ceiling} navigations inside the reviewed window of ${state.limit.window} ms`;
|
|
466
|
+
list.append(item);
|
|
467
|
+
}
|
|
468
|
+
navigationroot.append(list);
|
|
469
|
+
}
|
|
470
|
+
const records = context.navrecords ?? [];
|
|
471
|
+
const record = records[0];
|
|
472
|
+
if (record) {
|
|
473
|
+
const chain = document.createElement("p");
|
|
474
|
+
chain.textContent = `latest navigation: ${Math.max(0, record.chain.hops.length - 1)} redirect${record.chain.hops.length - 1 === 1 ? "" : "s"} \xB7 final url ${record.finalurl}`;
|
|
475
|
+
navigationroot.append(chain);
|
|
476
|
+
const hops = document.createElement("ul");
|
|
477
|
+
for (const hop of record.chain.hops.slice(0, 6)) {
|
|
478
|
+
const item = document.createElement("li");
|
|
479
|
+
let path = hop.url;
|
|
480
|
+
try {
|
|
481
|
+
path = new URL(hop.url).pathname;
|
|
482
|
+
} catch {
|
|
483
|
+
path = hop.url;
|
|
484
|
+
}
|
|
485
|
+
item.textContent = `hop ${path} \xB7 status ${hop.status} \xB7 ${new Date(hop.at).toLocaleTimeString()}`;
|
|
486
|
+
hops.append(item);
|
|
487
|
+
}
|
|
488
|
+
navigationroot.append(hops);
|
|
489
|
+
}
|
|
490
|
+
const curatedlists = context.curated ?? [];
|
|
491
|
+
if (curatedlists.length > 0) {
|
|
492
|
+
const heading = document.createElement("p");
|
|
493
|
+
heading.textContent = "curated link lists with per url safety states:";
|
|
494
|
+
navigationroot.append(heading);
|
|
495
|
+
for (const list of curatedlists.slice(0, 3)) {
|
|
496
|
+
const card = document.createElement("div");
|
|
497
|
+
card.className = "bannercard";
|
|
498
|
+
const title = document.createElement("p");
|
|
499
|
+
title.textContent = `${list.links.length} curated url${list.links.length === 1 ? "" : "s"}${list.reviewedat ? " \xB7 opened after review" : " \xB7 waiting for review"}`;
|
|
500
|
+
card.append(title);
|
|
501
|
+
for (const link of list.links.slice(0, 8)) {
|
|
502
|
+
const line = document.createElement("p");
|
|
503
|
+
line.textContent = `${link.verdict === "safe" ? "\u2713" : "\u2717"} ${link.url}${link.reasons.length > 0 ? ` \xB7 ${link.reasons.join("; ")}` : ""}`;
|
|
504
|
+
card.append(line);
|
|
505
|
+
}
|
|
506
|
+
navigationroot.append(card);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const safeties = context.safeties ?? [];
|
|
510
|
+
const runner = document.createElement("p");
|
|
511
|
+
const checkinput = document.createElement("input");
|
|
512
|
+
checkinput.type = "url";
|
|
513
|
+
checkinput.placeholder = "https://external.example/link";
|
|
514
|
+
checkinput.setAttribute("aria-label", "url to verify with checksafe");
|
|
515
|
+
const checkbutton = button("Run checksafe", async () => {
|
|
516
|
+
const verdict = await request({ kind: "checksafe", url: checkinput.value });
|
|
517
|
+
status(verdict.safe ? `${verdict.url} passed every safety check.` : `${verdict.url} is unsafe: ${verdict.reasons.join("; ")}.`);
|
|
518
|
+
await refresh();
|
|
519
|
+
});
|
|
520
|
+
runner.append(checkinput, " ", checkbutton);
|
|
521
|
+
navigationroot.append(runner);
|
|
522
|
+
if (safeties.length > 0) {
|
|
523
|
+
const list = document.createElement("ul");
|
|
524
|
+
for (const verdict of safeties.slice(0, 6)) {
|
|
525
|
+
const item = document.createElement("li");
|
|
526
|
+
item.textContent = `${verdict.safe ? "safe" : "unsafe"} \xB7 ${verdict.url}${verdict.reasons.length > 0 ? ` \xB7 ${verdict.reasons.join("; ")}` : ""}`;
|
|
527
|
+
list.append(item);
|
|
528
|
+
}
|
|
529
|
+
navigationroot.append(list);
|
|
530
|
+
}
|
|
531
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
532
|
+
const authcard = document.createElement("div");
|
|
533
|
+
authcard.className = "bannercard";
|
|
534
|
+
const authtitle = document.createElement("p");
|
|
535
|
+
authtitle.textContent = active ? "basic auth credentials (stored only after your explicit review):" : "basic auth credentials need an active session before they can be reviewed.";
|
|
536
|
+
authcard.append(authtitle);
|
|
537
|
+
if (active) {
|
|
538
|
+
const origininput = document.createElement("input");
|
|
539
|
+
origininput.type = "url";
|
|
540
|
+
origininput.placeholder = context.session?.origin ?? "https://example.com";
|
|
541
|
+
origininput.setAttribute("aria-label", "auth origin");
|
|
542
|
+
const userinput = document.createElement("input");
|
|
543
|
+
userinput.type = "text";
|
|
544
|
+
userinput.placeholder = "username";
|
|
545
|
+
userinput.setAttribute("aria-label", "auth username");
|
|
546
|
+
const passinput = document.createElement("input");
|
|
547
|
+
passinput.type = "password";
|
|
548
|
+
passinput.placeholder = "password";
|
|
549
|
+
passinput.setAttribute("aria-label", "auth password");
|
|
550
|
+
const storebutton = button("Store reviewed credentials", async () => {
|
|
551
|
+
const stored = await request({ kind: "storeauth", origin: origininput.value, username: userinput.value, password: passinput.value });
|
|
552
|
+
status(`Reviewed basic auth credentials stored for ${stored.origin}.`);
|
|
553
|
+
await refresh();
|
|
554
|
+
});
|
|
555
|
+
authcard.append(origininput, " ", userinput, " ", passinput, " ", storebutton);
|
|
556
|
+
}
|
|
557
|
+
navigationroot.append(authcard);
|
|
558
|
+
const auths = context.auths ?? [];
|
|
559
|
+
if (auths.length > 0) {
|
|
560
|
+
const list = document.createElement("ul");
|
|
561
|
+
for (const record2 of auths.slice(0, 4)) {
|
|
562
|
+
const item = document.createElement("li");
|
|
563
|
+
item.textContent = `basic auth for ${record2.origin} as ${record2.username}, reviewed ${new Date(record2.reviewedat).toLocaleString()}`;
|
|
564
|
+
list.append(item);
|
|
565
|
+
}
|
|
566
|
+
navigationroot.append(list);
|
|
567
|
+
}
|
|
568
|
+
const artifacts = context.artifacts ?? [];
|
|
569
|
+
if (artifacts.length > 0) {
|
|
570
|
+
const heading = document.createElement("p");
|
|
571
|
+
heading.textContent = "task artifacts:";
|
|
572
|
+
navigationroot.append(heading);
|
|
573
|
+
const list = document.createElement("ul");
|
|
574
|
+
for (const artifact of artifacts.slice(0, 6)) {
|
|
575
|
+
const item = document.createElement("li");
|
|
576
|
+
item.textContent = `${artifact.kind}: ${artifact.name} \xB7 step ${artifact.stepid}`;
|
|
577
|
+
list.append(item);
|
|
578
|
+
}
|
|
579
|
+
navigationroot.append(list);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
222
582
|
function rendercapabilities(report) {
|
|
223
583
|
if (!capabilitiestext) return;
|
|
224
584
|
if (!report) {
|
|
@@ -255,6 +615,15 @@ async function refresh() {
|
|
|
255
615
|
renderplan(context.plan, context.progress, context.outcomes ?? [], context.retries ?? []);
|
|
256
616
|
renderdiagnostic(context.diagnostic);
|
|
257
617
|
rendermap(context.map);
|
|
618
|
+
rendera11y(context.a11y);
|
|
619
|
+
renderreader(context.reader);
|
|
620
|
+
renderdetections(context.plan, context.outcomes ?? []);
|
|
621
|
+
renderstream(context.mutationevents ?? [], context.focusevents ?? []);
|
|
622
|
+
renderdiffs(context.diffs ?? []);
|
|
623
|
+
renderbanners(context.banners ?? []);
|
|
624
|
+
renderselectors(context.selectors ?? []);
|
|
625
|
+
rendertrail(context.trail?.trail ?? []);
|
|
626
|
+
rendernavigation(context);
|
|
258
627
|
renderaudit(context.audit);
|
|
259
628
|
rendercapabilities(context.capabilities);
|
|
260
629
|
if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|