@tonyclaw/agent-inspector 3.0.29 → 3.0.31
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/.output/cli.js +791 -191
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DB2KggLE.js +1 -0
- package/.output/public/assets/InspectorPet-CfwDw6kD.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-PcrO96_F.js +126 -0
- package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-3Pl7EKjp.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-BcPjcHLX.js} +1 -1
- package/.output/public/assets/ResponseView-DOx51wV3.js +2 -0
- package/.output/public/assets/StreamingChunkSequence-LkYJWM1A.js +1 -0
- package/.output/public/assets/_sessionId-BSg32vC6.js +1 -0
- package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-C0CA68QV.js} +1 -1
- package/.output/public/assets/{index-Bq-nqolG.js → index-BpBUqdAB.js} +1 -1
- package/.output/public/assets/index-D93dCgAQ.js +1 -0
- package/.output/public/assets/{index-DPiESBo2.js → index-FV5sxqx3.js} +6 -6
- package/.output/public/assets/index-VUsF4nhf.js +76 -0
- package/.output/public/assets/index-hyQHZeQI.css +1 -0
- package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-WctIbAh_.js} +1 -1
- package/.output/public/assets/jszip.min-DnSIoTix.js +2 -0
- package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
- package/.output/server/_libs/jszip.mjs +35 -9
- package/.output/server/_libs/lucide-react.mjs +80 -80
- package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
- package/.output/server/_libs/tanstack__react-router.mjs +1 -1
- package/.output/server/{_sessionId-CwGXPD4C.mjs → _sessionId-BHBK3VbF.mjs} +15 -15
- package/.output/server/_sessionId-CyuJ6Kfl.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-27amlv_a.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-CQnGXfLs.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-DlAl0uF-.mjs} +1569 -1646
- package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-5m2jtlLa.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy--1WtQNY9.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-CgbqS8sr.mjs} +60 -71
- package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-BwpnopWx.mjs} +16 -16
- package/.output/server/_ssr/{index-D4DvByuW.mjs → index-CNVi1TzT.mjs} +15 -15
- package/.output/server/_ssr/index-DMCjH_4V.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-DNGN2uoe.mjs} +58 -58
- package/.output/server/_ssr/{router-BYlePDj8.mjs → router-CgsqLnk5.mjs} +16007 -12632
- package/.output/server/_tanstack-start-manifest_v-BH5f_kWV.mjs +4 -0
- package/.output/server/index.mjs +99 -71
- package/.output/workers/logFinalizer.worker.js +16908 -0
- package/.output/workers/sessionWorkerEntry.js +16904 -0
- package/README.md +21 -5
- package/package.json +6 -2
- package/src/assets/logos/qwen.webp +0 -0
- package/src/cli.ts +85 -10
- package/src/components/OnboardingBanner.tsx +41 -21
- package/src/components/ProxyViewer.tsx +198 -269
- package/src/components/ProxyViewerContainer.tsx +128 -17
- package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
- package/src/components/errors/SafeErrorBoundary.tsx +201 -0
- package/src/components/pi-agent/PiAgentPanel.tsx +209 -35
- package/src/components/pi-agent/piAgentChatLogic.ts +263 -0
- package/src/components/providers/ImportWizardDialog.tsx +18 -42
- package/src/components/providers/ProviderLogo.tsx +1 -1
- package/src/components/providers/ProvidersPanel.tsx +8 -5
- package/src/components/providers/SettingsDialog.tsx +34 -11
- package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
- package/src/components/proxy-viewer/LogEntry.tsx +283 -275
- package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
- package/src/components/proxy-viewer/accessibility.ts +8 -0
- package/src/components/proxy-viewer/lazy.ts +4 -0
- package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
- package/src/components/proxy-viewer/viewerState.ts +2 -6
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/transient-toast.tsx +7 -6
- package/src/lib/apiClient.ts +17 -2
- package/src/lib/ecosystemContract.ts +34 -0
- package/src/lib/export-logs.ts +1 -1
- package/src/lib/piAgentContract.ts +80 -0
- package/src/lib/providerContract.ts +70 -4
- package/src/lib/providerImportContract.ts +27 -0
- package/src/lib/providerModelMetadata.ts +16 -7
- package/src/lib/resourceLimits.ts +152 -0
- package/src/lib/safeDiagnostic.ts +38 -0
- package/src/lib/useProviders.ts +4 -4
- package/src/mcp/server.ts +39 -6
- package/src/mcp/toolHandlers.ts +131 -4
- package/src/proxy/chunkStorage.ts +20 -6
- package/src/proxy/config.ts +20 -6
- package/src/proxy/dataDir.ts +3 -0
- package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
- package/src/proxy/ecosystemExecutionRoute.ts +116 -0
- package/src/proxy/ecosystemTasks.ts +48 -0
- package/src/proxy/evidenceExporter.ts +23 -9
- package/src/proxy/groupEvidenceExporter.ts +26 -5
- package/src/proxy/groupStore.ts +13 -3
- package/src/proxy/handler.ts +103 -20
- package/src/proxy/identityProxy.ts +333 -14
- package/src/proxy/logFinalizer.ts +73 -6
- package/src/proxy/logImportUpload.ts +128 -0
- package/src/proxy/logImporter.ts +321 -70
- package/src/proxy/logIndex.ts +16 -6
- package/src/proxy/logger.ts +294 -41
- package/src/proxy/privateDataPath.ts +183 -0
- package/src/proxy/providerScanStore.ts +87 -0
- package/src/proxy/providerSecretStore.ts +58 -19
- package/src/proxy/providers.ts +175 -58
- package/src/proxy/rawStreamCapture.ts +66 -5
- package/src/proxy/runStore.ts +13 -3
- package/src/proxy/runtimeAdmission.ts +52 -0
- package/src/proxy/runtimeHealth.ts +206 -0
- package/src/proxy/runtimeShutdown.ts +75 -0
- package/src/proxy/sessionArchive.ts +15 -2
- package/src/proxy/sessionProcess.ts +19 -0
- package/src/proxy/sessionRuntime.ts +7 -0
- package/src/proxy/shutdownCoordinator.ts +90 -0
- package/src/proxy/sqliteLogIndex.ts +18 -2
- package/src/proxy/store.ts +18 -2
- package/src/routes/__root.tsx +18 -1
- package/src/routes/api/alerts.summary.ts +12 -8
- package/src/routes/api/alerts.ts +27 -9
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
- package/src/routes/api/health.ts +9 -2
- package/src/routes/api/knowledge.project-context.ts +11 -0
- package/src/routes/api/knowledge.search.ts +33 -2
- package/src/routes/api/logs.$id.body.ts +16 -2
- package/src/routes/api/logs.import.ts +7 -18
- package/src/routes/api/logs.stream.ts +147 -69
- package/src/routes/api/logs.ts +55 -14
- package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
- package/src/routes/api/providers.$providerId.ts +37 -6
- package/src/routes/api/providers.export.ts +34 -16
- package/src/routes/api/providers.import.ts +42 -8
- package/src/routes/api/providers.scan.ts +13 -8
- package/src/routes/api/providers.ts +36 -5
- package/src/routes/api/runs.ts +12 -12
- package/src/routes/api/sessions.ts +15 -8
- package/src/routes/index.tsx +6 -0
- package/src/routes/livez.ts +13 -0
- package/src/routes/readyz.ts +18 -0
- package/src/routes/session/$sessionId.tsx +6 -0
- package/src/services/piAgent.ts +131 -102
- package/src/services/piAgentRetrieval.ts +1180 -0
- package/styles/globals.css +19 -3
- package/.output/public/assets/CompareDrawer-Cb5ccPeX.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-COBe5GTT.js +0 -4234
- package/.output/public/assets/ResponseView-Cqq87lX_.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CnTa9mCr.js +0 -1
- package/.output/public/assets/index-DTLNVcgc.js +0 -76
- package/.output/public/assets/index-DliqmwUw.css +0 -1
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/server/_tanstack-start-manifest_v-DMCA8KK3.mjs +0 -4
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/components/ui/mcp-logo.tsx +0 -20
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Suspense, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import type { JSX } from "react";
|
|
2
|
+
import type { JSX, KeyboardEvent as ReactKeyboardEvent } from "react";
|
|
3
3
|
import {
|
|
4
4
|
Check,
|
|
5
5
|
ChevronRight,
|
|
@@ -46,6 +46,11 @@ type ChangedOp = Extract<DiffOp, { kind: "changed" }>;
|
|
|
46
46
|
|
|
47
47
|
type DiffMode = "unified" | "split";
|
|
48
48
|
|
|
49
|
+
export const COMPARE_RESPONSIVE_HEADER_CLASS_NAME =
|
|
50
|
+
"flex flex-col gap-3 border-b border-border px-3 py-3 sm:flex-row sm:items-start sm:gap-4 sm:px-4";
|
|
51
|
+
export const COMPARE_RESPONSIVE_SPLIT_CLASS_NAME =
|
|
52
|
+
"grid min-w-0 grid-cols-1 gap-x-2 gap-y-0.5 px-3 py-2 text-xs md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]";
|
|
53
|
+
|
|
49
54
|
/** Walk the JsonNode tree and pretty-print it back to a JSON string for the
|
|
50
55
|
* expanded-equal-subtree view. The node is a plain object structure so
|
|
51
56
|
* `JSON.stringify` produces correct output. */
|
|
@@ -162,23 +167,23 @@ const KIND_VISUAL: Record<
|
|
|
162
167
|
> = {
|
|
163
168
|
added: {
|
|
164
169
|
icon: Plus,
|
|
165
|
-
accent: "text-
|
|
166
|
-
bg: "bg-
|
|
167
|
-
border: "border-l-
|
|
170
|
+
accent: "text-status-success",
|
|
171
|
+
bg: "bg-status-success/5 hover:bg-status-success/10",
|
|
172
|
+
border: "border-l-status-success",
|
|
168
173
|
label: "ADDED",
|
|
169
174
|
},
|
|
170
175
|
removed: {
|
|
171
176
|
icon: Minus,
|
|
172
|
-
accent: "text-
|
|
173
|
-
bg: "bg-
|
|
174
|
-
border: "border-l-
|
|
177
|
+
accent: "text-status-danger",
|
|
178
|
+
bg: "bg-status-danger/5 hover:bg-status-danger/10",
|
|
179
|
+
border: "border-l-status-danger",
|
|
175
180
|
label: "REMOVED",
|
|
176
181
|
},
|
|
177
182
|
changed: {
|
|
178
183
|
icon: Pencil,
|
|
179
|
-
accent: "text-
|
|
180
|
-
bg: "bg-
|
|
181
|
-
border: "border-l-
|
|
184
|
+
accent: "text-status-watch",
|
|
185
|
+
bg: "bg-status-watch/5 hover:bg-status-watch/10",
|
|
186
|
+
border: "border-l-status-watch",
|
|
182
187
|
label: "CHANGED",
|
|
183
188
|
},
|
|
184
189
|
equal: {
|
|
@@ -294,19 +299,19 @@ function UnifiedOpRow({
|
|
|
294
299
|
? [
|
|
295
300
|
{
|
|
296
301
|
text: previewNode(op.left, 400),
|
|
297
|
-
tone: "text-
|
|
302
|
+
tone: "text-status-danger line-through",
|
|
298
303
|
},
|
|
299
|
-
{ text: previewNode(op.right, 400), tone: "text-
|
|
304
|
+
{ text: previewNode(op.right, 400), tone: "text-status-success" },
|
|
300
305
|
]
|
|
301
306
|
: op.kind === "removed"
|
|
302
307
|
? [
|
|
303
308
|
{
|
|
304
309
|
text: previewNode(op.value, 400),
|
|
305
|
-
tone: "text-
|
|
310
|
+
tone: "text-status-danger line-through",
|
|
306
311
|
},
|
|
307
312
|
]
|
|
308
313
|
: op.kind === "added"
|
|
309
|
-
? [{ text: previewNode(op.value, 400), tone: "text-
|
|
314
|
+
? [{ text: previewNode(op.value, 400), tone: "text-status-success" }]
|
|
310
315
|
: [{ text: previewNode(op.value, 400), tone: "text-muted-foreground" }];
|
|
311
316
|
|
|
312
317
|
const justCopied = copiedPath === op.path && op.path !== "";
|
|
@@ -376,7 +381,7 @@ function UnifiedOpRow({
|
|
|
376
381
|
className={cn(
|
|
377
382
|
"shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",
|
|
378
383
|
justCopied
|
|
379
|
-
? "text-
|
|
384
|
+
? "text-status-success"
|
|
380
385
|
: "text-muted-foreground/50 hover:text-foreground hover:bg-muted",
|
|
381
386
|
)}
|
|
382
387
|
aria-label={justCopied ? "Copied" : "Copy"}
|
|
@@ -427,12 +432,12 @@ function ExpandedSubtree({ op }: { op: AddedOp | RemovedOp | ChangedOp }): JSX.E
|
|
|
427
432
|
}
|
|
428
433
|
return (
|
|
429
434
|
<div className="pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
430
|
-
<div className="border border-
|
|
431
|
-
<div className="text-[10px] uppercase tracking-wider text-
|
|
435
|
+
<div className="rounded border border-status-danger/30 bg-status-danger/5 p-2">
|
|
436
|
+
<div className="mb-1 text-[10px] uppercase tracking-wider text-status-danger">Old</div>
|
|
432
437
|
<LazyJsonStringView text={nodeToJsonString(op.left)} defaultExpandDepth={0} />
|
|
433
438
|
</div>
|
|
434
|
-
<div className="border border-
|
|
435
|
-
<div className="text-[10px] uppercase tracking-wider text-
|
|
439
|
+
<div className="rounded border border-status-success/30 bg-status-success/5 p-2">
|
|
440
|
+
<div className="mb-1 text-[10px] uppercase tracking-wider text-status-success">New</div>
|
|
436
441
|
<LazyJsonStringView text={nodeToJsonString(op.right)} defaultExpandDepth={0} />
|
|
437
442
|
</div>
|
|
438
443
|
</div>
|
|
@@ -461,7 +466,7 @@ function SummaryChips({
|
|
|
461
466
|
className={cn(
|
|
462
467
|
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
463
468
|
counts.removed > 0
|
|
464
|
-
? "border-
|
|
469
|
+
? "border-status-danger/40 bg-status-danger/10 text-status-danger hover:bg-status-danger/20"
|
|
465
470
|
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
466
471
|
)}
|
|
467
472
|
title={counts.removed > 0 ? "Jump to first removed" : "No removals"}
|
|
@@ -476,7 +481,7 @@ function SummaryChips({
|
|
|
476
481
|
className={cn(
|
|
477
482
|
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
478
483
|
counts.added > 0
|
|
479
|
-
? "border-
|
|
484
|
+
? "border-status-success/40 bg-status-success/10 text-status-success hover:bg-status-success/20"
|
|
480
485
|
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
481
486
|
)}
|
|
482
487
|
title={counts.added > 0 ? "Jump to first added" : "No additions"}
|
|
@@ -491,7 +496,7 @@ function SummaryChips({
|
|
|
491
496
|
className={cn(
|
|
492
497
|
"inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",
|
|
493
498
|
counts.changed > 0
|
|
494
|
-
? "border-
|
|
499
|
+
? "border-status-watch/40 bg-status-watch/10 text-status-watch hover:bg-status-watch/20"
|
|
495
500
|
: "border-border text-muted-foreground/40 cursor-not-allowed",
|
|
496
501
|
)}
|
|
497
502
|
title={counts.changed > 0 ? "Jump to first changed" : "No changes"}
|
|
@@ -563,27 +568,29 @@ function SideSummary({
|
|
|
563
568
|
className={cn(
|
|
564
569
|
"text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",
|
|
565
570
|
side === "left"
|
|
566
|
-
? "border-
|
|
567
|
-
: "border-
|
|
571
|
+
? "border-status-danger/40 text-status-danger"
|
|
572
|
+
: "border-status-success/40 text-status-success",
|
|
568
573
|
)}
|
|
569
574
|
>
|
|
570
575
|
{side === "left" ? "← Left" : "Right →"}
|
|
571
576
|
</Badge>
|
|
572
|
-
<span className="font-mono text-
|
|
577
|
+
<span className="font-mono text-status-info" title={`Log ID ${String(log.id)}`}>
|
|
573
578
|
#{displayNumber}
|
|
574
579
|
</span>
|
|
575
580
|
{log.model !== null && (
|
|
576
|
-
<span className="font-mono leading-snug text-muted-foreground">
|
|
581
|
+
<span className="min-w-0 truncate font-mono leading-snug text-muted-foreground">
|
|
582
|
+
{log.model}
|
|
583
|
+
</span>
|
|
577
584
|
)}
|
|
578
585
|
</div>
|
|
579
|
-
<div className="flex items-center gap-3
|
|
586
|
+
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 font-mono text-muted-foreground">
|
|
580
587
|
{log.cacheCreationInputTokens !== null && log.cacheCreationInputTokens > 0 && (
|
|
581
|
-
<span className="text-
|
|
588
|
+
<span className="text-status-success">
|
|
582
589
|
KV Cache +{formatTokens(log.cacheCreationInputTokens)}
|
|
583
590
|
</span>
|
|
584
591
|
)}
|
|
585
592
|
{log.cacheReadInputTokens !== null && log.cacheReadInputTokens > 0 && (
|
|
586
|
-
<span className="text-
|
|
593
|
+
<span className="text-status-accent">
|
|
587
594
|
KV Cache ~{formatTokens(log.cacheReadInputTokens)}
|
|
588
595
|
</span>
|
|
589
596
|
)}
|
|
@@ -605,6 +612,9 @@ export function CompareDrawer({
|
|
|
605
612
|
rightDisplayNumber,
|
|
606
613
|
onClose,
|
|
607
614
|
}: CompareDrawerProps): JSX.Element {
|
|
615
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
616
|
+
const closeButtonRef = useRef<HTMLButtonElement>(null);
|
|
617
|
+
const previousActiveElementRef = useRef<HTMLElement | null>(null);
|
|
608
618
|
// Memoize the diff so re-renders (e.g. parent re-renders) don't recompute.
|
|
609
619
|
const ops = useMemo<DiffOp[]>(() => {
|
|
610
620
|
const leftRequest = getLogFormatAdapter(resolveLogFormat(left)).analyzeRequest(
|
|
@@ -716,8 +726,12 @@ export function CompareDrawer({
|
|
|
716
726
|
}
|
|
717
727
|
};
|
|
718
728
|
|
|
719
|
-
// Esc keybinding
|
|
729
|
+
// Esc keybinding, initial focus, focus restoration, and body scroll lock while open.
|
|
720
730
|
useEffect(() => {
|
|
731
|
+
previousActiveElementRef.current =
|
|
732
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
733
|
+
closeButtonRef.current?.focus();
|
|
734
|
+
|
|
721
735
|
const onKey = (e: KeyboardEvent) => {
|
|
722
736
|
if (e.key === "Escape") onClose();
|
|
723
737
|
};
|
|
@@ -727,9 +741,36 @@ export function CompareDrawer({
|
|
|
727
741
|
return () => {
|
|
728
742
|
document.removeEventListener("keydown", onKey);
|
|
729
743
|
document.body.style.overflow = prevOverflow;
|
|
744
|
+
previousActiveElementRef.current?.focus();
|
|
730
745
|
};
|
|
731
746
|
}, [onClose]);
|
|
732
747
|
|
|
748
|
+
const handlePanelKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>): void => {
|
|
749
|
+
event.stopPropagation();
|
|
750
|
+
if (event.key !== "Tab") return;
|
|
751
|
+
|
|
752
|
+
const panel = panelRef.current;
|
|
753
|
+
if (panel === null) return;
|
|
754
|
+
const focusable = Array.from(
|
|
755
|
+
panel.querySelectorAll<HTMLElement>(
|
|
756
|
+
'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
|
757
|
+
),
|
|
758
|
+
);
|
|
759
|
+
const first = focusable[0];
|
|
760
|
+
const last = focusable[focusable.length - 1];
|
|
761
|
+
if (first === undefined || last === undefined) return;
|
|
762
|
+
|
|
763
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
764
|
+
event.preventDefault();
|
|
765
|
+
last.focus();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (!event.shiftKey && document.activeElement === last) {
|
|
769
|
+
event.preventDefault();
|
|
770
|
+
first.focus();
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
733
774
|
const sameSession = getConversationId(left) === getConversationId(right);
|
|
734
775
|
const allEqual = ops.length === 1 && ops[0]?.kind === "equal";
|
|
735
776
|
|
|
@@ -751,22 +792,24 @@ export function CompareDrawer({
|
|
|
751
792
|
|
|
752
793
|
{/* Drawer panel */}
|
|
753
794
|
<div
|
|
795
|
+
ref={panelRef}
|
|
754
796
|
className={cn(
|
|
755
|
-
"relative bg-background border-l border-border shadow-xl",
|
|
797
|
+
"relative overflow-x-hidden bg-background border-l border-border shadow-xl",
|
|
756
798
|
"w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full",
|
|
757
799
|
)}
|
|
758
800
|
onClick={(e) => e.stopPropagation()}
|
|
759
|
-
onKeyDown={
|
|
801
|
+
onKeyDown={handlePanelKeyDown}
|
|
760
802
|
>
|
|
761
803
|
{/* Header */}
|
|
762
|
-
<div className=
|
|
763
|
-
<div className="flex-1
|
|
804
|
+
<div className={COMPARE_RESPONSIVE_HEADER_CLASS_NAME}>
|
|
805
|
+
<div className="grid min-w-0 flex-1 grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-4">
|
|
764
806
|
<SideSummary log={left} side="left" displayNumber={leftDisplayNumber} />
|
|
765
807
|
<SideSummary log={right} side="right" displayNumber={rightDisplayNumber} />
|
|
766
808
|
</div>
|
|
767
|
-
<div className="flex items-center gap-2
|
|
809
|
+
<div className="flex w-full shrink-0 items-center justify-between gap-2 sm:w-auto sm:justify-start">
|
|
768
810
|
<ModeToggle mode={mode} onChange={setMode} />
|
|
769
811
|
<button
|
|
812
|
+
ref={closeButtonRef}
|
|
770
813
|
type="button"
|
|
771
814
|
onClick={onClose}
|
|
772
815
|
aria-label="Close"
|
|
@@ -778,7 +821,7 @@ export function CompareDrawer({
|
|
|
778
821
|
</div>
|
|
779
822
|
|
|
780
823
|
{!sameSession && (
|
|
781
|
-
<div className="px-4 py-1.5 text-xs text-
|
|
824
|
+
<div className="border-b border-border bg-warning/10 px-4 py-1.5 text-xs text-foreground">
|
|
782
825
|
Heads up: the two selected logs are from different sessions.
|
|
783
826
|
</div>
|
|
784
827
|
)}
|
|
@@ -835,7 +878,7 @@ export function CompareDrawer({
|
|
|
835
878
|
);
|
|
836
879
|
}
|
|
837
880
|
|
|
838
|
-
/**
|
|
881
|
+
/** Three-column on wide screens and a bounded stacked comparison on narrow screens. */
|
|
839
882
|
function SplitBody({
|
|
840
883
|
grouped,
|
|
841
884
|
left,
|
|
@@ -850,9 +893,9 @@ function SplitBody({
|
|
|
850
893
|
rightDisplayNumber: number;
|
|
851
894
|
}): JSX.Element {
|
|
852
895
|
return (
|
|
853
|
-
<div
|
|
896
|
+
<div data-compare-layout="responsive-split" className={COMPARE_RESPONSIVE_SPLIT_CLASS_NAME}>
|
|
854
897
|
{/* Column headers */}
|
|
855
|
-
<div className="grid grid-cols-
|
|
898
|
+
<div className="col-span-1 mb-2 grid grid-cols-1 gap-x-2 gap-y-1 border-b border-border pb-2 text-[10px] uppercase tracking-wider text-muted-foreground md:col-span-3 md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]">
|
|
856
899
|
<span>Path</span>
|
|
857
900
|
<span title={`Log ID ${String(left.id)}`}>Left (Log #{leftDisplayNumber})</span>
|
|
858
901
|
<span title={`Log ID ${String(right.id)}`}>Right (Log #{rightDisplayNumber})</span>
|
|
@@ -864,7 +907,7 @@ function SplitBody({
|
|
|
864
907
|
<div
|
|
865
908
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
866
909
|
key={i}
|
|
867
|
-
className="col-span-
|
|
910
|
+
className="col-span-1 px-2 py-1 text-xs text-muted-foreground/60 md:col-span-3"
|
|
868
911
|
>
|
|
869
912
|
{g.ops.length} equal siblings collapsed — switch to Unified to expand
|
|
870
913
|
</div>
|
|
@@ -876,7 +919,7 @@ function SplitBody({
|
|
|
876
919
|
<div
|
|
877
920
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
878
921
|
key={i}
|
|
879
|
-
className="col-span-
|
|
922
|
+
className="col-span-1 grid min-w-0 grid-cols-1 gap-x-2 gap-y-1 px-2 py-0.5 text-muted-foreground md:col-span-3 md:grid-cols-[200px_minmax(0,1fr)_minmax(0,1fr)]"
|
|
880
923
|
>
|
|
881
924
|
<span className="font-mono text-xs truncate" title={op.path}>
|
|
882
925
|
{op.path}
|
|
@@ -895,10 +938,10 @@ function SplitBody({
|
|
|
895
938
|
<div
|
|
896
939
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
897
940
|
key={i}
|
|
898
|
-
className="col-span-
|
|
941
|
+
className="col-span-1 rounded border-l-2 border-l-status-success bg-status-success/5 px-2 py-1 text-xs md:col-span-3"
|
|
899
942
|
>
|
|
900
943
|
<div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
|
|
901
|
-
<div className="font-mono break-all text-
|
|
944
|
+
<div className="font-mono break-all text-status-success">
|
|
902
945
|
+ {previewNode(op.value, 400)}
|
|
903
946
|
</div>
|
|
904
947
|
</div>
|
|
@@ -909,10 +952,10 @@ function SplitBody({
|
|
|
909
952
|
<div
|
|
910
953
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
911
954
|
key={i}
|
|
912
|
-
className="col-span-
|
|
955
|
+
className="col-span-1 rounded border-l-2 border-l-status-danger bg-status-danger/5 px-2 py-1 text-xs md:col-span-3"
|
|
913
956
|
>
|
|
914
957
|
<div className="font-mono text-xs text-muted-foreground mb-0.5">{op.path}</div>
|
|
915
|
-
<div className="font-mono break-all text-
|
|
958
|
+
<div className="font-mono break-all text-status-danger line-through">
|
|
916
959
|
− {previewNode(op.value, 400)}
|
|
917
960
|
</div>
|
|
918
961
|
</div>
|
|
@@ -922,14 +965,14 @@ function SplitBody({
|
|
|
922
965
|
<div
|
|
923
966
|
// biome-ignore lint/suspicious/noArrayIndexKey: positional in the grouped list
|
|
924
967
|
key={i}
|
|
925
|
-
className="col-span-
|
|
968
|
+
className="col-span-1 rounded border-l-2 border-l-status-watch bg-status-watch/5 px-2 py-1 text-xs md:col-span-3"
|
|
926
969
|
>
|
|
927
970
|
<div className="font-mono text-xs text-muted-foreground mb-1">{op.path}</div>
|
|
928
|
-
<div className="grid grid-cols-
|
|
929
|
-
<div className="font-mono text-
|
|
971
|
+
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
972
|
+
<div className="font-mono break-all text-status-danger line-through">
|
|
930
973
|
{previewNode(op.left, 400)}
|
|
931
974
|
</div>
|
|
932
|
-
<div className="font-mono text-
|
|
975
|
+
<div className="font-mono break-all text-status-success">
|
|
933
976
|
{previewNode(op.right, 400)}
|
|
934
977
|
</div>
|
|
935
978
|
</div>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import { CircleStop, RefreshCw } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import type { LiveConnectionPhase, LiveConnectionState } from "./liveConnectionState";
|
|
5
|
+
|
|
6
|
+
type ConnectionPresentation = {
|
|
7
|
+
label: string;
|
|
8
|
+
description: string;
|
|
9
|
+
toneClass: string;
|
|
10
|
+
dotClass: string;
|
|
11
|
+
canRetry: boolean;
|
|
12
|
+
canStop: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function formatLastSuccess(lastSuccessAt: number | null): string | null {
|
|
16
|
+
if (lastSuccessAt === null) return null;
|
|
17
|
+
return new Date(lastSuccessAt).toLocaleTimeString([], {
|
|
18
|
+
hour: "2-digit",
|
|
19
|
+
minute: "2-digit",
|
|
20
|
+
second: "2-digit",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildDescription(state: LiveConnectionState, message: string, hasLogs: boolean): string {
|
|
25
|
+
const lastSuccess = formatLastSuccess(state.lastSuccessAt);
|
|
26
|
+
const retry = state.retryCount > 0 ? ` Retry ${state.retryCount.toString()}.` : "";
|
|
27
|
+
const retained = hasLogs ? " Captured logs remain visible." : "";
|
|
28
|
+
const lastUpdate = lastSuccess === null ? "" : ` Last update ${lastSuccess}.`;
|
|
29
|
+
return `${message}${lastUpdate}${retry}${retained}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function presentationForStatus(
|
|
33
|
+
status: LiveConnectionPhase,
|
|
34
|
+
state: LiveConnectionState,
|
|
35
|
+
hasLogs: boolean,
|
|
36
|
+
): ConnectionPresentation {
|
|
37
|
+
switch (status) {
|
|
38
|
+
case "connecting":
|
|
39
|
+
return {
|
|
40
|
+
label: "Connecting",
|
|
41
|
+
description: buildDescription(state, "Waiting for the first live update.", hasLogs),
|
|
42
|
+
toneClass: "border-cyan-400/20 bg-cyan-500/8 text-cyan-100",
|
|
43
|
+
dotClass: "bg-cyan-300 animate-pulse motion-reduce:animate-none",
|
|
44
|
+
canRetry: false,
|
|
45
|
+
canStop: false,
|
|
46
|
+
};
|
|
47
|
+
case "live":
|
|
48
|
+
return {
|
|
49
|
+
label: "Live",
|
|
50
|
+
description: buildDescription(state, "Live updates are current.", hasLogs),
|
|
51
|
+
toneClass: "border-emerald-400/20 bg-emerald-500/8 text-emerald-100",
|
|
52
|
+
dotClass: "bg-emerald-300",
|
|
53
|
+
canRetry: false,
|
|
54
|
+
canStop: false,
|
|
55
|
+
};
|
|
56
|
+
case "reconnecting":
|
|
57
|
+
return {
|
|
58
|
+
label: "Reconnecting",
|
|
59
|
+
description: buildDescription(state, "Live updates were interrupted.", hasLogs),
|
|
60
|
+
toneClass: "border-amber-400/20 bg-amber-500/8 text-amber-100",
|
|
61
|
+
dotClass: "bg-amber-300 animate-pulse motion-reduce:animate-none",
|
|
62
|
+
canRetry: true,
|
|
63
|
+
canStop: true,
|
|
64
|
+
};
|
|
65
|
+
case "stale":
|
|
66
|
+
return {
|
|
67
|
+
label: "Stale data",
|
|
68
|
+
description: buildDescription(state, "Live updates have not resumed.", hasLogs),
|
|
69
|
+
toneClass: "border-orange-400/25 bg-orange-500/10 text-orange-100",
|
|
70
|
+
dotClass: "bg-orange-300",
|
|
71
|
+
canRetry: true,
|
|
72
|
+
canStop: true,
|
|
73
|
+
};
|
|
74
|
+
case "offline":
|
|
75
|
+
return {
|
|
76
|
+
label: "Offline",
|
|
77
|
+
description: buildDescription(
|
|
78
|
+
state,
|
|
79
|
+
state.errorMessage ?? "Live updates are paused.",
|
|
80
|
+
hasLogs,
|
|
81
|
+
),
|
|
82
|
+
toneClass: "border-slate-400/20 bg-slate-500/10 text-slate-200",
|
|
83
|
+
dotClass: "bg-slate-400",
|
|
84
|
+
canRetry: true,
|
|
85
|
+
canStop: false,
|
|
86
|
+
};
|
|
87
|
+
case "error":
|
|
88
|
+
return {
|
|
89
|
+
label: "Connection error",
|
|
90
|
+
description: buildDescription(
|
|
91
|
+
state,
|
|
92
|
+
state.errorMessage ?? "The live stream reported a recoverable error.",
|
|
93
|
+
hasLogs,
|
|
94
|
+
),
|
|
95
|
+
toneClass: "border-red-400/25 bg-red-500/10 text-red-100",
|
|
96
|
+
dotClass: "bg-red-300",
|
|
97
|
+
canRetry: true,
|
|
98
|
+
canStop: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function LiveConnectionBadge({ state }: { state: LiveConnectionState }): JSX.Element {
|
|
104
|
+
const presentation = presentationForStatus(state.status, state, false);
|
|
105
|
+
const lastSuccess = formatLastSuccess(state.lastSuccessAt);
|
|
106
|
+
const retryLabel = state.retryCount > 0 ? `Retry ${state.retryCount.toString()}` : null;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<span
|
|
110
|
+
role="status"
|
|
111
|
+
aria-label={presentation.description}
|
|
112
|
+
data-connection-state={state.status}
|
|
113
|
+
className={cn(
|
|
114
|
+
"inline-flex h-7 max-w-full items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium",
|
|
115
|
+
presentation.toneClass,
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<span className={cn("size-1.5 shrink-0 rounded-full", presentation.dotClass)} />
|
|
119
|
+
<span className="shrink-0">{presentation.label}</span>
|
|
120
|
+
{retryLabel !== null && (
|
|
121
|
+
<span className="hidden truncate text-current/70 sm:inline">{retryLabel}</span>
|
|
122
|
+
)}
|
|
123
|
+
{lastSuccess !== null && (
|
|
124
|
+
<span className="hidden truncate font-mono text-[10px] text-current/65 md:inline">
|
|
125
|
+
Last {lastSuccess}
|
|
126
|
+
</span>
|
|
127
|
+
)}
|
|
128
|
+
</span>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function LiveConnectionStatus({
|
|
133
|
+
state,
|
|
134
|
+
hasLogs,
|
|
135
|
+
onRetry,
|
|
136
|
+
onStop,
|
|
137
|
+
}: {
|
|
138
|
+
state: LiveConnectionState;
|
|
139
|
+
hasLogs: boolean;
|
|
140
|
+
onRetry: () => void;
|
|
141
|
+
onStop: () => void;
|
|
142
|
+
}): JSX.Element {
|
|
143
|
+
const presentation = presentationForStatus(state.status, state, hasLogs);
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
role="status"
|
|
148
|
+
aria-live="polite"
|
|
149
|
+
data-connection-state={state.status}
|
|
150
|
+
className={cn(
|
|
151
|
+
"mt-2 flex flex-wrap items-center gap-x-3 gap-y-2 rounded-md border px-3 py-2 text-xs",
|
|
152
|
+
presentation.toneClass,
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
156
|
+
<span className={cn("size-2 shrink-0 rounded-full", presentation.dotClass)} />
|
|
157
|
+
<span className="shrink-0 font-semibold">{presentation.label}</span>
|
|
158
|
+
<span className="min-w-0 text-current/75">{presentation.description}</span>
|
|
159
|
+
</div>
|
|
160
|
+
{(presentation.canRetry || presentation.canStop) && (
|
|
161
|
+
<div className="flex shrink-0 items-center gap-1.5">
|
|
162
|
+
{presentation.canRetry && (
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
onClick={onRetry}
|
|
166
|
+
className="inline-flex h-7 items-center gap-1 rounded-md bg-black/15 px-2 font-medium transition-colors hover:bg-black/25 focus-visible:ring-1 focus-visible:ring-current focus-visible:outline-none"
|
|
167
|
+
>
|
|
168
|
+
<RefreshCw className="size-3" />
|
|
169
|
+
Retry now
|
|
170
|
+
</button>
|
|
171
|
+
)}
|
|
172
|
+
{presentation.canStop && (
|
|
173
|
+
<button
|
|
174
|
+
type="button"
|
|
175
|
+
onClick={onStop}
|
|
176
|
+
className="inline-flex h-7 items-center gap-1 rounded-md px-2 text-current/70 transition-colors hover:bg-black/15 hover:text-current focus-visible:ring-1 focus-visible:ring-current focus-visible:outline-none"
|
|
177
|
+
>
|
|
178
|
+
<CircleStop className="size-3" />
|
|
179
|
+
Stop retrying
|
|
180
|
+
</button>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|