@stacknext/dashboard 0.11.0 → 0.11.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.
package/README.md
CHANGED
|
@@ -12,6 +12,45 @@ npx stacknext dev
|
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The dashboard is in-memory, loopback-only and receives already-redacted
|
|
15
|
-
telemetry from the collector. It is not a hosted service
|
|
15
|
+
telemetry from the collector. It is not a hosted service and has no runtime
|
|
16
|
+
exports other than `./package.json`.
|
|
17
|
+
|
|
18
|
+
## Views
|
|
19
|
+
|
|
20
|
+
- Overview: counts, latency, recent errors, coverage and top diagnostics.
|
|
21
|
+
- Requests and request detail.
|
|
22
|
+
- Traces and trace waterfall.
|
|
23
|
+
- Database queries.
|
|
24
|
+
- Errors and error detail.
|
|
25
|
+
- Diagnostics list and detail.
|
|
26
|
+
- Runs, baselines and comparisons, including deep links.
|
|
27
|
+
- Snapshots, including scoped traces, errors and database queries.
|
|
28
|
+
- Settings and MCP setup.
|
|
29
|
+
|
|
30
|
+
Routes and deep links are documented in [docs/routes.md](docs/routes.md).
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm --filter @stacknext/dashboard dev
|
|
36
|
+
pnpm --filter @stacknext/dashboard build
|
|
37
|
+
pnpm --filter @stacknext/dashboard typecheck
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The dev server uses Vite. The built `dist` is served by the CLI collector; the
|
|
41
|
+
dashboard must build before the CLI can serve it. See
|
|
42
|
+
[docs/development.md](docs/development.md) for the shell, design tokens and
|
|
43
|
+
testing notes.
|
|
44
|
+
|
|
45
|
+
## Documentation and AI agents
|
|
46
|
+
|
|
47
|
+
- [Package overview](docs/index.md)
|
|
48
|
+
- [Routes](docs/routes.md)
|
|
49
|
+
- [Development](docs/development.md)
|
|
50
|
+
|
|
51
|
+
The package docs ship in the npm archive. User-facing guides live on the
|
|
52
|
+
[documentation site](https://github.com/marrrkkk/stacknext/tree/main/apps/docs).
|
|
53
|
+
Agents changing a route should read the package docs and the repository
|
|
54
|
+
[dashboard layout specification](https://github.com/marrrkkk/stacknext/blob/main/docs/architecture/dashboard-layout.md).
|
|
16
55
|
|
|
17
56
|
License: MIT.
|
|
@@ -104,4 +104,4 @@ instrumentPrismaClient(prisma, { databaseSystem: 'sqlite' })
|
|
|
104
104
|
|
|
105
105
|
export { prisma }`,yk=[{label:"Preferences",items:[{id:"appearance",label:"Appearance"},{id:"navigation",label:"Navigation"}]},{label:"Capture & storage",items:[{id:"configuration",label:"Configuration"},{id:"snapshots",label:"Snapshots"},{id:"data-safety",label:"Data safety"}]},{label:"Connections",items:[{id:"collector",label:"Collector"},{id:"instrumentation",label:"Instrumentation"},{id:"adapters",label:"Adapters"},{id:"mcp-server",label:"MCP server"}]},{label:"About",items:[{id:"limitations",label:"Limitations"}]}],_k=[{value:"dark",label:"Dark",icon:"moon"},{value:"light",label:"Light",icon:"sun"},{value:"system",label:"System",icon:"monitor"}],uc={dark:"light",light:"system",system:"dark"},xb={dark:"Dark",light:"Light",system:"System"};function dc(a){return a.split(/(withStackNextAction\(\))/g).map((l,r)=>l==="withStackNextAction()"?n.jsx("code",{children:l},r):l)}function Nk({refreshKey:a,theme:l,onThemeChange:r,onCycleTheme:o,onNavigate:d}){const f=vt(async m=>{const[h,v,x]=await Promise.all([ke.config(m),ke.health(m),ke.testRuns({limit:1},m).catch(()=>null)]);return{config:h,health:v,latestRun:x?.items[0]??null}},"settings",a,3e4);return f.loading&&!f.data?n.jsx(n.Fragment,{children:n.jsx(Wl,{title:"Settings",description:"Loading configuration…",children:n.jsx(Et,{rows:6})})}):f.error&&!f.data?n.jsx(n.Fragment,{children:n.jsx(Wl,{title:"Settings",children:n.jsx(pt,{message:f.error,onRetry:f.reload})})}):f.data?n.jsx(Sk,{data:f.data,refreshing:f.loading,onRefresh:f.reload,theme:l,onThemeChange:r,onCycleTheme:o,onNavigate:d}):n.jsx(Et,{})}function Sk({data:a,refreshing:l,onRefresh:r,theme:o,onThemeChange:d,onCycleTheme:f,onNavigate:m}){const{config:h,health:v}=a,x=v.connections.filter(Y=>Y.connected),b=v.instrumentation.nextOtelVerbose,g=["drizzle","prisma"].filter(Y=>h.config.database[Y]),[N,y]=w.useState(()=>g.includes("prisma")&&!g.includes("drizzle")?"prisma":"drizzle"),C=[h.config.snapshots.autoOnExit?"exit":null,h.config.snapshots.autoOnError?"error":null].filter(Y=>Y!==null),O=C.length>0?`${C.join(" and ")} · keep ${E(h.config.snapshots.autoKeep)}`:`keep ${E(h.config.snapshots.autoKeep)}`,D=h.config.snapshots.maxBytes===UE.snapshots.maxBytes,z=v.store.dropped.capacity+v.store.dropped.retention+v.server.droppedMalformed+v.server.droppedPayload+v.exporter.droppedSpans+v.exporter.droppedEvents,U=h.limitations,_=U.filter(Y=>/Next\.js built-in spans|Server Components|Server Actions/.test(Y)),R=[...U.filter(Y=>/Drizzle Logger/.test(Y)),"Database queries cannot be correlated when the driver runs outside the request async context."],S=U.filter(Y=>/Edge Runtime/.test(Y)),Q=new Set([..._,...R,...S]),A=U.filter(Y=>!Q.has(Y)),J=JSON.stringify(h.config,null,2),te=new Set(g);return n.jsxs(Wl,{eyebrow:h.projectName,title:"Settings",description:"Capture boundaries, snapshot storage, connections and dashboard preferences.",actions:n.jsxs(n.Fragment,{children:[n.jsxs("a",{className:"button button--secondary",href:"/api/export",download:!0,title:"JSON with the same redacted fields as the dashboard",children:[n.jsx(ne,{name:"download",size:15})," Export"]}),n.jsxs("button",{type:"button",className:"button button--secondary",onClick:r,disabled:l,"aria-busy":l,children:[n.jsx(ne,{name:"refresh",size:15,className:l?"button__spin":void 0}),l?"Refreshing…":"Refresh"]})]}),stats:n.jsxs(Jt,{three:!0,children:[n.jsx(de,{label:"Collector",value:v.connected?"connected":"waiting",tone:v.connected?"good":"warn",hint:n.jsxs("span",{title:Qs(v.uptimeMs),children:["v",v.version," · ",Ra(v.uptimeMs)]}),href:"#collector",ariaLabel:`Collector ${v.connected?"connected":"waiting"}; version ${v.version}; uptime ${Ra(v.uptimeMs)}. Jump to collector details.`,className:"stat--wrap"}),n.jsx(de,{label:"Snapshots",value:C.length>0?"Auto-save on":"Auto-save off",hint:O,href:"#snapshots",ariaLabel:`Snapshots auto-save ${C.length>0?`on ${C.join(" and ")}`:"off"}; keep ${E(h.config.snapshots.autoKeep)}. Jump to snapshot settings.`,className:"stat--wrap"}),n.jsx(de,{label:"Retention",value:`${E(v.store.limits.maxSpans)} spans`,hint:n.jsx("span",{title:Qs(v.store.limits.retentionMs),children:Ra(v.store.limits.retentionMs)}),href:"#configuration",ariaLabel:`Retention ${E(v.store.limits.maxSpans)} spans for ${Ra(v.store.limits.retentionMs)}. Jump to configuration.`,className:"stat--wrap"})]}),rail:n.jsxs(n.Fragment,{children:[n.jsx(mk,{groups:yk}),n.jsx(hk,{})]}),children:[n.jsx(oc,{label:"Preferences",children:n.jsxs("div",{className:"settings-duo",children:[n.jsx(ln,{id:"appearance",title:"Appearance",description:"Theme preference is stored in this browser",source:"Stored in this browser",children:n.jsxs("div",{className:"settings-panel",children:[n.jsxs("fieldset",{className:"theme-fieldset",children:[n.jsx("legend",{className:"sr-only",children:"Theme"}),n.jsx("div",{className:"theme-options",children:_k.map(Y=>{const W=o===Y.value;return n.jsxs("label",{className:W?"theme-option theme-option--active":"theme-option",children:[n.jsx("input",{type:"radio",name:"theme",value:Y.value,checked:W,onChange:()=>d(Y.value)}),n.jsx(ne,{name:Y.icon,size:18}),n.jsx("span",{className:"theme-option__label",children:Y.label}),W&&n.jsx(ne,{name:"check",size:14,className:"theme-option__check"})]},Y.value)})})]}),n.jsx("p",{className:"settings-note",children:"The dashboard follows the system theme by default and works fully offline."})]})}),n.jsx(ln,{id:"navigation",title:"Navigation",description:"Make the dashboard fit the way you move through it",children:n.jsxs("div",{className:"settings-group",children:[n.jsxs("div",{className:"settings-row",children:[n.jsxs("div",{className:"settings-row__body",children:[n.jsx("div",{className:"settings-row__label",children:"Command menu"}),n.jsx("div",{className:"settings-row__description",children:"Search and jump to any view, filtered list or action."})]}),n.jsx("div",{className:"settings-row__value",children:n.jsx("kbd",{className:"kbd",children:hr("K")})})]}),n.jsxs("div",{className:"settings-row",children:[n.jsxs("div",{className:"settings-row__body",children:[n.jsx("div",{className:"settings-row__label",children:"Cycle theme"}),n.jsx("div",{className:"settings-row__description",children:"Move between dark, light and system without opening this page."})]}),n.jsx("div",{className:"settings-row__value",children:n.jsxs("button",{type:"button",className:"button button--secondary button--small",onClick:f,"aria-label":`Switch to ${xb[uc[o]]} theme`,children:[n.jsx(ne,{name:uc[o]==="dark"?"moon":uc[o]==="light"?"sun":"monitor",size:14}),"Next: ",xb[uc[o]]]})})]})]})})]})}),n.jsxs(oc,{label:"Capture & storage",children:[n.jsx(ln,{id:"configuration",title:"Configuration",description:"Read from stacknext.config.json",source:"From stacknext.config.json",action:n.jsx(St,{value:J,label:"Copy config",small:!0}),children:n.jsx("div",{className:"settings-panel settings-panel--tight",children:n.jsx(rn,{entries:[{label:"Dashboard port",value:E(h.config.dashboard.port),numeric:!0},{label:"Max spans",value:E(h.config.collector.maxSpans),numeric:!0},{label:"Retention",value:Ra(h.config.collector.retentionMs),title:Qs(h.config.collector.retentionMs)},{label:"Slow threshold",value:Ra(h.config.slowRequestMs),title:Qs(h.config.slowRequestMs)},{label:"Snapshot auto-save",value:[h.config.snapshots.autoOnExit?"exit":null,h.config.snapshots.autoOnError?"first error":null].filter(Boolean).join(" · ")||"disabled"},{label:"Automatic snapshots kept",value:E(h.config.snapshots.autoKeep),numeric:!0},{label:"Snapshot size cap",value:`${qx(h.config.snapshots.maxBytes)}${D?" (default)":""}`,title:Lx(h.config.snapshots.maxBytes)},{label:"Capture fetch",value:h.config.instrumentation.captureFetch?"enabled":"disabled"},{label:"Capture errors",value:h.config.instrumentation.captureErrors?"enabled":"disabled"},{label:"Next.js spans",value:b.enabled?`verbose (${b.source})`:b.source==="unknown"?"NEXT_OTEL_VERBOSE has an unrecognized value":`standard (${b.source})`},{label:"Adapters",value:[h.config.database.drizzle?"Drizzle":null,h.config.database.prisma?"Prisma":null].filter(Boolean).join(", ")||"none enabled"}]})})}),n.jsx(ln,{id:"snapshots",title:"Snapshots",description:"Keep evidence after the in-memory retention window ends",source:"From stacknext.config.json",children:n.jsxs("div",{className:"settings-panel",children:[n.jsxs("div",{className:"snapshot-state",children:[n.jsxs("div",{className:"snapshot-state__row",children:[n.jsx("span",{children:"Auto-save on exit"}),n.jsx("strong",{children:h.config.snapshots.autoOnExit?"enabled":"disabled"})]}),n.jsxs("div",{className:"snapshot-state__row",children:[n.jsx("span",{children:"Auto-save on error"}),n.jsx("strong",{children:h.config.snapshots.autoOnError?"enabled · 60s cooldown":"disabled"})]}),n.jsxs("div",{className:"snapshot-state__row",children:[n.jsx("span",{children:"Automatic keep"}),n.jsx("strong",{children:ce(h.config.snapshots.autoKeep,"snapshot")})]}),n.jsxs("div",{className:"snapshot-state__row",children:[n.jsx("span",{children:"Per-snapshot cap"}),n.jsxs("strong",{title:Lx(h.config.snapshots.maxBytes),children:[qx(h.config.snapshots.maxBytes),D?" (default)":""]})]})]}),n.jsxs("div",{className:"snapshot-scope",children:[n.jsxs("div",{className:"snapshot-scope__row",children:[n.jsx("span",{className:"snapshot-scope__label",children:"Includes:"}),n.jsxs("span",{className:"chip-row",children:[n.jsx(ee,{tone:"muted",children:"Traces"}),n.jsx(ee,{tone:"muted",children:"Errors"}),n.jsx(ee,{tone:"muted",children:"Queries"})]})]}),n.jsxs("div",{className:"snapshot-scope__row",children:[n.jsx("span",{className:"snapshot-scope__label",children:"Not included:"}),n.jsxs("span",{className:"chip-row",children:[n.jsx(ee,{tone:"muted",children:"Diagnostics"}),n.jsx(ee,{tone:"muted",children:"Coverage"}),n.jsx(ee,{tone:"muted",children:"Playwright artifacts"})]})]})]}),n.jsx("div",{className:"settings-actions",children:n.jsxs("button",{type:"button",className:"button button--primary",onClick:()=>m("/snapshots"),children:[n.jsx(ne,{name:"layers",size:15})," Open snapshots"]})}),n.jsx(dk,{title:"How snapshots work",storageKey:"settings-snapshots-how",children:n.jsx("p",{className:"settings-note",children:"Snapshots are frozen, redacted copies of one collector sample. Automatic snapshots are written on a graceful exit and on the first server error, then rotated. Manual and imported snapshots are never auto-deleted."})})]})}),n.jsx(ln,{id:"data-safety",title:"Data safety",description:"What is stored, and what is not",children:n.jsxs("div",{className:"settings-panel settings-panel--tight",children:[n.jsx(cc,{title:"Not stored",tone:"good",children:n.jsx(ir,{items:[{tone:"good",text:"Request bodies are never captured"},{tone:"good",text:"Headers, cookies and authorization values are dropped"},{tone:"good",text:"URL query strings and SQL bind parameters are removed"}]})}),n.jsx(cc,{title:"Stays local",tone:"good",children:n.jsx(ir,{items:[{tone:"good",text:"Collector binds to 127.0.0.1 only"}]})}),n.jsx(cc,{title:"Stored in memory",tone:"warn",children:n.jsx(ir,{items:[{tone:"warn",text:"Stack traces and redacted SQL are stored in memory"},{tone:"warn",text:n.jsxs(n.Fragment,{children:["Data is bounded to"," ",ce(v.store.limits.maxSpans,"span")," and"," ",n.jsx("span",{title:Qs(v.store.limits.retentionMs),children:Ra(v.store.limits.retentionMs)})," ","of retention."]})}]})}),n.jsx(cc,{title:"Stored on disk",tone:"warn",children:n.jsx(ir,{items:[{tone:"warn",text:"Runs, baselines and comparisons are redacted files under .stacknext"},{tone:"warn",text:"Snapshots are redacted, frozen files under .stacknext/snapshots"}]})}),n.jsx("p",{className:"settings-note settings-note--footer",children:"Exports and snapshots are JSON and contain the same redacted fields as the dashboard."})]})})]}),n.jsxs(oc,{label:"Connections",children:[n.jsx(ln,{id:"collector",title:"Collector",description:"Local-only ingestion endpoint",source:"Live from collector",action:n.jsx(ee,{tone:v.connected?"good":"warn",children:v.connected?"connected":"waiting"}),children:n.jsx("div",{className:"settings-panel settings-panel--tight",children:n.jsx(rn,{entries:[{label:"Dashboard",value:n.jsx(Ks,{value:v.dashboardUrl})},{label:"Ingestion",value:n.jsx(Ks,{value:`${v.dashboardUrl}/__stacknext/v1/ingest`})},{label:"Version",value:`v${v.version}`,mono:!0},{label:"Uptime",value:Ra(v.uptimeMs),title:Qs(v.uptimeMs),numeric:!0},{label:"Connected apps",value:x.length>0?x.map(Y=>Y.serviceName).join(", "):"none yet"},{label:"Telemetry",value:`${ce(v.counts.spans,"span")} · ${ce(v.counts.errors,"error")} · ${ce(v.counts.databaseQueries,"query")}`,numeric:!0},{label:"Dropped events",value:ce(z,"event"),numeric:!0}]})})}),n.jsxs(ln,{id:"instrumentation",title:"Instrumentation",description:"Connections and exporter health",source:"Live from collector",children:[x.length===0?n.jsx("div",{className:"settings-panel",children:n.jsx(Ke,{compact:!0,icon:"server",title:"No app heartbeat",description:"Start Next.js with the StackNext instrumentation registered."})}):n.jsx("div",{className:"settings-group",children:x.map(Y=>{const W=Y.diagnostics,re=W?.droppedSpans??0,V=W?.queueDepth??0,se=[re>0?`${E(re)} dropped`:null,V>0?`${E(V)} queued`:null].filter(ve=>ve!==null);return n.jsxs("div",{className:"instrumentation-row",children:[n.jsxs("div",{className:"instrumentation-row__identity",children:[n.jsxs("div",{className:"settings-row__label",children:[Y.serviceName,se.length>0&&n.jsx(ee,{tone:"warn",children:se.join(" · ")})]}),n.jsxs("div",{className:"settings-row__description",children:[Y.nextVersion?`Next.js ${Y.nextVersion} · `:"","SDK ",Y.sdkVersion," · last seen"," ",n.jsx(Jf,{timestamp:Y.lastSeen})]})]}),n.jsxs("dl",{className:"instrumentation-figures",children:[n.jsxs("div",{children:[n.jsx("dt",{children:"Exported"}),n.jsx("dd",{children:E(W?.exportedSpans??0)})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Dropped"}),n.jsx("dd",{children:E(re)})]}),n.jsxs("div",{children:[n.jsx("dt",{children:"Queued"}),n.jsx("dd",{children:E(V)})]})]})]},Y.instanceId)})}),v.exporter.lastError&&n.jsx(bs,{tone:"warn",icon:"errors",children:v.exporter.lastError})]}),n.jsx(ln,{id:"adapters",title:"Adapters",description:"Database instrumentation snippets",children:n.jsxs("div",{className:"settings-panel",children:[n.jsx("div",{className:"segmented",role:"group","aria-label":"Choose a database adapter snippet",children:["drizzle","prisma"].map(Y=>n.jsxs("button",{type:"button",className:N===Y?"segmented__button is-active":"segmented__button","aria-pressed":N===Y,onClick:()=>y(Y),children:[Y==="drizzle"?"Drizzle":"Prisma",te.has(Y)&&n.jsx(ee,{tone:"good",className:"segmented__badge",children:"In use"})]},Y))}),n.jsx("p",{className:"settings-note",children:N==="drizzle"?"Duration is not exposed by the Drizzle Logger API":"Subscribes to Prisma query events with real durations"}),n.jsx(Kj,{label:N==="drizzle"?"Drizzle adapter · TypeScript":"Prisma adapter · TypeScript",code:N==="drizzle"?bk:jk,copyLabel:`Copy ${N==="drizzle"?"Drizzle":"Prisma"} adapter snippet`})]})}),n.jsx(ln,{id:"mcp-server",title:"MCP server",description:"Read-only stdio access for AI clients",children:n.jsxs("div",{className:"settings-panel settings-panel--split",children:[n.jsxs("div",{children:[n.jsxs("p",{className:"mcp-summary",children:[n.jsxs("span",{className:"mono",children:[gr.length," tools"]}),n.jsx("span",{"aria-hidden":"true",children:"·"}),n.jsx("span",{children:"read-only"}),n.jsx("span",{"aria-hidden":"true",children:"·"}),n.jsx("span",{className:"mono",children:"stdio"})]}),n.jsx("p",{className:"settings-note",children:"Connection snippets, the full tool catalog and Agent Skill installation commands live on the MCP page."})]}),n.jsxs("button",{type:"button",className:"button button--secondary",onClick:()=>m("/mcp"),children:[n.jsx(ne,{name:"terminal",size:15})," Open MCP setup"]})]})})]}),n.jsx(oc,{label:"About",children:n.jsx(ln,{id:"limitations",title:"Limitations",description:"Honest boundaries of StackNext 0.6",children:n.jsxs("div",{className:"settings-panel settings-panel--quiet",children:[n.jsxs("div",{className:"limitation-group",children:[n.jsx("h3",{children:"Tracing"}),n.jsx("ul",{className:"limitations",children:_.map(Y=>n.jsx("li",{children:dc(Y)},Y))})]}),n.jsxs("div",{className:"limitation-group",children:[n.jsx("h3",{children:"Database"}),n.jsx("ul",{className:"limitations",children:R.map(Y=>n.jsx("li",{children:dc(Y)},Y))})]}),n.jsxs("div",{className:"limitation-group",children:[n.jsx("h3",{children:"Runtime"}),n.jsx("ul",{className:"limitations",children:S.map(Y=>n.jsx("li",{children:dc(Y)},Y))})]}),A.length>0&&n.jsxs("div",{className:"limitation-group",children:[n.jsx("h3",{children:"Other"}),n.jsx("ul",{className:"limitations",children:A.map(Y=>n.jsx("li",{children:dc(Y)},Y))})]}),n.jsxs("div",{className:"about-footer",children:[n.jsxs("div",{className:"badge-row",children:[n.jsx(ee,{tone:"good",children:"local-first"}),n.jsx(ee,{tone:"accent",children:"MIT licensed"}),n.jsx(ee,{tone:"muted",children:"no accounts"}),n.jsx(ee,{tone:"muted",children:"no cloud"})]}),n.jsxs("span",{className:"about-version",children:["StackNext v",h.version]})]})]})})})]})}function wk({refreshKey:a,onNavigate:l}){const r=vt(async o=>{const[d,f]=await Promise.all([ke.config(o),ke.health(o)]);return{config:d,health:f}},"mcp",a,3e4);return r.loading&&!r.data?n.jsx(Wl,{title:"MCP server",description:"Loading MCP status…",children:n.jsx(Et,{rows:6})}):r.error&&!r.data?n.jsx(Wl,{title:"MCP server",children:n.jsx(pt,{message:r.error,onRetry:r.reload})}):r.data?n.jsx(Tk,{data:r.data,refreshing:r.loading,onRefresh:r.reload,onNavigate:l}):n.jsx(Et,{})}function Tk({data:a,refreshing:l,onRefresh:r,onNavigate:o}){const{config:d,health:f}=a,m=f.connections.filter(O=>O.connected),h=m.length>0?Math.max(...m.map(O=>O.lastSeen)):null,v=Fj(),x=gr.length,[b,g]=w.useState(""),[N,y]=w.useState("all"),C=gk(gr,b,N);return n.jsxs(Wl,{eyebrow:d.projectName,title:"MCP server",description:"Local, read-only Model Context Protocol server over stdio for telemetry, diagnostics, regression records and snapshots.",railWhenNarrow:"end",actions:n.jsxs("button",{type:"button",className:"button button--secondary",onClick:r,disabled:l,"aria-busy":l,children:[n.jsx(ne,{name:"refresh",size:15,className:l?"button__spin":void 0}),l?"Refreshing…":"Refresh"]}),stats:n.jsxs(Jt,{children:[n.jsx(de,{label:"Tools",value:E(x),hint:gb(),className:"stat--wrap",ariaLabel:`Tools: ${E(x)}. ${gb()}.`}),n.jsx(de,{label:"Collector",value:f.connected?"connected":"waiting",tone:f.connected?"good":"warn",hint:h?n.jsxs(n.Fragment,{children:["Last heartbeat ",n.jsx(Jf,{timestamp:h})]}):"No app heartbeat",className:"stat--wrap"}),n.jsx(de,{label:"Telemetry",value:ce(f.counts.spans,"span"),hint:`${ce(f.counts.errors,"error")} · ${ce(f.counts.databaseQueries,"query")}`,className:"stat--wrap"}),n.jsx(de,{label:"Uptime",value:n.jsx("span",{title:Qs(f.uptimeMs),children:Ra(f.uptimeMs)}),hint:`collector v${f.version}`,className:"stat--wrap"})]}),rail:n.jsxs(n.Fragment,{children:[n.jsx(ln,{id:"collector-rail",title:"Collector",description:"Where the MCP server reads from",action:n.jsx(ee,{tone:f.connected?"good":"warn",children:f.connected?"connected":"waiting"}),className:"settings-section--rail",children:n.jsxs("div",{className:"settings-panel settings-panel--tight",children:[n.jsx(rn,{entries:[{label:"Last heartbeat",value:h?n.jsx(Jf,{timestamp:h}):"no heartbeat"},{label:"Dashboard",value:n.jsx(Ks,{value:f.dashboardUrl})},{label:"Ingestion",value:n.jsx(Ks,{value:`${f.dashboardUrl}/__stacknext/v1/ingest`})},{label:"Connected apps",value:m.length>0?m.map(O=>O.serviceName).join(", "):"none yet"}]}),n.jsxs("button",{type:"button",className:"button button--secondary button--small",onClick:()=>o("/settings#collector"),children:[n.jsx(ne,{name:"settings",size:14})," Open collector settings"]})]})}),n.jsx(ln,{id:"safety-boundaries",title:"Safety boundaries",description:"What the MCP server will never do",className:"settings-section--rail",children:n.jsx("div",{className:"settings-panel settings-panel--tight",children:n.jsx(ir,{label:"MCP safety boundaries",items:[{tone:"good",text:"No tool can mutate telemetry, write, delete, capture, compare, run SQL, edit source or change configuration"},{tone:"good",text:"Only GET requests to a loopback collector; non-loopback URLs are refused"},{tone:"good",text:"stdout is protocol-only; diagnostics go to stderr"},{tone:"good",text:"Responses are bounded, paginated and re-sanitized"},{tone:"good",text:"Snapshots are frozen evidence, never replayed into the live store"},{tone:"good",text:"Prompts and the Agent Skill cannot add capabilities"}]})})})]}),children:[n.jsxs(bs,{tone:"good",icon:"shield",children:[n.jsxs("span",{children:[n.jsx("strong",{children:"Read-only by construction:"})," every tool declares"," ",n.jsx("code",{children:"readOnlyHint: true"}),"; GET requests only, against a loopback collector."]})," ",n.jsx("a",{className:"link",href:"#safety-boundaries",children:"Safety boundaries"})]}),n.jsx(ln,{id:"connect",title:"Connect a client",description:"Add StackNext to any MCP-capable client on this machine",children:n.jsx("div",{className:"settings-panel",children:n.jsxs("ol",{className:"setup-steps",children:[n.jsxs("li",{className:"setup-step",children:[n.jsx("div",{className:"setup-step__marker","aria-hidden":"true",children:f.connected?n.jsx(ne,{name:"check",size:14}):"1"}),n.jsxs("div",{className:"setup-step__body",children:[n.jsxs("div",{className:"setup-step__title",children:["Start the app",n.jsx(ee,{tone:f.connected?"good":"warn",children:f.connected?"Done":"Waiting"})]}),n.jsxs("p",{className:"settings-note",children:["Start the app with ",n.jsx(Ks,{value:"stacknext dev"})," so the collector and bearer token exist."]})]})]}),n.jsxs("li",{className:"setup-step",children:[n.jsx("div",{className:"setup-step__marker","aria-hidden":"true",children:"2"}),n.jsxs("div",{className:"setup-step__body",children:[n.jsx("div",{className:"setup-step__title",children:"Add the server entry"}),n.jsxs("p",{className:"settings-note",children:["Add the server entry below to your MCP client configuration. Point"," ",n.jsx("code",{children:"cwd"})," at the Next.js app directory."]}),n.jsx(Kj,{label:"MCP client config · JSON",code:pb,copyLabel:"Copy MCP config",children:pb.split(`
|
|
106
106
|
`).map((O,D,z)=>{const U=O.split(vb);return n.jsxs("span",{children:[U[0],U.length>1&&n.jsx("mark",{className:"code-highlight",title:"Edit this value",children:vb}),U[1],D<z.length-1?`
|
|
107
|
-
`:""]},D)})}),n.jsxs("p",{className:"settings-note",children:["The server reads the bearer token from"," ",n.jsx("code",{children:".stacknext/run.json"}),". The token stays on this machine and is never printed by the MCP server."]})]})]}),n.jsxs("li",{className:"setup-step",children:[n.jsx("div",{className:"setup-step__marker","aria-hidden":"true",children:"3"}),n.jsxs("div",{className:"setup-step__body",children:[n.jsx("div",{className:"setup-step__title",children:"Ask for a summary"}),n.jsxs("p",{className:"settings-note",children:["Restart the client and ask it to call"," ",n.jsx(Ks,{value:"get_project_summary"})," first."]})]})]})]})})}),n.jsx(ln,{id:"agent-skill",title:"Agent skill",description:"Portable investigation guidance for supported clients",action:n.jsx(St,{value:xk,label:"Copy all",small:!0}),children:n.jsxs("div",{className:"settings-panel",children:[n.jsx("p",{className:"settings-note settings-note--prose",children:"The bundled Agent Skill teaches an AI client the evidence-first StackNext workflow: start with coverage, read the sample window, use diagnostics as hypotheses, and keep snapshot evidence separate from live telemetry."}),n.jsx("ul",{className:"skill-clients",children:vk.map(O=>n.jsxs("li",{className:"skill-client",children:[n.jsx("div",{className:"skill-client__name",children:O.name}),n.jsx("code",{className:"skill-client__command",children:O.command}),n.jsx(St,{value:O.command,label:`Copy ${O.name}`,small:!0})]},O.name))}),n.jsxs("p",{className:"settings-note",children:["Client support is only claimed after a real end-to-end test. See"," ",n.jsx("code",{children:"docs/agent-testing.md"})," in the repository."]})]})}),n.jsx(ln,{id:"tools",title:"Tool catalog",description:`All ${E(x)} tools are read-only and scoped to the running collector`,children:n.jsxs("div",{className:"settings-panel settings-panel--flush tool-catalog",children:[n.jsxs("p",{className:"tool-catalog__note",children:["Tool names are shown exactly as the server registers them. Live tools are unprefixed; diagnostics, regression and snapshot tools use the"," ",n.jsx("code",{children:"stacknext_"})," prefix."]}),n.jsxs("div",{className:"tool-catalog__toolbar",children:[n.jsx(Rn,{value:b,onChange:g,placeholder:"Search tools and purposes…",label:"Search MCP tools"}),n.jsxs("div",{className:"tool-filters",role:"group","aria-label":"Filter tools by group",children:[n.jsxs("button",{type:"button",className:N==="all"?"filter-chip is-active":"filter-chip","aria-pressed":N==="all",onClick:()=>y("all"),children:["All ",n.jsx("span",{className:"tool-filters__count",children:x})]}),v.map(O=>n.jsxs("button",{type:"button",className:N===O.id?"filter-chip is-active":"filter-chip","aria-pressed":N===O.id,onClick:()=>y(O.id),children:[O.label," ",n.jsx("span",{className:"tool-filters__count",children:O.count})]},O.id))]}),n.jsxs("span",{className:"sr-only",role:"status","aria-live":"polite",children:[E(C.length)," of ",E(x)," ","tools shown"]})]}),n.jsx("div",{className:"table-scroll data-table-scroll tool-catalog__scroll",role:"region","aria-label":"MCP tools",tabIndex:0,children:n.jsxs("table",{className:"table tool-table",children:[n.jsx("caption",{className:"sr-only",children:"MCP tools"}),n.jsx("thead",{children:n.jsxs("tr",{children:[n.jsx("th",{scope:"col",className:"tool-table__name",children:"Tool"}),n.jsx("th",{scope:"col",children:"Purpose"})]})}),ym.map(O=>{const D=C.filter(_=>_.group===O.id);if(D.length===0)return null;const z=v.find(_=>_.id===O.id)?.count??0,U=D.length===z;return n.jsxs("tbody",{children:[n.jsx("tr",{className:"tool-group-row",children:n.jsxs("th",{scope:"colgroup",colSpan:2,children:[O.label,n.jsx("span",{children:U?E(z):`${E(D.length)} of ${E(z)}`})]})}),D.map(_=>n.jsxs("tr",{children:[n.jsx("th",{scope:"row",className:"tool-table__name",children:n.jsx(Ks,{value:_.name,label:"Copy tool name",children:_.name})}),n.jsx("td",{className:"tool-table__purpose",children:_.purpose})]},_.name))]},O.id)}),C.length===0&&n.jsx("tbody",{children:n.jsx("tr",{children:n.jsx("td",{colSpan:2,children:n.jsx(Ke,{compact:!0,icon:"search",title:"No tools match",description:"Try a different name or clear the group filter."})})})})]})})]})})]})}function Ek(a){if(a<=0)return"Sample expired";if(a<6e4)return`Expires in ${Math.max(1,Math.ceil(a/1e3))}s`;if(a<36e5)return`Expires in ${Math.ceil(a/6e4)}m`;const l=Math.floor(a/36e5),r=Math.ceil(a%36e5/6e4);return`Expires in ${l}h ${r}m`}function Ck({health:a,onNavigate:l}){const[r,o]=w.useState(()=>Date.now()),[d,f]=w.useState(!1),[m,h]=w.useState(""),[v,x]=w.useState(!1),[b,g]=w.useState(null),[N,y]=w.useState(null),C=w.useRef(null),O=w.useRef(null),D=w.useRef(null);if(w.useEffect(()=>{const W=window.setInterval(()=>o(Date.now()),1e3);return()=>window.clearInterval(W)},[]),w.useEffect(()=>{if(!d)return;D.current=document.activeElement instanceof HTMLElement?document.activeElement:null;const W=window.requestAnimationFrame(()=>C.current?.focus()),re=document.querySelector(".shell"),V=re?.hasAttribute("inert")??!1;re&&!V&&re.setAttribute("inert","");const se=()=>Array.from(O.current?.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')??[]).filter(K=>K.tabIndex>=0),ve=K=>{if(K.key==="Escape"){K.preventDefault(),f(!1);return}if(K.key!=="Tab")return;const oe=se();if(oe.length===0){K.preventDefault(),O.current?.focus();return}const he=oe[0],Re=oe[oe.length-1];K.shiftKey&&document.activeElement===he?(K.preventDefault(),Re?.focus()):!K.shiftKey&&document.activeElement===Re&&(K.preventDefault(),he?.focus())};return window.addEventListener("keydown",ve),()=>{window.cancelAnimationFrame(W),window.removeEventListener("keydown",ve),re&&!V&&re.removeAttribute("inert"),D.current?.isConnected&&D.current.focus()}},[d]),w.useEffect(()=>{!d||!v&&!b||O.current?.focus()},[d,b,v]),!a)return null;const z=a.sample,U=z.lastEventTime,_=z.lastReceivedAt,R=_===null?null:_+z.retentionMs,S=R===null?null:R-r,Q=a.counts.spans+a.counts.errors+a.counts.databaseQueries>0,A=S!==null&&S<=6e4,J=()=>{g(null),y(null),f(!0)},te=async()=>{x(!0),y(null);try{const W=await ke.saveSnapshot(m.trim()||void 0);g(W),h("")}catch(W){y(W instanceof Error?W.message:String(W))}finally{x(!1)}},Y=W=>{f(!1),l(W)};return n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"topbar__sample",children:[n.jsxs("span",{className:A?"sample-countdown sample-countdown--urgent":"sample-countdown",title:R===null?"No events in the current sample yet":`Last event ${Ie(U)} · retention ${Ie(R)}`,children:[n.jsx(ne,{name:"clock",size:13}),S===null?"No live sample":Ek(S)]}),n.jsxs("button",{type:"button",className:"button button--secondary button--small topbar__save-sample",onClick:J,disabled:!Q,title:Q?"Save the current sample as a frozen local snapshot":"No telemetry in the current sample to save",children:[n.jsx(ne,{name:"layers",size:14})," Save sample"]})]}),d&&Ps.createPortal(n.jsx("div",{className:"command-overlay",onMouseDown:W=>{W.target===W.currentTarget&&f(!1)},children:n.jsxs("div",{ref:O,className:"command-dialog sample-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"sample-dialog-title",tabIndex:-1,children:[n.jsxs("header",{className:"sample-dialog__head",children:[n.jsxs("div",{children:[n.jsx("h2",{className:"sample-dialog__title",id:"sample-dialog-title",children:b?"Snapshot saved":"Save current sample"}),n.jsx("p",{className:"sample-dialog__subtitle",children:b?"The snapshot is now stored under .stacknext/snapshots.":"Freeze the current in-memory telemetry before the retention window expires."})]}),n.jsx("button",{type:"button",className:"button button--ghost button--icon button--compact",onClick:()=>f(!1),"aria-label":"Close save snapshot dialog",children:n.jsx(ne,{name:"close",size:16})})]}),n.jsx("div",{className:"sample-dialog__body",children:b?n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"sample-dialog__success",children:[n.jsx(ne,{name:"check",size:16}),n.jsxs("div",{children:[n.jsx("div",{className:"mono",children:b.id}),n.jsxs("div",{className:"muted",children:[E(b.stats.requests)," requests ·"," ",E(b.stats.spans)," spans ·"," ",E(b.stats.errors)," errors ·"," ",E(b.stats.databaseQueries)," queries"]})]})]}),n.jsxs("div",{className:"sample-dialog__actions",children:[n.jsx("button",{type:"button",className:"button button--primary",onClick:()=>Y(`/snapshots/${b.id}`),children:"Open snapshot"}),n.jsx("button",{type:"button",className:"button button--secondary",onClick:()=>Y("/snapshots"),children:"View all snapshots"}),n.jsx("button",{type:"button",className:"button button--ghost",onClick:()=>f(!1),children:"Done"})]})]}):n.jsxs(n.Fragment,{children:[n.jsxs("label",{className:"sample-dialog__field",children:[n.jsx("span",{children:"Label"}),n.jsx("input",{ref:C,type:"text",value:m,maxLength:200,placeholder:"e.g. before index change",onChange:W=>h(W.target.value),onKeyDown:W=>{W.key==="Enter"&&!v&&te()}})]}),n.jsx("p",{className:"muted",children:"Optional. Manual snapshots are never deleted by automatic rotation. The snapshot is redacted and frozen; it never replaces live telemetry."}),N&&n.jsxs("div",{className:"sample-dialog__error",role:"alert",children:[n.jsx(ne,{name:"errors",size:15})," ",N]}),n.jsxs("div",{className:"sample-dialog__actions",children:[n.jsxs("button",{type:"button",className:"button button--primary",onClick:()=>{te()},disabled:v,"aria-busy":v||void 0,children:[n.jsx(ne,{name:v?"refresh":"layers",size:15,className:v?"icon-button__spin":void 0}),v?"Saving…":"Save snapshot"]}),n.jsx("button",{type:"button",className:"button button--ghost",onClick:()=>f(!1),disabled:v,children:"Cancel"})]})]})})]})}),document.body)]})}function bb(a){return a instanceof Element&&!!a.closest(".table tbody tr, .timeline__row, .latency-chart__item, .list-row, .coverage-table tbody tr")}const zk=[{id:"observe",label:"Observe",items:[{label:"Overview",path:"/",icon:"overview",match:a=>a==="/"||a===""},{label:"Traces",path:"/traces",icon:"traces",match:a=>a.startsWith("/traces")},{label:"Database",path:"/database",icon:"database",match:a=>a.startsWith("/database")},{label:"Snapshots",path:"/snapshots",icon:"layers",match:a=>a.startsWith("/snapshots")}]},{id:"investigate",label:"Investigate",items:[{label:"Errors",path:"/errors",icon:"errors",match:a=>a.startsWith("/errors")},{label:"Diagnostics",path:"/diagnostics",icon:"pulse",match:a=>a.startsWith("/diagnostics")}]},{id:"test",label:"Test",items:[{label:"Test runs",path:"/runs",icon:"terminal",match:a=>a.startsWith("/runs")},{label:"Baselines",path:"/baselines",icon:"flag",match:a=>a.startsWith("/baselines")},{label:"Comparisons",path:"/comparisons",icon:"compare",match:a=>a.startsWith("/comparisons")}]},{id:"configure",label:"Configure",items:[{label:"Settings",path:"/settings",icon:"settings",match:a=>a.startsWith("/settings")},{label:"MCP server",path:"/mcp",icon:"terminal",match:a=>a.startsWith("/mcp")}]}],kk={dark:"moon",light:"sun",system:"monitor"};function Ok(a){return a==="dark"?"light":a==="light"?"system":"dark"}const Xj="stacknext-errors-seen",Pf=new Set;let Oc,Wf=null;function Mk(a){if(!a)return null;try{const l=JSON.parse(a);if(l&&typeof l.id=="string"&&typeof l.timestamp=="number"&&Number.isFinite(l.timestamp))return{id:l.id,timestamp:l.timestamp}}catch{}return null}function jb(){let a=null;try{a=window.localStorage.getItem(Xj)}catch{a=null}return a!==Oc&&(Oc=a,Wf=Mk(a)),Wf}function Ak(a){return Pf.add(a),()=>Pf.delete(a)}function Rk(a){const l=JSON.stringify(a);try{window.localStorage.setItem(Xj,l),Oc=l}catch{Oc=null}Wf=a;for(const r of Pf)r()}function yb(a,l){return a.timestamp>l.timestamp||a.timestamp===l.timestamp&&a.id!==l.id}function $k({onNavigate:a}){return n.jsxs("div",{className:"not-found",children:[n.jsx("div",{className:"not-found__code",children:"404"}),n.jsx("h1",{children:"This view does not exist"}),n.jsxs("p",{className:"muted",children:["The dashboard has Overview, Requests, Traces, Database, Snapshots, Errors, Diagnostics, Test runs, Baselines, Comparisons, Settings and MCP server. Press"," ",n.jsx(ms,{children:hr("K")})," to jump to any view."]}),n.jsx("button",{type:"button",className:"button button--primary",onClick:()=>a("/"),children:"Back to overview"})]})}function Dk({health:a}){if(!a)return n.jsxs("div",{className:"sidebar__connection",children:[n.jsx("div",{className:"sidebar__row",children:n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:"connecting"}),"Connecting…"]})}),n.jsx("div",{className:"sidebar__row-meta",children:"local collector"})]});const l=a.connections.filter(r=>r.connected);return n.jsxs("div",{className:"sidebar__connection",children:[n.jsx("div",{className:"sidebar__row",children:n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:a.connected?"live":"connecting"}),a.connected?l[0]?.serviceName??"App connected":"Waiting for app"]})}),n.jsxs("div",{className:"sidebar__row-meta",children:[l[0]?.nextVersion?`Next.js ${l[0].nextVersion} · `:"",a.connected?`${E(a.counts.spans)} spans · ${xe(a.uptimeMs)} up`:"make a request to start a trace"]})]})}function pc(a){return yC(a)??a}function _b(a){return vm(a.split("?")[0]??"/").page==="traces"}function qk(){const a=`${window.location.pathname}${window.location.search}${window.location.hash}`,l=pc(a);return l!==a&&window.history.replaceState({},"",l),l}function Lk(){const[a,l]=w.useState(qk),[r,o]=w.useState(0),[d,f]=w.useState(!1),[m,h]=w.useState(()=>localStorage.getItem("stacknext-sidebar-collapsed")==="1"),[v,x]=w.useState(!1),[b,g]=w.useState(()=>window.matchMedia("(max-width: 900px)").matches),N=w.useRef(null),y=w.useRef(null),C=w.useRef(null),O=w.useRef(null),D=w.useRef(!1),z=w.useRef(null),U=w.useRef(null),_=bC(),[R,S]=w.useState(0),[Q,A]=w.useState(null),J=w.useSyncExternalStore(Ak,jb,jb),te=w.useRef(0),Y=w.useRef(0),W=w.useRef(_.sequence),re=w.useRef({x:-1,y:-1}),V=w.useCallback(()=>{te.current<=Y.current||(Y.current=te.current,S(te.current))},[]),{theme:se,setTheme:ve}=jC(),K=Q!==null,oe=K?Math.max(0,_.sequence-Q):0,he=w.useCallback(()=>{if(Q!==null){A(null),V();return}A(_.sequence)},[V,_.sequence,Q]);w.useEffect(()=>{if(_.sequence===W.current||(W.current=_.sequence,te.current+=1,Q!==null))return;const{x:Ne,y:Te}=re.current,Ze=Ne>=0?document.elementFromPoint(Ne,Te):null;bb(Ze)||V()},[_.sequence,Q,V]),w.useEffect(()=>{if(Q!==null)return;const Ne=Ze=>{re.current={x:Ze.clientX,y:Ze.clientY},bb(Ze.target)||V()},Te=Ze=>{Ze.relatedTarget===null&&(re.current={x:-1,y:-1},V())};return window.addEventListener("pointermove",Ne,{passive:!0}),window.addEventListener("pointerout",Te),()=>{window.removeEventListener("pointermove",Ne),window.removeEventListener("pointerout",Te)}},[V,Q]);const[Re,De]=w.useState(null),ue=w.useMemo(()=>vm(a.split("?")[0]??"/"),[a]),Ut=vt(Ne=>ke.health(Ne),"shell-health",0,1e4),Zt=vt(Ne=>ke.errors({limit:1},Ne),"shell-latest-error",0,1e4),M=w.useMemo(()=>{const Ne=Zt.data?.items[0];return Ne?{id:Ne.id,timestamp:Ne.timestamp}:null},[Zt.data]);w.useEffect(()=>{M&&(ue.page!=="errors"&&ue.page!=="error"||J&&!yb(M,J)||Rk(M))},[M,ue.page,J]),w.useEffect(()=>{const Ne=()=>{f(!1),A(null),V();const Te=`${window.location.pathname}${window.location.search}${window.location.hash}`,Ze=pc(Te);Ze!==Te&&window.history.replaceState({},"",Ze),l(Ze),_b(Ze)&&o(cn=>cn+1)};return window.addEventListener("popstate",Ne),()=>window.removeEventListener("popstate",Ne)},[V]),w.useEffect(()=>{if(!d)return;y.current?.focus();const Ne=Te=>{Te.key==="Escape"&&f(!1)};return window.addEventListener("keydown",Ne),()=>window.removeEventListener("keydown",Ne)},[d]),w.useEffect(()=>{localStorage.setItem("stacknext-sidebar-collapsed",m?"1":"0")},[m]),w.useEffect(()=>{const Ne=window.matchMedia("(max-width: 900px)"),Te=()=>{g(Ne.matches),Ne.matches||f(!1)};return Te(),Ne.addEventListener("change",Te),()=>Ne.removeEventListener("change",Te)},[]);const H=w.useRef(!1);w.useEffect(()=>{if(d){H.current=!0;return}H.current&&(H.current=!1,N.current?.offsetParent?N.current.focus():(document.querySelector(".nav-item--active")??document.querySelector(".sidebar__logo"))?.focus())},[d]);const le=()=>{f(!1)},fe=w.useCallback(()=>{f(Ne=>!Ne)},[]),ze=w.useCallback(()=>{D.current=!0,h(!0)},[]),Ae=w.useCallback(()=>{D.current=!0,h(!1)},[]);w.useEffect(()=>{if(!D.current)return;D.current=!1,(m?O.current:C.current)?.focus()},[m]);const qe=w.useCallback(()=>{ve(Ok(se))},[ve,se]);w.useEffect(()=>{const Ne=Te=>{if(!(Te.metaKey||Te.ctrlKey)||Te.altKey||Te.defaultPrevented)return;Te.key.toLowerCase()==="k"&&(Te.preventDefault(),v||f(!1),x(!v))};return window.addEventListener("keydown",Ne),()=>window.removeEventListener("keydown",Ne)},[v]);const ae=Ne=>{const Te=pc(Ne);Te!==`${window.location.pathname}${window.location.search}${window.location.hash}`&&(f(!1),x(!1),Q!==null&&A(null),V(),window.history.pushState({},"",Te),l(Te),_b(Te)&&o(Ze=>Ze+1))},ye=w.useCallback(Ne=>{const Te=pc(Ne);Te!==`${window.location.pathname}${window.location.search}`&&(window.history.replaceState({},"",Te),l(Te))},[]);w.useLayoutEffect(()=>{const Ne=a.indexOf("#"),Te=Ne>=0?a.slice(Ne+1):"";if(!Te){U.current?.scrollTo({top:0});return}const Ze=()=>{const qa=document.getElementById(Te);return qa?(qa.scrollIntoView({block:"start"}),!0):!1};if(Ze())return;const cn=U.current;if(!cn||typeof MutationObserver>"u")return;let la=0;const Kn=new MutationObserver(()=>{Ze()&&(Kn.disconnect(),window.clearTimeout(la))});return Kn.observe(cn,{childList:!0,subtree:!0}),la=window.setTimeout(()=>Kn.disconnect(),5e3),()=>{Kn.disconnect(),window.clearTimeout(la)}},[a]);const Ct=Ut.data,Qe=ue.page,Pt=Ct?.projectName??"StackNext",Bt=Ct?.counts.errors??0,jt=M!==null&&(J===null||yb(M,J))&&Qe!=="errors"&&Qe!=="error",Pe=w.useCallback(Ne=>{De(Ne)},[]),Ht=w.useMemo(()=>({breadcrumb:Re,projectName:Pt}),[Re,Pt]),Dn=m&&!b;return n.jsxs("div",{className:_e("app",d&&"app--sidebar-open",Dn&&"app--sidebar-collapsed"),children:[n.jsx("a",{className:"skip-link",href:"#main-content",children:"Skip to content"}),n.jsxs("div",{className:"shell",children:[n.jsxs("aside",{className:"sidebar",id:"app-sidebar",children:[n.jsxs("div",{className:"sidebar__brand",children:[Dn?n.jsx("button",{type:"button",className:"sidebar__expand button button--ghost button--icon button--compact",onClick:Ae,"aria-label":"Expand sidebar","aria-expanded":!1,"aria-controls":"app-sidebar",title:"Expand sidebar",ref:O,children:n.jsx(ne,{name:"panel-left",size:16})}):n.jsxs("button",{type:"button",className:"sidebar__logo",onClick:()=>ae("/"),"aria-label":"StackNext overview",children:[n.jsx("span",{className:"logo-mark",children:n.jsx(_C,{size:20})}),n.jsxs("span",{className:"sidebar__brand-text",children:[n.jsx("span",{className:"sidebar__brand-name",children:"StackNext"}),n.jsx("span",{className:"sidebar__brand-sub",children:Pt})]})]}),n.jsxs("div",{className:"sidebar__brand-actions",children:[n.jsx("button",{type:"button",className:"sidebar__collapse button button--ghost button--icon button--compact",onClick:ze,"aria-label":"Collapse sidebar","aria-expanded":!0,"aria-controls":"app-sidebar",title:"Collapse sidebar",ref:C,children:n.jsx(ne,{name:"panel-left",size:15})}),n.jsx("button",{type:"button",className:"sidebar__close button button--ghost button--icon",onClick:le,"aria-label":"Close navigation",ref:y,children:n.jsx(ne,{name:"close",size:16})})]})]}),n.jsx("nav",{className:"sidebar__nav","aria-label":"Primary",children:zk.map(Ne=>n.jsxs("div",{className:"nav-group",children:[n.jsx("div",{className:"nav-group__label","aria-hidden":"true",children:Ne.label}),Ne.items.map(Te=>{const Ze=Te.match(a.split("?")[0]??"/");return n.jsxs("button",{type:"button",className:_e("nav-item",Ze&&"nav-item--active"),onClick:()=>ae(Te.path),"aria-current":Ze?"page":void 0,title:Dn?Te.label:void 0,children:[n.jsx(ne,{name:Te.icon,size:16}),n.jsx("span",{className:"nav-item__label",children:Te.label}),Te.path==="/errors"&&Bt>0&&n.jsx("span",{className:_e("nav-item__count",jt&&"nav-item__count--unseen"),title:jt?"New errors since your last visit":`${Bt} errors`,children:Bt})]},Te.path)})]},Ne.id))}),n.jsx("div",{className:"sidebar__spacer"}),n.jsxs("div",{className:"sidebar__live",children:[n.jsxs("div",{className:"sidebar__row",children:[n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:_.status}),n.jsx("span",{className:"sidebar__row-text",children:_.status==="live"?"Live":_.status})]}),_.lastEventAt&&n.jsx("span",{className:"sidebar__row-time",children:Ge(_.lastEventAt)})]}),n.jsx("p",{className:"sidebar__hint",children:"Local SSE feed. Nothing leaves this machine."})]}),n.jsx(Dk,{health:Ct}),n.jsx("div",{className:"sidebar__footer",children:n.jsxs("div",{className:"sidebar__footer-row",children:[n.jsxs("span",{className:"mono sidebar__version",children:["v",Ct?.version??"0.6.0"]}),n.jsxs("div",{className:"sidebar__footer-actions",children:[n.jsx("button",{type:"button",className:"button button--ghost button--icon button--compact",onClick:qe,title:`Theme: ${se}`,"aria-label":`Theme: ${se}. Switch theme`,children:n.jsx(ne,{name:kk[se],size:14})}),n.jsx("a",{className:"button button--ghost button--icon button--compact",href:"https://github.com/marrrkkk/stacknext",target:"_blank",rel:"noreferrer","aria-label":"StackNext on GitHub",title:"Docs and source",children:n.jsx(ne,{name:"code",size:14})})]})]})})]}),d&&n.jsx("button",{type:"button",className:"sidebar-scrim",onClick:le,"aria-label":"Close navigation"}),n.jsxs("div",{className:"main",inert:d||v||void 0,children:[n.jsxs("header",{className:"topbar",children:[n.jsx("button",{type:"button",className:"button button--ghost button--icon topbar__sidebar-toggle",onClick:fe,"aria-label":d?"Close navigation":"Open navigation","aria-expanded":d,"aria-controls":"app-sidebar",title:"Toggle navigation",ref:N,children:n.jsx(ne,{name:"panel-left",size:16})}),n.jsxs("div",{className:"topbar__breadcrumb",children:[n.jsx("button",{type:"button",className:"topbar__project topbar__project--link",onClick:()=>ae("/"),title:`Open ${Pt} overview`,children:Pt}),n.jsx(ne,{name:"chevron-right",size:14,className:"topbar__separator"}),n.jsx("div",{className:"topbar__slot",ref:Pe})]}),n.jsx(Ck,{health:Ct,onNavigate:ae}),n.jsxs("button",{type:"button",className:"topbar__command",ref:z,onClick:()=>x(!0),"aria-label":"Search views and actions",title:`Search or jump to (${hr("K")})`,children:[n.jsx(ne,{name:"search",size:14}),n.jsx("span",{className:"topbar__command-label",children:"Search or jump to…"}),n.jsx(ms,{children:hr("K")})]})]}),n.jsx(Aj.Provider,{value:Ht,children:n.jsxs("main",{className:"content",id:"main-content",ref:U,tabIndex:-1,children:[Qe==="overview"&&n.jsx(QC,{refreshKey:R,onNavigate:ae,liveStatus:_.status,paused:K,pendingLive:oe,onTogglePause:he}),Qe==="traces"&&n.jsx(eb,{refreshKey:R,onNavigate:ae,onReplaceUrl:ye,slowThresholdMs:Ct?.config.slowRequestMs??250},r),Qe==="trace"&&ue.id&&n.jsx(Wx,{traceId:ue.id,refreshKey:R,onNavigate:ae},`trace:${ue.id}`),Qe==="redirect"&&null,Qe==="database"&&n.jsx(tb,{refreshKey:R,onNavigate:ae}),Qe==="database-query"&&ue.id&&n.jsx(Mz,{shapeId:ue.id,refreshKey:R,onNavigate:ae},`database-shape:${ue.id}`),Qe==="errors"&&n.jsx(ib,{refreshKey:R,onNavigate:ae}),Qe==="error"&&ue.id&&n.jsx(rb,{errorId:ue.id,refreshKey:R,onNavigate:ae}),Qe==="diagnostics"&&n.jsx(Iz,{refreshKey:R,onNavigate:ae}),Qe==="diagnostic"&&ue.id&&n.jsx(Fz,{diagnosticId:ue.id,refreshKey:R,onNavigate:ae}),Qe==="runs"&&n.jsx(Xz,{refreshKey:R,onNavigate:ae}),Qe==="run"&&ue.id&&n.jsx(Jz,{runId:ue.id,refreshKey:R,onNavigate:ae},`run:${ue.id}`),Qe==="baselines"&&n.jsx(Pz,{refreshKey:R,onNavigate:ae}),Qe==="baseline"&&ue.id&&n.jsx(Wz,{baselineId:ue.id,refreshKey:R,onNavigate:ae},`baseline:${ue.id}`),Qe==="comparisons"&&n.jsx(ek,{refreshKey:R,onNavigate:ae}),Qe==="comparison"&&ue.id&&n.jsx(ik,{comparisonId:ue.id,refreshKey:R,onNavigate:ae},`comparison:${ue.id}`),Qe==="snapshots"&&n.jsx(rk,{refreshKey:0,onNavigate:ae}),Qe==="snapshot"&&ue.id&&n.jsx(uk,{snapshotId:ue.id,refreshKey:0,onNavigate:ae},`snapshot:${ue.id}`),Qe==="snapshot-traces"&&ue.id&&n.jsx(eb,{refreshKey:0,onNavigate:ae,onReplaceUrl:ye,slowThresholdMs:Ct?.config.slowRequestMs??250,snapshotId:ue.id},`snapshot-traces:${ue.id}`),Qe==="snapshot-trace"&&ue.id&&ue.childId&&n.jsx(Wx,{traceId:ue.childId,refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-trace:${ue.id}:${ue.childId}`),Qe==="snapshot-errors"&&ue.id&&n.jsx(ib,{refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-errors:${ue.id}`),Qe==="snapshot-error"&&ue.id&&ue.childId&&n.jsx(rb,{errorId:ue.childId,refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-error:${ue.id}:${ue.childId}`),Qe==="snapshot-database"&&ue.id&&n.jsx(tb,{refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-database:${ue.id}`),Qe==="settings"&&n.jsx(Nk,{refreshKey:R,theme:se,onThemeChange:ve,onCycleTheme:qe,onNavigate:ae}),Qe==="mcp"&&n.jsx(wk,{refreshKey:R,onNavigate:ae}),Qe==="not-found"&&n.jsx($k,{onNavigate:ae})]})})]})]}),n.jsx(kC,{open:v,projectName:Pt,theme:se,returnFocusRef:z,onClose:()=>x(!1),onNavigate:ae,onCycleTheme:qe}),n.jsx(RC,{})]})}const Jj=document.getElementById("root");if(!Jj)throw new Error("Dashboard root element is missing");tN.createRoot(Jj).render(n.jsx(w.StrictMode,{children:n.jsx(Lk,{})}));
|
|
107
|
+
`:""]},D)})}),n.jsxs("p",{className:"settings-note",children:["The server reads the bearer token from"," ",n.jsx("code",{children:".stacknext/run.json"}),". The token stays on this machine and is never printed by the MCP server."]})]})]}),n.jsxs("li",{className:"setup-step",children:[n.jsx("div",{className:"setup-step__marker","aria-hidden":"true",children:"3"}),n.jsxs("div",{className:"setup-step__body",children:[n.jsx("div",{className:"setup-step__title",children:"Ask for a summary"}),n.jsxs("p",{className:"settings-note",children:["Restart the client and ask it to call"," ",n.jsx(Ks,{value:"get_project_summary"})," first."]})]})]})]})})}),n.jsx(ln,{id:"agent-skill",title:"Agent skill",description:"Portable investigation guidance for supported clients",action:n.jsx(St,{value:xk,label:"Copy all",small:!0}),children:n.jsxs("div",{className:"settings-panel",children:[n.jsx("p",{className:"settings-note settings-note--prose",children:"The bundled Agent Skill teaches an AI client the evidence-first StackNext workflow: start with coverage, read the sample window, use diagnostics as hypotheses, and keep snapshot evidence separate from live telemetry."}),n.jsx("ul",{className:"skill-clients",children:vk.map(O=>n.jsxs("li",{className:"skill-client",children:[n.jsx("div",{className:"skill-client__name",children:O.name}),n.jsx("code",{className:"skill-client__command",children:O.command}),n.jsx(St,{value:O.command,label:`Copy ${O.name}`,small:!0})]},O.name))}),n.jsxs("p",{className:"settings-note",children:["Client support is only claimed after a real end-to-end test. See"," ",n.jsx("code",{children:"docs/testing/agent-compatibility.md"})," in the repository."]})]})}),n.jsx(ln,{id:"tools",title:"Tool catalog",description:`All ${E(x)} tools are read-only and scoped to the running collector`,children:n.jsxs("div",{className:"settings-panel settings-panel--flush tool-catalog",children:[n.jsxs("p",{className:"tool-catalog__note",children:["Tool names are shown exactly as the server registers them. Live tools are unprefixed; diagnostics, regression and snapshot tools use the"," ",n.jsx("code",{children:"stacknext_"})," prefix."]}),n.jsxs("div",{className:"tool-catalog__toolbar",children:[n.jsx(Rn,{value:b,onChange:g,placeholder:"Search tools and purposes…",label:"Search MCP tools"}),n.jsxs("div",{className:"tool-filters",role:"group","aria-label":"Filter tools by group",children:[n.jsxs("button",{type:"button",className:N==="all"?"filter-chip is-active":"filter-chip","aria-pressed":N==="all",onClick:()=>y("all"),children:["All ",n.jsx("span",{className:"tool-filters__count",children:x})]}),v.map(O=>n.jsxs("button",{type:"button",className:N===O.id?"filter-chip is-active":"filter-chip","aria-pressed":N===O.id,onClick:()=>y(O.id),children:[O.label," ",n.jsx("span",{className:"tool-filters__count",children:O.count})]},O.id))]}),n.jsxs("span",{className:"sr-only",role:"status","aria-live":"polite",children:[E(C.length)," of ",E(x)," ","tools shown"]})]}),n.jsx("div",{className:"table-scroll data-table-scroll tool-catalog__scroll",role:"region","aria-label":"MCP tools",tabIndex:0,children:n.jsxs("table",{className:"table tool-table",children:[n.jsx("caption",{className:"sr-only",children:"MCP tools"}),n.jsx("thead",{children:n.jsxs("tr",{children:[n.jsx("th",{scope:"col",className:"tool-table__name",children:"Tool"}),n.jsx("th",{scope:"col",children:"Purpose"})]})}),ym.map(O=>{const D=C.filter(_=>_.group===O.id);if(D.length===0)return null;const z=v.find(_=>_.id===O.id)?.count??0,U=D.length===z;return n.jsxs("tbody",{children:[n.jsx("tr",{className:"tool-group-row",children:n.jsxs("th",{scope:"colgroup",colSpan:2,children:[O.label,n.jsx("span",{children:U?E(z):`${E(D.length)} of ${E(z)}`})]})}),D.map(_=>n.jsxs("tr",{children:[n.jsx("th",{scope:"row",className:"tool-table__name",children:n.jsx(Ks,{value:_.name,label:"Copy tool name",children:_.name})}),n.jsx("td",{className:"tool-table__purpose",children:_.purpose})]},_.name))]},O.id)}),C.length===0&&n.jsx("tbody",{children:n.jsx("tr",{children:n.jsx("td",{colSpan:2,children:n.jsx(Ke,{compact:!0,icon:"search",title:"No tools match",description:"Try a different name or clear the group filter."})})})})]})})]})})]})}function Ek(a){if(a<=0)return"Sample expired";if(a<6e4)return`Expires in ${Math.max(1,Math.ceil(a/1e3))}s`;if(a<36e5)return`Expires in ${Math.ceil(a/6e4)}m`;const l=Math.floor(a/36e5),r=Math.ceil(a%36e5/6e4);return`Expires in ${l}h ${r}m`}function Ck({health:a,onNavigate:l}){const[r,o]=w.useState(()=>Date.now()),[d,f]=w.useState(!1),[m,h]=w.useState(""),[v,x]=w.useState(!1),[b,g]=w.useState(null),[N,y]=w.useState(null),C=w.useRef(null),O=w.useRef(null),D=w.useRef(null);if(w.useEffect(()=>{const W=window.setInterval(()=>o(Date.now()),1e3);return()=>window.clearInterval(W)},[]),w.useEffect(()=>{if(!d)return;D.current=document.activeElement instanceof HTMLElement?document.activeElement:null;const W=window.requestAnimationFrame(()=>C.current?.focus()),re=document.querySelector(".shell"),V=re?.hasAttribute("inert")??!1;re&&!V&&re.setAttribute("inert","");const se=()=>Array.from(O.current?.querySelectorAll('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')??[]).filter(K=>K.tabIndex>=0),ve=K=>{if(K.key==="Escape"){K.preventDefault(),f(!1);return}if(K.key!=="Tab")return;const oe=se();if(oe.length===0){K.preventDefault(),O.current?.focus();return}const he=oe[0],Re=oe[oe.length-1];K.shiftKey&&document.activeElement===he?(K.preventDefault(),Re?.focus()):!K.shiftKey&&document.activeElement===Re&&(K.preventDefault(),he?.focus())};return window.addEventListener("keydown",ve),()=>{window.cancelAnimationFrame(W),window.removeEventListener("keydown",ve),re&&!V&&re.removeAttribute("inert"),D.current?.isConnected&&D.current.focus()}},[d]),w.useEffect(()=>{!d||!v&&!b||O.current?.focus()},[d,b,v]),!a)return null;const z=a.sample,U=z.lastEventTime,_=z.lastReceivedAt,R=_===null?null:_+z.retentionMs,S=R===null?null:R-r,Q=a.counts.spans+a.counts.errors+a.counts.databaseQueries>0,A=S!==null&&S<=6e4,J=()=>{g(null),y(null),f(!0)},te=async()=>{x(!0),y(null);try{const W=await ke.saveSnapshot(m.trim()||void 0);g(W),h("")}catch(W){y(W instanceof Error?W.message:String(W))}finally{x(!1)}},Y=W=>{f(!1),l(W)};return n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"topbar__sample",children:[n.jsxs("span",{className:A?"sample-countdown sample-countdown--urgent":"sample-countdown",title:R===null?"No events in the current sample yet":`Last event ${Ie(U)} · retention ${Ie(R)}`,children:[n.jsx(ne,{name:"clock",size:13}),S===null?"No live sample":Ek(S)]}),n.jsxs("button",{type:"button",className:"button button--secondary button--small topbar__save-sample",onClick:J,disabled:!Q,title:Q?"Save the current sample as a frozen local snapshot":"No telemetry in the current sample to save",children:[n.jsx(ne,{name:"layers",size:14})," Save sample"]})]}),d&&Ps.createPortal(n.jsx("div",{className:"command-overlay",onMouseDown:W=>{W.target===W.currentTarget&&f(!1)},children:n.jsxs("div",{ref:O,className:"command-dialog sample-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"sample-dialog-title",tabIndex:-1,children:[n.jsxs("header",{className:"sample-dialog__head",children:[n.jsxs("div",{children:[n.jsx("h2",{className:"sample-dialog__title",id:"sample-dialog-title",children:b?"Snapshot saved":"Save current sample"}),n.jsx("p",{className:"sample-dialog__subtitle",children:b?"The snapshot is now stored under .stacknext/snapshots.":"Freeze the current in-memory telemetry before the retention window expires."})]}),n.jsx("button",{type:"button",className:"button button--ghost button--icon button--compact",onClick:()=>f(!1),"aria-label":"Close save snapshot dialog",children:n.jsx(ne,{name:"close",size:16})})]}),n.jsx("div",{className:"sample-dialog__body",children:b?n.jsxs(n.Fragment,{children:[n.jsxs("div",{className:"sample-dialog__success",children:[n.jsx(ne,{name:"check",size:16}),n.jsxs("div",{children:[n.jsx("div",{className:"mono",children:b.id}),n.jsxs("div",{className:"muted",children:[E(b.stats.requests)," requests ·"," ",E(b.stats.spans)," spans ·"," ",E(b.stats.errors)," errors ·"," ",E(b.stats.databaseQueries)," queries"]})]})]}),n.jsxs("div",{className:"sample-dialog__actions",children:[n.jsx("button",{type:"button",className:"button button--primary",onClick:()=>Y(`/snapshots/${b.id}`),children:"Open snapshot"}),n.jsx("button",{type:"button",className:"button button--secondary",onClick:()=>Y("/snapshots"),children:"View all snapshots"}),n.jsx("button",{type:"button",className:"button button--ghost",onClick:()=>f(!1),children:"Done"})]})]}):n.jsxs(n.Fragment,{children:[n.jsxs("label",{className:"sample-dialog__field",children:[n.jsx("span",{children:"Label"}),n.jsx("input",{ref:C,type:"text",value:m,maxLength:200,placeholder:"e.g. before index change",onChange:W=>h(W.target.value),onKeyDown:W=>{W.key==="Enter"&&!v&&te()}})]}),n.jsx("p",{className:"muted",children:"Optional. Manual snapshots are never deleted by automatic rotation. The snapshot is redacted and frozen; it never replaces live telemetry."}),N&&n.jsxs("div",{className:"sample-dialog__error",role:"alert",children:[n.jsx(ne,{name:"errors",size:15})," ",N]}),n.jsxs("div",{className:"sample-dialog__actions",children:[n.jsxs("button",{type:"button",className:"button button--primary",onClick:()=>{te()},disabled:v,"aria-busy":v||void 0,children:[n.jsx(ne,{name:v?"refresh":"layers",size:15,className:v?"icon-button__spin":void 0}),v?"Saving…":"Save snapshot"]}),n.jsx("button",{type:"button",className:"button button--ghost",onClick:()=>f(!1),disabled:v,children:"Cancel"})]})]})})]})}),document.body)]})}function bb(a){return a instanceof Element&&!!a.closest(".table tbody tr, .timeline__row, .latency-chart__item, .list-row, .coverage-table tbody tr")}const zk=[{id:"observe",label:"Observe",items:[{label:"Overview",path:"/",icon:"overview",match:a=>a==="/"||a===""},{label:"Traces",path:"/traces",icon:"traces",match:a=>a.startsWith("/traces")},{label:"Database",path:"/database",icon:"database",match:a=>a.startsWith("/database")},{label:"Snapshots",path:"/snapshots",icon:"layers",match:a=>a.startsWith("/snapshots")}]},{id:"investigate",label:"Investigate",items:[{label:"Errors",path:"/errors",icon:"errors",match:a=>a.startsWith("/errors")},{label:"Diagnostics",path:"/diagnostics",icon:"pulse",match:a=>a.startsWith("/diagnostics")}]},{id:"test",label:"Test",items:[{label:"Test runs",path:"/runs",icon:"terminal",match:a=>a.startsWith("/runs")},{label:"Baselines",path:"/baselines",icon:"flag",match:a=>a.startsWith("/baselines")},{label:"Comparisons",path:"/comparisons",icon:"compare",match:a=>a.startsWith("/comparisons")}]},{id:"configure",label:"Configure",items:[{label:"Settings",path:"/settings",icon:"settings",match:a=>a.startsWith("/settings")},{label:"MCP server",path:"/mcp",icon:"terminal",match:a=>a.startsWith("/mcp")}]}],kk={dark:"moon",light:"sun",system:"monitor"};function Ok(a){return a==="dark"?"light":a==="light"?"system":"dark"}const Xj="stacknext-errors-seen",Pf=new Set;let Oc,Wf=null;function Mk(a){if(!a)return null;try{const l=JSON.parse(a);if(l&&typeof l.id=="string"&&typeof l.timestamp=="number"&&Number.isFinite(l.timestamp))return{id:l.id,timestamp:l.timestamp}}catch{}return null}function jb(){let a=null;try{a=window.localStorage.getItem(Xj)}catch{a=null}return a!==Oc&&(Oc=a,Wf=Mk(a)),Wf}function Ak(a){return Pf.add(a),()=>Pf.delete(a)}function Rk(a){const l=JSON.stringify(a);try{window.localStorage.setItem(Xj,l),Oc=l}catch{Oc=null}Wf=a;for(const r of Pf)r()}function yb(a,l){return a.timestamp>l.timestamp||a.timestamp===l.timestamp&&a.id!==l.id}function $k({onNavigate:a}){return n.jsxs("div",{className:"not-found",children:[n.jsx("div",{className:"not-found__code",children:"404"}),n.jsx("h1",{children:"This view does not exist"}),n.jsxs("p",{className:"muted",children:["The dashboard has Overview, Requests, Traces, Database, Snapshots, Errors, Diagnostics, Test runs, Baselines, Comparisons, Settings and MCP server. Press"," ",n.jsx(ms,{children:hr("K")})," to jump to any view."]}),n.jsx("button",{type:"button",className:"button button--primary",onClick:()=>a("/"),children:"Back to overview"})]})}function Dk({health:a}){if(!a)return n.jsxs("div",{className:"sidebar__connection",children:[n.jsx("div",{className:"sidebar__row",children:n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:"connecting"}),"Connecting…"]})}),n.jsx("div",{className:"sidebar__row-meta",children:"local collector"})]});const l=a.connections.filter(r=>r.connected);return n.jsxs("div",{className:"sidebar__connection",children:[n.jsx("div",{className:"sidebar__row",children:n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:a.connected?"live":"connecting"}),a.connected?l[0]?.serviceName??"App connected":"Waiting for app"]})}),n.jsxs("div",{className:"sidebar__row-meta",children:[l[0]?.nextVersion?`Next.js ${l[0].nextVersion} · `:"",a.connected?`${E(a.counts.spans)} spans · ${xe(a.uptimeMs)} up`:"make a request to start a trace"]})]})}function pc(a){return yC(a)??a}function _b(a){return vm(a.split("?")[0]??"/").page==="traces"}function qk(){const a=`${window.location.pathname}${window.location.search}${window.location.hash}`,l=pc(a);return l!==a&&window.history.replaceState({},"",l),l}function Lk(){const[a,l]=w.useState(qk),[r,o]=w.useState(0),[d,f]=w.useState(!1),[m,h]=w.useState(()=>localStorage.getItem("stacknext-sidebar-collapsed")==="1"),[v,x]=w.useState(!1),[b,g]=w.useState(()=>window.matchMedia("(max-width: 900px)").matches),N=w.useRef(null),y=w.useRef(null),C=w.useRef(null),O=w.useRef(null),D=w.useRef(!1),z=w.useRef(null),U=w.useRef(null),_=bC(),[R,S]=w.useState(0),[Q,A]=w.useState(null),J=w.useSyncExternalStore(Ak,jb,jb),te=w.useRef(0),Y=w.useRef(0),W=w.useRef(_.sequence),re=w.useRef({x:-1,y:-1}),V=w.useCallback(()=>{te.current<=Y.current||(Y.current=te.current,S(te.current))},[]),{theme:se,setTheme:ve}=jC(),K=Q!==null,oe=K?Math.max(0,_.sequence-Q):0,he=w.useCallback(()=>{if(Q!==null){A(null),V();return}A(_.sequence)},[V,_.sequence,Q]);w.useEffect(()=>{if(_.sequence===W.current||(W.current=_.sequence,te.current+=1,Q!==null))return;const{x:Ne,y:Te}=re.current,Ze=Ne>=0?document.elementFromPoint(Ne,Te):null;bb(Ze)||V()},[_.sequence,Q,V]),w.useEffect(()=>{if(Q!==null)return;const Ne=Ze=>{re.current={x:Ze.clientX,y:Ze.clientY},bb(Ze.target)||V()},Te=Ze=>{Ze.relatedTarget===null&&(re.current={x:-1,y:-1},V())};return window.addEventListener("pointermove",Ne,{passive:!0}),window.addEventListener("pointerout",Te),()=>{window.removeEventListener("pointermove",Ne),window.removeEventListener("pointerout",Te)}},[V,Q]);const[Re,De]=w.useState(null),ue=w.useMemo(()=>vm(a.split("?")[0]??"/"),[a]),Ut=vt(Ne=>ke.health(Ne),"shell-health",0,1e4),Zt=vt(Ne=>ke.errors({limit:1},Ne),"shell-latest-error",0,1e4),M=w.useMemo(()=>{const Ne=Zt.data?.items[0];return Ne?{id:Ne.id,timestamp:Ne.timestamp}:null},[Zt.data]);w.useEffect(()=>{M&&(ue.page!=="errors"&&ue.page!=="error"||J&&!yb(M,J)||Rk(M))},[M,ue.page,J]),w.useEffect(()=>{const Ne=()=>{f(!1),A(null),V();const Te=`${window.location.pathname}${window.location.search}${window.location.hash}`,Ze=pc(Te);Ze!==Te&&window.history.replaceState({},"",Ze),l(Ze),_b(Ze)&&o(cn=>cn+1)};return window.addEventListener("popstate",Ne),()=>window.removeEventListener("popstate",Ne)},[V]),w.useEffect(()=>{if(!d)return;y.current?.focus();const Ne=Te=>{Te.key==="Escape"&&f(!1)};return window.addEventListener("keydown",Ne),()=>window.removeEventListener("keydown",Ne)},[d]),w.useEffect(()=>{localStorage.setItem("stacknext-sidebar-collapsed",m?"1":"0")},[m]),w.useEffect(()=>{const Ne=window.matchMedia("(max-width: 900px)"),Te=()=>{g(Ne.matches),Ne.matches||f(!1)};return Te(),Ne.addEventListener("change",Te),()=>Ne.removeEventListener("change",Te)},[]);const H=w.useRef(!1);w.useEffect(()=>{if(d){H.current=!0;return}H.current&&(H.current=!1,N.current?.offsetParent?N.current.focus():(document.querySelector(".nav-item--active")??document.querySelector(".sidebar__logo"))?.focus())},[d]);const le=()=>{f(!1)},fe=w.useCallback(()=>{f(Ne=>!Ne)},[]),ze=w.useCallback(()=>{D.current=!0,h(!0)},[]),Ae=w.useCallback(()=>{D.current=!0,h(!1)},[]);w.useEffect(()=>{if(!D.current)return;D.current=!1,(m?O.current:C.current)?.focus()},[m]);const qe=w.useCallback(()=>{ve(Ok(se))},[ve,se]);w.useEffect(()=>{const Ne=Te=>{if(!(Te.metaKey||Te.ctrlKey)||Te.altKey||Te.defaultPrevented)return;Te.key.toLowerCase()==="k"&&(Te.preventDefault(),v||f(!1),x(!v))};return window.addEventListener("keydown",Ne),()=>window.removeEventListener("keydown",Ne)},[v]);const ae=Ne=>{const Te=pc(Ne);Te!==`${window.location.pathname}${window.location.search}${window.location.hash}`&&(f(!1),x(!1),Q!==null&&A(null),V(),window.history.pushState({},"",Te),l(Te),_b(Te)&&o(Ze=>Ze+1))},ye=w.useCallback(Ne=>{const Te=pc(Ne);Te!==`${window.location.pathname}${window.location.search}`&&(window.history.replaceState({},"",Te),l(Te))},[]);w.useLayoutEffect(()=>{const Ne=a.indexOf("#"),Te=Ne>=0?a.slice(Ne+1):"";if(!Te){U.current?.scrollTo({top:0});return}const Ze=()=>{const qa=document.getElementById(Te);return qa?(qa.scrollIntoView({block:"start"}),!0):!1};if(Ze())return;const cn=U.current;if(!cn||typeof MutationObserver>"u")return;let la=0;const Kn=new MutationObserver(()=>{Ze()&&(Kn.disconnect(),window.clearTimeout(la))});return Kn.observe(cn,{childList:!0,subtree:!0}),la=window.setTimeout(()=>Kn.disconnect(),5e3),()=>{Kn.disconnect(),window.clearTimeout(la)}},[a]);const Ct=Ut.data,Qe=ue.page,Pt=Ct?.projectName??"StackNext",Bt=Ct?.counts.errors??0,jt=M!==null&&(J===null||yb(M,J))&&Qe!=="errors"&&Qe!=="error",Pe=w.useCallback(Ne=>{De(Ne)},[]),Ht=w.useMemo(()=>({breadcrumb:Re,projectName:Pt}),[Re,Pt]),Dn=m&&!b;return n.jsxs("div",{className:_e("app",d&&"app--sidebar-open",Dn&&"app--sidebar-collapsed"),children:[n.jsx("a",{className:"skip-link",href:"#main-content",children:"Skip to content"}),n.jsxs("div",{className:"shell",children:[n.jsxs("aside",{className:"sidebar",id:"app-sidebar",children:[n.jsxs("div",{className:"sidebar__brand",children:[Dn?n.jsx("button",{type:"button",className:"sidebar__expand button button--ghost button--icon button--compact",onClick:Ae,"aria-label":"Expand sidebar","aria-expanded":!1,"aria-controls":"app-sidebar",title:"Expand sidebar",ref:O,children:n.jsx(ne,{name:"panel-left",size:16})}):n.jsxs("button",{type:"button",className:"sidebar__logo",onClick:()=>ae("/"),"aria-label":"StackNext overview",children:[n.jsx("span",{className:"logo-mark",children:n.jsx(_C,{size:20})}),n.jsxs("span",{className:"sidebar__brand-text",children:[n.jsx("span",{className:"sidebar__brand-name",children:"StackNext"}),n.jsx("span",{className:"sidebar__brand-sub",children:Pt})]})]}),n.jsxs("div",{className:"sidebar__brand-actions",children:[n.jsx("button",{type:"button",className:"sidebar__collapse button button--ghost button--icon button--compact",onClick:ze,"aria-label":"Collapse sidebar","aria-expanded":!0,"aria-controls":"app-sidebar",title:"Collapse sidebar",ref:C,children:n.jsx(ne,{name:"panel-left",size:15})}),n.jsx("button",{type:"button",className:"sidebar__close button button--ghost button--icon",onClick:le,"aria-label":"Close navigation",ref:y,children:n.jsx(ne,{name:"close",size:16})})]})]}),n.jsx("nav",{className:"sidebar__nav","aria-label":"Primary",children:zk.map(Ne=>n.jsxs("div",{className:"nav-group",children:[n.jsx("div",{className:"nav-group__label","aria-hidden":"true",children:Ne.label}),Ne.items.map(Te=>{const Ze=Te.match(a.split("?")[0]??"/");return n.jsxs("button",{type:"button",className:_e("nav-item",Ze&&"nav-item--active"),onClick:()=>ae(Te.path),"aria-current":Ze?"page":void 0,title:Dn?Te.label:void 0,children:[n.jsx(ne,{name:Te.icon,size:16}),n.jsx("span",{className:"nav-item__label",children:Te.label}),Te.path==="/errors"&&Bt>0&&n.jsx("span",{className:_e("nav-item__count",jt&&"nav-item__count--unseen"),title:jt?"New errors since your last visit":`${Bt} errors`,children:Bt})]},Te.path)})]},Ne.id))}),n.jsx("div",{className:"sidebar__spacer"}),n.jsxs("div",{className:"sidebar__live",children:[n.jsxs("div",{className:"sidebar__row",children:[n.jsxs("span",{className:"sidebar__row-label",children:[n.jsx(Ff,{status:_.status}),n.jsx("span",{className:"sidebar__row-text",children:_.status==="live"?"Live":_.status})]}),_.lastEventAt&&n.jsx("span",{className:"sidebar__row-time",children:Ge(_.lastEventAt)})]}),n.jsx("p",{className:"sidebar__hint",children:"Local SSE feed. Nothing leaves this machine."})]}),n.jsx(Dk,{health:Ct}),n.jsx("div",{className:"sidebar__footer",children:n.jsxs("div",{className:"sidebar__footer-row",children:[n.jsxs("span",{className:"mono sidebar__version",children:["v",Ct?.version??"0.6.0"]}),n.jsxs("div",{className:"sidebar__footer-actions",children:[n.jsx("button",{type:"button",className:"button button--ghost button--icon button--compact",onClick:qe,title:`Theme: ${se}`,"aria-label":`Theme: ${se}. Switch theme`,children:n.jsx(ne,{name:kk[se],size:14})}),n.jsx("a",{className:"button button--ghost button--icon button--compact",href:"https://github.com/marrrkkk/stacknext",target:"_blank",rel:"noreferrer","aria-label":"StackNext on GitHub",title:"Docs and source",children:n.jsx(ne,{name:"code",size:14})})]})]})})]}),d&&n.jsx("button",{type:"button",className:"sidebar-scrim",onClick:le,"aria-label":"Close navigation"}),n.jsxs("div",{className:"main",inert:d||v||void 0,children:[n.jsxs("header",{className:"topbar",children:[n.jsx("button",{type:"button",className:"button button--ghost button--icon topbar__sidebar-toggle",onClick:fe,"aria-label":d?"Close navigation":"Open navigation","aria-expanded":d,"aria-controls":"app-sidebar",title:"Toggle navigation",ref:N,children:n.jsx(ne,{name:"panel-left",size:16})}),n.jsxs("div",{className:"topbar__breadcrumb",children:[n.jsx("button",{type:"button",className:"topbar__project topbar__project--link",onClick:()=>ae("/"),title:`Open ${Pt} overview`,children:Pt}),n.jsx(ne,{name:"chevron-right",size:14,className:"topbar__separator"}),n.jsx("div",{className:"topbar__slot",ref:Pe})]}),n.jsx(Ck,{health:Ct,onNavigate:ae}),n.jsxs("button",{type:"button",className:"topbar__command",ref:z,onClick:()=>x(!0),"aria-label":"Search views and actions",title:`Search or jump to (${hr("K")})`,children:[n.jsx(ne,{name:"search",size:14}),n.jsx("span",{className:"topbar__command-label",children:"Search or jump to…"}),n.jsx(ms,{children:hr("K")})]})]}),n.jsx(Aj.Provider,{value:Ht,children:n.jsxs("main",{className:"content",id:"main-content",ref:U,tabIndex:-1,children:[Qe==="overview"&&n.jsx(QC,{refreshKey:R,onNavigate:ae,liveStatus:_.status,paused:K,pendingLive:oe,onTogglePause:he}),Qe==="traces"&&n.jsx(eb,{refreshKey:R,onNavigate:ae,onReplaceUrl:ye,slowThresholdMs:Ct?.config.slowRequestMs??250},r),Qe==="trace"&&ue.id&&n.jsx(Wx,{traceId:ue.id,refreshKey:R,onNavigate:ae},`trace:${ue.id}`),Qe==="redirect"&&null,Qe==="database"&&n.jsx(tb,{refreshKey:R,onNavigate:ae}),Qe==="database-query"&&ue.id&&n.jsx(Mz,{shapeId:ue.id,refreshKey:R,onNavigate:ae},`database-shape:${ue.id}`),Qe==="errors"&&n.jsx(ib,{refreshKey:R,onNavigate:ae}),Qe==="error"&&ue.id&&n.jsx(rb,{errorId:ue.id,refreshKey:R,onNavigate:ae}),Qe==="diagnostics"&&n.jsx(Iz,{refreshKey:R,onNavigate:ae}),Qe==="diagnostic"&&ue.id&&n.jsx(Fz,{diagnosticId:ue.id,refreshKey:R,onNavigate:ae}),Qe==="runs"&&n.jsx(Xz,{refreshKey:R,onNavigate:ae}),Qe==="run"&&ue.id&&n.jsx(Jz,{runId:ue.id,refreshKey:R,onNavigate:ae},`run:${ue.id}`),Qe==="baselines"&&n.jsx(Pz,{refreshKey:R,onNavigate:ae}),Qe==="baseline"&&ue.id&&n.jsx(Wz,{baselineId:ue.id,refreshKey:R,onNavigate:ae},`baseline:${ue.id}`),Qe==="comparisons"&&n.jsx(ek,{refreshKey:R,onNavigate:ae}),Qe==="comparison"&&ue.id&&n.jsx(ik,{comparisonId:ue.id,refreshKey:R,onNavigate:ae},`comparison:${ue.id}`),Qe==="snapshots"&&n.jsx(rk,{refreshKey:0,onNavigate:ae}),Qe==="snapshot"&&ue.id&&n.jsx(uk,{snapshotId:ue.id,refreshKey:0,onNavigate:ae},`snapshot:${ue.id}`),Qe==="snapshot-traces"&&ue.id&&n.jsx(eb,{refreshKey:0,onNavigate:ae,onReplaceUrl:ye,slowThresholdMs:Ct?.config.slowRequestMs??250,snapshotId:ue.id},`snapshot-traces:${ue.id}`),Qe==="snapshot-trace"&&ue.id&&ue.childId&&n.jsx(Wx,{traceId:ue.childId,refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-trace:${ue.id}:${ue.childId}`),Qe==="snapshot-errors"&&ue.id&&n.jsx(ib,{refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-errors:${ue.id}`),Qe==="snapshot-error"&&ue.id&&ue.childId&&n.jsx(rb,{errorId:ue.childId,refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-error:${ue.id}:${ue.childId}`),Qe==="snapshot-database"&&ue.id&&n.jsx(tb,{refreshKey:0,onNavigate:ae,snapshotId:ue.id},`snapshot-database:${ue.id}`),Qe==="settings"&&n.jsx(Nk,{refreshKey:R,theme:se,onThemeChange:ve,onCycleTheme:qe,onNavigate:ae}),Qe==="mcp"&&n.jsx(wk,{refreshKey:R,onNavigate:ae}),Qe==="not-found"&&n.jsx($k,{onNavigate:ae})]})})]})]}),n.jsx(kC,{open:v,projectName:Pt,theme:se,returnFocusRef:z,onClose:()=>x(!1),onNavigate:ae,onCycleTheme:qe}),n.jsx(RC,{})]})}const Jj=document.getElementById("root");if(!Jj)throw new Error("Dashboard root element is missing");tN.createRoot(Jj).render(n.jsx(w.StrictMode,{children:n.jsx(Lk,{})}));
|
package/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
12
12
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
13
13
|
<link rel="manifest" href="/site.webmanifest" />
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-CurWVdIC.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-DoVYohzr.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacknext/dashboard",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Static React dashboard served by the StackNext local collector",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"./package.json": "./package.json"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@stacknext/core": "0.11.
|
|
16
|
-
"@stacknext/regression": "0.11.
|
|
15
|
+
"@stacknext/core": "0.11.1",
|
|
16
|
+
"@stacknext/regression": "0.11.1",
|
|
17
17
|
"@types/react": "19.3.0",
|
|
18
18
|
"@types/react-dom": "19.3.0",
|
|
19
19
|
"@vitejs/plugin-react": "5.2.0",
|