feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
<!doctype html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
|
<title>Screenshot</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
:root { color-scheme: light dark;
|
|
|
|
|
|
--bg:#0e131c; --panel:#141a24; --line:rgba(255,255,255,.09);
|
|
|
|
|
|
--ink:#e7eaf1; --mut:#8b98a9; --dim:#5e6678; --acid:#d6ff3d;
|
|
|
|
|
|
--btn:#1c2432; --btn-h:#242e40; --err:#ff9081; }
|
|
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
|
|
|
|
:root { --bg:#f8faff; --panel:#ffffff; --line:rgba(0,0,0,.10);
|
|
|
|
|
|
--ink:#1a1f2b; --mut:#5c6577; --dim:#8a93a5;
|
|
|
|
|
|
--btn:#f0f3fa; --btn-h:#e3e8f2; --acid:#088A66; --err:#c81f0f; }
|
|
|
|
|
|
}
|
|
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
|
|
html, body { margin: 0; height: 100%; }
|
|
|
|
|
|
body { background: var(--bg); color: var(--ink);
|
|
|
|
|
|
font: 13px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
|
|
|
|
|
display: flex; flex-direction: column; }
|
2026-09-09 22:01:06 +02:00
|
|
|
|
header { display: flex; align-items: center;
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
padding: 10px 14px; border-bottom: 1px solid var(--line);
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
background: var(--panel); gap: 8px; }
|
|
|
|
|
|
header .t { font-weight: 600; display: flex; gap: 8px; align-items: center; min-width: 0; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
header .t .em { font-size: 15px; }
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
header .m { color: var(--dim); font-size: 11.5px; min-height: 15px; flex: 1; text-align: right; }
|
2026-09-09 22:01:06 +02:00
|
|
|
|
/* Force the sound / max / close cluster to hug the right edge no matter
|
|
|
|
|
|
what else fills the header. justify-content on the parent kept nudging
|
|
|
|
|
|
them mid-row when the status text was empty. */
|
|
|
|
|
|
header #btn-sound { margin-left: auto; }
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
header .max {
|
|
|
|
|
|
border: 1px solid var(--line); border-radius: 6px; background: var(--btn);
|
|
|
|
|
|
color: var(--ink); cursor: pointer; padding: 4px 6px; font: inherit; line-height: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
header .max:hover { background: var(--btn-h); }
|
|
|
|
|
|
header .max svg { width: 14px; height: 14px; display: block; }
|
2026-09-09 10:55:21 +02:00
|
|
|
|
header .max.close:hover { border-color: rgba(255,91,91,.55); color: var(--err); }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
main { flex: 1; overflow-y: auto; padding: 12px 14px; display: flex; flex-direction: column; gap: 12px; }
|
|
|
|
|
|
.modes { display: flex; gap: 6px; }
|
|
|
|
|
|
button.mode {
|
|
|
|
|
|
flex: 1; min-width: 0;
|
|
|
|
|
|
padding: 10px 6px; border: 1px solid var(--line); border-radius: 8px;
|
|
|
|
|
|
background: var(--btn); color: var(--ink); cursor: pointer;
|
|
|
|
|
|
font: inherit; text-align: center;
|
|
|
|
|
|
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
|
|
|
|
|
transition: background 120ms;
|
|
|
|
|
|
}
|
|
|
|
|
|
button.mode:hover:not(:disabled) { background: var(--btn-h); }
|
|
|
|
|
|
button.mode:disabled { opacity: .5; cursor: default; }
|
|
|
|
|
|
button.mode .em { font-size: 18px; line-height: 1; }
|
|
|
|
|
|
button.mode .lbl { font-size: 11.5px; }
|
|
|
|
|
|
.prev-wrap {
|
|
|
|
|
|
border: 1px dashed var(--line); border-radius: 8px;
|
|
|
|
|
|
background:
|
|
|
|
|
|
repeating-conic-gradient(rgba(255,255,255,.02) 0% 25%, transparent 0% 50%)
|
|
|
|
|
|
50%/16px 16px;
|
|
|
|
|
|
padding: 6px; min-height: 120px;
|
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
|
color: var(--dim); font-size: 12px; text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
.prev-wrap img {
|
|
|
|
|
|
max-width: 100%; max-height: 320px; border-radius: 4px; display: block;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
.meta { color: var(--dim); font-size: 11.5px; display: flex; justify-content: space-between; gap: 8px; }
|
|
|
|
|
|
.meta .host { color: var(--mut); }
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
.actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
|
|
|
|
|
.actions .row { display: flex; gap: 6px; flex: 1 1 100%; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
button.act {
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
flex: 1; padding: 9px 8px; border: 1px solid var(--line); border-radius: 8px;
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
background: var(--btn); color: var(--ink); cursor: pointer; font: inherit;
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
|
|
|
|
line-height: 1;
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
}
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
button.act svg { width: 14px; height: 14px; display: block; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
button.act.primary { background: var(--acid); color: #101418; border-color: transparent; font-weight: 600; }
|
|
|
|
|
|
button.act:hover:not(:disabled) { background: var(--btn-h); }
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
button.act.primary:hover:not(:disabled) { filter: brightness(1.05); background: var(--acid); }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
button.act:disabled { opacity: .45; cursor: default; }
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
.confirm {
|
|
|
|
|
|
display: none; gap: 6px; align-items: center;
|
|
|
|
|
|
padding: 8px 10px; border: 1px solid rgba(255,91,91,.40); border-radius: 8px;
|
|
|
|
|
|
background: rgba(255,91,91,.06); font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.confirm.on { display: flex; }
|
|
|
|
|
|
.confirm .msg { flex: 1; color: var(--mut); }
|
|
|
|
|
|
.confirm button {
|
|
|
|
|
|
padding: 5px 10px; border: 1px solid var(--line); border-radius: 6px;
|
|
|
|
|
|
background: var(--btn); color: var(--ink); cursor: pointer; font: inherit;
|
|
|
|
|
|
}
|
|
|
|
|
|
.confirm button.yes { background: #ff5b5b; color: #101418; border-color: transparent; font-weight: 600; }
|
|
|
|
|
|
.confirm button:hover { background: var(--btn-h); }
|
|
|
|
|
|
.confirm button.yes:hover { filter: brightness(1.05); background: #ff5b5b; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
.status { color: var(--mut); font-size: 12px; min-height: 16px; }
|
|
|
|
|
|
.status.err { color: var(--err); }
|
|
|
|
|
|
.status.ok { color: var(--acid); }
|
|
|
|
|
|
.recent { display: none; }
|
|
|
|
|
|
.recent.on { display: block; }
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
.recent .lbl { color: var(--dim); font-size: 11.5px; text-transform: uppercase; letter-spacing: .5px; margin: 4px 0 6px; display: flex; justify-content: space-between; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
.recent .strip { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 4px; }
|
|
|
|
|
|
.recent .tile {
|
|
|
|
|
|
flex: 0 0 auto; width: 78px; height: 52px; border-radius: 4px;
|
|
|
|
|
|
border: 1px solid var(--line); background: var(--btn);
|
|
|
|
|
|
display: flex; align-items: center; justify-content: center;
|
|
|
|
|
|
cursor: pointer; overflow: hidden; position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
.recent .tile img { width: 100%; height: 100%; object-fit: cover; background: #fff; }
|
|
|
|
|
|
.recent .tile:hover { border-color: var(--acid); }
|
2026-09-09 22:01:06 +02:00
|
|
|
|
.recent .tile .tdel {
|
|
|
|
|
|
position: absolute; top: 2px; right: 2px;
|
|
|
|
|
|
width: 18px; height: 18px; line-height: 16px; text-align: center;
|
|
|
|
|
|
background: rgba(11,14,20,.85); color: #fff; border-radius: 50%;
|
|
|
|
|
|
font-size: 14px; font-weight: 700; cursor: pointer;
|
|
|
|
|
|
opacity: 0; transition: opacity 120ms, background 120ms, color 120ms;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
.recent .tile:hover .tdel { opacity: 1; }
|
|
|
|
|
|
.recent .tile .tdel:hover { background: #ff5b5b; color: #101418; }
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
.recent .clear { background: none; border: 0; color: var(--dim); font: inherit; cursor: pointer; text-transform: none; letter-spacing: 0; }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
.recent .clear:hover { color: var(--err); }
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<div class="t"><span class="em">📸</span> <span>Screenshot</span></div>
|
|
|
|
|
|
<div class="m" id="hdr-status"></div>
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
<button class="max" id="btn-sound" title="Toggle sound" aria-label="Toggle sound"></button>
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
<button class="max" id="btn-max" title="Expand the sidebar to full window width" aria-label="Expand sidebar">
|
|
|
|
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
|
|
|
|
<path d="M2 6V2h4M14 6V2h-4M2 10v4h4M14 10v4h-4"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
2026-09-09 10:55:21 +02:00
|
|
|
|
<button class="max close" id="btn-close" title="Close the sidebar" aria-label="Close sidebar">
|
|
|
|
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
|
|
|
|
|
<path d="M4 4l8 8M12 4l-8 8"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
</header>
|
|
|
|
|
|
<main>
|
|
|
|
|
|
<div class="modes">
|
|
|
|
|
|
<button class="mode" data-mode="visible" title="Capture the visible part of the current tab">
|
|
|
|
|
|
<span class="em">🖼️</span><span class="lbl">Visible</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="mode" data-mode="full" title="Capture the entire scrollable page">
|
|
|
|
|
|
<span class="em">📄</span><span class="lbl">Full page</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="mode" data-mode="region" title="Drag a rectangle on the page to select what to capture">
|
|
|
|
|
|
<span class="em">✂️</span><span class="lbl">Region</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="prev-wrap" id="prev-wrap">
|
|
|
|
|
|
<span id="prev-empty">No capture yet. Pick a mode above.</span>
|
|
|
|
|
|
<img id="prev-img" alt="" hidden>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="meta">
|
|
|
|
|
|
<span id="meta-size">—</span>
|
|
|
|
|
|
<span class="host" id="meta-host"></span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="actions">
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="act" id="btn-copy" disabled title="Copy the raw capture to the clipboard">
|
|
|
|
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="4" width="9" height="10"/><path d="M3 12V3h9"/></svg>
|
|
|
|
|
|
<span>Copy</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="act" id="btn-save" disabled title="Save the raw capture straight to Downloads">
|
|
|
|
|
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M8 2v9M4 7l4 4 4-4M2 14h12"/></svg>
|
|
|
|
|
|
<span>Save</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<button class="act" id="btn-discard" disabled>Discard</button>
|
|
|
|
|
|
<button class="act primary" id="btn-edit" disabled title="Open the annotate editor — arrow, rect, ellipse, pen, text, undo, save, copy">Edit</button>
|
|
|
|
|
|
</div>
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="status" id="status"></div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="recent" id="recent">
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
<div class="lbl"><span>Recent</span>
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
<button class="clear" id="clear-recent" title="Delete every past capture">clear all</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="strip" id="recent-strip"></div>
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
<div class="confirm" id="clear-confirm">
|
|
|
|
|
|
<span class="msg">Delete every past capture?</span>
|
|
|
|
|
|
<button id="clear-no">Cancel</button>
|
|
|
|
|
|
<button class="yes" id="clear-yes">Delete</button>
|
|
|
|
|
|
</div>
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
</div>
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
<a id="download-link" style="display:none"></a>
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
|
|
const previewImg = $("prev-img");
|
|
|
|
|
|
const previewEmpty = $("prev-empty");
|
|
|
|
|
|
const status = $("status");
|
|
|
|
|
|
const metaSize = $("meta-size");
|
|
|
|
|
|
const metaHost = $("meta-host");
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
const btnEdit = $("btn-edit");
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
const btnDiscard = $("btn-discard");
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
const btnCopy = $("btn-copy");
|
|
|
|
|
|
const btnSave = $("btn-save");
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
const btnMax = $("btn-max");
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
const btnSound = $("btn-sound");
|
|
|
|
|
|
const dl = $("download-link");
|
|
|
|
|
|
|
|
|
|
|
|
// ---- audio ----------------------------------------------------------
|
|
|
|
|
|
// Synthesized in-page so the tarball stays small — no wav shipped.
|
|
|
|
|
|
// Preference is stored under silentmode.storage so the editor sees the
|
|
|
|
|
|
// same setting when it loads (default on).
|
|
|
|
|
|
let soundOn = true;
|
|
|
|
|
|
let audioCtx = null;
|
|
|
|
|
|
function actx() {
|
|
|
|
|
|
if (!audioCtx) {
|
|
|
|
|
|
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch {}
|
|
|
|
|
|
}
|
|
|
|
|
|
// A user gesture is needed to resume in some Chromium setups; the
|
|
|
|
|
|
// toggle button click satisfies that.
|
|
|
|
|
|
if (audioCtx && audioCtx.state === "suspended") { audioCtx.resume().catch(() => {}); }
|
|
|
|
|
|
return audioCtx;
|
|
|
|
|
|
}
|
|
|
|
|
|
function tone(freq, dur, type, vol, at) {
|
|
|
|
|
|
if (!soundOn) return;
|
|
|
|
|
|
const c = actx(); if (!c) return;
|
|
|
|
|
|
const t0 = c.currentTime + (at || 0);
|
|
|
|
|
|
const osc = c.createOscillator();
|
|
|
|
|
|
const g = c.createGain();
|
|
|
|
|
|
osc.type = type || "sine";
|
|
|
|
|
|
osc.frequency.setValueAtTime(freq, t0);
|
|
|
|
|
|
g.gain.setValueAtTime(vol || 0.15, t0);
|
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
|
|
|
|
|
osc.connect(g); g.connect(c.destination);
|
|
|
|
|
|
osc.start(t0);
|
|
|
|
|
|
osc.stop(t0 + dur + 0.02);
|
|
|
|
|
|
}
|
2026-09-09 22:34:47 +02:00
|
|
|
|
// Filtered noise burst — camera clicks, printer thwacks, and (with
|
|
|
|
|
|
// texture=true) the crackle of crumpled paper. `bp` = band-pass centre
|
|
|
|
|
|
// freq, `q` = sharpness of the band.
|
|
|
|
|
|
function noise(dur, vol, bp, q, at, texture) {
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
if (!soundOn) return;
|
|
|
|
|
|
const c = actx(); if (!c) return;
|
|
|
|
|
|
const t0 = c.currentTime + (at || 0);
|
|
|
|
|
|
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
|
|
|
|
|
const buf = c.createBuffer(1, n, c.sampleRate);
|
|
|
|
|
|
const d = buf.getChannelData(0);
|
2026-09-09 22:34:47 +02:00
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
|
|
let s = Math.random() * 2 - 1;
|
|
|
|
|
|
if (texture) s *= 0.4 + Math.random() * 0.6;
|
|
|
|
|
|
d[i] = s;
|
|
|
|
|
|
}
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
const src = c.createBufferSource(); src.buffer = buf;
|
|
|
|
|
|
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
|
|
|
|
|
flt.frequency.setValueAtTime(bp || 2000, t0);
|
|
|
|
|
|
flt.Q.setValueAtTime(q || 4, t0);
|
|
|
|
|
|
const g = c.createGain();
|
|
|
|
|
|
g.gain.setValueAtTime(vol || 0.2, t0);
|
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
|
|
|
|
|
src.connect(flt); flt.connect(g); g.connect(c.destination);
|
|
|
|
|
|
src.start(t0);
|
|
|
|
|
|
src.stop(t0 + dur + 0.02);
|
|
|
|
|
|
}
|
2026-09-09 22:34:47 +02:00
|
|
|
|
// Sweeping band-pass noise — used as the film-advance whir tail of the
|
|
|
|
|
|
// Polaroid shutter.
|
|
|
|
|
|
function sweep(dur, vol, fromHz, toHz, q, at) {
|
|
|
|
|
|
if (!soundOn) return;
|
|
|
|
|
|
const c = actx(); if (!c) return;
|
|
|
|
|
|
const t0 = c.currentTime + (at || 0);
|
|
|
|
|
|
const n = Math.max(1, Math.floor(c.sampleRate * dur));
|
|
|
|
|
|
const buf = c.createBuffer(1, n, c.sampleRate);
|
|
|
|
|
|
const d = buf.getChannelData(0);
|
|
|
|
|
|
for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1;
|
|
|
|
|
|
const src = c.createBufferSource(); src.buffer = buf;
|
|
|
|
|
|
const flt = c.createBiquadFilter(); flt.type = "bandpass";
|
|
|
|
|
|
flt.frequency.setValueAtTime(fromHz, t0);
|
|
|
|
|
|
flt.frequency.exponentialRampToValueAtTime(toHz, t0 + dur);
|
|
|
|
|
|
flt.Q.setValueAtTime(q || 8, t0);
|
|
|
|
|
|
const g = c.createGain();
|
|
|
|
|
|
g.gain.setValueAtTime(0.0001, t0);
|
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(vol, t0 + dur * 0.2);
|
|
|
|
|
|
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
|
|
|
|
|
|
src.connect(flt); flt.connect(g); g.connect(c.destination);
|
|
|
|
|
|
src.start(t0);
|
|
|
|
|
|
src.stop(t0 + dur + 0.02);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Polaroid: mirror + curtain click, then a film-advance whir.
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
function playShutter() {
|
2026-09-09 22:34:47 +02:00
|
|
|
|
noise(0.02, 0.32, 5200, 8, 0);
|
|
|
|
|
|
tone(160, 0.03, "square", 0.14, 0.003);
|
|
|
|
|
|
noise(0.03, 0.24, 3200, 5, 0.03);
|
|
|
|
|
|
tone(120, 0.04, "square", 0.10, 0.035);
|
|
|
|
|
|
sweep(0.28, 0.10, 900, 400, 12, 0.07);
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
}
|
2026-09-09 22:34:47 +02:00
|
|
|
|
// Printer chika-chika-chika — 3 descending percussive bursts.
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
function playCopy() {
|
2026-09-09 22:34:47 +02:00
|
|
|
|
noise(0.028, 0.22, 3800, 9, 0.00);
|
|
|
|
|
|
tone(1200, 0.03, "sine", 0.10, 0.00);
|
|
|
|
|
|
noise(0.028, 0.22, 3200, 9, 0.06);
|
|
|
|
|
|
tone(1000, 0.03, "sine", 0.10, 0.06);
|
|
|
|
|
|
noise(0.028, 0.22, 2600, 9, 0.12);
|
|
|
|
|
|
tone(800, 0.03, "sine", 0.10, 0.12);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Paper crumple — long band-limited noise with crackle texture and a
|
|
|
|
|
|
// descending centre frequency.
|
|
|
|
|
|
function playDiscard() {
|
|
|
|
|
|
noise(0.16, 0.28, 2600, 3, 0.00, true);
|
|
|
|
|
|
noise(0.12, 0.22, 1800, 3, 0.06, true);
|
|
|
|
|
|
noise(0.08, 0.16, 1200, 3, 0.12, true);
|
feat(theseus/screenshot): 0.6.0 — crop + mosaic redaction, right-anchored sidebar controls, real shutter+print sounds
Editor:
- Crop tool restored — drag to select, marquee sits with a dashed acid
border and a dimmed backdrop for the area you'll discard, then the
topbar shows Apply crop / Cancel. Applying trims #base to the rect,
resets undo (dimensions changed), and drops back into the select tool.
Enter / Esc keyboard shortcuts while a crop is pending.
- Blur / mosaic redaction tool back — drag a rectangle, editor
downsamples that region of #base to ~12-block granularity and paints
the blocks back nearest-neighbour. Commits directly (no confirm step).
- Sidebar-window controls (Back, name, Copy, Save, Sound, Maximize)
reflow: Back + name on the left, Copy + Save + Sound + Maximize on
the right so the "put the sidebar back to normal size" affordance
lives where users expect it. Toolbar's drawing tools stay centred.
- Back arrow icon swapped from a chevron to a proper flat arrow
(line + arrowhead), matching the new browser back/forward glyphs.
Sounds — modeled on Firefox Screenshots' feedback rather than beeps:
- Shutter is now a real photoshoot click: two mirror-slaps built from a
band-passed noise burst (metallic ping) plus a very short square-wave
thud each. Sounds like a camera, not a beep.
- Copy is a two-chirp "printer feed" — filtered noise burst on top of a
sine chirp per beat, staccato ascending pair. Same shape Firefox Easy
Screenshot uses for "copied to clipboard".
- Save keeps its ascending triad; Discard keeps its descending pair;
new small ascending pair for Apply crop.
Chrome:
- Browser Back / Forward chevrons (M10 3 L5 8 L10 13 — two segments
meeting at a point, no shaft) replaced with straight-arrow glyphs
(line + arrowhead). Reads as a navigation arrow, not an angle bracket.
Bundled but not shipped separately — parent session OTA-signs and pushes.
2026-09-09 10:24:03 +02:00
|
|
|
|
}
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
function paintSound() {
|
|
|
|
|
|
btnSound.title = soundOn ? "Sounds on — click to mute" : "Sounds off — click to enable";
|
|
|
|
|
|
btnSound.innerHTML = soundOn
|
|
|
|
|
|
? '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6v4h3l3.5 3V3L5.5 6z"/><path d="M11 5.5c1 .8 1 4.2 0 5"/><path d="M12.5 4c2 1.5 2 6.5 0 8"/></svg>'
|
|
|
|
|
|
: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 6v4h3l3.5 3V3L5.5 6z"/><path d="M11 5l4 6M15 5l-4 6"/></svg>';
|
|
|
|
|
|
}
|
|
|
|
|
|
(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const v = await window.silentmode.storage.get("soundOn", true);
|
|
|
|
|
|
soundOn = v !== false; // default on
|
|
|
|
|
|
} catch {}
|
|
|
|
|
|
paintSound();
|
|
|
|
|
|
})();
|
|
|
|
|
|
btnSound.addEventListener("click", async () => {
|
|
|
|
|
|
soundOn = !soundOn;
|
|
|
|
|
|
paintSound();
|
|
|
|
|
|
try { await window.silentmode.storage.set("soundOn", soundOn); } catch {}
|
|
|
|
|
|
if (soundOn) playShutter(); // audible confirmation
|
|
|
|
|
|
});
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
const hdrStatus = $("hdr-status");
|
|
|
|
|
|
const recentBox = $("recent");
|
|
|
|
|
|
const recentStrip = $("recent-strip");
|
|
|
|
|
|
const clearBtn = $("clear-recent");
|
|
|
|
|
|
|
|
|
|
|
|
let last = null; // { name, dataUrl, width, height, host, bytes, mode }
|
|
|
|
|
|
let busy = false;
|
|
|
|
|
|
|
|
|
|
|
|
const fmt = (n) => n >= 1e6 ? (n / 1e6).toFixed(1) + " MB"
|
|
|
|
|
|
: n >= 1e3 ? (n / 1e3).toFixed(0) + " KB"
|
|
|
|
|
|
: n + " B";
|
|
|
|
|
|
|
|
|
|
|
|
function setStatus(text, cls = "") {
|
|
|
|
|
|
status.textContent = text || "";
|
|
|
|
|
|
status.className = "status" + (cls ? " " + cls : "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderPreview() {
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
const on = !!(last && last.dataUrl);
|
|
|
|
|
|
previewImg.hidden = !on;
|
|
|
|
|
|
previewImg.src = on ? last.dataUrl : "";
|
|
|
|
|
|
previewEmpty.hidden = on;
|
|
|
|
|
|
metaSize.textContent = on ? `${last.width}×${last.height} · ${fmt(last.bytes || 0)}` : "—";
|
|
|
|
|
|
metaHost.textContent = on ? (last.host || "") : "";
|
|
|
|
|
|
btnEdit.disabled = !on;
|
|
|
|
|
|
btnDiscard.disabled = !on;
|
|
|
|
|
|
btnCopy.disabled = !on;
|
|
|
|
|
|
btnSave.disabled = !on;
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshRecent() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const list = await window.silentmode.invoke("listRecent", {});
|
|
|
|
|
|
if (!Array.isArray(list) || list.length === 0) {
|
|
|
|
|
|
recentBox.classList.remove("on");
|
|
|
|
|
|
recentStrip.innerHTML = "";
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
recentBox.classList.add("on");
|
2026-09-09 22:01:06 +02:00
|
|
|
|
recentStrip.innerHTML = list.map((r) => {
|
|
|
|
|
|
const nameAttr = r.name.replace(/"/g, """);
|
|
|
|
|
|
return `<div class="tile" data-name="${nameAttr}" title="${nameAttr} · ${fmt(r.bytes || 0)}">`
|
|
|
|
|
|
+ `<span class="tdel" data-del="${nameAttr}" title="Delete this capture">×</span>`
|
|
|
|
|
|
+ `</div>`;
|
|
|
|
|
|
}).join("");
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
for (const tile of recentStrip.querySelectorAll(".tile")) {
|
|
|
|
|
|
const name = tile.dataset.name;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const b = await window.silentmode.invoke("getBytes", { name });
|
2026-09-09 22:01:06 +02:00
|
|
|
|
if (b && b.dataUrl) {
|
|
|
|
|
|
// Keep the delete button; drop it back on top of the image.
|
|
|
|
|
|
const del = tile.querySelector(".tdel");
|
|
|
|
|
|
tile.innerHTML = `<img src="${b.dataUrl}" alt="">`;
|
|
|
|
|
|
if (del) tile.appendChild(del);
|
|
|
|
|
|
}
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
} catch {}
|
2026-09-09 22:01:06 +02:00
|
|
|
|
tile.addEventListener("click", async (ev) => {
|
|
|
|
|
|
// The per-tile X sits inside the tile; don't count its click as
|
|
|
|
|
|
// a tile click.
|
|
|
|
|
|
if (ev.target && ev.target.classList.contains("tdel")) return;
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
try {
|
|
|
|
|
|
const b = await window.silentmode.invoke("getBytes", { name });
|
|
|
|
|
|
if (!b) return;
|
|
|
|
|
|
last = { name: b.name, dataUrl: b.dataUrl, width: 0, height: 0, host: "", bytes: b.bytes };
|
|
|
|
|
|
const im = new Image();
|
|
|
|
|
|
im.onload = () => { last.width = im.naturalWidth; last.height = im.naturalHeight; renderPreview(); };
|
|
|
|
|
|
im.src = b.dataUrl;
|
|
|
|
|
|
renderPreview();
|
|
|
|
|
|
setStatus(`Loaded ${b.name}.`, "ok");
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
setStatus("Couldn't load: " + (e && e.message || e), "err");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-09-09 22:01:06 +02:00
|
|
|
|
// Per-tile delete — no confirmation strip (single-item destroy is
|
|
|
|
|
|
// small enough to be safe with just the X; the sweeping "clear all"
|
|
|
|
|
|
// still gates on the inline red confirmation).
|
|
|
|
|
|
for (const del of recentStrip.querySelectorAll(".tdel")) {
|
|
|
|
|
|
del.addEventListener("click", async (ev) => {
|
|
|
|
|
|
ev.stopPropagation();
|
|
|
|
|
|
const nm = del.dataset.del;
|
|
|
|
|
|
try {
|
|
|
|
|
|
await window.silentmode.invoke("clearRecent", { name: nm });
|
|
|
|
|
|
if (last && last.name === nm) { last = null; renderPreview(); }
|
|
|
|
|
|
await refreshRecent();
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
setStatus("Delete failed: " + (e && e.message || e), "err");
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("refreshRecent failed:", e);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function doCapture(mode) {
|
|
|
|
|
|
if (busy) return;
|
|
|
|
|
|
busy = true;
|
|
|
|
|
|
hdrStatus.textContent = "capturing…";
|
|
|
|
|
|
setStatus("");
|
|
|
|
|
|
if (mode === "region") setStatus("Drag on the page. Esc to cancel.");
|
|
|
|
|
|
else if (mode === "full") setStatus("Rendering full page — this can take a moment.");
|
|
|
|
|
|
for (const b of document.querySelectorAll("button.mode")) b.disabled = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await window.silentmode.invoke("capture", { mode });
|
|
|
|
|
|
if (res && res.cancelled) {
|
|
|
|
|
|
setStatus("Region capture cancelled.");
|
|
|
|
|
|
} else if (res && res.dataUrl) {
|
|
|
|
|
|
last = res;
|
|
|
|
|
|
renderPreview();
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
playShutter();
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
setStatus("Captured. Click Edit to annotate.", "ok");
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
refreshRecent();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setStatus("Nothing captured.", "err");
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("capture failed:", e);
|
|
|
|
|
|
setStatus(String(e && e.message || e), "err");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
busy = false;
|
|
|
|
|
|
hdrStatus.textContent = "";
|
|
|
|
|
|
for (const b of document.querySelectorAll("button.mode")) b.disabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
async function doEdit() {
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
if (!last || !last.name || busy) return;
|
|
|
|
|
|
// No IPC handshake — the editor pulls bytes on load via silentmode
|
|
|
|
|
|
// .invoke("getBytes", {name}) directly. That's idempotent, so a refresh
|
|
|
|
|
|
// or back-and-forth navigation always shows the image.
|
|
|
|
|
|
location.href = "editor.html?name=" + encodeURIComponent(last.name);
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
async function toggleMax() {
|
|
|
|
|
|
try { await window.silentmode.sidebar.toggleMax(); }
|
|
|
|
|
|
catch (e) { console.warn("toggleMax failed:", e); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
// Convert a base64 data URL into a Blob without a fetch round-trip.
|
|
|
|
|
|
function dataUrlToBlob(dataUrl) {
|
|
|
|
|
|
const m = /^data:([^;,]+);base64,(.+)$/.exec(String(dataUrl || ""));
|
|
|
|
|
|
if (!m) throw new Error("expected base64 data URL");
|
|
|
|
|
|
const bin = atob(m[2]);
|
|
|
|
|
|
const buf = new Uint8Array(bin.length);
|
|
|
|
|
|
for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
|
|
|
|
|
|
return new Blob([buf], { type: m[1] });
|
|
|
|
|
|
}
|
|
|
|
|
|
async function doCopy() {
|
|
|
|
|
|
if (!last || !last.dataUrl || busy) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const blob = dataUrlToBlob(last.dataUrl);
|
|
|
|
|
|
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
|
|
|
|
|
playCopy();
|
|
|
|
|
|
setStatus("Copied to clipboard.", "ok");
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("copy failed:", e);
|
|
|
|
|
|
setStatus("Copy failed: " + (e && e.message || e), "err");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
async function doSave() {
|
|
|
|
|
|
if (!last || !last.dataUrl || busy) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const blob = dataUrlToBlob(last.dataUrl);
|
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
|
dl.href = url;
|
|
|
|
|
|
dl.download = last.name || "screenshot.png";
|
|
|
|
|
|
dl.click();
|
|
|
|
|
|
setTimeout(() => URL.revokeObjectURL(url), 5000);
|
|
|
|
|
|
setStatus(`Saved ${dl.download} (${(blob.size / 1024).toFixed(1)} KB)`, "ok");
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn("save failed:", e);
|
|
|
|
|
|
setStatus("Save failed: " + (e && e.message || e), "err");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
for (const b of document.querySelectorAll("button.mode")) {
|
|
|
|
|
|
b.addEventListener("click", () => doCapture(b.dataset.mode));
|
|
|
|
|
|
}
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
btnEdit.addEventListener("click", doEdit);
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
btnDiscard.addEventListener("click", () => { playDiscard(); last = null; renderPreview(); setStatus(""); });
|
|
|
|
|
|
btnCopy.addEventListener("click", doCopy);
|
|
|
|
|
|
btnSave.addEventListener("click", doSave);
|
feat(theseus/screenshot): 0.4.0 — editor lives inside the sidebar, maximizable
User report: the sidebar preview lands correctly, but the moment the editor
opens in its own tab the picture is blank. Rather than chase that class of
handoff race again, put the editor in the same webContents as the panel:
the sidebar view navigates panel.html ↔ editor.html in place. Same
document object, same silentmode.storage surface, no cross-tab __pending
transfer at all.
- panel.html "Edit" button now calls silentmode.invoke("arm", …) — the
add-on rewrites __pending with the currently-previewed capture's bytes,
and the panel does location.href = "editor.html?name=…". Sidebar view
loads the editor with the same preload; editor.js's storage-based load
path pulls the pending entry out and paints.
- editor.html gains a "Back" arrow (returns to panel.html) and a
maximize / restore icon.
- discard() now navigates to panel.html instead of closeTab() — there is
no tab to close.
- Manifest drops the "open-tab" capability entirely (no more full-tab
editor); keeps sidebar-panel + capture-tab.
Framework: new silentmode.sidebar.{maximize, restore, toggleMax, isMax,
onMaxChange}. main.js honours them via new sidebar-maximize / -restore /
-toggle-max / -is-max IPCs, remembering the pre-maximize width so a
restore drops back exactly. The sidebar drag-grip auto-exits maximize
mode on any user drag, so pulling the edge always lands on the pre-max
value plus/minus the delta. sidebar-preload exposes the surface;
chrome.html renderer is untouched — this is a per-panel affordance.
Editor tools (crop / arrow / rect / ellipse / pen / text / mosaic /
undo / redo / copy / save) unchanged. Save still goes through Chromium's
<a download> path, so the file lands in Downloads and appears in the
download chip like any other save.
Bundled but not shipped — leaving version bump + deploy to parent session.
2026-09-08 22:18:41 +02:00
|
|
|
|
btnMax.addEventListener("click", toggleMax);
|
2026-09-09 10:55:21 +02:00
|
|
|
|
document.getElementById("btn-close").addEventListener("click", async () => {
|
|
|
|
|
|
// Explicit close from the sidebar itself — same IPC the toolbar dock
|
|
|
|
|
|
// icon toggles. No need to un-maximize first; setSidebar(false) on the
|
|
|
|
|
|
// main side flips visibility and the maximize state is remembered so
|
|
|
|
|
|
// the next open lands where it left off.
|
|
|
|
|
|
try { await window.silentmode.sidebar.close(); }
|
|
|
|
|
|
catch (e) { setStatus("Close failed: " + (e && e.message || e), "err"); }
|
|
|
|
|
|
});
|
feat(theseus/screenshot): 0.5.0 — sidebar-first editor, direct save/copy, sounds
Two problems the old editor kept hitting:
- __pending drain race: opening the editor a second time (refresh, back-and-
forth navigation) found the storage entry already consumed and bailed to
a blank canvas silently.
- Cross-origin img loading: editor.html at file:///…/addons/screenshot/
loading a scratch PNG at file:///…/addons-data/ counts as cross-origin
under Chromium's file-URL policy; setting crossOrigin="anonymous" made
the load fail outright.
Rebuilt editor v2:
- Load path is idempotent: silentmode.invoke("getBytes", {name}) → addon
reads the scratch file and returns a data URL. No __pending drain, no
cross-origin trickery — data: URLs are same-origin and never taint the
canvas, so getImageData / toBlob keep working.
- Two-canvas model (#base + #over, over is pointer-events:none) so live
previews don't cost a full re-composite per mousemove.
- Tools: cursor, arrow, rect, ellipse, pen, text. 6 swatches, 3 widths,
undo / redo (25-deep). Copy + Save at the top bar. Back and Maximize
buttons in the same top bar so navigation controls stay reachable when
the toolbar wraps at narrow widths.
- Keyboard: A/R/O/P/T select tool, Esc = cursor, Ctrl+Z/Shift+Z undo/redo,
Ctrl+S save, Ctrl+C copy.
- Toast surface for save/copy/error feedback.
Sidebar panel gains a direct raw-save path so the user can copy or save the
capture without entering the editor:
- Two-row actions: [Copy] [Save] on top, [Discard] [Edit] below.
- Copy uses navigator.clipboard.write(ClipboardItem); Save uses
<a download> with a Blob URL — same path Chromium's will-download
tracker already handles, so the file lands in Downloads and the chip
updates like any other save.
Inline "clear all" confirmation replaces the native confirm() — the old
system-modal opened over the tab area (out of the sidebar's visual
context) and looked like Windows 95. Now a compact red strip appears
under the Recent header with Cancel / Delete buttons.
Sounds + a sound-on/off toggle in both surfaces:
- Web Audio oscillator-synthesized (no .wav shipped): shutter click on
capture, two-tone bloop on copy, descending pair on discard/back,
ascending triad on save.
- Preference stored in silentmode.storage under "soundOn" (default on),
shared between the panel and the editor.
Simplifications:
- Dropped the addon's "arm" onMessage handler (superseded by getBytes).
- Manifest capabilities: sidebar-panel + capture-tab (no open-tab,
no toolbar-menu).
Bundled but not shipped — parent session handles the OTA sign + push.
2026-09-09 00:56:41 +02:00
|
|
|
|
|
|
|
|
|
|
// Inline confirmation for "clear all" — the native confirm() looks like an
|
|
|
|
|
|
// OS-modal from the last decade AND opens over the tab area (out of the
|
|
|
|
|
|
// sidebar's visual context). Toggle a styled strip inside the sidebar so
|
|
|
|
|
|
// the destructive action stays where the user is looking.
|
|
|
|
|
|
const confirmBox = $("clear-confirm");
|
|
|
|
|
|
clearBtn.addEventListener("click", () => {
|
|
|
|
|
|
confirmBox.classList.add("on");
|
|
|
|
|
|
clearBtn.disabled = true;
|
|
|
|
|
|
});
|
|
|
|
|
|
$("clear-no").addEventListener("click", () => {
|
|
|
|
|
|
confirmBox.classList.remove("on");
|
|
|
|
|
|
clearBtn.disabled = false;
|
|
|
|
|
|
});
|
|
|
|
|
|
$("clear-yes").addEventListener("click", async () => {
|
|
|
|
|
|
confirmBox.classList.remove("on");
|
|
|
|
|
|
clearBtn.disabled = false;
|
|
|
|
|
|
try { await window.silentmode.invoke("clearRecent", {}); await refreshRecent(); setStatus("Cleared past captures.", "ok"); }
|
feat(theseus/screenshot): 0.3.0 — sidebar-first flow with explicit "open in tab"
0.3.33 still ships blank screenshots because the whole toolbar-menu → auto-
open-editor path can't be made race-free: the moment the editor tab opens
it becomes the active tab, and a snapshot of the editor's own tab (before
its canvas has drawn from storage) is a valid-looking 24 KB all-white PNG.
The lastCapturableTabId fallback I added in a279864 catches the second
click, but the first click can still land on the addon-owned tab whenever
the user re-triggers before setActive has settled.
Rebuild the UX so this class of race can't happen at all:
- Drop the toolbar-menu capability. Manifest is back to sidebar-panel +
capture-tab + open-tab, so the dock icon opens the panel (never the
editor directly). No dropdown, no clip-under-tab-view issue, no auto-
jump into an addon-owned tab.
- Sidebar has the three capture buttons + a preview <img> + a "Open in
editor tab" button. The preview is fed a data:image/png URL returned
straight from api.captureTab, rendered inside the sidebar's own
document — same origin, no file:// cross-directory gotcha, and the user
can see immediately whether the shot actually landed.
- Editor.html tab opens only on an explicit "Open in editor tab" click.
The addon rewrites __pending at that moment (so the editor always sees
the just-selected capture even if a prior editor tab drained the entry),
then api.openTab("editor.html", {name}). The editor's storage-based
load path is unchanged.
- Recent captures ring is kept and now exposed as a horizontal thumbnail
strip in the sidebar; clicking a tile re-previews that capture and
arms "Open in editor tab" for it.
Editor page (editor.html/js/css) unchanged — same crop / arrow / rect /
ellipse / pen / text / mosaic-redact / undo / redo / copy / save.
Bundled but not shipped — leaving version bump + deploy to the parent
session.
2026-09-08 20:26:48 +02:00
|
|
|
|
catch (e) { setStatus("Clear failed: " + (e && e.message || e), "err"); }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
refreshRecent();
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|