@tonyclaw/agent-inspector 2.1.15 → 2.1.17

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 (73) hide show
  1. package/.output/cli.js +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-B9sLBHw5.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-D9I9W9n3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-DGXK_Rii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-df_JzwsS.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
  9. package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
  10. package/.output/public/assets/index-Blqvndcn.js +1 -0
  11. package/.output/public/assets/index-Cs79WRQj.css +1 -0
  12. package/.output/public/assets/{index-DjKt8XKe.js → index-Dqc2r1ea.js} +1 -1
  13. package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-D3QWQlSB.js} +1 -1
  14. package/.output/public/assets/{main-DpD1N0S8.js → main-ZhxhOCmF.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +255 -201
  16. package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +2732 -1849
  19. package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-HQfrxjO1.mjs} +3 -3
  21. package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-QSlSD11e.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +3 -3
  23. package/.output/server/_ssr/{index-47XVPghS.mjs → index-D_EzfXaN.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
  26. package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-__o2wrfO.mjs} +505 -136
  27. package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
  28. package/.output/server/index.mjs +63 -63
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +535 -163
  34. package/src/components/ProxyViewerContainer.tsx +77 -67
  35. package/src/components/alerts/AlertsDialog.tsx +2 -1
  36. package/src/components/clients/ClientLogo.tsx +1 -1
  37. package/src/components/groups/GroupsDialog.tsx +19 -1
  38. package/src/components/providers/ProviderCard.tsx +133 -32
  39. package/src/components/providers/ProviderForm.tsx +61 -29
  40. package/src/components/providers/ProviderLogo.tsx +6 -1
  41. package/src/components/providers/ProvidersPanel.tsx +7 -7
  42. package/src/components/providers/SettingsDialog.tsx +13 -6
  43. package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
  44. package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
  45. package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
  46. package/src/components/proxy-viewer/ConversationHeader.tsx +153 -120
  47. package/src/components/proxy-viewer/LogEntry.tsx +130 -43
  48. package/src/components/proxy-viewer/LogEntryHeader.tsx +357 -354
  49. package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
  50. package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
  51. package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
  52. package/src/components/proxy-viewer/TurnGroup.tsx +33 -69
  53. package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
  54. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  55. package/src/components/proxy-viewer/viewerState.ts +0 -4
  56. package/src/components/ui/icon-trigger.ts +2 -0
  57. package/src/components/ui/transient-toast.tsx +104 -0
  58. package/src/lib/providerTestContract.ts +56 -10
  59. package/src/lib/providerTestPrompt.ts +23 -1
  60. package/src/lib/sessionInfoContract.ts +4 -0
  61. package/src/lib/upstreamUrl.ts +105 -1
  62. package/src/mcp/server.ts +71 -27
  63. package/src/mcp/toolHandlers.ts +24 -5
  64. package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
  65. package/src/proxy/sessionInfo.ts +31 -2
  66. package/src/proxy/sessionSupervisor.ts +20 -1
  67. package/src/proxy/store.ts +1 -1
  68. package/src/routes/api/providers.$providerId.test.log.ts +93 -14
  69. package/styles/globals.css +180 -180
  70. package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
  71. package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
  72. package/.output/public/assets/index-CI1-G8ua.js +0 -1
  73. package/.output/public/assets/index-DdhFqPsI.css +0 -1
@@ -1,4 +1,4 @@
1
- import { AlertTriangle, GitCompareArrows } from "lucide-react";
1
+ import { AlertTriangle, GitCompareArrows, Loader2, RefreshCw } from "lucide-react";
2
2
  import { Suspense, type JSX, type ReactNode } from "react";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState, memo } from "react";
4
4
  import {
@@ -27,6 +27,12 @@ import {
27
27
  } from "./lazy";
28
28
  import type { AnatomySegment } from "./anatomy/types";
29
29
  import { useAnatomyJump } from "./anatomy/useAnatomyJump";
30
+ import {
31
+ describeFullBodyHydrationError,
32
+ formatBodyHydrationFootprint,
33
+ formatBytes,
34
+ type FullBodyHydrationError,
35
+ } from "./bodyHydration";
30
36
  import { computeHeadersDiff, computeRequestDiff, DiffView } from "./diff";
31
37
  import { LogEntryHeader } from "./LogEntryHeader";
32
38
  import { RequestTools } from "./RequestToolsPanel";
@@ -57,7 +63,7 @@ function resolveFocusedTab(tab: LogFocusTab, anatomySegments: AnatomySegment[] |
57
63
  }
58
64
  }
