ccdeck 3.36.8 → 3.36.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/cli/leaving.js +54 -0
- package/bin/cli/uninstall.js +9 -0
- package/dist/web/assets/{BrowserWatchModal-CeVGLslQ.js → BrowserWatchModal-BCrDvuug.js} +1 -1
- package/dist/web/assets/{UsageHistoryModal-BcUzGS2-.js → UsageHistoryModal-BtRCuo33.js} +1 -1
- package/dist/web/assets/{index-4ecbC_Cy.js → index-tVBMXvDv.js} +3 -3
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/release-notes.json +2 -1
- package/src/server/reports.mjs +34 -1
package/README.md
CHANGED
|
@@ -208,7 +208,7 @@ Quota is the one thing that is not just reading. It needs a live token, so when
|
|
|
208
208
|
|
|
209
209
|
It never steers an agent or edits your code, but it is not read-only either — besides the hook entry and its own event log, it manages the two tools it leans on, and it refreshes the Codex token it reads quota with, rewriting `~/.codex/auth.json` the way `codex` itself does. It also reads your browser's history, because Browser Watch is on unless you switch it off; that is its own section below, because it is the one thing here that is about you rather than about an agent.
|
|
210
210
|
|
|
211
|
-
What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for `macmon`, and, while the page is open, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (`status.claude.com`, `status.openai.com`), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. The [desktop app](#desktop-app) also asks this repository's GitHub releases for its own updates. With **Local network** on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see [Local network](#local-network). The deck also sends usage reports to `api.ccdeck.dev`: an install event, an update event, one "active" a day, one "activated" when a new install's first session arrives (how long after the install, in rough buckets such as "under five minutes", and whether it was a Claude or a Codex session), and the errors it runs into with folders, addresses and keys scrubbed out. Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), the runtime, your IP address and a device fingerprint (a stable hashed machine id), and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions — and whether the Claude hooks went in and Codex is being watched, with the daily "active" also carrying how many sessions, subagents and projects you ran on the last day you used the deck, that day's date, and which of the deck's own features you used that day — fixed names such as "usage history" or "account switch", a yes for each and nothing about what you did with them. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. The deck has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now can be seen. **Send feedback** (the topbar's Feedback button) sends what you write, and any screenshots you add, to the same place, where the people who make ccdeck read it and may open a public issue from it, never with the contact you gave or your screenshots. `AGENTS_DECK_NO_INSTALL=1` turns off everything but the quota reads; `AGENTS_DECK_NO_DOWNLOAD=1` is the narrower version — no `uv` binary is fetched, the managed installs stay.
|
|
211
|
+
What does go out is short and ordinary: a ~20-byte version check against the npm registry (plus one small request to confirm a version it has not seen before), installs and daily version checks for the two tools the deck manages (claude-swap from PyPI, ccusage from npm), on an Apple Silicon Mac whose sensors stay silent one release lookup and one binary download from GitHub for `macmon`, and, while the page is open, quota reads to Anthropic and OpenAI signed with your own credentials — that is where those numbers live. While the page is open, and while the desktop app has a session running or waiting, it also reads Anthropic's and OpenAI's public status pages (`status.claude.com`, `status.openai.com`), unsigned, about every three minutes, and once a minute at most while a page is not answering, so an outage on their side shows as a chip in the topbar instead of looking like a fault on yours. The [desktop app](#desktop-app) also asks this repository's GitHub releases for its own updates. With **Local network** on, which it is unless you switch it off, the deck also announces itself to other decks on your local network over UDP (port 45317) and pairs with the ones that answer — see [Local network](#local-network). The deck also sends usage reports to `api.ccdeck.dev`: an install event, an update event, one "active" a day, one "uninstall" when you run `ccdeck --uninstall` (which asks, at a terminal, one skippable question about why, from a short list), one "activated" when a new install's first session arrives (how long after the install, in rough buckets such as "under five minutes", and whether it was a Claude or a Codex session), and the errors it runs into with folders, addresses and keys scrubbed out. Each carries the version, your OS and CPU architecture, the channel (desktop, npm or a source checkout), the runtime, your IP address and a device fingerprint (a stable hashed machine id), and the machine's rough shape — the number of CPU cores, the amount of RAM, the locale, the shell, the terminal, and the Claude Code and Codex versions — and whether the Claude hooks went in and Codex is being watched, with the daily "active" also carrying how many sessions, subagents and projects you ran on the last day you used the deck, that day's date, and which of the deck's own features you used that day — fixed names such as "usage history" or "account switch", a yes for each and nothing about what you did with them. The fingerprint is a one-way hash of stable machine traits, never those traits in the clear; everything else is coarse counts and short tokens — no path, no project name, no free text. These reports are no longer anonymous: the install id is random, but the fingerprint follows this machine and the server records the IP the report arrives from. What still never leaves is your sessions, prompts, files, project names and paths. The deck has no switch for them; `AGENTS_DECK_NO_REPORTS=1` keeps them off from the first start. While the deck runs it also sends a tiny heartbeat every few minutes, the install id and nothing else, so the count of decks running right now can be seen. **Send feedback** (the topbar's Feedback button) sends what you write, and any screenshots you add, to the same place, where the people who make ccdeck read it and may open a public issue from it, never with the contact you gave or your screenshots. `AGENTS_DECK_NO_INSTALL=1` turns off everything but the quota reads; `AGENTS_DECK_NO_DOWNLOAD=1` is the narrower version — no `uv` binary is fetched, the managed installs stay.
|
|
212
212
|
|
|
213
213
|
## Browser Watch
|
|
214
214
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// The one question `ccdeck --uninstall` asks, and only of a person at a
|
|
2
|
+
// terminal: why are they leaving. Skippable with Enter, given up after half a
|
|
3
|
+
// minute, and never asked at all when reports are off — the answer would have
|
|
4
|
+
// nowhere to go. The answer is one of a short fixed list (UNINSTALL_REASONS in
|
|
5
|
+
// reports.mjs), so nothing typed ever leaves.
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { PRODUCT } from "../../src/server/brand.mjs";
|
|
8
|
+
|
|
9
|
+
/** The answers, in the order they are offered, with the words they are shown in. */
|
|
10
|
+
export const REASONS = Object.freeze([
|
|
11
|
+
["not-useful", "It wasn't useful to me"],
|
|
12
|
+
["too-noisy", "Too noisy or distracting"],
|
|
13
|
+
["broken", "Something didn't work"],
|
|
14
|
+
["other-tool", "I use something else"],
|
|
15
|
+
["privacy", "Privacy"],
|
|
16
|
+
["other", "Another reason"],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/** Ask, and answer with a reason's token or null — for a skip, a timeout, an
|
|
20
|
+
* answer that is not on the list, or no terminal to ask on. Never throws. */
|
|
21
|
+
export async function askWhy({ input = process.stdin, output = process.stdout, timeoutMs = 30_000 } = {}) {
|
|
22
|
+
if (!input?.isTTY || !output?.isTTY) return null;
|
|
23
|
+
let rl;
|
|
24
|
+
try {
|
|
25
|
+
output.write(`\n${PRODUCT}: one question before you go, and you can skip it: why are you uninstalling?\n`);
|
|
26
|
+
REASONS.forEach(([, label], i) => output.write(` ${i + 1} ${label}\n`));
|
|
27
|
+
rl = createInterface({ input, output });
|
|
28
|
+
const answer = await rl.question("Number, or Enter to skip: ", { signal: AbortSignal.timeout(timeoutMs) });
|
|
29
|
+
const n = Number.parseInt(String(answer).trim(), 10);
|
|
30
|
+
return REASONS[n - 1]?.[0] ?? null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
} finally {
|
|
34
|
+
rl?.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Say the install is leaving, with the reason if one is given. Only when a
|
|
40
|
+
* report would go out anyway: reports off, or the machine vetoing them, means
|
|
41
|
+
* no question and nothing sent. `reporter` and `ask` are parameters so the
|
|
42
|
+
* suite runs this without a network or a terminal.
|
|
43
|
+
*/
|
|
44
|
+
export async function sayGoodbye({ reporter, ask = askWhy, out = line => console.log(line) } = {}) {
|
|
45
|
+
try {
|
|
46
|
+
if (!reporter || !(await reporter.willReport())) return false;
|
|
47
|
+
const reason = await ask();
|
|
48
|
+
const sent = await reporter.reportUninstall(reason);
|
|
49
|
+
if (sent && reason) out(`${PRODUCT}: thank you, that helps.`);
|
|
50
|
+
return sent;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/bin/cli/uninstall.js
CHANGED
|
@@ -12,6 +12,7 @@ import { PRODUCT } from "../../src/server/brand.mjs";
|
|
|
12
12
|
import { COMMAND, PKG_ROOT } from "./package.js";
|
|
13
13
|
import { G } from "./screen.js";
|
|
14
14
|
import { removedRecord } from "./login-item.js";
|
|
15
|
+
import { sayGoodbye } from "./leaving.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Remove this deck's hooks and its login item, stop every deck still running
|
|
@@ -177,6 +178,14 @@ export async function uninstall(flags) {
|
|
|
177
178
|
if (named.size > 0) {
|
|
178
179
|
console.error(`${PRODUCT}: repair the JSON (or move the file aside), then run \`${COMMAND} --uninstall\` again.`);
|
|
179
180
|
}
|
|
181
|
+
|
|
182
|
+
// Last, once everything above is done, and only while reports are on: the
|
|
183
|
+
// usage reports hear that this install left, with a reason if the person at
|
|
184
|
+
// the terminal picks one. See bin/cli/leaving.js.
|
|
185
|
+
{
|
|
186
|
+
const { reporter } = await import(pathToFileURL(join(PKG_ROOT, "src/server/reports.mjs")).href);
|
|
187
|
+
await sayGoodbye({ reporter });
|
|
188
|
+
}
|
|
180
189
|
// Non-zero when any half of it refused, so `ccdeck --uninstall && …` and every
|
|
181
190
|
// CI step that runs this stops on the failure instead of continuing past it.
|
|
182
191
|
return refused ? 1 : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{j as e,r as c,k as _,l as V,m as Y,n as z,s as X,u as Z,g as ee}from"./index-4ecbC_Cy.js";function se({snap:s}){return e.jsxs("section",{className:"bw-feed",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Live activity",e.jsxs("span",{className:"bw-feed-count",children:[s.log.length," ",s.log.length===1?"event":"events"]})]}),e.jsx("p",{className:"bw-since bw-feed-note",children:"Pages you opened yourself — counted to run the rule, never written down. Addresses appear under Findings, and only for pages a program opened."}),e.jsx("div",{className:"bw-log","aria-live":"polite","aria-relevant":"additions",children:s.log.length===0?e.jsx("p",{className:"bw-note",children:"The deck writes a line here each time it looks at a profile."}):s.log.map(t=>e.jsxs("div",{className:`bw-log-line ${t.level}${t.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(t.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),t.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:t.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:t.parts.profile,children:t.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[t.parts.value,t.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",t.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:t.text})]},`${t.atMs}-${t.level}-${t.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function te(s){const t=l=>new Date(l).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?t(s.startMs):`${t(s.startMs)} → ${t(s.endMs)}`}function ne(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const t=Math.round((s.endMs-s.startMs)/6e4);return t<1?"":`${t} min`}function ae({snap:s,dismiss:t,refreshRef:l}){const[r,h]=c.useState(null),o=c.useRef(new Map),a=c.useRef(null);c.useEffect(()=>{var i,w;const d=a.current;!d||s.episodes.some(m=>E(m)===d.id)||(a.current=null,_(((i=document.activeElement)==null?void 0:i.tagName)??null)&&((w=(d.next?o.current.get(d.next):void 0)??l.current)==null||w.focus()))},[s]);const n=d=>{a.current={id:E(d),next:V(s.episodes.map(E),E(d))},t(d)},u=c.useMemo(()=>{const d=[];for(const i of s.episodes){const w=ne(i.startMs),m=d[d.length-1];m&&m.label===w?m.episodes.push(i):d.push({label:w,episodes:[i]})}return d},[s]);return e.jsxs("section",{className:"bw-findings",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Findings",s.episodes.length>0&&e.jsxs("span",{className:"bw-feed-count",children:[s.episodes.length," ",s.episodes.length===1?"episode":"episodes"]})]}),e.jsx("div",{className:"bw-eps",children:u.length===0?e.jsxs("div",{className:"bw-empty",children:[e.jsx("p",{className:"bw-empty-head",children:"Nothing found yet"}),e.jsxs("p",{className:"bw-empty-note",children:["An episode lands here when a program opens pages in a browser nobody has touched for"," ",s.settings.quietMinutes," ",s.settings.quietMinutes===1?"minute":"minutes",". On most machines that is rare, so an empty list is the ordinary result rather than a sign something is wrong."]}),!s.settings.enabled&&e.jsx("p",{className:"bw-empty-note",children:"Watching is off, so this shows only what this deck has seen since it started. Anything an earlier run recorded comes back when you switch it on."})]}):u.map(d=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:d.label}),d.episodes.map(i=>{const w=E(i),m=r===w;return e.jsxs("div",{className:`bw-ep${m?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:b=>{b?o.current.set(w,b):o.current.delete(w)},onClick:()=>h(m?null:w),"aria-expanded":m,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:m?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:i.host}),e.jsxs("span",{className:"bw-ep-meta",children:[te(i),U(i)&&e.jsxs(e.Fragment,{children:[" · ",U(i)]}),i.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[i.count," ",i.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>n(i),"aria-label":`Dismiss ${i.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),m&&e.jsx("ul",{className:"bw-urls",children:i.urls.map((b,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(b.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:b.url})]},`${b.url}-${b.timeMs}-${y}`))})]},w)})]},d.label))})]})}function re(s){const t=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!t)return!1;const l=parseInt(t[1],16),r=[l>>16&255,l>>8&255,l&255].map(h=>{const o=h/255;return o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4)});return .2126*r[0]+.7152*r[1]+.0722*r[2]>.5}const G=1e4,ie=12e3;function oe({browsers:s,findings:t,watching:l,palette:r}){const h=c.useRef(null),o=c.useRef(null);return o.current={browsers:s,findings:t,watching:l},c.useEffect(()=>{var m;const a=h.current;if(!a)return;const n=a.getContext("2d");if(!n)return;const u=((m=window.matchMedia)==null?void 0:m.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let d=0,i=!1;const w=()=>{var q;if(i)return;const b=o.current;if(!b)return;const{browsers:y,findings:p,watching:P}=b,R=r["--accent"]||"#7dd3fc",f=r["--line"]||"#1f2229",j=r["--text-dim"]||"#6e727c",C=re(r["--bg"]),N=C?42:26,v=C?.85:.65,I=Date.now(),$=Math.min(window.devicePixelRatio||1,2),W=a.clientWidth,k=a.clientHeight;(a.width!==W*$||a.height!==k*$)&&(a.width=W*$,a.height=k*$),n.setTransform($,0,0,$,0,0),n.clearRect(0,0,W,k);const x=W/2,M=k/2,S=Math.min(W,k)/2-6;n.strokeStyle=f,n.lineWidth=1,n.globalAlpha=C?1:.9;for(const g of[.4,.7,1])n.beginPath(),n.arc(x,M,S*g,0,Math.PI*2),n.stroke();n.beginPath(),n.moveTo(x-S,M),n.lineTo(x+S,M),n.moveTo(x,M-S),n.lineTo(x,M+S),n.stroke(),n.globalAlpha=1;const A=(P&&!u?I%G/G:0)*Math.PI*2-Math.PI/2;if(P){const g=(q=n.createConicGradient)==null?void 0:q.call(n,A-.85,x,M);g&&(g.addColorStop(0,"transparent"),g.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${R} ${N}%, transparent)`),g.addColorStop(1,"transparent"),n.fillStyle=g,n.beginPath(),n.moveTo(x,M),n.arc(x,M,S,A-.85,A),n.closePath(),n.fill()),n.strokeStyle=R,n.globalAlpha=v,n.beginPath(),n.moveTo(x,M),n.lineTo(x+Math.cos(A)*S,M+Math.sin(A)*S),n.stroke(),n.globalAlpha=1}y.forEach((g,T)=>{const B=T/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=g.lastReadMs===null?1:Math.min(1,(I-g.lastReadMs)/12e4),O=S*(.42+D*.5),F=x+Math.cos(B)*O,L=M+Math.sin(B)*O,Q=Math.abs((A-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=P?Math.max(0,1-Q/.55):0;n.beginPath(),n.arc(F,L,3+K*1.6,0,Math.PI*2),n.fillStyle=g.running?R:j,n.globalAlpha=g.running?.55+K*.45:.35,n.fill(),n.globalAlpha=1});for(const g of p){const T=(I-g.atMs)/ie;if(T<0||T>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===g.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=x+Math.cos(D)*S*.6,F=M+Math.sin(D)*S*.6;n.beginPath(),n.arc(O,F,4+T*22,0,Math.PI*2),n.strokeStyle=R,n.globalAlpha=(1-T)*.8,n.lineWidth=1.5,n.stroke(),n.globalAlpha=1}d=requestAnimationFrame(w)};return w(),()=>{i=!0,cancelAnimationFrame(d)}},[r]),e.jsx("canvas",{ref:h,className:"bw-radar",role:"img","aria-label":l?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function le(s,t,l){if(s===null)return null;const r=s+t-l;return r>0?r:null}function ce(s){const t=Math.ceil(s/1e3),l=Math.floor(t/60),r=t%60;return l>0?`${l}m ${r}s`:`${r}s`}function J(s){return(s??[]).filter(t=>t.installed&&t.profiles>0)}function de(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function he(s,t){return s.map(l=>({key:l.key,name:l.name,running:l.running,visits:t.filter(r=>r.browser===l.key).reduce((r,h)=>r+h.programVisits,0)}))}function ue(s,t){if(s===null)return null;const l=Math.max(0,Math.round((t-s)/1e3));if(l<5)return"just now";if(l<60)return`${l} sec ago`;const r=Math.round(l/60);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`}function we(s){const t=s.profiles.filter(r=>r.degraded);if(t.length>0){const r=t[0],h=t.length-1;return{kind:"unreadable",text:`${r.name}/${r.profile} could not be read — ${r.reason??"no reason given"}`+(h>0?`, and ${h} more`:"")+". The watch continues on the profiles it can read."}}const l=J(s.browsers);return l.length===0?{kind:"no-profiles",text:"No browser on this machine has a profile to read. Open one of the browsers below once and it will be watched from then on."}:l.some(r=>r.running)?null:{kind:"none-running",text:"No watched browser is running, so nothing can be navigated right now. Their history is still read, and anything found while they were open is still here."}}function me({snap:s,watching:t,palette:l}){const[r,h]=c.useState(!1),[o,a]=c.useState(()=>Date.now());c.useEffect(()=>{const u=setInterval(()=>a(Date.now()),1e3);return()=>clearInterval(u)},[]);const n=le(s.coverage.lastHumanMs,s.coverage.quietMs,o);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Activity overview",e.jsx("button",{className:"bw-help",onClick:()=>h(u=>!u),"aria-expanded":r,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(oe,{browsers:t.map(u=>{var d;return{key:u.key,name:u.name,running:u.running,lastReadMs:((d=s.profiles.find(i=>i.browser===u.key))==null?void 0:d.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(u=>({browser:u.browser??null,atMs:u.endMs})),watching:s.settings.enabled,palette:l}),e.jsx("ul",{className:"bw-counts",children:he(t,s.profiles).map(u=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:u.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:u.name})]},u.key))})]}),e.jsx("p",{className:"bw-since",children:"Pages a program opened, since this deck started"}),s.coverage.checkedMs>0&&e.jsxs("p",{className:"bw-since",children:["Last checked ",ue(s.coverage.checkedMs,o),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),n!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",ce(n)]}),r&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function be({watching:s,rest:t}){const[l,r]=c.useState(!1),[h,o]=c.useState(!1);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Watched profiles",e.jsx("button",{className:"bw-help",onClick:()=>r(a=>!a),"aria-expanded":l,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),l&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Running"}),e.jsx("dd",{children:"the browser has a process on this machine right now"}),e.jsx("dt",{children:"Connected"}),e.jsx("dd",{children:"an open connection to an address Anthropic's relay uses — which it shares with claude.ai, so an open tab looks the same as an agent channel"})]}),e.jsx("ul",{className:"bw-profiles",children:s.map(a=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${a.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:a.name}),e.jsx("span",{className:"bw-prof-state",children:a.running?"running":"idle"}),a.running&&a.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},a.key))}),t.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," ",t.length," not watched"]}),h&&e.jsxs("p",{className:"bw-rest",children:[t.map(a=>a.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function fe({snap:s,quiet:t,setQuiet:l,save:r,access:h,setAccess:o}){return e.jsxs("div",{className:"bw-settings",children:[e.jsxs("label",{title:"A program opening a page counts as a finding only if nobody had touched the browser for this long. Shorter catches more and reports more of your own work; longer is quieter.",children:[e.jsx("span",{children:"Nobody browsing for"}),e.jsxs("select",{value:t??s.settings.quietMinutes,onChange:a=>{l(Number(a.target.value)),r({quietMinutes:Number(a.target.value)})},children:[e.jsx("option",{value:1,children:"1 min"}),e.jsx("option",{value:5,children:"5 min"}),e.jsx("option",{value:15,children:"15 min"}),e.jsx("option",{value:30,children:"30 min"}),e.jsx("option",{value:60,children:"60 min"})]})]}),e.jsxs("label",{children:[e.jsx("span",{children:"When it finds one"}),e.jsx("select",{value:s.settings.reaction,onChange:a=>void r({reaction:a.target.value}),disabled:!s.settings.enabled,title:s.settings.enabled?"What to do besides writing it down.":"Turn watching on to arm a reaction.",children:(s.reactions??["notify"]).map(a=>e.jsx("option",{value:a,children:a==="notify"?"notify me":a==="close-tab"?"close the tab":"quit the browser"},a))})]}),e.jsxs("div",{className:"bw-settings-note",children:[e.jsxs("p",{children:["Chrome marks a navigation that came from an extension or a command rather than from a click. These are the ones that happened while nobody had touched the browser for the time above. ",e.jsx("strong",{children:"Usually that is your own agent doing what you asked."})]}),e.jsxs("button",{className:"bw-why",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," What Browser Watch can access"]}),h&&e.jsxs("dl",{className:"bw-access",children:[e.jsx("dt",{children:"Reads"}),e.jsxs("dd",{children:["A copy of each browser's own history database — the live file is locked while the browser holds it. Only rows newer than the moment this deck started:"," ",new Date(s.coverage.startedMs).toLocaleString(),"."]}),e.jsx("dt",{children:"Keeps"}),e.jsxs("dd",{children:["Only while the switch is on, and only the episodes it flagged — never your ordinary browsing. In ",e.jsx("code",{className:"bw-path",children:s.coverage.logPath}),typeof s.coverage.logBytes=="number"?` (${de(s.coverage.logBytes)})`:"",", with every address written in full so you can check it yourself."]}),e.jsx("dt",{children:"Sends"}),e.jsx("dd",{children:"Nothing. No part of this reads or writes over the network; the deck serves on 127.0.0.1 and this panel talks only to it."}),e.jsx("dt",{children:"Never reads"}),e.jsx("dd",{children:"Anything from before this deck started, cookies, saved passwords, page contents, or any browser profile with no history file."})]})]})]})}function H(s,t){return t?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function ge({snap:s,saving:t,save:l,why:r,setWhy:h}){return e.jsxs("footer",{className:"bw-status",children:[e.jsx("span",{className:`bw-mode-dot${s.settings.enabled?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-mode-word",children:H(s,t).word},H(s,t).kind),e.jsx("span",{className:"bw-status-text",children:s.coverage.archived===0?s.settings.enabled?"No episodes recorded yet":"No episodes on disk":`${s.coverage.archived} ${s.coverage.archived===1?"episode":"episodes"} on disk`}),e.jsx("button",{className:`glyph-btn bw-gear${r?" on":""}`,onClick:()=>h(o=>!o),"aria-expanded":r,"aria-label":"Settings",title:"Settings",children:e.jsxs("svg",{width:"13",height:"13",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.3","aria-hidden":!0,children:[e.jsx("circle",{cx:"7",cy:"7",r:"2.1"}),e.jsx("path",{d:"M7 1.2v1.6M7 11.2v1.6M1.2 7h1.6M11.2 7h1.6M2.9 2.9l1.1 1.1M10 10l1.1 1.1M11.1 2.9L10 4M4 10l-1.1 1.1"})]})}),e.jsxs("label",{className:"bw-switch",htmlFor:"bw-enabled",children:[e.jsx("span",{className:"bw-switch-label",id:"bw-enabled-label",children:"Watch browser activity"}),e.jsx("button",{id:"bw-enabled",type:"button",role:"switch","aria-checked":s.settings.enabled,"aria-labelledby":"bw-enabled-label",className:"switch",onClick:()=>void l({enabled:!s.settings.enabled}),title:s.settings.enabled?"On — every episode it finds is written down, so the list outlives the browsing history being cleared":"Off — showing only what this deck has seen since it started. Anything an earlier run archived is hidden until you switch back on, and nothing new is kept",children:e.jsx("span",{className:"switch-knob"})})]})]})}function pe({relay:s}){const[t,l]=c.useState(!1),[r,h]=c.useState(!1),o=s.killswitch.blocked?"unblock":"block",a=s.command[o],n=c.useCallback(async()=>{await z(a.command)&&(h(!0),window.setTimeout(()=>h(!1),1600))},[a.command]),u=s.profiles.filter(i=>{var w;return(w=i.report)==null?void 0:w.present}),d=s.profiles.filter(i=>i.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(i=>e.jsx("code",{className:"bw-path",children:i},i))]}),e.jsxs("button",{className:"bw-why",onClick:()=>l(i=>!i),"aria-expanded":t,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:t?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),t&&e.jsxs("div",{className:"bw-rc-body",children:[u.length>0&&e.jsx("dl",{className:"bw-key",children:u.map(i=>e.jsxs(c.Fragment,{children:[e.jsxs("dt",{children:[i.name,i.profile==="Default"?"":` · ${i.profile}`]}),e.jsxs("dd",{children:[i.report.enabled?"enabled":"installed but switched off",i.report.allUrls?", can act on every site":", limited to the sites it was granted",i.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",i.report.sensitiveApis.join(", ")]})]})]},`${i.browser}/${i.profile}`))}),d.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[d.map(i=>i.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),u.some(i=>i.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:a.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:n,children:r?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:a.note})]})]})}function xe(s){const[t,l]=c.useState(null),[r,h]=c.useState(null),[o,a]=c.useState(null),[n,u]=c.useState(!1),d=c.useRef(!1),[i,w]=c.useState(null),[m,b]=c.useState(!1),y=c.useRef(!1),p=c.useCallback(async f=>{var j,C;if(!(f&&!X(d.current))){f&&(d.current=!0,u(!0));try{const N=await fetch(`/api/browser-watch${f?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const v=await N.json();l(v),y.current||w(((j=v==null?void 0:v.settings)==null?void 0:j.quietMinutes)??15),s(((C=v==null?void 0:v.settings)==null?void 0:C.enabled)===!0),h(null)}catch(N){h(N instanceof Error?N.message:String(N))}finally{f&&(d.current=!1,u(!1))}}},[s]);c.useEffect(()=>{p(!1)},[p]),c.useEffect(()=>{const f=setInterval(()=>{p(!1)},1e4);return()=>clearInterval(f)},[p]);const P=c.useCallback(async f=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:f.host,startMs:f.startMs})})}finally{await p(!1)}},[p]),R=c.useCallback(async f=>{y.current=!0,b(!0);try{const j=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)});if(!j.ok)throw new Error(`the deck answered ${j.status}`);await p(!1),a(null)}catch(j){a(j instanceof Error?j.message:String(j))}finally{y.current=!1,b(!1)}},[p]);return{snap:t,error:r,writeError:o,setWriteError:a,busy:n,quiet:i,setQuiet:w,saving:m,load:p,dismiss:P,save:R}}function ye({onClose:s,onSeen:t,onWatching:l,palette:r}){const h=Z(s),{snap:o,error:a,writeError:n,setWriteError:u,busy:d,quiet:i,setQuiet:w,saving:m,load:b,dismiss:y,save:p}=xe(l),[P,R]=c.useState(!1),[f,j]=c.useState(!1),C=c.useRef(null),N=c.useRef(t);N.current=t,c.useEffect(()=>()=>N.current(Date.now()),[]);const v=J(o==null?void 0:o.browsers),I=((o==null?void 0:o.browsers)??[]).filter(k=>!(k.installed&&k.profiles>0)),$=o?we(o):null,W=v.reduce((k,x)=>k+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:e.jsxs("div",{ref:h,className:"modal bw-modal",onClick:k=>k.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"bw-title",children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"bw-title",children:"Browser watch"}),e.jsx("span",{className:"modal-tool-id",children:o?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:C,className:"glyph-btn",onClick:()=>void b(!0),...ee(d),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:d?"…":"↻"}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]})]}),e.jsxs("div",{className:"modal-body bw-body",children:[!o&&!a&&e.jsxs("div",{className:"bw-loading",children:[e.jsx("span",{className:"bw-loading-bar","aria-hidden":!0}),e.jsx("p",{children:"Reading each browser's history. The first look copies the file, which takes a moment."})]}),a&&e.jsx("div",{className:"bw-state",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not read"}),e.jsx("span",{className:"bw-row-detail",children:a})]})}),o&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),o&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(me,{snap:o,watching:v,palette:r}),e.jsx(be,{watching:v,rest:I}),o.relay&&e.jsx(pe,{relay:o.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(ae,{snap:o,dismiss:y,refreshRef:C}),e.jsx(se,{snap:o})]})]}),o&&P&&e.jsx(fe,{snap:o,quiet:i,setQuiet:w,save:p,access:f,setAccess:j}),n&&e.jsx("div",{className:"bw-state bw-write-err",role:"alert",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not save that setting"}),e.jsxs("span",{className:"bw-row-detail",children:[n," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>u(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),o&&e.jsx(ge,{snap:o,saving:m,save:p,why:P,setWhy:R})]})]})})}export{ye as default};
|
|
1
|
+
import{j as e,r as c,k as _,l as V,m as Y,n as z,s as X,u as Z,g as ee}from"./index-tVBMXvDv.js";function se({snap:s}){return e.jsxs("section",{className:"bw-feed",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Live activity",e.jsxs("span",{className:"bw-feed-count",children:[s.log.length," ",s.log.length===1?"event":"events"]})]}),e.jsx("p",{className:"bw-since bw-feed-note",children:"Pages you opened yourself — counted to run the rule, never written down. Addresses appear under Findings, and only for pages a program opened."}),e.jsx("div",{className:"bw-log","aria-live":"polite","aria-relevant":"additions",children:s.log.length===0?e.jsx("p",{className:"bw-note",children:"The deck writes a line here each time it looks at a profile."}):s.log.map(t=>e.jsxs("div",{className:`bw-log-line ${t.level}${t.parts?"":" sys"}`,children:[e.jsx("span",{className:"bw-log-time",children:new Date(t.atMs).toLocaleTimeString("en-GB",{hour12:!1})}),t.parts?e.jsxs(e.Fragment,{children:[e.jsx("span",{className:"bw-log-browser",children:t.parts.browser}),e.jsx("span",{className:"bw-log-profile",title:t.parts.profile,children:t.parts.profile}),e.jsxs("span",{className:"bw-log-value",children:[t.parts.value,t.parts.flagged>0&&e.jsxs("span",{className:"bw-log-flag",children:[" · ",t.parts.flagged," flagged"]})]})]}):e.jsx("span",{className:"bw-log-sys",children:t.text})]},`${t.atMs}-${t.level}-${t.text}`))})]})}const E=s=>`${s.host}-${s.startMs}`;function te(s){const t=l=>new Date(l).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1});return s.count===1||s.endMs-s.startMs<6e4?t(s.startMs):`${t(s.startMs)} → ${t(s.endMs)}`}function ne(s){return new Date(s).toLocaleDateString(void 0,{weekday:"short",day:"numeric",month:"short"})}function U(s){const t=Math.round((s.endMs-s.startMs)/6e4);return t<1?"":`${t} min`}function ae({snap:s,dismiss:t,refreshRef:l}){const[r,h]=c.useState(null),o=c.useRef(new Map),a=c.useRef(null);c.useEffect(()=>{var i,w;const d=a.current;!d||s.episodes.some(m=>E(m)===d.id)||(a.current=null,_(((i=document.activeElement)==null?void 0:i.tagName)??null)&&((w=(d.next?o.current.get(d.next):void 0)??l.current)==null||w.focus()))},[s]);const n=d=>{a.current={id:E(d),next:V(s.episodes.map(E),E(d))},t(d)},u=c.useMemo(()=>{const d=[];for(const i of s.episodes){const w=ne(i.startMs),m=d[d.length-1];m&&m.label===w?m.episodes.push(i):d.push({label:w,episodes:[i]})}return d},[s]);return e.jsxs("section",{className:"bw-findings",children:[e.jsxs("h4",{className:"bw-sec-head bw-head-row",children:["Findings",s.episodes.length>0&&e.jsxs("span",{className:"bw-feed-count",children:[s.episodes.length," ",s.episodes.length===1?"episode":"episodes"]})]}),e.jsx("div",{className:"bw-eps",children:u.length===0?e.jsxs("div",{className:"bw-empty",children:[e.jsx("p",{className:"bw-empty-head",children:"Nothing found yet"}),e.jsxs("p",{className:"bw-empty-note",children:["An episode lands here when a program opens pages in a browser nobody has touched for"," ",s.settings.quietMinutes," ",s.settings.quietMinutes===1?"minute":"minutes",". On most machines that is rare, so an empty list is the ordinary result rather than a sign something is wrong."]}),!s.settings.enabled&&e.jsx("p",{className:"bw-empty-note",children:"Watching is off, so this shows only what this deck has seen since it started. Anything an earlier run recorded comes back when you switch it on."})]}):u.map(d=>e.jsxs("div",{className:"bw-day",children:[e.jsx("h4",{className:"bw-sec-head",children:d.label}),d.episodes.map(i=>{const w=E(i),m=r===w;return e.jsxs("div",{className:`bw-ep${m?" open":""}`,children:[e.jsxs("div",{className:"bw-ep-row",children:[e.jsxs("button",{className:"bw-ep-head",ref:b=>{b?o.current.set(w,b):o.current.delete(w)},onClick:()=>h(m?null:w),"aria-expanded":m,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:m?"▾":"▸"}),e.jsx("span",{className:"bw-ep-host",children:i.host}),e.jsxs("span",{className:"bw-ep-meta",children:[te(i),U(i)&&e.jsxs(e.Fragment,{children:[" · ",U(i)]}),i.provisional&&e.jsxs(e.Fragment,{children:[" · ",e.jsx("span",{title:"Its quiet window has not closed yet. If somebody used the browser in the minutes after it, it is withdrawn; until then it is not written down and no reaction runs.",children:"provisional"})]})]}),e.jsxs("span",{className:"bw-ep-count",children:[i.count," ",i.count===1?"page":"pages"]})]}),e.jsx("button",{className:"glyph-btn bw-ep-x",onClick:()=>n(i),"aria-label":`Dismiss ${i.host}`,title:"Dismiss — it leaves this list for good, and stays in the log file",children:"×"})]}),m&&e.jsx("ul",{className:"bw-urls",children:i.urls.map((b,y)=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-url-time",children:new Date(b.timeMs).toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",hour12:!1})}),e.jsx("span",{className:"bw-url",children:b.url})]},`${b.url}-${b.timeMs}-${y}`))})]},w)})]},d.label))})]})}function re(s){const t=/^#?([0-9a-f]{6})$/i.exec((s??"").trim());if(!t)return!1;const l=parseInt(t[1],16),r=[l>>16&255,l>>8&255,l&255].map(h=>{const o=h/255;return o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4)});return .2126*r[0]+.7152*r[1]+.0722*r[2]>.5}const G=1e4,ie=12e3;function oe({browsers:s,findings:t,watching:l,palette:r}){const h=c.useRef(null),o=c.useRef(null);return o.current={browsers:s,findings:t,watching:l},c.useEffect(()=>{var m;const a=h.current;if(!a)return;const n=a.getContext("2d");if(!n)return;const u=((m=window.matchMedia)==null?void 0:m.call(window,"(prefers-reduced-motion: reduce)").matches)??!1;let d=0,i=!1;const w=()=>{var q;if(i)return;const b=o.current;if(!b)return;const{browsers:y,findings:p,watching:P}=b,R=r["--accent"]||"#7dd3fc",f=r["--line"]||"#1f2229",j=r["--text-dim"]||"#6e727c",C=re(r["--bg"]),N=C?42:26,v=C?.85:.65,I=Date.now(),$=Math.min(window.devicePixelRatio||1,2),W=a.clientWidth,k=a.clientHeight;(a.width!==W*$||a.height!==k*$)&&(a.width=W*$,a.height=k*$),n.setTransform($,0,0,$,0,0),n.clearRect(0,0,W,k);const x=W/2,M=k/2,S=Math.min(W,k)/2-6;n.strokeStyle=f,n.lineWidth=1,n.globalAlpha=C?1:.9;for(const g of[.4,.7,1])n.beginPath(),n.arc(x,M,S*g,0,Math.PI*2),n.stroke();n.beginPath(),n.moveTo(x-S,M),n.lineTo(x+S,M),n.moveTo(x,M-S),n.lineTo(x,M+S),n.stroke(),n.globalAlpha=1;const A=(P&&!u?I%G/G:0)*Math.PI*2-Math.PI/2;if(P){const g=(q=n.createConicGradient)==null?void 0:q.call(n,A-.85,x,M);g&&(g.addColorStop(0,"transparent"),g.addColorStop(.85/(Math.PI*2),`color-mix(in srgb, ${R} ${N}%, transparent)`),g.addColorStop(1,"transparent"),n.fillStyle=g,n.beginPath(),n.moveTo(x,M),n.arc(x,M,S,A-.85,A),n.closePath(),n.fill()),n.strokeStyle=R,n.globalAlpha=v,n.beginPath(),n.moveTo(x,M),n.lineTo(x+Math.cos(A)*S,M+Math.sin(A)*S),n.stroke(),n.globalAlpha=1}y.forEach((g,T)=>{const B=T/Math.max(y.length,1)*Math.PI*2-Math.PI/2,D=g.lastReadMs===null?1:Math.min(1,(I-g.lastReadMs)/12e4),O=S*(.42+D*.5),F=x+Math.cos(B)*O,L=M+Math.sin(B)*O,Q=Math.abs((A-B+Math.PI*3)%(Math.PI*2)-Math.PI),K=P?Math.max(0,1-Q/.55):0;n.beginPath(),n.arc(F,L,3+K*1.6,0,Math.PI*2),n.fillStyle=g.running?R:j,n.globalAlpha=g.running?.55+K*.45:.35,n.fill(),n.globalAlpha=1});for(const g of p){const T=(I-g.atMs)/ie;if(T<0||T>1)continue;const D=Math.max(0,y.findIndex(L=>L.key===g.browser))/Math.max(y.length,1)*Math.PI*2-Math.PI/2,O=x+Math.cos(D)*S*.6,F=M+Math.sin(D)*S*.6;n.beginPath(),n.arc(O,F,4+T*22,0,Math.PI*2),n.strokeStyle=R,n.globalAlpha=(1-T)*.8,n.lineWidth=1.5,n.stroke(),n.globalAlpha=1}d=requestAnimationFrame(w)};return w(),()=>{i=!0,cancelAnimationFrame(d)}},[r]),e.jsx("canvas",{ref:h,className:"bw-radar",role:"img","aria-label":l?`Watching ${s.length} browser${s.length===1?"":"s"}`:"Not watching"})}function le(s,t,l){if(s===null)return null;const r=s+t-l;return r>0?r:null}function ce(s){const t=Math.ceil(s/1e3),l=Math.floor(t/60),r=t%60;return l>0?`${l}m ${r}s`:`${r}s`}function J(s){return(s??[]).filter(t=>t.installed&&t.profiles>0)}function de(s){return!Number.isFinite(s)||s<=0?"empty":Y(s)}function he(s,t){return s.map(l=>({key:l.key,name:l.name,running:l.running,visits:t.filter(r=>r.browser===l.key).reduce((r,h)=>r+h.programVisits,0)}))}function ue(s,t){if(s===null)return null;const l=Math.max(0,Math.round((t-s)/1e3));if(l<5)return"just now";if(l<60)return`${l} sec ago`;const r=Math.round(l/60);return r<60?`${r} min ago`:`${Math.round(r/60)} hr ago`}function we(s){const t=s.profiles.filter(r=>r.degraded);if(t.length>0){const r=t[0],h=t.length-1;return{kind:"unreadable",text:`${r.name}/${r.profile} could not be read — ${r.reason??"no reason given"}`+(h>0?`, and ${h} more`:"")+". The watch continues on the profiles it can read."}}const l=J(s.browsers);return l.length===0?{kind:"no-profiles",text:"No browser on this machine has a profile to read. Open one of the browsers below once and it will be watched from then on."}:l.some(r=>r.running)?null:{kind:"none-running",text:"No watched browser is running, so nothing can be navigated right now. Their history is still read, and anything found while they were open is still here."}}function me({snap:s,watching:t,palette:l}){const[r,h]=c.useState(!1),[o,a]=c.useState(()=>Date.now());c.useEffect(()=>{const u=setInterval(()=>a(Date.now()),1e3);return()=>clearInterval(u)},[]);const n=le(s.coverage.lastHumanMs,s.coverage.quietMs,o);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Activity overview",e.jsx("button",{className:"bw-help",onClick:()=>h(u=>!u),"aria-expanded":r,"aria-label":"What the sweep and the dots mean",children:"?"})]}),e.jsxs("div",{className:"bw-glance",children:[e.jsx(oe,{browsers:t.map(u=>{var d;return{key:u.key,name:u.name,running:u.running,lastReadMs:((d=s.profiles.find(i=>i.browser===u.key))==null?void 0:d.lastWrittenMs)??null}}),findings:s.episodes.slice(0,6).map(u=>({browser:u.browser??null,atMs:u.endMs})),watching:s.settings.enabled,palette:l}),e.jsx("ul",{className:"bw-counts",children:he(t,s.profiles).map(u=>e.jsxs("li",{children:[e.jsx("span",{className:"bw-count-n",children:u.visits.toLocaleString("en-US")}),e.jsx("span",{className:"bw-count-of",children:u.name})]},u.key))})]}),e.jsx("p",{className:"bw-since",children:"Pages a program opened, since this deck started"}),s.coverage.checkedMs>0&&e.jsxs("p",{className:"bw-since",children:["Last checked ",ue(s.coverage.checkedMs,o),s.coverage.checks>0&&e.jsxs(e.Fragment,{children:[" · ",s.coverage.checks.toLocaleString("en-US")," ",s.coverage.checks===1?"check":"checks"]})]}),n!==null&&e.jsxs("p",{className:"bw-gate",children:["You are browsing — a program page would count in ",ce(n)]}),r&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Sweep"}),e.jsx("dd",{children:"one poll of every watched profile"}),e.jsx("dt",{children:"Dot"}),e.jsx("dd",{children:"a browser, further out the longer since it last wrote history"}),e.jsx("dt",{children:"Ring"}),e.jsx("dd",{children:"a finding, leaving the browser it came from"})]})]})}function be({watching:s,rest:t}){const[l,r]=c.useState(!1),[h,o]=c.useState(!1);return e.jsxs("section",{className:"bw-sec",children:[e.jsxs("h4",{className:"bw-sec-head",children:["Watched profiles",e.jsx("button",{className:"bw-help",onClick:()=>r(a=>!a),"aria-expanded":l,"aria-label":"What running, idle and connected to Anthropic mean",children:"?"})]}),l&&e.jsxs("dl",{className:"bw-key",children:[e.jsx("dt",{children:"Running"}),e.jsx("dd",{children:"the browser has a process on this machine right now"}),e.jsx("dt",{children:"Connected"}),e.jsx("dd",{children:"an open connection to an address Anthropic's relay uses — which it shares with claude.ai, so an open tab looks the same as an agent channel"})]}),e.jsx("ul",{className:"bw-profiles",children:s.map(a=>e.jsxs("li",{children:[e.jsx("span",{className:`bw-prof-dot${a.running?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-prof-name",children:a.name}),e.jsx("span",{className:"bw-prof-state",children:a.running?"running":"idle"}),a.running&&a.relay.state==="live"&&e.jsx("span",{className:"bw-relay",children:"connected to Anthropic"})]},a.key))}),t.length>0&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{className:"bw-why bw-rest-head",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," ",t.length," not watched"]}),h&&e.jsxs("p",{className:"bw-rest",children:[t.map(a=>a.name).join(", ")," — not installed, or installed and never opened, so there is no history to read."]})]})]})}function fe({snap:s,quiet:t,setQuiet:l,save:r,access:h,setAccess:o}){return e.jsxs("div",{className:"bw-settings",children:[e.jsxs("label",{title:"A program opening a page counts as a finding only if nobody had touched the browser for this long. Shorter catches more and reports more of your own work; longer is quieter.",children:[e.jsx("span",{children:"Nobody browsing for"}),e.jsxs("select",{value:t??s.settings.quietMinutes,onChange:a=>{l(Number(a.target.value)),r({quietMinutes:Number(a.target.value)})},children:[e.jsx("option",{value:1,children:"1 min"}),e.jsx("option",{value:5,children:"5 min"}),e.jsx("option",{value:15,children:"15 min"}),e.jsx("option",{value:30,children:"30 min"}),e.jsx("option",{value:60,children:"60 min"})]})]}),e.jsxs("label",{children:[e.jsx("span",{children:"When it finds one"}),e.jsx("select",{value:s.settings.reaction,onChange:a=>void r({reaction:a.target.value}),disabled:!s.settings.enabled,title:s.settings.enabled?"What to do besides writing it down.":"Turn watching on to arm a reaction.",children:(s.reactions??["notify"]).map(a=>e.jsx("option",{value:a,children:a==="notify"?"notify me":a==="close-tab"?"close the tab":"quit the browser"},a))})]}),e.jsxs("div",{className:"bw-settings-note",children:[e.jsxs("p",{children:["Chrome marks a navigation that came from an extension or a command rather than from a click. These are the ones that happened while nobody had touched the browser for the time above. ",e.jsx("strong",{children:"Usually that is your own agent doing what you asked."})]}),e.jsxs("button",{className:"bw-why",onClick:()=>o(a=>!a),"aria-expanded":h,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:h?"▾":"▸"})," What Browser Watch can access"]}),h&&e.jsxs("dl",{className:"bw-access",children:[e.jsx("dt",{children:"Reads"}),e.jsxs("dd",{children:["A copy of each browser's own history database — the live file is locked while the browser holds it. Only rows newer than the moment this deck started:"," ",new Date(s.coverage.startedMs).toLocaleString(),"."]}),e.jsx("dt",{children:"Keeps"}),e.jsxs("dd",{children:["Only while the switch is on, and only the episodes it flagged — never your ordinary browsing. In ",e.jsx("code",{className:"bw-path",children:s.coverage.logPath}),typeof s.coverage.logBytes=="number"?` (${de(s.coverage.logBytes)})`:"",", with every address written in full so you can check it yourself."]}),e.jsx("dt",{children:"Sends"}),e.jsx("dd",{children:"Nothing. No part of this reads or writes over the network; the deck serves on 127.0.0.1 and this panel talks only to it."}),e.jsx("dt",{children:"Never reads"}),e.jsx("dd",{children:"Anything from before this deck started, cookies, saved passwords, page contents, or any browser profile with no history file."})]})]})]})}function H(s,t){return t?{kind:"saving",word:"Saving"}:s.settings.enabled?{kind:"on",word:"Watching"}:{kind:"off",word:"Paused"}}function ge({snap:s,saving:t,save:l,why:r,setWhy:h}){return e.jsxs("footer",{className:"bw-status",children:[e.jsx("span",{className:`bw-mode-dot${s.settings.enabled?" on":""}`,"aria-hidden":!0}),e.jsx("span",{className:"bw-mode-word",children:H(s,t).word},H(s,t).kind),e.jsx("span",{className:"bw-status-text",children:s.coverage.archived===0?s.settings.enabled?"No episodes recorded yet":"No episodes on disk":`${s.coverage.archived} ${s.coverage.archived===1?"episode":"episodes"} on disk`}),e.jsx("button",{className:`glyph-btn bw-gear${r?" on":""}`,onClick:()=>h(o=>!o),"aria-expanded":r,"aria-label":"Settings",title:"Settings",children:e.jsxs("svg",{width:"13",height:"13",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.3","aria-hidden":!0,children:[e.jsx("circle",{cx:"7",cy:"7",r:"2.1"}),e.jsx("path",{d:"M7 1.2v1.6M7 11.2v1.6M1.2 7h1.6M11.2 7h1.6M2.9 2.9l1.1 1.1M10 10l1.1 1.1M11.1 2.9L10 4M4 10l-1.1 1.1"})]})}),e.jsxs("label",{className:"bw-switch",htmlFor:"bw-enabled",children:[e.jsx("span",{className:"bw-switch-label",id:"bw-enabled-label",children:"Watch browser activity"}),e.jsx("button",{id:"bw-enabled",type:"button",role:"switch","aria-checked":s.settings.enabled,"aria-labelledby":"bw-enabled-label",className:"switch",onClick:()=>void l({enabled:!s.settings.enabled}),title:s.settings.enabled?"On — every episode it finds is written down, so the list outlives the browsing history being cleared":"Off — showing only what this deck has seen since it started. Anything an earlier run archived is hidden until you switch back on, and nothing new is kept",children:e.jsx("span",{className:"switch-knob"})})]})]})}function pe({relay:s}){const[t,l]=c.useState(!1),[r,h]=c.useState(!1),o=s.killswitch.blocked?"unblock":"block",a=s.command[o],n=c.useCallback(async()=>{await z(a.command)&&(h(!0),window.setTimeout(()=>h(!1),1600))},[a.command]),u=s.profiles.filter(i=>{var w;return(w=i.report)==null?void 0:w.present}),d=s.profiles.filter(i=>i.report===null);return e.jsxs("section",{className:"bw-sec",children:[e.jsx("h4",{className:"bw-sec-head",children:"Remote control"}),e.jsx("p",{className:`bw-rc-verdict ${s.verdict}`,role:"status",children:s.verdict==="exposed"?"Claude in Chrome is live here, so any Claude Code session signed in to your Anthropic account can drive this browser — from any machine, with no prompt on this one.":s.verdict==="protected"?`The relay is black-holed in ${s.hostsPath}, so no new connection to it can be made from this machine.`:"Claude in Chrome is not installed and enabled in any profile here, so there is nothing for the relay to drive."}),s.killswitch.foreign.length>0&&e.jsxs("p",{className:"bw-rc-foreign",children:["Another entry for this host is already in ",s.hostsPath,", and ccdeck will not touch it:",s.killswitch.foreign.map(i=>e.jsx("code",{className:"bw-path",children:i},i))]}),e.jsxs("button",{className:"bw-why",onClick:()=>l(i=>!i),"aria-expanded":t,children:[e.jsx("span",{className:"bw-chev","aria-hidden":!0,children:t?"▾":"▸"})," ",s.verdict==="protected"?"How to undo the block":"What it can reach, and how to stop it"]}),t&&e.jsxs("div",{className:"bw-rc-body",children:[u.length>0&&e.jsx("dl",{className:"bw-key",children:u.map(i=>e.jsxs(c.Fragment,{children:[e.jsxs("dt",{children:[i.name,i.profile==="Default"?"":` · ${i.profile}`]}),e.jsxs("dd",{children:[i.report.enabled?"enabled":"installed but switched off",i.report.allUrls?", can act on every site":", limited to the sites it was granted",i.report.sensitiveApis.length>0&&e.jsxs(e.Fragment,{children:[" — ",i.report.sensitiveApis.join(", ")]})]})]},`${i.browser}/${i.profile}`))}),d.length>0&&e.jsxs("p",{className:"bw-rc-note",children:[d.map(i=>i.name).join(", ")," — its settings file could not be read, so nothing is claimed about it either way."]}),!s.hostsRead&&e.jsxs("p",{className:"bw-rc-note",children:[s.hostsPath," could not be read, so whether the relay is already blocked is unknown."]}),u.some(i=>i.report.allUrls)&&e.jsxs("p",{className:"bw-rc-note",children:["Narrowing the sites in ",e.jsx("code",{className:"bw-path",children:"chrome://extensions"})," does not change this: the grant is held at the browser level and the per-site list is enforced inside the extension, by the extension."]}),e.jsxs("div",{className:"bw-rc-cmd",children:[e.jsx("code",{children:a.command}),e.jsx("button",{className:"btn bw-rc-copy",onClick:n,children:r?"Copied":"Copy"})]}),e.jsx("p",{className:"bw-rc-note",children:a.note})]})]})}function xe(s){const[t,l]=c.useState(null),[r,h]=c.useState(null),[o,a]=c.useState(null),[n,u]=c.useState(!1),d=c.useRef(!1),[i,w]=c.useState(null),[m,b]=c.useState(!1),y=c.useRef(!1),p=c.useCallback(async f=>{var j,C;if(!(f&&!X(d.current))){f&&(d.current=!0,u(!0));try{const N=await fetch(`/api/browser-watch${f?"?refresh=1":""}`);if(!N.ok)throw new Error(`the deck answered ${N.status}`);const v=await N.json();l(v),y.current||w(((j=v==null?void 0:v.settings)==null?void 0:j.quietMinutes)??15),s(((C=v==null?void 0:v.settings)==null?void 0:C.enabled)===!0),h(null)}catch(N){h(N instanceof Error?N.message:String(N))}finally{f&&(d.current=!1,u(!1))}}},[s]);c.useEffect(()=>{p(!1)},[p]),c.useEffect(()=>{const f=setInterval(()=>{p(!1)},1e4);return()=>clearInterval(f)},[p]);const P=c.useCallback(async f=>{try{await fetch("/api/browser-watch/dismiss",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({host:f.host,startMs:f.startMs})})}finally{await p(!1)}},[p]),R=c.useCallback(async f=>{y.current=!0,b(!0);try{const j=await fetch("/api/browser-watch",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(f)});if(!j.ok)throw new Error(`the deck answered ${j.status}`);await p(!1),a(null)}catch(j){a(j instanceof Error?j.message:String(j))}finally{y.current=!1,b(!1)}},[p]);return{snap:t,error:r,writeError:o,setWriteError:a,busy:n,quiet:i,setQuiet:w,saving:m,load:p,dismiss:P,save:R}}function ye({onClose:s,onSeen:t,onWatching:l,palette:r}){const h=Z(s),{snap:o,error:a,writeError:n,setWriteError:u,busy:d,quiet:i,setQuiet:w,saving:m,load:b,dismiss:y,save:p}=xe(l),[P,R]=c.useState(!1),[f,j]=c.useState(!1),C=c.useRef(null),N=c.useRef(t);N.current=t,c.useEffect(()=>()=>N.current(Date.now()),[]);const v=J(o==null?void 0:o.browsers),I=((o==null?void 0:o.browsers)??[]).filter(k=>!(k.installed&&k.profiles>0)),$=o?we(o):null,W=v.reduce((k,x)=>k+x.profiles,0);return e.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:e.jsxs("div",{ref:h,className:"modal bw-modal",onClick:k=>k.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"bw-title",children:[e.jsxs("header",{className:"modal-head",children:[e.jsxs("div",{className:"modal-title",children:[e.jsx("span",{className:"modal-tool-name",id:"bw-title",children:"Browser watch"}),e.jsx("span",{className:"modal-tool-id",children:o?`${W} ${W===1?"profile":"profiles"} · local only`:"local only"})]}),e.jsxs("div",{className:"modal-actions",children:[e.jsx("button",{ref:C,className:"glyph-btn",onClick:()=>void b(!0),...ee(d),"aria-label":"Re-read every profile now",title:"Re-read every profile now",children:d?"…":"↻"}),e.jsx("button",{className:"glyph-btn",onClick:s,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]})]}),e.jsxs("div",{className:"modal-body bw-body",children:[!o&&!a&&e.jsxs("div",{className:"bw-loading",children:[e.jsx("span",{className:"bw-loading-bar","aria-hidden":!0}),e.jsx("p",{children:"Reading each browser's history. The first look copies the file, which takes a moment."})]}),a&&e.jsx("div",{className:"bw-state",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not read"}),e.jsx("span",{className:"bw-row-detail",children:a})]})}),o&&$&&e.jsx("p",{className:`bw-trouble ${$.kind}`,role:"status",children:$.text}),o&&e.jsxs("div",{className:"bw-work",children:[e.jsxs("aside",{className:"bw-side",children:[e.jsx(me,{snap:o,watching:v,palette:r}),e.jsx(be,{watching:v,rest:I}),o.relay&&e.jsx(pe,{relay:o.relay})]}),e.jsxs("div",{className:"bw-main",children:[e.jsx(ae,{snap:o,dismiss:y,refreshRef:C}),e.jsx(se,{snap:o})]})]}),o&&P&&e.jsx(fe,{snap:o,quiet:i,setQuiet:w,save:p,access:f,setAccess:j}),n&&e.jsx("div",{className:"bw-state bw-write-err",role:"alert",children:e.jsxs("div",{className:"bw-row err",children:[e.jsx("span",{className:"bw-dot","aria-hidden":!0}),e.jsx("span",{className:"bw-row-label",children:"Could not save that setting"}),e.jsxs("span",{className:"bw-row-detail",children:[n," — the switches below show what is stored, not what you pressed."]}),e.jsx("button",{className:"glyph-btn bw-write-err-x",onClick:()=>u(null),"aria-label":"Dismiss",title:"Dismiss",children:"×"})]})}),o&&e.jsx(ge,{snap:o,saving:m,save:p,why:P,setWhy:R})]})]})})}export{ye as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as U,f as p,r as c,s as Z,p as _,j as a,b as g,c as O,u as D,d as ee,e as se,g as E,h as ae,i as te}from"./index-
|
|
1
|
+
import{a as U,f as p,r as c,s as Z,p as _,j as a,b as g,c as O,u as D,d as ee,e as se,g as E,h as ae,i as te}from"./index-tVBMXvDv.js";function ne(s){return!s.answer||s.answer.range!==s.want?{phase:"busy",stale:!1}:s.answer.ok?{phase:s.answer.days>0?"chart":"empty",stale:s.loading}:{phase:"error",stale:s.loading}}function I(s){const e=new Map;for(const n of s)for(const o of n.agents??[]){if(!o||typeof o.agent!="string"||o.agent==="")continue;const r=e.get(o.agent)??{id:o.agent,cost:0,tokens:0};r.cost+=Number.isFinite(o.totalCost)?o.totalCost:0,r.tokens+=Number.isFinite(o.totalTokens)?o.totalTokens:0,e.set(o.agent,r)}return[...e.values()].sort((n,o)=>o.cost-n.cost||n.id.localeCompare(o.id))}function oe(s){const e=I([{agents:s}]);return e.length<2?null:e.map(n=>`${U(n.id)} ${p(n.cost)}`).join(" · ")}function F(s){switch(s.toLowerCase()){case"claude":return"var(--model-opus)";case"codex":return"var(--model-codex)";case"gemini":return"var(--model-gemini)";case"copilot":return"var(--model-haiku)";default:return"var(--model-other)"}}function le(s,e){if(!(e>0)||!(s>0))return"0%";const n=s/e*100;return n<.1?"<0.1%":`${n.toFixed(1)}%`}const u=s=>typeof s=="number"&&Number.isFinite(s)?s:0,M=s=>typeof s=="string"?s:"",P=s=>Array.isArray(s)?s.filter(e=>typeof e=="string"):[];function re(s){const e=s??{};return{modelName:M(e.modelName),cost:u(e.cost),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens)}}function ce(s){const e=s??{},n=Array.isArray(e.agents)?e.agents.map(o=>{const r=o??{};return{agent:M(r.agent),totalCost:u(r.totalCost),totalTokens:u(r.totalTokens)}}):void 0;return{period:M(e.period),totalCost:u(e.totalCost),totalTokens:u(e.totalTokens),inputTokens:u(e.inputTokens),outputTokens:u(e.outputTokens),cacheCreationTokens:u(e.cacheCreationTokens),cacheReadTokens:u(e.cacheReadTokens),modelsUsed:P(e.modelsUsed),modelBreakdowns:Array.isArray(e.modelBreakdowns)?e.modelBreakdowns.map(re):[],...n?{agents:n}:{},...e.metadata&&typeof e.metadata=="object"?{metadata:{agents:P(e.metadata.agents)}}:{}}}function ie(s){const e=s??{};return{...e,ok:e.ok===!0,days:Array.isArray(e.days)?e.days.map(ce):void 0}}function T(s){const e=s.toLowerCase();return e.includes("opus")?"var(--model-opus)":e.includes("sonnet")?"var(--model-sonnet)":e.includes("haiku")?"var(--model-haiku)":e.includes("gpt-5")||e.includes("gpt5")?"var(--model-gpt5)":e.includes("gpt")?"var(--model-gpt)":e.includes("gemini")?"var(--model-gemini)":e.includes("codex")?"var(--model-codex)":e.includes("fable")||e.includes("mythos")?"var(--model-fable)":"var(--model-other)"}function ue(s){let e=0,n=0,o=0,r=0;const m=new Map;for(const l of s){e+=l.totalCost,n+=l.totalTokens,o+=l.inputTokens+l.outputTokens,r+=l.cacheReadTokens;for(const d of l.modelBreakdowns)m.set(d.modelName,(m.get(d.modelName)??0)+d.cost)}return{totalCost:e,totalTok:n,inOut:o,cacheRead:r,modelCosts:m}}function de(s){return Array.from(s.entries()).sort((e,n)=>n[1]-e[1])}function q(s){return s.slice().sort((e,n)=>n.cost-e.cost)}function C(s,e){return e>0?s/e*100:0}function he(s){var n,o;return oe(s.agents)??((o=(n=s.metadata)==null?void 0:n.agents)!=null&&o.length?s.metadata.agents.join(" · "):null)}function me(){let s=0;return{begin(){const e=++s;return()=>e===s},cancel(){s++}}}function pe(s){const[e,n]=c.useState(null),[o,r]=c.useState(!1),m=c.useState(me)[0],l=c.useRef(!1),d=(f=!1)=>{if(f&&!Z(l.current))return;const x=m.begin();l.current=f;const k=s;r(!0);const i=`/api/ccusage?since=${_(k)}${f?"&refresh=1":""}`;fetch(i).then(j=>j.json()).then(j=>{x()&&n({range:k,resp:ie(j)})}).catch(()=>{x()&&n({range:k,resp:{ok:!1,reason:"unreachable"}})}).finally(()=>{x()&&(l.current=!1,r(!1))})};return c.useEffect(()=>(d(!1),()=>m.cancel()),[s]),{landed:e,loading:o,reload:()=>d(!0)}}function ge({selectedDay:s}){const e=he(s);return a.jsxs("div",{className:"uh-detail",children:[a.jsxs("div",{className:"uh-detail-head",children:[a.jsx("span",{className:"uh-detail-date",children:s.period}),a.jsx("span",{className:"uh-detail-cost",children:p(s.totalCost)}),e&&a.jsx("span",{className:"uh-detail-agents",title:e,children:e})]}),a.jsxs("div",{className:"uh-detail-mini",children:[a.jsx(y,{label:"input",val:g(s.inputTokens)}),a.jsx(y,{label:"output",val:g(s.outputTokens)}),a.jsx(y,{label:"cache write",val:g(s.cacheCreationTokens)}),a.jsx(y,{label:"cache read",val:g(s.cacheReadTokens)})]}),a.jsx("div",{className:"uh-detail-models",children:q(s.modelBreakdowns).map(n=>{const o=C(n.cost,s.totalCost);return a.jsxs("div",{className:"uh-model-row",title:n.modelName,children:[a.jsxs("span",{className:"uh-model-name",children:[a.jsx("span",{className:"uh-legend-dot",style:{background:T(n.modelName)}}),a.jsx("span",{className:"uh-model-label",children:O(n.modelName)})]}),a.jsx("span",{className:"uh-model-bar",children:a.jsx("span",{className:"uh-model-bar-fill",style:{width:`${o}%`,background:T(n.modelName)}})}),a.jsx("span",{className:"uh-model-cost",children:p(n.cost)})]},n.modelName)})})]})}function y({label:s,val:e}){return a.jsxs("div",{className:"uh-ministat",children:[a.jsx("span",{className:"uh-ministat-val",children:e}),a.jsx("span",{className:"uh-ministat-label",children:s})]})}const fe=[7,14,30,90];function je({onClose:s,providers:e}){var B;const[n,o]=c.useState(30),[r,m]=c.useState(null),{landed:l,loading:d,reload:f}=pe(n),x=c.useRef(null),k=D(s,{focusRef:x}),h=ne({loading:d,want:n,answer:l&&{range:l.range,ok:l.resp.ok===!0,days:((B=l.resp.days)==null?void 0:B.length)??0}}),i=h.phase==="chart"?l.resp.days??[]:[],j=c.useRef(null),G=ee(h.phase!=="error",j),H=c.useMemo(()=>i.reduce((t,N)=>Math.max(t,N.totalCost),0),[i]),S=c.useRef(null);c.useLayoutEffect(()=>{const t=S.current;t&&(t.scrollLeft=t.scrollWidth)},[i]);const{totalCost:w,totalTok:V,inOut:W,cacheRead:z,modelCosts:A}=c.useMemo(()=>ue(i),[i]),J=c.useMemo(()=>de(A),[A]),b=c.useMemo(()=>I(i),[i]),K=b.length>1,L=r?i.find(t=>t.period===r)??null:null;return a.jsx("div",{className:"modal-backdrop",onClick:s,role:"presentation",children:a.jsxs("div",{ref:k,className:"modal uh-modal",onClick:t=>t.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":"Usage history",children:[a.jsxs("header",{className:"uh-head",children:[a.jsxs("div",{className:"uh-titlewrap",children:[a.jsx("div",{className:"uh-title",children:"Usage history"}),a.jsx("div",{className:"uh-sub",children:h.stale?"running ccusage…":se(e,b.map(t=>t.id))})]}),a.jsx("div",{className:"uh-range",role:"group","aria-label":"Range",children:fe.map(t=>a.jsxs("button",{type:"button","aria-pressed":n===t,className:"uh-range-btn",onClick:()=>{o(t),m(null)},children:[t,"d"]},t))}),a.jsx("button",{ref:j,className:"glyph-btn uh-reload",onClick:f,...E(d),title:"Re-run ccusage","aria-label":"Reload",children:d?"…":"↻"}),a.jsx("button",{ref:x,className:"glyph-btn uh-close",onClick:s,"aria-label":"Close",children:"×"})]}),h.phase==="busy"?a.jsxs("div",{className:"uh-status uh-status-wait","aria-busy":"true",children:[a.jsx("span",{className:"uh-wait-word",children:l?"running ccusage…":"running ccusage… (first run downloads the package)"}),a.jsx("span",{className:"uh-wait-axis","aria-hidden":!0})]}):h.phase==="error"?a.jsxs("div",{className:"uh-status uh-err",title:te(l.resp)||void 0,children:[ae(l.resp,"ccusage did not report usage"),a.jsx("div",{children:a.jsx("button",{className:"btn uh-retry",onClick:()=>{G(),f()},...E(d),children:d?"trying…":"Try again"})})]}):h.phase==="empty"?a.jsx("div",{className:`uh-status${h.stale?" uh-stale":""}`,"aria-busy":h.stale||void 0,children:"no usage in this range"}):a.jsxs("div",{className:h.stale?"uh-stale":void 0,"aria-busy":h.stale||void 0,children:[a.jsxs("div",{className:"uh-totals",children:[a.jsx(v,{label:"total cost",val:p(w),accent:!0}),a.jsx(v,{label:"tokens",val:g(V)}),a.jsx(v,{label:"input+output",val:g(W)}),a.jsx(v,{label:"cache reads",val:g(z)})]}),K&&a.jsxs("div",{className:"uh-agents",role:"group","aria-label":"Cost by CLI",children:[a.jsx("div",{className:"uh-agent-bar","aria-hidden":"true",children:b.map(t=>a.jsx("span",{className:"uh-agent-seg",style:{width:`${C(t.cost,w)}%`,background:F(t.id)}},t.id))}),a.jsx("div",{className:"uh-agent-keys",children:b.map(t=>a.jsxs("span",{className:"uh-agent-key",title:`${t.id} · ${g(t.tokens)} tokens`,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:F(t.id)}}),U(t.id),a.jsx("span",{className:"uh-agent-cost",children:p(t.cost)}),a.jsx("span",{className:"uh-agent-share",children:le(t.cost,w)})]},t.id))})]}),a.jsx("div",{ref:S,className:"uh-chart",role:"group","aria-label":"Daily cost by model",children:i.map((t,N)=>{const Q=C(t.totalCost,H),R=t.period===r,X=`${t.period}, ${p(t.totalCost)}`;return a.jsxs("button",{className:`uh-bar-col${R?" sel":""}`,onClick:()=>m(R?null:t.period),"aria-pressed":R,"aria-label":X,title:`${t.period} · ${p(t.totalCost)}`,style:{flexBasis:`${100/i.length}%`,"--uh-i":N,"--uh-step":`${Math.min(10,200/Math.max(i.length,1))}ms`},children:[a.jsx("div",{className:"uh-bar",style:{height:`${Math.max(Q,t.totalCost>0?2:0)}%`},children:q(t.modelBreakdowns).map($=>{const Y=C($.cost,t.totalCost);return a.jsx("div",{className:"uh-bar-seg",style:{height:`${Y}%`,background:T($.modelName)}},$.modelName)})}),a.jsx("span",{className:"uh-bar-label",children:t.period.slice(5)})]},t.period)})}),a.jsx("div",{className:"uh-legend",children:J.map(([t,N])=>a.jsxs("span",{className:"uh-legend-item",title:t,children:[a.jsx("span",{className:"uh-legend-dot",style:{background:T(t)}}),O(t)," ",a.jsx("span",{className:"uh-legend-cost",children:p(N)})]},t))}),L&&a.jsx(ge,{selectedDay:L})]})]})})}function v({label:s,val:e,accent:n}){return a.jsxs("div",{className:"uh-stat",children:[a.jsx("span",{className:`uh-stat-val${n?" accent":""}`,children:e}),a.jsx("span",{className:"uh-stat-label",children:s})]})}export{je as default};
|
|
@@ -97,7 +97,7 @@ ${e.map(s=>` ${s.label}: ${li(s.waiting)} (${cd(r-s.waiting.since)})`).join(`
|
|
|
97
97
|
${Fe(o.total)} spent${u?` · ${u}`:""}`:""}`,onClick:()=>{try{r(e.id)}catch{}},children:[a.jsx("span",{className:`state-pill state-${e.state}`,children:e.state==="active"?"live":e.state}),a.jsx("span",{className:"selected-label",children:e.label}),o.total>0&&a.jsxs("span",{className:"selected-cost",children:[Fe(o.total),u?a.jsxs("span",{className:"selected-rate",children:[" · ",u]}):null]}),d>0&&a.jsxs("span",{className:"selected-extra",children:["+",d]}),a.jsx("span",{"aria-hidden":!0,className:"selected-close",onClick:h=>{h.stopPropagation(),s()},children:"×"})]})}function Yne({catBarRef:e,catBarOccluded:t,presentCats:n,hiddenCats:r,toggleCat:s}){return a.jsx("div",{ref:e,className:`cat-filter-bar${t?" occluded":""}`,role:"group","aria-label":"Filter tools by category",children:n.map(o=>{const l=r.has(o);return a.jsxs("button",{type:"button",className:`cat-filter${l?" off":""}`,onClick:()=>s(o),"aria-pressed":!l,title:`${l?"Show":"Hide"} ${Sd[o]} tools`,children:[a.jsx(Wte,{cat:o}),a.jsx("span",{className:"cat-name",children:Sd[o]})]},o)})})}const Xne=["a[href]","button:not(:disabled)","input:not(:disabled)","select:not(:disabled)","textarea:not(:disabled)","summary","[tabindex]"].join(",");function Zne(e){var n,r;const t=e.target;!(t!=null&&t.closest)||t.closest(Xne)!==e.currentTarget||(e.preventDefault(),(r=(n=document.activeElement)==null?void 0:n.blur)==null||r.call(n))}function Jne({trashZoneRef:e,trashState:t,trashPhase:n,trashLabel:r}){return a.jsxs("div",{ref:e,className:`drag-trash-zone ${t}${n==="leaving"?" leaving":""}`,role:"status",children:[a.jsx("svg",{className:"drag-trash-icon",viewBox:"0 0 24 24","aria-hidden":"true",children:a.jsx("path",{d:"M8 7V5.8C8 4.8 8.8 4 9.8 4h4.4c1 0 1.8.8 1.8 1.8V7m-10 0h12M8 10v8m4-8v8m4-8v8M7 7l.7 13h8.6L17 7"})}),a.jsxs("span",{className:"drag-trash-copy",children:[a.jsx("span",{className:"drag-trash-text",children:t==="over"?a.jsxs(a.Fragment,{children:["Release to remove ",a.jsx("strong",{className:"drag-trash-name",children:r||"this card"})]}):"Drop here to remove from the board"}),a.jsx("span",{className:"drag-trash-hint",children:t==="over"?"The session list (L) brings it back":"The session keeps running"})]})]})}function Qne({canvasRef:e,bubbling:t,dragging:n,trash:r,zoom:s,viewport:o,stateRef:l,peek:u,children:d}){const{trashDragging:h,trashState:f,trashPhase:p,trashZoneRef:g,trashLabel:v}=r,{lod:k,lodRef:b}=s,{markCanvasInput:x}=o,{peekAgent:N,peekLabel:S,peekRecap:_,peekBounds:E}=u;return a.jsxs("main",{id:"canvas",tabIndex:-1,className:`canvas-wrap${t?" bubbling":""}${n?" dragging-any":""}${h&&f==="over"?" trash-hover":""}`,"data-lod":k,ref:e,onMouseDownCapture:Zne,onPointerDownCapture:x,onPointerUpCapture:x,onWheelCapture:x,onFocusCapture:T=>{const A=T.target;if(!_0(A)||b.current==null||b.current==="detail")return;const R=A.getAttribute("data-id");R&&l.current.agents.has(R)&&A.matches(":focus-visible")&&DO(R,A,"focus")},onBlurCapture:T=>{var R,B;const A=(B=(R=T.target).getAttribute)==null?void 0:B.call(R,"data-id");A&&ta(A)},children:[d,Bu(p)&&a.jsx(Jne,{trashZoneRef:g,trashState:f,trashPhase:p,trashLabel:v}),a.jsx(jee,{agentFor:N,recapFor:_,labelFor:S,bounds:E})]})}function ere({restarting:e,restartMode:t,live:n,paused:r}){return a.jsxs("div",{className:"conn-banner",role:"alert",children:[a.jsx("span",{className:"conn-dot"}),e?t==="npx"?"Fetching the new version with npx — this can take a minute…":`Restarting ${bt}…`:(()=>{const s=bU({connected:n,paused:r});return a.jsxs(a.Fragment,{children:[`Lost connection to the ${bt} server. Reconnecting…`,s&&a.jsx("span",{className:"conn-sub",children:s})]})})()]})}function tre({oldName:e,version:t,dismissOldName:n}){return a.jsxs("div",{className:"ver-banner",role:"status",children:[a.jsx("span",{className:"ver-dot"}),a.jsxs("strong",{children:[e," still works — the deck is called ",bt," now."]}),t!=null&&t.renameFix?a.jsx("span",{className:"ver-sub",children:t.renameFix}):null,a.jsx("button",{type:"button","aria-label":"Dismiss",className:"ver-close",onClick:n,children:"×"})]})}const nre=3e4;function rre({bundle:e,running:t,lastTried:n}){return!t||e===t?!1:n!==t}function sre({bundle:e,running:t,pending:n,lastTried:r}){return t?rre({bundle:e,running:t,lastTried:r})?"reload":n==null||n&&n!==t?"wait":"confirm":"wait"}function are(e){return!e||e.state!=="failed"?null:`${typeof e.at=="number"?e.at:0}:${e.error??""}`}function ore({asked:e,reported:t}){return t?t!==e:!1}function QO(e){const t=e.thresholdMs??nre;return!e.enabled||e.kind!=="restart"||!e.canRestart||!e.noticeOpen||!e.visible||e.busy?null:e.idleSince==null||e.now<e.idleSince?t:Math.max(0,t-(e.now-e.idleSince))}function ire(e){const t=Math.max(0,Math.ceil(e/1e3));return t<60?`${t}s`:`${Math.floor(t/60)}:${String(t%60).padStart(2,"0")}`}function lre(e,t){return e?t==null?"auto when idle":`auto in ${ire(t)}`:"auto-restart off"}function gR(e){let t=0;for(const n of e)n.state==="active"&&t++;return t}function cre(e,t=!1){return e<=0&&t?{label:"Restart anyway",clause:"the canvas is paused — running agents cannot be seen"}:e<=0?{label:"Restart now",clause:"nothing is running"}:{label:"Restart anyway",clause:`${e===1?"1 agent is running":`${e} agents are running`} — their events during the restart are lost`}}function ure(e){const t=QO(e);return t==null?{idleSince:null,restart:!1,remainingMs:null}:e.idleSince==null||e.now<e.idleSince?{idleSince:e.now,restart:!1,remainingMs:t}:{idleSince:e.idleSince,restart:t===0,remainingMs:t}}const yR={git_checkout:"this deck runs from a git checkout — pull instead:",npx:"npx runs from a cache that cannot be upgraded in place — run:",not_writable:"the install directory is not writable by this user — run:",opted_out:"installs are off (AGENTS_DECK_NO_INSTALL=1) — run:",retired_name:"this install came from a name that is no longer published — run:"};function dre({notice:e,version:t,dismissNotice:n,upgradeState:r,startUpgrade:s,copyCommand:o,cmdCopied:l,autoRestart:u,toggleAutoRestart:d,askRestart:h,restartCopy:f,restartFuseMs:p,restarting:g}){var v,k,b;return a.jsxs("div",{className:`ver-banner ${e.kind}`,role:"status",children:[a.jsx("span",{className:`ver-dot${p==null?"":" armed"}`}),e.kind==="restart"?a.jsxs(a.Fragment,{children:[a.jsxs("strong",{children:["v",e.to," is installed — this deck still runs v",e.from,"."]}),t!=null&&t.canRestart?a.jsxs(a.Fragment,{children:[a.jsx("button",{type:"button",className:"ver-act",onClick:()=>h(),...Or(g),title:`Stop this process and bring it back on the same port. The canvas replays from the event log.
|
|
98
98
|
|
|
99
99
|
Node loads every module once, at startup. An upgrade replaces the files on disk but not the code already in memory, so this process keeps running the old version until it is restarted.`,children:g?"restarting…":f.label}),!g&&a.jsx("span",{className:"ver-sub",children:f.clause}),a.jsxs("label",{className:"ver-auto",children:[a.jsx("button",{type:"button",className:"switch",role:"switch","aria-checked":u,onClick:d,"aria-label":"Auto-restart when idle",title:u?"Updates on its own. While you are here it restarts once nothing has been running for 30 seconds; while you are away it also installs the new version first. Click to require a click instead.":"Only updates when you click. Click to let it update itself when idle or while you are away.",children:a.jsx("span",{className:"switch-knob"})}),a.jsx("span",{"aria-hidden":!0,children:lre(u,p)})]})]}):a.jsx("span",{className:"ver-sub",title:"Node loads every module once, at startup. An upgrade replaces the files on disk but not the code already in memory, so this process keeps running the old version until it is restarted.",children:"Restart it to pick up the new code."})]}):a.jsxs(a.Fragment,{children:[a.jsxs("strong",{children:[bt," v",e.to," is out — you are on v",e.from,"."]}),(t==null?void 0:t.upgradeMode)==="install"&&r!=="failed"&&a.jsx("button",{type:"button",className:"ver-act",onClick:s,...Or(r==="running",r==="done"),title:`Runs ${((v=t==null?void 0:t.upgrade)==null?void 0:v.command)??(t==null?void 0:t.command)??"npm i -g"} here, then restarts once nothing is running.`,children:r==="running"?"installing…":r==="done"?"installed":"Update now"}),(t==null?void 0:t.upgradeMode)==="npx"&&(t==null?void 0:t.canRestart)&&a.jsx("button",{type:"button",className:"ver-act",onClick:()=>h({upgrade:!0}),...Or(g),title:`Runs ${t==null?void 0:t.command} and hands it this port. Nothing is installed globally — npx unpacks its own copy.`,children:g?"fetching…":r==="failed"?"Retry update":"Update & restart"}),r==="failed"?a.jsxs("span",{className:"ver-sub fail",title:((k=t==null?void 0:t.upgrade)==null?void 0:k.error)??"",children:[(t==null?void 0:t.upgradeMode)==="npx"?"update failed":"install failed",": ",((b=t==null?void 0:t.upgrade)==null?void 0:b.error)??"unknown error"," — run it yourself:"]}):(t==null?void 0:t.upgradeMode)==="npx"?a.jsx("span",{className:"ver-sub",children:t!=null&&t.canRestart?"npx cannot upgrade in place, so the deck re-runs:":yR.npx}):t!=null&&t.upgradeBlocked?a.jsx("span",{className:"ver-sub",children:Ot(yR,t.upgradeBlocked)??"cannot install from here"}):null,a.jsxs("button",{type:"button",className:"ver-cmd",onClick:o,title:"Copy to clipboard",children:[a.jsx("code",{children:t==null?void 0:t.command}),a.jsx("span",{className:"ver-cmd-hint",children:l?"copied":"copy"})]})]}),a.jsx("button",{type:"button","aria-label":"Dismiss",className:"ver-close",onClick:n,children:"×"})]})}function hre({restart:e,versionCheck:t,upgrade:n,oldNameNotice:r,everConnected:s,live:o,paused:l}){const{autoRestart:u,toggleAutoRestart:d,restarting:h,restartMode:f,restartedTo:p,askRestart:g,restartCopy:v,restartFuseMs:k}=e,{version:b,notice:x,noticeOpen:N,dismissNotice:S}=t,{upgradeState:_,startUpgrade:E,copyCommand:T,cmdCopied:A}=n,{oldName:R,oldNameOpen:B,dismissOldName:P}=r;return p?a.jsxs("div",{className:"ver-banner done",role:"status",children:[a.jsx("span",{className:"ver-dot"}),a.jsxs("strong",{children:["Restarted — now running v",p,"."]}),a.jsx("span",{className:"ver-sub",children:"The canvas replayed from the event log."})]}):s&&!o?a.jsx(ere,{restarting:h,restartMode:f,live:o,paused:l}):N&&x?a.jsx(dre,{notice:x,version:b,dismissNotice:S,upgradeState:_,startUpgrade:E,copyCommand:T,cmdCopied:A,autoRestart:u,toggleAutoRestart:d,askRestart:g,restartCopy:v,restartFuseMs:k,restarting:h}):B&&R?a.jsx(tre,{oldName:R,version:b,dismissOldName:P}):null}function fre(e,t){return e==="confirmation"?t.confirmOpen?"clear":"ignore":t.modalOpen||t.confirmOpen?"ignore":"confirm"}function pre(e){if(!e||typeof e!="object")return null;const t=e;if(typeof t.decks!="number"||typeof t.mine!="boolean")return null;const n=t.owner&&typeof t.owner=="object"?t.owner.port:null;return{path:typeof t.path=="string"&&t.path!==""?t.path:null,decks:t.decks,mine:t.mine,ownerPort:typeof n=="number"?n:null}}function mre(e){return e<=0?null:e===1?"the one agent":`all ${e} agents`}function gre(e,t){const n=mre(e),r=(...l)=>[n?`${n} on the canvas`:null,...l,"layout, pins and selection"].filter(u=>u!==null),s="This cannot be undone.";if(!t)return{goes:r("the event log on disk — the file a restarted deck replays to rebuild what you see, and the file every deck on this machine shares unless one was told otherwise"),stays:[],final:s,confirm:"Clear everything"};if(!t.path)return{goes:r(),stays:[],final:`This deck keeps no event log, so nothing on disk changes — and nothing can bring the canvas back. ${s}`,confirm:"Clear everything"};if(!t.mine){const l=t.ownerPort===null?"another running deck owns that file":`the deck on port ${t.ownerPort} owns that file`,u=t.decks>2?`, one of ${t.decks} decks sharing it`:"";return{goes:r(),stays:[`the event log — ${l}${u}`,"restart this deck and the log replays back onto it"],final:null,confirm:"Clear this canvas"}}if(t.decks<=1)return{goes:r("this deck's event log — the file a restarted deck replays to rebuild what you see"),stays:[],final:s,confirm:"Clear everything"};const o=t.decks-1;return{goes:r(`the event log ${o===1?"1 other running deck shares":`${o} other running decks share`} with this one — every session recorded in it, not just the ones drawn here`),stays:[o===1?"what that deck shows, until it restarts — then it is gone there too":"what those decks show, until they restart — then it is gone there too"],final:s,confirm:"Clear everything"}}function yre({agentCount:e,onConfirm:t,onCancel:n}){const r=w.useRef(null),s=ft(n,{focusRef:r,layer:Zq}),[o,l]=w.useState(null);w.useEffect(()=>{let p=!1;return fetch("/api/clear").then(g=>g.ok?g.json():null).then(g=>{p||l(pre(g))}).catch(()=>{}),()=>{p=!0}},[]);const{goes:u,stays:d,final:h,confirm:f}=gre(e,o);return a.jsx("div",{className:"modal-backdrop",onClick:n,role:"presentation",children:a.jsxs("div",{ref:s,className:"modal clear-confirm",onClick:p=>p.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"clear-confirm-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsxs("div",{className:"modal-title",children:[a.jsx("span",{className:"status-dot err","aria-hidden":!0}),a.jsx("span",{id:"clear-confirm-title",className:"modal-tool-name",children:"Clear the deck?"})]}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:n,"aria-label":"Cancel (Esc)",title:"Cancel (Esc)",children:"×"})})]}),a.jsxs("section",{className:"modal-body",children:[a.jsxs("dl",{className:"cc-plan",children:[a.jsx("dt",{children:"Goes"}),a.jsx("dd",{children:u.map(p=>a.jsx("span",{children:p},p))}),a.jsx("dt",{children:"Stays"}),a.jsx("dd",{children:d.length>0?d.map(p=>a.jsx("span",{children:p},p)):a.jsx("span",{children:"nothing"})})]}),h&&a.jsx("p",{className:"cc-final",children:h}),a.jsxs("div",{className:"cc-actions",children:[a.jsx("button",{type:"button",ref:r,className:"btn",onClick:n,children:"Cancel"}),a.jsx("button",{type:"button",className:"btn danger",onClick:t,children:f})]})]})]})})}const nb=[{value:"bug",label:"Bug",question:"What happened?",placeholder:`I was trying to…
|
|
100
|
-
What did you expect to happen?`},{value:"idea",label:"Idea",question:"What would make ccdeck better?",placeholder:"It would be useful if…"},{value:"other",label:"Other",question:"What would you like to tell us?",placeholder:"Write anything…"}];function eL(e){return nb.find(t=>t.value===e)??nb[0]}const rb=3,Gv=120,wre=1e4,vre=200,xre="Write something first.";function wR(e){return e.trim()!==""}function bre(e){var o;const t=e.trim();if(t==="")return"";const n=vR(t.split(/\r?\n/,1)[0]),r=((o=/^(.+?[.!?])(?=\s|$)/.exec(n))==null?void 0:o[1])??n,s=Kv(r).length>=rb?Kv(r):Kv(n);return Nre(s.length>=rb?s:vR(t))}function kre(e,t){const n=bre(t);return n.length>=rb?n:`${eL(e).label}: ${n}`}function vR(e){return e.replace(/\s+/g," ").trim()}function Kv(e){return/[^.]\.$/.test(e)?e.slice(0,-1):e}function Nre(e){if(e.length<=Gv)return e;const t=e.slice(0,Gv-1),n=t.lastIndexOf(" ");return`${(n>=Gv/2?t.slice(0,n):t).replace(/[\uD800-\uDBFF]$/,"").replace(/[\s,;:.\-–—]+$/,"")}…`}function jre({kind:e,body:t,contact:n}){const r={kind:e,title:kre(e,t),body:t.trim()};return n.trim()!==""&&(r.contact=n.trim()),r}function Sre(e){const t=e;return(t==null?void 0:t.ok)!==!0||typeof t.appVersion!="string"||typeof t.platform!="string"||t.appVersion===""||t.platform===""?null:{appVersion:t.appVersion,platform:t.platform}}const _re={darwin:"macOS",win32:"Windows",linux:"Linux"};function Ere(e){const t=e.indexOf("-"),n=t===-1?e:e.slice(0,t),r=t===-1?"":e.slice(t+1),s=_re[n]??n;return r?`${s} · ${r}`:s}function Cre(e){return e?`ccdeck ${e.appVersion} · ${Ere(e.platform)}`:null}const Tre="sent with it",Are="Your ccdeck version and system are sent with it.",Rre="Add details",Mre="How to reach you",$re="Email or GitHub @name",Ire="Only if you would like an answer.",Ore=["It all goes to the people who make ccdeck, with the version and system below.","Nothing becomes public by being sent. They may open a public GitHub issue from it, choosing its title and text; your contact and screenshots are never put on one."];function Lre(e){return e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&!e.altKey&&!e.shiftKey&&!e.isComposing}function Pre(e){return e.key==="Enter"&&!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey&&!e.isComposing}function Dre(e){return/mac|iphone|ipad/i.test(e)?["⌘","Enter"]:["Ctrl","Enter"]}function Fre(e){return[...e].length===1}const tL="Thanks — feedback sent.";function xR(e,t,n){if(t==="vetoed")return"This deck was started with AGENTS_DECK_NO_INSTALL=1, which keeps it off the network, so nothing was sent.";if(e===429||t==="too_many")return"Too much feedback from this network in the last hour. Try again later; your text is still here.";if(e===413||t==="too_large")return"The images are too large to send together. Remove one and send again; your text is still here.";const r=n==null?void 0:n.images;return e===400&&Array.isArray(r)&&typeof r[0]=="string"?`The server did not accept an image. ${r[0]} Remove it and send again; your text is still here.`:e===400?"The server did not accept the message. It is still here; check it and send again.":"ccdeck's server could not be reached. Nothing was sent; your text is still here, so try again in a moment."}function Bre(e){return e==="sending"?"Sending…":e==="sent"?tL:""}const bR=1400,qre=160;function Ure({kind:e,onChange:t}){return a.jsxs("fieldset",{className:"fb-kinds",children:[a.jsx("legend",{className:"vis-hidden",children:"Kind of feedback"}),nb.map(n=>a.jsxs("label",{className:"fb-kind",children:[a.jsx("input",{type:"radio",name:"feedback-kind",value:n.value,checked:e===n.value,onChange:()=>t(n.value)}),n.label]},n.value))]})}const nL="fb-details";function zre({open:e,onToggle:t}){return a.jsxs("button",{type:"button",className:"fb-tool fb-more","aria-expanded":e,"aria-controls":nL,onClick:t,children:[Rre,a.jsx("svg",{className:"fb-chev",width:"10",height:"10",viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,focusable:"false",children:a.jsx("path",{d:"M2.5 3.75 5 6.25l2.5-2.5"})})]})}function Wre({open:e,contact:t,onContact:n}){const r=w.useId();return a.jsx("div",{id:nL,className:"fb-details","data-open":e||void 0,children:a.jsx("div",{className:"fb-details-clip",children:a.jsxs("div",{className:"fb-details-body",children:[a.jsxs("div",{className:"fb-field",children:[a.jsxs("label",{className:"fb-field-label",htmlFor:"fb-contact",children:[Mre,a.jsx("span",{className:"fb-optional",children:"optional"})]}),a.jsx("input",{id:"fb-contact",className:"ap-manage-input",type:"text",value:t,maxLength:vre,autoComplete:"email",placeholder:$re,"aria-describedby":r,onChange:s=>n(s.target.value),onKeyDown:s=>{Pre(s.nativeEvent)&&s.preventDefault()}}),a.jsx("p",{id:r,className:"fb-hint",children:Ire})]}),a.jsx("div",{className:"fb-where",children:Ore.map(s=>a.jsx("p",{children:s},s))})]})})})}const Ko=3,Hre=5*1024*1024,Vre=12*1024*1024,sb=8192,Gre=64*1024,Kre=256*1024,Yre=.92,Xre=640,rL=.9,Zre=.85,Jre=6,Qre=512*1024,Ed={png:"image/png",jpeg:"image/jpeg"},ese={png:"png",jpeg:"jpg"},tse=[137,80,78,71,13,10,26,10],nse=[255,216,255];function sL(e){const t=n=>e.length>=n.length&&n.every((r,s)=>e[s]===r);return t(tse)?"png":t(nse)?"jpeg":null}function rse(e){const t=sL(e);return t==="png"?sse(e):t==="jpeg"?ose(e):null}function sse(e){const t=[73,72,68,82];if(e.length<24||!t.every((r,s)=>e[12+s]===r))return null;const n=new DataView(e.buffer,e.byteOffset,e.byteLength);return{width:n.getUint32(16),height:n.getUint32(20)}}function ase(e){return e>=192&&e<=207&&e!==196&&e!==200&&e!==204}function ose(e){let t=2;for(;t<e.length;){if(e[t]!==255)return null;for(;t<e.length&&e[t]===255;)t++;const n=e[t++];if(n===void 0||n===217||n===218)return null;if(n===1||n>=208&&n<=215)continue;if(t+2>e.length)return null;const r=e[t]<<8|e[t+1];if(r<2)return null;if(ase(n))return t+7>e.length?null:{height:e[t+3]<<8|e[t+4],width:e[t+5]<<8|e[t+6]};t+=r}return null}function kR(e,t){return e.bytes>t||e.width>sb||e.height>sb}function ise(e){return Math.min(Hre,Vre-Gre-e)}function aL(e,t){return{width:Math.max(1,Math.floor(e.width*t)),height:Math.max(1,Math.floor(e.height*t))}}function lse(e,t){const n=sb/Math.max(e.width,e.height),r=e.bytes>t?Math.sqrt(t/e.bytes)*rL:1;return aL(e,Math.min(1,n,r))}async function cse(e,t,n){let r=lse(e,t),s=e.format;for(let o=0;o<Jre;o++){const l=await n(r,s,s==="jpeg"?Yre:void 0);if(!l)return null;if(l.size<=t)return{...r,blob:l,format:s};if(s==="png"){s="jpeg";continue}if(r=aL(r,Math.min(Zre,Math.sqrt(t/l.size)*rL)),Math.max(r.width,r.height)<Xre)return null}return null}async function use(e,t,n){const r=new Uint8Array(await e.slice(0,Qre).arrayBuffer()),s=sL(r);if(!s)return{ok:!1,problem:"type"};if(t<Kre)return{ok:!1,problem:"no_room"};const o=e.type===Ed[s]?e:new Blob([e],{type:Ed[s]}),l=rse(r);if(l&&!kR({...l,bytes:e.size},t))return{ok:!0,blob:o,resized:null};const u=await n(e).catch(()=>null);if(!u)return{ok:!1,problem:"unreadable"};try{const d={width:u.width,height:u.height,bytes:e.size,format:s};if(!kR(d,t))return{ok:!0,blob:o,resized:null};const h=await cse(d,t,u.encoder);return h?{ok:!0,blob:h.blob,resized:{width:h.width,height:h.height}}:{ok:!1,problem:"too_large"}}finally{u.release()}}function dse(e){return({width:t,height:n},r,s)=>new Promise(o=>{const l=document.createElement("canvas");l.width=t,l.height=n;const u=l.getContext("2d");if(!u)return o(null);r==="jpeg"&&(u.fillStyle="white",u.fillRect(0,0,t,n)),u.imageSmoothingQuality="high",u.drawImage(e,0,0,t,n),l.toBlob(d=>{l.width=0,l.height=0,o(d)},Ed[r],s)})}const hse=async e=>{const t=await createImageBitmap(e);return{width:t.width,height:t.height,encoder:dse(t),release:()=>t.close()}};function fse(e){return e.includes("Files")}function pse({files:e,hasText:t,intoTextField:n}){return e>0&&(!t||!n)}function mse(e,t){const n=e.indexOf(t);return e[n+1]??e[n-1]??null}function gse(e,t){if(t.length===0)return{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e)};const n=new FormData;return n.append("kind",e.kind),n.append("title",e.title),n.append("body",e.body),e.contact!==void 0&&n.append("contact",e.contact),t.forEach((r,s)=>{const o=r.type===Ed.jpeg?"jpeg":"png";n.append("images",r,`image-${s+1}.${ese[o]}`)}),{method:"POST",body:n}}const yse="Add screenshot",NR="PNG or JPEG, up to three. You can also paste one, or drop it on this dialog.",wse="A screenshot can show emails, costs and paths. Crop out what should not be seen.",jR="Three images at most. Remove one to add another.";function vse(e,t,n){const r=n?`, resized to ${n.width} × ${n.height} to fit`:"";return`Replace image ${e+1}: ${t}${r}`}function xse(e,t){const n=`${e} of ${Ko}`;return t>0?`${n} · ${t} resized to fit`:n}function bse(e){return`Three images at most, so ${e===1?"one was":`${e} were`} left out.`}function kse(e){return`“${e.length>40?`${e.slice(0,30)}…${e.slice(-9)}`:e}”`}function Nse(e,t){const n=kse(t);return e==="type"?`${n} is not a PNG or a JPEG, so it was not added. GIF, WebP and HEIC are not converted; save it as a PNG or a JPEG first.`:e==="no_room"?`There is no room left for ${n}: images go at up to 12 MB in all. Remove one to add it.`:e==="unreadable"?`${n} could not be read as an image.`:`${n} could not be made small enough to send: images go at up to 5 MB each.`}const oL="image/png,image/jpeg";function iL({className:e="fb-glyph"}){return a.jsxs("svg",{className:e,width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,focusable:"false",children:[a.jsx("rect",{x:"1.8",y:"2.4",width:"10.4",height:"9.2",rx:"1.6"}),a.jsx("circle",{cx:"5",cy:"5.6",r:"1"}),a.jsx("path",{d:"M2.4 10.4 5.5 7.6l2.1 1.8 1.5-1.3 2.5 2.2"})]})}function jse({images:e,buttonRef:t}){const n=w.useRef(null),r=e.shots.length>=Ko;return a.jsxs(a.Fragment,{children:[a.jsxs("button",{ref:t,type:"button",className:"fb-tool",title:NR,"aria-describedby":"fb-attach-hint",onClick:()=>{var s;return r?e.sayFull():(s=n.current)==null?void 0:s.click()},children:[a.jsx(iL,{}),yse]}),a.jsx("span",{id:"fb-attach-hint",hidden:!0,children:NR}),a.jsx("input",{ref:n,type:"file",accept:oL,multiple:!0,hidden:!0,onChange:s=>{e.add(Array.from(s.target.files??[])),s.target.value=""}})]})}function Sse({images:e,addRef:t}){const n=w.useRef(new Map),r=w.useRef(null),s=w.useRef(null),o=w.useRef(null),[l,u]=w.useState(null),{shots:d}=e,h=xse(d.length,d.filter(g=>g.resized).length);w.useLayoutEffect(()=>{var g;l!==null&&((g=l==="add"?t.current:n.current.get(l))==null||g.focus(),u(null))},[l,t]),w.useEffect(()=>{var g;e.problem&&((g=r.current)==null||g.scrollIntoView({block:"nearest"}))},[e.problem]);function f(g){u(mse(d.map(v=>v.id),g)??"add"),e.remove(g)}function p(g){var v;o.current=g,(v=s.current)==null||v.click()}return a.jsxs(a.Fragment,{children:[a.jsx("p",{className:"vis-hidden",role:"status",children:e.announcement}),a.jsx("input",{ref:s,type:"file",accept:oL,hidden:!0,onChange:g=>{const v=o.current;v!==null&&e.replace(v,Array.from(g.target.files??[])),o.current=null,g.target.value=""}}),d.length>0&&a.jsxs("div",{className:"fb-shots",children:[a.jsxs("div",{className:"fb-shots-row",children:[a.jsx("ul",{className:"fb-shots-list",children:d.map((g,v)=>a.jsxs("li",{className:"fb-shot","data-fitting":g.blob?void 0:!0,children:[a.jsxs("button",{type:"button",className:"fb-shot-pick","aria-label":vse(v,g.name,g.resized),onClick:()=>p(g.id),children:[a.jsx("img",{className:"fb-shot-img",src:g.url,alt:""}),g.blob?a.jsx("span",{className:"fb-shot-cue","aria-hidden":"true",children:"Replace"}):a.jsx("span",{className:"fb-shot-busy","aria-hidden":"true",children:"Resizing…"})]}),a.jsx("button",{ref:k=>{k?n.current.set(g.id,k):n.current.delete(g.id)},type:"button",className:"glyph-btn fb-shot-remove","aria-label":`Remove image ${v+1}`,title:`Remove image ${v+1}`,onClick:()=>f(g.id),children:a.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round","aria-hidden":!0,focusable:"false",children:a.jsx("path",{d:"M2.5 2.5 7.5 7.5M7.5 2.5 2.5 7.5"})})})]},g.id))}),a.jsx("span",{className:"fb-shots-count",children:h})]}),a.jsx("p",{className:"fb-hint",children:wse})]}),e.problem&&a.jsx("p",{ref:r,className:"fb-error",role:"alert",children:e.problem})]})}function _se(e,t,n){return n?e.map(r=>r.id===t.id?t:r):[...e,t]}function Ese(e,t,n){return n?e.map(r=>r.id===t?n:r):e.filter(r=>r.id!==t)}function Cse(){const[e,t]=w.useState([]),[n,r]=w.useState(""),[s,o]=w.useState(""),l=w.useRef([]),u=w.useRef(null),d=w.useRef(1),h=w.useRef(!0),f=w.useCallback(E=>{l.current=E(l.current),t(l.current)},[]);w.useEffect(()=>(h.current=!0,()=>{h.current=!1;for(const E of l.current)URL.revokeObjectURL(E.url)}),[]);const p=w.useCallback(async(E,T)=>{const A=l.current.reduce((M,$)=>{var q;return M+($.id===(T==null?void 0:T.id)?0:((q=$.blob)==null?void 0:q.size)??0)},0),R=(T==null?void 0:T.id)??d.current++,B=URL.createObjectURL(E),P={id:R,name:E.name||"image",url:B,blob:null,resized:null};let D=!1;const I=()=>{D||(D=!0,f(M=>_se(M,P,T!==void 0)))},O=await use(E,ise(A),async M=>(I(),hse(M))).catch(()=>({ok:!1,problem:"unreadable"})),z=l.current.some(M=>M.id===R);return!h.current||!z&&(D||T)?(URL.revokeObjectURL(B),T&&URL.revokeObjectURL(T.url),""):O.ok?(I(),T&&URL.revokeObjectURL(T.url),f(M=>M.map($=>$.id===R?{...$,blob:O.blob,resized:O.resized}:$)),""):(URL.revokeObjectURL(B),D&&f(M=>Ese(M,R,T)),Nse(O.problem,E.name||"image"))},[f]),g=w.useCallback(async E=>{r("");const T=[];let A=0,R=0;for(const P of E){if(!h.current)return;if(l.current.length>=Ko){A++;continue}const D=l.current.length,I=await p(P);I&&T.push(I),l.current.length>D&&R++}if(!h.current)return;A>0&&T.push(R===0&&T.length===0?jR:bse(A)),r(T.join(" "));const B=l.current.length;R>0&&o(`${R===1?"Image":`${R} images`} added, ${B} of ${Ko}.`)},[p]),v=w.useCallback(async(E,T)=>{r("");const A=l.current.find(P=>P.id===E);if(!A)return g([T]);const R=await p(T,A);if(!h.current)return;r(R);const B=l.current.findIndex(P=>P.id===E);!R&&B!==-1&&o(`Image ${B+1} replaced, ${l.current.length} of ${Ko}.`)},[g,p]),k=w.useCallback(E=>{u.current=(u.current??Promise.resolve()).then(E).catch(()=>{})},[]),b=w.useCallback(E=>{E.length>0&&k(()=>g(E))},[g,k]),x=w.useCallback((E,T)=>{const A=T[0];A&&k(()=>v(E,A))},[k,v]),N=w.useCallback(E=>{const T=l.current.find(A=>A.id===E);T&&(URL.revokeObjectURL(T.url),f(A=>A.filter(R=>R.id!==E)),r(""),o(`Image removed, ${l.current.length} of ${Ko}.`))},[f]),S=w.useCallback(()=>r(jR),[]),_=w.useCallback(async()=>(await u.current,l.current.flatMap(E=>E.blob?[E.blob]:[])),[]);return{shots:e,problem:n,announcement:s,add:b,replace:x,remove:N,sayFull:S,ready:_}}function Tse(){const[e,t]=w.useState(null);return w.useEffect(()=>{let n=!0;return fetch("/api/feedback").then(r=>r.ok?r.json():null).then(r=>{n&&t(Sre(r))}).catch(()=>{}),()=>{n=!1}},[]),e}function Ase(e){const[t,n]=w.useState({state:"idle"}),r=w.useRef(!1);async function s(o){if(lr(r.current)){r.current=!0,n({state:"sending"});try{const l=await e.ready(),u=await fetch("/api/feedback",gse(jre(o),l)),d=await u.json().catch(()=>null);n(u.ok&&(d!=null&&d.ok)?{state:"sent"}:{state:"failed",message:xR(u.status,d==null?void 0:d.reason,d==null?void 0:d.errors)})}catch{n({state:"failed",message:xR(0,null)})}finally{r.current=!1}}}return{outcome:t,sending:t.state==="sending",send:s}}function Rse(e,t){const[n,r]=w.useState(!1),s=w.useRef(t);return s.current=t,w.useEffect(()=>{if(!e)return;const o=window.setTimeout(()=>r(!0),bR),l=window.setTimeout(()=>s.current(),bR+qre);return()=>{window.clearTimeout(o),window.clearTimeout(l)}},[e]),n}function Mse(){var t;return typeof navigator>"u"?"":((t=navigator.userAgentData)==null?void 0:t.platform)||navigator.platform||""}function lL({onClose:e,initialKind:t,initialBody:n}){const r=w.useRef(null),s=w.useRef(null),o=w.useRef(null),l=w.useRef(null),u=w.useRef(0),d=ft(e,{focusRef:r}),[h,f]=w.useState(t??"bug"),[p,g]=w.useState(n??""),[v,k]=w.useState(""),[b,x]=w.useState(!1),[N,S]=w.useState(!1),[_,E]=w.useState(!1),T=Cse(),A=Cre(Tse()),{outcome:R,sending:B,send:P}=Ase(T),D=R.state==="sent",I=Rse(D,e),O=eL(h),z=N&&!wR(p),[M,$]=Dre(Mse());w.useEffect(()=>{var ee,re;const K=l.current;if(!K)return;const Z=K.contains(document.activeElement)||Dr(((ee=document.activeElement)==null?void 0:ee.tagName)??null);K.inert=D,D&&Z&&((re=s.current)==null||re.focus())},[D]);function q(K){var Z;if(K.preventDefault(),!wR(p)){S(!0),(Z=r.current)==null||Z.focus();return}P({kind:h,body:p,contact:v})}const X=!D,H=K=>fse(Array.from(K.dataTransfer.types));function L(K){!H(K)||!X||(K.preventDefault(),u.current++,E(!0))}function W(K){H(K)&&(K.preventDefault(),K.dataTransfer.dropEffect=X?"copy":"none")}function F(K){H(K)&&(u.current=Math.max(0,u.current-1),u.current===0&&E(!1))}function Y(K){H(K)&&(K.preventDefault(),u.current=0,E(!1),X&&T.add(Array.from(K.dataTransfer.files)))}function J(K){K.target!==K.currentTarget||!H(K)||(K.preventDefault(),K.dataTransfer.dropEffect="none")}function V(K){H(K)&&K.preventDefault()}function G(K){if(!X)return;const Z=Array.from(K.clipboardData.files),ee=K.target,re=ee instanceof HTMLTextAreaElement||ee instanceof HTMLInputElement,ue=Array.from(K.clipboardData.types).includes("text/plain");pse({files:Z.length,hasText:ue,intoTextField:re})&&(K.preventDefault(),T.add(Z))}return a.jsx("div",{className:"modal-backdrop",onClick:e,role:"presentation",onDragOver:J,onDrop:V,"data-leaving":I||void 0,children:a.jsxs("div",{ref:d,className:"modal feedback-dialog",onClick:K=>K.stopPropagation(),onDragEnter:L,onDragOver:W,onDragLeave:F,onDrop:Y,onPaste:G,role:"dialog","aria-modal":"true","aria-labelledby":"feedback-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"feedback-title",className:"modal-tool-name",children:"Send feedback"})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{ref:s,type:"button",className:"glyph-btn",onClick:e,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})})]}),a.jsx("p",{className:"vis-hidden",role:"status",children:Bre(R.state)}),a.jsxs("div",{className:"fb-stage",children:[a.jsxs("form",{ref:l,className:"fb-form",onSubmit:q,onKeyDown:K=>{Lre(K.nativeEvent)&&(K.preventDefault(),K.currentTarget.requestSubmit())},noValidate:!0,children:[a.jsxs("section",{className:"modal-body fb-body",children:[a.jsx(Ure,{kind:h,onChange:f}),a.jsxs("div",{className:"fb-compose",children:[a.jsx("label",{className:"fb-label",htmlFor:"fb-body",children:O.question}),a.jsx("textarea",{ref:r,id:"fb-body",className:"ap-manage-input fb-message",value:p,maxLength:wre,rows:5,placeholder:O.placeholder,onChange:K=>g(K.target.value),"aria-invalid":z||void 0,"aria-describedby":z?"fb-body-error":void 0,required:!0}),z&&a.jsx("p",{id:"fb-body-error",className:"fb-error",children:xre}),a.jsx(Sse,{images:T,addRef:o}),a.jsxs("div",{className:"fb-tools",children:[a.jsx(jse,{images:T,buttonRef:o}),a.jsx(zre,{open:b,onToggle:()=>x(K=>!K)})]}),a.jsx(Wre,{open:b,contact:v,onContact:k})]}),a.jsx("p",{className:"fb-facts",children:A?a.jsxs(a.Fragment,{children:[a.jsx("span",{className:"fb-facts-value",children:A})," — ",Tre]}):Are}),R.state==="failed"&&a.jsx("p",{className:"fb-error",role:"alert",children:R.message})]}),a.jsxs("div",{className:"fb-foot",children:[a.jsxs("span",{className:"fb-shortcut","aria-hidden":"true",children:[a.jsx("kbd",{"data-symbol":Fre(M)||void 0,children:M}),a.jsx("kbd",{children:$})," to send"]}),a.jsx("button",{type:"button",className:"btn",onClick:e,children:"Cancel"}),a.jsx("button",{type:"submit",className:"btn primary fb-send","aria-keyshortcuts":"Meta+Enter Control+Enter",...Or(B),children:a.jsxs("span",{className:"fb-send-label",children:[a.jsx("span",{className:"fb-send-idle",children:"Send feedback"}),a.jsx("span",{className:"fb-send-busy",children:"Sending…"})]})})]})]}),D&&a.jsxs("div",{className:"fb-done",children:[a.jsx(fd,{}),a.jsx("p",{className:"fb-done-line",children:tL})]})]}),a.jsx("div",{className:"fb-drop","aria-hidden":"true","data-active":_||void 0,children:a.jsxs("div",{className:"fb-drop-frame",children:[a.jsx(iL,{className:"fb-drop-glyph"}),a.jsx("p",{className:"fb-drop-title",children:"Drop to attach"}),a.jsx("p",{className:"fb-drop-note",children:"PNG or JPEG, up to three"})]})})]})})}const $se="Press Esc first if a key does nothing.",Ise=[{title:"Canvas",rows:[{cap:"Space",action:"pause or resume the stream",binds:[" "]},{cap:"J",action:"next agent",binds:["j","J"]},{cap:"K",action:"previous agent",binds:["k","K"]},{cap:"W",action:"the session waiting on you — oldest first, again for the next",binds:["w","W"]},{cap:"F",action:"fit every agent on screen",binds:["f","F"]},{cap:"Z",action:"zoom to the selected agent and its session",binds:["z","Z"]},{cap:"Delete",action:"take the selected card off the board — Undo brings it back",binds:["Delete"]},{cap:"R",action:"re-arrange the canvas and drop the pins",binds:["r","R"]},{cap:"C",action:"clear the canvas and the event log — asks first",binds:["c","C"]}]},{title:"Panels and dialogs",rows:[{cap:"U",action:"usage panel",binds:["u","U"]},{cap:"L",action:"session list",binds:["l","L"]},{cap:"D",action:"detail panel",binds:["d","D"]},{cap:"H",action:"usage history",binds:["h","H"]},{cap:"A",action:"Claude accounts, where Claude Code is installed",binds:["a","A"]},{cap:"S",action:"this machine — cores, memory, temperature",binds:["s","S"]},{cap:"B",action:"Browser Watch",binds:["b","B"]},{cap:"V",action:"the sound menu — volume, tones, notifications",binds:["v","V"]},{cap:"?",action:"this sheet",binds:["?"]}]},{title:"Settings",rows:[{cap:"M",action:"sound on or off",binds:["m","M"]},{cap:"T",action:"light or dark theme",binds:["t","T"]}]},{title:"Focus and selection",rows:[{cap:"Tab",action:"reach the agent cards",binds:[]},{cap:"Enter",action:"select the focused card",binds:["Enter"]},{cap:"Shift + Enter",action:"add the focused card to the selection",binds:["Enter"]},{cap:"Esc",action:"close what is open, deselect, release focus",binds:["Escape"]}]},{title:"Mouse",rows:[{cap:"drag",action:"move a node, and pin it where you dropped it",binds:[]},{cap:"shift-click",action:"add an agent to the selection",binds:[]},{cap:"click",action:"a card: go to it and its session",binds:[]},{cap:"double-click",action:"a card: its prompt, every tool call, tokens and timing",binds:[]},{cap:"hover",action:"zoomed out, a card's name, state and numbers",binds:[]},{cap:"click",action:"the topbar speaker opens volume and sound settings",binds:[]}]}];function Ose({onClose:e,onTour:t}){const n=ft(e);return a.jsx("div",{className:"modal-backdrop",onClick:e,role:"presentation",children:a.jsxs("div",{ref:n,className:"modal key-help",onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"key-help-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"key-help-title",className:"modal-tool-name",children:"Keyboard shortcuts"})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:e,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})})]}),a.jsxs("section",{className:"modal-body",children:[t&&a.jsxs("div",{className:"guide-door",children:[a.jsx("span",{children:"Eight pictures of what the deck shows."}),a.jsx("button",{type:"button",className:"btn",onClick:t,children:"Take the tour"})]}),a.jsx("div",{className:"shortcuts",children:Ise.map(r=>a.jsxs(w.Fragment,{children:[a.jsx("h3",{className:"kh-group",children:r.title}),r.rows.map(s=>a.jsxs("div",{className:"sc",children:[a.jsx("kbd",{children:s.cap}),a.jsx("span",{children:s.action})]},`${r.title}:${s.cap}:${s.action}`))]},r.title))}),a.jsx("p",{className:"kh-foot",children:$se})]})]})})}function Lse(e,t){const n=(e??[]).filter(s=>s&&typeof s.fp=="string"&&!t.has(s.fp));if(!n.length)return{request:null,waiting:0};const r=[...n].sort((s,o)=>(s.at??0)-(o.at??0));return{request:r[0],waiting:r.length-1}}function Pse({request:e,waiting:t,busy:n,now:r,onAccept:s,onDecline:o,onLater:l}){const u=w.useRef(null),d=ft(l,{focusRef:u}),h=ea(n,"accept"),f=ea(n,"dismiss");return a.jsx("div",{className:"modal-backdrop",onClick:l,role:"presentation",children:a.jsxs("div",{ref:d,className:"modal lan-ask",onClick:p=>p.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"lan-ask-title","aria-describedby":"lan-ask-who",children:[a.jsxs("header",{className:"modal-head",children:[a.jsxs("div",{className:"modal-title",children:[a.jsx("i",{className:"ap-pulse","aria-hidden":!0}),a.jsx("span",{id:"lan-ask-title",className:"modal-tool-name",children:"A deck wants to pair"})]}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:l,"aria-label":"Answer later (Esc)",title:"Answer later (Esc)",children:"×"})})]}),a.jsxs("section",{className:"modal-body",children:[a.jsxs("p",{id:"lan-ask-who",className:"lan-ask-who",children:[a.jsx("strong",{className:"lan-ask-name",children:e.name})," at ",a.jsx("code",{className:"ap-lan-code",children:e.addr})," · asked ",e0(e.at,r)]}),a.jsx("p",{className:"modal-note",children:"Paired decks repair each other's expired logins. Accept only if you know this machine — what it shares is an account, not a screen."}),a.jsxs("p",{className:"lan-ask-fp",children:["Its fingerprint is ",a.jsx("code",{className:"ap-lan-code",children:e.fp})," — that machine shows its own under This deck on the network; the two should match."]}),t>0&&a.jsx("p",{className:"lan-ask-more",children:t===1?"One more deck is waiting behind this one.":`${t} more decks are waiting behind this one.`}),a.jsxs("div",{className:"lan-ask-acts",children:[a.jsx("button",{type:"button",ref:u,className:"btn",disabled:f.disabled,"aria-busy":f.busy,onClick:o,title:"Nothing is shared. If that deck asks again, this comes back.",children:n==="dismiss"?"declining…":"Decline"}),a.jsx("button",{type:"button",className:"btn primary",disabled:h.disabled,"aria-busy":h.busy,onClick:s,title:"Talk to this deck from now on, and repair its expired logins with this deck's own.",children:n==="accept"?"accepting…":"Accept"})]})]})]})})}function Dse({lanPending:e,lanDeferred:t,lanBusy:n,answerLanPair:r,deferLanPair:s}){const{request:o,waiting:l}=Lse(e,t.current);return o?a.jsx(Pse,{request:o,waiting:l,busy:n,now:Date.now(),onAccept:()=>void r("accept",o.fp),onDecline:()=>void r("dismiss",o.fp),onLater:()=>s(o.fp)},o.fp):null}function Fse(e,t){return`${e}#${t}`}function Bse(e,{lan:t,now:n,closed:r}){if(t===void 0)return[];const s=[];for(const o of e??[]){const l=o.reauth;if(!l||l.dismissed||o.origin!=="ccdeck_signin")continue;const u=Fse(l.key,l.since);if(r.has(u)||MV(l.key,o.email,t,n))continue;const d=o.email??"";s.push({id:u,key:l.key,since:l.since,num:o.num,email:d,name:d||o.alias||`Account ${o.num}`,alias:d&&o.alias?o.alias:null})}return s}function qse(e,t){const n=String((t==null?void 0:t.email)??"").trim().toLowerCase();return n?e.filter(r=>r.email.toLowerCase()===n&&((t==null?void 0:t.num)==null||String(r.num)===String(t.num))).map(r=>r.id):[]}function Use({rows:e,ours:t,dialogs:n}){return e===0?!1:t||n===0}function zse(e){return e===1?"Account needs your attention":"Accounts need your attention"}function Wse(e){return`${e===1?"One Claude account needs":`${e} Claude accounts need`} you to sign in again.`}const Hse="Login expired",Vse="Until then the deck cannot read its usage or switch to it. Signing in again brings it back — it keeps its slot, its alias and its history.",Gse="The account stays marked in the Accounts panel. This will not ask again until it has worked and stopped again.";function Kse({rows:e,onSignIn:t,onLater:n,firstRef:r}){const s=e.length===1?e[0]:null;return a.jsxs("section",{className:"modal-body",children:[s?a.jsxs(a.Fragment,{children:[a.jsxs("p",{id:"reauth-lead",className:"reauth-lead",children:["The Claude login for ",a.jsx("strong",{className:"reauth-name",children:s.name}),s.alias?a.jsxs("span",{className:"reauth-alias",children:[" (",s.alias,")"]}):null," ","has expired and needs a new sign","‑","in."]}),a.jsx("p",{className:"reauth-note",children:Vse})]}):a.jsxs(a.Fragment,{children:[a.jsx("p",{id:"reauth-lead",className:"reauth-lead",children:Wse(e.length)}),a.jsx("ul",{className:"reauth-list",children:e.map((o,l)=>a.jsxs("li",{className:"reauth-row",children:[a.jsxs("span",{className:"reauth-who",title:o.alias?`${o.alias} — ${o.name}`:o.name,children:[o.name,o.alias?a.jsxs("span",{className:"reauth-alias",children:[" ",o.alias]}):null]}),a.jsx("span",{className:"reauth-what",children:Hse}),a.jsx("button",{type:"button",className:"btn",ref:l===0?r:void 0,"aria-label":`Sign in again as ${o.name}`,onClick:()=>t(o),children:"Sign in again"})]},o.id))}),a.jsx("p",{className:"reauth-note",children:"Each keeps its slot, its alias and its history."})]}),a.jsxs("div",{className:"reauth-acts",children:[a.jsx("button",{type:"button",className:"btn",title:Gse,onClick:n,children:"Not now"}),s&&a.jsx("button",{type:"button",className:"btn primary",ref:r,onClick:()=>t(s),children:"Sign in again"})]})]})}function Yse({rows:e,onSignIn:t,onLater:n}){const r=w.useRef(null),s=ft(n,{focusRef:r});return a.jsx("div",{className:"modal-backdrop",onClick:n,role:"presentation",children:a.jsxs("div",{ref:s,className:"modal reauth-ask",onClick:o=>o.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"reauth-title","aria-describedby":"reauth-lead",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"reauth-title",className:"modal-tool-name",children:zse(e.length)})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:n,"aria-label":"Not now (Esc)",title:"Not now (Esc)",children:"×"})})]}),a.jsx(Kse,{rows:e,onSignIn:t,onLater:n,firstRef:r})]})})}function Xse({rows:e,signingIn:t,signIn:n,signedIn:r,refresh:s,closeSignIn:o,later:l}){const u=w.useRef(!1),d=t!=null||Use({rows:e.length,ours:u.current,dialogs:Qo.dialogDepth()});return u.current=d,t?a.jsx(G$,{email:t.email||null,onClose:o,onChanged:s,onSignedIn:r}):d?a.jsx(Yse,{rows:e,onSignIn:n,onLater:l},e.length===1?"one":"many"):null}const Zse={"//":["What a user would NOTICE. One key per version that has something to say,","and no key at all for a version that has nothing — which is most of them.","","The rule this file exists to serve: before every release, ask 'what would a","user notice?' and write the answer here. 'Nothing user-visible' is a valid","and expected answer, and it is spelled by adding no key. A release with no","key shows no modal, and that silence is what keeps the modal worth reading","on the day it is not silent.","","What belongs here: something that changed on the user's machine or under","their hands — a hook written to or removed from settings.json, a default","that flipped, a control that moved or went away, a stored preference that","changed meaning. What does not: bounds, races, assertions that could not","fire, anything that is invisible when it is correct.","","Write it for the person running the deck, not for the person reading the","diff. `title` is the sentence they scan; `body` is what they need in order","to act on it, or to stop worrying about it.","","Keys are versions exactly as package.json spells them. A key that is not a","version, or an entry missing either field, fails the suite rather than","being quietly dropped — see src/web/__tests__/release-notes-file.test.ts.","Ordering here is for humans; the deck sorts by version itself.","","A title may open with an emoji, and if it does, put exactly ONE space after","it. Not two: the dialog gives a leading emoji its own optical gap, because","a colour glyph is drawn edge to edge inside its advance and one word space","after one reads as none. A second space here would be a lie in the data","about a defect in the type, and the suite refuses both it and no space at","all.","","A release that genuinely has nothing to say is spelled TWICE: no key here,",'and its version written into "//nothing-to-say" below. One spelling on its',"own is ambiguous — a missing key reads the same whether it was a decision","or a forgotten step, and five releases in a row were once forgotten. The list","is what tells the two apart, and release-notes-current-version.test.ts fails","the release whose version is in neither place. Any key opening with // is a","note to the author and never a release."],"//nothing-to-say":["3.23.1","3.23.2","3.23.3","3.23.4","3.26.0","3.26.1","3.26.3","3.26.4","3.26.5","3.27.6","3.27.7","3.28.0","3.28.1","3.29.3","3.29.4","3.29.6","3.29.7","3.29.9","3.33.1","3.35.0","3.35.1","3.36.0","3.36.1","3.36.4","3.36.5","3.36.6","3.36.7","3.36.8"],"3.36.3":[{title:"🔑 An expired login asks to be signed in again",body:"When an account you added with + → Sign in stops working, the deck now tells you in one dialog, with every affected account together and Sign in again beside each. That opens the usual sign-in with the address already filled in, and the account keeps its slot, its alias and its history. Not now leaves it marked in the Accounts panel and does not ask again until it has worked and stopped again. A paired deck holding a working copy gets to repair it first, and accounts that came from a share or Local network are never asked about."}],"3.34.1":[{title:"🗂️ The tray menu stays open while you read it",body:"On Linux the menu behind the tray icon could close by itself while agents were working, often before the row you pressed took the click, so it had to be opened and pressed again. It now changes only when something it says has changed, and stays open while you choose."}],"3.34.0":[{title:"🗺️ The network map shows who you are connected to",body:"Paired decks that are on stand closest to this one, and a light runs along their wires. Paired decks that are away come next, in a colour of their own, with how long they have been gone, and machines that are not paired stand outside in grey. Decks reached over Tailscale sit together in their own shaded slice instead of on dashed wires. Pointing at a deck opens its details beside the map: whether it is there, what to do next if anything, its address and what it runs, and every login the two share, the ones that need a look first."},{title:"🔑 A login that expired everywhere says so",body:`When a shared login expired on this deck and every paired deck that is on held an expired copy too, the row said "Login expired" and waited for a repair that could never come. It now says "Login expired on all online decks". Claude retires a login's other copies each time one machine refreshes it, so shared copies expire together: sign in again on any one machine and the others repair from it on their next round.`},{title:"💬 Send feedback from inside the deck",body:'Appearance settings has a "Send feedback…" button: pick what kind it is, write a title and a few lines, and add a way to reach you if you want an answer. It goes to the people who make ccdeck, who may open a public issue from it, never with your contact in it. If it cannot be sent, the form stays filled in and says why.'}],"3.33.0":[{title:"🪝 Hooks you add in Claude Code are no longer removed",body:"Claude Code's hook editor often files a new hook into the same group as the deck's own. Starting the deck, or uninstalling it, then removed the whole group, and your hook went with it without a word. The deck now takes out only its own command and leaves yours where it was, and the same goes for the old finish-sound hook. `ccdeck --uninstall` also stops the decks that are running, so an update in the background cannot put the hooks back."},{title:"🧵 Resumed Codex sessions show up again",body:"A Codex session picked up with `codex resume`, or one that ran for days, could go dark on the canvas and drop out of the 5-hour and 7-day usage, because the deck only looked in the two newest day folders. It now follows those sessions and counts their tokens. Two Codex sessions in the same folder get different id suffixes, and a `bash -lc` call shows the command it runs."},{title:"🤝 A deck paired by the accept switch brings in less",body:'A deck that paired through the automatic accept switch, and not through an invite, now brings in only the logins you have ticked on this deck, and nothing from it is ticked for sharing onward. Its dialog says "paired automatically — pair by invite to take it". Pairing by invite works as before. A typed address is no longer dropped after one failed round, and the sync port stays the same after a rename.'},{title:"⬆️ Desktop updates install when you expect them",body:'On Windows and Linux a downloaded update now installs when you quit. On a Mac, Quit installs it and the app stays closed. The Linux .deb needs an administrator password to install, so it no longer does that by itself: it waits for "Restart to update" and says a password is coming. "Start at login" now works on Linux, and "Restart ccdeck" stops a deck that has hung.'},{title:"🕵️ Browser Watch waits before it acts, and quits only its own browser",body:"A program's visit is now logged and reacted to once its quiet window has closed, about 16 minutes after it, and not on the first poll, so a visit that is withdrawn no longer quits the browser. On Linux the quit reaction ends only the browser that holds the watched profile; other Chrome channels and headless browsers keep running. When it cannot tell which process that is, it does nothing and says so, which includes a Flatpak Brave."},{title:"🧮 Figures that were off are right",body:`Spend by labelled and workflow subagents counts toward a session's tokens and cost. The Projects report prices 1-hour cache writes the way ccusage does, so its dollars match the Usage panel. After a restart the deck rebuilds its whole event history, where it used to rebuild about a third. On Linux the process list shows what each process is using now, not its lifetime average. A count near a boundary reads "1.00M", and a full quota window says "used up".`},{title:"🔑 The Usage panel's reads answer the deck's own page only",body:"The routes behind the Usage panel (/api/quota, /api/ccusage, /api/codex-usage, /api/codex-quota and /api/cswap-auto) now answer the deck's own page, or a client that sends the deck's token. A script that read them with a plain request gets 401 from this version on. The hook, the desktop app and the `ccdeck` command are not affected."},{title:"🛠️ Smaller fixes you may notice",body:"With the canvas paused, an update no longer restarts the server while an agent is working: it waits until no turn is open. `ccdeck --stop -p4317` stops that one deck. A deck started at login can update itself on Homebrew and nvm setups. Dialogs keep keyboard focus after a button goes away, screen readers hear pairing requests by deck name and the machine panel's readings, dim text is readable in both themes, Windows High Contrast shows what is selected, and below 640px the detail panel opens over the canvas."}],"3.32.2":[{title:"🔒 Self-updates wait until your screen is unlocked",body:"On Linux with GNOME, the app installs an update by itself when you leave it alone, which is often while your screen is locked. GNOME switches the panel's tray off under the lock screen, and a version that started then never showed its icon. The app now waits until you are back before restarting by itself, so its icon comes back with it."}],"3.32.1":[{title:"🔔 The tray icon comes back on Linux",body:"On Linux the app could lose its icon in the panel, after it updated itself or when the panel restarted, and stay without it until it was quit. It now notices within a minute and restarts by itself the next time you are away from its window, the way it installs an update. It does this at most once every six hours, and not again if a restart did not help."},{title:"🔕 A window you closed stays closed after a self-update",body:"When the app restarted by itself to install an update, it opened its window again even if you had closed it, and while that window was open the deck held back its notifications. A window you closed now stays closed."},{title:"🪟 No empty icon when ccdeck is already open",body:'Opening ccdeck again while it was running flashed an empty "…" icon in the Linux panel before bringing the window forward. It now only brings the window forward.'}],"3.32.0":[{title:"🗺️ A map of your network",body:"A new button on the Local network row, and in its view's header, opens a map of every deck around this one. This deck is at the centre and the paired decks that are on stand around it. Further out are the quiet ones, the machines nearby and any deck asking to pair. Wires are solid on your local network and dashed over Tailscale. Pointing at a deck says how it is reached, what it runs and which logins the two share, and pressing it opens that deck's own dialog."}],"3.31.0":[{title:"📊 Account capacity",body:"A gauge button in the accounts panel's header opens Account capacity: how many of your Claude accounts are ready now, what the 5-hour and 7-day windows have left across all of them, and a row for each account with its use, its state (Ready, Limited, Exhausted or Stale) and when it was last read. Each window also says whose reset comes next."},{title:"🚦 Claude and Codex incidents",body:"When Anthropic's or OpenAI's status page reports an incident on something your sessions use, the deck says so in the topbar, the Usage panel and the tray, and every mark links to the status page. A provider with nothing wrong draws nothing."},{title:"↕️ Order your other accounts",body:"The Other accounts row can sort the list by slot, by how full a window is, or by most room, and open every account at once. Both stay as you left them after a reload."},{title:"🔐 A settings folder left by sudo can be given back",body:'On a Mac, running ccdeck once with sudo left its settings folder owned by another user, and every settings change was refused, sharing an account included. The message now offers "give it back": macOS asks for your password, the folder is yours again, and the deck reads its settings back without a restart.'}],"3.30.0":[{title:"🧮 Token counts and costs are no longer doubled",body:"Claude Code writes a reply that has thinking, text and tool calls as several transcript lines, each carrying the whole request's usage. The deck added up every line, so a session's tokens and cost on its card came out close to twice what it was billed, and the context panel counted each part of a reply as its own message. Each request is now counted once. Expect the totals on your cards to come down."},{title:"⏳ Claude's saved limit resets on the Usage panel",body:`When your Claude account has free limit resets to spend ("Reset for free" in Claude's Settings → Usage), the Claude card now says how many there are and when the first one expires, the way the Codex card already did. The deck only reports them; you spend them in Claude's own settings.`},{title:"🔑 LAN invites need the other deck updated",body:`An invite's code is now a long random secret instead of six digits, so nobody else on the network can work it out and pair in your place, and a wrong code is refused by name. An invite made on this version can only be joined from a deck on this version or later: an older deck answers "That is not an invite". Update that deck, or make the invite on the older one and join it here.`},{title:"🔕 AGENTS_DECK_NO_NOTIFY keeps Browser Watch quiet too",body:"A deck started with AGENTS_DECK_NO_NOTIFY=1 still raised a desktop notification for every new Browser Watch finding. It no longer does. The finding is still in the Browser Watch panel and its log, which says why no notification came."},{title:"🛠️ Smaller fixes",body:'Delete removes the card you just clicked. A LAN address you typed stays in the list when a round cannot reach it, and the status line under the LAN switch now agrees with the rows below it. In the desktop app, a dismissed "update is ready" notice stays dismissed after you answer the first-launch question. Sizes read the same in every panel ("1.1 KB"). Faded text stays readable, a keyboard can see the scrollbar, and numbers use a monospaced font on Windows and Linux as well.'}],"3.29.11":[{title:"💲 Claude Sonnet 5.5 has a price",body:`Sessions on Claude Sonnet 5.5 showed "not priced" on the board, and the Projects report could only show a minimum for them. They are now priced at Anthropic's published rate, the same as Sonnet 5: $2 per million input tokens and $10 per million output tokens.`}],"3.29.10":[{title:"💲 Claude Opus 5.5 has a price",body:`Sessions on Claude Opus 5.5 showed "not priced" on the board, and the Projects report counted them as costing nothing, so its totals read far too low. They are now priced at Anthropic's published rate. If a model still has no price, Projects shows its total as a minimum, like $11.19+, and names the model, instead of a total that looks complete.`}],"3.29.8":[{title:"🔁 Removed cards come back when their agent works again",body:"Remove from board is for finished work. If the agent behind a card you removed starts a tool call, gets a new prompt or starts a subagent, the card returns on its own, together with the card it hangs from, and it stays back after a reload. Cards whose agents stay idle stay removed."}],"3.29.1":[{title:"🗂️ One row per project in Projects",body:"The Projects report now counts a session's work under the folder you started it in, the same name its card has on the canvas, instead of adding a row for every folder the agent moved into. The first start after updating re-reads your transcripts once to regroup the report."},{title:"🔔 Tidier custom sounds",body:"Importing a sound, recording one and choosing a spoken voice now sit in compact cards that respond to clicks and focus like the rest of the deck."}],"3.29.0":[{title:"🎵 Your own stations in Claude FM",body:"Add any YouTube live link or radio stream as a station in Appearance, next to the built-in ones, and mute the music without losing your volume. When a station is not live, the picker now says so instead of staying silent."},{title:"🔔 Notification sounds and voices of your own",body:"Pick a local audio file or a spoken voice for each alert, so the sound that means an agent is waiting is one you will recognise."},{title:"🗑️ Drag a card off the board to remove it",body:"Drag a card to the target at the bottom of the canvas to take it off the board, or use Remove from board in the detail panel (Delete). The session keeps running, and the session list (L) brings it back."},{title:"🔐 Optional invite-only pairing",body:"Pairing works as before, with or without an invite. If you want, Local network can now be set to accept only decks that join with an invite you created, so a nearby machine cannot ask to pair on its own."},{title:"🌐 Account sharing between your machines is more reliable",body:"A locked macOS Keychain is explained instead of failing silently, a login that is broken or belongs to another account is never shared, and a sync that stops partway keeps what already arrived."},{title:"📅 Projects opens on Today",body:"The Projects report per account starts with today's work, and an empty window offers the next wider one. The topbar also shows this month's usage, and the desktop app can restart into an update from its own window."}],"3.27.0":[{title:"📊 Projects: where each account spent its work",body:"Each account card's ⋯ menu now breaks its cost down by project, over 7d / 30d / all."}],"3.27.1":[{title:"🎨 Make Appearance easier to tune",body:"The Appearance dialog now has clearer sections, stronger selection states, and a calmer layout for choosing a theme and configuring music. Your existing settings and keyboard shortcuts continue to work."}],"3.27.2":[{title:"💰 Project costs now match Usage",body:"Project cost totals no longer count the same Claude request once for every content block. Existing project history is rebuilt automatically so the totals align with the Usage panel."}],"3.27.3":[{title:"🎵 Station selection works in the desktop app",body:"The music station picker now opens inside ccdeck instead of relying on the operating system popup, so choosing a station works reliably in the Electron app."}],"3.27.4":[{title:"✅ Desktop picker release checks are green",body:"The station picker now keeps the same keyboard, motion, focus, and layout behavior as the rest of ccdeck across desktop and browser builds."}],"3.27.5":[{title:"🧭 The welcome tour stays dismissed",body:"Electron now remembers that you completed the onboarding tour at the deck level, so an update or loopback port change no longer shows the same tour again."},{title:"🎵 The station picker has a clearer finish",body:"The music selector has a tighter trigger, a purposeful chevron, and more deliberate option spacing so it reads like a native part of Appearance."}],"3.25.0":[{title:"🖱️ A click goes to the session, a double-click opens the details",body:"Clicking a card frames it with the rest of its session and leaves the detail panel shut. Double-click a card to open its details; `j`/`k`, the session list, Enter and `D` still open them too."},{title:"🔭 Cards stay readable when you zoom out",body:"At every zoom a card shows its name, its state and anything waiting on you, instead of shrinking to a coloured pill. Hover or focus a card to see it whole. Recap notes read at a distance too, and show the full recap on hover."},{title:"🌐 The Machine panel shows your network",body:"A new Network section shows download, upload and the round trip to Claude's API, and names the route when traffic goes through a VPN or a Tailscale exit node. Those checks run only while the panel is open. The panel is regrouped into CPU, Memory, Network and Thermal, with a flag at the top when something needs attention."},{title:"🎨 See each theme before you pick it",body:"The Appearance menu shows a small preview of the deck in Light and in Dark. Claude FM also keeps its volume when a notification chime plays, instead of dipping under it."}],"3.24.1":[{title:"🎛️ Appearance is one small menu now",body:"The topbar's appearance control opens a compact menu where you can choose Light or Dark and show or hide the character. The character stays on by default, and your choice is remembered."}],"3.24.0":[{title:"🎧 Claude FM plays from the minimap",body:"A small character stands on the minimap's top edge. Press it and the Claude channel's live stream plays; press it again and it stops. It only turns up when the channel is actually broadcasting, so a deck with no network never shows it at all."},{title:"🤠 And it gets on with its day",body:"It walks that edge, tidies up what it finds, kicks a ball along it, sits with its legs over the side, and watches the board through a scope. Now and then it drops onto the canvas and ropes its way back up. Headphones on while the music plays, a hat when it does not."}],"3.23.5":[{title:"🧭 The accounts panel opens on the account you are on",body:"Its windows, its resets and how old the numbers are — whole, at the top of the column. Every other account stands behind one row that says how many of them a switch could actually reach; press it to see them, or rest on it to be shown their names and how much room each has left."},{title:"⚙️ Auto-switch moved in there with them",body:"It is behind that same row now, after the accounts it switches between, rather than at the foot of the panel. The row says `auto 90%` or `auto off` so you can tell what the policy is doing without opening it — and says so in amber when it is armed with nowhere left to go."}],"3.23.0":[{title:"🧭 The accounts column leads with accounts",body:`The live account is the open row: its windows as bars, when each one resets, and how old the numbers are. Every other account rests on the two figures a switch is decided by, and opens with a click when you want the rest.
|
|
100
|
+
What did you expect to happen?`},{value:"idea",label:"Idea",question:"What would make ccdeck better?",placeholder:"It would be useful if…"},{value:"other",label:"Other",question:"What would you like to tell us?",placeholder:"Write anything…"}];function eL(e){return nb.find(t=>t.value===e)??nb[0]}const rb=3,Gv=120,wre=1e4,vre=200,xre="Write something first.";function wR(e){return e.trim()!==""}function bre(e){var o;const t=e.trim();if(t==="")return"";const n=vR(t.split(/\r?\n/,1)[0]),r=((o=/^(.+?[.!?])(?=\s|$)/.exec(n))==null?void 0:o[1])??n,s=Kv(r).length>=rb?Kv(r):Kv(n);return Nre(s.length>=rb?s:vR(t))}function kre(e,t){const n=bre(t);return n.length>=rb?n:`${eL(e).label}: ${n}`}function vR(e){return e.replace(/\s+/g," ").trim()}function Kv(e){return/[^.]\.$/.test(e)?e.slice(0,-1):e}function Nre(e){if(e.length<=Gv)return e;const t=e.slice(0,Gv-1),n=t.lastIndexOf(" ");return`${(n>=Gv/2?t.slice(0,n):t).replace(/[\uD800-\uDBFF]$/,"").replace(/[\s,;:.\-–—]+$/,"")}…`}function jre({kind:e,body:t,contact:n}){const r={kind:e,title:kre(e,t),body:t.trim()};return n.trim()!==""&&(r.contact=n.trim()),r}function Sre(e){const t=e;return(t==null?void 0:t.ok)!==!0||typeof t.appVersion!="string"||typeof t.platform!="string"||t.appVersion===""||t.platform===""?null:{appVersion:t.appVersion,platform:t.platform}}const _re={darwin:"macOS",win32:"Windows",linux:"Linux"};function Ere(e){const t=e.indexOf("-"),n=t===-1?e:e.slice(0,t),r=t===-1?"":e.slice(t+1),s=_re[n]??n;return r?`${s} · ${r}`:s}function Cre(e){return e?`ccdeck ${e.appVersion} · ${Ere(e.platform)}`:null}const Tre="sent with it",Are="Your ccdeck version and system are sent with it.",Rre="Add details",Mre="How to reach you",$re="Email or GitHub @name",Ire="Only if you would like an answer.",Ore=["It all goes to the people who make ccdeck, with the version and system below.","Nothing becomes public by being sent. They may open a public GitHub issue from it, choosing its title and text; your contact and screenshots are never put on one."];function Lre(e){return e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&!e.altKey&&!e.shiftKey&&!e.isComposing}function Pre(e){return e.key==="Enter"&&!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey&&!e.isComposing}function Dre(e){return/mac|iphone|ipad/i.test(e)?["⌘","Enter"]:["Ctrl","Enter"]}function Fre(e){return[...e].length===1}const tL="Thanks — feedback sent.";function xR(e,t,n){if(t==="vetoed")return"This deck was started with AGENTS_DECK_NO_INSTALL=1, which keeps it off the network, so nothing was sent.";if(e===429||t==="too_many")return"Too much feedback from this network in the last hour. Try again later; your text is still here.";if(e===413||t==="too_large")return"The images are too large to send together. Remove one and send again; your text is still here.";const r=n==null?void 0:n.images;return e===400&&Array.isArray(r)&&typeof r[0]=="string"?`The server did not accept an image. ${r[0]} Remove it and send again; your text is still here.`:e===400?"The server did not accept the message. It is still here; check it and send again.":"ccdeck's server could not be reached. Nothing was sent; your text is still here, so try again in a moment."}function Bre(e){return e==="sending"?"Sending…":e==="sent"?tL:""}const bR=1400,qre=160;function Ure({kind:e,onChange:t}){return a.jsxs("fieldset",{className:"fb-kinds",children:[a.jsx("legend",{className:"vis-hidden",children:"Kind of feedback"}),nb.map(n=>a.jsxs("label",{className:"fb-kind",children:[a.jsx("input",{type:"radio",name:"feedback-kind",value:n.value,checked:e===n.value,onChange:()=>t(n.value)}),n.label]},n.value))]})}const nL="fb-details";function zre({open:e,onToggle:t}){return a.jsxs("button",{type:"button",className:"fb-tool fb-more","aria-expanded":e,"aria-controls":nL,onClick:t,children:[Rre,a.jsx("svg",{className:"fb-chev",width:"10",height:"10",viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,focusable:"false",children:a.jsx("path",{d:"M2.5 3.75 5 6.25l2.5-2.5"})})]})}function Wre({open:e,contact:t,onContact:n}){const r=w.useId();return a.jsx("div",{id:nL,className:"fb-details","data-open":e||void 0,children:a.jsx("div",{className:"fb-details-clip",children:a.jsxs("div",{className:"fb-details-body",children:[a.jsxs("div",{className:"fb-field",children:[a.jsxs("label",{className:"fb-field-label",htmlFor:"fb-contact",children:[Mre,a.jsx("span",{className:"fb-optional",children:"optional"})]}),a.jsx("input",{id:"fb-contact",className:"ap-manage-input",type:"text",value:t,maxLength:vre,autoComplete:"email",placeholder:$re,"aria-describedby":r,onChange:s=>n(s.target.value),onKeyDown:s=>{Pre(s.nativeEvent)&&s.preventDefault()}}),a.jsx("p",{id:r,className:"fb-hint",children:Ire})]}),a.jsx("div",{className:"fb-where",children:Ore.map(s=>a.jsx("p",{children:s},s))})]})})})}const Ko=3,Hre=5*1024*1024,Vre=12*1024*1024,sb=8192,Gre=64*1024,Kre=256*1024,Yre=.92,Xre=640,rL=.9,Zre=.85,Jre=6,Qre=512*1024,Ed={png:"image/png",jpeg:"image/jpeg"},ese={png:"png",jpeg:"jpg"},tse=[137,80,78,71,13,10,26,10],nse=[255,216,255];function sL(e){const t=n=>e.length>=n.length&&n.every((r,s)=>e[s]===r);return t(tse)?"png":t(nse)?"jpeg":null}function rse(e){const t=sL(e);return t==="png"?sse(e):t==="jpeg"?ose(e):null}function sse(e){const t=[73,72,68,82];if(e.length<24||!t.every((r,s)=>e[12+s]===r))return null;const n=new DataView(e.buffer,e.byteOffset,e.byteLength);return{width:n.getUint32(16),height:n.getUint32(20)}}function ase(e){return e>=192&&e<=207&&e!==196&&e!==200&&e!==204}function ose(e){let t=2;for(;t<e.length;){if(e[t]!==255)return null;for(;t<e.length&&e[t]===255;)t++;const n=e[t++];if(n===void 0||n===217||n===218)return null;if(n===1||n>=208&&n<=215)continue;if(t+2>e.length)return null;const r=e[t]<<8|e[t+1];if(r<2)return null;if(ase(n))return t+7>e.length?null:{height:e[t+3]<<8|e[t+4],width:e[t+5]<<8|e[t+6]};t+=r}return null}function kR(e,t){return e.bytes>t||e.width>sb||e.height>sb}function ise(e){return Math.min(Hre,Vre-Gre-e)}function aL(e,t){return{width:Math.max(1,Math.floor(e.width*t)),height:Math.max(1,Math.floor(e.height*t))}}function lse(e,t){const n=sb/Math.max(e.width,e.height),r=e.bytes>t?Math.sqrt(t/e.bytes)*rL:1;return aL(e,Math.min(1,n,r))}async function cse(e,t,n){let r=lse(e,t),s=e.format;for(let o=0;o<Jre;o++){const l=await n(r,s,s==="jpeg"?Yre:void 0);if(!l)return null;if(l.size<=t)return{...r,blob:l,format:s};if(s==="png"){s="jpeg";continue}if(r=aL(r,Math.min(Zre,Math.sqrt(t/l.size)*rL)),Math.max(r.width,r.height)<Xre)return null}return null}async function use(e,t,n){const r=new Uint8Array(await e.slice(0,Qre).arrayBuffer()),s=sL(r);if(!s)return{ok:!1,problem:"type"};if(t<Kre)return{ok:!1,problem:"no_room"};const o=e.type===Ed[s]?e:new Blob([e],{type:Ed[s]}),l=rse(r);if(l&&!kR({...l,bytes:e.size},t))return{ok:!0,blob:o,resized:null};const u=await n(e).catch(()=>null);if(!u)return{ok:!1,problem:"unreadable"};try{const d={width:u.width,height:u.height,bytes:e.size,format:s};if(!kR(d,t))return{ok:!0,blob:o,resized:null};const h=await cse(d,t,u.encoder);return h?{ok:!0,blob:h.blob,resized:{width:h.width,height:h.height}}:{ok:!1,problem:"too_large"}}finally{u.release()}}function dse(e){return({width:t,height:n},r,s)=>new Promise(o=>{const l=document.createElement("canvas");l.width=t,l.height=n;const u=l.getContext("2d");if(!u)return o(null);r==="jpeg"&&(u.fillStyle="white",u.fillRect(0,0,t,n)),u.imageSmoothingQuality="high",u.drawImage(e,0,0,t,n),l.toBlob(d=>{l.width=0,l.height=0,o(d)},Ed[r],s)})}const hse=async e=>{const t=await createImageBitmap(e);return{width:t.width,height:t.height,encoder:dse(t),release:()=>t.close()}};function fse(e){return e.includes("Files")}function pse({files:e,hasText:t,intoTextField:n}){return e>0&&(!t||!n)}function mse(e,t){const n=e.indexOf(t);return e[n+1]??e[n-1]??null}function gse(e,t){if(t.length===0)return{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e)};const n=new FormData;return n.append("kind",e.kind),n.append("title",e.title),n.append("body",e.body),e.contact!==void 0&&n.append("contact",e.contact),t.forEach((r,s)=>{const o=r.type===Ed.jpeg?"jpeg":"png";n.append("images",r,`image-${s+1}.${ese[o]}`)}),{method:"POST",body:n}}const yse="Add screenshot",NR="PNG or JPEG, up to three. You can also paste one, or drop it on this dialog.",wse="A screenshot can show emails, costs and paths. Crop out what should not be seen.",jR="Three images at most. Remove one to add another.";function vse(e,t,n){const r=n?`, resized to ${n.width} × ${n.height} to fit`:"";return`Replace image ${e+1}: ${t}${r}`}function xse(e,t){const n=`${e} of ${Ko}`;return t>0?`${n} · ${t} resized to fit`:n}function bse(e){return`Three images at most, so ${e===1?"one was":`${e} were`} left out.`}function kse(e){return`“${e.length>40?`${e.slice(0,30)}…${e.slice(-9)}`:e}”`}function Nse(e,t){const n=kse(t);return e==="type"?`${n} is not a PNG or a JPEG, so it was not added. GIF, WebP and HEIC are not converted; save it as a PNG or a JPEG first.`:e==="no_room"?`There is no room left for ${n}: images go at up to 12 MB in all. Remove one to add it.`:e==="unreadable"?`${n} could not be read as an image.`:`${n} could not be made small enough to send: images go at up to 5 MB each.`}const oL="image/png,image/jpeg";function iL({className:e="fb-glyph"}){return a.jsxs("svg",{className:e,width:"14",height:"14",viewBox:"0 0 14 14",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":!0,focusable:"false",children:[a.jsx("rect",{x:"1.8",y:"2.4",width:"10.4",height:"9.2",rx:"1.6"}),a.jsx("circle",{cx:"5",cy:"5.6",r:"1"}),a.jsx("path",{d:"M2.4 10.4 5.5 7.6l2.1 1.8 1.5-1.3 2.5 2.2"})]})}function jse({images:e,buttonRef:t}){const n=w.useRef(null),r=e.shots.length>=Ko;return a.jsxs(a.Fragment,{children:[a.jsxs("button",{ref:t,type:"button",className:"fb-tool",title:NR,"aria-describedby":"fb-attach-hint",onClick:()=>{var s;return r?e.sayFull():(s=n.current)==null?void 0:s.click()},children:[a.jsx(iL,{}),yse]}),a.jsx("span",{id:"fb-attach-hint",hidden:!0,children:NR}),a.jsx("input",{ref:n,type:"file",accept:oL,multiple:!0,hidden:!0,onChange:s=>{e.add(Array.from(s.target.files??[])),s.target.value=""}})]})}function Sse({images:e,addRef:t}){const n=w.useRef(new Map),r=w.useRef(null),s=w.useRef(null),o=w.useRef(null),[l,u]=w.useState(null),{shots:d}=e,h=xse(d.length,d.filter(g=>g.resized).length);w.useLayoutEffect(()=>{var g;l!==null&&((g=l==="add"?t.current:n.current.get(l))==null||g.focus(),u(null))},[l,t]),w.useEffect(()=>{var g;e.problem&&((g=r.current)==null||g.scrollIntoView({block:"nearest"}))},[e.problem]);function f(g){u(mse(d.map(v=>v.id),g)??"add"),e.remove(g)}function p(g){var v;o.current=g,(v=s.current)==null||v.click()}return a.jsxs(a.Fragment,{children:[a.jsx("p",{className:"vis-hidden",role:"status",children:e.announcement}),a.jsx("input",{ref:s,type:"file",accept:oL,hidden:!0,onChange:g=>{const v=o.current;v!==null&&e.replace(v,Array.from(g.target.files??[])),o.current=null,g.target.value=""}}),d.length>0&&a.jsxs("div",{className:"fb-shots",children:[a.jsxs("div",{className:"fb-shots-row",children:[a.jsx("ul",{className:"fb-shots-list",children:d.map((g,v)=>a.jsxs("li",{className:"fb-shot","data-fitting":g.blob?void 0:!0,children:[a.jsxs("button",{type:"button",className:"fb-shot-pick","aria-label":vse(v,g.name,g.resized),onClick:()=>p(g.id),children:[a.jsx("img",{className:"fb-shot-img",src:g.url,alt:""}),g.blob?a.jsx("span",{className:"fb-shot-cue","aria-hidden":"true",children:"Replace"}):a.jsx("span",{className:"fb-shot-busy","aria-hidden":"true",children:"Resizing…"})]}),a.jsx("button",{ref:k=>{k?n.current.set(g.id,k):n.current.delete(g.id)},type:"button",className:"glyph-btn fb-shot-remove","aria-label":`Remove image ${v+1}`,title:`Remove image ${v+1}`,onClick:()=>f(g.id),children:a.jsx("svg",{width:"10",height:"10",viewBox:"0 0 10 10",fill:"none",stroke:"currentColor",strokeWidth:"1.4",strokeLinecap:"round","aria-hidden":!0,focusable:"false",children:a.jsx("path",{d:"M2.5 2.5 7.5 7.5M7.5 2.5 2.5 7.5"})})})]},g.id))}),a.jsx("span",{className:"fb-shots-count",children:h})]}),a.jsx("p",{className:"fb-hint",children:wse})]}),e.problem&&a.jsx("p",{ref:r,className:"fb-error",role:"alert",children:e.problem})]})}function _se(e,t,n){return n?e.map(r=>r.id===t.id?t:r):[...e,t]}function Ese(e,t,n){return n?e.map(r=>r.id===t?n:r):e.filter(r=>r.id!==t)}function Cse(){const[e,t]=w.useState([]),[n,r]=w.useState(""),[s,o]=w.useState(""),l=w.useRef([]),u=w.useRef(null),d=w.useRef(1),h=w.useRef(!0),f=w.useCallback(E=>{l.current=E(l.current),t(l.current)},[]);w.useEffect(()=>(h.current=!0,()=>{h.current=!1;for(const E of l.current)URL.revokeObjectURL(E.url)}),[]);const p=w.useCallback(async(E,T)=>{const A=l.current.reduce((M,$)=>{var q;return M+($.id===(T==null?void 0:T.id)?0:((q=$.blob)==null?void 0:q.size)??0)},0),R=(T==null?void 0:T.id)??d.current++,B=URL.createObjectURL(E),P={id:R,name:E.name||"image",url:B,blob:null,resized:null};let D=!1;const I=()=>{D||(D=!0,f(M=>_se(M,P,T!==void 0)))},O=await use(E,ise(A),async M=>(I(),hse(M))).catch(()=>({ok:!1,problem:"unreadable"})),z=l.current.some(M=>M.id===R);return!h.current||!z&&(D||T)?(URL.revokeObjectURL(B),T&&URL.revokeObjectURL(T.url),""):O.ok?(I(),T&&URL.revokeObjectURL(T.url),f(M=>M.map($=>$.id===R?{...$,blob:O.blob,resized:O.resized}:$)),""):(URL.revokeObjectURL(B),D&&f(M=>Ese(M,R,T)),Nse(O.problem,E.name||"image"))},[f]),g=w.useCallback(async E=>{r("");const T=[];let A=0,R=0;for(const P of E){if(!h.current)return;if(l.current.length>=Ko){A++;continue}const D=l.current.length,I=await p(P);I&&T.push(I),l.current.length>D&&R++}if(!h.current)return;A>0&&T.push(R===0&&T.length===0?jR:bse(A)),r(T.join(" "));const B=l.current.length;R>0&&o(`${R===1?"Image":`${R} images`} added, ${B} of ${Ko}.`)},[p]),v=w.useCallback(async(E,T)=>{r("");const A=l.current.find(P=>P.id===E);if(!A)return g([T]);const R=await p(T,A);if(!h.current)return;r(R);const B=l.current.findIndex(P=>P.id===E);!R&&B!==-1&&o(`Image ${B+1} replaced, ${l.current.length} of ${Ko}.`)},[g,p]),k=w.useCallback(E=>{u.current=(u.current??Promise.resolve()).then(E).catch(()=>{})},[]),b=w.useCallback(E=>{E.length>0&&k(()=>g(E))},[g,k]),x=w.useCallback((E,T)=>{const A=T[0];A&&k(()=>v(E,A))},[k,v]),N=w.useCallback(E=>{const T=l.current.find(A=>A.id===E);T&&(URL.revokeObjectURL(T.url),f(A=>A.filter(R=>R.id!==E)),r(""),o(`Image removed, ${l.current.length} of ${Ko}.`))},[f]),S=w.useCallback(()=>r(jR),[]),_=w.useCallback(async()=>(await u.current,l.current.flatMap(E=>E.blob?[E.blob]:[])),[]);return{shots:e,problem:n,announcement:s,add:b,replace:x,remove:N,sayFull:S,ready:_}}function Tse(){const[e,t]=w.useState(null);return w.useEffect(()=>{let n=!0;return fetch("/api/feedback").then(r=>r.ok?r.json():null).then(r=>{n&&t(Sre(r))}).catch(()=>{}),()=>{n=!1}},[]),e}function Ase(e){const[t,n]=w.useState({state:"idle"}),r=w.useRef(!1);async function s(o){if(lr(r.current)){r.current=!0,n({state:"sending"});try{const l=await e.ready(),u=await fetch("/api/feedback",gse(jre(o),l)),d=await u.json().catch(()=>null);n(u.ok&&(d!=null&&d.ok)?{state:"sent"}:{state:"failed",message:xR(u.status,d==null?void 0:d.reason,d==null?void 0:d.errors)})}catch{n({state:"failed",message:xR(0,null)})}finally{r.current=!1}}}return{outcome:t,sending:t.state==="sending",send:s}}function Rse(e,t){const[n,r]=w.useState(!1),s=w.useRef(t);return s.current=t,w.useEffect(()=>{if(!e)return;const o=window.setTimeout(()=>r(!0),bR),l=window.setTimeout(()=>s.current(),bR+qre);return()=>{window.clearTimeout(o),window.clearTimeout(l)}},[e]),n}function Mse(){var t;return typeof navigator>"u"?"":((t=navigator.userAgentData)==null?void 0:t.platform)||navigator.platform||""}function lL({onClose:e,initialKind:t,initialBody:n}){const r=w.useRef(null),s=w.useRef(null),o=w.useRef(null),l=w.useRef(null),u=w.useRef(0),d=ft(e,{focusRef:r}),[h,f]=w.useState(t??"bug"),[p,g]=w.useState(n??""),[v,k]=w.useState(""),[b,x]=w.useState(!1),[N,S]=w.useState(!1),[_,E]=w.useState(!1),T=Cse(),A=Cre(Tse()),{outcome:R,sending:B,send:P}=Ase(T),D=R.state==="sent",I=Rse(D,e),O=eL(h),z=N&&!wR(p),[M,$]=Dre(Mse());w.useEffect(()=>{var ee,re;const K=l.current;if(!K)return;const Z=K.contains(document.activeElement)||Dr(((ee=document.activeElement)==null?void 0:ee.tagName)??null);K.inert=D,D&&Z&&((re=s.current)==null||re.focus())},[D]);function q(K){var Z;if(K.preventDefault(),!wR(p)){S(!0),(Z=r.current)==null||Z.focus();return}P({kind:h,body:p,contact:v})}const X=!D,H=K=>fse(Array.from(K.dataTransfer.types));function L(K){!H(K)||!X||(K.preventDefault(),u.current++,E(!0))}function W(K){H(K)&&(K.preventDefault(),K.dataTransfer.dropEffect=X?"copy":"none")}function F(K){H(K)&&(u.current=Math.max(0,u.current-1),u.current===0&&E(!1))}function Y(K){H(K)&&(K.preventDefault(),u.current=0,E(!1),X&&T.add(Array.from(K.dataTransfer.files)))}function J(K){K.target!==K.currentTarget||!H(K)||(K.preventDefault(),K.dataTransfer.dropEffect="none")}function V(K){H(K)&&K.preventDefault()}function G(K){if(!X)return;const Z=Array.from(K.clipboardData.files),ee=K.target,re=ee instanceof HTMLTextAreaElement||ee instanceof HTMLInputElement,ue=Array.from(K.clipboardData.types).includes("text/plain");pse({files:Z.length,hasText:ue,intoTextField:re})&&(K.preventDefault(),T.add(Z))}return a.jsx("div",{className:"modal-backdrop",onClick:e,role:"presentation",onDragOver:J,onDrop:V,"data-leaving":I||void 0,children:a.jsxs("div",{ref:d,className:"modal feedback-dialog",onClick:K=>K.stopPropagation(),onDragEnter:L,onDragOver:W,onDragLeave:F,onDrop:Y,onPaste:G,role:"dialog","aria-modal":"true","aria-labelledby":"feedback-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"feedback-title",className:"modal-tool-name",children:"Send feedback"})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{ref:s,type:"button",className:"glyph-btn",onClick:e,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})})]}),a.jsx("p",{className:"vis-hidden",role:"status",children:Bre(R.state)}),a.jsxs("div",{className:"fb-stage",children:[a.jsxs("form",{ref:l,className:"fb-form",onSubmit:q,onKeyDown:K=>{Lre(K.nativeEvent)&&(K.preventDefault(),K.currentTarget.requestSubmit())},noValidate:!0,children:[a.jsxs("section",{className:"modal-body fb-body",children:[a.jsx(Ure,{kind:h,onChange:f}),a.jsxs("div",{className:"fb-compose",children:[a.jsx("label",{className:"fb-label",htmlFor:"fb-body",children:O.question}),a.jsx("textarea",{ref:r,id:"fb-body",className:"ap-manage-input fb-message",value:p,maxLength:wre,rows:5,placeholder:O.placeholder,onChange:K=>g(K.target.value),"aria-invalid":z||void 0,"aria-describedby":z?"fb-body-error":void 0,required:!0}),z&&a.jsx("p",{id:"fb-body-error",className:"fb-error",children:xre}),a.jsx(Sse,{images:T,addRef:o}),a.jsxs("div",{className:"fb-tools",children:[a.jsx(jse,{images:T,buttonRef:o}),a.jsx(zre,{open:b,onToggle:()=>x(K=>!K)})]}),a.jsx(Wre,{open:b,contact:v,onContact:k})]}),a.jsx("p",{className:"fb-facts",children:A?a.jsxs(a.Fragment,{children:[a.jsx("span",{className:"fb-facts-value",children:A})," — ",Tre]}):Are}),R.state==="failed"&&a.jsx("p",{className:"fb-error",role:"alert",children:R.message})]}),a.jsxs("div",{className:"fb-foot",children:[a.jsxs("span",{className:"fb-shortcut","aria-hidden":"true",children:[a.jsx("kbd",{"data-symbol":Fre(M)||void 0,children:M}),a.jsx("kbd",{children:$})," to send"]}),a.jsx("button",{type:"button",className:"btn",onClick:e,children:"Cancel"}),a.jsx("button",{type:"submit",className:"btn primary fb-send","aria-keyshortcuts":"Meta+Enter Control+Enter",...Or(B),children:a.jsxs("span",{className:"fb-send-label",children:[a.jsx("span",{className:"fb-send-idle",children:"Send feedback"}),a.jsx("span",{className:"fb-send-busy",children:"Sending…"})]})})]})]}),D&&a.jsxs("div",{className:"fb-done",children:[a.jsx(fd,{}),a.jsx("p",{className:"fb-done-line",children:tL})]})]}),a.jsx("div",{className:"fb-drop","aria-hidden":"true","data-active":_||void 0,children:a.jsxs("div",{className:"fb-drop-frame",children:[a.jsx(iL,{className:"fb-drop-glyph"}),a.jsx("p",{className:"fb-drop-title",children:"Drop to attach"}),a.jsx("p",{className:"fb-drop-note",children:"PNG or JPEG, up to three"})]})})]})})}const $se="Press Esc first if a key does nothing.",Ise=[{title:"Canvas",rows:[{cap:"Space",action:"pause or resume the stream",binds:[" "]},{cap:"J",action:"next agent",binds:["j","J"]},{cap:"K",action:"previous agent",binds:["k","K"]},{cap:"W",action:"the session waiting on you — oldest first, again for the next",binds:["w","W"]},{cap:"F",action:"fit every agent on screen",binds:["f","F"]},{cap:"Z",action:"zoom to the selected agent and its session",binds:["z","Z"]},{cap:"Delete",action:"take the selected card off the board — Undo brings it back",binds:["Delete"]},{cap:"R",action:"re-arrange the canvas and drop the pins",binds:["r","R"]},{cap:"C",action:"clear the canvas and the event log — asks first",binds:["c","C"]}]},{title:"Panels and dialogs",rows:[{cap:"U",action:"usage panel",binds:["u","U"]},{cap:"L",action:"session list",binds:["l","L"]},{cap:"D",action:"detail panel",binds:["d","D"]},{cap:"H",action:"usage history",binds:["h","H"]},{cap:"A",action:"Claude accounts, where Claude Code is installed",binds:["a","A"]},{cap:"S",action:"this machine — cores, memory, temperature",binds:["s","S"]},{cap:"B",action:"Browser Watch",binds:["b","B"]},{cap:"V",action:"the sound menu — volume, tones, notifications",binds:["v","V"]},{cap:"?",action:"this sheet",binds:["?"]}]},{title:"Settings",rows:[{cap:"M",action:"sound on or off",binds:["m","M"]},{cap:"T",action:"light or dark theme",binds:["t","T"]}]},{title:"Focus and selection",rows:[{cap:"Tab",action:"reach the agent cards",binds:[]},{cap:"Enter",action:"select the focused card",binds:["Enter"]},{cap:"Shift + Enter",action:"add the focused card to the selection",binds:["Enter"]},{cap:"Esc",action:"close what is open, deselect, release focus",binds:["Escape"]}]},{title:"Mouse",rows:[{cap:"drag",action:"move a node, and pin it where you dropped it",binds:[]},{cap:"shift-click",action:"add an agent to the selection",binds:[]},{cap:"click",action:"a card: go to it and its session",binds:[]},{cap:"double-click",action:"a card: its prompt, every tool call, tokens and timing",binds:[]},{cap:"hover",action:"zoomed out, a card's name, state and numbers",binds:[]},{cap:"click",action:"the topbar speaker opens volume and sound settings",binds:[]}]}];function Ose({onClose:e,onTour:t}){const n=ft(e);return a.jsx("div",{className:"modal-backdrop",onClick:e,role:"presentation",children:a.jsxs("div",{ref:n,className:"modal key-help",onClick:r=>r.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"key-help-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"key-help-title",className:"modal-tool-name",children:"Keyboard shortcuts"})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:e,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})})]}),a.jsxs("section",{className:"modal-body",children:[t&&a.jsxs("div",{className:"guide-door",children:[a.jsx("span",{children:"Eight pictures of what the deck shows."}),a.jsx("button",{type:"button",className:"btn",onClick:t,children:"Take the tour"})]}),a.jsx("div",{className:"shortcuts",children:Ise.map(r=>a.jsxs(w.Fragment,{children:[a.jsx("h3",{className:"kh-group",children:r.title}),r.rows.map(s=>a.jsxs("div",{className:"sc",children:[a.jsx("kbd",{children:s.cap}),a.jsx("span",{children:s.action})]},`${r.title}:${s.cap}:${s.action}`))]},r.title))}),a.jsx("p",{className:"kh-foot",children:$se})]})]})})}function Lse(e,t){const n=(e??[]).filter(s=>s&&typeof s.fp=="string"&&!t.has(s.fp));if(!n.length)return{request:null,waiting:0};const r=[...n].sort((s,o)=>(s.at??0)-(o.at??0));return{request:r[0],waiting:r.length-1}}function Pse({request:e,waiting:t,busy:n,now:r,onAccept:s,onDecline:o,onLater:l}){const u=w.useRef(null),d=ft(l,{focusRef:u}),h=ea(n,"accept"),f=ea(n,"dismiss");return a.jsx("div",{className:"modal-backdrop",onClick:l,role:"presentation",children:a.jsxs("div",{ref:d,className:"modal lan-ask",onClick:p=>p.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"lan-ask-title","aria-describedby":"lan-ask-who",children:[a.jsxs("header",{className:"modal-head",children:[a.jsxs("div",{className:"modal-title",children:[a.jsx("i",{className:"ap-pulse","aria-hidden":!0}),a.jsx("span",{id:"lan-ask-title",className:"modal-tool-name",children:"A deck wants to pair"})]}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:l,"aria-label":"Answer later (Esc)",title:"Answer later (Esc)",children:"×"})})]}),a.jsxs("section",{className:"modal-body",children:[a.jsxs("p",{id:"lan-ask-who",className:"lan-ask-who",children:[a.jsx("strong",{className:"lan-ask-name",children:e.name})," at ",a.jsx("code",{className:"ap-lan-code",children:e.addr})," · asked ",e0(e.at,r)]}),a.jsx("p",{className:"modal-note",children:"Paired decks repair each other's expired logins. Accept only if you know this machine — what it shares is an account, not a screen."}),a.jsxs("p",{className:"lan-ask-fp",children:["Its fingerprint is ",a.jsx("code",{className:"ap-lan-code",children:e.fp})," — that machine shows its own under This deck on the network; the two should match."]}),t>0&&a.jsx("p",{className:"lan-ask-more",children:t===1?"One more deck is waiting behind this one.":`${t} more decks are waiting behind this one.`}),a.jsxs("div",{className:"lan-ask-acts",children:[a.jsx("button",{type:"button",ref:u,className:"btn",disabled:f.disabled,"aria-busy":f.busy,onClick:o,title:"Nothing is shared. If that deck asks again, this comes back.",children:n==="dismiss"?"declining…":"Decline"}),a.jsx("button",{type:"button",className:"btn primary",disabled:h.disabled,"aria-busy":h.busy,onClick:s,title:"Talk to this deck from now on, and repair its expired logins with this deck's own.",children:n==="accept"?"accepting…":"Accept"})]})]})]})})}function Dse({lanPending:e,lanDeferred:t,lanBusy:n,answerLanPair:r,deferLanPair:s}){const{request:o,waiting:l}=Lse(e,t.current);return o?a.jsx(Pse,{request:o,waiting:l,busy:n,now:Date.now(),onAccept:()=>void r("accept",o.fp),onDecline:()=>void r("dismiss",o.fp),onLater:()=>s(o.fp)},o.fp):null}function Fse(e,t){return`${e}#${t}`}function Bse(e,{lan:t,now:n,closed:r}){if(t===void 0)return[];const s=[];for(const o of e??[]){const l=o.reauth;if(!l||l.dismissed||o.origin!=="ccdeck_signin")continue;const u=Fse(l.key,l.since);if(r.has(u)||MV(l.key,o.email,t,n))continue;const d=o.email??"";s.push({id:u,key:l.key,since:l.since,num:o.num,email:d,name:d||o.alias||`Account ${o.num}`,alias:d&&o.alias?o.alias:null})}return s}function qse(e,t){const n=String((t==null?void 0:t.email)??"").trim().toLowerCase();return n?e.filter(r=>r.email.toLowerCase()===n&&((t==null?void 0:t.num)==null||String(r.num)===String(t.num))).map(r=>r.id):[]}function Use({rows:e,ours:t,dialogs:n}){return e===0?!1:t||n===0}function zse(e){return e===1?"Account needs your attention":"Accounts need your attention"}function Wse(e){return`${e===1?"One Claude account needs":`${e} Claude accounts need`} you to sign in again.`}const Hse="Login expired",Vse="Until then the deck cannot read its usage or switch to it. Signing in again brings it back — it keeps its slot, its alias and its history.",Gse="The account stays marked in the Accounts panel. This will not ask again until it has worked and stopped again.";function Kse({rows:e,onSignIn:t,onLater:n,firstRef:r}){const s=e.length===1?e[0]:null;return a.jsxs("section",{className:"modal-body",children:[s?a.jsxs(a.Fragment,{children:[a.jsxs("p",{id:"reauth-lead",className:"reauth-lead",children:["The Claude login for ",a.jsx("strong",{className:"reauth-name",children:s.name}),s.alias?a.jsxs("span",{className:"reauth-alias",children:[" (",s.alias,")"]}):null," ","has expired and needs a new sign","‑","in."]}),a.jsx("p",{className:"reauth-note",children:Vse})]}):a.jsxs(a.Fragment,{children:[a.jsx("p",{id:"reauth-lead",className:"reauth-lead",children:Wse(e.length)}),a.jsx("ul",{className:"reauth-list",children:e.map((o,l)=>a.jsxs("li",{className:"reauth-row",children:[a.jsxs("span",{className:"reauth-who",title:o.alias?`${o.alias} — ${o.name}`:o.name,children:[o.name,o.alias?a.jsxs("span",{className:"reauth-alias",children:[" ",o.alias]}):null]}),a.jsx("span",{className:"reauth-what",children:Hse}),a.jsx("button",{type:"button",className:"btn",ref:l===0?r:void 0,"aria-label":`Sign in again as ${o.name}`,onClick:()=>t(o),children:"Sign in again"})]},o.id))}),a.jsx("p",{className:"reauth-note",children:"Each keeps its slot, its alias and its history."})]}),a.jsxs("div",{className:"reauth-acts",children:[a.jsx("button",{type:"button",className:"btn",title:Gse,onClick:n,children:"Not now"}),s&&a.jsx("button",{type:"button",className:"btn primary",ref:r,onClick:()=>t(s),children:"Sign in again"})]})]})}function Yse({rows:e,onSignIn:t,onLater:n}){const r=w.useRef(null),s=ft(n,{focusRef:r});return a.jsx("div",{className:"modal-backdrop",onClick:n,role:"presentation",children:a.jsxs("div",{ref:s,className:"modal reauth-ask",onClick:o=>o.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"reauth-title","aria-describedby":"reauth-lead",children:[a.jsxs("header",{className:"modal-head",children:[a.jsx("div",{className:"modal-title",children:a.jsx("span",{id:"reauth-title",className:"modal-tool-name",children:zse(e.length)})}),a.jsx("div",{className:"modal-actions",children:a.jsx("button",{type:"button",className:"glyph-btn",onClick:n,"aria-label":"Not now (Esc)",title:"Not now (Esc)",children:"×"})})]}),a.jsx(Kse,{rows:e,onSignIn:t,onLater:n,firstRef:r})]})})}function Xse({rows:e,signingIn:t,signIn:n,signedIn:r,refresh:s,closeSignIn:o,later:l}){const u=w.useRef(!1),d=t!=null||Use({rows:e.length,ours:u.current,dialogs:Qo.dialogDepth()});return u.current=d,t?a.jsx(G$,{email:t.email||null,onClose:o,onChanged:s,onSignedIn:r}):d?a.jsx(Yse,{rows:e,onSignIn:n,onLater:l},e.length===1?"one":"many"):null}const Zse={"//":["What a user would NOTICE. One key per version that has something to say,","and no key at all for a version that has nothing — which is most of them.","","The rule this file exists to serve: before every release, ask 'what would a","user notice?' and write the answer here. 'Nothing user-visible' is a valid","and expected answer, and it is spelled by adding no key. A release with no","key shows no modal, and that silence is what keeps the modal worth reading","on the day it is not silent.","","What belongs here: something that changed on the user's machine or under","their hands — a hook written to or removed from settings.json, a default","that flipped, a control that moved or went away, a stored preference that","changed meaning. What does not: bounds, races, assertions that could not","fire, anything that is invisible when it is correct.","","Write it for the person running the deck, not for the person reading the","diff. `title` is the sentence they scan; `body` is what they need in order","to act on it, or to stop worrying about it.","","Keys are versions exactly as package.json spells them. A key that is not a","version, or an entry missing either field, fails the suite rather than","being quietly dropped — see src/web/__tests__/release-notes-file.test.ts.","Ordering here is for humans; the deck sorts by version itself.","","A title may open with an emoji, and if it does, put exactly ONE space after","it. Not two: the dialog gives a leading emoji its own optical gap, because","a colour glyph is drawn edge to edge inside its advance and one word space","after one reads as none. A second space here would be a lie in the data","about a defect in the type, and the suite refuses both it and no space at","all.","","A release that genuinely has nothing to say is spelled TWICE: no key here,",'and its version written into "//nothing-to-say" below. One spelling on its',"own is ambiguous — a missing key reads the same whether it was a decision","or a forgotten step, and five releases in a row were once forgotten. The list","is what tells the two apart, and release-notes-current-version.test.ts fails","the release whose version is in neither place. Any key opening with // is a","note to the author and never a release."],"//nothing-to-say":["3.23.1","3.23.2","3.23.3","3.23.4","3.26.0","3.26.1","3.26.3","3.26.4","3.26.5","3.27.6","3.27.7","3.28.0","3.28.1","3.29.3","3.29.4","3.29.6","3.29.7","3.29.9","3.33.1","3.35.0","3.35.1","3.36.0","3.36.1","3.36.4","3.36.5","3.36.6","3.36.7","3.36.8","3.36.9"],"3.36.3":[{title:"🔑 An expired login asks to be signed in again",body:"When an account you added with + → Sign in stops working, the deck now tells you in one dialog, with every affected account together and Sign in again beside each. That opens the usual sign-in with the address already filled in, and the account keeps its slot, its alias and its history. Not now leaves it marked in the Accounts panel and does not ask again until it has worked and stopped again. A paired deck holding a working copy gets to repair it first, and accounts that came from a share or Local network are never asked about."}],"3.34.1":[{title:"🗂️ The tray menu stays open while you read it",body:"On Linux the menu behind the tray icon could close by itself while agents were working, often before the row you pressed took the click, so it had to be opened and pressed again. It now changes only when something it says has changed, and stays open while you choose."}],"3.34.0":[{title:"🗺️ The network map shows who you are connected to",body:"Paired decks that are on stand closest to this one, and a light runs along their wires. Paired decks that are away come next, in a colour of their own, with how long they have been gone, and machines that are not paired stand outside in grey. Decks reached over Tailscale sit together in their own shaded slice instead of on dashed wires. Pointing at a deck opens its details beside the map: whether it is there, what to do next if anything, its address and what it runs, and every login the two share, the ones that need a look first."},{title:"🔑 A login that expired everywhere says so",body:`When a shared login expired on this deck and every paired deck that is on held an expired copy too, the row said "Login expired" and waited for a repair that could never come. It now says "Login expired on all online decks". Claude retires a login's other copies each time one machine refreshes it, so shared copies expire together: sign in again on any one machine and the others repair from it on their next round.`},{title:"💬 Send feedback from inside the deck",body:'Appearance settings has a "Send feedback…" button: pick what kind it is, write a title and a few lines, and add a way to reach you if you want an answer. It goes to the people who make ccdeck, who may open a public issue from it, never with your contact in it. If it cannot be sent, the form stays filled in and says why.'}],"3.33.0":[{title:"🪝 Hooks you add in Claude Code are no longer removed",body:"Claude Code's hook editor often files a new hook into the same group as the deck's own. Starting the deck, or uninstalling it, then removed the whole group, and your hook went with it without a word. The deck now takes out only its own command and leaves yours where it was, and the same goes for the old finish-sound hook. `ccdeck --uninstall` also stops the decks that are running, so an update in the background cannot put the hooks back."},{title:"🧵 Resumed Codex sessions show up again",body:"A Codex session picked up with `codex resume`, or one that ran for days, could go dark on the canvas and drop out of the 5-hour and 7-day usage, because the deck only looked in the two newest day folders. It now follows those sessions and counts their tokens. Two Codex sessions in the same folder get different id suffixes, and a `bash -lc` call shows the command it runs."},{title:"🤝 A deck paired by the accept switch brings in less",body:'A deck that paired through the automatic accept switch, and not through an invite, now brings in only the logins you have ticked on this deck, and nothing from it is ticked for sharing onward. Its dialog says "paired automatically — pair by invite to take it". Pairing by invite works as before. A typed address is no longer dropped after one failed round, and the sync port stays the same after a rename.'},{title:"⬆️ Desktop updates install when you expect them",body:'On Windows and Linux a downloaded update now installs when you quit. On a Mac, Quit installs it and the app stays closed. The Linux .deb needs an administrator password to install, so it no longer does that by itself: it waits for "Restart to update" and says a password is coming. "Start at login" now works on Linux, and "Restart ccdeck" stops a deck that has hung.'},{title:"🕵️ Browser Watch waits before it acts, and quits only its own browser",body:"A program's visit is now logged and reacted to once its quiet window has closed, about 16 minutes after it, and not on the first poll, so a visit that is withdrawn no longer quits the browser. On Linux the quit reaction ends only the browser that holds the watched profile; other Chrome channels and headless browsers keep running. When it cannot tell which process that is, it does nothing and says so, which includes a Flatpak Brave."},{title:"🧮 Figures that were off are right",body:`Spend by labelled and workflow subagents counts toward a session's tokens and cost. The Projects report prices 1-hour cache writes the way ccusage does, so its dollars match the Usage panel. After a restart the deck rebuilds its whole event history, where it used to rebuild about a third. On Linux the process list shows what each process is using now, not its lifetime average. A count near a boundary reads "1.00M", and a full quota window says "used up".`},{title:"🔑 The Usage panel's reads answer the deck's own page only",body:"The routes behind the Usage panel (/api/quota, /api/ccusage, /api/codex-usage, /api/codex-quota and /api/cswap-auto) now answer the deck's own page, or a client that sends the deck's token. A script that read them with a plain request gets 401 from this version on. The hook, the desktop app and the `ccdeck` command are not affected."},{title:"🛠️ Smaller fixes you may notice",body:"With the canvas paused, an update no longer restarts the server while an agent is working: it waits until no turn is open. `ccdeck --stop -p4317` stops that one deck. A deck started at login can update itself on Homebrew and nvm setups. Dialogs keep keyboard focus after a button goes away, screen readers hear pairing requests by deck name and the machine panel's readings, dim text is readable in both themes, Windows High Contrast shows what is selected, and below 640px the detail panel opens over the canvas."}],"3.32.2":[{title:"🔒 Self-updates wait until your screen is unlocked",body:"On Linux with GNOME, the app installs an update by itself when you leave it alone, which is often while your screen is locked. GNOME switches the panel's tray off under the lock screen, and a version that started then never showed its icon. The app now waits until you are back before restarting by itself, so its icon comes back with it."}],"3.32.1":[{title:"🔔 The tray icon comes back on Linux",body:"On Linux the app could lose its icon in the panel, after it updated itself or when the panel restarted, and stay without it until it was quit. It now notices within a minute and restarts by itself the next time you are away from its window, the way it installs an update. It does this at most once every six hours, and not again if a restart did not help."},{title:"🔕 A window you closed stays closed after a self-update",body:"When the app restarted by itself to install an update, it opened its window again even if you had closed it, and while that window was open the deck held back its notifications. A window you closed now stays closed."},{title:"🪟 No empty icon when ccdeck is already open",body:'Opening ccdeck again while it was running flashed an empty "…" icon in the Linux panel before bringing the window forward. It now only brings the window forward.'}],"3.32.0":[{title:"🗺️ A map of your network",body:"A new button on the Local network row, and in its view's header, opens a map of every deck around this one. This deck is at the centre and the paired decks that are on stand around it. Further out are the quiet ones, the machines nearby and any deck asking to pair. Wires are solid on your local network and dashed over Tailscale. Pointing at a deck says how it is reached, what it runs and which logins the two share, and pressing it opens that deck's own dialog."}],"3.31.0":[{title:"📊 Account capacity",body:"A gauge button in the accounts panel's header opens Account capacity: how many of your Claude accounts are ready now, what the 5-hour and 7-day windows have left across all of them, and a row for each account with its use, its state (Ready, Limited, Exhausted or Stale) and when it was last read. Each window also says whose reset comes next."},{title:"🚦 Claude and Codex incidents",body:"When Anthropic's or OpenAI's status page reports an incident on something your sessions use, the deck says so in the topbar, the Usage panel and the tray, and every mark links to the status page. A provider with nothing wrong draws nothing."},{title:"↕️ Order your other accounts",body:"The Other accounts row can sort the list by slot, by how full a window is, or by most room, and open every account at once. Both stay as you left them after a reload."},{title:"🔐 A settings folder left by sudo can be given back",body:'On a Mac, running ccdeck once with sudo left its settings folder owned by another user, and every settings change was refused, sharing an account included. The message now offers "give it back": macOS asks for your password, the folder is yours again, and the deck reads its settings back without a restart.'}],"3.30.0":[{title:"🧮 Token counts and costs are no longer doubled",body:"Claude Code writes a reply that has thinking, text and tool calls as several transcript lines, each carrying the whole request's usage. The deck added up every line, so a session's tokens and cost on its card came out close to twice what it was billed, and the context panel counted each part of a reply as its own message. Each request is now counted once. Expect the totals on your cards to come down."},{title:"⏳ Claude's saved limit resets on the Usage panel",body:`When your Claude account has free limit resets to spend ("Reset for free" in Claude's Settings → Usage), the Claude card now says how many there are and when the first one expires, the way the Codex card already did. The deck only reports them; you spend them in Claude's own settings.`},{title:"🔑 LAN invites need the other deck updated",body:`An invite's code is now a long random secret instead of six digits, so nobody else on the network can work it out and pair in your place, and a wrong code is refused by name. An invite made on this version can only be joined from a deck on this version or later: an older deck answers "That is not an invite". Update that deck, or make the invite on the older one and join it here.`},{title:"🔕 AGENTS_DECK_NO_NOTIFY keeps Browser Watch quiet too",body:"A deck started with AGENTS_DECK_NO_NOTIFY=1 still raised a desktop notification for every new Browser Watch finding. It no longer does. The finding is still in the Browser Watch panel and its log, which says why no notification came."},{title:"🛠️ Smaller fixes",body:'Delete removes the card you just clicked. A LAN address you typed stays in the list when a round cannot reach it, and the status line under the LAN switch now agrees with the rows below it. In the desktop app, a dismissed "update is ready" notice stays dismissed after you answer the first-launch question. Sizes read the same in every panel ("1.1 KB"). Faded text stays readable, a keyboard can see the scrollbar, and numbers use a monospaced font on Windows and Linux as well.'}],"3.29.11":[{title:"💲 Claude Sonnet 5.5 has a price",body:`Sessions on Claude Sonnet 5.5 showed "not priced" on the board, and the Projects report could only show a minimum for them. They are now priced at Anthropic's published rate, the same as Sonnet 5: $2 per million input tokens and $10 per million output tokens.`}],"3.29.10":[{title:"💲 Claude Opus 5.5 has a price",body:`Sessions on Claude Opus 5.5 showed "not priced" on the board, and the Projects report counted them as costing nothing, so its totals read far too low. They are now priced at Anthropic's published rate. If a model still has no price, Projects shows its total as a minimum, like $11.19+, and names the model, instead of a total that looks complete.`}],"3.29.8":[{title:"🔁 Removed cards come back when their agent works again",body:"Remove from board is for finished work. If the agent behind a card you removed starts a tool call, gets a new prompt or starts a subagent, the card returns on its own, together with the card it hangs from, and it stays back after a reload. Cards whose agents stay idle stay removed."}],"3.29.1":[{title:"🗂️ One row per project in Projects",body:"The Projects report now counts a session's work under the folder you started it in, the same name its card has on the canvas, instead of adding a row for every folder the agent moved into. The first start after updating re-reads your transcripts once to regroup the report."},{title:"🔔 Tidier custom sounds",body:"Importing a sound, recording one and choosing a spoken voice now sit in compact cards that respond to clicks and focus like the rest of the deck."}],"3.29.0":[{title:"🎵 Your own stations in Claude FM",body:"Add any YouTube live link or radio stream as a station in Appearance, next to the built-in ones, and mute the music without losing your volume. When a station is not live, the picker now says so instead of staying silent."},{title:"🔔 Notification sounds and voices of your own",body:"Pick a local audio file or a spoken voice for each alert, so the sound that means an agent is waiting is one you will recognise."},{title:"🗑️ Drag a card off the board to remove it",body:"Drag a card to the target at the bottom of the canvas to take it off the board, or use Remove from board in the detail panel (Delete). The session keeps running, and the session list (L) brings it back."},{title:"🔐 Optional invite-only pairing",body:"Pairing works as before, with or without an invite. If you want, Local network can now be set to accept only decks that join with an invite you created, so a nearby machine cannot ask to pair on its own."},{title:"🌐 Account sharing between your machines is more reliable",body:"A locked macOS Keychain is explained instead of failing silently, a login that is broken or belongs to another account is never shared, and a sync that stops partway keeps what already arrived."},{title:"📅 Projects opens on Today",body:"The Projects report per account starts with today's work, and an empty window offers the next wider one. The topbar also shows this month's usage, and the desktop app can restart into an update from its own window."}],"3.27.0":[{title:"📊 Projects: where each account spent its work",body:"Each account card's ⋯ menu now breaks its cost down by project, over 7d / 30d / all."}],"3.27.1":[{title:"🎨 Make Appearance easier to tune",body:"The Appearance dialog now has clearer sections, stronger selection states, and a calmer layout for choosing a theme and configuring music. Your existing settings and keyboard shortcuts continue to work."}],"3.27.2":[{title:"💰 Project costs now match Usage",body:"Project cost totals no longer count the same Claude request once for every content block. Existing project history is rebuilt automatically so the totals align with the Usage panel."}],"3.27.3":[{title:"🎵 Station selection works in the desktop app",body:"The music station picker now opens inside ccdeck instead of relying on the operating system popup, so choosing a station works reliably in the Electron app."}],"3.27.4":[{title:"✅ Desktop picker release checks are green",body:"The station picker now keeps the same keyboard, motion, focus, and layout behavior as the rest of ccdeck across desktop and browser builds."}],"3.27.5":[{title:"🧭 The welcome tour stays dismissed",body:"Electron now remembers that you completed the onboarding tour at the deck level, so an update or loopback port change no longer shows the same tour again."},{title:"🎵 The station picker has a clearer finish",body:"The music selector has a tighter trigger, a purposeful chevron, and more deliberate option spacing so it reads like a native part of Appearance."}],"3.25.0":[{title:"🖱️ A click goes to the session, a double-click opens the details",body:"Clicking a card frames it with the rest of its session and leaves the detail panel shut. Double-click a card to open its details; `j`/`k`, the session list, Enter and `D` still open them too."},{title:"🔭 Cards stay readable when you zoom out",body:"At every zoom a card shows its name, its state and anything waiting on you, instead of shrinking to a coloured pill. Hover or focus a card to see it whole. Recap notes read at a distance too, and show the full recap on hover."},{title:"🌐 The Machine panel shows your network",body:"A new Network section shows download, upload and the round trip to Claude's API, and names the route when traffic goes through a VPN or a Tailscale exit node. Those checks run only while the panel is open. The panel is regrouped into CPU, Memory, Network and Thermal, with a flag at the top when something needs attention."},{title:"🎨 See each theme before you pick it",body:"The Appearance menu shows a small preview of the deck in Light and in Dark. Claude FM also keeps its volume when a notification chime plays, instead of dipping under it."}],"3.24.1":[{title:"🎛️ Appearance is one small menu now",body:"The topbar's appearance control opens a compact menu where you can choose Light or Dark and show or hide the character. The character stays on by default, and your choice is remembered."}],"3.24.0":[{title:"🎧 Claude FM plays from the minimap",body:"A small character stands on the minimap's top edge. Press it and the Claude channel's live stream plays; press it again and it stops. It only turns up when the channel is actually broadcasting, so a deck with no network never shows it at all."},{title:"🤠 And it gets on with its day",body:"It walks that edge, tidies up what it finds, kicks a ball along it, sits with its legs over the side, and watches the board through a scope. Now and then it drops onto the canvas and ropes its way back up. Headphones on while the music plays, a hat when it does not."}],"3.23.5":[{title:"🧭 The accounts panel opens on the account you are on",body:"Its windows, its resets and how old the numbers are — whole, at the top of the column. Every other account stands behind one row that says how many of them a switch could actually reach; press it to see them, or rest on it to be shown their names and how much room each has left."},{title:"⚙️ Auto-switch moved in there with them",body:"It is behind that same row now, after the accounts it switches between, rather than at the foot of the panel. The row says `auto 90%` or `auto off` so you can tell what the policy is doing without opening it — and says so in amber when it is armed with nowhere left to go."}],"3.23.0":[{title:"🧭 The accounts column leads with accounts",body:`The live account is the open row: its windows as bars, when each one resets, and how old the numbers are. Every other account rests on the two figures a switch is decided by, and opens with a click when you want the rest.
|
|
101
101
|
|
|
102
102
|
A problem stays on the row — an expired login, one that was never stored, a keychain the deck cannot open — with the reason and the sign-in one press away. Auto-switch is one row now: its threshold and its switch. Local network moved out of the column into a view of its own, reached from a row at the foot that says how many of the paired machines are on right now, and shows which ones when you hover it.`},{title:"💬 A resting session shows Claude Code's recap beside its card",body:"When a session stops, Claude Code's own recap of what it did is drawn beside the card — so a board you come back to says what happened without opening anything."},{title:"🪟 The chrome went quiet",body:`The topbar is borderless, one set of corners runs through the app, the canvas ground is flat, and the minimap, the zoom controls, the type filter and the Machine and Usage panels read as tooling rather than as cards.
|
|
103
103
|
|
|
@@ -299,8 +299,8 @@ Nothing was added to the card and nothing new is polled. The deck already reads
|
|
|
299
299
|
`).map(n=>({text:`+ ${n}`,tone:"add"}))]}function bae(e){if(!Array.isArray(e)||e.length===0)return null;const t=[];for(const n of e){if(!Pr(n)||!Array.isArray(n.lines))return null;t.push({text:`@@ -${n.oldStart},${n.oldLines} +${n.newStart},${n.newLines} @@`,tone:"ctx"});for(const r of n.lines){const s=String(r);t.push(s.startsWith("+")?{text:s,tone:"add"}:s.startsWith("-")?{text:s,tone:"del"}:{text:s})}}return t}function ER(e){if(typeof e=="string")return[Qt(void 0,e)];if(!Pr(e))return[Qt(void 0,za(e))];const t=[],n=wn(e.type);n==="create"&&t.push(vn("result","created the file"));const r=bae(e.structuredPatch);return r&&t.push(pL("applied",r)),e.userModified===!0&&t.push(vn("note","the user changed it before it was applied")),t.length?t:[vn("result",n==="update"?"updated the file":"done")]}function kae(e){const t=typeof e.offset=="number"?e.offset:null,n=typeof e.limit=="number"?e.limit:null;if(t==null&&n==null)return null;const r=t??1;return n!=null?`${r}–${r+n-1}`:`from ${r}`}function Nae(e){if(typeof e=="string")return[Qt(void 0,e)];const t=Pr(e)&&Pr(e.file)?e.file:null,n=wn(t==null?void 0:t.content);if(t&&n!=null){const s=[],o=Number(t.numLines),l=Number(t.totalLines),u=Number(t.startLine);return Number.isFinite(o)&&Number.isFinite(l)&&l>0&&s.push(vn("lines",o>=l?`all ${l}`:Number.isFinite(u)?`${u}–${u+o-1} of ${l}`:`${o} of ${l}`)),s.push(Qt("content",n)),s}const r=Pr(e)?wn(e.type):null;return r&&r!=="text"?[vn("result",`${/^[aeiou]/i.test(r)?"an":"a"} ${r} file`)]:[Qt(void 0,za(e))]}function jae(e){if(typeof e=="string")return[Qt("output",e)];if(!Pr(e))return[Qt(void 0,za(e))];const t=wn(e.stdout),n=wn(e.stderr);if(t==null&&n==null)return[Qt(void 0,za(e))];const r=[];return t&&r.push(Qt("output",t.replace(/\n$/,""))),n&&r.push(Qt("errors",n.replace(/\n$/,""),!0)),e.interrupted===!0&&r.push(vn("note","interrupted before it finished")),r.length?r:[vn("output","none")]}function Sae(e,t,n){const r=Pr(t)?t:null;if(r&&(e==="Edit"||e==="MultiEdit")){const s=wn(r.file_path),o=(e==="MultiEdit"?Array.isArray(r.edits)?r.edits:[]:[r]).filter(Pr).filter(l=>wn(l.old_string)!=null&&wn(l.new_string)!=null);if(s&&o.length){const l=[vn("file",s)];return o.forEach((u,d)=>{const h=u.replace_all===!0?", every occurrence":"";l.push(pL(o.length>1?`change ${d+1}${h}`:`change${h}`,xae(u.old_string,u.new_string)))}),{input:l,response:ER(n)}}}if(r&&e==="Write"){const s=wn(r.file_path),o=wn(r.content);if(s&&o!=null)return{input:[vn("file",s),Qt("content",o)],response:ER(n)}}if(r&&e==="Read"){const s=wn(r.file_path);if(s){const o=kae(r);return{input:o?[vn("file",s),vn("lines",o)]:[vn("file",s)],response:Nae(n)}}}if(r&&e==="Bash"){const s=wn(r.command);if(s!=null){const o=wn(r.description);return{input:o?[Qt("command",s),vn("why",o)]:[Qt("command",s)],response:jae(n)}}}return{input:[Qt(void 0,za(t))],response:[Qt(void 0,za(n))]}}function CR(e,t,n){if(n==null)return"";if(typeof n=="string")return n;if(Pr(n)){if(e==="Bash"&&t==="input"&&typeof n.command=="string")return n.command;if(e==="Bash"&&t==="response"&&(typeof n.stdout=="string"||typeof n.stderr=="string"))return[n.stdout,n.stderr].filter(r=>typeof r=="string"&&r!=="").join(`
|
|
300
300
|
`);if(e==="Read"&&t==="response"&&Pr(n.file)&&typeof n.file.content=="string")return n.file.content}try{return JSON.stringify(n,null,2)??String(n)}catch{return String(n)}}function _ae({tool:e,onClose:t}){const n=ft(t),r=e.endedAt==null?"inflight":e.ok===!1?"err":"done",s=e.input??e.inputPreview,o=e.response??e.errorPreview,l=Sae(e.name,s,o);return a.jsx("div",{className:"modal-backdrop",onClick:t,role:"presentation",children:a.jsxs("div",{ref:n,className:"modal",onClick:u=>u.stopPropagation(),role:"dialog","aria-modal":"true","aria-labelledby":"tool-modal-title",children:[a.jsxs("header",{className:"modal-head",children:[a.jsxs("div",{className:"modal-title",children:[a.jsx("span",{className:`status-dot ${r}`,"aria-hidden":!0}),a.jsx("span",{id:"tool-modal-title",className:"modal-tool-name",children:e.name}),a.jsxs("span",{className:"modal-tool-id",title:e.id,children:[e.id.slice(0,12),"…"]})]}),a.jsxs("div",{className:"modal-actions",children:[a.jsx("span",{className:"modal-dur",children:IM(e,"in-flight…")}),a.jsx("button",{className:"glyph-btn",onClick:t,"aria-label":"Close (Esc)",title:"Close (Esc)",children:"×"})]})]}),a.jsxs("section",{className:"modal-body",children:[e.trimmed&&a.jsx("div",{className:"modal-section",children:a.jsx("p",{className:"modal-note",children:"Full payloads for this call were released to keep memory bounded — only the previews below are still held."})}),a.jsxs("div",{className:"modal-section",children:[a.jsxs("div",{className:"tm-head",children:[a.jsx("h4",{children:"Input"}),a.jsx(AR,{text:CR(e.name,"input",s),what:"input"})]}),l.input.map((u,d)=>a.jsx(TR,{block:u},d))]}),a.jsxs("div",{className:"modal-section",children:[a.jsxs("div",{className:"tm-head",children:[a.jsxs("h4",{children:["Response ",r==="err"&&a.jsx("span",{className:"err-tag",children:"error"})]}),e.endedAt!=null&&a.jsx(AR,{text:CR(e.name,"response",o),what:"response"})]}),e.endedAt==null?a.jsx("pre",{children:"(waiting…)"}):l.response.map((u,d)=>a.jsx(TR,{block:u},d))]})]})]})})}const Eae={del:"tm-del",add:"tm-add",ctx:"tm-ctx"};function TR({block:e}){const[t,n]=w.useState(!1);if(e.inline)return a.jsxs("p",{className:"tm-inline",children:[e.label&&a.jsx("span",{className:"tm-label",children:e.label}),a.jsx("code",{children:e.text})]});const r=wae(e),{shown:s}=fL(r,t),o=vae(r,t);return a.jsxs("div",{className:"tm-block",children:[e.label&&a.jsx("span",{className:"tm-label",children:e.label}),a.jsx("pre",{className:e.err?"tm-err":void 0,children:e.lines?s.map((l,u)=>a.jsxs("span",{className:l.tone?Eae[l.tone]:void 0,children:[l.text,u<s.length-1?`
|
|
301
301
|
`:""]},u)):s.map(l=>l.text).join(`
|
|
302
|
-
`)}),o&&a.jsx("button",{type:"button",className:"btn tm-more","aria-expanded":o.expanded,onClick:()=>n(l=>!l),children:o.label})]})}function AR({text:e,what:t}){const[n,r]=w.useState(!1),s=w.useRef(!0);return w.useEffect(()=>()=>{s.current=!1},[]),a.jsx("button",{type:"button",className:"btn tm-copy",disabled:!e,"aria-label":n?`The ${t} was copied`:`Copy the ${t}`,onClick:()=>void Ha(e).then(o=>{!o||!s.current||(r(!0),window.setTimeout(()=>{s.current&&r(!1)},1600))}),children:n?"copied":"copy"})}const Cae=w.lazy(()=>qb(()=>import("./UsageHistoryModal-
|
|
302
|
+
`)}),o&&a.jsx("button",{type:"button",className:"btn tm-more","aria-expanded":o.expanded,onClick:()=>n(l=>!l),children:o.label})]})}function AR({text:e,what:t}){const[n,r]=w.useState(!1),s=w.useRef(!0);return w.useEffect(()=>()=>{s.current=!1},[]),a.jsx("button",{type:"button",className:"btn tm-copy",disabled:!e,"aria-label":n?`The ${t} was copied`:`Copy the ${t}`,onClick:()=>void Ha(e).then(o=>{!o||!s.current||(r(!0),window.setTimeout(()=>{s.current&&r(!1)},1600))}),children:n?"copied":"copy"})}const Cae=w.lazy(()=>qb(()=>import("./UsageHistoryModal-BtRCuo33.js"),[])),Tae=w.lazy(()=>qb(()=>import("./BrowserWatchModal-BCrDvuug.js"),[]));function Aae({dialogs:e,welcome:t,desktopUpdate:n,versionCheck:r,restart:s,lanPairs:o,attention:l,clearFlow:u,watchBadge:d,announcements:h,appearance:f,providers:p,stateRef:g,agentCount:v}){const{openedTool:k,setOpenedToolKey:b,usageHistoryOpen:x,setUsageHistoryOpen:N,browserWatchOpen:S,setBrowserWatchOpen:_,contextAgent:E,setContextFor:T,summaryFor:A,setSummaryFor:R,keyHelpOpen:B,setKeyHelpOpen:P,feedbackOpen:D,setFeedbackOpen:I,feedbackPrefill:O,setFeedbackPrefill:z}=e,{tourOpen:M,openTour:$,closeTour:q,releaseNotes:X,closeReleaseNotes:H,chipVersion:L}=t,{desktopUpdateRestarting:W,desktopUpdateFailure:F,readyAppUpdate:Y,askDesktopUpdateRestart:J}=n,{version:V}=r,{askRestart:G}=s,{clearConfirmOpen:K,setClearConfirmOpen:Z,requestClear:ee}=u,{setWatchOn:re,markWatchSeen:ue}=d,{setWatchSaid:me}=h,{palette:ie}=f;return a.jsxs(a.Fragment,{children:[k&&a.jsx(_ae,{tool:k,onClose:()=>b(null)}),x&&a.jsx(w.Suspense,{fallback:null,children:a.jsx(Cae,{providers:p,onClose:()=>N(!1)})}),S&&a.jsx(w.Suspense,{fallback:null,children:a.jsx(Tae,{onClose:()=>_(!1),onSeen:fe=>{me(""),ue(fe)},onWatching:re,palette:ie})}),E&&a.jsx(g8,{agent:E,onClose:()=>T(null)}),D&&a.jsx(lL,{initialKind:O==null?void 0:O.initialKind,initialBody:O==null?void 0:O.initialBody,onClose:()=>{I(!1),z(null)}}),A&&a.jsx(pae,{state:g.current,sessionId:A,onClose:()=>R(null)}),X&&a.jsx(fae,{entries:X.entries,since:X.since,firstRun:X.firstRun,running:L,onClose:H,onTour:()=>{H(),$()},updateVersion:Y==null?void 0:Y.version,updateBusy:W,updateFailure:F&&(!Y||Y.version===F.version)?Lne(F.failure,F.version):void 0,onUpdateRestart:Y?()=>{J(Y.version)}:void 0,onRestart:!Y&&(V!=null&&V.canRestart)?()=>{H(),G()}:void 0}),a.jsx(Dse,{...o}),a.jsx(Xse,{...l}),B&&a.jsx(Ose,{onClose:()=>P(!1),onTour:()=>{P(!1),$()}}),M&&a.jsx(eI,{title:"What the deck shows you",steps:JV,onClose:q}),K&&a.jsx(yre,{agentCount:v,onConfirm:()=>ee("confirmation"),onCancel:()=>Z(!1)})]})}const Rae=10,mL=500,gL=4e3,Mae=[/ResizeObserver loop/i,/^Script error\.?$/i];function RR(e){return String(e??"").replace(/\/(?:home|Users)\/[^/\s:'"]+/g,"~").replace(/[A-Za-z]:\\(?:Users|Documents and Settings)\\[^\\\r\n:'"]+/gi,"~").replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,"<email>").replace(/\b(?:sk-ant-[A-Za-z0-9_-]{8,}|sk-[A-Za-z0-9_-]{20,}|gh[pousr]_[A-Za-z0-9]{20,}|xox[abpr]-[A-Za-z0-9-]{10,}|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|[A-Fa-f0-9]{40,}|[A-Za-z0-9+_=-]{48,})/g,"<secret>")}const yL=e=>{fetch("/api/client-error",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e),keepalive:!0}).catch(()=>{})};function $ae(e,t,n=yL){const r=String(e??"").slice(0,mL).trim();r&&n({message:r,stack:typeof t=="string"?t.slice(0,gL):void 0})}function Iae(e,t=window,n=yL){let r=0;const s=new Set,o=(d,h)=>{const f=String(d??"").slice(0,mL).trim();!e()||!f||r>=Rae||s.has(f)||Mae.some(p=>p.test(f))||(s.add(f),r++,n({message:f,stack:typeof h=="string"?h.slice(0,gL):void 0}))},l=d=>{var h,f;return o(((h=d.error)==null?void 0:h.message)??d.message,(f=d.error)==null?void 0:f.stack)},u=d=>typeof d.reason=="object"&&d.reason!==null?o(d.reason.message,d.reason.stack):o(d.reason,void 0);return t.addEventListener("error",l),t.addEventListener("unhandledrejection",u),()=>{t.removeEventListener("error",l),t.removeEventListener("unhandledrejection",u)}}const Oae=12;function Lae(e,t){const n=[RR(e).trim()||"The deck threw an error with no message."],r=t==null?void 0:t.trim();if(r){const s=r.split(`
|
|
303
303
|
`).slice(0,Oae).join(`
|
|
304
304
|
`);n.push("",RR(s).trim())}return n.join(`
|
|
305
305
|
`)}class Pae extends w.Component{constructor(){super(...arguments);W1(this,"state",{error:null,componentStack:null,reportOpen:!1})}static getDerivedStateFromError(n){return{error:n}}componentDidCatch(n,r){console.error("ccdeck hit a render error it could not draw past",n,r.componentStack),this.setState({componentStack:r.componentStack??null}),$ae(n.message,n.stack)}render(){const{error:n,componentStack:r,reportOpen:s}=this.state;return n?a.jsxs("div",{className:"error-fallback",role:"alert",children:[a.jsxs("div",{className:"error-fallback-card",children:[a.jsx("p",{className:"error-fallback-title",children:"Something went wrong"}),a.jsx("p",{className:"error-fallback-note",children:"The deck hit an error it could not draw past. Reloading usually clears it — and if it keeps happening, a report tells the people who make ccdeck what broke."}),a.jsxs("div",{className:"error-fallback-actions",children:[a.jsx("button",{type:"button",className:"btn primary",onClick:()=>window.location.reload(),children:"Reload"}),a.jsx("button",{type:"button",className:"btn",onClick:()=>this.setState({reportOpen:!0}),"aria-haspopup":"dialog",children:"Send report"})]})]}),s&&a.jsx(lL,{initialKind:"bug",initialBody:Lae(n.message,r),onClose:()=>this.setState({reportOpen:!1})})]}):this.props.children}}const Dae=1e3;function Fae(e={}){const t=Math.max(1,Math.floor(e.limit??Dae)),n=e.protect;let r=!1,s=[],o=new Set,l=0,u=0,d=null;return{get paused(){return r},get size(){return s.length},get dropped(){return l},get overflowed(){return l>0},get through(){return u},accept(h){const f=typeof(h==null?void 0:h.seq)=="number"?h.seq:null,p=(h==null?void 0:h.epoch)??null,g=p!==null&&p!==d;g&&(d=p,u=0);const v=f!==null&&!g&&f<=u;if(f!==null&&f>u&&(u=f),!r)return!0;if(v)return!1;if(s.push(h),n!=null&&n(h)&&o.add(h),s.length>t){let k=0;if(o.size<s.length)for(;o.has(s[k]);)k++;const[b]=s.splice(k,1);o.delete(b),l++}return!1},setPaused(h){if(r=h,h)return[];const f=s;return s=[],o=new Set,l=0,f}}}function Bae(e){const t=w.useState(()=>Fae({protect:o=>HB(e.current,o)}))[0],[n,r]=w.useState(!1),s=w.useCallback(()=>{const o=t.paused&&t.dropped>0,l=t.setPaused(!t.paused);o&&bq(e.current);for(const u of l)e.current=_M(e.current,u);r(t.paused)},[t,e]);return{pauseGate:t,paused:n,togglePause:s}}const ab={kind:"assumed",claude:!0,codex:!0};function qae(e){if(!e||typeof e!="object")return ab;const t=e.providers;if(!t||typeof t!="object")return ab;const n=t;return{kind:"reported",claude:n.claude!==!1,codex:n.codex!==!1}}function Uae(e){const[t,n]=w.useState(null),[r,s]=w.useState(ab);w.useEffect(()=>{let l=!1;return fetch("/api/health").then(u=>u.ok?u.json():null).then(u=>{l||(s(qae(u)),!(!u||typeof u.workspace!="string")&&n(u.workspace))}).catch(()=>{}),()=>{l=!0}},[e]);const o=bn(r);return{workspace:t,providers:r,providersRef:o}}const wL=3*6e4,zae=2e4;function Wae({fetchReport:e,visible:t,now:n,onReport:r}){let s=0,o=!0;const l=async()=>{if(!t())return;const u=await e();!o||!u||(s=n(),r(u))};return{tick:l,wake:async()=>{t()&&n()-s>=wL&&await l()},stop:()=>{o=!1}}}async function Hae(){try{const e=await fetch("/api/provider-status",{signal:AbortSignal.timeout(zae)});return e.ok?await e.json():null}catch{return null}}function Vae(e){const[t,n]=w.useState(null);return w.useEffect(()=>{if(!e){n(null);return}const r=Wae({fetchReport:Hae,visible:()=>document.visibilityState==="visible",now:()=>Date.now(),onReport:n});r.tick();const s=window.setInterval(r.tick,wL),o=()=>{r.wake()};return document.addEventListener("visibilitychange",o),()=>{r.stop(),window.clearInterval(s),document.removeEventListener("visibilitychange",o)}},[e]),t}const Gae={degraded:"degraded performance",partial_outage:"partial outage",major_outage:"major outage",maintenance:"maintenance"},Kae={degraded:"degraded",partial_outage:"partial outage",major_outage:"major outage",maintenance:"maintenance"},Yae={degraded:"degraded",partial_outage:"outage",major_outage:"outage",maintenance:"maint."},Xae={claude:"Claude",codex:"Codex"},Zae=new Set(["status.claude.com","status.openai.com"]),Jae=30*6e4,Qae=10*6e4;function eoe(e){if(typeof e!="string")return null;try{const t=new URL(e);return t.protocol==="https:"&&Zae.has(t.hostname)?t:null}catch{return null}}function MR(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",hourCycle:"h23"})}function toe(e){const t=Math.floor(e/6e4);return t<1?"just now":t<60?`${t} min ago`:`${Math.floor(t/60)} h ago`}function noe(e,t){if(!e||typeof e!="object")return null;const n=Ot(Gae,e.state),r=Ot(Xae,e.provider);if(!n||!r||typeof e.checkedAt!="number"||t-e.checkedAt>Jae)return null;const s=eoe(e.statusPageUrl);if(!s)return null;const o=Array.isArray(e.components)?e.components:[],l=e.stale===!0||t-e.checkedAt>Qae,u=[`${r}: ${n}${e.summary?` — ${e.summary}`:""}`,o.length>0?`Affected: ${o.join(", ")}`:null,e.scope==="page"?`The page's overall status: it names none of ${r}'s components`:null,l?`Last read from ${s.hostname} at ${MR(e.checkedAt)}; this is the last thing it said`:`Reported by ${s.hostname}, checked ${toe(t-e.checkedAt)}`,"Opens the status page"],d=l?MR(e.checkedAt):null;return{provider:e.provider,state:e.state,name:r,label:`${r} · ${n}${d?` · as of ${d}`:""}`,words:n,chipWords:Ot(Kae,e.state)??n,shortWords:Ot(Yae,e.state)??n,asOf:d,what:e.summary||(o.length>0?o.join(", "):null),href:s.href,host:s.hostname,stale:l,detail:u.filter(Boolean).join(`
|
|
306
|
-
`)}}function roe(e,t){return!(e!=null&&e.ok)||e.disabled||!Array.isArray(e.providers)?[]:e.providers.map(n=>noe(n,t)).filter(n=>n!==null)}function soe(e){return e.map(t=>`${t.name}'s status page reports ${t.state.endsWith("outage")?`a ${t.words}`:t.words}.`).join(" ")}const aoe="The providers' status pages report no incident.";function ooe({version:e,loadVersion:t}){var h;const[n,r]=w.useState(!1),s=((h=e==null?void 0:e.upgrade)==null?void 0:h.state)??"idle",o=are(e==null?void 0:e.upgrade),l=w.useRef(!1),u=w.useCallback(async()=>{if(lr(l.current||s==="running")){l.current=!0;try{await fetch("/api/upgrade",{method:"POST"})}catch{}await t(),l.current=!1}},[s,t]);w.useEffect(()=>{if(s!=="running")return;const f=window.setInterval(t,3e3);return()=>window.clearInterval(f)},[s,t]);const d=w.useCallback(async()=>{const f=e==null?void 0:e.command;!f||!await Ha(f)||(r(!0),window.setTimeout(()=>r(!1),1600))},[e==null?void 0:e.command]);return{upgradeState:s,upgradeFailure:o,startUpgrade:u,copyCommand:d,cmdCopied:n}}const $R="agent-dag.browserWatch.seenMs";function ioe(e,t){return e.filter(n=>n.startMs>t)}function loe(){const[e,t]=w.useState(()=>Number(et($R))||0),[n,r]=w.useState([]),[s,o]=w.useState(!1);w.useEffect(()=>{let d=!0;const h=()=>{fetch("/api/browser-watch?live=0").then(p=>p.ok?p.json():null).then(p=>{var g;!d||!(p!=null&&p.ok)||(r(p.episodes??[]),o(((g=p.settings)==null?void 0:g.enabled)===!0))}).catch(()=>{})};h();const f=setInterval(h,5*6e4);return()=>{d=!1,clearInterval(f)}},[]);const l=w.useMemo(()=>ioe(n,e).length,[n,e]),u=w.useCallback(d=>{t(d),Xe($R,String(d))},[]);return{watchOn:s,setWatchOn:o,watchUnseen:l,markWatchSeen:u}}const vL=["--err","--inflight","--ok","--grid-line","--minimap-mask","--panel","--line","--accent","--text-dim","--bg"];function IR(e){const t={};for(const n of vL)t[n]=e(n);return t}function coe(e){return t=>e[t]??""}function uoe(e,t){return vL.every(n=>e[n]===t[n])}const xL="agent-dag.theme";function doe(e,t=!1){return e==="light"||e==="dark"?e:t?"light":"dark"}function hoe(){try{return window.matchMedia("(prefers-color-scheme: light)").matches}catch{return!1}}function foe(){return doe(et(xL),hoe())}function OR(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}function poe(){const[e,t]=w.useState(foe),[n,r]=w.useState(ene),[s,o]=w.useState(()=>IR(OR)),l=w.useMemo(()=>coe(s),[s]),u=w.useCallback(d=>ste(d,l),[l]);return w.useEffect(()=>{document.documentElement.dataset.theme=e,Xe(xL,e),o(d=>{const h=IR(OR);return uoe(d,h)?d:h})},[e]),w.useEffect(()=>{Xe(YO,n?"1":"0")},[n]),w.useEffect(()=>{const d=document.querySelector('meta[name="theme-color"]'),h=s["--panel"];d&&h&&d.setAttribute("content",h)},[s]),{theme:e,setTheme:t,characterEnabled:n,setCharacterEnabled:r,palette:s,minimapNodeFill:u}}function moe(e){const t=w.useRef(null),[n,r]=w.useState(!1),[s,o]=w.useState(()=>new Set),l=w.useMemo(()=>{const d=new Set;for(const h of e.current.agents.values())for(const f of h.tools)d.add(GO(f.name));return Object.keys(VO).filter(h=>d.has(h))},[e.current,e.current.revision]);w.useEffect(()=>{if(l.length<=1){r(!1);return}let d=0;const h=()=>{const f=t.current;if(f&&!document.hidden){const p=f.getBoundingClientRect(),g=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(k=>k.getBoundingClientRect()),v=Kee(p,g,8);r(k=>k===v?k:v)}d=window.setTimeout(h,300)};return h(),()=>window.clearTimeout(d)},[l.length]);const u=w.useCallback(d=>{o(h=>{const f=new Set(h);return f.has(d)?f.delete(d):f.add(d),f})},[]);return{presentCats:l,hiddenCats:s,toggleCat:u,catBarRef:t,catBarOccluded:n}}function goe(e={enabled:()=>!0}){const t=e.ctor??(typeof window<"u"?window.AudioContext??window.webkitAudioContext??null:null);let n=null;const r=()=>e.enabled()?(n==null?void 0:n.state)==="running"?"ready":"locked":"off",s=()=>{var f;return(f=e.onState)==null?void 0:f.call(e,r())};function o(){var f,p;if(!(!t||(n==null?void 0:n.state)==="running"))try{n||(n=new t,(f=n.addEventListener)==null||f.call(n,"statechange",s)),(p=n.resume)==null||p.call(n).then(s,()=>{}),s()}catch{}}function l(f,p,g=p.figure){if(!n||n.state!=="running")return!1;const v=Hx(f,g),k=aX(p.level,v),b=n.currentTime;for(const x of v.notes){const N=n.createOscillator(),S=n.createGain();N.type=v.type??"sine",N.frequency.value=x.hz;const _=b+x.at,E=_+x.ms/1e3;S.gain.setValueAtTime(IS,_),S.gain.exponentialRampToValueAtTime(k,_+oX),S.gain.exponentialRampToValueAtTime(IS,E),N.connect(S).connect(n.destination),N.start(_),N.stop(E+.02)}return!0}async function u(f,p){if(!e.loadCustom)return"unavailable";let g;try{g=await e.loadCustom(f)}catch{return"unavailable"}if(!g)return"missing";if(g.kind==="tts"){if(typeof window>"u"||!("speechSynthesis"in window)||typeof SpeechSynthesisUtterance>"u")return"unavailable";const k=new SpeechSynthesisUtterance(g.text),b=window.speechSynthesis.getVoices();k.voice=b.find(x=>x.voiceURI===g.voiceURI)??null,k.rate=g.rate,k.pitch=g.pitch,k.volume=iX(p.level);try{return window.speechSynthesis.cancel(),window.speechSynthesis.speak(k),"played"}catch{return"unavailable"}}if(!n||n.state!=="running")return"locked";let v;try{v=await n.decodeAudioData(g.bytes.slice(0))}catch{return"broken"}try{const k=n.createBufferSource(),b=n.createGain();return k.buffer=v,b.gain.setValueAtTime(lX(g.normalizationGain,p.level),n.currentTime),k.connect(b).connect(n.destination),k.start(n.currentTime),"played"}catch{return"unavailable"}}function d(f,p=!1){var k,b;if(!p&&!e.enabled())return!1;const g=mX((k=e.prefs)==null?void 0:k.call(e),f),v=((b=e.customSelection)==null?void 0:b.call(e)[f])??null;return v&&e.loadCustom?(u(v,g).then(x=>{var N;x==="played"||x==="locked"||((x==="missing"||x==="broken")&&((N=e.onCustomFailure)==null||N.call(e,f,v)),l(f,{...g,figure:xn},xn))}),!0):!n||n.state!=="running"?!1:l(f,g)}function h(f,p=Ua){return u(f,{level:fi(p),figure:xn}),!0}return{unlock:o,play:d,previewCustom:h,state:r,get context(){return n}}}function yoe({chimesRef:e,soundOnRef:t,tonePrefsRef:n,customSelectionsRef:r,fallbackCustomRef:s}){const[o,l]=w.useState("locked");return w.useEffect(()=>{const u=goe({enabled:()=>t.current===!0,prefs:()=>n.current,customSelection:()=>r.current,loadCustom:eX,onCustomFailure:(p,g)=>s.current(p,g),onState:l});e.current=u,l(u.state());const d=["pointerdown","pointerup","keydown"],h=()=>{for(const p of d)window.removeEventListener(p,f,{capture:!0})},f=()=>{var p;u.unlock(),((p=u.context)==null?void 0:p.state)==="running"&&h()};for(const p of d)window.addEventListener(p,f,{capture:!0});return h},[]),{chimeState:o}}function woe(){const[e,t]=w.useState(nne),[n,r]=w.useState(()=>gB(et(GN))),[s,o]=w.useState(()=>mB(et(KN))),[l,u]=w.useState(()=>yB(et(lR),n,Jte)),[d,h]=w.useState(()=>new Set),[f,p]=w.useState(0);w.useEffect(()=>{Xe(XO,String(e))},[e]),w.useEffect(()=>{Xe(KN,s?"1":"0")},[s]),w.useEffect(()=>{Xe(GN,JSON.stringify(n))},[n]),w.useEffect(()=>{Xe(lR,l)},[l]);const g=w.useCallback(N=>{r(S=>S.some(_=>_.id===N.id)?S:[...S,N]),h(S=>{if(!S.has(N.id))return S;const _=new Set(S);return _.delete(N.id),_})},[]),v=w.useCallback((N,S)=>{const _=S.trim();!_||_.length>Dl||r(E=>E.map(T=>T.id===N?{...T,name:_}:T))},[]),k=w.useCallback(N=>{r(S=>S.filter(_=>_.id!==N)),u(S=>wB(S,N)),h(S=>{if(!S.has(N))return S;const _=new Set(S);return _.delete(N),_})},[]),b=w.useCallback(N=>{const S=YN(N),_=d.has(S);N===l&&!_||(_&&h(E=>{const T=new Set(E);return T.delete(S),T}),u(N),p(E=>E+1))},[l,d]),x=w.useCallback((N,S)=>{const _=YN(N);h(E=>{if(E.has(_)===S)return E;const A=new Set(E);return S?A.add(_):A.delete(_),A})},[]);return{fmVolume:e,setFmVolume:t,fmMuted:s,setFmMuted:o,fmSource:l,customFmStations:n,unavailableFmStations:d,fmPlayRequest:f,addFmStation:g,renameFmStation:v,removeFmStation:k,pickFmSource:b,markFmStationAvailability:x}}function voe(e){const[t,n]=w.useState(null),[r,s]=w.useState(!1),[o,l]=w.useState(null),u=w.useRef(0);w.useEffect(()=>{if(!e||!Zl())return;let k=!1;const b=u.current;return fetch("/api/desktop-update").then(x=>x.ok?x.json():null).then(x=>{if(k||u.current!==b)return;const N=mR(x);N&&n(N)}).catch(()=>{}),()=>{k=!0}},[e]);const d=Rne(t),h=w.useRef(!1),f=w.useRef(0),p=w.useRef(0);w.useEffect(()=>()=>window.clearTimeout(f.current),[]);const g=w.useCallback(async k=>{if(!lr(h.current))return;const b=++p.current;window.clearTimeout(f.current),h.current=!0,s(!0),l(null);const x=N=>{b===p.current&&(window.clearTimeout(f.current),h.current=!1,s(!1),l({failure:N,version:k}))};try{const N=await fetch("/api/desktop-update/restart",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({version:k})});if(!N.ok)return x(One(await N.json().catch(()=>null)))}catch{return x("unreachable")}b===p.current&&(f.current=window.setTimeout(()=>x("timeout"),ZO))},[]),v=w.useCallback(k=>{if(Zl())try{const b=mR(JSON.parse(k));b&&(u.current++,n(b),b.status!=="ready"&&(p.current++,window.clearTimeout(f.current),h.current=!1,s(!1)))}catch{}},[]);return{desktopUpdateRestarting:r,desktopUpdateFailure:o,readyAppUpdate:d,askDesktopUpdateRestart:g,onDesktopUpdateEvent:v}}const Ou="agent-dag.autoRestart",LR="agent-dag.bundleReloadedFor";function xoe({now:e,stateRef:t,version:n,notice:r,noticeOpen:s,upgradeFailure:o,paused:l}){const[u,d]=w.useState(()=>et(Ou)!=="0"),h=w.useRef(!1),f=w.useCallback(()=>{const D=!u;h.current=!0,us(Ou),d(D),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({autoUpdate:D})}).then(I=>I.ok?I.json():null).then(I=>{var O;I!=null&&I.ok&&d(((O=I.prefs)==null?void 0:O.autoUpdate)!==!1)}).catch(()=>{})},[u]),[p,g]=w.useState(!1),[v,k]=w.useState("restart"),[b,x]=w.useState(null),N=w.useRef(!1),S=w.useRef(null),_=w.useRef(0),E=w.useRef(null),T=w.useCallback(async D=>{if(!lr(N.current))return;const I=(D==null?void 0:D.upgrade)===!0,O=(D==null?void 0:D.whenIdle)===!0;N.current=!0,S.current=o;const z=++_.current,M=()=>{k(I?"npx":"restart"),g(!0)};O||M();try{window.sessionStorage.setItem("agent-dag.restartPending",(r==null?void 0:r.to)??"")}catch{}let $=!1;try{const q=await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(O?{upgrade:I,whenIdle:O}:{upgrade:I})});$=O&&q.status===409}catch{}if($){N.current=!1,E.current=null;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}return}O&&M(),window.setTimeout(()=>{if(_.current===z&&N.current){N.current=!1,g(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},I?18e4:3e4)},[r==null?void 0:r.to,o]);w.useEffect(()=>{const D=gR(t.current.agents.values())>0,I=ure({enabled:u,kind:r==null?void 0:r.kind,canRestart:(n==null?void 0:n.canRestart)===!0,noticeOpen:s,visible:document.visibilityState==="visible",busy:D,idleSince:E.current,now:e});E.current=I.idleSince,I.restart&&T({whenIdle:!0})},[u,r==null?void 0:r.kind,n==null?void 0:n.canRestart,s,e,T]);const A=gR(t.current.agents.values()),R=cre(A,l),B=QO({enabled:u,kind:r==null?void 0:r.kind,canRestart:(n==null?void 0:n.canRestart)===!0,noticeOpen:s,visible:document.visibilityState==="visible",busy:A>0,idleSince:E.current,now:e});w.useEffect(()=>{const D=n==null?void 0:n.running;let I=null,O=null;try{I=window.sessionStorage.getItem("agent-dag.restartPending"),O=window.sessionStorage.getItem(LR)}catch{return}const z=sre({bundle:"3.36.8",running:D,pending:I,lastTried:O});if(z==="reload"){try{window.sessionStorage.setItem(LR,D??"")}catch{return}window.location.reload();return}if(z!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}N.current=!1,g(!1),x(D??null);const M=window.setTimeout(()=>x(null),6e3);return()=>window.clearTimeout(M)},[n==null?void 0:n.running]),w.useEffect(()=>{if(p&&ore({asked:S.current,reported:o})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}N.current=!1,g(!1)}},[p,o]);const P=w.useCallback(D=>{var I,O;if(!h.current){const z=et(Ou)==="0";us(Ou),z&&((I=D.prefs)==null?void 0:I.autoUpdate)!==!1?(d(!1),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({autoUpdate:!1})}).catch(()=>{})):d(((O=D.prefs)==null?void 0:O.autoUpdate)!==!1)}},[]);return{autoRestart:u,toggleAutoRestart:f,restarting:p,restartMode:v,restartedTo:b,askRestart:T,restartCopy:R,restartFuseMs:B,loadAutoRestartPrefs:P}}function boe(){const[e,t]=w.useState([]),n=w.useRef(new Set),[r,s]=w.useState(null),[o,l]=w.useState(void 0);w.useEffect(()=>{let h=!0,f=0;const p=()=>{fetch("/api/lan").then(g=>g.ok?g.json():null).then(g=>h?(g!=null&&g.ok&&t(iS(Array.isArray(g.pending)?g.pending:[],g.aliases)),g):null).catch(()=>null).then(g=>{h&&(l(v=>g!=null&&g.ok?g:v===void 0?null:v),f=window.setTimeout(p,(g==null?void 0:g.enabled)===!0?Dx:Q$))})};return p(),()=>{h=!1,window.clearTimeout(f)}},[]);const u=w.useCallback(async(h,f)=>{if(!r){s(h);try{const g=await(await fetch("/api/lan/peer",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:h,fp:f})})).json().catch(()=>null);g&&Array.isArray(g.pending)?t(iS(g.pending,g.aliases)):t(v=>v.filter(k=>k.fp!==f))}catch{}finally{s(null)}}},[r]),d=w.useCallback(h=>{r||(n.current.add(h),t(f=>[...f]))},[r]);return{lanPending:e,lanDeferred:n,lanBusy:r,answerLanPair:u,deferLanPair:d,lanStatus:o}}const koe=6e4;function Noe({enabled:e,lanStatus:t,now:n}){const[r,s]=w.useState(null),[o,l]=w.useState(()=>new Set),[u,d]=w.useState(null),h=w.useCallback(N=>{N!=null&&N.ok&&Array.isArray(N.accounts)&&s(N.accounts)},[]),f=w.useCallback(async N=>{try{const S=await fetch(`/api/claude-accounts${N?"?refresh=1":""}`);S.ok&&h(await S.json())}catch{}},[h]);w.useEffect(()=>{e&&f(!1)},[e,f]),w.useEffect(()=>{if(!e)return;let N=Date.now();const S=()=>{document.visibilityState!=="visible"||Date.now()-N<koe||(N=Date.now(),f(!1))};return document.addEventListener("visibilitychange",S),window.addEventListener("focus",S),()=>{document.removeEventListener("visibilitychange",S),window.removeEventListener("focus",S)}},[e,f]);const p=w.useMemo(()=>e?Bse(r,{lan:t,now:n,closed:o}):[],[e,r,t,n,o]),g=w.useRef(p);g.current=p;const v=w.useCallback(N=>{N.length&&l(S=>new Set([...S,...N]))},[]),k=w.useCallback(()=>{const N=g.current;N.length&&(v(N.map(S=>S.id)),fetch("/api/claude-accounts/admin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"reauth-later",incidents:N.map(S=>({key:S.key,since:S.since}))})}).catch(()=>{}))},[v]),b=w.useRef(u);b.current=u;const x=w.useCallback(N=>{const S=b.current;v(qse(S?[S,...g.current]:g.current,N))},[v]);return{rows:p,signingIn:u,observe:h,signIn:d,signedIn:x,refresh:w.useCallback(()=>{f(!0)},[f]),closeSignIn:w.useCallback(()=>d(null),[]),later:k}}const bL="agent-dag.sessionListOpen",kL="agent-dag.accountsPanelOpen";function joe(){return et(bL)==="1"}function Soe(e){Xe(bL,e?"1":"0")}function _oe(){const e=et(kL);return e===null?!0:e==="1"}function Eoe(){const[e,t]=w.useState(joe);w.useEffect(()=>{Soe(e)},[e]);const n=w.useRef(!1),[r,s]=w.useState(()=>{const h=_oe();return h&&e?(n.current=!0,!1):h});w.useEffect(()=>{!r&&n.current||Xe(kL,r?"1":"0")},[r]),w.useEffect(()=>{!e&&n.current&&(n.current=!1,s(!0))},[e]);const o=w.useCallback(()=>{t(h=>(h||s(f=>(f&&(n.current=!0),!1)),!h))},[]),l=w.useCallback(()=>{n.current=!1,s(h=>(h||t(!1),!h))},[]),u=w.useCallback(()=>t(!1),[]),d=w.useCallback(()=>s(!1),[]);return{sessionListOpen:e,accountsPanelOpen:r,toggleSessionList:o,toggleAccountsPanel:l,closeSessionList:u,closeAccountsPanel:d}}const NL="agent-dag.detailOpen",jL="agent-dag.usagePanelOpen",SL="agent-dag.systemPanelOpen";function Coe(){return et(NL)==="1"}function Toe(e){Xe(NL,e?"1":"0")}function Aoe(){const e=et(jL);return e===null?!0:e==="1"}function Roe(e){Xe(jL,e?"1":"0")}function Moe(){const e=et(SL);return e===null?!0:e==="1"}function $oe(e){Xe(SL,e?"1":"0")}function Ioe(){const[e,t]=w.useState(Coe);w.useEffect(()=>{Toe(e)},[e]);const[n,r]=w.useState(Aoe);w.useEffect(()=>{Roe(n)},[n]);const[s,o]=w.useState(Moe);return w.useEffect(()=>{$oe(s)},[s]),qt("detail-panel",e),qt("usage-panel",n),qt("machine-panel",s),{detailOpen:e,setDetailOpen:t,usagePanelOpen:n,setUsagePanelOpen:r,machinePanelOpen:s,setMachinePanelOpen:o}}const Ooe={permission:"your permission",asked:"your answer"},Loe="No sessions are waiting for you.";function Poe(e){if(e.length===0)return"";const[t,...n]=e,r=t.waiting.kind,s=n.every(l=>l.waiting.kind===r)?Ooe[r]??"you":"you";if(n.length===0)return`${t.label} is waiting for ${s}.`;const o=`${n.length} more session${n.length===1?"":"s"}`;return`${t.label} and ${o} are waiting for ${s}.`}function Yv(e,t,n=Loe){return t!==""?t:e===""?"":n}function Doe({waitingSessions:e,watchUnseen:t,incidents:n=[]}){const[r,s]=w.useState(""),o=Poe(e);w.useEffect(()=>{s(g=>Yv(g,o))},[o]);const[l,u]=w.useState(""),d=t>0?`Browser watch has ${t} unread ${t===1?"finding":"findings"}.`:"";w.useEffect(()=>{u(g=>Yv(g,d,"Browser watch has no unread findings."))},[d]);const[h,f]=w.useState(""),p=soe(n);return w.useEffect(()=>{f(g=>Yv(g,p,aoe))},[p]),{blockedSaid:r,watchSaid:l,setWatchSaid:u,incidentSaid:h}}const Foe="/icon-192.png";function Boe({waitingSessions:e,liveSince:t,focusSession:n}){const[r,s]=w.useState(()=>typeof Notification>"u"?"denied":Notification.permission),[o,l]=w.useState(null),[u,d]=w.useState(!1),[h,f]=w.useState(!1),p=w.useRef(()=>{}),g=w.useRef(new Set),v=bn(e),k=w.useCallback(()=>{const T=!u;d(T),T&&(g.current=bu(v.current)),T&&typeof Notification<"u"&&Notification.permission==="default"&&p.current(),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({notifications:T})}).then(A=>A.ok?A.json():null).then(A=>{var R;A!=null&&A.ok&&(d(((R=A.prefs)==null?void 0:R.notifications)===!0),f(A.notificationsVetoed===!0))}).catch(()=>{})},[u]),b=typeof Notification<"u";w.useEffect(()=>{if(!o)return;const A=setTimeout(()=>l(null),o==="blocked"?8e3:4e3);return()=>clearTimeout(A)},[o]);const x=w.useCallback(()=>{typeof Notification>"u"||Notification.requestPermission().then(T=>{const A=T;s(A),A==="granted"?l("on"):A==="denied"&&l("blocked")})},[]);p.current=x;const N=w.useRef(null),S=e.map(T=>Ld(T.id,T.waiting)).join("|");w.useEffect(()=>{qq(N.current,r)&&(N.current=r,g.current=bu(e))},[r]);const _=w.useRef(!1);w.useEffect(()=>{Uq(_.current,t)&&(_.current=!0,g.current=bu(e))},[t]),w.useEffect(()=>{if(typeof Notification>"u"||Notification.permission!=="granted"||!zq(N.current,t))return;const T=typeof document>"u"||!document.hidden,A=Fq(e,g.current,T,u);for(const R of A)try{const B=new Notification(R.title,{body:R.body,tag:R.key,icon:Foe});B.onclick=()=>{window.focus(),n(R.sessionId),B.close()}}catch{}g.current=T?bu(e):Bq(e,A,g.current)},[S,t]);const E=w.useCallback(T=>{var A;d(((A=T.prefs)==null?void 0:A.notifications)===!0),f(T.notificationsVetoed===!0)},[]);return{notifyPermission:r,notifySaid:o,notifyOn:u,notifyVetoed:h,toggleNotify:k,notifySupported:b,askForNotifications:x,loadNotifyPrefs:E}}function qoe({openedTool:e,usageHistoryOpen:t,contextFor:n,tourOpen:r,summaryFor:s,browserWatchOpen:o,keyHelpOpen:l,releaseNotes:u,feedbackOpen:d}){const h=bn(l),f=w.useRef(!1);return f.current=e!=null||t||n!=null||r||s!=null||o||l||u!=null||d,{keyHelpOpenRef:h,modalOpenRef:f}}function Uoe({stateRef:e,tourOpen:t,releaseNotes:n}){const[r,s]=w.useState(null),o=w.useCallback((D,I)=>s({agentId:D,toolId:I}),[]),[l,u]=w.useState(null),[d,h]=w.useState(null),f=w.useCallback(D=>h(D),[]),[p,g]=w.useState(!1),[v,k]=w.useState(!1),[b,x]=w.useState(!1),[N,S]=w.useState(!1),[_,E]=w.useState(null),T=w.useCallback(D=>{E(D??null),S(!0)},[]);qt("tool-detail",r!==null),qt("session-summary",l!==null),qt("context-window",d!==null),qt("keyboard-help",p),qt("usage-history",v),qt("browser-watch",b),qt("feedback",N);const A=r?BB(e.current.agents,r.agentId,r.toolId):null,R=d?e.current.agents.get(d):void 0,{keyHelpOpenRef:B,modalOpenRef:P}=qoe({openedTool:A,usageHistoryOpen:v,contextFor:d,tourOpen:t,summaryFor:l,browserWatchOpen:b,keyHelpOpen:p,releaseNotes:n,feedbackOpen:N});return{setOpenedToolKey:s,openTool:o,openedTool:A,summaryFor:l,setSummaryFor:u,contextFor:d,setContextFor:h,openContext:f,contextAgent:R,keyHelpOpen:p,setKeyHelpOpen:g,usageHistoryOpen:v,setUsageHistoryOpen:k,browserWatchOpen:b,setBrowserWatchOpen:x,feedbackOpen:N,setFeedbackOpen:S,feedbackPrefill:_,setFeedbackPrefill:E,openFeedback:T,keyHelpOpenRef:B,modalOpenRef:P}}function zoe({stateRef:e,pinnedRef:t,measuredRef:n,positionsRef:r,lastLayoutSigRef:s,forgetRemovals:o,clearSelection:l,rerender:u,modalOpenRef:d}){const[h,f]=w.useState(!1),p=w.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}e.current=Eb(),t.current.clear(),n.current.clear(),r.current.clear(),s.current="",zO(),o(),l(),u()},[u,l,o]),g=bn(h),v=w.useCallback(k=>{const b=fre(k,{confirmOpen:g.current,modalOpen:d.current});b==="confirm"?f(!0):b==="clear"&&(f(!1),p())},[p]);return{clearConfirmOpen:h,setClearConfirmOpen:f,requestClear:v}}const PR="agent-dag.oldNameNoticeDismissed";function Woe(e){const[t,n]=w.useState(()=>et(PR)??""),r=e!=null&&e.invokedAs&&e.invokedAs!==bt?e.invokedAs:null,s=r!=null&&t!==r,o=w.useCallback(()=>{r&&(n(r),Xe(PR,r))},[r]);return{oldName:r,oldNameOpen:s,dismissOldName:o}}function Hoe({chimesRef:e,setTonePrefs:t,tonePrefsRef:n,previewTone:r}){const[s,o]=w.useState(()=>UY(et)),l=bn(s),[u,d]=w.useState([]),h=w.useCallback(S=>{o(_=>{const E={..._,[S]:null};return l.current=E,E}),us(Tl[S])},[]),f=w.useCallback((S,_)=>{const E=l.current[S];_&&E!==_||(h(S),t(T=>{const A={...T,[S]:{...T[S],figure:xn}};return n.current=A,A}),Xe(vd[S],xn))},[h]),p=bn(f);w.useEffect(()=>{let S=!0;return QY().then(_=>{if(!S)return;d(_);const E=new Set(_.map(T=>T.id));for(const T of Hu){const A=l.current[T];A&&!E.has(A)&&p.current(T,A)}},()=>{}),()=>{S=!1}},[]);const g=w.useCallback((S,_)=>{if(!u.some(T=>T.id===_))return;const E={...l.current,[S]:_};l.current=E,o(E),Xe(Tl[S],_),r(S,!0)},[u,r]),v=w.useCallback(async S=>{var A;const _=Wx(u.length);if(_)throw new Error(_);const E=window.AudioContext??window.webkitAudioContext;if(!E)throw new Error("This browser cannot decode audio files.");const T=new E;try{const R=await KY(S,P=>T.decodeAudioData(P));await MS(R);const B=Kl(R);d(P=>[...P.filter(D=>D.id!==B.id),B])}finally{(A=T.close)==null||A.call(T)}},[u.length]),k=w.useCallback(async S=>{const _=Wx(u.length);if(_)throw new Error(_);const E=YY(S);await MS(E);const T=Kl(E);d(A=>[...A.filter(R=>R.id!==T.id),T])},[u.length]),b=w.useCallback(async(S,_)=>{const E=u.find(A=>A.id===S),T=_.trim().slice(0,80);!E||!T||T===E.name||(await tX(S,T),d(A=>A.map(R=>R.id===S?{...R,name:T}:R)))},[u]),x=w.useCallback(async S=>{await nX(S),d(A=>A.filter(R=>R.id!==S));const _=l.current,E=zY(_,S);l.current=E,o(E);const T=Hu.filter(A=>_[A]===S);for(const A of T)us(Tl[A]),Xe(vd[A],xn);T.length>0&&t(A=>{const R={...A};for(const B of T)R[B]={...R[B],figure:xn};return n.current=R,R})},[]),N=w.useCallback(S=>{var T,A;const _=Hu.find(R=>l.current[R]===S),E=_?n.current[_].level:Ua;(T=e.current)==null||T.unlock(),(A=e.current)==null||A.previewCustom(S,E)},[]);return{customSelections:s,customSelectionsRef:l,customAssets:u,clearCustomOnly:h,fallbackCustomRef:p,selectCustomTone:g,importNotificationAudio:v,createNotificationVoice:k,renameCustomAsset:b,deleteCustomAsset:x,previewCustomAsset:N}}function Voe(e){const[t,n]=w.useState(()=>pX(et)),r=bn(t),s=w.useRef(null),o=w.useCallback((u,d=!1)=>{var h,f;if((h=e.current)==null||h.unlock(),s.current!==null&&clearTimeout(s.current),s.current=null,!d){(f=e.current)==null||f.play(u,!0);return}s.current=setTimeout(()=>{var p;s.current=null,(p=e.current)==null||p.play(u,!0)},cX)},[]),l=w.useCallback((u,d)=>{n(h=>{const f={level:fi(d.level??h[u].level),figure:EI(u,d.figure??h[u].figure)};return Xe(AI[u],String(f.level)),Xe(vd[u],f.figure),{...h,[u]:f}}),o(u,!0)},[o]);return w.useEffect(()=>()=>{s.current!==null&&clearTimeout(s.current)},[]),{tonePrefs:t,setTonePrefs:n,tonePrefsRef:r,previewTone:o,changeTone:l}}const Goe=15e3;function Koe(e){return e.visibilityState==="visible"&&e.hasFocus()}function Yoe(e,t){return t||e===!0}function Xoe(){try{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID()}catch{}return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}function Zoe(){w.useEffect(()=>{const e=Xoe();let t=null;const n=l=>{fetch("/api/presence",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tab:e,looking:l}),keepalive:!0}).catch(()=>{})},r=()=>{const l=Koe(document);Yoe(t,l)&&n(l),t=l},s=()=>{t&&n(!1),t=!1};r();const o=window.setInterval(r,Goe);return window.addEventListener("focus",r),window.addEventListener("blur",r),document.addEventListener("visibilitychange",r),window.addEventListener("pagehide",s),()=>{window.clearInterval(o),window.removeEventListener("focus",r),window.removeEventListener("blur",r),document.removeEventListener("visibilitychange",r),window.removeEventListener("pagehide",s),s()}},[])}function Joe({loadAutoRestartPrefs:e,loadNotifyPrefs:t,loadReportsPrefs:n}){w.useEffect(()=>{let r=!0;return fetch("/api/prefs").then(s=>s.ok?s.json():null).then(s=>{!r||!(s!=null&&s.ok)||(e(s),t(s),n(s))}).catch(()=>{}),()=>{r=!1}},[])}const Xv="agent-dag.versionNoticeDismissed",Qoe=15*6e4;function eie(e){const[t,n]=w.useState(null),[r,s]=w.useState(()=>et(Xv)??""),o=w.useRef(0),[l,u]=w.useState(!1),d=w.useCallback((b=!1)=>(b&&(o.current=Date.now(),u(!0)),fetch(b?"/api/version?refresh=1":"/api/version").then(x=>x.ok?x.json():null).then(x=>{x&&n(x)}).catch(()=>{}).finally(()=>{b&&u(!1)})),[]),h=w.useCallback(()=>{d(Date.now()-o.current>=Qoe)},[d]);w.useEffect(()=>{d();const b=window.setInterval(h,5*6e4),x=()=>{document.visibilityState==="visible"&&h()};return document.addEventListener("visibilitychange",x),()=>{window.clearInterval(b),document.removeEventListener("visibilitychange",x)}},[d,h]),w.useEffect(()=>{e&&d()},[e,d]);const f=(t==null?void 0:t.notice)??null,p=JO(f),g=qne(f,r),v=w.useCallback(()=>{s(""),Xe(Xv,"")},[]),k=w.useCallback(()=>{f&&(s(p),Xe(Xv,p))},[f,p]);return{version:t,notice:f,noticeOpen:g,showNotice:v,dismissNotice:k,versionChecking:l,loadVersion:d}}function tie({version:e,readyAppUpdate:t}){const[n,r]=w.useState(!1),s=w.useRef(null),[o,l]=w.useState(null),u=o&&t?t.version:null,d=w.useRef(null);w.useEffect(()=>{!u||d.current===u||(d.current=u,fetch("/api/desktop-update/seen",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({version:u})}).catch(()=>{}))},[u]);const h=w.useRef(!1);w.useEffect(()=>{if(h.current)return;const x=El(),N=aae(x),S=dae({stored:N,running:(e==null?void 0:e.running)??null,notes:_R,remembers:oae(x)});if(S.reason==="no-version")return;h.current=!0,S.record&&iae(x,S.record);let _=!0;return(async()=>{var B;let T=lae(x);if(Zl())try{const P=await fetch("/api/prefs");if(!P.ok)throw new Error("desktop tour preferences unavailable");const D=await P.json();if(!(D!=null&&D.ok))throw new Error("desktop tour preferences unavailable");const I=((B=D.prefs)==null?void 0:B.tourSeen)===!0;T&&!I&&fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tourSeen:!0}),keepalive:!0}).catch(()=>{}),T=T||I}catch{T=!0}if(!_)return;const A=uae({tourSeen:T,decision:S}),R=S.show.length?{entries:S.show,since:N,firstRun:!1}:null;A.tour&&r(!0),A.notes==="now"?l(R):A.notes==="after"&&(s.current=R)})(),()=>{_=!1}},[e==null?void 0:e.running]);const f=(e==null?void 0:e.running)??"3.36.8",p=w.useMemo(()=>cL(_R,null,f),[f]),g=w.useCallback(()=>{l({entries:p,since:null,firstRun:!1})},[p]),v=w.useCallback(()=>r(!0),[]),k=w.useCallback(()=>l(null),[]),b=w.useCallback(()=>{r(!1),cae(El()),Zl()&&fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tourSeen:!0}),keepalive:!0}).catch(()=>{});const x=s.current;s.current=null,x&&l(x)},[]);return{tourOpen:n,openTour:v,closeTour:b,releaseNotes:o,closeReleaseNotes:k,chipVersion:f,openReleaseNotes:g}}function nie(){const[e,t]=w.useState(void 0),[n,r]=w.useState(!1),s=w.useRef(!1);return s.current=e===!0&&!n,w.useEffect(()=>Iae(()=>s.current),[]),{loadReportsPrefs:w.useCallback(l=>{var u;t(((u=l.prefs)==null?void 0:u.reports)!==!1),r(l.reportsVetoed===!0)},[])}}function rie(){const[e,t]=w.useState(null),[n,r]=w.useState(!1),s=w.useRef(null);return w.useEffect(()=>{let o=!0,l=!1,u=null,d=null,h="";const f=b=>{o&&(r(!0),b!==h&&t(null))},p=()=>{if(l)return;l=!0,u=Date.now();const b=fR();d=b,fetch(`/api/ccusage?since=${b}`).then(x=>x.ok?x.json():null).then(x=>{if(o){if(!(x!=null&&x.ok)){f(b);return}h=b,t(Tne(x)),r(!1)}}).catch(()=>f(b)).finally(()=>{l=!1})},g=()=>{const b=s.current;Cne({shown:!!b&&b.getClientRects().length>0,tabVisible:document.visibilityState==="visible",lastSince:d,since:fR(),lastReadAt:u,now:Date.now()})&&p()};g();const v=window.setInterval(g,Ene);document.addEventListener("visibilitychange",g);let k=null;return s.current&&typeof ResizeObserver<"u"&&(k=new ResizeObserver(g),k.observe(s.current)),()=>{o=!1,window.clearInterval(v),document.removeEventListener("visibilitychange",g),k==null||k.disconnect()}},[]),{monthlyUsage:e,monthlyUsageUnavailable:n,monthUsageRef:s}}function sie(e){const[t,n]=w.useState(null);w.useEffect(()=>{const u=et("agent-dag.sound");n(u===null?!0:u==="on")},[]);const r=w.useCallback(()=>{n(u=>{var h;const d=u!==!0;return Xe("agent-dag.sound",d?"on":"off"),d&&((h=e.current)==null||h.unlock()),d})},[]),s=w.useCallback(u=>{r()},[r]),o=bn(s),l=bn(t);return{soundOn:t,toggleSound:r,activateSoundRef:o,soundOnRef:l}}function aie(){const[e,t]=w.useState(!1),n=w.useRef(null),[r,s]=w.useState(!1),o=w.useRef(null);return w.useEffect(()=>{e&&s(!1)},[e]),{soundMenuOpen:e,setSoundMenuOpen:t,soundButtonRef:n,appearanceMenuOpen:r,setAppearanceMenuOpen:s,appearanceButtonRef:o}}const oie="agent-dag.autoFitDisabled";function iie(e){const t=w.useRef(!1),[n,r]=w.useState(!1);w.useEffect(()=>{us(oie)},[]);const s=w.useCallback(()=>{t.current||(t.current=!0,r(!0))},[]),o=w.useCallback(()=>{t.current=!1,r(!1),e(400)},[e]);return{autoFitDisabled:n,autoFitDisabledRef:t,disableAutoFit:s,enableAutoFitAndRefit:o}}function lie(){return a.jsx(jb,{children:a.jsx(Pae,{children:a.jsx(cie,{})})})}function cie(){const e=gi(),t=w.useState(Eb)[0],n=w.useRef(t),[,r]=w.useState(0),s=w.useCallback(()=>r(_r=>_r+1),[]),{detailOpen:o,setDetailOpen:l,usagePanelOpen:u,setUsagePanelOpen:d,machinePanelOpen:h,setMachinePanelOpen:f}=Ioe(),{selectedIds:p,primarySelectedId:g,selectAgent:v,clearSelection:k,pruneSelectionToBoard:b}=MX(n,l),{sessionListOpen:x,accountsPanelOpen:N,toggleSessionList:S,toggleAccountsPanel:_,closeSessionList:E,closeAccountsPanel:T}=Eoe(),A=rie(),R=qu(N,200),B=qu(u,130),P=qu(h,130),D=w.useRef(null),I=sie(D),{activateSoundRef:O,soundOnRef:z}=I,M=Voe(D),{setTonePrefs:$,tonePrefsRef:q,previewTone:X}=M,H=Hoe({chimesRef:D,setTonePrefs:$,tonePrefsRef:q,previewTone:X}),{customSelectionsRef:L,fallbackCustomRef:W}=H,F=aie(),{setSoundMenuOpen:Y}=F,{chimeState:J}=yoe({chimesRef:D,soundOnRef:z,tonePrefsRef:q,customSelectionsRef:L,fallbackCustomRef:W}),V=Bae(n),{pauseGate:G,paused:K,togglePause:Z}=V,ee=w.useRef(()=>{}),{live:re,tabCapped:ue,everConnected:me,liveSince:ie}=kX({stateRef:n,rerender:s,pauseGate:G,chimesRef:D,desktopUpdateRef:ee}),fe=eie(re),{version:ve,notice:se,noticeOpen:ke,loadVersion:pe}=fe,he=voe(re),{readyAppUpdate:ae,onDesktopUpdateEvent:Ne}=he;ee.current=Ne,Zoe();const Te=tie({version:ve,readyAppUpdate:ae}),{tourOpen:Ie,openTour:Oe,releaseNotes:He}=Te,{workspace:Ye,providers:Ve,providersRef:Ke}=Uae(re),nt=Woe(ve),it=ooe({version:ve,loadVersion:pe}),{upgradeFailure:At}=it,Be=Uoe({stateRef:n,tourOpen:Ie,releaseNotes:He}),{openTool:Ze,setSummaryFor:Je,setContextFor:rt,openContext:gt,setKeyHelpOpen:Rt,setUsageHistoryOpen:yt,setBrowserWatchOpen:lt,keyHelpOpenRef:en,modalOpenRef:Pt}=Be,zt=nie(),Dt=loe(),{watchOn:Yt,watchUnseen:Ur}=Dt,zn=boe(),[zr,Wr]=w.useState(0),{presentCats:Wn,hiddenCats:gr,toggleCat:Hn,catBarRef:Hr,catBarOccluded:Nn}=moe(n),gs=w.useRef(null),[jn,yr]=w.useState(0),vt=$X({stateRef:n,rerender:s,pruneSelectionToBoard:b,setContextFor:rt,setSummaryFor:Je}),wr=Vae(Ve.claude||Ve.codex),vr=roe(wr,vt),Vr=Noe({enabled:Ve.claude,lanStatus:zn.lanStatus,now:vt}),Vn=xoe({now:vt,stateRef:n,version:ve,notice:se,noticeOpen:ke,upgradeFailure:At,paused:V.paused}),{askRestart:pn,loadAutoRestartPrefs:Gr}=Vn,Sn=poe(),{setTheme:we,palette:De}=Sn,qe=woe(),tn=Aee(),{applyViewport:ys,moveCamera:xr,fitLeft:Gn,cameraEpochRef:Qe,lastFitTimeRef:br}=tn,Kn=Uee(Gn),{pinnedRef:Kr,positionsRef:Yn,lastLayoutSigRef:Xa,handleRelayout:Za}=Kn,Yr=w.useRef(null),Xt=w.useRef(()=>{}),sa=ie!==null,nn=w.useRef(0),hc=w.useCallback(()=>{nn.current=Date.now()},[]),{canvasRef:Xr,canvasSize:wh,paneSizeRef:Ja}=Nte(),aa=iie(Gn),{autoFitDisabledRef:ws,disableAutoFit:vs}=aa,_i=w.useRef(!1),{measuredRef:Zr,measuredVersionRef:Ei,sizeVersion:fc,domSizeVersion:Ci}=jte(_i),{removedNodes:xs,removedAgentIds:bs,removeNode:Ti,removeSelectedNode:ze,removalNotice:Ai,bringBack:Qa,bringBackAll:Ri,forgetRemovals:pc}=fte({stateRef:n,pinnedRef:Kr,positionsRef:Yn,canvasRef:Xr,clearSelection:k,primarySelectedId:g}),eo=lte({stateRef:n,now:vt,removedAgentIds:bs,sizeVersion:fc,domSizeVersion:Ci});zee(eo,Kn),hte({layoutSig:eo,rf:e,stateRef:n,measuredRef:Zr,pinnedRef:Kr,positionsRef:Yn,paneSizeRef:Ja,lastInteractRef:nn,autoFitDisabledRef:ws,lastFitTimeRef:br,fitLeft:Gn});const Mi=Ste(),{bubbling:$i,endBubble:to,onBubble:kr}=_Y(),[_n,Nr]=w.useState(!1),ks=jY(),{beginTrashDrag:oa,trackTrashDrag:jr,endTrashDrag:vh}=ks,Ii=OY({stateRef:n,measuredRef:Zr,measuredVersionRef:Ei,canvasRef:Xr}),{lodRef:ia,applyZoom:xh}=Ii,Oi=bte({applyViewport:ys,lastFitTimeRef:br,cameraEpochRef:Qe,disableAutoFit:vs,markInteract:hc,canvasRef:Xr,applyZoom:xh}),Sr=g?n.current.agents.get(g):null,Li=o&&Sr!=null,{railInsetRef:mc,availableWidth:Ns,availableHeight:js}=Tee({canvasRef:Xr,canvasSize:wh,machinePhase:P,usagePhase:B,usagePanelOpen:u,detailShown:Li}),Jr=cte({stateRef:n,now:vt,availableWidth:Ns,availableHeight:js,layout:Kn,measuredRef:Zr,onBubble:kr,settled:Mi,dragging:_n,layoutSig:eo,selectedIds:p,openContext:gt,historyReplayed:sa,removedNodes:xs,removedAgentIds:bs,dragTick:zr,dragPatchRef:gs,dragMoveTick:jn}),{nodes:Ss,edges:bh}=Jr,Pi=bn(g);Wee({nodes:Ss,edges:bh,settled:Mi,dragging:_n,availableWidth:Ns,availableHeight:js,layout:Kn,camera:tn,rf:e,measuredRef:Zr,stateRef:n,lastFocusRef:Yr,primarySelectedIdRef:Pi,focusAgentRef:Xt,autoFitDisabledRef:ws});const no=zoe({stateRef:n,pinnedRef:Kr,measuredRef:Zr,positionsRef:Yn,lastLayoutSigRef:Xa,forgetRemovals:pc,clearSelection:k,rerender:s,modalOpenRef:Pt}),{requestClear:la}=no,Di=Cte({nodes:Ss,stateRef:n,pinnedRef:Kr,positionsRef:Yn,draggingRef:_i,dragPatchRef:gs,setDragging:Nr,setDragMoveTick:yr,setDragTick:Wr,endBubble:to,markInteract:hc,disableAutoFit:vs,beginTrashDrag:oa,trackTrashDrag:jr,endTrashDrag:vh,removeNode:Ti}),gc=w.useCallback(()=>Gn(500),[Gn]),En=bn(Ss),{focusAgent:_s,stepAgent:ro,focusSession:Es}=Mee({canvasRef:Xr,stateRef:n,measuredRef:Zr,nodesRef:En,railInsetRef:mc,moveCamera:xr,disableAutoFit:vs,lastFocusRef:Yr,focusAgentRef:Xt,selectAgent:v,primarySelectedIdRef:Pi}),so=kte({clearSelection:k,selectAgent:v,detailOpen:o,setDetailOpen:l,detailShown:Li,focusAgent:_s,draggingRef:_i,lodRef:ia}),ao=$ee({nodesRef:En,stateRef:n,canvasRef:Xr,railInsetRef:mc}),yc=bn(ze),Wt=w.useRef(null),Cs=w.useCallback(_r=>{bs.has(_r)&&Qa([_r]),Es(_r)},[bs,Qa,Es]),Fi=_te();Ete({pointerFocusRef:Fi,nodesRef:En,stateRef:n,primarySelectedIdRef:Pi,providersRef:Ke,soundOnRef:z,keyHelpOpenRef:en,modalOpenRef:Pt,waitingCursorRef:Wt,removeSelectedRef:yc,activateSoundRef:O,clearSelection:k,selectAgent:v,focusAgent:_s,stepAgent:ro,focusSession:Es,requestClear:la,handleRelayout:Za,handleFit:gc,togglePause:Z,toggleSessionList:S,toggleAccountsPanel:_,setDetailOpen:l,setUsageHistoryOpen:yt,setUsagePanelOpen:d,setMachinePanelOpen:f,setBrowserWatchOpen:lt,setSoundMenuOpen:Y,setKeyHelpOpen:Rt,setTheme:we});const Ts=n.current.agents.size,ca=w.useMemo(()=>mx(n.current.agents.values()),[n.current,n.current.revision]);pte({stateRef:n,waitingSessions:ca,removedAgentIds:bs,removedNodes:xs,bringBack:Qa}),wte({stateRef:n,waitingSessions:ca,live:re});const oo=Doe({waitingSessions:ca,watchUnseen:Ur,incidents:vr}),ua=Boe({waitingSessions:ca,liveSince:ie,focusSession:Es}),{loadNotifyPrefs:da}=ua;return Joe({loadAutoRestartPrefs:Gr,loadNotifyPrefs:da,loadReportsPrefs:zt.loadReportsPrefs}),a.jsxs("div",{className:"app",children:[a.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),a.jsxs("header",{className:"topbar",children:[a.jsx(Gne,{versionCheck:fe,welcome:Te,desktopUpdate:he,pause:V,monthly:A,announcements:oo,notify:ua,waitingSessions:ca,waitingCursorRef:Wt,focusSession:Es,live:re,now:vt,incidents:vr}),Sr&&a.jsx(Kne,{selected:Sr,now:vt,selectedIds:p,focusAgent:_s,clearSelection:k}),a.jsxs("div",{className:"actions",children:[a.jsx(Nne,{sessionListOpen:x,toggleSessionList:S,usagePanelOpen:u,setUsagePanelOpen:d,setUsageHistoryOpen:yt}),a.jsx(jne,{providers:Ve,accountsPanelOpen:N,toggleAccountsPanel:_,machinePanelOpen:h,setMachinePanelOpen:f,watchOn:Yt,watchUnseen:Ur,setBrowserWatchOpen:lt}),a.jsx(Sne,{providers:Ve,sound:I,tones:M,customTones:H,notify:ua,chimeState:J,menus:F,appearance:Sn,fm:qe,onFeedback:()=>Be.openFeedback()})]})]}),a.jsx("div",{className:"vis-hidden",role:"status","aria-atomic":"true",children:Ai?`${Ai.label} removed from the board.`:""}),a.jsx(hre,{restart:Vn,versionCheck:fe,upgrade:it,oldNameNotice:nt,everConnected:me,live:re,paused:K}),Bu(R)&&Ve.claude&&a.jsx(yY,{leaving:R==="leaving",onClose:T,onReport:Be.openFeedback,onRoster:Vr.observe}),x&&a.jsx(SW,{state:n.current,now:vt,selectedIds:p,onSelect:Cs,onClose:E,removedIds:bs,onBringBackAll:Ri}),a.jsxs(Qne,{canvasRef:Xr,bubbling:$i,dragging:_n,trash:ks,zoom:Ii,viewport:Oi,stateRef:n,peek:ao,children:[Ts===0&&(!re&&ue?a.jsx(Lte,{}):a.jsx(Pte,{live:re,everConnected:me,providers:Ve,workspace:Ye,onTour:Oe})),(Wn.length>1||gr.size>0)&&a.jsx(Yne,{catBarRef:Hr,catBarOccluded:Nn,presentCats:Wn,hiddenCats:gr,toggleCat:Hn}),a.jsx(yW,{graph:Jr,layout:Kn,viewport:Oi,clicks:so,drag:Di,appearance:Sn,fm:qe,autoFit:aa,pause:V,stateRef:n,measuredRef:Zr,hiddenCats:gr,now:vt,openTool:Ze,focusAgent:_s,requestClear:la,setKeyHelpOpen:Rt})]}),Bu(B)&&a.jsx(vH,{state:n.current,now:vt,providers:Ve,incidents:vr,liveSince:ie,leaving:B==="leaving",onClose:()=>d(!1)}),Bu(P)&&a.jsx(n7,{usageOpen:u,leaving:P==="leaving",onClose:()=>f(!1)}),o&&Sr?a.jsx(Xte,{selected:Sr,now:vt,openTool:Ze,setSummaryFor:Je,setDetailOpen:l,stateRef:n,removeSelectedNode:ze}):null,a.jsx(Aae,{dialogs:Be,welcome:Te,desktopUpdate:he,versionCheck:fe,restart:Vn,lanPairs:zn,attention:Vr,clearFlow:no,watchBadge:Dt,announcements:oo,appearance:Sn,providers:Ve,stateRef:n,agentCount:Ts})]})}try{k8(window.localStorage)}catch{}const uie=iP.createRoot(document.getElementById("root"));uie.render(a.jsx(lie,{}));export{Rte as a,ot as b,Qs as c,yd as d,fie as e,Fe as f,Or as g,hie as h,Vl as i,a as j,Dr as k,VY as l,_a as m,Ha as n,Ex as p,w as r,lr as s,ft as u};
|
|
306
|
+
`)}}function roe(e,t){return!(e!=null&&e.ok)||e.disabled||!Array.isArray(e.providers)?[]:e.providers.map(n=>noe(n,t)).filter(n=>n!==null)}function soe(e){return e.map(t=>`${t.name}'s status page reports ${t.state.endsWith("outage")?`a ${t.words}`:t.words}.`).join(" ")}const aoe="The providers' status pages report no incident.";function ooe({version:e,loadVersion:t}){var h;const[n,r]=w.useState(!1),s=((h=e==null?void 0:e.upgrade)==null?void 0:h.state)??"idle",o=are(e==null?void 0:e.upgrade),l=w.useRef(!1),u=w.useCallback(async()=>{if(lr(l.current||s==="running")){l.current=!0;try{await fetch("/api/upgrade",{method:"POST"})}catch{}await t(),l.current=!1}},[s,t]);w.useEffect(()=>{if(s!=="running")return;const f=window.setInterval(t,3e3);return()=>window.clearInterval(f)},[s,t]);const d=w.useCallback(async()=>{const f=e==null?void 0:e.command;!f||!await Ha(f)||(r(!0),window.setTimeout(()=>r(!1),1600))},[e==null?void 0:e.command]);return{upgradeState:s,upgradeFailure:o,startUpgrade:u,copyCommand:d,cmdCopied:n}}const $R="agent-dag.browserWatch.seenMs";function ioe(e,t){return e.filter(n=>n.startMs>t)}function loe(){const[e,t]=w.useState(()=>Number(et($R))||0),[n,r]=w.useState([]),[s,o]=w.useState(!1);w.useEffect(()=>{let d=!0;const h=()=>{fetch("/api/browser-watch?live=0").then(p=>p.ok?p.json():null).then(p=>{var g;!d||!(p!=null&&p.ok)||(r(p.episodes??[]),o(((g=p.settings)==null?void 0:g.enabled)===!0))}).catch(()=>{})};h();const f=setInterval(h,5*6e4);return()=>{d=!1,clearInterval(f)}},[]);const l=w.useMemo(()=>ioe(n,e).length,[n,e]),u=w.useCallback(d=>{t(d),Xe($R,String(d))},[]);return{watchOn:s,setWatchOn:o,watchUnseen:l,markWatchSeen:u}}const vL=["--err","--inflight","--ok","--grid-line","--minimap-mask","--panel","--line","--accent","--text-dim","--bg"];function IR(e){const t={};for(const n of vL)t[n]=e(n);return t}function coe(e){return t=>e[t]??""}function uoe(e,t){return vL.every(n=>e[n]===t[n])}const xL="agent-dag.theme";function doe(e,t=!1){return e==="light"||e==="dark"?e:t?"light":"dark"}function hoe(){try{return window.matchMedia("(prefers-color-scheme: light)").matches}catch{return!1}}function foe(){return doe(et(xL),hoe())}function OR(e){return typeof window>"u"?"":getComputedStyle(document.documentElement).getPropertyValue(e).trim()||""}function poe(){const[e,t]=w.useState(foe),[n,r]=w.useState(ene),[s,o]=w.useState(()=>IR(OR)),l=w.useMemo(()=>coe(s),[s]),u=w.useCallback(d=>ste(d,l),[l]);return w.useEffect(()=>{document.documentElement.dataset.theme=e,Xe(xL,e),o(d=>{const h=IR(OR);return uoe(d,h)?d:h})},[e]),w.useEffect(()=>{Xe(YO,n?"1":"0")},[n]),w.useEffect(()=>{const d=document.querySelector('meta[name="theme-color"]'),h=s["--panel"];d&&h&&d.setAttribute("content",h)},[s]),{theme:e,setTheme:t,characterEnabled:n,setCharacterEnabled:r,palette:s,minimapNodeFill:u}}function moe(e){const t=w.useRef(null),[n,r]=w.useState(!1),[s,o]=w.useState(()=>new Set),l=w.useMemo(()=>{const d=new Set;for(const h of e.current.agents.values())for(const f of h.tools)d.add(GO(f.name));return Object.keys(VO).filter(h=>d.has(h))},[e.current,e.current.revision]);w.useEffect(()=>{if(l.length<=1){r(!1);return}let d=0;const h=()=>{const f=t.current;if(f&&!document.hidden){const p=f.getBoundingClientRect(),g=Array.from(document.querySelectorAll(".react-flow__node, .tool-burst")).map(k=>k.getBoundingClientRect()),v=Kee(p,g,8);r(k=>k===v?k:v)}d=window.setTimeout(h,300)};return h(),()=>window.clearTimeout(d)},[l.length]);const u=w.useCallback(d=>{o(h=>{const f=new Set(h);return f.has(d)?f.delete(d):f.add(d),f})},[]);return{presentCats:l,hiddenCats:s,toggleCat:u,catBarRef:t,catBarOccluded:n}}function goe(e={enabled:()=>!0}){const t=e.ctor??(typeof window<"u"?window.AudioContext??window.webkitAudioContext??null:null);let n=null;const r=()=>e.enabled()?(n==null?void 0:n.state)==="running"?"ready":"locked":"off",s=()=>{var f;return(f=e.onState)==null?void 0:f.call(e,r())};function o(){var f,p;if(!(!t||(n==null?void 0:n.state)==="running"))try{n||(n=new t,(f=n.addEventListener)==null||f.call(n,"statechange",s)),(p=n.resume)==null||p.call(n).then(s,()=>{}),s()}catch{}}function l(f,p,g=p.figure){if(!n||n.state!=="running")return!1;const v=Hx(f,g),k=aX(p.level,v),b=n.currentTime;for(const x of v.notes){const N=n.createOscillator(),S=n.createGain();N.type=v.type??"sine",N.frequency.value=x.hz;const _=b+x.at,E=_+x.ms/1e3;S.gain.setValueAtTime(IS,_),S.gain.exponentialRampToValueAtTime(k,_+oX),S.gain.exponentialRampToValueAtTime(IS,E),N.connect(S).connect(n.destination),N.start(_),N.stop(E+.02)}return!0}async function u(f,p){if(!e.loadCustom)return"unavailable";let g;try{g=await e.loadCustom(f)}catch{return"unavailable"}if(!g)return"missing";if(g.kind==="tts"){if(typeof window>"u"||!("speechSynthesis"in window)||typeof SpeechSynthesisUtterance>"u")return"unavailable";const k=new SpeechSynthesisUtterance(g.text),b=window.speechSynthesis.getVoices();k.voice=b.find(x=>x.voiceURI===g.voiceURI)??null,k.rate=g.rate,k.pitch=g.pitch,k.volume=iX(p.level);try{return window.speechSynthesis.cancel(),window.speechSynthesis.speak(k),"played"}catch{return"unavailable"}}if(!n||n.state!=="running")return"locked";let v;try{v=await n.decodeAudioData(g.bytes.slice(0))}catch{return"broken"}try{const k=n.createBufferSource(),b=n.createGain();return k.buffer=v,b.gain.setValueAtTime(lX(g.normalizationGain,p.level),n.currentTime),k.connect(b).connect(n.destination),k.start(n.currentTime),"played"}catch{return"unavailable"}}function d(f,p=!1){var k,b;if(!p&&!e.enabled())return!1;const g=mX((k=e.prefs)==null?void 0:k.call(e),f),v=((b=e.customSelection)==null?void 0:b.call(e)[f])??null;return v&&e.loadCustom?(u(v,g).then(x=>{var N;x==="played"||x==="locked"||((x==="missing"||x==="broken")&&((N=e.onCustomFailure)==null||N.call(e,f,v)),l(f,{...g,figure:xn},xn))}),!0):!n||n.state!=="running"?!1:l(f,g)}function h(f,p=Ua){return u(f,{level:fi(p),figure:xn}),!0}return{unlock:o,play:d,previewCustom:h,state:r,get context(){return n}}}function yoe({chimesRef:e,soundOnRef:t,tonePrefsRef:n,customSelectionsRef:r,fallbackCustomRef:s}){const[o,l]=w.useState("locked");return w.useEffect(()=>{const u=goe({enabled:()=>t.current===!0,prefs:()=>n.current,customSelection:()=>r.current,loadCustom:eX,onCustomFailure:(p,g)=>s.current(p,g),onState:l});e.current=u,l(u.state());const d=["pointerdown","pointerup","keydown"],h=()=>{for(const p of d)window.removeEventListener(p,f,{capture:!0})},f=()=>{var p;u.unlock(),((p=u.context)==null?void 0:p.state)==="running"&&h()};for(const p of d)window.addEventListener(p,f,{capture:!0});return h},[]),{chimeState:o}}function woe(){const[e,t]=w.useState(nne),[n,r]=w.useState(()=>gB(et(GN))),[s,o]=w.useState(()=>mB(et(KN))),[l,u]=w.useState(()=>yB(et(lR),n,Jte)),[d,h]=w.useState(()=>new Set),[f,p]=w.useState(0);w.useEffect(()=>{Xe(XO,String(e))},[e]),w.useEffect(()=>{Xe(KN,s?"1":"0")},[s]),w.useEffect(()=>{Xe(GN,JSON.stringify(n))},[n]),w.useEffect(()=>{Xe(lR,l)},[l]);const g=w.useCallback(N=>{r(S=>S.some(_=>_.id===N.id)?S:[...S,N]),h(S=>{if(!S.has(N.id))return S;const _=new Set(S);return _.delete(N.id),_})},[]),v=w.useCallback((N,S)=>{const _=S.trim();!_||_.length>Dl||r(E=>E.map(T=>T.id===N?{...T,name:_}:T))},[]),k=w.useCallback(N=>{r(S=>S.filter(_=>_.id!==N)),u(S=>wB(S,N)),h(S=>{if(!S.has(N))return S;const _=new Set(S);return _.delete(N),_})},[]),b=w.useCallback(N=>{const S=YN(N),_=d.has(S);N===l&&!_||(_&&h(E=>{const T=new Set(E);return T.delete(S),T}),u(N),p(E=>E+1))},[l,d]),x=w.useCallback((N,S)=>{const _=YN(N);h(E=>{if(E.has(_)===S)return E;const A=new Set(E);return S?A.add(_):A.delete(_),A})},[]);return{fmVolume:e,setFmVolume:t,fmMuted:s,setFmMuted:o,fmSource:l,customFmStations:n,unavailableFmStations:d,fmPlayRequest:f,addFmStation:g,renameFmStation:v,removeFmStation:k,pickFmSource:b,markFmStationAvailability:x}}function voe(e){const[t,n]=w.useState(null),[r,s]=w.useState(!1),[o,l]=w.useState(null),u=w.useRef(0);w.useEffect(()=>{if(!e||!Zl())return;let k=!1;const b=u.current;return fetch("/api/desktop-update").then(x=>x.ok?x.json():null).then(x=>{if(k||u.current!==b)return;const N=mR(x);N&&n(N)}).catch(()=>{}),()=>{k=!0}},[e]);const d=Rne(t),h=w.useRef(!1),f=w.useRef(0),p=w.useRef(0);w.useEffect(()=>()=>window.clearTimeout(f.current),[]);const g=w.useCallback(async k=>{if(!lr(h.current))return;const b=++p.current;window.clearTimeout(f.current),h.current=!0,s(!0),l(null);const x=N=>{b===p.current&&(window.clearTimeout(f.current),h.current=!1,s(!1),l({failure:N,version:k}))};try{const N=await fetch("/api/desktop-update/restart",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({version:k})});if(!N.ok)return x(One(await N.json().catch(()=>null)))}catch{return x("unreachable")}b===p.current&&(f.current=window.setTimeout(()=>x("timeout"),ZO))},[]),v=w.useCallback(k=>{if(Zl())try{const b=mR(JSON.parse(k));b&&(u.current++,n(b),b.status!=="ready"&&(p.current++,window.clearTimeout(f.current),h.current=!1,s(!1)))}catch{}},[]);return{desktopUpdateRestarting:r,desktopUpdateFailure:o,readyAppUpdate:d,askDesktopUpdateRestart:g,onDesktopUpdateEvent:v}}const Ou="agent-dag.autoRestart",LR="agent-dag.bundleReloadedFor";function xoe({now:e,stateRef:t,version:n,notice:r,noticeOpen:s,upgradeFailure:o,paused:l}){const[u,d]=w.useState(()=>et(Ou)!=="0"),h=w.useRef(!1),f=w.useCallback(()=>{const D=!u;h.current=!0,us(Ou),d(D),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({autoUpdate:D})}).then(I=>I.ok?I.json():null).then(I=>{var O;I!=null&&I.ok&&d(((O=I.prefs)==null?void 0:O.autoUpdate)!==!1)}).catch(()=>{})},[u]),[p,g]=w.useState(!1),[v,k]=w.useState("restart"),[b,x]=w.useState(null),N=w.useRef(!1),S=w.useRef(null),_=w.useRef(0),E=w.useRef(null),T=w.useCallback(async D=>{if(!lr(N.current))return;const I=(D==null?void 0:D.upgrade)===!0,O=(D==null?void 0:D.whenIdle)===!0;N.current=!0,S.current=o;const z=++_.current,M=()=>{k(I?"npx":"restart"),g(!0)};O||M();try{window.sessionStorage.setItem("agent-dag.restartPending",(r==null?void 0:r.to)??"")}catch{}let $=!1;try{const q=await fetch("/api/restart",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(O?{upgrade:I,whenIdle:O}:{upgrade:I})});$=O&&q.status===409}catch{}if($){N.current=!1,E.current=null;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}return}O&&M(),window.setTimeout(()=>{if(_.current===z&&N.current){N.current=!1,g(!1);try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}}},I?18e4:3e4)},[r==null?void 0:r.to,o]);w.useEffect(()=>{const D=gR(t.current.agents.values())>0,I=ure({enabled:u,kind:r==null?void 0:r.kind,canRestart:(n==null?void 0:n.canRestart)===!0,noticeOpen:s,visible:document.visibilityState==="visible",busy:D,idleSince:E.current,now:e});E.current=I.idleSince,I.restart&&T({whenIdle:!0})},[u,r==null?void 0:r.kind,n==null?void 0:n.canRestart,s,e,T]);const A=gR(t.current.agents.values()),R=cre(A,l),B=QO({enabled:u,kind:r==null?void 0:r.kind,canRestart:(n==null?void 0:n.canRestart)===!0,noticeOpen:s,visible:document.visibilityState==="visible",busy:A>0,idleSince:E.current,now:e});w.useEffect(()=>{const D=n==null?void 0:n.running;let I=null,O=null;try{I=window.sessionStorage.getItem("agent-dag.restartPending"),O=window.sessionStorage.getItem(LR)}catch{return}const z=sre({bundle:"3.36.9",running:D,pending:I,lastTried:O});if(z==="reload"){try{window.sessionStorage.setItem(LR,D??"")}catch{return}window.location.reload();return}if(z!=="confirm")return;try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}N.current=!1,g(!1),x(D??null);const M=window.setTimeout(()=>x(null),6e3);return()=>window.clearTimeout(M)},[n==null?void 0:n.running]),w.useEffect(()=>{if(p&&ore({asked:S.current,reported:o})){try{window.sessionStorage.removeItem("agent-dag.restartPending")}catch{}N.current=!1,g(!1)}},[p,o]);const P=w.useCallback(D=>{var I,O;if(!h.current){const z=et(Ou)==="0";us(Ou),z&&((I=D.prefs)==null?void 0:I.autoUpdate)!==!1?(d(!1),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({autoUpdate:!1})}).catch(()=>{})):d(((O=D.prefs)==null?void 0:O.autoUpdate)!==!1)}},[]);return{autoRestart:u,toggleAutoRestart:f,restarting:p,restartMode:v,restartedTo:b,askRestart:T,restartCopy:R,restartFuseMs:B,loadAutoRestartPrefs:P}}function boe(){const[e,t]=w.useState([]),n=w.useRef(new Set),[r,s]=w.useState(null),[o,l]=w.useState(void 0);w.useEffect(()=>{let h=!0,f=0;const p=()=>{fetch("/api/lan").then(g=>g.ok?g.json():null).then(g=>h?(g!=null&&g.ok&&t(iS(Array.isArray(g.pending)?g.pending:[],g.aliases)),g):null).catch(()=>null).then(g=>{h&&(l(v=>g!=null&&g.ok?g:v===void 0?null:v),f=window.setTimeout(p,(g==null?void 0:g.enabled)===!0?Dx:Q$))})};return p(),()=>{h=!1,window.clearTimeout(f)}},[]);const u=w.useCallback(async(h,f)=>{if(!r){s(h);try{const g=await(await fetch("/api/lan/peer",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:h,fp:f})})).json().catch(()=>null);g&&Array.isArray(g.pending)?t(iS(g.pending,g.aliases)):t(v=>v.filter(k=>k.fp!==f))}catch{}finally{s(null)}}},[r]),d=w.useCallback(h=>{r||(n.current.add(h),t(f=>[...f]))},[r]);return{lanPending:e,lanDeferred:n,lanBusy:r,answerLanPair:u,deferLanPair:d,lanStatus:o}}const koe=6e4;function Noe({enabled:e,lanStatus:t,now:n}){const[r,s]=w.useState(null),[o,l]=w.useState(()=>new Set),[u,d]=w.useState(null),h=w.useCallback(N=>{N!=null&&N.ok&&Array.isArray(N.accounts)&&s(N.accounts)},[]),f=w.useCallback(async N=>{try{const S=await fetch(`/api/claude-accounts${N?"?refresh=1":""}`);S.ok&&h(await S.json())}catch{}},[h]);w.useEffect(()=>{e&&f(!1)},[e,f]),w.useEffect(()=>{if(!e)return;let N=Date.now();const S=()=>{document.visibilityState!=="visible"||Date.now()-N<koe||(N=Date.now(),f(!1))};return document.addEventListener("visibilitychange",S),window.addEventListener("focus",S),()=>{document.removeEventListener("visibilitychange",S),window.removeEventListener("focus",S)}},[e,f]);const p=w.useMemo(()=>e?Bse(r,{lan:t,now:n,closed:o}):[],[e,r,t,n,o]),g=w.useRef(p);g.current=p;const v=w.useCallback(N=>{N.length&&l(S=>new Set([...S,...N]))},[]),k=w.useCallback(()=>{const N=g.current;N.length&&(v(N.map(S=>S.id)),fetch("/api/claude-accounts/admin",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({action:"reauth-later",incidents:N.map(S=>({key:S.key,since:S.since}))})}).catch(()=>{}))},[v]),b=w.useRef(u);b.current=u;const x=w.useCallback(N=>{const S=b.current;v(qse(S?[S,...g.current]:g.current,N))},[v]);return{rows:p,signingIn:u,observe:h,signIn:d,signedIn:x,refresh:w.useCallback(()=>{f(!0)},[f]),closeSignIn:w.useCallback(()=>d(null),[]),later:k}}const bL="agent-dag.sessionListOpen",kL="agent-dag.accountsPanelOpen";function joe(){return et(bL)==="1"}function Soe(e){Xe(bL,e?"1":"0")}function _oe(){const e=et(kL);return e===null?!0:e==="1"}function Eoe(){const[e,t]=w.useState(joe);w.useEffect(()=>{Soe(e)},[e]);const n=w.useRef(!1),[r,s]=w.useState(()=>{const h=_oe();return h&&e?(n.current=!0,!1):h});w.useEffect(()=>{!r&&n.current||Xe(kL,r?"1":"0")},[r]),w.useEffect(()=>{!e&&n.current&&(n.current=!1,s(!0))},[e]);const o=w.useCallback(()=>{t(h=>(h||s(f=>(f&&(n.current=!0),!1)),!h))},[]),l=w.useCallback(()=>{n.current=!1,s(h=>(h||t(!1),!h))},[]),u=w.useCallback(()=>t(!1),[]),d=w.useCallback(()=>s(!1),[]);return{sessionListOpen:e,accountsPanelOpen:r,toggleSessionList:o,toggleAccountsPanel:l,closeSessionList:u,closeAccountsPanel:d}}const NL="agent-dag.detailOpen",jL="agent-dag.usagePanelOpen",SL="agent-dag.systemPanelOpen";function Coe(){return et(NL)==="1"}function Toe(e){Xe(NL,e?"1":"0")}function Aoe(){const e=et(jL);return e===null?!0:e==="1"}function Roe(e){Xe(jL,e?"1":"0")}function Moe(){const e=et(SL);return e===null?!0:e==="1"}function $oe(e){Xe(SL,e?"1":"0")}function Ioe(){const[e,t]=w.useState(Coe);w.useEffect(()=>{Toe(e)},[e]);const[n,r]=w.useState(Aoe);w.useEffect(()=>{Roe(n)},[n]);const[s,o]=w.useState(Moe);return w.useEffect(()=>{$oe(s)},[s]),qt("detail-panel",e),qt("usage-panel",n),qt("machine-panel",s),{detailOpen:e,setDetailOpen:t,usagePanelOpen:n,setUsagePanelOpen:r,machinePanelOpen:s,setMachinePanelOpen:o}}const Ooe={permission:"your permission",asked:"your answer"},Loe="No sessions are waiting for you.";function Poe(e){if(e.length===0)return"";const[t,...n]=e,r=t.waiting.kind,s=n.every(l=>l.waiting.kind===r)?Ooe[r]??"you":"you";if(n.length===0)return`${t.label} is waiting for ${s}.`;const o=`${n.length} more session${n.length===1?"":"s"}`;return`${t.label} and ${o} are waiting for ${s}.`}function Yv(e,t,n=Loe){return t!==""?t:e===""?"":n}function Doe({waitingSessions:e,watchUnseen:t,incidents:n=[]}){const[r,s]=w.useState(""),o=Poe(e);w.useEffect(()=>{s(g=>Yv(g,o))},[o]);const[l,u]=w.useState(""),d=t>0?`Browser watch has ${t} unread ${t===1?"finding":"findings"}.`:"";w.useEffect(()=>{u(g=>Yv(g,d,"Browser watch has no unread findings."))},[d]);const[h,f]=w.useState(""),p=soe(n);return w.useEffect(()=>{f(g=>Yv(g,p,aoe))},[p]),{blockedSaid:r,watchSaid:l,setWatchSaid:u,incidentSaid:h}}const Foe="/icon-192.png";function Boe({waitingSessions:e,liveSince:t,focusSession:n}){const[r,s]=w.useState(()=>typeof Notification>"u"?"denied":Notification.permission),[o,l]=w.useState(null),[u,d]=w.useState(!1),[h,f]=w.useState(!1),p=w.useRef(()=>{}),g=w.useRef(new Set),v=bn(e),k=w.useCallback(()=>{const T=!u;d(T),T&&(g.current=bu(v.current)),T&&typeof Notification<"u"&&Notification.permission==="default"&&p.current(),fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({notifications:T})}).then(A=>A.ok?A.json():null).then(A=>{var R;A!=null&&A.ok&&(d(((R=A.prefs)==null?void 0:R.notifications)===!0),f(A.notificationsVetoed===!0))}).catch(()=>{})},[u]),b=typeof Notification<"u";w.useEffect(()=>{if(!o)return;const A=setTimeout(()=>l(null),o==="blocked"?8e3:4e3);return()=>clearTimeout(A)},[o]);const x=w.useCallback(()=>{typeof Notification>"u"||Notification.requestPermission().then(T=>{const A=T;s(A),A==="granted"?l("on"):A==="denied"&&l("blocked")})},[]);p.current=x;const N=w.useRef(null),S=e.map(T=>Ld(T.id,T.waiting)).join("|");w.useEffect(()=>{qq(N.current,r)&&(N.current=r,g.current=bu(e))},[r]);const _=w.useRef(!1);w.useEffect(()=>{Uq(_.current,t)&&(_.current=!0,g.current=bu(e))},[t]),w.useEffect(()=>{if(typeof Notification>"u"||Notification.permission!=="granted"||!zq(N.current,t))return;const T=typeof document>"u"||!document.hidden,A=Fq(e,g.current,T,u);for(const R of A)try{const B=new Notification(R.title,{body:R.body,tag:R.key,icon:Foe});B.onclick=()=>{window.focus(),n(R.sessionId),B.close()}}catch{}g.current=T?bu(e):Bq(e,A,g.current)},[S,t]);const E=w.useCallback(T=>{var A;d(((A=T.prefs)==null?void 0:A.notifications)===!0),f(T.notificationsVetoed===!0)},[]);return{notifyPermission:r,notifySaid:o,notifyOn:u,notifyVetoed:h,toggleNotify:k,notifySupported:b,askForNotifications:x,loadNotifyPrefs:E}}function qoe({openedTool:e,usageHistoryOpen:t,contextFor:n,tourOpen:r,summaryFor:s,browserWatchOpen:o,keyHelpOpen:l,releaseNotes:u,feedbackOpen:d}){const h=bn(l),f=w.useRef(!1);return f.current=e!=null||t||n!=null||r||s!=null||o||l||u!=null||d,{keyHelpOpenRef:h,modalOpenRef:f}}function Uoe({stateRef:e,tourOpen:t,releaseNotes:n}){const[r,s]=w.useState(null),o=w.useCallback((D,I)=>s({agentId:D,toolId:I}),[]),[l,u]=w.useState(null),[d,h]=w.useState(null),f=w.useCallback(D=>h(D),[]),[p,g]=w.useState(!1),[v,k]=w.useState(!1),[b,x]=w.useState(!1),[N,S]=w.useState(!1),[_,E]=w.useState(null),T=w.useCallback(D=>{E(D??null),S(!0)},[]);qt("tool-detail",r!==null),qt("session-summary",l!==null),qt("context-window",d!==null),qt("keyboard-help",p),qt("usage-history",v),qt("browser-watch",b),qt("feedback",N);const A=r?BB(e.current.agents,r.agentId,r.toolId):null,R=d?e.current.agents.get(d):void 0,{keyHelpOpenRef:B,modalOpenRef:P}=qoe({openedTool:A,usageHistoryOpen:v,contextFor:d,tourOpen:t,summaryFor:l,browserWatchOpen:b,keyHelpOpen:p,releaseNotes:n,feedbackOpen:N});return{setOpenedToolKey:s,openTool:o,openedTool:A,summaryFor:l,setSummaryFor:u,contextFor:d,setContextFor:h,openContext:f,contextAgent:R,keyHelpOpen:p,setKeyHelpOpen:g,usageHistoryOpen:v,setUsageHistoryOpen:k,browserWatchOpen:b,setBrowserWatchOpen:x,feedbackOpen:N,setFeedbackOpen:S,feedbackPrefill:_,setFeedbackPrefill:E,openFeedback:T,keyHelpOpenRef:B,modalOpenRef:P}}function zoe({stateRef:e,pinnedRef:t,measuredRef:n,positionsRef:r,lastLayoutSigRef:s,forgetRemovals:o,clearSelection:l,rerender:u,modalOpenRef:d}){const[h,f]=w.useState(!1),p=w.useCallback(async()=>{try{await fetch("/api/clear",{method:"POST"})}catch{}e.current=Eb(),t.current.clear(),n.current.clear(),r.current.clear(),s.current="",zO(),o(),l(),u()},[u,l,o]),g=bn(h),v=w.useCallback(k=>{const b=fre(k,{confirmOpen:g.current,modalOpen:d.current});b==="confirm"?f(!0):b==="clear"&&(f(!1),p())},[p]);return{clearConfirmOpen:h,setClearConfirmOpen:f,requestClear:v}}const PR="agent-dag.oldNameNoticeDismissed";function Woe(e){const[t,n]=w.useState(()=>et(PR)??""),r=e!=null&&e.invokedAs&&e.invokedAs!==bt?e.invokedAs:null,s=r!=null&&t!==r,o=w.useCallback(()=>{r&&(n(r),Xe(PR,r))},[r]);return{oldName:r,oldNameOpen:s,dismissOldName:o}}function Hoe({chimesRef:e,setTonePrefs:t,tonePrefsRef:n,previewTone:r}){const[s,o]=w.useState(()=>UY(et)),l=bn(s),[u,d]=w.useState([]),h=w.useCallback(S=>{o(_=>{const E={..._,[S]:null};return l.current=E,E}),us(Tl[S])},[]),f=w.useCallback((S,_)=>{const E=l.current[S];_&&E!==_||(h(S),t(T=>{const A={...T,[S]:{...T[S],figure:xn}};return n.current=A,A}),Xe(vd[S],xn))},[h]),p=bn(f);w.useEffect(()=>{let S=!0;return QY().then(_=>{if(!S)return;d(_);const E=new Set(_.map(T=>T.id));for(const T of Hu){const A=l.current[T];A&&!E.has(A)&&p.current(T,A)}},()=>{}),()=>{S=!1}},[]);const g=w.useCallback((S,_)=>{if(!u.some(T=>T.id===_))return;const E={...l.current,[S]:_};l.current=E,o(E),Xe(Tl[S],_),r(S,!0)},[u,r]),v=w.useCallback(async S=>{var A;const _=Wx(u.length);if(_)throw new Error(_);const E=window.AudioContext??window.webkitAudioContext;if(!E)throw new Error("This browser cannot decode audio files.");const T=new E;try{const R=await KY(S,P=>T.decodeAudioData(P));await MS(R);const B=Kl(R);d(P=>[...P.filter(D=>D.id!==B.id),B])}finally{(A=T.close)==null||A.call(T)}},[u.length]),k=w.useCallback(async S=>{const _=Wx(u.length);if(_)throw new Error(_);const E=YY(S);await MS(E);const T=Kl(E);d(A=>[...A.filter(R=>R.id!==T.id),T])},[u.length]),b=w.useCallback(async(S,_)=>{const E=u.find(A=>A.id===S),T=_.trim().slice(0,80);!E||!T||T===E.name||(await tX(S,T),d(A=>A.map(R=>R.id===S?{...R,name:T}:R)))},[u]),x=w.useCallback(async S=>{await nX(S),d(A=>A.filter(R=>R.id!==S));const _=l.current,E=zY(_,S);l.current=E,o(E);const T=Hu.filter(A=>_[A]===S);for(const A of T)us(Tl[A]),Xe(vd[A],xn);T.length>0&&t(A=>{const R={...A};for(const B of T)R[B]={...R[B],figure:xn};return n.current=R,R})},[]),N=w.useCallback(S=>{var T,A;const _=Hu.find(R=>l.current[R]===S),E=_?n.current[_].level:Ua;(T=e.current)==null||T.unlock(),(A=e.current)==null||A.previewCustom(S,E)},[]);return{customSelections:s,customSelectionsRef:l,customAssets:u,clearCustomOnly:h,fallbackCustomRef:p,selectCustomTone:g,importNotificationAudio:v,createNotificationVoice:k,renameCustomAsset:b,deleteCustomAsset:x,previewCustomAsset:N}}function Voe(e){const[t,n]=w.useState(()=>pX(et)),r=bn(t),s=w.useRef(null),o=w.useCallback((u,d=!1)=>{var h,f;if((h=e.current)==null||h.unlock(),s.current!==null&&clearTimeout(s.current),s.current=null,!d){(f=e.current)==null||f.play(u,!0);return}s.current=setTimeout(()=>{var p;s.current=null,(p=e.current)==null||p.play(u,!0)},cX)},[]),l=w.useCallback((u,d)=>{n(h=>{const f={level:fi(d.level??h[u].level),figure:EI(u,d.figure??h[u].figure)};return Xe(AI[u],String(f.level)),Xe(vd[u],f.figure),{...h,[u]:f}}),o(u,!0)},[o]);return w.useEffect(()=>()=>{s.current!==null&&clearTimeout(s.current)},[]),{tonePrefs:t,setTonePrefs:n,tonePrefsRef:r,previewTone:o,changeTone:l}}const Goe=15e3;function Koe(e){return e.visibilityState==="visible"&&e.hasFocus()}function Yoe(e,t){return t||e===!0}function Xoe(){try{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")return crypto.randomUUID()}catch{}return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}function Zoe(){w.useEffect(()=>{const e=Xoe();let t=null;const n=l=>{fetch("/api/presence",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tab:e,looking:l}),keepalive:!0}).catch(()=>{})},r=()=>{const l=Koe(document);Yoe(t,l)&&n(l),t=l},s=()=>{t&&n(!1),t=!1};r();const o=window.setInterval(r,Goe);return window.addEventListener("focus",r),window.addEventListener("blur",r),document.addEventListener("visibilitychange",r),window.addEventListener("pagehide",s),()=>{window.clearInterval(o),window.removeEventListener("focus",r),window.removeEventListener("blur",r),document.removeEventListener("visibilitychange",r),window.removeEventListener("pagehide",s),s()}},[])}function Joe({loadAutoRestartPrefs:e,loadNotifyPrefs:t,loadReportsPrefs:n}){w.useEffect(()=>{let r=!0;return fetch("/api/prefs").then(s=>s.ok?s.json():null).then(s=>{!r||!(s!=null&&s.ok)||(e(s),t(s),n(s))}).catch(()=>{}),()=>{r=!1}},[])}const Xv="agent-dag.versionNoticeDismissed",Qoe=15*6e4;function eie(e){const[t,n]=w.useState(null),[r,s]=w.useState(()=>et(Xv)??""),o=w.useRef(0),[l,u]=w.useState(!1),d=w.useCallback((b=!1)=>(b&&(o.current=Date.now(),u(!0)),fetch(b?"/api/version?refresh=1":"/api/version").then(x=>x.ok?x.json():null).then(x=>{x&&n(x)}).catch(()=>{}).finally(()=>{b&&u(!1)})),[]),h=w.useCallback(()=>{d(Date.now()-o.current>=Qoe)},[d]);w.useEffect(()=>{d();const b=window.setInterval(h,5*6e4),x=()=>{document.visibilityState==="visible"&&h()};return document.addEventListener("visibilitychange",x),()=>{window.clearInterval(b),document.removeEventListener("visibilitychange",x)}},[d,h]),w.useEffect(()=>{e&&d()},[e,d]);const f=(t==null?void 0:t.notice)??null,p=JO(f),g=qne(f,r),v=w.useCallback(()=>{s(""),Xe(Xv,"")},[]),k=w.useCallback(()=>{f&&(s(p),Xe(Xv,p))},[f,p]);return{version:t,notice:f,noticeOpen:g,showNotice:v,dismissNotice:k,versionChecking:l,loadVersion:d}}function tie({version:e,readyAppUpdate:t}){const[n,r]=w.useState(!1),s=w.useRef(null),[o,l]=w.useState(null),u=o&&t?t.version:null,d=w.useRef(null);w.useEffect(()=>{!u||d.current===u||(d.current=u,fetch("/api/desktop-update/seen",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({version:u})}).catch(()=>{}))},[u]);const h=w.useRef(!1);w.useEffect(()=>{if(h.current)return;const x=El(),N=aae(x),S=dae({stored:N,running:(e==null?void 0:e.running)??null,notes:_R,remembers:oae(x)});if(S.reason==="no-version")return;h.current=!0,S.record&&iae(x,S.record);let _=!0;return(async()=>{var B;let T=lae(x);if(Zl())try{const P=await fetch("/api/prefs");if(!P.ok)throw new Error("desktop tour preferences unavailable");const D=await P.json();if(!(D!=null&&D.ok))throw new Error("desktop tour preferences unavailable");const I=((B=D.prefs)==null?void 0:B.tourSeen)===!0;T&&!I&&fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tourSeen:!0}),keepalive:!0}).catch(()=>{}),T=T||I}catch{T=!0}if(!_)return;const A=uae({tourSeen:T,decision:S}),R=S.show.length?{entries:S.show,since:N,firstRun:!1}:null;A.tour&&r(!0),A.notes==="now"?l(R):A.notes==="after"&&(s.current=R)})(),()=>{_=!1}},[e==null?void 0:e.running]);const f=(e==null?void 0:e.running)??"3.36.9",p=w.useMemo(()=>cL(_R,null,f),[f]),g=w.useCallback(()=>{l({entries:p,since:null,firstRun:!1})},[p]),v=w.useCallback(()=>r(!0),[]),k=w.useCallback(()=>l(null),[]),b=w.useCallback(()=>{r(!1),cae(El()),Zl()&&fetch("/api/prefs",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({tourSeen:!0}),keepalive:!0}).catch(()=>{});const x=s.current;s.current=null,x&&l(x)},[]);return{tourOpen:n,openTour:v,closeTour:b,releaseNotes:o,closeReleaseNotes:k,chipVersion:f,openReleaseNotes:g}}function nie(){const[e,t]=w.useState(void 0),[n,r]=w.useState(!1),s=w.useRef(!1);return s.current=e===!0&&!n,w.useEffect(()=>Iae(()=>s.current),[]),{loadReportsPrefs:w.useCallback(l=>{var u;t(((u=l.prefs)==null?void 0:u.reports)!==!1),r(l.reportsVetoed===!0)},[])}}function rie(){const[e,t]=w.useState(null),[n,r]=w.useState(!1),s=w.useRef(null);return w.useEffect(()=>{let o=!0,l=!1,u=null,d=null,h="";const f=b=>{o&&(r(!0),b!==h&&t(null))},p=()=>{if(l)return;l=!0,u=Date.now();const b=fR();d=b,fetch(`/api/ccusage?since=${b}`).then(x=>x.ok?x.json():null).then(x=>{if(o){if(!(x!=null&&x.ok)){f(b);return}h=b,t(Tne(x)),r(!1)}}).catch(()=>f(b)).finally(()=>{l=!1})},g=()=>{const b=s.current;Cne({shown:!!b&&b.getClientRects().length>0,tabVisible:document.visibilityState==="visible",lastSince:d,since:fR(),lastReadAt:u,now:Date.now()})&&p()};g();const v=window.setInterval(g,Ene);document.addEventListener("visibilitychange",g);let k=null;return s.current&&typeof ResizeObserver<"u"&&(k=new ResizeObserver(g),k.observe(s.current)),()=>{o=!1,window.clearInterval(v),document.removeEventListener("visibilitychange",g),k==null||k.disconnect()}},[]),{monthlyUsage:e,monthlyUsageUnavailable:n,monthUsageRef:s}}function sie(e){const[t,n]=w.useState(null);w.useEffect(()=>{const u=et("agent-dag.sound");n(u===null?!0:u==="on")},[]);const r=w.useCallback(()=>{n(u=>{var h;const d=u!==!0;return Xe("agent-dag.sound",d?"on":"off"),d&&((h=e.current)==null||h.unlock()),d})},[]),s=w.useCallback(u=>{r()},[r]),o=bn(s),l=bn(t);return{soundOn:t,toggleSound:r,activateSoundRef:o,soundOnRef:l}}function aie(){const[e,t]=w.useState(!1),n=w.useRef(null),[r,s]=w.useState(!1),o=w.useRef(null);return w.useEffect(()=>{e&&s(!1)},[e]),{soundMenuOpen:e,setSoundMenuOpen:t,soundButtonRef:n,appearanceMenuOpen:r,setAppearanceMenuOpen:s,appearanceButtonRef:o}}const oie="agent-dag.autoFitDisabled";function iie(e){const t=w.useRef(!1),[n,r]=w.useState(!1);w.useEffect(()=>{us(oie)},[]);const s=w.useCallback(()=>{t.current||(t.current=!0,r(!0))},[]),o=w.useCallback(()=>{t.current=!1,r(!1),e(400)},[e]);return{autoFitDisabled:n,autoFitDisabledRef:t,disableAutoFit:s,enableAutoFitAndRefit:o}}function lie(){return a.jsx(jb,{children:a.jsx(Pae,{children:a.jsx(cie,{})})})}function cie(){const e=gi(),t=w.useState(Eb)[0],n=w.useRef(t),[,r]=w.useState(0),s=w.useCallback(()=>r(_r=>_r+1),[]),{detailOpen:o,setDetailOpen:l,usagePanelOpen:u,setUsagePanelOpen:d,machinePanelOpen:h,setMachinePanelOpen:f}=Ioe(),{selectedIds:p,primarySelectedId:g,selectAgent:v,clearSelection:k,pruneSelectionToBoard:b}=MX(n,l),{sessionListOpen:x,accountsPanelOpen:N,toggleSessionList:S,toggleAccountsPanel:_,closeSessionList:E,closeAccountsPanel:T}=Eoe(),A=rie(),R=qu(N,200),B=qu(u,130),P=qu(h,130),D=w.useRef(null),I=sie(D),{activateSoundRef:O,soundOnRef:z}=I,M=Voe(D),{setTonePrefs:$,tonePrefsRef:q,previewTone:X}=M,H=Hoe({chimesRef:D,setTonePrefs:$,tonePrefsRef:q,previewTone:X}),{customSelectionsRef:L,fallbackCustomRef:W}=H,F=aie(),{setSoundMenuOpen:Y}=F,{chimeState:J}=yoe({chimesRef:D,soundOnRef:z,tonePrefsRef:q,customSelectionsRef:L,fallbackCustomRef:W}),V=Bae(n),{pauseGate:G,paused:K,togglePause:Z}=V,ee=w.useRef(()=>{}),{live:re,tabCapped:ue,everConnected:me,liveSince:ie}=kX({stateRef:n,rerender:s,pauseGate:G,chimesRef:D,desktopUpdateRef:ee}),fe=eie(re),{version:ve,notice:se,noticeOpen:ke,loadVersion:pe}=fe,he=voe(re),{readyAppUpdate:ae,onDesktopUpdateEvent:Ne}=he;ee.current=Ne,Zoe();const Te=tie({version:ve,readyAppUpdate:ae}),{tourOpen:Ie,openTour:Oe,releaseNotes:He}=Te,{workspace:Ye,providers:Ve,providersRef:Ke}=Uae(re),nt=Woe(ve),it=ooe({version:ve,loadVersion:pe}),{upgradeFailure:At}=it,Be=Uoe({stateRef:n,tourOpen:Ie,releaseNotes:He}),{openTool:Ze,setSummaryFor:Je,setContextFor:rt,openContext:gt,setKeyHelpOpen:Rt,setUsageHistoryOpen:yt,setBrowserWatchOpen:lt,keyHelpOpenRef:en,modalOpenRef:Pt}=Be,zt=nie(),Dt=loe(),{watchOn:Yt,watchUnseen:Ur}=Dt,zn=boe(),[zr,Wr]=w.useState(0),{presentCats:Wn,hiddenCats:gr,toggleCat:Hn,catBarRef:Hr,catBarOccluded:Nn}=moe(n),gs=w.useRef(null),[jn,yr]=w.useState(0),vt=$X({stateRef:n,rerender:s,pruneSelectionToBoard:b,setContextFor:rt,setSummaryFor:Je}),wr=Vae(Ve.claude||Ve.codex),vr=roe(wr,vt),Vr=Noe({enabled:Ve.claude,lanStatus:zn.lanStatus,now:vt}),Vn=xoe({now:vt,stateRef:n,version:ve,notice:se,noticeOpen:ke,upgradeFailure:At,paused:V.paused}),{askRestart:pn,loadAutoRestartPrefs:Gr}=Vn,Sn=poe(),{setTheme:we,palette:De}=Sn,qe=woe(),tn=Aee(),{applyViewport:ys,moveCamera:xr,fitLeft:Gn,cameraEpochRef:Qe,lastFitTimeRef:br}=tn,Kn=Uee(Gn),{pinnedRef:Kr,positionsRef:Yn,lastLayoutSigRef:Xa,handleRelayout:Za}=Kn,Yr=w.useRef(null),Xt=w.useRef(()=>{}),sa=ie!==null,nn=w.useRef(0),hc=w.useCallback(()=>{nn.current=Date.now()},[]),{canvasRef:Xr,canvasSize:wh,paneSizeRef:Ja}=Nte(),aa=iie(Gn),{autoFitDisabledRef:ws,disableAutoFit:vs}=aa,_i=w.useRef(!1),{measuredRef:Zr,measuredVersionRef:Ei,sizeVersion:fc,domSizeVersion:Ci}=jte(_i),{removedNodes:xs,removedAgentIds:bs,removeNode:Ti,removeSelectedNode:ze,removalNotice:Ai,bringBack:Qa,bringBackAll:Ri,forgetRemovals:pc}=fte({stateRef:n,pinnedRef:Kr,positionsRef:Yn,canvasRef:Xr,clearSelection:k,primarySelectedId:g}),eo=lte({stateRef:n,now:vt,removedAgentIds:bs,sizeVersion:fc,domSizeVersion:Ci});zee(eo,Kn),hte({layoutSig:eo,rf:e,stateRef:n,measuredRef:Zr,pinnedRef:Kr,positionsRef:Yn,paneSizeRef:Ja,lastInteractRef:nn,autoFitDisabledRef:ws,lastFitTimeRef:br,fitLeft:Gn});const Mi=Ste(),{bubbling:$i,endBubble:to,onBubble:kr}=_Y(),[_n,Nr]=w.useState(!1),ks=jY(),{beginTrashDrag:oa,trackTrashDrag:jr,endTrashDrag:vh}=ks,Ii=OY({stateRef:n,measuredRef:Zr,measuredVersionRef:Ei,canvasRef:Xr}),{lodRef:ia,applyZoom:xh}=Ii,Oi=bte({applyViewport:ys,lastFitTimeRef:br,cameraEpochRef:Qe,disableAutoFit:vs,markInteract:hc,canvasRef:Xr,applyZoom:xh}),Sr=g?n.current.agents.get(g):null,Li=o&&Sr!=null,{railInsetRef:mc,availableWidth:Ns,availableHeight:js}=Tee({canvasRef:Xr,canvasSize:wh,machinePhase:P,usagePhase:B,usagePanelOpen:u,detailShown:Li}),Jr=cte({stateRef:n,now:vt,availableWidth:Ns,availableHeight:js,layout:Kn,measuredRef:Zr,onBubble:kr,settled:Mi,dragging:_n,layoutSig:eo,selectedIds:p,openContext:gt,historyReplayed:sa,removedNodes:xs,removedAgentIds:bs,dragTick:zr,dragPatchRef:gs,dragMoveTick:jn}),{nodes:Ss,edges:bh}=Jr,Pi=bn(g);Wee({nodes:Ss,edges:bh,settled:Mi,dragging:_n,availableWidth:Ns,availableHeight:js,layout:Kn,camera:tn,rf:e,measuredRef:Zr,stateRef:n,lastFocusRef:Yr,primarySelectedIdRef:Pi,focusAgentRef:Xt,autoFitDisabledRef:ws});const no=zoe({stateRef:n,pinnedRef:Kr,measuredRef:Zr,positionsRef:Yn,lastLayoutSigRef:Xa,forgetRemovals:pc,clearSelection:k,rerender:s,modalOpenRef:Pt}),{requestClear:la}=no,Di=Cte({nodes:Ss,stateRef:n,pinnedRef:Kr,positionsRef:Yn,draggingRef:_i,dragPatchRef:gs,setDragging:Nr,setDragMoveTick:yr,setDragTick:Wr,endBubble:to,markInteract:hc,disableAutoFit:vs,beginTrashDrag:oa,trackTrashDrag:jr,endTrashDrag:vh,removeNode:Ti}),gc=w.useCallback(()=>Gn(500),[Gn]),En=bn(Ss),{focusAgent:_s,stepAgent:ro,focusSession:Es}=Mee({canvasRef:Xr,stateRef:n,measuredRef:Zr,nodesRef:En,railInsetRef:mc,moveCamera:xr,disableAutoFit:vs,lastFocusRef:Yr,focusAgentRef:Xt,selectAgent:v,primarySelectedIdRef:Pi}),so=kte({clearSelection:k,selectAgent:v,detailOpen:o,setDetailOpen:l,detailShown:Li,focusAgent:_s,draggingRef:_i,lodRef:ia}),ao=$ee({nodesRef:En,stateRef:n,canvasRef:Xr,railInsetRef:mc}),yc=bn(ze),Wt=w.useRef(null),Cs=w.useCallback(_r=>{bs.has(_r)&&Qa([_r]),Es(_r)},[bs,Qa,Es]),Fi=_te();Ete({pointerFocusRef:Fi,nodesRef:En,stateRef:n,primarySelectedIdRef:Pi,providersRef:Ke,soundOnRef:z,keyHelpOpenRef:en,modalOpenRef:Pt,waitingCursorRef:Wt,removeSelectedRef:yc,activateSoundRef:O,clearSelection:k,selectAgent:v,focusAgent:_s,stepAgent:ro,focusSession:Es,requestClear:la,handleRelayout:Za,handleFit:gc,togglePause:Z,toggleSessionList:S,toggleAccountsPanel:_,setDetailOpen:l,setUsageHistoryOpen:yt,setUsagePanelOpen:d,setMachinePanelOpen:f,setBrowserWatchOpen:lt,setSoundMenuOpen:Y,setKeyHelpOpen:Rt,setTheme:we});const Ts=n.current.agents.size,ca=w.useMemo(()=>mx(n.current.agents.values()),[n.current,n.current.revision]);pte({stateRef:n,waitingSessions:ca,removedAgentIds:bs,removedNodes:xs,bringBack:Qa}),wte({stateRef:n,waitingSessions:ca,live:re});const oo=Doe({waitingSessions:ca,watchUnseen:Ur,incidents:vr}),ua=Boe({waitingSessions:ca,liveSince:ie,focusSession:Es}),{loadNotifyPrefs:da}=ua;return Joe({loadAutoRestartPrefs:Gr,loadNotifyPrefs:da,loadReportsPrefs:zt.loadReportsPrefs}),a.jsxs("div",{className:"app",children:[a.jsx("a",{className:"skip-link",href:"#canvas",children:"Skip to the canvas"}),a.jsxs("header",{className:"topbar",children:[a.jsx(Gne,{versionCheck:fe,welcome:Te,desktopUpdate:he,pause:V,monthly:A,announcements:oo,notify:ua,waitingSessions:ca,waitingCursorRef:Wt,focusSession:Es,live:re,now:vt,incidents:vr}),Sr&&a.jsx(Kne,{selected:Sr,now:vt,selectedIds:p,focusAgent:_s,clearSelection:k}),a.jsxs("div",{className:"actions",children:[a.jsx(Nne,{sessionListOpen:x,toggleSessionList:S,usagePanelOpen:u,setUsagePanelOpen:d,setUsageHistoryOpen:yt}),a.jsx(jne,{providers:Ve,accountsPanelOpen:N,toggleAccountsPanel:_,machinePanelOpen:h,setMachinePanelOpen:f,watchOn:Yt,watchUnseen:Ur,setBrowserWatchOpen:lt}),a.jsx(Sne,{providers:Ve,sound:I,tones:M,customTones:H,notify:ua,chimeState:J,menus:F,appearance:Sn,fm:qe,onFeedback:()=>Be.openFeedback()})]})]}),a.jsx("div",{className:"vis-hidden",role:"status","aria-atomic":"true",children:Ai?`${Ai.label} removed from the board.`:""}),a.jsx(hre,{restart:Vn,versionCheck:fe,upgrade:it,oldNameNotice:nt,everConnected:me,live:re,paused:K}),Bu(R)&&Ve.claude&&a.jsx(yY,{leaving:R==="leaving",onClose:T,onReport:Be.openFeedback,onRoster:Vr.observe}),x&&a.jsx(SW,{state:n.current,now:vt,selectedIds:p,onSelect:Cs,onClose:E,removedIds:bs,onBringBackAll:Ri}),a.jsxs(Qne,{canvasRef:Xr,bubbling:$i,dragging:_n,trash:ks,zoom:Ii,viewport:Oi,stateRef:n,peek:ao,children:[Ts===0&&(!re&&ue?a.jsx(Lte,{}):a.jsx(Pte,{live:re,everConnected:me,providers:Ve,workspace:Ye,onTour:Oe})),(Wn.length>1||gr.size>0)&&a.jsx(Yne,{catBarRef:Hr,catBarOccluded:Nn,presentCats:Wn,hiddenCats:gr,toggleCat:Hn}),a.jsx(yW,{graph:Jr,layout:Kn,viewport:Oi,clicks:so,drag:Di,appearance:Sn,fm:qe,autoFit:aa,pause:V,stateRef:n,measuredRef:Zr,hiddenCats:gr,now:vt,openTool:Ze,focusAgent:_s,requestClear:la,setKeyHelpOpen:Rt})]}),Bu(B)&&a.jsx(vH,{state:n.current,now:vt,providers:Ve,incidents:vr,liveSince:ie,leaving:B==="leaving",onClose:()=>d(!1)}),Bu(P)&&a.jsx(n7,{usageOpen:u,leaving:P==="leaving",onClose:()=>f(!1)}),o&&Sr?a.jsx(Xte,{selected:Sr,now:vt,openTool:Ze,setSummaryFor:Je,setDetailOpen:l,stateRef:n,removeSelectedNode:ze}):null,a.jsx(Aae,{dialogs:Be,welcome:Te,desktopUpdate:he,versionCheck:fe,restart:Vn,lanPairs:zn,attention:Vr,clearFlow:no,watchBadge:Dt,announcements:oo,appearance:Sn,providers:Ve,stateRef:n,agentCount:Ts})]})}try{k8(window.localStorage)}catch{}const uie=iP.createRoot(document.getElementById("root"));uie.render(a.jsx(lie,{}));export{Rte as a,ot as b,Qs as c,yd as d,fie as e,Fe as f,Or as g,hie as h,Vl as i,a as j,Dr as k,VY as l,_a as m,Ha as n,Ex as p,w as r,lr as s,ft as u};
|
package/dist/web/index.html
CHANGED
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
stored === "light" || (stored !== "dark" && light) ? "light" : "dark");
|
|
65
65
|
})();
|
|
66
66
|
</script>
|
|
67
|
-
<script type="module" crossorigin src="/assets/index-
|
|
67
|
+
<script type="module" crossorigin src="/assets/index-tVBMXvDv.js"></script>
|
|
68
68
|
<link rel="stylesheet" crossorigin href="/assets/index-BkXTN4dZ.css">
|
|
69
69
|
</head>
|
|
70
70
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccdeck",
|
|
3
|
-
"version": "3.36.
|
|
3
|
+
"version": "3.36.9",
|
|
4
4
|
"description": "Local dashboard for Claude Code and Codex CLI sessions: which Claude Code session needs input, every tool call and Claude Code subagent live, token cost and quota, several Claude accounts. Run npx ccdeck.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/release-notes.json
CHANGED
package/src/server/reports.mjs
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
// and which of the deck's features were used that day, as fixed names
|
|
18
18
|
// ("usage-history", "account-switch") and nothing about what was done
|
|
19
19
|
// with them (feature-use.mjs),
|
|
20
|
+
// - an "uninstall" event when somebody runs `ccdeck --uninstall`, with the
|
|
21
|
+
// reason they picked from a short list if they picked one (bin/cli/leaving.js),
|
|
20
22
|
// - an "activated" event, once, when a new install's first session arrives:
|
|
21
23
|
// how long after the install it came, as a bucket ("5m", "1h", "1d", "7d",
|
|
22
24
|
// "later"), and whether it was a Claude or a Codex session (activation.mjs),
|
|
@@ -92,6 +94,10 @@ const SAME_ERROR_QUIET_MS = 60 * 60 * 1000;
|
|
|
92
94
|
const MESSAGE_MAX = 500;
|
|
93
95
|
const STACK_MAX = 4000;
|
|
94
96
|
|
|
97
|
+
/** Why somebody uninstalled, when they said: the answers `ccdeck --uninstall`
|
|
98
|
+
* offers (bin/cli/leaving.js), and nothing else ever leaves as a reason. */
|
|
99
|
+
export const UNINSTALL_REASONS = Object.freeze(["not-useful", "too-noisy", "broken", "other-tool", "privacy", "other"]);
|
|
100
|
+
|
|
95
101
|
const EMPTY_REPORT = Object.freeze({
|
|
96
102
|
installId: "", lastVersion: "", lastActiveDay: "", forget: "", usage: null,
|
|
97
103
|
installedAt: "", firstSessionAt: "", firstProvider: "", activationSent: false,
|
|
@@ -543,6 +549,33 @@ export function createReporter({
|
|
|
543
549
|
if (await call("POST", "/v1/app/events", body)) await remember(r.installId, { activationSent: true });
|
|
544
550
|
}
|
|
545
551
|
|
|
552
|
+
/** Whether a report would go out right now: reports on, the machine not
|
|
553
|
+
* vetoing them, and an install id to send under. `ccdeck --uninstall` asks
|
|
554
|
+
* before it asks the person anything. Never throws. */
|
|
555
|
+
async function willReport() {
|
|
556
|
+
try {
|
|
557
|
+
await Promise.resolve(ready).catch(() => {});
|
|
558
|
+
const p = prefs.current();
|
|
559
|
+
return reportsOn(p, env) && Boolean(p.report.installId);
|
|
560
|
+
} catch {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** The install leaving: `ccdeck --uninstall`, with the reason picked off
|
|
566
|
+
* UNINSTALL_REASONS if one was. The id stays: a deck run again later is the
|
|
567
|
+
* same install coming back. Resolves to whether it got through; never throws. */
|
|
568
|
+
async function reportUninstall(reason) {
|
|
569
|
+
try {
|
|
570
|
+
if (!(await willReport())) return false;
|
|
571
|
+
const body = { installId: prefs.current().report.installId, kind: "uninstall", ...factsNow() };
|
|
572
|
+
addIf(body, "reason", UNINSTALL_REASONS.includes(reason) ? reason : undefined);
|
|
573
|
+
return await call("POST", "/v1/app/events", body);
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
546
579
|
/** The first session this run heard, sent the moment it lands rather than at
|
|
547
580
|
* the next check-in, hours later or on a next launch that may never come. */
|
|
548
581
|
async function onFirstSession() {
|
|
@@ -643,7 +676,7 @@ export function createReporter({
|
|
|
643
676
|
pingTimer = null;
|
|
644
677
|
}
|
|
645
678
|
|
|
646
|
-
return { checkIn, ping, setReports, reportError, restoreUsage, saveUsage, sendActivation, start, stop };
|
|
679
|
+
return { checkIn, ping, setReports, reportError, restoreUsage, saveUsage, sendActivation, willReport, reportUninstall, start, stop };
|
|
647
680
|
}
|
|
648
681
|
|
|
649
682
|
// ── the deck's own providers ─────────────────────────────────────────────────
|