@wenathlan/devthink 2.0.13 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/accounts.ts +2087 -0
- package/agent.ts +2331 -0
- package/antigravity.ts +2139 -0
- package/apifreeze.ts +4146 -0
- package/auth.ts +642 -0
- package/background.ts +16837 -0
- package/bridge.ts +2080 -0
- package/build.ts +54 -0
- package/bun.ts +26 -0
- package/capture.ts +1614 -0
- package/checksecrets.ts +72 -0
- package/cjs.ts +9 -0
- package/cli.ts +3500 -0
- package/commands.ts +1101 -0
- package/companion.ts +450 -0
- package/compatibility.ts +37 -0
- package/config.ts +3137 -0
- package/constants.ts +943 -0
- package/core.ts +1473 -0
- package/crossbrowser.ts +1359 -0
- package/dashdone.ts +605 -0
- package/data.ts +943 -0
- package/database.ts +133 -0
- package/debug.ts +850 -0
- package/deno.ts +27 -0
- package/devthink.ts +707 -0
- package/dist/accounts.d.ts +409 -0
- package/dist/agent.d.ts +837 -0
- package/dist/antigravity.d.ts +380 -0
- package/dist/apifreeze.d.ts +113 -0
- package/dist/auth.d.ts +224 -0
- package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
- package/dist/capture.d.ts +595 -0
- package/dist/cjs.d.ts +6 -0
- package/{cli.js → dist/cli.js} +2 -2
- package/dist/commands.d.ts +310 -0
- package/dist/compatibility.d.ts +17 -0
- package/dist/config.d.ts +1853 -0
- package/dist/constants.d.ts +637 -0
- package/dist/core.d.ts +361 -0
- package/dist/data.d.ts +330 -0
- package/dist/database.d.ts +24 -0
- package/dist/debug.d.ts +270 -0
- package/dist/engine.d.ts +74 -0
- package/dist/environments.d.ts +244 -0
- package/dist/evidence.d.ts +277 -0
- package/dist/export.d.ts +311 -0
- package/dist/fingerprint.d.ts +594 -0
- package/dist/flow.d.ts +300 -0
- package/dist/gates.d.ts +103 -0
- package/dist/identity.d.ts +51 -0
- package/dist/ids.d.ts +7 -0
- package/dist/llm.d.ts +652 -0
- package/dist/models.d.ts +1807 -0
- package/dist/net.d.ts +325 -0
- package/dist/neutral.d.ts +41 -0
- package/dist/oauth.d.ts +703 -0
- package/dist/page.d.ts +1166 -0
- package/dist/perf.d.ts +481 -0
- package/dist/plan.d.ts +134 -0
- package/dist/project.d.ts +395 -0
- package/dist/providers.d.ts +37 -0
- package/dist/quota.d.ts +453 -0
- package/dist/recovery.d.ts +621 -0
- package/dist/request.d.ts +1492 -0
- package/dist/run.d.ts +779 -0
- package/dist/runtime.d.ts +212 -0
- package/dist/search.d.ts +375 -0
- package/dist/security.d.ts +534 -0
- package/dist/serve.d.ts +136 -0
- package/dist/session.d.ts +296 -0
- package/dist/storage.d.ts +53 -0
- package/dist/streaming.d.ts +1119 -0
- package/dist/swarm.d.ts +328 -0
- package/dist/tools.d.ts +170 -0
- package/dist/types.d.ts +8357 -0
- package/dist/utils.d.ts +82 -0
- package/dist/version.d.ts +7 -0
- package/dist/versionregistry.d.ts +406 -0
- package/dist/views.d.ts +579 -0
- package/dist/workbenchsession.d.ts +69 -0
- package/dist/workflow.d.ts +649 -0
- package/docs/readme.md +92 -0
- package/docs/schemas/antigravity.json +192 -0
- package/engine.ts +1524 -0
- package/environments.ts +1005 -0
- package/evidence.ts +643 -0
- package/export.ts +889 -0
- package/fingerprint.ts +1458 -0
- package/flow.ts +1190 -0
- package/gates.ts +307 -0
- package/gateway.ts +34 -0
- package/hardening.ts +191 -0
- package/headless.ts +369 -0
- package/http.ts +2110 -0
- package/identity.ts +246 -0
- package/ids.ts +26 -0
- package/index.ts +369 -0
- package/llm.ts +2682 -0
- package/mcp.ts +1835 -0
- package/{memory.js → memory.ts} +3834 -2310
- package/models.ts +3805 -0
- package/modes.ts +43 -0
- package/net.ts +1214 -0
- package/neutral.ts +362 -0
- package/node.ts +26 -0
- package/oauth.ts +2216 -0
- package/offscreen.ts +170 -0
- package/pack.ts +956 -0
- package/package.json +175 -82
- package/page.ts +7824 -0
- package/pagebridge.ts +1116 -0
- package/perf.ts +957 -0
- package/plan.ts +672 -0
- package/plugin.ts +76 -0
- package/{policy.js → policy.ts} +5073 -4679
- package/prisma.config.ts +27 -0
- package/progress.ts +1308 -0
- package/project.ts +1291 -0
- package/protocol.ts +4187 -0
- package/providers.ts +246 -0
- package/quota.ts +1887 -0
- package/recovery.ts +2433 -0
- package/request.ts +4482 -0
- package/run.ts +2135 -0
- package/runtime.ts +526 -0
- package/search.ts +1760 -0
- package/security.ts +1673 -0
- package/serve.ts +419 -0
- package/server.ts +5538 -0
- package/session.ts +1162 -0
- package/storage.ts +182 -0
- package/streaming.ts +4101 -0
- package/swarm.ts +884 -0
- package/sync.ts +56 -0
- package/tests/agentcert.test.ts +128 -0
- package/tests/agentfleet.test.ts +214 -0
- package/tests/agentreview.test.ts +241 -0
- package/tests/agentstream.test.ts +335 -0
- package/tests/agentwork.test.ts +356 -0
- package/tests/apifreeze.test.ts +290 -0
- package/tests/apimap.test.ts +112 -0
- package/tests/approvalgate.test.ts +175 -0
- package/tests/artifactmanifest.test.ts +204 -0
- package/tests/attentionfeed.test.ts +163 -0
- package/tests/backgroundruns.test.ts +100 -0
- package/tests/batchscheduling.test.ts +131 -0
- package/tests/blackboard.test.ts +238 -0
- package/tests/bridge.test.ts +1088 -0
- package/tests/browsercoverage.test.ts +134 -0
- package/tests/browserpolyfills.test.ts +124 -0
- package/tests/build.mjs +1260 -0
- package/tests/clientauth.test.ts +223 -0
- package/tests/clitools.test.ts +578 -0
- package/tests/confirmgates.test.ts +190 -0
- package/tests/containerpack.test.ts +143 -0
- package/tests/controlflow.test.ts +2120 -0
- package/tests/coordination.test.ts +345 -0
- package/tests/copyscreen.test.ts +139 -0
- package/tests/core.test.ts +417 -0
- package/tests/costcert.test.ts +95 -0
- package/tests/crossbrowser.test.ts +115 -0
- package/tests/cspaudit.test.ts +79 -0
- package/tests/dashdone.test.ts +615 -0
- package/tests/datagrid.test.ts +137 -0
- package/tests/doccheck.test.ts +107 -0
- package/tests/dombatch.test.ts +109 -0
- package/tests/emulation.test.ts +282 -0
- package/tests/environments.test.ts +259 -0
- package/tests/evidenceviews.test.ts +88 -0
- package/tests/exporttools.test.ts +185 -0
- package/tests/firefoxprep.test.ts +134 -0
- package/tests/flowlibrary.test.ts +242 -0
- package/tests/flowrun.test.ts +303 -0
- package/tests/forensics.test.ts +439 -0
- package/tests/gateway.test.ts +939 -0
- package/tests/headlesslib.test.ts +171 -0
- package/tests/httpstream.test.ts +277 -0
- package/tests/immutablelog.test.ts +157 -0
- package/tests/inboundguard.test.ts +177 -0
- package/tests/lazyload.test.ts +72 -0
- package/tests/librarymodes.test.ts +623 -0
- package/tests/llm.test.ts +696 -0
- package/tests/manifest.test.ts +226 -0
- package/tests/maskinputs.test.ts +150 -0
- package/tests/matrixverify.test.ts +111 -0
- package/tests/mavenpack.test.ts +75 -0
- package/tests/mcpserver.test.ts +704 -0
- package/tests/memory.test.ts +4155 -0
- package/tests/memorycare.test.ts +230 -0
- package/tests/migrateplan.test.ts +349 -0
- package/tests/minimization.test.ts +414 -0
- package/tests/modelroute.test.ts +112 -0
- package/tests/multiagent.test.ts +440 -0
- package/tests/nativebridge.test.ts +807 -0
- package/tests/navigation.test.ts +284 -0
- package/tests/nugetpack.test.ts +83 -0
- package/tests/orchestration.test.ts +449 -0
- package/tests/originpolicy.test.ts +405 -0
- package/tests/outputcompare.test.ts +97 -0
- package/tests/package.test.ts +91 -0
- package/tests/pagebridge.test.ts +5909 -0
- package/tests/pentest.test.ts +75 -0
- package/tests/perfrecords.test.ts +257 -0
- package/tests/permdiff.test.ts +85 -0
- package/tests/phishguard.test.ts +128 -0
- package/tests/pickerviews.test.ts +155 -0
- package/tests/pipelines.test.ts +381 -0
- package/tests/planlint.test.ts +240 -0
- package/tests/planreview.test.ts +383 -0
- package/tests/platformtargets.test.ts +56 -0
- package/tests/policy.test.ts +8284 -0
- package/tests/portability.test.ts +96 -0
- package/tests/progress.test.ts +1036 -0
- package/tests/promptlibrary.test.ts +116 -0
- package/tests/protocol.test.ts +4793 -0
- package/tests/publishingpipeline.test.ts +314 -0
- package/tests/quickactions.test.ts +164 -0
- package/tests/readiness.test.ts +78 -0
- package/tests/recipes.test.ts +163 -0
- package/tests/redactshots.test.ts +216 -0
- package/tests/registernvidiakeys.ts +51 -0
- package/tests/relayserve.test.ts +304 -0
- package/tests/releasenotes.test.ts +276 -0
- package/tests/resilience.test.ts +467 -0
- package/tests/resourceaware.test.ts +187 -0
- package/tests/runbudget.test.ts +125 -0
- package/tests/runhistory.test.ts +299 -0
- package/tests/runreplay.test.ts +113 -0
- package/tests/runresume.test.ts +184 -0
- package/tests/runstate.test.ts +288 -0
- package/tests/runtimeadapters.test.ts +251 -0
- package/tests/safariskeleton.test.ts +115 -0
- package/tests/sandboxframe.test.ts +196 -0
- package/tests/sbom.test.ts +105 -0
- package/tests/secretvault.test.ts +188 -0
- package/tests/selcache.test.ts +111 -0
- package/tests/sessioninterface.test.ts +791 -0
- package/tests/sessions.test.ts +591 -0
- package/tests/siteprefs.test.ts +228 -0
- package/tests/snapshotdelta.test.ts +165 -0
- package/tests/statusviews.test.ts +237 -0
- package/tests/stepmeter.test.ts +200 -0
- package/tests/streamparse.test.ts +163 -0
- package/tests/stylepruning.test.ts +90 -0
- package/tests/surfaces.test.ts +282 -0
- package/tests/sweep.test.ts +78 -0
- package/tests/syncbridge.test.ts +164 -0
- package/tests/taskqueue.test.ts +206 -0
- package/tests/toolcalls.test.ts +336 -0
- package/tests/toolcatalog.test.ts +201 -0
- package/tests/torture-agent.test.ts +706 -0
- package/tests/torture-auth.test.ts +461 -0
- package/tests/torture-commands.test.ts +895 -0
- package/tests/torture-data.test.ts +923 -0
- package/tests/torture-environments.test.ts +285 -0
- package/tests/torture-export.test.ts +263 -0
- package/tests/torture-http.test.ts +1379 -0
- package/tests/torture-llm.test.ts +808 -0
- package/tests/torture-memory.test.ts +1084 -0
- package/tests/torture-plan.test.ts +829 -0
- package/tests/torture-run.test.ts +626 -0
- package/tests/torture-session.test.ts +928 -0
- package/tests/torture-swarm.test.ts +1245 -0
- package/tests/torture-tools.test.ts +964 -0
- package/tests/torture-workflow.test.ts +2354 -0
- package/tests/tourviews.test.ts +52 -0
- package/tests/transparency.test.ts +147 -0
- package/tests/trigger.test.ts +941 -0
- package/tests/v1sunset.test.ts +428 -0
- package/tests/virtlist.test.ts +94 -0
- package/tests/vision.test.ts +540 -0
- package/tests/vsixpack.test.ts +145 -0
- package/tests/webapi.test.ts +523 -0
- package/tests/workflow.test.ts +1785 -0
- package/tests/workfloweditor.test.ts +759 -0
- package/tests/xpipack.test.ts +128 -0
- package/tools.ts +734 -0
- package/types.ts +9908 -0
- package/umd.ts +9 -0
- package/utils.ts +249 -0
- package/version.ts +8 -0
- package/versionregistry.ts +1310 -0
- package/views.ts +2153 -0
- package/vitest.config.ts +12 -0
- package/web/App.tsx +78 -0
- package/web/README.md +47 -0
- package/web/capacitor.config.ts +46 -0
- package/web/const.ts +19 -0
- package/web/control.shell.tsx +66 -0
- package/web/dashboardpage.ts +554 -0
- package/web/db.ts +493 -0
- package/web/design.html +541 -0
- package/web/gatewayview/Gateway.tsx +345 -0
- package/web/gatewayview/config.ts +1042 -0
- package/web/gatewayview/definition.ts +174 -0
- package/web/icons.ts +15 -0
- package/{manifest.json → web/manifest.json} +1 -1
- package/web/optionspage.ts +691 -0
- package/web/popup.ts +1651 -0
- package/web/schema.prisma +176 -0
- package/web/sidepanel.ts +7941 -0
- package/web/sitemanifest.json +13 -0
- package/web/terminal-ui.tsx +622 -0
- package/web/transparencypage.ts +541 -0
- package/web/vite.config.ts +39 -0
- package/web/workspace.ts +23 -0
- package/workbenchmemory.ts +104 -0
- package/workbenchsession.ts +304 -0
- package/workflow.ts +4495 -0
- package/bridge.d.ts.map +0 -1
- package/bridge.js +0 -1877
- package/bun.d.ts.map +0 -1
- package/bun.js +0 -52329
- package/caps/background.json +0 -712
- package/caps/cli.json +0 -360
- package/caps/library.json +0 -3611
- package/caps/mcp.json +0 -126
- package/caps/pagebridge.json +0 -377
- package/caps/popup.json +0 -51
- package/caps/sidepanel.json +0 -522
- package/checksums.txt +0 -142
- package/cli.d.ts.map +0 -1
- package/companion.d.ts.map +0 -1
- package/companion.js +0 -389
- package/crossbrowser.d.ts.map +0 -1
- package/crossbrowser.js +0 -1191
- package/dashdone.d.ts.map +0 -1
- package/dashdone.js +0 -368
- package/deno.d.ts.map +0 -1
- package/deno.js +0 -52325
- package/devthink.umd.js +0 -52346
- package/fixtures/bun.ts +0 -7
- package/fixtures/cjs.js +0 -6
- package/fixtures/deno.ts +0 -6
- package/fixtures/example-org-pagestate.json +0 -22
- package/fixtures/importers/automa-workflow.json +0 -18
- package/fixtures/importers/selenium-side.json +0 -20
- package/fixtures/importers/tabular-plan.csv +0 -7
- package/fixtures/importers/uivision-macro.json +0 -10
- package/fixtures/importers/v1-plan.json +0 -13
- package/fixtures/mcp-client.mjs +0 -14
- package/fixtures/pages/checkout.html +0 -192
- package/fixtures/pages/dashboard.html +0 -110
- package/fixtures/pages/feed.html +0 -113
- package/fixtures/pages/product-grid.html +0 -150
- package/fixtures/plans/form-inventory-plan.json +0 -11
- package/fixtures/plans/release-notes-plan.json +0 -11
- package/fixtures/recipes/agentscompete.json +0 -11
- package/fixtures/recipes/agentsforms.json +0 -11
- package/fixtures/recipes/agentsmonitor.json +0 -11
- package/fixtures/recipes/agentsreview.json +0 -29
- package/fixtures/recipes/agentswarm.json +0 -11
- package/fixtures/recipes/fillcalendar.json +0 -12
- package/fixtures/recipes/fillcheckout.json +0 -13
- package/fixtures/recipes/filldependentdropdowns.json +0 -12
- package/fixtures/recipes/fillfromcsv.json +0 -13
- package/fixtures/recipes/fillloginform.json +0 -12
- package/fixtures/recipes/fillmultistepwizard.json +0 -13
- package/fixtures/recipes/fillregistration.json +0 -12
- package/fixtures/recipes/fillsearchfilters.json +0 -12
- package/fixtures/recipes/filltypeahead.json +0 -11
- package/fixtures/recipes/monitoralerts.json +0 -12
- package/fixtures/recipes/monitoravailability.json +0 -11
- package/fixtures/recipes/monitornetworkquiet.json +0 -12
- package/fixtures/recipes/monitorpagechanges.json +0 -11
- package/fixtures/recipes/monitorpricechange.json +0 -11
- package/fixtures/recipes/monitorschedule.json +0 -12
- package/fixtures/recipes/scrapedynamicprices.json +0 -13
- package/fixtures/recipes/scrapeimagegallery.json +0 -12
- package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
- package/fixtures/recipes/scrapenestedlists.json +0 -12
- package/fixtures/recipes/scrapeproductgrid.json +0 -12
- package/fixtures/recipes/scrapereviews.json +0 -12
- package/fixtures/recipes/scrapesearchresults.json +0 -12
- package/fixtures/recipes/scrapetablepagination.json +0 -12
- package/fixtures/recipes/testaccessibility.json +0 -34
- package/fixtures/recipes/testconsoleerrors.json +0 -12
- package/fixtures/recipes/testdeeplinks.json +0 -12
- package/fixtures/recipes/testformvalidation.json +0 -13
- package/fixtures/recipes/testlayoutshift.json +0 -12
- package/fixtures/recipes/testlinkcheck.json +0 -12
- package/fixtures/recipes/testloadtiming.json +0 -12
- package/fixtures/recipes/testvisualdiff.json +0 -12
- package/fixtures/workflows/changelog-digest-workflow.json +0 -13
- package/fixtures/workflows/form-survey-workflow.json +0 -13
- package/gallery.json +0 -706
- package/hardening.d.ts.map +0 -1
- package/hardening.js +0 -188
- package/headless.d.ts.map +0 -1
- package/headless.js +0 -11522
- package/http.d.ts.map +0 -1
- package/http.js +0 -2165
- package/index.cjs +0 -54682
- package/index.d.ts.map +0 -1
- package/index.js +0 -52310
- package/index.neutral.js +0 -46343
- package/mcp.d.ts.map +0 -1
- package/mcp.js +0 -20515
- package/memory.d.ts.map +0 -1
- package/nativehost.template.json +0 -10
- package/node.cjs +0 -54686
- package/node.d.ts.map +0 -1
- package/pack.d.ts.map +0 -1
- package/pack.js +0 -805
- package/policy.d.ts.map +0 -1
- package/progress.d.ts.map +0 -1
- package/progress.js +0 -645
- package/protocol.d.ts.map +0 -1
- package/protocol.js +0 -8334
- package/schemas/audit.schema.json +0 -226
- package/schemas/capability.schema.json +0 -113
- package/schemas/envelope.schema.json +0 -162
- package/schemas/memory.schema.json +0 -51
- package/schemas/observation.schema.json +0 -113
- package/schemas/plan.schema.json +0 -208
- package/schemas/progress.schema.json +0 -126
- package/schemas/proposal.schema.json +0 -68
- package/schemas/session.schema.json +0 -82
- package/schemas/tool.schema.json +0 -94
- package/server.d.ts.map +0 -1
- package/server.js +0 -28877
- package/umd-example.html +0 -18
- package/umd.d.ts.map +0 -1
- /package/{bun.d.ts → dist/bun.d.ts} +0 -0
- /package/{cli.d.ts → dist/cli.d.ts} +0 -0
- /package/{companion.d.ts → dist/companion.d.ts} +0 -0
- /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
- /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
- /package/{deno.d.ts → dist/deno.d.ts} +0 -0
- /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
- /package/{headless.d.ts → dist/headless.d.ts} +0 -0
- /package/{http.d.ts → dist/http.d.ts} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
- /package/{memory.d.ts → dist/memory.d.ts} +0 -0
- /package/{node.d.ts → dist/node.d.ts} +0 -0
- /package/{pack.d.ts → dist/pack.d.ts} +0 -0
- /package/{policy.d.ts → dist/policy.d.ts} +0 -0
- /package/{progress.d.ts → dist/progress.d.ts} +0 -0
- /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
- /package/{server.d.ts → dist/server.d.ts} +0 -0
- /package/{umd.d.ts → dist/umd.d.ts} +0 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Optionspage runtime of the 1.1.64 family.
|
|
5
|
+
* The optionspage gathers every setting into one page with sections: the onboarding replay, the commandpalette recent window and shortcut, the logstream live buffer bound, the taskinput history retention, the diffpreview offscreen offload ceiling and the session interface options of the 1.1.63 family, while the transparency, consent and security sections of the earlier releases import through the embedded transparencypage; every write takes effect without reloading the extension because the background reads its settings live on each decision. The 2.0.2 final polish adds the contrast preference of the rc.2 high contrast theme: the toggle persists through the surface layout seam and resolves the high contrast token set over the --theme-* custom properties the darklight family already writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { applycontrasttheme, contrasttokensof } from "../views.js";
|
|
9
|
+
|
|
10
|
+
const statusnode = document.querySelector<HTMLElement>("#status");
|
|
11
|
+
const onboardingroot = document.querySelector<HTMLElement>("#onboarding");
|
|
12
|
+
const paletterecentsinput = document.querySelector<HTMLInputElement>("#paletterecents");
|
|
13
|
+
const paletteshortcutinput = document.querySelector<HTMLInputElement>("#paletteshortcut");
|
|
14
|
+
const logstreambufferinput = document.querySelector<HTMLInputElement>("#logstreambuffer");
|
|
15
|
+
const taskinputretentioninput = document.querySelector<HTMLInputElement>("#taskinputretention");
|
|
16
|
+
const diffpreviewbytesinput = document.querySelector<HTMLInputElement>("#diffpreviewbytes");
|
|
17
|
+
const recallwindowinput = document.querySelector<HTMLInputElement>("#recallwindow");
|
|
18
|
+
const noteretentioninput = document.querySelector<HTMLInputElement>("#noteretention");
|
|
19
|
+
const summarywindowinput = document.querySelector<HTMLInputElement>("#summarywindow");
|
|
20
|
+
|
|
21
|
+
function status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? "error" : "ready"; } }
|
|
22
|
+
async 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; }
|
|
23
|
+
|
|
24
|
+
type surfacecontext = { sessionpreferences?: { recallwindow?: number; noteretention?: number; summarywindow?: number }; surfacepreferences?: { paletterecents?: number; paletteshortcut?: string; logstreambuffer?: number; taskinputretention?: number; diffpreviewbytes?: number }; perfpreferences?: { prewarmset?: string[]; lazybudget?: number; debouncewindows?: Partial<Record<"scroll" | "input" | "resize" | "mutation", number>>; snapshotcadence?: number; workerdepth?: number; perfretention?: number; virtlistrows?: number; capturepriority?: "speed" | "evidence"; batchwindow?: number; domainlimits?: Record<string, number>; politedelay?: number; politejitter?: number; pollgrowth?: number; stepbudget?: number; memorybudget?: number; budgetthresholds?: { warning?: number; critical?: number }; timeoutbound?: number; suspendwindow?: number; startuptarget?: number; selectorlatency?: number; slowmofactor?: number; runcachepin?: boolean; logpruneretention?: number; logsizewindow?: number; artifactcodec?: "deflate" | "store"; batteryfloor?: number; netbackoff?: Record<string, number>; readlanes?: number; cadencewidening?: number } };
|
|
25
|
+
|
|
26
|
+
/** Loads the stored surface options into the inputs; an absent value keeps the placeholder that names the documented default. */
|
|
27
|
+
async function load(): Promise<void> {
|
|
28
|
+
try {
|
|
29
|
+
const context = await request({ kind: "context" }) as surfacecontext;
|
|
30
|
+
const preferences = context.surfacepreferences ?? {};
|
|
31
|
+
const session = context.sessionpreferences ?? {};
|
|
32
|
+
if (preferences.paletterecents !== undefined && paletterecentsinput) paletterecentsinput.value = String(preferences.paletterecents);
|
|
33
|
+
if (preferences.paletteshortcut !== undefined && paletteshortcutinput) paletteshortcutinput.value = preferences.paletteshortcut;
|
|
34
|
+
if (preferences.logstreambuffer !== undefined && logstreambufferinput) logstreambufferinput.value = String(preferences.logstreambuffer);
|
|
35
|
+
if (preferences.taskinputretention !== undefined && taskinputretentioninput) taskinputretentioninput.value = String(preferences.taskinputretention);
|
|
36
|
+
if (preferences.diffpreviewbytes !== undefined && diffpreviewbytesinput) diffpreviewbytesinput.value = String(preferences.diffpreviewbytes);
|
|
37
|
+
if (session.recallwindow !== undefined && recallwindowinput) recallwindowinput.value = String(session.recallwindow);
|
|
38
|
+
if (session.noteretention !== undefined && noteretentioninput) noteretentioninput.value = String(session.noteretention);
|
|
39
|
+
if (session.summarywindow !== undefined && summarywindowinput) summarywindowinput.value = String(session.summarywindow);
|
|
40
|
+
await loadperf();
|
|
41
|
+
status("The surface options loaded; every write takes effect without reloading the extension.");
|
|
42
|
+
} catch (error) { status(error instanceof Error ? error.message : String(error), true); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Writes the commandpalette options; the recent window and the shortcut stay user choices with no engine defaults forced. */
|
|
46
|
+
async function savepalette(): Promise<void> {
|
|
47
|
+
const paletterecents = paletterecentsinput?.value.trim() ?? "";
|
|
48
|
+
const paletteshortcut = paletteshortcutinput?.value.trim() ?? "";
|
|
49
|
+
await request({ kind: "surface", settings: { ...(paletterecents !== "" ? { paletterecents: Number(paletterecents) } : {}), ...(paletteshortcut !== "" ? { paletteshortcut } : {}) } });
|
|
50
|
+
status(`The palette options saved${paletterecents !== "" ? ` with the recent window of ${paletterecents}` : ""}${paletteshortcut !== "" ? ` and the shortcut ${paletteshortcut}` : ""}.`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Writes the logstream live buffer bound; the full history stays in memory whatever the bound. */
|
|
54
|
+
async function savelogstream(): Promise<void> {
|
|
55
|
+
const bound = logstreambufferinput?.value.trim() ?? "";
|
|
56
|
+
await request({ kind: "surface", settings: { ...(bound !== "" ? { logstreambuffer: Number(bound) } : {}) } });
|
|
57
|
+
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.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Writes the taskinput history retention. */
|
|
61
|
+
async function savetaskinput(): Promise<void> {
|
|
62
|
+
const retention = taskinputretentioninput?.value.trim() ?? "";
|
|
63
|
+
await request({ kind: "surface", settings: { ...(retention !== "" ? { taskinputretention: Number(retention) } : {}) } });
|
|
64
|
+
status(retention === "" ? "No retention configured; the taskinput history keeps every entry." : `The taskinput history retention of ${retention} milliseconds saved.`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Writes the diffpreview offscreen offload byte ceiling. */
|
|
68
|
+
async function savediff(): Promise<void> {
|
|
69
|
+
const bytes = diffpreviewbytesinput?.value.trim() ?? "";
|
|
70
|
+
await request({ kind: "surface", settings: { ...(bytes !== "" ? { diffpreviewbytes: Number(bytes) } : {}) } });
|
|
71
|
+
status(bytes === "" ? "No ceiling configured; every diff stays inline." : `The diffpreview offload ceiling of ${bytes} bytes saved.`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Writes the session interface options of the 1.1.63 family through the sessions settings seam. */
|
|
75
|
+
async function savesession(): Promise<void> {
|
|
76
|
+
const recallwindow = recallwindowinput?.value.trim() ?? "";
|
|
77
|
+
const noteretention = noteretentioninput?.value.trim() ?? "";
|
|
78
|
+
const summarywindow = summarywindowinput?.value.trim() ?? "";
|
|
79
|
+
await request({ kind: "sessions", settings: { ...(recallwindow !== "" ? { recallwindow: Number(recallwindow) } : {}), ...(noteretention !== "" ? { noteretention: Number(noteretention) } : {}), ...(summarywindow !== "" ? { summarywindow: Number(summarywindow) } : {}) } });
|
|
80
|
+
status("The session interface options saved; the recall window, the note retention and the summary window take effect at once.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Loads the 1.1.68 performance options: the incrsnapshot cadence, the debouncedom windows per event kind, the worker queue depth, the lazymods prewarm set, the startup module budget, the perf retention, the virtlist row window and the capture priority, every one a user choice with no engine default. */
|
|
84
|
+
async function loadperf(): Promise<void> {
|
|
85
|
+
const perf = ((await request({ kind: "context" }) as surfacecontext).perfpreferences) ?? {};
|
|
86
|
+
if (perf.snapshotcadence !== undefined && document.querySelector<HTMLInputElement>("#snapshotcadence")) document.querySelector<HTMLInputElement>("#snapshotcadence")!.value = String(perf.snapshotcadence);
|
|
87
|
+
if (perf.debouncewindows?.scroll !== undefined && document.querySelector<HTMLInputElement>("#debouncescroll")) document.querySelector<HTMLInputElement>("#debouncescroll")!.value = String(perf.debouncewindows.scroll);
|
|
88
|
+
if (perf.debouncewindows?.input !== undefined && document.querySelector<HTMLInputElement>("#debounceinput")) document.querySelector<HTMLInputElement>("#debounceinput")!.value = String(perf.debouncewindows.input);
|
|
89
|
+
if (perf.debouncewindows?.resize !== undefined && document.querySelector<HTMLInputElement>("#debouncerresize")) document.querySelector<HTMLInputElement>("#debouncerresize")!.value = String(perf.debouncewindows.resize);
|
|
90
|
+
if (perf.debouncewindows?.mutation !== undefined && document.querySelector<HTMLInputElement>("#debouncemutation")) document.querySelector<HTMLInputElement>("#debouncemutation")!.value = String(perf.debouncewindows.mutation);
|
|
91
|
+
if (perf.workerdepth !== undefined && document.querySelector<HTMLInputElement>("#workerdepth")) document.querySelector<HTMLInputElement>("#workerdepth")!.value = String(perf.workerdepth);
|
|
92
|
+
if (perf.prewarmset !== undefined && document.querySelector<HTMLInputElement>("#prewarmset")) document.querySelector<HTMLInputElement>("#prewarmset")!.value = perf.prewarmset.join(",");
|
|
93
|
+
if (perf.lazybudget !== undefined && document.querySelector<HTMLInputElement>("#lazybudget")) document.querySelector<HTMLInputElement>("#lazybudget")!.value = String(perf.lazybudget);
|
|
94
|
+
if (perf.perfretention !== undefined && document.querySelector<HTMLInputElement>("#perfretention")) document.querySelector<HTMLInputElement>("#perfretention")!.value = String(perf.perfretention);
|
|
95
|
+
if (perf.virtlistrows !== undefined && document.querySelector<HTMLInputElement>("#virtlistrows")) document.querySelector<HTMLInputElement>("#virtlistrows")!.value = String(perf.virtlistrows);
|
|
96
|
+
if (perf.capturepriority !== undefined && document.querySelector<HTMLSelectElement>("#capturepriority")) document.querySelector<HTMLSelectElement>("#capturepriority")!.value = perf.capturepriority;
|
|
97
|
+
const schedulefields: Array<[keyof typeof perf, string]> = [["batchwindow", "#batchwindow"], ["politedelay", "#politedelay"], ["politejitter", "#politejitter"], ["pollgrowth", "#pollgrowth"], ["stepbudget", "#stepbudget"], ["memorybudget", "#memorybudget"], ["timeoutbound", "#timeoutbound"], ["suspendwindow", "#suspendwindow"], ["startuptarget", "#startuptarget"], ["selectorlatency", "#selectorlatency"], ["slowmofactor", "#slowmofactor"], ["logpruneretention", "#logpruneretention"], ["logsizewindow", "#logsizewindow"], ["batteryfloor", "#batteryfloor"], ["readlanes", "#readlanes"], ["cadencewidening", "#cadencewidening"]];
|
|
98
|
+
for (const [field, id] of schedulefields) {
|
|
99
|
+
const value = perf[field];
|
|
100
|
+
if (typeof value === "number" && document.querySelector<HTMLInputElement>(id)) document.querySelector<HTMLInputElement>(id)!.value = String(value);
|
|
101
|
+
}
|
|
102
|
+
if (perf.domainlimits !== undefined && document.querySelector<HTMLInputElement>("#domainlimits")) document.querySelector<HTMLInputElement>("#domainlimits")!.value = Object.entries(perf.domainlimits).map(([domain, slots]) => `${domain}=${slots}`).join(",");
|
|
103
|
+
if (perf.budgetthresholds?.warning !== undefined && document.querySelector<HTMLInputElement>("#budgetwarning")) document.querySelector<HTMLInputElement>("#budgetwarning")!.value = String(perf.budgetthresholds.warning);
|
|
104
|
+
if (perf.budgetthresholds?.critical !== undefined && document.querySelector<HTMLInputElement>("#budgetcritical")) document.querySelector<HTMLInputElement>("#budgetcritical")!.value = String(perf.budgetthresholds.critical);
|
|
105
|
+
if (perf.artifactcodec !== undefined && document.querySelector<HTMLSelectElement>("#artifactcodec")) document.querySelector<HTMLSelectElement>("#artifactcodec")!.value = perf.artifactcodec;
|
|
106
|
+
if (document.querySelector<HTMLInputElement>("#runcachepin")) document.querySelector<HTMLInputElement>("#runcachepin")!.checked = perf.runcachepin === true;
|
|
107
|
+
await renderstartupbudget();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Renders the startup module budget view: the prewarmed modules, the lazy rest and the budget the user configured, reported without ever refusing a load. */
|
|
111
|
+
async function renderstartupbudget(): Promise<void> {
|
|
112
|
+
const budgetnode = document.querySelector<HTMLElement>("#startupbudget");
|
|
113
|
+
if (!budgetnode) return;
|
|
114
|
+
try {
|
|
115
|
+
const view = await request({ kind: "perf", lazy: { list: true } }) as { budget: { prewarmed: string[]; lazy: string[]; total: number; budget?: number; over: boolean }; cost: { startupmodules: number; lazy: number; note: string } };
|
|
116
|
+
budgetnode.textContent = `Startup budget: ${view.budget.prewarmed.length} prewarmed module${view.budget.prewarmed.length === 1 ? "" : "s"} of ${view.budget.total}${view.budget.prewarmed.length > 0 ? ` (${view.budget.prewarmed.join(", ")})` : ""} · ${view.budget.lazy.length} lazy module${view.budget.lazy.length === 1 ? "" : "s"} out of the startup path${view.budget.budget !== undefined ? ` · user budget ${view.budget.budget}${view.budget.over ? " (over, reported never refused)" : ""}` : " · no budget set"} — ${view.cost.note}`;
|
|
117
|
+
} catch { budgetnode.textContent = "The startup module budget loads with the lazy module catalog."; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Saves the 1.1.68 performance options through the perf settings command; every window, depth, cadence and budget stays the user's choice. */
|
|
121
|
+
async function saveperf(): Promise<void> {
|
|
122
|
+
const numberof = (id: string): number | undefined => { const raw = document.querySelector<HTMLInputElement>(id)?.value.trim() ?? ""; return raw !== "" && Number.isFinite(Number(raw)) ? Number(raw) : undefined; };
|
|
123
|
+
const windows: Record<string, number> = {};
|
|
124
|
+
for (const [id, kind] of [["#debouncescroll", "scroll"], ["#debounceinput", "input"], ["#debouncerresize", "resize"], ["#debouncemutation", "mutation"]] as const) {
|
|
125
|
+
const window = numberof(id);
|
|
126
|
+
if (window !== undefined) windows[kind] = window;
|
|
127
|
+
}
|
|
128
|
+
const prewarmraw = document.querySelector<HTMLInputElement>("#prewarmset")?.value.trim() ?? "";
|
|
129
|
+
const prewarmset = prewarmraw === "" ? undefined : prewarmraw.split(",").map(id => id.trim()).filter(id => id !== "");
|
|
130
|
+
const priority = document.querySelector<HTMLSelectElement>("#capturepriority")?.value ?? "";
|
|
131
|
+
await request({ kind: "perf", settings: { ...(windows !== undefined && Object.keys(windows).length > 0 ? { debouncewindows: windows } : {}), ...(prewarmset !== undefined ? { prewarmset } : {}), ...(numberof("#snapshotcadence") !== undefined ? { snapshotcadence: numberof("#snapshotcadence") } : {}), ...(numberof("#workerdepth") !== undefined ? { workerdepth: numberof("#workerdepth") } : {}), ...(numberof("#lazybudget") !== undefined ? { lazybudget: numberof("#lazybudget") } : {}), ...(numberof("#perfretention") !== undefined ? { perfretention: numberof("#perfretention") } : {}), ...(numberof("#virtlistrows") !== undefined ? { virtlistrows: numberof("#virtlistrows") } : {}), ...(priority === "speed" ? { capturepriority: "speed" } : {}) } });
|
|
132
|
+
status("The performance options saved; the cadence, the windows, the depth, the prewarm set and the budget take effect at once.");
|
|
133
|
+
await renderstartupbudget();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Saves the 1.1.69 scheduling and budget options through the schedule settings command: the batch backpressure window, the domain limits, the politedelay windows, the adaptivepoll growth, the step and memory budgets with their alert thresholds, the timeout bound, the suspend window, the cold start target, the selector latency threshold, the slowmo default factor, the runcache pin, the logprune windows, the artifact codec, the battery floor, the read lane ceiling and the cadence widening factor, every one a user choice that optimizes and never caps. */
|
|
137
|
+
async function saveschedule(): Promise<void> {
|
|
138
|
+
const numberof = (id: string): number | undefined => { const raw = document.querySelector<HTMLInputElement>(id)?.value.trim() ?? ""; return raw !== "" && Number.isFinite(Number(raw)) ? Number(raw) : undefined; };
|
|
139
|
+
const domainlimits: Record<string, number> = {};
|
|
140
|
+
const domainraw = document.querySelector<HTMLInputElement>("#domainlimits")?.value.trim() ?? "";
|
|
141
|
+
for (const entry of domainraw.split(",").map(part => part.trim()).filter(part => part !== "")) {
|
|
142
|
+
const [domain, slots] = entry.split("=").map(part => part.trim());
|
|
143
|
+
if (domain !== undefined && domain !== "" && slots !== undefined && Number.isFinite(Number(slots)) && Number(slots) > 0) domainlimits[domain] = Number(slots);
|
|
144
|
+
}
|
|
145
|
+
const warning = numberof("#budgetwarning");
|
|
146
|
+
const critical = numberof("#budgetcritical");
|
|
147
|
+
const codec = document.querySelector<HTMLSelectElement>("#artifactcodec")?.value ?? "";
|
|
148
|
+
await request({ kind: "schedule", settings: { ...(numberof("#batchwindow") !== undefined ? { batchwindow: numberof("#batchwindow") } : {}), ...(Object.keys(domainlimits).length > 0 ? { domainlimits } : {}), ...(numberof("#politedelay") !== undefined ? { politedelay: numberof("#politedelay") } : {}), ...(numberof("#politejitter") !== undefined ? { politejitter: numberof("#politejitter") } : {}), ...(numberof("#pollgrowth") !== undefined ? { pollgrowth: numberof("#pollgrowth") } : {}), ...(numberof("#stepbudget") !== undefined ? { stepbudget: numberof("#stepbudget") } : {}), ...(numberof("#memorybudget") !== undefined ? { memorybudget: numberof("#memorybudget") } : {}), ...(warning !== undefined || critical !== undefined ? { budgetthresholds: { ...(warning !== undefined ? { warning } : {}), ...(critical !== undefined ? { critical } : {}) } } : {}), ...(numberof("#timeoutbound") !== undefined ? { timeoutbound: numberof("#timeoutbound") } : {}), ...(numberof("#suspendwindow") !== undefined ? { suspendwindow: numberof("#suspendwindow") } : {}), ...(numberof("#startuptarget") !== undefined ? { startuptarget: numberof("#startuptarget") } : {}), ...(numberof("#selectorlatency") !== undefined ? { selectorlatency: numberof("#selectorlatency") } : {}), ...(numberof("#slowmofactor") !== undefined ? { slowmofactor: numberof("#slowmofactor") } : {}), ...(document.querySelector<HTMLInputElement>("#runcachepin")?.checked === true ? { runcachepin: true } : { runcachepin: false }), ...(numberof("#logpruneretention") !== undefined ? { logpruneretention: numberof("#logpruneretention") } : {}), ...(numberof("#logsizewindow") !== undefined ? { logsizewindow: numberof("#logsizewindow") } : {}), ...(codec === "deflate" ? { artifactcodec: "deflate" } : { artifactcodec: "store" }), ...(numberof("#batteryfloor") !== undefined ? { batteryfloor: numberof("#batteryfloor") } : {}), ...(numberof("#readlanes") !== undefined ? { readlanes: numberof("#readlanes") } : {}), ...(numberof("#cadencewidening") !== undefined ? { cadencewidening: numberof("#cadencewidening") } : {}) } });
|
|
149
|
+
status("The scheduling and budget options saved; the windows, the limits, the delays, the budgets and the slowmo default factor take effect at once and none ever caps the work.");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Renders the onboarding state and replays the walkthrough on demand. */
|
|
153
|
+
async function renderonboarding(): Promise<void> {
|
|
154
|
+
if (!onboardingroot) return;
|
|
155
|
+
try {
|
|
156
|
+
const result = await request({ kind: "surface", onboarding: {} }) as { steps: Array<{ id: string; title: string; body: string; surface: string }>; state?: { stepscompleted: string[]; done: boolean } };
|
|
157
|
+
onboardingroot.replaceChildren();
|
|
158
|
+
const state = result.state;
|
|
159
|
+
const line = document.createElement("p");
|
|
160
|
+
line.textContent = state === undefined ? "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.`;
|
|
161
|
+
onboardingroot.append(line);
|
|
162
|
+
const list = document.createElement("ol");
|
|
163
|
+
list.className = "audit";
|
|
164
|
+
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) ? " — done" : ""}` }));
|
|
165
|
+
onboardingroot.append(list);
|
|
166
|
+
} catch (error) { onboardingroot.textContent = error instanceof Error ? error.message : String(error); }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
document.querySelector<HTMLButtonElement>("#replayonboarding")?.addEventListener("click", () => { void (async () => { await request({ kind: "surface", onboarding: { replay: true } }); status("The onboarding walkthrough replays; it opens on the next popup open."); await renderonboarding(); })().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
170
|
+
document.querySelector<HTMLButtonElement>("#palettesettings")?.addEventListener("click", () => { void savepalette().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
171
|
+
document.querySelector<HTMLButtonElement>("#logstreamsettings")?.addEventListener("click", () => { void savelogstream().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
172
|
+
document.querySelector<HTMLButtonElement>("#taskinputsettings")?.addEventListener("click", () => { void savetaskinput().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
173
|
+
document.querySelector<HTMLButtonElement>("#diffsettings")?.addEventListener("click", () => { void savediff().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
174
|
+
document.querySelector<HTMLButtonElement>("#sessionsettings")?.addEventListener("click", () => { void savesession().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
175
|
+
|
|
176
|
+
/** The optionspage sees settings changes through the single broadcast channel so two open pages never drift. */
|
|
177
|
+
const surfacechannel: BroadcastChannel | undefined = typeof BroadcastChannel === "function" ? new BroadcastChannel("devthinksurfaces") : undefined;
|
|
178
|
+
surfacechannel?.addEventListener("message", (event: MessageEvent) => { const frame = event.data as { channel?: string }; if (frame?.channel === "settings") { void load().catch(() => { /* a failing reload keeps the last loaded options */ }); } });
|
|
179
|
+
|
|
180
|
+
void load();
|
|
181
|
+
void renderonboarding();
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Interface finishing options of the 1.1.65 family: the darklight theme preference with its manual override, the interface language of the locale bundles, the recenttray depth, the step toast live count, the notification consent and preference, the user editable shortcutkeys, the importexport bundles with their dropimport zone and the featuretour replay with the guidedtip recall.
|
|
185
|
+
*/
|
|
186
|
+
const themepreferenceselect = document.querySelector<HTMLSelectElement>("#themepreference");
|
|
187
|
+
const contrastpreferenceselect = document.querySelector<HTMLSelectElement>("#contrastpreference");
|
|
188
|
+
const uilanguageselect = document.querySelector<HTMLSelectElement>("#uilanguage");
|
|
189
|
+
const recenttraydepthinput = document.querySelector<HTMLInputElement>("#recenttraydepth");
|
|
190
|
+
const toastlivecountinput = document.querySelector<HTMLInputElement>("#toastlivecount");
|
|
191
|
+
const notifyconsentinput = document.querySelector<HTMLInputElement>("#notifyconsent");
|
|
192
|
+
const notifyenabledinput = document.querySelector<HTMLInputElement>("#notifyenabled");
|
|
193
|
+
const shortcutlistnode = document.querySelector<HTMLElement>("#shortcutlist");
|
|
194
|
+
const shortcuteditinput = document.querySelector<HTMLInputElement>("#shortcutedit");
|
|
195
|
+
const bundlenode = document.querySelector<HTMLElement>("#bundlestatus");
|
|
196
|
+
const dropzonenode = document.querySelector<HTMLElement>("#dropzone");
|
|
197
|
+
const tourstopsnode = document.querySelector<HTMLElement>("#tourstops");
|
|
198
|
+
|
|
199
|
+
type finishingcontext = { surfacepreferences?: { recenttraydepth?: number; notifyconsent?: boolean; notifyenabled?: boolean; themepreference?: string; uilanguage?: string; toastlivecount?: number } };
|
|
200
|
+
|
|
201
|
+
/** The surfaces the contrast preference covers: the optionspage owns the toggle while the sidepanel and the dashboardpage render the themed panels, so the preference persists into every one of their layout preference records. */
|
|
202
|
+
const contrastsurfaces = ["optionspage", "sidepanel", "dashboardpage"] as const;
|
|
203
|
+
|
|
204
|
+
/** Reads the persisted contrast preference through the surface layout seam. */
|
|
205
|
+
async function contrastpreferenceof(): Promise<"default" | "high"> {
|
|
206
|
+
try {
|
|
207
|
+
const layout = await request({ kind: "surface", layout: { get: { surface: "optionspage" } } }) as { layout?: { preferences?: { contrastpreference?: string } } };
|
|
208
|
+
return layout.layout?.preferences?.contrastpreference === "high" ? "high" : "default";
|
|
209
|
+
} catch { return "default"; /* an unreadable preference keeps the default contrast */ }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Persists the contrast preference through the surface layout seam: the toggle writes the preference into the layout record of every themed surface, merging into the preferences each surface already stores. */
|
|
213
|
+
async function savecontrastpreference(contrast: "default" | "high"): Promise<void> {
|
|
214
|
+
for (const surface of contrastsurfaces) {
|
|
215
|
+
const layout = await request({ kind: "surface", layout: { get: { surface } } }) as { layout?: { preferences?: Record<string, string> } };
|
|
216
|
+
const preferences = { ...(layout.layout?.preferences ?? {}), contrastpreference: contrast };
|
|
217
|
+
await request({ kind: "surface", layout: { set: { surface, preferences } } });
|
|
218
|
+
}
|
|
219
|
+
status(contrast === "high" ? "The high contrast preference saved for every themed surface; each surface resolves the high contrast token set on its next open." : "The default contrast preference saved; every themed surface keeps its shipped tokens.");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Loads the finishing options beside the surface options. */
|
|
223
|
+
async function loadfinishing(): Promise<void> {
|
|
224
|
+
try {
|
|
225
|
+
const context = await request({ kind: "context" }) as finishingcontext;
|
|
226
|
+
const preferences = context.surfacepreferences ?? {};
|
|
227
|
+
if (preferences.themepreference !== undefined && themepreferenceselect) themepreferenceselect.value = preferences.themepreference;
|
|
228
|
+
if (preferences.uilanguage !== undefined && uilanguageselect) uilanguageselect.value = preferences.uilanguage;
|
|
229
|
+
if (preferences.recenttraydepth !== undefined && recenttraydepthinput) recenttraydepthinput.value = String(preferences.recenttraydepth);
|
|
230
|
+
if (preferences.toastlivecount !== undefined && toastlivecountinput) toastlivecountinput.value = String(preferences.toastlivecount);
|
|
231
|
+
if (notifyconsentinput) notifyconsentinput.checked = preferences.notifyconsent === true;
|
|
232
|
+
if (notifyenabledinput) notifyenabledinput.checked = preferences.notifyenabled !== false;
|
|
233
|
+
if (contrastpreferenceselect) contrastpreferenceselect.value = await contrastpreferenceof();
|
|
234
|
+
} catch (error) { status(error instanceof Error ? error.message : String(error), true); }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Saves the finishing options: the theme preference, the contrast preference, the language, the tray depth, the toast live count and the notification consent and preference. */
|
|
238
|
+
async function savefinishing(): Promise<void> {
|
|
239
|
+
const themepreference = themepreferenceselect?.value ?? "";
|
|
240
|
+
const contrast = contrastpreferenceselect?.value === "high" ? "high" : "default";
|
|
241
|
+
const uilanguage = uilanguageselect?.value ?? "";
|
|
242
|
+
const recenttraydepth = recenttraydepthinput?.value.trim() ?? "";
|
|
243
|
+
const toastlivecount = toastlivecountinput?.value.trim() ?? "";
|
|
244
|
+
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 } });
|
|
245
|
+
await savecontrastpreference(contrast);
|
|
246
|
+
await applythemelive();
|
|
247
|
+
status("The interface finishing options saved; the theme, the contrast, the language, the tray depth and the toast live count take effect at once.");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Resolves and applies the darklight tokens with their contrast variant on the live document so the theme covers the optionspage too: the contrast preference of the 2.0.2 final polish resolves the high contrast token set of the resolved mode and writes it through the same --theme-* custom properties while the body carries its data-contrast marker. */
|
|
251
|
+
async function applythemelive(): Promise<void> {
|
|
252
|
+
try {
|
|
253
|
+
const result = await request({ kind: "views", theme: { resolve: true, preference: themepreferenceselect?.value ?? "" } }) as { appearance: { mode: "dark" | "light"; tokens: Record<string, string>; source: string } };
|
|
254
|
+
const contrast = contrastpreferenceselect?.value === "high" ? "high" : "default";
|
|
255
|
+
const tokens = contrast === "high" ? contrasttokensof({ mode: result.appearance.mode, contrast }) : { mode: result.appearance.mode, tokens: result.appearance.tokens };
|
|
256
|
+
applycontrasttheme(document.documentElement, document.body, tokens, contrast);
|
|
257
|
+
} catch { /* a failing theme read keeps the shipped light theme */ }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Renders the user editable shortcutkeys bindings with their display text. */
|
|
261
|
+
async function rendershortcuts(): Promise<void> {
|
|
262
|
+
if (!shortcutlistnode) return;
|
|
263
|
+
try {
|
|
264
|
+
const result = await request({ kind: "views", shortcut: { list: true } }) as { bindings?: Array<{ command: string; key: string; modifiers: string[]; display?: string }> };
|
|
265
|
+
shortcutlistnode.replaceChildren();
|
|
266
|
+
const list = document.createElement("ul");
|
|
267
|
+
list.className = "audit";
|
|
268
|
+
for (const binding of result.bindings ?? []) {
|
|
269
|
+
const combination = [...binding.modifiers, binding.key].join("+");
|
|
270
|
+
list.append(Object.assign(document.createElement("li"), { textContent: `${binding.command}: ${binding.display ?? combination}` }));
|
|
271
|
+
}
|
|
272
|
+
shortcutlistnode.append(list);
|
|
273
|
+
} catch (error) { shortcutlistnode.textContent = error instanceof Error ? error.message : String(error); }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Saves one edited shortcut binding from its "command: combination" text. */
|
|
277
|
+
async function saveshortcut(): Promise<void> {
|
|
278
|
+
const text = shortcuteditinput?.value.trim() ?? "";
|
|
279
|
+
const split = text.split(":");
|
|
280
|
+
const command = split[0]?.trim() ?? "";
|
|
281
|
+
const combination = split.slice(1).join(":").trim();
|
|
282
|
+
if (command === "" || combination === "") { status("The shortcut edit needs its command and its combination, such as pauserun: alt+q.", true); return; }
|
|
283
|
+
await request({ kind: "views", shortcut: { edit: { command, text: combination } } });
|
|
284
|
+
status(`The ${command} shortcut now binds ${combination}; the command keeps its palette gates.`);
|
|
285
|
+
await rendershortcuts();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Renders the featuretour stops so the replay walkthrough reads its route first. */
|
|
289
|
+
async function rendertourstops(): Promise<void> {
|
|
290
|
+
if (!tourstopsnode) return;
|
|
291
|
+
try {
|
|
292
|
+
const result = await request({ kind: "views", tour: { stops: true } }) as { stops?: Array<{ id: string; surface: string; title: string; body: string }> };
|
|
293
|
+
tourstopsnode.replaceChildren();
|
|
294
|
+
const list = document.createElement("ol");
|
|
295
|
+
list.className = "audit";
|
|
296
|
+
for (const stop of result.stops ?? []) list.append(Object.assign(document.createElement("li"), { textContent: `${stop.title} (${stop.surface}): ${stop.body}` }));
|
|
297
|
+
tourstopsnode.append(list);
|
|
298
|
+
} catch (error) { tourstopsnode.textContent = error instanceof Error ? error.message : String(error); }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/** Exports the settings bundle and renders its honest exclusion list. */
|
|
302
|
+
async function exportbundle(): Promise<void> {
|
|
303
|
+
const result = await request({ kind: "views", importexport: { export: true } }) as { payload: { profile: string; exclusions: string[]; contents: { originprofiles: unknown[]; siteprofiles: unknown[]; notes: unknown[] } } };
|
|
304
|
+
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.`;
|
|
305
|
+
status("The settings bundle exported with its exclusion list.");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Validates one pasted bundle and applies its preferences when the validation passes. */
|
|
309
|
+
async function importbundle(): Promise<void> {
|
|
310
|
+
const text = window.prompt("Paste the importexport bundle json") ?? "";
|
|
311
|
+
if (text.trim() === "") return;
|
|
312
|
+
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
313
|
+
const validation = await request({ kind: "views", importexport: { validate: parsed } }) as { ok: boolean; reason: string };
|
|
314
|
+
if (!validation.ok) { status(validation.reason, true); return; }
|
|
315
|
+
const applied = await request({ kind: "views", importexport: { apply: { preferences: (parsed as { contents?: { preferences?: Record<string, unknown> } }).contents?.preferences ?? {} } } }) as { applied: string[] };
|
|
316
|
+
status(`The bundle applied ${applied.applied.length} preference key${applied.applied.length === 1 ? "" : "s"} after its validation passed.`);
|
|
317
|
+
await loadfinishing();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Accepts the csv, json and workflow files the user drops on the dropimport zone. */
|
|
321
|
+
function wir(dropimportzone: HTMLElement): void {
|
|
322
|
+
dropimportzone.addEventListener("dragover", event => { event.preventDefault(); });
|
|
323
|
+
dropimportzone.addEventListener("drop", event => {
|
|
324
|
+
event.preventDefault();
|
|
325
|
+
const file = event.dataTransfer?.files[0];
|
|
326
|
+
if (file === undefined) return;
|
|
327
|
+
void file.text().then(head => request({ kind: "views", dropimport: { file: { filename: file.name, bytes: file.size, head: head.slice(0, 2000) } } })).then(result => {
|
|
328
|
+
const session = (result as { session: { filename: string; kind: string } }).session;
|
|
329
|
+
status(`The dropimport detected the ${session.kind} kind of ${session.filename}; the import path takes the file from here.`);
|
|
330
|
+
}).catch(error => status(error instanceof Error ? error.message : String(error), true));
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
if (dropzonenode) wir(dropzonenode);
|
|
334
|
+
|
|
335
|
+
document.querySelector<HTMLButtonElement>("#finishingsettings")?.addEventListener("click", () => { void savefinishing().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
336
|
+
document.querySelector<HTMLButtonElement>("#shortcutsettings")?.addEventListener("click", () => { void saveshortcut().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
337
|
+
document.querySelector<HTMLButtonElement>("#exportbundle")?.addEventListener("click", () => { void exportbundle().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
338
|
+
document.querySelector<HTMLButtonElement>("#importbundle")?.addEventListener("click", () => { void importbundle().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
339
|
+
document.querySelector<HTMLButtonElement>("#replaytour")?.addEventListener("click", () => { void (async () => { await request({ kind: "views", tour: { replay: true } }); status("The featuretour replays with its datagrid, compareviewer and pickeroverlay stops."); })().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
340
|
+
document.querySelector<HTMLButtonElement>("#recalltips")?.addEventListener("click", () => { void (async () => { await request({ kind: "views", tips: { recall: true } }); status("The guidedtips recalled; they show again during the picker sessions."); })().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
341
|
+
themepreferenceselect?.addEventListener("change", () => { void applythemelive().catch(() => { /* a failing theme read keeps the shipped light theme */ }); });
|
|
342
|
+
contrastpreferenceselect?.addEventListener("change", () => { void (async () => { await savecontrastpreference(contrastpreferenceselect?.value === "high" ? "high" : "default"); await applythemelive(); })().catch(() => { /* a failing preference write keeps the shipped tokens */ }); });
|
|
343
|
+
|
|
344
|
+
void loadfinishing();
|
|
345
|
+
void applythemelive();
|
|
346
|
+
void rendershortcuts();
|
|
347
|
+
void rendertourstops();
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Syncbridge options of the 1.1.66 ecosystem family: the optionspage manages the manifest sync providers and their explicit opt in state and shows the conflict records with their resolve actions; no hook ever defaults on.
|
|
351
|
+
*/
|
|
352
|
+
const syncproviderlist = document.querySelector<HTMLElement>("#syncproviderlist");
|
|
353
|
+
const syncconflictsroot = document.querySelector<HTMLElement>("#syncconflicts");
|
|
354
|
+
const syncendpointinput = document.querySelector<HTMLInputElement>("#syncendpoint");
|
|
355
|
+
const syncproviderselect = document.querySelector<HTMLSelectElement>("#syncprovider");
|
|
356
|
+
|
|
357
|
+
/** Renders the syncbridge providers and hooks with their opt in states beside the open conflict records. */
|
|
358
|
+
async function rendersyncbridge(): Promise<void> {
|
|
359
|
+
if (syncproviderlist) {
|
|
360
|
+
try {
|
|
361
|
+
const providers = await request({ kind: "ecosystem", sync: { providers: true } }) as { providers: Array<{ provider: string; label: string; note: string }> };
|
|
362
|
+
const hooks = await request({ kind: "ecosystem", sync: { hooks: true } }) as { hooks: Array<{ id: string; provider: string; direction: string; optin: boolean; endpoint: string; state: string }> };
|
|
363
|
+
syncproviderlist.replaceChildren();
|
|
364
|
+
for (const provider of providers.providers) {
|
|
365
|
+
const row = document.createElement("p");
|
|
366
|
+
row.textContent = `${provider.label}: ${provider.note}`;
|
|
367
|
+
syncproviderlist.append(row);
|
|
368
|
+
}
|
|
369
|
+
for (const hook of hooks.hooks) {
|
|
370
|
+
const row = document.createElement("p");
|
|
371
|
+
row.textContent = `${hook.provider} hook — ${hook.endpoint} — ${hook.direction} — ${hook.optin ? "opted in" : "opted out"} — ${hook.state}`;
|
|
372
|
+
row.append(" ", (() => { const element = document.createElement("button"); element.type = "button"; element.className = "secondary"; element.textContent = hook.optin ? "Opt out" : "Opt in"; element.addEventListener("click", () => { void request({ kind: "ecosystem", sync: { hook: { optin: { hookid: hook.id, optin: !hook.optin } } } }).then(() => rendersyncbridge()).catch(error => status(error instanceof Error ? error.message : String(error), true)); }); return element; })());
|
|
373
|
+
syncproviderlist.append(row);
|
|
374
|
+
}
|
|
375
|
+
} catch (error) { syncproviderlist.textContent = error instanceof Error ? error.message : String(error); }
|
|
376
|
+
}
|
|
377
|
+
if (syncconflictsroot) {
|
|
378
|
+
try {
|
|
379
|
+
const conflicts = await request({ kind: "ecosystem", sync: { conflicts: true } }) as { conflicts: Array<{ id: string; manifestid: string; local: { version: string }; remote: { version: string }; resolution?: string }> };
|
|
380
|
+
syncconflictsroot.replaceChildren();
|
|
381
|
+
if (conflicts.conflicts.length === 0) { syncconflictsroot.textContent = "No syncbridge conflict stands open; both versions surface here whenever digests differ."; return; }
|
|
382
|
+
for (const conflict of conflicts.conflicts) {
|
|
383
|
+
const row = document.createElement("p");
|
|
384
|
+
row.textContent = `${conflict.manifestid}: local ${conflict.local.version} vs remote ${conflict.remote.version}${conflict.resolution !== undefined ? ` — resolved ${conflict.resolution}` : ""}`;
|
|
385
|
+
if (conflict.resolution === undefined) for (const resolution of ["local", "remote", "merge"] as const) {
|
|
386
|
+
const element = document.createElement("button");
|
|
387
|
+
element.type = "button";
|
|
388
|
+
element.className = "secondary";
|
|
389
|
+
element.textContent = resolution;
|
|
390
|
+
element.addEventListener("click", () => { void request({ kind: "ecosystem", sync: { resolve: { conflictid: conflict.id, resolution } } }).then(() => rendersyncbridge()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
391
|
+
row.append(" ", element);
|
|
392
|
+
}
|
|
393
|
+
syncconflictsroot.append(row);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) { syncconflictsroot.textContent = error instanceof Error ? error.message : String(error); }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
document.querySelector<HTMLButtonElement>("#synccreate")?.addEventListener("click", () => { void (async () => {
|
|
400
|
+
const endpoint = syncendpointinput?.value.trim() ?? "";
|
|
401
|
+
if (endpoint === "") throw new Error("The syncbridge hook needs its user configured endpoint; no provider address is ever hardcoded.");
|
|
402
|
+
await request({ kind: "ecosystem", sync: { hook: { add: { provider: syncproviderselect?.value === "web" ? "web" : "file", direction: "both", endpoint } } } });
|
|
403
|
+
if (syncendpointinput) syncendpointinput.value = "";
|
|
404
|
+
status(`The syncbridge hook of ${endpoint} was added opted out; no hook ever defaults on.`);
|
|
405
|
+
await rendersyncbridge();
|
|
406
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
407
|
+
|
|
408
|
+
void rendersyncbridge();
|
|
409
|
+
/**
|
|
410
|
+
* Model provider gateway options of the 1.1.83 family: the optionspage manages the baseurlconfig of every provider adapter (the base url the user typed, the optional path prefix of gateway deployments and the enable toggle that ships disabled), the consent stamp of the first remote call, the api key behind the vault seam with its explicit consent and one click revocation and the model choice of the discovered model list; no cloud default ever applies and the ollamalocal adapter alone carries the localhost default.
|
|
411
|
+
*/
|
|
412
|
+
const gatewayproviderlist = document.querySelector<HTMLElement>("#gatewayproviderlist");
|
|
413
|
+
const gatewaykindselect = document.querySelector<HTMLSelectElement>("#gatewaykind");
|
|
414
|
+
const gatewayidinput = document.querySelector<HTMLInputElement>("#gatewayid");
|
|
415
|
+
const gatewaybaseinput = document.querySelector<HTMLInputElement>("#gatewaybase");
|
|
416
|
+
const gatewayprefixinput = document.querySelector<HTMLInputElement>("#gatewayprefix");
|
|
417
|
+
const gatewaykeyinput = document.querySelector<HTMLInputElement>("#gatewaykey");
|
|
418
|
+
|
|
419
|
+
/** Renders the provider gateways with their base urls, enable toggles, consent stamps, key references and discovered model lists. */
|
|
420
|
+
async function rendergateways(): Promise<void> {
|
|
421
|
+
if (!gatewayproviderlist) return;
|
|
422
|
+
try {
|
|
423
|
+
const view = await request({ kind: "gateway", view: true }) as { configs: Array<{ providerid: string; kind: string; baseurl: string; pathprefix?: string; enabled: boolean; consented?: boolean; keyref?: { name: string }; local: boolean; costpermilliontokens?: number }>; models: Array<{ providerid: string; models: Array<{ id: string; contextwindow?: number; modalities?: string[] }>; fetchedat: number }>; chat: { provider?: string; model?: string; source: string; reason?: string } };
|
|
424
|
+
gatewayproviderlist.replaceChildren();
|
|
425
|
+
if (view.configs.length === 0) { gatewayproviderlist.textContent = "No provider gateway configured yet; add one below — every provider starts disabled and no cloud default applies."; return; }
|
|
426
|
+
for (const config of view.configs) {
|
|
427
|
+
const row = document.createElement("p");
|
|
428
|
+
const models = view.models.find(entry => entry.providerid === config.providerid);
|
|
429
|
+
row.textContent = `${config.providerid} (${config.kind}) — ${config.baseurl}${config.pathprefix !== undefined ? ` under ${config.pathprefix}` : ""} — ${config.enabled ? "enabled" : "disabled"}${config.consented === true ? ", consented" : config.local ? ", local runtime" : ", consent pending"}${config.keyref !== undefined ? `, key ${config.keyref.name} in the vault` : ", no key"}${models !== undefined ? `, ${models.models.length} model${models.models.length === 1 ? "" : "s"} discovered` : ""}.`;
|
|
430
|
+
const toggle = document.createElement("button");
|
|
431
|
+
toggle.type = "button";
|
|
432
|
+
toggle.className = "secondary";
|
|
433
|
+
toggle.textContent = config.enabled ? "Disable" : "Enable";
|
|
434
|
+
toggle.addEventListener("click", () => { void request({ kind: "gateway", config: { providerid: config.providerid, kind: config.kind, baseurl: config.baseurl, ...(config.pathprefix !== undefined ? { pathprefix: config.pathprefix } : {}), enabled: !config.enabled } }).then(() => rendergateways()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
435
|
+
row.append(" ", toggle);
|
|
436
|
+
if (!config.local) {
|
|
437
|
+
const consent = document.createElement("button");
|
|
438
|
+
consent.type = "button";
|
|
439
|
+
consent.className = "secondary";
|
|
440
|
+
consent.textContent = config.consented === true ? "Withdraw consent" : "Consent to remote calls";
|
|
441
|
+
consent.addEventListener("click", () => { void request({ kind: "gateway", consent: { providerid: config.providerid, grant: config.consented !== true } }).then(() => rendergateways()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
442
|
+
row.append(" ", consent);
|
|
443
|
+
}
|
|
444
|
+
const refresh = document.createElement("button");
|
|
445
|
+
refresh.type = "button";
|
|
446
|
+
refresh.className = "secondary";
|
|
447
|
+
refresh.textContent = "Refresh models";
|
|
448
|
+
refresh.addEventListener("click", () => { void request({ kind: "gateway", models: { providerid: config.providerid, refresh: true } }).then(() => rendergateways()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
449
|
+
row.append(" ", refresh);
|
|
450
|
+
if (config.keyref !== undefined) {
|
|
451
|
+
const revoke = document.createElement("button");
|
|
452
|
+
revoke.type = "button";
|
|
453
|
+
revoke.className = "secondary";
|
|
454
|
+
revoke.textContent = "Revoke key";
|
|
455
|
+
revoke.addEventListener("click", () => { void request({ kind: "gateway", key: { providerid: config.providerid, revoke: true } }).then(() => rendergateways()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
456
|
+
row.append(" ", revoke);
|
|
457
|
+
}
|
|
458
|
+
const remove = document.createElement("button");
|
|
459
|
+
remove.type = "button";
|
|
460
|
+
remove.className = "secondary";
|
|
461
|
+
remove.textContent = "Remove";
|
|
462
|
+
remove.addEventListener("click", () => { void request({ kind: "gateway", config: { providerid: config.providerid, kind: config.kind, remove: true } }).then(() => rendergateways()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
463
|
+
row.append(" ", remove);
|
|
464
|
+
gatewayproviderlist.append(row);
|
|
465
|
+
}
|
|
466
|
+
const route = document.createElement("p");
|
|
467
|
+
route.textContent = view.chat.provider !== undefined ? `The chat task kind routes to ${view.chat.provider}${view.chat.model !== undefined ? ` with ${view.chat.model}` : ""} (${view.chat.source}).` : `The chat task kind stays unrouted — ${view.chat.reason ?? "the user picks the pair from the models section of the sidepanel."}`;
|
|
468
|
+
gatewayproviderlist.append(route);
|
|
469
|
+
} catch (error) { gatewayproviderlist.textContent = error instanceof Error ? error.message : String(error); }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
document.querySelector<HTMLButtonElement>("#gatewaycreate")?.addEventListener("click", () => { void (async () => {
|
|
473
|
+
const providerid = gatewayidinput?.value.trim() ?? "";
|
|
474
|
+
const kind = gatewaykindselect?.value ?? "openaicompat";
|
|
475
|
+
const baseurl = gatewaybaseinput?.value.trim() ?? "";
|
|
476
|
+
const pathprefix = gatewayprefixinput?.value.trim() ?? "";
|
|
477
|
+
const keyvalue = gatewaykeyinput?.value ?? "";
|
|
478
|
+
if (providerid === "") throw new Error("The provider gateway needs its provider id.");
|
|
479
|
+
await request({ kind: "gateway", config: { providerid, kind, baseurl, ...(pathprefix !== "" ? { pathprefix } : {}), enabled: false } });
|
|
480
|
+
if (keyvalue !== "") await request({ kind: "gateway", key: { providerid, value: keyvalue, consent: true } });
|
|
481
|
+
if (gatewayidinput) gatewayidinput.value = "";
|
|
482
|
+
if (gatewaybaseinput) gatewaybaseinput.value = "";
|
|
483
|
+
if (gatewayprefixinput) gatewayprefixinput.value = "";
|
|
484
|
+
if (gatewaykeyinput) gatewaykeyinput.value = "";
|
|
485
|
+
status(`Added the ${kind} provider gateway ${providerid}; it starts disabled and the key ${keyvalue !== "" ? "stores behind the vault seam" : "stays unset"}.`);
|
|
486
|
+
await rendergateways();
|
|
487
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
488
|
+
|
|
489
|
+
void rendergateways();
|
|
490
|
+
|
|
491
|
+
document.querySelector<HTMLButtonElement>("#perfsettings")?.addEventListener("click", () => { void saveperf().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
492
|
+
document.querySelector<HTMLButtonElement>("#schedulesettings")?.addEventListener("click", () => { void saveschedule().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Site bridge options of the 1.1.82 family: the relay url stays the user's setting with no default (an empty value disables the bridge completely), the consent gate asks before the first socket connection, the page content consent flag stays off by default so plan text and statuses only cross, the idle window, the heartbeat interval, the rate cap and the pairing lifetime stay user choices, and the pairing panel shows the code with its expiry countdown while the revoke all and the kill switch close everything instantly.
|
|
496
|
+
*/
|
|
497
|
+
const bridgestatusline = document.querySelector<HTMLElement>("#bridgestatusline");
|
|
498
|
+
|
|
499
|
+
async function renderbridgeoptions(): Promise<void> {
|
|
500
|
+
if (!bridgestatusline) return;
|
|
501
|
+
try {
|
|
502
|
+
const view = await request({ kind: "bridge", view: true }) as { status: { status: string; paired: boolean; queued: number }; pairing?: { code: string; countdown: { secondsleft: number; expired: boolean; label: string } }; tokens: { live: number }; queue: { depth: number }; killswitch: boolean; consent: boolean; settings: { serverurl?: string; bridgeidlewindow?: number; bridgeheartbeatinterval?: number; bridgeratelimit?: number; bridgeratewindow?: number; bridgepairinglifetime?: number; bridgepageconsent?: boolean } };
|
|
503
|
+
const urlinput = document.querySelector<HTMLInputElement>("#serverurl");
|
|
504
|
+
if (urlinput && document.activeElement !== urlinput) urlinput.value = view.settings.serverurl ?? "";
|
|
505
|
+
const consentinput = document.querySelector<HTMLInputElement>("#bridgeconsent");
|
|
506
|
+
if (consentinput) consentinput.checked = view.consent;
|
|
507
|
+
const pageconsentinput = document.querySelector<HTMLInputElement>("#bridgepageconsent");
|
|
508
|
+
if (pageconsentinput) pageconsentinput.checked = view.settings.bridgepageconsent === true;
|
|
509
|
+
const numberof = (id: string, value: number | undefined): void => { const node = document.querySelector<HTMLInputElement>(id); if (node && document.activeElement !== node) node.value = value !== undefined ? String(value) : ""; };
|
|
510
|
+
numberof("#bridgeidlewindow", view.settings.bridgeidlewindow);
|
|
511
|
+
numberof("#bridgeheartbeatinterval", view.settings.bridgeheartbeatinterval);
|
|
512
|
+
numberof("#bridgeratelimit", view.settings.bridgeratelimit);
|
|
513
|
+
numberof("#bridgeratewindow", view.settings.bridgeratewindow);
|
|
514
|
+
numberof("#bridgepairinglifetime", view.settings.bridgepairinglifetime);
|
|
515
|
+
const killbutton = document.querySelector<HTMLButtonElement>("#bridgekill");
|
|
516
|
+
if (killbutton) killbutton.textContent = view.killswitch ? "Release kill switch" : "Kill switch";
|
|
517
|
+
bridgestatusline.textContent = `Bridge: ${view.status.status}${view.status.paired ? " and paired" : ""} · ${view.tokens.live} live token${view.tokens.live === 1 ? "" : "s"} · ${view.queue.depth} queued frame${view.queue.depth === 1 ? "" : "s"}${view.pairing !== undefined ? ` · pairing ${view.pairing.code} ${view.pairing.countdown.label}` : " · no pairing code minted"}.`;
|
|
518
|
+
} catch (error) { bridgestatusline.textContent = error instanceof Error ? error.message : String(error); }
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
document.querySelector<HTMLButtonElement>("#bridgesettings")?.addEventListener("click", () => { void (async () => {
|
|
522
|
+
const numberof = (id: string): number | undefined => { const raw = document.querySelector<HTMLInputElement>(id)?.value.trim() ?? ""; return raw !== "" && Number.isFinite(Number(raw)) ? Number(raw) : undefined; };
|
|
523
|
+
const url = document.querySelector<HTMLInputElement>("#serverurl")?.value.trim() ?? "";
|
|
524
|
+
await request({ kind: "bridge", settings: { ...(url !== "" ? { serverurl: url } : { serverurl: "" }), ...(numberof("#bridgeidlewindow") !== undefined ? { bridgeidlewindow: numberof("#bridgeidlewindow") } : {}), ...(numberof("#bridgeheartbeatinterval") !== undefined ? { bridgeheartbeatinterval: numberof("#bridgeheartbeatinterval") } : {}), ...(numberof("#bridgeratelimit") !== undefined ? { bridgeratelimit: numberof("#bridgeratelimit") } : {}), ...(numberof("#bridgeratewindow") !== undefined ? { bridgeratewindow: numberof("#bridgeratewindow") } : {}), ...(numberof("#bridgepairinglifetime") !== undefined ? { bridgepairinglifetime: numberof("#bridgepairinglifetime") } : {}), ...(document.querySelector<HTMLInputElement>("#bridgepageconsent") !== undefined ? { bridgepageconsent: document.querySelector<HTMLInputElement>("#bridgepageconsent")?.checked === true } : {}) } });
|
|
525
|
+
status("The site bridge options saved; the relay url stays your setting with no default and an empty value disables the bridge completely.");
|
|
526
|
+
await renderbridgeoptions();
|
|
527
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
528
|
+
|
|
529
|
+
document.querySelector<HTMLButtonElement>("#bridgeconsent")?.addEventListener("change", () => { void (async () => {
|
|
530
|
+
const granted = document.querySelector<HTMLInputElement>("#bridgeconsent")?.checked === true;
|
|
531
|
+
await request({ kind: "bridge", consent: granted ? { grant: true } : { revoke: true } });
|
|
532
|
+
status(granted ? "The bridge consent is granted; the first socket connection may open." : "The bridge consent is revoked; the socket closed and no further connection opens.");
|
|
533
|
+
await renderbridgeoptions();
|
|
534
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
535
|
+
|
|
536
|
+
document.querySelector<HTMLButtonElement>("#bridgeconnect")?.addEventListener("click", () => { void (async () => {
|
|
537
|
+
const granted = document.querySelector<HTMLInputElement>("#bridgeconsent")?.checked === true;
|
|
538
|
+
if (granted) await request({ kind: "bridge", consent: { grant: true } }).catch(() => { /* an already granted consent records no second event */ });
|
|
539
|
+
await request({ kind: "bridge", connect: true });
|
|
540
|
+
status("The bridge socket connected behind the consent gate; the pairing code of the origin rides the session create frame.");
|
|
541
|
+
await renderbridgeoptions();
|
|
542
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
543
|
+
|
|
544
|
+
document.querySelector<HTMLButtonElement>("#bridgedisconnect")?.addEventListener("click", () => { void (async () => {
|
|
545
|
+
await request({ kind: "bridge", disconnect: { reason: "The user closed the bridge socket from the options." } });
|
|
546
|
+
status("The bridge socket closed; the offline queue buffers every further frame for the replay.");
|
|
547
|
+
await renderbridgeoptions();
|
|
548
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
549
|
+
|
|
550
|
+
document.querySelector<HTMLButtonElement>("#bridgepair")?.addEventListener("click", () => { void (async () => {
|
|
551
|
+
const minted = await request({ kind: "bridge", pair: {} }) as { minted: { code: string; countdown: { label: string } } };
|
|
552
|
+
status(`The pairing code ${minted.minted.code} was minted inside the extension options; ${minted.minted.countdown.label}`);
|
|
553
|
+
await renderbridgeoptions();
|
|
554
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
555
|
+
|
|
556
|
+
document.querySelector<HTMLButtonElement>("#bridgerevoke")?.addEventListener("click", () => { void (async () => {
|
|
557
|
+
await request({ kind: "bridge", revoke: { all: true } });
|
|
558
|
+
status("Every relay session revoked with one click; the socket closed instantly and no frame passes afterwards.");
|
|
559
|
+
await renderbridgeoptions();
|
|
560
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
561
|
+
|
|
562
|
+
document.querySelector<HTMLButtonElement>("#bridgekill")?.addEventListener("click", () => { void (async () => {
|
|
563
|
+
const view = await request({ kind: "bridge", view: true }) as { killswitch: boolean };
|
|
564
|
+
await request({ kind: "bridge", killswitch: view.killswitch ? { release: true } : { engage: true } });
|
|
565
|
+
status(view.killswitch ? "The bridge kill switch released; the connect opens the socket again behind the consent gate." : "The bridge kill switch engaged; the socket and the pairing disabled instantly.");
|
|
566
|
+
await renderbridgeoptions();
|
|
567
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
568
|
+
|
|
569
|
+
void renderbridgeoptions();
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Native host bridge options of the 1.1.85 family: the panel shows the install state and the versions the handshake reported, offers the install consent that explains the scope before any host registration (the manifest write itself runs through the reviewed cli installer because the extension never writes the profile), the per class and per surface consents, the attach and the detach of the native port, the diagnostics report, the kill switch and the escape hatch key that stops every native call in one press; the idle window, the heartbeat interval and the rate cap stay user choices with no default.
|
|
573
|
+
*/
|
|
574
|
+
const nativestatusline = document.querySelector<HTMLElement>("#nativestatusline");
|
|
575
|
+
|
|
576
|
+
async function rendernativeoptions(): Promise<void> {
|
|
577
|
+
if (!nativestatusline) return;
|
|
578
|
+
try {
|
|
579
|
+
const view = await request({ kind: "native", view: true }) as { state: { installed: boolean; port: string; hostname?: string; companionversion?: string; companionprotocol?: string; installerversion?: string; wsbridgeport?: number }; diagnostics: { degraded: { degraded: boolean; reason: string } }; killswitch: boolean; installconsent: boolean; transportconsent: boolean; classconsents: string[]; surfaceconsents: string[]; calls: number; settings: { nativeidlewindow?: number; nativeheartbeatinterval?: number; nativecallratelimit?: number; nativecallratewindow?: number } };
|
|
580
|
+
const checkboxof = (id: string, value: boolean): void => { const node = document.querySelector<HTMLInputElement>(id); if (node) node.checked = value; };
|
|
581
|
+
checkboxof("#nativeinstallconsent", view.installconsent);
|
|
582
|
+
checkboxof("#nativetransportconsent", view.transportconsent);
|
|
583
|
+
checkboxof("#nativereadconsent", view.classconsents.includes("read"));
|
|
584
|
+
checkboxof("#nativeinteractionconsent", view.classconsents.includes("interaction"));
|
|
585
|
+
checkboxof("#nativesensitiveconsent", view.classconsents.includes("sensitive"));
|
|
586
|
+
checkboxof("#nativeosdialogconsent", view.surfaceconsents.includes("osdialog"));
|
|
587
|
+
checkboxof("#nativenotificationconsent", view.surfaceconsents.includes("notification"));
|
|
588
|
+
const numberof = (id: string, value: number | undefined): void => { const node = document.querySelector<HTMLInputElement>(id); if (node && document.activeElement !== node) node.value = value !== undefined ? String(value) : ""; };
|
|
589
|
+
numberof("#nativeidlewindow", view.settings.nativeidlewindow);
|
|
590
|
+
numberof("#nativeheartbeatinterval", view.settings.nativeheartbeatinterval);
|
|
591
|
+
numberof("#nativecallratelimit", view.settings.nativecallratelimit);
|
|
592
|
+
numberof("#nativecallratewindow", view.settings.nativecallratewindow);
|
|
593
|
+
const killbutton = document.querySelector<HTMLButtonElement>("#nativekill");
|
|
594
|
+
if (killbutton) killbutton.textContent = view.killswitch ? "Release kill switch" : "Kill switch";
|
|
595
|
+
nativestatusline.textContent = `Native bridge: ${view.state.installed ? `installed${view.state.hostname !== undefined ? ` as ${view.state.hostname}` : ""}` : "nothing installed"} · port ${view.state.port} · companion ${view.state.companionversion ?? "absent"} of protocol ${view.state.companionprotocol ?? "none"} · wsbridge port ${view.state.wsbridgeport ?? 0} · ${view.calls} recorded call${view.calls === 1 ? "" : "s"}${view.diagnostics.degraded ? ` · degraded: ${view.diagnostics.degraded.reason}` : ""}.`;
|
|
596
|
+
} catch (error) { nativestatusline.textContent = error instanceof Error ? error.message : String(error); }
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
document.querySelector<HTMLButtonElement>("#nativesettings")?.addEventListener("click", () => { void (async () => {
|
|
600
|
+
const numberof = (id: string): number | undefined => { const raw = document.querySelector<HTMLInputElement>(id)?.value.trim() ?? ""; return raw !== "" && Number.isFinite(Number(raw)) ? Number(raw) : undefined; };
|
|
601
|
+
await request({ kind: "native", settings: { ...(numberof("#nativeidlewindow") !== undefined ? { nativeidlewindow: numberof("#nativeidlewindow") } : {}), ...(numberof("#nativeheartbeatinterval") !== undefined ? { nativeheartbeatinterval: numberof("#nativeheartbeatinterval") } : {}), ...(numberof("#nativecallratelimit") !== undefined ? { nativecallratelimit: numberof("#nativecallratelimit") } : {}), ...(numberof("#nativecallratewindow") !== undefined ? { nativecallratewindow: numberof("#nativecallratewindow") } : {}) } });
|
|
602
|
+
status("The native bridge options saved; the idle window, the heartbeat interval and the rate cap stay your choices with no default.");
|
|
603
|
+
await rendernativeoptions();
|
|
604
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
605
|
+
|
|
606
|
+
document.querySelector<HTMLButtonElement>("#nativeinstall")?.addEventListener("click", () => { void (async () => {
|
|
607
|
+
const hostname = document.querySelector<HTMLInputElement>("#nativehostname")?.value.trim() ?? "";
|
|
608
|
+
const profiledir = document.querySelector<HTMLInputElement>("#nativeprofiledir")?.value.trim() ?? "";
|
|
609
|
+
await request({ kind: "native", installconsent: { grant: true, hostname, profiledir } });
|
|
610
|
+
status(`The install consent for ${hostname} into ${profiledir} is recorded with the scope explained; run the reviewed installer (devthink native install) for the manifest write because the extension never writes your profile.`);
|
|
611
|
+
await rendernativeoptions();
|
|
612
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
613
|
+
|
|
614
|
+
document.querySelector<HTMLButtonElement>("#nativeuninstall")?.addEventListener("click", () => { void (async () => {
|
|
615
|
+
await request({ kind: "native", installconsent: { revoke: true } });
|
|
616
|
+
status("The install consent is revoked; the transport detached and the reviewed uninstaller (devthink native uninstall) removes the host manifest and its preferences.");
|
|
617
|
+
await rendernativeoptions();
|
|
618
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
619
|
+
|
|
620
|
+
for (const [id, callclass] of [["#nativereadconsent", "read"], ["#nativeinteractionconsent", "interaction"], ["#nativesensitiveconsent", "sensitive"]] as Array<[string, "read" | "interaction" | "sensitive"]>) {
|
|
621
|
+
document.querySelector<HTMLInputElement>(id)?.addEventListener("change", () => { void (async () => {
|
|
622
|
+
const grant = document.querySelector<HTMLInputElement>(id)?.checked === true;
|
|
623
|
+
await request({ kind: "native", classconsent: { callclass, grant } });
|
|
624
|
+
status(grant ? `The ${callclass} call class is granted over the native transport; the gate asks per class.` : `The ${callclass} call class is revoked over the native transport.`);
|
|
625
|
+
await rendernativeoptions();
|
|
626
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (const [id, surface] of [["#nativeosdialogconsent", "osdialog"], ["#nativenotificationconsent", "notification"]] as Array<[string, "osdialog" | "notification"]>) {
|
|
630
|
+
document.querySelector<HTMLInputElement>(id)?.addEventListener("change", () => { void (async () => {
|
|
631
|
+
const grant = document.querySelector<HTMLInputElement>(id)?.checked === true;
|
|
632
|
+
await request({ kind: "native", surfaceconsent: { surface, grant } });
|
|
633
|
+
status(grant ? `The ${surface} surface holds its own consent grant; a desktop surface never opens from a class grant alone.` : `The ${surface} surface consent is revoked.`);
|
|
634
|
+
await rendernativeoptions();
|
|
635
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
document.querySelector<HTMLInputElement>("#nativetransportconsent")?.addEventListener("change", () => { void (async () => {
|
|
639
|
+
const grant = document.querySelector<HTMLInputElement>("#nativetransportconsent")?.checked === true;
|
|
640
|
+
await request({ kind: "native", transportconsent: grant ? { grant: true } : { revoke: true } });
|
|
641
|
+
status(grant ? "The consent to the first native host attach is granted; the attach may open behind the remaining gates." : "The native transport consent is revoked; the port detached and the transport stays deny by default.");
|
|
642
|
+
await rendernativeoptions();
|
|
643
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
644
|
+
|
|
645
|
+
document.querySelector<HTMLButtonElement>("#nativeattach")?.addEventListener("click", () => { void (async () => {
|
|
646
|
+
const hostname = document.querySelector<HTMLInputElement>("#nativehostname")?.value.trim() ?? "";
|
|
647
|
+
await request({ kind: "native", attach: { ...(hostname !== "" ? { hostname } : {}) } });
|
|
648
|
+
status("The native port attached behind the consent gates; the handshake frame rode the port and the diagnostics report the versions.");
|
|
649
|
+
await rendernativeoptions();
|
|
650
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
651
|
+
|
|
652
|
+
document.querySelector<HTMLButtonElement>("#nativedetach")?.addEventListener("click", () => { void (async () => {
|
|
653
|
+
await request({ kind: "native", detach: { reason: "The user closed the native transport from the options." } });
|
|
654
|
+
status("The native port detached; the install state persists and the next attach reuses it.");
|
|
655
|
+
await rendernativeoptions();
|
|
656
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
657
|
+
|
|
658
|
+
document.querySelector<HTMLButtonElement>("#nativediagnostics")?.addEventListener("click", () => { void (async () => {
|
|
659
|
+
await request({ kind: "native", diagnostics: true });
|
|
660
|
+
status("The native diagnostics ran: the port state, the versions and the last errors report in the status line above.");
|
|
661
|
+
await rendernativeoptions();
|
|
662
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
663
|
+
|
|
664
|
+
document.querySelector<HTMLButtonElement>("#nativekill")?.addEventListener("click", () => { void (async () => {
|
|
665
|
+
const view = await request({ kind: "native", view: true }) as { killswitch: boolean };
|
|
666
|
+
await request({ kind: "native", killswitch: view.killswitch ? { release: true } : { engage: true } });
|
|
667
|
+
status(view.killswitch ? "The native transport kill switch released; the attach opens again behind the consent gates." : "The native transport kill switch engaged: the port detached and every native call stops instantly.");
|
|
668
|
+
await rendernativeoptions();
|
|
669
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
670
|
+
|
|
671
|
+
document.querySelector<HTMLButtonElement>("#nativeescape")?.addEventListener("click", () => { void (async () => {
|
|
672
|
+
const answer = await request({ kind: "native", escapehatch: { pressed: true } }) as { banner?: { title: string; explanation: string; halted: number } };
|
|
673
|
+
status("The escape hatch key stopped every native call in one press; the transport detached until the user reattaches.");
|
|
674
|
+
/* the 1.1.95 hardening: the escape hatch shows its visible confirmation banner, because a one press halt of every native call deserves a confirmation the user sees */
|
|
675
|
+
const banner = document.querySelector<HTMLElement>("#nativebanner");
|
|
676
|
+
if (banner) {
|
|
677
|
+
banner.replaceChildren();
|
|
678
|
+
if (answer.banner !== undefined) {
|
|
679
|
+
const title = document.createElement("p");
|
|
680
|
+
title.textContent = answer.banner.title;
|
|
681
|
+
const explanation = document.createElement("p");
|
|
682
|
+
explanation.textContent = answer.banner.explanation;
|
|
683
|
+
banner.append(title, explanation);
|
|
684
|
+
banner.hidden = false;
|
|
685
|
+
} else banner.hidden = true;
|
|
686
|
+
}
|
|
687
|
+
await rendernativeoptions();
|
|
688
|
+
})().catch(error => status(error instanceof Error ? error.message : String(error), true)); });
|
|
689
|
+
|
|
690
|
+
void rendernativeoptions();
|
|
691
|
+
|