@clemrig/flow-ui 0.5.1 → 0.7.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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ npm install -g @clemrig/flow-ui
11
11
  flow-ui [RUNS_DIR] [--port PORT]
12
12
  ```
13
13
 
14
- `flow-ui` serves the runs kept in `RUNS_DIR` (default: `.flow/runs`, where `@clemrig/flow` keeps them; a `RUNS_DIR` given must be an existing directory) on `127.0.0.1`, on `PORT` or a port the system chooses, prints its URL and stops on Ctrl-C. The page lists the runs with their workflow, status, current stage, duration, estimated cost and last update, and for a run a card with its start, end, active time and cost, then each launch (the first one, then each resumption) with its status, the cause of a failure and a strip of its stages sized by duration; the list of its stages beside the selected one's panel (start, duration, outcome, the agent call's model, settings, tools and tokens, the tools used, then a folded Output section with its agent's answer and the correction it sent back or could not send, the journal and the tool calls); its correction counters, its result and its runtimes. It follows a running run as it goes, and follows the selected run again when it is resumed. A result shows the run's validated data as JSON.
14
+ `flow-ui` serves the runs kept in `RUNS_DIR` (default: `.flow/runs`, where `@clemrig/flow` keeps them; a `RUNS_DIR` given must be an existing directory) on `127.0.0.1`, on `PORT` or a port the system chooses, prints its URL and stops on Ctrl-C. The page shows the runs in a rail you can fold, grouped by day, named by their input, filtered by status or workflow and searched (`/`). A run shows its name, status, links its result gives, input and figures (active time, launches, rounds, cost, its own budget with the share spent, stage executions); its launches (the first one, then each resumption) on one bar, each sized by its active time with its day and start time, the pause between two written out, and why each failed one stopped; its stage executions over its active time, a lane per agent stage, with the resumptions, the rounds its corrections started and each correction taken, zoomed on demand (Fit, 2×, 4×, or a pinch or Ctrl/⌘ with the wheel on the lanes); its correction counters. Below, the selected execution's panel with four tabs: Overview (outcome, the agent call's model, session and settings, saying which ones the profile leaves to the machine or the runtime, tools, whether the call opens its conversation or continues it, tokens and cost), Input (the correction it received, then what flow sent each call, loaded on demand: the workflow's prompt, the instructions, the context and the output schema as trees, the whole request and the command), Output (its agent's answer, the correction it sent back or could not send, and each call's answer when it made several) and Activity (its calls, tool calls, journal and warnings in one timeline you can filter). Beside it, its cost by session and over its active time with each launch's budget when known, then its result, its data as a tree you unfold, and its runtimes. The address keeps the run, the execution chosen and the tab (`#/<id>/stages/<n>/<tab>`); `j`, `k`, `f` and `1` to `4` move between executions and tabs. It follows a running run as it goes, and follows the selected run again when it is resumed. A result shows the run's validated data as JSON.
15
15
 
16
16
  To watch a run on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `flow-ui --port 4321` there, and open `http://localhost:4321/`.
17
17
 
@@ -32,6 +32,8 @@ console.log(dashboard.url);
32
32
  // later: await dashboard.close();
