pi-mega-compact 0.13.0 → 0.13.2

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 (212) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/embedder-health.js +11 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +225 -0
  3. package/dist/extensions/dashboard-server/api-contracts/endpoints/types.js +1 -0
  4. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +5 -224
  5. package/dist/extensions/dashboard-server/api-contracts/raptor.js +8 -0
  6. package/dist/extensions/dashboard-server/api-contracts.test/_helpers.js +32 -0
  7. package/dist/extensions/dashboard-server/routes-cache.test/_helpers.js +48 -0
  8. package/dist/extensions/dashboard-server/routes-embedder-health.js +67 -0
  9. package/dist/extensions/dashboard-server/routes-raptor.js +88 -0
  10. package/dist/extensions/dashboard-server/routes-setup.js +1 -1
  11. package/dist/extensions/dashboard-server/routes.js +2 -0
  12. package/dist/extensions/dashboard-server/server.js +5 -1
  13. package/dist/extensions/mega-cache-replay.test/_helpers.js +175 -0
  14. package/dist/extensions/mega-commands/dataCommands.js +151 -0
  15. package/dist/extensions/mega-commands/helpers.js +61 -0
  16. package/dist/extensions/mega-commands/historyCommands.js +97 -0
  17. package/dist/extensions/mega-commands/setupCommand.js +200 -0
  18. package/dist/extensions/mega-commands.js +14 -499
  19. package/dist/extensions/mega-compact-s38.test/_helpers.js +194 -0
  20. package/dist/extensions/mega-compact.js +12 -2
  21. package/dist/extensions/mega-compact.test/_helpers.js +225 -0
  22. package/dist/extensions/mega-events/agent-handlers/agentEndHandler.js +211 -0
  23. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.js +49 -0
  24. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.js +33 -0
  25. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.js +387 -0
  26. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/event.js +7 -0
  27. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.js +55 -0
  28. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.js +12 -0
  29. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.js +22 -0
  30. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +38 -0
  31. package/dist/extensions/mega-events/agent-handlers/turnEndHandler.js +36 -0
  32. package/dist/extensions/mega-events/agent-handlers.js +4 -799
  33. package/dist/extensions/mega-events/context-handler/afterCompact.js +163 -0
  34. package/dist/extensions/mega-events/context-handler/messageText.js +12 -0
  35. package/dist/extensions/mega-events/context-handler/tailResult.js +46 -0
  36. package/dist/extensions/mega-events/context-handler.js +11 -198
  37. package/dist/extensions/mega-events/separated-prompt.test/_helpers.js +25 -0
  38. package/dist/src/config.js +2 -0
  39. package/dist/src/dedup-engine.test/_helpers.js +55 -0
  40. package/dist/src/e2e.test/_helpers.js +52 -0
  41. package/dist/src/embedder.js +1 -0
  42. package/dist/src/httpEmbedder.js +6 -0
  43. package/dist/src/hyde.js +146 -0
  44. package/dist/src/importance.test/_helpers.js +5 -0
  45. package/dist/src/memoryGraph/sources/checkpoints.js +78 -0
  46. package/dist/src/memoryGraph/sources/edges.js +17 -0
  47. package/dist/src/memoryGraph/sources/flags.js +22 -0
  48. package/dist/src/memoryGraph/sources/helpers.js +14 -0
  49. package/dist/src/memoryGraph/sources/memories.js +88 -0
  50. package/dist/src/memoryGraph/sources/raptor.js +26 -0
  51. package/dist/src/memoryGraph/sources/turns.js +176 -0
  52. package/dist/src/memoryGraph/sources.js +15 -441
  53. package/dist/src/memoryGraph-gates.test/_helpers.js +120 -0
  54. package/dist/src/ratio.bench.test/_helpers.js +198 -0
  55. package/dist/src/recall/async.js +112 -0
  56. package/dist/src/recall/format.js +149 -0
  57. package/dist/src/recall/memory.js +62 -0
  58. package/dist/src/recall/reformulate.js +82 -0
  59. package/dist/src/recall/sync.js +169 -0
  60. package/dist/src/recall/types.js +1 -0
  61. package/dist/src/recall.js +13 -530
  62. package/dist/src/store/sqlite/schema/core.js +232 -0
  63. package/dist/src/store/sqlite/schema/game.js +65 -0
  64. package/dist/src/store/sqlite/schema/plan-v2.js +107 -0
  65. package/dist/src/store/sqlite/schema/turns.js +59 -0
  66. package/dist/src/store/sqlite/schema.js +8 -441
  67. package/dist/src/store/turns/contract-compliance.test/_helpers.js +21 -0
  68. package/dist/src/store/turns/sqlite-store/admin.js +171 -0
  69. package/dist/src/store/turns/sqlite-store/ctx.js +1 -0
  70. package/dist/src/store/turns/sqlite-store/read.js +111 -0
  71. package/dist/src/store/turns/sqlite-store/rows.js +42 -0
  72. package/dist/src/store/turns/sqlite-store/write.js +102 -0
  73. package/dist/src/store/turns/sqlite-store.js +31 -367
  74. package/dist/src/vectorStore/class.js +356 -0
  75. package/dist/src/vectorStore/hash.js +10 -0
  76. package/dist/src/vectorStore/types.js +2 -0
  77. package/dist/src/vectorStore.js +8 -359
  78. package/dist/src/vectorStore.test/_helpers.js +17 -0
  79. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js → CacheTab-DPRHjYZ0.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{CacheTab-BZNnKhh5.js.map → CacheTab-DPRHjYZ0.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js → EventsTab-CgwYq-Qf.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{EventsTab-CzF5L42U.js.map → EventsTab-CgwYq-Qf.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js → HealthTab-BgOCjVzR.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{HealthTab-BLpidMrX.js.map → HealthTab-BgOCjVzR.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js +2 -0
  86. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Yw1yFpsy.js.map +1 -0
  87. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js +2 -0
  88. package/extensions/dashboard-client/dist/assets/MemoryMapTab-yHUX7Yzi.js.map +1 -0
  89. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js → MetricsTab-CcH9fP8P.js} +2 -2
  90. package/extensions/dashboard-client/dist/assets/{MetricsTab-B0PCysqs.js.map → MetricsTab-CcH9fP8P.js.map} +1 -1
  91. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js → OverviewTab-BOs8ZNev.js} +2 -2
  92. package/extensions/dashboard-client/dist/assets/{OverviewTab-DsOhqFu4.js.map → OverviewTab-BOs8ZNev.js.map} +1 -1
  93. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js → ReposTab-B-MfSPvg.js} +2 -2
  94. package/extensions/dashboard-client/dist/assets/{ReposTab-B3YKYuOf.js.map → ReposTab-B-MfSPvg.js.map} +1 -1
  95. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js → SessionsTab-BmeN649Q.js} +2 -2
  96. package/extensions/dashboard-client/dist/assets/{SessionsTab-B96FWcrC.js.map → SessionsTab-BmeN649Q.js.map} +1 -1
  97. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js +2 -0
  98. package/extensions/dashboard-client/dist/assets/SetupTab-nOknoph5.js.map +1 -0
  99. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js → TimeSavedCard-DnPg0L0z.js} +2 -2
  100. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BfkUVTvA.js.map → TimeSavedCard-DnPg0L0z.js.map} +1 -1
  101. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js → TopicsTab-BODHOg_b.js} +2 -2
  102. package/extensions/dashboard-client/dist/assets/{TopicsTab-DLRsZmXJ.js.map → TopicsTab-BODHOg_b.js.map} +1 -1
  103. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js → TurnsTab-BK0I7hKr.js} +2 -2
  104. package/extensions/dashboard-client/dist/assets/{TurnsTab-DKGU2Bf6.js.map → TurnsTab-BK0I7hKr.js.map} +1 -1
  105. package/extensions/dashboard-client/dist/assets/{index-D-lPLRWB.js → index-mOlljTFI.js} +10 -10
  106. package/extensions/dashboard-client/dist/assets/index-mOlljTFI.js.map +1 -0
  107. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js → useSSE-BYOtv1-3.js} +2 -2
  108. package/extensions/dashboard-client/dist/assets/{useSSE-ClbbcgIp.js.map → useSSE-BYOtv1-3.js.map} +1 -1
  109. package/extensions/dashboard-client/dist/index.html +1 -1
  110. package/extensions/dashboard-client/src/App.tsx +0 -4
  111. package/extensions/dashboard-client/src/api/client.ts +16 -0
  112. package/extensions/dashboard-client/src/tabs/MaintenanceTab/ActionsCard.tsx +178 -0
  113. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DbStatsCard.tsx +140 -0
  114. package/extensions/dashboard-client/src/tabs/MaintenanceTab/DebugBundleCard.tsx +109 -0
  115. package/extensions/dashboard-client/src/tabs/MaintenanceTab/HealthMitigationCard.tsx +98 -0
  116. package/extensions/dashboard-client/src/tabs/MaintenanceTab/SchemaHealthCard.tsx +108 -0
  117. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +12 -598
  118. package/extensions/dashboard-client/src/tabs/MemoryMapTab/MemoryMapView.tsx +476 -0
  119. package/extensions/dashboard-client/src/tabs/MemoryMapTab/RaptorTreeView.tsx +163 -0
  120. package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +56 -466
  121. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderHealthCard.tsx +109 -0
  122. package/extensions/dashboard-client/src/tabs/SetupTab/EmbedderSetup.tsx +468 -0
  123. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +23 -484
  124. package/extensions/dashboard-server/api-contracts/embedder-health.ts +32 -0
  125. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +360 -0
  126. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +350 -0
  127. package/extensions/dashboard-server/api-contracts/endpoints.ts +27 -648
  128. package/extensions/dashboard-server/api-contracts/index.ts +6 -0
  129. package/extensions/dashboard-server/api-contracts/raptor.ts +49 -0
  130. package/extensions/dashboard-server/api-contracts.test/_helpers.ts +47 -0
  131. package/extensions/dashboard-server/routes-cache.test/_helpers.ts +94 -0
  132. package/extensions/dashboard-server/routes-embedder-health.ts +77 -0
  133. package/extensions/dashboard-server/routes-raptor.ts +128 -0
  134. package/extensions/dashboard-server/routes-setup.ts +1 -1
  135. package/extensions/dashboard-server/routes.ts +2 -0
  136. package/extensions/dashboard-server/server.ts +4 -0
  137. package/extensions/mega-cache-replay.test/_helpers.ts +188 -0
  138. package/extensions/mega-commands/dataCommands.ts +169 -0
  139. package/extensions/mega-commands/helpers.ts +62 -0
  140. package/extensions/mega-commands/historyCommands.ts +117 -0
  141. package/extensions/mega-commands/setupCommand.ts +235 -0
  142. package/extensions/mega-commands.ts +25 -538
  143. package/extensions/mega-compact-s38.test/_helpers.ts +201 -0
  144. package/extensions/mega-compact.test/_helpers.ts +237 -0
  145. package/extensions/mega-compact.ts +12 -2
  146. package/extensions/mega-events/agent-handlers/agentEndHandler.ts +238 -0
  147. package/extensions/mega-events/agent-handlers/turnEndHandler/cacheStripe.ts +74 -0
  148. package/extensions/mega-events/agent-handlers/turnEndHandler/contextHealth.ts +50 -0
  149. package/extensions/mega-events/agent-handlers/turnEndHandler/errorRetry.ts +421 -0
  150. package/extensions/mega-events/agent-handlers/turnEndHandler/event.ts +16 -0
  151. package/extensions/mega-events/agent-handlers/turnEndHandler/gameScoring.ts +73 -0
  152. package/extensions/mega-events/agent-handlers/turnEndHandler/lengthStop.ts +29 -0
  153. package/extensions/mega-events/agent-handlers/turnEndHandler/memoryReview.ts +42 -0
  154. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +61 -0
  155. package/extensions/mega-events/agent-handlers/turnEndHandler.ts +64 -0
  156. package/extensions/mega-events/agent-handlers.ts +15 -861
  157. package/extensions/mega-events/context-handler/afterCompact.ts +213 -0
  158. package/extensions/mega-events/context-handler/messageText.ts +20 -0
  159. package/extensions/mega-events/context-handler/tailResult.ts +63 -0
  160. package/extensions/mega-events/context-handler.ts +17 -235
  161. package/extensions/mega-events/separated-prompt.test/_helpers.ts +35 -0
  162. package/package.json +1 -1
  163. package/src/config.ts +4 -0
  164. package/src/dedup-engine.test/_helpers.ts +79 -0
  165. package/src/e2e.test/_helpers.ts +59 -0
  166. package/src/embedder.ts +4 -0
  167. package/src/httpEmbedder.ts +7 -0
  168. package/src/hyde.ts +162 -0
  169. package/src/importance.test/_helpers.ts +5 -0
  170. package/src/memoryGraph/sources/checkpoints.ts +99 -0
  171. package/src/memoryGraph/sources/edges.ts +23 -0
  172. package/src/memoryGraph/sources/flags.ts +26 -0
  173. package/src/memoryGraph/sources/helpers.ts +13 -0
  174. package/src/memoryGraph/sources/memories.ts +108 -0
  175. package/src/memoryGraph/sources/raptor.ts +37 -0
  176. package/src/memoryGraph/sources/turns.ts +226 -0
  177. package/src/memoryGraph/sources.ts +19 -544
  178. package/src/memoryGraph-gates.test/_helpers.ts +164 -0
  179. package/src/ratio.bench.test/_helpers.ts +268 -0
  180. package/src/recall/async.ts +142 -0
  181. package/src/recall/format.ts +173 -0
  182. package/src/recall/memory.ts +90 -0
  183. package/src/recall/reformulate.ts +111 -0
  184. package/src/recall/sync.ts +196 -0
  185. package/src/recall/types.ts +63 -0
  186. package/src/recall.ts +25 -712
  187. package/src/store/sqlite/schema/core.ts +233 -0
  188. package/src/store/sqlite/schema/game.ts +66 -0
  189. package/src/store/sqlite/schema/plan-v2.ts +108 -0
  190. package/src/store/sqlite/schema/turns.ts +60 -0
  191. package/src/store/sqlite/schema.ts +13 -441
  192. package/src/store/turns/contract-compliance.test/_helpers.ts +38 -0
  193. package/src/store/turns/sqlite-store/admin.ts +249 -0
  194. package/src/store/turns/sqlite-store/ctx.ts +14 -0
  195. package/src/store/turns/sqlite-store/read.ts +174 -0
  196. package/src/store/turns/sqlite-store/rows.ts +55 -0
  197. package/src/store/turns/sqlite-store/write.ts +193 -0
  198. package/src/store/turns/sqlite-store.ts +33 -514
  199. package/src/vectorStore/class.ts +433 -0
  200. package/src/vectorStore/hash.ts +11 -0
  201. package/src/vectorStore/types.ts +58 -0
  202. package/src/vectorStore.test/_helpers.ts +20 -0
  203. package/src/vectorStore.ts +13 -489
  204. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js +0 -2
  205. package/extensions/dashboard-client/dist/assets/ConfigTab-DG0WkuuG.js.map +0 -1
  206. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js +0 -2
  207. package/extensions/dashboard-client/dist/assets/MaintenanceTab-BQF7W7xX.js.map +0 -1
  208. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js +0 -2
  209. package/extensions/dashboard-client/dist/assets/MemoryMapTab-CPtHLv0A.js.map +0 -1
  210. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +0 -2
  211. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +0 -1
  212. package/extensions/dashboard-client/dist/assets/index-D-lPLRWB.js.map +0 -1
