pi-mega-compact 0.8.18 → 0.8.20
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 +2 -2
- package/dist/extensions/mega-events/context-handler.js +22 -8
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-CNWZ5NjP.js → AchievementTiles-QPo46Pwg.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-CNWZ5NjP.js.map → AchievementTiles-QPo46Pwg.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/AchievementsTab-BDJxRXRY.js +2 -0
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-BKPifNKj.js.map → AchievementsTab-BDJxRXRY.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab--epzrQHD.js → CacheTab-CUMqXQjd.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab--epzrQHD.js.map → CacheTab-CUMqXQjd.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-D3ralwSj.js → ConfigTab-j1dRzurG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-D3ralwSj.js.map → ConfigTab-j1dRzurG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-BKkuVQsM.js → EventsTab-CMglXK4p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-BKkuVQsM.js.map → EventsTab-CMglXK4p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CgUvITYy.js → GameTab-fpaDoyxi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CgUvITYy.js.map → GameTab-fpaDoyxi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DrlJqXGL.js → MetricsTab-CWG2crG_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-DrlJqXGL.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-DjEZmqUb.js → ReposTab-C-0kuZ_0.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DjEZmqUb.js.map → ReposTab-C-0kuZ_0.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DsYMUzoh.js → SessionsTab-CSYhddCA.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-DsYMUzoh.js.map → SessionsTab-CSYhddCA.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BbAXT1wO.js → TimeSavedCard-Dy4KRjSM.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BbAXT1wO.js.map → TimeSavedCard-Dy4KRjSM.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CnRzusfi.js → index-D_WtU2TV.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CnRzusfi.js.map → index-D_WtU2TV.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-BTft6DM_.css → index-og3ghxyY.css} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf-1rZ2N.js → useSSE-C9yVJlGP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf-1rZ2N.js.map → useSSE-C9yVJlGP.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/components/RepoContextStack.tsx +238 -0
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +181 -0
- package/extensions/dashboard-client/src/main.tsx +2 -0
- package/extensions/dashboard-client/src/styles/repostack.css +87 -0
- package/extensions/dashboard-client/src/styles/session-gauges.css +85 -0
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +58 -8
- package/extensions/mega-events/context-handler.ts +40 -18
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/AchievementsTab-BKPifNKj.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-_gD756wJ.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-_gD756wJ.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}}
|
|
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-Bf-1rZ2N.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,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/components/RepoContextStack.tsx — Stacked per-repo context bar (S40).
|
|
3
|
+
*
|
|
4
|
+
* Horizontal stacked bar: one colored segment per active repo, aggregating
|
|
5
|
+
* that repo's active sessions' token counts. Renders a combined total line
|
|
6
|
+
* and a legend mapping each repo (basename of repoRoot) to its segment color
|
|
7
|
+
* + token count + % of total.
|
|
8
|
+
*
|
|
9
|
+
* Stable per-repo colors: the repoRoot basename is hashed to a deterministic
|
|
10
|
+
* index into a fixed palette array, so the same repo always gets the same
|
|
11
|
+
* color across renders/tabs (mirrors SessionsMemoryChart's stable-color
|
|
12
|
+
* contract from S39).
|
|
13
|
+
*
|
|
14
|
+
* PREVENT-PI-004: this is a pure presentational component — no fetch, no
|
|
15
|
+
* network. Data is passed in by the parent (OverviewTab) via
|
|
16
|
+
* useApi(fetchSessions) + useSSE() for real-time updates.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type React from "react";
|
|
20
|
+
import type { ActiveSession, SessionsResponse } from "@contracts";
|
|
21
|
+
|
|
22
|
+
export interface RepoContextStackProps {
|
|
23
|
+
/** Active sessions from /api/sessions (may be null while loading). */
|
|
24
|
+
sessions: SessionsResponse | null;
|
|
25
|
+
/** Whether the initial fetch is in flight. */
|
|
26
|
+
loading: boolean;
|
|
27
|
+
/** Fetch error, if any. */
|
|
28
|
+
error: Error | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Stable color palette ─────────────────────────────────────────────────────
|
|
32
|
+
// Fixed set of hex colors used for stable per-repo coloring. The hash function
|
|
33
|
+
// maps a repoRoot basename to a deterministic palette index so the same repo
|
|
34
|
+
// always renders the same color.
|
|
35
|
+
const REPO_PALETTE: ReadonlyArray<string> = [
|
|
36
|
+
"#58a6ff", // blue
|
|
37
|
+
"#3fb950", // green
|
|
38
|
+
"#d29922", // yellow
|
|
39
|
+
"#f85149", // red
|
|
40
|
+
"#bc8cff", // purple
|
|
41
|
+
"#ff7b72", // salmon
|
|
42
|
+
"#79c0ff", // light blue
|
|
43
|
+
"#56d4dd", // cyan
|
|
44
|
+
"#e3b341", // gold
|
|
45
|
+
"#a5d6ff", // pale blue
|
|
46
|
+
"#7ee787", // mint
|
|
47
|
+
"#ffa657", // orange
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Simple deterministic string hash (djb2 variant). Returns a non-negative
|
|
52
|
+
* 32-bit integer.
|
|
53
|
+
*/
|
|
54
|
+
function hashString(str: string): number {
|
|
55
|
+
let hash = 5381;
|
|
56
|
+
for (let i = 0; i < str.length; i++) {
|
|
57
|
+
hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0;
|
|
58
|
+
}
|
|
59
|
+
return hash;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Pick a stable color for a repoRoot basename. Falls back to "(unknown)" if
|
|
64
|
+
* the basename is empty.
|
|
65
|
+
*/
|
|
66
|
+
function colorForRepo(basename: string): string {
|
|
67
|
+
if (!basename) return REPO_PALETTE[0];
|
|
68
|
+
return REPO_PALETTE[hashString(basename) % REPO_PALETTE.length];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Extract the basename from a repoRoot path. Uses `"/"` as the universal
|
|
73
|
+
* separator on all OSes (repoRoot is an absolute path from the dashboard
|
|
74
|
+
* server; on Windows pi actually uses forward-slash normalized paths anyway).
|
|
75
|
+
*/
|
|
76
|
+
function repoBasename(repoRoot: string | null): string {
|
|
77
|
+
if (!repoRoot) return "(unknown)";
|
|
78
|
+
const trimmed = repoRoot.replace(/[\\/]+$/, "");
|
|
79
|
+
if (!trimmed) return "(unknown)";
|
|
80
|
+
const parts = trimmed.split(/[\\/]/);
|
|
81
|
+
const last = parts[parts.length - 1];
|
|
82
|
+
return last || trimmed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Aggregation ─────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
interface RepoAggregate {
|
|
88
|
+
repoRoot: string | null;
|
|
89
|
+
basename: string;
|
|
90
|
+
tokens: number;
|
|
91
|
+
color: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function aggregateRepos(sessions: ReadonlyArray<ActiveSession>): {
|
|
95
|
+
repos: RepoAggregate[];
|
|
96
|
+
totalTokens: number;
|
|
97
|
+
maxWindow: number;
|
|
98
|
+
sessionCount: number;
|
|
99
|
+
} {
|
|
100
|
+
const map = new Map<string, RepoAggregate>();
|
|
101
|
+
for (const s of sessions) {
|
|
102
|
+
const key = s.repoRoot ?? "__null__";
|
|
103
|
+
const tokens = s.tokens ?? 0;
|
|
104
|
+
const existing = map.get(key);
|
|
105
|
+
if (existing) {
|
|
106
|
+
existing.tokens += tokens;
|
|
107
|
+
} else {
|
|
108
|
+
const basename = repoBasename(s.repoRoot);
|
|
109
|
+
map.set(key, {
|
|
110
|
+
repoRoot: s.repoRoot,
|
|
111
|
+
basename,
|
|
112
|
+
tokens,
|
|
113
|
+
color: colorForRepo(basename),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const repos = [...map.values()];
|
|
118
|
+
const totalTokens = repos.reduce((sum, r) => sum + r.tokens, 0);
|
|
119
|
+
// Combined max window: the largest ctxWindow among the active sessions.
|
|
120
|
+
// This mirrors SessionsTab's SummaryTiles combined-% logic.
|
|
121
|
+
const maxWindow = sessions.reduce(
|
|
122
|
+
(max, s) => Math.max(max, s.ctxWindow),
|
|
123
|
+
0,
|
|
124
|
+
);
|
|
125
|
+
return {
|
|
126
|
+
repos,
|
|
127
|
+
totalTokens,
|
|
128
|
+
maxWindow,
|
|
129
|
+
sessionCount: sessions.length,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function fmtTokens(n: number): string {
|
|
134
|
+
if (n >= 100_000) return `${(n / 1000).toFixed(0)}k`;
|
|
135
|
+
return n.toLocaleString();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function RepoContextStack({
|
|
139
|
+
sessions,
|
|
140
|
+
loading,
|
|
141
|
+
error,
|
|
142
|
+
}: RepoContextStackProps): React.ReactElement | null {
|
|
143
|
+
if (error) {
|
|
144
|
+
return (
|
|
145
|
+
<div className="card repo-stack-card">
|
|
146
|
+
<h3>Context per Repo (live)</h3>
|
|
147
|
+
<div className="sessions-empty">
|
|
148
|
+
Error loading sessions: {error.message}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (loading && !sessions) {
|
|
155
|
+
return (
|
|
156
|
+
<div className="card repo-stack-card">
|
|
157
|
+
<h3>Context per Repo (live)</h3>
|
|
158
|
+
<div className="sessions-empty">Loading sessions…</div>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const activeSessions = sessions?.sessions ?? [];
|
|
164
|
+
const { repos, totalTokens, maxWindow, sessionCount } =
|
|
165
|
+
aggregateRepos(activeSessions);
|
|
166
|
+
|
|
167
|
+
if (repos.length === 0) {
|
|
168
|
+
return (
|
|
169
|
+
<div className="card repo-stack-card">
|
|
170
|
+
<h3>Context per Repo (live)</h3>
|
|
171
|
+
<div className="sessions-empty">No active sessions.</div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const combinedPct =
|
|
177
|
+
maxWindow > 0 ? Math.round((totalTokens / maxWindow) * 100) : 0;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div className="card repo-stack-card">
|
|
181
|
+
<h3>Context per Repo (live)</h3>
|
|
182
|
+
<div
|
|
183
|
+
className="repo-stack-bar"
|
|
184
|
+
role="group"
|
|
185
|
+
aria-label="Per-repo context usage"
|
|
186
|
+
>
|
|
187
|
+
{repos.map((r) => {
|
|
188
|
+
const pctOfTotal =
|
|
189
|
+
totalTokens > 0 ? (r.tokens / totalTokens) * 100 : 0;
|
|
190
|
+
const widthPct = totalTokens > 0 ? pctOfTotal : 0;
|
|
191
|
+
const tooltip =
|
|
192
|
+
`${r.basename}: ${fmtTokens(r.tokens)} tokens ` +
|
|
193
|
+
`(${pctOfTotal.toFixed(1)}% of total)`;
|
|
194
|
+
return (
|
|
195
|
+
<div
|
|
196
|
+
key={r.repoRoot ?? "__null__"}
|
|
197
|
+
className="repo-stack-segment"
|
|
198
|
+
style={{
|
|
199
|
+
width: `${Math.max(widthPct, 0.5)}%`,
|
|
200
|
+
backgroundColor: r.color,
|
|
201
|
+
}}
|
|
202
|
+
title={tooltip}
|
|
203
|
+
aria-label={tooltip}
|
|
204
|
+
/>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
207
|
+
</div>
|
|
208
|
+
<p className="repo-stack-total">
|
|
209
|
+
{fmtTokens(totalTokens)} tokens across {repos.length}{" "}
|
|
210
|
+
{repos.length === 1 ? "repo" : "repos"} /{" "}
|
|
211
|
+
{sessionCount}{" "}
|
|
212
|
+
{sessionCount === 1 ? "session" : "sessions"}{" "}
|
|
213
|
+
<span className="pct">— {combinedPct}% of combined max window</span>
|
|
214
|
+
</p>
|
|
215
|
+
<div className="repo-stack-legend">
|
|
216
|
+
{repos.map((r) => {
|
|
217
|
+
const pctOfTotal =
|
|
218
|
+
totalTokens > 0 ? (r.tokens / totalTokens) * 100 : 0;
|
|
219
|
+
return (
|
|
220
|
+
<div
|
|
221
|
+
key={r.repoRoot ?? "__null__"}
|
|
222
|
+
className="repo-stack-legend-item"
|
|
223
|
+
>
|
|
224
|
+
<span
|
|
225
|
+
className="repo-stack-legend-swatch"
|
|
226
|
+
style={{ backgroundColor: r.color }}
|
|
227
|
+
/>
|
|
228
|
+
<span className="repo-stack-legend-name">{r.basename}</span>
|
|
229
|
+
<span>
|
|
230
|
+
{fmtTokens(r.tokens)} ({pctOfTotal.toFixed(0)}%)
|
|
231
|
+
</span>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
})}
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -16,6 +16,8 @@ import "./styles/config.css";
|
|
|
16
16
|
import "./styles/metrics-extra.css";
|
|
17
17
|
import "./styles/game-achievements.css";
|
|
18
18
|
import "./styles/sessions.css";
|
|
19
|
+
import "./styles/repostack.css";
|
|
20
|
+
import "./styles/session-gauges.css";
|
|
19
21
|
|
|
20
22
|
const rootEl = document.getElementById("root");
|
|
21
23
|
if (!rootEl) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-client/src/styles/repostack.css — Stacked per-repo context bar (S40).
|
|
3
|
+
*
|
|
4
|
+
* Horizontal stacked bar: one colored segment per active repo (aggregating
|
|
5
|
+
* token counts across that repo's active sessions). Depends on base.css
|
|
6
|
+
* tokens (--panel, --border, --text, --muted, --accent, --ok, --warn, --err).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
.repo-stack-card {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 10px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.repo-stack-card h3 {
|
|
16
|
+
margin: 0;
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
color: var(--text);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.repo-stack-bar {
|
|
23
|
+
display: flex;
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 22px;
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
border: 1px solid var(--border);
|
|
29
|
+
background: var(--bg);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.repo-stack-segment {
|
|
33
|
+
height: 100%;
|
|
34
|
+
min-width: 2px;
|
|
35
|
+
transition: opacity 0.15s, filter 0.15s;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.repo-stack-segment:hover {
|
|
39
|
+
opacity: 0.85;
|
|
40
|
+
filter: brightness(1.15);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.repo-stack-total {
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
color: var(--text);
|
|
46
|
+
font-variant-numeric: tabular-nums;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.repo-stack-total .pct {
|
|
50
|
+
color: var(--muted);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.repo-stack-legend {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
56
|
+
gap: 6px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.repo-stack-legend-item {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 6px;
|
|
63
|
+
font-size: 12px;
|
|
64
|
+
color: var(--muted);
|
|
65
|
+
font-variant-numeric: tabular-nums;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.repo-stack-legend-swatch {
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
width: 12px;
|
|
71
|
+
height: 12px;
|
|
72
|
+
border-radius: 3px;
|
|
73
|
+
border: 1px solid var(--border);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.repo-stack-legend-name {
|
|
77
|
+
color: var(--text);
|
|
78
|
+
overflow: hidden;
|
|
79
|
+
text-overflow: ellipsis;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@media (max-width: 640px) {
|
|
84
|
+
.repo-stack-legend {
|
|
85
|
+
grid-template-columns: 1fr;
|
|
86
|
+
}
|
|
87
|
+
}
|