pi-mega-compact 0.8.8 → 0.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/core.js +7 -0
  2. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +104 -0
  3. package/dist/extensions/dashboard-server/api-contracts/game.js +9 -0
  4. package/dist/extensions/dashboard-server/api-contracts/index.js +9 -0
  5. package/dist/extensions/dashboard-server/api-contracts/infrastructure.js +10 -0
  6. package/dist/extensions/dashboard-server/api-contracts/multi-repo.js +9 -0
  7. package/dist/extensions/dashboard-server/api-contracts/snapshot.js +8 -0
  8. package/dist/extensions/dashboard-server/api-contracts.js +8 -0
  9. package/dist/extensions/dashboard-server/api-contracts.test.js +869 -0
  10. package/dist/extensions/dashboard-server/auth.js +18 -0
  11. package/dist/extensions/dashboard-server/helpers.js +37 -0
  12. package/dist/extensions/dashboard-server/html/all-repos-tab.js +26 -0
  13. package/dist/extensions/dashboard-server/html/body-open.js +23 -0
  14. package/dist/extensions/dashboard-server/html/current-repo-tab.js +130 -0
  15. package/dist/extensions/dashboard-server/html/head-open.js +16 -0
  16. package/dist/extensions/dashboard-server/html/high-score-tab.js +25 -0
  17. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +26 -0
  18. package/dist/extensions/dashboard-server/html/script.js +259 -0
  19. package/dist/extensions/dashboard-server/html/styles.js +103 -0
  20. package/dist/extensions/dashboard-server/html/summary-tab.js +19 -0
  21. package/dist/extensions/dashboard-server/html-template.js +41 -0
  22. package/dist/extensions/dashboard-server/server.js +191 -39
  23. package/dist/extensions/dashboard-server/tailscale.js +8 -0
  24. package/dist/extensions/dashboard-server/types.js +4 -0
  25. package/dist/src/store/sqlite/connection.js +35 -0
  26. package/dist/src/store/sqlite/index-store.js +167 -0
  27. package/dist/src/store/sqlite/memory.js +54 -0
  28. package/dist/src/store/sqlite/minhash-lsh.js +47 -0
  29. package/dist/src/store/sqlite/sessions.js +39 -0
  30. package/dist/src/store/sqlite/transaction.js +19 -0
  31. package/dist/src/vectorStore/add.js +260 -0
  32. package/dist/src/vectorStore/dedup.js +52 -0
  33. package/dist/src/vectorStore/index.js +10 -0
  34. package/dist/src/vectorStore/queries.js +83 -0
  35. package/dist/src/vectorStore/search.js +95 -0
  36. package/dist/src/vectorStore/session.js +19 -0
  37. package/dist/src/vectorStore/store.js +105 -0
  38. package/dist/src/vectorStore/types.js +6 -0
  39. package/dist/src/vectorStore/utils.js +23 -0
  40. package/extensions/dashboard-client/package-lock.json +1771 -0
  41. package/extensions/dashboard-client/package.json +27 -0
  42. package/extensions/dashboard-client/src/App.tsx +82 -0
  43. package/extensions/dashboard-client/src/api/client.ts +145 -0
  44. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +44 -0
  45. package/extensions/dashboard-client/src/components/CompressionCard.tsx +61 -0
  46. package/extensions/dashboard-client/src/components/ContextGauge.tsx +59 -0
  47. package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +49 -0
  48. package/extensions/dashboard-client/src/components/ErrorBoundary.tsx +53 -0
  49. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +16 -0
  50. package/extensions/dashboard-client/src/components/EventStream.tsx +152 -0
  51. package/extensions/dashboard-client/src/components/LoadingSpinner.tsx +7 -0
  52. package/extensions/dashboard-client/src/components/MemoryStatusCard.tsx +24 -0
  53. package/extensions/dashboard-client/src/components/ModelBadge.tsx +41 -0
  54. package/extensions/dashboard-client/src/components/PerfChart.tsx +160 -0
  55. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +126 -0
  56. package/extensions/dashboard-client/src/components/RepoTable.tsx +150 -0
  57. package/extensions/dashboard-client/src/components/SessionInfo.tsx +65 -0
  58. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +52 -0
  59. package/extensions/dashboard-client/src/components/TabBar.tsx +34 -0
  60. package/extensions/dashboard-client/src/components/TriggerStatus.tsx +62 -0
  61. package/extensions/dashboard-client/src/hooks/useApi.ts +77 -0
  62. package/extensions/dashboard-client/src/hooks/useSSE.ts +87 -0
  63. package/extensions/dashboard-client/src/index.html +12 -0
  64. package/extensions/dashboard-client/src/main.tsx +23 -0
  65. package/extensions/dashboard-client/src/styles/base.css +121 -0
  66. package/extensions/dashboard-client/src/styles/overview-events.css +318 -0
  67. package/extensions/dashboard-client/src/styles/repos-metrics.css +307 -0
  68. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +16 -0
  69. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +37 -0
  70. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +49 -0
  71. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +80 -0
  72. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +59 -0
  73. package/extensions/dashboard-client/tsconfig.json +28 -0
  74. package/extensions/dashboard-client/vite.config.ts +38 -0
  75. package/extensions/dashboard-server/api-contracts/core.ts +302 -0
  76. package/extensions/dashboard-server/api-contracts/endpoints.ts +476 -0
  77. package/extensions/dashboard-server/api-contracts/game.ts +145 -0
  78. package/extensions/dashboard-server/api-contracts/index.ts +159 -0
  79. package/extensions/dashboard-server/api-contracts/infrastructure.ts +465 -0
  80. package/extensions/dashboard-server/api-contracts/multi-repo.ts +328 -0
  81. package/extensions/dashboard-server/api-contracts/snapshot.ts +386 -0
  82. package/extensions/dashboard-server/api-contracts.test.ts +1050 -0
  83. package/extensions/dashboard-server/api-contracts.ts +96 -0
  84. package/extensions/dashboard-server/auth.ts +20 -0
  85. package/extensions/dashboard-server/server.ts +862 -582
  86. package/extensions/dashboard-server/tailscale.ts +7 -0
  87. package/extensions/dashboard-server/types.ts +23 -114
  88. package/package.json +2 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * dashboard-client/src/components/TriggerStatus.tsx — trigger armed/ready status.
