@apisurf/wireui 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +157 -0
  3. package/bin/wireui.mjs +233 -0
  4. package/dist/nitro.json +17 -0
  5. package/dist/public/assets/collections-CLHcfRfi.js +1 -0
  6. package/dist/public/assets/globals-CZiNFGaK.css +1 -0
  7. package/dist/public/assets/index-D5VFd_3H.js +14 -0
  8. package/dist/public/assets/kv-table-oB5gcq0i.js +2 -0
  9. package/dist/public/assets/query-BKAhHNIi.js +19 -0
  10. package/dist/public/assets/requests._requestId-Bu7E2eoI.js +1 -0
  11. package/dist/public/assets/routes-DDY2CfzA.js +1 -0
  12. package/dist/public/assets/runs._runId-CzwtVjYq.js +1 -0
  13. package/dist/public/assets/scoped-DB8raIWx.js +1 -0
  14. package/dist/public/assets/split-Br1RX2I4.js +1 -0
  15. package/dist/public/favicon.svg +1 -0
  16. package/dist/server/_chunks/ssr-renderer.mjs +26 -0
  17. package/dist/server/_libs/@tanstack/react-router+[...].mjs +15246 -0
  18. package/dist/server/_libs/h3+rou3+srvx.mjs +2086 -0
  19. package/dist/server/_libs/hookable.mjs +41 -0
  20. package/dist/server/_libs/tanstack__history.mjs +369 -0
  21. package/dist/server/_libs/ufo.mjs +64 -0
  22. package/dist/server/_runtime.mjs +35 -0
  23. package/dist/server/_ssr/api-Dw1Zb_PK.mjs +931 -0
  24. package/dist/server/_ssr/collections-CqidTQpw.mjs +495 -0
  25. package/dist/server/_ssr/empty-plugin-adapters-CCuiUfKt.mjs +5 -0
  26. package/dist/server/_ssr/kv-table-DQUkoItO.mjs +458 -0
  27. package/dist/server/_ssr/query-D4so9xiY.mjs +416 -0
  28. package/dist/server/_ssr/requests._requestId-BFukRwqL.mjs +942 -0
  29. package/dist/server/_ssr/router-CPcgc_kh.mjs +3176 -0
  30. package/dist/server/_ssr/routes-DaV9vGHn.mjs +375 -0
  31. package/dist/server/_ssr/runs._runId-Df_LmPHa.mjs +836 -0
  32. package/dist/server/_ssr/scoped-CnQ56K8h.mjs +42 -0
  33. package/dist/server/_ssr/split-DWrMIs7K.mjs +115 -0
  34. package/dist/server/_ssr/ssr.mjs +6827 -0
  35. package/dist/server/_ssr/start-zk_ZfrWl.mjs +4 -0
  36. package/dist/server/_tanstack-start-manifest_v-CARKjHfs.mjs +50 -0
  37. package/dist/server/index.mjs +368 -0
  38. package/package.json +71 -0