@@ -1,366 +1,15 @@
1
1
  /**
2
2
  * vectorStore.ts — Layer 3 (CLUSTER): the local vector database.
3
3
  *
4
- * One store, three consumers (per PLAN.md): auto-inline on resume, on-demand
5
- * /recall-context, and the dedup sentinel. All share `add / search / dedupe`.
6
- *
7
- * Backed by the gzipped on-disk checkpoint files (store.ts). Similarity is a
8
- * linear cosine scan — checkpoint counts are small, so no ANN index is needed.
4
+ * Thin pointer / barrel: the implementation (VectorStore class), the public
5
+ * types, and the region-hash helper live in ./vectorStore/* to keep every file
6
+ * under the 500-line limit. The full public API — including the read/search
7
+ * helper re-exports is re-exported here so existing callers importing
8
+ * "./vectorStore.js" are unchanged.
9
9
  */
10
- import { createHash } from "node:crypto";
11
- import { cosineSimilarity, defaultEmbedder } from "./embedder.js";
12
- import { loadDedupConfig, } from "./config/dedup.js";
13
- import { logDecision } from "./monitoring.js";
14
- import { repoKey } from "./store/repoKey.js";
15
- import { getStateDir, normalizeSessionId, compressSmart } from "./store.js";
16
- import { computeContentDigest } from "./dedup/digest.js";
17
- import { minhashSignature, SIGNATURE_VERSION, NUM_HASHES, } from "./dedup/l1-minhash.js";
18
- import { lshBands } from "./dedup/l1-lsh.js";
19
- import { isNearDuplicate } from "./dedup/l1-verify.js";
20
- import { openBloom, saveBloom } from "./store/bloom.js";
21
- import { listCheckpoints, nextCheckpointId, upsertCheckpoint, loadSessionState, saveSessionState, upsertMinhashSignature, insertLshBuckets, lshCandidateChunks, addTokensSaved, bumpDedupStats, } from "./store/sqlite.js";
22
- import { migrateJsonToSqlite } from "./store/migrate.js";
23
- /** Default L2 semantic-dedup enable flag (trigram embedder is local, zero-network). */
24
- export const L2_ENABLED = true;
25
- /** Stable hash of a compacted region, the dedup sentinel key. */
26
- export function computeRegionHash(regionText) {
27
- // Normalize whitespace before hashing so "foo bar" and "foo bar" dedup.
28
- const normalized = regionText.replace(/\s+/g, " ").trim();
29
- return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
30
- }
31
- export class VectorStore {
32
- // These fields are `readonly` (set once in the constructor) but NOT private:
33
- // the read/search/dedup helpers split into vector-read.ts, vector-search.ts,
34
- // and vector-dedup.ts access them directly. Marking them private would force
35
- // ugly `as unknown as` casts in those modules; keeping them package-public
36
- // makes VectorStore a thin barrel whose helpers live in sibling files.
37
- embedder;
38
- stateDir;
39
- l2Threshold;
40
- /** Single source of truth for tier flags + thresholds (Sprint 14). */
41
- cfg;
42
- /** Optional monitoring target (Sprint 14). Undefined → no monitoring. */
43
- eventsPath;
44
- /**
45
- * Repo key for the async PGlite vector index (Slice 2). We use the stateDir
46
- * itself as the repo id — it is already unique per repo and available here
47
- * without crossing into the pi-runtime layer (src/ stays pi-agnostic). The
48
- * global index keys on repoId so recall can span repos.
49
- */
50
- repoId;
51
- /** S25: per-session cached rehydrated RaptorTree. Keyed by sessionId.
52
- * Freshness-validated via maxRaptorNodeBuiltAt on each lookup — a cheap
53
- * indexed MAX query replaces the O(n·leaves) rehydrate on every search. */
54
- raptorCache = new Map();
55
- constructor(opts = {}) {
56
- this.embedder = opts.embedder ?? defaultEmbedder();
57
- this.stateDir = opts.stateDir ?? getStateDir();
58
- // S25: single repo-scope key shared with the memory index (git-root
59
- // scoped; falls back to stateDir outside git).
60
- this.repoId = opts.repoId ?? repoKey(this.stateDir);
61
- // Sprint 14: all tier flags/thresholds flow from the single config source
62
- // (DedupConfig). The legacy opts.dedupSim / opts.l2Enabled remain accepted
63
- // for backward-compat callers but flags are authoritative via `cfg`.
64
- void opts.dedupSim;
65
- void opts.l2Enabled;
66
- // Sprint 12 L2 semantic tier. Threshold 0.85 is the default trigram
67
- // embedder's honest firing point; a direct override is allowed for tests.
68
- this.cfg = opts.config ?? loadDedupConfig();
69
- this.l2Threshold = opts.l2Threshold ?? this.cfg.L2_COSINE;
70
- this.eventsPath = opts.eventsPath;
71
- // Sprint 8: bring any v0.1.0 JSON checkpoint files into SQLite (idempotent).
72
- migrateJsonToSqlite(this.stateDir);
73
- // Sprint 10: warm the bloom accelerator (accelerator only — SQLite stays
74
- // source of truth; a bloom hit is always confirmed by a query below).
75
- openBloom(this.stateDir);
76
- }
77
- /** Emit a structured dedup-decision event (best-effort, never throws). */
78
- record(tier, result, reason, latencyMs, similarityScore, matchedId) {
79
- if (!this.eventsPath)
80
- return;
81
- logDecision(this.eventsPath, {
82
- ts: Date.now(),
83
- tier,
84
- result,
85
- reason,
86
- latencyMs: Math.round(latencyMs * 100) / 100,
87
- similarityScore,
88
- matchedId,
89
- });
90
- }
91
- /**
92
- * Add a checkpoint. Dedup cascade:
93
- * 1. regionHash exact match (legacy, backward-compat)
94
- * 2. summaryHash exact match (new: catches same-topic incremental compactions)
95
- * 3. content similarity ≥ dedupSim (catches near-identical summaries)
96
- * 4. If none match → create new checkpoint
97
- */
98
- add(input) {
99
- const t0 = Date.now();
100
- const sessionId = normalizeSessionId(input.sessionId);
101
- const regionHash = computeRegionHash(input.regionText);
102
- const all = listCheckpoints(sessionId, this.stateDir);
103
- // Honest "tokens saved" base for this region. For a deduped add the whole
104
- // original region is discarded (nothing new stored); for a new checkpoint
105
- // we persist (orig − stored). Falls back to stored when orig is unknown.
106
- const origTokens = input.originalTokenEstimate ?? input.tokenEstimate ?? 0;
107
- const cfg = this.cfg;
108
- // Live per-tier progress hook (Phase 1). Sync + optional; fired at each tier
109
- // so the UI can paint "L0 ✓ → L1 ✓ → L2 0.91 → stored" during a compaction.
110
- const onTier = input.onTier;
111
- // Tracks whether a tier matched while in MARK_ONLY (record-but-don't-collapse),
112
- // and which tier.
113
- let markOnly = null;
114
- // 0. L0 content-hash dedup (Sprint 9) — catches identical content arriving
115
- // under different regionText. Normalization handles case/whitespace/ANSI so
116
- // variants collapse to one row. Dual-hash guards a single-hash collision.
117
- // Sprint 10: bloom is the accelerator — a miss means "definitely new" and
118
- // skips the scan; a hit is only a candidate, confirmed against `all` below.
119
- // Gated by L0_ENABLED (Sprint 14). MARK_ONLY_L0 records the decision but
120
- // does not collapse — the new region is still stored.
121
- onTier?.({ tier: "L0", status: "scanning" });
122
- const digest = computeContentDigest(input.regionText);
123
- const bloom = openBloom(this.stateDir);
124
- if (cfg.L0_ENABLED && bloom.maybeHas(digest.contentHash)) {
125
- const contentMatch = all.find((cp) => cp.contentHash === digest.contentHash &&
126
- cp.contentHash2 === digest.contentHash2);
127
- if (contentMatch) {
128
- if (cfg.MARK_ONLY_L0) {
129
- markOnly = "L0"; // Record-but-don't-collapse: fall through.
130
- }
131
- else {
132
- contentMatch.timestamp = input.timestamp;
133
- upsertCheckpoint(contentMatch, this.stateDir);
134
- bumpDedupStats(true, this.stateDir);
135
- // Deduped: whole original region discarded, nothing new stored.
136
- addTokensSaved(origTokens, this.stateDir);
137
- const r = {
138
- checkpoint: contentMatch,
139
- deduped: true,
140
- reason: "contentHash",
141
- };
142
- this.record("L0", "deduped", "contentHash", Date.now() - t0, 1, contentMatch.checkpointId);
143
- onTier?.({ tier: "L0", status: "deduped", detail: "contentHash" });
144
- return r;
145
- }
146
- }
147
- }
148
- // 1. Legacy regionHash dedup (backward-compat) — part of L0 tier gating.
149
- if (cfg.L0_ENABLED) {
150
- const regionMatch = all.find((cp) => cp.regionHash === regionHash);
151
- if (regionMatch) {
152
- if (cfg.MARK_ONLY_L0) {
153
- markOnly = "L0"; // fall through
154
- }
155
- else {
156
- bumpDedupStats(true, this.stateDir);
157
- // Deduped: whole original region discarded, nothing new stored.
158
- addTokensSaved(origTokens, this.stateDir);
159
- const r = {
160
- checkpoint: regionMatch,
161
- deduped: true,
162
- reason: "regionHash",
163
- };
164
- this.record("L0", "deduped", "regionHash", Date.now() - t0, 1, regionMatch.checkpointId);
165
- onTier?.({ tier: "L0", status: "deduped", detail: "regionHash" });
166
- return r;
167
- }
168
- }
169
- }
170
- // 2. SummaryHash dedup — catches same-topic incremental compactions.
171
- // Full 64-hex SHA-256 (was 16-hex in Sprint 8 — collision-prone).
172
- const summaryHash = input.topicSummary
173
- ? createHash("sha256").update(input.topicSummary).digest("hex")
174
- : undefined;
175
- if (summaryHash && cfg.L0_ENABLED) {
176
- const summaryMatch = all.find((cp) => cp.summaryHash === summaryHash);
177
- if (summaryMatch) {
178
- if (cfg.MARK_ONLY_L0) {
179
- markOnly = "L0"; // fall through
180
- }
181
- else {
182
- summaryMatch.timestamp = input.timestamp;
183
- upsertCheckpoint(summaryMatch, this.stateDir);
184
- bumpDedupStats(true, this.stateDir);
185
- // Deduped: whole original region discarded, nothing new stored.
186
- addTokensSaved(origTokens, this.stateDir);
187
- const r = {
188
- checkpoint: summaryMatch,
189
- deduped: true,
190
- reason: "summaryHash",
191
- };
192
- this.record("L0", "deduped", "summaryHash", Date.now() - t0, 1, summaryMatch.checkpointId);
193
- onTier?.({ tier: "L0", status: "deduped", detail: "summaryHash" });
194
- return r;
195
- }
196
- }
197
- }
198
- // L0 did not collapse this region.
199
- onTier?.({ tier: "L0", status: "passed" });
200
- // 2b. L1 MinHash/LSH near-duplicate dedup (Sprint 11) — catches one-word
201
- // edits / rewordings that L0's exact hash misses. Cheap LSH bucket
202
- // retrieval → trigram verification (pg_trgm-equivalent) as the final gate.
203
- // Gated by L1_ENABLED (Sprint 14); MARK_ONLY_L1 records but doesn't collapse.
204
- onTier?.({ tier: "L1", status: "scanning" });
205
- if (cfg.L1_ENABLED) {
206
- const l1 = this.findL1Duplicate(sessionId, input.regionText, all);
207
- if (l1 && !cfg.MARK_ONLY_L1) {
208
- l1.timestamp = input.timestamp;
209
- upsertCheckpoint(l1, this.stateDir);
210
- bumpDedupStats(true, this.stateDir);
211
- const r = { checkpoint: l1, deduped: true, reason: "l1MinHash" };
212
- this.record("L1", "deduped", "l1MinHash", Date.now() - t0, 1, l1.checkpointId);
213
- onTier?.({ tier: "L1", status: "deduped", detail: "l1MinHash" });
214
- return r;
215
- }
216
- if (l1 && cfg.MARK_ONLY_L1)
217
- markOnly = "L1";
218
- }
219
- onTier?.({ tier: "L1", status: "passed" });
220
- // 3. L2 semantic dedup — catches near-identical / semantically-similar regions
221
- // via cosine over the embedding. topicSummary is used for summaryHash dedup
222
- // (tier 2); the vector index is keyed on the original region for backward-
223
- // compat search semantics. Threshold from cfg (L2_COSINE trigram honest
224
- // firing point). QA #13 timeout guard: if the O(n) scan exceeds the budget,
225
- // degrade to "store without dedup this pass" so we never lose a checkpoint.
226
- // Gated by L2_ENABLED (Sprint 14); MARK_ONLY_L2 records but doesn't collapse.
227
- const SIMILARITY_BUDGET_MS = cfg.SIMILARITY_BUDGET_MS;
228
- const simThreshold = this.l2Threshold; // from cfg.L2_COSINE (default 0.85 trigram)
229
- const embedding = this.embedder.embed(input.regionText);
230
- onTier?.({ tier: "L2", status: "scanning" });
231
- if (cfg.L2_ENABLED && all.length > 0) {
232
- const start = Date.now();
233
- let timedOut = false;
234
- const nearest = all.reduce((best, cp) => {
235
- if (!timedOut && Date.now() - start > SIMILARITY_BUDGET_MS)
236
- timedOut = true;
237
- if (timedOut)
238
- return best;
239
- const sim = cosineSimilarity(embedding, cp.embedding);
240
- return sim > best.sim ? { checkpoint: cp, sim } : best;
241
- }, { checkpoint: all[0], sim: -1 });
242
- if (!timedOut && nearest.sim >= simThreshold) {
243
- if (!cfg.MARK_ONLY_L2) {
244
- // Near-identical — update timestamp on existing checkpoint
245
- nearest.checkpoint.timestamp = input.timestamp;
246
- upsertCheckpoint(nearest.checkpoint, this.stateDir);
247
- bumpDedupStats(true, this.stateDir);
248
- // Deduped: whole original region discarded, nothing new stored.
249
- addTokensSaved(origTokens, this.stateDir);
250
- const r = {
251
- checkpoint: nearest.checkpoint,
252
- deduped: true,
253
- reason: "contentSimilarity",
254
- };
255
- this.record("L2", "deduped", "contentSimilarity", Date.now() - t0, nearest.sim, nearest.checkpoint.checkpointId);
256
- onTier?.({
257
- tier: "L2",
258
- status: "deduped",
259
- detail: nearest.sim.toFixed(2),
260
- });
261
- return r;
262
- }
263
- markOnly = "L2";
264
- }
265
- onTier?.({
266
- tier: "L2",
267
- status: "passed",
268
- detail: `best ${nearest.sim.toFixed(2)}`,
269
- });
270
- }
271
- // 4. Genuinely new — create checkpoint
272
- const checkpointId = nextCheckpointId(sessionId, this.stateDir);
273
- const checkpoint = {
274
- checkpointId,
275
- sessionId,
276
- repoId: this.repoId,
277
- summary: input.summary,
278
- topicSummary: input.topicSummary,
279
- summaryHash,
280
- keyDecisions: input.keyDecisions ?? [],
281
- nextSteps: input.nextSteps ?? [],
282
- filesModified: input.filesModified ?? [],
283
- tokenEstimate: input.tokenEstimate ?? 0,
284
- originalTokenEstimate: input.originalTokenEstimate,
285
- regionHash,
286
- contentHash: digest.contentHash,
287
- contentHash2: digest.contentHash2,
288
- contentHashVersion: digest.contentHashVersion,
289
- normalizedText: digest.normalizedText,
290
- compressedOriginal: compressSmart(Buffer.from(input.regionText, "utf-8"), input.compressionPressure),
291
- embedding,
292
- timestamp: input.timestamp,
293
- };
294
- // Persistence is SQLite (store/sqlite.ts). upsertCheckpoint keeps the
295
- // idempotent-by-id semantics the old JSON append implied.
296
- upsertCheckpoint(checkpoint, this.stateDir);
297
- // Cumulative "tokens saved" counter (per-repo SQLite meta). For a NEW
298
- // checkpoint the saved amount is (original − stored); for a deduped add the
299
- // whole original region is discarded (handled in the deduped return paths
300
- // below). Survives sessions and travels with the repo.
301
- const stored = input.tokenEstimate ?? 0;
302
- addTokensSaved(Math.max(0, origTokens - stored), this.stateDir);
303
- // L1: persist this checkpoint's MinHash signature + LSH buckets so future
304
- // near-duplicate inserts can find it. Deterministic given the seed.
305
- const sig = minhashSignature(input.regionText);
306
- upsertMinhashSignature(checkpointId, sessionId, SIGNATURE_VERSION, sig, this.stateDir);
307
- insertLshBuckets(checkpointId, sessionId, SIGNATURE_VERSION, lshBands(sig, sessionId, SIGNATURE_VERSION), this.stateDir);
308
- // Bloom accelerator: record the new content_hash so a future add() can short-
309
- // circuit the scan on a hit (still confirmed by the SELECT-based `all` above).
310
- bloom.add(digest.contentHash);
311
- saveBloom(this.stateDir);
312
- // Track the region hash in session state for fast sentinel checks.
313
- const state = loadSessionState(sessionId, this.stateDir);
314
- if (!state.storedRegionHashes.includes(regionHash)) {
315
- state.storedRegionHashes.push(regionHash);
316
- saveSessionState(sessionId, state, this.stateDir);
317
- }
318
- // A new checkpoint. If a tier matched while MARK_ONLY, record that (the
319
- // decision fired but we intentionally did not collapse).
320
- if (markOnly) {
321
- this.record(markOnly, "mark_only", "mark_only", Date.now() - t0);
322
- }
323
- else {
324
- this.record("L0", "new", undefined, Date.now() - t0);
325
- }
326
- // Cumulative store-wide dedup accounting (attempt, not collapsed).
327
- bumpDedupStats(false, this.stateDir);
328
- onTier?.({ tier: "new", status: "stored" });
329
- return { checkpoint, deduped: false };
330
- }
331
- /**
332
- * L1 near-duplicate lookup: MinHash → LSH candidate retrieval → trigram verify.
333
- * Returns the matching checkpoint or undefined. Bounded by a 100-candidate cap
334
- * and a 20ms verify budget (QA #7/#15) so it never hangs a large session.
335
- */
336
- findL1Duplicate(sessionId, regionText, all) {
337
- if (all.length === 0)
338
- return undefined;
339
- const sig = minhashSignature(regionText);
340
- if (sig.length !== NUM_HASHES)
341
- return undefined;
342
- const bands = lshBands(sig, sessionId, SIGNATURE_VERSION);
343
- // Cheap candidate retrieval (single query, capped). Exclude nothing yet —
344
- // the new checkpoint has no id, so pass a sentinel that never matches.
345
- const candidateIds = lshCandidateChunks(bands, sessionId, "__new__", this.stateDir, 100);
346
- if (candidateIds.length === 0)
347
- return undefined;
348
- const byId = new Map(all.map((cp) => [cp.checkpointId, cp]));
349
- const VERIFY_BUDGET_MS = 20;
350
- const start = Date.now();
351
- for (const id of candidateIds) {
352
- if (Date.now() - start > VERIFY_BUDGET_MS)
353
- break; // QA #15: abort → "not dup"
354
- const cand = byId.get(id);
355
- if (!cand)
356
- continue;
357
- const candText = cand.normalizedText ?? cand.summary ?? "";
358
- if (isNearDuplicate(regionText, candText))
359
- return cand;
360
- }
361
- return undefined;
362
- }
363
- }
10
+ export { VectorStore } from "./vectorStore/class.js";
11
+ export { computeRegionHash } from "./vectorStore/hash.js";
12
+ export { L2_ENABLED, } from "./vectorStore/types.js";
364
13
  // Re-exports (back-compat): existing call sites keep importing from "./vectorStore.js"
