pi-mega-compact 0.8.12 → 0.8.14

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.
Files changed (76) hide show
  1. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
  2. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
  3. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
  4. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
  5. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
  6. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
  7. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
  8. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
  9. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
  10. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
  11. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
  12. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
  13. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
  14. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
  15. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
  16. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
  17. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
  18. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
  19. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
  20. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
  21. package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
  22. package/extensions/dashboard-client/dist/assets/{index-XAIN8km6.js → index-DMntLu-8.js} +11 -10
  23. package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
  24. package/extensions/dashboard-client/dist/index.html +2 -2
  25. package/extensions/dashboard-client/src/App.tsx +18 -4
  26. package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
  27. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
  28. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
  29. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
  30. package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
  31. package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
  32. package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
  33. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
  34. package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
  35. package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
  36. package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
  37. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
  38. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
  39. package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
  40. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
  41. package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
  42. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
  43. package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
  44. package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
  45. package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
  46. package/extensions/dashboard-client/src/main.tsx +5 -0
  47. package/extensions/dashboard-client/src/styles/base.css +76 -9
  48. package/extensions/dashboard-client/src/styles/config.css +149 -0
  49. package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
  50. package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
  51. package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
  52. package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
  53. package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
  54. package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
  55. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
  56. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
  57. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
  58. package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
  59. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
  60. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
  61. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
  62. package/extensions/dashboard-client/src/utils/format.ts +85 -0
  63. package/extensions/dashboard-client/src/utils/types.ts +27 -0
  64. package/package.json +1 -1
  65. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js +0 -2
  66. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js.map +0 -1
  67. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js +0 -2
  68. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js.map +0 -1
  69. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js +0 -2
  70. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js.map +0 -1
  71. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js +0 -2
  72. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js.map +0 -1
  73. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js +0 -2
  74. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js.map +0 -1
  75. package/extensions/dashboard-client/dist/assets/index-Buer5yxB.css +0 -1
  76. package/extensions/dashboard-client/dist/assets/index-XAIN8km6.js.map +0 -1
@@ -1,49 +1,58 @@
1
1
  /**
2
2
  * dashboard-client/src/components/DataSafetyCard.tsx — Data Safety / Shield.
3
3
  *
4
- * Shows regions retained, compressed-original size, storage dedup %,
5
- * and permanently deleted count. Read-only (no mutations).
4
+ * 4 fields: Regions Retained, Compressed-Original bytes, Dedup Duplicates,
5
+ * Permanently Deleted (always 0B, shown in green).
6
+ * Plus the safe-note text (verbatim from html.ts).
6
7
  */
8
+
7
9
  import type React from "react";
10
+ import { fmtBytes } from "../utils/format";
8
11
 
9
12
  export interface DataSafetyCardProps {
10
13
  regionsRetained: number;
11
- compressedOriginalKiB: number;
12
- storageDedupPct: number;
13
- permanentlyDeleted: number;
14
+ /** Original byte size of compressed data (bytes). */
15
+ compressedOriginalBytes: number;
16
+ /** Number of duplicate chunks collapsed. */
17
+ duplicatesCollapsed: number;
18
+ /** Bytes permanently deleted (always 0). */
19
+ bytesPermanentlyDeleted: number;
14
20
  }
15
21
 