33
33
  ```
34
34
 
35
+ With `presentResult`, a run's result is its lines, and the page shows the run's validated data folded below them.
36
+
35
37
  `dashboardHandler(options)` gives the same dashboard as a request handler, for a server that serves it beside other pages: it answers `(request, response, url)`, where `url` is the request's address relative to where the dashboard is mounted (`/api/runs` for `/runs/api/runs`), and refuses, like `startDashboard`, every method but GET and any `Host` other than its own address. The page's own addresses are relative, so it works under any path that ends with `/`.
36
38
 
37
39
  ## License
package/dist/server.d.ts CHANGED
@@ -9,6 +9,11 @@ export interface DashboardOptions {
9
9
  label: string;
10
10
  /** A run's result lines; by default, its validated data as JSON. */
11
11
  presentResult?(state: Readonly<RunState>): string[];
12
+ /** A link back to the page that serves the dashboard beside its own, such as a Kanban. */
13
+ home?: {
14
+ label: string;
15
+ href: string;
16
+ };
12
17
  }
13
18
  /**
14
19
  * Answers one request; `url` is its address relative to where the dashboard is
package/dist/server.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { createServer, } from "node:http";
3
- import { listRuns, loadRun, loadRunEvents, presentResult, summarizeCorrections, summarizeRun, UnknownRunError, } from "@clemrig/flow";
4
- /** The runs, newest first; an unreadable run with its error. */
3
+ import { fileURLToPath } from "node:url";
4
+ import { listRuns, loadAgentCall, loadRun, loadRunEvents, presentResult, summarizeCorrections, summarizeRun, UnknownRunError, } from "@clemrig/flow";
5
+ /** The runs, newest first, with their input; an unreadable run with its error. */
5
6
  async function runs(options) {
6
7
  return {
7
8
  title: options.title,
8
9
  label: options.label,
10
+ ...(options.home ? { home: options.home } : {}),
9
11
  runs: (await listRuns(options.runsDir))
10
12
  .reverse()
11
- .map((entry) => ("state" in entry ? summarizeRun(entry.state) : entry)),
13
+ .map((entry) => "state" in entry
14
+ ? { ...summarizeRun(entry.state), input: entry.state.input }
15
+ : entry),
12
16
  };
13
17
  }
14
18
  /** A run with its events from the `after`-th: the page asks only for what it has not received. */
@@ -17,6 +21,10 @@ async function detail(options, id, after) {
17
21
  const events = await loadRunEvents(options.runsDir, id);
18
22
  return {
19
23
  run: summarizeRun(state),
24
+ input: state.input,
25
+ // The run's own budget; its workflow's is not kept in the run.
26
+ maxCostUsd: state.maxCostUsd ?? null,
27
+ sessions: state.metrics?.sessions ?? {},
20
28
  runtimes: state.runtimes ?? {},
21
29
  error: state.error ?? null,
22
30
  events: events.slice(after),
@@ -24,24 +32,32 @@ async function detail(options, id, after) {
24
32
  // Each correction's maximum comes from all its events, not only those sent.
25
33
  corrections: summarizeCorrections(state, events),
26
34
  result: presentResult(options, state),
35
+ // Without its own rendering, the result is already this data.
36
+ data: options.presentResult ? state.data : null,
27
37
  };
28
38
  }
29
- /** The page's files, served as they are: no script or style is ever inline. */
39
+ /**
40
+ * The page's files, built from `src/page/` into `page/` beside `src/` and
41
+ * `dist/`, and served as they are: no script or style is ever inline.
42
+ */
30
43
  const pageFiles = {
31
44
  "/": ["index.html", "text/html; charset=utf-8"],
32
- "/style.css": ["style.css", "text/css; charset=utf-8"],
33
- "/dashboard.js": ["dashboard.js", "text/javascript; charset=utf-8"],
34
- "/timeline.js": ["timeline.js", "text/javascript; charset=utf-8"],
35
- "/follow.js": ["follow.js", "text/javascript; charset=utf-8"],
36
- "/detail.js": ["detail.js", "text/javascript; charset=utf-8"],
45
+ "/app.js": ["app.js", "text/javascript; charset=utf-8"],
46
+ "/app.css": ["app.css", "text/css; charset=utf-8"],
37
47
  };
38
48
  async function loadPage() {
39
49
  const page = new Map();
40
- for (const [path, [file, type]] of Object.entries(pageFiles))
41
- page.set(path, {
42
- body: await readFile(new URL(`./static/${file}`, import.meta.url), "utf8"),
43
- type,
44
- });
50
+ for (const [path, [file, type]] of Object.entries(pageFiles)) {
51
+ const location = new URL(`../page/${file}`, import.meta.url);
52
+ let body;
53
+ try {
54
+ body = await readFile(location, "utf8");
55
+ }
56
+ catch (error) {
57
+ throw new Error(`The dashboard's page is not built (${fileURLToPath(location)}): npm run build -w packages/flow-ui`, { cause: error });
58
+ }
59
+ page.set(path, { body, type });
60
+ }
45
61
  return page;
