@wenathlan/extension 1.1.36 → 1.1.38

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.
@@ -1,7 +1,7 @@
1
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"]);
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", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract"]);
3
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"]);
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", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance"]);
5
5
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
6
6
 
7
7
  // extension/tabscommand.ts
@@ -17,6 +17,30 @@ function switcherlist(tabs, recency, filter) {
17
17
  });
18
18
  }
19
19
 
20
+ // extension/pageforms.ts
21
+ function cardmask(value) {
22
+ const trimmed = value.trim();
23
+ if (/^\d[\d\s-]{11,18}$/.test(trimmed)) {
24
+ const compact = trimmed.replace(/[\s-]/g, "");
25
+ const last = compact.slice(-4);
26
+ return `${"\u2022".repeat(Math.max(0, compact.length - 4))}${last}`;
27
+ }
28
+ return "\u2022".repeat(trimmed.length);
29
+ }
30
+
31
+ // extension/datacommand.ts
32
+ function sortrows(rows, key, direction) {
33
+ const sign = direction === "desc" ? -1 : 1;
34
+ return [...rows].sort((left, right) => {
35
+ const a = left[key] ?? "";
36
+ const b = right[key] ?? "";
37
+ const numeric = Number(a);
38
+ const numericb = Number(b);
39
+ if (Number.isFinite(numeric) && Number.isFinite(numericb) && a.trim() !== "" && b.trim() !== "") return (numeric - numericb) * sign;
40
+ return a.localeCompare(b) * sign;
41
+ });
42
+ }
43
+
20
44
  // extension/sidepanel.ts
21
45
  var objective = document.querySelector("#objective");
22
46
  var localbutton = document.querySelector("#localplan");
@@ -36,6 +60,8 @@ var selectorsroot = document.querySelector("#selectors");
36
60
  var trailroot = document.querySelector("#trail");
37
61
  var navigationroot = document.querySelector("#navigation");
38
62
  var tabswindowsroot = document.querySelector("#tabswindows");
63
+ var formsroot = document.querySelector("#forms");
64
+ var datasetsroot = document.querySelector("#datasets");
39
65
  var statusnode = document.querySelector("#status");
40
66
  var progressnode = document.querySelector("#planprogress");
41
67
  var capabilitiestext = document.querySelector("#capabilitiestext");
@@ -68,7 +94,7 @@ function button(label, action, disabled = false) {
68
94
  element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
69
95
  return element;
70
96
  }
