Bundled screenshot addon bump:
- version 0.2.1 → 0.2.2
- palette grows from 5 to 8 colors: adds Orange (#ff9500), Blue
(#0a84ff), Purple (#bf5af2) alongside acid/red/yellow/white/black —
common annotation colors that were conspicuously missing
- updateURL swings from the aspirational addons.silentmode.st (which
never resolved) to the live gateway URL
https://navigate.st/bns/theseus.x/extensions/screenshot/updates.json,
where the operator's first signed update entry is now published
The gateway URL is deliberate over the bare `theseus.x/...` form: the
add-on updater runs from Node's main-process https module, which uses
the OS resolver. On installs without Ariadne's Thread the OS can't
resolve theseus.x (BNS-only TLD), so the poll would silently fail;
the navigate.st gateway resolves via standard DNS and forwards to the
same BNS-backed Sia content, so every install reaches the endpoint.
First signed update is live at:
https://navigate.st/bns/theseus.x/extensions/screenshot/updates.jsonhttps://navigate.st/bns/theseus.x/extensions/screenshot/screenshot-0.2.2.tar.gz
signed 59a35370fdbc9d1e24834fa26c7765d27e8763fe928bfa23b202ca666a6a6973
by the ops key baked into 0.3.19. End-to-end verified via
scratchpad/decoupling-test/verify-live.mjs against the live endpoint:
fetch, sig-verify, download, sha-verify, extract, stage, promote,
backup — all pass.
Installs polling the previous updateURL (addons.silentmode.st) get
this new URL only after their bundled copy is refreshed to 0.2.2,
which means either a Theseus release with 0.2.2 bundled (0.3.20+) or
a signed update at the old URL that carries the URL change (impossible
because addons.silentmode.st doesn't resolve). Ship a Theseus release
that bundles this 0.2.2 to activate the update path on existing
installs; from then on the endpoint self-perpetuates via the theseus.x
URL.
Decouples bundled-add-on updates from Theseus releases. An add-on
whose addon.json declares an updateURL can be republished at any time
without shipping a new Theseus installer; existing installs pick it up
on the next boot's +30 s background check.
Client flow (main-process only, no UI touchpoints in this commit):
initAddons()
├── promoteStagedUpdates() # promote signed stage if newer
├── seedBundledAddons() # bundle wins over on-disk if newer
└── AddonHost.discoverAndActivate()
30 s later:
└── checkAndStageUpdates() # fetch, verify, download, stage
Signature: Ed25519 over
"silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>",
verified against a hardcoded set of operator pubkeys living in
addon-update-pubkeys.js. Domain-separated so the operator key can't
be tricked into signing a message with a different purpose. Empty
pubkey array is the shipping default — checkAndStageUpdates() then
short-circuits and no outbound requests are made, which is the safe
posture until the operator ceremonies a key in.
Payload: gzipped tar, extracted with the system tar (present on
Win10 1803+, macOS, Linux). Path traversal defended by tar's default
refusal of `..` entries; the extracted manifest's id + version are
re-checked against the signed values before staging.
Staged updates go to <userData>/addons-updates-staged/<id>-<version>/.
Promotion into <userData>/addons/<id>/ reuses seedBundledAddons's
backup dance: existing folder moves to
<userData>/addons-backups/<id>-<oldver>-<timestamp>/ so any local
edits survive.
New files:
- addon-updater.js — client
- addon-update-pubkeys.js — hardcoded pubkeys (empty; edit + rebuild to rotate)
- scripts/generate-update-keypair.mjs — one-time keygen
- scripts/sign-addon-update.mjs — operator packager+signer
- docs/ADDON-UPDATES.md — operator brief + threat model
Wired into main.js at boot; screenshot add-on's addon.json advertises
the reference updateURL for when the endpoint goes live.
seedBundledAddons() only copied a bundled add-on when the target folder
was missing, so an updated bundled add-on never landed on any machine
that had ever run Theseus before — the 0.3.14 shipped screenshot editor
would sit in resources/ and be ignored by every dev machine with an
older screenshot/ folder from a previous test.
Compare the bundled addon.json version to the user's on-disk version.
On mismatch, rename the user copy to
<userData>/addons-backups/<id>-<oldver>-<stamp>/ and cp the fresh
bundle in. Backups live outside addonsDir so AddonHost's folder scan
doesn't pick them up as duplicate add-ons under the same manifest id.
Bump screenshot 0.2.0 -> 0.2.1 so the first build carrying this fix
actually reseeds the shipped-0.3.14 editor on existing dev copies.
Users who genuinely fork a bundled add-on should bump their local
version to something different from the bundled one — that keeps them
pinned. Users who edit files without bumping accept upstream updates,
with the timestamped backup as safety net.
Reworks the screenshot addon into the flow the user asked for: the
dock icon opens a small dropdown menu (Visible viewport / Full page /
Region…) instead of the sidebar picker, and each capture opens a
full browser tab hosting an editor.
Two new addon-host capabilities land alongside:
- toolbar-menu: the addon declares an icon + item list in its manifest;
the chrome dock renders a button that, on click, opens a small menu
and dispatches the selection to the addon via addon-menu-select IPC.
- open-tab: api.openTab(path) opens a browser tab whose URL is the
addon's local file. Origin-gated per addon; the editor uses a
dedicated addon-tab-preload for its main → renderer bridge.
Editor page (editor.html/js/css):
- Crop, arrow, rectangle, circle, freehand pen, text, blur
- Colour swatches (red / yellow / acid / white / black), 3 stroke widths
- Undo/redo command stack, zoom controls
- Save PNG (goes through the download pipeline, chip picks it up)
- Copy to clipboard via ClipboardItem
New capture-tab capability on the addon-host, and the screenshot add-on
uses it to expose three modes in a sidebar launcher panel:
- Visible viewport: Electron's WebContents.capturePage() on the active tab
- Full scrollable page: temp-resize the tab view to document.scrollHeight,
capturePage, restore
- Region: preload overlays a translucent selection div, tracks mousedown /
move / up, sends the rect back; main takes the visible capture and
crops via nativeImage.crop({x,y,width,height})
Saves land in the user's Downloads folder via session.downloadURL — same
pipeline as any file download, so the download chip picks them up.
Filename: theseus-screenshot-<host>-<ISO date>.png. JPEG option for
smaller files.
A follow-up task (task_b9608dc6) reworks this to open captures in a
full-tab editor with crop / draw / annotate / undo / copy-to-clipboard
instead of the current bare launcher.