46
62
  }
47
63
  function send(response, status, body, type = "text/plain; charset=utf-8") {
@@ -72,15 +88,19 @@ export async function dashboardHandler(options) {
72
88
  return send(response, 200, file.body, file.type);
73
89
  if (url.pathname === "/api/runs")
74
90
  return send(response, 200, JSON.stringify(await runs(options)), "application/json");
75
- const id = /^\/api\/runs\/([^/]+)$/.exec(url.pathname)?.[1];
91
+ // One agent call's record, which the page asks for when the operator opens it.
92
+ const call = /^\/api\/runs\/([^/]+)\/calls\/([^/]+)$/.exec(url.pathname);
93
+ const id = call?.[1] ?? /^\/api\/runs\/([^/]+)$/.exec(url.pathname)?.[1];
76
94
  if (id === undefined)
77
95
  return send(response, 404, "Not Found");
78
96
  const after = url.searchParams.get("after") ?? "0";
79
- if (!/^\d+$/.test(after))
97
+ if (!/^\d+$/.test(after) || (call && !/^[1-9]\d{0,8}$/.test(call[2] ?? "")))
80
98
  return send(response, 400, "Bad Request");
81
99
  let body;
82
100
  try {
83
- body = await detail(options, decodeURIComponent(id), Number(after));
101
+ body = call
102
+ ? await loadAgentCall(options.runsDir, decodeURIComponent(id), Number(call[2]))
103
+ : await detail(options, decodeURIComponent(id), Number(after));
84
104
  }
85
105
  catch (error) {
86
106
  // A run that cannot be read now may be read at the next request.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clemrig/flow-ui",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "A local read-only dashboard of the runs of @clemrig/flow workflows",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,7 +18,8 @@
18
18
  }
19
19
  },
20
20
  "files": [
21
- "dist"
21
+ "dist",
22
+ "page"
22
23
  ],
23
24
  "engines": {
24
25
  "node": ">=24"
@@ -27,13 +28,22 @@
27
28
  "access": "public"
28
29
  },
29
30
  "scripts": {
30
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node -e \"require('node:fs').cpSync('src/static','dist/static',{recursive:true})\"",
31
- "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.ui.json",
31
+ "build": "node -e \"for (const dir of ['dist','page']) require('node:fs').rmSync(dir,{recursive:true,force:true})\" && tsc -p tsconfig.build.json && vite build",
32
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.page.json",
32
33
  "test": "vitest run",
33
34
  "test:coverage": "vitest run --coverage",
34
35
  "prepack": "npm run build"
35
36
  },
36
37
  "dependencies": {
37
- "@clemrig/flow": "0.7.1"
38
+ "@clemrig/flow": "0.8.0"
39
+ },
40
+ "devDependencies": {
41
+ "@testing-library/dom": "10.4.2",
42
+ "@testing-library/react": "16.3.3",
43
+ "@types/react": "19.3.0",
44
+ "@types/react-dom": "19.3.0",
45
+ "happy-dom": "20.14.5",
46
+ "react": "19.3.0",
47
+ "react-dom": "19.3.0"
38
48
  }
39
49
  }
