pi-mega-compact 0.4.18 → 0.4.19

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.
@@ -99,6 +99,7 @@ function readSnapshot(snapshotPath) {
99
99
  crew: { activeAgents: 0, currentTurn: 0 },
100
100
  repo: { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupAttempts: 0, dedupCollapsed: 0, storageDedupRate: 0 },
101
101
  integrity: { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 },
102
+ model: undefined,
102
103
  };
103
104
  }
104
105
  }
@@ -195,13 +196,26 @@ function dashboardHtml(tierName) {
195
196
  .repo-model { color: #a371f7; }
196
197
  .repo-none { color: #484f58; font-style: italic; }
197
198
  .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
199
+ .model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
200
+ .card.cost h2 { color: #a371f7; }
201
+ .cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
202
+ .cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
203
+ .repo-link { cursor: pointer; }
204
+ .repo-link:hover td { color: #58a6ff; }
205
+ .repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
206
+ .repo-detail.open { display: flex; }
207
+ .repo-detail-box { background: #161b22; border: 1px solid #30363d; border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
208
+ .repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
209
+ .repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
210
+ .repo-close:hover { color: #f0f6fc; }
211
+ .repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
198
212
  </style>
199
213
  </head>
200
214
  <body>
201
215
 
202
216
  <div class="offline-banner" id="offline-banner">Dashboard data unavailable — waiting for a pi session to write snapshot...</div>
203
217
 
204
- <h1><span>mega-compact</span><span class="tier">${tierName}</span></h1>
218
+ <h1><span>mega-compact</span><span class="tier">${tierName}</span><span class="model-pill" id="hdr-model">—</span></h1>
205
219
 
206
220
  <nav class="tabs">
207
221
  <button class="tab active" data-tab="current">Current repo</button>
@@ -270,6 +284,17 @@ function dashboardHtml(tierName) {
270
284
  <span class="label">Anchor</span><span class="value" id="cf-anchor">—</span>
271
285
  </div>
272
286
  </div>
287
+ <div class="card cost">
288
+ <h2>💰 Model &amp; Cost Savings</h2>
289
+ <div class="cost-usd" id="cost-usd">≈ $0.00 saved</div>
290
+ <div class="cost-sub" id="cost-windows">0 context-windows extended</div>
291
+ <div class="stat-grid" style="margin-top:12px">
292
+ <span class="label" title="The model pi is currently using — its pricing drives the cost figure.">Model</span><span class="value" id="md-name">—</span>
293
+ <span class="label" title="The provider serving the model.">Provider</span><span class="value" id="md-provider">—</span>
294
+ <span class="label" title="USD per input token, from the model's pricing.">Input Rate</span><span class="value" id="md-input">—</span>
295
+ <span class="label" title="USD per output token, from the model's pricing.">Output Rate</span><span class="value" id="md-output">—</span>
296
+ </div>
297
+ </div>
273
298
  <div class="card">
274
299
  <h2>Crew / Agents</h2>
275
300
  <div class="stat-grid">
@@ -297,9 +322,40 @@ function dashboardHtml(tierName) {
297
322
  <div class="events-wrap" id="events"><div class="empty">connecting…</div></div>
298
323
  </div>
299
324
 
325
+ <h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">All Repositories</h2>
326
+ <table class="repos">
327
+ <thead>
328
+ <tr>
329
+ <th>Repo</th><th>Model</th>
330
+ <th style="text-align:right">Checkpoints</th>
331
+ <th style="text-align:right">Tokens Saved</th>
332
+ <th style="text-align:right">Retained</th>
333
+ <th style="text-align:right">Last Compacted</th>
334
+ </tr>
335
+ </thead>
336
+ <tbody id="cur-rows"><tr><td colspan="6" class="repo-none">loading…</td></tr></tbody>
337
+ </table>
338
+ <div class="updated" id="cur-updated"></div>
339
+
300
340
  <div class="updated" id="updated"></div>
301
341
  </div><!-- /panel-current -->
302
342
 
343
+ <!-- Per-repo detail modal -->
344
+ <div class="repo-detail" id="repo-detail">
345
+ <div class="repo-detail-box">
346
+ <h2><span id="rd-name">Repo</span><button class="repo-close" id="rd-close" title="Close">×</button></h2>
347
+ <div class="repo-path" id="rd-path"></div>
348
+ <div class="stat-grid">
349
+ <span class="label">Model</span><span class="value" id="rd-model">—</span>
350
+ <span class="label">Checkpoints</span><span class="value" id="rd-cp">0</span>
351
+ <span class="label">Tokens Saved</span><span class="value" id="rd-saved">0</span>
352
+ <span class="label">Compressed-Original</span><span class="value" id="rd-bytes">0 B</span>
353
+ <span class="label">Last Compacted</span><span class="value" id="rd-when">—</span>
354
+ <span class="label">Provider</span><span class="value" id="rd-provider">—</span>
355
+ </div>
356
+ </div>
357
+ </div>
358
+
303
359
  <!-- All repos (machine-wide registry from index.sqlite) -->
304
360
  <div class="tab-panel" id="panel-all">
305
361
  <table class="repos">
@@ -406,6 +462,24 @@ function dashboardHtml(tierName) {
406
462
  document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
407
463
  document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
408
464
 
465
+ // --- Active model + cost savings (same calc as /mega-status) ---------------
466
+ var model = d.model;
467
+ document.getElementById('hdr-model').textContent = model && model.name ? model.name : '—';
468
+ document.getElementById('md-name').textContent = model && model.name ? model.name : '—';
469
+ document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : '—');
470
+ document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : '—';
471
+ document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : '—';
472
+ if (model && model.inputRate && repo.tokensSaved > 0) {
473
+ var usd = (repo.tokensSaved * model.inputRate);
474
+ var win = d.context.contextWindow || 0;
475
+ var windows = win > 0 ? (repo.tokensSaved / win).toFixed(1) : '0';
476
+ document.getElementById('cost-usd').textContent = '≈ $' + usd.toFixed(4) + ' saved';
477
+ document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
478
+ } else {
479
+ document.getElementById('cost-usd').textContent = '≈ $0.00 saved';
480
+ document.getElementById('cost-windows').textContent = '0 context-windows extended';
481
+ }
482
+
409
483
  document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
410
484
  }
411
485
 
@@ -469,16 +543,16 @@ function dashboardHtml(tierName) {
469
543
  document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
470
544
  document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
471
545
 
472
- var body = document.getElementById('all-rows');
473
- if (!repos.length) {
474
- body.innerHTML = '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
475
- } else {
476
- body.innerHTML = repos.map(function(r) {
546
+ // Shared clickable-row renderer for both the in-current table and the
547
+ // All-repos tab — each row opens the per-repo detail modal.
548
+ function rowsHtml() {
549
+ if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
550
+ return repos.map(function(r) {
477
551
  var model = r.modelName
478
552
  ? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
479
553
  : '<span class="repo-none">—</span>';
480
554
  var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
481
- return '<tr>' +
555
+ return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
482
556
  '<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
483
557
  '<td>' + model + '</td>' +
484
558
  '<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
@@ -488,12 +562,48 @@ function dashboardHtml(tierName) {
488
562
  '</tr>';
489
563
  }).join('');
490
564
  }
565
+ document.getElementById('cur-rows').innerHTML = rowsHtml();
566
+ document.getElementById('all-rows').innerHTML = rowsHtml();
567
+ bindRepoRows();
568
+
491
569
  var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
570
+ document.getElementById('cur-updated').textContent = stamp;
492
571
  document.getElementById('all-updated').textContent = stamp;
493
572
  document.getElementById('sm-updated').textContent = stamp;
494
573
  }
574
+
575
+ // Per-repo detail modal ---------------------------------------------------
576
+ var detailEl = document.getElementById('repo-detail');
577
+ var indexCache = { repos: [] };
578
+ function openRepoDetail(root) {
579
+ var r = null;
580
+ for (var i = 0; i < indexCache.repos.length; i++) {
581
+ if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
582
+ }
583
+ if (!r) return;
584
+ document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
585
+ document.getElementById('rd-path').textContent = r.repoRoot;
586
+ document.getElementById('rd-model').textContent = r.modelName || '—';
587
+ document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
588
+ document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
589
+ document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
590
+ document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
591
+ document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
592
+ detailEl.classList.add('open');
593
+ }
594
+ document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
595
+ detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
596
+ function bindRepoRows() {
597
+ var rows = document.querySelectorAll('.repo-link');
598
+ for (var i = 0; i < rows.length; i++) {
599
+ rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
600
+ }
601
+ }
495
602
  function pollIndex() {
496
- fetch('/api/index').then(function(r) { return r.json(); }).then(renderIndex).catch(function() {});
603
+ fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) {
604
+ indexCache = d && d.repos ? d : indexCache;
605
+ renderIndex(d);
606
+ }).catch(function() {});
497
607
  }
498
608
  pollIndex();
499
609
  setInterval(pollIndex, 5000);
@@ -148,6 +148,19 @@ interface Snapshot {
148
148
  dedupCollapsed: number;
149
149
  storageDedupRate: number;
150
150
  };
151
+ integrity: {
152
+ regionsRetained: number;
153
+ compressedOriginalBytes: number;
154
+ duplicatesCollapsed: number;
155
+ bytesPermanentlyDeleted: number;
156
+ };
157
+ model?: {
158
+ name: string;
159
+ provider: string;
160
+ providerName: string;
161
+ inputRate: number;
162
+ outputRate: number;
163
+ };
151
164
  }
152
165
 
153
166
  // ---------------------------------------------------------------------------
@@ -171,6 +184,7 @@ function readSnapshot(snapshotPath: string) {
171
184
  crew: { activeAgents: 0, currentTurn: 0 },
172
185
  repo: { checkpointCount: 0, totalTokenEstimate: 0, originalTokens: 0, tokensSaved: 0, sessionCount: 0, dedupAttempts: 0, dedupCollapsed: 0, storageDedupRate: 0 },
173
186
  integrity: { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 },
187
+ model: undefined,
174
188
  } as Snapshot;
175
189
  }
176
190
  }
@@ -268,13 +282,26 @@ function dashboardHtml(tierName: string): string {
268
282
  .repo-model { color: #a371f7; }
269
283
  .repo-none { color: #484f58; font-style: italic; }
270
284
  .updated { font-size: 11px; color: #484f58; margin-top: 16px; text-align: right; }
285
+ .model-pill { background: #6e40c9; color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
286
+ .card.cost h2 { color: #a371f7; }
287
+ .cost-usd { font-size: 22px; font-weight: 700; color: #3fb950; }
288
+ .cost-sub { font-size: 12px; color: #8b949e; margin-top: 4px; }
289
+ .repo-link { cursor: pointer; }
290
+ .repo-link:hover td { color: #58a6ff; }
291
+ .repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
292
+ .repo-detail.open { display: flex; }
293
+ .repo-detail-box { background: #161b22; border: 1px solid #30363d; border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
294
+ .repo-detail-box h2 { font-size: 14px; color: #f0f6fc; margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
295
+ .repo-close { cursor: pointer; color: #8b949e; font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
296
+ .repo-close:hover { color: #f0f6fc; }
297
+ .repo-path { font-size: 11px; color: #484f58; word-break: break-all; margin: -8px 0 12px; }
271
298
  </style>
272
299
  </head>
273
300
  <body>
274
301
 
275
302
  <div class="offline-banner" id="offline-banner">Dashboard data unavailable — waiting for a pi session to write snapshot...</div>
276
303
 
277
- <h1><span>mega-compact</span><span class="tier">${tierName}</span></h1>
304
+ <h1><span>mega-compact</span><span class="tier">${tierName}</span><span class="model-pill" id="hdr-model">—</span></h1>
278
305
 
279
306
  <nav class="tabs">
280
307
  <button class="tab active" data-tab="current">Current repo</button>
@@ -343,6 +370,17 @@ function dashboardHtml(tierName: string): string {
343
370
  <span class="label">Anchor</span><span class="value" id="cf-anchor">—</span>
344
371
  </div>
345
372
  </div>
373
+ <div class="card cost">
374
+ <h2>💰 Model &amp; Cost Savings</h2>
375
+ <div class="cost-usd" id="cost-usd">≈ $0.00 saved</div>
376
+ <div class="cost-sub" id="cost-windows">0 context-windows extended</div>
377
+ <div class="stat-grid" style="margin-top:12px">
378
+ <span class="label" title="The model pi is currently using — its pricing drives the cost figure.">Model</span><span class="value" id="md-name">—</span>
379
+ <span class="label" title="The provider serving the model.">Provider</span><span class="value" id="md-provider">—</span>
380
+ <span class="label" title="USD per input token, from the model's pricing.">Input Rate</span><span class="value" id="md-input">—</span>
381
+ <span class="label" title="USD per output token, from the model's pricing.">Output Rate</span><span class="value" id="md-output">—</span>
382
+ </div>
383
+ </div>
346
384
  <div class="card">
347
385
  <h2>Crew / Agents</h2>
348
386
  <div class="stat-grid">
@@ -370,9 +408,40 @@ function dashboardHtml(tierName: string): string {
370
408
  <div class="events-wrap" id="events"><div class="empty">connecting…</div></div>
371
409
  </div>
372
410
 
411
+ <h2 style="margin-top:24px;font-size:13px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px">All Repositories</h2>
412
+ <table class="repos">
413
+ <thead>
414
+ <tr>
415
+ <th>Repo</th><th>Model</th>
416
+ <th style="text-align:right">Checkpoints</th>
417
+ <th style="text-align:right">Tokens Saved</th>
418
+ <th style="text-align:right">Retained</th>
419
+ <th style="text-align:right">Last Compacted</th>
420
+ </tr>
421
+ </thead>
422
+ <tbody id="cur-rows"><tr><td colspan="6" class="repo-none">loading…</td></tr></tbody>
423
+ </table>
424
+ <div class="updated" id="cur-updated"></div>
425
+
373
426
  <div class="updated" id="updated"></div>
374
427
  </div><!-- /panel-current -->
375
428
 
429
+ <!-- Per-repo detail modal -->
430
+ <div class="repo-detail" id="repo-detail">
431
+ <div class="repo-detail-box">
432
+ <h2><span id="rd-name">Repo</span><button class="repo-close" id="rd-close" title="Close">×</button></h2>
433
+ <div class="repo-path" id="rd-path"></div>
434
+ <div class="stat-grid">
435
+ <span class="label">Model</span><span class="value" id="rd-model">—</span>
436
+ <span class="label">Checkpoints</span><span class="value" id="rd-cp">0</span>
437
+ <span class="label">Tokens Saved</span><span class="value" id="rd-saved">0</span>
438
+ <span class="label">Compressed-Original</span><span class="value" id="rd-bytes">0 B</span>
439
+ <span class="label">Last Compacted</span><span class="value" id="rd-when">—</span>
440
+ <span class="label">Provider</span><span class="value" id="rd-provider">—</span>
441
+ </div>
442
+ </div>
443
+ </div>
444
+
376
445
  <!-- All repos (machine-wide registry from index.sqlite) -->
377
446
  <div class="tab-panel" id="panel-all">
378
447
  <table class="repos">
@@ -479,6 +548,24 @@ function dashboardHtml(tierName: string): string {
479
548
  document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
480
549
  document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
481
550
 
551
+ // --- Active model + cost savings (same calc as /mega-status) ---------------
552
+ var model = d.model;
553
+ document.getElementById('hdr-model').textContent = model && model.name ? model.name : '—';
554
+ document.getElementById('md-name').textContent = model && model.name ? model.name : '—';
555
+ document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : '—');
556
+ document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : '—';
557
+ document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : '—';
558
+ if (model && model.inputRate && repo.tokensSaved > 0) {
559
+ var usd = (repo.tokensSaved * model.inputRate);
560
+ var win = d.context.contextWindow || 0;
561
+ var windows = win > 0 ? (repo.tokensSaved / win).toFixed(1) : '0';
562
+ document.getElementById('cost-usd').textContent = '≈ $' + usd.toFixed(4) + ' saved';
563
+ document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
564
+ } else {
565
+ document.getElementById('cost-usd').textContent = '≈ $0.00 saved';
566
+ document.getElementById('cost-windows').textContent = '0 context-windows extended';
567
+ }
568
+
482
569
  document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
483
570
  }
484
571
 
@@ -542,16 +629,16 @@ function dashboardHtml(tierName: string): string {
542
629
  document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
543
630
  document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
544
631
 
545
- var body = document.getElementById('all-rows');
546
- if (!repos.length) {
547
- body.innerHTML = '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
548
- } else {
549
- body.innerHTML = repos.map(function(r) {
632
+ // Shared clickable-row renderer for both the in-current table and the
633
+ // All-repos tab — each row opens the per-repo detail modal.
634
+ function rowsHtml() {
635
+ if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
636
+ return repos.map(function(r) {
550
637
  var model = r.modelName
551
638
  ? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
552
639
  : '<span class="repo-none">—</span>';
553
640
  var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
554
- return '<tr>' +
641
+ return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
555
642
  '<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
556
643
  '<td>' + model + '</td>' +
557
644
  '<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
@@ -561,12 +648,48 @@ function dashboardHtml(tierName: string): string {
561
648
  '</tr>';
562
649
  }).join('');
563
650
  }
651
+ document.getElementById('cur-rows').innerHTML = rowsHtml();
652
+ document.getElementById('all-rows').innerHTML = rowsHtml();
653
+ bindRepoRows();
654
+
564
655
  var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
656
+ document.getElementById('cur-updated').textContent = stamp;
565
657
  document.getElementById('all-updated').textContent = stamp;
566
658
  document.getElementById('sm-updated').textContent = stamp;
567
659
  }
660
+
661
+ // Per-repo detail modal ---------------------------------------------------
662
+ var detailEl = document.getElementById('repo-detail');
663
+ var indexCache = { repos: [] };
664
+ function openRepoDetail(root) {
665
+ var r = null;
666
+ for (var i = 0; i < indexCache.repos.length; i++) {
667
+ if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
668
+ }
669
+ if (!r) return;
670
+ document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
671
+ document.getElementById('rd-path').textContent = r.repoRoot;
672
+ document.getElementById('rd-model').textContent = r.modelName || '—';
673
+ document.getElementById('rd-provider').textContent = r.providerName || (r.provider || '—');
674
+ document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
675
+ document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
676
+ document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
677
+ document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : '—';
678
+ detailEl.classList.add('open');
679
+ }
680
+ document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
681
+ detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
682
+ function bindRepoRows() {
683
+ var rows = document.querySelectorAll('.repo-link');
684
+ for (var i = 0; i < rows.length; i++) {
685
+ rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
686
+ }
687
+ }
568
688
  function pollIndex() {
569
- fetch('/api/index').then(function(r) { return r.json(); }).then(renderIndex).catch(function() {});
689
+ fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) {
690
+ indexCache = d && d.repos ? d : indexCache;
691
+ renderIndex(d);
692
+ }).catch(function() {});
570
693
  }
571
694
  pollIndex();
572
695
  setInterval(pollIndex, 5000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.4.18",
3
+ "version": "0.4.19",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-2-Clause",