pi-mega-compact 0.12.7 → 0.13.0

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 (51) hide show
  1. package/dist/extensions/dashboard-server/routes-setup.js +20 -1
  2. package/dist/extensions/mega-runtime/env-loader.js +6 -1
  3. package/dist/src/httpEmbedder.js +24 -11
  4. package/extensions/dashboard-client/dist/assets/{CacheTab-Bjge1v_l.js → CacheTab-BZNnKhh5.js} +2 -2
  5. package/extensions/dashboard-client/dist/assets/{CacheTab-Bjge1v_l.js.map → CacheTab-BZNnKhh5.js.map} +1 -1
  6. package/extensions/dashboard-client/dist/assets/{ConfigTab-BlbRWORC.js → ConfigTab-DG0WkuuG.js} +2 -2
  7. package/extensions/dashboard-client/dist/assets/{ConfigTab-BlbRWORC.js.map → ConfigTab-DG0WkuuG.js.map} +1 -1
  8. package/extensions/dashboard-client/dist/assets/{EventsTab-BrLTX5EP.js → EventsTab-CzF5L42U.js} +2 -2
  9. package/extensions/dashboard-client/dist/assets/{EventsTab-BrLTX5EP.js.map → EventsTab-CzF5L42U.js.map} +1 -1
  10. package/extensions/dashboard-client/dist/assets/{HealthTab-0LsKCRFE.js → HealthTab-BLpidMrX.js} +2 -2
  11. package/extensions/dashboard-client/dist/assets/{HealthTab-0LsKCRFE.js.map → HealthTab-BLpidMrX.js.map} +1 -1
  12. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C4L2Ohea.js → MaintenanceTab-BQF7W7xX.js} +2 -2
  13. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-C4L2Ohea.js.map → MaintenanceTab-BQF7W7xX.js.map} +1 -1
  14. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DMMQBq5X.js → MemoryMapTab-CPtHLv0A.js} +2 -2
  15. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-DMMQBq5X.js.map → MemoryMapTab-CPtHLv0A.js.map} +1 -1
  16. package/extensions/dashboard-client/dist/assets/{MetricsTab-BscQn0ej.js → MetricsTab-B0PCysqs.js} +2 -2
  17. package/extensions/dashboard-client/dist/assets/{MetricsTab-BscQn0ej.js.map → MetricsTab-B0PCysqs.js.map} +1 -1
  18. package/extensions/dashboard-client/dist/assets/{OverviewTab-D3ryhlRc.js → OverviewTab-DsOhqFu4.js} +2 -2
  19. package/extensions/dashboard-client/dist/assets/{OverviewTab-D3ryhlRc.js.map → OverviewTab-DsOhqFu4.js.map} +1 -1
  20. package/extensions/dashboard-client/dist/assets/{ReposTab-Ds9VkpDu.js → ReposTab-B3YKYuOf.js} +2 -2
  21. package/extensions/dashboard-client/dist/assets/{ReposTab-Ds9VkpDu.js.map → ReposTab-B3YKYuOf.js.map} +1 -1
  22. package/extensions/dashboard-client/dist/assets/{SessionsTab-D4Ez9XCI.js → SessionsTab-B96FWcrC.js} +2 -2
  23. package/extensions/dashboard-client/dist/assets/{SessionsTab-D4Ez9XCI.js.map → SessionsTab-B96FWcrC.js.map} +1 -1
  24. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js +2 -0
  25. package/extensions/dashboard-client/dist/assets/SetupTab-hbzQ_NZv.js.map +1 -0
  26. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D6S_QMPY.js → TimeSavedCard-BfkUVTvA.js} +2 -2
  27. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-D6S_QMPY.js.map → TimeSavedCard-BfkUVTvA.js.map} +1 -1
  28. package/extensions/dashboard-client/dist/assets/{TopicsTab-DYMNWd0N.js → TopicsTab-DLRsZmXJ.js} +2 -2
  29. package/extensions/dashboard-client/dist/assets/{TopicsTab-DYMNWd0N.js.map → TopicsTab-DLRsZmXJ.js.map} +1 -1
  30. package/extensions/dashboard-client/dist/assets/{TurnsTab-DpQgbzKj.js → TurnsTab-DKGU2Bf6.js} +2 -2
  31. package/extensions/dashboard-client/dist/assets/{TurnsTab-DpQgbzKj.js.map → TurnsTab-DKGU2Bf6.js.map} +1 -1
  32. package/extensions/dashboard-client/dist/assets/{index-m3rSvxMW.js → index-D-lPLRWB.js} +11 -11
  33. package/extensions/dashboard-client/dist/assets/{index-m3rSvxMW.js.map → index-D-lPLRWB.js.map} +1 -1
  34. package/extensions/dashboard-client/dist/assets/{useSSE-JtuzXAZy.js → useSSE-ClbbcgIp.js} +2 -2
  35. package/extensions/dashboard-client/dist/assets/{useSSE-JtuzXAZy.js.map → useSSE-ClbbcgIp.js.map} +1 -1
  36. package/extensions/dashboard-client/dist/index.html +1 -1
  37. package/extensions/dashboard-client/src/App.tsx +0 -8
  38. package/extensions/dashboard-client/src/tabs/SetupTab.tsx +118 -3
  39. package/extensions/dashboard-server/api-contracts/setup.ts +5 -3
  40. package/extensions/dashboard-server/routes-setup.ts +19 -2
  41. package/extensions/mega-runtime/env-loader.ts +6 -1
  42. package/package.json +3 -1
  43. package/src/httpEmbedder.ts +25 -11
  44. package/extensions/dashboard-client/dist/assets/AchievementTiles-B4KbY2gd.js +0 -2
  45. package/extensions/dashboard-client/dist/assets/AchievementTiles-B4KbY2gd.js.map +0 -1
  46. package/extensions/dashboard-client/dist/assets/AchievementsTab-CT2H3e9d.js +0 -2
  47. package/extensions/dashboard-client/dist/assets/AchievementsTab-CT2H3e9d.js.map +0 -1
  48. package/extensions/dashboard-client/dist/assets/GameTab-CYWnWrwC.js +0 -2
  49. package/extensions/dashboard-client/dist/assets/GameTab-CYWnWrwC.js.map +0 -1
  50. package/extensions/dashboard-client/dist/assets/SetupTab-DwXPFzgS.js +0 -2
  51. package/extensions/dashboard-client/dist/assets/SetupTab-DwXPFzgS.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{r as e}from"./index-m3rSvxMW.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
