feat(theseus/settings): Aegis update card + DevTools open in tab sidebar

Two additions:

1) Aegis (bchwallet) update card lands in Settings > General beside
   the Ariadne one. Same look, different substance: Aegis is a
   bundled add-on, not a system service, so no Install/Uninstall
   buttons — the checkboxes there are 'Check for updates' and (only
   when an update is staged) 'Restart to apply update'. Reuses the
   existing signed OTA endpoint (addons-check-updates IPC) and
   addons-list-staged for the pending-update surface, so new wallet
   versions ship without a Theseus release.
   New app-restart IPC (app.relaunch + app.quit) does the promotion
   handoff — addons-host promotes staged updates on next boot.

2) DevTools (F12 / Ctrl+Shift+I) opens docked to the right of the
   tab view (mode: 'right') instead of popping a detached window. A
   user debugging a page gets the tools alongside it, matching stock
   Chrome; anyone who prefers detached can still drag it out via the
   DevTools own toolbar.
This commit is contained in:
Local Dev 2026-09-08 18:09:55 +02:00
parent ce53db3063
commit b5f1cf468b
3 changed files with 67 additions and 4 deletions

11
main.js
View file

@ -2978,6 +2978,9 @@ ipcMain.handle("find-stop", () => {
// Just the app version — no network. Used by Settings > General so the
// user can see which Theseus they're on without clicking "Check for updates".
ipcMain.handle("app-version", () => app.getVersion());
// Clean relaunch — used by the Aegis card to apply a staged add-on update
// (promotion happens on next boot; this is just how the user gets there).
ipcMain.handle("app-restart", () => { try { app.relaunch(); } catch {} app.quit(); });
ipcMain.handle("recheck-update", async () => {
// Manual "Check for updates" also un-dismisses any chip the user closed
// in this session — they're actively asking to see the status, so honour
@ -4056,9 +4059,9 @@ app.on("web-contents-created", (_event, wc) => {
return e.preventDefault();
}
// DevTools: F12 or Ctrl+Shift+I toggles Chromium DevTools on the active
// TAB (not the chrome/overlay that received the keystroke — a user
// debugging a page wants the page's inspector, always). Detach into
// its own window so we don't shove tools into the tab strip.
// TAB. Docked to the right of the tab view (mode: "right") so the tools
// ride alongside the page instead of popping a separate Theseus window.
// Users who prefer detached can still drag out via the DevTools UI.
const isI = input.key === "I" || input.key === "i";
if (input.key === "F12" || (input.control && input.shift && isI)) {
const t = activeTab();
@ -4066,7 +4069,7 @@ app.on("web-contents-created", (_event, wc) => {
try {
const twc = t.view.webContents;
if (twc.isDevToolsOpened()) twc.closeDevTools();
else twc.openDevTools({ mode: "detach" });
else twc.openDevTools({ mode: "right" });
} catch {}
}
return e.preventDefault();

View file

@ -40,6 +40,7 @@ contextBridge.exposeInMainWorld("cfg", {
// fetches the release manifest. Returns { updateAvailable, currentVersion }.
recheckUpdate: () => ipcRenderer.invoke("recheck-update"),
appVersion: () => ipcRenderer.invoke("app-version"),
restartApp: () => ipcRenderer.invoke("app-restart"),
// Add-ons management (Settings > Add-ons tab).
listAddons: () => ipcRenderer.invoke("addons-list"),
setAddonEnabled: (id, enabled) => ipcRenderer.invoke("addons-set-enabled", id, !!enabled),

View file

@ -234,6 +234,22 @@
<a href="https://silentmode.st/tools/">silentmode.st/tools</a>.
</div>
</div>
<div class="row" style="flex-direction:column;align-items:stretch;gap:8px">
<div class="txt">
<div class="t">Built-in wallet — Aegis</div>
<div class="d">
Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) that ships in the box.
Bundled with Theseus, but updates are delivered <b>over-the-air</b>: new signed
versions land without a Theseus release. Runs entirely inside this browser —
never system-wide.
</div>
</div>
<div id="aegisStatus" class="pmuted" style="font-size:12.5px">Loading…</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button id="aegisCheck" class="btn">Check for updates</button>
<button id="aegisApply" class="btn" hidden>Restart to apply update</button>
</div>
</div>
</section>
<!-- SEARCH -->
<section id="search" hidden>
@ -982,6 +998,49 @@
arRefresh.onclick = refreshAriadne;
refreshAriadne();
// ---- Aegis (bchwallet) update card --------------------------------------
// Aegis is a bundled add-on but its updates are shipped OTA via the signed
// add-on update endpoint, so users can get new wallet versions without
// updating Theseus. The same addons-check-updates IPC the Extensions page
// uses is filtered for the bchwallet id so this card only speaks for Aegis.
const aegisStatus = document.getElementById("aegisStatus");
const aegisCheck = document.getElementById("aegisCheck");
const aegisApply = document.getElementById("aegisApply");
async function refreshAegis() {
aegisApply.hidden = true;
try {
const [installed, staged] = await Promise.all([
C.listAddons ? C.listAddons() : Promise.resolve([]),
C.listStagedAddonUpdates ? C.listStagedAddonUpdates() : Promise.resolve([]),
]);
const cur = (installed || []).find((a) => a.id === "bchwallet");
const upd = (staged || []).find((a) => a.id === "bchwallet");
if (!cur) { aegisStatus.textContent = "Aegis isn't installed. Reinstall Theseus to add it back, or open Settings > Extensions to load it manually."; return; }
const curVer = cur.version || "?";
if (upd && upd.version) {
aegisStatus.innerHTML = `<b style="color:var(--acid-text)">v${upd.version}</b> staged — restart Theseus to switch to it. You're on v${curVer}.`;
aegisApply.hidden = false;
} else {
aegisStatus.textContent = `You're on v${curVer}. Updates arrive over-the-air; the next check runs at boot and every 6h.`;
}
} catch (e) { aegisStatus.textContent = "Status check failed: " + (e?.message || e); }
}
aegisCheck.onclick = async () => {
const orig = aegisCheck.textContent;
aegisCheck.disabled = true; aegisCheck.textContent = "Checking…";
aegisStatus.textContent = "Checking…";
try {
const staged = await (C.checkAddonUpdates ? C.checkAddonUpdates() : Promise.resolve([]));
const upd = (staged || []).find((a) => a.id === "bchwallet");
if (!upd) { aegisStatus.textContent = "You're on the latest Aegis."; }
// refresh will populate everything else + toggle the Apply button
refreshAegis();
} catch (e) { aegisStatus.textContent = "Check failed: " + (e?.message || e); }
finally { aegisCheck.disabled = false; aegisCheck.textContent = orig; }
};
aegisApply.onclick = () => { if (C && C.restartApp) C.restartApp(); };
refreshAegis();
// ---- Manual "Check for updates" -----------------------------------------
// Fires an IPC that un-dismisses any lingering session chip and hits the
// release manifest. Renders either "you're on the latest (vN.M.O)" or