theseus-site/extensions/index.html
Local Dev 1ee9cc423e fix(theseus): address bar follows the page; tab strip stops blinking; extensions page
- Address bar: the chrome view keeps document.activeElement on the URL
  input after the user clicks into the page (focus moved to the tab's own
  view), and the "don't clobber typed text" guard then froze the bar until
  something blurred the field. The guard now requires real focus
  (document.hasFocus()), and the field is blurred when the view loses focus.
- Tab strip: rebuilt with innerHTML on every tabs event, which recreated
  every favicon <img> — a blink on the other tabs whenever one tab loaded,
  reloaded or changed title — and dropped drag state mid-gesture. Elements
  are now keyed by tab id (chips by group colour), updated in place, and
  moved into order; the strip is never rebuilt.
- Settings › Extensions links to theseus.x/extensions. That page now has a
  card per bundled add-on with the current signed version, tarball and
  hash read from each add-on's updates.json at load (it still claimed
  Screenshot 0.2.4 while the channel serves 0.6.5).
2026-09-20 14:39:07 +02:00

208 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>theseus.x — Extensions</title>
<meta name="description" content="Signed over-the-air update endpoint for Theseus's bundled add-ons. Each entry is Ed25519-signed by the Silent Mode operator; verified on every install without waiting for a Theseus release.">
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="/assets/favicon.svg" color="#d6ff3d">
<meta property="og:type" content="website">
<meta property="og:site_name" content="theseus.x">
<meta property="og:title" content="theseus.x — Extensions">
<meta property="og:description" content="Signed OTA update endpoint for Theseus's bundled add-ons. Ed25519, no cert authority, no app-store gatekeeper.">
<meta property="og:url" content="https://theseus.x/extensions/">
<style>
:root{
--bg:#0b0e14; --panel:#141a24; --panel2:#18202c; --line:rgba(255,255,255,.09);
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
--ok:#4fd1a5;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1100px 560px at 50% -12%,#16202e,var(--bg));
color:var(--ink);font:16px/1.65 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;min-height:100vh}
a{color:var(--acid)}
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem}
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);
border-bottom:1px solid var(--line)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:flex;align-items:center;gap:8px}
.topnav .brand .g{color:var(--acid)}
.topnav .brand svg{width:20px;height:20px}
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
.topnav a:hover{color:var(--ink);background:var(--panel)}
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
header.hero{text-align:center;padding:3.5rem 1.2rem 1rem}
h1{font-size:clamp(1.8rem,4.5vw,2.4rem);margin:.6rem 0 .3rem;letter-spacing:-.3px}
h1 .g{color:var(--acid)}
.tag{color:var(--mut);font-size:1.02rem;max-width:640px;margin:0 auto}
section{padding:2rem 0;border-top:1px solid var(--line);margin-top:1.6rem}
section:first-of-type{border-top:none;margin-top:1rem}
h2{font-size:1.35rem;margin:0 0 .5rem}
.lede{color:var(--mut);margin:0 0 1.2rem;max-width:720px}
code,.mono{background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-family:ui-monospace,monospace;font-size:13px}
pre{background:#0e131b;border:1px solid var(--line);border-radius:10px;padding:12px 14px;overflow-x:auto;
font-family:ui-monospace,monospace;font-size:12.5px;color:var(--ink);margin:.8rem 0}
.ext{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px;margin:1rem 0}
.ext h3{margin:0 0 .3rem;font-size:1.15rem;display:flex;align-items:center;gap:10px}
.ext h3 .icon{font-size:1.4rem}
.ext .cur{color:var(--acid);font-size:12px;font-weight:600;letter-spacing:.14em;text-transform:uppercase}
.ext .desc{color:var(--mut);font-size:14px;margin:.3rem 0 .8rem}
.ext .verifybox{margin-top:.9rem;padding-top:.8rem;border-top:1px solid var(--line);font-size:11.5px;color:var(--dim)}
.ext .verifybox .lbl{color:var(--mut);font-size:12px;margin-bottom:6px}
.ext .h{display:flex;gap:8px;flex-wrap:wrap;align-items:baseline;margin-top:4px}
.ext .h b{color:var(--mut);font-weight:600;min-width:70px}
.ext .h code{font-family:ui-monospace,monospace;font-size:10.5px;color:#c9d4e3;word-break:break-all;background:transparent;border:none;padding:0}
.ext .meta{color:var(--dim);font-size:12.5px;margin-top:.4rem}
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin:1.4rem 0}
.note b{color:var(--ink)}
.pubkey{background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;
font-family:ui-monospace,monospace;font-size:11px;color:#c9d4e3;word-break:break-all;margin:.6rem 0}
.flow{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:16px 20px;margin-top:1rem;color:var(--mut);font-size:14px}
.flow ol{margin:0;padding-left:20px}
.flow li{margin:.4rem 0}
.flow b{color:var(--ink)}
footer{color:var(--dim);font-size:12.5px;margin:3rem 0 2rem;text-align:center}
footer a{color:var(--mut)}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="/">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
<path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/>
<path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/>
<path d="M6 32 L32 27 L58 32 L32 37 Z" fill="#d6ff3d" opacity=".55"/>
<circle cx="32" cy="32" r="3" fill="#0b0e14"/>
</svg>
<span>theseus<span class="g">.x</span></span>
</a>
<a href="/">Home</a>
<a href="/#download">Download</a>
<a class="here" href="./">Extensions</a>
<a href="https://sirius.x/">sirius.x</a>
</nav>
<header class="hero">
<h1>Theseus <span class="g">Extensions</span></h1>
<p class="tag">Signed over-the-air update endpoint for Theseus's bundled add-ons.
Every entry is Ed25519-signed by the Silent Mode operator, verified on the
device before install. No cert authority, no app-store gatekeeper.</p>
</header>
<div class="wrap">
<section>
<h2>Available extensions</h2>
<p class="lede">What ships inside Theseus today. Versions and hashes on this page are read from each
add-on's signed <code>updates.json</code> when the page loads, so they match what the browser
would install. First-party components show up under <b>Settings › Plug-ins</b> in Theseus,
everything else under <b>Settings › Extensions</b>.</p>
<div class="ext" id="aegis" data-ext="aegis">
<h3><span class="icon">🛡️</span> Aegis <span class="cur" style="margin-left:auto">current <span data-live="version">0.6.21</span></span></h3>
<div class="desc">Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault, with
a dApp bridge for the pages you visit. Bundled; updates over this signed channel and through the
panel's own "Stage → Restart" flow.</div>
<div class="meta">Plug-in · Settings › Plug-ins · <span data-live="count"></span></div>
<div class="verifybox">
<div class="lbl">Signed update manifest — SHA-256 verified before install</div>
<div class="h"><b>updates.json</b><code><a href="./aegis/updates.json">theseus.x/extensions/aegis/updates.json</a></code></div>
<div class="h"><b>Tarball</b><code><a data-live="url" href="./aegis/aegis-0.6.21.tar.gz">aegis-0.6.21.tar.gz</a></code></div>
<div class="h"><b>sha256</b><code data-live="sha256">—</code></div>
</div>
</div>
<div class="ext" id="screenshot" data-ext="screenshot">
<h3><span class="icon">📸</span> Screenshot <span class="cur" style="margin-left:auto">current <span data-live="version">0.6.5</span></span></h3>
<div class="desc">Capture the current tab — visible viewport, entire scrollable page, or a rectangle
you draw. The capture opens in a full-tab editor: crop, annotate, line tool, resizable text,
redact, save.</div>
<div class="meta">Extension · Settings › Extensions · <span data-live="count"></span></div>
<div class="verifybox">
<div class="lbl">Signed update manifest — SHA-256 verified before install</div>
<div class="h"><b>updates.json</b><code><a href="./screenshot/updates.json">theseus.x/extensions/screenshot/updates.json</a></code></div>
<div class="h"><b>Tarball</b><code><a data-live="url" href="./screenshot/screenshot-0.6.5.tar.gz">screenshot-0.6.5.tar.gz</a></code></div>
<div class="h"><b>sha256</b><code data-live="sha256">87ae730d329ff9f552bbf513efba19bd14bb016bb90e24f7b62868434dadda60</code></div>
</div>
</div>
<div class="ext" id="notepad">
<h3><span class="icon">📝</span> Notepad <span class="cur" style="margin-left:auto">bundled 0.1.0</span></h3>
<div class="desc">Right-sidebar notes. Text lives on this machine only and autosaves as you type.</div>
<div class="meta">Extension · Settings › Extensions · no update channel yet — ships with each Theseus release.</div>
</div>
<div class="note"><b>Installing something else.</b> An extension is a folder with an <code>addon.json</code>.
Drop it into the extensions directory (Settings › Extensions › <i>Open extensions folder</i>), press
<i>Reload</i>, and it is live. Extensions run with full app access — treat one like an unsigned executable
and load only what you trust.</div>
</section>
<script>
// Fill each card from its signed manifest. Relative URLs keep this working on
// every origin the page is served from (navigate.st relay, bns:// in Theseus).
(async () => {
for (const card of document.querySelectorAll(".ext[data-ext]")) {
const id = card.dataset.ext;
try {
const r = await fetch("./" + id + "/updates.json", { cache: "no-store" });
if (!r.ok) continue;
const m = await r.json();
const list = (m.addons || []).slice().sort((x, y) => cmp(y.version, x.version));
const top = list[0]; if (!top) continue;
const set = (k, f) => card.querySelectorAll('[data-live="' + k + '"]').forEach(f);
set("version", (el) => { el.textContent = top.version; });
set("sha256", (el) => { el.textContent = top.sha256 || "—"; });
set("url", (el) => { el.href = top.url; el.textContent = String(top.url).split("/").pop(); });
set("count", (el) => { el.textContent = list.length + " signed version" + (list.length === 1 ? "" : "s") + " on the channel"; });
} catch {}
}
function cmp(a, b) { const x = String(a).split(".").map(Number), y = String(b).split(".").map(Number); for (let i = 0; i < Math.max(x.length, y.length); i++) { const d = (x[i] || 0) - (y[i] || 0); if (d) return d; } return 0; }
})();
</script>
<section>
<h2>How updates work</h2>
<p class="lede">Same mechanism a Firefox XPI update uses, cut down to what a name-native browser needs.</p>
<div class="flow">
<ol>
<li>Every 30 seconds after boot Theseus fetches <code>updates.json</code> for each installed add-on that declares an <code>updateURL</code> in its manifest.</li>
<li>Each entry carries an Ed25519 signature over <code>silentmode.addon-update-v1|&lt;id&gt;|&lt;version&gt;|&lt;sha256&gt;</code>. Theseus verifies against a small set of operator public keys baked into the build. Unsigned or badly-signed entries are dropped silently.</li>
<li>If a signed version beats what's installed, Theseus downloads the tarball, re-checks its sha256, and stages the new copy under <code>&lt;userData&gt;/addons-updates-staged/&lt;id&gt;-&lt;version&gt;/</code>.</li>
<li>On the next Theseus launch, <code>promoteStagedUpdates</code> moves the staged copy into <code>addons/&lt;id&gt;/</code>. Any prior copy is renamed to <code>addons-backups/&lt;id&gt;-&lt;oldver&gt;-&lt;timestamp&gt;/</code> so hand-edits survive.</li>
<li>Settings › Extensions has a <b>Check for updates</b> button and shows what is staged; first-party plug-ins (Aegis) can also stage and restart from their own panel.</li>
</ol>
</div>
</section>
<section>
<h2>Operator key</h2>
<p class="lede">The Ed25519 public key baked into Theseus 0.3.19+.
Rotation is by adding a new pubkey and shipping a Theseus release that carries both, then a follow-up release removing the old — during the overlap, entries can be signed by either.</p>
<div class="pubkey">732b1263a236b0030383a2376597cfa43c3624b3ca2912a46134f8f2a06e6012</div>
<p style="color:var(--dim);font-size:12.5px;margin-top:.6rem">Generated 2026-09-07. Held offline by the Silent Mode operator.</p>
</section>
<section>
<h2>Roadmap</h2>
<p class="lede">The endpoint is generic — any bundled add-on can advertise an <code>updateURL</code>.
Concrete next-steps:</p>
<ul style="color:var(--mut);font-size:14px;line-height:1.7">
<li>On-BCNR pubkey publication so rotation is auditable end-to-end.</li>
<li>Multi-sig (N-of-M operator keys) so a single compromised private key can't push code.</li>
<li>Extend the channel to <b>Notepad</b> (bundled, no channel yet).</li>
</ul>
</section>
<footer>
Silent Mode · <a href="https://code.silentmode.st/silentmode/theseus/src/branch/master/TheseusNavigator/docs/ADDON-UPDATES.md">source &amp; operator brief</a>
</footer>
</div>
</body>
</html>