71
- 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"];
97
+ 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", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword"];
72
98
  var topictags = [
73
99
  { topic: "pointer", kinds: ["movepointer", "clickpoint", "shiftclick", "clicktext", "clickaria", "clickname", "pierceshadow"] },
74
100
  { topic: "typing", kinds: ["typetime", "appendtext", "setvalue", "typeedit", "submitsearch"] },
@@ -82,7 +108,8 @@ var topictags = [
82
108
  { topic: "detection", kinds: ["detectlists", "detecttables", "detectinfinitescroll", "detectvirtual", "detectlazy", "detectsticky", "detectscrolllock", "countpages", "classifypage", "fingerprintsection"] },
83
109
  { topic: "watch", kinds: ["watchmutate", "watchbanner", "watchfocus", "waitquiet", "readjson", "diffsnapshots", "deriveselector"] },
84
110
  { 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"] }
111
+ { 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"] },
112
+ { topic: "forms", kinds: ["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"] }
86
113
  ];
87
114
  function steptopic(kind) {
88
115
  return topictags.find((tag) => tag.kinds.includes(kind))?.topic;
@@ -137,6 +164,19 @@ function quietdetail(step, outcomes) {
137
164
  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"}`;
138
165
  return node;
139
166
  }
167
+ function loopdetail(step, progress) {
168
+ if (step.kind !== "looprows") return null;
169
+ const iterations = (progress?.outcomes ?? []).filter((outcome) => outcome.stepid === step.id && outcome.details?.iteration !== void 0);
170
+ if (iterations.length === 0) return null;
171
+ const node = document.createElement("p");
172
+ node.className = "timeline";
173
+ const latest = iterations[iterations.length - 1];
174
+ if (!latest) return null;
175
+ const variables = latest.details?.variables;
176
+ const shown = variables ? Object.entries(variables).slice(0, 4).map(([key, value]) => `${key}=${value}`).join(", ") : "";
177
+ node.textContent = `loop timeline: ${iterations.length} iteration${iterations.length === 1 ? "" : "s"} \xB7 ${String(latest.details?.variable ?? "row")} variables ${shown}`;
178
+ return node;
179
+ }
140
180
  function navlistdetail(step, progress, outcomes) {
141
181
  if (step.kind !== "navlist") return null;
142
182
  const entries = (progress?.outcomes ?? []).filter((outcome) => outcome.stepid === step.id && outcome.details?.naventry !== void 0).map((outcome) => outcome.details?.naventry);
@@ -195,6 +235,8 @@ function stepitem(plan, step, completed, outcomes, retries, progress) {
195
235
  if (timeline) item.append(timeline);
196
236
  const navlist = navlistdetail(step, progress, outcomes);
197
237
  if (navlist) item.append(navlist);
238
+ const loopvars = loopdetail(step, progress);
239
+ if (loopvars) item.append(loopvars);
198
240
  const redirects = redirectdetail(step, outcomes);
199
241
  if (redirects) item.append(redirects);
200
242
  const preview = renderpreview(step);
@@ -785,6 +827,314 @@ function rendertabswindows(context) {
785
827
  controlcard.append(" ", controlbutton);
786
828
  tabswindowsroot.append(controlcard);
787
829
  }
830
+ function renderforms(context) {
831
+ if (!formsroot) return;
832
+ formsroot.replaceChildren();
833
+ const outcomes = context.outcomes ?? [];
834
+ const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
835
+ const opencaptcha = (context.captchas ?? []).find((handoff) => !handoff.resolved);
836
+ if (opencaptcha) {
837
+ const card = document.createElement("div");
838
+ card.className = "bannercard";
839
+ card.textContent = `Captcha handoff open on ${opencaptcha.origin}: control is yours and the plan waits until you resolve it.`;
840
+ card.append(" ", button("Captcha resolved", async () => {
841
+ await request({ kind: "resolvecaptcha" });
842
+ status("Captcha handoff resolved; the plan continues.");
843
+ await refresh();
844
+ }));
845
+ formsroot.append(card);
846
+ }
847
+ const detections = context.detections ?? [];
848
+ if (detections.length > 0) {
849
+ const badges = document.createElement("p");
850
+ badges.textContent = `template badges: ${detections.slice(0, 6).map((record) => `${record.kind} on ${record.origin}${record.markers.length > 0 ? ` (${record.markers.join(", ")})` : ""}`).join(" \xB7 ")}`;
851
+ formsroot.append(badges);
852
+ }
853
+ const mapoutcome = [...outcomes].reverse().find((outcome) => outcome.details?.report !== void 0 && outcome.details?.count !== void 0);
854
+ const skippedselectors = new Set(outcomes.flatMap((outcome) => Array.isArray(outcome.details?.skipped) ? outcome.details?.skipped : []).map((trap) => trap.selector));
855
+ if (mapoutcome) {
856
+ const report = mapoutcome.details?.report;
857
+ const heading = document.createElement("p");
858
+ heading.textContent = `form map${report.form ? ` of ${report.form}` : ""}: ${report.fields.length} detected field${report.fields.length === 1 ? "" : "s"} with their kinds${skippedselectors.size > 0 ? `; ${skippedselectors.size} honeypot field${skippedselectors.size === 1 ? "" : "s"} highlighted as skipped` : ""}`;
859
+ formsroot.append(heading);
860
+ const list = document.createElement("ul");
861
+ for (const field of report.fields.slice(0, 10)) {
862
+ const item = document.createElement("li");
863
+ const skipped = skippedselectors.has(field.selector);
864
+ item.textContent = `${field.label || field.selector} \xB7 ${field.kind}${field.matched ? "" : " \xB7 unmatched"}${skipped ? " \xB7 honeypot, skipped" : ""}`;
865
+ list.append(item);
866
+ }
867
+ formsroot.append(list);
868
+ }
869
+ const valuesoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.values) && outcome.details?.locale !== void 0);
870
+ if (valuesoutcome) {
871
+ const values = valuesoutcome.details?.values;
872
+ const locale = typeof valuesoutcome.details?.locale === "string" ? valuesoutcome.details.locale : "en";
873
+ const seed = typeof valuesoutcome.details?.seed === "number" ? valuesoutcome.details.seed : 1;
874
+ const heading = document.createElement("p");
875
+ heading.textContent = `generated values (locale ${locale}, seed ${seed}) with a regenerate button per field:`;
876
+ formsroot.append(heading);
877
+ const list = document.createElement("ul");
878
+ for (const entry of values.slice(0, 10)) {
879
+ const item = document.createElement("li");
880
+ item.textContent = `${entry.label} \xB7 ${entry.kind} \xB7 ${entry.value}`;
881
+ item.append(" ", button("Regenerate", async () => {
882
+ const regenerated = await request({ kind: "regeneratevalue", field: entry.kind, locale, seed: seed + 1 });
883
+ status(`Regenerated ${entry.label}: ${regenerated.value}.`);
884
+ }));
885
+ list.append(item);
886
+ }
887
+ formsroot.append(list);
888
+ }
889
+ const cardoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.segments));
890
+ if (cardoutcome) {
891
+ const segments = cardoutcome.details?.segments;
892
+ const cardline = document.createElement("p");
893
+ cardline.textContent = `card fill segments (masked): ${segments.map((segment) => `${segment.label} ${cardmask(segment.masked)}`).join(" \xB7 ")}`;
894
+ formsroot.append(cardline);
895
+ }
896
+ const profiles = context.profiles ?? [];
897
+ if (profiles.length > 0) {
898
+ const heading = document.createElement("p");
899
+ heading.textContent = "saved form profiles with origin grants:";
900
+ formsroot.append(heading);
901
+ const list = document.createElement("ul");
902
+ for (const profile of profiles.slice(0, 6)) {
903
+ const item = document.createElement("li");
904
+ item.textContent = `${profile.name} \xB7 ${profile.fields.length} field${profile.fields.length === 1 ? "" : "s"} \xB7 grants ${profile.grants.join(", ")} \xB7 saved ${new Date(profile.savedat).toLocaleString()}`;
905
+ item.append(" ", button("Apply", async () => {
906
+ const applied = await request({ kind: "applyprofile", name: profile.name });
907
+ pickhint(`profile hint: ${profile.name} with ${applied.profile.fields.length} reviewed field entries`);
908
+ }), " ", button("Remove", async () => {
909
+ await request({ kind: "removeprofile", name: profile.name });
910
+ status(`Form profile ${profile.name} removed.`);
911
+ await refresh();
912
+ }));
913
+ list.append(item);
914
+ }
915
+ formsroot.append(list);
916
+ }
917
+ const pending = (context.tickets ?? []).filter((ticket) => ticket.approved === void 0);
918
+ for (const ticket of pending) {
919
+ const card = document.createElement("div");
920
+ card.className = "bannercard";
921
+ const title = document.createElement("p");
922
+ title.textContent = `asksubmit for form ${ticket.form || "the reviewed form"} \xB7 values hash ${ticket.valueshash}`;
923
+ card.append(title);
924
+ const askoutcome = [...outcomes].reverse().find((outcome) => Array.isArray(outcome.details?.values) && outcome.details?.ticket !== void 0);
925
+ const values = askoutcome?.details?.values;
926
+ if (values) {
927
+ const diff = document.createElement("ul");
928
+ for (const entry of values.slice(0, 10)) {
929
+ const item = document.createElement("li");
930
+ item.textContent = `${entry.label}: ${entry.value}`;
931
+ diff.append(item);
932
+ }
933
+ card.append(diff);
934
+ } else {
935
+ const note = document.createElement("p");
936
+ note.textContent = "The full values diff appears here once the asksubmit step reads the form.";
937
+ card.append(note);
938
+ }
939
+ card.append(button("Approve submission", async () => {
940
+ await request({ kind: "approvesubmit", id: ticket.id, approved: true });
941
+ status("Submission approved; the reviewed submitform step may run.");
942
+ await refresh();
943
+ }), " ", button("Decline", async () => {
944
+ await request({ kind: "approvesubmit", id: ticket.id, approved: false });
945
+ status("Submission declined.");
946
+ await refresh();
947
+ }));
948
+ formsroot.append(card);
949
+ }
950
+ const wizards = context.wizards?.wizards ?? [];
951
+ if (wizards.length > 0) {
952
+ const wizard = wizards[0];
953
+ const heading = document.createElement("p");
954
+ const indicators = Array.from({ length: wizard.steps }, (_, index) => `${index < wizard.index ? wizard.completed[index] ? "\u2713" : "\xB7" : "\u25CB"}`).join(" ");
955
+ heading.textContent = `wizard progress: step ${Math.min(wizard.index + 1, wizard.steps)} of ${wizard.steps} ${indicators}`;
956
+ formsroot.append(heading);
957
+ }
958
+ const picks = context.wizards?.picks ?? [];
959
+ if (picks.length > 0) {
960
+ const pickline = document.createElement("p");
961
+ pickline.textContent = `typeahead picks: ${picks.slice(0, 6).map((pick) => `"${pick.pick}" for "${pick.query}"`).join(" \xB7 ")}`;
962
+ formsroot.append(pickline);
963
+ }
964
+ const reports = context.errorreports ?? [];
965
+ if (reports.length > 0) {
966
+ const heading = document.createElement("p");
967
+ heading.textContent = "inline error reports with field refs for correction loops:";
968
+ formsroot.append(heading);
969
+ const list = document.createElement("ul");
970
+ for (const report of reports.slice(0, 3)) {
971
+ const item = document.createElement("li");
972
+ item.textContent = `${report.form || "the reviewed form"}: ${report.errors.map((error) => `${error.field} \u2014 ${error.message}`).join("; ") || "no message"}`;
973
+ list.append(item);
974
+ }
975
+ formsroot.append(list);
976
+ }
977
+ const codecard = document.createElement("div");
978
+ codecard.className = "bannercard";
979
+ const codetitle = document.createElement("p");
980
+ codetitle.textContent = active ? "one time code entry (stored behind the consent gate of the active session):" : "one time code entry needs an active session first.";
981
+ codecard.append(codetitle);
982
+ if (active) {
983
+ const codeinput = document.createElement("input");
984
+ codeinput.type = "text";
985
+ codeinput.inputMode = "numeric";
986
+ codeinput.placeholder = context.codeentry ? "a reviewed code is stored" : "one time code";
987
+ codeinput.setAttribute("aria-label", "one time code");
988
+ const storebutton = button("Store reviewed code", async () => {
989
+ await request({ kind: "storecode", code: codeinput.value });
990
+ status("The reviewed one time code is stored behind the consent gate.");
991
+ await refresh();
992
+ });
993
+ codecard.append(codeinput, " ", storebutton);
994
+ }
995
+ formsroot.append(codecard);
996
+ }
997
+ function renderdatasets(context) {
998
+ if (!datasetsroot) return;
999
+ datasetsroot.replaceChildren();
1000
+ const outcomes = context.outcomes ?? [];
1001
+ const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
1002
+ const extractsessions = context.extractsessions ?? [];
1003
+ for (const extract of extractsessions.slice(0, 4)) {
1004
+ const card = document.createElement("p");
1005
+ const interrupted2 = extract.done !== true;
1006
+ card.textContent = `extraction ${extract.name}: ${extract.pages.length} page${extract.pages.length === 1 ? "" : "s"} visited \xB7 ${extract.rows} row${extract.rows === 1 ? "" : "s"} collected \xB7 cursor ${extract.cursor} of ${extract.planned}${extract.done ? " \xB7 complete" : " \xB7 interrupted"}`;
1007
+ datasetsroot.append(card);
1008
+ }
1009
+ const interrupted = extractsessions.find((extract) => extract.done !== true);
1010
+ if (interrupted && active) {
1011
+ const prompt = document.createElement("div");
1012
+ prompt.className = "bannercard";
1013
+ prompt.textContent = `Extraction ${interrupted.name} was interrupted at cursor ${interrupted.cursor}; run its resumeextract reviewed step to continue from the stored cursor.`;
1014
+ datasetsroot.append(prompt);
1015
+ }
1016
+ const streams = context.streams ?? [];
1017
+ if (streams.length > 0) {
1018
+ const stream = streams[0];
1019
+ const card = document.createElement("p");
1020
+ card.textContent = `stream state: ${stream.name} chunk ${stream.chunk} of ${stream.chunks} \xB7 ${stream.written} row${stream.written === 1 ? "" : "s"} written${stream.done ? " \xB7 complete" : " \xB7 resumable"}`;
1021
+ datasetsroot.append(card);
1022
+ }
1023
+ const rules = (context.taskrules ?? [])[0];
1024
+ if (rules && rules.transforms.length > 0) {
1025
+ const transforms = document.createElement("p");
1026
+ transforms.textContent = `transform rules: ${rules.transforms.map((rule) => `${rule.sources.join("+")} \u2192 ${rule.target} (${rule.expression})`).join(" \xB7 ")}`;
1027
+ datasetsroot.append(transforms);
1028
+ }
1029
+ if (rules && rules.dedupekeys.length > 0) {
1030
+ const keys = document.createElement("p");
1031
+ keys.textContent = `dedupe keys: ${rules.dedupekeys.join(", ")}`;
1032
+ datasetsroot.append(keys);
1033
+ }
1034
+ const dedupeoutcome = [...outcomes].reverse().find((outcome) => outcome.details?.dedupe !== void 0);
1035
+ if (dedupeoutcome) {
1036
+ const dedupe = dedupeoutcome.details?.dedupe;
1037
+ const card = document.createElement("p");
1038
+ card.textContent = `last dedupe: removed ${dedupe.removed} duplicate row${dedupe.removed === 1 ? "" : "s"}, kept ${dedupe.kept} by ${dedupe.keys.join(", ")}`;
1039
+ datasetsroot.append(card);
1040
+ }
1041
+ for (const datasetvalue of (context.datasets ?? []).slice(0, 4)) {
1042
+ const card = document.createElement("div");
1043
+ card.className = "panel";
1044
+ const head = document.createElement("p");
1045
+ head.textContent = `dataset ${datasetvalue.name}: ${datasetvalue.rows.length} row${datasetvalue.rows.length === 1 ? "" : "s"} \xB7 ${datasetvalue.columns.length} column${datasetvalue.columns.length === 1 ? "" : "s"}${(context.imports ?? []).some((item) => item.id === datasetvalue.id) ? " \xB7 imported csv" : ""}`;
1046
+ card.append(head);
1047
+ const grid = document.createElement("table");
1048
+ const headerrow = document.createElement("tr");
1049
+ for (const column of datasetvalue.columns.slice(0, 6)) {
1050
+ const cell = document.createElement("th");
1051
+ cell.textContent = `${column.label || column.key} ${column.kind === "number" ? "#" : ""}`;
1052
+ cell.addEventListener("click", () => {
1053
+ const body2 = grid.querySelector("tbody");
1054
+ if (!body2) return;
1055
+ const sorted = sortrows(datasetvalue.rows.slice(0, 5), column.key, cell.dataset.sorted === "asc" ? "desc" : "asc");
1056
+ cell.dataset.sorted = cell.dataset.sorted === "asc" ? "desc" : "asc";
1057
+ body2.replaceChildren(...sorted.map((row) => {
1058
+ const line = document.createElement("tr");
1059
+ for (const columnspec of datasetvalue.columns.slice(0, 6)) {
1060
+ const value = document.createElement("td");
1061
+ value.textContent = row[columnspec.key] ?? "";
1062
+ line.append(value);
1063
+ }
1064
+ return line;
1065
+ }));
1066
+ });
1067
+ headerrow.append(cell);
1068
+ }
1069
+ grid.append(headerrow);
1070
+ const body = document.createElement("tbody");
1071
+ for (const row of datasetvalue.rows.slice(0, 5)) {
1072
+ const line = document.createElement("tr");
1073
+ for (const column of datasetvalue.columns.slice(0, 6)) {
1074
+ const cell = document.createElement("td");
1075
+ cell.textContent = row[column.key] ?? "";
1076
+ line.append(cell);
1077
+ }
1078
+ body.append(line);
1079
+ }
1080
+ grid.append(body);
1081
+ card.append(grid);
1082
+ if (active) {
1083
+ const actions = document.createElement("p");
1084
+ for (const format of ["csv", "json", "excel"]) {
1085
+ actions.append(" ", button(`Export ${format}`, async () => {
1086
+ const artifact = await request({ kind: "exportdataset", datasetid: datasetvalue.id, format });
1087
+ status(`Exported ${datasetvalue.name} to ${artifact.name} with checksum ${artifact.checksum}.`);
1088
+ await refresh();
1089
+ }));
1090
+ }
1091
+ card.append(actions);
1092
+ }
1093
+ datasetsroot.append(card);
1094
+ }
1095
+ const provenances = context.provenances ?? [];
1096
+ if (provenances.length > 0) {
1097
+ const list = document.createElement("ul");
1098
+ for (const record of provenances.slice(0, 6)) {
1099
+ const item = document.createElement("li");
1100
+ item.textContent = `${record.name}: rows ${record.rowstart}\u2013${record.rowend} \xB7 checksum ${record.checksum} \xB7 source ${record.url}`;
1101
+ list.append(item);
1102
+ }
1103
+ datasetsroot.append(list);
1104
+ }
1105
+ const sheetendpoints = context.sheetendpoints ?? [];
1106
+ if (sheetendpoints.length > 0) {
1107
+ const sheets = document.createElement("p");
1108
+ sheets.textContent = `sheet endpoints: ${sheetendpoints.map((config) => `${config.origin} ${config.granted ? "granted" : "not granted"}`).join(" \xB7 ")}`;
1109
+ datasetsroot.append(sheets);
1110
+ }
1111
+ if (active) {
1112
+ const importer = document.createElement("div");
1113
+ importer.className = "panel";
1114
+ const csvinput = document.createElement("textarea");
1115
+ csvinput.rows = 3;
1116
+ csvinput.placeholder = "Paste reviewed csv content for a fill loop (header line first).";
1117
+ const nameinput = document.createElement("input");
1118
+ nameinput.placeholder = "dataset name (optional)";
1119
+ const mappinginput = document.createElement("input");
1120
+ mappinginput.placeholder = "column mapping json (optional, csv header \u2192 target)";
1121
+ importer.append(csvinput, nameinput, mappinginput, " ", button("Import csv", async () => {
1122
+ let mapping = {};
1123
+ if (mappinginput.value.trim()) {
1124
+ try {
1125
+ mapping = JSON.parse(mappinginput.value);
1126
+ } catch {
1127
+ status("The column mapping must be a json object.", true);
1128
+ return;
1129
+ }
1130
+ }
1131
+ const imported = await request({ kind: "importcsv", csv: csvinput.value, name: nameinput.value, mapping });
1132
+ status(`Imported ${imported.rows} rows as dataset ${imported.name} for fill loops.`);
1133
+ await refresh();
1134
+ }));
1135
+ datasetsroot.append(importer);
1136
+ }
1137
+ }
788
1138
  function rendercapabilities(report) {
789
1139
  if (!capabilitiestext) return;
790
1140
  if (!report) {
@@ -831,6 +1181,8 @@ async function refresh() {
831
1181
  rendertrail(context.trail?.trail ?? []);
832
1182
  rendernavigation(context);
833
1183
  rendertabswindows(context);
1184
+ renderforms(context);
1185
+ renderdatasets(context);
834
1186
  renderaudit(context.audit);
835
1187
  rendercapabilities(context.capabilities);
836
1188
  if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");