package/page/app.css ADDED
@@ -0,0 +1 @@
1
+ :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:#f7f7f5;--panel:#fff;--fg:#1d1d1b;--muted:#6f6e69;--line:#e4e3df;--accent:#2557d6;--ok:#19773a;--bad:#b42318;--run:#a15c07}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;--bg:#111110;--panel:#1b1b19;--fg:#e8e7e3;--muted:#9b9a94;--line:#2c2c29;--accent:#7aa2ff;--ok:#5bd28a;--bad:#ff7a6b;--run:#f2b14c}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);margin:0;font:14px/1.45 ui-sans-serif,system-ui,-apple-system,sans-serif}h2,h3,h4{margin:0}h3{overflow-wrap:anywhere;font-size:14px}h4{color:var(--muted);letter-spacing:.04em;text-transform:uppercase;margin:14px 0 6px;font-size:11px;font-weight:600}ol{margin:0;padding:0;list-style:none}pre,.mono{overflow-wrap:anywhere;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace}pre{white-space:pre-wrap;margin:0}a{color:var(--accent);text-decoration:none}.empty{color:var(--muted);margin:0}.muted{color:var(--muted)}.warn{color:var(--run)}.bad{color:var(--bad)}[data-state=passed],[data-state=completed]{--state:var(--ok)}[data-state=correction]{--state:var(--run)}[data-state=failed]{--state:var(--bad)}[data-state=running]{--state:var(--accent)}.pill{background:color-mix(in srgb, var(--state) 16%, transparent);color:var(--state);border-radius:999px;padding:1px 8px;font-size:12px;font-weight:600}.topbar{z-index:3;border-bottom:1px solid var(--line);background:var(--panel);align-items:center;gap:6px 14px;height:46px;padding:0 14px;display:flex;position:sticky;top:0}.topbar h1{margin:0;font-size:15px}.topbar .label,.topbar .updated{color:var(--muted);font-size:13px}.topbar .label{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.topbar nav{gap:4px;margin-left:8px;display:flex}.topbar nav>*{color:var(--muted);border-radius:6px;padding:3px 10px;font-size:13px}.topbar nav [aria-current=page]{background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--accent);font-weight:600}.topbar .updated{white-space:nowrap;margin-left:auto;font-size:12px}.rail-toggle{color:var(--muted);cursor:pointer;background:0 0;border:0;padding:2px 6px;font-size:16px}.app{grid-template-columns:264px minmax(0,1fr);display:grid}.rail{border-right:1px solid var(--line);background:var(--panel);height:calc(100vh - 46px);padding:10px;position:sticky;top:46px;overflow:auto}@media (width<=900px){.app{grid-template-columns:minmax(0,1fr)}.rail{border-right:0;border-bottom:1px solid var(--line);height:auto;max-height:45vh;position:static}}.folded .app{grid-template-columns:minmax(0,1fr)}.folded .rail{display:none}.rail-search,.feed-search{border:1px solid var(--line);background:var(--bg);color:var(--fg);font:inherit;border-radius:7px;padding:5px 9px;font-size:13px}.rail-search{width:100%}.filters,.chips,.feed-bar,.corrections{flex-wrap:wrap;align-items:center;gap:4px;display:flex}.filters{margin:8px 0 4px}.chip{border:1px solid var(--line);background:var(--panel);color:inherit;font:inherit;font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;padding:1px 8px;font-size:12px}button.chip{color:var(--muted);cursor:pointer}button.chip[aria-pressed=true]{border-color:var(--accent);background:color-mix(in srgb, var(--accent) 12%, transparent);color:var(--accent)}.chip.warn{border-color:color-mix(in srgb, var(--run) 50%, transparent)}.chip.bad{border-color:color-mix(in srgb, var(--bad) 50%, transparent);color:var(--bad)}.rail-group{color:var(--muted);letter-spacing:.05em;text-transform:uppercase;margin:12px 6px 4px;font-size:11px;font-weight:600}.rail-runs button{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:7px;grid-template-columns:8px minmax(0,1fr) auto;align-items:center;gap:1px 8px;padding:6px 8px;display:grid}.rail-runs button:hover{background:color-mix(in srgb, var(--accent) 6%, transparent)}.rail-runs button[aria-current=true]{background:color-mix(in srgb, var(--accent) 13%, transparent)}.dot{background:var(--state,var(--muted));border-radius:50%;width:8px;height:8px}.rail-runs .name,.rail-runs .meta{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.rail-runs .name{font-size:13px;font-weight:600}.rail-runs .cost,.rail-runs .meta{color:var(--muted);font-variant-numeric:tabular-nums;font-size:12px}.rail-runs .meta{grid-column:2/4}.rail-runs .meta.bad{color:var(--bad)}main.run{min-width:0;padding:14px 16px 32px}main.run>.empty{padding:24px 4px}.run-head{margin-bottom:14px}.run-title{flex-wrap:wrap;align-items:center;gap:6px 12px;display:flex}.run-title h2{overflow-wrap:anywhere;font-size:18px}.run-links{gap:12px;margin-left:auto;font-size:13px;display:flex}.run-input{color:var(--muted);overflow-wrap:anywhere;margin:4px 0 0;font-size:13px}.figures{flex-wrap:wrap;gap:8px 28px;margin:12px 0 0;display:flex}.figures dt{color:var(--muted);letter-spacing:.04em;text-transform:uppercase;font-size:11px}.figures dd{font-variant-numeric:tabular-nums;margin:0;font-size:16px;font-weight:650}.figures small{color:var(--muted);font-size:12px;font-weight:400}.gauge{background:color-mix(in srgb, var(--muted) 18%, transparent);border-radius:3px;width:160px;height:6px;margin-top:4px;display:block;overflow:hidden}.gauge span{background:var(--run);height:100%;display:block}.run-grid{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);align-items:start;gap:14px;display:grid}.card,.drawer{border:1px solid var(--line);background:var(--panel);border-radius:10px;min-width:0;margin-bottom:14px;padding:12px}@media (width<=1100px){.run-grid{grid-template-columns:minmax(0,1fr)}}.card>h2{color:var(--muted);letter-spacing:.05em;text-transform:uppercase;flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-size:11px;font-weight:600;display:flex}.hint{letter-spacing:0;text-align:right;text-transform:none;margin-left:auto;font-size:12px;font-weight:400}.corrections{margin:10px 0 0;font-size:12px}.launch-bar{align-items:stretch;display:flex}.launch{flex:1 1 0;grid-template-rows:18px 28px 18px;grid-template-columns:minmax(0,1fr);min-width:34px;display:grid}.launch-day{white-space:nowrap;font-size:12px;font-weight:600}.launch-span{background:var(--state);color:var(--panel);white-space:nowrap;border-radius:4px;justify-content:center;gap:6px;font:700 11px/28px ui-sans-serif,system-ui,sans-serif;display:flex;overflow:hidden;container-type:inline-size}.launch-span small{font-weight:500}@container (width<=84px){.launch-span small{display:none}}.launch-at,.launch-pause{color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}.launch-pause{flex:none;align-content:center;padding:0 4px;display:grid}.launch-pause span{border-top:1px dashed var(--muted);border-bottom:1px dashed var(--muted);padding:0 5px;line-height:16px}.launch-causes{gap:2px;margin-top:8px;font-size:12px;display:grid}.launch-causes li{grid-template-columns:28px minmax(0,1fr);gap:6px;display:grid}.launch-cause{color:var(--bad);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.lanes{grid-template-columns:112px minmax(0,1fr);column-gap:8px;display:grid}.lane-names{padding-top:16px}.lane-names div{align-content:center;height:28px;line-height:1.15;display:grid}.lane-names strong{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.lane-names span{color:var(--muted);white-space:nowrap;font-size:10.5px}.lane-area{background:repeating-linear-gradient(to bottom, transparent 0 27px, var(--line) 27px 28px) 0 16px / 100% calc(100% - 16px) no-repeat;position:relative}.round-band{background:color-mix(in srgb, var(--muted) 9%, transparent);position:absolute;top:16px;bottom:0}.resumption{border-left:1px dashed var(--bad);position:absolute;top:0;bottom:0}.resumption span{color:var(--bad);font-size:10.5px;position:absolute;top:0;left:3px}.sent-back{border-left:2px solid var(--run);position:absolute}.sent-back:after{content:"";border:4px solid #0000;position:absolute;left:-5px}.sent-back[data-direction=up]:after{border-bottom-color:var(--run);top:-7px}.sent-back[data-direction=down]:after{border-top-color:var(--run);bottom:-7px}.bar{border:1px solid var(--state);background:color-mix(in srgb, var(--state) 15%, var(--panel));min-width:3px;height:18px;color:var(--state);white-space:nowrap;cursor:pointer;border-radius:3px;padding:0 4px;font:700 10.5px/16px ui-sans-serif,system-ui,sans-serif;position:absolute;overflow:hidden}.bar:empty{padding:0}.gate{background:var(--state);cursor:pointer;border:0;border-radius:2px;width:10px;height:10px;margin-left:-5px;padding:0;position:absolute;transform:rotate(45deg)}.gate[data-state=passed]{background:color-mix(in srgb, var(--ok) 55%, var(--panel))}:is(.bar,.gate)[aria-current=true]{outline:2px solid var(--fg);outline-offset:1px}.lane-scroll{min-width:0;position:relative}.lane-scroll.zoomed{padding:0 8px;overflow:auto hidden}.lane-canvas{min-width:100%}.lane-axis,.cost-axis{height:30px;color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:10.5px;position:relative}.cost-axis{height:18px}.lane-axis span,.cost-axis span{position:absolute;transform:translate(-50%)}.lane-axis .tick,.cost-axis .tick{border-left:1px solid var(--muted);padding-top:2px;padding-left:3px;transform:none}.lane-axis .tick{top:14px}.zoom{letter-spacing:0;text-transform:none;align-items:baseline;gap:4px;display:flex}.zoom-level{color:var(--accent);font-variant-numeric:tabular-nums;font-size:12px}.cost-plot{border-bottom:1px solid var(--line);position:relative}.cost-plot svg{width:100%;height:100%;position:absolute;inset:0;overflow:visible}.cost-plot .spent{fill:none;stroke:var(--accent);stroke-width:2px}.cost-plot .budget{stroke:var(--bad);stroke-dasharray:4 3;stroke-width:1.5px}.cost-resumption{border-left:1px dashed color-mix(in srgb, var(--bad) 45%, transparent);position:absolute;top:0;bottom:0}.budget-label,.cost-now{white-space:nowrap;padding:0 2px;font-size:10.5px;position:absolute}.budget-label{color:var(--bad)}.cost-now{background:var(--panel);color:var(--accent);font-weight:600;right:0}.panel-title{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.panel-title h3{font-size:16px}.tag{border:1px solid var(--line);color:var(--muted);letter-spacing:.04em;border-radius:4px;padding:0 6px;font-size:11px}.panel-nav{gap:2px;margin-left:auto;display:flex}.panel-nav button{border:1px solid var(--line);background:var(--panel);width:26px;height:24px;color:inherit;cursor:pointer;border-radius:5px}.panel-nav button:disabled{opacity:.4;cursor:default}.panel-meta{color:var(--muted);flex-wrap:wrap;align-items:baseline;gap:4px 8px;margin:4px 0 8px;font-size:12.5px;display:flex}.panel-meta .link{margin-left:auto}.tabs{border-bottom:1px solid var(--line);gap:2px;margin:0 -12px 12px;padding:0 12px;display:flex}.tabs button{color:var(--muted);font:inherit;cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;align-items:center;gap:5px;padding:7px 10px;font-size:13px;display:flex}.tabs button[aria-selected=true]{border-bottom-color:var(--accent);color:var(--fg);font-weight:600}.badge{background:color-mix(in srgb, var(--muted) 16%, transparent);color:var(--muted);border-radius:999px;padding:0 5px;font-size:10.5px;font-weight:600}.badge.warn{background:color-mix(in srgb, var(--run) 16%, transparent);color:var(--run)}.tab-body>h4:first-child{margin-top:0}.sheet{grid-template-columns:max-content minmax(0,1fr);gap:5px 14px;margin:12px 0 0;font-size:13px;display:grid}.sheet dt{color:var(--muted)}.sheet dd{overflow-wrap:anywhere;font-variant-numeric:tabular-nums;margin:0}.callout{background:color-mix(in srgb, currentColor 8%, transparent);white-space:pre-wrap;border-left:3px solid;border-radius:4px;margin:0 0 10px;padding:8px 10px;font-size:12px}pre.frame{border:1px solid var(--line);border-radius:6px;max-height:360px;padding:6px 8px;overflow:auto}pre.result{max-height:280px}.feed-bar{margin:10px 0 8px}.feed-search{width:140px;margin-left:auto;padding:2px 8px;font-size:12px}.feed{border:1px solid var(--line);border-radius:8px;max-height:440px;font-size:12.5px;overflow:auto}.feed li{border-bottom:1px solid var(--line);grid-template-columns:62px 14px minmax(0,1fr) auto;align-items:baseline;gap:6px;padding:3px 8px;display:grid}.feed li:last-child{border-bottom:0}.feed .at,.feed .took{color:var(--muted);font:11.5px ui-monospace,SFMono-Regular,Menlo,monospace}.feed .icon{color:var(--muted);text-align:center}.feed .text{overflow-wrap:anywhere;white-space:pre-wrap}.feed li[data-kind=call]{background:color-mix(in srgb, var(--accent) 9%, transparent)}.feed li[data-kind=call] .icon{color:var(--accent)}.feed li[data-kind=warning]{color:var(--run)}.feed li.bad .text,.feed li.bad .icon{color:var(--bad)}.sent-part{margin:2px 0}.sent-part>summary{cursor:pointer;flex-wrap:wrap;align-items:baseline;gap:2px 8px;font-size:13px;display:flex}.sent-part>summary .part{font-weight:600}.sent-part>summary .muted{font-size:12px}.sent-part>pre{margin:4px 0 8px}details.entry-data summary{color:var(--muted);cursor:pointer}details.entry-data pre{max-height:200px;overflow:auto}.cost-bars{grid-template-columns:minmax(80px,160px) minmax(0,1fr) 72px;align-items:center;gap:5px 10px;margin:0;font-size:12px;display:grid}.cost-bars dt{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.cost-bars dd{margin:0}.cost-track{height:12px}.cost-bar{background:var(--accent);opacity:.75;border-radius:3px;height:100%;display:block}.cost-bars .value{text-align:right;font-variant-numeric:tabular-nums}.cost-bars .total{border-top:1px solid var(--line);padding-top:5px;font-weight:600}.data{margin-top:12px}.data-head{justify-content:space-between;align-items:baseline;display:flex}.data-head h4{margin:0}.tree{margin:4px 0 0;padding:0;font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;list-style:none}.tree .tree{border-left:1px solid var(--line);margin:0 0 4px 6px;padding-left:10px}.tree .leaf{gap:8px;padding-left:18px;display:flex}details.data-key summary{cursor:pointer;gap:8px;display:flex}details.data-key pre.text{margin:2px 0 6px;font-family:ui-sans-serif,system-ui,sans-serif;font-size:12.5px}.tree .key{color:var(--accent);flex:none}.tree .label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.tree .type,.tree .size,.tree .inline.null{color:var(--muted)}.tree .size{flex:none;margin-left:auto}.tree .inline{overflow-wrap:anywhere}.tree .inline.number,.tree .inline.boolean{color:var(--run)}.sent-part>.tree{margin:4px 0 8px}details:is(.sent-part,.data-key)>summary{list-style:none}details:is(.sent-part,.data-key)>summary::-webkit-details-marker{display:none}details:is(.sent-part,.data-key)>summary:before{width:10px;color:var(--muted);content:"▸";flex:none}details:is(.sent-part,.data-key)[open]>summary:before{content:"▾"}.runtime{color:var(--muted);overflow-wrap:anywhere;margin:10px 0 0;font-size:12px}button.link{color:var(--accent);font:inherit;cursor:pointer;background:0 0;border:0;padding:0;font-size:12px}:is(.bar,.gate,button,a,input,summary):focus-visible{outline:2px solid var(--accent);outline-offset:1px}