16
- export function DataSafetyCard({
17
- regionsRetained,
18
- compressedOriginalKiB,
19
- storageDedupPct,
20
- permanentlyDeleted,
21
- }: DataSafetyCardProps): React.ReactElement {
22
+ export function DataSafetyCard(
23
+ props: DataSafetyCardProps,
24
+ ): React.ReactElement {
22
25
  return (
23
- <div className="data-safety-card">
24
- <h4>Data Safety</h4>
25
- <div className="safety-grid">
26
- <div className="safety-row">
27
- <span className="label">Regions Retained</span>
28
- <span className="value">{regionsRetained}</span>
29
- </div>
30
- <div className="safety-row">
31
- <span className="label">Compressed-Original</span>
32
- <span className="value">{compressedOriginalKiB.toFixed(1)} KiB</span>
33
- </div>
34
- <div className="safety-row">
35
- <span className="label">Storage Dedup</span>
36
- <span className="value">{storageDedupPct}%</span>
37
- </div>
38
- <div className="safety-row">
39
- <span className="label">Permanently Deleted</span>
40
- <span className="value">{permanentlyDeleted}</span>
41
- </div>
26
+ <div className="card data-safety-card">
27
+ <h3>🛡 Data Safety</h3>
28
+ <div className="ov-stat-row">
29
+ <span className="ov-stat-label">Regions Retained</span>
30
+ <span className="ov-stat-value">
31
+ {props.regionsRetained.toLocaleString()}
32
+ </span>
33
+ </div>
34
+ <div className="ov-stat-row">
35
+ <span className="ov-stat-label">Compressed-Original</span>
36
+ <span className="ov-stat-value">
37
+ {fmtBytes(props.compressedOriginalBytes)}
38
+ </span>
39
+ </div>
40
+ <div className="ov-stat-row">
41
+ <span className="ov-stat-label">Dedup Duplicates</span>
42
+ <span className="ov-stat-value">
43
+ {props.duplicatesCollapsed.toLocaleString()}
44
+ </span>
42
45
  </div>
43
- <div className="safety-note">
44
- Every compacted region is kept verbatim (compressed). "Drop" = removed
45
- from the live window only. We never delete your data.
46
+ <div className="ov-stat-row">
47
+ <span className="ov-stat-label">Permanently Deleted</span>
48
+ <span className="ov-stat-value ov-value-ok">
49
+ {fmtBytes(props.bytesPermanentlyDeleted)}
50
+ </span>
46
51
  </div>
52
+ <p className="safe-note">
53
+ Every compacted region is kept verbatim (compressed). &quot;Drop&quot; =
54
+ removed from the live window only. We never delete your data.
55
+ </p>
47
56
  </div>
48
57
  );
49
58
  }
@@ -1,16 +1,53 @@
1
+ /**
2
+ * dashboard-client/src/components/EventCategoryFilter.tsx
3
+ *
4
+ * Category-level filter chips for the Events tab. Filters SSE events by
5
+ * category: all / compact / recall / config / crew / game.
6
+ */
7
+
1
8
  import type React from "react";
9
+
10
+ export type EventCategory =
11
+ | "all"
12
+ | "compact"
13
+ | "recall"
14
+ | "config"
15
+ | "crew"
16
+ | "game";
17
+
18
+ const CATEGORIES: readonly EventCategory[] = [
19
+ "all",
20
+ "compact",
21
+ "recall",
22
+ "config",
23
+ "crew",
24
+ "game",
25
+ ] as const;
26
+
27
+ export interface EventCategoryFilterProps {
28
+ /** Currently active category. */
29
+ active: EventCategory;
30
+ /** Called when the user selects a category. */
31
+ onFilter: (category: EventCategory) => void;
32
+ }
33
+
2
34
  export function EventCategoryFilter({
35
+ active,
3
36
  onFilter,
4
- }: {
5
- onFilter: (f: string) => void;
6
- }): React.ReactElement {
37
+ }: EventCategoryFilterProps): React.ReactElement {
7
38
  return (
8
- <div className="event-filter">
9
- <button onClick={() => onFilter("all")}>All</button>
10
- <button onClick={() => onFilter("agent_start")}>Agent Start</button>
11
- <button onClick={() => onFilter("agent_end")}>Agent End</button>
12
- <button onClick={() => onFilter("turn_start")}>Turn Start</button>
13
- <button onClick={() => onFilter("turn_end")}>Turn End</button>
39
+ <div className="event-category-filter" role="toolbar" aria-label="Event category filter">
40
+ {CATEGORIES.map((cat) => (
41
+ <button
42
+ key={cat}
43
+ type="button"
44
+ className={`category-chip${active === cat ? " active" : ""}`}
45
+ onClick={() => onFilter(cat)}
46
+ aria-pressed={active === cat}
47
+ >
48
+ {cat}
49
+ </button>
50
+ ))}
14
51
  </div>
15
52
  );
16
53
  }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * dashboard-client/src/components/LegendCard.tsx — "What these numbers mean" legend.
