From c748e43851544597f6a755ad731957bb42697ca5 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Sat, 9 May 2026 19:16:03 -0400 Subject: [PATCH 1/4] fix(icons): replace scaffold icons with Saar branded set [GET-39] Toolbar showed Chrome's default puzzle on v1.0.0 installs because public/icon/{16,32,48,96,128}.png were the WXT scaffold placeholders, not the Saar terracotta-S used in the CWS listing. Regenerated all five sizes from SVG sources via rsvg-convert. Used the existing icon.svg (S plus SAAR wordmark) for 48/96/128 to match the CWS listing. Added icon-mark.svg (S only, larger glyph, no wordmark) for 16/32 because the wordmark renders as an illegible smear at that size; the toolbar reads as a clean orange S instead. Built manifest now binds icons[16..128] to the new PNGs. --- docs/cws-assets/icon-mark.svg | 11 +++++++++++ public/icon/128.png | Bin 3074 -> 3730 bytes public/icon/16.png | Bin 559 -> 443 bytes public/icon/32.png | Bin 916 -> 900 bytes public/icon/48.png | Bin 1334 -> 1257 bytes public/icon/96.png | Bin 2366 -> 2616 bytes 6 files changed, 11 insertions(+) create mode 100644 docs/cws-assets/icon-mark.svg diff --git a/docs/cws-assets/icon-mark.svg b/docs/cws-assets/icon-mark.svg new file mode 100644 index 0000000..ce37fef --- /dev/null +++ b/docs/cws-assets/icon-mark.svg @@ -0,0 +1,11 @@ + + + S + diff --git a/public/icon/128.png b/public/icon/128.png index 9e35d130796978714468fa149241172744bf3992..3e42eb992dcd419c3d3d9afe41a545d89cb2b4dc 100644 GIT binary patch literal 3730 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 delta 3068 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 LNkvXXu0mjflP=2K delta 534 zcmV+x0_pv`1Fr;-B!3BTNLh0L01FcU01FcV0GgZ_00001b5ch_0Itp)=>Px#1ZP1_ zK>z@;j|==^1poj6#7RU!RCoc6Q%y1hQ4sEL#7~ulxq)o`RdE7wgE)ZX*)Ue(1mOgc zm9Uy{0J%YqV3c5)af4VWtGxF0Fls17FXnY#cYpc%>wb`L)_)kf@byKnRdgL9BcAPx z)vHta#e(k_EN+QtB~a;#B7vY4-MPwd0I*M%0Ii$vMXQ0J*IDiEx*HeHO#2~|QUWDP zMwmjovlOSF45Is5Q{nqwq^gh#fRPv6S}@*-M+8d*2rWit4ITS_^ytbtKuJ{`srD|` zy4ycdB$KV&w|_!E_BS&24U;R^pbXJl`G*;GfNcmCK3lMl0-HrzlZ@@G=fbi@q^3H1 z8y-Bk>ZD>QQnp<--S!4Mjcf|0_UyFw>5k3|J^y9Vpx(D#$yzYAMJ45!LiwjLH{}tQ zqPyBLHCW_Tm2}#Z3g2l5mhUKLT%sUNTM%yENoVcycz+TA_vDZogcL8Ho5xYfE(6te zv3QTmMRX?y44j-T$Gsv5`_4A?!~bgggL(@p29~hV(0hMfd<*vP6z@6zoiWJvfLfT( zx8RIkXC;3NI^^t{j8`m=*&z@lUjVW_$aekXb*VntVbj^auW>@}anp*yqw|$G&H>&4 Yr{U7xuMZ*^fdBvi07*qoM6N<$f+I2a(*OVf diff --git a/public/icon/32.png b/public/icon/32.png index f51ce1b5c93db65ffd15c7440f98c9ff06e2d498..0ff91053ba493a13ba01d3ab4c10c35ab23a9664 100644 GIT binary patch delta 877 zcmV-z1Csod2ZRTZB!32COGiWi{{a60|De66lK=n%IY~r8R9J<@m(6b*MHt3^GwZGG zUE7IG-NcS#m68?|A~h`nEL9*tNFYJ~0LX=2_z-_UPe@$2R6<-hpj_ZUt6q9*)go1t zN+3ZI)Q3{3$dPDbxB2Ra?e*HuZg)7uaT}UQW17UzX*KUV&wumGt9fU31RacxjfGv; z{Rns;m;h|H77gGUaMH4@=~AiG1`q(bTy7)?f^)#ZEf(DjMJeUG<#M?qIxGKe2gSQ4 zEXyhwkx1lI;Da3&2N?D|Z$T=hrurbhhN+McV!YQ`d*fjWLpE94#8)s|Z}aoK!*bJS za<@gP+QRVy9e=6`kW6wRs8zTvRnXhx>ew(lq6oz9wod|PvslkhTY(BeE zp;T>aafWoo0j3V5cr%yayBl}-VYb>GKR;XLz5OYU?j2kul|q|S>Lw2EGI(n=fgdO? z&Q;gN6mYt9k2zk)J+bldfCeXSM*prDcXKz-itvt{c@7VyXo&~Rs z#Caz_K!4UY*Tq~v;G4ge_z7*Rye68nQ9v!wxxdb)Mp~tHq-OH;mnF`+QkkV4>*)FdEyXORI0d zl!8|?u}$M%ptxMA0T4C>x@VEL0WVM#hGUOyynjcrb_153hi$o~223gVa6C8F2cjUz19y>wPqET?!$-@1uc}(z2{+px6iTVltVWe)MsA7MR>A@wLaP0Z^;e z>iK;B+g7W!2&8~PK>te9g9H2loJb~Px#1ZP1_ zK>z@;j|==^1poj8DM>^@RCoc!S4~nAK@@(kCs-;A;|;>nf>gl?3@0G$B^efy=n79j zasqH;4Y0_72MC@ZxdBm#E*WnS7nTbv{d_%>nd$jSG7JfRRez~Ucfam_)BWD}zDCH1 z!#lJ1=QkYzLKTQg1TaFtD>Sfey|Nw13Cv>jc4%%Q$sQXQ)5Zy*b+&%lh8&pdK=X%L zqOt7p-_p#5NNihWyASt-m}fv^KK5X9bks(t8ERmU(cy_w{V!^GVze8#nbXUtV?69Dh7p#qEX~Q9q>RVv)i?9v{+DOO!Au3^3Zbt(FuMbifq(XB^VwG zNk3E8XMYODgzu&W$~4d_0OGBEGh1q?RcQa&5|F!(TVx;?h2y@o4=A@LOl^ T19~-w00000NkvXXu0mjfD<+i1 diff --git a/public/icon/48.png b/public/icon/48.png index cb7a4494a5eb5d54abdf1903c6895d9fef60f55f..a3fe9f31813cc54a8b1617a261bfe639223bf25a 100644 GIT binary patch delta 1237 zcmV;`1SFRUF4Z=k%7g z>+ZG|HreeEwhV*wPvQe%7+IKqUrd~kjQC_?&^Mob)x<=3&}R)X@ks-TQ6E)6aq&+y z0_qD243KtZP}z43L0Qp)-%8; zunXt`;*I*<0cLE>w(H$o{+mRbfPd{>arX5m$dw)5{w>R_;{h<^ zcpS}4^Jl)oTid(oh!|SaN$N7Rx6iMGzh0fG1z*pVa+O2p3VNODvK2EVo6U6qINsvJ z`~5_T>}(Y&Kx)b(EdtinMe}enwrD?3DSpV7^f}gLr#RmYHUWOj{A!1pHH~vh%)H zqJ}@IoiT$U%Vcn4oRAP?XR1_nMO}xmEK8f!RD2z;KhjmZpzE<+#gTN8FV9agU3GQ2 z)GGi$3c;@KR-WC|MqgXxKka$j;r&ySoGaQ|{C{Z|P)}c5gyB>hk8h0AV%%6SW*v_= zeoB+8I7|F`d1$)^8d0!aPUdF$CS4?INc!9T83{{)5b*o8a??JvETen-5mb5n#o`Jin#mw$=5s@d69^+7b@waIHr`)l+PU8cSAT@Kfr# z?SJQezEaiUT9*L-^`)&zj3s743W(2-{2vKphyz*e@qN()g`WRs;n3{9> z>Qb?Brxl{*exQB_I-D|DA2s;o%r!1f&jJ4F*+4SJYXe$XeDap{bO34aOrFf&I-| z3Ie6nhY~mxbRermN_kiSSeA7ZcrxI4R)i4ZcqWtCEdgBD-4Emfj%g+2g%JB;ei@yd zoIJ0T+5_YR4QqMil~N<=bowmR)lPwIHajMz98^ku7g%Vw=UXY|j)nh80F574rBbP< zfKjE?E?^UD^dxf`lz>Y@h%w+$CX+dKYlnXU-L;NeZ)~7~00000NkvXXu0mjfUZz{w delta 1315 zcmV+;1>E}S3APH5B!3BTNLh0L01FcU01FcV0GgZ_00001b5ch_0Itp)=>Px#1ZP1_ zK>z@;j|==^1poj9%Sl8*RCodHT0wT(HV~ZwwMo< z{22)0j@ZAli#Ip&84}hAsEPnZ0vICTIil>g)=nMB5eRp{-n;rUl5nIZozTq{LL5B* z*?A2)AhQ9D%laV^98KF_fMq|gbh0<75!N|tS6;g z9j_=BaTw}E;DX0ics46r6@nK}-~G1r@mijsfD-v{i+=*OYkUt0;8k!b;6|SD{1GDE zy!%p;y*H@4XtA3`Rx}Q`E~IEtO7Yj^VM_$Zj=Xw0*xi|?vuD6KFsJAAML@#H`Qy^A zru8d~*+L$gZnAsy`H!3U1jILxI1za!K)8?$c3G-L8-3b+lr#`yXp#L8r}o2nAS>+( zAffr%qJJl^kd=0YWCa;)P`l}(CqIGzr9)DKcld`%V}zz8iT;Bn0wr98u7Q%2ZZ<8n zO892LvY{Ek`e*aW;PJkqHCPQ4_XayBjkk8qbQ4*7Q2bcd_ZA*6bU*l~?vQ{u8KLQ< z6c-|!cj?{=t%jzPrilSHsHbE$tO%4&nz&TK*MB>Kn@}}yZ|UZmlwLZ3yn({y_xC54 zU@7EOHsmS9Ox%wtj5-FXeFu?9FB4(G0#0YAVJ z!GA7u5oszWvJhu!E5kerg!mxRG>kPsB|1^1%GuQgED7ViD4Mn5xzPX>^^#Dlq6y2& zr9hhoKkd~D4qTq;78-+{P&!FoekyB*^2tIH9kueDB|Z$!M~-AwuYjY=+i<_YfnldV zmOHa1f<<7&=L~{ETFA6n@z?h>S|R0eUVqV+gJ=8YlG02Qm|TwA6~|HkC$f-|8N$Mi zmvb(OXN6Y*m$dtWH`+;?VFkFPLUhKqy`Eb#@%5?_^Dhe*yimOoLs!9bboBGb<2EWgNA%UMah2BDtIxaxg`A$37Gwi z3`wgP2t=OzX*1ZSn2cFTOF`q3B7a}+^rG#EB2j-C$|~4rDvhoAGr1@OO2Pi^N;kHK z|71{qA96S36HTf1nb0gBaFKb-q<8q)Wk`U5Mv3!JsKJ!Llmig|{?`gPQPn>@48Ai~ z??4K2FD)WqrXqmSJFT^`-buopHnKdR2ti1B(Eeq*U%9M=0p6t5!3M|Py&Uhc8Pz*U Z_yT_&D{uF$1FQf5002ovPDHLkV1n;BZQ}p{ diff --git a/public/icon/96.png b/public/icon/96.png index c28ad52d56fc409195f52f57c447442326d94b6b..ff99b9958af8095fe0da2f980d6fe75874f73730 100644 GIT binary patch delta 2607 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 Z{s+z4`hnYx@$LWs002ovPDHLkV1mpyZZ7}; From d181623be348ca99dd6fbfcba8965420333646f9 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Sat, 9 May 2026 20:43:28 -0400 Subject: [PATCH 2/4] fix(overlay): suppress in-page widget when side panel is visible [GET-41] The side panel and the in-page overlay were rendering the same data (cost, health, weekly bar, totals, coaching) at the same time, with the overlay overlapping the composer textarea and chat content. Both DDIA's "two views of one truth" rule and basic UX hierarchy say to pick one canonical surface per context, not to reposition the second one. So when the side panel is open the overlay now steps aside. Mechanism: side panel App.tsx opens a chrome.runtime.connect port on mount and reconnects on disconnect (rides through SW idle restarts). Background's onConnect handler mirrors port lifetime into chrome.storage.session as sidePanelVisible. The content script reads the flag on overlay setup and subscribes to onChanged for live updates, calling overlay.setSuppressed. Subtle bug found during E2E verification: chrome.storage.session defaults to TRUSTED_CONTEXTS, which silently excludes content scripts (reads return empty, onChanged never fires). Background now calls setAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' }) at module load so the signal actually reaches the overlay. Overlay refactor: render() and the new setSuppressed both go through applyVisibility(), which reconciles a one-way shouldShow latch with the externally-toggled suppressed flag into the actual display style. Suppression always wins over the data-arrival latch. Standalone case (no side panel) also fixed: bottom anchor lowered from 88px (composer level) to 16px (viewport corner), so the overlay no longer overlaps the textarea on its own. --- entrypoints/background.ts | 28 ++++++++++++++++++++++++++++ entrypoints/claude-ai.content.ts | 15 +++++++++++++++ entrypoints/sidepanel/App.tsx | 30 +++++++++++++++++++++++++++++- ui/overlay-styles.ts | 6 +++++- ui/overlay.ts | 28 +++++++++++++++++++++++++--- 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/entrypoints/background.ts b/entrypoints/background.ts index bded995..e892971 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -85,6 +85,16 @@ if (typeof chrome !== 'undefined' && chrome.sidePanel) { chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }); } +// chrome.storage.session defaults to TRUSTED_CONTEXTS, which excludes content +// scripts entirely (reads return empty, onChanged never fires). The in-page +// overlay reads sidePanelVisible from session storage to decide whether to +// suppress itself, so we widen access to all contexts here. Persists across +// service-worker restarts; safe to call on every boot. +if (typeof chrome !== 'undefined' && chrome.storage?.session?.setAccessLevel) { + chrome.storage.session.setAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' }) + .catch((err: unknown) => console.error('[LCO-ERROR] Failed to expose session storage to content scripts:', err)); +} + // Storage Helpers /** Build the per-tab storage key for token state */ @@ -499,6 +509,24 @@ export default defineBackground({ } }); + // Side panel visibility signal. The side panel page connects on mount + // and its port disconnects when Chrome destroys the page (close pin, close + // window). We mirror the connection state into chrome.storage.session so + // the in-page overlay (claude-ai.content.ts) can suppress itself while + // the side panel is showing the same data with more detail. + // Service-worker termination also fires onDisconnect; the side panel + // reconnects on its end, which re-fires onConnect here and restores the + // flag within ~100ms. + browser.runtime.onConnect.addListener((port) => { + if (port.name !== 'side-panel') return; + browser.storage.session.set({ sidePanelVisible: true }) + .catch((err) => console.error('[LCO-ERROR] Failed to mark side panel visible:', err)); + port.onDisconnect.addListener(() => { + browser.storage.session.set({ sidePanelVisible: false }) + .catch((err) => console.error('[LCO-ERROR] Failed to mark side panel hidden:', err)); + }); + }); + // Detect when a tab navigates away from claude.ai (logout, redirect). // Full cleanup: finalize active conversation and clear all tab storage. browser.tabs.onUpdated.addListener((tabId, changeInfo) => { diff --git a/entrypoints/claude-ai.content.ts b/entrypoints/claude-ai.content.ts index 4c1455a..958cd25 100644 --- a/entrypoints/claude-ai.content.ts +++ b/entrypoints/claude-ai.content.ts @@ -752,6 +752,21 @@ async function initializeMonitoring(): Promise { const shadow = host.attachShadow({ mode: 'closed' }); overlay.mount(shadow); + // Side panel visibility: background writes sidePanelVisible to + // chrome.storage.session via its onConnect listener. Suppress the in-page + // overlay while the side panel is showing the same data, so the user + // isn't shown two competing views. Read once on startup, then watch for + // changes for the rest of the page lifetime. + browser.storage.session.get('sidePanelVisible') + .then((data) => { overlay.setSuppressed(data.sidePanelVisible === true); }) + .catch(() => { /* non-critical: default unsuppressed if read fails */ }); + browser.storage.onChanged.addListener((changes, areaName) => { + if (areaName !== 'session') return; + if ('sidePanelVisible' in changes) { + overlay.setSuppressed(changes.sidePanelVisible.newValue === true); + } + }); + // "Start fresh" flow: build handoff summary, copy to clipboard, navigate to new chat. overlay.onStartFresh(async () => { try { diff --git a/entrypoints/sidepanel/App.tsx b/entrypoints/sidepanel/App.tsx index e440a6f..901a04b 100644 --- a/entrypoints/sidepanel/App.tsx +++ b/entrypoints/sidepanel/App.tsx @@ -15,7 +15,7 @@ // the budget data to explain why the section is empty. Today and History are // org-scoped historical data and remain visible at all times. -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDashboardData } from './hooks/useDashboardData'; import Header from './components/Header'; import CollapsibleSection from './components/CollapsibleSection'; @@ -46,6 +46,34 @@ export default function App() { // toggles state and renders nothing. const [settingsOpen, setSettingsOpen] = useState(false); + // Hold a port to the background while this side panel page is alive. + // Background mirrors the connection state into chrome.storage.session as + // sidePanelVisible; the in-page overlay watches that flag and suppresses + // itself while we're showing the same data here in more detail. + // The port disconnects automatically when Chrome destroys this page + // (panel close, window close); React cleanup is a belt-and-suspenders + // disconnect for normal unmount paths. Reconnects on disconnect to ride + // through service-worker idle restarts. + useEffect(() => { + let port: chrome.runtime.Port | null = null; + let alive = true; + + const connect = () => { + if (!alive) return; + port = chrome.runtime.connect({ name: 'side-panel' }); + port.onDisconnect.addListener(() => { + if (alive) setTimeout(connect, 100); + }); + }; + + connect(); + + return () => { + alive = false; + port?.disconnect(); + }; + }, []); + if (loading) { return (
diff --git a/ui/overlay-styles.ts b/ui/overlay-styles.ts index ce2f177..98d071e 100644 --- a/ui/overlay-styles.ts +++ b/ui/overlay-styles.ts @@ -83,7 +83,11 @@ export const OVERLAY_CSS = ` .lco-widget { position: fixed; - bottom: 88px; + /* 88px sat at the level of claude.ai's composer textarea; 16px puts the + widget in the empty viewport corner below the composer footer text. + The "two surfaces showing the same data" overlap is handled separately + by setSuppressed() in ui/overlay.ts (driven by side panel state). */ + bottom: 16px; right: 16px; z-index: 2147483647; min-width: 210px; diff --git a/ui/overlay.ts b/ui/overlay.ts index 459dbad..a03955f 100644 --- a/ui/overlay.ts +++ b/ui/overlay.ts @@ -19,6 +19,10 @@ export interface OverlayHandle { hideNudge(): void; /** Register the callback for the "Start fresh" button. Called once at setup. */ onStartFresh(callback: () => void): void; + /** Force-hide the overlay regardless of render state. Used when the side + * panel is open: it surfaces the same data with more detail, so the + * in-page widget steps aside to avoid duplicated views and overlap. */ + setSuppressed(value: boolean): void; } function fmt(n: number): string { @@ -138,6 +142,21 @@ export function createOverlay(): OverlayHandle { let elWeeklyLabel: HTMLElement | null = null; let elWeeklyEta: HTMLElement | null = null; + // Visibility state. shouldShow is a one-way latch flipped true by render() + // on first data arrival; suppressed is toggled externally by setSuppressed + // to defer to the side panel when it's open. applyVisibility reconciles + // both into the actual display style. + let shouldShow = false; + let suppressed = false; + function applyVisibility(): void { + if (!overlayWidget) return; + overlayWidget.style.display = (shouldShow && !suppressed) ? '' : 'none'; + } + function setSuppressed(value: boolean): void { + suppressed = value; + applyVisibility(); + } + function mount(shadow: ShadowRoot): void { const style = document.createElement('style'); style.textContent = OVERLAY_CSS; @@ -424,8 +443,11 @@ export function createOverlay(): OverlayHandle { if (!overlayWidget) return; // Reveal widget on first data arrival or when draft estimate is available. - if ((state.lastRequest !== null || state.draftEstimate !== null) && overlayWidget.style.display === 'none') { - overlayWidget.style.display = ''; + // Routed through applyVisibility so suppressed (side panel open) wins + // over the data-arrival latch. + if (state.lastRequest !== null || state.draftEstimate !== null) { + shouldShow = true; + applyVisibility(); } // Draft estimate: pre-submit cost preview. @@ -677,5 +699,5 @@ export function createOverlay(): OverlayHandle { startFreshCallback = callback; } - return { mount, render, showNudge, hideNudge, onStartFresh }; + return { mount, render, showNudge, hideNudge, onStartFresh, setSuppressed }; } From 0eec957e01c772389df1f3bdb2029878ef00877e Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Mon, 11 May 2026 00:23:24 -0400 Subject: [PATCH 3/4] fix(ticker): cap bar width and pad empty slots so single turn renders cleanly [GET-40] With one tracked turn, the flex-based row stretched that bar to fill 100% of the width AND 100% of the height (it is its own peak), which read as a solid orange wall instead of a per-turn histogram. Switched the bars container to a 12-column CSS grid (minmax(2px, 1fr)) so each bar is always 1/12 of the row regardless of count. Padded the remaining positions with faint .lco-ticker-slot rails so the row reads "this will fill in over time" instead of leaving raw negative space beside the real bar. Peak normalization for bar height is intentionally preserved. The relative-magnitude story (turn 5 was the biggest of the run) is the whole point of the ticker; the broken visual was width, not height. Grid column count (12) mirrors maxBars in TurnTicker.tsx; comment in the CSS calls this out so a future bump keeps the two in sync. --- .../sidepanel/components/TurnTicker.tsx | 14 +++++++++++ entrypoints/sidepanel/dashboard.css | 23 +++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/entrypoints/sidepanel/components/TurnTicker.tsx b/entrypoints/sidepanel/components/TurnTicker.tsx index 9424b7d..bdb91ce 100644 --- a/entrypoints/sidepanel/components/TurnTicker.tsx +++ b/entrypoints/sidepanel/components/TurnTicker.tsx @@ -38,6 +38,13 @@ export default function TurnTicker({ turns, maxBars = 12 }: Props): React.ReactE const prev = window.length >= 2 ? window[window.length - 2] : null; const trend = computeTrend(prev?.deltaUtilization ?? null, last.deltaUtilization ?? null); + // Pad the row out to maxBars with faint placeholder slots. The grid in + // dashboard.css fixes each column to 1/maxBars of the row, so this stops + // a single tracked turn (or two) from stretching to fill the entire row + // and reading as a "solid orange wall". The empty slots also hint at + // "this fills in over time" rather than leaving raw empty space. + const emptySlots = Math.max(0, maxBars - window.length); + return (
); })} + {Array.from({ length: emptySlots }, (_, i) => ( +
{trend !== null && ( diff --git a/entrypoints/sidepanel/dashboard.css b/entrypoints/sidepanel/dashboard.css index 8176570..1c6d4a8 100644 --- a/entrypoints/sidepanel/dashboard.css +++ b/entrypoints/sidepanel/dashboard.css @@ -550,16 +550,20 @@ body { } .lco-ticker-bars { - display: flex; - align-items: flex-end; + /* 12-column grid keeps each bar at a fixed 1/12 of the row width + regardless of how many turns have been tracked. The flex-based + earlier version made a single tracked turn stretch to fill the whole + row, which read as a solid orange wall rather than a per-turn + histogram. Grid column count must match maxBars in TurnTicker.tsx. */ + display: grid; + grid-template-columns: repeat(12, minmax(2px, 1fr)); + align-items: end; gap: 3px; flex: 1; height: 100%; } .lco-ticker-bar { - flex: 1; - min-width: 4px; min-height: 2px; background: var(--lco-accent); border-radius: 1px; @@ -570,6 +574,17 @@ body { cursor: default; } +.lco-ticker-slot { + /* Placeholder rail for turn positions that haven't been filled yet. + Renders as a faint baseline so the row reads as "this will fill in + as the conversation grows" instead of empty negative space beside + the real bars. */ + height: 2px; + background: var(--lco-border); + border-radius: 1px; + opacity: 0.6; +} + .lco-ticker-bar:focus-visible { outline: 2px solid var(--lco-accent); outline-offset: 1px; From 1592bbcfb4af06f4f1e41409cb3dd14f91ea22a8 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Mon, 11 May 2026 00:29:56 -0400 Subject: [PATCH 4/4] chore(release): v1.0.1 hotfix [GET-39, GET-40, GET-41] Bumps package.json (and via WXT, the built manifest) for the post-CWS hotfix bundle: - GET-39: replace WXT scaffold icons with Saar branded set so the Chrome toolbar shows the Saar S instead of the default puzzle. - GET-41: suppress the in-page overlay when the side panel is visible. Two surfaces showing the same data is a duplicate-view problem, not a positioning problem; the overlay defers to the panel. Standalone case (no side panel) also moves the overlay anchor from composer level to viewport corner. - GET-40: fix the TurnTicker rendering as a solid orange wall when only one turn is tracked. Bars now sit in a 12-column grid with faint placeholder rails for the remaining positions. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 951f9e9..21dd295 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "saar", "description": "Saar: real-time AI usage tracker for Claude", "private": true, - "version": "1.0.0", + "version": "1.0.1", "type": "module", "engines": { "node": ">=20.0.0"