2
- //# sourceMappingURL=useSSE-JtuzXAZy.js.map
1
+ import{r as e}from"./index-D-lPLRWB.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
2
+ //# sourceMappingURL=useSSE-ClbbcgIp.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useSSE-JtuzXAZy.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
1
+ {"version":3,"file":"useSSE-ClbbcgIp.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>mega-compact dashboard</title>
7
- <script type="module" crossorigin src="./assets/index-m3rSvxMW.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-D-lPLRWB.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
9
9
  </head>
10
10
  <body>
@@ -21,8 +21,6 @@ const ConfigTab = React.lazy(() => import("./tabs/ConfigTab"));
21
21
  const SetupTab = React.lazy(() => import("./tabs/SetupTab"));
22
22
  const MetricsTab = React.lazy(() => import("./tabs/MetricsTab"));
23
23
  const CacheTab = React.lazy(() => import("./tabs/CacheTab"));
24
- const GameTab = React.lazy(() => import("./tabs/GameTab"));
25
- const AchievementsTab = React.lazy(() => import("./tabs/AchievementsTab"));
26
24
  const SessionsTab = React.lazy(() => import("./tabs/SessionsTab"));
27
25
  const TopicsTab = React.lazy(() => import("./tabs/TopicsTab"));
28
26
  const TurnsTab = React.lazy(() => import("./tabs/TurnsTab"));
@@ -38,8 +36,6 @@ export type TabId =
38
36
  | "setup"
39
37
  | "metrics"
40
38
  | "cache"
41
- | "game"
42
- | "achievements"
43
39
  | "sessions"
44
40
  | "topics"
45
41
  | "turns"
@@ -63,8 +59,6 @@ const ADVANCED_TABS: TabDef[] = [
63
59
  { id: "config", label: "Config" },
64
60
  { id: "setup", label: "Setup" },
65
61
  { id: "metrics", label: "Metrics" },
66
- { id: "game", label: "Game" },
67
- { id: "achievements", label: "Achievements" },
68
62
  { id: "topics", label: "Topics" },
69
63
  { id: "maintenance", label: "Maintenance" },
70
64
  { id: "memory-map", label: "Memory Map" },
@@ -121,8 +115,6 @@ export default function App(): React.ReactElement {
121
115
  {activeTab === "setup" && <SetupTab />}
122
116
  {activeTab === "metrics" && <MetricsTab />}
123
117
  {activeTab === "cache" && <CacheTab />}
124
- {activeTab === "game" && <GameTab />}
125
- {activeTab === "achievements" && <AchievementsTab />}
126
118
  {activeTab === "sessions" && <SessionsTab />}
127
119
  {activeTab === "topics" && <TopicsTab />}
128
120
  {activeTab === "turns" && <TurnsTab />}
@@ -12,6 +12,8 @@ import type React from "react";
12
12
  import { useState, useEffect, useCallback } from "react";
13
13
  import type { SetupStatusResponse, SetupDetectResponse, SetupConfigureResponse } from "@contracts";
14
14
  import { fetchSetupStatus, fetchSetupDetect, configureEmbedder } from "../api/client";
15
+ import GameTab from "./GameTab";
16
+ import AchievementsTab from "./AchievementsTab";
15
17
 
16
18
  // ---------------------------------------------------------------------------
17
19
  // Style constants
@@ -84,7 +86,7 @@ const styles: Record<string, React.CSSProperties> = {
84
86
  // SetupTab component
85
87
  // ---------------------------------------------------------------------------
86
88
 
87
- export default function SetupTab(): React.ReactElement {
89
+ export function EmbedderSetup(): React.ReactElement {
88
90
  const [status, setStatus] = useState<SetupStatusResponse | null>(null);
89
91
  const [detect, setDetect] = useState<SetupDetectResponse | null>(null);
90
92
  const [statusError, setStatusError] = useState<string | null>(null);
@@ -93,6 +95,7 @@ export default function SetupTab(): React.ReactElement {
93
95
  const [configuring, setConfiguring] = useState<string | null>(null);
94
96
  const [configResult, setConfigResult] = useState<SetupConfigureResponse | null>(null);
95
97
  const [configError, setConfigError] = useState<string | null>(null);
98
+ const [customUrl, setCustomUrl] = useState("");
96
99
 
97
100
  const loadStatus = useCallback(() => {
98
101
  fetchSetupStatus()
@@ -102,11 +105,11 @@ export default function SetupTab(): React.ReactElement {
102
105
  );
103
106
  }, []);
104
107
 
105
- const applyEmbedder = useCallback((embedder: "ollama" | "llama" | "trigram") => {
108
+ const applyEmbedder = useCallback((embedder: "ollama" | "llama" | "trigram" | "custom", url?: string) => {
106
109
  setConfiguring(embedder);
107
110
  setConfigError(null);
108
111
  setConfigResult(null);
109
- configureEmbedder({ embedder })
112
+ configureEmbedder({ embedder, url })
110
113
  .then((r) => {
111
114
  setConfigResult(r);
112
115
  setConfiguring(null);
@@ -403,6 +406,49 @@ export default function SetupTab(): React.ReactElement {
403
406
  )}
404
407
  </div>
405
408
 
409
+ {/* Custom (remote) embedder — third-party / hosted endpoint */}
410
+ <div style={styles.section}>
411
+ <h3 style={styles.sectionTitle}>Custom Endpoint (remote / third-party)</h3>
412
+ <p style={{ fontSize: "0.85rem", color: "#a0a0c0", marginTop: 0 }}>
413
+ Point at any OpenAI-compatible embeddings API or hosted endpoint.
414
+ This opts in to a non-loopback connection (sets{" "}
415
+ <code>MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1</code>) — your prompts'
416
+ embedding requests will be sent to the URL you enter. Default is
417
+ loopback-only.
418
+ </p>
419
+ <div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap", alignItems: "center" }}>
420
+ <input
421
+ type="url"
422
+ placeholder="https://api.example.com/v1/embeddings"
423
+ value={customUrl}
424
+ onChange={(e) => setCustomUrl(e.target.value)}
425
+ style={{
426
+ flex: "1 1 300px",
427
+ background: "#12122a",
428
+ color: "#e0e0e0",
429
+ border: "1px solid #2a2a4e",
430
+ borderRadius: "6px",
431
+ padding: "0.4rem 0.6rem",
432
+ fontSize: "0.85rem",
433
+ fontFamily: "monospace",
434
+ }}
435
+ />
436
+ <button
437
+ style={{ ...styles.button, opacity: customUrl ? 1 : 0.4 }}
438
+ onClick={() => applyEmbedder("custom", customUrl)}
439
+ disabled={!customUrl || configuring !== null}
440
+ >
441
+ {configuring === "custom" ? "Writing..." : "Use Custom URL"}
442
+ </button>
443
+ </div>
444
+ <div style={styles.warning}>
445
+ <strong>Heads up:</strong> a remote endpoint receives the text you embed
446
+ (it does NOT receive your full conversation — only the strings passed to
447
+ <code> embed()</code> for recall/dedup). Verify the endpoint's privacy
448
+ policy before use.
449
+ </div>
450
+ </div>
451
+
406
452
  {/* Help Section */}
407
453
  <div style={styles.section}>
408
454
  <h3 style={styles.sectionTitle}>How to Upgrade</h3>
@@ -435,3 +481,72 @@ export default function SetupTab(): React.ReactElement {
435
481
  </div>
436
482
  );
437
483
  }
484
+
485
+
486
+ // ---------------------------------------------------------------------------
487
+ // SetupTab shell — sub-tabs for Embedder / Game Mode / Achievements.
488
+ // Game + Achievements moved here from the top-level tab bar (audit #9: 15 tabs
489
+ // was too many; the novelty surfaces crowd the bar for the 95% who don't use
490
+ // them). The sub-tab nav reuses the inline-style approach of the embedder
491
+ // section for visual consistency.
492
+ // ---------------------------------------------------------------------------
493
+
494
+ type SetupSubTab = "embedder" | "game" | "achievements";
495
+
496
+ const subTabNavStyle: React.CSSProperties = {
497
+ display: "flex",
498
+ gap: "0.5rem",
499
+ marginBottom: "1rem",
500
+ borderBottom: "1px solid #2a2a4e",
501
+ paddingBottom: "0.5rem",
502
+ };
503
+
504
+ function subTabBtnStyle(active: boolean): React.CSSProperties {
505
+ return {
506
+ background: active ? "#3a5a9f" : "transparent",
507
+ color: active ? "#fff" : "#a0a0c0",
508
+ border: "1px solid",
509
+ borderColor: active ? "#3a5a9f" : "#2a2a4e",
510
+ borderRadius: "6px",
511
+ padding: "0.4rem 0.8rem",
512
+ cursor: "pointer",
513
+ fontSize: "0.9rem",
514
+ fontWeight: active ? 600 : 400,
515
+ };
516
+ }
517
+
518
+ export default function SetupTab(): React.ReactElement {
519
+ const [subTab, setSubTab] = useState<SetupSubTab>("embedder");
520
+
521
+ return (
522
+ <div style={styles.container}>
523
+ <nav style={subTabNavStyle} aria-label="Setup sections">
524
+ <button
525
+ type="button"
526
+ style={subTabBtnStyle(subTab === "embedder")}
527
+ onClick={() => setSubTab("embedder")}
528
+ >
529
+ Embedder
530
+ </button>
531
+ <button
532
+ type="button"
533
+ style={subTabBtnStyle(subTab === "game")}
534
+ onClick={() => setSubTab("game")}
535
+ >
536
+ Game Mode
537
+ </button>
538
+ <button
539
+ type="button"
540
+ style={subTabBtnStyle(subTab === "achievements")}
541
+ onClick={() => setSubTab("achievements")}
542
+ >
543
+ Achievements
544
+ </button>
545
+ </nav>
546
+
547
+ {subTab === "embedder" && <EmbedderSetup />}
548
+ {subTab === "game" && <GameTab />}
549
+ {subTab === "achievements" && <AchievementsTab />}
550
+ </div>
551
+ );
552
+ }
@@ -65,9 +65,11 @@ export interface SetupDetectResponse {
65
65
 
66
66
  /** Request body for POST /api/setup-configure. */
67
67
  export interface SetupConfigureRequest {
68
- /** Which embedder to configure. "trigram" clears the URL (uses built-in). */
69
- readonly embedder: "ollama" | "llama" | "trigram";
70
- /** Optional override URL (for non-default Ollama ports or llama.cpp). */
68
+ /** Which embedder to configure. "trigram" clears the URL (uses built-in).
69
+ * "custom" writes a user-supplied URL and opts in to non-loopback endpoints
70
+ * (sets MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1) for third-party / hosted APIs. */
71
+ readonly embedder: "ollama" | "llama" | "trigram" | "custom";
72
+ /** Override URL (required for "custom"; optional default for ollama/llama). */
71
73
  readonly url?: string;
72
74
  }
73
75
 
@@ -265,16 +265,27 @@ export function handleSetupConfigure(
265
265
  }
266
266
  const body = parsed.value as unknown as SetupConfigureRequest;
267
267
  const embedder = body.embedder;
268
- if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram") {
268
+ if (embedder !== "ollama" && embedder !== "llama" && embedder !== "trigram" && embedder !== "custom") {
269
269
  res.writeHead(400, { "Content-Type": "application/json" });
270
270
  res.end(JSON.stringify({ error: "invalid_embedder" }));
271
271
  return;
272
272
  }
273
273
  const url = body.url;
274
274
  let resolvedUrl: string | null;
275
+ let allowRemote = false;
275
276
  if (embedder === "ollama") resolvedUrl = typeof url === "string" && url ? url : OLLAMA_DEFAULT_URL;
276
277
  else if (embedder === "llama") resolvedUrl = typeof url === "string" && url ? url : LLAMA_DEFAULT_URL;
277
- else resolvedUrl = null;
278
+ else if (embedder === "custom") {
279
+ // Third-party / remote endpoint. Requires a URL; opts in to the
280
+ // non-loopback allowlist so embeddingConfigFromEnv() accepts it.
281
+ if (typeof url !== "string" || !url) {
282
+ res.writeHead(400, { "Content-Type": "application/json" });
283
+ res.end(JSON.stringify({ error: "custom_embedder_requires_url" }));
284
+ return;
285
+ }
286
+ resolvedUrl = url;
287
+ allowRemote = true;
288
+ } else resolvedUrl = null;
278
289
 
279
290
  // Write .mega-compact.env to the state dir (loaded by env-loader at next startup).
280
291
  const stateDir = ctx.stateDir;
@@ -285,10 +296,16 @@ export function handleSetupConfigure(
285
296
  ];
286
297
  if (resolvedUrl) {
287
298
  lines.push(`export MEGACOMPACT_EMBEDDING_URL="${resolvedUrl}"`);
299
+ if (allowRemote) {
300
+ lines.push(`export MEGACOMPACT_ALLOW_REMOTE_EMBEDDER="1"`);
301
+ } else {
302
+ lines.push(`# MEGACOMPACT_ALLOW_REMOTE_EMBEDDER not set (loopback-only)`);
303
+ }
288
304
  } else {
289
305
  lines.push("# trigram: built-in embedder, no URL needed");
290
306
  lines.push("# unset MEGACOMPACT_EMBEDDING_URL (commented to override any shell-set value)");
291
307
  lines.push("# export MEGACOMPACT_EMBEDDING_URL=");
308
+ lines.push("# export MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=");
292
309
  }
293
310
  lines.push("");
294
311
  try {
@@ -38,7 +38,12 @@ export function loadMegaEnv(stateDir: string): void {
38
38
  if (!trimmed || trimmed.startsWith("#")) continue;
39
39
  const eq = trimmed.indexOf("=");
40
40
  if (eq < 0) continue;
41
- const key = trimmed.slice(0, eq).trim();
41
+ let key = trimmed.slice(0, eq).trim();
42
+ // The setup wizard + Setup tab write shell-sourceable syntax
43
+ // (`export KEY="VALUE"`). Strip the leading `export ` so the key is
44
+ // `KEY`, not `export KEY` — otherwise process.env gets a useless entry
45
+ // with a space in the name and the real var is never set.
46
+ key = key.replace(/^export\s+/, "");
42
47
  if (!key) continue;
43
48
  let val = trimmed.slice(eq + 1).trim();
44
49
  if (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.12.7",
3
+ "version": "0.13.0",
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",
@@ -48,6 +48,7 @@
48
48
  "prebuild": "npm run clean",
49
49
  "build": "tsc -p tsconfig.json",
50
50
  "build:dashboard": "cd extensions/dashboard-client && npx vite build",
51
+ "test:dashboard-smoke": "node scripts/dashboard-tab-smoke.mjs",
51
52
  "lint": "tsc --noEmit && node scripts/guardrails-scan.mjs && node scripts/semantic-scan.mjs",
52
53
  "test": "npm run build && node scripts/run-tests.mjs",
53
54
  "guardrails": "python3 scripts/regression_check.py --all || node scripts/guardrails-scan.mjs || node scripts/semantic-scan.mjs",
@@ -62,6 +63,7 @@
62
63
  },
63
64
  "devDependencies": {
64
65
  "@types/node": "^22.20.1",
66
+ "playwright": "^1.60.0",
65
67
  "typescript": "^5.4.0"
66
68
  },
67
69
  "dependencies": {
@@ -126,18 +126,32 @@ export function embeddingConfigFromEnv(): HttpEmbedderOptions | null {
126
126
 
127
127
  const hostname = parsed.hostname.replace(/^\[|\]$/g, ""); // strip IPv6 brackets
128
128
 
129
- if (isIP(hostname)) {
130
- // Literal IP must be loopback.
131
- if (!isLoopbackIP(hostname)) {
132
- console.warn(`MEGACOMPACT_EMBEDDING_URL must be a loopback IP (got ${hostname}) falling back to default embedder (PREVENT-PI-004)`);
133
- return null;
129
+ // Opt-in escape hatch for a remote/third-party embedding endpoint (e.g. a
130
+ // hosted embeddings API). Default OFF loopback-only (PREVENT-PI-004). When
131
+ // MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1 the loopback check is skipped, so an
132
+ // advanced user can point at a non-loopback URL. The fetch still goes through
133
+ // HttpEmbedder (guardrails-allowed below) and credentials-in-URL are still
134
+ // rejected. Flag-OFF = byte-identical to the pre-change loopback enforcement.
135
+ const allowRemote =
136
+ process.env.MEGACOMPACT_ALLOW_REMOTE_EMBEDDER === "1" ||
137
+ process.env.MEGACOMPACT_ALLOW_REMOTE_EMBEDDER === "true";
138
+
139
+ if (!allowRemote) {
140
+ if (isIP(hostname)) {
141
+ // Literal IP — must be loopback.
142
+ if (!isLoopbackIP(hostname)) {
143
+ console.warn(`MEGACOMPACT_EMBEDDING_URL must be a loopback IP (got ${hostname}) — falling back to default embedder (PREVENT-PI-004)`);
144
+ return null;
145
+ }
146
+ } else {
147
+ // Hostname — resolve via dns.lookup and require ALL addresses loopback.
148
+ if (!hostnameResolvesToLoopback(hostname)) {
149
+ console.warn(`MEGACOMPACT_EMBEDDING_URL hostname "${hostname}" does not resolve to loopback — falling back to default embedder (PREVENT-PI-004)`);
150
+ return null;
151
+ }
134
152
  }
135
153
  } else {
136
- // Hostname resolve via dns.lookup and require ALL addresses loopback.
137
- if (!hostnameResolvesToLoopback(hostname)) {
138
- console.warn(`MEGACOMPACT_EMBEDDING_URL hostname "${hostname}" does not resolve to loopback — falling back to default embedder (PREVENT-PI-004)`);
139
- return null;
140
- }
154
+ console.warn(`MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1allowing non-loopback embedder endpoint ${hostname} (user-opted-in)`);
141
155
  }
142
156
 
143
157
  const headers: Record<string, string> = {};
@@ -180,7 +194,7 @@ function parseEmbedding(body: unknown): number[] {
180
194
  const WORKER = String.raw`
181
195
  const u = process.env.MC_URL, b = process.env.MC_BODY, h = JSON.parse(process.env.MC_HEADERS || "{}");
182
196
  try {
183
- const r = await fetch(u, { method: "POST", headers: h, body: b }); // guardrails-allow PREVENT-PI-004: localhost-only user-spawned embedding server (BYO backend, never remote)
197
+ const r = await fetch(u, { method: "POST", headers: h, body: b }); // guardrails-allow PREVENT-PI-004: BYO embedding endpoint — loopback-only by default; remote allowed only when MEGACOMPACT_ALLOW_REMOTE_EMBEDDER=1 (user opt-in)
184
198
  const out = JSON.stringify({ status: r.status, ok: r.ok, json: await r.json() });
185
199
  process.stdout.write(out);
186
200
  } catch (e) {
@@ -1,2 +0,0 @@
1
- import{r as o,j as n}from"./index-m3rSvxMW.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-B4KbY2gd.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AchievementTiles-B4KbY2gd.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 +0,0 @@
1
- import{r as t,n as m,j as s}from"./index-m3rSvxMW.js";import{A as v}from"./AchievementTiles-B4KbY2gd.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-CT2H3e9d.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AchievementsTab-CT2H3e9d.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 +0,0 @@
1
- import{r as n,m,n as I,j as e,d as $}from"./index-m3rSvxMW.js";import{A as k}from"./AchievementTiles-B4KbY2gd.js";function g(s){return(Math.round(s*10)/10).toString()+"%"}function b(s){return s!=null?new Date(s).toLocaleString():"—"}function M(s){return s&&typeof s=="object"?s:{}}function F(s){return s.split("/").pop()||s}function p({title:s,rows:u,badge:i,renderValue:v}){return e.jsxs("div",{className:"lb-card",children:[e.jsxs("h3",{children:[s,i]}),u.length===0?e.jsx("div",{className:"lb-empty",children:"—"}):e.jsx("table",{children:e.jsx("tbody",{children:u.map((o,x)=>e.jsxs("tr",{children:[e.jsx("td",{title:o.repo_root,children:F(o.repo_root)}),e.jsx("td",{className:"num",children:v(o)})]},`${o.repo_root}-${x}`))})})]})}function B(){const[s,u]=n.useState({}),[i,v]=n.useState(0),[o,x]=n.useState([]),[T,R]=n.useState(!1),[_,G]=n.useState(!0),[w,N]=n.useState(null),j=n.useRef(0),y=n.useCallback(async()=>{try{const[t,a,c,l,D,H]=await Promise.all([m({metric:"cache",limit:25}),m({metric:"dedupe",limit:25}),m({metric:"turns",limit:25}),m({metric:"mega_cache",limit:25}),m({metric:"repos",limit:1}),I()]),E=l.find(f=>f.ts>j.current&&f.value>100);j.current>0&&E&&(N(`oopsie! cache went to ${Math.round(E.value)}% — MEGA CACHE 🥧`),setTimeout(()=>N(null),4e3)),l.length>0&&(j.current=Math.max(...l.map(f=>f.ts))),u({cache:t,dedupe:a,turns:c,mega_cache:l}),v(D[0]?.value??0),x(H)}catch{}finally{G(!1)}},[]);n.useEffect(()=>{let t=!0;const a=async()=>{t&&await y()};a();const c=setInterval(()=>void a(),15e3);return()=>{t=!1,clearInterval(c)}},[y]),n.useEffect(()=>{let t=!0;const a=async()=>{try{const l=await $();t&&(R(!!l.game_mode_on),l.theme&&(document.documentElement.dataset.theme=l.theme))}catch{}};a();const c=setInterval(()=>void a(),15e3);return()=>{t=!1,clearInterval(c)}},[]);const S=s.mega_cache??[];let r=null,d=null;for(const t of S){(r==null||t.value>r)&&(r=t.value);const a=M(t.meta),c=a.firstSeenTs??a.firstSeen??t.ts;(d==null||c<d)&&(d=c)}const A=r!=null&&r>100,h=s.turns??[],L=h.reduce((t,a)=>Math.max(t,a.value),0),V=Math.floor(Math.log2(L+1))+1,C=(s.cache?.length??0)>0||(s.dedupe?.length??0)>0||h.length>0||S.length>0||i>0;return e.jsxs("div",{className:`game-tab${T?" game-mode-on":""}`,children:[w!=null&&e.jsx("div",{className:"mega-cache-toast show",children:w}),A&&e.jsxs("div",{className:"mega-cache-banner",children:["🥧"," MEGA CACHE! peak ",g(r)," — first reached ",b(d)]}),A&&e.jsxs("div",{className:"achievement-tile unlocked",children:["🏆"," Opie's Wild Ride",e.jsxs("span",{className:"ach-detail",children:["best ",g(r)," · first ",b(d)]})]}),e.jsx("h2",{children:"High Scores"}),!C&&!_&&e.jsx("div",{className:"game-empty",children:"No scores yet — run a session with game mode on."}),C&&e.jsxs("div",{className:"game-leaderboards",children:[e.jsx(p,{title:"Cache %",rows:s.cache??[],renderValue:t=>g(t.value)}),e.jsx(p,{title:"Dedupe (collapsed)",rows:s.dedupe??[],renderValue:t=>t.value.toLocaleString()}),e.jsx(p,{title:"Turns",rows:h,badge:h.length>0?e.jsxs("span",{className:"lvl-badge",children:["LVL ",V]}):void 0,renderValue:t=>t.value.toLocaleString()}),e.jsx(p,{title:"MEGA CACHE trophies",rows:S,renderValue:t=>e.jsxs(e.Fragment,{children:[g(t.value),e.jsx("span",{className:"lb-meta",children:b(M(t.meta).firstSeenTs??M(t.meta).firstSeen??t.ts)})]})})]}),i>0&&e.jsx("div",{className:"repos-badge-line",children:e.jsxs("span",{className:"repos-badge",children:[i," repos"]})}),e.jsx("h3",{children:"Achievements"}),e.jsx(k,{achievements:o})]})}export{B as default};
2
- //# sourceMappingURL=GameTab-CYWnWrwC.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"GameTab-CYWnWrwC.js","sources":["../../src/tabs/GameTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/GameTab.tsx — Game tab (NEW).\n *\n * Renders:\n * (1) MEGA CACHE banner (animated gradient, shown when any mega_cache > 100)\n * (2) Opie unlock tile (shown when mega_cache > 100)\n * (3) High Scores header\n * (4) 4 leaderboards in 2×2 grid: Cache %, Dedupe, Turns (LVL badge),\n * MEGA CACHE trophies (firstSeen date meta)\n * (5) Repos count badge via fetchGameScores({metric:'repos',limit:1})\n * (6) Achievements sub-section (AchievementTiles shared component)\n * (7) Game empty state\n * Polls scores + achievements every 15s, game-state every 15s.\n * Applies data-theme accent vars when game_mode_on.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport type { GameScoreRow, AchievementRow } from \"@contracts\";\nimport { fetchGameScores, fetchAchievements, fetchGameState } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\n/** Actual flat game state shape from the server (NOT the nested contract). */\ninterface FlatGameState {\n\tgame_mode_on: boolean;\n\ttheme: string;\n\ttui_display_mode: \"full\" | \"minimal\";\n}\n\n/** Metadata object inside a mega_cache GameScoreRow. */\ninterface TrophyMeta {\n\tfirstSeenTs?: number;\n\tfirstSeen?: number;\n}\n\n/* ── Format helpers (matching html.ts logic) ─────────────────────────── */\n\nfunction fmtPct(v: number): string {\n\treturn (Math.round(v * 10) / 10).toString() + \"%\";\n}\n\nfunction fmtDate(ts: number | null | undefined): string {\n\treturn ts != null ? new Date(ts).toLocaleString() : \"\\u2014\";\n}\n\nfunction trophyMeta(m: unknown): TrophyMeta {\n\treturn m && typeof m === \"object\" ? (m as TrophyMeta) : {};\n}\n\nfunction repoBasename(path: string): string {\n\treturn path.split(\"/\").pop() || path;\n}\n\n/* ── Leaderboard card ────────────────────────────────────────────────── */\n\ninterface LeaderboardCardProps {\n\ttitle: string;\n\trows: GameScoreRow[];\n\tbadge?: React.ReactNode;\n\trenderValue: (r: GameScoreRow) => React.ReactNode;\n}\n\nfunction LeaderboardCard({\n\ttitle,\n\trows,\n\tbadge,\n\trenderValue,\n}: LeaderboardCardProps): React.ReactElement {\n\treturn (\n\t\t<div className=\"lb-card\">\n\t\t\t<h3>\n\t\t\t\t{title}\n\t\t\t\t{badge}\n\t\t\t</h3>\n\t\t\t{rows.length === 0 ? (\n\t\t\t\t<div className=\"lb-empty\">—</div>\n\t\t\t) : (\n\t\t\t\t<table>\n\t\t\t\t\t<tbody>\n\t\t\t\t\t\t{rows.map((r, i) => (\n\t\t\t\t\t\t\t<tr key={`${r.repo_root}-${i}`}>\n\t\t\t\t\t\t\t\t<td title={r.repo_root}>{repoBasename(r.repo_root)}</td>\n\t\t\t\t\t\t\t\t<td className=\"num\">{renderValue(r)}</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tbody>\n\t\t\t\t</table>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n\n/* ── Main GameTab component ──────────────────────────────────────────── */\n\nexport default function GameTab(): React.ReactElement {\n\tconst [scores, setScores] = useState<\n\t\tRecord<string, GameScoreRow[]>\n\t>({});\n\tconst [reposCount, setReposCount] = useState(0);\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [gameModeOn, setGameModeOn] = useState(false);\n\tconst [loading, setLoading] = useState(true);\n\tconst [megaToast, setMegaToast] = useState<string | null>(null);\n\tconst lastMegaTsRef = useRef(0);\n\n\tconst fetchAll = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst [cache, dedupe, turns, megaCache, repos, ach] =\n\t\t\t\tawait Promise.all([\n\t\t\t\t\tfetchGameScores({ metric: \"cache\", limit: 25 }),\n\t\t\t\t\tfetchGameScores({ metric: \"dedupe\", limit: 25 }),\n\t\t\t\t\tfetchGameScores({ metric: \"turns\", limit: 25 }),\n\t\t\t\t\tfetchGameScores({ metric: \"mega_cache\", limit: 25 }),\n\t\t\t\t\tfetchGameScores({ metric: \"repos\", limit: 1 }),\n\t\t\t\t\tfetchAchievements(),\n\t\t\t\t]);\n\n\t\t\t/* Mega-cache toast: new row with value > 100 since last poll */\n\t\t\tconst newRow = megaCache.find(\n\t\t\t\t(r) => r.ts > lastMegaTsRef.current && r.value > 100,\n\t\t\t);\n\t\t\tif (lastMegaTsRef.current > 0 && newRow) {\n\t\t\t\tsetMegaToast(\n\t\t\t\t\t`oopsie! cache went to ${Math.round(newRow.value)}% — MEGA CACHE \\uD83E\\uDD67`,\n\t\t\t\t);\n\t\t\t\tsetTimeout(() => setMegaToast(null), 4000);\n\t\t\t}\n\t\t\tif (megaCache.length > 0) {\n\t\t\t\tlastMegaTsRef.current = Math.max(\n\t\t\t\t\t...megaCache.map((r) => r.ts),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tsetScores({ cache, dedupe, turns, mega_cache: megaCache });\n\t\t\tsetReposCount(repos[0]?.value ?? 0);\n\t\t\tsetAchievements(ach);\n\t\t} catch {\n\t\t\t/* non-fatal */\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\t/* Poll scores + achievements every 15s. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\tif (active) await fetchAll();\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [fetchAll]);\n\n\t/* Poll game state every 15s for styling. */\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\tsetGameModeOn(!!gs.game_mode_on);\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(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* Compute mega-cache banner data */\n\tconst megaRows = scores.mega_cache ?? [];\n\tlet bestMega: number | null = null;\n\tlet firstSeen: number | null = null;\n\tfor (const r of megaRows) {\n\t\tif (bestMega == null || r.value > bestMega) bestMega = r.value;\n\t\tconst m = trophyMeta(r.meta);\n\t\tconst fs = m.firstSeenTs ?? m.firstSeen ?? r.ts;\n\t\tif (firstSeen == null || fs < firstSeen) firstSeen = fs;\n\t}\n\tconst showMega = bestMega != null && bestMega > 100;\n\n\t/* Turns level badge */\n\tconst turnsRows = scores.turns ?? [];\n\tconst maxTurns = turnsRows.reduce((mx, r) => Math.max(mx, r.value), 0);\n\tconst lvl = Math.floor(Math.log2(maxTurns + 1)) + 1;\n\n\t/* Game empty state */\n\tconst hasData =\n\t\t(scores.cache?.length ?? 0) > 0 ||\n\t\t(scores.dedupe?.length ?? 0) > 0 ||\n\t\tturnsRows.length > 0 ||\n\t\tmegaRows.length > 0 ||\n\t\treposCount > 0;\n\n\treturn (\n\t\t<div className={`game-tab${gameModeOn ? \" game-mode-on\" : \"\"}`}>\n\t\t\t{megaToast != null && (\n\t\t\t\t<div className=\"mega-cache-toast show\">{megaToast}</div>\n\t\t\t)}\n\n\t\t\t{showMega && (\n\t\t\t\t<div className=\"mega-cache-banner\">\n\t\t\t\t\t{\"\\uD83E\\uDD67\"} MEGA CACHE! peak {fmtPct(bestMega!)} — first\n\t\t\t\t\treached {fmtDate(firstSeen)}\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{showMega && (\n\t\t\t\t<div className=\"achievement-tile unlocked\">\n\t\t\t\t\t{\"\\uD83C\\uDFC6\"} Opie's Wild Ride\n\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\tbest {fmtPct(bestMega!)} · first {fmtDate(firstSeen)}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<h2>High Scores</h2>\n\n\t\t\t{!hasData && !loading && (\n\t\t\t\t<div className=\"game-empty\">\n\t\t\t\t\tNo scores yet — run a session with game mode on.\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{hasData && (\n\t\t\t\t<div className=\"game-leaderboards\">\n\t\t\t\t\t<LeaderboardCard\n\t\t\t\t\t\ttitle=\"Cache %\"\n\t\t\t\t\t\trows={scores.cache ?? []}\n\t\t\t\t\t\trenderValue={(r) => fmtPct(r.value)}\n\t\t\t\t\t/>\n\t\t\t\t\t<LeaderboardCard\n\t\t\t\t\t\ttitle=\"Dedupe (collapsed)\"\n\t\t\t\t\t\trows={scores.dedupe ?? []}\n\t\t\t\t\t\trenderValue={(r) => r.value.toLocaleString()}\n\t\t\t\t\t/>\n\t\t\t\t\t<LeaderboardCard\n\t\t\t\t\t\ttitle=\"Turns\"\n\t\t\t\t\t\trows={turnsRows}\n\t\t\t\t\t\tbadge={\n\t\t\t\t\t\t\tturnsRows.length > 0 ? (\n\t\t\t\t\t\t\t\t<span className=\"lvl-badge\">LVL {lvl}</span>\n\t\t\t\t\t\t\t) : undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t\trenderValue={(r) => r.value.toLocaleString()}\n\t\t\t\t\t/>\n\t\t\t\t\t<LeaderboardCard\n\t\t\t\t\t\ttitle=\"MEGA CACHE trophies\"\n\t\t\t\t\t\trows={megaRows}\n\t\t\t\t\t\trenderValue={(r) => (\n\t\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\t{fmtPct(r.value)}\n\t\t\t\t\t\t\t\t<span className=\"lb-meta\">\n\t\t\t\t\t\t\t\t\t{fmtDate(\n\t\t\t\t\t\t\t\t\t\ttrophyMeta(r.meta).firstSeenTs ??\n\t\t\t\t\t\t\t\t\t\t\ttrophyMeta(r.meta).firstSeen ??\n\t\t\t\t\t\t\t\t\t\t\tr.ts,\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</>\n\t\t\t\t\t\t)}\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t{reposCount > 0 && (\n\t\t\t\t<div className=\"repos-badge-line\">\n\t\t\t\t\t<span className=\"repos-badge\">{reposCount} repos</span>\n\t\t\t\t</div>\n\t\t\t)}\n\n\t\t\t<h3>Achievements</h3>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["fmtPct","v","fmtDate","ts","trophyMeta","m","repoBasename","path","LeaderboardCard","title","rows","badge","renderValue","jsxs","jsx","r","i","GameTab","scores","setScores","useState","reposCount","setReposCount","achievements","setAchievements","gameModeOn","setGameModeOn","loading","setLoading","megaToast","setMegaToast","lastMegaTsRef","useRef","fetchAll","useCallback","cache","dedupe","turns","megaCache","repos","ach","fetchGameScores","fetchAchievements","newRow","useEffect","active","doFetch","timer","gs","fetchGameState","megaRows","bestMega","firstSeen","fs","showMega","turnsRows","maxTurns","mx","lvl","hasData","Fragment","AchievementTiles"],"mappings":"kHAqCA,SAASA,EAAOC,EAAmB,CAClC,OAAQ,KAAK,MAAMA,EAAI,EAAE,EAAI,IAAI,WAAa,GAC/C,CAEA,SAASC,EAAQC,EAAuC,CACvD,OAAOA,GAAM,KAAO,IAAI,KAAKA,CAAE,EAAE,iBAAmB,GACrD,CAEA,SAASC,EAAWC,EAAwB,CAC3C,OAAOA,GAAK,OAAOA,GAAM,SAAYA,EAAmB,CAAA,CACzD,CAEA,SAASC,EAAaC,EAAsB,CAC3C,OAAOA,EAAK,MAAM,GAAG,EAAE,OAASA,CACjC,CAWA,SAASC,EAAgB,CACxB,MAAAC,EACA,KAAAC,EACA,MAAAC,EACA,YAAAC,CACD,EAA6C,CAC5C,OACCC,EAAAA,KAAC,MAAA,CAAI,UAAU,UACd,SAAA,CAAAA,OAAC,KAAA,CACC,SAAA,CAAAJ,EACAE,CAAA,EACF,EACCD,EAAK,SAAW,QACf,MAAA,CAAI,UAAU,WAAW,SAAA,IAAC,QAE1B,QAAA,CACA,SAAAI,MAAC,SACC,SAAAJ,EAAK,IAAI,CAACK,EAAGC,WACZ,KAAA,CACA,SAAA,CAAAF,EAAAA,IAAC,MAAG,MAAOC,EAAE,UAAY,SAAAT,EAAaS,EAAE,SAAS,EAAE,QAClD,KAAA,CAAG,UAAU,MAAO,SAAAH,EAAYG,CAAC,CAAA,CAAE,CAAA,GAF5B,GAAGA,EAAE,SAAS,IAAIC,CAAC,EAG5B,CACA,EACF,CAAA,CACD,CAAA,EAEF,CAEF,CAIA,SAAwBC,GAA8B,CACrD,KAAM,CAACC,EAAQC,CAAS,EAAIC,EAAAA,SAE1B,CAAA,CAAE,EACE,CAACC,EAAYC,CAAa,EAAIF,EAAAA,SAAS,CAAC,EACxC,CAACG,EAAcC,CAAe,EAAIJ,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACK,EAAYC,CAAa,EAAIN,EAAAA,SAAS,EAAK,EAC5C,CAACO,EAASC,CAAU,EAAIR,EAAAA,SAAS,EAAI,EACrC,CAACS,EAAWC,CAAY,EAAIV,EAAAA,SAAwB,IAAI,EACxDW,EAAgBC,EAAAA,OAAO,CAAC,EAExBC,EAAWC,EAAAA,YAAY,SAA2B,CACvD,GAAI,CACH,KAAM,CAACC,EAAOC,EAAQC,EAAOC,EAAWC,EAAOC,CAAG,EACjD,MAAM,QAAQ,IAAI,CACjBC,EAAgB,CAAE,OAAQ,QAAS,MAAO,GAAI,EAC9CA,EAAgB,CAAE,OAAQ,SAAU,MAAO,GAAI,EAC/CA,EAAgB,CAAE,OAAQ,QAAS,MAAO,GAAI,EAC9CA,EAAgB,CAAE,OAAQ,aAAc,MAAO,GAAI,EACnDA,EAAgB,CAAE,OAAQ,QAAS,MAAO,EAAG,EAC7CC,EAAA,CAAkB,CAClB,EAGIC,EAASL,EAAU,KACvBvB,GAAMA,EAAE,GAAKgB,EAAc,SAAWhB,EAAE,MAAQ,GAAA,EAE9CgB,EAAc,QAAU,GAAKY,IAChCb,EACC,yBAAyB,KAAK,MAAMa,EAAO,KAAK,CAAC,mBAAA,EAElD,WAAW,IAAMb,EAAa,IAAI,EAAG,GAAI,GAEtCQ,EAAU,OAAS,IACtBP,EAAc,QAAU,KAAK,IAC5B,GAAGO,EAAU,IAAKvB,GAAMA,EAAE,EAAE,CAAA,GAI9BI,EAAU,CAAE,MAAAgB,EAAO,OAAAC,EAAQ,MAAAC,EAAO,WAAYC,EAAW,EACzDhB,EAAciB,EAAM,CAAC,GAAG,OAAS,CAAC,EAClCf,EAAgBgB,CAAG,CACpB,MAAQ,CAER,QAAA,CACCZ,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAGLgB,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAU,SAA2B,CACtCD,SAAcZ,EAAA,CACnB,EACKa,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAW,IAAK,EACrD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACd,CAAQ,CAAC,EAGbW,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAU,SAA2B,CAC1C,GAAI,CACH,MAAME,EAAM,MAAMC,EAAA,EACdJ,IACHnB,EAAc,CAAC,CAACsB,EAAG,YAAY,EAC3BA,EAAG,QACN,SAAS,gBAAgB,QAAQ,MAAQA,EAAG,OAE/C,MAAQ,CAER,CACD,EACKF,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAW,IAAK,EACrD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAAA,CAAE,EAGL,MAAMG,EAAWhC,EAAO,YAAc,CAAA,EACtC,IAAIiC,EAA0B,KAC1BC,EAA2B,KAC/B,UAAWrC,KAAKmC,EAAU,EACrBC,GAAY,MAAQpC,EAAE,MAAQoC,OAAqBpC,EAAE,OACzD,MAAMV,EAAID,EAAWW,EAAE,IAAI,EACrBsC,EAAKhD,EAAE,aAAeA,EAAE,WAAaU,EAAE,IACzCqC,GAAa,MAAQC,EAAKD,KAAWA,EAAYC,EACtD,CACA,MAAMC,EAAWH,GAAY,MAAQA,EAAW,IAG1CI,EAAYrC,EAAO,OAAS,CAAA,EAC5BsC,EAAWD,EAAU,OAAO,CAACE,EAAI1C,IAAM,KAAK,IAAI0C,EAAI1C,EAAE,KAAK,EAAG,CAAC,EAC/D2C,EAAM,KAAK,MAAM,KAAK,KAAKF,EAAW,CAAC,CAAC,EAAI,EAG5CG,GACJzC,EAAO,OAAO,QAAU,GAAK,IAC7BA,EAAO,QAAQ,QAAU,GAAK,GAC/BqC,EAAU,OAAS,GACnBL,EAAS,OAAS,GAClB7B,EAAa,EAEd,cACE,MAAA,CAAI,UAAW,WAAWI,EAAa,gBAAkB,EAAE,GAC1D,SAAA,CAAAI,GAAa,MACbf,EAAAA,IAAC,MAAA,CAAI,UAAU,wBAAyB,SAAAe,EAAU,EAGlDyB,GACAzC,EAAAA,KAAC,MAAA,CAAI,UAAU,oBACb,SAAA,CAAA,KAAe,qBAAmBb,EAAOmD,CAAS,EAAE,oBAC5CjD,EAAQkD,CAAS,CAAA,EAC3B,EAGAE,GACAzC,EAAAA,KAAC,MAAA,CAAI,UAAU,4BACb,SAAA,CAAA,KAAe,oBAChBA,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,QACtBb,EAAOmD,CAAS,EAAE,YAAUjD,EAAQkD,CAAS,CAAA,CAAA,CACpD,CAAA,EACD,EAGDtC,EAAAA,IAAC,MAAG,SAAA,aAAA,CAAW,EAEd,CAAC6C,GAAW,CAAChC,SACZ,MAAA,CAAI,UAAU,aAAa,SAAA,mDAE5B,EAGAgC,GACA9C,EAAAA,KAAC,MAAA,CAAI,UAAU,oBACd,SAAA,CAAAC,EAAAA,IAACN,EAAA,CACA,MAAM,UACN,KAAMU,EAAO,OAAS,CAAA,EACtB,YAAcH,GAAMf,EAAOe,EAAE,KAAK,CAAA,CAAA,EAEnCD,EAAAA,IAACN,EAAA,CACA,MAAM,qBACN,KAAMU,EAAO,QAAU,CAAA,EACvB,YAAcH,GAAMA,EAAE,MAAM,eAAA,CAAe,CAAA,EAE5CD,EAAAA,IAACN,EAAA,CACA,MAAM,QACN,KAAM+C,EACN,MACCA,EAAU,OAAS,EAClB1C,EAAAA,KAAC,OAAA,CAAK,UAAU,YAAY,SAAA,CAAA,OAAK6C,CAAA,CAAA,CAAI,EAClC,OAEL,YAAc3C,GAAMA,EAAE,MAAM,eAAA,CAAe,CAAA,EAE5CD,EAAAA,IAACN,EAAA,CACA,MAAM,sBACN,KAAM0C,EACN,YAAcnC,GACbF,EAAAA,KAAA+C,EAAAA,SAAA,CACE,SAAA,CAAA5D,EAAOe,EAAE,KAAK,EACfD,EAAAA,IAAC,OAAA,CAAK,UAAU,UACd,SAAAZ,EACAE,EAAWW,EAAE,IAAI,EAAE,aAClBX,EAAWW,EAAE,IAAI,EAAE,WACnBA,EAAE,EAAA,CACJ,CACD,CAAA,CAAA,CACD,CAAA,CAAA,CAEF,EACD,EAGAM,EAAa,GACbP,EAAAA,IAAC,MAAA,CAAI,UAAU,mBACd,SAAAD,EAAAA,KAAC,OAAA,CAAK,UAAU,cAAe,SAAA,CAAAQ,EAAW,QAAA,CAAA,CAAM,CAAA,CACjD,EAGDP,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAAC+C,GAAiB,aAAAtC,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
@@ -1,2 +0,0 @@
1
- import{r as s,e as T,g as k,h as D,j as e}from"./index-m3rSvxMW.js";const t={container:{padding:"1rem",maxWidth:"720px",margin:"0 auto",fontFamily:"system-ui, sans-serif",color:"#e0e0e0"},section:{background:"#1a1a2e",borderRadius:"8px",padding:"1rem",marginBottom:"1rem",border:"1px solid #2a2a4e"},sectionTitle:{fontSize:"1.1rem",fontWeight:700,marginTop:0,marginBottom:"0.75rem",color:"#e0e0e0",borderBottom:"1px solid #333",paddingBottom:"0.5rem"},label:{fontWeight:600,color:"#a0a0c0",marginRight:"0.5rem"},value:{color:"#e0e0e0"},row:{marginBottom:"0.4rem",fontSize:"0.9rem"},warning:{background:"#3a2a00",border:"1px solid #665500",borderRadius:"8px",padding:"0.75rem",marginTop:"0.5rem",color:"#ffcc00",fontSize:"0.85rem"},badge:{display:"inline-block",padding:"0.15rem 0.5rem",borderRadius:"4px",fontSize:"0.8rem",fontWeight:600,marginLeft:"0.5rem"},detectRow:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"0.5rem 0",borderBottom:"1px solid #2a2a4e"},button:{background:"#3a5a9f",color:"#fff",border:"none",borderRadius:"6px",padding:"0.5rem 1rem",cursor:"pointer",fontSize:"0.9rem",fontWeight:600}};function B(){const[l,S]=s.useState(null),[n,E]=s.useState(null),[c,C]=s.useState(null),[p,x]=s.useState(null),[f,m]=s.useState(!1),[a,u]=s.useState(null),[d,j]=s.useState(null),[b,y]=s.useState(null),o=s.useCallback(()=>{T().then(S).catch(r=>C(r instanceof Error?r.message:String(r)))},[]),h=s.useCallback(r=>{u(r),y(null),j(null),k({embedder:r}).then(i=>{j(i),u(null),o()}).catch(i=>{y(i instanceof Error?i.message:String(i)),u(null)})},[o]),w=s.useCallback(()=>{m(!0),x(null),D().then(r=>{E(r),m(!1)}).catch(r=>{x(r instanceof Error?r.message:String(r)),m(!1)})},[]);s.useEffect(()=>{o()},[o]),s.useEffect(()=>{const r=setInterval(o,3e4);return()=>clearInterval(r)},[o]);const v=r=>{switch(r){case"trigram":return"TrigramEmbedder (heuristic, default)";case"http":return"HTTP Embedder (BYO localhost)";case"minilm":return"MiniLM (experimental)";default:return"Unknown"}},R=l?.currentEmbedder==="trigram"&&e.jsxs("div",{style:t.warning,children:[e.jsx("strong",{children:"Note:"})," TrigramEmbedder is a heuristic-strength default embedder. It works without any setup but recall quality may be lower than a dedicated embedding backend. If you notice poor recall results, consider installing Ollama and running the"," ",e.jsx("code",{children:"/megasetup"})," command, or set"," ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL"})," to point at your own localhost embedding server."]}),g=r=>{const i=r?"#1a5a1a":"#3a1a1a",z=r?"#4caf50":"#f44336";return e.jsx("span",{style:{...t.badge,background:i,color:z},children:r?"Detected":"Not found"})};return e.jsxs("div",{style:t.container,children:[e.jsx("h2",{style:{fontSize:"1.3rem",marginTop:0,marginBottom:"1rem"},children:"Embedder Setup"}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Current Embedder Configuration"}),c&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",c]}),l&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Active Embedder:"}),e.jsx("span",{style:t.value,children:v(l.currentEmbedder)})]}),"configuredEmbedder"in l&&l.configuredEmbedder!==l.currentEmbedder&&e.jsxs("div",{style:t.warning,children:[e.jsx("strong",{children:"Configured but not active:"})," ",v(l.configuredEmbedder)," is configured in .mega-compact.env but not yet loaded."," ",e.jsx("strong",{children:"Restart pi"})," to activate it.",l.configuredUrl&&e.jsxs(e.Fragment,{children:[" (",l.configuredUrl,")"]})]}),"restartRequired"in l&&l.restartRequired&&e.jsxs("div",{style:{...t.row,color:"#ff9800"},children:[e.jsx("span",{style:t.label,children:"Status:"}),e.jsx("span",{style:t.value,children:"Restart required to activate the configured embedder"})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embedding URL:"}),e.jsx("span",{style:t.value,children:l.embeddingUrl??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"Embed Cache:"}),e.jsx("span",{style:t.value,children:l.embedCache??e.jsx("span",{style:{color:"#888"},children:"not set"})})]}),e.jsxs("div",{style:t.row,children:[e.jsx("span",{style:t.label,children:"MiniLM:"}),e.jsx("span",{style:t.value,children:l.minilm?"enabled":"disabled"})]}),R]}),!l&&!c&&e.jsx("p",{style:{color:"#888",fontSize:"0.85rem"},children:"Loading configuration..."})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"Local Embedder Detection"}),e.jsx("p",{style:{fontSize:"0.85rem",color:"#a0a0c0",marginTop:0},children:"Detect which local embedding backends are available on this machine. No software is installed — detection only."}),n&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"Ollama"})}),n.ollama!==null?e.jsxs("span",{children:[g(n.ollama.installed),n.ollama.installed&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.ollama.running?`server running, ${n.ollama.models.length} model(s)`:"server not running"})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"llama.cpp"})}),n.llamaCpp!==null?e.jsxs("span",{children:[g(n.llamaCpp.installed),n.llamaCpp.installed&&n.llamaCpp.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.llamaCpp.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),e.jsxs("div",{style:t.detectRow,children:[e.jsx("span",{children:e.jsx("strong",{children:"ONNX Runtime"})}),n.onnx!==null?e.jsxs("span",{children:[g(n.onnx.installed),n.onnx.installed&&n.onnx.detail&&e.jsx("span",{style:{fontSize:"0.8rem",color:"#a0a0c0",marginLeft:"0.5rem"},children:n.onnx.detail})]}):e.jsx("span",{style:{color:"#888",fontSize:"0.85rem"},children:"not checked"})]}),n.error&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.8rem",marginTop:"0.5rem"},children:["Detection error: ",n.error]})]}),p&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem"},children:["Error: ",p]}),e.jsxs("div",{style:{marginTop:"1rem"},children:[e.jsx("button",{style:t.button,onClick:w,disabled:f,children:f?"Detecting...":"Run Detection"}),e.jsxs("span",{style:{fontSize:"0.8rem",color:"#888",marginLeft:"0.75rem"},children:["Or run ",e.jsx("code",{children:"/megasetup"})," in the chat for an interactive wizard."]})]}),n&&e.jsxs("div",{style:{marginTop:"1rem",paddingTop:"0.75rem",borderTop:"1px solid #2a2a4e"},children:[e.jsx("div",{style:{fontSize:"0.9rem",fontWeight:600,marginBottom:"0.5rem"},children:"Configure"}),e.jsxs("div",{style:{display:"flex",gap:"0.5rem",flexWrap:"wrap"},children:[e.jsx("button",{style:{...t.button,opacity:n.ollama?.installed?1:.4},onClick:()=>h("ollama"),disabled:!n.ollama?.installed||a!==null,children:a==="ollama"?"Writing...":"Use Ollama"}),e.jsx("button",{style:{...t.button,opacity:n.llamaCpp?.installed?1:.4},onClick:()=>h("llama"),disabled:!n.llamaCpp?.installed||a!==null,children:a==="llama"?"Writing...":"Use llama.cpp"}),e.jsx("button",{style:{...t.button,background:"#444"},onClick:()=>h("trigram"),disabled:a!==null,children:a==="trigram"?"Writing...":"Use Trigram (default)"})]}),d&&e.jsx("div",{style:t.warning,children:d.alreadyActive?"Already active — no restart needed.":`Configured ${d.embedder}. Restart pi to activate (config written to ${d.envPath}).`}),b&&e.jsxs("p",{style:{color:"#f44336",fontSize:"0.85rem",marginTop:"0.5rem"},children:["Configure error: ",b]})]})]}),e.jsxs("div",{style:t.section,children:[e.jsx("h3",{style:t.sectionTitle,children:"How to Upgrade"}),e.jsxs("ul",{style:{fontSize:"0.85rem",lineHeight:1.6,paddingLeft:"1.25rem"},children:[e.jsxs("li",{children:[e.jsx("strong",{children:"Ollama:"})," Install from"," ",e.jsx("a",{href:"https://ollama.com",style:{color:"#6a9fff"},target:"_blank",rel:"noopener noreferrer",children:"ollama.com"}),", then ",e.jsx("code",{children:"ollama pull nomic-embed-text"})," and restart pi. Set ",e.jsx("code",{children:"MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings"}),"."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"llama.cpp:"})," Build or install llama.cpp, run"," ",e.jsx("code",{children:"llama-server"})," with an embedding model, and set the URL accordingly."]}),e.jsxs("li",{children:[e.jsx("strong",{children:"ONNX Runtime:"})," Install"," ",e.jsx("code",{children:"onnxruntime-node"})," in the project root. Not yet integrated — see docs for manual setup."]})]})]})]})}export{B as default};
2
- //# sourceMappingURL=SetupTab-DwXPFzgS.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SetupTab-DwXPFzgS.js","sources":["../../src/tabs/SetupTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/SetupTab.tsx — Setup wizard tab (P0b).\n *\n * Shows current embedder configuration, warns about trigram recall quality,\n * and mirrors the /megasetup command with detection results.\n *\n * PREVENT-PI-004: all data comes from localhost dashboard API endpoints —\n * no external network calls.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SetupStatusResponse, SetupDetectResponse, SetupConfigureResponse } from \"@contracts\";\nimport { fetchSetupStatus, fetchSetupDetect, configureEmbedder } from \"../api/client\";\n\n// ---------------------------------------------------------------------------\n// Style constants\n// ---------------------------------------------------------------------------\n\nconst styles: Record<string, React.CSSProperties> = {\n\tcontainer: {\n\t\tpadding: \"1rem\",\n\t\tmaxWidth: \"720px\",\n\t\tmargin: \"0 auto\",\n\t\tfontFamily: \"system-ui, sans-serif\",\n\t\tcolor: \"#e0e0e0\",\n\t},\n\tsection: {\n\t\tbackground: \"#1a1a2e\",\n\t\tborderRadius: \"8px\",\n\t\tpadding: \"1rem\",\n\t\tmarginBottom: \"1rem\",\n\t\tborder: \"1px solid #2a2a4e\",\n\t},\n\tsectionTitle: {\n\t\tfontSize: \"1.1rem\",\n\t\tfontWeight: 700,\n\t\tmarginTop: 0,\n\t\tmarginBottom: \"0.75rem\",\n\t\tcolor: \"#e0e0e0\",\n\t\tborderBottom: \"1px solid #333\",\n\t\tpaddingBottom: \"0.5rem\",\n\t},\n\tlabel: { fontWeight: 600, color: \"#a0a0c0\", marginRight: \"0.5rem\" },\n\tvalue: { color: \"#e0e0e0\" },\n\trow: { marginBottom: \"0.4rem\", fontSize: \"0.9rem\" },\n\twarning: {\n\t\tbackground: \"#3a2a00\",\n\t\tborder: \"1px solid #665500\",\n\t\tborderRadius: \"8px\",\n\t\tpadding: \"0.75rem\",\n\t\tmarginTop: \"0.5rem\",\n\t\tcolor: \"#ffcc00\",\n\t\tfontSize: \"0.85rem\",\n\t},\n\tbadge: {\n\t\tdisplay: \"inline-block\",\n\t\tpadding: \"0.15rem 0.5rem\",\n\t\tborderRadius: \"4px\",\n\t\tfontSize: \"0.8rem\",\n\t\tfontWeight: 600,\n\t\tmarginLeft: \"0.5rem\",\n\t},\n\tdetectRow: {\n\t\tdisplay: \"flex\",\n\t\tjustifyContent: \"space-between\",\n\t\talignItems: \"center\",\n\t\tpadding: \"0.5rem 0\",\n\t\tborderBottom: \"1px solid #2a2a4e\",\n\t},\n\tbutton: {\n\t\tbackground: \"#3a5a9f\",\n\t\tcolor: \"#fff\",\n\t\tborder: \"none\",\n\t\tborderRadius: \"6px\",\n\t\tpadding: \"0.5rem 1rem\",\n\t\tcursor: \"pointer\",\n\t\tfontSize: \"0.9rem\",\n\t\tfontWeight: 600,\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// SetupTab component\n// ---------------------------------------------------------------------------\n\nexport default function SetupTab(): React.ReactElement {\n\tconst [status, setStatus] = useState<SetupStatusResponse | null>(null);\n\tconst [detect, setDetect] = useState<SetupDetectResponse | null>(null);\n\tconst [statusError, setStatusError] = useState<string | null>(null);\n\tconst [detectError, setDetectError] = useState<string | null>(null);\n\tconst [runningDetect, setRunningDetect] = useState(false);\n\tconst [configuring, setConfiguring] = useState<string | null>(null);\n\tconst [configResult, setConfigResult] = useState<SetupConfigureResponse | null>(null);\n\tconst [configError, setConfigError] = useState<string | null>(null);\n\n\tconst loadStatus = useCallback(() => {\n\t\tfetchSetupStatus()\n\t\t\t.then(setStatus)\n\t\t\t.catch((e: unknown) =>\n\t\t\t\tsetStatusError(e instanceof Error ? e.message : String(e)),\n\t\t\t);\n\t}, []);\n\n\tconst applyEmbedder = useCallback((embedder: \"ollama\" | \"llama\" | \"trigram\") => {\n\t\tsetConfiguring(embedder);\n\t\tsetConfigError(null);\n\t\tsetConfigResult(null);\n\t\tconfigureEmbedder({ embedder })\n\t\t\t.then((r) => {\n\t\t\t\tsetConfigResult(r);\n\t\t\t\tsetConfiguring(null);\n\t\t\t\tloadStatus();\n\t\t\t})\n\t\t\t.catch((e: unknown) => {\n\t\t\t\tsetConfigError(e instanceof Error ? e.message : String(e));\n\t\t\t\tsetConfiguring(null);\n\t\t\t});\n\t}, [loadStatus]);\n\n\tconst runDetect = useCallback(() => {\n\t\tsetRunningDetect(true);\n\t\tsetDetectError(null);\n\t\tfetchSetupDetect()\n\t\t\t.then((d) => {\n\t\t\t\tsetDetect(d);\n\t\t\t\tsetRunningDetect(false);\n\t\t\t})\n\t\t\t.catch((e: unknown) => {\n\t\t\t\tsetDetectError(e instanceof Error ? e.message : String(e));\n\t\t\t\tsetRunningDetect(false);\n\t\t\t});\n\t}, []);\n\n\tuseEffect(() => {\n\t\tloadStatus();\n\t}, [loadStatus]);\n\n\t// Poll status every 30s\n\tuseEffect(() => {\n\t\tconst id = setInterval(loadStatus, 30000);\n\t\treturn () => clearInterval(id);\n\t}, [loadStatus]);\n\n\t// -----------------------------------------------------------------------\n\t// Render helpers\n\t// -----------------------------------------------------------------------\n\n\tconst embedderLabel = (e: string): string => {\n\t\tswitch (e) {\n\t\t\tcase \"trigram\":\n\t\t\t\treturn \"TrigramEmbedder (heuristic, default)\";\n\t\t\tcase \"http\":\n\t\t\t\treturn \"HTTP Embedder (BYO localhost)\";\n\t\t\tcase \"minilm\":\n\t\t\t\treturn \"MiniLM (experimental)\";\n\t\t\tdefault:\n\t\t\t\treturn \"Unknown\";\n\t\t}\n\t};\n\n\tconst trigramWarning = status?.currentEmbedder === \"trigram\" && (\n\t\t<div style={styles.warning}>\n\t\t\t<strong>Note:</strong> TrigramEmbedder is a heuristic-strength default\n\t\t\tembedder. It works without any setup but recall quality may be lower\n\t\t\tthan a dedicated embedding backend. If you notice poor recall results,\n\t\t\tconsider installing Ollama and running the{\" \"}\n\t\t\t<code>/megasetup</code> command, or set{\" \"}\n\t\t\t<code>MEGACOMPACT_EMBEDDING_URL</code> to point at your own localhost\n\t\t\tembedding server.\n\t\t</div>\n\t);\n\n\tconst detectBadge = (installed: boolean): React.ReactElement => {\n\t\tconst bg = installed ? \"#1a5a1a\" : \"#3a1a1a\";\n\t\tconst color = installed ? \"#4caf50\" : \"#f44336\";\n\t\treturn (\n\t\t\t<span style={{ ...styles.badge, background: bg, color }}>\n\t\t\t\t{installed ? \"Detected\" : \"Not found\"}\n\t\t\t</span>\n\t\t);\n\t};\n\n\t// -----------------------------------------------------------------------\n\t// Main render\n\t// -----------------------------------------------------------------------\n\n\treturn (\n\t\t<div style={styles.container}>\n\t\t\t<h2 style={{ fontSize: \"1.3rem\", marginTop: 0, marginBottom: \"1rem\" }}>\n\t\t\t\tEmbedder Setup\n\t\t\t</h2>\n\n\t\t\t{/* Current Config Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>Current Embedder Configuration</h3>\n\t\t\t\t{statusError && (\n\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tError: {statusError}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t{status && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Active Embedder:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{embedderLabel(status.currentEmbedder)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{\"configuredEmbedder\" in status && status.configuredEmbedder !== status.currentEmbedder && (\n\t\t\t\t\t\t\t<div style={styles.warning}>\n\t\t\t\t\t\t\t\t<strong>Configured but not active:</strong>{\" \"}\n\t\t\t\t\t\t\t\t{embedderLabel(status.configuredEmbedder)} is configured in\n\t\t\t\t\t\t\t\t.mega-compact.env but not yet loaded.{\" \"}\n\t\t\t\t\t\t\t\t<strong>Restart pi</strong> to activate it.\n\t\t\t\t\t\t\t\t{status.configuredUrl && (\n\t\t\t\t\t\t\t\t\t<> ({status.configuredUrl})</>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{\"restartRequired\" in status && status.restartRequired && (\n\t\t\t\t\t\t\t<div style={{ ...styles.row, color: \"#ff9800\" }}>\n\t\t\t\t\t\t\t\t<span style={styles.label}>Status:</span>\n\t\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t\tRestart required to activate the configured embedder\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\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Embedding URL:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{status.embeddingUrl ?? (\n\t\t\t\t\t\t\t\t\t<span style={{ color: \"#888\" }}>not set</span>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>Embed Cache:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>\n\t\t\t\t\t\t\t\t{status.embedCache ?? (\n\t\t\t\t\t\t\t\t\t<span style={{ color: \"#888\" }}>not set</span>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={styles.row}>\n\t\t\t\t\t\t\t<span style={styles.label}>MiniLM:</span>\n\t\t\t\t\t\t\t<span style={styles.value}>{status.minilm ? \"enabled\" : \"disabled\"}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{trigramWarning}\n\t\t\t\t\t</>\n\t\t\t\t)}\n\t\t\t\t{!status && !statusError && (\n\t\t\t\t\t<p style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tLoading configuration...\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Detection Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>Local Embedder Detection</h3>\n\t\t\t\t<p style={{ fontSize: \"0.85rem\", color: \"#a0a0c0\", marginTop: 0 }}>\n\t\t\t\t\tDetect which local embedding backends are available on this machine.\n\t\t\t\t\tNo software is installed — detection only.\n\t\t\t\t</p>\n\n\t\t\t\t{detect && (\n\t\t\t\t\t<>\n\t\t\t\t\t\t{/* Ollama */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>Ollama</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.ollama !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.ollama.installed)}\n\t\t\t\t\t\t\t\t\t{detect.ollama.installed && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.ollama.running\n\t\t\t\t\t\t\t\t\t\t\t\t? `server running, ${detect.ollama.models.length} model(s)`\n\t\t\t\t\t\t\t\t\t\t\t\t: \"server not running\"}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* llama.cpp */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>llama.cpp</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.llamaCpp !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.llamaCpp.installed)}\n\t\t\t\t\t\t\t\t\t{detect.llamaCpp.installed && detect.llamaCpp.detail && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.llamaCpp.detail}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{/* ONNX */}\n\t\t\t\t\t\t<div style={styles.detectRow}>\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t<strong>ONNX Runtime</strong>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t{detect.onnx !== null ? (\n\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t{detectBadge(detect.onnx.installed)}\n\t\t\t\t\t\t\t\t\t{detect.onnx.installed && detect.onnx.detail && (\n\t\t\t\t\t\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#a0a0c0\", marginLeft: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\t\t\t\t{detect.onnx.detail}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span style={{ color: \"#888\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\t\t\t\tnot checked\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t{detect.error && (\n\t\t\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.8rem\", marginTop: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\tDetection error: {detect.error}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</>\n\t\t\t\t)}\n\n\t\t\t\t{detectError && (\n\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\" }}>\n\t\t\t\t\t\tError: {detectError}\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\n\t\t\t\t<div style={{ marginTop: \"1rem\" }}>\n\t\t\t\t\t<button\n\t\t\t\t\t\tstyle={styles.button}\n\t\t\t\t\t\tonClick={runDetect}\n\t\t\t\t\t\tdisabled={runningDetect}\n\t\t\t\t\t>\n\t\t\t\t\t\t{runningDetect ? \"Detecting...\" : \"Run Detection\"}\n\t\t\t\t\t</button>\n\t\t\t\t\t<span style={{ fontSize: \"0.8rem\", color: \"#888\", marginLeft: \"0.75rem\" }}>\n\t\t\t\t\t\tOr run <code>/megasetup</code> in the chat for an interactive wizard.\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\n\t\t\t\t{/* Configure buttons — appear after detection runs */}\n\t\t\t\t{detect && (\n\t\t\t\t\t<div style={{ marginTop: \"1rem\", paddingTop: \"0.75rem\", borderTop: \"1px solid #2a2a4e\" }}>\n\t\t\t\t\t\t<div style={{ fontSize: \"0.9rem\", fontWeight: 600, marginBottom: \"0.5rem\" }}>\n\t\t\t\t\t\t\tConfigure\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div style={{ display: \"flex\", gap: \"0.5rem\", flexWrap: \"wrap\" }}>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, opacity: detect.ollama?.installed ? 1 : 0.4 }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"ollama\")}\n\t\t\t\t\t\t\t\tdisabled={!detect.ollama?.installed || configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"ollama\" ? \"Writing...\" : \"Use Ollama\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, opacity: detect.llamaCpp?.installed ? 1 : 0.4 }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"llama\")}\n\t\t\t\t\t\t\t\tdisabled={!detect.llamaCpp?.installed || configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"llama\" ? \"Writing...\" : \"Use llama.cpp\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\tstyle={{ ...styles.button, background: \"#444\" }}\n\t\t\t\t\t\t\t\tonClick={() => applyEmbedder(\"trigram\")}\n\t\t\t\t\t\t\t\tdisabled={configuring !== null}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{configuring === \"trigram\" ? \"Writing...\" : \"Use Trigram (default)\"}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{configResult && (\n\t\t\t\t\t\t\t<div style={styles.warning}>\n\t\t\t\t\t\t\t\t{configResult.alreadyActive\n\t\t\t\t\t\t\t\t\t? \"Already active — no restart needed.\"\n\t\t\t\t\t\t\t\t\t: `Configured ${configResult.embedder}. Restart pi to activate (config written to ${configResult.envPath}).`}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{configError && (\n\t\t\t\t\t\t\t<p style={{ color: \"#f44336\", fontSize: \"0.85rem\", marginTop: \"0.5rem\" }}>\n\t\t\t\t\t\t\t\tConfigure error: {configError}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</div>\n\n\t\t\t{/* Help Section */}\n\t\t\t<div style={styles.section}>\n\t\t\t\t<h3 style={styles.sectionTitle}>How to Upgrade</h3>\n\t\t\t\t<ul style={{ fontSize: \"0.85rem\", lineHeight: 1.6, paddingLeft: \"1.25rem\" }}>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>Ollama:</strong> Install from{\" \"}\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref=\"https://ollama.com\"\n\t\t\t\t\t\t\tstyle={{ color: \"#6a9fff\" }}\n\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\trel=\"noopener noreferrer\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\tollama.com\n\t\t\t\t\t\t</a>\n\t\t\t\t\t\t, then <code>ollama pull nomic-embed-text</code> and restart pi.\n\t\t\t\t\t\tSet <code>MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings</code>.\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>llama.cpp:</strong> Build or install llama.cpp, run{\" \"}\n\t\t\t\t\t\t<code>llama-server</code> with an embedding model, and set\n\t\t\t\t\t\tthe URL accordingly.\n\t\t\t\t\t</li>\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<strong>ONNX Runtime:</strong> Install{\" \"}\n\t\t\t\t\t\t<code>onnxruntime-node</code> in the project root. Not yet\n\t\t\t\t\t\tintegrated — see docs for manual setup.\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["styles","SetupTab","status","setStatus","useState","detect","setDetect","statusError","setStatusError","detectError","setDetectError","runningDetect","setRunningDetect","configuring","setConfiguring","configResult","setConfigResult","configError","setConfigError","loadStatus","useCallback","fetchSetupStatus","e","applyEmbedder","embedder","configureEmbedder","r","runDetect","fetchSetupDetect","d","useEffect","id","embedderLabel","trigramWarning","jsx","detectBadge","installed","bg","color","jsxs","Fragment"],"mappings":"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"}