59
65
 
60
- type BodyHydrationState = "idle" | "loading" | "failed";
66
+ type BodyHydrationStatus = "idle" | "loading" | "failed";
61
67
  type BodyChunkState = "idle" | "loading" | "success" | "failed";
62
68
 
63
69
  type BodyPreviewState = {
@@ -75,7 +81,7 @@ const BODY_CHUNK_BYTES = 256 * 1024;
75
81
  const CHUNKED_BODY_THRESHOLD_BYTES = 1024 * 1024;
76
82
 
77
83
  const TAB_TRIGGER_CLASS =
78
- "h-9 flex-none rounded-none border-0 border-b-2 border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:bg-cyan-400/[0.05] hover:text-foreground data-[state=active]:border-cyan-300 data-[state=active]:bg-cyan-300/[0.04] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
84
+ "h-9 flex-none rounded-md border border-transparent bg-transparent px-3 text-xs font-semibold text-muted-foreground shadow-none transition-colors hover:border-white/10 hover:bg-white/[0.045] hover:text-foreground data-[state=active]:border-cyan-200/25 data-[state=active]:bg-cyan-300/[0.08] data-[state=active]:text-cyan-100 data-[state=active]:shadow-none";
79
85
 
80
86
  function createEmptyBodyPreviewState(totalBytes: number | null = null): BodyPreviewState {
81
87
  return {
@@ -108,13 +114,6 @@ function shouldHydrateLogBody(log: CapturedLog): boolean {
108
114
  return isCompactBody(log) && !shouldUseChunkedBodyLoading(log);
109
115
  }
110
116
 
111
- function formatBytes(bytes: number): string {
112
- if (bytes < 1024) return `${bytes.toLocaleString()} B`;
113
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
114
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MiB`;
115
- return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GiB`;
116
- }
117
-
118
117
  function bodyPreviewErrorMessage(error: unknown): string {
119
118
  if (error instanceof ApiTimeoutError) {
120
119
  return "Body preview timed out. The log is still available on disk; try loading again.";
@@ -123,6 +122,57 @@ function bodyPreviewErrorMessage(error: unknown): string {
123
122
  return "Body preview could not be loaded.";
124
123
  }
125
124
 
125
+ function FullBodyHydrationNotice({
126
+ status,
127
+ error,
128
+ footprint,
129
+ onRetry,
130
+ }: {
131
+ status: BodyHydrationStatus;
132
+ error: FullBodyHydrationError | null;
133
+ footprint: string | null;
134
+ onRetry: () => void;
135
+ }): JSX.Element | null {
136
+ switch (status) {
137
+ case "idle":
138
+ return null;
139
+ case "loading":
140
+ return (
141
+ <NoticeBlock tone="loading" title="Loading full bodies">
142
+ <div className="flex flex-wrap items-center justify-between gap-2">
143
+ <span className="flex min-w-0 items-center gap-2">
144
+ <Loader2 className="size-3.5 shrink-0 animate-spin text-cyan-200" />
145
+ <span>Loading full request and response bodies from the log store.</span>
146
+ </span>
147
+ {footprint !== null && (
148
+ <span className="rounded border border-cyan-300/15 bg-cyan-300/[0.06] px-2 py-0.5 font-mono text-[11px] text-cyan-100">
149
+ {footprint}
150
+ </span>
151
+ )}
152
+ </div>
153
+ </NoticeBlock>
154
+ );
155
+ case "failed":
156
+ return (
157
+ <NoticeBlock tone="warning" title={error?.title ?? "Full bodies could not be loaded"}>
158
+ <div className="flex flex-wrap items-center justify-between gap-2">
159
+ <span>{error?.detail ?? "Compact metadata is still available."}</span>
160
+ <Button
161
+ type="button"
162
+ variant="outline"
163
+ size="sm"
164
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 gap-1.5 text-xs text-amber-100"
165
+ onClick={onRetry}
166
+ >
167
+ <RefreshCw className="size-3" />
168
+ Retry
169
+ </Button>
170
+ </div>
171
+ </NoticeBlock>
172
+ );
173
+ }
174
+ }
175
+
126
176
  function bodyPreviewTextForView(enabled: boolean, state: BodyPreviewState): string | null {
127
177
  if (!enabled) return null;
128
178
  if (state.text !== "") return state.text;
@@ -140,7 +190,7 @@ function ExpandedPanel({
140
190
  return (
141
191
  <div
142
192
  className={cn(
143
- "inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-md border border-cyan-300/10 bg-[#05070a] p-3 shadow-[0_16px_45px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.05)]",
193
+ "border border-border bg-card shadow-sm inspector-scrollbar mx-3 mb-3 max-h-[72vh] overflow-auto rounded-[8px] p-3",
144
194
  className,
145
195
  )}
146
196
  >
@@ -151,7 +201,7 @@ function ExpandedPanel({
151
201
 
152
202
  function EmptyState({ children }: { children: ReactNode }): JSX.Element {
153
203
  return (
154
- <div className="rounded-md border border-dashed border-white/10 bg-white/[0.02] px-3 py-5 text-center text-xs italic text-muted-foreground">
204
+ <div className="border border-border bg-muted/40 rounded-md border-dashed px-3 py-5 text-center text-xs italic text-muted-foreground">
155
205
  {children}
156
206
  </div>
157
207
  );
@@ -159,7 +209,7 @@ function EmptyState({ children }: { children: ReactNode }): JSX.Element {
159
209
 
160
210
  function RawTextBlock({ children }: { children: ReactNode }): JSX.Element {
161
211
  return (
162
- <pre className="inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-md border border-white/10 bg-black/30 p-3 font-mono text-xs leading-relaxed text-muted-foreground">
212
+ <pre className="border border-border bg-card shadow-sm inspector-scrollbar max-h-[64vh] overflow-auto whitespace-pre-wrap break-words rounded-[8px] p-3 font-mono text-xs leading-relaxed text-muted-foreground">
163
213
  {children}
164
214
  </pre>
165
215
  );
@@ -231,7 +281,7 @@ function BodyPreviewNotice({
231
281
  type="button"
232
282
  variant="outline"
233
283
  size="sm"
234
- className="h-7 border-white/10 bg-black/20 text-xs text-cyan-100 hover:border-cyan-300/35 hover:bg-cyan-400/10"
284
+ className="border border-input bg-background hover:bg-accent hover:text-accent-foreground h-7 text-xs text-cyan-100"
235
285
  onClick={onLoadMore}
236
286
  >
237
287
  {actionLabel}
@@ -257,13 +307,13 @@ function HeaderRows({
257
307
  if (entries.length === 0) return <EmptyState>{emptyLabel}</EmptyState>;
258
308
 
259
309
  return (
260
- <div className="overflow-hidden rounded-md border border-white/10 bg-black/20 font-mono text-xs">
310
+ <div className="border border-border bg-card shadow-sm overflow-hidden rounded-[8px] font-mono text-xs">
261
311
  {entries.map(([key, value]) => (
262
312
  <div
263
313
  key={key}
264
- className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/5 last:border-b-0"
314
+ className="grid min-w-0 grid-cols-[minmax(140px,0.32fr)_minmax(0,1fr)] border-b border-white/10 last:border-b-0"
265
315
  >
266
- <div className="min-w-0 truncate border-r border-white/5 bg-white/[0.03] px-2.5 py-2 font-semibold text-cyan-200">
316
+ <div className="min-w-0 truncate border-r border-white/10 bg-white/[0.035] px-2.5 py-2 font-semibold text-cyan-200">
267
317
  {key}
268
318
  </div>
269
319
  <div className="min-w-0 truncate px-2.5 py-2 text-muted-foreground" title={value}>
@@ -297,6 +347,8 @@ export type LogEntryProps = {
297
347
  cacheTrend?: CacheTrendEntry | null;
298
348
  /** Callback to open CompareDrawer with this log and its immediate predecessor. */
299
349
  onCompareWithPrevious?: (log: CapturedLog) => void;
350
+ /** Show client PID/project metadata on the log header row. */
351
+ showClientMetadata?: boolean;
300
352
  };
301
353
 
302
354
  function DiffToggleButton({
@@ -316,7 +368,7 @@ function DiffToggleButton({
316
368
  "h-8 border-white/10 text-xs shadow-none",
317
369
  active
318
370
  ? "bg-cyan-300 text-black hover:bg-cyan-200"
319
- : "bg-[#0a0d11] text-muted-foreground hover:border-cyan-300/30 hover:bg-cyan-400/10 hover:text-cyan-100",
371
+ : "border border-input bg-background hover:bg-accent hover:text-accent-foreground text-muted-foreground hover:text-cyan-100",
320
372
  )}
321
373
  onClick={onClick}
322
374
  aria-pressed={active}
@@ -370,6 +422,7 @@ export const LogEntry = memo(function ({
370
422
  timeDisplayFormat,
371
423
  cacheTrend = null,
372
424
  onCompareWithPrevious,
425
+ showClientMetadata = true,
373
426
  }: LogEntryProps): JSX.Element {
374
427
  const [expanded, setExpanded] = useState<boolean>(false);
375
428
  const [replayOpen, setReplayOpen] = useState<boolean>(false);
@@ -378,7 +431,9 @@ export const LogEntry = memo(function ({
378
431
  const [activeTab, setActiveTab] = useState("request");
379
432
  const [expandToPath, setExpandToPath] = useState<string | null>(null);
380
433
  const [hydratedLog, setHydratedLog] = useState<CapturedLog | null>(null);
381
- const [bodyHydrationState, setBodyHydrationState] = useState<BodyHydrationState>("idle");
434
+ const [bodyHydrationStatus, setBodyHydrationStatus] = useState<BodyHydrationStatus>("idle");
435
+ const [bodyHydrationError, setBodyHydrationError] = useState<FullBodyHydrationError | null>(null);
436
+ const [bodyHydrationRetryNonce, setBodyHydrationRetryNonce] = useState(0);
382
437
  const [requestPreview, setRequestPreview] = useState<BodyPreviewState>(() =>
383
438
  createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null),
384
439
  );
@@ -390,6 +445,10 @@ export const LogEntry = memo(function ({
390
445
  const bodyHydrationInFlightRef = useRef(false);
391
446
  const displayLog = hydratedLog ?? log;
392
447
  const useChunkedBody = hydratedLog === null && shouldUseChunkedBodyLoading(log);
448
+ const fullBodyHydrationFootprint = useMemo(
449
+ () => formatBodyHydrationFootprint(log.rawRequestBodyBytes, log.responseTextBytes),
450
+ [log.rawRequestBodyBytes, log.responseTextBytes],
451
+ );
393
452
  const requestPreviewBody = bodyPreviewTextForView(useChunkedBody, requestPreview);
394
453
  const responsePreviewBody = bodyPreviewTextForView(useChunkedBody, responsePreview);
395
454
  const rawRequestBodyForView = displayLog.rawRequestBody ?? requestPreviewBody;
@@ -432,7 +491,7 @@ export const LogEntry = memo(function ({
432
491
  );
433
492
 
434
493
  // Headers are rendered as a flat list, so we copy them as pretty-printed JSON.
435
- // Only build the string when there's at least one entry empty headers would
494
+ // Only build the string when there's at least one entry; empty headers would
436
495
  // otherwise copy "{}", which is misleading.
437
496
  const headersText = useMemo(
438
497
  () =>
@@ -628,6 +687,14 @@ export const LogEntry = memo(function ({
628
687
  [log.id],
629
688
  );
630
689
 
690
+ const retryFullBodyHydration = useCallback(() => {
691
+ if (bodyHydrationInFlightRef.current) return;
692
+ setHydratedLog(null);
693
+ setBodyHydrationStatus("idle");
694
+ setBodyHydrationError(null);
695
+ setBodyHydrationRetryNonce((current) => current + 1);
696
+ }, []);
697
+
631
698
  const handleToggleExpanded = useCallback(() => {
632
699
  const nextExpanded = !expanded;
633
700
  if (nextExpanded) {
@@ -640,7 +707,9 @@ export const LogEntry = memo(function ({
640
707
  logIdRef.current = log.id;
641
708
  bodyHydrationInFlightRef.current = false;
642
709
  setHydratedLog(null);
643
- setBodyHydrationState("idle");
710
+ setBodyHydrationStatus("idle");
711
+ setBodyHydrationError(null);
712
+ setBodyHydrationRetryNonce(0);
644
713
  setRequestPreview(createEmptyBodyPreviewState(log.rawRequestBodyBytes ?? null));
645
714
  setResponsePreview(createEmptyBodyPreviewState(log.responseTextBytes ?? null));
646
715
  }, [log.id, log.rawRequestBodyBytes, log.responseTextBytes]);
@@ -652,7 +721,8 @@ export const LogEntry = memo(function ({
652
721
 
653
722
  let cancelled = false;
654
723
  bodyHydrationInFlightRef.current = true;
655
- setBodyHydrationState("loading");
724
+ setBodyHydrationStatus("loading");
725
+ setBodyHydrationError(null);
656
726
  void fetchJsonWithTimeout(
657
727
  `/api/logs/${String(log.id)}`,
658
728
  CapturedLogSchema,
@@ -662,12 +732,14 @@ export const LogEntry = memo(function ({
662
732
  if (cancelled) return;
663
733
  bodyHydrationInFlightRef.current = false;
664
734
  setHydratedLog(fullLog);
665
- setBodyHydrationState("idle");
735
+ setBodyHydrationStatus("idle");
736
+ setBodyHydrationError(null);
666
737
  })
667
- .catch(() => {
738
+ .catch((error: unknown) => {
668
739
  if (cancelled) return;
669
740
  bodyHydrationInFlightRef.current = false;
670
- setBodyHydrationState("failed");
741
+ setBodyHydrationStatus("failed");
742
+ setBodyHydrationError(describeFullBodyHydrationError(error));
671
743
  });
672
744
 
673
745
  return () => {
@@ -681,6 +753,7 @@ export const LogEntry = memo(function ({
681
753
  log.id,
682
754
  log.rawRequestBodyBytes,
683
755
  log.responseTextBytes,
756
+ bodyHydrationRetryNonce,
684
757
  ]);
685
758
 
686
759
  useEffect(() => {
@@ -718,16 +791,16 @@ export const LogEntry = memo(function ({
718
791
  <TooltipProvider>
719
792
  <div
720
793
  className={cn(
721
- "relative mb-2 overflow-hidden rounded-lg border bg-[#07090d] transition-[background-color,border-color,box-shadow]",
794
+ "border border-border bg-card shadow-sm relative mb-2 overflow-hidden rounded-[8px] transition-[background-color,border-color,box-shadow]",
722
795
  expanded
723
- ? "border-cyan-300/25 shadow-[0_24px_80px_rgba(0,0,0,0.34),0_0_0_1px_rgba(103,232,249,0.04),inset_0_1px_0_rgba(255,255,255,0.06)]"
724
- : "border-white/10 shadow-[0_6px_24px_rgba(0,0,0,0.16)] hover:border-cyan-300/15",
796
+ ? "border-cyan-200/25 shadow-[0_24px_80px_rgba(0,0,0,0.36),0_0_0_1px_rgba(103,232,249,0.05),inset_0_1px_0_rgba(255,255,255,0.14)]"
797
+ : "hover:border-cyan-200/18",
725
798
  )}
726
799
  >
727
800
  <div
728
801
  className={cn(
729
802
  "pointer-events-none absolute inset-y-3 left-1 w-px rounded-full",
730
- expanded ? "bg-cyan-300/45 shadow-[0_0_20px_rgba(103,232,249,0.28)]" : "bg-white/10",
803
+ expanded ? "bg-cyan-200/55 shadow-[0_0_20px_rgba(103,232,249,0.26)]" : "bg-white/12",
731
804
  )}
732
805
  aria-hidden="true"
733
806
  />
@@ -744,6 +817,7 @@ export const LogEntry = memo(function ({
744
817
  onToggle={handleToggleExpanded}
745
818
  cacheTrend={cacheTrend}
746
819
  slowResponseThresholdSeconds={slowResponseThresholdSeconds}
820
+ showClientMetadata={showClientMetadata}
747
821
  timeDisplayFormat={timeDisplayFormat}
748
822
  activeTab={activeTab}
749
823
  tabActions={tabActions}
@@ -758,18 +832,18 @@ export const LogEntry = memo(function ({
758
832
 
759
833
  {expanded && (
760
834
  <div
761
- className="relative border-t border-cyan-300/10 bg-[linear-gradient(180deg,#06090d_0%,#040506_100%)] pt-2"
835
+ className="relative border-t border-white/10 bg-black/[0.18] pt-2"
762
836
  onClick={(e) => e.stopPropagation()}
763
837
  onKeyDown={(e) => e.stopPropagation()}
764
838
  >
765
839
  <div
766
- className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-cyan-300/[0.035] to-transparent"
840
+ className="pointer-events-none absolute inset-x-0 top-0 h-12 bg-gradient-to-b from-white/[0.05] to-transparent"
767
841
  aria-hidden="true"
768
842
  />
769
843
  <Tabs value={activeTab} onValueChange={setActiveTab} className="gap-2">
770
844
  <TabsList
771
845
  variant="line"
772
- className="inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-none border-b border-cyan-300/10 bg-black/10 p-0"
846
+ className="border border-border bg-muted/40 inspector-scrollbar relative mx-3 h-auto w-[calc(100%-1.5rem)] justify-start overflow-x-auto rounded-[8px] p-1"
773
847
  >
774
848
  {viewMode === "full" && (
775
849
  <TabsTrigger className={TAB_TRIGGER_CLASS} value="raw-headers">
@@ -808,6 +882,12 @@ export const LogEntry = memo(function ({
808
882
  <TabsContent value="raw-request">
809
883
  {activeTab === "raw-request" && (
810
884
  <ExpandedPanel>
885
+ <FullBodyHydrationNotice
886
+ status={bodyHydrationStatus}
887
+ error={bodyHydrationError}
888
+ footprint={fullBodyHydrationFootprint}
889
+ onRetry={retryFullBodyHydration}
890
+ />
811
891
  {useChunkedBody && (
812
892
  <BodyPreviewNotice
813
893
  label="Request"
@@ -839,17 +919,12 @@ export const LogEntry = memo(function ({
839
919
  <TabsContent value="request">
840
920
  {activeTab === "request" && (
841
921
  <ExpandedPanel>
842
- {bodyHydrationState === "loading" && (
843
- <NoticeBlock tone="loading">
844
- Loading full request and response bodies...
845
- </NoticeBlock>
846
- )}
847
- {bodyHydrationState === "failed" && (
848
- <NoticeBlock tone="warning">
849
- Full request and response bodies could not be loaded. Compact metadata is
850
- still available.
851
- </NoticeBlock>
852
- )}
922
+ <FullBodyHydrationNotice
923
+ status={bodyHydrationStatus}
924
+ error={bodyHydrationError}
925
+ footprint={fullBodyHydrationFootprint}
926
+ onRetry={retryFullBodyHydration}
927
+ />
853
928
  {useChunkedBody && (
854
929
  <BodyPreviewNotice
855
930
  label="Request"
@@ -978,6 +1053,12 @@ export const LogEntry = memo(function ({
978
1053
  <TabsContent value="raw">
979
1054
  {activeTab === "raw" && (
980
1055
  <ExpandedPanel className="space-y-3">
1056
+ <FullBodyHydrationNotice
1057
+ status={bodyHydrationStatus}
1058
+ error={bodyHydrationError}
1059
+ footprint={fullBodyHydrationFootprint}
1060
+ onRetry={retryFullBodyHydration}
1061
+ />
981
1062
  {useChunkedBody && (
982
1063
  <BodyPreviewNotice
983
1064
  label="Response"
@@ -1022,6 +1103,12 @@ export const LogEntry = memo(function ({
1022
1103
  <TabsContent value="parsed">
1023
1104
  {activeTab === "parsed" && (
1024
1105
  <ExpandedPanel>
1106
+ <FullBodyHydrationNotice
1107
+ status={bodyHydrationStatus}
1108
+ error={bodyHydrationError}
1109
+ footprint={fullBodyHydrationFootprint}
1110
+ onRetry={retryFullBodyHydration}
1111
+ />
1025
1112
  {useChunkedBody && (
1026
1113
  <BodyPreviewNotice
1027
1114
  label="Response"