365
14
  export { vectorSemDedup, vectorDedupe, vectorMarkInjected, vectorWasInjected, vectorSimilarity, vectorList, vectorTopSimilar, vectorStats, vectorRepoStats, vectorDataInvariant, } from "./vector-read.js";
366
15
  // Re-exports: vectorSearch / vectorSearchAsync (moved to vector-search.ts)
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared helpers for the vectorStore.test split files.
3
+ * Extracted from src/vectorStore.test.ts: baseTmp, store(), imports.
4
+ */
5
+ import { mkdtempSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { VectorStore } from "../vectorStore.js";
9
+ export const baseTmp = mkdtempSync(join(tmpdir(), "mc-test-"));
10
+ let counter = 0;
11
+ export function store(opts = {}) {
12
+ const dir = join(baseTmp, `run-${counter++}`);
13
+ return new VectorStore({ dedupSim: opts.dedupSim ?? 0.9, stateDir: dir });
14
+ }
15
+ export function nextDir() {
16
+ return join(baseTmp, `run-${counter++}`);
17
+ }
@@ -1,2 +1,2 @@
1
- import{j as e,r as h,u as j,m as R,n as P,l as E,c as $}from"./index-D-lPLRWB.js";import{C as L,T as A}from"./TimeSavedCard-BfkUVTvA.js";import{b as v,c as u,d as H,e as M,f as o}from"./format-COjDIVGj.js";import{l as I}from"./pricing-CvE4NllQ.js";function N(a){return a>=80?"ov-stat-value-green":a>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function k(a){return a==null?"":a>=0?"ov-stat-value-green":"ov-stat-value-red"}function r({label:a,value:s,className:t,title:l}){return e.jsxs("div",{className:"ov-stat-row",children:[e.jsx("span",{className:"ov-stat-label",title:l,children:a}),e.jsx("span",{className:`ov-stat-value${t?` ${t}`:""}`,children:s})]})}function w(a){return e.jsxs("div",{className:"card provider-cache-card",children:[e.jsx(r,{label:"Cache Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:N(a.hitPct)}),e.jsx(r,{label:"Turns Recorded",value:String(a.turnCount)}),e.jsx(r,{label:"Cache Read Tokens",value:v(a.totalCacheRead)}),e.jsx(r,{label:"Cache Write Tokens",value:v(a.totalCacheWrite)}),e.jsx(r,{label:"Input Tokens",value:v(a.totalInput)}),e.jsx(r,{label:"$ Saved (reads)",value:u(a.cacheReadSaved)}),e.jsx(r,{label:"Write Investment",value:u(a.cacheWriteCost)}),e.jsx(r,{label:"Net Saved",value:u(a.netSaved),className:k(a.netSaved)}),e.jsx(r,{label:"Model",value:a.modelLabel??"—"}),e.jsx(r,{label:"Tracked Since",value:H(a.firstTurnAt)}),e.jsx(r,{label:"Last Updated",value:M(a.latestTurnAt)}),a.byModel.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-stat-section-label",children:"Per Model"}),a.byModel.map(s=>{const t=I(s.model),l=t!=null?s.totalCacheRead/1e6*t:null,n=t!=null?s.totalCacheWrite/1e6*t:null;return e.jsxs("div",{className:"ov-stat-group",children:[e.jsx(r,{label:"Model",value:s.model}),e.jsx(r,{label:"Hit Rate",value:`${s.hitPct.toFixed(1)}%`,className:N(s.hitPct)}),e.jsx(r,{label:"Cache Read",value:v(s.totalCacheRead)}),e.jsx(r,{label:"Cache Write",value:v(s.totalCacheWrite)}),e.jsx(r,{label:"Samples",value:String(s.sampleCount)}),e.jsx(r,{label:"$ Saved (reads)",value:l!=null?u(l):"—"}),e.jsx(r,{label:"Write Investment",value:n!=null?u(n):"—"})]},s.model)})]})]})}const W=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],D=["Permanent","Epoch","Topic","Thread","Volatile"],_={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function F({data:a}){const s=Math.max(...a.buckets.map(l=>l.count),1),t=_[a.health.label]??"ov-text-gray";return e.jsxs("div",{className:"ov-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Stripe Distribution"}),e.jsxs("div",{className:"ov-stripe-health-summary",children:[e.jsx("span",{className:`ov-stripe-health-badge ${t}`,children:a.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:o(a.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",o(a.health.churnRate)]}),e.jsxs("span",{className:"ov-stripe-total",children:[a.totalChunks," total chunks"]})]}),e.jsx("div",{className:"ov-stripe-bars",children:a.buckets.map(l=>{const n=s>0?Math.max(0,Math.min(100,l.count/s*100)):0,c=W[l.stripe]??"ov-bar-gray",i=l.label||(D[l.stripe]??`Stripe ${l.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:i,children:i}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${c}`,style:{width:`${n}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:l.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${o(l.minStability)} / max ${o(l.maxStability)}`,children:o(l.avgStability)})]},l.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",a.epochId??"—","· Dominant tier: ",o(a.health.dominantTier)]})]})}function m(a){return`${a.toFixed(1)}%`}function O(a){return a>=80?"ov-bar-green":a>=60?"ov-bar-teal":a>=40?"ov-bar-yellow":a>=20?"ov-bar-orange":"ov-bar-red"}const G=({perf:a})=>{const{cache_hit_pct:s}=a,t=s.samples&&s.samples.length>0?s.samples:[];return e.jsxs("div",{className:"ov-card cache-trend-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Hit-Rate Trend"}),e.jsxs("div",{className:"ov-perf-summary",children:[e.jsxs("div",{className:"ov-perf-stat",children:["Avg: ",e.jsx("strong",{children:m(s.avg)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Latest: ",e.jsx("strong",{children:m(s.latest)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Samples: ",e.jsx("strong",{children:s.n})]})]}),t.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${s.n} samples, average ${m(s.avg)}, latest ${m(s.latest)}`,children:t.map((l,n)=>{const c=Math.max(2,Math.round(l.pct*.6)),i=O(l.pct),x=n===t.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${i}${x?" ov-bar-latest":""}`,style:{height:`${c}px`},title:`${m(l.pct)} at ${new Date(l.ts).toLocaleTimeString()}`},n)})}),e.jsxs("div",{className:"ov-card-footer",children:[t.length," sample",t.length!==1?"s":""," over"," ",t.length>1?`${Math.round((t[t.length-1].ts-t[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"ov-empty-state",children:"No cache-hit samples yet"})]})};function q(){const[a,s]=h.useState(!1),{data:t,loading:l,error:n}=j(h.useCallback(()=>R(),[]),{pollInterval:5e3}),{data:c,loading:i,error:x}=j(h.useCallback(()=>P(),[]),{pollInterval:15e3}),{data:g,loading:T,error:C}=j(h.useCallback(()=>E({minutes:30}),[]),{pollInterval:15e3}),{data:d,loading:y,error:S}=j(h.useCallback(()=>$(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"cache-tab",children:[e.jsxs("div",{className:"card-section",children:[e.jsx("button",{type:"button",className:"info-toggle",onClick:()=>s(!a),children:a?"Hide Guidance":"Show Cache-Friendly Prompt Ordering Guidance"}),a&&e.jsxs("div",{className:"info-panel",children:[e.jsx("h3",{children:"Cache-Friendly Prompt Ordering"}),e.jsxs("ul",{children:[e.jsx("li",{children:"Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix."}),e.jsxs("li",{children:["Tool results and volatile content are automatically moved to the tail by message separation (",e.jsx("code",{children:"MEGACOMPACT_MESSAGE_SEPARATION"}),") so they don't invalidate the cache prefix."]}),e.jsxs("li",{children:["Cache striping (",e.jsx("code",{children:"MEGACOMPACT_CACHE_STRIPING"}),") further orders stable context by a stability score so the most durable chunks lead."]}),e.jsx("li",{children:"Avoid inserting new instructions mid-conversation — prepend them instead."})]}),d&&e.jsxs("div",{className:"cache-status-indicator",children:[e.jsx("strong",{children:"Status:"})," ",d.config.messageSeparation?"Separation Enabled":"Separation Disabled (Check env)"," |"," ",d.config.cacheStriping?"Striping Enabled":"Striping Disabled (Check env)"]})]})]}),e.jsx("h2",{children:"Provider Prompt Cache"}),l?e.jsx("p",{className:"tab-stub",children:"Loading..."}):n?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",n.message]}):t?e.jsx(w,{hitPct:t.cache.avgHitPct,turnCount:t.cache.turnCount,totalCacheRead:t.cache.totalCacheRead,totalCacheWrite:t.cache.totalCacheWrite,totalInput:t.cache.totalInput,cacheReadSaved:t.savings?.cacheReadSaved??null,cacheWriteCost:t.savings?.cacheWriteCost??null,netSaved:t.savings?.netSaved??null,modelLabel:t.savings?.model??null,firstTurnAt:t.cache.firstTurnAt,latestTurnAt:t.cache.latestTurnAt,byModel:t.cache.byModel??[]}):null,e.jsx("h2",{children:"Cache Stripe Distribution"}),i?e.jsx("p",{className:"tab-stub",children:"Loading..."}):x?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",x.message]}):c?e.jsx(F,{data:c}):null,e.jsx("h2",{children:"Cache Hit-Rate Trend"}),T?e.jsx("p",{className:"tab-stub",children:"Loading..."}):C?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",C.message]}):g?e.jsx(G,{perf:g}):null,e.jsx("h2",{children:"Mega-Compact Dedup Cache"}),y?e.jsx("p",{className:"tab-stub",children:"Loading..."}):S?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",S.message]}):d&&(()=>{const{cacheHits:p,compacts:f,timeSaved:b}=d;return e.jsxs("div",{className:"card-grid overview-card-grid",children:[e.jsx(L,{cacheHitsSession:p.session,cacheHitsTotal:p.total,tokensSavedSession:p.sessionTokensSaved,tokensSavedTotal:p.totalTokensSaved,compactionsSession:f.session,compactionsTotal:f.total}),e.jsx(A,{compactSessionSec:b.compact.sessionSec,compactTotalSec:b.compact.totalSec,cacheHitSessionSec:b.cacheHit.sessionSec,cacheHitTotalSec:b.cacheHit.totalSec})]})})()]})}export{q as default};
2
- //# sourceMappingURL=CacheTab-BZNnKhh5.js.map
1
+ import{j as e,r as h,u as j,n as R,o as P,m as E,g as $}from"./index-mOlljTFI.js";import{C as L,T as A}from"./TimeSavedCard-DnPg0L0z.js";import{b as v,c as u,d as H,e as M,f as o}from"./format-COjDIVGj.js";import{l as I}from"./pricing-CvE4NllQ.js";function N(a){return a>=80?"ov-stat-value-green":a>=50?"ov-stat-value-yellow":"ov-stat-value-red"}function k(a){return a==null?"":a>=0?"ov-stat-value-green":"ov-stat-value-red"}function r({label:a,value:s,className:t,title:l}){return e.jsxs("div",{className:"ov-stat-row",children:[e.jsx("span",{className:"ov-stat-label",title:l,children:a}),e.jsx("span",{className:`ov-stat-value${t?` ${t}`:""}`,children:s})]})}function w(a){return e.jsxs("div",{className:"card provider-cache-card",children:[e.jsx(r,{label:"Cache Hit Rate",value:`${a.hitPct.toFixed(1)}%`,className:N(a.hitPct)}),e.jsx(r,{label:"Turns Recorded",value:String(a.turnCount)}),e.jsx(r,{label:"Cache Read Tokens",value:v(a.totalCacheRead)}),e.jsx(r,{label:"Cache Write Tokens",value:v(a.totalCacheWrite)}),e.jsx(r,{label:"Input Tokens",value:v(a.totalInput)}),e.jsx(r,{label:"$ Saved (reads)",value:u(a.cacheReadSaved)}),e.jsx(r,{label:"Write Investment",value:u(a.cacheWriteCost)}),e.jsx(r,{label:"Net Saved",value:u(a.netSaved),className:k(a.netSaved)}),e.jsx(r,{label:"Model",value:a.modelLabel??"—"}),e.jsx(r,{label:"Tracked Since",value:H(a.firstTurnAt)}),e.jsx(r,{label:"Last Updated",value:M(a.latestTurnAt)}),a.byModel.length>0&&e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-stat-section-label",children:"Per Model"}),a.byModel.map(s=>{const t=I(s.model),l=t!=null?s.totalCacheRead/1e6*t:null,n=t!=null?s.totalCacheWrite/1e6*t:null;return e.jsxs("div",{className:"ov-stat-group",children:[e.jsx(r,{label:"Model",value:s.model}),e.jsx(r,{label:"Hit Rate",value:`${s.hitPct.toFixed(1)}%`,className:N(s.hitPct)}),e.jsx(r,{label:"Cache Read",value:v(s.totalCacheRead)}),e.jsx(r,{label:"Cache Write",value:v(s.totalCacheWrite)}),e.jsx(r,{label:"Samples",value:String(s.sampleCount)}),e.jsx(r,{label:"$ Saved (reads)",value:l!=null?u(l):"—"}),e.jsx(r,{label:"Write Investment",value:n!=null?u(n):"—"})]},s.model)})]})]})}const W=["ov-bar-green","ov-bar-blue","ov-bar-teal","ov-bar-yellow","ov-bar-gray"],D=["Permanent","Epoch","Topic","Thread","Volatile"],_={good:"ov-text-green",fair:"ov-text-yellow",degraded:"ov-text-orange",poor:"ov-text-red"};function F({data:a}){const s=Math.max(...a.buckets.map(l=>l.count),1),t=_[a.health.label]??"ov-text-gray";return e.jsxs("div",{className:"ov-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Stripe Distribution"}),e.jsxs("div",{className:"ov-stripe-health-summary",children:[e.jsx("span",{className:`ov-stripe-health-badge ${t}`,children:a.health.label}),e.jsx("span",{className:"ov-stripe-health-score",children:o(a.health.score)}),e.jsxs("span",{className:"ov-stripe-churn",children:["Churn: ",o(a.health.churnRate)]}),e.jsxs("span",{className:"ov-stripe-total",children:[a.totalChunks," total chunks"]})]}),e.jsx("div",{className:"ov-stripe-bars",children:a.buckets.map(l=>{const n=s>0?Math.max(0,Math.min(100,l.count/s*100)):0,c=W[l.stripe]??"ov-bar-gray",i=l.label||(D[l.stripe]??`Stripe ${l.stripe}`);return e.jsxs("div",{className:"ov-stripe-row",children:[e.jsx("span",{className:"ov-stripe-label",title:i,children:i}),e.jsx("div",{className:"ov-bar-track",children:e.jsx("div",{className:`ov-bar-fill ${c}`,style:{width:`${n}%`}})}),e.jsx("span",{className:"ov-stripe-count",children:l.count}),e.jsx("span",{className:"ov-stripe-stability",title:`min ${o(l.minStability)} / max ${o(l.maxStability)}`,children:o(l.avgStability)})]},l.stripe)})}),e.jsxs("div",{className:"ov-stripe-footer",children:["Epoch: ",a.epochId??"—","· Dominant tier: ",o(a.health.dominantTier)]})]})}function m(a){return`${a.toFixed(1)}%`}function O(a){return a>=80?"ov-bar-green":a>=60?"ov-bar-teal":a>=40?"ov-bar-yellow":a>=20?"ov-bar-orange":"ov-bar-red"}const G=({perf:a})=>{const{cache_hit_pct:s}=a,t=s.samples&&s.samples.length>0?s.samples:[];return e.jsxs("div",{className:"ov-card cache-trend-card",children:[e.jsx("h3",{className:"ov-card-title",children:"Cache Hit-Rate Trend"}),e.jsxs("div",{className:"ov-perf-summary",children:[e.jsxs("div",{className:"ov-perf-stat",children:["Avg: ",e.jsx("strong",{children:m(s.avg)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Latest: ",e.jsx("strong",{children:m(s.latest)})]}),e.jsxs("div",{className:"ov-perf-stat",children:["Samples: ",e.jsx("strong",{children:s.n})]})]}),t.length>0?e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"ov-sparkline",role:"img","aria-label":`Cache hit rate trend: ${s.n} samples, average ${m(s.avg)}, latest ${m(s.latest)}`,children:t.map((l,n)=>{const c=Math.max(2,Math.round(l.pct*.6)),i=O(l.pct),x=n===t.length-1;return e.jsx("div",{className:`ov-sparkline-bar ${i}${x?" ov-bar-latest":""}`,style:{height:`${c}px`},title:`${m(l.pct)} at ${new Date(l.ts).toLocaleTimeString()}`},n)})}),e.jsxs("div",{className:"ov-card-footer",children:[t.length," sample",t.length!==1?"s":""," over"," ",t.length>1?`${Math.round((t[t.length-1].ts-t[0].ts)/1e3)}s`:"latest turn"]})]}):e.jsx("div",{className:"ov-empty-state",children:"No cache-hit samples yet"})]})};function q(){const[a,s]=h.useState(!1),{data:t,loading:l,error:n}=j(h.useCallback(()=>R(),[]),{pollInterval:5e3}),{data:c,loading:i,error:x}=j(h.useCallback(()=>P(),[]),{pollInterval:15e3}),{data:g,loading:T,error:C}=j(h.useCallback(()=>E({minutes:30}),[]),{pollInterval:15e3}),{data:d,loading:y,error:S}=j(h.useCallback(()=>$(),[]),{pollInterval:5e3});return e.jsxs("div",{className:"cache-tab",children:[e.jsxs("div",{className:"card-section",children:[e.jsx("button",{type:"button",className:"info-toggle",onClick:()=>s(!a),children:a?"Hide Guidance":"Show Cache-Friendly Prompt Ordering Guidance"}),a&&e.jsxs("div",{className:"info-panel",children:[e.jsx("h3",{children:"Cache-Friendly Prompt Ordering"}),e.jsxs("ul",{children:[e.jsx("li",{children:"Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix."}),e.jsxs("li",{children:["Tool results and volatile content are automatically moved to the tail by message separation (",e.jsx("code",{children:"MEGACOMPACT_MESSAGE_SEPARATION"}),") so they don't invalidate the cache prefix."]}),e.jsxs("li",{children:["Cache striping (",e.jsx("code",{children:"MEGACOMPACT_CACHE_STRIPING"}),") further orders stable context by a stability score so the most durable chunks lead."]}),e.jsx("li",{children:"Avoid inserting new instructions mid-conversation — prepend them instead."})]}),d&&e.jsxs("div",{className:"cache-status-indicator",children:[e.jsx("strong",{children:"Status:"})," ",d.config.messageSeparation?"Separation Enabled":"Separation Disabled (Check env)"," |"," ",d.config.cacheStriping?"Striping Enabled":"Striping Disabled (Check env)"]})]})]}),e.jsx("h2",{children:"Provider Prompt Cache"}),l?e.jsx("p",{className:"tab-stub",children:"Loading..."}):n?e.jsxs("p",{className:"tab-stub",children:["Provider cache unavailable: ",n.message]}):t?e.jsx(w,{hitPct:t.cache.avgHitPct,turnCount:t.cache.turnCount,totalCacheRead:t.cache.totalCacheRead,totalCacheWrite:t.cache.totalCacheWrite,totalInput:t.cache.totalInput,cacheReadSaved:t.savings?.cacheReadSaved??null,cacheWriteCost:t.savings?.cacheWriteCost??null,netSaved:t.savings?.netSaved??null,modelLabel:t.savings?.model??null,firstTurnAt:t.cache.firstTurnAt,latestTurnAt:t.cache.latestTurnAt,byModel:t.cache.byModel??[]}):null,e.jsx("h2",{children:"Cache Stripe Distribution"}),i?e.jsx("p",{className:"tab-stub",children:"Loading..."}):x?e.jsxs("p",{className:"tab-stub",children:["Cache stripe data unavailable: ",x.message]}):c?e.jsx(F,{data:c}):null,e.jsx("h2",{children:"Cache Hit-Rate Trend"}),T?e.jsx("p",{className:"tab-stub",children:"Loading..."}):C?e.jsxs("p",{className:"tab-stub",children:["Hit-rate trend unavailable: ",C.message]}):g?e.jsx(G,{perf:g}):null,e.jsx("h2",{children:"Mega-Compact Dedup Cache"}),y?e.jsx("p",{className:"tab-stub",children:"Loading..."}):S?e.jsxs("p",{className:"tab-stub",children:["Snapshot unavailable: ",S.message]}):d&&(()=>{const{cacheHits:p,compacts:f,timeSaved:b}=d;return e.jsxs("div",{className:"card-grid overview-card-grid",children:[e.jsx(L,{cacheHitsSession:p.session,cacheHitsTotal:p.total,tokensSavedSession:p.sessionTokensSaved,tokensSavedTotal:p.totalTokensSaved,compactionsSession:f.session,compactionsTotal:f.total}),e.jsx(A,{compactSessionSec:b.compact.sessionSec,compactTotalSec:b.compact.totalSec,cacheHitSessionSec:b.cacheHit.sessionSec,cacheHitTotalSec:b.cacheHit.totalSec})]})})()]})}export{q as default};
2
+ //# sourceMappingURL=CacheTab-DPRHjYZ0.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CacheTab-BZNnKhh5.js","sources":["../../src/components/ProviderCacheCard.tsx","../../src/components/StripeDistributionCard.tsx","../../src/components/CacheHitRateTrendCard.tsx","../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/ProviderCacheCard.tsx — Provider prompt cache stats card.\n *\n * Displays lifetime provider prompt cache hit-rate aggregates + estimated\n * dollar savings from the /api/provider-cache endpoint.\n * Reuses the `ov-stat-row` / `StatRow` pattern from CacheHitsCard.tsx.\n *\n * B.2 spec: Cache Hit Rate with color classes, humanized token counts,\n * priced $ rows with green/red net, model name, locale date + relative time.\n */\n\nimport type React from \"react\";\nimport {\n\tfmtTokens,\n\tfmtDollars,\n\tfmtDate,\n\tfmtRelativeTime,\n} from \"../utils/format\";\nimport { lookupModelInputRate } from \"@pricing\";\n\n// ---------------------------------------------------------------------------\n// Per-model breakdown type (mirrors the API contract).\n// ---------------------------------------------------------------------------\n\nexport interface ByModelEntry {\n\tmodel: string;\n\thitPct: number;\n\ttotalCacheRead: number;\n\ttotalCacheWrite: number;\n\tsampleCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Flattened props — destructured from ProviderCacheResponse in CacheTab.\n// ---------------------------------------------------------------------------\n\nexport interface ProviderCacheCardProps {\n\t/** Cache hit rate percentage (0–100). */\n\thitPct: number;\n\t/** Number of turns with cache_hit_pct samples. */\n\tturnCount: number;\n\t/** Total cache-read tokens across all sampled turns. */\n\ttotalCacheRead: number;\n\t/** Total cache-write tokens across all sampled turns. */\n\ttotalCacheWrite: number;\n\t/** Total input tokens across all sampled turns. */\n\ttotalInput: number;\n\t/** $ saved from cache reads (null when unpriced). */\n\tcacheReadSaved: number | null;\n\t/** $ spent on cache writes (null when unpriced). */\n\tcacheWriteCost: number | null;\n\t/** netSaved = cacheReadSaved - cacheWriteCost (null when unpriced). */\n\tnetSaved: number | null;\n\t/** Model display name (modelName || modelId, or null). */\n\tmodelLabel: string | null;\n\t/** ISO timestamp of first recorded turn. */\n\tfirstTurnAt: string | null;\n\t/** ISO timestamp of latest recorded turn. */\n\tlatestTurnAt: string | null;\n\t/** Per-model breakdown (F4). */\n\tbyModel: ByModelEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Hit-rate color: green ≥80, yellow ≥50, red <50. */\nfunction hitPctClass(pct: number): string {\n\tif (pct >= 80) return \"ov-stat-value-green\";\n\tif (pct >= 50) return \"ov-stat-value-yellow\";\n\treturn \"ov-stat-value-red\";\n}\n\n/** Net-saved color: green positive, red negative. */\nfunction netClass(v: number | null): string {\n\tif (v == null) return \"\";\n\treturn v >= 0 ? \"ov-stat-value-green\" : \"ov-stat-value-red\";\n}\n\nfunction StatRow({\n\tlabel,\n\tvalue,\n\tclassName,\n\ttitle,\n}: {\n\tlabel: string;\n\tvalue: string;\n\tclassName?: string;\n\ttitle?: string;\n}): React.ReactElement {\n\treturn (\n\t\t<div className=\"ov-stat-row\">\n\t\t\t<span className=\"ov-stat-label\" title={title}>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t\t<span className={`ov-stat-value${className ? ` ${className}` : \"\"}`}>\n\t\t\t\t{value}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\nexport function ProviderCacheCard(\n\tprops: ProviderCacheCardProps,\n): React.ReactElement {\n\treturn (\n\t\t<div className=\"card provider-cache-card\">\n\t\t\t{/* --- Hit Rate + Turns --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Hit Rate\"\n\t\t\t\tvalue={`${props.hitPct.toFixed(1)}%`}\n\t\t\t\tclassName={hitPctClass(props.hitPct)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Turns Recorded\" value={String(props.turnCount)} />\n\n\t\t\t{/* --- Token counts --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Read Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheRead)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Write Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheWrite)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Input Tokens\" value={fmtTokens(props.totalInput)} />\n\n\t\t\t{/* --- Dollar rows (null when unpriced) --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\tvalue={fmtDollars(props.cacheReadSaved)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\tvalue={fmtDollars(props.cacheWriteCost)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Net Saved\"\n\t\t\t\tvalue={fmtDollars(props.netSaved)}\n\t\t\t\tclassName={netClass(props.netSaved)}\n\t\t\t/>\n\n\t\t\t{/* --- Model + timestamps --- */}\n\t\t\t<StatRow label=\"Model\" value={props.modelLabel ?? \"\\u2014\"} />\n\t\t\t<StatRow label=\"Tracked Since\" value={fmtDate(props.firstTurnAt)} />\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Last Updated\"\n\t\t\t\tvalue={fmtRelativeTime(props.latestTurnAt)}\n\t\t\t/>\n\n\t\t\t{/* --- Per-model breakdown (F4) --- */}\n\t\t\t{props.byModel.length > 0 && (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"ov-stat-section-label\">Per Model</div>\n\t\t\t\t\t{props.byModel.map((m) => {\n\t\t\t\t\t\tconst rate = lookupModelInputRate(m.model);\n\t\t\t\t\t\tconst cr =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheRead / 1_000_000) * rate : null;\n\t\t\t\t\t\tconst cw =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheWrite / 1_000_000) * rate : null;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={m.model} className=\"ov-stat-group\">\n\t\t\t\t\t\t\t\t<StatRow label=\"Model\" value={m.model} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Hit Rate\"\n\t\t\t\t\t\t\t\t\tvalue={`${m.hitPct.toFixed(1)}%`}\n\t\t\t\t\t\t\t\t\tclassName={hitPctClass(m.hitPct)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Read\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheRead)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Write\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheWrite)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow label=\"Samples\" value={String(m.sampleCount)} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\t\t\t\t\t\tvalue={cr != null ? fmtDollars(cr) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\t\t\t\t\t\tvalue={cw != null ? fmtDollars(cw) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/StripeDistributionCard.tsx — Cache stripe\n * distribution visualization (A3, PLAN_V2 Phase 4).\n *\n * Renders a horizontal bar chart of stripe buckets (0–4) with stability\n * indicators, plus a health-score summary box.\n *\n * Uses only CSS-based bar rendering — no external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { CacheStripesResponse } from \"@contracts\";\nimport { fmtPctFromFraction } from \"../utils/format\";\n\nconst STRIPE_COLORS = [\n\t\"ov-bar-green\", // 0 permanent\n\t\"ov-bar-blue\", // 1 epoch\n\t\"ov-bar-teal\", // 2 topic\n\t\"ov-bar-yellow\", // 3 thread\n\t\"ov-bar-gray\", // 4 volatile\n];\n\nconst STRIPE_LABELS = [\n\t\"Permanent\",\n\t\"Epoch\",\n\t\"Topic\",\n\t\"Thread\",\n\t\"Volatile\",\n];\n\nconst HEALTH_LABEL_COLOR: Record<string, string> = {\n\tgood: \"ov-text-green\",\n\tfair: \"ov-text-yellow\",\n\tdegraded: \"ov-text-orange\",\n\tpoor: \"ov-text-red\",\n};\n\nexport interface StripeDistributionCardProps {\n\tdata: CacheStripesResponse;\n}\n\nexport function StripeDistributionCard({\n\tdata,\n}: StripeDistributionCardProps): React.ReactElement {\n\tconst maxCount = Math.max(...data.buckets.map((b) => b.count), 1);\n\tconst healthColor = HEALTH_LABEL_COLOR[data.health.label] ?? \"ov-text-gray\";\n\n\treturn (\n\t\t<div className=\"ov-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Stripe Distribution</h3>\n\n\t\t\t{/* Health score summary */}\n\t\t\t<div className=\"ov-stripe-health-summary\">\n\t\t\t\t<span className={`ov-stripe-health-badge ${healthColor}`}>\n\t\t\t\t\t{data.health.label}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-health-score\">\n\t\t\t\t\t{fmtPctFromFraction(data.health.score)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-churn\">\n\t\t\t\t\tChurn: {fmtPctFromFraction(data.health.churnRate)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-total\">\n\t\t\t\t\t{data.totalChunks} total chunks\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t{/* Bar chart — one row per stripe */}\n\t\t\t<div className=\"ov-stripe-bars\">\n\t\t\t\t{data.buckets.map((bucket) => {\n\t\t\t\t\tconst pct =\n\t\t\t\t\t\tmaxCount > 0\n\t\t\t\t\t\t\t? Math.max(0, Math.min(100, (bucket.count / maxCount) * 100))\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst colorClass =\n\t\t\t\t\t\tSTRIPE_COLORS[bucket.stripe] ?? \"ov-bar-gray\";\n\t\t\t\t\tconst label =\n\t\t\t\t\t\tbucket.label || (STRIPE_LABELS[bucket.stripe] ?? `Stripe ${bucket.stripe}`);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={bucket.stripe} className=\"ov-stripe-row\">\n\t\t\t\t\t\t\t<span className=\"ov-stripe-label\" title={label}>\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<div className=\"ov-bar-track\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName={`ov-bar-fill ${colorClass}`}\n\t\t\t\t\t\t\t\t\tstyle={{ width: `${pct}%` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<span className=\"ov-stripe-count\">{bucket.count}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"ov-stripe-stability\"\n\t\t\t\t\t\t\t\ttitle={`min ${fmtPctFromFraction(bucket.minStability)} / max ${fmtPctFromFraction(bucket.maxStability)}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{fmtPctFromFraction(bucket.avgStability)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\n\t\t\t{/* Epoch info */}\n\t\t\t<div className=\"ov-stripe-footer\">\n\t\t\t\tEpoch: {data.epochId ?? \"—\"}\n\t\t\t\t&middot;\n\t\t\t\tDominant tier: {fmtPctFromFraction(data.health.dominantTier)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/CacheHitRateTrendCard.tsx -- Cache hit-rate\n * time-series trend chart (A3, PLAN_V2 Phase 4).\n *\n * Renders a sparkline view of cache hit rate from the /api/perf endpoint using\n * real cache_hit_pct samples with timestamps. Each bar represents one sample.\n * Shows the latest, average, and sample count.\n *\n * Uses .ov-sparkline / .ov-sparkline-bar CSS classes from cache.css.\n * No external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { PerfResponse, CacheHitSample } from \"@contracts\";\n\nexport interface CacheHitRateTrendCardProps {\n\tperf: PerfResponse;\n}\n\nfunction fmtPct(n: number): string {\n\treturn `${n.toFixed(1)}%`;\n}\n\n/**\n * Map a percentage (0–100) to a CSS color class:\n * >= 80: green (healthy)\n * >= 60: teal (fair)\n * >= 40: yellow (degraded)\n * >= 20: orange (poor)\n * < 20: red (critical)\n */\nfunction pctColorClass(pct: number): string {\n\tif (pct >= 80) return \"ov-bar-green\";\n\tif (pct >= 60) return \"ov-bar-teal\";\n\tif (pct >= 40) return \"ov-bar-yellow\";\n\tif (pct >= 20) return \"ov-bar-orange\";\n\treturn \"ov-bar-red\";\n}\n\nexport const CacheHitRateTrendCard: React.FC<CacheHitRateTrendCardProps> = ({\n\tperf,\n}) => {\n\tconst { cache_hit_pct: c } = perf;\n\tconst samples: CacheHitSample[] =\n\t\tc.samples && c.samples.length > 0 ? c.samples : [];\n\n\treturn (\n\t\t<div className=\"ov-card cache-trend-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Hit-Rate Trend</h3>\n\n\t\t\t{/* Metric summary */}\n\t\t\t<div className=\"ov-perf-summary\">\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tAvg: <strong>{fmtPct(c.avg)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tLatest: <strong>{fmtPct(c.latest)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tSamples: <strong>{c.n}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Sparkline bars from real samples */}\n\t\t\t{samples.length > 0 ? (\n\t\t\t\t<>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"ov-sparkline\"\n\t\t\t\t\t\trole=\"img\"\n\t\t\t\t\t\taria-label={`Cache hit rate trend: ${c.n} samples, average ${fmtPct(c.avg)}, latest ${fmtPct(c.latest)}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{samples.map((s, i) => {\n\t\t\t\t\t\t\tconst barH = Math.max(2, Math.round(s.pct * 0.6));\n\t\t\t\t\t\t\tconst colorClass = pctColorClass(s.pct);\n\t\t\t\t\t\t\tconst isLatest = i === samples.length - 1;\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\tclassName={`ov-sparkline-bar ${colorClass}${isLatest ? \" ov-bar-latest\" : \"\"}`}\n\t\t\t\t\t\t\t\t\tstyle={{ height: `${barH}px` }}\n\t\t\t\t\t\t\t\t\ttitle={`${fmtPct(s.pct)} at ${new Date(s.ts).toLocaleTimeString()}`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"ov-card-footer\">\n\t\t\t\t\t\t{samples.length} sample{samples.length !== 1 ? \"s\" : \"\"} over{\" \"}\n\t\t\t\t\t\t{samples.length > 1\n\t\t\t\t\t\t\t? `${Math.round(\n\t\t\t\t\t\t\t\t\t(samples[samples.length - 1].ts - samples[0].ts) / 1000,\n\t\t\t\t\t\t\t\t)}s`\n\t\t\t\t\t\t\t: \"latest turn\"}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t<div className=\"ov-empty-state\">No cache-hit samples yet</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n","/**\n * dashboard-client/src/tabs/CacheTab.tsx -- Cache tab (B.3 + A3).\n *\n * Sections:\n * 1. Provider Prompt Cache -> ProviderCacheCard (/api/provider-cache)\n * 2. Cache Stripe Distribution -> StripeDistributionCard (/api/cache-stripes)\n * 3. Cache Hit-Rate Trend -> CacheHitRateTrendCard (/api/perf)\n * 4. Mega-Compact Dedup Cache -> CacheHitsCard + TimeSavedCard (/api/snapshot)\n *\n * Each section fetches independently; failures in one do not affect others.\n * 5s polling on provider cache and activity snapshot; 15s on stripes + trend.\n */\n\nimport { useState, useCallback } from \"react\";\nimport type {\n\tSnapshotResponse,\n\tProviderCacheResponse,\n\tCacheStripesResponse,\n\tPerfResponse,\n} from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport {\n\tfetchSnapshot,\n\tfetchProviderCache,\n\tfetchCacheStripes,\n\tfetchPerf,\n} from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\nimport { ProviderCacheCard } from \"../components/ProviderCacheCard\";\nimport { StripeDistributionCard } from \"../components/StripeDistributionCard\";\nimport { CacheHitRateTrendCard } from \"../components/CacheHitRateTrendCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\tconst [infoExpanded, setInfoExpanded] = useState(false);\n\t/* --- Provider prompt cache (5s poll) --- */\n\tconst {\n\t\tdata: providerCache,\n\t\tloading: pcLoading,\n\t\terror: pcError,\n\t} = useApi<ProviderCacheResponse>(\n\t\tuseCallback(() => fetchProviderCache(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\t/* --- Cache stripe distribution (15s poll) --- */\n\tconst {\n\t\tdata: stripes,\n\t\tloading: stripesLoading,\n\t\terror: stripesError,\n\t} = useApi<CacheStripesResponse>(\n\t\tuseCallback(() => fetchCacheStripes(), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Cache hit-rate trend from perf samples (15s poll) --- */\n\tconst {\n\t\tdata: perf,\n\t\tloading: perfLoading,\n\t\terror: perfError,\n\t} = useApi<PerfResponse>(\n\t\tuseCallback(() => fetchPerf({ minutes: 30 }), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Dedup cache snapshot (5s poll) --- */\n\tconst {\n\t\tdata: snapshot,\n\t\tloading: snapLoading,\n\t\terror: snapError,\n\t} = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t<div className=\"card-section\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"info-toggle\"\n\t\t\t\t\tonClick={() => setInfoExpanded(!infoExpanded)}\n\t\t\t\t>\n\t\t\t\t\t{infoExpanded ? \"Hide Guidance\" : \"Show Cache-Friendly Prompt Ordering Guidance\"}\n\t\t\t\t</button>\n\t\t\t\t{infoExpanded && (\n\t\t\t\t\t<div className=\"info-panel\">\n\t\t\t\t\t\t<h3>Cache-Friendly Prompt Ordering</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>\n\t\t\t\t\t\t\t<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>\n\t\t\t\t\t\t\t<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>\n\t\t\t\t\t\t\t<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t{snapshot && (\n\t\t\t\t\t\t\t<div className=\"cache-status-indicator\">\n\t\t\t\t\t\t\t\t<strong>Status:</strong>{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.messageSeparation ? \"Separation Enabled\" : \"Separation Disabled (Check env)\"} |{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.cacheStriping ? \"Striping Enabled\" : \"Striping Disabled (Check env)\"}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t{/* ==============================================================\n\t\t\t Section 1 -- Provider Prompt Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Provider Prompt Cache</h2>\n\t\t\t{pcLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : pcError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tProvider cache unavailable: {pcError.message}\n\t\t\t\t</p>\n\t\t\t) : providerCache ? (\n\t\t\t\t<ProviderCacheCard\n\t\t\t\t\thitPct={providerCache.cache.avgHitPct}\n\t\t\t\t\tturnCount={providerCache.cache.turnCount}\n\t\t\t\t\ttotalCacheRead={providerCache.cache.totalCacheRead}\n\t\t\t\t\ttotalCacheWrite={providerCache.cache.totalCacheWrite}\n\t\t\t\t\ttotalInput={providerCache.cache.totalInput}\n\t\t\t\t\tcacheReadSaved={providerCache.savings?.cacheReadSaved ?? null}\n\t\t\t\t\tcacheWriteCost={providerCache.savings?.cacheWriteCost ?? null}\n\t\t\t\t\tnetSaved={providerCache.savings?.netSaved ?? null}\n\t\t\t\t\tmodelLabel={providerCache.savings?.model ?? null}\n\t\t\t\t\tfirstTurnAt={providerCache.cache.firstTurnAt}\n\t\t\t\t\tlatestTurnAt={providerCache.cache.latestTurnAt}\n\t\t\t\t\tbyModel={providerCache.cache.byModel ?? []}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 2 -- Cache Stripe Distribution\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Stripe Distribution</h2>\n\t\t\t{stripesLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : stripesError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tCache stripe data unavailable: {stripesError.message}\n\t\t\t\t</p>\n\t\t\t) : stripes ? (\n\t\t\t\t<StripeDistributionCard data={stripes} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 3 -- Cache Hit-Rate Trend\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Hit-Rate Trend</h2>\n\t\t\t{perfLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : perfError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tHit-rate trend unavailable: {perfError.message}\n\t\t\t\t</p>\n\t\t\t) : perf ? (\n\t\t\t\t<CacheHitRateTrendCard perf={perf} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 4 -- Mega-Compact Dedup Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Mega-Compact Dedup Cache</h2>\n\t\t\t{snapLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : snapError ? (\n\t\t\t\t<p className=\"tab-stub\">Snapshot unavailable: {snapError.message}</p>\n\t\t\t) : (\n\t\t\t\tsnapshot &&\n\t\t\t\t(() => {\n\t\t\t\t\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t\t\t\t<CacheHitsCard\n\t\t\t\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<TimeSavedCard\n\t\t\t\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})()\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["hitPctClass","pct","netClass","v","StatRow","label","value","className","title","jsxs","jsx","ProviderCacheCard","props","fmtTokens","fmtDollars","fmtDate","fmtRelativeTime","Fragment","m","rate","lookupModelInputRate","cr","cw","STRIPE_COLORS","STRIPE_LABELS","HEALTH_LABEL_COLOR","StripeDistributionCard","data","maxCount","b","healthColor","fmtPctFromFraction","bucket","colorClass","fmtPct","n","pctColorClass","CacheHitRateTrendCard","perf","c","samples","s","i","barH","isLatest","CacheTab","infoExpanded","setInfoExpanded","useState","providerCache","pcLoading","pcError","useApi","useCallback","fetchProviderCache","stripes","stripesLoading","stripesError","fetchCacheStripes","perfLoading","perfError","fetchPerf","snapshot","snapLoading","snapError","fetchSnapshot","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"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"}
1
+ {"version":3,"file":"CacheTab-DPRHjYZ0.js","sources":["../../src/components/ProviderCacheCard.tsx","../../src/components/StripeDistributionCard.tsx","../../src/components/CacheHitRateTrendCard.tsx","../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/ProviderCacheCard.tsx — Provider prompt cache stats card.\n *\n * Displays lifetime provider prompt cache hit-rate aggregates + estimated\n * dollar savings from the /api/provider-cache endpoint.\n * Reuses the `ov-stat-row` / `StatRow` pattern from CacheHitsCard.tsx.\n *\n * B.2 spec: Cache Hit Rate with color classes, humanized token counts,\n * priced $ rows with green/red net, model name, locale date + relative time.\n */\n\nimport type React from \"react\";\nimport {\n\tfmtTokens,\n\tfmtDollars,\n\tfmtDate,\n\tfmtRelativeTime,\n} from \"../utils/format\";\nimport { lookupModelInputRate } from \"@pricing\";\n\n// ---------------------------------------------------------------------------\n// Per-model breakdown type (mirrors the API contract).\n// ---------------------------------------------------------------------------\n\nexport interface ByModelEntry {\n\tmodel: string;\n\thitPct: number;\n\ttotalCacheRead: number;\n\ttotalCacheWrite: number;\n\tsampleCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Flattened props — destructured from ProviderCacheResponse in CacheTab.\n// ---------------------------------------------------------------------------\n\nexport interface ProviderCacheCardProps {\n\t/** Cache hit rate percentage (0–100). */\n\thitPct: number;\n\t/** Number of turns with cache_hit_pct samples. */\n\tturnCount: number;\n\t/** Total cache-read tokens across all sampled turns. */\n\ttotalCacheRead: number;\n\t/** Total cache-write tokens across all sampled turns. */\n\ttotalCacheWrite: number;\n\t/** Total input tokens across all sampled turns. */\n\ttotalInput: number;\n\t/** $ saved from cache reads (null when unpriced). */\n\tcacheReadSaved: number | null;\n\t/** $ spent on cache writes (null when unpriced). */\n\tcacheWriteCost: number | null;\n\t/** netSaved = cacheReadSaved - cacheWriteCost (null when unpriced). */\n\tnetSaved: number | null;\n\t/** Model display name (modelName || modelId, or null). */\n\tmodelLabel: string | null;\n\t/** ISO timestamp of first recorded turn. */\n\tfirstTurnAt: string | null;\n\t/** ISO timestamp of latest recorded turn. */\n\tlatestTurnAt: string | null;\n\t/** Per-model breakdown (F4). */\n\tbyModel: ByModelEntry[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Hit-rate color: green ≥80, yellow ≥50, red <50. */\nfunction hitPctClass(pct: number): string {\n\tif (pct >= 80) return \"ov-stat-value-green\";\n\tif (pct >= 50) return \"ov-stat-value-yellow\";\n\treturn \"ov-stat-value-red\";\n}\n\n/** Net-saved color: green positive, red negative. */\nfunction netClass(v: number | null): string {\n\tif (v == null) return \"\";\n\treturn v >= 0 ? \"ov-stat-value-green\" : \"ov-stat-value-red\";\n}\n\nfunction StatRow({\n\tlabel,\n\tvalue,\n\tclassName,\n\ttitle,\n}: {\n\tlabel: string;\n\tvalue: string;\n\tclassName?: string;\n\ttitle?: string;\n}): React.ReactElement {\n\treturn (\n\t\t<div className=\"ov-stat-row\">\n\t\t\t<span className=\"ov-stat-label\" title={title}>\n\t\t\t\t{label}\n\t\t\t</span>\n\t\t\t<span className={`ov-stat-value${className ? ` ${className}` : \"\"}`}>\n\t\t\t\t{value}\n\t\t\t</span>\n\t\t</div>\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Card\n// ---------------------------------------------------------------------------\n\nexport function ProviderCacheCard(\n\tprops: ProviderCacheCardProps,\n): React.ReactElement {\n\treturn (\n\t\t<div className=\"card provider-cache-card\">\n\t\t\t{/* --- Hit Rate + Turns --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Hit Rate\"\n\t\t\t\tvalue={`${props.hitPct.toFixed(1)}%`}\n\t\t\t\tclassName={hitPctClass(props.hitPct)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Turns Recorded\" value={String(props.turnCount)} />\n\n\t\t\t{/* --- Token counts --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Read Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheRead)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Cache Write Tokens\"\n\t\t\t\tvalue={fmtTokens(props.totalCacheWrite)}\n\t\t\t/>\n\t\t\t<StatRow label=\"Input Tokens\" value={fmtTokens(props.totalInput)} />\n\n\t\t\t{/* --- Dollar rows (null when unpriced) --- */}\n\t\t\t<StatRow\n\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\tvalue={fmtDollars(props.cacheReadSaved)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\tvalue={fmtDollars(props.cacheWriteCost)}\n\t\t\t/>\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Net Saved\"\n\t\t\t\tvalue={fmtDollars(props.netSaved)}\n\t\t\t\tclassName={netClass(props.netSaved)}\n\t\t\t/>\n\n\t\t\t{/* --- Model + timestamps --- */}\n\t\t\t<StatRow label=\"Model\" value={props.modelLabel ?? \"\\u2014\"} />\n\t\t\t<StatRow label=\"Tracked Since\" value={fmtDate(props.firstTurnAt)} />\n\t\t\t<StatRow\n\t\t\t\tlabel=\"Last Updated\"\n\t\t\t\tvalue={fmtRelativeTime(props.latestTurnAt)}\n\t\t\t/>\n\n\t\t\t{/* --- Per-model breakdown (F4) --- */}\n\t\t\t{props.byModel.length > 0 && (\n\t\t\t\t<>\n\t\t\t\t\t<div className=\"ov-stat-section-label\">Per Model</div>\n\t\t\t\t\t{props.byModel.map((m) => {\n\t\t\t\t\t\tconst rate = lookupModelInputRate(m.model);\n\t\t\t\t\t\tconst cr =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheRead / 1_000_000) * rate : null;\n\t\t\t\t\t\tconst cw =\n\t\t\t\t\t\t\trate != null ? (m.totalCacheWrite / 1_000_000) * rate : null;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={m.model} className=\"ov-stat-group\">\n\t\t\t\t\t\t\t\t<StatRow label=\"Model\" value={m.model} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Hit Rate\"\n\t\t\t\t\t\t\t\t\tvalue={`${m.hitPct.toFixed(1)}%`}\n\t\t\t\t\t\t\t\t\tclassName={hitPctClass(m.hitPct)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Read\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheRead)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Cache Write\"\n\t\t\t\t\t\t\t\t\tvalue={fmtTokens(m.totalCacheWrite)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow label=\"Samples\" value={String(m.sampleCount)} />\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"$ Saved (reads)\"\n\t\t\t\t\t\t\t\t\tvalue={cr != null ? fmtDollars(cr) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t<StatRow\n\t\t\t\t\t\t\t\t\tlabel=\"Write Investment\"\n\t\t\t\t\t\t\t\t\tvalue={cw != null ? fmtDollars(cw) : \"\\u2014\"}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/StripeDistributionCard.tsx — Cache stripe\n * distribution visualization (A3, PLAN_V2 Phase 4).\n *\n * Renders a horizontal bar chart of stripe buckets (0–4) with stability\n * indicators, plus a health-score summary box.\n *\n * Uses only CSS-based bar rendering — no external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { CacheStripesResponse } from \"@contracts\";\nimport { fmtPctFromFraction } from \"../utils/format\";\n\nconst STRIPE_COLORS = [\n\t\"ov-bar-green\", // 0 permanent\n\t\"ov-bar-blue\", // 1 epoch\n\t\"ov-bar-teal\", // 2 topic\n\t\"ov-bar-yellow\", // 3 thread\n\t\"ov-bar-gray\", // 4 volatile\n];\n\nconst STRIPE_LABELS = [\n\t\"Permanent\",\n\t\"Epoch\",\n\t\"Topic\",\n\t\"Thread\",\n\t\"Volatile\",\n];\n\nconst HEALTH_LABEL_COLOR: Record<string, string> = {\n\tgood: \"ov-text-green\",\n\tfair: \"ov-text-yellow\",\n\tdegraded: \"ov-text-orange\",\n\tpoor: \"ov-text-red\",\n};\n\nexport interface StripeDistributionCardProps {\n\tdata: CacheStripesResponse;\n}\n\nexport function StripeDistributionCard({\n\tdata,\n}: StripeDistributionCardProps): React.ReactElement {\n\tconst maxCount = Math.max(...data.buckets.map((b) => b.count), 1);\n\tconst healthColor = HEALTH_LABEL_COLOR[data.health.label] ?? \"ov-text-gray\";\n\n\treturn (\n\t\t<div className=\"ov-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Stripe Distribution</h3>\n\n\t\t\t{/* Health score summary */}\n\t\t\t<div className=\"ov-stripe-health-summary\">\n\t\t\t\t<span className={`ov-stripe-health-badge ${healthColor}`}>\n\t\t\t\t\t{data.health.label}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-health-score\">\n\t\t\t\t\t{fmtPctFromFraction(data.health.score)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-churn\">\n\t\t\t\t\tChurn: {fmtPctFromFraction(data.health.churnRate)}\n\t\t\t\t</span>\n\t\t\t\t<span className=\"ov-stripe-total\">\n\t\t\t\t\t{data.totalChunks} total chunks\n\t\t\t\t</span>\n\t\t\t</div>\n\n\t\t\t{/* Bar chart — one row per stripe */}\n\t\t\t<div className=\"ov-stripe-bars\">\n\t\t\t\t{data.buckets.map((bucket) => {\n\t\t\t\t\tconst pct =\n\t\t\t\t\t\tmaxCount > 0\n\t\t\t\t\t\t\t? Math.max(0, Math.min(100, (bucket.count / maxCount) * 100))\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst colorClass =\n\t\t\t\t\t\tSTRIPE_COLORS[bucket.stripe] ?? \"ov-bar-gray\";\n\t\t\t\t\tconst label =\n\t\t\t\t\t\tbucket.label || (STRIPE_LABELS[bucket.stripe] ?? `Stripe ${bucket.stripe}`);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={bucket.stripe} className=\"ov-stripe-row\">\n\t\t\t\t\t\t\t<span className=\"ov-stripe-label\" title={label}>\n\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<div className=\"ov-bar-track\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName={`ov-bar-fill ${colorClass}`}\n\t\t\t\t\t\t\t\t\tstyle={{ width: `${pct}%` }}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<span className=\"ov-stripe-count\">{bucket.count}</span>\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclassName=\"ov-stripe-stability\"\n\t\t\t\t\t\t\t\ttitle={`min ${fmtPctFromFraction(bucket.minStability)} / max ${fmtPctFromFraction(bucket.maxStability)}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{fmtPctFromFraction(bucket.avgStability)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\n\t\t\t{/* Epoch info */}\n\t\t\t<div className=\"ov-stripe-footer\">\n\t\t\t\tEpoch: {data.epochId ?? \"—\"}\n\t\t\t\t&middot;\n\t\t\t\tDominant tier: {fmtPctFromFraction(data.health.dominantTier)}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","/**\n * dashboard-client/src/components/CacheHitRateTrendCard.tsx -- Cache hit-rate\n * time-series trend chart (A3, PLAN_V2 Phase 4).\n *\n * Renders a sparkline view of cache hit rate from the /api/perf endpoint using\n * real cache_hit_pct samples with timestamps. Each bar represents one sample.\n * Shows the latest, average, and sample count.\n *\n * Uses .ov-sparkline / .ov-sparkline-bar CSS classes from cache.css.\n * No external charting dependencies.\n */\n\nimport type React from \"react\";\nimport type { PerfResponse, CacheHitSample } from \"@contracts\";\n\nexport interface CacheHitRateTrendCardProps {\n\tperf: PerfResponse;\n}\n\nfunction fmtPct(n: number): string {\n\treturn `${n.toFixed(1)}%`;\n}\n\n/**\n * Map a percentage (0–100) to a CSS color class:\n * >= 80: green (healthy)\n * >= 60: teal (fair)\n * >= 40: yellow (degraded)\n * >= 20: orange (poor)\n * < 20: red (critical)\n */\nfunction pctColorClass(pct: number): string {\n\tif (pct >= 80) return \"ov-bar-green\";\n\tif (pct >= 60) return \"ov-bar-teal\";\n\tif (pct >= 40) return \"ov-bar-yellow\";\n\tif (pct >= 20) return \"ov-bar-orange\";\n\treturn \"ov-bar-red\";\n}\n\nexport const CacheHitRateTrendCard: React.FC<CacheHitRateTrendCardProps> = ({\n\tperf,\n}) => {\n\tconst { cache_hit_pct: c } = perf;\n\tconst samples: CacheHitSample[] =\n\t\tc.samples && c.samples.length > 0 ? c.samples : [];\n\n\treturn (\n\t\t<div className=\"ov-card cache-trend-card\">\n\t\t\t<h3 className=\"ov-card-title\">Cache Hit-Rate Trend</h3>\n\n\t\t\t{/* Metric summary */}\n\t\t\t<div className=\"ov-perf-summary\">\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tAvg: <strong>{fmtPct(c.avg)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tLatest: <strong>{fmtPct(c.latest)}</strong>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"ov-perf-stat\">\n\t\t\t\t\tSamples: <strong>{c.n}</strong>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t{/* Sparkline bars from real samples */}\n\t\t\t{samples.length > 0 ? (\n\t\t\t\t<>\n\t\t\t\t\t<div\n\t\t\t\t\t\tclassName=\"ov-sparkline\"\n\t\t\t\t\t\trole=\"img\"\n\t\t\t\t\t\taria-label={`Cache hit rate trend: ${c.n} samples, average ${fmtPct(c.avg)}, latest ${fmtPct(c.latest)}`}\n\t\t\t\t\t>\n\t\t\t\t\t\t{samples.map((s, i) => {\n\t\t\t\t\t\t\tconst barH = Math.max(2, Math.round(s.pct * 0.6));\n\t\t\t\t\t\t\tconst colorClass = pctColorClass(s.pct);\n\t\t\t\t\t\t\tconst isLatest = i === samples.length - 1;\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={i}\n\t\t\t\t\t\t\t\t\tclassName={`ov-sparkline-bar ${colorClass}${isLatest ? \" ov-bar-latest\" : \"\"}`}\n\t\t\t\t\t\t\t\t\tstyle={{ height: `${barH}px` }}\n\t\t\t\t\t\t\t\t\ttitle={`${fmtPct(s.pct)} at ${new Date(s.ts).toLocaleTimeString()}`}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t})}\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"ov-card-footer\">\n\t\t\t\t\t\t{samples.length} sample{samples.length !== 1 ? \"s\" : \"\"} over{\" \"}\n\t\t\t\t\t\t{samples.length > 1\n\t\t\t\t\t\t\t? `${Math.round(\n\t\t\t\t\t\t\t\t\t(samples[samples.length - 1].ts - samples[0].ts) / 1000,\n\t\t\t\t\t\t\t\t)}s`\n\t\t\t\t\t\t\t: \"latest turn\"}\n\t\t\t\t\t</div>\n\t\t\t\t</>\n\t\t\t) : (\n\t\t\t\t<div className=\"ov-empty-state\">No cache-hit samples yet</div>\n\t\t\t)}\n\t\t</div>\n\t);\n};\n","/**\n * dashboard-client/src/tabs/CacheTab.tsx -- Cache tab (B.3 + A3).\n *\n * Sections:\n * 1. Provider Prompt Cache -> ProviderCacheCard (/api/provider-cache)\n * 2. Cache Stripe Distribution -> StripeDistributionCard (/api/cache-stripes)\n * 3. Cache Hit-Rate Trend -> CacheHitRateTrendCard (/api/perf)\n * 4. Mega-Compact Dedup Cache -> CacheHitsCard + TimeSavedCard (/api/snapshot)\n *\n * Each section fetches independently; failures in one do not affect others.\n * 5s polling on provider cache and activity snapshot; 15s on stripes + trend.\n */\n\nimport { useState, useCallback } from \"react\";\nimport type {\n\tSnapshotResponse,\n\tProviderCacheResponse,\n\tCacheStripesResponse,\n\tPerfResponse,\n} from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport {\n\tfetchSnapshot,\n\tfetchProviderCache,\n\tfetchCacheStripes,\n\tfetchPerf,\n} from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\nimport { ProviderCacheCard } from \"../components/ProviderCacheCard\";\nimport { StripeDistributionCard } from \"../components/StripeDistributionCard\";\nimport { CacheHitRateTrendCard } from \"../components/CacheHitRateTrendCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\tconst [infoExpanded, setInfoExpanded] = useState(false);\n\t/* --- Provider prompt cache (5s poll) --- */\n\tconst {\n\t\tdata: providerCache,\n\t\tloading: pcLoading,\n\t\terror: pcError,\n\t} = useApi<ProviderCacheResponse>(\n\t\tuseCallback(() => fetchProviderCache(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\t/* --- Cache stripe distribution (15s poll) --- */\n\tconst {\n\t\tdata: stripes,\n\t\tloading: stripesLoading,\n\t\terror: stripesError,\n\t} = useApi<CacheStripesResponse>(\n\t\tuseCallback(() => fetchCacheStripes(), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Cache hit-rate trend from perf samples (15s poll) --- */\n\tconst {\n\t\tdata: perf,\n\t\tloading: perfLoading,\n\t\terror: perfError,\n\t} = useApi<PerfResponse>(\n\t\tuseCallback(() => fetchPerf({ minutes: 30 }), []),\n\t\t{ pollInterval: 15000 },\n\t);\n\n\t/* --- Dedup cache snapshot (5s poll) --- */\n\tconst {\n\t\tdata: snapshot,\n\t\tloading: snapLoading,\n\t\terror: snapError,\n\t} = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t<div className=\"card-section\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"info-toggle\"\n\t\t\t\t\tonClick={() => setInfoExpanded(!infoExpanded)}\n\t\t\t\t>\n\t\t\t\t\t{infoExpanded ? \"Hide Guidance\" : \"Show Cache-Friendly Prompt Ordering Guidance\"}\n\t\t\t\t</button>\n\t\t\t\t{infoExpanded && (\n\t\t\t\t\t<div className=\"info-panel\">\n\t\t\t\t\t\t<h3>Cache-Friendly Prompt Ordering</h3>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>\n\t\t\t\t\t\t\t<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>\n\t\t\t\t\t\t\t<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>\n\t\t\t\t\t\t\t<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t\t{snapshot && (\n\t\t\t\t\t\t\t<div className=\"cache-status-indicator\">\n\t\t\t\t\t\t\t\t<strong>Status:</strong>{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.messageSeparation ? \"Separation Enabled\" : \"Separation Disabled (Check env)\"} |{\" \"}\n\t\t\t\t\t\t\t\t{snapshot.config.cacheStriping ? \"Striping Enabled\" : \"Striping Disabled (Check env)\"}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t{/* ==============================================================\n\t\t\t Section 1 -- Provider Prompt Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Provider Prompt Cache</h2>\n\t\t\t{pcLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : pcError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tProvider cache unavailable: {pcError.message}\n\t\t\t\t</p>\n\t\t\t) : providerCache ? (\n\t\t\t\t<ProviderCacheCard\n\t\t\t\t\thitPct={providerCache.cache.avgHitPct}\n\t\t\t\t\tturnCount={providerCache.cache.turnCount}\n\t\t\t\t\ttotalCacheRead={providerCache.cache.totalCacheRead}\n\t\t\t\t\ttotalCacheWrite={providerCache.cache.totalCacheWrite}\n\t\t\t\t\ttotalInput={providerCache.cache.totalInput}\n\t\t\t\t\tcacheReadSaved={providerCache.savings?.cacheReadSaved ?? null}\n\t\t\t\t\tcacheWriteCost={providerCache.savings?.cacheWriteCost ?? null}\n\t\t\t\t\tnetSaved={providerCache.savings?.netSaved ?? null}\n\t\t\t\t\tmodelLabel={providerCache.savings?.model ?? null}\n\t\t\t\t\tfirstTurnAt={providerCache.cache.firstTurnAt}\n\t\t\t\t\tlatestTurnAt={providerCache.cache.latestTurnAt}\n\t\t\t\t\tbyModel={providerCache.cache.byModel ?? []}\n\t\t\t\t/>\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 2 -- Cache Stripe Distribution\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Stripe Distribution</h2>\n\t\t\t{stripesLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : stripesError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tCache stripe data unavailable: {stripesError.message}\n\t\t\t\t</p>\n\t\t\t) : stripes ? (\n\t\t\t\t<StripeDistributionCard data={stripes} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 3 -- Cache Hit-Rate Trend\n\t\t\t ============================================================== */}\n\t\t\t<h2>Cache Hit-Rate Trend</h2>\n\t\t\t{perfLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : perfError ? (\n\t\t\t\t<p className=\"tab-stub\">\n\t\t\t\t\tHit-rate trend unavailable: {perfError.message}\n\t\t\t\t</p>\n\t\t\t) : perf ? (\n\t\t\t\t<CacheHitRateTrendCard perf={perf} />\n\t\t\t) : null}\n\n\t\t\t{/* ==============================================================\n\t\t\t Section 4 -- Mega-Compact Dedup Cache\n\t\t\t ============================================================== */}\n\t\t\t<h2>Mega-Compact Dedup Cache</h2>\n\t\t\t{snapLoading ? (\n\t\t\t\t<p className=\"tab-stub\">Loading...</p>\n\t\t\t) : snapError ? (\n\t\t\t\t<p className=\"tab-stub\">Snapshot unavailable: {snapError.message}</p>\n\t\t\t) : (\n\t\t\t\tsnapshot &&\n\t\t\t\t(() => {\n\t\t\t\t\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t\t\t\t<CacheHitsCard\n\t\t\t\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<TimeSavedCard\n\t\t\t\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})()\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["hitPctClass","pct","netClass","v","StatRow","label","value","className","title","jsxs","jsx","ProviderCacheCard","props","fmtTokens","fmtDollars","fmtDate","fmtRelativeTime","Fragment","m","rate","lookupModelInputRate","cr","cw","STRIPE_COLORS","STRIPE_LABELS","HEALTH_LABEL_COLOR","StripeDistributionCard","data","maxCount","b","healthColor","fmtPctFromFraction","bucket","colorClass","fmtPct","n","pctColorClass","CacheHitRateTrendCard","perf","c","samples","s","i","barH","isLatest","CacheTab","infoExpanded","setInfoExpanded","useState","providerCache","pcLoading","pcError","useApi","useCallback","fetchProviderCache","stripes","stripesLoading","stripesError","fetchCacheStripes","perfLoading","perfError","fetchPerf","snapshot","snapLoading","snapError","fetchSnapshot","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"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"}