pi-mega-compact 0.8.15 → 0.8.16

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 (40) hide show
  1. package/README.md +51 -486
  2. package/dist/extensions/dashboard-client/src/hooks/useApi.js +51 -0
  3. package/dist/extensions/dashboard-client/src/hooks/useSSE.js +63 -0
  4. package/dist/extensions/dashboard-server/html.js +15 -2
  5. package/dist/extensions/mega-runtime/state.js +66 -0
  6. package/dist/extensions/mega-runtime/state.test.js +37 -0
  7. package/dist/extensions/mega-runtime/widget.js +6 -2
  8. package/dist/extensions/mega-runtime/widget.test.js +47 -0
  9. package/extensions/dashboard-client/dist/assets/{AchievementTiles-BH26e95w.js → AchievementTiles-BozP_h3c.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{AchievementTiles-BH26e95w.js.map → AchievementTiles-BozP_h3c.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{AchievementsTab-B94wvRV_.js → AchievementsTab-DKgqx1RQ.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{AchievementsTab-B94wvRV_.js.map → AchievementsTab-DKgqx1RQ.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{CacheTab-DPmw3NHR.js → CacheTab-CPvso4-0.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{CacheTab-DPmw3NHR.js.map → CacheTab-CPvso4-0.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/ConfigTab-DXD7zbqy.js +2 -0
  16. package/extensions/dashboard-client/dist/assets/ConfigTab-DXD7zbqy.js.map +1 -0
  17. package/extensions/dashboard-client/dist/assets/{EventsTab-B5PicfFw.js → EventsTab-BGeuYvM3.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{EventsTab-B5PicfFw.js.map → EventsTab-BGeuYvM3.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{GameTab-CsVwp78a.js → GameTab-Qj19W1Y-.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{GameTab-CsVwp78a.js.map → GameTab-Qj19W1Y-.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cf72UewX.js → MetricsTab-DA27vA95.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{MetricsTab-Cf72UewX.js.map → MetricsTab-DA27vA95.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/{OverviewTab-Dh6aG3gy.js → OverviewTab-lVGR1Jl_.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{OverviewTab-Dh6aG3gy.js.map → OverviewTab-lVGR1Jl_.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{ReposTab-HXf2dorl.js → ReposTab-_HNR3qYj.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{ReposTab-HXf2dorl.js.map → ReposTab-_HNR3qYj.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-kUnoospE.js → TimeSavedCard-B7aPcBMs.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-kUnoospE.js.map → TimeSavedCard-B7aPcBMs.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{index-DMntLu-8.js → index-B2JJExE9.js} +3 -3
  30. package/extensions/dashboard-client/dist/assets/{index-DMntLu-8.js.map → index-B2JJExE9.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/index.html +1 -1
  32. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +19 -2
  33. package/extensions/dashboard-server/html.ts +15 -2
  34. package/extensions/mega-runtime/state.test.ts +33 -0
  35. package/extensions/mega-runtime/state.ts +64 -0
  36. package/extensions/mega-runtime/widget.test.ts +48 -0
  37. package/extensions/mega-runtime/widget.ts +6 -2
  38. package/package.json +1 -1
  39. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +0 -2
  40. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +0 -1
@@ -0,0 +1,63 @@
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
+ import { useState, useEffect, useRef, useCallback } from 'react';
8
+ // SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).
9
+ // SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.
10
+ export function useSSE(options = {}) {
11
+ const { maxEvents = 500, maxReconnects = 5 } = options;
12
+ const [events, setEvents] = useState([]);
13
+ const [status, setStatus] = useState('connecting');
14
+ const [eventCount, setEventCount] = useState(0);
15
+ const [lastEventAt, setLastEventAt] = useState(null);
16
+ const reconnectCount = useRef(0);
17
+ const sourceRef = useRef(null);
18
+ const connect = useCallback(() => {
19
+ if (sourceRef.current) {
20
+ sourceRef.current.close();
21
+ }
22
+ setStatus('connecting');
23
+ const source = new EventSource('/api/events');
24
+ sourceRef.current = source;
25
+ source.onopen = () => {
26
+ setStatus('connected');
27
+ reconnectCount.current = 0;
28
+ };
29
+ source.onmessage = (event) => {
30
+ try {
31
+ const parsed = JSON.parse(event.data);
32
+ setEvents(prev => {
33
+ const next = [...prev, parsed];
34
+ return next.length > maxEvents ? next.slice(-maxEvents) : next;
35
+ });
36
+ setEventCount(c => c + 1);
37
+ setLastEventAt(Date.now());
38
+ }
39
+ catch {
40
+ // non-fatal: skip malformed events
41
+ }
42
+ };
43
+ source.onerror = () => {
44
+ source.close();
45
+ if (reconnectCount.current < maxReconnects) {
46
+ reconnectCount.current += 1;
47
+ setStatus('connecting');
48
+ // SPRINT-D1: use exponential backoff instead of fixed delay
49
+ setTimeout(connect, 1000);
50
+ }
51
+ else {
52
+ setStatus('disconnected');
53
+ }
54
+ };
55
+ }, [maxEvents, maxReconnects]);
56
+ useEffect(() => {
57
+ connect();
58
+ return () => {
59
+ sourceRef.current?.close();
60
+ };
61
+ }, [connect]);
62
+ return { events, status, eventCount, lastEventAt };
63
+ }
@@ -176,8 +176,8 @@ export function dashboardHtml(tierName) {
176
176
 
177
177
  <div class="settings-strip">
178
178
  <label title="Turn game mode on/off (themes the widget + dashboard)"><input type="checkbox" id="set-game-mode"> Game mode</label>
179
- <label title="Visual theme (applies instantly)">Theme <select id="set-theme">${THEME_OPTIONS}</select></label>
180
- <label title="TUI widget display density">TUI <select id="set-tui-mode"><option value="full">Full</option><option value="minimal">Minimal</option></select></label>
179
+ <label title="Visual theme (applies instantly)">Theme <select id="set-theme">${THEME_OPTIONS}</select> <button type="button" id="set-theme-next" title="Cycle to the next theme (P3)">Next theme →</button></label>
180
+ <label title="TUI widget display density">TUI <select id="set-tui-mode"><option value="full">Full</option><option value="minimal">Minimal</option></select> <span class="tui-mode-hint">(affects the in-app TUI widget; not shown here)</span></label>
181
181
  </div>
182
182
 
183
183
  <nav class="tabs">
@@ -869,6 +869,19 @@ export function dashboardHtml(tierName) {
869
869
  }
870
870
  if (gmCheckbox) gmCheckbox.addEventListener('change', function() { putGameState({ game_mode_on: gmCheckbox.checked }); });
871
871
  if (gmTheme) gmTheme.addEventListener('change', function() { putGameState({ theme: gmTheme.value }); });
872
+ // P3: client-side theme cycling (no new endpoint). Reads the already-fetched
873
+ // <option> values from the theme select, so it stays in sync with the server's
874
+ // THEMES list without a second fetch.
875
+ var gmThemeNext = document.getElementById('set-theme-next');
876
+ if (gmThemeNext) gmThemeNext.addEventListener('click', function() {
877
+ var opts = gmTheme ? gmTheme.options : [];
878
+ var n = opts.length;
879
+ var cur = gmTheme ? gmTheme.value : 'transparent';
880
+ var idx = -1;
881
+ for (var i = 0; i < n; i++) { if (opts[i].value === cur) { idx = i; break; } }
882
+ var next = n > 0 ? opts[(idx < 0 ? 0 : (idx + 1) % n)].value : 'transparent';
883
+ putGameState({ theme: next });
884
+ });
872
885
  if (gmTui) gmTui.addEventListener('change', function() { putGameState({ tui_display_mode: gmTui.value }); });
873
886
  pollGameState();
874
887
  setInterval(pollGameState, 5000);
@@ -167,6 +167,11 @@ export class MegaRuntime {
167
167
  // getCachedGameState() snapshot re-queries the DB anyway.
168
168
  gameStateWatcher;
169
169
  gameStateWatchDir;
170
+ // P2: the last ExtensionContext handed to snapshot()/renderWidget(), stashed
171
+ // so the fs.watch game-state callback can force a widget re-render without
172
+ // a context event (cross-process dashboard edits while pi is idle). Cleared
173
+ // implicitly on construction (undefined → watcher skips until first snap).
174
+ lastWidgetCtx;
170
175
  /**
171
176
  * DIAG counters for the "team run doesn't relieve context" investigation.
172
177
  * Plain integers, incremented at the three compaction decision points. They
@@ -321,6 +326,8 @@ export class MegaRuntime {
321
326
  // ---- dashboard snapshot + widget ------------------------------------------
322
327
  /** Collect live state and write it to disk (+ paint the above-editor widget). */
323
328
  snapshot(ctx) {
329
+ if (ctx)
330
+ this.lastWidgetCtx = ctx;
324
331
  if (ctx)
325
332
  this.bindRepo(ctx.cwd);
326
333
  // v0.8.5: gate the expensive body (6 sync SQLite opens +
@@ -908,6 +915,30 @@ export class MegaRuntime {
908
915
  if (typeof filename === "string" && filename.startsWith("sqlite.db")) {
909
916
  this.cachedGameState = undefined;
910
917
  this.gameStateBump++;
918
+ // P2: force a widget re-render so a dashboard-made theme/toggle/
919
+ // tui-mode change reflects in the live TUI immediately, even when
920
+ // pi is idle (no context event to drive snapshot()). Use the
921
+ // LIGHTWEIGHT refreshWidgetGameState() — NOT the full snapshot():
922
+ // snapshot() recomputes 6 sync SQLite opens + writes dashboard.json
923
+ // + writes to the store, and those store writes RETRIGGER this
924
+ // same fs.watch callback (it fires on every sqlite.db* write) →
925
+ // re-entrant thrash → 190s test timeout under mega-compact.test.js
926
+ // / mega-teamrun.test.js. The lightweight path re-reads ONLY the
927
+ // game_state row and patches the three game-mode fields on the
928
+ // existing widgetData, then re-registers the factory via
929
+ // renderWidget() — it writes nothing to the store or
930
+ // dashboard.json, so it cannot retrigger itself. Guard: skip until
931
+ // the first snapshot stashed a ctx (no widget registered yet →
932
+ // nothing to refresh). Non-fatal: next context event re-snapshots.
933
+ const ctx = this.lastWidgetCtx;
934
+ if (ctx) {
935
+ try {
936
+ this.refreshWidgetGameState(ctx);
937
+ }
938
+ catch {
939
+ /* non-fatal */
940
+ }
941
+ }
911
942
  }
912
943
  });
913
944
  this.gameStateWatchDir = this.currentStateDir;
@@ -987,6 +1018,33 @@ export class MegaRuntime {
987
1018
  }
988
1019
  return this.cachedGameState;
989
1020
  }
1021
+ /** P2 cross-process re-render: lightweight game-state refresh for the
1022
+ * fs.watch callback. Eviction of cachedGameState + gameStateBump++ happens
1023
+ * in the caller BEFORE this runs. Here we re-read ONLY the game_state row
1024
+ * via getCachedGameState() (one SELECT; the cache is already evicted) and
1025
+ * patch ONLY the three game-mode fields on the EXISTING widgetData, then
1026
+ * re-register the widget factory via renderWidget() so pi redraws next
1027
+ * frame.
1028
+ *
1029
+ * WHY a lightweight path: the full snapshot(ctx) recomputes 6 synchronous
1030
+ * SQLite opens + writeFileSync(dashboard.json) + store writes, and those
1031
+ * store writes RETRIGGER this same fs.watch callback → re-entrant thrash
1032
+ * (the watcher fires on every sqlite.db* write, including context-event
1033
+ * checkpoint writes) → 190s test timeout under mega-compact.test.js /
1034
+ * mega-teamrun.test.js. This path writes NOTHING to the store or
1035
+ * dashboard.json, so it cannot retrigger itself.
1036
+ *
1037
+ * Guard: no-op when widgetData is null (no snapshot has run yet → nothing
1038
+ * to patch) or ctx is undefined. Field values mirror snapshot() exactly. */
1039
+ refreshWidgetGameState(ctx) {
1040
+ if (!this.widgetData || !ctx)
1041
+ return;
1042
+ const gs = this.getCachedGameState();
1043
+ this.widgetData.gameMode = gs.game_mode_on;
1044
+ this.widgetData.theme = getTheme(gs.theme) ? gs.theme : "transparent";
1045
+ this.widgetData.tuiMode = gs.tui_display_mode;
1046
+ this.renderWidget(ctx);
1047
+ }
990
1048
  /** S31: evict the cached game-mode state so the next widget render re-reads
991
1049
  * the game_state row. Called by /mega-game after every setGameState() so
992
1050
  * the panel picks up theme/mode/toggle changes live. */
@@ -1060,6 +1118,14 @@ export class MegaRuntime {
1060
1118
  }
1061
1119
  // Phase 3 — recall/activity ticker ring buffer.
1062
1120
  pushTicker(text) {
1121
+ // P1: dedupe consecutive identical entries — skip the append when the
1122
+ // last entry's text matches, so a re-fired compact/recall/dedup event
1123
+ // doesn't flood the ring (keeps it at TICKER_MAX for real variety).
1124
+ // `at` is NOT refreshed on a skip (the original event time stands).
1125
+ if (this.ticker[this.ticker.length - 1]?.text === text) {
1126
+ this.lastActivityAt = Date.now();
1127
+ return;
1128
+ }
1063
1129
  this.ticker.push({ text, at: Date.now() });
1064
1130
  while (this.ticker.length > this.TICKER_MAX)
1065
1131
  this.ticker.shift();
@@ -110,6 +110,43 @@ describe("MegaRuntime setEffect lifecycle (v0.8.3)", () => {
110
110
  assert.equal(rt.activeEffect, null, "cleared once expired");
111
111
  });
112
112
  });
113
+ describe("MegaRuntime pushTicker dedupe (P1)", () => {
114
+ const dirs = [];
115
+ after(() => {
116
+ for (const d of dirs) {
117
+ try {
118
+ closeStore(d);
119
+ }
120
+ catch { /* */ }
121
+ }
122
+ delete process.env.MEGACOMPACT_STATE_DIR;
123
+ for (const d of dirs)
124
+ rmSync(d, { recursive: true, force: true });
125
+ });
126
+ it("pushTicker skips consecutive identical entries (keeps ring at capacity for variety)", () => {
127
+ const dir = freshDir();
128
+ dirs.push(dir);
129
+ process.env.MEGACOMPACT_STATE_DIR = dir;
130
+ const rt = new MegaRuntime(minimalConfig(dir));
131
+ rt.pushTicker("compact");
132
+ rt.pushTicker("compact"); // dedup — skipped
133
+ rt.pushTicker("compact"); // dedup — skipped
134
+ rt.pushTicker("recall");
135
+ assert.equal(rt.ticker.length, 2, "only 2 unique entries kept");
136
+ assert.equal(rt.ticker[0].text, "compact");
137
+ assert.equal(rt.ticker[1].text, "recall");
138
+ });
139
+ it("pushTicker keeps distinct entries and respects TICKER_MAX", () => {
140
+ const dir = freshDir();
141
+ dirs.push(dir);
142
+ process.env.MEGACOMPACT_STATE_DIR = dir;
143
+ const rt = new MegaRuntime(minimalConfig(dir));
144
+ for (let i = 0; i < rt.TICKER_MAX + 3; i++)
145
+ rt.pushTicker(`t${i}`);
146
+ assert.equal(rt.ticker.length, rt.TICKER_MAX, "capped at TICKER_MAX");
147
+ assert.equal(rt.ticker[rt.TICKER_MAX - 1].text, `t${rt.TICKER_MAX + 2}`);
148
+ });
149
+ });
113
150
  describe("MegaRuntime game-state cache (S31)", () => {
114
151
  const dirs = [];
115
152
  after(() => {
@@ -292,8 +292,12 @@ export function buildWidgetLines(wd, width, activeAgents) {
292
292
  lines.push(panelLine(` ${pulse}${wd.tierTrace}`, width, panelBg));
293
293
  }
294
294
  else if (wd.ticker.length > 0) {
295
- const step = Math.floor(Date.now() / 250);
296
- const idx = wd.ticker.length - 1 - (step % wd.ticker.length);
295
+ // P1: pin to the most-recent entry (no 250ms rotation). The old
296
+ // `step = floor(Date.now()/250)` re-picked the head every TUI frame,
297
+ // flipping the footer line on a 250·N ms cycle. Pinning makes the
298
+ // footer hold stable until a real state change (new pushTicker /
299
+ // tierTrace / pulsing transition). (+N more) + lastWhy are preserved.
300
+ const idx = wd.ticker.length - 1;
297
301
  const head = wd.ticker[idx].text;
298
302
  const why = wd.lastWhy ? ` ${C.gray}· ${wd.lastWhy}${C.reset}` : "";
299
303
  const more = wd.ticker.length > 1
@@ -181,6 +181,53 @@ describe("buildWidgetLines ambient border effect (v0.8.3)", () => {
181
181
  assert.ok(borders.some((b) => b.includes("\x1b[38;5;203m")), `flash-on phase uses red base 203`);
182
182
  });
183
183
  });
184
+ describe("buildWidgetLines footer stability (P1 — no 250ms rotation)", () => {
185
+ // P1: the L5 ticker branch used to re-pick the head text every 250ms via
186
+ // `step = floor(Date.now()/250)`, flipping the footer line on a 250·N ms
187
+ // cycle. After the fix it pins to the most-recent entry, so two renders of
188
+ // the SAME WidgetData at t and t+500ms (well past one rotation slot) must
189
+ // produce byte-identical footer lines. Proves the metronome is gone.
190
+ const stabBase = (overrides = {}) => baseWd({
191
+ theme: DEFAULT_THEME, tuiMode: "full", gameMode: false, level: 1, cachePct: 42, ...overrides,
192
+ });
193
+ it("ticker footer is byte-identical across simulated frames 500ms apart", () => {
194
+ const wd = stabBase({
195
+ ticker: [
196
+ { text: "first", at: 1000 },
197
+ { text: "second", at: 2000 },
198
+ { text: "third", at: 3000 },
199
+ ],
200
+ lastWhy: "because",
201
+ });
202
+ const realNow = Date.now;
203
+ try {
204
+ Date.now = () => 1_000_000; // t
205
+ const a = buildWidgetLines(wd, WIDTH, 0);
206
+ Date.now = () => 1_000_500; // t+500ms (would have rotated twice under the old code)
207
+ const b = buildWidgetLines(wd, WIDTH, 0);
208
+ assert.deepEqual(a, b, "footer byte-identical across 500ms with no state change");
209
+ // And the pinned head is the most-recent entry (third), not a rotation:
210
+ assert.ok(a.some((l) => l.includes("third")), "shows most-recent ticker entry");
211
+ assert.ok(a.some((l) => l.includes("(+2 more)")), "(+N more) suffix preserved");
212
+ assert.ok(a.some((l) => l.includes("because")), "lastWhy preserved");
213
+ }
214
+ finally {
215
+ Date.now = realNow;
216
+ }
217
+ });
218
+ it("footer still flips when the ticker actually changes (new pushTicker)", () => {
219
+ const realNow = Date.now;
220
+ try {
221
+ Date.now = () => 1_000_000;
222
+ const before = buildWidgetLines(stabBase({ ticker: [{ text: "a", at: 1 }] }), WIDTH, 0);
223
+ const after = buildWidgetLines(stabBase({ ticker: [{ text: "a", at: 1 }, { text: "b", at: 2 }] }), WIDTH, 0);
224
+ assert.notDeepEqual(before, after, "footer changes when ticker grows");
225
+ }
226
+ finally {
227
+ Date.now = realNow;
228
+ }
229
+ });
230
+ });
184
231
  describe("buildWidgetLines achievement flare (S35)", () => {
185
232
  const achBase = (overrides = {}) => baseWd({
186
233
  theme: DEFAULT_THEME, tuiMode: "full", gameMode: true, level: 1, cachePct: 42, ...overrides,
@@ -1,2 +1,2 @@
1
- import{r as o,j as n}from"./index-DMntLu-8.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
2
- //# sourceMappingURL=AchievementTiles-BH26e95w.js.map
1
+ import{r as o,j as n}from"./index-B2JJExE9.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
2
+ //# sourceMappingURL=AchievementTiles-BozP_h3c.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementTiles-BH26e95w.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
1
+ {"version":3,"file":"AchievementTiles-BozP_h3c.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
@@ -1,2 +1,2 @@
1
- import{r as t,g as m,j as s}from"./index-DMntLu-8.js";import{A as v}from"./AchievementTiles-BH26e95w.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
2
- //# sourceMappingURL=AchievementsTab-B94wvRV_.js.map
1
+ import{r as t,g as m,j as s}from"./index-B2JJExE9.js";import{A as v}from"./AchievementTiles-BozP_h3c.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
2
+ //# sourceMappingURL=AchievementsTab-DKgqx1RQ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementsTab-B94wvRV_.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
1
+ {"version":3,"file":"AchievementsTab-DKgqx1RQ.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
@@ -1,2 +1,2 @@
1
- import{u as n,r,b as d,j as s}from"./index-DMntLu-8.js";import{C as l,T as h}from"./TimeSavedCard-kUnoospE.js";function p(){const{data:a,loading:i,error:c}=n(r.useCallback(()=>d(),[]),{pollInterval:5e3});if(i&&!a)return s.jsx("div",{className:"tab-stub",children:"Loading snapshot…"});if(c&&!a)return s.jsxs("div",{className:"tab-stub",children:["Error: ",c.message]});if(!a)return s.jsx("div",{className:"tab-stub",children:"No snapshot data."});const{cacheHits:e,compacts:o,timeSaved:t}=a;return s.jsx("div",{className:"cache-tab",children:s.jsxs("div",{className:"card-grid overview-card-grid",children:[s.jsx(l,{cacheHitsSession:e.session,cacheHitsTotal:e.total,tokensSavedSession:e.sessionTokensSaved,tokensSavedTotal:e.totalTokensSaved,compactionsSession:o.session,compactionsTotal:o.total}),s.jsx(h,{compactSessionSec:t.compact.sessionSec,compactTotalSec:t.compact.totalSec,cacheHitSessionSec:t.cacheHit.sessionSec,cacheHitTotalSec:t.cacheHit.totalSec})]})})}export{p as default};
2
- //# sourceMappingURL=CacheTab-DPmw3NHR.js.map
1
+ import{u as n,r,b as d,j as s}from"./index-B2JJExE9.js";import{C as l,T as h}from"./TimeSavedCard-B7aPcBMs.js";function p(){const{data:a,loading:i,error:c}=n(r.useCallback(()=>d(),[]),{pollInterval:5e3});if(i&&!a)return s.jsx("div",{className:"tab-stub",children:"Loading snapshot…"});if(c&&!a)return s.jsxs("div",{className:"tab-stub",children:["Error: ",c.message]});if(!a)return s.jsx("div",{className:"tab-stub",children:"No snapshot data."});const{cacheHits:e,compacts:o,timeSaved:t}=a;return s.jsx("div",{className:"cache-tab",children:s.jsxs("div",{className:"card-grid overview-card-grid",children:[s.jsx(l,{cacheHitsSession:e.session,cacheHitsTotal:e.total,tokensSavedSession:e.sessionTokensSaved,tokensSavedTotal:e.totalTokensSaved,compactionsSession:o.session,compactionsTotal:o.total}),s.jsx(h,{compactSessionSec:t.compact.sessionSec,compactTotalSec:t.compact.totalSec,cacheHitSessionSec:t.cacheHit.sessionSec,cacheHitTotalSec:t.cacheHit.totalSec})]})})}export{p as default};
2
+ //# sourceMappingURL=CacheTab-CPvso4-0.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CacheTab-DPmw3NHR.js","sources":["../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/CacheTab.tsx — Cache tab (NEW).\n *\n * Renders CacheHitsCard + TimeSavedCard from /api/snapshot\n * via the useApi hook with 5s polling.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport type { SnapshotResponse } from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchSnapshot } from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\tconst { data: snapshot, loading, error } = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\tif (loading && !snapshot)\n\t\treturn <div className=\"tab-stub\">Loading snapshot…</div>;\n\tif (error && !snapshot)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\tif (!snapshot) return <div className=\"tab-stub\">No snapshot data.</div>;\n\n\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t<CacheHitsCard\n\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t/>\n\t\t\t\t<TimeSavedCard\n\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["CacheTab","snapshot","loading","error","useApi","useCallback","fetchSnapshot","jsx","jsxs","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"+GAeA,SAAwBA,GAA+B,CACtD,KAAM,CAAE,KAAMC,EAAU,QAAAC,EAAS,MAAAC,GAAUC,EAC1CC,cAAY,IAAMC,EAAA,EAAiB,EAAE,EACrC,CAAE,aAAc,GAAA,CAAK,EAGtB,GAAIJ,GAAW,CAACD,EACf,OAAOM,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EACnD,GAAIJ,GAAS,CAACF,EACb,OAAOO,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQL,EAAM,OAAA,EAAQ,EACxD,GAAI,CAACF,EAAU,aAAQ,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EAEjE,KAAM,CAAE,UAAAQ,EAAW,SAAAC,EAAU,UAAAC,CAAA,EAAcV,EAE3C,aACE,MAAA,CAAI,UAAU,YACd,SAAAO,EAAAA,KAAC,MAAA,CAAI,UAAU,+BACd,SAAA,CAAAD,EAAAA,IAACK,EAAA,CACA,iBAAkBH,EAAU,QAC5B,eAAgBA,EAAU,MAC1B,mBAAoBA,EAAU,mBAC9B,iBAAkBA,EAAU,iBAC5B,mBAAoBC,EAAS,QAC7B,iBAAkBA,EAAS,KAAA,CAAA,EAE5BH,EAAAA,IAACM,EAAA,CACA,kBAAmBF,EAAU,QAAQ,WACrC,gBAAiBA,EAAU,QAAQ,SACnC,mBAAoBA,EAAU,SAAS,WACvC,iBAAkBA,EAAU,SAAS,QAAA,CAAA,CACtC,CAAA,CACD,CAAA,CACD,CAEF"}
1
+ {"version":3,"file":"CacheTab-CPvso4-0.js","sources":["../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/CacheTab.tsx — Cache tab (NEW).\n *\n * Renders CacheHitsCard + TimeSavedCard from /api/snapshot\n * via the useApi hook with 5s polling.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport type { SnapshotResponse } from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchSnapshot } from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\tconst { data: snapshot, loading, error } = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\tif (loading && !snapshot)\n\t\treturn <div className=\"tab-stub\">Loading snapshot…</div>;\n\tif (error && !snapshot)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\tif (!snapshot) return <div className=\"tab-stub\">No snapshot data.</div>;\n\n\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t<CacheHitsCard\n\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t/>\n\t\t\t\t<TimeSavedCard\n\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["CacheTab","snapshot","loading","error","useApi","useCallback","fetchSnapshot","jsx","jsxs","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"+GAeA,SAAwBA,GAA+B,CACtD,KAAM,CAAE,KAAMC,EAAU,QAAAC,EAAS,MAAAC,GAAUC,EAC1CC,cAAY,IAAMC,EAAA,EAAiB,EAAE,EACrC,CAAE,aAAc,GAAA,CAAK,EAGtB,GAAIJ,GAAW,CAACD,EACf,OAAOM,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EACnD,GAAIJ,GAAS,CAACF,EACb,OAAOO,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQL,EAAM,OAAA,EAAQ,EACxD,GAAI,CAACF,EAAU,aAAQ,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EAEjE,KAAM,CAAE,UAAAQ,EAAW,SAAAC,EAAU,UAAAC,CAAA,EAAcV,EAE3C,aACE,MAAA,CAAI,UAAU,YACd,SAAAO,EAAAA,KAAC,MAAA,CAAI,UAAU,+BACd,SAAA,CAAAD,EAAAA,IAACK,EAAA,CACA,iBAAkBH,EAAU,QAC5B,eAAgBA,EAAU,MAC1B,mBAAoBA,EAAU,mBAC9B,iBAAkBA,EAAU,iBAC5B,mBAAoBC,EAAS,QAC7B,iBAAkBA,EAAS,KAAA,CAAA,EAE5BH,EAAAA,IAACM,EAAA,CACA,kBAAmBF,EAAU,QAAQ,WACrC,gBAAiBA,EAAU,QAAQ,SACnC,mBAAoBA,EAAU,SAAS,WACvC,iBAAkBA,EAAU,SAAS,QAAA,CAAA,CACtC,CAAA,CACD,CAAA,CACD,CAEF"}
@@ -0,0 +1,2 @@
1
+ import{r as i,p as N,j as e,b as w,c as y}from"./index-B2JJExE9.js";const b=[{id:"transparent",label:"Transparent (accents only)",css:{bg:null,fg:"#c9d1d9",accent:"#3fb950",mega:"#f0883e"},ansi:{bg:null,fg:"39",accent:"32",mega:"33"}},{id:"retro",label:"Retro 8-bit green",css:{bg:"#003300",fg:"#33ff33",accent:"#00ff41",mega:"#39ff14"},ansi:{bg:"40",fg:"92",accent:"92",mega:"1;92"}},{id:"orange-bold",label:"Orange bold",css:{bg:"#1a0f00",fg:"#ff8c00",accent:"#ff6b00",mega:"#ff4500"},ansi:{bg:"48;5;52",fg:"38;5;208",accent:"38;5;208",mega:"1;38;5;202"}},{id:"cyan-neon",label:"Cyan neon",css:{bg:"#001a1a",fg:"#00ffff",accent:"#22d3ee",mega:"#7df9ff"},ansi:{bg:"48;5;23",fg:"38;5;51",accent:"38;5;51",mega:"1;38;5;51"}},{id:"amber-mono",label:"Amber phosphor",css:{bg:"#1a1200",fg:"#ffb000",accent:"#ff8c00",mega:"#ff5500"},ansi:{bg:"48;5;58",fg:"38;5;214",accent:"38;5;214",mega:"1;38;5;208"}},{id:"grayscale",label:"Grayscale minimal",css:{bg:"#161616",fg:"#b0b0b0",accent:"#8b949e",mega:"#e6edf3"},ansi:{bg:"48;5;235",fg:"38;5;249",accent:"38;5;249",mega:"1;38;5;255"}}],g=b.map(t=>t.id),c={gameMode:"Turn game mode on/off (themes the widget + dashboard)",theme:"Visual theme (applies instantly)",tuiMode:"TUI widget display density",tier:"Live pressure band — climbs low→mega as context fills the window.",preset:"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.",pressure:"Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window).",threshold:"Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size.",fastGate:"Fast-gate arming floor — the live trim arms once context passes this % of the window."};function T(t,d,l){let o=t.toLocaleString();return d!=null&&l>0&&(o+=` (${Math.round(d*100)}% of ${l.toLocaleString()})`),o}function S(t){return`${Math.round((t||0)*100)}%`}function E(){const[t,d]=i.useState(null),[l,o]=i.useState(null),[m,u]=i.useState(!1);i.useEffect(()=>{let s=!0;const a=async()=>{try{const r=await w();s&&d(r)}catch{}};a();const f=setInterval(()=>void a(),5e3);return()=>{s=!1,clearInterval(f)}},[]),i.useEffect(()=>{let s=!0;const a=async()=>{try{const r=await y();s&&(o(r),r.theme&&(document.documentElement.dataset.theme=r.theme))}catch{}};a();const f=setInterval(()=>void a(),5e3);return()=>{s=!1,clearInterval(f)}},[]);const h=i.useCallback(async s=>{u(!0);try{const a=await N(s);o(a),a.theme&&(document.documentElement.dataset.theme=a.theme)}catch{}u(!1)},[]),x=l?.game_mode_on??!1,p=l?.theme??"transparent",v=l?.tui_display_mode??"full",n=t?.config,j=t?.context.contextWindow??0;return e.jsxs("div",{className:"config-tab",children:[e.jsxs("section",{className:"config-settings",children:[e.jsx("h3",{children:"Settings"}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.gameMode,children:[e.jsx("input",{type:"checkbox",checked:x,onChange:s=>h({game_mode_on:s.currentTarget.checked}),disabled:m}),"Game Mode"]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.theme,children:["Theme",e.jsx("select",{value:p,onChange:s=>h({theme:s.currentTarget.value}),disabled:m,children:b.map(s=>e.jsx("option",{value:s.id,children:s.label},s.id))}),e.jsx("button",{type:"button",disabled:m,className:"theme-cycle-btn",title:"Cycle to the next theme (P3)",onClick:()=>{const s=g.indexOf(p),a=g[(s<0?0:s+1)%g.length];a&&h({theme:a})},children:"Next theme →"})]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.tuiMode,children:["TUI Mode",e.jsxs("select",{value:v,onChange:s=>h({tui_display_mode:s.currentTarget.value}),disabled:m,children:[e.jsx("option",{value:"full",children:"Full"}),e.jsx("option",{value:"minimal",children:"Minimal"})]})," ",e.jsx("span",{className:"tui-mode-hint",children:"(affects the in-app TUI widget; not shown here)"})]})})]}),e.jsxs("section",{className:"config-display",children:[e.jsx("h3",{children:"Configuration"}),e.jsxs("div",{className:"config-grid",children:[e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.tier,children:"Tier"}),e.jsx("span",{className:"config-value",children:t?`${t.tier} (live)`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.preset,children:"Preset"}),e.jsx("span",{className:"config-value",children:t?.presetTier??"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.pressure,children:"Pressure"}),e.jsx("span",{className:"config-value",children:t?S(t.pressure):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.threshold,children:"Threshold"}),e.jsx("span",{className:"config-value",children:n?T(n.thresholdTokens,n.tierPct,j):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.fastGate,children:"Fast Gate"}),e.jsx("span",{className:"config-value",children:n?`${n.fastGatePct}%`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Auto"}),e.jsx("span",{className:"config-value",children:n?n.auto?"enabled":"disabled":"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Anchor"}),e.jsx("span",{className:"config-value",children:n?.anchorUserMessages??"—"})]})]})]})]})}export{E as default};
2
+ //# sourceMappingURL=ConfigTab-DXD7zbqy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigTab-DXD7zbqy.js","sources":["../../../../src/config/themes.ts","../../src/tabs/ConfigTab.tsx"],"sourcesContent":["/**\n * themes.ts — SINGLE SOURCE OF TRUTH for game-mode visual-effect themes (S30).\n *\n * Each theme defines BOTH an HTML/CSS palette (dashboard) AND an ANSI accent\n * mapping (TUI widget), since the TUI is ANSI and the dashboard is HTML.\n *\n * Theme scope (QA1 correction): themes restyle the mega-compact statusbar\n * widget + the whole dashboard HTML page. We do NOT theme pi's chrome.\n *\n * `transparent` (default) semantics: no background fill, colored accents only —\n * so game-mode stays visible against any terminal background.\n *\n * PREVENT-PI-004: pure config, no network. Pi-agnostic: no pi runtime types.\n */\n\n/** HTML/CSS palette for the dashboard (hex strings, or `null` for \"no bg\"). */\nexport interface ThemeCss {\n /** Page/cell background hex, or `null` for transparent (no bg fill). */\n bg: string | null;\n /** Default foreground (text) hex. */\n fg: string;\n /** Accent hex (bars, highlights, level numbers). */\n accent: string;\n /** MEGA CACHE highlight hex (fires at cache >= 100%). */\n mega: string;\n}\n\n/** ANSI accent mapping for the TUI widget (SGR parameter strings, no `\\x1b[`).\n * `bg` is `null` for transparent themes (no background fill). */\nexport interface ThemeAnsi {\n /** Background SGR params (e.g. \"48;5;23\"), or `null` for no bg fill. */\n bg: string | null;\n /** Foreground SGR params (e.g. \"39\" for default, \"92\" for bright green). */\n fg: string;\n /** Accent SGR params. */\n accent: string;\n /** MEGA CACHE SGR params (fires at cache >= 100%). */\n mega: string;\n}\n\n/** A complete theme = CSS palette + ANSI mapping + display metadata. */\nexport interface Theme {\n id: string;\n label: string;\n css: ThemeCss;\n ansi: ThemeAnsi;\n}\n\nexport const DEFAULT_THEME = \"transparent\";\n\nexport const THEMES: readonly Theme[] = [\n {\n id: \"transparent\",\n label: \"Transparent (accents only)\",\n css: { bg: null, fg: \"#c9d1d9\", accent: \"#3fb950\", mega: \"#f0883e\" },\n ansi: { bg: null, fg: \"39\", accent: \"32\", mega: \"33\" },\n },\n {\n id: \"retro\",\n label: \"Retro 8-bit green\",\n css: { bg: \"#003300\", fg: \"#33ff33\", accent: \"#00ff41\", mega: \"#39ff14\" },\n ansi: { bg: \"40\", fg: \"92\", accent: \"92\", mega: \"1;92\" },\n },\n {\n id: \"orange-bold\",\n label: \"Orange bold\",\n css: { bg: \"#1a0f00\", fg: \"#ff8c00\", accent: \"#ff6b00\", mega: \"#ff4500\" },\n ansi: { bg: \"48;5;52\", fg: \"38;5;208\", accent: \"38;5;208\", mega: \"1;38;5;202\" },\n },\n {\n id: \"cyan-neon\",\n label: \"Cyan neon\",\n css: { bg: \"#001a1a\", fg: \"#00ffff\", accent: \"#22d3ee\", mega: \"#7df9ff\" },\n ansi: { bg: \"48;5;23\", fg: \"38;5;51\", accent: \"38;5;51\", mega: \"1;38;5;51\" },\n },\n {\n id: \"amber-mono\",\n label: \"Amber phosphor\",\n css: { bg: \"#1a1200\", fg: \"#ffb000\", accent: \"#ff8c00\", mega: \"#ff5500\" },\n ansi: { bg: \"48;5;58\", fg: \"38;5;214\", accent: \"38;5;214\", mega: \"1;38;5;208\" },\n },\n {\n id: \"grayscale\",\n label: \"Grayscale minimal\",\n css: { bg: \"#161616\", fg: \"#b0b0b0\", accent: \"#8b949e\", mega: \"#e6edf3\" },\n ansi: { bg: \"48;5;235\", fg: \"38;5;249\", accent: \"38;5;249\", mega: \"1;38;5;255\" },\n },\n] as const;\n\n/** All valid theme ids, in definition order (used for `theme next` cycling). */\nexport const THEME_IDS: readonly string[] = THEMES.map((t) => t.id);\n\n/** Look up a theme by id; returns `undefined` for an unknown id (caller\n * validates + falls back to DEFAULT_THEME). */\nexport function getTheme(id: string): Theme | undefined {\n return THEMES.find((t) => t.id === id);\n}\n\n/** True if `id` is a known theme id. */\nexport function isValidTheme(id: string): boolean {\n return THEME_IDS.includes(id);\n}\n\n/** Cycle to the next theme id (wraps at the end). Falls back to DEFAULT_THEME\n * if `current` is unknown (defensive — the stored id should always be valid). */\nexport function nextTheme(current: string): string {\n const i = THEME_IDS.indexOf(current);\n if (i < 0) return DEFAULT_THEME;\n return THEME_IDS[(i + 1) % THEME_IDS.length]!;\n}\n\n/** CSS variable declarations for a theme's CSS palette. `bg` is `null` for the\n * transparent theme → `--bg: transparent` (no page fill, accents only). Pure,\n * no pi types. */\nexport function themeCssVars(theme: Theme): string {\n const bg = theme.css.bg ?? \"transparent\";\n return `--bg: ${bg}; --fg: ${theme.css.fg}; --accent: ${theme.css.accent}; --mega: ${theme.css.mega};`;\n}\n\n/** A `:root[data-theme=\"<id>\"]` override block carrying the theme's CSS vars,\n * for client-side instant theme switching. Pure, no pi types. */\nexport function themeDataBlock(theme: Theme): string {\n return `:root[data-theme=\"${theme.id}\"]{ ${themeCssVars(theme)} }`;\n}\n","/**\n * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (C3).\n *\n * Full configuration editor:\n * (1) Game mode toggle (checkbox)\n * (2) Theme selector (dropdown) from src/config/themes.ts THEMES array\n * (3) TUI mode selector (Full/Minimal dropdown)\n * (4) Read-only config display section from snapshot (Tier, Preset, Pressure,\n * Threshold, Fast Gate, Auto, Anchor) with verbatim tooltips from html.ts.\n *\n * Uses fetchGameState() to load current state, putGameState() to save changes.\n * Polls gameState and snapshot every 5s. Applies theme by setting\n * document.documentElement.dataset.theme.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SnapshotResponse, GameStatePatch } from \"@contracts\";\nimport { fetchSnapshot, fetchGameState, putGameState } from \"../api/client\";\nimport { THEMES, THEME_IDS } from \"../../../../src/config/themes\";\n\n/** Actual flat game state returned by the server (the server returns a flat\n * { game_mode_on, theme, tui_display_mode } shape, NOT the contract's nested\n * { config: { enabled, theme, displayMode }, activeRitual } shape). */\ninterface FlatGameState {\n\tgame_mode_on: boolean;\n\ttheme: string;\n\ttui_display_mode: \"full\" | \"minimal\";\n}\ntype FlatGameStatePatch = Partial<FlatGameState>;\n\n/** Extended snapshot config — the runtime includes tierPct (not in contract). */\ninterface ConfigExt extends SnapshotResponse[\"config\"] {\n\ttierPct?: number | null;\n}\n\n/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */\nconst TOOLTIPS = {\n\tgameMode: \"Turn game mode on/off (themes the widget + dashboard)\",\n\ttheme: \"Visual theme (applies instantly)\",\n\ttuiMode: \"TUI widget display density\",\n\ttier: \"Live pressure band — climbs low\\u2192mega as context fills the window.\",\n\tpreset:\n\t\t\"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.\",\n\tpressure:\n\t\t\"Live pressure = currentTokens / threshold \\u2014 % of the model context window (threshold fires at the tier's % of window).\",\n\tthreshold:\n\t\t\"Compaction threshold = tierPct \\u00d7 model context window \\u2014 mega-compact trims BELOW pi's native ~80% auto-compact for any model size.\",\n\tfastGate:\n\t\t\"Fast-gate arming floor \\u2014 the live trim arms once context passes this % of the window.\",\n} as const;\n\nfunction formatThreshold(\n\ttokens: number,\n\ttierPct: number | null | undefined,\n\tcw: number,\n): string {\n\tlet txt = tokens.toLocaleString();\n\tif (tierPct != null && cw > 0) {\n\t\ttxt += ` (${Math.round(tierPct * 100)}% of ${cw.toLocaleString()})`;\n\t}\n\treturn txt;\n}\n\nfunction formatPressure(pressure: number): string {\n\t/* Runtime sends 0\\u20131 fraction despite contract saying 0\\u2013100. */\n\treturn `${Math.round((pressure || 0) * 100)}%`;\n}\n\nexport default function ConfigTab(): React.ReactElement {\n\tconst [snapshot, setSnapshot] = useState<SnapshotResponse | null>(null);\n\tconst [gameState, setGameState] = useState<FlatGameState | null>(null);\n\tconst [saving, setSaving] = useState(false);\n\n\t/* Poll snapshot every 5s for config display section. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst s = await fetchSnapshot();\n\t\t\t\tif (active) setSnapshot(s);\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* Poll game state every 5s. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst gs = (await fetchGameState()) as unknown as FlatGameState;\n\t\t\t\tif (active) {\n\t\t\t\t\tsetGameState(gs);\n\t\t\t\t\tif (gs.theme)\n\t\t\t\t\t\tdocument.documentElement.dataset.theme = gs.theme;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* PUT a patch to the server immediately on change. */\n\tconst handlePatch = useCallback(async (patch: FlatGameStatePatch) => {\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tconst result = (await putGameState(\n\t\t\t\tpatch as unknown as GameStatePatch,\n\t\t\t)) as unknown as FlatGameState;\n\t\t\tsetGameState(result);\n\t\t\tif (result.theme)\n\t\t\t\tdocument.documentElement.dataset.theme = result.theme;\n\t\t} catch {\n\t\t\t/* non-fatal */\n\t\t}\n\t\tsetSaving(false);\n\t}, []);\n\n\tconst gmOn = gameState?.game_mode_on ?? false;\n\tconst themeVal = gameState?.theme ?? \"transparent\";\n\tconst tuiVal = gameState?.tui_display_mode ?? \"full\";\n\n\tconst cfg = snapshot?.config as ConfigExt | undefined;\n\tconst cw = snapshot?.context.contextWindow ?? 0;\n\n\treturn (\n\t\t<div className=\"config-tab\">\n\t\t\t<section className=\"config-settings\">\n\t\t\t\t<h3>Settings</h3>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.gameMode}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={gmOn}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({ game_mode_on: e.currentTarget.checked })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tGame Mode\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.theme}>\n\t\t\t\t\t\tTheme\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={themeVal}\n\t\t\t\t\t\t\tonChange={(e) => handlePatch({ theme: e.currentTarget.value })}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{THEMES.map((t) => (\n\t\t\t\t\t\t\t\t<option key={t.id} value={t.id}>\n\t\t\t\t\t\t\t\t\t{t.label}\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</select>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t\tclassName=\"theme-cycle-btn\"\n\t\t\t\t\t\t\ttitle=\"Cycle to the next theme (P3)\"\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tconst i = THEME_IDS.indexOf(themeVal);\n\t\t\t\t\t\t\t\tconst next =\n\t\t\t\t\t\t\t\t\tTHEME_IDS[(i < 0 ? 0 : i + 1) % THEME_IDS.length];\n\t\t\t\t\t\t\t\tif (next) void handlePatch({ theme: next });\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tNext theme →\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.tuiMode}>\n\t\t\t\t\t\tTUI Mode\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={tuiVal}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({\n\t\t\t\t\t\t\t\t\ttui_display_mode: e.currentTarget.value as \"full\" | \"minimal\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"full\">Full</option>\n\t\t\t\t\t\t\t<option value=\"minimal\">Minimal</option>\n\t\t\t\t\t\t</select>{\" \"}\n\t\t\t\t\t\t\t<span className=\"tui-mode-hint\">\n\t\t\t\t\t\t\t\t(affects the in-app TUI widget; not shown here)\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"config-display\">\n\t\t\t\t<h3>Configuration</h3>\n\t\t\t\t<div className=\"config-grid\">\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.tier}>\n\t\t\t\t\t\t\tTier\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? `${snapshot.tier} (live)` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.preset}>\n\t\t\t\t\t\t\tPreset\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot?.presetTier ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.pressure}>\n\t\t\t\t\t\t\tPressure\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? formatPressure(snapshot.pressure) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.threshold}>\n\t\t\t\t\t\t\tThreshold\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? formatThreshold(cfg.thresholdTokens, cfg.tierPct, cw) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.fastGate}>\n\t\t\t\t\t\t\tFast Gate\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? `${cfg.fastGatePct}%` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Auto</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? (cfg.auto ? \"enabled\" : \"disabled\") : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Anchor</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg?.anchorUserMessages ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t);\n}\n"],"names":["THEMES","THEME_IDS","TOOLTIPS","formatThreshold","tokens","tierPct","cw","txt","formatPressure","pressure","ConfigTab","snapshot","setSnapshot","useState","gameState","setGameState","saving","setSaving","useEffect","active","doFetch","s","fetchSnapshot","timer","gs","fetchGameState","handlePatch","useCallback","patch","result","putGameState","gmOn","themeVal","tuiVal","cfg","jsxs","jsx","e","t","i","next"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{r,j as s}from"./index-DMntLu-8.js";function E(e={}){const{maxEvents:t=500,maxReconnects:c=5}=e,[o,l]=r.useState([]),[i,n]=r.useState("connecting"),[a,u]=r.useState(0),[f,$]=r.useState(null),_=r.useRef(0),m=r.useRef(null),g=r.useCallback(()=>{m.current&&m.current.close(),n("connecting");const d=new EventSource("/api/events");m.current=d,d.onopen=()=>{n("connected"),_.current=0},d.onmessage=x=>{try{const j=JSON.parse(x.data);l(p=>{const h=[...p,j];return h.length>t?h.slice(-t):h}),u(p=>p+1),$(Date.now())}catch{}},d.onerror=()=>{d.close(),_.current<c?(_.current+=1,n("connecting"),setTimeout(g,1e3)):n("disconnected")}},[t,c]);return r.useEffect(()=>(g(),()=>{m.current?.close()}),[g]),{events:o,status:i,eventCount:a,lastEventAt:f}}const S={compact_start:"ev-compact",compact_end:"ev-compact",compact_trigger:"ev-trigger",compact_skip:"ev-trigger",tier_changed:"ev-tier",model_changed:"ev-tier",pressure_lifted:"ev-tier",checkpoint_persisted:"ev-checkpoint",recall_inject:"ev-recall",anchors_updated:"ev-recall",config_updated:"ev-config",config_preset:"ev-config",crew_presence_changed:"ev-crew",crew_turn_changed:"ev-crew",crew_bandit_chosen:"ev-crew",game_ritual_start:"ev-game",game_ritual_stage:"ev-game",game_ritual_end:"ev-game",game_mode_changed:"ev-game",game_render:"ev-game"},y=["all","compact_start","compact_end","recall_inject","checkpoint_persisted"];function k(e){switch(e.type){case"compact_start":return`trigger=${e.trigger} session=${e.sessionId.slice(0,8)}`;case"compact_end":return`freed=${e.tokensFreed} ok=${e.success} cp=${e.checkpointId.slice(0,8)}`;case"compact_trigger":return`pressure=${e.pressure}% threshold=${e.threshold}% armed=${e.armed}`;case"compact_skip":return`reason=${e.reason}`;case"tier_changed":return`${e.from} → ${e.to} ctx=${e.contextPct}%`;case"model_changed":return`${e.providerName}/${e.model}`;case"pressure_lifted":return`${e.beforePct}% → ${e.afterPct}%`;case"checkpoint_persisted":return`cp=${e.checkpointId.slice(0,8)} sessionTok=${e.sessionTokens}`;case"recall_inject":return`q="${e.query.slice(0,40)}" chunks=${e.chunks} tok=${e.tokens}`;case"anchors_updated":return`count=${e.count} pinned=${e.pinned}`;case"config_updated":return`key=${e.key}`;case"config_preset":return`preset=${e.preset}`;case"crew_presence_changed":return`agents=${e.activeAgents} turn=${e.currentTurn}`;case"crew_turn_changed":return`turn=${e.turnIndex} agent=${e.agentName}`;case"crew_bandit_chosen":return`agent=${e.chosenAgent} score=${e.score} regret=${e.regret}`;case"game_ritual_start":case"game_ritual_stage":case"game_ritual_end":case"game_mode_changed":case"game_render":return`stage=${e.type}`}}function N(e){try{return new Date(e).toLocaleTimeString()}catch{return e}}function v({events:e}){const[t,c]=r.useState("all"),[o,l]=r.useState(null),i=r.useMemo(()=>(t==="all"?e:e.filter(a=>a.type===t)).slice(-200).reverse(),[e,t]);return s.jsxs("div",{className:"event-stream",children:[s.jsxs("div",{className:"event-filters",role:"toolbar",children:[y.map(n=>s.jsx("button",{type:"button",className:`filter-chip ${t===n?"active":""}`,onClick:()=>c(n),children:n==="all"?"all":n.replace(/_/g," ")},n)),s.jsxs("span",{className:"event-count",children:[e.length," buffered"]})]}),s.jsxs("ul",{className:"event-list",children:[i.length===0&&s.jsx("li",{className:"event-empty",children:"No events yet."}),i.map((n,a)=>{const u=S[n.type]??"ev-default";return s.jsxs("li",{className:`event-row ${u}`,onClick:()=>l(o===a?null:a),children:[s.jsx("span",{className:"ev-time",children:N(n.ts)}),s.jsx("span",{className:"ev-type",children:n.type}),s.jsx("span",{className:"ev-summary",children:k(n)}),o===a&&s.jsx("pre",{className:"ev-detail",children:JSON.stringify(n,null,2)})]},`${n.ts}-${a}`)})]})]})}const w=["all","compact","recall","config","crew","game"];function C({active:e,onFilter:t}){return s.jsx("div",{className:"event-category-filter",role:"toolbar","aria-label":"Event category filter",children:w.map(c=>s.jsx("button",{type:"button",className:`category-chip${e===c?" active":""}`,onClick:()=>t(c),"aria-pressed":e===c,children:c},c))})}const b=new Set(["compact_start","compact_end","compact_trigger","compact_skip"]),T=new Set(["config_updated","config_preset","tier_changed","model_changed","pressure_lifted","anchors_updated","checkpoint_persisted"]),P=new Set(["crew_presence_changed","crew_turn_changed","crew_bandit_chosen"]),R=new Set(["game_ritual_start","game_ritual_stage","game_ritual_end","game_mode_changed","game_render"]);function A(e,t){if(t==="all")return!0;const c=e.type;return t==="compact"?b.has(c):t==="recall"?c==="recall_inject":t==="config"?T.has(c):t==="crew"?P.has(c):R.has(c)}function O(){const{events:e,status:t,eventCount:c}=E(),[o,l]=r.useState("all"),i=r.useMemo(()=>e.filter(u=>A(u,o)),[e,o]),n=`sse-status sse-${t}`,a=t==="connected"?"connected":t==="connecting"?"connecting…":t==="error"?"error":"disconnected";return s.jsxs("div",{className:"events-tab",children:[s.jsxs("div",{className:"events-header",children:[s.jsxs("span",{className:n,role:"status",children:[s.jsx("span",{className:"sse-dot","aria-hidden":"true"}),a]}),s.jsxs("span",{className:"event-count",children:[c," events received"]})]}),s.jsx(C,{active:o,onFilter:l}),s.jsx(v,{events:i})]})}export{O as default};
2
- //# sourceMappingURL=EventsTab-B5PicfFw.js.map
1
+ import{r,j as s}from"./index-B2JJExE9.js";function E(e={}){const{maxEvents:t=500,maxReconnects:c=5}=e,[o,l]=r.useState([]),[i,n]=r.useState("connecting"),[a,u]=r.useState(0),[f,$]=r.useState(null),_=r.useRef(0),m=r.useRef(null),g=r.useCallback(()=>{m.current&&m.current.close(),n("connecting");const d=new EventSource("/api/events");m.current=d,d.onopen=()=>{n("connected"),_.current=0},d.onmessage=x=>{try{const j=JSON.parse(x.data);l(p=>{const h=[...p,j];return h.length>t?h.slice(-t):h}),u(p=>p+1),$(Date.now())}catch{}},d.onerror=()=>{d.close(),_.current<c?(_.current+=1,n("connecting"),setTimeout(g,1e3)):n("disconnected")}},[t,c]);return r.useEffect(()=>(g(),()=>{m.current?.close()}),[g]),{events:o,status:i,eventCount:a,lastEventAt:f}}const S={compact_start:"ev-compact",compact_end:"ev-compact",compact_trigger:"ev-trigger",compact_skip:"ev-trigger",tier_changed:"ev-tier",model_changed:"ev-tier",pressure_lifted:"ev-tier",checkpoint_persisted:"ev-checkpoint",recall_inject:"ev-recall",anchors_updated:"ev-recall",config_updated:"ev-config",config_preset:"ev-config",crew_presence_changed:"ev-crew",crew_turn_changed:"ev-crew",crew_bandit_chosen:"ev-crew",game_ritual_start:"ev-game",game_ritual_stage:"ev-game",game_ritual_end:"ev-game",game_mode_changed:"ev-game",game_render:"ev-game"},y=["all","compact_start","compact_end","recall_inject","checkpoint_persisted"];function k(e){switch(e.type){case"compact_start":return`trigger=${e.trigger} session=${e.sessionId.slice(0,8)}`;case"compact_end":return`freed=${e.tokensFreed} ok=${e.success} cp=${e.checkpointId.slice(0,8)}`;case"compact_trigger":return`pressure=${e.pressure}% threshold=${e.threshold}% armed=${e.armed}`;case"compact_skip":return`reason=${e.reason}`;case"tier_changed":return`${e.from} → ${e.to} ctx=${e.contextPct}%`;case"model_changed":return`${e.providerName}/${e.model}`;case"pressure_lifted":return`${e.beforePct}% → ${e.afterPct}%`;case"checkpoint_persisted":return`cp=${e.checkpointId.slice(0,8)} sessionTok=${e.sessionTokens}`;case"recall_inject":return`q="${e.query.slice(0,40)}" chunks=${e.chunks} tok=${e.tokens}`;case"anchors_updated":return`count=${e.count} pinned=${e.pinned}`;case"config_updated":return`key=${e.key}`;case"config_preset":return`preset=${e.preset}`;case"crew_presence_changed":return`agents=${e.activeAgents} turn=${e.currentTurn}`;case"crew_turn_changed":return`turn=${e.turnIndex} agent=${e.agentName}`;case"crew_bandit_chosen":return`agent=${e.chosenAgent} score=${e.score} regret=${e.regret}`;case"game_ritual_start":case"game_ritual_stage":case"game_ritual_end":case"game_mode_changed":case"game_render":return`stage=${e.type}`}}function N(e){try{return new Date(e).toLocaleTimeString()}catch{return e}}function v({events:e}){const[t,c]=r.useState("all"),[o,l]=r.useState(null),i=r.useMemo(()=>(t==="all"?e:e.filter(a=>a.type===t)).slice(-200).reverse(),[e,t]);return s.jsxs("div",{className:"event-stream",children:[s.jsxs("div",{className:"event-filters",role:"toolbar",children:[y.map(n=>s.jsx("button",{type:"button",className:`filter-chip ${t===n?"active":""}`,onClick:()=>c(n),children:n==="all"?"all":n.replace(/_/g," ")},n)),s.jsxs("span",{className:"event-count",children:[e.length," buffered"]})]}),s.jsxs("ul",{className:"event-list",children:[i.length===0&&s.jsx("li",{className:"event-empty",children:"No events yet."}),i.map((n,a)=>{const u=S[n.type]??"ev-default";return s.jsxs("li",{className:`event-row ${u}`,onClick:()=>l(o===a?null:a),children:[s.jsx("span",{className:"ev-time",children:N(n.ts)}),s.jsx("span",{className:"ev-type",children:n.type}),s.jsx("span",{className:"ev-summary",children:k(n)}),o===a&&s.jsx("pre",{className:"ev-detail",children:JSON.stringify(n,null,2)})]},`${n.ts}-${a}`)})]})]})}const w=["all","compact","recall","config","crew","game"];function C({active:e,onFilter:t}){return s.jsx("div",{className:"event-category-filter",role:"toolbar","aria-label":"Event category filter",children:w.map(c=>s.jsx("button",{type:"button",className:`category-chip${e===c?" active":""}`,onClick:()=>t(c),"aria-pressed":e===c,children:c},c))})}const b=new Set(["compact_start","compact_end","compact_trigger","compact_skip"]),T=new Set(["config_updated","config_preset","tier_changed","model_changed","pressure_lifted","anchors_updated","checkpoint_persisted"]),P=new Set(["crew_presence_changed","crew_turn_changed","crew_bandit_chosen"]),R=new Set(["game_ritual_start","game_ritual_stage","game_ritual_end","game_mode_changed","game_render"]);function A(e,t){if(t==="all")return!0;const c=e.type;return t==="compact"?b.has(c):t==="recall"?c==="recall_inject":t==="config"?T.has(c):t==="crew"?P.has(c):R.has(c)}function O(){const{events:e,status:t,eventCount:c}=E(),[o,l]=r.useState("all"),i=r.useMemo(()=>e.filter(u=>A(u,o)),[e,o]),n=`sse-status sse-${t}`,a=t==="connected"?"connected":t==="connecting"?"connecting…":t==="error"?"error":"disconnected";return s.jsxs("div",{className:"events-tab",children:[s.jsxs("div",{className:"events-header",children:[s.jsxs("span",{className:n,role:"status",children:[s.jsx("span",{className:"sse-dot","aria-hidden":"true"}),a]}),s.jsxs("span",{className:"event-count",children:[c," events received"]})]}),s.jsx(C,{active:o,onFilter:l}),s.jsx(v,{events:i})]})}export{O as default};
2
+ //# sourceMappingURL=EventsTab-BGeuYvM3.js.map