pi-mega-compact 0.14.1 → 0.15.1

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 (82) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
  2. package/dist/extensions/dashboard-server/resolve-snapshot.js +20 -8
  3. package/dist/extensions/dashboard-server/routes-game.js +60 -0
  4. package/dist/extensions/dashboard-server/routes.js +1 -1
  5. package/dist/extensions/dashboard-server/server.js +3 -1
  6. package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js +2 -0
  7. package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js.map +1 -0
  8. package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js +2 -0
  9. package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js.map +1 -0
  10. package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js → CacheTab-EhqkEglo.js} +2 -2
  11. package/extensions/dashboard-client/dist/assets/{CacheTab-VNFZSdRr.js.map → CacheTab-EhqkEglo.js.map} +1 -1
  12. package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js → EventsTab-BUACwWXf.js} +2 -2
  13. package/extensions/dashboard-client/dist/assets/{EventsTab-Dr7RDBy2.js.map → EventsTab-BUACwWXf.js.map} +1 -1
  14. package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js → HealthTab-CDEYVRBb.js} +2 -2
  15. package/extensions/dashboard-client/dist/assets/{HealthTab-B8VZ8gbb.js.map → HealthTab-CDEYVRBb.js.map} +1 -1
  16. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js → MaintenanceTab-DtanIeCQ.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DMtrXRcn.js.map → MaintenanceTab-DtanIeCQ.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js → MemoryMapTab-BGEhL7OG.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-PFrMdLOm.js.map → MemoryMapTab-BGEhL7OG.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js +2 -0
  21. package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js.map +1 -0
  22. package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js +16 -0
  23. package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js.map +1 -0
  24. package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js → ReposTab-Bg72Ozdc.js} +2 -2
  25. package/extensions/dashboard-client/dist/assets/{ReposTab-D6pEUtEJ.js.map → ReposTab-Bg72Ozdc.js.map} +1 -1
  26. package/extensions/dashboard-client/dist/assets/SessionsTab-BcTK9Lg6.js +2 -0
  27. package/extensions/dashboard-client/dist/assets/{SessionsTab-CcFzDJ7W.js.map → SessionsTab-BcTK9Lg6.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js → SetupTab-Bt-Ru1xp.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{SetupTab-YCt5NGB5.js.map → SetupTab-Bt-Ru1xp.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js → TimeSavedCard-ic_LjJ79.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BsLNhOOj.js.map → TimeSavedCard-ic_LjJ79.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js → TurnsTab-D9AZTMgG.js} +4 -4
  33. package/extensions/dashboard-client/dist/assets/{TurnsTab-C1jpVZrH.js.map → TurnsTab-D9AZTMgG.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/WikiTab-hRcJPi_m.js +2 -0
  35. package/extensions/dashboard-client/dist/assets/{WikiTab-RHoVt95g.js.map → WikiTab-hRcJPi_m.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js → button-BdAtIbVu.js} +2 -2
  37. package/extensions/dashboard-client/dist/assets/{button-DSZI4aZ5.js.map → button-BdAtIbVu.js.map} +1 -1
  38. package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js → card-BFYc_QrK.js} +2 -2
  39. package/extensions/dashboard-client/dist/assets/{card-CsZzIyPW.js.map → card-BFYc_QrK.js.map} +1 -1
  40. package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js +70 -0
  41. package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js.map +1 -0
  42. package/extensions/dashboard-client/dist/assets/{index-B1_J_Y0b.js → index-pmtEr3sb.js} +8 -8
  43. package/extensions/dashboard-client/dist/assets/index-pmtEr3sb.js.map +1 -0
  44. package/extensions/dashboard-client/dist/assets/index-za_gmEmQ.css +1 -0
  45. package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js → switch-DEyGlFwJ.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{switch-DOIA1Nxu.js.map → switch-DEyGlFwJ.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js → toggle-pDMBQYQj.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{toggle-CyS6PHCO.js.map → toggle-pDMBQYQj.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js → useSSE-BKaC4dao.js} +2 -2
  50. package/extensions/dashboard-client/dist/assets/{useSSE-CY7UIUzm.js.map → useSSE-BKaC4dao.js.map} +1 -1
  51. package/extensions/dashboard-client/dist/index.html +2 -2
  52. package/extensions/dashboard-client/package-lock.json +56 -39
  53. package/extensions/dashboard-client/package.json +3 -0
  54. package/extensions/dashboard-client/src/api/client.ts +11 -0
  55. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +14 -8
  56. package/extensions/dashboard-client/src/components/WidgetDetailModal.tsx +69 -0
  57. package/extensions/dashboard-client/src/components/charts/PerfBarChart.tsx +89 -0
  58. package/extensions/dashboard-client/src/components/charts/PerfLineChart.tsx +108 -0
  59. package/extensions/dashboard-client/src/components/ui/SortableCard.tsx +75 -0
  60. package/extensions/dashboard-client/src/hooks/useCardPositions.ts +74 -0
  61. package/extensions/dashboard-client/src/styles/session-gauges.css +28 -15
  62. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +310 -79
  63. package/extensions/dashboard-client/src/types/card.ts +40 -0
  64. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +10 -0
  65. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +36 -0
  66. package/extensions/dashboard-server/api-contracts/endpoints.ts +3 -0
  67. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  68. package/extensions/dashboard-server/resolve-snapshot.ts +22 -10
  69. package/extensions/dashboard-server/routes-game.ts +72 -0
  70. package/extensions/dashboard-server/routes.ts +1 -0
  71. package/extensions/dashboard-server/server.ts +2 -0
  72. package/package.json +1 -1
  73. package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js +0 -70
  74. package/extensions/dashboard-client/dist/assets/AreaChart-BUUketyS.js.map +0 -1
  75. package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js +0 -2
  76. package/extensions/dashboard-client/dist/assets/MetricsTab-Dd1kXuvg.js.map +0 -1
  77. package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js +0 -2
  78. package/extensions/dashboard-client/dist/assets/OverviewTab-DOpObVA9.js.map +0 -1
  79. package/extensions/dashboard-client/dist/assets/SessionsTab-CcFzDJ7W.js +0 -2
  80. package/extensions/dashboard-client/dist/assets/WikiTab-RHoVt95g.js +0 -2
  81. package/extensions/dashboard-client/dist/assets/index-B1_J_Y0b.js.map +0 -1
  82. package/extensions/dashboard-client/dist/assets/index-C5TGTOd6.css +0 -1
@@ -348,3 +348,39 @@ export interface PerfResponse {
348
348
  /** Diagnostic counters from the live snapshot, or null if unavailable. */
349
349
  readonly diag: PerfDiag | null;
350
350
  }
351
+
352
+ /**
353
+ * Query parameters for GET /api/perf/samples. `kind` selects which perf metric
354
+ * to return raw samples for; `minutes` controls the rolling window size.
355
+ */
356
+ export interface PerfSamplesQuery {
357
+ /** Perf metric kind (turn_latency_ms, tps, disk_write_ms, cache_hit_pct, ...). Required. */
358
+ readonly kind: string;
359
+ /** Rolling window size in minutes (default: 60). Optional. */
360
+ readonly minutes?: number;
361
+ }
362
+
363
+ /** A single raw perf sample for time-series charting. */
364
+ export interface PerfSamplePoint {
365
+ /** Epoch ms timestamp of the sample. */
366
+ readonly ts: number;
367
+ /** Sample value for the requested kind. */
368
+ readonly value: number;
369
+ /** Optional stringified metadata associated with the sample. */
370
+ readonly meta?: string;
371
+ }
372
+
373
+ /**
374
+ * Response for GET /api/perf/samples. Raw perf samples for a single kind,
375
+ * ascending by ts, for chart drill-down in the Perf tab.
376
+ */
377
+ export interface PerfSamplesResponse {
378
+ /** Array of raw samples ascending by ts. */
379
+ readonly samples: PerfSamplePoint[];
380
+ /** The requested perf kind. */
381
+ readonly kind: string;
382
+ /** The rolling window size in minutes used. */
383
+ readonly minutes: number;
384
+ /** Epoch ms timestamp when the response was generated. */
385
+ readonly updatedAt: number;
386
+ }
@@ -31,6 +31,9 @@ export type {
31
31
  PerfDiag,
32
32
  PerfQuery,
33
33
  PerfResponse,
34
+ PerfSamplesQuery,
35
+ PerfSamplePoint,
36
+ PerfSamplesResponse,
34
37
  GameScoreRow,
35
38
  GameScoresQuery,
36
39
  AchievementRow,
@@ -140,6 +140,9 @@ export type {
140
140
  PerfDiag,
141
141
  PerfQuery,
142
142
  PerfResponse,
143
+ PerfSamplesQuery,
144
+ PerfSamplePoint,
145
+ PerfSamplesResponse,
143
146
  GameScoreRow,
144
147
  GameScoresQuery,
145
148
  AchievementRow,
@@ -5,11 +5,17 @@
5
5
  * active pi session may be running in a different repo. This module queries
6
6
  * the repo registry to find the most recently active repo's dashboard.json,
7
7
  * so the Overview always shows live data from the repo pi is actually running
8
- * in — not the stale launcher dir.
8
+ * in.
9
+ *
10
+ * The launch stateDir is a candidate like any other (NOT excluded): when
11
+ * `/dashboard` was run in the repo you are still coding in, the launch dir
12
+ * IS the most-recently-active repo and must win. Excluding it would serve a
13
+ * stale other-repo snapshot, surfacing a phantom 0% "this session" gauge
14
+ * plus a duplicate live-session gauge.
9
15
  *
10
16
  * Resolution order:
11
- * 1. Most recently seen repo (by last_seen in repo_registry) whose
12
- * dashboard.json exists and has a non-null updatedAt
17
+ * 1. Most recently seen repo (by last_seen in repo_registry, launch dir
18
+ * included) whose dashboard.json exists and has a non-null updatedAt
13
19
  * 2. The static launch stateDir's dashboard.json (original behavior)
14
20
  */
15
21
 
@@ -35,23 +41,30 @@ interface RepoRegistryRow {
35
41
 
36
42
  /**
37
43
  * Find the most recently active repo's stateDir by scanning the repo registry
38
- * for repos whose dashboard.json exists and was written recently. Returns null
39
- * if no suitable repo is found.
44
+ * for the repo with the highest last_seen whose dashboard.json exists and has
45
+ * a non-null updatedAt. Returns null if no suitable repo is found.
46
+ *
47
+ * The launch stateDir is NOT excluded: if it is itself the most-recently-active
48
+ * repo (the common case — `/dashboard` was run in the repo you are still
49
+ * coding in), its live dashboard.json must be served. Excluding it would cause
50
+ * a stale *other*-repo snapshot to win, producing a phantom 0% "this session"
51
+ * gauge + a duplicate live-session gauge.
40
52
  */
41
- function mostRecentRepoStateDir(staticStateDir: string): string | null {
53
+ function mostRecentRepoStateDir(_staticStateDir: string): string | null {
42
54
  const indexPath = join(getIndexDir(), "index.sqlite");
43
55
  if (!existsSync(indexPath)) return null;
44
56
  let db;
45
57
  try {
46
- const { DatabaseSync } = require("node:sqlite") as typeof import("node:sqlite");
58
+ const { DatabaseSync } =
59
+ require("node:sqlite") as typeof import("node:sqlite");
47
60
  db = new DatabaseSync(indexPath, { readOnly: true });
48
61
  const rows = db
49
62
  .prepare(
50
63
  `SELECT state_dir, last_seen FROM repo_registry
51
- WHERE state_dir IS NOT NULL AND state_dir != ?
64
+ WHERE state_dir IS NOT NULL AND last_seen IS NOT NULL
52
65
  ORDER BY last_seen DESC`,
53
66
  )
54
- .all(staticStateDir) as unknown as RepoRegistryRow[];
67
+ .all() as unknown as RepoRegistryRow[];
55
68
  for (const r of rows) {
56
69
  const dashPath = join(r.state_dir, "dashboard.json");
57
70
  if (!existsSync(dashPath)) continue;
@@ -95,4 +108,3 @@ export function resolveSnapshot(
95
108
  stateDir: staticStateDir,
96
109
  };
97
110
  }
98
-
@@ -8,6 +8,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
8
8
 
9
9
  import type { RouteContext } from "./routes-core.js";
10
10
  import type { GameMetric } from "../../src/game/scoring.js";
11
+ import type { PerfKind } from "../../src/store/sqlite/perf-samples.js";
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // handleGameState — "/api/game-state" (GET + PUT)
@@ -351,6 +352,77 @@ export function handlePerf(
351
352
  return true;
352
353
  }
353
354
 
355
+ // ---------------------------------------------------------------------------
356
+ // handlePerfSamples — "/api/perf/samples"
357
+ // ---------------------------------------------------------------------------
358
+
359
+ export function handlePerfSamples(
360
+ req: IncomingMessage,
361
+ res: ServerResponse,
362
+ ctx: RouteContext,
363
+ ): boolean {
364
+ if (!req.url?.startsWith("/api/perf/samples")) return false;
365
+
366
+ const { stateDir } = ctx;
367
+
368
+ // /api/perf/samples — dashboard Perf tab chart drill-down. GET returns raw
369
+ // perf_samples for a single kind (?kind=<k>&minutes=<n>) ascending by ts so
370
+ // the client can render a time-series for any perf metric (turn_latency_ms,
371
+ // tps, disk_write_ms, cache_hit_pct, db_recompute_ms, etc.). `kind` is
372
+ // required and validated against the PERF_KINDS allow-list (400 on unknown);
373
+ // `minutes` defaults to 60 and is capped at 1440 (24h). Reads perf_samples
374
+ // via the same require()'d sqlite helper as /api/perf — no new SQLite code.
375
+ // Non-GET -> 405. PREVENT-PI-004: loopback.
376
+ const psReq = createRequire(import.meta.url);
377
+ const { readPerfSamples, PERF_KINDS } = psReq(
378
+ "../../src/store/sqlite.js",
379
+ ) as typeof import("../../src/store/sqlite.js");
380
+ if (req.method !== "GET") {
381
+ res.writeHead(405, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
382
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
383
+ return true;
384
+ }
385
+ try {
386
+ const url = new URL(req.url, "http://x"); // guardrails-allow PREVENT-PI-004: localhost dashboard URL base (loopback-only)
387
+ const kindParam = url.searchParams.get("kind") ?? "";
388
+ if (!(PERF_KINDS as readonly string[]).includes(kindParam)) {
389
+ res.writeHead(400, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
390
+ res.end(JSON.stringify({ error: "unknown_kind", kind: kindParam }));
391
+ return true;
392
+ }
393
+ let minutes = Number(url.searchParams.get("minutes") ?? "60");
394
+ if (!Number.isFinite(minutes) || minutes <= 0) minutes = 60;
395
+ minutes = Math.min(minutes, 1440); // cap at 24h
396
+ const sinceTs = Date.now() - minutes * 60_000;
397
+ const kind = kindParam as PerfKind; // validated against PERF_KINDS above
398
+ const rows = readPerfSamples(stateDir, sinceTs, kind); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
399
+ const samples = rows.map((r) => ({
400
+ ts: r.ts,
401
+ value: r.value,
402
+ ...((r.meta != null && typeof r.meta === "object"
403
+ ? { meta: JSON.stringify(r.meta) }
404
+ : r.meta != null
405
+ ? { meta: String(r.meta) }
406
+ : {}) as { meta?: string }),
407
+ }));
408
+ res.writeHead(200, { "Content-Type": "application/json" });
409
+ res.end(
410
+ JSON.stringify({
411
+ samples,
412
+ kind: kindParam,
413
+ minutes,
414
+ updatedAt: Date.now(),
415
+ }),
416
+ );
417
+ } catch (e) {
418
+ res.writeHead(500, { "Content-Type": "application/json" });
419
+ res.end(
420
+ JSON.stringify({ error: "perf_samples_unavailable", detail: String(e) }),
421
+ );
422
+ }
423
+ return true;
424
+ }
425
+
354
426
  // ---------------------------------------------------------------------------
355
427
  // handleAchievements — "/api/achievements"
356
428
  // ---------------------------------------------------------------------------
@@ -13,6 +13,7 @@ export {
13
13
  handleGameState,
14
14
  handleGameScores,
15
15
  handlePerf,
16
+ handlePerfSamples,
16
17
  handleAchievements,
17
18
  } from "./routes-game.js";
18
19
  export { handleEvents, handleSessions } from "./routes-sessions.js";
@@ -33,6 +33,7 @@ import {
33
33
  handleGameState,
34
34
  handleGameScores,
35
35
  handlePerf,
36
+ handlePerfSamples,
36
37
  handleAchievements,
37
38
  handleSessions,
38
39
  handleTopics,
@@ -247,6 +248,7 @@ export async function launchDashboardServer(
247
248
  if (handleEvents(req, res, ctx)) return;
248
249
  if (handleGameState(req, res, ctx)) return;
249
250
  if (handleGameScores(req, res, ctx)) return;
251
+ if (handlePerfSamples(req, res, ctx)) return;
250
252
  if (handlePerf(req, res, ctx)) return;
251
253
  if (handleAchievements(req, res, ctx)) return;
252
254
  if (handleSessions(req, res, ctx)) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.14.1",
3
+ "version": "0.15.1",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",