pi-mega-compact 0.8.19 → 0.8.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/dist/extensions/dashboard-client/src/hooks/useApi.js +51 -0
- package/dist/extensions/dashboard-client/src/hooks/useSSE.js +63 -0
- package/dist/extensions/mega-compact-s38.test.js +28 -2
- package/dist/extensions/mega-events/agent-handlers.js +16 -0
- package/dist/extensions/mega-events/error-classifier.js +8 -3
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js → AchievementTiles-QPo46Pwg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DVgFowc-.js.map → AchievementTiles-QPo46Pwg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js → AchievementsTab-BDJxRXRY.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-Bg86TWPU.js.map → AchievementsTab-BDJxRXRY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js → CacheTab-CUMqXQjd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-DBzoH_q_.js.map → CacheTab-CUMqXQjd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js → ConfigTab-j1dRzurG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-Bj12LgTh.js.map → ConfigTab-j1dRzurG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js → EventsTab-CMglXK4p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-EC0wMsp_.js.map → EventsTab-CMglXK4p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js → GameTab-fpaDoyxi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CISQitbu.js.map → GameTab-fpaDoyxi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js → MetricsTab-CWG2crG_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-us4vVG5D.js.map → MetricsTab-CWG2crG_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-BaV2D2nf.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js → ReposTab-C-0kuZ_0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-CiU9AJkb.js.map → ReposTab-C-0kuZ_0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js → SessionsTab-CSYhddCA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DaCUvTC7.js.map → SessionsTab-CSYhddCA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js → TimeSavedCard-Dy4KRjSM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BAUSCzq5.js.map → TimeSavedCard-Dy4KRjSM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js → index-D_WtU2TV.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-5MPZyayK.js.map → index-D_WtU2TV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CDkmYP9l.css → index-og3ghxyY.css} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js → useSSE-C9yVJlGP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js.map → useSSE-C9yVJlGP.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/package-lock.json +1 -1
- package/extensions/dashboard-client/package.json +1 -1
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +181 -0
- package/extensions/dashboard-client/src/main.tsx +1 -0
- package/extensions/dashboard-client/src/styles/session-gauges.css +85 -0
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +13 -11
- package/extensions/mega-compact-s38.test.ts +32 -2
- package/extensions/mega-events/agent-handlers.ts +15 -0
- package/extensions/mega-events/error-classifier.ts +8 -2
- package/package.json +1 -1
- package/dist/extensions/dashboard-server/helpers.js +0 -37
- package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
- package/dist/extensions/dashboard-server/html/body-open.js +0 -23
- package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
- package/dist/extensions/dashboard-server/html/head-open.js +0 -16
- package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
- package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
- package/dist/extensions/dashboard-server/html/script.js +0 -259
- package/dist/extensions/dashboard-server/html/styles.js +0 -103
- package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
- package/dist/extensions/dashboard-server/html-template.js +0 -41
- package/dist/src/store/sqlite/connection.js +0 -35
- package/dist/src/store/sqlite/index-store.js +0 -167
- package/dist/src/store/sqlite/memory.js +0 -54
- package/dist/src/store/sqlite/minhash-lsh.js +0 -47
- package/dist/src/store/sqlite/sessions.js +0 -39
- package/dist/src/store/sqlite/transaction.js +0 -19
- package/dist/src/vectorStore/add.js +0 -260
- package/dist/src/vectorStore/dedup.js +0 -52
- package/dist/src/vectorStore/index.js +0 -10
- package/dist/src/vectorStore/queries.js +0 -83
- package/dist/src/vectorStore/search.js +0 -95
- package/dist/src/vectorStore/session.js +0 -19
- package/dist/src/vectorStore/store.js +0 -105
- package/dist/src/vectorStore/types.js +0 -6
- package/dist/src/vectorStore/utils.js +0 -23
- package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-eJ7vFgBE.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bg: #0d1117;--panel: #161b22;--border: #30363d;--text: #e6edf3;--muted: #8b949e;--accent: #58a6ff;--ok: #3fb950;--warn: #d29922;--err: #f85149;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:clamp(13px,.9vw,16px)}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text)}.dashboard-app{max-width:min(1600px,96vw);margin:0 auto;padding:clamp(8px,2vw,24px)}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:clamp(8px,1.5vw,16px)}.dashboard-header h1{font-size:clamp(16px,1.4vw,22px);font-weight:600;margin:0 0 clamp(8px,1vw,16px)}.dashboard-header .tier,.dashboard-header .version-pill{display:inline-block;margin-left:8px;padding:2px 8px;font-size:clamp(10px,.8vw,12px);border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--muted)}.dashboard-header .tier{color:var(--accent)}.tab-bar{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:clamp(8px,1vw,16px);overflow-x:auto;flex-wrap:nowrap}.tab-bar button{background:none;border:none;color:var(--muted);padding:8px 14px;cursor:pointer;font-size:clamp(12px,.85vw,14px);border-bottom:2px solid transparent;white-space:nowrap}.tab-bar button.active{color:var(--text);border-bottom-color:var(--accent)}.tab-bar button:hover{color:var(--text)}.dashboard-content{min-height:400px}.loading-spinner{display:flex;align-items:center;justify-content:center;padding:48px;color:var(--muted)}.error-fallback{padding:24px;border:1px solid var(--err);border-radius:8px;background:#f8514914}.error-fallback button{margin-top:12px;padding:6px 14px;background:var(--panel);border:1px solid var(--border);color:var(--text);border-radius:6px;cursor:pointer}.tab-stub{padding:32px;color:var(--muted);border:1px dashed var(--border);border-radius:8px;text-align:center}.repos{width:100%;border-collapse:collapse;font-size:clamp(12px,.8vw,14px)}.repos th,.repos td{padding:6px 10px;border-bottom:1px solid var(--border-soft, var(--border));text-align:left}.repos th{background:var(--bg);position:sticky;top:0}.table-wrap{overflow-x:auto}@media (max-width: 640px){.card-grid{grid-template-columns:1fr}.tab-bar button{padding:6px 10px;font-size:12px}}@media (min-width: 641px) and (max-width: 1024px){.card-grid{grid-template-columns:repeat(2,1fr)}}@media (min-width: 1025px){.card-grid{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}}@media (min-width: 1601px){.dashboard-app{max-width:min(1800px,96vw)}.card-grid{grid-template-columns:repeat(4,1fr)}}.overview-tab{display:flex;flex-direction:column;gap:12px}.overview-header{display:flex;align-items:center;gap:12px}.tier-pill{padding:2px 10px;font-size:12px;border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--accent)}.updated{color:var(--muted);font-size:12px}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.card{padding:14px 16px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.card h3{margin:0 0 10px;font-size:13px;color:var(--muted);font-weight:600}.gauge-bar{height:10px;background:var(--bg);border-radius:5px;overflow:hidden;border:1px solid var(--border)}.gauge-fill{height:100%;transition:width .4s ease}.gauge-green .gauge-fill{background:var(--ok)}.gauge-yellow .gauge-fill{background:var(--warn)}.gauge-red .gauge-fill{background:var(--err)}.gauge-label{margin:8px 0 0;font-size:12px;color:var(--muted)}.stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.stat{display:flex;flex-direction:column;gap:2px}.stat-label{font-size:11px;color:var(--muted);text-transform:uppercase}.stat-value{font-size:16px;font-weight:600}.stat-highlight .stat-value{color:var(--ok)}.bullet-row{display:flex;gap:14px;margin-bottom:8px}.bullet{display:flex;align-items:center;gap:6px;font-size:13px}.bullet-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.bullet-on .bullet-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.bullet-off .bullet-dot{background:var(--border)}.bullet-on .bullet-label{color:var(--text)}.bullet-off .bullet-label{color:var(--muted)}.trigger-detail{margin:4px 0;font-size:12px;color:var(--muted)}.state-badge{display:inline-block;padding:2px 10px;font-size:12px;border-radius:10px;background:var(--bg);border:1px solid var(--border)}.state-idle,.state-none{color:var(--muted)}.state-compacting{color:var(--warn);border-color:var(--warn)}.persisted-tag{margin-left:8px;font-size:11px;color:var(--ok)}.events-tab{display:flex;flex-direction:column;gap:10px}.events-header{display:flex;align-items:center;gap:14px}.sse-status{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}.sse-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.sse-connected .sse-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.sse-connecting .sse-dot{background:var(--warn)}.sse-disconnected .sse-dot,.sse-error .sse-dot{background:var(--err)}.event-count{color:var(--muted);font-size:12px}.event-filters{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:8px}.filter-chip{padding:3px 10px;font-size:11px;border:1px solid var(--border);background:var(--bg);color:var(--muted);border-radius:12px;cursor:pointer}.filter-chip.active{color:var(--text);border-color:var(--accent);background:var(--panel)}.event-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.event-row{display:grid;grid-template-columns:70px 150px 1fr;gap:8px;padding:6px 10px;border-bottom:1px solid var(--border);font-size:12px;cursor:pointer}.event-row:hover{background:var(--panel)}.ev-time{color:var(--muted)}.ev-type{font-size:11px;padding:1px 6px;border-radius:8px;background:var(--bg);border:1px solid var(--border);color:var(--muted);text-align:center}.ev-compact .ev-type{color:var(--accent);border-color:var(--accent)}.ev-trigger .ev-type{color:var(--warn);border-color:var(--warn)}.ev-tier .ev-type{color:var(--muted)}.ev-checkpoint .ev-type{color:var(--ok);border-color:var(--ok)}.ev-recall .ev-type{color:#d2a8ff;border-color:#d2a8ff}.ev-config .ev-type{color:var(--text)}.ev-crew .ev-type{color:#79c0ff;border-color:#79c0ff}.ev-game .ev-type{color:var(--warn)}.ev-summary{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ev-detail{grid-column:1 / -1;margin:6px 0 0;padding:8px;background:var(--bg);border-radius:4px;font-size:11px;color:var(--muted);white-space:pre-wrap}.event-empty{padding:24px;color:var(--muted);text-align:center}@media (max-width: 768px){.card-grid{grid-template-columns:1fr}.event-row{grid-template-columns:60px 110px 1fr;font-size:11px}}.summary-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:16px}.tile{display:flex;flex-direction:column;gap:4px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.tile-label{font-size:11px;color:var(--muted);text-transform:uppercase}.tile-value{font-size:20px;font-weight:600}.tile-highlight .tile-value{color:var(--ok)}.repo-table-wrap{overflow-x:auto}.repo-search{width:100%;padding:8px 10px;margin-bottom:10px;background:var(--panel);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px}.repo-table{width:100%;border-collapse:collapse;font-size:13px}.repo-table th{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border)}.repo-table td{padding:8px 10px;border-bottom:1px solid var(--border)}.repo-table tr{cursor:pointer}.repo-table tbody tr:hover{background:var(--panel)}.sort-header{background:none;border:none;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;padding:0}.sort-header.active-asc,.sort-header.active-desc{color:var(--text)}.sort-arrow{color:var(--accent)}.repo-name{font-weight:600}.repo-model{color:var(--muted)}.modal-backdrop{position:fixed;inset:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:100}.modal-card{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:20px 24px;max-width:560px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;justify-content:space-between;align-items:center}.modal-header h2{margin:0;font-size:18px}.modal-close{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;padding:0 4px}.modal-close:hover{color:var(--text)}.modal-path{font-size:11px;color:var(--muted);margin:4px 0 12px;word-break:break-all}.modal-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px}.modal-stat{display:flex;flex-direction:column;gap:2px}.modal-label{font-size:11px;color:var(--muted);text-transform:uppercase}.modal-value{font-size:16px;font-weight:600}.modal-highlight .modal-value{color:var(--ok)}.modal-card h3{font-size:13px;color:var(--muted);margin:16px 0 6px}.modal-dl{display:grid;grid-template-columns:130px 1fr;gap:4px 12px;margin:0}.modal-dl dt{color:var(--muted);font-size:12px}.modal-dl dd{margin:0;font-size:13px}.model-badge{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,160px),1fr));gap:8px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px;margin-bottom:12px}.model-field{display:flex;flex-direction:column;gap:2px}.model-field-label{font-size:11px;color:var(--muted);text-transform:uppercase}.model-field-value{font-size:14px;font-weight:600}.perf-chart{display:flex;flex-direction:column;gap:12px}.perf-header{display:flex;justify-content:space-between;align-items:center}.perf-header h3{margin:0;font-size:13px;color:var(--muted)}.perf-window{font-size:11px;color:var(--muted)}.perf-grid,.perf-resources{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.perf-resources{padding-top:10px;border-top:1px solid var(--border)}.perf-metric{display:flex;flex-direction:column;gap:4px;padding:10px 12px;background:var(--panel);border:1px solid var(--border);border-radius:6px}.perf-label{font-size:11px;color:var(--muted)}.perf-value{font-size:15px;font-weight:600}.perf-bar-track{height:6px;background:var(--bg);border-radius:3px;overflow:hidden}.perf-bar-fill{height:100%;transition:width .4s ease}.perf-blue .perf-bar-fill{background:var(--accent)}.perf-purple .perf-bar-fill{background:#d2a8ff}.perf-green .perf-bar-fill{background:var(--ok)}.perf-yellow .perf-bar-fill{background:var(--warn)}.perf-diag{display:flex;flex-wrap:wrap;gap:14px;padding-top:10px;border-top:1px solid var(--border)}@media (max-width: 768px){.modal-grid,.model-badge{grid-template-columns:1fr 1fr}}.section-header{font-size:13px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin:20px 0 10px;font-weight:600}.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.active-repos-table,.savings-table{width:100%;border-collapse:collapse;font-size:13px}.active-repos-table th,.savings-table th{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border);white-space:nowrap}.active-repos-table th.num,.savings-table th.num{text-align:right}.active-repos-table td,.savings-table td{padding:6px 10px;border-bottom:1px solid var(--border);white-space:nowrap}.active-repos-table td.num,.savings-table td.num{text-align:right;font-family:monospace}.active-repos-table tbody tr:hover,.savings-table tbody tr:hover{background:var(--panel)}.savings-table .repo-model{font-weight:600}.repo-empty{text-align:center;color:var(--muted);padding:24px}.active-repos-section{margin-bottom:8px}.savings-by-model{margin-bottom:16px}@media (max-width: 640px){.active-repos-table{min-width:900px}.savings-table{min-width:1200px}}@media (min-width: 641px) and (max-width: 1024px){.savings-table{min-width:1000px}}.overview-card-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr))}.ov-stat-row{display:flex;justify-content:space-between;align-items:baseline;padding:4px 0;border-bottom:1px solid var(--border);font-size:clamp(13px,1vw,17px)}.ov-stat-label{color:var(--muted);font-size:12px;cursor:help}.ov-stat-value{font-weight:600;color:var(--text);font-family:monospace}.ov-value-ok{color:var(--ok)!important}.ov-compression-meter{margin-top:10px}.meter-track{height:clamp(6px,.6vw,10px);background:var(--bg);border-radius:5px;overflow:hidden;border:1px solid var(--border)}.meter-fill{height:100%;transition:width .4s ease;border-radius:4px}.meter-green{background:var(--ok)}.meter-yellow{background:var(--warn)}.meter-red{background:var(--err)}.meter-sub{display:block;margin-top:4px;font-size:11px;color:var(--muted)}.bullet-na .bullet-dot{background:var(--warn);box-shadow:0 0 6px var(--warn)}.bullet-na .bullet-label{color:var(--warn)}.trigger-state-text{margin:6px 0 0;font-size:12px;color:var(--muted)}.data-safety-card .safe-note{margin:10px 0 0;padding:8px;font-size:11px;color:var(--muted);background:#3fb9500f;border-radius:6px;border:1px solid rgba(63,185,80,.2)}.model-cost-card .ov-cost-usd{font-size:clamp(16px,1.4vw,22px);font-weight:700;color:#a371f7;margin-bottom:2px}.model-cost-card .ov-cost-sub{font-size:12px;color:var(--muted);margin-bottom:8px}.crew-card .ov-stat-value{font-family:inherit}.legend-card{grid-column:1 / -1}.legend-card details{cursor:pointer}.legend-card summary{font-size:13px;color:var(--muted);font-weight:600;margin-bottom:4px}.legend-list{margin:8px 0;padding-left:20px;font-size:12px;color:var(--muted);line-height:1.6}.legend-list li{margin-bottom:4px}.legend-list b{color:var(--text)}.legend-note{font-size:11px;color:var(--dim);font-style:italic}.cache-tab{display:flex;flex-direction:column;gap:12px}@media (max-width: 640px){.overview-card-grid{grid-template-columns:1fr}}@media (min-width: 1025px){.overview-card-grid{grid-template-columns:repeat(3,1fr)}}@media (min-width: 1601px){.overview-card-grid{grid-template-columns:repeat(4,1fr);font-size:clamp(13px,.9vw,16px)}}.config-tab{display:flex;flex-direction:column;gap:16px}.config-settings,.config-display{padding:14px 16px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.config-settings h3,.config-display h3{margin:0 0 12px;font-size:clamp(12px,1vw,14px);color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}.settings-row{margin-bottom:10px}.settings-row:last-child{margin-bottom:0}.settings-row label{display:flex;align-items:center;gap:8px;font-size:clamp(13px,.9vw,15px);color:var(--text);cursor:pointer}.settings-row select{padding:4px 8px;font-size:clamp(12px,.85vw,14px);background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;cursor:pointer}.settings-row select:disabled{opacity:.5;cursor:not-allowed}.settings-row input[type=checkbox]{width:16px;height:16px;cursor:pointer}.config-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:10px}.config-item{display:flex;flex-direction:column;gap:3px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px}.config-label{font-size:clamp(11px,.75vw,13px);color:var(--muted);text-transform:uppercase;letter-spacing:.4px}.config-value{font-size:clamp(13px,1vw,16px);font-weight:600;color:var(--text)}.event-category-filter{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.category-chip{padding:3px 10px;font-size:clamp(11px,.75vw,13px);border:1px solid var(--border);background:var(--bg);color:var(--muted);border-radius:12px;cursor:pointer;text-transform:capitalize;transition:color .15s,border-color .15s,background .15s}.category-chip:hover{color:var(--text)}.category-chip.active{color:var(--text);border-color:var(--accent);background:var(--panel)}@media (max-width: 640px){.config-grid{grid-template-columns:1fr}.settings-row label{flex-direction:column;align-items:flex-start;gap:4px}.settings-row select{width:100%}}.perf-cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:12px;margin-top:12px}.perf-card{display:flex;flex-direction:column;gap:8px;padding:14px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.perf-card-title{margin:0 0 4px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}.perf-card-body{display:flex;flex-direction:column;gap:6px}.perf-card-note{font-size:11px;color:var(--muted);font-style:italic;margin-top:2px}.perf-card-body .perf-metric{padding:6px 8px;background:transparent;border:none;border-radius:0}.perf-footer{font-size:11px;color:var(--muted);margin-top:10px;padding-top:8px;border-top:1px solid var(--border)}@media (max-width: 640px){.perf-cards-grid{grid-template-columns:1fr}}:root{--mega: #f0883e;--mega-gold: #ffd700;--game-blue: #1f6feb;--game-purple: #a371f7}.game-tab{display:flex;flex-direction:column;gap:12px}.game-mode-on{--accent: var(--mega)}.mega-cache-banner{display:block;background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;padding:10px 14px;border-radius:8px;margin:12px 0;font-size:clamp(13px,1vw,16px);animation:mega-banner-shimmer 3s ease-in-out infinite}@keyframes mega-banner-shimmer{0%,to{filter:brightness(1)}50%{filter:brightness(1.15)}}.mega-cache-toast{display:none;position:fixed;top:16px;left:50%;transform:translate(-50%);background:var(--mega);color:#1a1006;font-weight:700;padding:10px 18px;border-radius:8px;z-index:1000;box-shadow:0 4px 20px #0008;max-width:92vw}.mega-cache-toast.show{display:block;animation:mega-flash .6s ease-in-out 2}@keyframes mega-flash{0%{background:transparent}25%{background:var(--mega-gold)}to{background:transparent}}.achievement-tile{display:none;background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;padding:12px 16px;border-radius:8px;margin:12px 0;box-shadow:0 0 16px #f0883e88;font-size:clamp(13px,1vw,16px)}.achievement-tile.unlocked{display:block}.achievement-tile .ach-detail{display:block;font-weight:500;font-size:12px;margin-top:4px}.game-leaderboards{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px}.lb-card{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:14px}.lb-card h3{font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin:0 0 10px;font-weight:600;display:flex;align-items:center;gap:4px}.lb-card table{width:100%;border-collapse:collapse;font-size:13px}.lb-card td{padding:4px 8px;border-bottom:1px solid var(--border)}.lb-card td.num{text-align:right;font-family:monospace;color:var(--text);font-weight:600}.lb-card .lb-meta{color:var(--muted);font-size:11px;margin-left:6px}.lb-empty{color:var(--muted);font-size:12px;padding:8px 0}.lvl-badge{display:inline-block;background:var(--game-blue);color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;margin-left:8px}.repos-badge-line{margin-top:8px}.repos-badge{display:inline-block;background:var(--game-blue);color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px}.game-empty{color:var(--muted);font-style:italic;padding:12px 0}.ach-section{position:relative}.ach-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin:8px 0 16px}.ach-tile{padding:10px 12px;border-radius:8px;border:1px solid var(--border);font-size:12px;background:var(--panel)}.ach-tile.unlocked{background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;box-shadow:0 0 12px #f0883e66}.ach-tile.locked{opacity:.55}.ach-tile.just-unlocked{animation:ach-unlock-pulse .6s ease-out}.ach-tile .ach-detail{display:block;font-weight:500;font-size:11px;margin-top:3px}@keyframes ach-unlock-pulse{0%{transform:scale(.9);opacity:.4}60%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}.ach-toast{display:none;position:fixed;top:16px;left:50%;transform:translate(-50%);background:var(--game-blue);color:#fff;font-weight:700;padding:10px 18px;border-radius:8px;z-index:1000;box-shadow:0 4px 20px #0008;max-width:92vw}.ach-toast.show{display:block}.achievements-tab{display:flex;flex-direction:column;gap:12px}.achievements-tab h2{font-size:clamp(16px,1.4vw,22px);margin:0}@media (max-width: 640px){.game-leaderboards{grid-template-columns:1fr}}@media (min-width: 1601px){.game-leaderboards{gap:20px}}.sessions-tab{display:flex;flex-direction:column;gap:14px}.sessions-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0}.sessions-toolbar h2{margin:0;font-size:14px;font-weight:600;color:var(--text)}.sessions-window-select{display:inline-flex;gap:2px;padding:2px;background:var(--panel);border:1px solid var(--border);border-radius:6px}.sessions-window-btn{padding:4px 10px;background:transparent;border:none;border-radius:4px;color:var(--muted);font-size:12px;cursor:pointer;transition:background .15s,color .15s}.sessions-window-btn:hover{color:var(--text)}.sessions-window-btn.active{background:var(--accent);color:#0d1117;font-weight:600}.sessions-summary-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}.sessions-chart{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:12px}.sessions-chart-empty,.sessions-empty{display:flex;align-items:center;justify-content:center;min-height:160px;color:var(--muted);font-size:14px;background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:32px;text-align:center}.sessions-table-scroll{overflow-x:auto;background:var(--panel);border:1px solid var(--border);border-radius:8px}.sessions-table{width:100%;border-collapse:collapse;font-size:13px}.sessions-table th,.sessions-table td{padding:8px 10px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap}.sessions-table th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);background:#ffffff05;position:sticky;top:0}.sessions-table td.num{text-align:right;font-variant-numeric:tabular-nums}.sessions-table tr:last-child td{border-bottom:none}.sessions-table tr:hover td{background:#58a6ff0f}.sessions-empty-row td{color:var(--muted);font-style:italic;text-align:center}.sessions-pct-pill{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.sessions-pct-ok{background:#3fb95026;color:var(--ok)}.sessions-pct-warn{background:#d2992226;color:var(--warn)}.sessions-pct-err{background:#f8514926;color:var(--err)}.sessions-sparkline{display:inline-block;vertical-align:middle}.sessions-sparkline-empty{color:var(--muted);font-size:11px}.sessions-meta{font-size:11px;color:var(--muted)}.sessions-footer{font-size:11px;color:var(--muted);margin-top:6px}@media (max-width: 640px){.sessions-summary-tiles{grid-template-columns:1fr}.sessions-table th,.sessions-table td{font-size:12px;padding:6px 8px}}.repo-stack-card{display:flex;flex-direction:column;gap:10px}.repo-stack-card h3{margin:0;font-size:14px;font-weight:600;color:var(--text)}.repo-stack-bar{display:flex;width:100%;height:22px;border-radius:6px;overflow:hidden;border:1px solid var(--border);background:var(--bg)}.repo-stack-segment{height:100%;min-width:2px;transition:opacity .15s,filter .15s}.repo-stack-segment:hover{opacity:.85;filter:brightness(1.15)}.repo-stack-total{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums}.repo-stack-total .pct{color:var(--muted)}.repo-stack-legend{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:6px}.repo-stack-legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}.repo-stack-legend-swatch{flex-shrink:0;width:12px;height:12px;border-radius:3px;border:1px solid var(--border)}.repo-stack-legend-name{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (max-width: 640px){.repo-stack-legend{grid-template-columns:1fr}}
|
|
1
|
+
:root{--bg: #0d1117;--panel: #161b22;--border: #30363d;--text: #e6edf3;--muted: #8b949e;--accent: #58a6ff;--ok: #3fb950;--warn: #d29922;--err: #f85149;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:clamp(13px,.9vw,16px)}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text)}.dashboard-app{max-width:min(1600px,96vw);margin:0 auto;padding:clamp(8px,2vw,24px)}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr));gap:clamp(8px,1.5vw,16px)}.dashboard-header h1{font-size:clamp(16px,1.4vw,22px);font-weight:600;margin:0 0 clamp(8px,1vw,16px)}.dashboard-header .tier,.dashboard-header .version-pill{display:inline-block;margin-left:8px;padding:2px 8px;font-size:clamp(10px,.8vw,12px);border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--muted)}.dashboard-header .tier{color:var(--accent)}.tab-bar{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:clamp(8px,1vw,16px);overflow-x:auto;flex-wrap:nowrap}.tab-bar button{background:none;border:none;color:var(--muted);padding:8px 14px;cursor:pointer;font-size:clamp(12px,.85vw,14px);border-bottom:2px solid transparent;white-space:nowrap}.tab-bar button.active{color:var(--text);border-bottom-color:var(--accent)}.tab-bar button:hover{color:var(--text)}.dashboard-content{min-height:400px}.loading-spinner{display:flex;align-items:center;justify-content:center;padding:48px;color:var(--muted)}.error-fallback{padding:24px;border:1px solid var(--err);border-radius:8px;background:#f8514914}.error-fallback button{margin-top:12px;padding:6px 14px;background:var(--panel);border:1px solid var(--border);color:var(--text);border-radius:6px;cursor:pointer}.tab-stub{padding:32px;color:var(--muted);border:1px dashed var(--border);border-radius:8px;text-align:center}.repos{width:100%;border-collapse:collapse;font-size:clamp(12px,.8vw,14px)}.repos th,.repos td{padding:6px 10px;border-bottom:1px solid var(--border-soft, var(--border));text-align:left}.repos th{background:var(--bg);position:sticky;top:0}.table-wrap{overflow-x:auto}@media (max-width: 640px){.card-grid{grid-template-columns:1fr}.tab-bar button{padding:6px 10px;font-size:12px}}@media (min-width: 641px) and (max-width: 1024px){.card-grid{grid-template-columns:repeat(2,1fr)}}@media (min-width: 1025px){.card-grid{grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}}@media (min-width: 1601px){.dashboard-app{max-width:min(1800px,96vw)}.card-grid{grid-template-columns:repeat(4,1fr)}}.overview-tab{display:flex;flex-direction:column;gap:12px}.overview-header{display:flex;align-items:center;gap:12px}.tier-pill{padding:2px 10px;font-size:12px;border-radius:10px;background:var(--panel);border:1px solid var(--border);color:var(--accent)}.updated{color:var(--muted);font-size:12px}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.card{padding:14px 16px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.card h3{margin:0 0 10px;font-size:13px;color:var(--muted);font-weight:600}.gauge-bar{height:10px;background:var(--bg);border-radius:5px;overflow:hidden;border:1px solid var(--border)}.gauge-fill{height:100%;transition:width .4s ease}.gauge-green .gauge-fill{background:var(--ok)}.gauge-yellow .gauge-fill{background:var(--warn)}.gauge-red .gauge-fill{background:var(--err)}.gauge-label{margin:8px 0 0;font-size:12px;color:var(--muted)}.stat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.stat{display:flex;flex-direction:column;gap:2px}.stat-label{font-size:11px;color:var(--muted);text-transform:uppercase}.stat-value{font-size:16px;font-weight:600}.stat-highlight .stat-value{color:var(--ok)}.bullet-row{display:flex;gap:14px;margin-bottom:8px}.bullet{display:flex;align-items:center;gap:6px;font-size:13px}.bullet-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.bullet-on .bullet-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.bullet-off .bullet-dot{background:var(--border)}.bullet-on .bullet-label{color:var(--text)}.bullet-off .bullet-label{color:var(--muted)}.trigger-detail{margin:4px 0;font-size:12px;color:var(--muted)}.state-badge{display:inline-block;padding:2px 10px;font-size:12px;border-radius:10px;background:var(--bg);border:1px solid var(--border)}.state-idle,.state-none{color:var(--muted)}.state-compacting{color:var(--warn);border-color:var(--warn)}.persisted-tag{margin-left:8px;font-size:11px;color:var(--ok)}.events-tab{display:flex;flex-direction:column;gap:10px}.events-header{display:flex;align-items:center;gap:14px}.sse-status{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted)}.sse-dot{width:8px;height:8px;border-radius:50%;background:var(--muted)}.sse-connected .sse-dot{background:var(--ok);box-shadow:0 0 6px var(--ok)}.sse-connecting .sse-dot{background:var(--warn)}.sse-disconnected .sse-dot,.sse-error .sse-dot{background:var(--err)}.event-count{color:var(--muted);font-size:12px}.event-filters{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:8px}.filter-chip{padding:3px 10px;font-size:11px;border:1px solid var(--border);background:var(--bg);color:var(--muted);border-radius:12px;cursor:pointer}.filter-chip.active{color:var(--text);border-color:var(--accent);background:var(--panel)}.event-list{list-style:none;margin:0;padding:0;max-height:60vh;overflow-y:auto}.event-row{display:grid;grid-template-columns:70px 150px 1fr;gap:8px;padding:6px 10px;border-bottom:1px solid var(--border);font-size:12px;cursor:pointer}.event-row:hover{background:var(--panel)}.ev-time{color:var(--muted)}.ev-type{font-size:11px;padding:1px 6px;border-radius:8px;background:var(--bg);border:1px solid var(--border);color:var(--muted);text-align:center}.ev-compact .ev-type{color:var(--accent);border-color:var(--accent)}.ev-trigger .ev-type{color:var(--warn);border-color:var(--warn)}.ev-tier .ev-type{color:var(--muted)}.ev-checkpoint .ev-type{color:var(--ok);border-color:var(--ok)}.ev-recall .ev-type{color:#d2a8ff;border-color:#d2a8ff}.ev-config .ev-type{color:var(--text)}.ev-crew .ev-type{color:#79c0ff;border-color:#79c0ff}.ev-game .ev-type{color:var(--warn)}.ev-summary{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ev-detail{grid-column:1 / -1;margin:6px 0 0;padding:8px;background:var(--bg);border-radius:4px;font-size:11px;color:var(--muted);white-space:pre-wrap}.event-empty{padding:24px;color:var(--muted);text-align:center}@media (max-width: 768px){.card-grid{grid-template-columns:1fr}.event-row{grid-template-columns:60px 110px 1fr;font-size:11px}}.summary-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:16px}.tile{display:flex;flex-direction:column;gap:4px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.tile-label{font-size:11px;color:var(--muted);text-transform:uppercase}.tile-value{font-size:20px;font-weight:600}.tile-highlight .tile-value{color:var(--ok)}.repo-table-wrap{overflow-x:auto}.repo-search{width:100%;padding:8px 10px;margin-bottom:10px;background:var(--panel);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px}.repo-table{width:100%;border-collapse:collapse;font-size:13px}.repo-table th{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border)}.repo-table td{padding:8px 10px;border-bottom:1px solid var(--border)}.repo-table tr{cursor:pointer}.repo-table tbody tr:hover{background:var(--panel)}.sort-header{background:none;border:none;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;padding:0}.sort-header.active-asc,.sort-header.active-desc{color:var(--text)}.sort-arrow{color:var(--accent)}.repo-name{font-weight:600}.repo-model{color:var(--muted)}.modal-backdrop{position:fixed;inset:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:100}.modal-card{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:20px 24px;max-width:560px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;justify-content:space-between;align-items:center}.modal-header h2{margin:0;font-size:18px}.modal-close{background:none;border:none;color:var(--muted);font-size:22px;cursor:pointer;padding:0 4px}.modal-close:hover{color:var(--text)}.modal-path{font-size:11px;color:var(--muted);margin:4px 0 12px;word-break:break-all}.modal-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px}.modal-stat{display:flex;flex-direction:column;gap:2px}.modal-label{font-size:11px;color:var(--muted);text-transform:uppercase}.modal-value{font-size:16px;font-weight:600}.modal-highlight .modal-value{color:var(--ok)}.modal-card h3{font-size:13px;color:var(--muted);margin:16px 0 6px}.modal-dl{display:grid;grid-template-columns:130px 1fr;gap:4px 12px;margin:0}.modal-dl dt{color:var(--muted);font-size:12px}.modal-dl dd{margin:0;font-size:13px}.model-badge{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,160px),1fr));gap:8px;padding:12px 14px;background:var(--panel);border:1px solid var(--border);border-radius:8px;margin-bottom:12px}.model-field{display:flex;flex-direction:column;gap:2px}.model-field-label{font-size:11px;color:var(--muted);text-transform:uppercase}.model-field-value{font-size:14px;font-weight:600}.perf-chart{display:flex;flex-direction:column;gap:12px}.perf-header{display:flex;justify-content:space-between;align-items:center}.perf-header h3{margin:0;font-size:13px;color:var(--muted)}.perf-window{font-size:11px;color:var(--muted)}.perf-grid,.perf-resources{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.perf-resources{padding-top:10px;border-top:1px solid var(--border)}.perf-metric{display:flex;flex-direction:column;gap:4px;padding:10px 12px;background:var(--panel);border:1px solid var(--border);border-radius:6px}.perf-label{font-size:11px;color:var(--muted)}.perf-value{font-size:15px;font-weight:600}.perf-bar-track{height:6px;background:var(--bg);border-radius:3px;overflow:hidden}.perf-bar-fill{height:100%;transition:width .4s ease}.perf-blue .perf-bar-fill{background:var(--accent)}.perf-purple .perf-bar-fill{background:#d2a8ff}.perf-green .perf-bar-fill{background:var(--ok)}.perf-yellow .perf-bar-fill{background:var(--warn)}.perf-diag{display:flex;flex-wrap:wrap;gap:14px;padding-top:10px;border-top:1px solid var(--border)}@media (max-width: 768px){.modal-grid,.model-badge{grid-template-columns:1fr 1fr}}.section-header{font-size:13px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin:20px 0 10px;font-weight:600}.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}.active-repos-table,.savings-table{width:100%;border-collapse:collapse;font-size:13px}.active-repos-table th,.savings-table th{text-align:left;padding:6px 10px;border-bottom:1px solid var(--border);white-space:nowrap}.active-repos-table th.num,.savings-table th.num{text-align:right}.active-repos-table td,.savings-table td{padding:6px 10px;border-bottom:1px solid var(--border);white-space:nowrap}.active-repos-table td.num,.savings-table td.num{text-align:right;font-family:monospace}.active-repos-table tbody tr:hover,.savings-table tbody tr:hover{background:var(--panel)}.savings-table .repo-model{font-weight:600}.repo-empty{text-align:center;color:var(--muted);padding:24px}.active-repos-section{margin-bottom:8px}.savings-by-model{margin-bottom:16px}@media (max-width: 640px){.active-repos-table{min-width:900px}.savings-table{min-width:1200px}}@media (min-width: 641px) and (max-width: 1024px){.savings-table{min-width:1000px}}.overview-card-grid{grid-template-columns:repeat(auto-fit,minmax(min(100%,320px),1fr))}.ov-stat-row{display:flex;justify-content:space-between;align-items:baseline;padding:4px 0;border-bottom:1px solid var(--border);font-size:clamp(13px,1vw,17px)}.ov-stat-label{color:var(--muted);font-size:12px;cursor:help}.ov-stat-value{font-weight:600;color:var(--text);font-family:monospace}.ov-value-ok{color:var(--ok)!important}.ov-compression-meter{margin-top:10px}.meter-track{height:clamp(6px,.6vw,10px);background:var(--bg);border-radius:5px;overflow:hidden;border:1px solid var(--border)}.meter-fill{height:100%;transition:width .4s ease;border-radius:4px}.meter-green{background:var(--ok)}.meter-yellow{background:var(--warn)}.meter-red{background:var(--err)}.meter-sub{display:block;margin-top:4px;font-size:11px;color:var(--muted)}.bullet-na .bullet-dot{background:var(--warn);box-shadow:0 0 6px var(--warn)}.bullet-na .bullet-label{color:var(--warn)}.trigger-state-text{margin:6px 0 0;font-size:12px;color:var(--muted)}.data-safety-card .safe-note{margin:10px 0 0;padding:8px;font-size:11px;color:var(--muted);background:#3fb9500f;border-radius:6px;border:1px solid rgba(63,185,80,.2)}.model-cost-card .ov-cost-usd{font-size:clamp(16px,1.4vw,22px);font-weight:700;color:#a371f7;margin-bottom:2px}.model-cost-card .ov-cost-sub{font-size:12px;color:var(--muted);margin-bottom:8px}.crew-card .ov-stat-value{font-family:inherit}.legend-card{grid-column:1 / -1}.legend-card details{cursor:pointer}.legend-card summary{font-size:13px;color:var(--muted);font-weight:600;margin-bottom:4px}.legend-list{margin:8px 0;padding-left:20px;font-size:12px;color:var(--muted);line-height:1.6}.legend-list li{margin-bottom:4px}.legend-list b{color:var(--text)}.legend-note{font-size:11px;color:var(--dim);font-style:italic}.cache-tab{display:flex;flex-direction:column;gap:12px}@media (max-width: 640px){.overview-card-grid{grid-template-columns:1fr}}@media (min-width: 1025px){.overview-card-grid{grid-template-columns:repeat(3,1fr)}}@media (min-width: 1601px){.overview-card-grid{grid-template-columns:repeat(4,1fr);font-size:clamp(13px,.9vw,16px)}}.config-tab{display:flex;flex-direction:column;gap:16px}.config-settings,.config-display{padding:14px 16px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.config-settings h3,.config-display h3{margin:0 0 12px;font-size:clamp(12px,1vw,14px);color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:.5px}.settings-row{margin-bottom:10px}.settings-row:last-child{margin-bottom:0}.settings-row label{display:flex;align-items:center;gap:8px;font-size:clamp(13px,.9vw,15px);color:var(--text);cursor:pointer}.settings-row select{padding:4px 8px;font-size:clamp(12px,.85vw,14px);background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;cursor:pointer}.settings-row select:disabled{opacity:.5;cursor:not-allowed}.settings-row input[type=checkbox]{width:16px;height:16px;cursor:pointer}.config-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,200px),1fr));gap:10px}.config-item{display:flex;flex-direction:column;gap:3px;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px}.config-label{font-size:clamp(11px,.75vw,13px);color:var(--muted);text-transform:uppercase;letter-spacing:.4px}.config-value{font-size:clamp(13px,1vw,16px);font-weight:600;color:var(--text)}.event-category-filter{display:flex;flex-wrap:wrap;gap:6px;align-items:center}.category-chip{padding:3px 10px;font-size:clamp(11px,.75vw,13px);border:1px solid var(--border);background:var(--bg);color:var(--muted);border-radius:12px;cursor:pointer;text-transform:capitalize;transition:color .15s,border-color .15s,background .15s}.category-chip:hover{color:var(--text)}.category-chip.active{color:var(--text);border-color:var(--accent);background:var(--panel)}@media (max-width: 640px){.config-grid{grid-template-columns:1fr}.settings-row label{flex-direction:column;align-items:flex-start;gap:4px}.settings-row select{width:100%}}.perf-cards-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,240px),1fr));gap:12px;margin-top:12px}.perf-card{display:flex;flex-direction:column;gap:8px;padding:14px;background:var(--panel);border:1px solid var(--border);border-radius:8px}.perf-card-title{margin:0 0 4px;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}.perf-card-body{display:flex;flex-direction:column;gap:6px}.perf-card-note{font-size:11px;color:var(--muted);font-style:italic;margin-top:2px}.perf-card-body .perf-metric{padding:6px 8px;background:transparent;border:none;border-radius:0}.perf-footer{font-size:11px;color:var(--muted);margin-top:10px;padding-top:8px;border-top:1px solid var(--border)}@media (max-width: 640px){.perf-cards-grid{grid-template-columns:1fr}}:root{--mega: #f0883e;--mega-gold: #ffd700;--game-blue: #1f6feb;--game-purple: #a371f7}.game-tab{display:flex;flex-direction:column;gap:12px}.game-mode-on{--accent: var(--mega)}.mega-cache-banner{display:block;background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;padding:10px 14px;border-radius:8px;margin:12px 0;font-size:clamp(13px,1vw,16px);animation:mega-banner-shimmer 3s ease-in-out infinite}@keyframes mega-banner-shimmer{0%,to{filter:brightness(1)}50%{filter:brightness(1.15)}}.mega-cache-toast{display:none;position:fixed;top:16px;left:50%;transform:translate(-50%);background:var(--mega);color:#1a1006;font-weight:700;padding:10px 18px;border-radius:8px;z-index:1000;box-shadow:0 4px 20px #0008;max-width:92vw}.mega-cache-toast.show{display:block;animation:mega-flash .6s ease-in-out 2}@keyframes mega-flash{0%{background:transparent}25%{background:var(--mega-gold)}to{background:transparent}}.achievement-tile{display:none;background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;padding:12px 16px;border-radius:8px;margin:12px 0;box-shadow:0 0 16px #f0883e88;font-size:clamp(13px,1vw,16px)}.achievement-tile.unlocked{display:block}.achievement-tile .ach-detail{display:block;font-weight:500;font-size:12px;margin-top:4px}.game-leaderboards{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:16px}.lb-card{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:14px}.lb-card h3{font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin:0 0 10px;font-weight:600;display:flex;align-items:center;gap:4px}.lb-card table{width:100%;border-collapse:collapse;font-size:13px}.lb-card td{padding:4px 8px;border-bottom:1px solid var(--border)}.lb-card td.num{text-align:right;font-family:monospace;color:var(--text);font-weight:600}.lb-card .lb-meta{color:var(--muted);font-size:11px;margin-left:6px}.lb-empty{color:var(--muted);font-size:12px;padding:8px 0}.lvl-badge{display:inline-block;background:var(--game-blue);color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px;margin-left:8px}.repos-badge-line{margin-top:8px}.repos-badge{display:inline-block;background:var(--game-blue);color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px}.game-empty{color:var(--muted);font-style:italic;padding:12px 0}.ach-section{position:relative}.ach-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:8px;margin:8px 0 16px}.ach-tile{padding:10px 12px;border-radius:8px;border:1px solid var(--border);font-size:12px;background:var(--panel)}.ach-tile.unlocked{background:linear-gradient(135deg,var(--mega),var(--mega-gold));color:#1a1006;font-weight:700;box-shadow:0 0 12px #f0883e66}.ach-tile.locked{opacity:.55}.ach-tile.just-unlocked{animation:ach-unlock-pulse .6s ease-out}.ach-tile .ach-detail{display:block;font-weight:500;font-size:11px;margin-top:3px}@keyframes ach-unlock-pulse{0%{transform:scale(.9);opacity:.4}60%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}.ach-toast{display:none;position:fixed;top:16px;left:50%;transform:translate(-50%);background:var(--game-blue);color:#fff;font-weight:700;padding:10px 18px;border-radius:8px;z-index:1000;box-shadow:0 4px 20px #0008;max-width:92vw}.ach-toast.show{display:block}.achievements-tab{display:flex;flex-direction:column;gap:12px}.achievements-tab h2{font-size:clamp(16px,1.4vw,22px);margin:0}@media (max-width: 640px){.game-leaderboards{grid-template-columns:1fr}}@media (min-width: 1601px){.game-leaderboards{gap:20px}}.sessions-tab{display:flex;flex-direction:column;gap:14px}.sessions-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:8px 0}.sessions-toolbar h2{margin:0;font-size:14px;font-weight:600;color:var(--text)}.sessions-window-select{display:inline-flex;gap:2px;padding:2px;background:var(--panel);border:1px solid var(--border);border-radius:6px}.sessions-window-btn{padding:4px 10px;background:transparent;border:none;border-radius:4px;color:var(--muted);font-size:12px;cursor:pointer;transition:background .15s,color .15s}.sessions-window-btn:hover{color:var(--text)}.sessions-window-btn.active{background:var(--accent);color:#0d1117;font-weight:600}.sessions-summary-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}.sessions-chart{background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:12px}.sessions-chart-empty,.sessions-empty{display:flex;align-items:center;justify-content:center;min-height:160px;color:var(--muted);font-size:14px;background:var(--panel);border:1px solid var(--border);border-radius:8px;padding:32px;text-align:center}.sessions-table-scroll{overflow-x:auto;background:var(--panel);border:1px solid var(--border);border-radius:8px}.sessions-table{width:100%;border-collapse:collapse;font-size:13px}.sessions-table th,.sessions-table td{padding:8px 10px;text-align:left;border-bottom:1px solid var(--border);white-space:nowrap}.sessions-table th{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);background:#ffffff05;position:sticky;top:0}.sessions-table td.num{text-align:right;font-variant-numeric:tabular-nums}.sessions-table tr:last-child td{border-bottom:none}.sessions-table tr:hover td{background:#58a6ff0f}.sessions-empty-row td{color:var(--muted);font-style:italic;text-align:center}.sessions-pct-pill{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;font-variant-numeric:tabular-nums}.sessions-pct-ok{background:#3fb95026;color:var(--ok)}.sessions-pct-warn{background:#d2992226;color:var(--warn)}.sessions-pct-err{background:#f8514926;color:var(--err)}.sessions-sparkline{display:inline-block;vertical-align:middle}.sessions-sparkline-empty{color:var(--muted);font-size:11px}.sessions-meta{font-size:11px;color:var(--muted)}.sessions-footer{font-size:11px;color:var(--muted);margin-top:6px}@media (max-width: 640px){.sessions-summary-tiles{grid-template-columns:1fr}.sessions-table th,.sessions-table td{font-size:12px;padding:6px 8px}}.repo-stack-card{display:flex;flex-direction:column;gap:10px}.repo-stack-card h3{margin:0;font-size:14px;font-weight:600;color:var(--text)}.repo-stack-bar{display:flex;width:100%;height:22px;border-radius:6px;overflow:hidden;border:1px solid var(--border);background:var(--bg)}.repo-stack-segment{height:100%;min-width:2px;transition:opacity .15s,filter .15s}.repo-stack-segment:hover{opacity:.85;filter:brightness(1.15)}.repo-stack-total{font-size:13px;color:var(--text);font-variant-numeric:tabular-nums}.repo-stack-total .pct{color:var(--muted)}.repo-stack-legend{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:6px}.repo-stack-legend-item{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}.repo-stack-legend-swatch{flex-shrink:0;width:12px;height:12px;border-radius:3px;border:1px solid var(--border)}.repo-stack-legend-name{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (max-width: 640px){.repo-stack-legend{grid-template-columns:1fr}}.session-gauges-card{grid-column:1 / -1}.session-gauges-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px}.session-gauge{display:flex;flex-direction:column;gap:6px;padding:10px 12px;background:var(--bg);border:1px solid var(--border);border-radius:6px}.session-gauge-self{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);background:color-mix(in srgb,var(--accent) 8%,var(--bg))}.session-gauge-head{display:flex;justify-content:space-between;align-items:baseline;gap:6px}.session-gauge-label{font-size:12px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%}.session-gauge-self .session-gauge-label{color:var(--accent)}.session-gauge-pct{font-size:12px;font-weight:600;color:var(--muted);white-space:nowrap}.session-gauges-grid .gauge-bar{height:8px}.session-gauge-sub{margin:0}.session-gauge-meta{margin:0;font-size:11px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media (max-width: 640px){.session-gauges-grid{grid-template-columns:1fr}}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./index-
|
|
2
|
-
//# sourceMappingURL=useSSE-
|
|
1
|
+
import{r as e}from"./index-D_WtU2TV.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
|
|
2
|
+
//# sourceMappingURL=useSSE-C9yVJlGP.js.map
|
package/extensions/dashboard-client/dist/assets/{useSSE-BSpmWZZi.js.map → useSSE-C9yVJlGP.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSSE-
|
|
1
|
+
{"version":3,"file":"useSSE-C9yVJlGP.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mega-compact dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-D_WtU2TV.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-og3ghxyY.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/SessionContextGauges.tsx — Per-session context gauges (S40).
|
|
3
|
+
*
|
|
4
|
+
* Replaces the single launcher-only ContextGauge on the Overview tab with a
|
|
5
|
+
* wrapping grid of compact gauges, one per active pi session. The launcher's
|
|
6
|
+
* own session is always shown first + highlighted ("this session"), sourced
|
|
7
|
+
* from the live snapshot (always available, never depends on heartbeat
|
|
8
|
+
* timing). Other active sessions come from /api/sessions, sorted by lastSeen
|
|
9
|
+
* descending, each labeled with repo basename + PID.
|
|
10
|
+
*
|
|
11
|
+
* Reuses the existing .gauge-bar / .gauge-fill / .gauge-{green,yellow,red}
|
|
12
|
+
* classes from overview-events.css so the fill bars match the original
|
|
13
|
+
* ContextGauge styling.
|
|
14
|
+
*
|
|
15
|
+
* PREVENT-PI-004: pure presentational component — no fetch, no network. Data
|
|
16
|
+
* is passed in by the parent (OverviewTab) via its useApi(fetchSessions) +
|
|
17
|
+
* useSSE() for real-time updates.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type React from "react";
|
|
21
|
+
import type { ActiveSession, SessionsResponse } from "@contracts";
|
|
22
|
+
|
|
23
|
+
export interface SessionContextGaugesProps {
|
|
24
|
+
/** Active sessions from /api/sessions (may be null while loading). */
|
|
25
|
+
sessions: SessionsResponse | null;
|
|
26
|
+
/** Whether the initial fetch is in flight. */
|
|
27
|
+
loading: boolean;
|
|
28
|
+
/** Fetch error, if any. */
|
|
29
|
+
error: Error | null;
|
|
30
|
+
/** Launcher's own session ID (from snapshot.session.id), to exclude it
|
|
31
|
+
* from the "other sessions" list (it's always rendered separately). */
|
|
32
|
+
launcherSessionId: string | null;
|
|
33
|
+
/** Launcher's own context values (from snapshot — always available). */
|
|
34
|
+
launcher: {
|
|
35
|
+
tokens: number | null;
|
|
36
|
+
percent: number | null;
|
|
37
|
+
contextWindow: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function severityClass(percent: number): string {
|
|
42
|
+
if (percent >= 90) return "gauge-red";
|
|
43
|
+
if (percent >= 70) return "gauge-yellow";
|
|
44
|
+
return "gauge-green";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fmtTokens(n: number | null): string {
|
|
48
|
+
if (n == null) return "?";
|
|
49
|
+
if (n >= 100_000) return `${(n / 1000).toFixed(0)}k`;
|
|
50
|
+
return n.toLocaleString();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function repoBasename(repoRoot: string | null): string {
|
|
54
|
+
if (!repoRoot) return "(unknown)";
|
|
55
|
+
const trimmed = repoRoot.replace(/[\\/]+$/, "");
|
|
56
|
+
if (!trimmed) return "(unknown)";
|
|
57
|
+
const parts = trimmed.split(/[\\/]/);
|
|
58
|
+
return parts[parts.length - 1] || trimmed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fmtAge(ms: number): string {
|
|
62
|
+
const s = Math.max(0, Math.round(ms / 1000));
|
|
63
|
+
if (s < 60) return `${s}s ago`;
|
|
64
|
+
const m = Math.floor(s / 60);
|
|
65
|
+
if (m < 60) return `${m}m ago`;
|
|
66
|
+
const h = Math.floor(m / 60);
|
|
67
|
+
return `${h}h ${m % 60}m ago`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface GaugeData {
|
|
71
|
+
key: string;
|
|
72
|
+
label: string;
|
|
73
|
+
sublabel: string;
|
|
74
|
+
tokens: number | null;
|
|
75
|
+
percent: number | null;
|
|
76
|
+
contextWindow: number;
|
|
77
|
+
isSelf: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A single compact gauge: header label, fill bar, tokens/window sublabel. */
|
|
81
|
+
function SessionGauge({ data }: { data: GaugeData }): React.ReactElement {
|
|
82
|
+
const pct = data.percent ?? 0;
|
|
83
|
+
const fillWidth = Math.max(pct, 1);
|
|
84
|
+
const cls = severityClass(pct);
|
|
85
|
+
const tokStr = fmtTokens(data.tokens);
|
|
86
|
+
const windowStr = data.contextWindow.toLocaleString();
|
|
87
|
+
const sublabel = `${tokStr} / ${windowStr} tokens`;
|
|
88
|
+
return (
|
|
89
|
+
<div className={`session-gauge ${data.isSelf ? "session-gauge-self" : ""}`}>
|
|
90
|
+
<div className="session-gauge-head">
|
|
91
|
+
<span className="session-gauge-label">{data.label}</span>
|
|
92
|
+
<span className="session-gauge-pct">{pct}%</span>
|
|
93
|
+
</div>
|
|
94
|
+
<div
|
|
95
|
+
className={`gauge-bar ${cls}`}
|
|
96
|
+
role="meter"
|
|
97
|
+
aria-valuenow={pct}
|
|
98
|
+
aria-valuemin={0}
|
|
99
|
+
aria-valuemax={100}
|
|
100
|
+
aria-label={`${data.label}: ${pct}%`}
|
|
101
|
+
>
|
|
102
|
+
<div className="gauge-fill" style={{ width: `${fillWidth}%` }} />
|
|
103
|
+
</div>
|
|
104
|
+
<p className="gauge-label session-gauge-sub">{sublabel}</p>
|
|
105
|
+
{data.sublabel && <p className="session-gauge-meta">{data.sublabel}</p>}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function SessionContextGauges({
|
|
111
|
+
sessions,
|
|
112
|
+
loading,
|
|
113
|
+
error,
|
|
114
|
+
launcherSessionId,
|
|
115
|
+
launcher,
|
|
116
|
+
}: SessionContextGaugesProps): React.ReactElement {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
|
|
119
|
+
// Launcher gauge (always rendered, sourced from the snapshot).
|
|
120
|
+
const gauges: GaugeData[] = [
|
|
121
|
+
{
|
|
122
|
+
key: "self",
|
|
123
|
+
label: "this session",
|
|
124
|
+
sublabel: "",
|
|
125
|
+
tokens: launcher.tokens,
|
|
126
|
+
percent: launcher.percent,
|
|
127
|
+
contextWindow: launcher.contextWindow,
|
|
128
|
+
isSelf: true,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
// Other active sessions (exclude the launcher by sessionId to avoid dup).
|
|
133
|
+
const others: ActiveSession[] = (sessions?.sessions ?? []).filter(
|
|
134
|
+
(s) => s.sessionId !== launcherSessionId,
|
|
135
|
+
);
|
|
136
|
+
// Sort by lastSeen descending (most recent first).
|
|
137
|
+
others.sort((a, b) => b.lastSeen - a.lastSeen);
|
|
138
|
+
for (const s of others) {
|
|
139
|
+
gauges.push({
|
|
140
|
+
key: s.sessionId,
|
|
141
|
+
label: repoBasename(s.repoRoot),
|
|
142
|
+
sublabel: `pid ${s.pid} · ${fmtAge(now - s.lastSeen)}${
|
|
143
|
+
s.model ? ` · ${s.model}` : ""
|
|
144
|
+
}`,
|
|
145
|
+
tokens: s.tokens,
|
|
146
|
+
percent: s.percent,
|
|
147
|
+
contextWindow: s.ctxWindow || 0,
|
|
148
|
+
isSelf: false,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const hasError = error && !sessions;
|
|
153
|
+
const showLoading = loading && !sessions;
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div className="card session-gauges-card">
|
|
157
|
+
<h3>Context per Session (live)</h3>
|
|
158
|
+
{hasError ? (
|
|
159
|
+
<div className="sessions-empty">
|
|
160
|
+
Error loading sessions: {error!.message}
|
|
161
|
+
</div>
|
|
162
|
+
) : showLoading ? (
|
|
163
|
+
<div className="sessions-empty">Loading sessions…</div>
|
|
164
|
+
) : gauges.length <= 1 && others.length === 0 ? (
|
|
165
|
+
// Launcher-only: just render the one self gauge (no "no sessions"
|
|
166
|
+
// message — the launcher always has a gauge).
|
|
167
|
+
<div className="session-gauges-grid">
|
|
168
|
+
{gauges.map((g) => (
|
|
169
|
+
<SessionGauge key={g.key} data={g} />
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
) : (
|
|
173
|
+
<div className="session-gauges-grid">
|
|
174
|
+
{gauges.map((g) => (
|
|
175
|
+
<SessionGauge key={g.key} data={g} />
|
|
176
|
+
))}
|
|
177
|
+
</div>
|
|
178
|
+
)}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -17,6 +17,7 @@ import "./styles/metrics-extra.css";
|
|
|
17
17
|
import "./styles/game-achievements.css";
|
|
18
18
|
import "./styles/sessions.css";
|
|
19
19
|
import "./styles/repostack.css";
|
|
20
|
+
import "./styles/session-gauges.css";
|
|
20
21
|
|
|
21
22
|
const rootEl = document.getElementById("root");
|
|
22
23
|
if (!rootEl) {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/session-gauges.css — Per-session context gauges (S40).
|
|
3
|
+
*
|
|
4
|
+
* Grid of compact gauges rendered on the Overview tab in place of the single
|
|
5
|
+
* launcher ContextGauge. The launcher's own gauge is highlighted via
|
|
6
|
+
* .session-gauge-self. Depends on .gauge-bar / .gauge-fill / .gauge-{green,
|
|
7
|
+
* yellow,red} from overview-events.css + base.css tokens.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
.session-gauges-card {
|
|
11
|
+
grid-column: 1 / -1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.session-gauges-grid {
|
|
15
|
+
display: grid;
|
|
16
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
17
|
+
gap: 14px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.session-gauge {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 6px;
|
|
24
|
+
padding: 10px 12px;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
border: 1px solid var(--border);
|
|
27
|
+
border-radius: 6px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.session-gauge-self {
|
|
31
|
+
border-color: var(--accent);
|
|
32
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
33
|
+
background: color-mix(in srgb, var(--accent) 8%, var(--bg));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.session-gauge-head {
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: space-between;
|
|
39
|
+
align-items: baseline;
|
|
40
|
+
gap: 6px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.session-gauge-label {
|
|
44
|
+
font-size: 12px;
|
|
45
|
+
font-weight: 600;
|
|
46
|
+
color: var(--text);
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
text-overflow: ellipsis;
|
|
49
|
+
white-space: nowrap;
|
|
50
|
+
max-width: 70%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.session-gauge-self .session-gauge-label {
|
|
54
|
+
color: var(--accent);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.session-gauge-pct {
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
font-weight: 600;
|
|
60
|
+
color: var(--muted);
|
|
61
|
+
white-space: nowrap;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.session-gauges-grid .gauge-bar {
|
|
65
|
+
height: 8px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.session-gauge-sub {
|
|
69
|
+
margin: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.session-gauge-meta {
|
|
73
|
+
margin: 0;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
color: var(--muted);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
text-overflow: ellipsis;
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@media (max-width: 640px) {
|
|
82
|
+
.session-gauges-grid {
|
|
83
|
+
grid-template-columns: 1fr;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -29,8 +29,8 @@ import type {
|
|
|
29
29
|
SessionsResponse,
|
|
30
30
|
} from "@contracts";
|
|
31
31
|
import type { RuntimeSnapshot } from "../utils/types";
|
|
32
|
-
import { ContextGauge } from "../components/ContextGauge";
|
|
33
32
|
import { RepoContextStack } from "../components/RepoContextStack";
|
|
33
|
+
import { SessionContextGauges } from "../components/SessionContextGauges";
|
|
34
34
|
import { TriggerStatus } from "../components/TriggerStatus";
|
|
35
35
|
import { VectorStoreCard } from "../components/VectorStoreCard";
|
|
36
36
|
import { RepoAllSessionsCard } from "../components/RepoAllSessionsCard";
|
|
@@ -81,10 +81,7 @@ export default function OverviewTab({
|
|
|
81
81
|
|
|
82
82
|
const { events } = useSSE();
|
|
83
83
|
|
|
84
|
-
const sampleEvents = useMemo(
|
|
85
|
-
() => events.filter(isSessionSample),
|
|
86
|
-
[events],
|
|
87
|
-
);
|
|
84
|
+
const sampleEvents = useMemo(() => events.filter(isSessionSample), [events]);
|
|
88
85
|
const lastSampleTs = useMemo(() => {
|
|
89
86
|
if (sampleEvents.length === 0) return null;
|
|
90
87
|
return sampleEvents[sampleEvents.length - 1].ts;
|
|
@@ -103,8 +100,7 @@ export default function OverviewTab({
|
|
|
103
100
|
|
|
104
101
|
// Cast to RuntimeSnapshot for tierPct (runtime-only field).
|
|
105
102
|
const d = snapshot as RuntimeSnapshot;
|
|
106
|
-
const { context, compression, trigger, session, store, tier, updatedAt } =
|
|
107
|
-
d;
|
|
103
|
+
const { context, compression, trigger, session, store, tier, updatedAt } = d;
|
|
108
104
|
const cfg = d.config;
|
|
109
105
|
const crew = d.crew;
|
|
110
106
|
const integrity = d.integrity;
|
|
@@ -121,10 +117,16 @@ export default function OverviewTab({
|
|
|
121
117
|
<span className="updated">updated {formatUpdatedAt(updatedAt)}</span>
|
|
122
118
|
</div>
|
|
123
119
|
<div className="card-grid overview-card-grid">
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
120
|
+
<SessionContextGauges
|
|
121
|
+
sessions={sessionsData}
|
|
122
|
+
loading={sessionsLoading}
|
|
123
|
+
error={sessionsErr}
|
|
124
|
+
launcherSessionId={session.id}
|
|
125
|
+
launcher={{
|
|
126
|
+
tokens: context.tokens,
|
|
127
|
+
percent: context.percent,
|
|
128
|
+
contextWindow: context.contextWindow,
|
|
129
|
+
}}
|
|
128
130
|
/>
|
|
129
131
|
<RepoContextStack
|
|
130
132
|
sessions={sessionsData}
|
|
@@ -121,9 +121,21 @@ async function s38TurnEnd(h: ReturnType<typeof harness>, stopReason: string | un
|
|
|
121
121
|
|
|
122
122
|
// ---- classifier unit tests (no extension harness needed) ----
|
|
123
123
|
|
|
124
|
-
test("S38: classifyError returns 'transient' for error
|
|
124
|
+
test("S38: classifyError returns 'transient' for error stopReason", () => {
|
|
125
125
|
assert.equal(classifyErrorFn({ stopReason: "error" }), "transient");
|
|
126
|
-
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("S38: classifyError returns 'cancelled' for aborted stopReason (ESC/Ctrl-C)", () => {
|
|
129
|
+
assert.equal(classifyErrorFn({ stopReason: "aborted" }), "cancelled");
|
|
130
|
+
assert.equal(classifyErrorFn({ stopReason: "aborted", errorMessage: "Operation aborted" }), "cancelled");
|
|
131
|
+
assert.equal(classifyErrorFn({ stopReason: "aborted", errorMessage: "Aborted after 3 retry attempts" }), "cancelled");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("S38: classifyError does NOT return 'cancelled' for error message containing 'aborted' with non-aborted stopReason", () => {
|
|
135
|
+
// Defense: the old text-based s.includes('aborted') path is gone,
|
|
136
|
+
// but verify that an error message mentioning 'aborted' with stopReason
|
|
137
|
+
// 'error' (not 'aborted') still classifies as transient, not cancelled.
|
|
138
|
+
assert.equal(classifyErrorFn({ stopReason: "error", errorMessage: "Connection aborted" }), "transient");
|
|
127
139
|
});
|
|
128
140
|
|
|
129
141
|
test("S38: classifyError returns 'transient' for max-output-token text", () => {
|
|
@@ -254,6 +266,24 @@ test("S38: context-overflow fires NO blind retry nudge and logs 'context_overflo
|
|
|
254
266
|
assert.ok(ev.includes("context_overflow"), "context-overflow: 'context_overflow' event logged");
|
|
255
267
|
});
|
|
256
268
|
|
|
269
|
+
test("S38: ESC-abort (stopReason='aborted') fires NO retry nudge and logs 'error_retry_cancelled'", async () => {
|
|
270
|
+
const h = harness();
|
|
271
|
+
await s38TurnEnd(h, "aborted", "Operation aborted");
|
|
272
|
+
const ev = eventTypes(h.stateDir);
|
|
273
|
+
assert.equal(h.sendUserMessages.length, 0, "cancelled: NO retry nudge fired after ESC abort");
|
|
274
|
+
assert.ok(ev.includes("error_retry_cancelled"), "cancelled: 'error_retry_cancelled' event logged");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("S38: ESC-abort resets errorRetryCount and consecutiveErrors so next transient retry starts fresh", async () => {
|
|
278
|
+
const h = harness();
|
|
279
|
+
// First: simulate an ESC abort (should reset both counters to 0)
|
|
280
|
+
await s38TurnEnd(h, "aborted", "Operation aborted");
|
|
281
|
+
assert.equal(h.sendUserMessages.length, 0, "cancelled: no nudge on abort");
|
|
282
|
+
// Then: simulate a real transient error — should fire a fresh retry from count=1
|
|
283
|
+
await s38TurnEnd(h, "error", "500 Internal Server Error");
|
|
284
|
+
assert.equal(h.sendUserMessages.length, 1, "transient after cancel: retry fires from fresh count");
|
|
285
|
+
});
|
|
286
|
+
|
|
257
287
|
// ---- integration tests (fire turn_end through the real extension) ----
|
|
258
288
|
|
|
259
289
|
test("S38: compaction-noop logs 'compaction_noop_diagnostic' + resets counter + no retry fired", async () => {
|
|
@@ -347,6 +347,21 @@ export function registerAgentHandlers(
|
|
|
347
347
|
sessionId: runtime.rt.sessionId,
|
|
348
348
|
turnIndex: event.turnIndex,
|
|
349
349
|
});
|
|
350
|
+
} else if (category === 'cancelled') {
|
|
351
|
+
// (4c) User ESC / Ctrl-C — stopReason === 'aborted'. NOT retryable:
|
|
352
|
+
// nudging would restart a task the user explicitly stopped.
|
|
353
|
+
// Reset both counters (a cancel is not an error for circuit-breaker
|
|
354
|
+
// purposes). Emit a diagnostic so the dashboard shows it.
|
|
355
|
+
runtime.rt.errorRetryCount = 0;
|
|
356
|
+
runtime.rt.consecutiveErrors = 0;
|
|
357
|
+
runtime.dashboard.event('error_retry_cancelled', {
|
|
358
|
+
turnIndex: event.turnIndex,
|
|
359
|
+
sessionId: runtime.rt.sessionId,
|
|
360
|
+
});
|
|
361
|
+
runtime.logger.info('error-retry-cancelled', {
|
|
362
|
+
sessionId: runtime.rt.sessionId,
|
|
363
|
+
turnIndex: event.turnIndex,
|
|
364
|
+
});
|
|
350
365
|
} else if (category === 'context-overflow') {
|
|
351
366
|
// (4b) context-window overflow 400 ("too long... even after compaction").
|
|
352
367
|
// NOT a blind retry: re-submitting the same oversized prompt would just
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
* exclusively (its agent_end nudge path is separate and must not be doubled).
|
|
13
13
|
*
|
|
14
14
|
* @param message the event.message (a pi AgentMessage) or an error string
|
|
15
|
-
* @returns 'transient' | 'permanent' | 'compaction-noop' | 'context-overflow' | null (success/unknown)
|
|
15
|
+
* @returns 'transient' | 'permanent' | 'compaction-noop' | 'context-overflow' | 'cancelled' | null (success/unknown)
|
|
16
16
|
*/
|
|
17
17
|
export function classifyError(message: unknown):
|
|
18
18
|
| 'transient'
|
|
19
19
|
| 'permanent'
|
|
20
20
|
| 'compaction-noop'
|
|
21
21
|
| 'context-overflow'
|
|
22
|
+
| 'cancelled'
|
|
22
23
|
| null {
|
|
23
24
|
// Resolve a searchable text blob from a pi AgentMessage or raw string.
|
|
24
25
|
let text = '';
|
|
@@ -33,6 +34,9 @@ export function classifyError(message: unknown):
|
|
|
33
34
|
const sr = typeof m.stopReason === 'string' ? m.stopReason : '';
|
|
34
35
|
// S28 guard: length stopReason is handled exclusively by the S28 path.
|
|
35
36
|
if (sr === 'length') return null;
|
|
37
|
+
// User ESC / Ctrl-C abort — stopReason === 'aborted'. Not retryable:
|
|
38
|
+
// nudging would restart a task the user explicitly stopped.
|
|
39
|
+
if (sr === 'aborted') return 'cancelled';
|
|
36
40
|
// Success / normal tool flow — not an error, nothing to retry.
|
|
37
41
|
if (sr === 'stop' || sr === 'toolUse' || sr === 'tool_use') return null;
|
|
38
42
|
const parts: string[] = [];
|
|
@@ -109,7 +113,9 @@ export function classifyError(message: unknown):
|
|
|
109
113
|
if (s.includes('error') && !/\b(permanent|invalid request|malformed|bad request|auth|unauthorized|invalid (api )?key|permission)\b/.test(s)) {
|
|
110
114
|
return 'transient'; // generic pi stopReason 'error' / 'aborted'
|
|
111
115
|
}
|
|
112
|
-
|
|
116
|
+
// NOTE: 'aborted' stopReason is handled by the sr==='aborted' early-return above.
|
|
117
|
+
// The text-based s.includes('aborted') was removed — it was the old path that
|
|
118
|
+
// misclassified user ESC/Ctrl-C as 'transient' (5 retry nudges after cancel).
|
|
113
119
|
if (/max(imum)? output token/.test(s)) return 'transient';
|
|
114
120
|
if (/rate[\s.-]?limit|429|too many requests/.test(s)) return 'transient';
|
|
115
121
|
if (/5\d\d|internal server|bad gateway|service unavailable/.test(s)) return 'transient';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.21",
|
|
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",
|