@@ -0,0 +1,942 @@
1
+ import { __toESM } from "../_runtime.mjs";
2
+ import { Link, require_jsx_runtime, require_react, useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
3
+ import { Callout, CopyButton, Empty, FilterInput, IconAlert, IconButton, IconChevronLeft, IconChevronRight, IconCollections, IconDown, IconRun, IconUp, Kbd, MethodBadge, Pill, Route$1, STATUS_TEXT, SectionTitle, Segmented, StatusBadge, TabList, css, cx, faintText, formatBytes, formatClockMs, formatDay, formatMs, formatOffset, mono, queryParams, requestCookies, responseCookies, tabKey, timingPhases, toCurl, truncate, usePrefs, useTab, useUnreadable } from "./router-CPcgc_kh.mjs";
4
+ import { Split } from "./split-DWrMIs7K.mjs";
5
+ import { BodyViewer, Code, JsonView, KvTable } from "./kv-table-DQUkoItO.mjs";
6
+ //#region node_modules/.nitro/vite/services/ssr/assets/requests._requestId-BFukRwqL.js
7
+ var import_react = /* @__PURE__ */ __toESM(require_react());
8
+ var import_jsx_runtime = require_jsx_runtime();
9
+ function MessagesPane({ messages, openedAt }) {
10
+ const [direction, setDirection] = import_react.useState("all");
11
+ const [filter, setFilter] = import_react.useState("");
12
+ const [selectedId, setSelectedId] = import_react.useState(messages[0]?.id ?? null);
13
+ const needle = filter.trim().toLowerCase();
14
+ const visible = messages.filter((m) => (direction === "all" || m.direction === direction) && (!needle || m.name.toLowerCase().includes(needle) || (m.text ?? "").toLowerCase().includes(needle)));
15
+ const selected = messages.find((m) => m.id === selectedId) ?? null;
16
+ const sentCount = messages.filter((m) => m.direction === "sent").length;
17
+ if (messages.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, {
18
+ title: "No messages",
19
+ children: "The connection opened but nothing was sent or received on it."
20
+ });
21
+ const list = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
22
+ className: css({
23
+ display: "flex",
24
+ flexDir: "column",
25
+ h: "full",
26
+ minH: "0"
27
+ }),
28
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
29
+ className: css({
30
+ display: "flex",
31
+ gap: "2",
32
+ px: "3",
33
+ py: "2",
34
+ alignItems: "center",
35
+ flexShrink: 0
36
+ }),
37
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Segmented, {
38
+ items: [
39
+ {
40
+ id: "all",
41
+ label: `All ${messages.length}`
42
+ },
43
+ {
44
+ id: "sent",
45
+ label: `Sent ${sentCount}`
46
+ },
47
+ {
48
+ id: "received",
49
+ label: `Received ${messages.length - sentCount}`
50
+ }
51
+ ],
52
+ value: direction,
53
+ onSelect: setDirection
54
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterInput, {
55
+ className: css({ flex: "1" }),
56
+ placeholder: "Filter messages…",
57
+ value: filter,
58
+ onChange: (e) => setFilter(e.target.value)
59
+ })]
60
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
61
+ className: css({
62
+ flex: "1",
63
+ minH: "0",
64
+ overflow: "auto"
65
+ }),
66
+ role: "listbox",
67
+ "aria-label": "Messages",
68
+ children: [visible.map((m) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
69
+ type: "button",
70
+ role: "option",
71
+ "aria-selected": m.id === selectedId,
72
+ onClick: () => setSelectedId(m.id),
73
+ className: css({
74
+ display: "grid",
75
+ gridTemplateColumns: "18px 76px 70px 1fr 64px",
76
+ alignItems: "center",
77
+ gap: "2",
78
+ w: "full",
79
+ px: "3",
80
+ py: "1",
81
+ border: "none",
82
+ borderBottom: "1px solid",
83
+ borderColor: "line",
84
+ bg: "transparent",
85
+ textAlign: "left",
86
+ cursor: "pointer",
87
+ _hover: { bg: "hover" },
88
+ "&[aria-selected=true]": { bg: "selected" }
89
+ }),
90
+ children: [
91
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
92
+ title: m.direction,
93
+ className: m.direction === "sent" ? css({ color: "post" }) : css({ color: "get" }),
94
+ children: m.direction === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconUp, { size: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconDown, { size: 14 })
95
+ }),
96
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
97
+ className: cx(mono, css({ color: "faint" })),
98
+ children: formatOffset(m.ts - openedAt)
99
+ }),
100
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
101
+ className: cx(mono, truncate, css({ color: "muted" })),
102
+ children: m.name
103
+ }),
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
105
+ className: cx(mono, truncate),
106
+ children: m.text ?? (m.code != null ? `close ${m.code}` : "—")
107
+ }),
108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
109
+ className: cx(mono, css({
110
+ color: "faint",
111
+ textAlign: "right"
112
+ })),
113
+ children: formatBytes(m.bytes)
114
+ })
115
+ ]
116
+ }, m.id)), visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
117
+ className: css({
118
+ p: "4",
119
+ color: "faint",
120
+ textAlign: "center"
121
+ }),
122
+ children: "No messages match."
123
+ }) : null]
124
+ })]
125
+ });
126
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Split, {
127
+ direction: "stacked",
128
+ storageKey: "messages",
129
+ initial: .55,
130
+ first: list,
131
+ second: selected ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageDetail, {
132
+ message: selected,
133
+ openedAt
134
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, { title: "Select a message" })
135
+ });
136
+ }
137
+ function MessageDetail({ message, openedAt }) {
138
+ const json = import_react.useMemo(() => parseJson(message.text), [message.text]);
139
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
140
+ className: css({
141
+ display: "flex",
142
+ flexDir: "column",
143
+ h: "full",
144
+ minH: "0"
145
+ }),
146
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
147
+ className: css({
148
+ display: "flex",
149
+ alignItems: "center",
150
+ gap: "2",
151
+ px: "3",
152
+ py: "1.5",
153
+ borderBottom: "1px solid",
154
+ borderColor: "line",
155
+ flexShrink: 0,
156
+ flexWrap: "wrap"
157
+ }),
158
+ children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
160
+ tone: message.direction === "sent" ? "warn" : "ok",
161
+ children: message.direction
162
+ }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
164
+ className: mono,
165
+ children: ["#", message.seq]
166
+ }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
168
+ className: cx(mono, css({ color: "muted" })),
169
+ children: message.name
170
+ }),
171
+ message.event_id ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
172
+ title: message.event_id,
173
+ className: cx(mono, truncate, css({
174
+ maxW: "240px",
175
+ color: "faint",
176
+ fontSize: "11px"
177
+ })),
178
+ children: ["id ", message.event_id]
179
+ }) : null,
180
+ message.code != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Pill, { children: ["code ", message.code] }) : null,
181
+ message.truncated ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, {
182
+ tone: "warn",
183
+ children: "truncated"
184
+ }) : null,
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
186
+ className: cx(mono, css({ color: "faint" })),
187
+ children: [
188
+ formatOffset(message.ts - openedAt),
189
+ " · ",
190
+ formatBytes(message.bytes)
191
+ ]
192
+ }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
194
+ className: css({ ml: "auto" }),
195
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, { text: message.text })
196
+ })
197
+ ]
198
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
199
+ className: css({
200
+ flex: "1",
201
+ minH: "0",
202
+ overflow: "auto",
203
+ bg: "well"
204
+ }),
205
+ children: message.text == null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Empty, { title: message.body_id == null ? "No payload" : "Binary payload" }) : json !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(JsonView, { value: json }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Code, {
206
+ text: message.text,
207
+ wrap: true
208
+ })
209
+ })]
210
+ });
211
+ }
212
+ /** The payload as JSON when it is an object or array, else undefined. */
213
+ function parseJson(text) {
214
+ if (!text) return void 0;
215
+ const first = text.trimStart()[0];
216
+ if (first !== "{" && first !== "[") return void 0;
217
+ try {
218
+ return JSON.parse(text);
219
+ } catch {
220
+ return;
221
+ }
222
+ }
223
+ var PHASE_BAR = {
224
+ dns: css({ bg: "sse" }),
225
+ connect: css({ bg: "info" }),
226
+ tls: css({ bg: "patch" }),
227
+ wait: css({ bg: "post" }),
228
+ download: css({ bg: "get" }),
229
+ other: css({ bg: "faint" })
230
+ };
231
+ function TimingWaterfall({ request }) {
232
+ const phases = timingPhases(request);
233
+ const total = Math.max(request.duration_ms, .001);
234
+ let offset = 0;
235
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, {
237
+ right: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
238
+ className: cx(mono, css({ fontWeight: "600" })),
239
+ children: formatMs(request.duration_ms)
240
+ }),
241
+ children: request.kind === "http" ? "Timing" : "Connection"
242
+ }),
243
+ phases.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
244
+ className: faintText,
245
+ children: "Only the total duration was measured for this exchange."
246
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
247
+ className: css({
248
+ display: "flex",
249
+ flexDir: "column",
250
+ gap: "1"
251
+ }),
252
+ children: phases.map((phase) => {
253
+ const left = offset / total * 100;
254
+ const width = Math.max(phase.ms / total * 100, .4);
255
+ offset += phase.ms;
256
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
257
+ title: phase.hint,
258
+ className: css({
259
+ display: "grid",
260
+ gridTemplateColumns: "150px 1fr 80px",
261
+ alignItems: "center",
262
+ gap: "3",
263
+ py: "1"
264
+ }),
265
+ children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
267
+ className: css({
268
+ color: "muted",
269
+ fontSize: "12px"
270
+ }),
271
+ children: phase.label
272
+ }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
274
+ className: css({
275
+ position: "relative",
276
+ h: "10px",
277
+ bg: "hover",
278
+ rounded: "full"
279
+ }),
280
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
281
+ className: cx(css({
282
+ position: "absolute",
283
+ top: "0",
284
+ bottom: "0",
285
+ rounded: "full"
286
+ }), PHASE_BAR[phase.key]),
287
+ style: {
288
+ left: `${left}%`,
289
+ width: `${Math.min(width, 100 - left)}%`
290
+ }
291
+ })
292
+ }),
293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
294
+ className: cx(mono, css({ textAlign: "right" })),
295
+ children: formatMs(phase.ms)
296
+ })
297
+ ]
298
+ }, phase.key);
299
+ })
300
+ }),
301
+ request.kind === "http" && request.dns_ms == null && request.connect_ms == null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
302
+ className: cx(faintText, css({
303
+ mt: "2",
304
+ fontSize: "12px"
305
+ })),
306
+ children: "No DNS, connect or TLS phase: the request reused an open connection."
307
+ }) : null
308
+ ] });
309
+ }
310
+ /**
311
+ * The exchange as a verbose transcript: `>` for what was sent, `<` for what
312
+ * came back, `*` for what happened in between. Reads like `curl -v`, which is
313
+ * the format most people already debug in.
314
+ */
315
+ function Transcript({ request, requestHeaders, responseHeaders }) {
316
+ const lines = transcriptLines(request, requestHeaders, responseHeaders);
317
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { children: "Transcript" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
318
+ className: cx(mono, css({
319
+ bg: "well",
320
+ border: "1px solid",
321
+ borderColor: "line",
322
+ rounded: "md",
323
+ p: "3",
324
+ lineHeight: "1.6",
325
+ overflowX: "auto"
326
+ })),
327
+ children: lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
328
+ className: cx(css({
329
+ whiteSpace: "pre-wrap",
330
+ wordBreak: "break-all"
331
+ }), line.tone === "sent" && css({ color: "post" }), line.tone === "received" && css({ color: "get" }), line.tone === "info" && css({ color: "faint" })),
332
+ children: [
333
+ line.prefix,
334
+ " ",
335
+ line.text
336
+ ]
337
+ }, i))
338
+ })] });
339
+ }
340
+ function transcriptLines(request, requestHeaders, responseHeaders) {
341
+ const lines = [];
342
+ const info = (text) => lines.push({
343
+ prefix: "*",
344
+ text,
345
+ tone: "info"
346
+ });
347
+ const sent = (text) => lines.push({
348
+ prefix: ">",
349
+ text,
350
+ tone: "sent"
351
+ });
352
+ const got = (text) => lines.push({
353
+ prefix: "<",
354
+ text,
355
+ tone: "received"
356
+ });
357
+ info(`${new Date(request.ts).toISOString()} ${request.kind.toUpperCase()} ${request.url}`);
358
+ for (const line of connectionLines(request)) info(line);
359
+ const target = request.query ? `${request.path}?${request.query}` : request.path;
360
+ sent(`${request.method} ${target}`);
361
+ if (!requestHeaders.some(([n]) => n.toLowerCase() === "host")) sent(`host: ${request.host}`);
362
+ for (const [name, value] of requestHeaders) sent(`${name}: ${value}`);
363
+ if (request.request_bytes > 0) info(`${request.request_bytes} bytes sent`);
364
+ if (request.status != null) {
365
+ got(`${request.status} ${STATUS_TEXT[request.status] ?? ""}`.trim());
366
+ for (const [name, value] of responseHeaders) got(`${name}: ${value}`);
367
+ if (request.ttfb_ms != null) info(`First byte after ${formatMs(request.ttfb_ms)}`);
368
+ info(`${request.response_bytes} bytes received`);
369
+ }
370
+ if (request.error_name) info(`${request.error_name}: ${request.error_message ?? ""}`);
371
+ info(`Completed in ${formatMs(request.duration_ms)}`);
372
+ return lines;
373
+ }
374
+ /** How the connection was established, or that an open one was reused. */
375
+ function connectionLines(request) {
376
+ if (request.dns_ms == null && request.connect_ms == null && request.tls_ms == null) return ["Re-using existing connection"];
377
+ const lines = [];
378
+ if (request.dns_ms != null) lines.push(`Resolved ${request.host} in ${formatMs(request.dns_ms)}`);
379
+ if (request.connect_ms != null) lines.push(`Connected in ${formatMs(request.connect_ms)}`);
380
+ if (request.tls_ms != null) lines.push(`TLS handshake in ${formatMs(request.tls_ms)}`);
381
+ return lines;
382
+ }
383
+ function RequestPage() {
384
+ const { detail, run, neighbours, runRequestCount } = Route$1.useLoaderData();
385
+ const search = Route$1.useSearch();
386
+ const navigate = useNavigate({ from: Route$1.fullPath });
387
+ const { prefs } = usePrefs();
388
+ const unreadable = useUnreadable();
389
+ const r = detail.request;
390
+ const failed = r.error_name != null || r.status == null || r.status >= 400;
391
+ useTab({
392
+ key: tabKey.request(r.id),
393
+ title: r.path,
394
+ icon: "request",
395
+ method: r.method,
396
+ kind: r.kind,
397
+ failed
398
+ });
399
+ const go = import_react.useCallback((id) => {
400
+ if (id == null) return;
401
+ navigate({
402
+ to: "/requests/$requestId",
403
+ params: { requestId: String(id) },
404
+ search: (s) => s
405
+ });
406
+ }, [navigate]);
407
+ import_react.useEffect(() => {
408
+ const onKey = (event) => {
409
+ const target = event.target;
410
+ if (target && /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName)) return;
411
+ if (event.key === "[") go(neighbours.prev);
412
+ if (event.key === "]") go(neighbours.next);
413
+ };
414
+ window.addEventListener("keydown", onKey);
415
+ return () => window.removeEventListener("keydown", onKey);
416
+ }, [
417
+ go,
418
+ neighbours.prev,
419
+ neighbours.next
420
+ ]);
421
+ if (unreadable) return unreadable;
422
+ const stream = r.kind !== "http";
423
+ const reqTab = search.req ?? (r.query ? "params" : detail.requestBody.present ? "body" : "headers");
424
+ const defaultRes = stream ? "messages" : "body";
425
+ const resTab = search.res && (stream ? search.res !== "body" : search.res !== "messages") ? search.res : defaultRes;
426
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
427
+ className: css({
428
+ display: "flex",
429
+ flexDir: "column",
430
+ h: "full",
431
+ minH: "0"
432
+ }),
433
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
434
+ className: css({
435
+ px: "4",
436
+ pt: "3",
437
+ pb: "2.5",
438
+ display: "flex",
439
+ flexDir: "column",
440
+ gap: "2",
441
+ flexShrink: 0
442
+ }),
443
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
444
+ className: css({
445
+ display: "flex",
446
+ alignItems: "center",
447
+ gap: "2",
448
+ fontSize: "12px",
449
+ color: "muted",
450
+ minW: "0"
451
+ }),
452
+ children: [
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
454
+ to: "/collections",
455
+ search: { entry: run.entry },
456
+ className: css({
457
+ display: "inline-flex",
458
+ alignItems: "center",
459
+ gap: "1",
460
+ color: "muted",
461
+ _hover: { color: "accent" }
462
+ }),
463
+ title: run.rel,
464
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconCollections, { size: 12 }), run.file]
465
+ }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
467
+ className: css({ color: "faint" }),
468
+ children: "›"
469
+ }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Link, {
471
+ to: "/runs/$runId",
472
+ params: { runId: String(run.id) },
473
+ className: css({
474
+ display: "inline-flex",
475
+ alignItems: "center",
476
+ gap: "1",
477
+ color: "muted",
478
+ _hover: { color: "accent" }
479
+ }),
480
+ children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconRun, { size: 10 }),
482
+ "Run #",
483
+ run.id
484
+ ]
485
+ }),
486
+ run.env ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, { children: run.env }) : null,
487
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
488
+ className: css({ color: "faint" }),
489
+ children: "›"
490
+ }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
492
+ className: css({ whiteSpace: "nowrap" }),
493
+ children: [
494
+ r.seq + 1,
495
+ " of ",
496
+ runRequestCount
497
+ ]
498
+ }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
500
+ className: cx(mono, css({
501
+ color: "faint",
502
+ whiteSpace: "nowrap"
503
+ })),
504
+ children: [
505
+ formatDay(Date.now(), r.ts),
506
+ " ",
507
+ formatClockMs(r.ts),
508
+ " ·",
509
+ " ",
510
+ formatOffset(r.ts - run.started_at)
511
+ ]
512
+ }),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
514
+ className: css({
515
+ ml: "auto",
516
+ display: "flex",
517
+ alignItems: "center",
518
+ gap: "1"
519
+ }),
520
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
521
+ label: "Previous request in run ([)",
522
+ disabled: neighbours.prev == null,
523
+ onClick: () => go(neighbours.prev),
524
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronLeft, {})
525
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconButton, {
526
+ label: "Next request in run (])",
527
+ disabled: neighbours.next == null,
528
+ onClick: () => go(neighbours.next),
529
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconChevronRight, {})
530
+ })]
531
+ })
532
+ ]
533
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(UrlBar, { detail })]
534
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
535
+ className: css({
536
+ flex: "1",
537
+ minH: "0",
538
+ display: "flex",
539
+ borderTop: "1px solid",
540
+ borderColor: "line"
541
+ }),
542
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Split, {
543
+ direction: prefs.layout,
544
+ storageKey: `request-${prefs.layout}`,
545
+ initial: prefs.layout === "side" ? .42 : .4,
546
+ first: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RequestPane, {
547
+ detail,
548
+ tab: reqTab,
549
+ onTab: (next) => void navigate({
550
+ search: (s) => ({
551
+ ...s,
552
+ req: next
553
+ }),
554
+ replace: true
555
+ })
556
+ }),
557
+ second: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ResponsePane, {
558
+ detail,
559
+ tab: resTab,
560
+ onTab: (next) => void navigate({
561
+ search: (s) => ({
562
+ ...s,
563
+ res: next
564
+ }),
565
+ replace: true
566
+ })
567
+ })
568
+ })
569
+ })]
570
+ });
571
+ }
572
+ function UrlBar({ detail }) {
573
+ const r = detail.request;
574
+ const curl = r.kind === "http" ? toCurl(r, detail.requestHeaders, detail.requestBody) : null;
575
+ const scheme = r.url.slice(0, r.url.indexOf(r.host));
576
+ const rest = r.url.slice(scheme.length + r.host.length);
577
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
578
+ className: css({
579
+ display: "flex",
580
+ gap: "2",
581
+ alignItems: "stretch"
582
+ }),
583
+ children: [
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
585
+ className: css({
586
+ display: "flex",
587
+ alignItems: "center",
588
+ flex: "1",
589
+ minW: "0",
590
+ h: "36px",
591
+ rounded: "md",
592
+ border: "1px solid",
593
+ borderColor: "lineStrong",
594
+ bg: "well",
595
+ overflow: "hidden"
596
+ }),
597
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
598
+ className: css({
599
+ display: "flex",
600
+ alignItems: "center",
601
+ px: "3",
602
+ h: "full",
603
+ borderRight: "1px solid",
604
+ borderColor: "line",
605
+ flexShrink: 0,
606
+ "& > span": { fontSize: "12px" }
607
+ }),
608
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MethodBadge, {
609
+ method: r.method,
610
+ kind: r.kind
611
+ })
612
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
613
+ className: cx(mono, truncate, css({
614
+ px: "3",
615
+ fontSize: "13px",
616
+ userSelect: "all",
617
+ flex: "1"
618
+ })),
619
+ title: r.url,
620
+ children: [
621
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
622
+ className: css({ color: "faint" }),
623
+ children: scheme
624
+ }),
625
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: r.host }),
626
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
627
+ className: css({ color: "muted" }),
628
+ children: rest
629
+ })
630
+ ]
631
+ })]
632
+ }),
633
+ curl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
634
+ text: curl,
635
+ label: "cURL"
636
+ }) : null,
637
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CopyButton, {
638
+ text: r.url,
639
+ label: "URL"
640
+ })
641
+ ]
642
+ });
643
+ }
644
+ function PaneHeader({ title, children }) {
645
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
646
+ className: css({
647
+ display: "flex",
648
+ alignItems: "center",
649
+ gap: "2.5",
650
+ px: "3",
651
+ h: "36px",
652
+ flexShrink: 0,
653
+ bg: "chrome",
654
+ borderBottom: "1px solid",
655
+ borderColor: "line"
656
+ }),
657
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
658
+ className: css({
659
+ fontWeight: "600",
660
+ fontSize: "12px"
661
+ }),
662
+ children: title
663
+ }), children]
664
+ });
665
+ }
666
+ var paneBody = css({
667
+ flex: "1",
668
+ minH: "0",
669
+ overflow: "auto"
670
+ });
671
+ function RequestPane({ detail, tab, onTab }) {
672
+ const r = detail.request;
673
+ const params = queryParams(r.query);
674
+ const cookies = requestCookies(detail.requestHeaders);
675
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
676
+ className: css({
677
+ display: "flex",
678
+ flexDir: "column",
679
+ h: "full",
680
+ minH: "0"
681
+ }),
682
+ children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PaneHeader, {
684
+ title: r.kind === "http" ? "Request" : "Handshake",
685
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
686
+ className: cx(mono, css({
687
+ color: "faint",
688
+ fontSize: "11px"
689
+ })),
690
+ children: [formatBytes(r.request_bytes), " sent"]
691
+ })
692
+ }),
693
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabList, {
694
+ dense: true,
695
+ items: [
696
+ {
697
+ id: "params",
698
+ label: "Params",
699
+ count: params.length
700
+ },
701
+ {
702
+ id: "headers",
703
+ label: "Headers",
704
+ count: detail.requestHeaders.length
705
+ },
706
+ {
707
+ id: "body",
708
+ label: "Body",
709
+ hidden: r.kind !== "http",
710
+ count: detail.requestBody.present ? "â—Ź" : void 0
711
+ },
712
+ {
713
+ id: "cookies",
714
+ label: "Cookies",
715
+ count: cookies.length,
716
+ hidden: cookies.length === 0
717
+ },
718
+ {
719
+ id: "tags",
720
+ label: "Tags",
721
+ count: detail.tags.length,
722
+ hidden: detail.tags.length === 0
723
+ }
724
+ ],
725
+ value: tab,
726
+ onSelect: onTab
727
+ }),
728
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
729
+ className: paneBody,
730
+ children: [
731
+ tab === "params" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
732
+ rows: params.map(([name, value]) => ({
733
+ name,
734
+ value
735
+ })),
736
+ empty: "No query parameters"
737
+ }) : null,
738
+ tab === "headers" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
739
+ rows: detail.requestHeaders.map(([name, value]) => ({
740
+ name,
741
+ value
742
+ })),
743
+ empty: "No request headers",
744
+ nameLabel: "Header"
745
+ }) : null,
746
+ tab === "body" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BodyViewer, {
747
+ body: detail.requestBody,
748
+ emptyTitle: "No request body"
749
+ }) : null,
750
+ tab === "cookies" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CookieTable, { cookies }) : null,
751
+ tab === "tags" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
752
+ rows: detail.tags.map((t) => ({
753
+ name: t.key,
754
+ value: t.value
755
+ })),
756
+ empty: "No tags"
757
+ }) : null
758
+ ]
759
+ })
760
+ ]
761
+ });
762
+ }
763
+ function ResponsePane({ detail, tab, onTab }) {
764
+ const r = detail.request;
765
+ const stream = r.kind !== "http";
766
+ const cookies = responseCookies(detail.responseHeaders);
767
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
768
+ className: css({
769
+ display: "flex",
770
+ flexDir: "column",
771
+ h: "full",
772
+ minH: "0"
773
+ }),
774
+ children: [
775
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PaneHeader, {
776
+ title: stream ? "Connection" : "Response",
777
+ children: [
778
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatusBadge, {
779
+ status: r.status,
780
+ error: r.error_name,
781
+ pill: true
782
+ }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
784
+ label: stream ? "Open" : "Time",
785
+ value: formatMs(r.duration_ms)
786
+ }),
787
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Metric, {
788
+ label: stream ? "Received" : "Size",
789
+ value: formatBytes(r.response_bytes)
790
+ })
791
+ ]
792
+ }),
793
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TabList, {
794
+ dense: true,
795
+ items: [
796
+ {
797
+ id: "body",
798
+ label: "Body",
799
+ hidden: stream
800
+ },
801
+ {
802
+ id: "messages",
803
+ label: "Messages",
804
+ count: detail.messages.length,
805
+ hidden: !stream
806
+ },
807
+ {
808
+ id: "headers",
809
+ label: "Headers",
810
+ count: detail.responseHeaders.length
811
+ },
812
+ {
813
+ id: "cookies",
814
+ label: "Cookies",
815
+ count: cookies.length,
816
+ hidden: cookies.length === 0
817
+ },
818
+ {
819
+ id: "timeline",
820
+ label: "Timeline"
821
+ }
822
+ ],
823
+ value: tab,
824
+ onSelect: onTab
825
+ }),
826
+ r.error_name ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
827
+ className: css({
828
+ px: "3",
829
+ pt: "2.5",
830
+ flexShrink: 0
831
+ }),
832
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Callout, {
833
+ tone: "bad",
834
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconAlert, { size: 15 }),
835
+ title: r.status == null ? "Could not get a response" : r.error_name,
836
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
837
+ className: mono,
838
+ children: [
839
+ r.error_name,
840
+ ": ",
841
+ r.error_message
842
+ ]
843
+ })
844
+ })
845
+ }) : null,
846
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
847
+ className: paneBody,
848
+ children: [
849
+ tab === "body" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BodyViewer, {
850
+ body: detail.responseBody,
851
+ emptyTitle: "No response body"
852
+ }) : null,
853
+ tab === "messages" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessagesPane, {
854
+ messages: detail.messages,
855
+ openedAt: r.ts
856
+ }) : null,
857
+ tab === "headers" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
858
+ rows: detail.responseHeaders.map(([name, value]) => ({
859
+ name,
860
+ value
861
+ })),
862
+ empty: "No response headers",
863
+ nameLabel: "Header"
864
+ }) : null,
865
+ tab === "cookies" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CookieTable, { cookies }) : null,
866
+ tab === "timeline" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
867
+ className: css({
868
+ p: "4",
869
+ display: "flex",
870
+ flexDir: "column",
871
+ gap: "5"
872
+ }),
873
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimingWaterfall, { request: r }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Transcript, {
874
+ request: r,
875
+ requestHeaders: detail.requestHeaders,
876
+ responseHeaders: detail.responseHeaders
877
+ })]
878
+ }) : null
879
+ ]
880
+ }),
881
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
882
+ className: css({
883
+ px: "3",
884
+ py: "1",
885
+ borderTop: "1px solid",
886
+ borderColor: "line",
887
+ color: "faint",
888
+ fontSize: "11px",
889
+ flexShrink: 0,
890
+ display: "flex",
891
+ gap: "3"
892
+ }),
893
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "[" }),
895
+ " ",
896
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "]" }),
897
+ " previous / next request"
898
+ ] }), r.ttfb_ms != null && !stream ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: ["TTFB ", formatMs(r.ttfb_ms)] }) : null]
899
+ })
900
+ ]
901
+ });
902
+ }
903
+ function Metric({ label, value }) {
904
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
905
+ className: css({
906
+ display: "inline-flex",
907
+ gap: "1",
908
+ fontSize: "12px",
909
+ whiteSpace: "nowrap"
910
+ }),
911
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
912
+ className: css({ color: "muted" }),
913
+ children: label
914
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
915
+ className: cx(mono, css({ fontWeight: "600" })),
916
+ children: value
917
+ })]
918
+ });
919
+ }
920
+ function CookieTable({ cookies }) {
921
+ const rows = cookies.map((c) => ({
922
+ name: c.name,
923
+ value: c.value,
924
+ extra: Object.keys(c.attributes).length ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
925
+ className: css({
926
+ display: "flex",
927
+ gap: "1",
928
+ flexWrap: "wrap"
929
+ }),
930
+ children: Object.entries(c.attributes).map(([k, v]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pill, { children: v === "true" ? k : `${k}=${v}` }, k))
931
+ }) : null
932
+ }));
933
+ const hasAttributes = cookies.some((c) => Object.keys(c.attributes).length > 0);
934
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(KvTable, {
935
+ rows,
936
+ empty: "No cookies",
937
+ nameLabel: "Name",
938
+ extraLabel: hasAttributes ? "Attributes" : void 0
939
+ });
940
+ }
941
+ //#endregion
942
+ export { RequestPage as component };