Theseus 0.3.122: Settings restructure — General first-rows, top-level Ariadne & Aegis, Sidebar native-apps card

Settings page UX restructure.

- Nav bar: reordered to General, Language, Search, Passwords, Performance,
  Privacy, Names & Certificates, Plug-ins, Ariadne's Thread, Aegis,
  Extensions, Sidebar, Apps. Dropped the top-level Theseus ID entry;
  Theseus ID is reachable from General now. Added two new top-level
  entries — Ariadne's Thread and Aegis — each a small landing section
  that chevrons into the existing plug-in sub-page (Ariadne) or opens the
  wallet panel via openPanel("aegis:main") (Aegis).

- General section: first four rows are now Make-Theseus-default, Theseus
  Shield (chevron + on/off toggle), Startup (restore tabs), Updates
  (version + Check button), followed by a Theseus ID chevron row.
  Appearance / Developer tools / Registries / About stay below in the
  same order. Removed the "Default handling" sub-head; the default-
  browser and file-associations rows moved to the top. The Shield row
  uses listAddons() + setAddonEnabled("blocker", on) for state; the
  chevron falls back to openPanel("blocker:main").

- Names & Certificates: dropped the always-on BNS row at the top.

- Plug-ins main page: Ariadne's Thread and Aegis rows removed — they
  moved to top-level. The page now links into the new landing sections.
  Hidden placeholders keep the DOM ids (ariadneStatusMain, aegisCheckMain
  …) so the shared refresh code keeps working. Sub-pages (plugins-
  ariadne, plugins-aegis) untouched.

- Extensions list: Hermes and Pithos hidden from the list (they live in
  the Sidebar card now). docx-editor and pdf-editor sort last within
  each group so smaller utilities stay near the top.

- Toolbar add-on strip (chrome.html): same last-sort rule for docx-editor
  and pdf-editor, applied to the rank() ordering so unknown keys without
  a saved dockOrder keep registration order and the editors land past
  them.

- Sidebar section: added two cards at the top. "Theseus native apps"
  (Hermes + Pithos) and "Third-party apps" (the sidebar quick-links /
  Web apps). Native apps appear first. The Extension panels section
  still lists everything else that opens from the strip; Hermes and
  Pithos are filtered out of it so they don't appear twice.

- Version bumped to 0.3.122. No new IPCs — reuses listAddons,
  setAddonEnabled, openPanel, sidebarLeftPanels, addonInvoke.
This commit is contained in:
Local Dev 2026-10-27 18:04:49 +02:00
parent 41eb0acec9
commit 1a31ac0393
3 changed files with 223 additions and 87 deletions

View file

@ -905,11 +905,19 @@
lastButtons.push({ kind: "menu", key: "m:" + m.addonId, addonId: m.addonId, icon: String(m.icon || "•"), title: m.title || m.addonId, items: m.items || [] });
}
// User prefs: drag-reorder order and buttons hidden via the right-click
// menu. Unknown keys keep registration order after the ordered ones.
// menu. Unknown keys keep registration order after the ordered ones; the
// document editors sort LAST so smaller utilities (Notepad, Screenshot,
// Translate…) stay near the start of the strip.
if (d.dock) lastDock = d.dock;
const order = Array.isArray(lastDock.order) ? lastDock.order : [];
const hidden = new Set(Array.isArray(lastDock.hidden) ? lastDock.hidden : []);
const rank = (k) => { const i = order.indexOf(k); return i < 0 ? order.length : i; };
const LAST_ADDON_IDS = new Set(["docx-editor", "pdf-editor"]);
const addonIdOf = (k) => k.startsWith("p:") ? k.slice(2).split(":")[0] : k.slice(2);
const rank = (k) => {
const i = order.indexOf(k);
if (i >= 0) return i;
return LAST_ADDON_IDS.has(addonIdOf(k)) ? order.length + 1 : order.length;
};
lastButtons = lastButtons.filter((b) => !hidden.has(b.key)).map((b, i) => ({ ...b, i })).sort((a, b) => rank(a.key) - rank(b.key) || a.i - b.i);
lastPanels = lastPanels.filter((p) => !hidden.has("p:" + p.panelId));
lastMenus = lastMenus.filter((m) => !hidden.has("m:" + m.addonId));

