pi-mega-compact 0.8.7 → 0.8.10

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 (110) 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/html.js +46 -1
  23. package/dist/extensions/dashboard-server/perf-server.test.js +80 -0
  24. package/dist/extensions/dashboard-server/server.js +269 -30
  25. package/dist/extensions/dashboard-server/tailscale.js +8 -0
  26. package/dist/extensions/dashboard-server/types.js +4 -0
  27. package/dist/extensions/mega-dashboard.js +9 -0
  28. package/dist/extensions/mega-events/perf-handler.js +71 -0
  29. package/dist/extensions/mega-events/register.js +2 -0
  30. package/dist/extensions/mega-events.js +1 -0
  31. package/dist/extensions/mega-runtime/state.js +59 -1
  32. package/dist/src/store/sqlite/connection.js +35 -0
  33. package/dist/src/store/sqlite/index-store.js +167 -0
  34. package/dist/src/store/sqlite/memory.js +54 -0
  35. package/dist/src/store/sqlite/minhash-lsh.js +47 -0
  36. package/dist/src/store/sqlite/perf-samples.js +81 -0
  37. package/dist/src/store/sqlite/perf-samples.test.js +54 -0
  38. package/dist/src/store/sqlite/schema.js +14 -0
  39. package/dist/src/store/sqlite/sessions.js +39 -0
  40. package/dist/src/store/sqlite/transaction.js +19 -0
  41. package/dist/src/store/sqlite.js +1 -0
  42. package/dist/src/vectorStore/add.js +260 -0
  43. package/dist/src/vectorStore/dedup.js +52 -0
  44. package/dist/src/vectorStore/index.js +10 -0
  45. package/dist/src/vectorStore/queries.js +83 -0
  46. package/dist/src/vectorStore/search.js +95 -0
  47. package/dist/src/vectorStore/session.js +19 -0
  48. package/dist/src/vectorStore/store.js +105 -0
  49. package/dist/src/vectorStore/types.js +6 -0
  50. package/dist/src/vectorStore/utils.js +23 -0
  51. package/extensions/dashboard-client/package-lock.json +1771 -0
  52. package/extensions/dashboard-client/package.json +27 -0
  53. package/extensions/dashboard-client/src/App.tsx +82 -0
  54. package/extensions/dashboard-client/src/api/client.ts +145 -0
  55. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +44 -0
  56. package/extensions/dashboard-client/src/components/CompressionCard.tsx +61 -0
  57. package/extensions/dashboard-client/src/components/ContextGauge.tsx +59 -0
  58. package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +49 -0
  59. package/extensions/dashboard-client/src/components/ErrorBoundary.tsx +53 -0
  60. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +16 -0
  61. package/extensions/dashboard-client/src/components/EventStream.tsx +152 -0
  62. package/extensions/dashboard-client/src/components/LoadingSpinner.tsx +7 -0
  63. package/extensions/dashboard-client/src/components/MemoryStatusCard.tsx +24 -0
  64. package/extensions/dashboard-client/src/components/ModelBadge.tsx +41 -0
  65. package/extensions/dashboard-client/src/components/PerfChart.tsx +160 -0
  66. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +126 -0
  67. package/extensions/dashboard-client/src/components/RepoTable.tsx +150 -0
  68. package/extensions/dashboard-client/src/components/SessionInfo.tsx +65 -0
  69. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +52 -0
  70. package/extensions/dashboard-client/src/components/TabBar.tsx +34 -0
  71. package/extensions/dashboard-client/src/components/TriggerStatus.tsx +62 -0
  72. package/extensions/dashboard-client/src/hooks/useApi.ts +77 -0
  73. package/extensions/dashboard-client/src/hooks/useSSE.ts +87 -0
  74. package/extensions/dashboard-client/src/index.html +12 -0
  75. package/extensions/dashboard-client/src/main.tsx +23 -0
  76. package/extensions/dashboard-client/src/styles/base.css +121 -0
  77. package/extensions/dashboard-client/src/styles/overview-events.css +318 -0
  78. package/extensions/dashboard-client/src/styles/repos-metrics.css +307 -0
  79. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +16 -0
  80. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +37 -0
  81. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +49 -0
  82. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +80 -0
  83. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +59 -0
  84. package/extensions/dashboard-client/tsconfig.json +28 -0
  85. package/extensions/dashboard-client/vite.config.ts +38 -0
  86. package/extensions/dashboard-server/api-contracts/core.ts +302 -0
  87. package/extensions/dashboard-server/api-contracts/endpoints.ts +476 -0
  88. package/extensions/dashboard-server/api-contracts/game.ts +145 -0
  89. package/extensions/dashboard-server/api-contracts/index.ts +159 -0
  90. package/extensions/dashboard-server/api-contracts/infrastructure.ts +465 -0
  91. package/extensions/dashboard-server/api-contracts/multi-repo.ts +328 -0
  92. package/extensions/dashboard-server/api-contracts/snapshot.ts +386 -0
  93. package/extensions/dashboard-server/api-contracts.test.ts +1050 -0
  94. package/extensions/dashboard-server/api-contracts.ts +96 -0
  95. package/extensions/dashboard-server/auth.ts +20 -0
  96. package/extensions/dashboard-server/html.ts +46 -1
  97. package/extensions/dashboard-server/perf-server.test.ts +101 -0
  98. package/extensions/dashboard-server/server.ts +862 -503
  99. package/extensions/dashboard-server/tailscale.ts +7 -0
  100. package/extensions/dashboard-server/types.ts +23 -114
  101. package/extensions/mega-dashboard.ts +17 -0
  102. package/extensions/mega-events/perf-handler.ts +113 -0
  103. package/extensions/mega-events/register.ts +2 -0
  104. package/extensions/mega-events.ts +1 -0
  105. package/extensions/mega-runtime/state.ts +57 -0
  106. package/package.json +2 -1
  107. package/src/store/sqlite/perf-samples.test.ts +65 -0
  108. package/src/store/sqlite/perf-samples.ts +125 -0
  109. package/src/store/sqlite/schema.ts +14 -0
  110. package/src/store/sqlite.ts +1 -0
