theseus/bundled-addons/screenshot/editor.js

792 lines
29 KiB
JavaScript
Raw Normal View History

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
// Screenshot editor v2. Lives inside the sidebar view; no separate tab.
//
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
// Load path (idempotent — this is the key change from v1's __pending drain):
// editor.html?name=<scratchfile.png>
// → silentmode.invoke("getBytes", {name})
// → addon reads scratch/<name>.png from disk and returns
// { name, dataUrl:"data:image/png;base64,…", bytes, at, mode }
// → we set an <img> src to that data URL and draw it onto #base
//
// Two canvases: #base holds the committed image; #over is a preview layer
// that hosts the live drag preview for each drawing tool. On mouseup the
// tool commits its result by drawing #over onto #base, clearing #over,
// and pushing a fresh ImageData snapshot onto the undo stack.
//
// No cross-origin img loading (data: URLs are same-origin in Chromium), so
// the canvas never gets tainted — getImageData / toBlob keep working.
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 $ = (id) => document.getElementById(id);
const UNDO_MAX = 25;
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
// ---- audio -----------------------------------------------------------
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
// Same synth engine as the sidebar panel: preference stored under
// silentmode.storage as "soundOn" (default true), no .wav ships in the
// tarball. Photoshoot-style shutter for capture, quick "printer chirp"
// for copy — modeled on Firefox's Screenshots feedback tones.
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
let soundOn = true;
let _audio = null;
function _actx() {
if (!_audio) { try { _audio = new (window.AudioContext || window.webkitAudioContext)(); } catch {} }
if (_audio && _audio.state === "suspended") _audio.resume().catch(() => {});
return _audio;
}
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);
}
feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
2026-09-09 22:34:47 +02:00
// Filtered noise burst. `bp` = band-pass centre freq, `q` = sharpness of
// the band. Optional `pan` gives a random-amplitude micro-modulation
// texture — used by the paper-crumple pass.
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);
feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
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; // crackle
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);
}
feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
2026-09-09 22:34:47 +02:00
// Slowly-sweeping band-pass noise — used as the film-advance "whir" tail
// under the shutter click.
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);
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.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
2026-09-09 22:34:47 +02:00
// Polaroid: sharp mechanical click (shutter mirror + curtain) followed by
// a short film-advance whir. Reads unmistakably as "camera taking a
// picture" rather than a UI beep.
function playShutter() {
_noise(0.02, 0.32, 5200, 8, 0); // sharp metallic tick
_tone(160, 0.03, "square", 0.14, 0.003); // mirror thud
_noise(0.03, 0.24, 3200, 5, 0.03); // curtain close
_tone(120, 0.04, "square", 0.10, 0.035);
_sweep(0.28, 0.10, 900, 400, 12, 0.07); // film-advance whir tail
}
// Printer chika-chika-chika — three descending percussive bursts modelled
// on a print head sweeping across a page. Fast and unmistakably "action
// happened", no residual hum.
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() {
feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
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);
}
// Photo dispensing — a soft pneumatic hiss with a small final click, the
// way a Polaroid ejects its print.
function playSave() {
_sweep(0.18, 0.14, 3800, 1600, 3, 0);
_noise(0.02, 0.20, 2400, 8, 0.18);
_tone(900, 0.04, "sine", 0.12, 0.20);
}
// Paper crumple — a long band-limited noise with texture crackle and a
// descending centre frequency, reading as "a page being scrunched".
function playDiscard() {
_noise(0.16, 0.28, 2600, 3, 0.00, true); // main body
_noise(0.12, 0.22, 1800, 3, 0.06, true); // trailing scrunch
_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
}
function playCropApply() { _tone(1200, 0.05, "sine", 0.14, 0); _tone(1600, 0.07, "sine", 0.14, 0.04); }
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 base = $("base");
const over = $("over");
const stage = $("stage");
const board = $("board");
const empty = $("empty");
const nameEl = $("name");
const undoBtn = $("undo");
const redoBtn = $("redo");
const toastEl = $("toast");
const dl = $("download-link");
const bctx = base.getContext("2d");
const octx = over.getContext("2d");
const params = new URLSearchParams(location.search);
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 NAME = params.get("name") || "";
let state = {
tool: "select",
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
color: "#ff3b30",
width: 5,
drag: null, // {x0,y0,x,y}
pen: null, // [{x,y}, …]
textInput: null, // {x, y, el}
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
cropRect: null, // committed-crop rectangle in canvas coords, {x,y,w,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
let undo = [];
let redo = [];
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
// -- utilities --------------------------------------------------------------
function toast(msg, err) {
toastEl.textContent = msg;
toastEl.classList.toggle("err", !!err);
toastEl.classList.add("on");
clearTimeout(toast._t);
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
toast._t = setTimeout(() => toastEl.classList.remove("on"), 1800);
}
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 updateUndoRedo() {
undoBtn.disabled = undo.length <= 1; // baseline snapshot always at index 0
redoBtn.disabled = redo.length === 0;
}
function pushSnapshot() {
try {
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 snap = bctx.getImageData(0, 0, base.width, base.height);
undo.push(snap);
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 (undo.length > UNDO_MAX) undo.shift();
redo = [];
updateUndoRedo();
} catch (e) {
console.warn("snapshot failed:", e);
}
}
function restoreSnapshot(snap) {
if (!snap) return;
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 (base.width !== snap.width || base.height !== snap.height) {
sizeCanvases(snap.width, snap.height);
}
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
bctx.putImageData(snap, 0, 0);
}
function sizeCanvases(w, 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
for (const c of [base, over]) {
c.width = w;
c.height = h;
c.style.width = w + "px";
c.style.height = h + "px";
}
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
stage.style.width = w + "px";
stage.style.height = h + "px";
}
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
// Scale the stage down to fit inside the board when the image is larger
// than the visible area. Purely visual — drawing math stays in natural px.
function fitBoard() {
const availW = Math.max(50, board.clientWidth - 24);
const availH = Math.max(50, board.clientHeight - 24);
const s = Math.min(1, availW / base.width, availH / base.height);
const scale = s > 0 && Number.isFinite(s) ? s : 1;
stage.style.transform = `scale(${scale})`;
stage.style.width = (base.width * scale) + "px";
stage.style.height = (base.height * scale) + "px";
stage._scale = scale;
}
// Convert a viewport-relative pointer event into natural canvas coords.
function pointToCanvas(ev) {
const rect = base.getBoundingClientRect();
const scale = stage._scale || 1;
const cssW = rect.width, cssH = rect.height;
const x = (ev.clientX - rect.left) * (base.width / (cssW || 1));
const y = (ev.clientY - rect.top) * (base.height / (cssH || 1));
return { x, y };
}
// -- loading ---------------------------------------------------------------
async function loadFromAddon(name) {
if (!window.silentmode?.invoke) {
throw new Error("silentmode API not available in this view");
}
const res = await window.silentmode.invoke("getBytes", { name });
if (!res || !res.dataUrl) throw new Error("addon returned no data URL");
return res;
}
function loadImage(dataUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
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
img.onload = () => resolve(img);
img.onerror = () => reject(new Error("image decode failed"));
// NB: no crossOrigin — data: URLs are same-origin, and setting it
// to anonymous would require CORS headers that a data URL can't carry,
// which is precisely what tripped up v1.
img.src = dataUrl;
});
}
async function init() {
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
document.title = NAME ? NAME + " — editor" : "Screenshot editor";
nameEl.textContent = NAME || "screenshot";
if (!NAME) {
empty.classList.add("err");
empty.innerHTML = "<div>Missing <code>?name</code> — open a capture from the Screenshot sidebar.</div>";
return;
}
let res;
try { res = await loadFromAddon(NAME); }
catch (e) {
console.warn("editor load failed:", e);
empty.classList.add("err");
empty.innerHTML = `<div>Couldn't load capture: ${(e && e.message || e)}</div>`;
return;
fix(theseus/screenshot): 0.2.4 — deliver capture via addon storage, not a cross-origin file:// Blank editor + broken buttons root cause: index.js was writing the capture to <userData>/addons-data/screenshot-scratch/<name>.png and passing "?src=file://<that path>" to editor.html. The editor lives at file:///<userData>/addons/screenshot/editor.html — different directory tree under file://. Chromium's file:// origin policy treats those as different origins and quietly refuses the <img> load, so init()'s loadImage() rejects, the canvas never gets an image, and every tool after that operates on a still-empty 300×150 default canvas — the tools appear to work but produce no visible output because the base image never landed. The sidebar version we replaced set `previewImg.src = dataUrl` (a base64 data URL) directly, which has no origin and just worked; the tab version regressed by adding the file hop. Fix keeps the scratch file for the recent-captures ring but hands the raw capture through the add-on's per-add-on kv store (`__pending` key). Same store, same origin scoping, no cross-directory read: index.js writes via api.storage.set from main; editor.js reads via window.silentmode.storage.get through the tab preload (packaged since 0.3.27). Fallback path retained for "openRecent" callers still passing ?src=… — those will need their own fix in a follow-up. Bumped to 0.2.4 and signed for the OTA endpoint — first real independent add-on ship: no Theseus release needed to fix this, 0.3.27 installs pick up 0.2.4 via the boot-time signed-update poll.
2026-09-08 12:57:46 +02:00
}
let img;
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
try { img = await loadImage(res.dataUrl); }
catch (e) {
empty.classList.add("err");
empty.innerHTML = `<div>Couldn't decode capture bytes (${res.bytes || 0} B)</div>`;
return;
}
sizeCanvases(img.naturalWidth, img.naturalHeight);
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
bctx.drawImage(img, 0, 0);
undo = [];
redo = [];
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
pushSnapshot();
empty.hidden = true;
stage.hidden = false;
fitBoard();
}
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
window.addEventListener("resize", () => { if (!stage.hidden) fitBoard(); });
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
// -- tool selection --------------------------------------------------------
function setTool(name) {
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
// Leaving crop mode with an unapplied marquee drops it.
if (state.tool === "crop" && name !== "crop" && state.cropRect) {
state.cropRect = null; clearOver();
}
state.tool = name;
stage.dataset.tool = name;
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
for (const b of document.querySelectorAll(".tool")) {
b.classList.toggle("active", b.dataset.tool === name);
}
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
cancelTextInput();
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
updateCropUI();
}
// Show/hide the Apply crop / Cancel crop buttons in the top bar. Visible
// only while the tool is "crop" AND a rectangle has been drawn.
function updateCropUI() {
const on = state.tool === "crop" && !!state.cropRect;
const ac = document.getElementById("apply-crop");
const cc = document.getElementById("cancel-crop");
if (ac) ac.hidden = !on;
if (cc) cc.hidden = !on;
}
// Apply the crop: resize the base canvas to the rect's size, draw the
// cropped region onto it, then clear undo/redo (the coordinate system has
// changed — pre-crop snapshots would restore into the wrong dimensions).
// Baseline snapshot for the cropped canvas becomes the new floor.
function applyCrop() {
const r = state.cropRect;
if (!r) return;
const w = Math.max(1, Math.round(r.w));
const h = Math.max(1, Math.round(r.h));
const x = Math.max(0, Math.round(r.x));
const y = Math.max(0, Math.round(r.y));
const tmp = document.createElement("canvas");
tmp.width = w; tmp.height = h;
tmp.getContext("2d").drawImage(base, x, y, w, h, 0, 0, w, h);
sizeCanvases(w, h);
bctx.drawImage(tmp, 0, 0);
state.cropRect = null;
clearOver();
undo = [];
redo = [];
pushSnapshot();
fitBoard();
updateCropUI();
playCropApply();
setTool("select");
}
function cancelCrop() {
state.cropRect = null;
clearOver();
updateCropUI();
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
}
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
// Clicking an already-active tool toggles it off (back to "select" = no
// tool). The dedicated "Select" button used to live here but read as
// broken to users — clicking it did nothing visible. Toggle-off gives the
// same "put the pen down" affordance without a mystery button.
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
for (const b of document.querySelectorAll(".tool")) {
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
b.addEventListener("click", () => {
setTool(state.tool === b.dataset.tool ? "select" : b.dataset.tool);
});
}
for (const b of document.querySelectorAll(".swatch")) {
b.addEventListener("click", () => {
state.color = b.dataset.color;
for (const x of document.querySelectorAll(".swatch")) x.classList.toggle("active", x === b);
});
}
for (const b of document.querySelectorAll(".width")) {
b.addEventListener("click", () => {
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
state.width = Number(b.dataset.width) || 5;
for (const x of document.querySelectorAll(".width")) x.classList.toggle("active", x === b);
});
}
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
// -- drawing ---------------------------------------------------------------
function clearOver() { octx.clearRect(0, 0, over.width, over.height); }
function strokeStyle() {
octx.strokeStyle = state.color;
octx.fillStyle = state.color;
octx.lineWidth = state.width;
octx.lineCap = "round";
octx.lineJoin = "round";
}
function drawArrow(a, b) {
strokeStyle();
octx.beginPath();
octx.moveTo(a.x, a.y); octx.lineTo(b.x, b.y); octx.stroke();
// arrowhead
const dx = b.x - a.x, dy = b.y - a.y;
const len = Math.hypot(dx, dy);
if (len < 1) return;
const head = Math.max(10, state.width * 3);
const ang = Math.atan2(dy, dx);
const spread = Math.PI / 6;
octx.beginPath();
octx.moveTo(b.x, b.y);
octx.lineTo(b.x - head * Math.cos(ang - spread), b.y - head * Math.sin(ang - spread));
octx.moveTo(b.x, b.y);
octx.lineTo(b.x - head * Math.cos(ang + spread), b.y - head * Math.sin(ang + spread));
octx.stroke();
}
// Plain line — same drag flow as arrow, minus the head. Useful for
// underlines / separators / crossing-out without pointing at anything.
function drawLine(a, b) {
strokeStyle();
octx.beginPath();
octx.moveTo(a.x, a.y); octx.lineTo(b.x, b.y); octx.stroke();
}
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 drawRect(a, b) {
strokeStyle();
const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
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 w = Math.abs(b.x - a.x), h = Math.abs(b.y - a.y);
octx.strokeRect(x, y, w, h);
}
function drawEllipse(a, b) {
strokeStyle();
const cx = (a.x + b.x) / 2, cy = (a.y + b.y) / 2;
const rx = Math.abs(b.x - a.x) / 2, ry = Math.abs(b.y - a.y) / 2;
octx.beginPath();
octx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
octx.stroke();
}
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 drawPen(points) {
if (!points || points.length < 2) return;
strokeStyle();
octx.beginPath();
octx.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) octx.lineTo(points[i].x, points[i].y);
octx.stroke();
}
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
// Crop overlay — dim the area outside the rect, thin dashed border. This
// is a live preview during drag AND the settled marquee while apply-crop
// is pending.
function drawCropRect(a, b) {
const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
const w = Math.abs(b.x - a.x), h = Math.abs(b.y - a.y);
clearOver();
octx.save();
octx.fillStyle = "rgba(10,13,19,0.55)";
octx.fillRect(0, 0, over.width, over.height);
octx.clearRect(x, y, w, h);
octx.strokeStyle = "#d6ff3d";
octx.lineWidth = 1.5;
octx.setLineDash([6, 4]);
octx.strokeRect(x + 0.5, y + 0.5, w - 1, h - 1);
octx.restore();
return { x, y, w, h };
}
// Mosaic redaction: downsample the selected region of #base to blocks of
// ~<blockSize> px, then paint the blocks back. The whole thing commits
// straight to #base (there's no live preview — the drag rect uses the
// crop-style marquee to show the target region).
function commitBlur(a, b) {
const x = Math.max(0, Math.min(a.x, b.x)) | 0;
const y = Math.max(0, Math.min(a.y, b.y)) | 0;
const w = Math.min(base.width - x, Math.abs(b.x - a.x) | 0);
const h = Math.min(base.height - y, Math.abs(b.y - a.y) | 0);
if (w < 4 || h < 4) return false;
const block = Math.max(6, Math.round(Math.min(w, h) / 12));
const tmp = document.createElement("canvas");
const scaleW = Math.max(1, Math.floor(w / block));
const scaleH = Math.max(1, Math.floor(h / block));
tmp.width = scaleW; tmp.height = scaleH;
const tctx = tmp.getContext("2d");
tctx.imageSmoothingEnabled = true;
tctx.drawImage(base, x, y, w, h, 0, 0, scaleW, scaleH);
bctx.imageSmoothingEnabled = false;
bctx.drawImage(tmp, 0, 0, scaleW, scaleH, x, y, w, h);
bctx.imageSmoothingEnabled = true;
return true;
}
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 commitOver() {
bctx.drawImage(over, 0, 0);
clearOver();
pushSnapshot();
}
// -- input --------------------------------------------------------------
over.style.pointerEvents = "none"; // draw layer never blocks input; base receives
base.addEventListener("pointerdown", (ev) => {
if (state.tool === "select") return;
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
if (state.tool === "text") {
ev.preventDefault();
openTextInput(pointToCanvas(ev));
return;
}
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
base.setPointerCapture(ev.pointerId);
const p = pointToCanvas(ev);
if (state.tool === "pen") { state.pen = [p]; }
else { state.drag = { x0: p.x, y0: p.y, x: p.x, y: p.y }; }
});
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
base.addEventListener("pointermove", (ev) => {
const p = pointToCanvas(ev);
if (state.tool === "pen" && state.pen) {
state.pen.push(p);
clearOver();
drawPen(state.pen);
return;
}
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 (!state.drag) return;
state.drag.x = p.x; state.drag.y = p.y;
const a = { x: state.drag.x0, y: state.drag.y0 }, b = { x: p.x, y: p.y };
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 (state.tool === "arrow") { clearOver(); drawArrow(a, b); }
else if (state.tool === "line") { clearOver(); drawLine(a, b); }
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
else if (state.tool === "rect") { clearOver(); drawRect(a, b); }
else if (state.tool === "ellipse") { clearOver(); drawEllipse(a, b); }
else if (state.tool === "crop"
|| state.tool === "blur") { drawCropRect(a, b); }
});
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
base.addEventListener("pointerup", (ev) => {
try { base.releasePointerCapture(ev.pointerId); } catch {}
if (state.tool === "pen" && state.pen) {
if (state.pen.length >= 2) commitOver();
else clearOver();
state.pen = null;
return;
}
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 (!state.drag) return;
const dx = state.drag.x - state.drag.x0, dy = state.drag.y - state.drag.y0;
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 (Math.hypot(dx, dy) < 2) { clearOver(); state.drag = null; state.cropRect = null; updateCropUI(); return; }
const a = { x: state.drag.x0, y: state.drag.y0 }, b = { x: state.drag.x, y: state.drag.y };
if (state.tool === "crop") {
// Don't commit yet — the marquee stays up until the user hits Apply.
state.cropRect = drawCropRect(a, b);
state.drag = null;
updateCropUI();
return;
}
if (state.tool === "blur") {
if (commitBlur(a, b)) { clearOver(); pushSnapshot(); }
else clearOver();
state.drag = null;
return;
}
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
commitOver();
state.drag = null;
});
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
// -- text tool -----------------------------------------------------------
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
// A little <input> floats over the canvas at the click point; Enter commits
// as fillText, Escape drops. Chases a few browser quirks:
// - Focus after the DOM mutation, not before: some Chromium builds
// drop focus() when the element hasn't yet been laid out.
// - Contain the input's own pointer events so a mousedown inside it
// doesn't bubble to the canvas and immediately re-fire openTextInput,
// spawning a fresh empty box.
// - Track height instead of a hard-coded 14 px offset so tall fonts sit
// on the click's baseline rather than 14 px above it.
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 openTextInput(pt) {
cancelTextInput();
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
const size = Math.max(16, state.width * 4);
// Textarea (not input) so the user can drag the corner to resize the
// box, and so text can wrap / span multiple lines. Enter commits;
// Shift+Enter inserts a newline.
const el = document.createElement("textarea");
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
el.className = "text-input";
el.placeholder = "type, then Enter (Shift+Enter for newline · drag corner to resize)";
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
el.autocomplete = "off";
el.setAttribute("autocorrect", "off");
el.setAttribute("spellcheck", "false");
el.rows = 1;
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
// Visual weight — a bright acid halo around the box + the actual
// ink colour on the text itself, so users see something clearly
// happened when they clicked.
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
el.style.color = state.color;
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
el.style.font = `${size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
el.style.lineHeight = "1.15";
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
el.style.background = "rgba(11,14,20,0.94)";
el.style.border = "2px solid var(--acid, #d6ff3d)";
el.style.boxShadow = "0 0 0 3px rgba(214,255,61,.25), 0 4px 14px rgba(0,0,0,.45)";
el.style.resize = "both";
el.style.overflow = "auto";
el.style.whiteSpace = "pre";
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 rect = base.getBoundingClientRect();
const scale = stage._scale || 1;
const cssX = pt.x * scale + rect.left;
const cssY = pt.y * scale + rect.top;
el.style.left = cssX + "px";
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
el.style.top = (cssY - size) + "px";
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
document.body.appendChild(el);
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
// Three-way focus attempt — Chromium is racy about focusing an element
// that appeared mid-pointer-event. Synchronous focus() first (works on
// most builds), then a paint tick, then a short timer as belt-and-braces.
el.focus();
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
requestAnimationFrame(() => el.focus());
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
setTimeout(() => { if (state.textInput && state.textInput.el === el) el.focus(); }, 30);
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
state.textInput = { x: pt.x, y: pt.y, size, el };
// Don't let the box's own pointer events bubble to the canvas —
// otherwise every click-through re-fires openTextInput on the base and
// spawns duplicate boxes, and drag-to-resize on the corner handle would
// start a canvas drag on the tool underneath.
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
const swallow = (ev) => ev.stopPropagation();
for (const t of ["pointerdown", "mousedown", "click", "pointerup", "pointermove"]) {
el.addEventListener(t, swallow);
}
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
el.addEventListener("keydown", (ev) => {
if (ev.key === "Enter" && !ev.shiftKey) {
commitTextInput(); ev.preventDefault(); ev.stopPropagation();
}
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
else if (ev.key === "Escape") { cancelTextInput(); ev.preventDefault(); ev.stopPropagation(); }
feat(theseus/screenshot): 0.6.3 — per-tile delete, no Select button, text tool halo, right-anchor panel controls Four issues from the user's report on 0.6.2: - Recent captures had a global "clear all" but no way to drop a single screenshot. Each tile now grows a small × button (visible on hover; drops in behind the thumbnail preview so it never obstructs the content). Clicking the × invokes clearRecent({name}) and removes both the ring entry and the scratch PNG on disk. Bubble-guarded so the × click doesn't also trigger the tile's "load into preview" handler. - Select tool button removed — clicking it did nothing visible, so users read it as broken. The internal "select" mode still exists as the no-tool state; you get back to it now by clicking the same drawing tool a second time (toggle-off) or hitting Escape. The active-drawing- tool button flips its border when armed. - Text tool made unmistakable: input paints with a 2 px acid border, a glowing acid halo, dark background, and the visible ink colour on the text itself. Focus attempt is three-layered (sync, rAF, timer) to outrun any Chromium build that drops the mid-pointer-event focus. Non- Enter/Escape keys get stopPropagation so a stray document listener can't steal the focus mid-typing. - Panel header's sound / max / close cluster kept nudging inward when the status text was empty. The parent's `justify-content: space- between` distributed the row unevenly. Force-anchor the cluster with `#btn-sound { margin-left: auto }` so the three window-control icons hug the right edge regardless of what fills the middle. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 22:01:06 +02:00
// Every other key stays inside the input — belt against a stray
// document-level keydown handler stealing focus.
else ev.stopPropagation();
});
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
el.addEventListener("blur", commitTextInput);
}
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 commitTextInput() {
const ti = state.textInput;
if (!ti) return;
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
const value = ti.el.value;
const trimmed = value.trim();
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
ti.el.remove();
state.textInput = null;
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
if (!trimmed) return;
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
bctx.fillStyle = state.color;
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
bctx.font = `${ti.size}px system-ui, -apple-system, Segoe UI, Roboto, sans-serif`;
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
bctx.textBaseline = "alphabetic";
// Multi-line rendering — one fillText per line, stepping down by the
// font's line height so the visible box's layout matches the baked-in
// pixels.
const lineH = ti.size * 1.15;
const lines = value.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
bctx.fillText(lines[i], ti.x, ti.y + i * lineH);
}
pushSnapshot();
}
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 cancelTextInput() {
if (!state.textInput) return;
state.textInput.el.remove();
state.textInput = null;
}
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
// -- undo / redo -------------------------------------------------------
function doUndo() {
if (undo.length <= 1) return;
const cur = undo.pop();
redo.push(cur);
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
restoreSnapshot(undo[undo.length - 1]);
updateUndoRedo();
}
function doRedo() {
const snap = redo.pop();
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 (!snap) return;
undo.push(snap);
restoreSnapshot(snap);
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
updateUndoRedo();
}
undoBtn.addEventListener("click", doUndo);
redoBtn.addEventListener("click", doRedo);
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
// -- save / copy ------------------------------------------------------
function canvasBlob() {
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
return new Promise((resolve, reject) =>
base.toBlob((b) => b ? resolve(b) : reject(new Error("toBlob returned null")), "image/png"));
}
async function save() {
try {
const blob = await canvasBlob();
const url = URL.createObjectURL(blob);
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
dl.href = url;
dl.download = NAME || "screenshot.png";
dl.click();
setTimeout(() => URL.revokeObjectURL(url), 5000);
playSave();
toast(`Saved ${dl.download} (${(blob.size / 1024).toFixed(1)} KB)`);
} catch (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
toast("Save failed: " + (e && e.message || e), true);
}
}
async function copy() {
try {
const blob = await canvasBlob();
await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
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
playCopy();
toast("Copied to clipboard");
} catch (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
toast("Copy failed: " + (e && e.message || e), true);
}
}
$("save").addEventListener("click", save);
$("copy").addEventListener("click", copy);
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 applyCropBtn = document.getElementById("apply-crop");
const cancelCropBtn = document.getElementById("cancel-crop");
if (applyCropBtn) applyCropBtn.addEventListener("click", applyCrop);
if (cancelCropBtn) cancelCropBtn.addEventListener("click", cancelCrop);
feat(theseus/addons): CDP capture + editor Discard + manual update controls Three tied-together fixes: 1) captureTab moves from WebContents.capturePage() to CDP Page.captureScreenshot for every mode (visible / full / region). Blank-screenshot symptom: after a toolbar-menu selection, the OS popup teardown left the tab view marked occluded for a few frames on some Windows setups, so capturePage() snapshotted a stale/transparent frame at the correct dimensions — no 0x0, no retry hit. CDP forces a fresh composite regardless of occlusion state (same path the "Full page" mode was already using) and returns a base64 PNG directly; PNG dimensions come out of the IHDR chunk (bytes 16-24). Attach only when nothing else has, and detach after only if WE attached, so an open DevTools stays attached. 2) Editor gets a Discard button. Toolbar picks up an "×" glyph next to Save/Copy that closes the editor tab and drops the working screenshot. Top-level Escape now falls through the same path after unwinding an in-flight text placement or crop rectangle. A new "addon-tab-close" IPC lets an add-on's own tab close itself (main matches the sender's webContents id against the tab list, so a page can only close its own tab); window.silentmode.closeTab() exposes it from addon-tab-preload.js. 3) Manual update controls in Settings > Extensions. New "Check for updates" button at the top of the Extensions surface calls the same signed-update polling the boot timer runs; the result is surfaced inline ("All extensions are up to date" / "N updates staged; restart Theseus to apply"). A "Pending updates" box below lists what's in <userData>/addons-updates-staged/ so the user knows what will be promoted on next restart. Toolbar-menu popup settle bumped from 120 ms to 250 ms with an explicit win.focus() in the popup close callback — the previous window wasn't enough on slower Windows setups. CDP capture no longer depends on this delay anyway, but the settle still helps any add-on that does DOM work in its click handler before capture. Screenshot add-on bumped 0.2.2 → 0.2.3 (Discard button; capture fixes come from the host, not the add-on).
2026-09-08 02:27:36 +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
// -- top-bar navigation ----------------------------------------------
$("back").addEventListener("click", () => { location.href = "panel.html"; });
feat(theseus/screenshot): 0.6.4 — Polaroid sounds, trash icon, centred cluster, filename footer + open-in-folder Rolling every user report from the 0.6.3 rollout into one bundle: Sounds — the Web-Audio synth palette matches the metaphor now: - Screenshot: Polaroid shutter — sharp metallic tick + curtain-close click chained to a film-advance whir (band-passed noise sweeping 900→400 Hz). - Copy: printer "chika-chika-chika" — three descending percussive noise bursts pinned by short sine ticks. Reads as a print-head sweep. - Discard: paper crumple — three overlapping band-limited noise beds with per-sample random-amplitude crackle, descending centre freq. No more descending sine "boop". - Save: soft "photo dispensing" hiss (Polaroid ejects) + a small click. - Both the panel and editor share the design so nothing sounds different depending on which surface fired it. UI polish: - Discard button now carries a trash-can icon so it's obviously not the same as the close-sidebar X (they both used to be plain X's). - Toolbar drawing tools centre themselves via a new .tool-cluster wrapper (flex:1 1 auto, justify-content:center); the Copy/Save actions stay right-anchored via margin-left:auto on their own tgroup. Fixes the maximized-sidebar case where the drawing groups all crowded the left with a big empty gap before Copy/Save on the right. - Filename moves out of the topbar into a dedicated footer strip under the canvas board, alongside a new "Open in folder" button. The topbar is now flex-wrap:nowrap and holds only fixed-width window controls, so a long filename can never push discard / sound / max / close onto a second row (the filename ellipsises instead). - "Open in folder" invokes a new "openFolder" addon message that calls Electron's shell.showItemInFolder() to open the OS file explorer with the specific scratch PNG highlighted (falls back to shell.openPath() on the scratch dir when no capture is named). Version bump so the OTA update endpoint picks it up on the next tick.
2026-09-09 22:34:47 +02:00
// "Open in folder" — sends the current capture's name to the addon's
// openFolder handler, which calls shell.showItemInFolder() so the file
// explorer opens with the exact scratch PNG highlighted.
const openFolderBtn = document.getElementById("open-folder");
if (openFolderBtn) openFolderBtn.addEventListener("click", async () => {
try {
if (window.silentmode?.invoke) {
await window.silentmode.invoke("openFolder", { name: NAME || "" });
}
} catch (e) {
console.warn("open-folder failed:", e);
toast("Couldn't open folder: " + (e && e.message || e), true);
}
});
// Discard — throw away this capture entirely (delete it from the Recent
// ring) and return to the panel. Back is non-destructive; Discard is not.
const discardBtn = document.getElementById("discard");
if (discardBtn) discardBtn.addEventListener("click", async () => {
playDiscard();
try {
if (NAME && window.silentmode?.invoke) {
await window.silentmode.invoke("clearRecent", { name: NAME });
}
} catch (e) { console.warn("discard: clearRecent failed:", e); }
location.href = "panel.html";
});
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
feat(theseus/screenshot): 0.6.1 — text tool fix, category wrap, sidebar close X Four user reports from the 0.6.0 rollout: - Text tool never committed. openTextInput placed the box correctly but a couple of Chromium quirks stopped a normal type-Enter cycle: focus() called synchronously right after appendChild lost the race in some builds, and the input's own mousedown / click was bubbling through to #base and re-firing openTextInput on every subsequent keystroke click-through, so what looked like "nothing happens" was actually "a new empty box spawned on top of the last one every time". Now: focus after requestAnimationFrame, contain pointerdown / mousedown / click inside the input so they don't bubble to the canvas, track the font size on the state so commit uses the same one openTextInput measured against, and preventDefault on the base pointerdown so Chromium doesn't reset focus back to <body>. - Toolbar wrapped one dot at a time when the sidebar was narrow (a lonely thin/medium/thick width would jump to a second row while the swatches stayed above it). Toolbar items are now wrapped in `<div class="tgroup">` per category — tools / swatches / widths / undo-redo — with `flex: 0 0 auto`, so a whole row wraps as a unit and lands cleanly under the previous one. `gap: 10px / row-gap: 6px` keeps the visual grouping obvious. - No way to close the sidebar without hunting for the dock icon. Added an X button in the top-right of both the sidebar panel and the editor toolbar. Both wire through a new `silentmode.sidebar.close()` preload method that calls the existing `sidebar-close` IPC. - Tightened the pointerdown text branch so preventDefault + explicit focus-after-frame make the click-through races impossible. Bundled but not shipped separately — parent session signs and pushes.
2026-09-09 10:55:21 +02:00
// Close the sidebar entirely — same IPC the dock icon toggles. We stop
// on the panel side (see panel.html); the editor mirrors the affordance
// so users don't need to navigate back before hiding the addon.
const closeSbBtn = document.getElementById("close-sidebar");
if (closeSbBtn && window.silentmode?.sidebar) {
closeSbBtn.addEventListener("click", () => {
try { window.silentmode.sidebar.close(); } catch {}
});
}
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
// Sound-toggle button — mirrors the sidebar panel's, both persist via
// silentmode.storage so opening either surface reflects the same setting.
const soundBtn = $("toggle-sound");
function paintSoundIcon() {
if (!soundBtn) return;
soundBtn.title = soundOn ? "Sounds on — click to mute" : "Sounds off — click to enable";
soundBtn.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;
} catch {}
paintSoundIcon();
})();
if (soundBtn) soundBtn.addEventListener("click", async () => {
soundOn = !soundOn;
paintSoundIcon();
try { await window.silentmode?.storage?.set("soundOn", soundOn); } catch {}
if (soundOn) playSave();
});
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 maxBtn = $("toggle-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
async function paintMaxIcon(isMax) {
maxBtn.title = isMax ? "Restore sidebar width" : "Expand the sidebar to full window";
maxBtn.innerHTML = isMax
? '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M6 2v4H2M10 2v4h4M6 14v-4H2M10 14v-4h4"/></svg>'
: '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 6V2h4M14 6V2h-4M2 10v4h4M14 10v4h-4"/></svg>';
}
if (window.silentmode?.sidebar) {
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
maxBtn.addEventListener("click", async () => {
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
window.silentmode.sidebar.onMaxChange(paintMaxIcon);
window.silentmode.sidebar.isMax().then(paintMaxIcon).catch(() => {});
feat(theseus/addons): CDP capture + editor Discard + manual update controls Three tied-together fixes: 1) captureTab moves from WebContents.capturePage() to CDP Page.captureScreenshot for every mode (visible / full / region). Blank-screenshot symptom: after a toolbar-menu selection, the OS popup teardown left the tab view marked occluded for a few frames on some Windows setups, so capturePage() snapshotted a stale/transparent frame at the correct dimensions — no 0x0, no retry hit. CDP forces a fresh composite regardless of occlusion state (same path the "Full page" mode was already using) and returns a base64 PNG directly; PNG dimensions come out of the IHDR chunk (bytes 16-24). Attach only when nothing else has, and detach after only if WE attached, so an open DevTools stays attached. 2) Editor gets a Discard button. Toolbar picks up an "×" glyph next to Save/Copy that closes the editor tab and drops the working screenshot. Top-level Escape now falls through the same path after unwinding an in-flight text placement or crop rectangle. A new "addon-tab-close" IPC lets an add-on's own tab close itself (main matches the sender's webContents id against the tab list, so a page can only close its own tab); window.silentmode.closeTab() exposes it from addon-tab-preload.js. 3) Manual update controls in Settings > Extensions. New "Check for updates" button at the top of the Extensions surface calls the same signed-update polling the boot timer runs; the result is surfaced inline ("All extensions are up to date" / "N updates staged; restart Theseus to apply"). A "Pending updates" box below lists what's in <userData>/addons-updates-staged/ so the user knows what will be promoted on next restart. Toolbar-menu popup settle bumped from 120 ms to 250 ms with an explicit win.focus() in the popup close callback — the previous window wasn't enough on slower Windows setups. CDP capture no longer depends on this delay anyway, but the settle still helps any add-on that does DOM work in its click handler before capture. Screenshot add-on bumped 0.2.2 → 0.2.3 (Discard button; capture fixes come from the host, not the add-on).
2026-09-08 02:27:36 +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
// -- keyboard shortcuts ---------------------------------------------
window.addEventListener("keydown", (ev) => {
if (state.textInput) return;
const meta = ev.ctrlKey || ev.metaKey;
if (meta && ev.key.toLowerCase() === "z" && !ev.shiftKey) { doUndo(); ev.preventDefault(); return; }
if (meta && ev.key.toLowerCase() === "z" && ev.shiftKey) { doRedo(); ev.preventDefault(); return; }
if (meta && ev.key.toLowerCase() === "s") { save(); ev.preventDefault(); return; }
if (meta && ev.key.toLowerCase() === "c") { copy(); ev.preventDefault(); return; }
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 (state.tool === "crop" && state.cropRect) {
if (ev.key === "Enter") { applyCrop(); ev.preventDefault(); return; }
if (ev.key === "Escape") { cancelCrop(); ev.preventDefault(); return; }
}
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 (ev.key === "a") setTool("arrow");
else if (ev.key === "l") setTool("line");
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
else if (ev.key === "r") setTool("rect");
else if (ev.key === "o") setTool("ellipse");
else if (ev.key === "p") setTool("pen");
else if (ev.key === "t") setTool("text");
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
else if (ev.key === "c") setTool("crop");
else if (ev.key === "b") setTool("blur");
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
else if (ev.key === "Escape") setTool("select");
});
init();