pi-mega-compact 0.11.0 → 0.11.2

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 (55) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints.js +19 -0
  2. package/dist/extensions/dashboard-server/api-contracts/maintenance.js +7 -0
  3. package/dist/extensions/dashboard-server/api-contracts.test.js +1 -1
  4. package/dist/extensions/dashboard-server/routes-maintenance.js +293 -0
  5. package/dist/extensions/dashboard-server/routes.js +1 -0
  6. package/dist/extensions/dashboard-server/server.js +3 -1
  7. package/dist/src/store/sqlite/maintenance.js +20 -1
  8. package/dist/src/store/sqlite/schema.js +8 -5
  9. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DZp_xQOW.js → AchievementTiles-Cml6YC8w.js} +2 -2
  10. package/extensions/dashboard-client/dist/assets/{AchievementTiles-DZp_xQOW.js.map → AchievementTiles-Cml6YC8w.js.map} +1 -1
  11. package/extensions/dashboard-client/dist/assets/{AchievementsTab-BzFHpAHM.js → AchievementsTab-B8NqDt7I.js} +2 -2
  12. package/extensions/dashboard-client/dist/assets/{AchievementsTab-BzFHpAHM.js.map → AchievementsTab-B8NqDt7I.js.map} +1 -1
  13. package/extensions/dashboard-client/dist/assets/{CacheTab-DEQbTq2T.js → CacheTab-BCLzVL4S.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{CacheTab-DEQbTq2T.js.map → CacheTab-BCLzVL4S.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{ConfigTab-DQ4FG33J.js → ConfigTab-CBYvp2mV.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{ConfigTab-DQ4FG33J.js.map → ConfigTab-CBYvp2mV.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{EventsTab-D5HoEVYo.js → EventsTab-DlTJqiDm.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{EventsTab-D5HoEVYo.js.map → EventsTab-DlTJqiDm.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{GameTab-Jzsc4yeY.js → GameTab-DkGTqKJW.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{GameTab-Jzsc4yeY.js.map → GameTab-DkGTqKJW.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js +2 -0
  22. package/extensions/dashboard-client/dist/assets/MaintenanceTab-Cw43zmUx.js.map +1 -0
  23. package/extensions/dashboard-client/dist/assets/{MetricsTab-D_XnsEYX.js → MetricsTab-BFo7pKM-.js} +2 -2
  24. package/extensions/dashboard-client/dist/assets/{MetricsTab-D_XnsEYX.js.map → MetricsTab-BFo7pKM-.js.map} +1 -1
  25. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMREvAth.js → OverviewTab-CClCUd3-.js} +2 -2
  26. package/extensions/dashboard-client/dist/assets/{OverviewTab-BMREvAth.js.map → OverviewTab-CClCUd3-.js.map} +1 -1
  27. package/extensions/dashboard-client/dist/assets/{ReposTab-DuyhOktG.js → ReposTab-_3R4RSLZ.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{ReposTab-DuyhOktG.js.map → ReposTab-_3R4RSLZ.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/{SessionsTab-mz_vz8i2.js → SessionsTab-CCYd-kSz.js} +2 -2
  30. package/extensions/dashboard-client/dist/assets/{SessionsTab-mz_vz8i2.js.map → SessionsTab-CCYd-kSz.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B8AIjNTY.js → TimeSavedCard-0TqsrY6y.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-B8AIjNTY.js.map → TimeSavedCard-0TqsrY6y.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{TopicsTab-DKuct4oD.js → TopicsTab-DRLJmVv0.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{TopicsTab-DKuct4oD.js.map → TopicsTab-DRLJmVv0.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{TurnsTab-11MGLxY8.js → TurnsTab-CLNM0T5R.js} +2 -2
  36. package/extensions/dashboard-client/dist/assets/{TurnsTab-11MGLxY8.js.map → TurnsTab-CLNM0T5R.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/{index-CqLN7STl.js → index-BcoSz-L4.js} +10 -10
  38. package/extensions/dashboard-client/dist/assets/{index-CqLN7STl.js.map → index-BcoSz-L4.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{useSSE-CXrQSbVY.js → useSSE-Cezlih5V.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{useSSE-CXrQSbVY.js.map → useSSE-Cezlih5V.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/index.html +1 -1
  42. package/extensions/dashboard-client/src/App.tsx +5 -1
  43. package/extensions/dashboard-client/src/api/client.ts +23 -0
  44. package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +293 -0
  45. package/extensions/dashboard-server/api-contracts/endpoints.ts +30 -0
  46. package/extensions/dashboard-server/api-contracts/index.ts +12 -0
  47. package/extensions/dashboard-server/api-contracts/maintenance.ts +84 -0
  48. package/extensions/dashboard-server/api-contracts.test.ts +1 -1
  49. package/extensions/dashboard-server/api-contracts.ts +9 -0
  50. package/extensions/dashboard-server/routes-maintenance.ts +345 -0
  51. package/extensions/dashboard-server/routes.ts +1 -0
  52. package/extensions/dashboard-server/server.ts +2 -0
  53. package/package.json +1 -1
  54. package/src/store/sqlite/maintenance.ts +20 -1
  55. package/src/store/sqlite/schema.ts +62 -49
@@ -169,4 +169,23 @@ export const ENDPOINTS = {
169
169
  path: "/api/topics/:topicId/memories",
170
170
  description: "Member memories assigned to a wiki topic (drill-down).",
171
171
  },
172
+ // ─── S49B Maintenance Tab ─────────────────────────────────────────
173
+ /** GET /api/maintenance — DB stats (table row counts + storage). */
174
+ maintenanceStats: {
175
+ method: "GET",
176
+ path: "/api/maintenance",
177
+ description: "SQLite table row counts, storage stats, and DB file sizes.",
178
+ },
179
+ /** GET /api/maintenance/schema-health — Schema version + integrity audit. */
180
+ schemaHealth: {
181
+ method: "GET",
182
+ path: "/api/maintenance/schema-health",
183
+ description: "SCHEMA_VERSION, PRAGMA integrity_check, FK check, and per-column audit.",
184
+ },
185
+ /** POST /api/maintenance/action — Trigger a maintenance action. */
186
+ maintenanceAction: {
187
+ method: "POST",
188
+ path: "/api/maintenance/action",
189
+ description: "Run vacuum, checkpoint, reindex, fts5-rebuild, reconcile-dedup, prune, or integrity-check.",
190
+ },
172
191
  };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * api-contracts/maintenance.ts — Maintenance tab API contract (S49B).
3
+ *
4
+ * Types for the /api/maintenance and /api/maintenance/action endpoints.
5
+ * All pi-agnostic, all PREVENT-PI-004 compliant (type definitions only).
6
+ */
7
+ export {};
@@ -76,7 +76,7 @@ void _c_achievements;
76
76
  // Verify the ENDPOINTS registry has exactly 15 entries with correct paths.
77
77
  // (Sprint 39 added /api/sessions + /api/sessions/timeseries.)
78
78
  const ENDPOINT_KEYS = Object.keys(ENDPOINTS);
79
- const EXPECTED_ENDPOINT_COUNT = 23;
79
+ const EXPECTED_ENDPOINT_COUNT = 26; // 23 base + 3 S49B maintenance
80
80
  /** All `/api/*` paths served by server.ts (extracted from the route handlers). */
81
81
  const SERVER_TS_PATHS = [
82
82
  "/api/snapshot",
@@ -0,0 +1,293 @@
1
+ /**
2
+ * dashboard-server/routes-maintenance.ts — S49B Maintenance tab API routes.
3
+ *
4
+ * Handlers for:
5
+ * GET /api/maintenance — DB stats (table row counts + storage)
6
+ * GET /api/maintenance/schema-health — SCHEMA_VERSION + integrity audit
7
+ * POST /api/maintenance/action — Trigger a maintenance action
8
+ *
9
+ * All pi-agnostic; lazy-requires maintenance primitives to avoid a top-level
10
+ * import of the store from the dashboard server process. PREVENT-PI-004: local
11
+ * SQLite only; zero network calls.
12
+ */
13
+ import { createRequire } from "node:module";
14
+ // ---------------------------------------------------------------------------
15
+ // Lazy imports (via createRequire)
16
+ // ---------------------------------------------------------------------------
17
+ const lazyMaintenance = (() => {
18
+ let mod = null;
19
+ return () => {
20
+ if (!mod)
21
+ mod = createRequire(import.meta.url)("../../src/store/sqlite/maintenance.js");
22
+ return mod;
23
+ };
24
+ })();
25
+ const lazyStoreUtils = (() => {
26
+ let mod = null;
27
+ return () => {
28
+ if (!mod)
29
+ mod = createRequire(import.meta.url)("../../src/store/sqlite/utils.js");
30
+ return mod;
31
+ };
32
+ })();
33
+ // ---------------------------------------------------------------------------
34
+ // GET /api/maintenance and sub-paths
35
+ // ---------------------------------------------------------------------------
36
+ export function handleMaintenance(req, res, ctx) {
37
+ const url = req.url ?? "";
38
+ if (!url.startsWith("/api/maintenance"))
39
+ return false;
40
+ const method = (req.method ?? "GET").toUpperCase();
41
+ // ── GET /api/maintenance/schema-health ────────────────────────────
42
+ if (method === "GET" && url === "/api/maintenance/schema-health") {
43
+ sendSchemaHealth(res, ctx);
44
+ return true;
45
+ }
46
+ // ── POST /api/maintenance/action ──────────────────────────────────
47
+ if (method === "POST" && url === "/api/maintenance/action") {
48
+ handleMaintenanceAction(req, res, ctx);
49
+ return true;
50
+ }
51
+ // ── GET /api/maintenance ──────────────────────────────────────────
52
+ if (method === "GET" && url === "/api/maintenance") {
53
+ sendDbStats(res, ctx);
54
+ return true;
55
+ }
56
+ // ── Unknown sub-path ──────────────────────────────────────────────
57
+ sendJson(res, 404, { error: "not found" });
58
+ return true;
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // GET /api/maintenance — DB stats
62
+ // ---------------------------------------------------------------------------
63
+ function sendDbStats(res, ctx) {
64
+ try {
65
+ const { getDbStats } = lazyMaintenance();
66
+ const raw = getDbStats(ctx.stateDir);
67
+ const tables = Object.entries(raw.tableCounts).map(([table, rowCount]) => ({
68
+ table,
69
+ rowCount,
70
+ }));
71
+ sendJson(res, 200, {
72
+ type: "db-stats",
73
+ tables,
74
+ storage: {
75
+ files: {
76
+ dbBytes: raw.dbBytes,
77
+ walBytes: raw.walBytes,
78
+ shmBytes: raw.shmBytes,
79
+ },
80
+ pageSize: raw.pageSize,
81
+ pageCount: raw.pageCount,
82
+ freelistPages: raw.freelistPages,
83
+ },
84
+ });
85
+ }
86
+ catch (err) {
87
+ sendJson(res, 500, { error: err.message });
88
+ }
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // GET /api/maintenance/schema-health — schema version + integrity audit
92
+ // ---------------------------------------------------------------------------
93
+ function sendSchemaHealth(res, ctx) {
94
+ try {
95
+ const { integrityCheck } = lazyMaintenance();
96
+ const integrity = integrityCheck(ctx.stateDir);
97
+ const { openStore } = lazyStoreUtils();
98
+ const db = openStore(ctx.stateDir);
99
+ // SCHEMA_VERSION from meta table
100
+ const metaRow = db
101
+ .prepare("SELECT value FROM meta WHERE key = ?")
102
+ .get("schema_version");
103
+ const schemaVersion = metaRow ? Number(metaRow.value) : 0;
104
+ // PRAGMA foreign_key_check
105
+ const fkRows = db
106
+ .prepare("PRAGMA foreign_key_check")
107
+ .all();
108
+ const fkCheck = fkRows.map((r) => JSON.stringify(r));
109
+ // Per-column audit: every ensureColumn migration column declared in
110
+ // schema.ts, checked via PRAGMA table_info (read-only).
111
+ const expectedColumns = [
112
+ { table: "context_chunks", column: "original_token_estimate", decl: "INTEGER" },
113
+ { table: "raw_transcript", column: "content_ref", decl: "TEXT" },
114
+ { table: "memories", column: "category", decl: "TEXT" },
115
+ { table: "memories", column: "target", decl: "TEXT" },
116
+ { table: "memories", column: "last_referenced", decl: "INTEGER" },
117
+ { table: "memories", column: "source_turn", decl: "INTEGER" },
118
+ { table: "raptor_nodes", column: "built_at", decl: "INTEGER" },
119
+ { table: "raw_transcript", column: "turn_index", decl: "INTEGER" },
120
+ { table: "session_state", column: "conversation_id", decl: "TEXT" },
121
+ { table: "session_state", column: "last_turn_id", decl: "INTEGER" },
122
+ ];
123
+ const columns = expectedColumns.map((ec) => {
124
+ const cols = db
125
+ .prepare(`PRAGMA table_info(${ec.table})`)
126
+ .all();
127
+ const present = cols.some((c) => c.name === ec.column);
128
+ return {
129
+ table: ec.table,
130
+ column: ec.column,
131
+ present,
132
+ expectedDecl: ec.decl,
133
+ };
134
+ });
135
+ const healthy = integrity.length === 1 &&
136
+ integrity[0] === "ok" &&
137
+ columns.every((c) => c.present);
138
+ sendJson(res, 200, {
139
+ type: "schema-health",
140
+ schemaVersion,
141
+ integrity,
142
+ fkCheck,
143
+ columns,
144
+ healthy,
145
+ });
146
+ }
147
+ catch (err) {
148
+ sendJson(res, 500, { error: err.message });
149
+ }
150
+ }
151
+ // ---------------------------------------------------------------------------
152
+ // POST /api/maintenance/action — Trigger a maintenance action
153
+ // ---------------------------------------------------------------------------
154
+ function handleMaintenanceAction(req, res, ctx) {
155
+ let body = "";
156
+ req.setEncoding("utf8");
157
+ req.on("data", (chunk) => {
158
+ body += chunk;
159
+ if (body.length > 10_000)
160
+ req.destroy();
161
+ });
162
+ req.on("end", () => {
163
+ let payload;
164
+ try {
165
+ payload = JSON.parse(body);
166
+ if (!payload || !payload.action) {
167
+ sendJson(res, 400, { error: "missing 'action' field" });
168
+ return;
169
+ }
170
+ }
171
+ catch {
172
+ sendJson(res, 400, { error: "invalid JSON" });
173
+ return;
174
+ }
175
+ try {
176
+ const m = lazyMaintenance();
177
+ const u = lazyStoreUtils();
178
+ // All destructive actions: temp backup first
179
+ if (payload.action !== "integrity-check") {
180
+ // Non-blocking — backup failure is non-fatal
181
+ m.backupDb(ctx.stateDir);
182
+ }
183
+ switch (payload.action) {
184
+ case "vacuum": {
185
+ const r = m.vacuumDb(ctx.stateDir);
186
+ sendJson(res, 200, {
187
+ operation: "vacuum",
188
+ success: true,
189
+ affected: r.affected,
190
+ reclaimedBytes: r.reclaimedBytes,
191
+ summary: r.summary,
192
+ });
193
+ return;
194
+ }
195
+ case "checkpoint": {
196
+ const r = m.checkpointWal(ctx.stateDir);
197
+ sendJson(res, 200, {
198
+ operation: "checkpoint",
199
+ success: true,
200
+ affected: r.affected,
201
+ reclaimedBytes: r.reclaimedBytes,
202
+ summary: r.summary,
203
+ });
204
+ return;
205
+ }
206
+ case "reindex": {
207
+ const db = u.openStore(ctx.stateDir);
208
+ db.exec("REINDEX");
209
+ sendJson(res, 200, {
210
+ operation: "reindex",
211
+ success: true,
212
+ affected: 0,
213
+ reclaimedBytes: 0,
214
+ summary: "REINDEX completed",
215
+ });
216
+ return;
217
+ }
218
+ case "fts5-rebuild": {
219
+ const db = u.openStore(ctx.stateDir);
220
+ db.exec("INSERT INTO context_chunks_trgm(context_chunks_trgm) VALUES('rebuild')");
221
+ sendJson(res, 200, {
222
+ operation: "fts5-rebuild",
223
+ success: true,
224
+ affected: 0,
225
+ reclaimedBytes: 0,
226
+ summary: "FTS5 rebuild triggered",
227
+ });
228
+ return;
229
+ }
230
+ case "reconcile-dedup": {
231
+ const r = m.reconcileDedupMirror(ctx.stateDir);
232
+ const lines = [];
233
+ if (r.fixedRefCount)
234
+ lines.push(`${r.fixedRefCount} ref_counts corrected`);
235
+ if (r.orphansDeleted)
236
+ lines.push(`${r.orphansDeleted} orphans deleted`);
237
+ if (r.refsBackfilled)
238
+ lines.push(`${r.refsBackfilled} refs backfilled`);
239
+ sendJson(res, 200, {
240
+ operation: "reconcile-dedup",
241
+ success: true,
242
+ affected: r.fixedRefCount + r.orphansDeleted + r.refsBackfilled,
243
+ reclaimedBytes: 0,
244
+ summary: lines.length ? lines.join(", ") : "nothing to fix",
245
+ });
246
+ return;
247
+ }
248
+ case "prune": {
249
+ const days = Number(payload.daysOld) || 30;
250
+ const r = m.pruneOldRows(ctx.stateDir, days);
251
+ sendJson(res, 200, {
252
+ operation: "prune",
253
+ success: true,
254
+ affected: r.affected,
255
+ reclaimedBytes: r.reclaimedBytes,
256
+ summary: r.summary,
257
+ });
258
+ return;
259
+ }
260
+ case "integrity-check": {
261
+ const lines = m.integrityCheck(ctx.stateDir);
262
+ sendJson(res, 200, {
263
+ operation: "integrity-check",
264
+ success: lines.length === 1 && lines[0] === "ok",
265
+ affected: 0,
266
+ reclaimedBytes: 0,
267
+ summary: lines.join("; "),
268
+ detail: lines,
269
+ });
270
+ return;
271
+ }
272
+ default:
273
+ sendJson(res, 400, {
274
+ error: `unknown action: ${payload.action}`,
275
+ });
276
+ }
277
+ }
278
+ catch (err) {
279
+ sendJson(res, 500, { error: err.message });
280
+ }
281
+ });
282
+ }
283
+ // ---------------------------------------------------------------------------
284
+ // JSON helper
285
+ // ---------------------------------------------------------------------------
286
+ function sendJson(res, status, data) {
287
+ const body = JSON.stringify(data);
288
+ res.writeHead(status, {
289
+ "Content-Type": "application/json",
290
+ "Content-Length": Buffer.byteLength(body),
291
+ });
292
+ res.end(body);
293
+ }
@@ -10,3 +10,4 @@ export { handleGameState, handleGameScores, handlePerf, handleAchievements, } fr
10
10
  export { handleEvents, handleSessions } from "./routes-sessions.js";
11
11
  export { handleTopics } from "./routes-topics.js";
12
12
  export { handleTurns } from "./routes-turns.js";
13
+ export { handleMaintenance } from "./routes-maintenance.js";
@@ -12,7 +12,7 @@ import { join, dirname } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { createRequire } from "node:module";
14
14
  import { log, setLogPath, setDashboardServerVersion } from "./state.js";
15
- import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleStatic, } from "./routes.js";
15
+ import { buildRouteContext, handleIndex, handleRepoIndex, handleEvents, handleGameState, handleGameScores, handlePerf, handleAchievements, handleSessions, handleTopics, handleTurns, handleMaintenance, handleStatic, } from "./routes.js";
16
16
  export async function launchDashboardServer(stateDir) {
17
17
  // Our own package version — exposed at /api/version so the launcher can
18
18
  // detect a stale server (started by an older build) and replace it on
@@ -191,6 +191,8 @@ export async function launchDashboardServer(stateDir) {
191
191
  return;
192
192
  if (handleTurns(req, res, ctx))
193
193
  return;
194
+ if (handleMaintenance(req, res, ctx))
195
+ return;
194
196
  handleStatic(req, res, ctx);
195
197
  });
196
198
  // Bind base + range are env-configurable so tests can use a private,
@@ -4,8 +4,9 @@
4
4
  * All pi-agnostic, all parameterized (PREVENT-002), all local (PREVENT-PI-004).
5
5
  * Exposed via the /mega-db-* slash commands in extensions/mega-db-cmds.ts.
6
6
  */
7
- import { statSync } from "node:fs";
7
+ import { statSync, mkdirSync, existsSync } from "node:fs";
8
8
  import { join } from "node:path";
9
+ import { tmpdir } from "node:os";
9
10
  import { getStateDir } from "../../store.js";
10
11
  import { openStore, withTx } from "./utils.js";
11
12
  const DB_TABLE_NAMES = [
@@ -70,6 +71,24 @@ export function getDbStats(stateDir = getStateDir()) {
70
71
  walFrames,
71
72
  };
72
73
  }
74
+ /**
75
+ * Lightweight temp backup of the main sqlite.db via VACUUM INTO.
76
+ *
77
+ * Creates a one-off backup in the OS tmpdir. Returns the backup path.
78
+ * VACUUM INTO produces a clean, defragmented copy — also serves as a
79
+ * pre-op safeguard for any destructive maintenance action.
80
+ */
81
+ export function backupDb(stateDir = getStateDir()) {
82
+ const backupDir = join(tmpdir(), "pi-mega-compact-backups");
83
+ if (!existsSync(backupDir))
84
+ mkdirSync(backupDir, { recursive: true });
85
+ const ts = Date.now();
86
+ const backupPath = join(backupDir, `sqlite-${ts}.db`);
87
+ const db = openStore(stateDir);
88
+ // guardrails-disable-next-line PREVENT-002: path built from tmpdir + timestamp, not user input
89
+ db.exec(`VACUUM INTO '${backupPath.replace(/'/g, "''")}'`);
90
+ return backupPath;
91
+ }
73
92
  /**
74
93
  * Prune raw_transcript + checkpoint_epochs rows older than `daysOld`.
75
94
  * Uses `message_timestamp` (raw_transcript) and `created_at` (epochs), both
@@ -105,10 +105,6 @@ export function initSchema(db) {
105
105
  );
106
106
  CREATE INDEX IF NOT EXISTS idx_raptor_session ON raptor_nodes(session_id);
107
107
  CREATE INDEX IF NOT EXISTS idx_raptor_parent ON raptor_nodes(parent_id);
108
- -- S42D/QA perf: session-scoped built_at ordering so the per-search cache
109
- -- invalidation MAX(built_at) is index-satisfied.
110
- CREATE INDEX IF NOT EXISTS idx_raptor_session_built
111
- ON raptor_nodes(session_id, built_at DESC);
112
108
 
113
109
  -- S42D: structured RAPTOR build history. One row per tree build; the
114
110
  -- freshness check (buildHistory.ts) uses completed_at + leaf_count to skip
@@ -375,6 +371,11 @@ export function initSchema(db) {
375
371
  // S25: RAPTOR freshness-guard timestamp. Additive; old DBs have NULL → 0 →
376
372
  // treated as stale → flat fallback (safe).
377
373
  ensureColumn(db, "raptor_nodes", "built_at", "INTEGER");
374
+ // S42D/QA perf: session-scoped built_at ordering. Must follow ensureColumn
375
+ // above — old DBs already have raptor_nodes without built_at, so CREATE TABLE
376
+ // IF NOT EXISTS is a no-op that doesn't add the column, and moving this index
377
+ // inside the DDL block caused "no such column: built_at" on upgrade.
378
+ db.exec("CREATE INDEX IF NOT EXISTS idx_raptor_session_built ON raptor_nodes(session_id, built_at DESC)");
378
379
  // S43: turn_index on raw_transcript so a message points directly at its
379
380
  // conversation turn (otherwise it must be inferred from seq ordering). NULL
380
381
  // for legacy rows — turns written before S43 have no turn link.
@@ -390,7 +391,9 @@ export function initSchema(db) {
390
391
  for (const d of ACHIEVEMENT_DEFS) {
391
392
  seedAch.run(d.id, d.title, d.description, d.hidden ? 1 : 0, d.icon);
392
393
  }
393
- const v = db.prepare("SELECT value FROM meta WHERE key='schema_version'").get();
394
+ const v = db
395
+ .prepare("SELECT value FROM meta WHERE key='schema_version'")
396
+ .get();
394
397
  if (!v) {
395
398
  db.prepare("INSERT INTO meta(key, value) VALUES(?, ?)").run("schema_version", String(SCHEMA_VERSION));
396
399
  }
@@ -1,2 +1,2 @@
1
- import{r as o,j as n}from"./index-CqLN7STl.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-DZp_xQOW.js.map
1
+ import{r as o,j as n}from"./index-BcoSz-L4.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-Cml6YC8w.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementTiles-DZp_xQOW.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-Cml6YC8w.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,h as m,j as s}from"./index-CqLN7STl.js";import{A as v}from"./AchievementTiles-DZp_xQOW.js";function x(){const[r,o]=t.useState([]),[l,h]=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{h(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const u=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(u)}},[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-BzFHpAHM.js.map
1
+ import{r as t,h as m,j as s}from"./index-BcoSz-L4.js";import{A as v}from"./AchievementTiles-Cml6YC8w.js";function x(){const[r,o]=t.useState([]),[l,h]=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{h(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const u=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(u)}},[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-B8NqDt7I.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AchievementsTab-BzFHpAHM.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-B8NqDt7I.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,c as d,j as s}from"./index-CqLN7STl.js";import{C as l,T as h}from"./TimeSavedCard-B8AIjNTY.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-DEQbTq2T.js.map
1
+ import{u as n,r,c as d,j as s}from"./index-BcoSz-L4.js";import{C as l,T as h}from"./TimeSavedCard-0TqsrY6y.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-BCLzVL4S.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CacheTab-DEQbTq2T.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-BCLzVL4S.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,2 +1,2 @@
1
- import{r as i,p as N,j as e,c as w,d as y}from"./index-CqLN7STl.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-DQ4FG33J.js.map
1
+ import{r as i,p as N,j as e,c as w,d as y}from"./index-BcoSz-L4.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-CBYvp2mV.js.map