@wenathlan/extension 1.1.34 → 1.1.36
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 +7 -5
- package/dist/index.js +533 -5
- package/dist/index.js.map +2 -2
- package/dist/memory.d.ts +91 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +20 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +37 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +271 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +2040 -17
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +331 -12
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +33 -2
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +396 -8
- package/extension/dist/sidepanel.js.map +4 -4
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
// policy.ts
|
|
2
|
+
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab"]);
|
|
3
|
+
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
|
|
4
|
+
var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta"]);
|
|
5
|
+
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
6
|
+
|
|
7
|
+
// extension/tabscommand.ts
|
|
8
|
+
function switcherlist(tabs, recency, filter) {
|
|
9
|
+
const needle = filter.trim().toLowerCase();
|
|
10
|
+
const matches = needle ? tabs.filter((tab) => tab.title.toLowerCase().includes(needle) || tab.url.toLowerCase().includes(needle)) : [...tabs];
|
|
11
|
+
const lastrun = new Map(recency.map((entry) => [entry.tabid, entry.at]));
|
|
12
|
+
return [...matches].sort((left, right) => {
|
|
13
|
+
const leftat = lastrun.get(left.tabid) ?? -1;
|
|
14
|
+
const rightat = lastrun.get(right.tabid) ?? -1;
|
|
15
|
+
if (leftat !== rightat) return rightat - leftat;
|
|
16
|
+
return left.index - right.index;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
1
20
|
// extension/sidepanel.ts
|
|
2
21
|
var objective = document.querySelector("#objective");
|
|
3
22
|
var localbutton = document.querySelector("#localplan");
|
|
@@ -14,6 +33,9 @@ var streamroot = document.querySelector("#stream");
|
|
|
14
33
|
var diffsroot = document.querySelector("#diffs");
|
|
15
34
|
var bannersroot = document.querySelector("#banners");
|
|
16
35
|
var selectorsroot = document.querySelector("#selectors");
|
|
36
|
+
var trailroot = document.querySelector("#trail");
|
|
37
|
+
var navigationroot = document.querySelector("#navigation");
|
|
38
|
+
var tabswindowsroot = document.querySelector("#tabswindows");
|
|
17
39
|
var statusnode = document.querySelector("#status");
|
|
18
40
|
var progressnode = document.querySelector("#planprogress");
|
|
19
41
|
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
@@ -58,7 +80,9 @@ var topictags = [
|
|
|
58
80
|
{ topic: "reads", kinds: ["mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"] },
|
|
59
81
|
{ topic: "observation", kinds: ["a11ytree", "readvisible", "readertree", "readoutline", "readselection", "readopengraph", "readlang", "detectlanguage", "listshadow", "listframes", "readscrollpos"] },
|
|
60
82
|
{ topic: "detection", kinds: ["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection"] },
|
|
61
|
-
{ topic: "watch", kinds: ["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"] }
|
|
83
|
+
{ topic: "watch", kinds: ["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"] },
|
|
84
|
+
{ 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"] },
|
|
85
|
+
{ topic: "tabs", kinds: ["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"] }
|
|
62
86
|
];
|
|
63
87
|
function steptopic(kind) {
|
|
64
88
|
return topictags.find((tag) => tag.kinds.includes(kind))?.topic;
|
|
@@ -113,6 +137,29 @@ function quietdetail(step, outcomes) {
|
|
|
113
137
|
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"}`;
|
|
114
138
|
return node;
|
|
115
139
|
}
|
|
140
|
+
function navlistdetail(step, progress, outcomes) {
|
|
141
|
+
if (step.kind !== "navlist") return null;
|
|
142
|
+
const entries = (progress?.outcomes ?? []).filter((outcome) => outcome.stepid === step.id && outcome.details?.naventry !== void 0).map((outcome) => outcome.details?.naventry);
|
|
143
|
+
const total = entries.length > 0 ? Math.max(...entries.map((entry) => entry.index)) + 1 : 0;
|
|
144
|
+
const latestoutcome = [...outcomes].reverse().find((outcome) => outcome.stepid === step.id);
|
|
145
|
+
const remaining = typeof latestoutcome?.details?.remaining === "number" ? latestoutcome.details?.remaining : 0;
|
|
146
|
+
const current = entries[entries.length - 1];
|
|
147
|
+
const node = document.createElement("p");
|
|
148
|
+
node.className = "timeline";
|
|
149
|
+
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`;
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
function redirectdetail(step, outcomes) {
|
|
153
|
+
if (!["navigate", "followlink", "spanav", "navlist", "openlink", "reloadcache"].includes(step.kind)) return null;
|
|
154
|
+
const latest = [...outcomes].reverse().find((outcome) => outcome.stepid === step.id && outcome.details?.hops !== void 0);
|
|
155
|
+
if (!latest) return null;
|
|
156
|
+
const hops = typeof latest.details?.hops === "number" ? latest.details?.hops : 0;
|
|
157
|
+
const final = typeof latest.details?.finalurl === "string" ? latest.details?.finalurl : "";
|
|
158
|
+
const node = document.createElement("p");
|
|
159
|
+
node.className = "timeline";
|
|
160
|
+
node.textContent = `redirects: ${Math.max(0, hops - 1)} hop${hops - 1 === 1 ? "" : "s"} \xB7 final url ${final || "unknown"}`;
|
|
161
|
+
return node;
|
|
162
|
+
}
|
|
116
163
|
function renderpreview(step) {
|
|
117
164
|
const preview = previews.get(step.id);
|
|
118
165
|
if (!preview) return null;
|
|
@@ -138,7 +185,7 @@ function renderpreview(step) {
|
|
|
138
185
|
}
|
|
139
186
|
return node;
|
|
140
187
|
}
|
|
141
|
-
function stepitem(plan, step, completed, outcomes, retries) {
|
|
188
|
+
function stepitem(plan, step, completed, outcomes, retries, progress) {
|
|
142
189
|
const item = document.createElement("li");
|
|
143
190
|
const done = completed.includes(step.id);
|
|
144
191
|
item.textContent = `${done ? "\u2713" : ""} ${step.summary}${holddetail(step)}`;
|
|
@@ -146,6 +193,10 @@ function stepitem(plan, step, completed, outcomes, retries) {
|
|
|
146
193
|
if (outcome) item.append(outcome);
|
|
147
194
|
const timeline = step.kind === "retryaction" ? retrydetail(step, retries) : quietdetail(step, outcomes);
|
|
148
195
|
if (timeline) item.append(timeline);
|
|
196
|
+
const navlist = navlistdetail(step, progress, outcomes);
|
|
197
|
+
if (navlist) item.append(navlist);
|
|
198
|
+
const redirects = redirectdetail(step, outcomes);
|
|
199
|
+
if (redirects) item.append(redirects);
|
|
149
200
|
const preview = renderpreview(step);
|
|
150
201
|
if (preview) item.append(preview);
|
|
151
202
|
const hastarget = Boolean(step.target) || options(step).targetref !== void 0;
|
|
@@ -162,7 +213,7 @@ function stepitem(plan, step, completed, outcomes, retries) {
|
|
|
162
213
|
}));
|
|
163
214
|
return item;
|
|
164
215
|
}
|
|
165
|
-
function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
216
|
+
function steplist(plan, steps, completed, outcomes, retries, risk, progress) {
|
|
166
217
|
const group = steps.filter((step) => step.risk === risk);
|
|
167
218
|
if (group.length === 0) return null;
|
|
168
219
|
const section = document.createElement("section");
|
|
@@ -172,7 +223,7 @@ function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
|
172
223
|
const general = group.filter((step) => steptopic(step.kind) === void 0);
|
|
173
224
|
if (general.length > 0) {
|
|
174
225
|
const list = document.createElement("ol");
|
|
175
|
-
for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries));
|
|
226
|
+
for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries, progress));
|
|
176
227
|
section.append(list);
|
|
177
228
|
}
|
|
178
229
|
for (const tag of topictags) {
|
|
@@ -182,7 +233,7 @@ function steplist(plan, steps, completed, outcomes, retries, risk) {
|
|
|
182
233
|
row.textContent = `${tag.topic} steps`;
|
|
183
234
|
section.append(row);
|
|
184
235
|
const list = document.createElement("ol");
|
|
185
|
-
for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries));
|
|
236
|
+
for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries, progress));
|
|
186
237
|
section.append(list);
|
|
187
238
|
}
|
|
188
239
|
return section;
|
|
@@ -200,9 +251,9 @@ function renderplan(plan, progress, outcomes = [], retries = []) {
|
|
|
200
251
|
planroot.append(title);
|
|
201
252
|
const completed = progress?.planid === plan.id ? progress.completedsteps : [];
|
|
202
253
|
renderprogress(plan, completed);
|
|
203
|
-
const sensitive = steplist(plan, plan.steps, completed, outcomes, retries, "sensitive");
|
|
204
|
-
const interaction = steplist(plan, plan.steps, completed, outcomes, retries, "interaction");
|
|
205
|
-
const read = steplist(plan, plan.steps, completed, outcomes, retries, "read");
|
|
254
|
+
const sensitive = steplist(plan, plan.steps, completed, outcomes, retries, "sensitive", progress);
|
|
255
|
+
const interaction = steplist(plan, plan.steps, completed, outcomes, retries, "interaction", progress);
|
|
256
|
+
const read = steplist(plan, plan.steps, completed, outcomes, retries, "read", progress);
|
|
206
257
|
for (const group of [sensitive, interaction, read]) if (group) planroot.append(group);
|
|
207
258
|
if (plan.state === "pending") {
|
|
208
259
|
planroot.append(button("Approve reviewed plan", async () => {
|
|
@@ -400,6 +451,340 @@ function renderselectors(selectors) {
|
|
|
400
451
|
selectorsroot.append(item);
|
|
401
452
|
}
|
|
402
453
|
}
|
|
454
|
+
function rendertrail(trail) {
|
|
455
|
+
if (!trailroot) return;
|
|
456
|
+
trailroot.replaceChildren();
|
|
457
|
+
if (trail.length === 0) {
|
|
458
|
+
trailroot.textContent = "No page has been visited inside a reviewed navigation step yet.";
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
for (const entry of [...trail].reverse().slice(0, 12)) {
|
|
462
|
+
const item = document.createElement("li");
|
|
463
|
+
item.textContent = `${new Date(entry.at).toLocaleTimeString()} \xB7 ${entry.url}${entry.title ? ` \xB7 ${entry.title}` : ""}${entry.stepid ? ` \xB7 step ${entry.stepid}` : ""}`;
|
|
464
|
+
trailroot.append(item);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function rendernavigation(context) {
|
|
468
|
+
if (!navigationroot) return;
|
|
469
|
+
navigationroot.replaceChildren();
|
|
470
|
+
const paused = document.createElement("p");
|
|
471
|
+
if (context.navcontrol?.pausedat) {
|
|
472
|
+
paused.className = "bannercard";
|
|
473
|
+
paused.textContent = `Navigation is paused while ${context.navcontrol.reason ?? "a consent prompt is open"}; reviewed navigation steps are blocked until it resumes.`;
|
|
474
|
+
} else {
|
|
475
|
+
paused.textContent = "Navigation is live; no consent prompt holds it.";
|
|
476
|
+
}
|
|
477
|
+
navigationroot.append(paused);
|
|
478
|
+
const rates = context.ratestates ?? [];
|
|
479
|
+
if (rates.length > 0) {
|
|
480
|
+
const heading = document.createElement("p");
|
|
481
|
+
heading.textContent = "rate limit windows per domain:";
|
|
482
|
+
navigationroot.append(heading);
|
|
483
|
+
const list = document.createElement("ul");
|
|
484
|
+
for (const state of rates.slice(0, 6)) {
|
|
485
|
+
const item = document.createElement("li");
|
|
486
|
+
item.textContent = `${state.domain}: ${state.count} of ${state.limit.ceiling} navigations inside the reviewed window of ${state.limit.window} ms`;
|
|
487
|
+
list.append(item);
|
|
488
|
+
}
|
|
489
|
+
navigationroot.append(list);
|
|
490
|
+
}
|
|
491
|
+
const records = context.navrecords ?? [];
|
|
492
|
+
const record = records[0];
|
|
493
|
+
if (record) {
|
|
494
|
+
const chain = document.createElement("p");
|
|
495
|
+
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}`;
|
|
496
|
+
navigationroot.append(chain);
|
|
497
|
+
const hops = document.createElement("ul");
|
|
498
|
+
for (const hop of record.chain.hops.slice(0, 6)) {
|
|
499
|
+
const item = document.createElement("li");
|
|
500
|
+
let path = hop.url;
|
|
501
|
+
try {
|
|
502
|
+
path = new URL(hop.url).pathname;
|
|
503
|
+
} catch {
|
|
504
|
+
path = hop.url;
|
|
505
|
+
}
|
|
506
|
+
item.textContent = `hop ${path} \xB7 status ${hop.status} \xB7 ${new Date(hop.at).toLocaleTimeString()}`;
|
|
507
|
+
hops.append(item);
|
|
508
|
+
}
|
|
509
|
+
navigationroot.append(hops);
|
|
510
|
+
}
|
|
511
|
+
const curatedlists = context.curated ?? [];
|
|
512
|
+
if (curatedlists.length > 0) {
|
|
513
|
+
const heading = document.createElement("p");
|
|
514
|
+
heading.textContent = "curated link lists with per url safety states:";
|
|
515
|
+
navigationroot.append(heading);
|
|
516
|
+
for (const list of curatedlists.slice(0, 3)) {
|
|
517
|
+
const card = document.createElement("div");
|
|
518
|
+
card.className = "bannercard";
|
|
519
|
+
const title = document.createElement("p");
|
|
520
|
+
title.textContent = `${list.links.length} curated url${list.links.length === 1 ? "" : "s"}${list.reviewedat ? " \xB7 opened after review" : " \xB7 waiting for review"}`;
|
|
521
|
+
card.append(title);
|
|
522
|
+
for (const link of list.links.slice(0, 8)) {
|
|
523
|
+
const line = document.createElement("p");
|
|
524
|
+
line.textContent = `${link.verdict === "safe" ? "\u2713" : "\u2717"} ${link.url}${link.reasons.length > 0 ? ` \xB7 ${link.reasons.join("; ")}` : ""}`;
|
|
525
|
+
card.append(line);
|
|
526
|
+
}
|
|
527
|
+
navigationroot.append(card);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const safeties = context.safeties ?? [];
|
|
531
|
+
const runner = document.createElement("p");
|
|
532
|
+
const checkinput = document.createElement("input");
|
|
533
|
+
checkinput.type = "url";
|
|
534
|
+
checkinput.placeholder = "https://external.example/link";
|
|
535
|
+
checkinput.setAttribute("aria-label", "url to verify with checksafe");
|
|
536
|
+
const checkbutton = button("Run checksafe", async () => {
|
|
537
|
+
const verdict = await request({ kind: "checksafe", url: checkinput.value });
|
|
538
|
+
status(verdict.safe ? `${verdict.url} passed every safety check.` : `${verdict.url} is unsafe: ${verdict.reasons.join("; ")}.`);
|
|
539
|
+
await refresh();
|
|
540
|
+
});
|
|
541
|
+
runner.append(checkinput, " ", checkbutton);
|
|
542
|
+
navigationroot.append(runner);
|
|
543
|
+
if (safeties.length > 0) {
|
|
544
|
+
const list = document.createElement("ul");
|
|
545
|
+
for (const verdict of safeties.slice(0, 6)) {
|
|
546
|
+
const item = document.createElement("li");
|
|
547
|
+
item.textContent = `${verdict.safe ? "safe" : "unsafe"} \xB7 ${verdict.url}${verdict.reasons.length > 0 ? ` \xB7 ${verdict.reasons.join("; ")}` : ""}`;
|
|
548
|
+
list.append(item);
|
|
549
|
+
}
|
|
550
|
+
navigationroot.append(list);
|
|
551
|
+
}
|
|
552
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
553
|
+
const authcard = document.createElement("div");
|
|
554
|
+
authcard.className = "bannercard";
|
|
555
|
+
const authtitle = document.createElement("p");
|
|
556
|
+
authtitle.textContent = active ? "basic auth credentials (stored only after your explicit review):" : "basic auth credentials need an active session before they can be reviewed.";
|
|
557
|
+
authcard.append(authtitle);
|
|
558
|
+
if (active) {
|
|
559
|
+
const origininput = document.createElement("input");
|
|
560
|
+
origininput.type = "url";
|
|
561
|
+
origininput.placeholder = context.session?.origin ?? "https://example.com";
|
|
562
|
+
origininput.setAttribute("aria-label", "auth origin");
|
|
563
|
+
const userinput = document.createElement("input");
|
|
564
|
+
userinput.type = "text";
|
|
565
|
+
userinput.placeholder = "username";
|
|
566
|
+
userinput.setAttribute("aria-label", "auth username");
|
|
567
|
+
const passinput = document.createElement("input");
|
|
568
|
+
passinput.type = "password";
|
|
569
|
+
passinput.placeholder = "password";
|
|
570
|
+
passinput.setAttribute("aria-label", "auth password");
|
|
571
|
+
const storebutton = button("Store reviewed credentials", async () => {
|
|
572
|
+
const stored = await request({ kind: "storeauth", origin: origininput.value, username: userinput.value, password: passinput.value });
|
|
573
|
+
status(`Reviewed basic auth credentials stored for ${stored.origin}.`);
|
|
574
|
+
await refresh();
|
|
575
|
+
});
|
|
576
|
+
authcard.append(origininput, " ", userinput, " ", passinput, " ", storebutton);
|
|
577
|
+
}
|
|
578
|
+
navigationroot.append(authcard);
|
|
579
|
+
const auths = context.auths ?? [];
|
|
580
|
+
if (auths.length > 0) {
|
|
581
|
+
const list = document.createElement("ul");
|
|
582
|
+
for (const record2 of auths.slice(0, 4)) {
|
|
583
|
+
const item = document.createElement("li");
|
|
584
|
+
item.textContent = `basic auth for ${record2.origin} as ${record2.username}, reviewed ${new Date(record2.reviewedat).toLocaleString()}`;
|
|
585
|
+
list.append(item);
|
|
586
|
+
}
|
|
587
|
+
navigationroot.append(list);
|
|
588
|
+
}
|
|
589
|
+
const artifacts = context.artifacts ?? [];
|
|
590
|
+
if (artifacts.length > 0) {
|
|
591
|
+
const heading = document.createElement("p");
|
|
592
|
+
heading.textContent = "task artifacts:";
|
|
593
|
+
navigationroot.append(heading);
|
|
594
|
+
const list = document.createElement("ul");
|
|
595
|
+
for (const artifact of artifacts.slice(0, 6)) {
|
|
596
|
+
const item = document.createElement("li");
|
|
597
|
+
item.textContent = `${artifact.kind}: ${artifact.name} \xB7 step ${artifact.stepid}`;
|
|
598
|
+
list.append(item);
|
|
599
|
+
}
|
|
600
|
+
navigationroot.append(list);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function rendertabswindows(context) {
|
|
604
|
+
if (!tabswindowsroot) return;
|
|
605
|
+
tabswindowsroot.replaceChildren();
|
|
606
|
+
const tabs = context.tabs ?? [];
|
|
607
|
+
const badges = context.badges ?? [];
|
|
608
|
+
const metas = context.tabmetas ?? [];
|
|
609
|
+
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
610
|
+
const gauge = context.tasktabgauge ?? { used: 0, ceiling: void 0, over: false };
|
|
611
|
+
const budget = document.createElement("p");
|
|
612
|
+
budget.className = gauge.over ? "bannercard" : "";
|
|
613
|
+
budget.textContent = `task tab budget: ${gauge.used} tab${gauge.used === 1 ? "" : "s"} with active tasks${gauge.ceiling !== void 0 ? ` of the user configured ceiling ${gauge.ceiling}` : " with no user ceiling configured"}${gauge.over ? " \u2014 over the reviewed budget" : ""}`;
|
|
614
|
+
tabswindowsroot.append(budget);
|
|
615
|
+
const ceilinginput = document.createElement("input");
|
|
616
|
+
ceilinginput.type = "number";
|
|
617
|
+
ceilinginput.min = "0";
|
|
618
|
+
ceilinginput.placeholder = gauge.ceiling !== void 0 ? String(gauge.ceiling) : "no ceiling";
|
|
619
|
+
ceilinginput.setAttribute("aria-label", "concurrent task tab ceiling");
|
|
620
|
+
const ceilingbutton = button("Save task tab ceiling", async () => {
|
|
621
|
+
await request({ kind: "settasktabceiling", ceiling: ceilinginput.value === "" ? void 0 : Number(ceilinginput.value) });
|
|
622
|
+
status(`Task tab ceiling saved as ${ceilinginput.value === "" ? "no ceiling" : ceilinginput.value}; the value stays a user choice.`);
|
|
623
|
+
await refresh();
|
|
624
|
+
});
|
|
625
|
+
tabswindowsroot.append(ceilinginput, " ", ceilingbutton);
|
|
626
|
+
const switcherheading = document.createElement("p");
|
|
627
|
+
switcherheading.textContent = "quick switcher (ordered by recency, filter by title or url):";
|
|
628
|
+
tabswindowsroot.append(switcherheading);
|
|
629
|
+
const filterinput = document.createElement("input");
|
|
630
|
+
filterinput.type = "search";
|
|
631
|
+
filterinput.placeholder = "filter open tabs";
|
|
632
|
+
filterinput.setAttribute("aria-label", "quick switcher filter");
|
|
633
|
+
const switchlist = document.createElement("ul");
|
|
634
|
+
const renderswitchlist = () => {
|
|
635
|
+
switchlist.replaceChildren();
|
|
636
|
+
const ordered = switcherlist(tabs, [], filterinput.value).slice(0, 10);
|
|
637
|
+
for (const tab of ordered) {
|
|
638
|
+
const item = document.createElement("li");
|
|
639
|
+
const jump = document.createElement("button");
|
|
640
|
+
jump.type = "button";
|
|
641
|
+
const badge = badges.find((entry) => entry.tabid === tab.tabid);
|
|
642
|
+
const meta = metas.find((entry) => entry.tabid === tab.tabid);
|
|
643
|
+
jump.textContent = `${tab.title || tab.url}${tab.pinned ? " \u{1F4CC}" : ""}${tab.audible || tab.muted ? ` ${tab.muted ? "\u{1F507}" : "\u{1F50A}"}` : ""}${badge ? ` [${badge.label}]` : ""}${meta && meta.labels.length > 0 ? ` (${meta.labels.join(", ")})` : ""}`;
|
|
644
|
+
jump.addEventListener("click", () => request({ kind: "jumptotab", tabid: tab.tabid }).then(() => status(`Jumped to tab ${tab.tabid}.`)).catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
645
|
+
item.append(jump);
|
|
646
|
+
switchlist.append(item);
|
|
647
|
+
}
|
|
648
|
+
if (ordered.length === 0) {
|
|
649
|
+
const empty = document.createElement("li");
|
|
650
|
+
empty.textContent = "no open tab matches the filter";
|
|
651
|
+
switchlist.append(empty);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
filterinput.addEventListener("input", renderswitchlist);
|
|
655
|
+
tabswindowsroot.append(filterinput, switchlist);
|
|
656
|
+
renderswitchlist();
|
|
657
|
+
const searchinput = document.createElement("input");
|
|
658
|
+
searchinput.type = "search";
|
|
659
|
+
searchinput.placeholder = "search across open tabs by title and url";
|
|
660
|
+
searchinput.setAttribute("aria-label", "searchtabs text");
|
|
661
|
+
const searchresults = document.createElement("ul");
|
|
662
|
+
const searchbutton = button("Run searchtabs", async () => {
|
|
663
|
+
const result = await request({ kind: "tabsearch", text: searchinput.value });
|
|
664
|
+
searchresults.replaceChildren();
|
|
665
|
+
for (const tab of result.matches.slice(0, 10)) {
|
|
666
|
+
const item = document.createElement("li");
|
|
667
|
+
const jump = document.createElement("button");
|
|
668
|
+
jump.type = "button";
|
|
669
|
+
jump.textContent = `${tab.title || tab.url} \xB7 ${tab.url}`;
|
|
670
|
+
jump.addEventListener("click", () => request({ kind: "jumptotab", tabid: tab.tabid }).then(() => status(`Jumped to tab ${tab.tabid}.`)).catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
671
|
+
item.append(jump);
|
|
672
|
+
searchresults.append(item);
|
|
673
|
+
}
|
|
674
|
+
status(`searchtabs matched ${result.matches.length} open tab${result.matches.length === 1 ? "" : "s"}.`);
|
|
675
|
+
});
|
|
676
|
+
tabswindowsroot.append(searchinput, " ", searchbutton, searchresults);
|
|
677
|
+
const clones = context.clones ?? [];
|
|
678
|
+
for (const clone of clones.slice(0, 3)) {
|
|
679
|
+
const warning = document.createElement("p");
|
|
680
|
+
warning.className = "bannercard";
|
|
681
|
+
warning.textContent = `duplicate tab warning: ${clone.tabids.length} open tabs share the url ${clone.url} (tabs ${clone.tabids.join(", ")})`;
|
|
682
|
+
tabswindowsroot.append(warning);
|
|
683
|
+
}
|
|
684
|
+
const groups = context.tabgroups ?? [];
|
|
685
|
+
if (groups.length > 0) {
|
|
686
|
+
const groupsheading = document.createElement("p");
|
|
687
|
+
groupsheading.textContent = "tab groups with colors and collapse states:";
|
|
688
|
+
tabswindowsroot.append(groupsheading);
|
|
689
|
+
const grouplist = document.createElement("ul");
|
|
690
|
+
for (const group of groups.slice(0, 6)) {
|
|
691
|
+
const item = document.createElement("li");
|
|
692
|
+
item.textContent = `${group.name} \xB7 ${group.color} \xB7 ${group.collapsed ? "collapsed" : "expanded"} \xB7 ${group.tabids.length} member tab${group.tabids.length === 1 ? "" : "s"}`;
|
|
693
|
+
grouplist.append(item);
|
|
694
|
+
}
|
|
695
|
+
tabswindowsroot.append(grouplist);
|
|
696
|
+
}
|
|
697
|
+
const windows = context.windows ?? [];
|
|
698
|
+
if (windows.length > 0) {
|
|
699
|
+
const windowsheading = document.createElement("p");
|
|
700
|
+
windowsheading.textContent = "windows with layouts and bounds:";
|
|
701
|
+
tabswindowsroot.append(windowsheading);
|
|
702
|
+
const windowlist = document.createElement("ul");
|
|
703
|
+
for (const item of windows.slice(0, 6)) {
|
|
704
|
+
const entry = document.createElement("li");
|
|
705
|
+
entry.textContent = `window ${item.windowid} \xB7 ${item.state} \xB7 bounds ${item.left}\xD7${item.top} ${item.width}\xD7${item.height}${item.incognito ? " \xB7 incognito, grants not inherited" : ""}${item.focused ? " \xB7 focused" : ""}`;
|
|
706
|
+
const closebutton = button("Close window", async () => {
|
|
707
|
+
const tasktabids = context.progress?.tasktabs ?? [];
|
|
708
|
+
const tabsoftask = (context.tabs ?? []).filter((tab) => tab.windowid === item.windowid && tasktabids.includes(tab.tabid));
|
|
709
|
+
const reviewed = tabsoftask.length > 1 ? window.confirm(`This window holds ${tabsoftask.length} task tabs. Close it anyway under explicit review?`) : true;
|
|
710
|
+
await request({ kind: "closewindow", windowid: item.windowid, reviewed });
|
|
711
|
+
status(`Closed window ${item.windowid}.`);
|
|
712
|
+
await refresh();
|
|
713
|
+
});
|
|
714
|
+
entry.append(" ", closebutton);
|
|
715
|
+
windowlist.append(entry);
|
|
716
|
+
}
|
|
717
|
+
tabswindowsroot.append(windowlist);
|
|
718
|
+
}
|
|
719
|
+
const layoutcontrols = document.createElement("p");
|
|
720
|
+
const layoutname = document.createElement("input");
|
|
721
|
+
layoutname.type = "text";
|
|
722
|
+
layoutname.placeholder = "layout name";
|
|
723
|
+
layoutname.setAttribute("aria-label", "layout name");
|
|
724
|
+
const savebutton = button("Save layout", async () => {
|
|
725
|
+
if (!active) {
|
|
726
|
+
status("Layout save stays inside an active session.", true);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
await request({ kind: "savelayout", name: layoutname.value });
|
|
730
|
+
status(`Saved the tab layout ${layoutname.value}.`);
|
|
731
|
+
await refresh();
|
|
732
|
+
});
|
|
733
|
+
const restorebutton = button("Restore layout", async () => {
|
|
734
|
+
if (!active) {
|
|
735
|
+
status("Layout restore stays inside an active session.", true);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const result = await request({ kind: "restorelayout", name: layoutname.value });
|
|
739
|
+
status(`Restored the tab layout ${layoutname.value}: ${result.reopened} tab${result.reopened === 1 ? "" : "s"} reopened.`);
|
|
740
|
+
await refresh();
|
|
741
|
+
});
|
|
742
|
+
layoutcontrols.append(layoutname, " ", savebutton, " ", restorebutton);
|
|
743
|
+
tabswindowsroot.append(layoutcontrols);
|
|
744
|
+
const layouts = context.layouts ?? [];
|
|
745
|
+
if (layouts.length > 0) {
|
|
746
|
+
const layoutlist = document.createElement("ul");
|
|
747
|
+
for (const layout of layouts.slice(0, 4)) {
|
|
748
|
+
const item = document.createElement("li");
|
|
749
|
+
item.textContent = `${layout.name} \xB7 ${layout.tabs.length} tab${layout.tabs.length === 1 ? "" : "s"} \xB7 ${layout.groups.length} group${layout.groups.length === 1 ? "" : "s"} \xB7 ${layout.windows.length} window bound${layout.windows.length === 1 ? "" : "s"} \xB7 saved ${new Date(layout.savedat).toLocaleString()}`;
|
|
750
|
+
layoutlist.append(item);
|
|
751
|
+
}
|
|
752
|
+
tabswindowsroot.append(layoutlist);
|
|
753
|
+
}
|
|
754
|
+
const snapshots = context.snapshots ?? [];
|
|
755
|
+
if (snapshots.length > 0) {
|
|
756
|
+
const snapcard = document.createElement("div");
|
|
757
|
+
snapcard.className = "bannercard";
|
|
758
|
+
const snaptitle = document.createElement("p");
|
|
759
|
+
snaptitle.textContent = `session snapshot card: ${snapshots.length} snapshot${snapshots.length === 1 ? "" : "s"} stored`;
|
|
760
|
+
snapcard.append(snaptitle);
|
|
761
|
+
for (const snapshot of snapshots.slice(0, 3)) {
|
|
762
|
+
const row = document.createElement("p");
|
|
763
|
+
row.textContent = `${snapshot.layout.tabs.length} tabs \xB7 captured ${new Date(snapshot.capturedat).toLocaleString()}`;
|
|
764
|
+
const restore = button("Restore snapshot", async () => {
|
|
765
|
+
const result = await request({ kind: "restoresnapshot", id: snapshot.id });
|
|
766
|
+
status(`Restored the session snapshot: ${result.reopened} tab${result.reopened === 1 ? "" : "s"} reopened.`);
|
|
767
|
+
await refresh();
|
|
768
|
+
});
|
|
769
|
+
row.append(" ", restore);
|
|
770
|
+
snapcard.append(row);
|
|
771
|
+
}
|
|
772
|
+
tabswindowsroot.append(snapcard);
|
|
773
|
+
}
|
|
774
|
+
const controlcard = document.createElement("div");
|
|
775
|
+
controlcard.className = "bannercard";
|
|
776
|
+
const controlstate = context.controltab;
|
|
777
|
+
const completed = context.progress?.completedsteps.length ?? 0;
|
|
778
|
+
const total = context.plan?.steps.length ?? 0;
|
|
779
|
+
controlcard.textContent = `pinned control tab feed: ${controlstate?.enabled ? `open as tab ${controlstate.tabid} with the live task status ${completed} of ${total} reviewed steps executed` : "disabled"}${context.plan ? ` \xB7 ${context.plan.state}` : " \xB7 no plan"}`;
|
|
780
|
+
const controlbutton = button(controlstate?.enabled ? "Close pinned control tab" : "Open pinned control tab", async () => {
|
|
781
|
+
await request({ kind: "controltab", enabled: !controlstate?.enabled });
|
|
782
|
+
status(controlstate?.enabled ? "The pinned control tab was closed." : "The pinned control tab was opened with the live task feed.");
|
|
783
|
+
await refresh();
|
|
784
|
+
});
|
|
785
|
+
controlcard.append(" ", controlbutton);
|
|
786
|
+
tabswindowsroot.append(controlcard);
|
|
787
|
+
}
|
|
403
788
|
function rendercapabilities(report) {
|
|
404
789
|
if (!capabilitiestext) return;
|
|
405
790
|
if (!report) {
|
|
@@ -443,6 +828,9 @@ async function refresh() {
|
|
|
443
828
|
renderdiffs(context.diffs ?? []);
|
|
444
829
|
renderbanners(context.banners ?? []);
|
|
445
830
|
renderselectors(context.selectors ?? []);
|
|
831
|
+
rendertrail(context.trail?.trail ?? []);
|
|
832
|
+
rendernavigation(context);
|
|
833
|
+
rendertabswindows(context);
|
|
446
834
|
renderaudit(context.audit);
|
|
447
835
|
rendercapabilities(context.capabilities);
|
|
448
836
|
if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|