View file

@ -1,6 +1,6 @@
{
"name": "theseus-navigator",
"version": "0.3.121",
"version": "0.3.122",
"license": "MPL-2.0",
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
"author": "Silent Mode",

View file

@ -313,16 +313,17 @@
<nav class="side">
<div class="brand">⛓ Theseus</div>
<a data-sec="general" class="active">General</a>
<a data-sec="sidebar">Sidebar</a>
<a data-sec="language">Language</a>
<a data-sec="search">Search</a>
<a data-sec="passwords">Passwords</a>
<a data-sec="theseusid">Theseus ID</a>
<a data-sec="performance">Performance</a>
<a data-sec="privacy">Privacy</a>
<a data-sec="names">Names &amp; Certificates</a>
<a data-sec="plugins">Plug-ins</a>
<a data-sec="ariadne">Ariadne's Thread</a>
<a data-sec="aegis">Aegis</a>
<a data-sec="addons">Extensions</a>
<a data-sec="sidebar">Sidebar</a>
<a data-sec="apps">Apps</a>
</nav>
<div class="content">
@ -330,20 +331,47 @@
<section id="general">
<h1>General</h1>
<p class="lede">Changes apply immediately and are saved for next time.</p>
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Startup</h2>
<!-- Default-browser row — moved to the top of General. Hidden until the
platform-check (Windows only, today) reveals it. -->
<div class="row" id="defaultBrowserRow" hidden>
<div class="txt">
<div class="t">Make Theseus the default browser</div>
<div class="d">Register Theseus for <code>http</code> and <code>https</code> links. Windows still asks you to confirm the switch in <b>Settings &rsaquo; Default apps</b>; the button opens that page.</div>
<div class="acts"><button class="btn small ghost" id="openSysDefaults">Open Default Apps…</button></div>
</div>
<label class="sw"><input type="checkbox" id="defaultBrowser"><span class="track"><span class="knob"></span></span></label>
</div>
<div class="row" id="fileAssocsRow" hidden>
<div class="txt">
<div class="t">Show "Open with Theseus" in file menus</div>
<div class="d">Right-click an <code>.html</code>, <code>.htm</code>, <code>.pdf</code>, <code>.svg</code> or <code>.xhtml</code> file and Theseus appears in the Open-with menu. Theseus opens it as a local-file tab.</div>
</div>
<label class="sw"><input type="checkbox" id="fileAssocs"><span class="track"><span class="knob"></span></span></label>
</div>
<!-- Theseus Shield — chevron opens the Shield add-on's own panel; the
toggle on the right flips it on/off. State comes from listAddons(). -->
<div class="row" id="generalShieldRow">
<div class="txt" id="generalShieldOpen" role="button" tabindex="0" style="cursor:pointer" title="Open Theseus Shield">
<div class="t">Theseus Shield <span class="chev">›</span></div>
<div class="d">Click to open its own settings. Blocks third-party trackers and ads on every site.</div>
</div>
<label class="sw" title="Turn Shield on/off"><input type="checkbox" id="generalShieldOn"><span class="track"><span class="knob"></span></span></label>
</div>
<!-- Startup — the restoreSession toggle lives here as a one-line row. -->
<div class="row">
<div class="txt"><div class="t">Open previous windows and tabs</div><div class="d">Restore the tabs from your last session when Theseus starts, even when history is cleared on quit.</div></div>
<div class="txt"><div class="t">Startup</div><div class="d">Restore the tabs from your last session when Theseus starts.</div></div>
<label class="sw"><input type="checkbox" id="restoreSession"><span class="track"><span class="knob"></span></span></label>
</div>
<div class="row" style="flex-direction:column;align-items:stretch;gap:8px">
<div class="txt">
<div class="t">Updates</div>
<div class="d">Theseus checks the release manifest shortly after launch — and retries with backoff if that first attempt is offline — then every 6 hours while it is running. Click below to check right now.</div>
</div>
<div id="updStatus" class="pmuted" style="font-size:12.5px">Loading…</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button id="updCheck" class="btn">Check for updates</button>
</div>
<!-- Updates — version + Check button, compressed to one line. The init
in initUpdatesPanel() still writes to #updStatus / #updCheck. -->
<div class="row">
<div class="txt"><div class="t">Updates</div><div class="d" id="updStatus">Loading…</div></div>
<div class="ctl"><button id="updCheck" class="btn">Check for updates</button></div>
</div>
<!-- Theseus ID moved out of the top nav — reach its sub-page from here. -->
<div class="row link" data-go="theseusid">
<div class="txt"><div class="t">Theseus ID</div><div class="d">Your Silent Mode account, kept in your Theseus Vault.</div></div>
<span class="chev">›</span>
</div>
<h2 class="sub">Appearance</h2>
<p class="subd">Choose a theme for the browser. <b>System</b> follows your operating system's light/dark setting.</p>
@ -400,22 +428,6 @@
<div id="blkSummary" class="pmuted" style="font-size:12.5px"></div>
<div><button id="resetBlocklist" class="btn">Forget "continue anyway" choices</button></div>
</div>
<h2 class="sub" id="defHandlingHead" hidden>Default handling</h2>
<div class="row" id="defaultBrowserRow" hidden>
<div class="txt">
<div class="t">Make Theseus the default browser</div>
<div class="d">Register Theseus for <code>http</code> and <code>https</code> links. Windows still asks you to confirm the switch in <b>Settings &rsaquo; Default apps</b>; the button opens that page.</div>
<div class="acts"><button class="btn small ghost" id="openSysDefaults">Open Default Apps…</button></div>
</div>
<label class="sw"><input type="checkbox" id="defaultBrowser"><span class="track"><span class="knob"></span></span></label>
</div>
<div class="row" id="fileAssocsRow" hidden>
<div class="txt">
<div class="t">Show "Open with Theseus" in file menus</div>
<div class="d">Right-click an <code>.html</code>, <code>.htm</code>, <code>.pdf</code>, <code>.svg</code> or <code>.xhtml</code> file and Theseus appears in the Open-with menu. Theseus opens it as a local-file tab.</div>
</div>
<label class="sw"><input type="checkbox" id="fileAssocs"><span class="track"><span class="knob"></span></span></label>
</div>
<h2 class="sub">About</h2>
<div class="row">
<div class="txt"><div class="t">Theseus Navigator is free software</div>
@ -486,14 +498,6 @@
<h2 class="sub" style="border-top:0;padding-top:0;margin-top:0">Name resolvers</h2>
<div class="row">
<div class="txt">
<div class="t">Bitcoin Cash Name System (BNS)</div>
<div class="d"><i>.x</i>, <i>.bch</i>, <i>.sia</i>, <i>.coin</i>, <i>.dgb</i>, and the rest of the seeded TLDs. Powered by <a data-go="plugins/ariadne" style="color:var(--acid-text);cursor:pointer">Ariadne's Thread</a>.</div>
</div>
<label class="sw" title="BNS is built in and always on"><input type="checkbox" checked disabled><span class="track"><span class="knob"></span></span></label>
</div>
<div class="row">
<div class="txt">
<div class="t">Ethereum Name Service (ENS)</div>
@ -599,38 +603,69 @@
<section id="plugins" hidden>
<h1>Plug-ins</h1>
<p class="lede">Silent Mode components that live alongside Theseus — each with its own on/off, updates and settings page.</p>
<p class="lede">Silent Mode components that live alongside Theseus — each with its own on/off, updates and settings page. Ariadne's Thread and Aegis now have their own entries in the sidebar.</p>
<div class="row" style="justify-content:flex-end;gap:8px"><a class="btn" style="text-decoration:none;display:inline-flex;align-items:center" href="https://theseus.x/plug-ins/" target="_blank" rel="noopener" title="Current signed versions, hashes and how plug-ins update">About plug-ins on theseus.x ↗</a></div>
<!-- Ariadne's Thread -->
<div class="row plug-row">
<div class="txt plug-open" data-go="plugins/ariadne" role="button" tabindex="0" title="Open Ariadne's Thread settings">
<div class="t">Ariadne's Thread <span class="pmuted" style="font-weight:400">— system-wide resolver</span> <span class="chev">›</span></div>
<div class="d">Resolves BCDN names for every browser on this machine — not just Theseus.</div>
<div id="ariadneStatusMain" class="pmuted" style="font-size:12.5px;margin-top:4px">checking…</div>
</div>
<div class="plug-ctl">
<button id="ariadneInstallMain" class="btn small" hidden>Install</button>
<button id="ariadneUpdateMain" class="btn small" hidden>Update</button>
<button id="ariadneCheckMain" class="btn small" title="Refresh Ariadne status">Refresh</button>
<label class="sw" id="ariadneSwWrap" title="Turn Ariadne on/off">
<input type="checkbox" id="ariadneToggleMain">
<span class="track"><span class="knob"></span></span>
</label>
</div>
<div class="row link" data-go="ariadne">
<div class="txt"><div class="t">Ariadne's Thread</div><div class="d">System-wide BCDN resolver.</div></div>
<span class="chev">›</span>
</div>
<div class="row link" data-go="aegis">
<div class="txt"><div class="t">Aegis</div><div class="d">Built-in multi-chain wallet.</div></div>
<span class="chev">›</span>
</div>
<!-- Hidden hooks: the plug-in init code wires several controls below by
id (status, toggles, install/update/apply buttons). Keep them in the
DOM so the sub-pages (plugins-ariadne / plugins-aegis) still work;
moving the plug-ins to top-level nav entries did not change their
sub-pages. These are offscreen placeholders that mirror the sub-page
controls, read by the shared refresh functions. -->
<div hidden aria-hidden="true" id="pluginsMainHooks">
<span id="ariadneStatusMain"></span>
<button id="ariadneInstallMain"></button>
<button id="ariadneUpdateMain"></button>
<button id="ariadneCheckMain"></button>
<span id="ariadneSwWrap"><input type="checkbox" id="ariadneToggleMain"></span>
<span id="aegisStatusMain"></span>
<button id="aegisCheckMain"></button>
<button id="aegisApplyMain"></button>
</div>
</section>
<!-- Aegis -->
<div class="row plug-row">
<div class="txt plug-open" data-go="plugins/aegis" role="button" tabindex="0" title="Open Aegis settings">
<div class="t">Aegis <span class="pmuted" style="font-weight:400">— built-in wallet</span> <span class="chev">›</span></div>
<div class="d">Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB). Ships in the box, updates over-the-air.</div>
<div id="aegisStatusMain" class="pmuted" style="font-size:12.5px;margin-top:4px">loading…</div>
<!-- Top-level landing section for Ariadne's Thread. Chevrons into the
existing sub-page under Plug-ins, where install / policy / sources /
status / uninstall live. The sub-page keeps its "Plug-ins ›" crumb
so Ariadne stays reachable from either entry. -->
<section id="ariadne" hidden>
<h1>Ariadne's Thread</h1>
<p class="lede">A local daemon that resolves BCDN names for <b>every browser on this machine</b>, not just Theseus. Theseus has its own built-in resolver either way; this plug-in is for Chrome, Edge, Firefox and the rest.</p>
<div class="row link" data-go="plugins/ariadne">
<div class="txt">
<div class="t">Open Ariadne's Thread settings</div>
<div class="d">Install, update, uninstall; collision policy, sources and the live status report.</div>
</div>
<div class="plug-ctl">
<button id="aegisCheckMain" class="btn small">Check for updates</button>
<button id="aegisApplyMain" class="btn small" hidden>Apply update</button>
<span class="chev">›</span>
</div>
</section>
<!-- Top-level landing section for Aegis. The chevron opens the Aegis
panel in the sidebar (openPanel), falling back to its sub-page under
Plug-ins if the panel isn't registered. -->
<section id="aegis" hidden>
<h1>Aegis</h1>
<p class="lede">Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) that ships with Theseus and updates over-the-air.</p>
<div class="row link" id="aegisOpenRow">
<div class="txt">
<div class="t">Open the Aegis wallet</div>
<div class="d">Opens Aegis in the sidebar panel beside the page.</div>
</div>
<span class="chev">›</span>
</div>
<div class="row link" data-go="plugins/aegis">
<div class="txt">
<div class="t">Aegis plug-in settings</div>
<div class="d">Version, updates and Apply update now.</div>
</div>
<span class="chev">›</span>
</div>
</section>
@ -1130,7 +1165,17 @@
<div class="d">The sparkle button at the bottom of the strip opens your default AI engine (right-click it to pick from any you've enabled, or set up more under <a data-go="search" style="color:var(--acid-text);cursor:pointer">Search</a>).</div>
</div>
</div>
<h2 class="sub">Web apps</h2>
<!-- Card A: Theseus native apps (Hermes, Pithos) — bundled system apps.
Rendered from sidebarLeftPanels(); filtered to the native-app ids. -->
<h2 class="sub">Theseus native apps</h2>
<p class="subd">System apps bundled with Theseus. They open from the sidebar strip.</p>
<div class="row" style="flex-direction:column;align-items:stretch;gap:6px">
<div id="sbNativeApps" style="display:flex;flex-direction:column;gap:6px"></div>
</div>
<!-- Card B: Third-party apps — the sidebar quick-links (Proton,
Telegram, WhatsApp, YouTube, X, …). Same qlList the Web apps
section used to drive, with the add-input below. -->
<h2 class="sub">Third-party apps</h2>
<p class="subd">Sites that open in the panel. Switch one off to take it off the strip; it stays here, still signed in, for switching back on. A site you installed as an app can be added from Apps.</p>
<div class="row" style="flex-direction:column;align-items:stretch;gap:8px">
<div id="qlList" style="display:flex;flex-direction:column;gap:6px"></div>
@ -1141,7 +1186,7 @@
</div>
</div>
<h2 class="sub">Extension panels</h2>
<p class="subd">Extensions that open from the strip. Switch one off to take it off the strip; the extension keeps working.</p>
<p class="subd">Extensions that open from the strip. Switch one off to take it off the strip; the extension keeps working. The native apps above live under their own card.</p>
<div class="row" style="flex-direction:column;align-items:stretch;gap:6px">
<div id="sbPanels" style="display:flex;flex-direction:column;gap:6px"></div>
</div>
@ -1240,7 +1285,7 @@
try {
if (C && typeof C.appVersion === "function") {
C.appVersion().then((v) => {
updStatus.textContent = v ? `You're on v${v}.` : "Version unavailable — try Check for updates.";
updStatus.textContent = v ? `Theseus current version v${v}.` : "Version unavailable — try Check for updates.";
}).catch((e) => {
updStatus.textContent = "Could not read version: " + (e?.message || e) + " — try Check for updates.";
});
@ -1272,7 +1317,6 @@
(async () => {
const row = document.getElementById("defaultBrowserRow");
const assocRow = document.getElementById("fileAssocsRow");
const head = document.getElementById("defHandlingHead");
const defBrowser = document.getElementById("defaultBrowser");
const assoc = document.getElementById("fileAssocs");
const openSys = document.getElementById("openSysDefaults");
@ -1280,7 +1324,7 @@
try {
const d = await cfg.defaultBrowserState();
if (!d.supported) return;
head.hidden = false; row.hidden = false; assocRow.hidden = false;
row.hidden = false; assocRow.hidden = false;
defBrowser.checked = !!d.isDefault;
assoc.checked = !!d.hasAssocs;
} catch {}
@ -1301,6 +1345,51 @@
openSys.addEventListener("click", () => cfg.openSystemDefaults());
refresh();
})();
// General › Theseus Shield row — chevron opens the Shield add-on's panel;
// toggle flips the blocker on/off. State comes from listAddons().
(function () {
const openEl = document.getElementById("generalShieldOpen");
const onEl = document.getElementById("generalShieldOn");
if (!openEl || !onEl) return;
const openShield = () => {
// Prefer the add-on's own panel; fall back to the Extensions page.
if (C && typeof C.openPanel === "function") {
try { C.openPanel("blocker:main"); return; } catch {}
}
try { showSection("addons"); } catch {}
};
openEl.addEventListener("click", openShield);
openEl.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openShield(); } });
async function refresh() {
try {
const list = await (C.listAddons ? C.listAddons() : Promise.resolve({}));
const blocker = ((list && list.installed) || []).find((a) => a.id === "blocker");
onEl.checked = !!(blocker && blocker.enabled);
} catch {}
}
onEl.addEventListener("change", async () => {
try { if (C.setAddonEnabled) await C.setAddonEnabled("blocker", onEl.checked); } catch {}
// Give the host a moment to flip, then refresh so the toggle shows the
// real state (an add-on that refused the flip stays as it was).
setTimeout(refresh, 400);
});
refresh();
document.addEventListener("section", (e) => { if (e.detail === "general") refresh(); });
})();
// Aegis landing-section chevron — opens Aegis's own sidebar panel. Falls
// back to the plug-in sub-page if the panel isn't registered.
(function () {
const row = document.getElementById("aegisOpenRow");
if (!row) return;
row.addEventListener("click", async (e) => {
// The row's own data-go would route to Settings, not the Aegis panel.
e.preventDefault(); e.stopPropagation();
if (C && typeof C.openPanel === "function") {
try { await C.openPanel("aegis:main"); return; } catch {}
}
try { showSection("plugins/aegis"); } catch {}
});
})();
// sidebar navigation
// "naming" is intentionally absent: the Registries section was folded
// into General in 0.3.21. Leaving the id in this list threw a
@ -1672,26 +1761,47 @@
if (C.onSettingsUpdate) C.onSettingsUpdate((next) => { cur = next; render(next.quickLinks || []); });
})();
// ---- Extension panels on the strip (Sidebar) ---------------------------
// Two cards: native apps (Hermes + Pithos) are rendered into #sbNativeApps,
// every other left panel into #sbPanels. The native-id test matches a
// panel.id of the form "p:<addon>:<panel>" (what sidebar-left-panels
// returns), so hermes:main / pithos:main land in the native card.
(function () {
const nativeBox = document.getElementById("sbNativeApps");
const box = document.getElementById("sbPanels");
if (!box || !C.sidebarLeftPanels) return;
if ((!nativeBox && !box) || !C.sidebarLeftPanels) return;
const esc = (v) => String(v || "").replace(/[&<>"']/g, (c) => ({ "&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;" })[c]);
const NATIVE_IDS = new Set(["hermes", "pithos"]);
const addonOf = (pid) => String(pid || "").replace(/^p:/, "").split(":")[0];
const isNative = (p) => NATIVE_IDS.has(addonOf(p.id));
let cur = s;
function rowHtml(p, widths, B) {
const icon = /^data:image\//.test(p.icon) ? `<img src="${esc(p.icon)}" alt="" style="width:20px;height:20px">` : esc(p.icon || (p.title || "?").slice(0, 1));
return `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px 12px">
<div style="width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.06);display:grid;place-items:center;flex:none">${icon}</div>
<div style="flex:1;min-width:140px;${p.hidden ? "opacity:.5" : ""}"><div style="font-weight:600">${esc(p.title)}</div><div style="font-size:12px;color:var(--mut)">${p.hidden ? "Off the strip" : "On the strip"}${widths[p.id] ? " · " + Number(widths[p.id]) + " px" : ""}</div></div>
${B ? B.widthBtnHtml(widths[p.id], p.id) : ""}
${B ? B.onStripHtml(!p.hidden, p.id) : ""}</div>`;
}
async function render() {
let panels = []; try { panels = (await C.sidebarLeftPanels()) || []; } catch {}
const widths = cur.leftPanelWidths || {};
const B = window.__sbButtons;
if (!panels.length) { box.innerHTML = `<div class="pmuted" style="font-size:12.5px">No extension opens from the strip.</div>`; return; }
box.innerHTML = panels.map((p) => {
const icon = /^data:image\//.test(p.icon) ? `<img src="${esc(p.icon)}" alt="" style="width:20px;height:20px">` : esc(p.icon || (p.title || "?").slice(0, 1));
return `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:8px 12px">
<div style="width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.06);display:grid;place-items:center;flex:none">${icon}</div>
<div style="flex:1;min-width:140px;${p.hidden ? "opacity:.5" : ""}"><div style="font-weight:600">${esc(p.title)}</div><div style="font-size:12px;color:var(--mut)">${p.hidden ? "Off the strip" : "On the strip"}${widths[p.id] ? " · " + Number(widths[p.id]) + " px" : ""}</div></div>
${B ? B.widthBtnHtml(widths[p.id], p.id) : ""}
${B ? B.onStripHtml(!p.hidden, p.id) : ""}</div>`;
}).join("");
if (B) B.wireOnStrip(box);
if (window.__sbWireWidth) window.__sbWireWidth(box);
const native = panels.filter(isNative);
const rest = panels.filter((p) => !isNative(p));
if (nativeBox) {
nativeBox.innerHTML = native.length
? native.map((p) => rowHtml(p, widths, B)).join("")
: `<div class="pmuted" style="font-size:12.5px">Hermes and Pithos are not available.</div>`;
if (B) B.wireOnStrip(nativeBox);
if (window.__sbWireWidth) window.__sbWireWidth(nativeBox);
}
if (box) {
box.innerHTML = rest.length
? rest.map((p) => rowHtml(p, widths, B)).join("")
: `<div class="pmuted" style="font-size:12.5px">No extension opens from the strip.</div>`;
if (B) B.wireOnStrip(box);
if (window.__sbWireWidth) window.__sbWireWidth(box);
}
}
render();
document.addEventListener("section", (e) => { if (String(e.detail) === "sidebar") render(); });
@ -3048,10 +3158,26 @@
"session-proxy": "Can route the browser's traffic through a proxy it controls.",
"tab-control": "Can open, read and operate your web tabs for a program on this computer that you connect to it, on sites you approve one by one.",
};
// Extensions list ordering rules:
// HIDDEN_IDS — Hermes and Pithos are Theseus native apps; they appear in
// the Sidebar settings card instead of here.
// LAST_IDS — docx-editor and pdf-editor sort last within each group so
// the smaller utilities (Notepad, Screenshot, Translate, …)
// stay near the top of the list.
const ADDONS_HIDDEN_IDS = new Set(["hermes", "pithos"]);
const ADDONS_LAST_IDS = ["docx-editor", "pdf-editor"];
function addonSortLast(a, b) {
const ai = ADDONS_LAST_IDS.indexOf(a.id);
const bi = ADDONS_LAST_IDS.indexOf(b.id);
const ar = ai < 0 ? -1 : ai;
const br = bi < 0 ? -1 : bi;
return ar - br;
}
function renderAddons(snap) {
lastAddonSnap = snap;
// Plug-ins (Aegis, category "plugin") have their own cards under Plug-ins.
const items = ((snap && snap.installed) || []).filter((a) => a.category !== "plugin");
const items = ((snap && snap.installed) || [])
.filter((a) => a.category !== "plugin" && !ADDONS_HIDDEN_IDS.has(a.id));
if (!items.length) {
addonsList.innerHTML = '<div class="d" style="color:var(--dim)">No extensions installed. Install one from theseus.x/extensions, or drop a folder into the extensions directory.</div>';
return;
@ -3071,8 +3197,10 @@
+ '<button class="xmore" data-more="' + escapeAttr(a.id) + '" title="More options" aria-label="More options">⋯</button></span>'
+ '</div>';
};
const enabled = items.filter((a) => !a.error && a.enabled);
const disabled = items.filter((a) => !a.error && !a.enabled);
// Stable sort inside each bucket keeps registration order; the compare
// only moves ADDONS_LAST_IDS to the end of their bucket.
const enabled = items.filter((a) => !a.error && a.enabled).sort(addonSortLast);
const disabled = items.filter((a) => !a.error && !a.enabled).sort(addonSortLast);
const failed = items.filter((a) => a.error);
let html = "";
if (enabled.length) html += '<div class="xgrp">Enabled</div>' + enabled.map(rowFor).join("");