@gmickel/gno 1.18.0 → 1.19.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.
Files changed (129) hide show
  1. package/README.md +5 -3
  2. package/assets/skill/SKILL.md +27 -0
  3. package/package.json +2 -1
  4. package/spec/AGENTS.md +83 -0
  5. package/spec/CLAUDE.md +83 -0
  6. package/spec/bench-fixture.schema.json +137 -0
  7. package/spec/cli.md +2894 -0
  8. package/spec/db/schema.sql +442 -0
  9. package/spec/evals-agentic.md +510 -0
  10. package/spec/evals.md +1106 -0
  11. package/spec/mcp.md +2229 -0
  12. package/spec/output-schemas/activation-verification.schema.json +515 -0
  13. package/spec/output-schemas/ask.schema.json +366 -0
  14. package/spec/output-schemas/backlinks.schema.json +131 -0
  15. package/spec/output-schemas/bench-result.schema.json +120 -0
  16. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  17. package/spec/output-schemas/collection-list.schema.json +45 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  20. package/spec/output-schemas/context-list.schema.json +21 -0
  21. package/spec/output-schemas/doctor.schema.json +313 -0
  22. package/spec/output-schemas/error.schema.json +30 -0
  23. package/spec/output-schemas/expansion.schema.json +37 -0
  24. package/spec/output-schemas/get.schema.json +140 -0
  25. package/spec/output-schemas/graph-query.schema.json +99 -0
  26. package/spec/output-schemas/graph.schema.json +371 -0
  27. package/spec/output-schemas/links-list.schema.json +186 -0
  28. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  29. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  30. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  31. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  32. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  33. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  34. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  35. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  36. package/spec/output-schemas/models-list.schema.json +93 -0
  37. package/spec/output-schemas/multi-get.schema.json +103 -0
  38. package/spec/output-schemas/process-status.schema.json +119 -0
  39. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  40. package/spec/output-schemas/resident-status.schema.json +154 -0
  41. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  42. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  43. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  44. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  45. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  46. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  47. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  48. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  49. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  50. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  51. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  52. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  53. package/spec/output-schemas/search-result.schema.json +154 -0
  54. package/spec/output-schemas/search-results.schema.json +338 -0
  55. package/spec/output-schemas/similar.schema.json +84 -0
  56. package/spec/output-schemas/status.schema.json +676 -0
  57. package/spec/output-schemas/tags-list.schema.json +48 -0
  58. package/src/app/context-runtime-types.ts +3 -0
  59. package/src/app/context-runtime.ts +15 -1
  60. package/src/cli/commands/ask.ts +106 -36
  61. package/src/cli/commands/context-build.ts +56 -9
  62. package/src/cli/commands/get.ts +64 -3
  63. package/src/cli/commands/query.ts +62 -23
  64. package/src/cli/commands/replay.ts +140 -0
  65. package/src/cli/commands/search.ts +48 -3
  66. package/src/cli/commands/shared.ts +3 -1
  67. package/src/cli/commands/trace.ts +200 -0
  68. package/src/cli/commands/vsearch.ts +75 -53
  69. package/src/cli/program.ts +255 -0
  70. package/src/config/index.ts +9 -0
  71. package/src/config/retrieval-traces.ts +56 -0
  72. package/src/config/types.ts +4 -0
  73. package/src/core/context-compiler.ts +11 -4
  74. package/src/core/retrieval-qrels.ts +405 -0
  75. package/src/core/retrieval-replay-candidate.ts +368 -0
  76. package/src/core/retrieval-replay-types.ts +109 -0
  77. package/src/core/retrieval-replay-validation.ts +89 -0
  78. package/src/core/retrieval-replay.ts +441 -0
  79. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  80. package/src/core/retrieval-trace-export.ts +113 -0
  81. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  82. package/src/core/retrieval-trace-filters.ts +19 -0
  83. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  84. package/src/core/retrieval-trace-management-types.ts +132 -0
  85. package/src/core/retrieval-trace-management.ts +422 -0
  86. package/src/core/retrieval-trace-request.ts +141 -0
  87. package/src/core/retrieval-trace-session.ts +494 -0
  88. package/src/core/retrieval-trace.ts +472 -0
  89. package/src/mcp/tools/context.ts +59 -8
  90. package/src/mcp/tools/get.ts +35 -1
  91. package/src/mcp/tools/index.ts +74 -0
  92. package/src/mcp/tools/query.ts +95 -64
  93. package/src/mcp/tools/search.ts +36 -13
  94. package/src/mcp/tools/trace.ts +143 -0
  95. package/src/mcp/tools/vsearch.ts +71 -38
  96. package/src/pipeline/answer.ts +167 -26
  97. package/src/pipeline/graph-retrieval.ts +15 -1
  98. package/src/pipeline/hybrid.ts +151 -43
  99. package/src/pipeline/search.ts +36 -3
  100. package/src/pipeline/trace-metadata.ts +47 -0
  101. package/src/pipeline/types.ts +43 -0
  102. package/src/pipeline/vsearch.ts +101 -38
  103. package/src/sdk/client.ts +380 -71
  104. package/src/sdk/documents.ts +48 -1
  105. package/src/sdk/index.ts +17 -0
  106. package/src/sdk/types.ts +28 -0
  107. package/src/serve/context-capsule.ts +67 -8
  108. package/src/serve/public/app.tsx +12 -1
  109. package/src/serve/public/globals.built.css +1 -1
  110. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  111. package/src/serve/public/pages/Dashboard.tsx +10 -0
  112. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  113. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  114. package/src/serve/retrieval-trace.ts +28 -0
  115. package/src/serve/routes/api.ts +366 -72
  116. package/src/serve/routes/traces.ts +156 -0
  117. package/src/serve/server.ts +87 -2
  118. package/src/store/index.ts +31 -0
  119. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  120. package/src/store/migrations/index.ts +2 -0
  121. package/src/store/retrieval-trace-codec.ts +384 -0
  122. package/src/store/sqlite/adapter.ts +153 -1
  123. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  124. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  125. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  126. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  127. package/src/store/types.ts +297 -0
  128. package/src/store/vector/sqlite-vec.ts +76 -1
  129. package/src/store/vector/types.ts +1 -1
