← Commit history
Cargo.lock+1−1
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"  [[package]] name = "adom-shotlog"-version = "0.24.4"+version = "0.24.5" dependencies = [  "axum",  "base64",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-shotlog"-version = "0.24.4"+version = "0.24.5" edition = "2021" description = "Screenshot log viewer and CLI for Adom" license = "MIT"
VERSION+1−1
@@ -1 +1 @@-0.24.4+0.24.5
bin/adom-shotlog
⋯ 1 unchanged line ⋯
dev-skills/adom-shotlog-dev/SKILL.md+11
@@ -356,3 +356,14 @@ resets the backFromFill toggle, and exiting with the modal closed just returns to the list (openModal resets state on the next open, so no stale flag). NOTE (bridge drift): the pup verb is now `browser_eval` (not browser_evaluate) and the eval result is TOP-LEVEL `d.result` (not d.output.result).++## Webview auto-size is OPT-IN (John, 2026-08-08)+open_webview took an `autosize` param. When FALSE (the new default, pref+`webview_autosize`), the RESIZE step (snap an existing split back to+webview_width on every open) is SKIPPED - the human's manually-dragged column+width sticks. The width pref now only SEEDS a brand-new panel (the initial+split). ON re-forces the width every open (old behavior). John found the+constant snap-back annoying: "rely on the user deciding what their webview+column width is." All 3 open_webview callers (CLI open, force_open_viewer,+force_open_pup fallback) thread the pref through notify_surface. An explicit+CLI --ratio still counts as autosize (deliberate size request).
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.24.4",+  "version": "0.24.5",   "type": "app",   "title": "Adom Shotlog",   "description": "See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
page.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "adom-shotlog",-  "version": "0.24.4",+  "version": "0.24.5",   "type": "app",   "title": "Adom Shotlog",   "description": "See the shots your AI never showed you, and know it actually SAW them. adom-shotlog streams every screenshot the AI gathers into a live gallery (webview/pup/browser/phone), reports whether a human is really watching and on which surface, and ingests Adom Desktop child-window arrays via --meta.",
src/html/index.html+15−2
@@ -567,13 +567,21 @@ header {         <button data-v="pup">Pup</button>       </div>     </div>+    <div class="pref-row">+      <div class="pref-info">+        <div class="pref-name">Auto-size the webview panel</div>+        <div class="pref-desc">When ON, shotlog re-forces the panel to the width below every time it opens the webview. When OFF (default), shotlog leaves the column at whatever width YOU last dragged it to; the width below only seeds a brand-new panel.</div>+        <div class="ai-chip"><span class="lbl">or say to your AI</span>turn on shotlog webview auto-sizing</div>+      </div>+      <button class="toggle" id="prefAutosize" role="switch" aria-checked="false" data-tooltip="ON: snap the webview column to your preferred width on every open. OFF (default): respect the width you set manually."></button>+    </div>     <div class="pref-row">       <div class="pref-info">         <div class="pref-name">Webview panel width</div>-        <div class="pref-desc">How wide the slim webview panel is when shotlog opens it, as a percent of your workspace (10-50).</div>+        <div class="pref-desc">The width shotlog uses when it first CREATES the slim webview panel, as a percent of your workspace (10-50). With auto-size OFF (default) this only seeds a new panel and never resizes an existing one; with it ON, every open snaps back to this.</div>         <div class="ai-chip"><span class="lbl">or say to your AI</span>make the shotlog webview panel 25% wide</div>       </div>-      <span class="num-wrap" data-tooltip="Percent of the workspace width for the skinny shotlog panel (10-50). Applies to newly opened/auto-opened panels.">+      <span class="num-wrap" data-tooltip="Percent of the workspace width for the skinny shotlog panel (10-50). Seeds a new panel; only re-applied on every open when auto-size is ON.">         <input class="num-input" id="prefWidth" type="number" min="10" max="50" step="5"><span class="unit">%</span>       </span>     </div>@@ -1397,6 +1405,7 @@ header {       hook.setAttribute('aria-checked', String(!!p.read_hook));       $('prefAlerts').setAttribute('aria-checked', String(p.update_alerts!==false));       $('prefAutoswitch').setAttribute('aria-checked', String(p.update_autoswitch!==false));+      $('prefAutosize').setAttribute('aria-checked', String(p.webview_autosize===true));       APREFS=p;       seg.querySelectorAll('button').forEach(b=>b.classList.toggle('on', b.dataset.v===p.default_canvas));       $('prefWidth').value = p.webview_width ?? 20;@@ -1422,6 +1431,10 @@ header {       const now=$('prefAutoswitch').getAttribute('aria-checked')==='true';       patch({update_autoswitch: !now}, !now?'auto-switch ON: updated channels come to the front.':'auto-switch OFF: tabs flash + badge, you stay put.');     });+    $('prefAutosize').addEventListener('click',()=>{+      const now=$('prefAutosize').getAttribute('aria-checked')==='true';+      patch({webview_autosize: !now}, !now?'auto-size ON: the webview column snaps to your width on every open.':'auto-size OFF: shotlog leaves the webview column at the width you set.');+    });     seg.addEventListener('click',e=>{       const b=e.target.closest('button[data-v]'); if(!b) return;       patch({default_canvas: b.dataset.v}, 'channels now default to '+b.dataset.v+'.');
src/main.rs+7−1
@@ -468,13 +468,19 @@ async fn main() {             } else {                 // Webview is ALWAYS the managed slim panel at the preferred                 // width - an unmanaged add-tab once landed at 50% of the screen.+                let ratio_flag_set = ratio.is_some();                 let ratio = ratio.unwrap_or_else(|| {                     prefs.as_ref()                         .and_then(|v| v.get("webview_width").and_then(|w| w.as_u64()))                         .map(|w| (w as f64 / 100.0).clamp(0.10, 0.50))                         .unwrap_or(0.2)                 });-                open::open_webview(&channel, name.as_deref(), icon.as_deref(), port, true, ratio);+                // An explicit --ratio on the CLI is a deliberate size request, so+                // honor it (autosize); otherwise follow the human's pref (default off).+                let autosize = ratio_flag_set || prefs.as_ref()+                    .and_then(|v| v.get("webview_autosize").and_then(|w| w.as_bool()))+                    .unwrap_or(false);+                open::open_webview(&channel, name.as_deref(), icon.as_deref(), port, true, ratio, autosize);                 if swap {                     if open::close_pup_tab(&channel) {                         ok(&format!("closed the pup tab for '{channel}' - webview is now this channel's canvas."));
src/open.rs+7−1
@@ -16,6 +16,10 @@ pub fn open_webview(     port: u16,     skinny: bool,     ratio: f64,+    // Re-force the column to `ratio` on every open. When false, we only set the+    // width when CREATING the panel (initial split) and never resize an existing+    // one, so the human's manual column width sticks. Default off (see AppPrefs).+    autosize: bool, ) {     let default_name = "Shotlog".to_string();     let name = display_name.unwrap_or(&default_name);@@ -88,7 +92,9 @@ pub fn open_webview(     report(out, name, channel, &url);      // Skinny + an existing split → resize so the shotlog pane is ~`ratio` wide.-    if skinny {+    // Only when AUTOSIZE is on: otherwise the human's manually-set column width+    // is respected and never snapped back on each open (their explicit request).+    if skinny && autosize {         if let Some((split_id, side)) = find_enclosing_split(root, &leaf_id) {             // ratio is the FIRST child's fraction. If shotlog is the second             // (right) child, first gets (1 - ratio); if it's the first, it gets ratio.
src/server.rs+13−9
@@ -460,7 +460,7 @@ async fn post_show(     });     let _ = state.store.ctrl_tx.send(frame.to_string());     println!("{} DECISION show '{}' consolidated into the open {desired} window ({} viewer(s)) - no new surface.", ts(), req.channel, infos.len());-    notify_surface(&req.channel, state.port, &infos, &prefs.default_canvas, prefs.update_alerts, prefs.webview_width as f64 / 100.0);+    notify_surface(&req.channel, state.port, &infos, &prefs.default_canvas, prefs.update_alerts, prefs.webview_width as f64 / 100.0, prefs.webview_autosize);     Json(serde_json::json!({         "ok": true, "consolidated": true, "viewers": infos.len(), "surfaces": surfaces,     }))@@ -523,6 +523,7 @@ struct PrefsPatch {     update_alerts: Option<bool>,     webview_width: Option<u8>,     update_autoswitch: Option<bool>,+    webview_autosize: Option<bool>, }  /// Patch global preferences from the viewer's Preferences dialog.@@ -561,6 +562,9 @@ async fn set_prefs(     if let Some(a) = patch.update_autoswitch {         p.update_autoswitch = a;     }+    if let Some(a) = patch.webview_autosize {+        p.webview_autosize = a;+    }     if let Some(w) = patch.webview_width {         if !(10..=50).contains(&w) {             return Json(serde_json::json!({"ok": false, "error": "webview_width must be 10-50 (percent)"}));@@ -818,7 +822,7 @@ fn debounce_ok(key: &str, secs: u64) -> bool { ///    hinted to flash the pup taskbar. ///  - **nobody** watching → force-open a slim ~20% webview, unless ///    `SHOTLOG_NO_FORCE_OPEN` is set.-fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], pref: &str, alerts: bool, ratio: f64) {+fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], pref: &str, alerts: bool, ratio: f64, autosize: bool) {     // Pup viewers get an automatic ORANGE TASKBAR FLASH (FlashWindowEx) on their     // hosting window — a gentle "update" cue, never a foreground steal. Works when     // the viewer URL carried ?session=<pup sessionId> (reported in the WS hello).@@ -856,9 +860,9 @@ fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo],         // a pup channel whose viewer blipped must NOT get a webview shoved at it.         println!("{} DECISION force-open '{channel}' canvas={pref} (nobody watching)", ts());         if pref == "pup" {-            force_open_pup(channel, ratio);+            force_open_pup(channel, ratio, autosize);         } else {-            force_open_viewer(channel, port, ratio);+            force_open_viewer(channel, port, ratio, autosize);         }     } }@@ -866,7 +870,7 @@ fn notify_surface(channel: &str, port: u16, infos: &[crate::store::ViewerInfo], /// Force-open the PUP canvas for a channel (its chosen canvas) when a shot /// lands with nobody watching. Runs via a self-subprocess (open_pup can exit). /// If pup genuinely cannot open, falls back to a webview - never invisible.-fn force_open_pup(channel: &str, fallback_ratio: f64) {+fn force_open_pup(channel: &str, fallback_ratio: f64, autosize: bool) {     // Debounce longer than the whole retry window so attempts never overlap.     if !debounce_ok(&format!("openpup:{channel}"), 240) {         return;@@ -899,7 +903,7 @@ fn force_open_pup(channel: &str, fallback_ratio: f64) {         }         println!("{} WARN: pup never came up for '{channel}' - falling back to a webview so the shots aren't invisible (no choice recorded).", ts());         set_notice("Tried to open pup (your default) - it was unreachable, so I fell back to this webview so shots stay visible. I'll keep retrying and move you back to pup automatically once it's up.");-        crate::open::open_webview(&channel, None, None, 8820, true, fallback_ratio);+        crate::open::open_webview(&channel, None, None, 8820, true, fallback_ratio, autosize);     }); } @@ -1020,7 +1024,7 @@ fn bring_webview_forward(channel: &str, port: u16, alerts: bool) { /// ~20%-wide right-side panel so cross-thread shots auto-appear without taking /// over the workspace — and CRUCIALLY passes a --panel-id (via open_webview) so /// the tab is actually CREATED, not silently skipped.-fn force_open_viewer(channel: &str, port: u16, ratio: f64) {+fn force_open_viewer(channel: &str, port: u16, ratio: f64, autosize: bool) {     if std::env::var("VSCODE_PROXY_URI").is_err() {         return; // not in an Adom container; nothing to open     }@@ -1030,7 +1034,7 @@ fn force_open_viewer(channel: &str, port: u16, ratio: f64) {     let channel = channel.to_string();     // Best-effort; don't block the inject response on the adom-cli round-trip.     std::thread::spawn(move || {-        crate::open::open_webview(&channel, None, None, port, true, ratio);+        crate::open::open_webview(&channel, None, None, port, true, ratio, autosize);     }); } @@ -1201,7 +1205,7 @@ async fn post_shots(     }     let surfacing = Some(serde_json::json!({"actions": actions}));     let ratio = (app_prefs.webview_width as f64 / 100.0).clamp(0.10, 0.50);-    notify_surface(&req.channel, state.port, &infos, &canvas_pref, alerts_on, ratio);+    notify_surface(&req.channel, state.port, &infos, &canvas_pref, alerts_on, ratio, app_prefs.webview_autosize);      // Progressive mini-skill hints — taught at the moment they're relevant, then     // quiet (hint_gate caps how many times each lesson repeats).
src/store.rs+8−1
@@ -124,9 +124,16 @@ pub struct AppPrefs {     /// that just received a shot (vs sit where you are with a badge + flash).     #[serde(default = "default_true")]     pub update_autoswitch: bool,+    /// Re-force the webview panel to `webview_width` every time shotlog opens+    /// it. OFF (default): shotlog leaves the column at whatever width the human+    /// last set it to; the width pref only seeds a brand-new panel. ON: every+    /// open snaps the column back to the pref width.+    #[serde(default = "default_false")]+    pub webview_autosize: bool, } fn default_canvas() -> String { "webview".into() } fn default_true() -> bool { true }+fn default_false() -> bool { false } fn default_max_shots() -> usize { 200 } fn default_retention_days() -> u64 { 30 } fn default_webview_width() -> u8 { 20 }@@ -134,7 +141,7 @@ impl Default for AppPrefs {     fn default() -> Self { Self { default_canvas: default_canvas(), read_hook: true,         max_shots: default_max_shots(), retention_days: default_retention_days(),         update_alerts: true, webview_width: default_webview_width(),-        update_autoswitch: true } }+        update_autoswitch: true, webview_autosize: false } } }  /// The flag file the read-hook script checks; present = hook disabled.