@@ -0,0 +1,307 @@
1
+ /**
2
+ * dashboard-client/src/styles/repos-metrics.css — Sprint C2 tab styles.
3
+ *
4
+ * Repos tab (summary tiles, sortable table, detail modal) + Metrics tab
5
+ * (model badge, perf chart bars, resource gauges). Depends on base.css tokens.
6
+ */
7
+
8
+ /* Summary tiles */
9
+ .summary-tiles {
10
+ display: grid;
11
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
12
+ gap: 10px;
13
+ margin-bottom: 16px;
14
+ }
15
+ .tile {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: 4px;
19
+ padding: 12px 14px;
20
+ background: var(--panel);
21
+ border: 1px solid var(--border);
22
+ border-radius: 8px;
23
+ }
24
+ .tile-label {
25
+ font-size: 11px;
26
+ color: var(--muted);
27
+ text-transform: uppercase;
28
+ }
29
+ .tile-value {
30
+ font-size: 20px;
31
+ font-weight: 600;
32
+ }
33
+ .tile-highlight .tile-value {
34
+ color: var(--ok);
35
+ }
36
+
37
+ /* Repo table */
38
+ .repo-table-wrap {
39
+ overflow-x: auto;
40
+ }
41
+ .repo-search {
42
+ width: 100%;
43
+ padding: 8px 10px;
44
+ margin-bottom: 10px;
45
+ background: var(--panel);
46
+ border: 1px solid var(--border);
47
+ border-radius: 6px;
48
+ color: var(--text);
49
+ font-size: 13px;
50
+ }
51
+ .repo-table {
52
+ width: 100%;
53
+ border-collapse: collapse;
54
+ font-size: 13px;
55
+ }
56
+ .repo-table th {
57
+ text-align: left;
58
+ padding: 6px 10px;
59
+ border-bottom: 1px solid var(--border);
60
+ }
61
+ .repo-table td {
62
+ padding: 8px 10px;
63
+ border-bottom: 1px solid var(--border);
64
+ }
65
+ .repo-table tr {
66
+ cursor: pointer;
67
+ }
68
+ .repo-table tbody tr:hover {
69
+ background: var(--panel);
70
+ }
71
+ .sort-header {
72
+ background: none;
73
+ border: none;
74
+ color: var(--muted);
75
+ font-size: 12px;
76
+ font-weight: 600;
77
+ cursor: pointer;
78
+ padding: 0;
79
+ }
80
+ .sort-header.active-asc,
81
+ .sort-header.active-desc {
82
+ color: var(--text);
83
+ }
84
+ .sort-arrow {
85
+ color: var(--accent);
86
+ }
87
+ .repo-name {
88
+ font-weight: 600;
89
+ }
90
+ .repo-model {
91
+ color: var(--muted);
92
+ }
93
+ .repo-empty {
94
+ text-align: center;
95
+ color: var(--muted);
96
+ padding: 24px;
97
+ }
98
+
99
+ /* Repo detail modal */
100
+ .modal-backdrop {
101
+ position: fixed;
102
+ inset: 0;
103
+ background: rgba(0, 0, 0, 0.6);
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ z-index: 100;
108
+ }
109
+ .modal-card {
110
+ background: var(--panel);
111
+ border: 1px solid var(--border);
112
+ border-radius: 10px;
113
+ padding: 20px 24px;
114
+ max-width: 560px;
115
+ width: 90%;
116
+ max-height: 80vh;
117
+ overflow-y: auto;
118
+ }
119
+ .modal-header {
120
+ display: flex;
121
+ justify-content: space-between;
122
+ align-items: center;
123
+ }
124
+ .modal-header h2 {
125
+ margin: 0;
126
+ font-size: 18px;
127
+ }
128
+ .modal-close {
129
+ background: none;
130
+ border: none;
131
+ color: var(--muted);
132
+ font-size: 22px;
133
+ cursor: pointer;
134
+ padding: 0 4px;
135
+ }
136
+ .modal-close:hover {
137
+ color: var(--text);
138
+ }
139
+ .modal-path {
140
+ font-size: 11px;
141
+ color: var(--muted);
142
+ margin: 4px 0 12px;
143
+ word-break: break-all;
144
+ }
145
+ .modal-grid {
146
+ display: grid;
147
+ grid-template-columns: repeat(3, 1fr);
148
+ gap: 10px;
149
+ margin-bottom: 16px;
150
+ }
151
+ .modal-stat {
152
+ display: flex;
153
+ flex-direction: column;
154
+ gap: 2px;
155
+ }
156
+ .modal-label {
157
+ font-size: 11px;
158
+ color: var(--muted);
159
+ text-transform: uppercase;
160
+ }
161
+ .modal-value {
162
+ font-size: 16px;
163
+ font-weight: 600;
164
+ }
165
+ .modal-highlight .modal-value {
166
+ color: var(--ok);
167
+ }
168
+ .modal-card h3 {
169
+ font-size: 13px;
170
+ color: var(--muted);
171
+ margin: 16px 0 6px;
172
+ }
173
+ .modal-dl {
174
+ display: grid;
175
+ grid-template-columns: 130px 1fr;
176
+ gap: 4px 12px;
177
+ margin: 0;
178
+ }
179
+ .modal-dl dt {
180
+ color: var(--muted);
181
+ font-size: 12px;
182
+ }
183
+ .modal-dl dd {
184
+ margin: 0;
185
+ font-size: 13px;
186
+ }
187
+
188
+ /* Model badge */
189
+ .model-badge {
190
+ display: flex;
191
+ flex-wrap: wrap;
192
+ align-items: center;
193
+ gap: 8px;
194
+ padding: 12px 14px;
195
+ background: var(--panel);
196
+ border: 1px solid var(--border);
197
+ border-radius: 8px;
198
+ margin-bottom: 12px;
199
+ }
200
+ .model-name {
201
+ font-size: 15px;
202
+ font-weight: 600;
203
+ }
204
+ .model-provider {
205
+ color: var(--accent);
206
+ font-size: 13px;
207
+ }
208
+ .model-provider-id {
209
+ color: var(--muted);
210
+ font-size: 11px;
211
+ }
212
+ .model-rates {
213
+ margin-left: auto;
214
+ display: flex;
215
+ gap: 12px;
216
+ }
217
+ .model-rates .rate {
218
+ font-size: 12px;
219
+ color: var(--muted);
220
+ }
221
+
222
+ /* Perf chart */
223
+ .perf-chart {
224
+ display: flex;
225
+ flex-direction: column;
226
+ gap: 12px;
227
+ }
228
+ .perf-header {
229
+ display: flex;
230
+ justify-content: space-between;
231
+ align-items: center;
232
+ }
233
+ .perf-header h3 {
234
+ margin: 0;
235
+ font-size: 13px;
236
+ color: var(--muted);
237
+ }
238
+ .perf-window {
239
+ font-size: 11px;
240
+ color: var(--muted);
241
+ }
242
+ .perf-grid,
243
+ .perf-resources {
244
+ display: grid;
245
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
246
+ gap: 10px;
247
+ }
248
+ .perf-resources {
249
+ padding-top: 10px;
250
+ border-top: 1px solid var(--border);
251
+ }
252
+ .perf-metric {
253
+ display: flex;
254
+ flex-direction: column;
255
+ gap: 4px;
256
+ padding: 10px 12px;
257
+ background: var(--panel);
258
+ border: 1px solid var(--border);
259
+ border-radius: 6px;
260
+ }
261
+ .perf-label {
262
+ font-size: 11px;
263
+ color: var(--muted);
264
+ }
265
+ .perf-value {
266
+ font-size: 15px;
267
+ font-weight: 600;
268
+ }
269
+ .perf-bar-track {
270
+ height: 6px;
271
+ background: var(--bg);
272
+ border-radius: 3px;
273
+ overflow: hidden;
274
+ }
275
+ .perf-bar-fill {
276
+ height: 100%;
277
+ transition: width 0.4s ease;
278
+ }
279
+ .perf-blue .perf-bar-fill {
280
+ background: var(--accent);
281
+ }
282
+ .perf-purple .perf-bar-fill {
283
+ background: #d2a8ff;
284
+ }
285
+ .perf-green .perf-bar-fill {
286
+ background: var(--ok);
287
+ }
288
+ .perf-yellow .perf-bar-fill {
289
+ background: var(--warn);
290
+ }
291
+ .perf-diag {
292
+ display: flex;
293
+ flex-wrap: wrap;
294
+ gap: 14px;
295
+ padding-top: 10px;
296
+ border-top: 1px solid var(--border);
297
+ }
298
+
299
+ @media (max-width: 768px) {
300
+ .modal-grid {
301
+ grid-template-columns: 1fr 1fr;
302
+ }
303
+ .model-rates {
304
+ margin-left: 0;
305
+ width: 100%;
306
+ }
307
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (B1 stub).
3
+ *
4
+ * SPRINT-C3: config form, game-mode settings, theme picker.
5
+ */
6
+
7
+ import type React from "react";
8
+
9
+ export default function ConfigTab(): React.ReactElement {
10
+ return (
11
+ <div className="tab-stub">
12
+ <strong>Config</strong> (B1 stub)
13
+ {/* SPRINT-C3-REMAINING: /mega-compact-settings form, theme picker */}
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * dashboard-client/src/tabs/EventsTab.tsx — Events tab (C1).
3
+ *
4
+ * Live SSE event stream via useSSE + EventStream with type filters.
5
+ * Shows connection status + event count.
6
+ */
7
+
8
+ import type React from "react";
9
+ import { useSSE } from "../hooks/useSSE";
10
+ import { EventStream } from "../components/EventStream";
11
+
12
+ export default function EventsTab(): React.ReactElement {
13
+ const { events, status, eventCount } = useSSE();
14
+
15
+ const statusCls = `sse-status sse-${status}`;
16
+ const statusLabel =
17
+ status === "connected"
18
+ ? "connected"
19
+ : status === "connecting"
20
+ ? "connecting…"
21
+ : status === "error"
22
+ ? "error"
23
+ : "disconnected";
24
+
25
+ return (
26
+ <div className="events-tab">
27
+ <div className="events-header">
28
+ <span className={statusCls} role="status">
29
+ <span className="sse-dot" aria-hidden="true" />
30
+ {statusLabel}
31
+ </span>
32
+ <span className="event-count">{eventCount} events received</span>
33
+ </div>
34
+ <EventStream events={events} />
35
+ </div>
36
+ );
37
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * dashboard-client/src/tabs/MetricsTab.tsx — Metrics tab (C2).
3
+ *
4
+ * Fetches /api/perf (rolling-window aggregates) + /api/snapshot (for model).
5
+ * Renders PerfChart + ModelBadge + resource gauges. Polls every 10s.
6
+ */
7
+
8
+ import type React from "react";
9
+ import { useCallback } from "react";
10
+ import { useApi } from "../hooks/useApi";
11
+ import { fetchPerf, fetchSnapshot } from "../api/client";
12
+ import type { PerfResponse, SnapshotResponse } from "@contracts";
13
+ import { PerfChart } from "../components/PerfChart";
14
+ import { ModelBadge } from "../components/ModelBadge";
15
+
16
+ export default function MetricsTab(): React.ReactElement {
17
+ const { data: perf, error: perfErr } = useApi<PerfResponse>(
18
+ useCallback(() => fetchPerf({ minutes: 30 }), []),
19
+ { pollInterval: 10_000 },
20
+ );
21
+ const { data: snapshot } = useApi<SnapshotResponse>(
22
+ useCallback(() => fetchSnapshot(), []),
23
+ { pollInterval: 10_000 },
24
+ );
25
+
26
+ if (perfErr && !perf) {
27
+ return (
28
+ <div className="tab-stub">Error loading perf: {perfErr.message}</div>
29
+ );
30
+ }
31
+ if (!perf) {
32
+ return <div className="tab-stub">Loading perf…</div>;
33
+ }
34
+
35
+ return (
36
+ <div className="metrics-tab">
37
+ {snapshot?.model && (
38
+ <ModelBadge
39
+ name={snapshot.model.name}
40
+ providerName={snapshot.model.providerName}
41
+ provider={snapshot.model.provider}
42
+ inputRate={snapshot.model.inputRate}
43
+ outputRate={snapshot.model.outputRate}
44
+ />
45
+ )}
46
+ <PerfChart perf={perf} />
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * dashboard-client/src/tabs/OverviewTab.tsx — Overview tab (C1).
3
+ *
4
+ * 4-card grid: ContextGauge, CompressionCard, TriggerStatus, SessionInfo.
5
+ * Header: tier pill + last updated timestamp. Polls /api/snapshot.
6
+ */
7
+
8
+ import type React from "react";
9
+ import type { SnapshotResponse } from "@contracts";
10
+ import { ContextGauge } from "../components/ContextGauge";
11
+ import { CompressionCard } from "../components/CompressionCard";
12
+ import { TriggerStatus } from "../components/TriggerStatus";
13
+ import { SessionInfo } from "../components/SessionInfo";
14
+
15
+ export interface OverviewTabProps {
16
+ snapshot: SnapshotResponse | null;
17
+ loading: boolean;
18
+ error: Error | null;
19
+ }
20
+
21
+ function formatUpdatedAt(ts: string | null): string {
22
+ if (!ts) return "never";
23
+ try {
24
+ return new Date(ts).toLocaleString();
25
+ } catch {
26
+ return ts;
27
+ }
28
+ }
29
+
30
+ export default function OverviewTab({
31
+ snapshot,
32
+ loading,
33
+ error,
34
+ }: OverviewTabProps): React.ReactElement {
35
+ if (loading && !snapshot)
36
+ return <div className="tab-stub">Loading snapshot…</div>;
37
+ if (error && !snapshot)
38
+ return <div className="tab-stub">Error: {error.message}</div>;
39
+ if (!snapshot) return <div className="tab-stub">No snapshot data.</div>;
40
+
41
+ const { context, compression, trigger, session, store, tier, updatedAt } =
42
+ snapshot;
43
+
44
+ return (
45
+ <div className="overview-tab">
46
+ <div className="overview-header">
47
+ <span className="tier-pill">{tier}</span>
48
+ <span className="updated">updated {formatUpdatedAt(updatedAt)}</span>
49
+ </div>
50
+ <div className="card-grid">
51
+ <ContextGauge
52
+ tokens={context.tokens}
53
+ percent={context.percent}
54
+ contextWindow={context.contextWindow}
55
+ />
56
+ <CompressionCard
57
+ tokensIn={compression.repo.tokensIn}
58
+ tokensOut={compression.repo.tokensOut}
59
+ tokensFreed={compression.repo.tokensFreed}
60
+ compressionPct={compression.repo.compressionPct}
61
+ dedupPct={compression.repo.dedupPct}
62
+ />
63
+ <TriggerStatus
64
+ armed={trigger.armed}
65
+ ready={trigger.ready}
66
+ currentTokens={trigger.currentTokens}
67
+ thresholdTokens={trigger.thresholdTokens}
68
+ fastGatePct={trigger.fastGatePct}
69
+ />
70
+ <SessionInfo
71
+ state={session.state}
72
+ persistedThisSession={session.persistedThisSession}
73
+ checkpointCount={store.checkpointCount}
74
+ tokensSaved={store.tokensSaved}
75
+ dedupHitRate={store.dedupHitRate}
76
+ />
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * dashboard-client/src/tabs/ReposTab.tsx — Repos tab (C2).
3
+ *
4
+ * Fetches /api/repos + /api/summary. Renders SummaryTiles + RepoTable.
5
+ * Row click opens RepoDetailModal. Polls every 10s.
6
+ */
7
+
8
+ import type React from "react";
9
+ import { useCallback, useState } from "react";
10
+ import { useApi } from "../hooks/useApi";
11
+ import { fetchRepos, fetchSummary } from "../api/client";
12
+ import type {
13
+ ReposResponse,
14
+ SummaryResponse,
15
+ IndexesIndexRow,
16
+ } from "@contracts";
17
+ import { SummaryTiles } from "../components/SummaryTiles";
18
+ import { RepoTable } from "../components/RepoTable";
19
+ import { RepoDetailModal } from "../components/RepoDetailModal";
20
+
21
+ export default function ReposTab(): React.ReactElement {
22
+ const [selected, setSelected] = useState<IndexesIndexRow | null>(null);
23
+
24
+ const { data: reposResp, error: reposErr } = useApi<ReposResponse>(
25
+ useCallback(() => fetchRepos(), []),
26
+ { pollInterval: 10_000 },
27
+ );
28
+ const { data: summaryResp } = useApi<SummaryResponse>(
29
+ useCallback(() => fetchSummary(), []),
30
+ { pollInterval: 10_000 },
31
+ );
32
+
33
+ if (reposErr && !reposResp) {
34
+ return (
35
+ <div className="tab-stub">Error loading repos: {reposErr.message}</div>
36
+ );
37
+ }
38
+ if (!reposResp) {
39
+ return <div className="tab-stub">Loading repos…</div>;
40
+ }
41
+
42
+ const repos = reposResp.repos;
43
+ const summary = summaryResp?.summary;
44
+
45
+ return (
46
+ <div className="repos-tab">
47
+ <SummaryTiles
48
+ totalRepos={summaryResp?.totalRepos ?? 0}
49
+ activeRepos={summaryResp?.activeRepos ?? 0}
50
+ totalCheckpoints={summary?.totalCheckpoints ?? 0}
51
+ totalTokensSaved={summary?.totalTokensSaved ?? 0}
52
+ />
53
+ <RepoTable repos={repos} onSelect={setSelected} />
54
+ {selected && (
55
+ <RepoDetailModal repo={selected} onClose={() => setSelected(null)} />
56
+ )}
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noUnusedLocals": true,
10
+ "noUnusedParameters": true,
11
+ "noImplicitOverride": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "verbatimModuleSyntax": true,
17
+ "noEmit": true,
18
+ "allowImportingTsExtensions": true,
19
+ "types": [],
20
+ "baseUrl": ".",
21
+ "paths": {
22
+ "@/*": ["./src/*"],
23
+ "@contracts": ["../dashboard-server/api-contracts/index.ts"]
24
+ }
25
+ },
26
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
27
+ "exclude": ["node_modules", "dist"]
28
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * dashboard-client/vite.config.ts — Vite build config.
3
+ *
4
+ * PREVENT-PI-004: the dev server binds to localhost only. The production
5
+ * build is a static bundle served by the existing Node HTTP server in
6
+ * extensions/dashboard-server/server.ts. No external network calls.
7
+ */
8
+
9
+ import { defineConfig } from "vite";
10
+ import react from "@vitejs/plugin-react";
11
+ import { fileURLToPath, URL } from "node:url";
12
+
13
+ // guardrails-allow PREVENT-PI-004: Vite dev server is loopback-only (dashboard UI tooling); production build is static files, no runtime network.
14
+ export default defineConfig({
15
+ plugins: [react()],
16
+ root: "src",
17
+ base: "./",
18
+ build: {
19
+ outDir: "../dist",
20
+ emptyOutDir: true,
21
+ sourcemap: true,
22
+ target: "es2022",
23
+ },
24
+ server: {
25
+ host: "localhost",
26
+ port: 5173,
27
+ strictPort: true,
28
+ },
29
+ resolve: {
30
+ alias: {
31
+ "@": fileURLToPath(new URL("./src", import.meta.url)),
32
+ // Allow importing A1 contract types from the dashboard-server tree.
33
+ "@contracts": fileURLToPath(
34
+ new URL("../dashboard-server/api-contracts", import.meta.url),
35
+ ),
36
+ },
37
+ },
38
+ });