@wenathlan/extension 1.1.63 → 1.1.65

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.
Files changed (55) hide show
  1. package/README.md +5 -3
  2. package/dist/datagrid.d.ts +46 -0
  3. package/dist/datagrid.d.ts.map +1 -0
  4. package/dist/evidenceviews.d.ts +45 -0
  5. package/dist/evidenceviews.d.ts.map +1 -0
  6. package/dist/index.d.ts +11 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1155 -1
  9. package/dist/index.js.map +4 -4
  10. package/dist/memory.d.ts +65 -1
  11. package/dist/memory.d.ts.map +1 -1
  12. package/dist/pickerviews.d.ts +72 -0
  13. package/dist/pickerviews.d.ts.map +1 -0
  14. package/dist/planreview.d.ts +87 -0
  15. package/dist/planreview.d.ts.map +1 -0
  16. package/dist/policy.d.ts +105 -0
  17. package/dist/policy.d.ts.map +1 -1
  18. package/dist/portability.d.ts +35 -0
  19. package/dist/portability.d.ts.map +1 -0
  20. package/dist/protocol.d.ts +248 -0
  21. package/dist/protocol.d.ts.map +1 -1
  22. package/dist/quickactions.d.ts +46 -0
  23. package/dist/quickactions.d.ts.map +1 -0
  24. package/dist/siteprefs.d.ts +45 -0
  25. package/dist/siteprefs.d.ts.map +1 -0
  26. package/dist/statusviews.d.ts +65 -0
  27. package/dist/statusviews.d.ts.map +1 -0
  28. package/dist/surfaces.d.ts +59 -0
  29. package/dist/surfaces.d.ts.map +1 -0
  30. package/dist/tourviews.d.ts +28 -0
  31. package/dist/tourviews.d.ts.map +1 -0
  32. package/dist/types.d.ts +429 -3
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/version.d.ts +1 -1
  35. package/extension/dist/background.js +1567 -2
  36. package/extension/dist/background.js.map +4 -4
  37. package/extension/dist/dashboardpage.html +14 -0
  38. package/extension/dist/dashboardpage.js +147 -0
  39. package/extension/dist/dashboardpage.js.map +7 -0
  40. package/extension/dist/manifest.json +5 -2
  41. package/extension/dist/offscreen.js +1 -0
  42. package/extension/dist/offscreen.js.map +2 -2
  43. package/extension/dist/optionspage.html +18 -0
  44. package/extension/dist/optionspage.js +267 -0
  45. package/extension/dist/optionspage.js.map +7 -0
  46. package/extension/dist/pagebridge.js.map +1 -1
  47. package/extension/dist/popup.html +4 -1
  48. package/extension/dist/popup.js +257 -0
  49. package/extension/dist/popup.js.map +3 -3
  50. package/extension/dist/sidepanel.html +7 -2
  51. package/extension/dist/sidepanel.js +360 -0
  52. package/extension/dist/sidepanel.js.map +2 -2
  53. package/extension/dist/style.css +3 -1
  54. package/extension/manifest.json +5 -2
  55. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink dashboard</title><link rel="stylesheet" href="style.css"></head>