3
+ *
4
+ * Two bullets (armed, ready) + threshold display + fast-gate %.
5
+ */
6
+
7
+ import type React from "react";
8
+
9
+ export interface TriggerStatusProps {
10
+ /** Whether the trigger is armed (waiting for threshold). */
11
+ armed: boolean;
12
+ /** Whether the trigger is ready to fire (pressure at/above threshold). */
13
+ ready: boolean;
14
+ /** Current context token count, or null if unknown. */
15
+ currentTokens: number | null;
16
+ /** Token threshold for firing. */
17
+ thresholdTokens: number;
18
+ /** Fast-gate pressure threshold (percent, 0–100). */
19
+ fastGatePct: number;
20
+ }
21
+
22
+ function Bullet({
23
+ on,
24
+ label,
25
+ }: {
26
+ on: boolean;
27
+ label: string;
28
+ }): React.ReactElement {
29
+ return (
30
+ <div className={`bullet ${on ? "bullet-on" : "bullet-off"}`}>
31
+ <span className="bullet-dot" aria-hidden="true" />
32
+ <span className="bullet-label">{label}</span>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export function TriggerStatus({
38
+ armed,
39
+ ready,
40
+ currentTokens,
41
+ thresholdTokens,
42
+ fastGatePct,
43
+ }: TriggerStatusProps): React.ReactElement {
44
+ const cur =
45
+ currentTokens !== null && currentTokens !== undefined
46
+ ? currentTokens.toLocaleString()
47
+ : "unknown";
48
+ return (
49
+ <div className="card trigger-status">
50
+ <h3>Trigger</h3>
51
+ <div className="bullet-row">
52
+ <Bullet on={armed} label="Armed" />
53
+ <Bullet on={ready} label="Ready" />
54
+ </div>
55
+ <p className="trigger-detail">
56
+ Threshold: {thresholdTokens.toLocaleString()} tokens
57
+ </p>
58
+ <p className="trigger-detail">Current: {cur} tokens</p>
59
+ <p className="trigger-detail">Fast gate: {fastGatePct}%</p>
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * dashboard-client/src/hooks/useApi.ts — Generic data fetching hook.
3
+ *
4
+ * Provides typed fetch with retry, stale detection, and error handling.
5
+ * SPRINT-B1: basic fetch. SPRINT-D1: retry + stale.
6
+ */
7
+
8
+ import { useState, useEffect, useCallback, useRef } from "react";
9
+
10
+ export interface UseApiResult<T> {
11
+ data: T | null;
12
+ error: Error | null;
13
+ loading: boolean;
14
+ refetch: () => void;
15
+ lastFetchedAt: number | null;
16
+ }
17
+
18
+ export interface UseApiOptions {
19
+ /** Polling interval in ms. 0 = no polling. */
20
+ pollInterval?: number;
21
+ /** Max retry attempts on failure. */
22
+ maxRetries?: number;
23
+ /** Base retry delay in ms. */
24
+ retryBaseMs?: number;
25
+ }
26
+
27
+ // SPRINT-D1-REMAINING: integrate retryWithBackoff from utils/retry.ts.
28
+ // SPRINT-D1-REMAINING: integrate staleness detection from utils/staleness.ts.
29
+ // SPRINT-T1-REMAINING: add Authorization header when auth token present.
30
+
31
+ export function useApi<T>(
32
+ fetchFn: () => Promise<T>,
33
+ options: UseApiOptions = {},
34
+ ): UseApiResult<T> {
35
+ const { pollInterval = 0 } = options;
36
+ const [data, setData] = useState<T | null>(null);
37
+ const [error, setError] = useState<Error | null>(null);
38
+ const [loading, setLoading] = useState(true);
39
+ const [lastFetchedAt, setLastFetchedAt] = useState<number | null>(null);
40
+ const mountedRef = useRef(true);
41
+
42
+ const doFetch = useCallback(async () => {
43
+ setLoading(true);
44
+ setError(null);
45
+ try {
46
+ const result = await fetchFn();
47
+ if (mountedRef.current) {
48
+ setData(result);
49
+ setLastFetchedAt(Date.now());
50
+ }
51
+ } catch (err) {
52
+ if (mountedRef.current) {
53
+ setError(err instanceof Error ? err : new Error(String(err)));
54
+ }
55
+ } finally {
56
+ if (mountedRef.current) {
57
+ setLoading(false);
58
+ }
59
+ }
60
+ }, [fetchFn]);
61
+
62
+ useEffect(() => {
63
+ mountedRef.current = true;
64
+ doFetch();
65
+ return () => {
66
+ mountedRef.current = false;
67
+ };
68
+ }, [doFetch]);
69
+
70
+ useEffect(() => {
71
+ if (pollInterval <= 0) return;
72
+ const timer = setInterval(doFetch, pollInterval);
73
+ return () => clearInterval(timer);
74
+ }, [doFetch, pollInterval]);
75
+
76
+ return { data, error, loading, refetch: doFetch, lastFetchedAt };
77
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.
3
+ *
4
+ * Connects to /api/events, provides real-time event stream.
5
+ * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.
6
+ */
7
+
8
+ import { useState, useEffect, useRef, useCallback } from 'react';
9
+ import type { SseEvent } from '../../../dashboard-server/api-contracts';
10
+
11
+ export type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
12
+
13
+ export interface UseSSEResult {
14
+ events: SseEvent[];
15
+ status: SseStatus;
16
+ eventCount: number;
17
+ lastEventAt: number | null;
18
+ }
19
+
20
+ export interface UseSSEOptions {
21
+ /** Max events to keep in memory (ring buffer). */
22
+ maxEvents?: number;
23
+ /** Max reconnect attempts before giving up. */
24
+ maxReconnects?: number;
25
+ }
26
+
27
+ // SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).
28
+ // SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.
29
+
30
+ export function useSSE(options: UseSSEOptions = {}): UseSSEResult {
31
+ const { maxEvents = 500, maxReconnects = 5 } = options;
32
+ const [events, setEvents] = useState<SseEvent[]>([]);
33
+ const [status, setStatus] = useState<SseStatus>('connecting');
34
+ const [eventCount, setEventCount] = useState(0);
35
+ const [lastEventAt, setLastEventAt] = useState<number | null>(null);
36
+ const reconnectCount = useRef(0);
37
+ const sourceRef = useRef<EventSource | null>(null);
38
+
39
+ const connect = useCallback(() => {
40
+ if (sourceRef.current) {
41
+ sourceRef.current.close();
42
+ }
43
+ setStatus('connecting');
44
+ const source = new EventSource('/api/events');
45
+ sourceRef.current = source;
46
+
47
+ source.onopen = () => {
48
+ setStatus('connected');
49
+ reconnectCount.current = 0;
50
+ };
51
+
52
+ source.onmessage = (event) => {
53
+ try {
54
+ const parsed = JSON.parse(event.data) as SseEvent;
55
+ setEvents(prev => {
56
+ const next = [...prev, parsed];
57
+ return next.length > maxEvents ? next.slice(-maxEvents) : next;
58
+ });
59
+ setEventCount(c => c + 1);
60
+ setLastEventAt(Date.now());
61
+ } catch {
62
+ // non-fatal: skip malformed events
63
+ }
64
+ };
65
+
66
+ source.onerror = () => {
67
+ source.close();
68
+ if (reconnectCount.current < maxReconnects) {
69
+ reconnectCount.current += 1;
70
+ setStatus('connecting');
71
+ // SPRINT-D1: use exponential backoff instead of fixed delay
72
+ setTimeout(connect, 1000);
73
+ } else {
74
+ setStatus('disconnected');
75
+ }
76
+ };
77
+ }, [maxEvents, maxReconnects]);
78
+
79
+ useEffect(() => {
80
+ connect();
81
+ return () => {
82
+ sourceRef.current?.close();
83
+ };
84
+ }, [connect]);
85
+
86
+ return { events, status, eventCount, lastEventAt };
87
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>mega-compact dashboard</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,23 @@
1
+ /**
2
+ * dashboard-client/src/main.tsx — React entry point.
3
+ *
4
+ * Mounts <App /> into #root. Loaded by index.html.
5
+ */
6
+
7
+ import { StrictMode } from "react";
8
+ import { createRoot } from "react-dom/client";
9
+ import App from "./App";
10
+ import "./styles/base.css";
11
+ import "./styles/overview-events.css";
12
+ import "./styles/repos-metrics.css";
13
+
14
+ const rootEl = document.getElementById("root");
15
+ if (!rootEl) {
16
+ throw new Error("dashboard-client: #root element not found in index.html");
17
+ }
18
+
19
+ createRoot(rootEl).render(
20
+ <StrictMode>
21
+ <App />
22
+ </StrictMode>,
23
+ );
@@ -0,0 +1,121 @@
1
+ /**
2
+ * dashboard-client/src/styles/base.css — shell + theme tokens + layout primitives.
3
+ *
4
+ * Shared by all tabs. Theme picker (Sprint C3) will override these tokens.
5
+ * Kept under 300 lines per project doc-length guideline.
6
+ */
7
+
8
+ :root {
9
+ --bg: #0d1117;
10
+ --panel: #161b22;
11
+ --border: #30363d;
12
+ --text: #e6edf3;
13
+ --muted: #8b949e;
14
+ --accent: #58a6ff;
15
+ --ok: #3fb950;
16
+ --warn: #d29922;
17
+ --err: #f85149;
18
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ background: var(--bg);
28
+ color: var(--text);
29
+ font-size: 14px;
30
+ }
31
+
32
+ .dashboard-app {
33
+ max-width: 1200px;
34
+ margin: 0 auto;
35
+ padding: 16px;
36
+ }
37
+
38
+ .dashboard-header h1 {
39
+ font-size: 18px;
40
+ font-weight: 600;
41
+ margin: 0 0 12px;
42
+ }
43
+
44
+ .dashboard-header .tier,
45
+ .dashboard-header .version-pill {
46
+ display: inline-block;
47
+ margin-left: 8px;
48
+ padding: 2px 8px;
49
+ font-size: 12px;
50
+ border-radius: 10px;
51
+ background: var(--panel);
52
+ border: 1px solid var(--border);
53
+ color: var(--muted);
54
+ }
55
+
56
+ .dashboard-header .tier {
57
+ color: var(--accent);
58
+ }
59
+
60
+ .tab-bar {
61
+ display: flex;
62
+ gap: 4px;
63
+ border-bottom: 1px solid var(--border);
64
+ margin-bottom: 16px;
65
+ }
66
+
67
+ .tab-bar button {
68
+ background: none;
69
+ border: none;
70
+ color: var(--muted);
71
+ padding: 8px 14px;
72
+ cursor: pointer;
73
+ font-size: 13px;
74
+ border-bottom: 2px solid transparent;
75
+ }
76
+
77
+ .tab-bar button.active {
78
+ color: var(--text);
79
+ border-bottom-color: var(--accent);
80
+ }
81
+
82
+ .tab-bar button:hover {
83
+ color: var(--text);
84
+ }
85
+
86
+ .dashboard-content {
87
+ min-height: 400px;
88
+ }
89
+
90
+ .loading-spinner {
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ padding: 48px;
95
+ color: var(--muted);
96
+ }
97
+
98
+ .error-fallback {
99
+ padding: 24px;
100
+ border: 1px solid var(--err);
101
+ border-radius: 8px;
102
+ background: rgba(248, 81, 73, 0.08);
103
+ }
104
+
105
+ .error-fallback button {
106
+ margin-top: 12px;
107
+ padding: 6px 14px;
108
+ background: var(--panel);
109
+ border: 1px solid var(--border);
110
+ color: var(--text);
111
+ border-radius: 6px;
112
+ cursor: pointer;
113
+ }
114
+
115
+ .tab-stub {
116
+ padding: 32px;
117
+ color: var(--muted);
118
+ border: 1px dashed var(--border);
119
+ border-radius: 8px;
120
+ text-align: center;
121
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * dashboard-client/src/styles/overview-events.css — Sprint C1 tab styles.
3
+ *
4
+ * Overview tab (cards, context gauge, compression, trigger, session) +
5
+ * Events tab (SSE stream, filters, event rows). Depends on tokens in base.css.
6
+ */
7
+
8
+ /* Overview layout */
9
+ .overview-tab {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 12px;
13
+ }
14
+
15
+ .overview-header {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 12px;
19
+ }
20
+
21
+ .tier-pill {
22
+ padding: 2px 10px;
23
+ font-size: 12px;
24
+ border-radius: 10px;
25
+ background: var(--panel);
26
+ border: 1px solid var(--border);
27
+ color: var(--accent);
28
+ }
29
+
30
+ .updated {
31
+ color: var(--muted);
32
+ font-size: 12px;
33
+ }
34
+
35
+ .card-grid {
36
+ display: grid;
37
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
38
+ gap: 12px;
39
+ }
40
+
41
+ .card {
42
+ padding: 14px 16px;
43
+ background: var(--panel);
44
+ border: 1px solid var(--border);
45
+ border-radius: 8px;
46
+ }
47
+
48
+ .card h3 {
49
+ margin: 0 0 10px;
50
+ font-size: 13px;
51
+ color: var(--muted);
52
+ font-weight: 600;
53
+ }
54
+
55
+ /* Context gauge */
56
+ .gauge-bar {
57
+ height: 10px;
58
+ background: var(--bg);
59
+ border-radius: 5px;
60
+ overflow: hidden;
61
+ border: 1px solid var(--border);
62
+ }
63
+ .gauge-fill {
64
+ height: 100%;
65
+ transition: width 0.4s ease;
66
+ }
67
+ .gauge-green .gauge-fill {
68
+ background: var(--ok);
69
+ }
70
+ .gauge-yellow .gauge-fill {
71
+ background: var(--warn);
72
+ }
73
+ .gauge-red .gauge-fill {
74
+ background: var(--err);
75
+ }
76
+ .gauge-label {
77
+ margin: 8px 0 0;
78
+ font-size: 12px;
79
+ color: var(--muted);
80
+ }
81
+
82
+ /* Stat grid (shared by CompressionCard + SessionInfo) */
83
+ .stat-grid {
84
+ display: grid;
85
+ grid-template-columns: repeat(3, 1fr);
86
+ gap: 8px;
87
+ }
88
+ .stat {
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 2px;
92
+ }
93
+ .stat-label {
94
+ font-size: 11px;
95
+ color: var(--muted);
96
+ text-transform: uppercase;
97
+ }
98
+ .stat-value {
99
+ font-size: 16px;
100
+ font-weight: 600;
101
+ }
102
+ .stat-highlight .stat-value {
103
+ color: var(--ok);
104
+ }
105
+
106
+ /* Trigger status */
107
+ .bullet-row {
108
+ display: flex;
109
+ gap: 14px;
110
+ margin-bottom: 8px;
111
+ }
112
+ .bullet {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 6px;
116
+ font-size: 13px;
117
+ }
118
+ .bullet-dot {
119
+ width: 8px;
120
+ height: 8px;
121
+ border-radius: 50%;
122
+ background: var(--muted);
123
+ }
124
+ .bullet-on .bullet-dot {
125
+ background: var(--ok);
126
+ box-shadow: 0 0 6px var(--ok);
127
+ }
128
+ .bullet-off .bullet-dot {
129
+ background: var(--border);
130
+ }
131
+ .bullet-on .bullet-label {
132
+ color: var(--text);
133
+ }
134
+ .bullet-off .bullet-label {
135
+ color: var(--muted);
136
+ }
137
+ .trigger-detail {
138
+ margin: 4px 0;
139
+ font-size: 12px;
140
+ color: var(--muted);
141
+ }
142
+
143
+ /* Session info */
144
+ .state-badge {
145
+ display: inline-block;
146
+ padding: 2px 10px;
147
+ font-size: 12px;
148
+ border-radius: 10px;
149
+ background: var(--bg);
150
+ border: 1px solid var(--border);
151
+ }
152
+ .state-idle,
153
+ .state-none {
154
+ color: var(--muted);
155
+ }
156
+ .state-compacting {
157
+ color: var(--warn);
158
+ border-color: var(--warn);
159
+ }
160
+ .persisted-tag {
161
+ margin-left: 8px;
162
+ font-size: 11px;
163
+ color: var(--ok);
164
+ }
165
+
166
+ /* Events tab */
167
+ .events-tab {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: 10px;
171
+ }
172
+ .events-header {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 14px;
176
+ }
177
+ .sse-status {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ gap: 6px;
181
+ font-size: 12px;
182
+ color: var(--muted);
183
+ }
184
+ .sse-dot {
185
+ width: 8px;
186
+ height: 8px;
187
+ border-radius: 50%;
188
+ background: var(--muted);
189
+ }
190
+ .sse-connected .sse-dot {
191
+ background: var(--ok);
192
+ box-shadow: 0 0 6px var(--ok);
193
+ }
194
+ .sse-connecting .sse-dot {
195
+ background: var(--warn);
196
+ }
197
+ .sse-disconnected .sse-dot,
198
+ .sse-error .sse-dot {
199
+ background: var(--err);
200
+ }
201
+ .event-count {
202
+ color: var(--muted);
203
+ font-size: 12px;
204
+ }
205
+
206
+ .event-filters {
207
+ display: flex;
208
+ flex-wrap: wrap;
209
+ gap: 6px;
210
+ align-items: center;
211
+ margin-bottom: 8px;
212
+ }
213
+ .filter-chip {
214
+ padding: 3px 10px;
215
+ font-size: 11px;
216
+ border: 1px solid var(--border);
217
+ background: var(--bg);
218
+ color: var(--muted);
219
+ border-radius: 12px;
220
+ cursor: pointer;
221
+ }
222
+ .filter-chip.active {
223
+ color: var(--text);
224
+ border-color: var(--accent);
225
+ background: var(--panel);
226
+ }
227
+
228
+ .event-list {
229
+ list-style: none;
230
+ margin: 0;
231
+ padding: 0;
232
+ max-height: 60vh;
233
+ overflow-y: auto;
234
+ }
235
+ .event-row {
236
+ display: grid;
237
+ grid-template-columns: 70px 150px 1fr;
238
+ gap: 8px;
239
+ padding: 6px 10px;
240
+ border-bottom: 1px solid var(--border);
241
+ font-size: 12px;
242
+ cursor: pointer;
243
+ }
244
+ .event-row:hover {
245
+ background: var(--panel);
246
+ }
247
+ .ev-time {
248
+ color: var(--muted);
249
+ }
250
+ .ev-type {
251
+ font-size: 11px;
252
+ padding: 1px 6px;
253
+ border-radius: 8px;
254
+ background: var(--bg);
255
+ border: 1px solid var(--border);
256
+ color: var(--muted);
257
+ text-align: center;
258
+ }
259
+ .ev-compact .ev-type {
260
+ color: var(--accent);
261
+ border-color: var(--accent);
262
+ }
263
+ .ev-trigger .ev-type {
264
+ color: var(--warn);
265
+ border-color: var(--warn);
266
+ }
267
+ .ev-tier .ev-type {
268
+ color: var(--muted);
269
+ }
270
+ .ev-checkpoint .ev-type {
271
+ color: var(--ok);
272
+ border-color: var(--ok);
273
+ }
274
+ .ev-recall .ev-type {
275
+ color: #d2a8ff;
276
+ border-color: #d2a8ff;
277
+ }
278
+ .ev-config .ev-type {
279
+ color: var(--text);
280
+ }
281
+ .ev-crew .ev-type {
282
+ color: #79c0ff;
283
+ border-color: #79c0ff;
284
+ }
285
+ .ev-game .ev-type {
286
+ color: var(--warn);
287
+ }
288
+ .ev-summary {
289
+ color: var(--text);
290
+ overflow: hidden;
291
+ text-overflow: ellipsis;
292
+ white-space: nowrap;
293
+ }
294
+ .ev-detail {
295
+ grid-column: 1 / -1;
296
+ margin: 6px 0 0;
297
+ padding: 8px;
298
+ background: var(--bg);
299
+ border-radius: 4px;
300
+ font-size: 11px;
301
+ color: var(--muted);
302
+ white-space: pre-wrap;
303
+ }
304
+ .event-empty {
305
+ padding: 24px;
306
+ color: var(--muted);
307
+ text-align: center;
308
+ }
309
+
310
+ @media (max-width: 768px) {
311
+ .card-grid {
312
+ grid-template-columns: 1fr;
313
+ }
314
+ .event-row {
315
+ grid-template-columns: 60px 110px 1fr;
316
+ font-size: 11px;
317
+ }
318
+ }