@@ -65,6 +65,8 @@ function getLocationLabel(location: string): string {
65
65
  return "Connectors";
66
66
  case "/graph":
67
67
  return "Graph";
68
+ case "/traces":
69
+ return "Trace history";
68
70
  case "/doc":
69
71
  case "/edit": {
70
72
  const uri = params.get("uri") ?? "";
@@ -7,6 +7,7 @@ import {
7
7
  FolderHeartIcon,
8
8
  FolderIcon,
9
9
  GitForkIcon,
10
+ HistoryIcon,
10
11
  Layers,
11
12
  Loader2Icon,
12
13
  MessageSquare,
@@ -499,6 +500,15 @@ export default function Dashboard({ navigate }: PageProps) {
499
500
  <GitForkIcon className="size-4" />
500
501
  Graph
501
502
  </Button>
503
+ <Button
504
+ className="gap-2"
505
+ onClick={() => navigate("/traces")}
506
+ size="lg"
507
+ variant="outline"
508
+ >
509
+ <HistoryIcon className="size-4" />
510
+ Trace history
511
+ </Button>
502
512
  </nav>
503
513
 
504
514
  {error && (
@@ -0,0 +1,478 @@
1
+ import {
2
+ ArrowLeftIcon,
3
+ DownloadIcon,
4
+ Loader2Icon,
5
+ RefreshCwIcon,
6
+ ShieldCheckIcon,
7
+ Trash2Icon,
8
+ } from "lucide-react";
9
+ import { useCallback, useEffect, useMemo, useState } from "react";
10
+
11
+ import type {
12
+ RetrievalTraceDetail,
13
+ RetrievalTraceExportResult,
14
+ RetrievalTraceLabelResult,
15
+ RetrievalTraceListResult,
16
+ RetrievalTracePurgeResult,
17
+ } from "../../../core/retrieval-trace-management";
18
+
19
+ import { Badge } from "../components/ui/badge";
20
+ import { Button } from "../components/ui/button";
21
+ import {
22
+ Card,
23
+ CardContent,
24
+ CardHeader,
25
+ CardTitle,
26
+ } from "../components/ui/card";
27
+ import {
28
+ Dialog,
29
+ DialogContent,
30
+ DialogDescription,
31
+ DialogFooter,
32
+ DialogHeader,
33
+ DialogTitle,
34
+ } from "../components/ui/dialog";
35
+ import {
36
+ Table,
37
+ TableBody,
38
+ TableCell,
39
+ TableHead,
40
+ TableHeader,
41
+ TableRow,
42
+ } from "../components/ui/table";
43
+ import { apiFetch } from "../hooks/use-api";
44
+ import {
45
+ TraceEvidenceList,
46
+ type TraceEvidenceSelection,
47
+ TraceLabelForm,
48
+ TracePurgeNotice,
49
+ } from "./trace-history-detail";
50
+
51
+ interface PageProps {
52
+ navigate: (to: string | number) => void;
53
+ }
54
+
55
+ type ConfirmAction =
56
+ | { kind: "delete"; traceId: string }
57
+ | { kind: "purge" }
58
+ | null;
59
+
60
+ const statusVariant = (
61
+ status: string
62
+ ): "default" | "destructive" | "outline" | "secondary" => {
63
+ if (status === "completed") return "default";
64
+ if (status === "failed" || status === "cancelled") return "destructive";
65
+ if (status === "partial") return "secondary";
66
+ return "outline";
67
+ };
68
+
69
+ const downloadJson = (value: unknown, filename: string): void => {
70
+ const blob = new Blob([`${JSON.stringify(value, null, 2)}\n`], {
71
+ type: "application/json",
72
+ });
73
+ const url = URL.createObjectURL(blob);
74
+ const anchor = document.createElement("a");
75
+ anchor.href = url;
76
+ anchor.download = filename;
77
+ anchor.click();
78
+ URL.revokeObjectURL(url);
79
+ };
80
+
81
+ export default function TraceHistory({ navigate }: PageProps) {
82
+ const [history, setHistory] = useState<RetrievalTraceListResult | null>(null);
83
+ const [detail, setDetail] = useState<RetrievalTraceDetail | null>(null);
84
+ const [selected, setSelected] = useState<Set<string>>(new Set());
85
+ const [busy, setBusy] = useState<string | null>("list");
86
+ const [error, setError] = useState<string | null>(null);
87
+ const [confirmAction, setConfirmAction] = useState<ConfirmAction>(null);
88
+ const [label, setLabel] = useState<
89
+ "relevant" | "irrelevant" | "missing_expected"
90
+ >("relevant");
91
+ const [targetRef, setTargetRef] = useState("");
92
+ const [sourceHash, setSourceHash] = useState("");
93
+ const [selectedEvidence, setSelectedEvidence] =
94
+ useState<TraceEvidenceSelection | null>(null);
95
+ const [purgeReceipt, setPurgeReceipt] =
96
+ useState<RetrievalTracePurgeResult | null>(null);
97
+
98
+ const loadHistory = useCallback(async (): Promise<void> => {
99
+ setBusy("list");
100
+ setError(null);
101
+ const result = await apiFetch<RetrievalTraceListResult>(
102
+ "/api/traces?limit=100"
103
+ );
104
+ if (result.error) {
105
+ setError(result.error);
106
+ } else {
107
+ setHistory(result.data);
108
+ const ids = new Set(result.data?.traces.map((trace) => trace.traceId));
109
+ setSelected(
110
+ (current) => new Set([...current].filter((traceId) => ids.has(traceId)))
111
+ );
112
+ }
113
+ setBusy(null);
114
+ }, []);
115
+
116
+ useEffect(() => {
117
+ void loadHistory();
118
+ }, [loadHistory]);
119
+
120
+ const inspect = useCallback(async (traceId: string): Promise<void> => {
121
+ setBusy(`show:${traceId}`);
122
+ setError(null);
123
+ const result = await apiFetch<RetrievalTraceDetail>(
124
+ `/api/traces/${encodeURIComponent(traceId)}?detailLimit=500`
125
+ );
126
+ setSelectedEvidence(null);
127
+ setTargetRef("");
128
+ setSourceHash("");
129
+ setDetail(result.data);
130
+ setError(result.error);
131
+ setBusy(null);
132
+ }, []);
133
+
134
+ const submitLabel = async (): Promise<void> => {
135
+ if (!detail || !targetRef.trim()) return;
136
+ setBusy("label");
137
+ setError(null);
138
+ const result = await apiFetch<RetrievalTraceLabelResult>(
139
+ `/api/traces/${encodeURIComponent(detail.trace.traceId)}/judgments`,
140
+ {
141
+ method: "POST",
142
+ body: JSON.stringify({
143
+ label,
144
+ targetRef: targetRef.trim(),
145
+ ...(label !== "missing_expected" &&
146
+ selectedEvidence?.ref === targetRef.trim()
147
+ ? {
148
+ targetKind: selectedEvidence.targetKind,
149
+ startLine: selectedEvidence.startLine,
150
+ endLine: selectedEvidence.endLine,
151
+ sourceHash: selectedEvidence.sourceHash,
152
+ docid: selectedEvidence.docid,
153
+ }
154
+ : {}),
155
+ ...(label === "missing_expected" && sourceHash.trim()
156
+ ? { sourceHash: sourceHash.trim() }
157
+ : {}),
158
+ }),
159
+ }
160
+ );
161
+ if (result.error) {
162
+ setError(result.error);
163
+ } else {
164
+ setTargetRef("");
165
+ setSourceHash("");
166
+ setSelectedEvidence(null);
167
+ await inspect(detail.trace.traceId);
168
+ }
169
+ setBusy(null);
170
+ };
171
+
172
+ const exportSelected = async (): Promise<void> => {
173
+ if (selected.size === 0) return;
174
+ setBusy("export");
175
+ setError(null);
176
+ const result = await apiFetch<RetrievalTraceExportResult>(
177
+ "/api/traces/export",
178
+ {
179
+ method: "POST",
180
+ body: JSON.stringify({
181
+ traceIds: [...selected],
182
+ format: "agentic-receipt",
183
+ }),
184
+ }
185
+ );
186
+ if (result.data) {
187
+ downloadJson(
188
+ result.data.artifact,
189
+ `gno-traces-${result.data.manifest.artifactHash.slice(0, 12)}.json`
190
+ );
191
+ }
192
+ setError(result.error);
193
+ setBusy(null);
194
+ };
195
+
196
+ const runConfirmedAction = async (): Promise<void> => {
197
+ if (!confirmAction) return;
198
+ const action = confirmAction;
199
+ setConfirmAction(null);
200
+ setBusy(action.kind);
201
+ setError(null);
202
+ const endpoint =
203
+ action.kind === "purge"
204
+ ? "/api/traces"
205
+ : `/api/traces/${encodeURIComponent(action.traceId)}`;
206
+ const result = await apiFetch<RetrievalTracePurgeResult>(endpoint, {
207
+ method: "DELETE",
208
+ });
209
+ if (result.error) {
210
+ setError(result.error);
211
+ } else {
212
+ if (action.kind === "purge") setPurgeReceipt(result.data);
213
+ setDetail(null);
214
+ setSelected(new Set());
215
+ await loadHistory();
216
+ }
217
+ setBusy(null);
218
+ };
219
+
220
+ const terminalSelected = useMemo(() => {
221
+ const byId = new Map(
222
+ history?.traces.map((trace) => [trace.traceId, trace]) ?? []
223
+ );
224
+ return [...selected].every(
225
+ (traceId) => byId.get(traceId)?.status !== "open"
226
+ );
227
+ }, [history, selected]);
228
+
229
+ return (
230
+ <div className="min-h-screen bg-background">
231
+ <header className="border-b border-border/50 bg-card/50 backdrop-blur-sm">
232
+ <div className="mx-auto flex max-w-7xl items-center justify-between p-6">
233
+ <div className="flex items-center gap-4">
234
+ <Button
235
+ aria-label="Back"
236
+ onClick={() => navigate(-1)}
237
+ size="icon"
238
+ variant="ghost"
239
+ >
240
+ <ArrowLeftIcon className="size-4" />
241
+ </Button>
242
+ <div>
243
+ <h1 className="font-display text-2xl font-semibold">
244
+ Trace history
245
+ </h1>
246
+ <p className="text-sm text-muted-foreground">
247
+ Private receipts. Local evidence. Explicit feedback only.
248
+ </p>
249
+ </div>
250
+ </div>
251
+ <div className="flex gap-2">
252
+ <Button
253
+ disabled={busy !== null}
254
+ onClick={() => void loadHistory()}
255
+ variant="outline"
256
+ >
257
+ <RefreshCwIcon className="mr-2 size-4" />
258
+ Refresh
259
+ </Button>
260
+ <Button
261
+ disabled={
262
+ busy !== null || selected.size === 0 || !terminalSelected
263
+ }
264
+ onClick={() => void exportSelected()}
265
+ >
266
+ <DownloadIcon className="mr-2 size-4" />
267
+ Export {selected.size || ""}
268
+ </Button>
269
+ <Button
270
+ disabled={busy !== null || !history?.traces.length}
271
+ onClick={() => setConfirmAction({ kind: "purge" })}
272
+ variant="destructive"
273
+ >
274
+ <Trash2Icon className="mr-2 size-4" />
275
+ Purge all
276
+ </Button>
277
+ </div>
278
+ </div>
279
+ </header>
280
+
281
+ <main className="mx-auto grid max-w-7xl gap-6 p-6 lg:grid-cols-[1.35fr_1fr]">
282
+ <div className="lg:col-span-2">
283
+ <TracePurgeNotice receipt={purgeReceipt} />
284
+ </div>
285
+ <Card>
286
+ <CardHeader>
287
+ <CardTitle className="flex items-center gap-2">
288
+ <ShieldCheckIcon className="size-5 text-primary" />
289
+ Local receipts
290
+ </CardTitle>
291
+ </CardHeader>
292
+ <CardContent>
293
+ {error && (
294
+ <div className="mb-4 rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive">
295
+ {error}
296
+ </div>
297
+ )}
298
+ {busy === "list" && !history ? (
299
+ <div className="flex items-center justify-center py-16 text-muted-foreground">
300
+ <Loader2Icon className="mr-2 size-5 animate-spin" />
301
+ Loading traces
302
+ </div>
303
+ ) : (
304
+ <Table>
305
+ <TableHeader>
306
+ <TableRow>
307
+ <TableHead className="w-10">Use</TableHead>
308
+ <TableHead>Trace</TableHead>
309
+ <TableHead>Status</TableHead>
310
+ <TableHead>Mode</TableHead>
311
+ <TableHead>Created</TableHead>
312
+ </TableRow>
313
+ </TableHeader>
314
+ <TableBody>
315
+ {history?.traces.map((trace) => (
316
+ <TableRow
317
+ className="cursor-pointer"
318
+ key={trace.traceId}
319
+ onClick={() => void inspect(trace.traceId)}
320
+ >
321
+ <TableCell>
322
+ <input
323
+ aria-label={`Select ${trace.traceId}`}
324
+ checked={selected.has(trace.traceId)}
325
+ onChange={(event) => {
326
+ event.stopPropagation();
327
+ setSelected((current) => {
328
+ const next = new Set(current);
329
+ if (next.has(trace.traceId)) {
330
+ next.delete(trace.traceId);
331
+ } else {
332
+ next.add(trace.traceId);
333
+ }
334
+ return next;
335
+ });
336
+ }}
337
+ onClick={(event) => event.stopPropagation()}
338
+ type="checkbox"
339
+ />
340
+ </TableCell>
341
+ <TableCell className="max-w-48 truncate font-mono text-xs">
342
+ {trace.traceId}
343
+ </TableCell>
344
+ <TableCell>
345
+ <Badge variant={statusVariant(trace.status)}>
346
+ {trace.status}
347
+ </Badge>
348
+ </TableCell>
349
+ <TableCell>{trace.redactionMode}</TableCell>
350
+ <TableCell className="text-xs text-muted-foreground">
351
+ {new Date(trace.createdAtMs).toLocaleString()}
352
+ </TableCell>
353
+ </TableRow>
354
+ ))}
355
+ </TableBody>
356
+ </Table>
357
+ )}
358
+ </CardContent>
359
+ </Card>
360
+
361
+ <Card>
362
+ <CardHeader>
363
+ <CardTitle>Receipt detail</CardTitle>
364
+ </CardHeader>
365
+ <CardContent>
366
+ {!detail ? (
367
+ <p className="py-12 text-center text-sm text-muted-foreground">
368
+ Select a trace to inspect bounded evidence and add explicit
369
+ feedback.
370
+ </p>
371
+ ) : (
372
+ <div className="space-y-5">
373
+ <div className="space-y-1 text-sm">
374
+ <div className="break-all font-mono text-xs">
375
+ {detail.trace.traceId}
376
+ </div>
377
+ <div className="flex gap-2">
378
+ <Badge variant={statusVariant(detail.trace.status)}>
379
+ {detail.trace.status}
380
+ </Badge>
381
+ <Badge variant="outline">
382
+ {detail.trace.redactionMode}
383
+ </Badge>
384
+ </div>
385
+ </div>
386
+
387
+ <div className="grid grid-cols-4 gap-2 text-center text-xs">
388
+ {(
389
+ [
390
+ ["Runs", detail.runs.length],
391
+ ["Events", detail.events.length],
392
+ ["Labels", detail.judgments.length],
393
+ ["Exports", detail.exports.length],
394
+ ] as const
395
+ ).map(([name, count]) => (
396
+ <div className="rounded-md bg-muted p-2" key={name}>
397
+ <div className="font-semibold">{count}</div>
398
+ <div className="text-muted-foreground">{name}</div>
399
+ </div>
400
+ ))}
401
+ </div>
402
+
403
+ <TraceEvidenceList
404
+ detail={detail}
405
+ onSelect={(selection) => {
406
+ setSelectedEvidence(selection);
407
+ setTargetRef(selection.ref);
408
+ setSourceHash(selection.sourceHash ?? "");
409
+ }}
410
+ />
411
+
412
+ <TraceLabelForm
413
+ busy={busy === "label"}
414
+ label={label}
415
+ onLabelChange={setLabel}
416
+ onSourceHashChange={setSourceHash}
417
+ onSubmit={() => void submitLabel()}
418
+ onTargetRefChange={(value) => {
419
+ setTargetRef(value);
420
+ setSelectedEvidence(null);
421
+ }}
422
+ sourceHash={sourceHash}
423
+ targetRef={targetRef}
424
+ />
425
+
426
+ <Button
427
+ className="w-full"
428
+ onClick={() =>
429
+ setConfirmAction({
430
+ kind: "delete",
431
+ traceId: detail.trace.traceId,
432
+ })
433
+ }
434
+ variant="destructive"
435
+ >
436
+ <Trash2Icon className="mr-2 size-4" />
437
+ Delete this trace
438
+ </Button>
439
+ </div>
440
+ )}
441
+ </CardContent>
442
+ </Card>
443
+ </main>
444
+
445
+ <Dialog
446
+ onOpenChange={(open) => {
447
+ if (!open) setConfirmAction(null);
448
+ }}
449
+ open={confirmAction !== null}
450
+ >
451
+ <DialogContent>
452
+ <DialogHeader>
453
+ <DialogTitle>
454
+ {confirmAction?.kind === "purge"
455
+ ? "Purge every retrieval trace?"
456
+ : "Delete this retrieval trace?"}
457
+ </DialogTitle>
458
+ <DialogDescription>
459
+ This removes local receipt data and cannot be undone. Source
460
+ documents are not changed.
461
+ </DialogDescription>
462
+ </DialogHeader>
463
+ <DialogFooter>
464
+ <Button onClick={() => setConfirmAction(null)} variant="outline">
465
+ Cancel
466
+ </Button>
467
+ <Button
468
+ onClick={() => void runConfirmedAction()}
469
+ variant="destructive"
470
+ >
471
+ Delete local receipts
472
+ </Button>
473
+ </DialogFooter>
474
+ </DialogContent>
475
+ </Dialog>
476
+ </div>
477
+ );
478
+ }