pi-mega-compact 0.8.10 → 0.8.12

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.
@@ -0,0 +1,2 @@
1
+ import{j as s}from"./index-XAIN8km6.js";function n(){return s.jsxs("div",{className:"tab-stub",children:[s.jsx("strong",{children:"Config"})," (B1 stub)"]})}export{n as default};
2
+ //# sourceMappingURL=ConfigTab-RrjXWfvr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigTab-RrjXWfvr.js","sources":["../../src/tabs/ConfigTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (B1 stub).\n *\n * SPRINT-C3: config form, game-mode settings, theme picker.\n */\n\nimport type React from \"react\";\n\nexport default function ConfigTab(): React.ReactElement {\n\treturn (\n\t\t<div className=\"tab-stub\">\n\t\t\t<strong>Config</strong> (B1 stub)\n\t\t\t{/* SPRINT-C3-REMAINING: /mega-compact-settings form, theme picker */}\n\t\t</div>\n\t);\n}\n"],"names":["ConfigTab","jsxs","jsx"],"mappings":"wCAQA,SAAwBA,GAAgC,CACvD,OACCC,EAAAA,KAAC,MAAA,CAAI,UAAU,WACd,SAAA,CAAAC,EAAAA,IAAC,UAAO,SAAA,QAAA,CAAM,EAAS,YAAA,EAExB,CAEF"}
@@ -0,0 +1,2 @@
1
+ import{r as c,j as s}from"./index-XAIN8km6.js";function k(e={}){const{maxEvents:n=500,maxReconnects:a=5}=e,[o,i]=c.useState([]),[u,t]=c.useState("connecting"),[r,m]=c.useState(0),[$,f]=c.useState(null),p=c.useRef(0),d=c.useRef(null),g=c.useCallback(()=>{d.current&&d.current.close(),t("connecting");const l=new EventSource("/api/events");d.current=l,l.onopen=()=>{t("connected"),p.current=0},l.onmessage=x=>{try{const j=JSON.parse(x.data);i(_=>{const h=[..._,j];return h.length>n?h.slice(-n):h}),m(_=>_+1),f(Date.now())}catch{}},l.onerror=()=>{l.close(),p.current<a?(p.current+=1,t("connecting"),setTimeout(g,1e3)):t("disconnected")}},[n,a]);return c.useEffect(()=>(g(),()=>{d.current?.close()}),[g]),{events:o,status:u,eventCount:r,lastEventAt:$}}const N={compact_start:"ev-compact",compact_end:"ev-compact",compact_trigger:"ev-trigger",compact_skip:"ev-trigger",tier_changed:"ev-tier",model_changed:"ev-tier",pressure_lifted:"ev-tier",checkpoint_persisted:"ev-checkpoint",recall_inject:"ev-recall",anchors_updated:"ev-recall",config_updated:"ev-config",config_preset:"ev-config",crew_presence_changed:"ev-crew",crew_turn_changed:"ev-crew",crew_bandit_chosen:"ev-crew",game_ritual_start:"ev-game",game_ritual_stage:"ev-game",game_ritual_end:"ev-game",game_mode_changed:"ev-game",game_render:"ev-game"},v=["all","compact_start","compact_end","recall_inject","checkpoint_persisted"];function y(e){switch(e.type){case"compact_start":return`trigger=${e.trigger} session=${e.sessionId.slice(0,8)}`;case"compact_end":return`freed=${e.tokensFreed} ok=${e.success} cp=${e.checkpointId.slice(0,8)}`;case"compact_trigger":return`pressure=${e.pressure}% threshold=${e.threshold}% armed=${e.armed}`;case"compact_skip":return`reason=${e.reason}`;case"tier_changed":return`${e.from} → ${e.to} ctx=${e.contextPct}%`;case"model_changed":return`${e.providerName}/${e.model}`;case"pressure_lifted":return`${e.beforePct}% → ${e.afterPct}%`;case"checkpoint_persisted":return`cp=${e.checkpointId.slice(0,8)} sessionTok=${e.sessionTokens}`;case"recall_inject":return`q="${e.query.slice(0,40)}" chunks=${e.chunks} tok=${e.tokens}`;case"anchors_updated":return`count=${e.count} pinned=${e.pinned}`;case"config_updated":return`key=${e.key}`;case"config_preset":return`preset=${e.preset}`;case"crew_presence_changed":return`agents=${e.activeAgents} turn=${e.currentTurn}`;case"crew_turn_changed":return`turn=${e.turnIndex} agent=${e.agentName}`;case"crew_bandit_chosen":return`agent=${e.chosenAgent} score=${e.score} regret=${e.regret}`;case"game_ritual_start":case"game_ritual_stage":case"game_ritual_end":case"game_mode_changed":case"game_render":return`stage=${e.type}`}}function E(e){try{return new Date(e).toLocaleTimeString()}catch{return e}}function S({events:e}){const[n,a]=c.useState("all"),[o,i]=c.useState(null),u=c.useMemo(()=>(n==="all"?e:e.filter(r=>r.type===n)).slice(-200).reverse(),[e,n]);return s.jsxs("div",{className:"event-stream",children:[s.jsxs("div",{className:"event-filters",role:"toolbar",children:[v.map(t=>s.jsx("button",{type:"button",className:`filter-chip ${n===t?"active":""}`,onClick:()=>a(t),children:t==="all"?"all":t.replace(/_/g," ")},t)),s.jsxs("span",{className:"event-count",children:[e.length," buffered"]})]}),s.jsxs("ul",{className:"event-list",children:[u.length===0&&s.jsx("li",{className:"event-empty",children:"No events yet."}),u.map((t,r)=>{const m=N[t.type]??"ev-default";return s.jsxs("li",{className:`event-row ${m}`,onClick:()=>i(o===r?null:r),children:[s.jsx("span",{className:"ev-time",children:E(t.ts)}),s.jsx("span",{className:"ev-type",children:t.type}),s.jsx("span",{className:"ev-summary",children:y(t)}),o===r&&s.jsx("pre",{className:"ev-detail",children:JSON.stringify(t,null,2)})]},`${t.ts}-${r}`)})]})]})}function b(){const{events:e,status:n,eventCount:a}=k(),o=`sse-status sse-${n}`,i=n==="connected"?"connected":n==="connecting"?"connecting…":n==="error"?"error":"disconnected";return s.jsxs("div",{className:"events-tab",children:[s.jsxs("div",{className:"events-header",children:[s.jsxs("span",{className:o,role:"status",children:[s.jsx("span",{className:"sse-dot","aria-hidden":"true"}),i]}),s.jsxs("span",{className:"event-count",children:[a," events received"]})]}),s.jsx(S,{events:e})]})}export{b as default};
2
+ //# sourceMappingURL=EventsTab-BH_FxJ08.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventsTab-BH_FxJ08.js","sources":["../../src/hooks/useSSE.ts","../../src/components/EventStream.tsx","../../src/tabs/EventsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n","/**\n * dashboard-client/src/components/EventStream.tsx — SSE event list with filters.\n *\n * Type badge colored by event type. Timestamp + expandable detail row.\n * Type filter chips (all + per-type). Virtualization: manual windowing for\n * 500+ events (render last N visible).\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { SseEvent } from \"@contracts\";\n\nexport interface EventStreamProps {\n\tevents: SseEvent[];\n}\n\nconst TYPE_COLORS: Record<string, string> = {\n\tcompact_start: \"ev-compact\",\n\tcompact_end: \"ev-compact\",\n\tcompact_trigger: \"ev-trigger\",\n\tcompact_skip: \"ev-trigger\",\n\ttier_changed: \"ev-tier\",\n\tmodel_changed: \"ev-tier\",\n\tpressure_lifted: \"ev-tier\",\n\tcheckpoint_persisted: \"ev-checkpoint\",\n\trecall_inject: \"ev-recall\",\n\tanchors_updated: \"ev-recall\",\n\tconfig_updated: \"ev-config\",\n\tconfig_preset: \"ev-config\",\n\tcrew_presence_changed: \"ev-crew\",\n\tcrew_turn_changed: \"ev-crew\",\n\tcrew_bandit_chosen: \"ev-crew\",\n\tgame_ritual_start: \"ev-game\",\n\tgame_ritual_stage: \"ev-game\",\n\tgame_ritual_end: \"ev-game\",\n\tgame_mode_changed: \"ev-game\",\n\tgame_render: \"ev-game\",\n};\n\n// Filterable event types (spec: compact_start/end, recall_inject, checkpoint_persisted + all).\nconst FILTER_TYPES = [\n\t\"all\",\n\t\"compact_start\",\n\t\"compact_end\",\n\t\"recall_inject\",\n\t\"checkpoint_persisted\",\n] as const;\ntype FilterType = (typeof FILTER_TYPES)[number];\n\nconst RENDER_WINDOW = 200; // render last 200 to keep DOM light under 500+ events\n\nfunction summarize(ev: SseEvent): string {\n\tswitch (ev.type) {\n\t\tcase \"compact_start\":\n\t\t\treturn `trigger=${ev.trigger} session=${ev.sessionId.slice(0, 8)}`;\n\t\tcase \"compact_end\":\n\t\t\treturn `freed=${ev.tokensFreed} ok=${ev.success} cp=${ev.checkpointId.slice(0, 8)}`;\n\t\tcase \"compact_trigger\":\n\t\t\treturn `pressure=${ev.pressure}% threshold=${ev.threshold}% armed=${ev.armed}`;\n\t\tcase \"compact_skip\":\n\t\t\treturn `reason=${ev.reason}`;\n\t\tcase \"tier_changed\":\n\t\t\treturn `${ev.from} → ${ev.to} ctx=${ev.contextPct}%`;\n\t\tcase \"model_changed\":\n\t\t\treturn `${ev.providerName}/${ev.model}`;\n\t\tcase \"pressure_lifted\":\n\t\t\treturn `${ev.beforePct}% → ${ev.afterPct}%`;\n\t\tcase \"checkpoint_persisted\":\n\t\t\treturn `cp=${ev.checkpointId.slice(0, 8)} sessionTok=${ev.sessionTokens}`;\n\t\tcase \"recall_inject\":\n\t\t\treturn `q=\"${ev.query.slice(0, 40)}\" chunks=${ev.chunks} tok=${ev.tokens}`;\n\t\tcase \"anchors_updated\":\n\t\t\treturn `count=${ev.count} pinned=${ev.pinned}`;\n\t\tcase \"config_updated\":\n\t\t\treturn `key=${ev.key}`;\n\t\tcase \"config_preset\":\n\t\t\treturn `preset=${ev.preset}`;\n\t\tcase \"crew_presence_changed\":\n\t\t\treturn `agents=${ev.activeAgents} turn=${ev.currentTurn}`;\n\t\tcase \"crew_turn_changed\":\n\t\t\treturn `turn=${ev.turnIndex} agent=${ev.agentName}`;\n\t\tcase \"crew_bandit_chosen\":\n\t\t\treturn `agent=${ev.chosenAgent} score=${ev.score} regret=${ev.regret}`;\n\t\tcase \"game_ritual_start\":\n\t\tcase \"game_ritual_stage\":\n\t\tcase \"game_ritual_end\":\n\t\tcase \"game_mode_changed\":\n\t\tcase \"game_render\":\n\t\t\treturn `stage=${ev.type}`;\n\t}\n}\n\nfunction formatTs(ts: string): string {\n\ttry {\n\t\tconst d = new Date(ts);\n\t\treturn d.toLocaleTimeString();\n\t} catch {\n\t\treturn ts;\n\t}\n}\n\nexport function EventStream({ events }: EventStreamProps): React.ReactElement {\n\tconst [filter, setFilter] = useState<FilterType>(\"all\");\n\tconst [expanded, setExpanded] = useState<number | null>(null);\n\n\tconst filtered = useMemo(() => {\n\t\tconst list =\n\t\t\tfilter === \"all\" ? events : events.filter((e) => e.type === filter);\n\t\t// Newest first; render only the last RENDER_WINDOW to keep DOM light.\n\t\treturn list.slice(-RENDER_WINDOW).reverse();\n\t}, [events, filter]);\n\n\treturn (\n\t\t<div className=\"event-stream\">\n\t\t\t<div className=\"event-filters\" role=\"toolbar\">\n\t\t\t\t{FILTER_TYPES.map((t) => (\n\t\t\t\t\t<button\n\t\t\t\t\t\tkey={t}\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName={`filter-chip ${filter === t ? \"active\" : \"\"}`}\n\t\t\t\t\t\tonClick={() => setFilter(t)}\n\t\t\t\t\t>\n\t\t\t\t\t\t{t === \"all\" ? \"all\" : t.replace(/_/g, \" \")}\n\t\t\t\t\t</button>\n\t\t\t\t))}\n\t\t\t\t<span className=\"event-count\">{events.length} buffered</span>\n\t\t\t</div>\n\t\t\t<ul className=\"event-list\">\n\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t<li className=\"event-empty\">No events yet.</li>\n\t\t\t\t)}\n\t\t\t\t{filtered.map((ev, idx) => {\n\t\t\t\t\tconst cls = TYPE_COLORS[ev.type] ?? \"ev-default\";\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<li\n\t\t\t\t\t\t\tkey={`${ev.ts}-${idx}`}\n\t\t\t\t\t\t\tclassName={`event-row ${cls}`}\n\t\t\t\t\t\t\tonClick={() => setExpanded(expanded === idx ? null : idx)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span className=\"ev-time\">{formatTs(ev.ts)}</span>\n\t\t\t\t\t\t\t<span className=\"ev-type\">{ev.type}</span>\n\t\t\t\t\t\t\t<span className=\"ev-summary\">{summarize(ev)}</span>\n\t\t\t\t\t\t\t{expanded === idx && (\n\t\t\t\t\t\t\t\t<pre className=\"ev-detail\">{JSON.stringify(ev, null, 2)}</pre>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</li>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</ul>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/EventsTab.tsx — Events tab (C1).\n *\n * Live SSE event stream via useSSE + EventStream with type filters.\n * Shows connection status + event count.\n */\n\nimport type React from \"react\";\nimport { useSSE } from \"../hooks/useSSE\";\nimport { EventStream } from \"../components/EventStream\";\n\nexport default function EventsTab(): React.ReactElement {\n\tconst { events, status, eventCount } = useSSE();\n\n\tconst statusCls = `sse-status sse-${status}`;\n\tconst statusLabel =\n\t\tstatus === \"connected\"\n\t\t\t? \"connected\"\n\t\t\t: status === \"connecting\"\n\t\t\t\t? \"connecting…\"\n\t\t\t\t: status === \"error\"\n\t\t\t\t\t? \"error\"\n\t\t\t\t\t: \"disconnected\";\n\n\treturn (\n\t\t<div className=\"events-tab\">\n\t\t\t<div className=\"events-header\">\n\t\t\t\t<span className={statusCls} role=\"status\">\n\t\t\t\t\t<span className=\"sse-dot\" aria-hidden=\"true\" />\n\t\t\t\t\t{statusLabel}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"event-count\">{eventCount} events received</span>\n\t\t\t</div>\n\t\t\t<EventStream events={events} />\n\t\t</div>\n\t);\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect","TYPE_COLORS","FILTER_TYPES","summarize","ev","formatTs","ts","EventStream","filter","setFilter","expanded","setExpanded","filtered","useMemo","e","jsxs","jsx","idx","cls","EventsTab","statusCls","statusLabel"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{j as s,u as n,r as t,b as m,c as p}from"./index-XAIN8km6.js";function r(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e.toFixed(0)}ms`}function c({value:e,max:a,colorClass:l}){const i=a>0?Math.max(0,Math.min(100,e/a*100)):0;return s.jsx("div",{className:"perf-bar-track",children:s.jsx("div",{className:`perf-bar-fill ${l}`,style:{width:`${i}%`}})})}function o({perf:e}){const a=Math.max(e.turn_latency_ms.p95,e.provider_latency_ms.p95,1),l=Math.max(e.tps.avg,1);return s.jsxs("div",{className:"perf-chart",children:[s.jsxs("div",{className:"perf-header",children:[s.jsx("h3",{children:"Performance"}),s.jsxs("span",{className:"perf-window",children:[e.windowMinutes,"min window · ",e.sampleCount," samples"]})]}),s.jsxs("div",{className:"perf-grid",children:[s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Turn latency p50 / p95"}),s.jsxs("span",{className:"perf-value",children:[r(e.turn_latency_ms.p50)," /"," ",r(e.turn_latency_ms.p95)]}),s.jsx(c,{value:e.turn_latency_ms.p95,max:a,colorClass:"perf-blue"})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Provider latency p50 / p95"}),s.jsxs("span",{className:"perf-value",children:[r(e.provider_latency_ms.p50)," /"," ",r(e.provider_latency_ms.p95)]}),s.jsx(c,{value:e.provider_latency_ms.p95,max:a,colorClass:"perf-purple"})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Tokens/sec (avg)"}),s.jsx("span",{className:"perf-value",children:e.tps.avg.toFixed(1)}),s.jsx(c,{value:e.tps.avg,max:l,colorClass:"perf-green"})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Cache hit (avg / latest)"}),s.jsxs("span",{className:"perf-value",children:[e.cache_hit_pct.avg.toFixed(1),"% /"," ",e.cache_hit_pct.latest.toFixed(1),"%"]}),s.jsx(c,{value:e.cache_hit_pct.avg,max:100,colorClass:"perf-green"})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"DB recompute p95"}),s.jsx("span",{className:"perf-value",children:r(e.db_recompute_ms.p95)}),s.jsx(c,{value:e.db_recompute_ms.p95,max:a,colorClass:"perf-yellow"})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Disk write p95"}),s.jsx("span",{className:"perf-value",children:r(e.disk_write_ms.p95)}),s.jsx(c,{value:e.disk_write_ms.p95,max:a,colorClass:"perf-yellow"})]})]}),s.jsxs("div",{className:"perf-resources",children:[s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"RSS"}),s.jsxs("span",{className:"perf-value",children:[e.rss_mb.latest.toFixed(0)," MB"]})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"Heap"}),s.jsxs("span",{className:"perf-value",children:[e.heap_mb.latest.toFixed(0)," MB"]})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"CPU user"}),s.jsxs("span",{className:"perf-value",children:[(e.cpu_user_ms.latest/1e3).toFixed(1),"s"]})]}),s.jsxs("div",{className:"perf-metric",children:[s.jsx("span",{className:"perf-label",children:"CPU sys"}),s.jsxs("span",{className:"perf-value",children:[(e.cpu_sys_ms.latest/1e3).toFixed(1),"s"]})]})]}),e.diag&&s.jsxs("div",{className:"perf-diag",children:[s.jsxs("span",{className:"perf-label",children:["Fast-gate fires: ",e.diag.ctxFastGate]}),s.jsxs("span",{className:"perf-label",children:["Live trim fires: ",e.diag.liveTrimFires]}),s.jsxs("span",{className:"perf-label",children:["Live trim replays: ",e.diag.liveTrimReplays]})]})]})}function x({name:e,providerName:a,provider:l,inputRate:i,outputRate:d}){return s.jsxs("div",{className:"model-badge",children:[s.jsx("span",{className:"model-name",children:e}),s.jsx("span",{className:"model-provider",children:a}),s.jsx("span",{className:"model-provider-id",children:l}),s.jsxs("div",{className:"model-rates",children:[s.jsxs("span",{className:"rate",children:["in ",i," tok/s"]}),s.jsxs("span",{className:"rate",children:["out ",d," tok/s"]})]})]})}function j(){const{data:e,error:a}=n(t.useCallback(()=>m({minutes:30}),[]),{pollInterval:1e4}),{data:l}=n(t.useCallback(()=>p(),[]),{pollInterval:1e4});return a&&!e?s.jsxs("div",{className:"tab-stub",children:["Error loading perf: ",a.message]}):e?s.jsxs("div",{className:"metrics-tab",children:[l?.model&&s.jsx(x,{name:l.model.name,providerName:l.model.providerName,provider:l.model.provider,inputRate:l.model.inputRate,outputRate:l.model.outputRate}),s.jsx(o,{perf:e})]}):s.jsx("div",{className:"tab-stub",children:"Loading perf…"})}export{j as default};
2
+ //# sourceMappingURL=MetricsTab-C-PoFV4p.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricsTab-C-PoFV4p.js","sources":["../../src/components/PerfChart.tsx","../../src/components/ModelBadge.tsx","../../src/tabs/MetricsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/PerfChart.tsx — perf stat chart.\n *\n * The /api/perf endpoint returns rolling-window aggregates (not time-series),\n * so this renders a stat-card \"chart\": p50/p95 latency bars, TPS, cache hit %,\n * db recompute + disk write. Each metric shows a normalized bar + numeric.\n */\n\nimport type React from \"react\";\nimport type { PerfResponse } from \"@contracts\";\n\nexport interface PerfChartProps {\n\tperf: PerfResponse;\n}\n\nfunction fmtMs(n: number): string {\n\treturn n >= 1000 ? `${(n / 1000).toFixed(1)}s` : `${n.toFixed(0)}ms`;\n}\n\ninterface BarProps {\n\t/** Current value. */\n\tvalue: number;\n\t/** Value that represents 100% bar fill (the \"max\" reference). */\n\tmax: number;\n\t/** Color class. */\n\tcolorClass: string;\n}\n\nfunction Bar({ value, max, colorClass }: BarProps): React.ReactElement {\n\tconst pct = max > 0 ? Math.max(0, Math.min(100, (value / max) * 100)) : 0;\n\treturn (\n\t\t<div className=\"perf-bar-track\">\n\t\t\t<div\n\t\t\t\tclassName={`perf-bar-fill ${colorClass}`}\n\t\t\t\tstyle={{ width: `${pct}%` }}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n\nexport function PerfChart({ perf }: PerfChartProps): React.ReactElement {\n\t// Reference maxes: p95 is the upper bound for latency bars; rates scale to a\n\t// reasonable reference (tok/s) so bars are visually meaningful even with\n\t// small samples. cache_hit_pct is already 0-100.\n\tconst latencyMax = Math.max(\n\t\tperf.turn_latency_ms.p95,\n\t\tperf.provider_latency_ms.p95,\n\t\t1,\n\t);\n\tconst tpsRef = Math.max(perf.tps.avg, 1);\n\n\treturn (\n\t\t<div className=\"perf-chart\">\n\t\t\t<div className=\"perf-header\">\n\t\t\t\t<h3>Performance</h3>\n\t\t\t\t<span className=\"perf-window\">\n\t\t\t\t\t{perf.windowMinutes}min window · {perf.sampleCount} samples\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<div className=\"perf-grid\">\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Turn latency p50 / p95</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{fmtMs(perf.turn_latency_ms.p50)} /{\" \"}\n\t\t\t\t\t\t{fmtMs(perf.turn_latency_ms.p95)}\n\t\t\t\t\t</span>\n\t\t\t\t\t<Bar\n\t\t\t\t\t\tvalue={perf.turn_latency_ms.p95}\n\t\t\t\t\t\tmax={latencyMax}\n\t\t\t\t\t\tcolorClass=\"perf-blue\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Provider latency p50 / p95</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{fmtMs(perf.provider_latency_ms.p50)} /{\" \"}\n\t\t\t\t\t\t{fmtMs(perf.provider_latency_ms.p95)}\n\t\t\t\t\t</span>\n\t\t\t\t\t<Bar\n\t\t\t\t\t\tvalue={perf.provider_latency_ms.p95}\n\t\t\t\t\t\tmax={latencyMax}\n\t\t\t\t\t\tcolorClass=\"perf-purple\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Tokens/sec (avg)</span>\n\t\t\t\t\t<span className=\"perf-value\">{perf.tps.avg.toFixed(1)}</span>\n\t\t\t\t\t<Bar value={perf.tps.avg} max={tpsRef} colorClass=\"perf-green\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Cache hit (avg / latest)</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{perf.cache_hit_pct.avg.toFixed(1)}% /{\" \"}\n\t\t\t\t\t\t{perf.cache_hit_pct.latest.toFixed(1)}%\n\t\t\t\t\t</span>\n\t\t\t\t\t<Bar\n\t\t\t\t\t\tvalue={perf.cache_hit_pct.avg}\n\t\t\t\t\t\tmax={100}\n\t\t\t\t\t\tcolorClass=\"perf-green\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">DB recompute p95</span>\n\t\t\t\t\t<span className=\"perf-value\">{fmtMs(perf.db_recompute_ms.p95)}</span>\n\t\t\t\t\t<Bar\n\t\t\t\t\t\tvalue={perf.db_recompute_ms.p95}\n\t\t\t\t\t\tmax={latencyMax}\n\t\t\t\t\t\tcolorClass=\"perf-yellow\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Disk write p95</span>\n\t\t\t\t\t<span className=\"perf-value\">{fmtMs(perf.disk_write_ms.p95)}</span>\n\t\t\t\t\t<Bar\n\t\t\t\t\t\tvalue={perf.disk_write_ms.p95}\n\t\t\t\t\t\tmax={latencyMax}\n\t\t\t\t\t\tcolorClass=\"perf-yellow\"\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div className=\"perf-resources\">\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">RSS</span>\n\t\t\t\t\t<span className=\"perf-value\">{perf.rss_mb.latest.toFixed(0)} MB</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">Heap</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{perf.heap_mb.latest.toFixed(0)} MB\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">CPU user</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{(perf.cpu_user_ms.latest / 1000).toFixed(1)}s\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"perf-metric\">\n\t\t\t\t\t<span className=\"perf-label\">CPU sys</span>\n\t\t\t\t\t<span className=\"perf-value\">\n\t\t\t\t\t\t{(perf.cpu_sys_ms.latest / 1000).toFixed(1)}s\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t{perf.diag && (\n\t\t\t\t<div className=\"perf-diag\">\n\t\t\t\t\t<span className=\"perf-label\">\n\t\t\t\t\t\tFast-gate fires: {perf.diag.ctxFastGate}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"perf-label\">\n\t\t\t\t\t\tLive trim fires: {perf.diag.liveTrimFires}\n\t\t\t\t\t</span>\n\t\t\t\t\t<span className=\"perf-label\">\n\t\t\t\t\t\tLive trim replays: {perf.diag.liveTrimReplays}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/ModelBadge.tsx — model + provider + rates.\n *\n * Shows the active model name, human-readable provider, and input/output\n * token rates. Used in the Metrics tab header.\n */\n\nimport type React from \"react\";\n\nexport interface ModelBadgeProps {\n\t/** Model name/identifier. */\n\tname: string;\n\t/** Human-readable provider name. */\n\tproviderName: string;\n\t/** Machine-readable provider identifier. */\n\tprovider: string;\n\t/** Model input processing rate (tokens per second). */\n\tinputRate: number;\n\t/** Model output processing rate (tokens per second). */\n\toutputRate: number;\n}\n\nexport function ModelBadge({\n\tname,\n\tproviderName,\n\tprovider,\n\tinputRate,\n\toutputRate,\n}: ModelBadgeProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"model-badge\">\n\t\t\t<span className=\"model-name\">{name}</span>\n\t\t\t<span className=\"model-provider\">{providerName}</span>\n\t\t\t<span className=\"model-provider-id\">{provider}</span>\n\t\t\t<div className=\"model-rates\">\n\t\t\t\t<span className=\"rate\">in {inputRate} tok/s</span>\n\t\t\t\t<span className=\"rate\">out {outputRate} tok/s</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/MetricsTab.tsx — Metrics tab (C2).\n *\n * Fetches /api/perf (rolling-window aggregates) + /api/snapshot (for model).\n * Renders PerfChart + ModelBadge + resource gauges. Polls every 10s.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchPerf, fetchSnapshot } from \"../api/client\";\nimport type { PerfResponse, SnapshotResponse } from \"@contracts\";\nimport { PerfChart } from \"../components/PerfChart\";\nimport { ModelBadge } from \"../components/ModelBadge\";\n\nexport default function MetricsTab(): React.ReactElement {\n\tconst { data: perf, error: perfErr } = useApi<PerfResponse>(\n\t\tuseCallback(() => fetchPerf({ minutes: 30 }), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\tconst { data: snapshot } = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\n\tif (perfErr && !perf) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">Error loading perf: {perfErr.message}</div>\n\t\t);\n\t}\n\tif (!perf) {\n\t\treturn <div className=\"tab-stub\">Loading perf…</div>;\n\t}\n\n\treturn (\n\t\t<div className=\"metrics-tab\">\n\t\t\t{snapshot?.model && (\n\t\t\t\t<ModelBadge\n\t\t\t\t\tname={snapshot.model.name}\n\t\t\t\t\tproviderName={snapshot.model.providerName}\n\t\t\t\t\tprovider={snapshot.model.provider}\n\t\t\t\t\tinputRate={snapshot.model.inputRate}\n\t\t\t\t\toutputRate={snapshot.model.outputRate}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<PerfChart perf={perf} />\n\t\t</div>\n\t);\n}\n"],"names":["fmtMs","n","Bar","value","max","colorClass","pct","jsx","PerfChart","perf","latencyMax","tpsRef","jsxs","ModelBadge","name","providerName","provider","inputRate","outputRate","MetricsTab","perfErr","useApi","useCallback","fetchPerf","snapshot","fetchSnapshot"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{j as e}from"./index-XAIN8km6.js";function m(s){return s>=80?"gauge-red":s>=60?"gauge-yellow":"gauge-green"}function d(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function j({tokens:s,percent:r,contextWindow:t}){const a=r??0,n=Math.max(0,Math.min(100,a)),i=m(a),l=s!=null?`${d(s)} / ${d(t)} tokens (${a.toFixed(1)}%)`:`${d(t)} window (usage unknown)`;return e.jsxs("div",{className:"card context-gauge",children:[e.jsx("h3",{children:"Context"}),e.jsx("div",{className:`gauge-bar ${i}`,role:"meter","aria-valuenow":a,"aria-valuemin":0,"aria-valuemax":100,children:e.jsx("div",{className:"gauge-fill",style:{width:`${n}%`}})}),e.jsx("p",{className:"gauge-label",children:l})]})}function o(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function p({tokensIn:s,tokensOut:r,tokensFreed:t,compressionPct:a,dedupPct:n}){return e.jsxs("div",{className:"card compression-card",children:[e.jsx("h3",{children:"Compression (repo)"}),e.jsxs("div",{className:"stat-grid",children:[e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"In"}),e.jsx("span",{className:"stat-value",children:o(s)})]}),e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"Out"}),e.jsx("span",{className:"stat-value",children:o(r)})]}),e.jsxs("div",{className:"stat stat-highlight",children:[e.jsx("span",{className:"stat-label",children:"Freed"}),e.jsx("span",{className:"stat-value",children:o(t)})]}),e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"Ratio"}),e.jsxs("span",{className:"stat-value",children:[a.toFixed(1),"%"]})]}),e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"Dedup"}),e.jsxs("span",{className:"stat-value",children:[n.toFixed(1),"%"]})]})]})]})}function u({on:s,label:r}){return e.jsxs("div",{className:`bullet ${s?"bullet-on":"bullet-off"}`,children:[e.jsx("span",{className:"bullet-dot","aria-hidden":"true"}),e.jsx("span",{className:"bullet-label",children:r})]})}function g({armed:s,ready:r,currentTokens:t,thresholdTokens:a,fastGatePct:n}){const i=t!=null?t.toLocaleString():"unknown";return e.jsxs("div",{className:"card trigger-status",children:[e.jsx("h3",{children:"Trigger"}),e.jsxs("div",{className:"bullet-row",children:[e.jsx(u,{on:s,label:"Armed"}),e.jsx(u,{on:r,label:"Ready"})]}),e.jsxs("p",{className:"trigger-detail",children:["Threshold: ",a.toLocaleString()," tokens"]}),e.jsxs("p",{className:"trigger-detail",children:["Current: ",i," tokens"]}),e.jsxs("p",{className:"trigger-detail",children:["Fast gate: ",n,"%"]})]})}function N(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function v({state:s,persistedThisSession:r,checkpointCount:t,tokensSaved:a,dedupHitRate:n}){const i=s??"no session";return e.jsxs("div",{className:"card session-info",children:[e.jsx("h3",{children:"Session"}),e.jsxs("p",{children:[e.jsx("span",{className:`state-badge state-${(s??"none").toLowerCase()}`,children:i}),r&&e.jsx("span",{className:"persisted-tag",children:"persisted"})]}),e.jsxs("div",{className:"stat-grid",children:[e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"Checkpoints"}),e.jsx("span",{className:"stat-value",children:t.toLocaleString()})]}),e.jsxs("div",{className:"stat stat-highlight",children:[e.jsx("span",{className:"stat-label",children:"Saved"}),e.jsx("span",{className:"stat-value",children:N(a)})]}),e.jsxs("div",{className:"stat",children:[e.jsx("span",{className:"stat-label",children:"Dedup hit"}),e.jsxs("span",{className:"stat-value",children:[n.toFixed(1),"%"]})]})]})]})}function f(s){if(!s)return"never";try{return new Date(s).toLocaleString()}catch{return s}}function k({snapshot:s,loading:r,error:t}){if(r&&!s)return e.jsx("div",{className:"tab-stub",children:"Loading snapshot…"});if(t&&!s)return e.jsxs("div",{className:"tab-stub",children:["Error: ",t.message]});if(!s)return e.jsx("div",{className:"tab-stub",children:"No snapshot data."});const{context:a,compression:n,trigger:i,session:l,store:c,tier:h,updatedAt:x}=s;return e.jsxs("div",{className:"overview-tab",children:[e.jsxs("div",{className:"overview-header",children:[e.jsx("span",{className:"tier-pill",children:h}),e.jsxs("span",{className:"updated",children:["updated ",f(x)]})]}),e.jsxs("div",{className:"card-grid",children:[e.jsx(j,{tokens:a.tokens,percent:a.percent,contextWindow:a.contextWindow}),e.jsx(p,{tokensIn:n.repo.tokensIn,tokensOut:n.repo.tokensOut,tokensFreed:n.repo.tokensFreed,compressionPct:n.repo.compressionPct,dedupPct:n.repo.dedupPct}),e.jsx(g,{armed:i.armed,ready:i.ready,currentTokens:i.currentTokens,thresholdTokens:i.thresholdTokens,fastGatePct:i.fastGatePct}),e.jsx(v,{state:l.state,persistedThisSession:l.persistedThisSession,checkpointCount:c.checkpointCount,tokensSaved:c.tokensSaved,dedupHitRate:c.dedupHitRate})]})]})}export{k as default};
2
+ //# sourceMappingURL=OverviewTab-Db-qBqFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverviewTab-Db-qBqFN.js","sources":["../../src/components/ContextGauge.tsx","../../src/components/CompressionCard.tsx","../../src/components/TriggerStatus.tsx","../../src/components/SessionInfo.tsx","../../src/tabs/OverviewTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/ContextGauge.tsx — token usage meter.\n *\n * Color-coded percent fill bar: green <60%, yellow 60–80%, red >80%.\n * Sublabel: \"{tokens} / {contextWindow} tokens ({percent}%)\".\n */\n\nimport type React from \"react\";\n\nexport interface ContextGaugeProps {\n\t/** Current token count in the context window, or null if unknown. */\n\ttokens: number | null;\n\t/** Context window usage percent (0–100), or null if unknown. */\n\tpercent: number | null;\n\t/** Maximum context window size for the active model. */\n\tcontextWindow: number;\n}\n\nfunction severityClass(percent: number): string {\n\tif (percent >= 80) return \"gauge-red\";\n\tif (percent >= 60) return \"gauge-yellow\";\n\treturn \"gauge-green\";\n}\n\nfunction formatTokens(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function ContextGauge({\n\ttokens,\n\tpercent,\n\tcontextWindow,\n}: ContextGaugeProps): React.ReactElement {\n\tconst pct = percent ?? 0;\n\tconst fillWidth = Math.max(0, Math.min(100, pct));\n\tconst cls = severityClass(pct);\n\tconst label =\n\t\ttokens !== null && tokens !== undefined\n\t\t\t? `${formatTokens(tokens)} / ${formatTokens(contextWindow)} tokens (${pct.toFixed(1)}%)`\n\t\t\t: `${formatTokens(contextWindow)} window (usage unknown)`;\n\n\treturn (\n\t\t<div className=\"card context-gauge\">\n\t\t\t<h3>Context</h3>\n\t\t\t<div\n\t\t\t\tclassName={`gauge-bar ${cls}`}\n\t\t\t\trole=\"meter\"\n\t\t\t\taria-valuenow={pct}\n\t\t\t\taria-valuemin={0}\n\t\t\t\taria-valuemax={100}\n\t\t\t>\n\t\t\t\t<div className=\"gauge-fill\" style={{ width: `${fillWidth}%` }} />\n\t\t\t</div>\n\t\t\t<p className=\"gauge-label\">{label}</p>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/CompressionCard.tsx — compression stats.\n *\n * Stat grid: tokens in → out, freed amount highlighted, compression %,\n * dedup contribution %. Consumes snapshot.compression.repo.\n */\n\nimport type React from \"react\";\n\nexport interface CompressionCardProps {\n\t/** Repo-level compression stats (tokens in/out/freed + percentages). */\n\ttokensIn: number;\n\ttokensOut: number;\n\ttokensFreed: number;\n\t/** Compression ratio (percent, 0–100). */\n\tcompressionPct: number;\n\t/** Dedup contribution (percent, 0–100). */\n\tdedupPct: number;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function CompressionCard({\n\ttokensIn,\n\ttokensOut,\n\ttokensFreed,\n\tcompressionPct,\n\tdedupPct,\n}: CompressionCardProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"card compression-card\">\n\t\t\t<h3>Compression (repo)</h3>\n\t\t\t<div className=\"stat-grid\">\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">In</span>\n\t\t\t\t\t<span className=\"stat-value\">{fmt(tokensIn)}</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">Out</span>\n\t\t\t\t\t<span className=\"stat-value\">{fmt(tokensOut)}</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat stat-highlight\">\n\t\t\t\t\t<span className=\"stat-label\">Freed</span>\n\t\t\t\t\t<span className=\"stat-value\">{fmt(tokensFreed)}</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">Ratio</span>\n\t\t\t\t\t<span className=\"stat-value\">{compressionPct.toFixed(1)}%</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">Dedup</span>\n\t\t\t\t\t<span className=\"stat-value\">{dedupPct.toFixed(1)}%</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/TriggerStatus.tsx — trigger armed/ready status.\n *\n * Two bullets (armed, ready) + threshold display + fast-gate %.\n */\n\nimport type React from \"react\";\n\nexport interface TriggerStatusProps {\n\t/** Whether the trigger is armed (waiting for threshold). */\n\tarmed: boolean;\n\t/** Whether the trigger is ready to fire (pressure at/above threshold). */\n\tready: boolean;\n\t/** Current context token count, or null if unknown. */\n\tcurrentTokens: number | null;\n\t/** Token threshold for firing. */\n\tthresholdTokens: number;\n\t/** Fast-gate pressure threshold (percent, 0–100). */\n\tfastGatePct: number;\n}\n\nfunction Bullet({\n\ton,\n\tlabel,\n}: {\n\ton: boolean;\n\tlabel: string;\n}): React.ReactElement {\n\treturn (\n\t\t<div className={`bullet ${on ? \"bullet-on\" : \"bullet-off\"}`}>\n\t\t\t<span className=\"bullet-dot\" aria-hidden=\"true\" />\n\t\t\t<span className=\"bullet-label\">{label}</span>\n\t\t</div>\n\t);\n}\n\nexport function TriggerStatus({\n\tarmed,\n\tready,\n\tcurrentTokens,\n\tthresholdTokens,\n\tfastGatePct,\n}: TriggerStatusProps): React.ReactElement {\n\tconst cur =\n\t\tcurrentTokens !== null && currentTokens !== undefined\n\t\t\t? currentTokens.toLocaleString()\n\t\t\t: \"unknown\";\n\treturn (\n\t\t<div className=\"card trigger-status\">\n\t\t\t<h3>Trigger</h3>\n\t\t\t<div className=\"bullet-row\">\n\t\t\t\t<Bullet on={armed} label=\"Armed\" />\n\t\t\t\t<Bullet on={ready} label=\"Ready\" />\n\t\t\t</div>\n\t\t\t<p className=\"trigger-detail\">\n\t\t\t\tThreshold: {thresholdTokens.toLocaleString()} tokens\n\t\t\t</p>\n\t\t\t<p className=\"trigger-detail\">Current: {cur} tokens</p>\n\t\t\t<p className=\"trigger-detail\">Fast gate: {fastGatePct}%</p>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/SessionInfo.tsx — session state + store metrics.\n *\n * State badge, checkpoint count, tokens saved, dedup hit rate.\n */\n\nimport type React from \"react\";\n\nexport interface SessionInfoProps {\n\t/** Session lifecycle state, or null when no session is active. */\n\tstate: string | null;\n\t/** Whether a checkpoint has been persisted during this session. */\n\tpersistedThisSession: boolean;\n\t/** Total checkpoints across all sessions. */\n\tcheckpointCount: number;\n\t/** Total tokens saved through compaction. */\n\ttokensSaved: number;\n\t/** Dedup hit rate (percent, 0–100). */\n\tdedupHitRate: number;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function SessionInfo({\n\tstate,\n\tpersistedThisSession,\n\tcheckpointCount,\n\ttokensSaved,\n\tdedupHitRate,\n}: SessionInfoProps): React.ReactElement {\n\tconst badge = state ?? \"no session\";\n\treturn (\n\t\t<div className=\"card session-info\">\n\t\t\t<h3>Session</h3>\n\t\t\t<p>\n\t\t\t\t<span\n\t\t\t\t\tclassName={`state-badge state-${(state ?? \"none\").toLowerCase()}`}\n\t\t\t\t>\n\t\t\t\t\t{badge}\n\t\t\t\t</span>\n\t\t\t\t{persistedThisSession && (\n\t\t\t\t\t<span className=\"persisted-tag\">persisted</span>\n\t\t\t\t)}\n\t\t\t</p>\n\t\t\t<div className=\"stat-grid\">\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">Checkpoints</span>\n\t\t\t\t\t<span className=\"stat-value\">{checkpointCount.toLocaleString()}</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat stat-highlight\">\n\t\t\t\t\t<span className=\"stat-label\">Saved</span>\n\t\t\t\t\t<span className=\"stat-value\">{fmt(tokensSaved)}</span>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"stat\">\n\t\t\t\t\t<span className=\"stat-label\">Dedup hit</span>\n\t\t\t\t\t<span className=\"stat-value\">{dedupHitRate.toFixed(1)}%</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/OverviewTab.tsx — Overview tab (C1).\n *\n * 4-card grid: ContextGauge, CompressionCard, TriggerStatus, SessionInfo.\n * Header: tier pill + last updated timestamp. Polls /api/snapshot.\n */\n\nimport type React from \"react\";\nimport type { SnapshotResponse } from \"@contracts\";\nimport { ContextGauge } from \"../components/ContextGauge\";\nimport { CompressionCard } from \"../components/CompressionCard\";\nimport { TriggerStatus } from \"../components/TriggerStatus\";\nimport { SessionInfo } from \"../components/SessionInfo\";\n\nexport interface OverviewTabProps {\n\tsnapshot: SnapshotResponse | null;\n\tloading: boolean;\n\terror: Error | null;\n}\n\nfunction formatUpdatedAt(ts: string | null): string {\n\tif (!ts) return \"never\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn ts;\n\t}\n}\n\nexport default function OverviewTab({\n\tsnapshot,\n\tloading,\n\terror,\n}: OverviewTabProps): React.ReactElement {\n\tif (loading && !snapshot)\n\t\treturn <div className=\"tab-stub\">Loading snapshot…</div>;\n\tif (error && !snapshot)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\tif (!snapshot) return <div className=\"tab-stub\">No snapshot data.</div>;\n\n\tconst { context, compression, trigger, session, store, tier, updatedAt } =\n\t\tsnapshot;\n\n\treturn (\n\t\t<div className=\"overview-tab\">\n\t\t\t<div className=\"overview-header\">\n\t\t\t\t<span className=\"tier-pill\">{tier}</span>\n\t\t\t\t<span className=\"updated\">updated {formatUpdatedAt(updatedAt)}</span>\n\t\t\t</div>\n\t\t\t<div className=\"card-grid\">\n\t\t\t\t<ContextGauge\n\t\t\t\t\ttokens={context.tokens}\n\t\t\t\t\tpercent={context.percent}\n\t\t\t\t\tcontextWindow={context.contextWindow}\n\t\t\t\t/>\n\t\t\t\t<CompressionCard\n\t\t\t\t\ttokensIn={compression.repo.tokensIn}\n\t\t\t\t\ttokensOut={compression.repo.tokensOut}\n\t\t\t\t\ttokensFreed={compression.repo.tokensFreed}\n\t\t\t\t\tcompressionPct={compression.repo.compressionPct}\n\t\t\t\t\tdedupPct={compression.repo.dedupPct}\n\t\t\t\t/>\n\t\t\t\t<TriggerStatus\n\t\t\t\t\tarmed={trigger.armed}\n\t\t\t\t\tready={trigger.ready}\n\t\t\t\t\tcurrentTokens={trigger.currentTokens}\n\t\t\t\t\tthresholdTokens={trigger.thresholdTokens}\n\t\t\t\t\tfastGatePct={trigger.fastGatePct}\n\t\t\t\t/>\n\t\t\t\t<SessionInfo\n\t\t\t\t\tstate={session.state}\n\t\t\t\t\tpersistedThisSession={session.persistedThisSession}\n\t\t\t\t\tcheckpointCount={store.checkpointCount}\n\t\t\t\t\ttokensSaved={store.tokensSaved}\n\t\t\t\t\tdedupHitRate={store.dedupHitRate}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["severityClass","percent","formatTokens","n","ContextGauge","tokens","contextWindow","pct","fillWidth","cls","label","jsxs","jsx","fmt","CompressionCard","tokensIn","tokensOut","tokensFreed","compressionPct","dedupPct","Bullet","on","TriggerStatus","armed","ready","currentTokens","thresholdTokens","fastGatePct","cur","SessionInfo","state","persistedThisSession","checkpointCount","tokensSaved","dedupHitRate","badge","formatUpdatedAt","ts","OverviewTab","snapshot","loading","error","context","compression","trigger","session","store","tier","updatedAt"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{j as e,r as i,u as b,f as C,a as L}from"./index-XAIN8km6.js";function R(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function w({totalRepos:s,activeRepos:t,totalCheckpoints:l,totalTokensSaved:c}){return e.jsxs("div",{className:"summary-tiles",children:[e.jsxs("div",{className:"tile",children:[e.jsx("span",{className:"tile-label",children:"Total repos"}),e.jsx("span",{className:"tile-value",children:s.toLocaleString()})]}),e.jsxs("div",{className:"tile",children:[e.jsx("span",{className:"tile-label",children:"Active (24h)"}),e.jsx("span",{className:"tile-value",children:t.toLocaleString()})]}),e.jsxs("div",{className:"tile",children:[e.jsx("span",{className:"tile-label",children:"Checkpoints"}),e.jsx("span",{className:"tile-value",children:l.toLocaleString()})]}),e.jsxs("div",{className:"tile tile-highlight",children:[e.jsx("span",{className:"tile-label",children:"Tokens saved"}),e.jsx("span",{className:"tile-value",children:R(c)})]})]})}const f=[{key:"displayName",label:"Name"},{key:"modelName",label:"Model"},{key:"checkpointCount",label:"Checkpoints"},{key:"tokensSaved",label:"Saved"},{key:"lastCompactedAt",label:"Last compacted"},{key:"sessions",label:"Sessions"}];function T(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function $(s){if(s==null)return"never";try{return new Date(s).toLocaleDateString()}catch{return String(s)}}function M({repos:s,onSelect:t}){const[l,c]=i.useState(""),[n,u]=i.useState("lastCompactedAt"),[d,j]=i.useState("desc"),p=i.useMemo(()=>{const a=l.trim().toLowerCase(),h=a?s.filter(x=>x.displayName.toLowerCase().includes(a)):s.slice();return h.sort((x,g)=>{const o=x[n],m=g[n],N=o==null,v=m==null;if(N&&v)return 0;if(N)return d==="desc"?1:-1;if(v)return d==="desc"?-1:1;const k=typeof o=="number"&&typeof m=="number"?o-m:String(o).localeCompare(String(m));return d==="desc"?-k:k}),h},[s,l,n,d]),y=a=>{n===a?j(h=>h==="asc"?"desc":"asc"):(u(a),j(a==="lastCompactedAt"||a==="tokensSaved"?"desc":"asc"))};return e.jsxs("div",{className:"repo-table-wrap",children:[e.jsx("input",{type:"search",className:"repo-search",placeholder:"Search repos…",value:l,onChange:a=>c(a.target.value),"aria-label":"Search repos"}),e.jsxs("table",{className:"repo-table",children:[e.jsx("thead",{children:e.jsx("tr",{children:f.map(a=>e.jsx("th",{children:e.jsxs("button",{type:"button",className:`sort-header ${n===a.key?`active-${d}`:""}`,onClick:()=>y(a.key),children:[a.label,n===a.key&&e.jsx("span",{className:"sort-arrow",children:d==="asc"?" ▲":" ▼"})]})},a.key))})}),e.jsxs("tbody",{children:[p.length===0&&e.jsx("tr",{children:e.jsx("td",{colSpan:f.length,className:"repo-empty",children:s.length===0?"No repos yet.":"No repos match your search."})}),p.map(a=>e.jsxs("tr",{onClick:()=>t(a),children:[e.jsx("td",{className:"repo-name",children:a.displayName}),e.jsx("td",{className:"repo-model",children:a.modelName??"—"}),e.jsx("td",{children:a.checkpointCount.toLocaleString()}),e.jsx("td",{children:T(a.tokensSaved)}),e.jsx("td",{children:$(a.lastCompactedAt)}),e.jsx("td",{children:a.sessions.toLocaleString()})]},a.stateDir))]})]})]})}function r(s){return s>=1e6?`${(s/1e6).toFixed(1)}M`:s>=1e3?`${(s/1e3).toFixed(1)}K`:String(s)}function S(s){if(s==null)return"never";try{return new Date(s).toLocaleString()}catch{return String(s)}}function D({repo:s,onClose:t}){return i.useEffect(()=>{const l=c=>{c.key==="Escape"&&t()};return window.addEventListener("keydown",l),()=>window.removeEventListener("keydown",l)},[t]),e.jsx("div",{className:"modal-backdrop",onClick:t,role:"dialog","aria-modal":"true",children:e.jsxs("div",{className:"modal-card",onClick:l=>l.stopPropagation(),children:[e.jsxs("header",{className:"modal-header",children:[e.jsx("h2",{children:s.displayName}),e.jsx("button",{type:"button",className:"modal-close",onClick:t,"aria-label":"Close",children:"×"})]}),e.jsx("p",{className:"modal-path",children:s.repoRoot}),e.jsxs("div",{className:"modal-grid",children:[e.jsxs("div",{className:"modal-stat",children:[e.jsx("span",{className:"modal-label",children:"Checkpoints"}),e.jsx("span",{className:"modal-value",children:s.checkpointCount.toLocaleString()})]}),e.jsxs("div",{className:"modal-stat",children:[e.jsx("span",{className:"modal-label",children:"Sessions"}),e.jsx("span",{className:"modal-value",children:s.sessions.toLocaleString()})]}),e.jsxs("div",{className:"modal-stat modal-highlight",children:[e.jsx("span",{className:"modal-label",children:"Tokens saved"}),e.jsx("span",{className:"modal-value",children:r(s.tokensSaved)})]}),e.jsxs("div",{className:"modal-stat",children:[e.jsx("span",{className:"modal-label",children:"Tokens kept"}),e.jsx("span",{className:"modal-value",children:r(s.tokensKept)})]}),e.jsxs("div",{className:"modal-stat",children:[e.jsx("span",{className:"modal-label",children:"Tokens dropped"}),e.jsx("span",{className:"modal-value",children:r(s.tokensDropped)})]}),e.jsxs("div",{className:"modal-stat",children:[e.jsx("span",{className:"modal-label",children:"Dedup rate"}),e.jsx("span",{className:"modal-value",children:"—"})]})]}),e.jsx("h3",{children:"Model"}),e.jsxs("dl",{className:"modal-dl",children:[e.jsx("dt",{children:"Provider"}),e.jsx("dd",{children:s.providerName??"—"}),e.jsx("dt",{children:"Model"}),e.jsx("dd",{children:s.modelName??"—"}),e.jsx("dt",{children:"Input rate"}),e.jsx("dd",{children:s.inputRate!==null?`${s.inputRate} tok/s`:"—"}),e.jsx("dt",{children:"Output rate"}),e.jsx("dd",{children:s.outputRate!==null?`${s.outputRate} tok/s`:"—"}),e.jsx("dt",{children:"Context window"}),e.jsx("dd",{children:s.contextWindow!==null?r(s.contextWindow):"—"}),e.jsx("dt",{children:"Max tokens"}),e.jsx("dd",{children:s.maxTokens!==null?r(s.maxTokens):"—"}),e.jsx("dt",{children:"Reasoning"}),e.jsx("dd",{children:s.reasoning===null?"—":s.reasoning?"on":"off"})]}),e.jsx("h3",{children:"Activity"}),e.jsxs("dl",{className:"modal-dl",children:[e.jsx("dt",{children:"Last compacted"}),e.jsx("dd",{children:S(s.lastCompactedAt)}),e.jsx("dt",{children:"Last seen"}),e.jsx("dd",{children:S(s.lastSeen)}),e.jsx("dt",{children:"Compressed bytes"}),e.jsx("dd",{children:r(s.compressedOriginalBytes)})]})]})})}function E(){const[s,t]=i.useState(null),{data:l,error:c}=b(i.useCallback(()=>C(),[]),{pollInterval:1e4}),{data:n}=b(i.useCallback(()=>L(),[]),{pollInterval:1e4});if(c&&!l)return e.jsxs("div",{className:"tab-stub",children:["Error loading repos: ",c.message]});if(!l)return e.jsx("div",{className:"tab-stub",children:"Loading repos…"});const u=l.repos,d=n?.summary;return e.jsxs("div",{className:"repos-tab",children:[e.jsx(w,{totalRepos:n?.totalRepos??0,activeRepos:n?.activeRepos??0,totalCheckpoints:d?.totalCheckpoints??0,totalTokensSaved:d?.totalTokensSaved??0}),e.jsx(M,{repos:u,onSelect:t}),s&&e.jsx(D,{repo:s,onClose:()=>t(null)})]})}export{E as default};
2
+ //# sourceMappingURL=ReposTab-DhCjJvuj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReposTab-DhCjJvuj.js","sources":["../../src/components/SummaryTiles.tsx","../../src/components/RepoTable.tsx","../../src/components/RepoDetailModal.tsx","../../src/tabs/ReposTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/SummaryTiles.tsx — aggregate header tiles.\n *\n * Total repos, active repos (24h), total checkpoints, total tokens saved.\n */\n\nimport type React from \"react\";\n\nexport interface SummaryTilesProps {\n\t/** Total repos in the registry. */\n\ttotalRepos: number;\n\t/** Repos active within the last 24 hours. */\n\tactiveRepos: number;\n\t/** Total checkpoints across all repos. */\n\ttotalCheckpoints: number;\n\t/** Total tokens saved across all repos. */\n\ttotalTokensSaved: number;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nexport function SummaryTiles({\n\ttotalRepos,\n\tactiveRepos,\n\ttotalCheckpoints,\n\ttotalTokensSaved,\n}: SummaryTilesProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"summary-tiles\">\n\t\t\t<div className=\"tile\">\n\t\t\t\t<span className=\"tile-label\">Total repos</span>\n\t\t\t\t<span className=\"tile-value\">{totalRepos.toLocaleString()}</span>\n\t\t\t</div>\n\t\t\t<div className=\"tile\">\n\t\t\t\t<span className=\"tile-label\">Active (24h)</span>\n\t\t\t\t<span className=\"tile-value\">{activeRepos.toLocaleString()}</span>\n\t\t\t</div>\n\t\t\t<div className=\"tile\">\n\t\t\t\t<span className=\"tile-label\">Checkpoints</span>\n\t\t\t\t<span className=\"tile-value\">{totalCheckpoints.toLocaleString()}</span>\n\t\t\t</div>\n\t\t\t<div className=\"tile tile-highlight\">\n\t\t\t\t<span className=\"tile-label\">Tokens saved</span>\n\t\t\t\t<span className=\"tile-value\">{fmt(totalTokensSaved)}</span>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoTable.tsx — sortable, searchable repo table.\n *\n * Columns: name, model, checkpoints, tokens saved, last compacted, sessions.\n * Client-side sort (click header to toggle asc/desc). Search by displayName\n * (case-insensitive substring). Default sort: lastSeen DESC.\n */\n\nimport type React from \"react\";\nimport { useState, useMemo } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\n\nexport interface RepoTableProps {\n\trepos: IndexesIndexRow[];\n\t/** Called when a row is clicked — opens the detail modal. */\n\tonSelect: (repo: IndexesIndexRow) => void;\n}\n\ntype SortKey =\n\t| \"displayName\"\n\t| \"modelName\"\n\t| \"checkpointCount\"\n\t| \"tokensSaved\"\n\t| \"lastCompactedAt\"\n\t| \"sessions\";\n\ntype SortDir = \"asc\" | \"desc\";\n\nconst COLUMNS: Array<{ key: SortKey; label: string }> = [\n\t{ key: \"displayName\", label: \"Name\" },\n\t{ key: \"modelName\", label: \"Model\" },\n\t{ key: \"checkpointCount\", label: \"Checkpoints\" },\n\t{ key: \"tokensSaved\", label: \"Saved\" },\n\t{ key: \"lastCompactedAt\", label: \"Last compacted\" },\n\t{ key: \"sessions\", label: \"Sessions\" },\n];\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"never\";\n\ttry {\n\t\treturn new Date(ts).toLocaleDateString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoTable({\n\trepos,\n\tonSelect,\n}: RepoTableProps): React.ReactElement {\n\tconst [search, setSearch] = useState(\"\");\n\tconst [sortKey, setSortKey] = useState<SortKey>(\"lastCompactedAt\");\n\tconst [sortDir, setSortDir] = useState<SortDir>(\"desc\");\n\n\tconst filtered = useMemo(() => {\n\t\tconst q = search.trim().toLowerCase();\n\t\tconst list = q\n\t\t\t? repos.filter((r) => r.displayName.toLowerCase().includes(q))\n\t\t\t: repos.slice();\n\t\tlist.sort((a, b) => {\n\t\t\tconst av = a[sortKey];\n\t\t\tconst bv = b[sortKey];\n\t\t\t// nulls last on desc, first on asc (consistent UX)\n\t\t\tconst aNull = av === null || av === undefined;\n\t\t\tconst bNull = bv === null || bv === undefined;\n\t\t\tif (aNull && bNull) return 0;\n\t\t\tif (aNull) return sortDir === \"desc\" ? 1 : -1;\n\t\t\tif (bNull) return sortDir === \"desc\" ? -1 : 1;\n\t\t\tconst cmp =\n\t\t\t\ttypeof av === \"number\" && typeof bv === \"number\"\n\t\t\t\t\t? av - bv\n\t\t\t\t\t: String(av).localeCompare(String(bv));\n\t\t\treturn sortDir === \"desc\" ? -cmp : cmp;\n\t\t});\n\t\treturn list;\n\t}, [repos, search, sortKey, sortDir]);\n\n\tconst handleSort = (key: SortKey): void => {\n\t\tif (sortKey === key) {\n\t\t\tsetSortDir((d) => (d === \"asc\" ? \"desc\" : \"asc\"));\n\t\t} else {\n\t\t\tsetSortKey(key);\n\t\t\tsetSortDir(\n\t\t\t\tkey === \"lastCompactedAt\" || key === \"tokensSaved\" ? \"desc\" : \"asc\",\n\t\t\t);\n\t\t}\n\t};\n\n\treturn (\n\t\t<div className=\"repo-table-wrap\">\n\t\t\t<input\n\t\t\t\ttype=\"search\"\n\t\t\t\tclassName=\"repo-search\"\n\t\t\t\tplaceholder=\"Search repos…\"\n\t\t\t\tvalue={search}\n\t\t\t\tonChange={(e) => setSearch(e.target.value)}\n\t\t\t\taria-label=\"Search repos\"\n\t\t\t/>\n\t\t\t<table className=\"repo-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t{COLUMNS.map((col) => (\n\t\t\t\t\t\t\t<th key={col.key}>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName={`sort-header ${sortKey === col.key ? `active-${sortDir}` : \"\"}`}\n\t\t\t\t\t\t\t\t\tonClick={() => handleSort(col.key)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{col.label}\n\t\t\t\t\t\t\t\t\t{sortKey === col.key && (\n\t\t\t\t\t\t\t\t\t\t<span className=\"sort-arrow\">\n\t\t\t\t\t\t\t\t\t\t\t{sortDir === \"asc\" ? \" ▲\" : \" ▼\"}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t{filtered.length === 0 && (\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td colSpan={COLUMNS.length} className=\"repo-empty\">\n\t\t\t\t\t\t\t\t{repos.length === 0\n\t\t\t\t\t\t\t\t\t? \"No repos yet.\"\n\t\t\t\t\t\t\t\t\t: \"No repos match your search.\"}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t)}\n\t\t\t\t\t{filtered.map((repo) => (\n\t\t\t\t\t\t<tr key={repo.stateDir} onClick={() => onSelect(repo)}>\n\t\t\t\t\t\t\t<td className=\"repo-name\">{repo.displayName}</td>\n\t\t\t\t\t\t\t<td className=\"repo-model\">{repo.modelName ?? \"—\"}</td>\n\t\t\t\t\t\t\t<td>{repo.checkpointCount.toLocaleString()}</td>\n\t\t\t\t\t\t\t<td>{fmt(repo.tokensSaved)}</td>\n\t\t\t\t\t\t\t<td>{formatTs(repo.lastCompactedAt)}</td>\n\t\t\t\t\t\t\t<td>{repo.sessions.toLocaleString()}</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t))}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/RepoDetailModal.tsx — repo drill-down.\n *\n * Full detail from an /api/index row: token breakdown (kept/dropped), model\n * info, context window, dedup rate, reasoning flag. Click backdrop to close.\n */\n\nimport type React from \"react\";\nimport { useEffect } from \"react\";\nimport type { IndexesIndexRow } from \"@contracts\";\n\nexport interface RepoDetailModalProps {\n\trepo: IndexesIndexRow;\n\tonClose: () => void;\n}\n\nfunction fmt(n: number): string {\n\tif (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n\tif (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n\treturn String(n);\n}\n\nfunction formatTs(ts: number | null): string {\n\tif (ts === null || ts === undefined) return \"never\";\n\ttry {\n\t\treturn new Date(ts).toLocaleString();\n\t} catch {\n\t\treturn String(ts);\n\t}\n}\n\nexport function RepoDetailModal({\n\trepo,\n\tonClose,\n}: RepoDetailModalProps): React.ReactElement {\n\t// Close on Escape key.\n\tuseEffect(() => {\n\t\tconst handler = (e: KeyboardEvent): void => {\n\t\t\tif (e.key === \"Escape\") onClose();\n\t\t};\n\t\twindow.addEventListener(\"keydown\", handler);\n\t\treturn () => window.removeEventListener(\"keydown\", handler);\n\t}, [onClose]);\n\n\treturn (\n\t\t<div\n\t\t\tclassName=\"modal-backdrop\"\n\t\t\tonClick={onClose}\n\t\t\trole=\"dialog\"\n\t\t\taria-modal=\"true\"\n\t\t>\n\t\t\t<div className=\"modal-card\" onClick={(e) => e.stopPropagation()}>\n\t\t\t\t<header className=\"modal-header\">\n\t\t\t\t\t<h2>{repo.displayName}</h2>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"modal-close\"\n\t\t\t\t\t\tonClick={onClose}\n\t\t\t\t\t\taria-label=\"Close\"\n\t\t\t\t\t>\n\t\t\t\t\t\t×\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<p className=\"modal-path\">{repo.repoRoot}</p>\n\t\t\t\t<div className=\"modal-grid\">\n\t\t\t\t\t<div className=\"modal-stat\">\n\t\t\t\t\t\t<span className=\"modal-label\">Checkpoints</span>\n\t\t\t\t\t\t<span className=\"modal-value\">\n\t\t\t\t\t\t\t{repo.checkpointCount.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"modal-stat\">\n\t\t\t\t\t\t<span className=\"modal-label\">Sessions</span>\n\t\t\t\t\t\t<span className=\"modal-value\">\n\t\t\t\t\t\t\t{repo.sessions.toLocaleString()}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"modal-stat modal-highlight\">\n\t\t\t\t\t\t<span className=\"modal-label\">Tokens saved</span>\n\t\t\t\t\t\t<span className=\"modal-value\">{fmt(repo.tokensSaved)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"modal-stat\">\n\t\t\t\t\t\t<span className=\"modal-label\">Tokens kept</span>\n\t\t\t\t\t\t<span className=\"modal-value\">{fmt(repo.tokensKept)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"modal-stat\">\n\t\t\t\t\t\t<span className=\"modal-label\">Tokens dropped</span>\n\t\t\t\t\t\t<span className=\"modal-value\">{fmt(repo.tokensDropped)}</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"modal-stat\">\n\t\t\t\t\t\t<span className=\"modal-label\">Dedup rate</span>\n\t\t\t\t\t\t<span className=\"modal-value\">—</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<h3>Model</h3>\n\t\t\t\t<dl className=\"modal-dl\">\n\t\t\t\t\t<dt>Provider</dt>\n\t\t\t\t\t<dd>{repo.providerName ?? \"—\"}</dd>\n\t\t\t\t\t<dt>Model</dt>\n\t\t\t\t\t<dd>{repo.modelName ?? \"—\"}</dd>\n\t\t\t\t\t<dt>Input rate</dt>\n\t\t\t\t\t<dd>{repo.inputRate !== null ? `${repo.inputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt>Output rate</dt>\n\t\t\t\t\t<dd>{repo.outputRate !== null ? `${repo.outputRate} tok/s` : \"—\"}</dd>\n\t\t\t\t\t<dt>Context window</dt>\n\t\t\t\t\t<dd>{repo.contextWindow !== null ? fmt(repo.contextWindow) : \"—\"}</dd>\n\t\t\t\t\t<dt>Max tokens</dt>\n\t\t\t\t\t<dd>{repo.maxTokens !== null ? fmt(repo.maxTokens) : \"—\"}</dd>\n\t\t\t\t\t<dt>Reasoning</dt>\n\t\t\t\t\t<dd>\n\t\t\t\t\t\t{repo.reasoning === null ? \"—\" : repo.reasoning ? \"on\" : \"off\"}\n\t\t\t\t\t</dd>\n\t\t\t\t</dl>\n\t\t\t\t<h3>Activity</h3>\n\t\t\t\t<dl className=\"modal-dl\">\n\t\t\t\t\t<dt>Last compacted</dt>\n\t\t\t\t\t<dd>{formatTs(repo.lastCompactedAt)}</dd>\n\t\t\t\t\t<dt>Last seen</dt>\n\t\t\t\t\t<dd>{formatTs(repo.lastSeen)}</dd>\n\t\t\t\t\t<dt>Compressed bytes</dt>\n\t\t\t\t\t<dd>{fmt(repo.compressedOriginalBytes)}</dd>\n\t\t\t\t</dl>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2).\n *\n * Fetches /api/repos + /api/summary. Renders SummaryTiles + RepoTable.\n * Row click opens RepoDetailModal. Polls every 10s.\n */\n\nimport type React from \"react\";\nimport { useCallback, useState } from \"react\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchRepos, fetchSummary } from \"../api/client\";\nimport type {\n\tReposResponse,\n\tSummaryResponse,\n\tIndexesIndexRow,\n} from \"@contracts\";\nimport { SummaryTiles } from \"../components/SummaryTiles\";\nimport { RepoTable } from \"../components/RepoTable\";\nimport { RepoDetailModal } from \"../components/RepoDetailModal\";\n\nexport default function ReposTab(): React.ReactElement {\n\tconst [selected, setSelected] = useState<IndexesIndexRow | null>(null);\n\n\tconst { data: reposResp, error: reposErr } = useApi<ReposResponse>(\n\t\tuseCallback(() => fetchRepos(), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\tconst { data: summaryResp } = useApi<SummaryResponse>(\n\t\tuseCallback(() => fetchSummary(), []),\n\t\t{ pollInterval: 10_000 },\n\t);\n\n\tif (reposErr && !reposResp) {\n\t\treturn (\n\t\t\t<div className=\"tab-stub\">Error loading repos: {reposErr.message}</div>\n\t\t);\n\t}\n\tif (!reposResp) {\n\t\treturn <div className=\"tab-stub\">Loading repos…</div>;\n\t}\n\n\tconst repos = reposResp.repos;\n\tconst summary = summaryResp?.summary;\n\n\treturn (\n\t\t<div className=\"repos-tab\">\n\t\t\t<SummaryTiles\n\t\t\t\ttotalRepos={summaryResp?.totalRepos ?? 0}\n\t\t\t\tactiveRepos={summaryResp?.activeRepos ?? 0}\n\t\t\t\ttotalCheckpoints={summary?.totalCheckpoints ?? 0}\n\t\t\t\ttotalTokensSaved={summary?.totalTokensSaved ?? 0}\n\t\t\t/>\n\t\t\t<RepoTable repos={repos} onSelect={setSelected} />\n\t\t\t{selected && (\n\t\t\t\t<RepoDetailModal repo={selected} onClose={() => setSelected(null)} />\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["fmt","n","SummaryTiles","totalRepos","activeRepos","totalCheckpoints","totalTokensSaved","jsxs","jsx","COLUMNS","formatTs","ts","RepoTable","repos","onSelect","search","setSearch","useState","sortKey","setSortKey","sortDir","setSortDir","filtered","useMemo","q","list","r","a","b","av","bv","aNull","bNull","cmp","handleSort","key","d","e","col","repo","RepoDetailModal","onClose","useEffect","handler","ReposTab","selected","setSelected","reposResp","reposErr","useApi","useCallback","fetchRepos","summaryResp","fetchSummary","summary"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ :root{--bg: #0d1117;--panel: #161b22;--border: #30363d;--text: #e6edf3;--muted: #8b949e;--accent: #58a6ff;--ok: #3fb950;--warn: #d29922;--err: #f85149;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font-size:14px}.dashboard-app{max-width:1200px;margin:0 auto;padding:16px}.dashboard-header h1{font-size:18px;font-weight:600;margin:0 0 12px}.dashboard-header .tier,.dashboard-header .version-pill{display:inline-block;margin-left:8px;padding:2px 8px;font-size:12px;border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--muted)}.dashboard-header .tier{color:var(--accent)}.tab-bar{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:16px}.tab-bar button{background:none;border:none;color:var(--muted);padding:8px 14px;cursor:pointer;font-size:13px;border-bottom:2px solid transparent}.tab-bar button.active{color:var(--text);border-bottom-color:var(--accent)}.tab-bar button:hover{color:var(--text)}.dashboard-content{min-height:400px}.loading-spinner{display:flex;align-items:center;justify-content:center;padding:48px;color:var(--muted)}.error-fallback{padding:24px;border:1px solid var(--err);border-radius:8px;background:#f8514914}.error-fallback button{margin-top:12px;padding:6px 14px;background:var(--panel);border:1px solid var(--border);color:var(--text);border-radius:6px;cursor:pointer}.tab-stub{padding:32px;color:var(--muted);border:1px dashed var(--border);border-radius:8px;text-align:center}.overview-tab{display:flex;flex-direction:column;gap:12px}.overview-header{display:flex;align-items:center;gap:12px}.tier-pill{padding:2px 10px;font-size:12px;border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--accent)}.updated{color:var(--muted);font-size:12px}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.card{padding:14px 16px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.card h3{margin:0 0 10px;font-size:13px;color:var(--muted);font-weight:600}.gauge-bar{height:10px;background:var(--bg);border-radius:5px;overflow:hidden;border:1px solid var(--border)}.gauge-fill{height:100%;transition:width .4s ease}.gauge-green .gauge-fill{background:var(--ok)}.gauge-yellow .gauge-fill{background:var(--warn)}.gauge-red .gauge-fill{background:var(--err)}.gauge-label{margin:8px 0 0;font-size:12px;color:var(--muted)}.stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.stat{display:flex;flex-direction:column;gap:2px}.stat-label{font-size:11px;color:var(--muted);text-transform:uppercase}.stat-value{font-size:16px;font-weight:600}.stat-highlight .stat-value{color:var(--ok)}.bullet-row{display:flex;gap:14px;margin-bottom:8px}.bullet{display:flex;align-items:center;gap:6px;font-size:13px}.bullet-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.bullet-on .bullet-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.bullet-off .bullet-dot{background:var(--border)}.bullet-on .bullet-label{color:var(--text)}.bullet-off .bullet-label{color:var(--muted)}.trigger-detail{margin:4px 0;font-size:12px;color:var(--muted)}.state-badge{display:inline-block;padding:2px 10px;font-size:12px;border-radius:10px;background:var(--bg);border:1px solid var(--border)}.state-idle,.state-none{color:var(--muted)}.state-compacting{color:var(--warn);border-color:var(--warn)}.persisted-tag{margin-left:8px;font-size:11px;color:var(--ok)}.events-tab{display:flex;flex-direction:column;gap:10px}.events-header{display:flex;align-items:center;gap:14px}.sse-status{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}.sse-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.sse-connected .sse-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.sse-connecting .sse-dot{background:var(--warn)}.sse-disconnected .sse-dot,.sse-error .sse-dot{background:var(--err)}.event-count{color:var(--muted);font-size:12px}.event-filters{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:8px}.filter-chip{padding:3px 10px;font-size:11px;border:1px solid var(--border);background:var(--bg);color:var(--muted);border-radius:12px;cursor:pointer}.filter-chip.active{color:var(--text);border-color:var(--accent);background:var(--panel)}.event-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.event-row{display:grid;grid-template-columns:70px 150px 1fr;gap:8px;padding:6px 10px;border-bottom:1px solid var(--border);font-size:12px;cursor:pointer}.event-row:hover{background:var(--panel)}.ev-time{color:var(--muted)}.ev-type{font-size:11px;padding:1px 6px;border-radius:8px;background:var(--bg);border:1px solid var(--border);color:var(--muted);text-align:center}.ev-compact .ev-type{color:var(--accent);border-color:var(--accent)}.ev-trigger .ev-type{color:var(--warn);border-color:var(--warn)}.ev-tier .ev-type{color:var(--muted)}.ev-checkpoint .ev-type{color:var(--ok);border-color:var(--ok)}.ev-recall .ev-type{color:#d2a8ff;border-color:#d2a8ff}.ev-config .ev-type{color:var(--text)}.ev-crew .ev-type{color:#79c0ff;border-color:#79c0ff}.ev-game .ev-type{color:var(--warn)}.ev-summary{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ev-detail{grid-column:1 / -1;margin:6px 0 0;padding:8px;background:var(--bg);border-radius:4px;font-size:11px;color:var(--muted);white-space:pre-wrap}.event-empty{padding:24px;color:var(--muted);text-align:center}@media (max-width: 768px){.card-grid{grid-template-columns:1fr}.event-row{grid-template-columns:60px 110px 1fr;font-size:11px}}.summary-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:16px}.tile{display:flex;flex-direction:column;gap:4px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.tile-label{font-size:11px;color:var(--muted);text-transform:uppercase}.tile-value{font-size:20px;font-weight:600}.tile-highlight .tile-value{color:var(--ok)}.repo-table-wrap{overflow-x:auto}.repo-search{width:100%;padding:8px 10px;margin-bottom:10px;background:var(--panel);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px}.repo-table{width:100%;border-collapse:collapse;font-size:13px}.repo-table th{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border)}.repo-table td{padding:8px 10px;border-bottom:1px solid var(--border)}.repo-table tr{cursor:pointer}.repo-table tbody tr:hover{background:var(--panel)}.sort-header{background:none;border:none;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;padding:0}.sort-header.active-asc,.sort-header.active-desc{color:var(--text)}.sort-arrow{color:var(--accent)}.repo-name{font-weight:600}.repo-model{color:var(--muted)}.repo-empty{text-align:center;color:var(--muted);padding:24px}.modal-backdrop{position:fixed;inset:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:100}.modal-card{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:20px 24px;max-width:560px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;justify-content:space-between;align-items:center}.modal-header h2{margin:0;font-size:18px}.modal-close{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;padding:0 4px}.modal-close:hover{color:var(--text)}.modal-path{font-size:11px;color:var(--muted);margin:4px 0 12px;word-break:break-all}.modal-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px}.modal-stat{display:flex;flex-direction:column;gap:2px}.modal-label{font-size:11px;color:var(--muted);text-transform:uppercase}.modal-value{font-size:16px;font-weight:600}.modal-highlight .modal-value{color:var(--ok)}.modal-card h3{font-size:13px;color:var(--muted);margin:16px 0 6px}.modal-dl{display:grid;grid-template-columns:130px 1fr;gap:4px 12px;margin:0}.modal-dl dt{color:var(--muted);font-size:12px}.modal-dl dd{margin:0;font-size:13px}.model-badge{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px;margin-bottom:12px}.model-name{font-size:15px;font-weight:600}.model-provider{color:var(--accent);font-size:13px}.model-provider-id{color:var(--muted);font-size:11px}.model-rates{margin-left:auto;display:flex;gap:12px}.model-rates .rate{font-size:12px;color:var(--muted)}.perf-chart{display:flex;flex-direction:column;gap:12px}.perf-header{display:flex;justify-content:space-between;align-items:center}.perf-header h3{margin:0;font-size:13px;color:var(--muted)}.perf-window{font-size:11px;color:var(--muted)}.perf-grid,.perf-resources{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.perf-resources{padding-top:10px;border-top:1px solid var(--border)}.perf-metric{display:flex;flex-direction:column;gap:4px;padding:10px 12px;background:var(--panel);border:1px solid var(--border);border-radius:6px}.perf-label{font-size:11px;color:var(--muted)}.perf-value{font-size:15px;font-weight:600}.perf-bar-track{height:6px;background:var(--bg);border-radius:3px;overflow:hidden}.perf-bar-fill{height:100%;transition:width .4s ease}.perf-blue .perf-bar-fill{background:var(--accent)}.perf-purple .perf-bar-fill{background:#d2a8ff}.perf-green .perf-bar-fill{background:var(--ok)}.perf-yellow .perf-bar-fill{background:var(--warn)}.perf-diag{display:flex;flex-wrap:wrap;gap:14px;padding-top:10px;border-top:1px solid var(--border)}@media (max-width: 768px){.modal-grid{grid-template-columns:1fr 1fr}.model-rates{margin-left:0;width:100%}}