3
+ *
4
+ * Collapsible <details> with the legend list (verbatim from html.ts).
5
+ * Full-width card (grid-column: 1 / -1).
6
+ */
7
+
8
+ import type React from "react";
9
+
10
+ const LEGEND_ITEMS: ReadonlyArray<React.ReactNode> = [
11
+ <li key="1">
12
+ <b>Original (dropped)</b> — everything compacted away (including
13
+ duplicates caught by dedup). The &quot;in.&quot;
14
+ </li>,
15
+ <li key="2">
16
+ <b>Kept (summaries)</b> — compact summaries still held as &quot;memory&quot;
17
+ (the &quot;out&quot;).
18
+ </li>,
19
+ <li key="3">
20
+ <b>Freed</b> = dropped − kept — tokens saved so far (higher = better).
21
+ </li>,
22
+ <li key="4">
23
+ <b>Compression %</b> — Freed ÷ Dropped — the headline efficiency
24
+ number. Higher = more space reclaimed.
25
+ </li>,
26
+ <li key="5">
27
+ <b>Storage dedup %</b> — how often new content matched something
28
+ already saved, so no duplicate copy was written.
29
+ </li>,
30
+ <li key="6">
31
+ <b>Data safety</b> — every compacted region is kept verbatim
32
+ (compressed). Nothing is permanently deleted; you can restore any of
33
+ it.
34
+ </li>,
35
+ ];
36
+
37
+ export function LegendCard(): React.ReactElement {
38
+ return (
39
+ <div className="card legend-card">
40
+ <details>
41
+ <summary>What these numbers mean</summary>
42
+ <ol className="legend-list">{LEGEND_ITEMS}</ol>
43
+ <p className="legend-note">
44
+ Hover any label above for a quick explanation.
45
+ </p>
46
+ </details>
47
+ </div>
48
+ );
49
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * dashboard-client/src/components/ModelBadge.tsx — model + provider + rates.
3
3
  *
4
- * Shows the active model name, human-readable provider, and input/output
5
- * token rates. Used in the Metrics tab header.
4
+ * Shows the active model name, provider, and input/output rates.
5
+ * Tooltips copied verbatim from html.ts. Used in the Metrics tab header.
6
6
  */
7
7
 
8
8
  import type React from "react";
@@ -14,12 +14,29 @@ export interface ModelBadgeProps {
14
14
  providerName: string;
15
15
  /** Machine-readable provider identifier. */
16
16
  provider: string;
17
- /** Model input processing rate (tokens per second). */
17
+ /** USD per input token (from model pricing). */
18
18
  inputRate: number;
19
- /** Model output processing rate (tokens per second). */
19
+ /** USD per output token (from model pricing). */
20
20
  outputRate: number;
21
21
  }
22
22
 
23
+ function Field({
24
+ label,
25
+ value,
26
+ title,
27
+ }: {
28
+ label: string;
29
+ value: string;
30
+ title?: string;
31
+ }): React.ReactElement {
32
+ return (
33
+ <div className="model-field" title={title}>
34
+ <span className="model-field-label">{label}</span>
35
+ <span className="model-field-value">{value}</span>
36
+ </div>
37
+ );
38
+ }
39
+
23
40
  export function ModelBadge({
24
41
  name,
25
42
  providerName,
@@ -27,15 +44,29 @@ export function ModelBadge({
27
44
  inputRate,
28
45
  outputRate,
29
46
  }: ModelBadgeProps): React.ReactElement {
47
+ const providerDisplay = providerName || provider || "—";
30
48
  return (
31
49
  <div className="model-badge">
32
- <span className="model-name">{name}</span>
33
- <span className="model-provider">{providerName}</span>
34
- <span className="model-provider-id">{provider}</span>
35
- <div className="model-rates">
36
- <span className="rate">in {inputRate} tok/s</span>
37
- <span className="rate">out {outputRate} tok/s</span>
38
- </div>
50
+ <Field
51
+ label="Model"
52
+ value={name}
53
+ title="The model pi is currently using — its pricing drives the cost figure."
54
+ />
55
+ <Field
56
+ label="Provider"
57
+ value={providerDisplay}
58
+ title="The provider serving the model."
59
+ />
60
+ <Field
61
+ label="Input Rate"
62
+ value={`$${inputRate.toFixed(6)}`}
63
+ title="USD per input token, from the model's pricing."
64
+ />
65
+ <Field
66
+ label="Output Rate"
67
+ value={`$${outputRate.toFixed(6)}`}
68
+ title="USD per output token, from the model's pricing."
69
+ />
39
70
  </div>
40
71
  );
41
72
  }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * dashboard-client/src/components/PerfCards.tsx — 5 perf metric cards.
3
+ *
4
+ * Mirrors the old html.ts perf grid: model latency, throughput, process,
5
+ * snapshot cost, and TUI lag proxy. Uses the existing perf-metric CSS
6
+ * classes from repos-metrics.css plus metrics-extra.css for card headers.
7
+ */
8
+
9
+ import type React from "react";
10
+ import type { PerfResponse } from "@contracts";
11
+
12
+ /** Format milliseconds (em-dash for null/undefined). */
13
+ function fmtMs(v: number | null | undefined): string {
14
+ return v == null ? "\u2014" : v >= 100 ? `${Math.round(v)}ms` : `${v.toFixed(1)}ms`;
15
+ }
16
+
17
+ /** Format a number with fixed decimals (em-dash for null/non-number). */
18
+ function fmtNum(v: number | null | undefined, dec: number): string {
19
+ return v == null || typeof v !== "number" ? "\u2014" : v.toFixed(dec);
20
+ }
21
+
22
+ /** Format a diag counter (em-dash for null). */
23
+ function fmtDiag(v: number | null | undefined): string {
24
+ return v == null ? "\u2014" : String(v);
25
+ }
26
+
27
+ interface PerfCardsProps {
28
+ perf: PerfResponse;
29
+ }
30
+
31
+ /** A single stat row inside a perf card. */
32
+ function Stat({ label, value }: { label: string; value: string }): React.ReactElement {
33
+ return (
34
+ <div className="perf-metric">
35
+ <span className="perf-label">{label}</span>
36
+ <span className="perf-value">{value}</span>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ /** Card wrapper with a title. */
42
+ function Card({ title, children }: { title: string; children: React.ReactNode }): React.ReactElement {
43
+ return (
44
+ <div className="perf-card">
45
+ <h3 className="perf-card-title">{title}</h3>
46
+ <div className="perf-card-body">{children}</div>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ export function PerfCards({ perf }: PerfCardsProps): React.ReactElement {
52
+ const cpuTxt = `${fmtNum(perf.cpu_user_ms.latest, 1)} / ${fmtNum(perf.cpu_sys_ms.latest, 1)} ms`;
53
+
54
+ return (
55
+ <div className="perf-cards-grid">
56
+ <Card title="Model latency">
57
+ <Stat label="Turn p50" value={fmtMs(perf.turn_latency_ms.p50)} />
58
+ <Stat label="Turn p95" value={fmtMs(perf.turn_latency_ms.p95)} />
59
+ <Stat label="Provider p50" value={fmtMs(perf.provider_latency_ms.p50)} />
60
+ <Stat label="Provider p95" value={fmtMs(perf.provider_latency_ms.p95)} />
61
+ </Card>
62
+
63
+ <Card title="Throughput">
64
+ <Stat label="TPS (avg)" value={fmtNum(perf.tps.avg, 1)} />
65
+ <Stat
66
+ label="Cache hit %"
67
+ value={`${fmtNum(perf.cache_hit_pct.avg, 1)}%`}
68
+ />
69
+ </Card>
70
+
71
+ <Card title="Process">
72
+ <Stat label="RSS" value={`${fmtNum(perf.rss_mb.latest, 1)} MB`} />
73
+ <Stat label="Heap" value={`${fmtNum(perf.heap_mb.latest, 1)} MB`} />
74
+ <Stat label="CPU user / sys" value={cpuTxt} />
75
+ </Card>
76
+
77
+ <Card title="Snapshot cost">
78
+ <Stat label="DB recompute p50" value={fmtMs(perf.db_recompute_ms.p50)} />
79
+ <Stat label="DB recompute p95" value={fmtMs(perf.db_recompute_ms.p95)} />
80
+ <Stat label="Disk write p50" value={fmtMs(perf.disk_write_ms.p50)} />
81
+ </Card>
82
+
83
+ <Card title="TUI lag proxy">
84
+ <Stat label="Live-trim fires" value={fmtDiag(perf.diag?.liveTrimFires)} />
85
+ <Stat label="Cache replays" value={fmtDiag(perf.diag?.liveTrimReplays)} />
86
+ <Stat label="Fast-gate skips" value={fmtDiag(perf.diag?.ctxFastGate)} />
87
+ <span className="perf-card-note">skip vs recompute vs replay cadence</span>
88
+ </Card>
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * dashboard-client/src/components/RepoAllSessionsCard.tsx — Repo (all sessions) card.
3
+ *
4
+ * 7 fields: Checkpoints, Original, Kept, Freed, Sessions, Collapsed, Storage Dedup.
5
+ * Plus a compression meter bar (green ≥90%, yellow ≥60%, red <60%).
6
+ *
7
+ * NOTE: storageDedupRate, compressionPct, dedupPct are 0–1 fractions
8
+ * at runtime (despite contract saying 0–100).
9
+ */
10
+
11
+ import type React from "react";
12
+ import { fmtPctFromFraction } from "../utils/format";
13
+
14
+ export interface RepoAllSessionsCardProps {
15
+ checkpointCount: number;
16
+ /** Original tokens dropped (repo-level). */
17
+ tokensIn: number;
18
+ /** Kept summary tokens (repo-level). */
19
+ tokensOut: number;
20
+ /** Tokens freed (repo-level). */
21
+ tokensFreed: number;
22
+ /** Number of sessions. */
23
+ sessionCount: number;
24
+ /** Duplicate chunks collapsed (repo-level). */
25
+ dedupCollapsed: number;
26
+ /** Storage dedup rate — 0–1 fraction. */
27
+ storageDedupRate: number;
28
+ /** Repo compression ratio — 0–1 fraction. */
29
+ compressionPct: number;
30
+ /** Repo dedup contribution — 0–1 fraction. */
31
+ dedupPct: number;
32
+ }
33
+
34
+ function compressClass(sp: number): string {
35
+ if (sp >= 0.9) return "meter-green";
36
+ if (sp >= 0.6) return "meter-yellow";
37
+ return "meter-red";
38
+ }
39
+
40
+ function StatRow({
41
+ label,
42
+ value,
43
+ }: {
44
+ label: string;
45
+ value: string | number;
46
+ }): React.ReactElement {
47
+ return (
48
+ <div className="ov-stat-row">
49
+ <span className="ov-stat-label">{label}</span>
50
+ <span className="ov-stat-value">{value}</span>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ export function RepoAllSessionsCard(
56
+ props: RepoAllSessionsCardProps,
57
+ ): React.ReactElement {
58
+ const sp = props.compressionPct;
59
+ const barCls = compressClass(sp);
60
+ const barWidth = Math.max(sp * 100, 0.5);
61
+ const compressSub = `${fmtPctFromFraction(sp)} tokens saved · dedup: ${fmtPctFromFraction(props.dedupPct)}`;
62
+
63
+ return (
64
+ <div className="card repo-sessions-card">
65
+ <h3>Repo (all sessions)</h3>
66
+ <StatRow
67
+ label="Checkpoints"
68
+ value={props.checkpointCount.toLocaleString()}
69
+ />
70
+ <StatRow
71
+ label="Original"
72
+ value={props.tokensIn.toLocaleString()}
73
+ />
74
+ <StatRow
75
+ label="Kept"
76
+ value={props.tokensOut.toLocaleString()}
77
+ />
78
+ <StatRow
79
+ label="Freed"
80
+ value={props.tokensFreed.toLocaleString()}
81
+ />
82
+ <StatRow
83
+ label="Sessions"
84
+ value={props.sessionCount.toLocaleString()}
85
+ />
86
+ <StatRow label="Collapsed" value={props.dedupCollapsed} />
87
+ <StatRow
88
+ label="Storage Dedup"
89
+ value={fmtPctFromFraction(props.storageDedupRate)}
90
+ />
91
+ <div className="ov-compression-meter">
92
+ <div className="meter-track">
93
+ <div
94
+ className={`meter-fill ${barCls}`}
95
+ style={{ width: `${barWidth}%` }}
96
+ />
97
+ </div>
98
+ <span className="meter-sub">{compressSub}</span>
99
+ </div>
100
+ </div>
101
+ );
102
+ }
@@ -20,6 +20,13 @@ function fmt(n: number): string {
20
20
  return String(n);
21
21
  }
22
22
 
23
+ /** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */
24
+ function fmtBytesTop(b: number): string {
25
+ if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;
26
+ if (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;
27
+ return `${b} B`;
28
+ }
29
+
23
30
  function formatTs(ts: number | null): string {
24
31
  if (ts === null || ts === undefined) return "never";
25
32
  try {
@@ -117,8 +124,8 @@ export function RepoDetailModal({
117
124
  <dd>{formatTs(repo.lastCompactedAt)}</dd>
118
125
  <dt>Last seen</dt>
119
126
  <dd>{formatTs(repo.lastSeen)}</dd>
120
- <dt>Compressed bytes</dt>
121
- <dd>{fmt(repo.compressedOriginalBytes)}</dd>
127
+ <dt>Compressed-Original</dt>
128
+ <dd>{fmtBytesTop(repo.compressedOriginalBytes)}</dd>
122
129
  </dl>
123
130
  </div>
124
131
  </div>
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * dashboard-client/src/components/RepoTable.tsx — sortable, searchable repo table.
3
3
  *
4
- * Columns: name, model, checkpoints, tokens saved, last compacted, sessions.
4
+ * 6 columns: Repo, Model, Checkpoints, Tokens Saved, Retained, Last Compacted.
5
5
  * Client-side sort (click header to toggle asc/desc). Search by displayName
6
- * (case-insensitive substring). Default sort: lastSeen DESC.
6
+ * (case-insensitive substring). Default sort: Last Compacted DESC.
7
+ * Row click opens RepoDetailModal.
7
8
  */
8
9
 
9
10
  import type React from "react";
@@ -21,30 +22,31 @@ type SortKey =
21
22
  | "modelName"
22
23
  | "checkpointCount"
23
24
  | "tokensSaved"
24
- | "lastCompactedAt"
25
- | "sessions";
25
+ | "compressedOriginalBytes"
26
+ | "lastCompactedAt";
26
27
 
27
28
  type SortDir = "asc" | "desc";
28
29
 
29
30
  const COLUMNS: Array<{ key: SortKey; label: string }> = [
30
- { key: "displayName", label: "Name" },
31
+ { key: "displayName", label: "Repo" },
31
32
  { key: "modelName", label: "Model" },
32
33
  { key: "checkpointCount", label: "Checkpoints" },
33
- { key: "tokensSaved", label: "Saved" },
34
- { key: "lastCompactedAt", label: "Last compacted" },
35
- { key: "sessions", label: "Sessions" },
34
+ { key: "tokensSaved", label: "Tokens Saved" },
35
+ { key: "compressedOriginalBytes", label: "Retained" },
36
+ { key: "lastCompactedAt", label: "Last Compacted" },
36
37
  ];
37
38
 
38
- function fmt(n: number): string {
39
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
40
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
41
- return String(n);
39
+ /** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */
40
+ function fmtBytesTop(b: number): string {
41
+ if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;
42
+ if (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;
43
+ return `${b} B`;
42
44
  }
43
45
 
44
46
  function formatTs(ts: number | null): string {
45
- if (ts === null || ts === undefined) return "never";
47
+ if (ts === null || ts === undefined) return "\u2014";
46
48
  try {
47
- return new Date(ts).toLocaleDateString();
49
+ return new Date(ts).toLocaleString();
48
50
  } catch {
49
51
  return String(ts);
50
52
  }
@@ -87,7 +89,11 @@ export function RepoTable({
87
89
  } else {
88
90
  setSortKey(key);
89
91
  setSortDir(
90
- key === "lastCompactedAt" || key === "tokensSaved" ? "desc" : "asc",
92
+ key === "lastCompactedAt" ||
93
+ key === "tokensSaved" ||
94
+ key === "compressedOriginalBytes"
95
+ ? "desc"
96
+ : "asc",
91
97
  );
92
98
  }
93
99
  };
@@ -128,19 +134,21 @@ export function RepoTable({
128
134
  <tr>
129
135
  <td colSpan={COLUMNS.length} className="repo-empty">
130
136
  {repos.length === 0
131
- ? "No repos yet."
137
+ ? "No repositories registered yet."
132
138
  : "No repos match your search."}
133
139
  </td>
134
140
  </tr>
135
141
  )}
136
142
  {filtered.map((repo) => (
137
143
  <tr key={repo.stateDir} onClick={() => onSelect(repo)}>
138
- <td className="repo-name">{repo.displayName}</td>
139
- <td className="repo-model">{repo.modelName ?? "—"}</td>
140
- <td>{repo.checkpointCount.toLocaleString()}</td>
141
- <td>{fmt(repo.tokensSaved)}</td>
142
- <td>{formatTs(repo.lastCompactedAt)}</td>
143
- <td>{repo.sessions.toLocaleString()}</td>
144
+ <td className="repo-name" title={repo.repoRoot}>
145
+ {repo.displayName}
146
+ </td>
147
+ <td className="repo-model">{repo.modelName ?? "\u2014"}</td>
148
+ <td className="num">{repo.checkpointCount.toLocaleString()}</td>
149
+ <td className="num">{repo.tokensSaved.toLocaleString()}</td>
150
+ <td className="num">{fmtBytesTop(repo.compressedOriginalBytes)}</td>
151
+ <td className="num">{formatTs(repo.lastCompactedAt)}</td>
144
152
  </tr>
145
153
  ))}
146
154
  </tbody>