4
+ <body><main class="wide"><header><p class="eyebrow">DASHBOARD</p><h1>Full page view</h1><p>Every session, run, note and transparency view of the profile workspace in one page.</p></header>
5
+ <nav class="actions"><button id="openpanel" class="secondary">Open review panel</button><button id="openoptions" class="secondary">Open options</button><button id="openpopuphint" class="secondary" hidden>Return to popup</button></nav>
6
+ <p id="status" role="status">Loading the dashboard view.</p>
7
+ <section><h2>Sessions and runs</h2><div id="sessiongrid" class="panel"></div></section>
8
+ <section><h2>History search</h2><div class="actions"><input id="historyquery" placeholder="Search sessions, notes and summaries" aria-label="History search"><button id="historyrun" class="secondary">Search</button></div><ol id="historyhits" class="audit"></ol></section>
9
+ <section><h2>Site notes</h2><div id="sitenotes" class="panel"></div></section>
10
+ <section><h2>Transparency views</h2><div id="transparency" class="panel"></div></section>
11
+ <section><h2>Onboarding</h2><div id="onboarding" class="panel"></div></section>
12
+ <section><h2>Drop import</h2><div id="dropzone" class="panel" role="region" aria-label="Drop csv, json or workflow files to import">Drop csv, json or workflow files here.</div><p id="dropstatus" class="muted">The dashboardpage accepts the same dropimport files as the optionspage.</p></section>
13
+ </main><script type="module" src="dashboardpage.js"></script></body>
14
+ </html>
@@ -0,0 +1,147 @@
1
+ // extension/dashboardpage.ts
2
+ var statusnode = document.querySelector("#status");
3
+ var sessiongridroot = document.querySelector("#sessiongrid");
4
+ var historyhitsroot = document.querySelector("#historyhits");
5
+ var sitenotesroot = document.querySelector("#sitenotes");
6
+ var transparencyroot = document.querySelector("#transparency");
7
+ var onboardingroot = document.querySelector("#onboarding");
8
+ var historyinput = document.querySelector("#historyquery");
9
+ function status(message, error = false) {
10
+ if (statusnode) {
11
+ statusnode.textContent = message;
12
+ statusnode.dataset.state = error ? "error" : "ready";
13
+ }
14
+ }
15
+ async function request(message) {
16
+ const response = await chrome.runtime.sendMessage(message);
17
+ if (!response.ok) throw new Error(response.error);
18
+ return response.value;
19
+ }
20
+ function button(label, action) {
21
+ const element = document.createElement("button");
22
+ element.type = "button";
23
+ element.className = "secondary";
24
+ element.textContent = label;
25
+ element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
26
+ return element;
27
+ }
28
+ function rendersessiongrid(view) {
29
+ if (!sessiongridroot) return;
30
+ sessiongridroot.replaceChildren();
31
+ if (view.grid.length === 0) {
32
+ sessiongridroot.textContent = "No session exists yet; start the first run from the popup taskinput.";
33
+ return;
34
+ }
35
+ const list = document.createElement("ol");
36
+ list.className = "audit";
37
+ for (const row of view.grid) {
38
+ const item = document.createElement("li");
39
+ item.textContent = `${row.state} ${row.runid} \u2014 ${row.origins.join(", ")} \u2014 ${row.completed}/${row.steps} steps \u2014 ${row.outcome} \u2014 ${row.lock}${row.sealhash !== void 0 ? ` \u2014 sealed ${row.sealhash.slice(0, 12)}` : ""}`;
40
+ for (const action of row.actions) item.append(" ", button(action === "cancelrun" ? "Cancel run" : action === "resume" ? "Resume" : "Reopen", async () => {
41
+ await request({ kind: "sessions", grid: action === "cancelrun" ? { open: { runid: row.runid } } : action === "resume" ? { resume: { runid: row.runid } } : { reopen: { runid: row.runid } } });
42
+ if (action === "cancelrun") await request({ kind: "surface", bus: { action: { surface: "dashboardpage", command: "cancelrun" } } });
43
+ status(`The ${action} action of the run ${row.runid} routed through the command bus.`);
44
+ await render();
45
+ }));
46
+ list.append(item);
47
+ }
48
+ sessiongridroot.append(list);
49
+ }
50
+ async function renderhistorysearch(text) {
51
+ if (!historyhitsroot) return;
52
+ try {
53
+ const result = await request({ kind: "sessions", search: { query: { text } } });
54
+ historyhitsroot.replaceChildren();
55
+ if (result.hits.length === 0) {
56
+ historyhitsroot.append(Object.assign(document.createElement("li"), { textContent: "No history matches yet." }));
57
+ return;
58
+ }
59
+ for (const hit of result.hits) {
60
+ const item = document.createElement("li");
61
+ item.textContent = `${hit.source} \u2014 ${hit.title}${hit.origin !== void 0 ? ` (${hit.origin})` : ""}: ${hit.excerpt}${hit.highlights.length > 0 ? ` [${hit.highlights.join(", ")}]` : ""}`;
62
+ historyhitsroot.append(item);
63
+ }
64
+ } catch (error) {
65
+ historyhitsroot.textContent = error instanceof Error ? error.message : String(error);
66
+ }
67
+ }
68
+ function rendersitenotes(view) {
69
+ if (!sitenotesroot) return;
70
+ if (view.notes.length === 0) {
71
+ sitenotesroot.textContent = "No site note exists yet; write the first note from the sidepanel.";
72
+ return;
73
+ }
74
+ sitenotesroot.replaceChildren();
75
+ const list = document.createElement("ul");
76
+ list.className = "audit";
77
+ for (const note of view.notes) list.append(Object.assign(document.createElement("li"), { textContent: `${note.title} (${note.origin}, by ${note.author}${note.sensitive ? ", sealed at rest" : ""}): ${note.body}` }));
78
+ sitenotesroot.append(list);
79
+ }
80
+ function rendertransparency(view) {
81
+ if (!transparencyroot) return;
82
+ transparencyroot.replaceChildren();
83
+ const grants = document.createElement("p");
84
+ grants.textContent = `Grants: ${view.grants.length} grant row${view.grants.length === 1 ? "" : "s"}${view.grants.length > 0 ? ` (${view.grants.slice(0, 5).map((grant) => `${grant.origin} \u2014 ${grant.scope} \u2014 ${grant.boundary}`).join(" \xB7 ")})` : ""}.`;
85
+ const windows = document.createElement("p");
86
+ windows.textContent = `Consent windows: ${view.windows.length} recorded window${view.windows.length === 1 ? "" : "s"}.`;
87
+ const permdiffs = document.createElement("p");
88
+ permdiffs.textContent = `Permdiffs: ${view.permdiffs.length} record${view.permdiffs.length === 1 ? "" : "s"}${view.permdiffs.length > 0 ? ` (latest ${view.permdiffs[0]?.fromversion} \u2192 ${view.permdiffs[0]?.toversion})` : ""}.`;
89
+ const safedefaults = document.createElement("p");
90
+ safedefaults.textContent = `Safedefaults applications: ${view.safedefaults.length} origin${view.safedefaults.length === 1 ? "" : "s"} under the reads only posture.`;
91
+ transparencyroot.append(grants, windows, permdiffs, safedefaults);
92
+ }
93
+ function renderonboarding(onboarding) {
94
+ if (!onboardingroot) return;
95
+ onboardingroot.replaceChildren();
96
+ const state = document.createElement("p");
97
+ state.textContent = onboarding.done ? "The onboarding walkthrough is done; replay it on demand from the optionspage." : `The onboarding walkthrough stands at ${onboarding.stepscompleted.length} completed step${onboarding.stepscompleted.length === 1 ? "" : "s"}.`;
98
+ onboardingroot.append(state);
99
+ }
100
+ async function render() {
101
+ const view = await request({ kind: "surface", dashboard: { view: true } });
102
+ rendersessiongrid(view.sessionview);
103
+ rendersitenotes(view.sessionview);
104
+ rendertransparency(view.transparency);
105
+ renderonboarding(view.onboarding);
106
+ status(`Dashboard view live: ${view.sessionview.grid.length} run${view.sessionview.grid.length === 1 ? "" : "s"}, ${view.sessionview.notes.length} note${view.sessionview.notes.length === 1 ? "" : "s"} and ${view.transparency.grants.length} grant row${view.transparency.grants.length === 1 ? "" : "s"}.`);
107
+ }
108
+ document.querySelector("#openpanel")?.addEventListener("click", () => {
109
+ void chrome.sidePanel.open({ windowId: chrome.windows.WINDOW_ID_CURRENT }).catch(() => {
110
+ });
111
+ });
112
+ document.querySelector("#openoptions")?.addEventListener("click", () => {
113
+ void chrome.tabs.create({ url: chrome.runtime.getURL("optionspage.html") });
114
+ });
115
+ document.querySelector("#historyrun")?.addEventListener("click", () => {
116
+ void renderhistorysearch(historyinput?.value ?? "").catch((error) => status(error instanceof Error ? error.message : String(error), true));
117
+ });
118
+ historyinput?.addEventListener("keydown", (event) => {
119
+ if (event.key === "Enter") document.querySelector("#historyrun")?.click();
120
+ });
121
+ var surfacechannel = typeof BroadcastChannel === "function" ? new BroadcastChannel("devthinksurfaces") : void 0;
122
+ surfacechannel?.addEventListener("message", () => {
123
+ void render().catch(() => {
124
+ });
125
+ });
126
+ void render().catch((error) => status(error instanceof Error ? error.message : String(error), true));
127
+ void renderhistorysearch("").catch(() => {
128
+ });
129
+ var dropzonenode = document.querySelector("#dropzone");
130
+ var dropstatusnode = document.querySelector("#dropstatus");
131
+ if (dropzonenode) {
132
+ dropzonenode.addEventListener("dragover", (event) => {
133
+ event.preventDefault();
134
+ });
135
+ dropzonenode.addEventListener("drop", (event) => {
136
+ event.preventDefault();
137
+ const file = event.dataTransfer?.files[0];
138
+ if (file === void 0) return;
139
+ void file.text().then((head) => request({ kind: "views", dropimport: { file: { filename: file.name, bytes: file.size, head: head.slice(0, 2e3) } } })).then((result) => {
140
+ const session = result.session;
141
+ if (dropstatusnode) dropstatusnode.textContent = `The dropimport detected the ${session.kind} kind of ${session.filename} (${session.bytes} byte${session.bytes === 1 ? "" : "s"}); the import path takes the file from here.`;
142
+ }).catch((error) => {
143
+ if (dropstatusnode) dropstatusnode.textContent = error instanceof Error ? error.message : String(error);
144
+ });
145
+ });
146
+ }
147
+ //# sourceMappingURL=dashboardpage.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../dashboardpage.ts"],
4
+ "sourcesContent": ["export {};\n\n/**\n * Dashboardpage runtime of the 1.1.64 family.\n * The dashboardpage opens in a new tab from the popup and the sidepanel and aggregates the sessions, the runs, the notes and the transparency views of the profile workspace; it renders the sessiongrid and the historysearch of the 1.1.63 session interface beside the transparency report of the 1.1.62 family and the onboarding state, and it subscribes to session and run updates through the single broadcast channel so the view stays live without a reload.\n */\n\ntype sessionview = {\n grid: Array<{ sessionid: string; runid: string; origins: string[]; state: string; outcome: string; steps: number; completed: number; lock: string; tabid?: number; sealhash?: string; updatedat: number; actions: string[] }>;\n notes: Array<{ id: string; origin: string; title: string; body: string; author: string; sensitive: boolean; updatedat: number }>;\n summaries: Array<{ runid: string; origins: string[]; kinds: string[]; steps: number; provenance: string; distilledat: number }>;\n};\ntype transparencyview = {\n grants: Array<{ origin: string; scope: string; boundary: string; grantedat: number }>;\n windows: Array<{ id: string; origin: string; state: string; boundary: string; startedat: number; expiresat: number }>;\n permdiffs: Array<{ fromversion: string; toversion: string; added: string[]; removed: string[]; computedat: number }>;\n safedefaults: Array<{ origin: string; firstseenat: number }>;\n};\ntype dashboardview = { sessionview: sessionview; transparency: transparencyview; onboarding: { stepscompleted: string[]; done: boolean }; environments?: { offscreengranted: boolean; parseoffload: boolean; workers?: number }; security?: { posture?: string } };\n\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst sessiongridroot = document.querySelector<HTMLElement>(\"#sessiongrid\");\nconst historyhitsroot = document.querySelector<HTMLElement>(\"#historyhits\");\nconst sitenotesroot = document.querySelector<HTMLElement>(\"#sitenotes\");\nconst transparencyroot = document.querySelector<HTMLElement>(\"#transparency\");\nconst onboardingroot = document.querySelector<HTMLElement>(\"#onboarding\");\nconst historyinput = document.querySelector<HTMLInputElement>(\"#historyquery\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\nfunction button(label: string, action: () => Promise<void>): HTMLButtonElement { const element = document.createElement(\"button\"); element.type = \"button\"; element.className = \"secondary\"; element.textContent = label; element.addEventListener(\"click\", () => action().catch(error => status(error instanceof Error ? error.message : String(error), true))); return element; }\n\n/** Renders the sessiongrid rows of the 1.1.63 session interface with their resume, cancelrun and reopen actions as deep links. */\nfunction rendersessiongrid(view: sessionview): void {\n if (!sessiongridroot) return;\n sessiongridroot.replaceChildren();\n if (view.grid.length === 0) { sessiongridroot.textContent = \"No session exists yet; start the first run from the popup taskinput.\"; return; }\n const list = document.createElement(\"ol\");\n list.className = \"audit\";\n for (const row of view.grid) {\n const item = document.createElement(\"li\");\n item.textContent = `${row.state} ${row.runid} \u2014 ${row.origins.join(\", \")} \u2014 ${row.completed}/${row.steps} steps \u2014 ${row.outcome} \u2014 ${row.lock}${row.sealhash !== undefined ? ` \u2014 sealed ${row.sealhash.slice(0, 12)}` : \"\"}`;\n for (const action of row.actions) item.append(\" \", button(action === \"cancelrun\" ? \"Cancel run\" : action === \"resume\" ? \"Resume\" : \"Reopen\", async () => {\n await request({ kind: \"sessions\", grid: action === \"cancelrun\" ? { open: { runid: row.runid } } : action === \"resume\" ? { resume: { runid: row.runid } } : { reopen: { runid: row.runid } } });\n if (action === \"cancelrun\") await request({ kind: \"surface\", bus: { action: { surface: \"dashboardpage\", command: \"cancelrun\" } } });\n status(`The ${action} action of the run ${row.runid} routed through the command bus.`);\n await render();\n }));\n list.append(item);\n }\n sessiongridroot.append(list);\n}\n\n/** Renders the historysearch corpus of the 1.1.63 session interface with the matched terms highlighted. */\nasync function renderhistorysearch(text: string): Promise<void> {\n if (!historyhitsroot) return;\n try {\n const result = await request({ kind: \"sessions\", search: { query: { text } } }) as { hits: Array<{ source: string; title: string; excerpt: string; highlights: string[]; origin?: string; at: number }> };\n historyhitsroot.replaceChildren();\n if (result.hits.length === 0) { historyhitsroot.append(Object.assign(document.createElement(\"li\"), { textContent: \"No history matches yet.\" })); return; }\n for (const hit of result.hits) {\n const item = document.createElement(\"li\");\n item.textContent = `${hit.source} \u2014 ${hit.title}${hit.origin !== undefined ? ` (${hit.origin})` : \"\"}: ${hit.excerpt}${hit.highlights.length > 0 ? ` [${hit.highlights.join(\", \")}]` : \"\"}`;\n historyhitsroot.append(item);\n }\n } catch (error) { historyhitsroot.textContent = error instanceof Error ? error.message : String(error); }\n}\n\n/** Renders the site notes of the session interface with their author provenance. */\nfunction rendersitenotes(view: sessionview): void {\n if (!sitenotesroot) return;\n if (view.notes.length === 0) { sitenotesroot.textContent = \"No site note exists yet; write the first note from the sidepanel.\"; return; }\n sitenotesroot.replaceChildren();\n const list = document.createElement(\"ul\");\n list.className = \"audit\";\n for (const note of view.notes) list.append(Object.assign(document.createElement(\"li\"), { textContent: `${note.title} (${note.origin}, by ${note.author}${note.sensitive ? \", sealed at rest\" : \"\"}): ${note.body}` }));\n sitenotesroot.append(list);\n}\n\n/** Renders the transparency views of the 1.1.62 family: the grants with their boundaries, the window history, the permdiffs and the safedefaults applications. */\nfunction rendertransparency(view: transparencyview): void {\n if (!transparencyroot) return;\n transparencyroot.replaceChildren();\n const grants = document.createElement(\"p\");\n grants.textContent = `Grants: ${view.grants.length} grant row${view.grants.length === 1 ? \"\" : \"s\"}${view.grants.length > 0 ? ` (${view.grants.slice(0, 5).map(grant => `${grant.origin} \u2014 ${grant.scope} \u2014 ${grant.boundary}`).join(\" \u00B7 \")})` : \"\"}.`;\n const windows = document.createElement(\"p\");\n windows.textContent = `Consent windows: ${view.windows.length} recorded window${view.windows.length === 1 ? \"\" : \"s\"}.`;\n const permdiffs = document.createElement(\"p\");\n permdiffs.textContent = `Permdiffs: ${view.permdiffs.length} record${view.permdiffs.length === 1 ? \"\" : \"s\"}${view.permdiffs.length > 0 ? ` (latest ${view.permdiffs[0]?.fromversion} \u2192 ${view.permdiffs[0]?.toversion})` : \"\"}.`;\n const safedefaults = document.createElement(\"p\");\n safedefaults.textContent = `Safedefaults applications: ${view.safedefaults.length} origin${view.safedefaults.length === 1 ? \"\" : \"s\"} under the reads only posture.`;\n transparencyroot.append(grants, windows, permdiffs, safedefaults);\n}\n\n/** Renders the onboarding state with its replay offer on demand. */\nfunction renderonboarding(onboarding: { stepscompleted: string[]; done: boolean }): void {\n if (!onboardingroot) return;\n onboardingroot.replaceChildren();\n const state = document.createElement(\"p\");\n state.textContent = onboarding.done ? \"The onboarding walkthrough is done; replay it on demand from the optionspage.\" : `The onboarding walkthrough stands at ${onboarding.stepscompleted.length} completed step${onboarding.stepscompleted.length === 1 ? \"\" : \"s\"}.`;\n onboardingroot.append(state);\n}\n\n/** Loads the aggregated dashboard view: the sessionview of 1.1.63, the transparency report of 1.1.62 and the onboarding state. */\nasync function render(): Promise<void> {\n const view = await request({ kind: \"surface\", dashboard: { view: true } }) as dashboardview;\n rendersessiongrid(view.sessionview);\n rendersitenotes(view.sessionview);\n rendertransparency(view.transparency);\n renderonboarding(view.onboarding);\n status(`Dashboard view live: ${view.sessionview.grid.length} run${view.sessionview.grid.length === 1 ? \"\" : \"s\"}, ${view.sessionview.notes.length} note${view.sessionview.notes.length === 1 ? \"\" : \"s\"} and ${view.transparency.grants.length} grant row${view.transparency.grants.length === 1 ? \"\" : \"s\"}.`);\n}\n\ndocument.querySelector<HTMLButtonElement>(\"#openpanel\")?.addEventListener(\"click\", () => { void chrome.sidePanel.open({ windowId: chrome.windows.WINDOW_ID_CURRENT }).catch(() => { /* the panel opens beside the dashboard tab */ }); });\ndocument.querySelector<HTMLButtonElement>(\"#openoptions\")?.addEventListener(\"click\", () => { void chrome.tabs.create({ url: chrome.runtime.getURL(\"optionspage.html\") }); });\ndocument.querySelector<HTMLButtonElement>(\"#historyrun\")?.addEventListener(\"click\", () => { void renderhistorysearch(historyinput?.value ?? \"\").catch(error => status(error instanceof Error ? error.message : String(error), true)); });\nhistoryinput?.addEventListener(\"keydown\", event => { if (event.key === \"Enter\") document.querySelector<HTMLButtonElement>(\"#historyrun\")?.click(); });\n\n/** The dashboardpage subscribes to session and run updates through the single broadcast channel. */\nconst surfacechannel: BroadcastChannel | undefined = typeof BroadcastChannel === \"function\" ? new BroadcastChannel(\"devthinksurfaces\") : undefined;\nsurfacechannel?.addEventListener(\"message\", () => { void render().catch(() => { /* a failing refresh keeps the last rendered dashboard */ }); });\n\nvoid render().catch(error => status(error instanceof Error ? error.message : String(error), true));\nvoid renderhistorysearch(\"\").catch(() => { /* an empty first search shows the empty state */ });\n\n/**\n * Dropimport zone of the 1.1.65 family: the dashboardpage accepts the csv, json and workflow files the user drops with the same file kind detection and the same import path as the optionspage.\n */\nconst dropzonenode = document.querySelector<HTMLElement>(\"#dropzone\");\nconst dropstatusnode = document.querySelector<HTMLElement>(\"#dropstatus\");\nif (dropzonenode) {\n dropzonenode.addEventListener(\"dragover\", event => { event.preventDefault(); });\n dropzonenode.addEventListener(\"drop\", event => {\n event.preventDefault();\n const file = event.dataTransfer?.files[0];\n if (file === undefined) return;\n void file.text().then(head => request({ kind: \"views\", dropimport: { file: { filename: file.name, bytes: file.size, head: head.slice(0, 2000) } } })).then(result => {\n const session = (result as { session: { filename: string; kind: string; bytes: number } }).session;\n if (dropstatusnode) dropstatusnode.textContent = `The dropimport detected the ${session.kind} kind of ${session.filename} (${session.bytes} byte${session.bytes === 1 ? \"\" : \"s\"}); the import path takes the file from here.`;\n }).catch(error => { if (dropstatusnode) dropstatusnode.textContent = error instanceof Error ? error.message : String(error); });\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -2,7 +2,7 @@
2
2
  "manifest_version": 3,
3
3
  "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnOEjO8Z0PDgQyfvawGcaO2j+o0GLCFTLNj7TkYC/Avo9l2NenMRq7gp90Nfd7E9MViv/OMcCKSYZ5unv12QPRtv31C+a5UQWDFAOP/cH5mwMd6hsayElrSoW8ta+FwFqmr9dIFkn7cQEU3YhZr4Gcbs+ycUHOxVgDA4NBKB0rQ6e9VW5LvTw0isRYUrqM+M72vKxHk9zUIYYn/LGPvottKBYi2GLr0PHSeC2UE+Shmq7vcFIXj6hDjvD4kLJ5sKoUllEcZ1TPuBcnHUQ9ndKA5iktXDQOIJCUJmi7a0YJ2PGg7fvpYfT9k0ai/qZ+pIoRfoOEwE01bPoDn7NjeYnNQIDAQAB",
4
4
  "name": "Devthink",
5
- "version": "1.1.63",
5
+ "version": "1.1.65",
6
6
  "description": "A consent-first bridge for reviewed browser-agent tasks.",
7
7
  "permissions": [
8
8
  "activeTab",
@@ -44,8 +44,11 @@
44
44
  "side_panel": {
45
45
  "default_path": "sidepanel.html"
46
46
  },
47
+ "chrome_url_overrides": {
48
+ "newtab": "dashboardpage.html"
49
+ },
47
50
  "options_ui": {
48
- "page": "transparencypage.html",
51
+ "page": "optionspage.html",
49
52
  "open_in_tab": true
50
53
  }
51
54
  }
@@ -7,6 +7,7 @@ self.onmessage = event => {
7
7
  if (task === "jsonpayload") { JSON.parse(payload); result = payload; }
8
8
  else if (task === "htmlsnapshot" || task === "readertree" || task === "readoutline" || task === "classifypage") { const doc = new DOMParser().parseFromString(payload, "text/html"); result = (doc.body && doc.body.textContent ? doc.body.textContent : payload).trim(); }
9
9
  else if (task === "tablerows" || task === "a11ytree" || task === "complexselector" || task === "stitchshots") { result = payload; }
10
+ else if (task === "diffpreview") { const states = JSON.parse(payload); if (!states || typeof states !== "object" || typeof states.before !== "object" || typeof states.after !== "object") throw new Error("malformed diff states"); result = payload; }
10
11
  else result = payload;
11
12
  self.postMessage({ id, ok: true, result, summary: "The offscreen worker finished the " + task + " parse." });
12
13
  } catch (error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../offscreen.ts"],
4
- "sourcesContent": ["/**\n * Offscreen document runtime of the 1.1.60 family.\n * The offscreen document hosts the worker pool that parses heavy payloads away from the page and the sandbox frame iframe that renders untrusted markup without extension privileges; the background spawns this document on first use through the offscreen api only after the user grants the optional offscreen capability, and every parse task still rides a reviewed step of an approved plan.\n */\n\ntype parserequest = { kind: \"offscreen\"; action: \"parse\"; request: { id: string; runid: string; stepid: string; task: string; payload: string; transferables: string[] } };\ntype summaryrequest = { kind: \"offscreen\"; action: \"summary\"; request: { id: string; runid: string; stepid: string; task: string; payload: string; transferables: string[] } };\ntype renderrequest = { kind: \"offscreen\"; action: \"sandboxrender\"; render: { id: string; nonce: string; markup: string; sourceorigin: string; stepid: string } };\ntype poolrequest = { kind: \"offscreen\"; action: \"pool\" };\n\n/** The worker sources of the parse families: pure functions that shape html, json, table, a11y tree, selector and stitch payloads without touching any page. */\nconst workersource = `\nself.onmessage = event => {\n const { id, task, payload } = event.data || {};\n try {\n let result = \"\";\n if (task === \"jsonpayload\") { JSON.parse(payload); result = payload; }\n else if (task === \"htmlsnapshot\" || task === \"readertree\" || task === \"readoutline\" || task === \"classifypage\") { const doc = new DOMParser().parseFromString(payload, \"text/html\"); result = (doc.body && doc.body.textContent ? doc.body.textContent : payload).trim(); }\n else if (task === \"tablerows\" || task === \"a11ytree\" || task === \"complexselector\" || task === \"stitchshots\") { result = payload; }\n else result = payload;\n self.postMessage({ id, ok: true, result, summary: \"The offscreen worker finished the \" + task + \" parse.\" });\n } catch (error) {\n self.postMessage({ id, ok: false, result: \"\", summary: \"The offscreen worker refused the \" + task + \" parse: \" + (error && error.message ? error.message : \"malformed payload\") });\n }\n};\n`;\n\nconst pool = new Set<Worker>();\nlet sandboxframe: HTMLIFrameElement | undefined;\nlet renderseq = 0;\nconst pendingrenders = new Map<string, (value: { ok: boolean; text: string; summary: string }) => void>();\n\nfunction ensureworker(): Worker {\n for (const worker of pool) return worker;\n const worker = new Worker(URL.createObjectURL(new Blob([workersource], { type: \"text/javascript\" })), { type: \"classic\" });\n pool.add(worker);\n return worker;\n}\n\nfunction ensuresandboxframe(): HTMLIFrameElement {\n if (sandboxframe) return sandboxframe;\n const frame = document.createElement(\"iframe\");\n frame.src = \"sandbox.html\";\n frame.style.display = \"none\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n document.body.append(frame);\n sandboxframe = frame;\n return frame;\n}\n\nwindow.addEventListener(\"message\", event => {\n const data = event.data as { channel?: string; type?: string; nonce?: string; ok?: boolean; text?: string; summary?: string };\n if (data?.channel !== \"devthinksandbox\" || data.type !== \"renderresult\" || data.nonce === undefined) return;\n const resolver = pendingrenders.get(data.nonce);\n if (!resolver) return;\n pendingrenders.delete(data.nonce);\n resolver({ ok: data.ok !== false, text: data.text ?? \"\", summary: data.summary ?? \"The sandbox frame returned its render result.\" });\n});\n\nfunction runparse(request: parserequest[\"request\"]): Promise<{ ok: boolean; result: string; summary: string }> {\n return new Promise(resolve => {\n const worker = ensureworker();\n const timeout = window.setTimeout(() => resolve({ ok: false, result: \"\", summary: `The offscreen worker never answered the ${request.task} parse of the step ${request.stepid}.` }), 30000);\n worker.onmessage = event => {\n const answer = event.data as { id?: string; ok?: boolean; result?: string; summary?: string };\n if (answer.id !== request.id) return;\n window.clearTimeout(timeout);\n resolve({ ok: answer.ok !== false, result: answer.result ?? \"\", summary: answer.summary ?? \"The offscreen worker answered.\" });\n };\n worker.postMessage({ id: request.id, task: request.task, payload: request.payload });\n });\n}\n\nfunction runrender(render: renderrequest[\"render\"]): Promise<{ ok: boolean; text: string; summary: string }> {\n return new Promise(resolve => {\n const frame = ensuresandboxframe();\n renderseq += 1;\n const nonce = render.nonce;\n pendingrenders.set(nonce, resolve);\n window.setTimeout(() => {\n if (pendingrenders.delete(nonce)) resolve({ ok: false, text: \"\", summary: `The sandbox frame never answered the render of the step ${render.stepid}.` });\n }, 15000);\n frame.contentWindow?.postMessage({ channel: \"devthinksandbox\", type: \"render\", nonce, markup: render.markup }, \"*\");\n void renderseq;\n });\n}\n\nchrome.runtime.onMessage.addListener((message: parserequest | summaryrequest | renderrequest | poolrequest, _sender, sendresponse) => {\n if (!message || (message as { kind?: string }).kind !== \"offscreen\") return false;\n if (message.action === \"parse\") {\n void runparse(message.request).then(answer => sendresponse({ ok: answer.ok, result: answer.result, summary: answer.summary }));\n return true;\n }\n if (message.action === \"summary\") {\n void runparse(message.request).then(answer => sendresponse({ ok: answer.ok, summary: `The offscreen worker verified the ${message.request.task} distillation of the run ${message.request.runid}: ${answer.summary}` }));\n return true;\n }\n if (message.action === \"sandboxrender\") {\n void runrender(message.render).then(answer => sendresponse({ ok: answer.ok, text: answer.text, summary: answer.summary }));\n return true;\n }\n if (message.action === \"pool\") {\n sendresponse({ workers: pool.size });\n return true;\n }\n return false;\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Offscreen document runtime of the 1.1.60 family.\n * The offscreen document hosts the worker pool that parses heavy payloads away from the page and the sandbox frame iframe that renders untrusted markup without extension privileges; the background spawns this document on first use through the offscreen api only after the user grants the optional offscreen capability, and every parse task still rides a reviewed step of an approved plan. The 1.1.64 family adds the diffpreview task: the offscreen worker parses the large before and after states of one write class step so the diff generation offloads from the service worker while the comparison itself stays a pure reviewed computation.\n */\n\ntype parserequest = { kind: \"offscreen\"; action: \"parse\"; request: { id: string; runid: string; stepid: string; task: string; payload: string; transferables: string[] } };\ntype summaryrequest = { kind: \"offscreen\"; action: \"summary\"; request: { id: string; runid: string; stepid: string; task: string; payload: string; transferables: string[] } };\ntype diffrequest = { kind: \"offscreen\"; action: \"parse\"; request: { id: string; runid: string; stepid: string; task: \"diffpreview\"; payload: string; transferables: string[] } };\ntype renderrequest = { kind: \"offscreen\"; action: \"sandboxrender\"; render: { id: string; nonce: string; markup: string; sourceorigin: string; stepid: string } };\ntype poolrequest = { kind: \"offscreen\"; action: \"pool\" };\n\n/** The worker sources of the parse families: pure functions that shape html, json, table, a11y tree, selector and stitch payloads without touching any page. */\nconst workersource = `\nself.onmessage = event => {\n const { id, task, payload } = event.data || {};\n try {\n let result = \"\";\n if (task === \"jsonpayload\") { JSON.parse(payload); result = payload; }\n else if (task === \"htmlsnapshot\" || task === \"readertree\" || task === \"readoutline\" || task === \"classifypage\") { const doc = new DOMParser().parseFromString(payload, \"text/html\"); result = (doc.body && doc.body.textContent ? doc.body.textContent : payload).trim(); }\n else if (task === \"tablerows\" || task === \"a11ytree\" || task === \"complexselector\" || task === \"stitchshots\") { result = payload; }\n else if (task === \"diffpreview\") { const states = JSON.parse(payload); if (!states || typeof states !== \"object\" || typeof states.before !== \"object\" || typeof states.after !== \"object\") throw new Error(\"malformed diff states\"); result = payload; }\n else result = payload;\n self.postMessage({ id, ok: true, result, summary: \"The offscreen worker finished the \" + task + \" parse.\" });\n } catch (error) {\n self.postMessage({ id, ok: false, result: \"\", summary: \"The offscreen worker refused the \" + task + \" parse: \" + (error && error.message ? error.message : \"malformed payload\") });\n }\n};\n`;\n\nconst pool = new Set<Worker>();\nlet sandboxframe: HTMLIFrameElement | undefined;\nlet renderseq = 0;\nconst pendingrenders = new Map<string, (value: { ok: boolean; text: string; summary: string }) => void>();\n\nfunction ensureworker(): Worker {\n for (const worker of pool) return worker;\n const worker = new Worker(URL.createObjectURL(new Blob([workersource], { type: \"text/javascript\" })), { type: \"classic\" });\n pool.add(worker);\n return worker;\n}\n\nfunction ensuresandboxframe(): HTMLIFrameElement {\n if (sandboxframe) return sandboxframe;\n const frame = document.createElement(\"iframe\");\n frame.src = \"sandbox.html\";\n frame.style.display = \"none\";\n frame.setAttribute(\"aria-hidden\", \"true\");\n document.body.append(frame);\n sandboxframe = frame;\n return frame;\n}\n\nwindow.addEventListener(\"message\", event => {\n const data = event.data as { channel?: string; type?: string; nonce?: string; ok?: boolean; text?: string; summary?: string };\n if (data?.channel !== \"devthinksandbox\" || data.type !== \"renderresult\" || data.nonce === undefined) return;\n const resolver = pendingrenders.get(data.nonce);\n if (!resolver) return;\n pendingrenders.delete(data.nonce);\n resolver({ ok: data.ok !== false, text: data.text ?? \"\", summary: data.summary ?? \"The sandbox frame returned its render result.\" });\n});\n\nfunction runparse(request: parserequest[\"request\"]): Promise<{ ok: boolean; result: string; summary: string }> {\n return new Promise(resolve => {\n const worker = ensureworker();\n const timeout = window.setTimeout(() => resolve({ ok: false, result: \"\", summary: `The offscreen worker never answered the ${request.task} parse of the step ${request.stepid}.` }), 30000);\n worker.onmessage = event => {\n const answer = event.data as { id?: string; ok?: boolean; result?: string; summary?: string };\n if (answer.id !== request.id) return;\n window.clearTimeout(timeout);\n resolve({ ok: answer.ok !== false, result: answer.result ?? \"\", summary: answer.summary ?? \"The offscreen worker answered.\" });\n };\n worker.postMessage({ id: request.id, task: request.task, payload: request.payload });\n });\n}\n\nfunction runrender(render: renderrequest[\"render\"]): Promise<{ ok: boolean; text: string; summary: string }> {\n return new Promise(resolve => {\n const frame = ensuresandboxframe();\n renderseq += 1;\n const nonce = render.nonce;\n pendingrenders.set(nonce, resolve);\n window.setTimeout(() => {\n if (pendingrenders.delete(nonce)) resolve({ ok: false, text: \"\", summary: `The sandbox frame never answered the render of the step ${render.stepid}.` });\n }, 15000);\n frame.contentWindow?.postMessage({ channel: \"devthinksandbox\", type: \"render\", nonce, markup: render.markup }, \"*\");\n void renderseq;\n });\n}\n\nchrome.runtime.onMessage.addListener((message: parserequest | summaryrequest | diffrequest | renderrequest | poolrequest, _sender, sendresponse) => {\n if (!message || (message as { kind?: string }).kind !== \"offscreen\") return false;\n if (message.action === \"parse\") {\n void runparse(message.request).then(answer => sendresponse({ ok: answer.ok, result: answer.result, summary: answer.summary }));\n return true;\n }\n if (message.action === \"summary\") {\n void runparse(message.request).then(answer => sendresponse({ ok: answer.ok, summary: `The offscreen worker verified the ${message.request.task} distillation of the run ${message.request.runid}: ${answer.summary}` }));\n return true;\n }\n if (message.action === \"sandboxrender\") {\n void runrender(message.render).then(answer => sendresponse({ ok: answer.ok, text: answer.text, summary: answer.summary }));\n return true;\n }\n if (message.action === \"pool\") {\n sendresponse({ workers: pool.size });\n return true;\n }\n return false;\n});\n"],
5
+ "mappings": "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",
6
6
  "names": ["worker"]
7
7
  }
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink options</title><link rel="stylesheet" href="style.css"></head>
4
+ <body><main class="wide"><header><p class="eyebrow">OPTIONS</p><h1>Every setting in one place</h1><p>Every write takes effect without reloading the extension.</p></header>
5
+ <p id="status" role="status">Loading the surface options.</p>
6
+ <section><h2>Onboarding</h2><div id="onboarding" class="panel"></div><div class="actions"><button id="replayonboarding" class="secondary">Replay onboarding</button></div></section>
7
+ <section><h2>Command palette</h2><div class="panel"><label for="paletterecents">Recent commands window</label><input id="paletterecents" type="number" min="0" placeholder="no window"><label for="paletteshortcut">Keyboard shortcut</label><input id="paletteshortcut" type="text" placeholder="ctrl+."><button id="palettesettings">Save palette options</button></div></section>
8
+ <section><h2>Log stream</h2><div class="panel"><label for="logstreambuffer">Live buffer bound</label><input id="logstreambuffer" type="number" min="1" placeholder="no bound (full history stays in memory)"><button id="logstreamsettings">Save logstream options</button></div></section>
9
+ <section><h2>Task input</h2><div class="panel"><label for="taskinputretention">History retention (ms)</label><input id="taskinputretention" type="number" min="1" placeholder="keep every entry"><button id="taskinputsettings">Save taskinput options</button></div></section>
10
+ <section><h2>Diff preview</h2><div class="panel"><label for="diffpreviewbytes">Offscreen offload byte ceiling</label><input id="diffpreviewbytes" type="number" min="1" placeholder="keep every diff inline"><button id="diffsettings">Save diffpreview options</button></div></section>
11
+ <section><h2>Session interface options</h2><div class="panel"><label for="recallwindow">Recall window (ms)</label><input id="recallwindow" type="number" min="1" placeholder="keep every entry"><label for="noteretention">Note retention (ms)</label><input id="noteretention" type="number" min="1" placeholder="keep every note"><label for="summarywindow">Summary window (steps)</label><input id="summarywindow" type="number" min="0" placeholder="keep every step"><button id="sessionsettings">Save session options</button></div></section>
12
+ <section><h2>Interface finishing options</h2><div class="panel"><label for="themepreference">Theme preference</label><select id="themepreference" aria-label="Theme preference"><option value="">Follow the os preference</option><option value="light">Light</option><option value="dark">Dark</option></select><label for="uilanguage">Interface language</label><select id="uilanguage" aria-label="Interface language"><option value="">English (fallback)</option><option value="pt">Português</option></select><label for="recenttraydepth">Recent tray depth</label><input id="recenttraydepth" type="number" min="1" placeholder="keep every run"><label for="toastlivecount">Step toast live count</label><input id="toastlivecount" type="number" min="1" placeholder="keep every toast live"><label for="notifyconsent">Notification page content consent</label><input id="notifyconsent" type="checkbox"><label for="notifyenabled">Done and attention notifications</label><input id="notifyenabled" type="checkbox" checked><button id="finishingsettings">Save interface finishing options</button></div></section>
13
+ <section><h2>Shortcutkeys</h2><div class="panel"><div id="shortcutlist"></div><label for="shortcutedit">Edit a shortcut (command: combination)</label><input id="shortcutedit" type="text" placeholder="pauserun: alt+q"><button id="shortcutsettings">Save shortcut</button></div></section>
14
+ <section><h2>Import and export</h2><div class="panel"><div class="actions"><button id="exportbundle" class="secondary">Export settings bundle</button><button id="importbundle" class="secondary">Validate and apply an imported bundle</button></div><p id="bundlestatus" class="muted">The bundle never carries secretvault values or unmasked logs.</p><div id="dropzone" class="panel" role="region" aria-label="Drop csv, json or workflow files to import">Drop csv, json or workflow files here.</div></div></section>
15
+ <section><h2>Feature tour and guided tips</h2><div class="panel"><div class="actions"><button id="replaytour" class="secondary">Replay the feature tour</button><button id="recalltips" class="secondary">Recall guided tips</button></div><div id="tourstops" class="panel"></div></div></section>
16
+ <section><h2>Transparency and consent options</h2><iframe src="transparencypage.html" title="Transparency, consent and security options" style="width:100%;min-height:24rem;border:0"></iframe></section>
17
+ </main><script type="module" src="optionspage.js"></script></body>
18
+ </html>
@@ -0,0 +1,267 @@
1
+ // extension/optionspage.ts
2
+ var statusnode = document.querySelector("#status");
3
+ var onboardingroot = document.querySelector("#onboarding");
4
+ var paletterecentsinput = document.querySelector("#paletterecents");
5
+ var paletteshortcutinput = document.querySelector("#paletteshortcut");
6
+ var logstreambufferinput = document.querySelector("#logstreambuffer");
7
+ var taskinputretentioninput = document.querySelector("#taskinputretention");
8
+ var diffpreviewbytesinput = document.querySelector("#diffpreviewbytes");
9
+ var recallwindowinput = document.querySelector("#recallwindow");
10
+ var noteretentioninput = document.querySelector("#noteretention");
11
+ var summarywindowinput = document.querySelector("#summarywindow");
12
+ function status(message, error = false) {
13
+ if (statusnode) {
14
+ statusnode.textContent = message;
15
+ statusnode.dataset.state = error ? "error" : "ready";
16
+ }
17
+ }
18
+ async function request(message) {
19
+ const response = await chrome.runtime.sendMessage(message);
20
+ if (!response.ok) throw new Error(response.error);
21
+ return response.value;
22
+ }
23
+ async function load() {
24
+ try {
25
+ const context = await request({ kind: "context" });
26
+ const preferences = context.surfacepreferences ?? {};
27
+ const session = context.sessionpreferences ?? {};
28
+ if (preferences.paletterecents !== void 0 && paletterecentsinput) paletterecentsinput.value = String(preferences.paletterecents);
29
+ if (preferences.paletteshortcut !== void 0 && paletteshortcutinput) paletteshortcutinput.value = preferences.paletteshortcut;
30
+ if (preferences.logstreambuffer !== void 0 && logstreambufferinput) logstreambufferinput.value = String(preferences.logstreambuffer);
31
+ if (preferences.taskinputretention !== void 0 && taskinputretentioninput) taskinputretentioninput.value = String(preferences.taskinputretention);
32
+ if (preferences.diffpreviewbytes !== void 0 && diffpreviewbytesinput) diffpreviewbytesinput.value = String(preferences.diffpreviewbytes);
33
+ if (session.recallwindow !== void 0 && recallwindowinput) recallwindowinput.value = String(session.recallwindow);
34
+ if (session.noteretention !== void 0 && noteretentioninput) noteretentioninput.value = String(session.noteretention);
35
+ if (session.summarywindow !== void 0 && summarywindowinput) summarywindowinput.value = String(session.summarywindow);
36
+ status("The surface options loaded; every write takes effect without reloading the extension.");
37
+ } catch (error) {
38
+ status(error instanceof Error ? error.message : String(error), true);
39
+ }
40
+ }
41
+ async function savepalette() {
42
+ const paletterecents = paletterecentsinput?.value.trim() ?? "";
43
+ const paletteshortcut = paletteshortcutinput?.value.trim() ?? "";
44
+ await request({ kind: "surface", settings: { ...paletterecents !== "" ? { paletterecents: Number(paletterecents) } : {}, ...paletteshortcut !== "" ? { paletteshortcut } : {} } });
45
+ status(`The palette options saved${paletterecents !== "" ? ` with the recent window of ${paletterecents}` : ""}${paletteshortcut !== "" ? ` and the shortcut ${paletteshortcut}` : ""}.`);
46
+ }
47
+ async function savelogstream() {
48
+ const bound = logstreambufferinput?.value.trim() ?? "";
49
+ await request({ kind: "surface", settings: { ...bound !== "" ? { logstreambuffer: Number(bound) } : {} } });
50
+ status(bound === "" ? "No bound configured; the live window keeps every event." : `The logstream live buffer bound of ${bound} saved; the full history stays in memory.`);
51
+ }
52
+ async function savetaskinput() {
53
+ const retention = taskinputretentioninput?.value.trim() ?? "";
54
+ await request({ kind: "surface", settings: { ...retention !== "" ? { taskinputretention: Number(retention) } : {} } });
55
+ status(retention === "" ? "No retention configured; the taskinput history keeps every entry." : `The taskinput history retention of ${retention} milliseconds saved.`);
56
+ }
57
+ async function savediff() {
58
+ const bytes = diffpreviewbytesinput?.value.trim() ?? "";
59
+ await request({ kind: "surface", settings: { ...bytes !== "" ? { diffpreviewbytes: Number(bytes) } : {} } });
60
+ status(bytes === "" ? "No ceiling configured; every diff stays inline." : `The diffpreview offload ceiling of ${bytes} bytes saved.`);
61
+ }
62
+ async function savesession() {
63
+ const recallwindow = recallwindowinput?.value.trim() ?? "";
64
+ const noteretention = noteretentioninput?.value.trim() ?? "";
65
+ const summarywindow = summarywindowinput?.value.trim() ?? "";
66
+ await request({ kind: "sessions", settings: { ...recallwindow !== "" ? { recallwindow: Number(recallwindow) } : {}, ...noteretention !== "" ? { noteretention: Number(noteretention) } : {}, ...summarywindow !== "" ? { summarywindow: Number(summarywindow) } : {} } });
67
+ status("The session interface options saved; the recall window, the note retention and the summary window take effect at once.");
68
+ }
69
+ async function renderonboarding() {
70
+ if (!onboardingroot) return;
71
+ try {
72
+ const result = await request({ kind: "surface", onboarding: {} });
73
+ onboardingroot.replaceChildren();
74
+ const state = result.state;
75
+ const line = document.createElement("p");
76
+ line.textContent = state === void 0 ? "The onboarding walkthrough never ran; it starts on the next popup open." : state.done ? `The walkthrough is done (${state.stepscompleted.length} steps); the replay restarts it on demand and writes no second consent event.` : `The walkthrough stands at ${state.stepscompleted.length} of ${result.steps.length} steps.`;
77
+ onboardingroot.append(line);
78
+ const list = document.createElement("ol");
79
+ list.className = "audit";
80
+ for (const step of result.steps) list.append(Object.assign(document.createElement("li"), { textContent: `${step.title} (${step.surface}): ${step.body}${state?.stepscompleted.includes(step.id) ? " \u2014 done" : ""}` }));
81
+ onboardingroot.append(list);
82
+ } catch (error) {
83
+ onboardingroot.textContent = error instanceof Error ? error.message : String(error);
84
+ }
85
+ }
86
+ document.querySelector("#replayonboarding")?.addEventListener("click", () => {
87
+ void (async () => {
88
+ await request({ kind: "surface", onboarding: { replay: true } });
89
+ status("The onboarding walkthrough replays; it opens on the next popup open.");
90
+ await renderonboarding();
91
+ })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
92
+ });
93
+ document.querySelector("#palettesettings")?.addEventListener("click", () => {
94
+ void savepalette().catch((error) => status(error instanceof Error ? error.message : String(error), true));
95
+ });
96
+ document.querySelector("#logstreamsettings")?.addEventListener("click", () => {
97
+ void savelogstream().catch((error) => status(error instanceof Error ? error.message : String(error), true));
98
+ });
99
+ document.querySelector("#taskinputsettings")?.addEventListener("click", () => {
100
+ void savetaskinput().catch((error) => status(error instanceof Error ? error.message : String(error), true));
101
+ });
102
+ document.querySelector("#diffsettings")?.addEventListener("click", () => {
103
+ void savediff().catch((error) => status(error instanceof Error ? error.message : String(error), true));
104
+ });
105
+ document.querySelector("#sessionsettings")?.addEventListener("click", () => {
106
+ void savesession().catch((error) => status(error instanceof Error ? error.message : String(error), true));
107
+ });
108
+ var surfacechannel = typeof BroadcastChannel === "function" ? new BroadcastChannel("devthinksurfaces") : void 0;
109
+ surfacechannel?.addEventListener("message", (event) => {
110
+ const frame = event.data;
111
+ if (frame?.channel === "settings") {
112
+ void load().catch(() => {
113
+ });
114
+ }
115
+ });
116
+ void load();
117
+ void renderonboarding();
118
+ var themepreferenceselect = document.querySelector("#themepreference");
119
+ var uilanguageselect = document.querySelector("#uilanguage");
120
+ var recenttraydepthinput = document.querySelector("#recenttraydepth");
121
+ var toastlivecountinput = document.querySelector("#toastlivecount");
122
+ var notifyconsentinput = document.querySelector("#notifyconsent");
123
+ var notifyenabledinput = document.querySelector("#notifyenabled");
124
+ var shortcutlistnode = document.querySelector("#shortcutlist");
125
+ var shortcuteditinput = document.querySelector("#shortcutedit");
126
+ var bundlenode = document.querySelector("#bundlestatus");
127
+ var dropzonenode = document.querySelector("#dropzone");
128
+ var tourstopsnode = document.querySelector("#tourstops");
129
+ async function loadfinishing() {
130
+ try {
131
+ const context = await request({ kind: "context" });
132
+ const preferences = context.surfacepreferences ?? {};
133
+ if (preferences.themepreference !== void 0 && themepreferenceselect) themepreferenceselect.value = preferences.themepreference;
134
+ if (preferences.uilanguage !== void 0 && uilanguageselect) uilanguageselect.value = preferences.uilanguage;
135
+ if (preferences.recenttraydepth !== void 0 && recenttraydepthinput) recenttraydepthinput.value = String(preferences.recenttraydepth);
136
+ if (preferences.toastlivecount !== void 0 && toastlivecountinput) toastlivecountinput.value = String(preferences.toastlivecount);
137
+ if (notifyconsentinput) notifyconsentinput.checked = preferences.notifyconsent === true;
138
+ if (notifyenabledinput) notifyenabledinput.checked = preferences.notifyenabled !== false;
139
+ } catch (error) {
140
+ status(error instanceof Error ? error.message : String(error), true);
141
+ }
142
+ }
143
+ async function savefinishing() {
144
+ const themepreference = themepreferenceselect?.value ?? "";
145
+ const uilanguage = uilanguageselect?.value ?? "";
146
+ const recenttraydepth = recenttraydepthinput?.value.trim() ?? "";
147
+ const toastlivecount = toastlivecountinput?.value.trim() ?? "";
148
+ await request({ kind: "views", settings: { ...themepreference !== "" ? { themepreference } : {}, ...uilanguage !== "" ? { uilanguage } : {}, ...recenttraydepth !== "" ? { recenttraydepth: Number(recenttraydepth) } : {}, ...toastlivecount !== "" ? { toastlivecount: Number(toastlivecount) } : {}, notifyconsent: notifyconsentinput?.checked === true, notifyenabled: notifyenabledinput?.checked === true } });
149
+ await applythemelive();
150
+ status("The interface finishing options saved; the theme, the language, the tray depth and the toast live count take effect at once.");
151
+ }
152
+ async function applythemelive() {
153
+ try {
154
+ const result = await request({ kind: "views", theme: { resolve: true, preference: themepreferenceselect?.value ?? "" } });
155
+ for (const [name, value] of Object.entries(result.appearance.tokens)) document.documentElement.style.setProperty(`--theme-${name}`, value);
156
+ document.documentElement.style.setProperty("color-scheme", result.appearance.mode);
157
+ } catch {
158
+ }
159
+ }
160
+ async function rendershortcuts() {
161
+ if (!shortcutlistnode) return;
162
+ try {
163
+ const result = await request({ kind: "views", shortcut: { list: true } });
164
+ shortcutlistnode.replaceChildren();
165
+ const list = document.createElement("ul");
166
+ list.className = "audit";
167
+ for (const binding of result.bindings ?? []) {
168
+ const combination = [...binding.modifiers, binding.key].join("+");
169
+ list.append(Object.assign(document.createElement("li"), { textContent: `${binding.command}: ${binding.display ?? combination}` }));
170
+ }
171
+ shortcutlistnode.append(list);
172
+ } catch (error) {
173
+ shortcutlistnode.textContent = error instanceof Error ? error.message : String(error);
174
+ }
175
+ }
176
+ async function saveshortcut() {
177
+ const text = shortcuteditinput?.value.trim() ?? "";
178
+ const split = text.split(":");
179
+ const command = split[0]?.trim() ?? "";
180
+ const combination = split.slice(1).join(":").trim();
181
+ if (command === "" || combination === "") {
182
+ status("The shortcut edit needs its command and its combination, such as pauserun: alt+q.", true);
183
+ return;
184
+ }
185
+ await request({ kind: "views", shortcut: { edit: { command, text: combination } } });
186
+ status(`The ${command} shortcut now binds ${combination}; the command keeps its palette gates.`);
187
+ await rendershortcuts();
188
+ }
189
+ async function rendertourstops() {
190
+ if (!tourstopsnode) return;
191
+ try {
192
+ const result = await request({ kind: "views", tour: { stops: true } });
193
+ tourstopsnode.replaceChildren();
194
+ const list = document.createElement("ol");
195
+ list.className = "audit";
196
+ for (const stop of result.stops ?? []) list.append(Object.assign(document.createElement("li"), { textContent: `${stop.title} (${stop.surface}): ${stop.body}` }));
197
+ tourstopsnode.append(list);
198
+ } catch (error) {
199
+ tourstopsnode.textContent = error instanceof Error ? error.message : String(error);
200
+ }
201
+ }
202
+ async function exportbundle() {
203
+ const result = await request({ kind: "views", importexport: { export: true } });
204
+ if (bundlenode) bundlenode.textContent = `The bundle of the profile ${result.payload.profile} carries ${result.payload.contents.siteprofiles.length} site profile${result.payload.contents.siteprofiles.length === 1 ? "" : "s"} and ${result.payload.contents.notes.length} note${result.payload.contents.notes.length === 1 ? "" : "s"}; ${result.payload.exclusions.join(" and ")} never enter any bundle.`;
205
+ status("The settings bundle exported with its exclusion list.");
206
+ }
207
+ async function importbundle() {
208
+ const text = window.prompt("Paste the importexport bundle json") ?? "";
209
+ if (text.trim() === "") return;
210
+ const parsed = JSON.parse(text);
211
+ const validation = await request({ kind: "views", importexport: { validate: parsed } });
212
+ if (!validation.ok) {
213
+ status(validation.reason, true);
214
+ return;
215
+ }
216
+ const applied = await request({ kind: "views", importexport: { apply: { preferences: parsed.contents?.preferences ?? {} } } });
217
+ status(`The bundle applied ${applied.applied.length} preference key${applied.applied.length === 1 ? "" : "s"} after its validation passed.`);
218
+ await loadfinishing();
219
+ }
220
+ function wir(dropimportzone) {
221
+ dropimportzone.addEventListener("dragover", (event) => {
222
+ event.preventDefault();
223
+ });
224
+ dropimportzone.addEventListener("drop", (event) => {
225
+ event.preventDefault();
226
+ const file = event.dataTransfer?.files[0];
227
+ if (file === void 0) return;
228
+ void file.text().then((head) => request({ kind: "views", dropimport: { file: { filename: file.name, bytes: file.size, head: head.slice(0, 2e3) } } })).then((result) => {
229
+ const session = result.session;
230
+ status(`The dropimport detected the ${session.kind} kind of ${session.filename}; the import path takes the file from here.`);
231
+ }).catch((error) => status(error instanceof Error ? error.message : String(error), true));
232
+ });
233
+ }
234
+ if (dropzonenode) wir(dropzonenode);
235
+ document.querySelector("#finishingsettings")?.addEventListener("click", () => {
236
+ void savefinishing().catch((error) => status(error instanceof Error ? error.message : String(error), true));
237
+ });
238
+ document.querySelector("#shortcutsettings")?.addEventListener("click", () => {
239
+ void saveshortcut().catch((error) => status(error instanceof Error ? error.message : String(error), true));
240
+ });
241
+ document.querySelector("#exportbundle")?.addEventListener("click", () => {
242
+ void exportbundle().catch((error) => status(error instanceof Error ? error.message : String(error), true));
243
+ });
244
+ document.querySelector("#importbundle")?.addEventListener("click", () => {
245
+ void importbundle().catch((error) => status(error instanceof Error ? error.message : String(error), true));
246
+ });
247
+ document.querySelector("#replaytour")?.addEventListener("click", () => {
248
+ void (async () => {
249
+ await request({ kind: "views", tour: { replay: true } });
250
+ status("The featuretour replays with its datagrid, compareviewer and pickeroverlay stops.");
251
+ })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
252
+ });
253
+ document.querySelector("#recalltips")?.addEventListener("click", () => {
254
+ void (async () => {
255
+ await request({ kind: "views", tips: { recall: true } });
256
+ status("The guidedtips recalled; they show again during the picker sessions.");
257
+ })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
258
+ });
259
+ themepreferenceselect?.addEventListener("change", () => {
260
+ void applythemelive().catch(() => {
261
+ });
262
+ });
263
+ void loadfinishing();
264
+ void applythemelive();
265
+ void rendershortcuts();
266
+ void rendertourstops();
267
+ //# sourceMappingURL=optionspage.js.map