@parhelia/alpaca 0.4.12784 → 0.4.12796

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 (70) hide show
  1. package/dist/index.d.ts +11 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +65 -2
  4. package/dist/tracing/components/CaptureCompleteness.d.ts +6 -0
  5. package/dist/tracing/components/CaptureCompleteness.d.ts.map +1 -0
  6. package/dist/tracing/components/CaptureCompleteness.js +26 -0
  7. package/dist/tracing/components/CaptureFilterDialog.d.ts +6 -0
  8. package/dist/tracing/components/CaptureFilterDialog.d.ts.map +1 -0
  9. package/dist/tracing/components/CaptureFilterDialog.js +19 -0
  10. package/dist/tracing/components/CaptureFilterForm.d.ts +13 -0
  11. package/dist/tracing/components/CaptureFilterForm.d.ts.map +1 -0
  12. package/dist/tracing/components/CaptureFilterForm.js +190 -0
  13. package/dist/tracing/components/CountersTab.d.ts +6 -0
  14. package/dist/tracing/components/CountersTab.d.ts.map +1 -0
  15. package/dist/tracing/components/CountersTab.js +47 -0
  16. package/dist/tracing/components/InstanceSwitcher.d.ts +11 -0
  17. package/dist/tracing/components/InstanceSwitcher.d.ts.map +1 -0
  18. package/dist/tracing/components/InstanceSwitcher.js +37 -0
  19. package/dist/tracing/components/LogMessage.d.ts +6 -0
  20. package/dist/tracing/components/LogMessage.d.ts.map +1 -0
  21. package/dist/tracing/components/LogMessage.js +50 -0
  22. package/dist/tracing/components/LogsTab.d.ts +12 -0
  23. package/dist/tracing/components/LogsTab.d.ts.map +1 -0
  24. package/dist/tracing/components/LogsTab.js +120 -0
  25. package/dist/tracing/components/StackTracePopover.d.ts +19 -0
  26. package/dist/tracing/components/StackTracePopover.d.ts.map +1 -0
  27. package/dist/tracing/components/StackTracePopover.js +45 -0
  28. package/dist/tracing/components/StoriesPane.d.ts +2 -0
  29. package/dist/tracing/components/StoriesPane.d.ts.map +1 -0
  30. package/dist/tracing/components/StoriesPane.js +204 -0
  31. package/dist/tracing/components/StoryDetail.d.ts +2 -0
  32. package/dist/tracing/components/StoryDetail.d.ts.map +1 -0
  33. package/dist/tracing/components/StoryDetail.js +81 -0
  34. package/dist/tracing/components/TimelineTab.d.ts +7 -0
  35. package/dist/tracing/components/TimelineTab.d.ts.map +1 -0
  36. package/dist/tracing/components/TimelineTab.js +59 -0
  37. package/dist/tracing/components/TimersTab.d.ts +6 -0
  38. package/dist/tracing/components/TimersTab.d.ts.map +1 -0
  39. package/dist/tracing/components/TimersTab.js +61 -0
  40. package/dist/tracing/components/TraceSourcesSidebar.d.ts +2 -0
  41. package/dist/tracing/components/TraceSourcesSidebar.d.ts.map +1 -0
  42. package/dist/tracing/components/TraceSourcesSidebar.js +74 -0
  43. package/dist/tracing/components/TracingTitlebar.d.ts +15 -0
  44. package/dist/tracing/components/TracingTitlebar.d.ts.map +1 -0
  45. package/dist/tracing/components/TracingTitlebar.js +98 -0
  46. package/dist/tracing/components/TracingWorkspace.d.ts +2 -0
  47. package/dist/tracing/components/TracingWorkspace.d.ts.map +1 -0
  48. package/dist/tracing/components/TracingWorkspace.js +38 -0
  49. package/dist/tracing/components/shared.d.ts +38 -0
  50. package/dist/tracing/components/shared.d.ts.map +1 -0
  51. package/dist/tracing/components/shared.js +95 -0
  52. package/dist/tracing/format.d.ts +18 -0
  53. package/dist/tracing/format.d.ts.map +1 -0
  54. package/dist/tracing/format.js +140 -0
  55. package/dist/tracing/messageTags.d.ts +20 -0
  56. package/dist/tracing/messageTags.d.ts.map +1 -0
  57. package/dist/tracing/messageTags.js +46 -0
  58. package/dist/tracing/traceStore.d.ts +123 -0
  59. package/dist/tracing/traceStore.d.ts.map +1 -0
  60. package/dist/tracing/traceStore.js +572 -0
  61. package/dist/tracing/tracingService.d.ts +131 -0
  62. package/dist/tracing/tracingService.d.ts.map +1 -0
  63. package/dist/tracing/tracingService.js +151 -0
  64. package/dist/tracing/types.d.ts +292 -0
  65. package/dist/tracing/types.d.ts.map +1 -0
  66. package/dist/tracing/types.js +16 -0
  67. package/dist/tracing/useTraceUpdates.d.ts +8 -0
  68. package/dist/tracing/useTraceUpdates.d.ts.map +1 -0
  69. package/dist/tracing/useTraceUpdates.js +31 -0
  70. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,3 +1,14 @@
1
1
  import { EditorConfiguration } from "@parhelia/core";
2
+ export declare const ALPACA_TRACING_WORKSPACE_ID = "alpaca-tracing";
3
+ export declare const ALPACA_TRACE_SOURCES_SIDEBAR_ID = "alpaca-trace-sources";
4
+ /**
5
+ * Registers the Alpaca Tracing workspace (administrators only, hidden unless the server advertises the
6
+ * "alpaca" module). Safe to call from any host app: it adds nothing else.
7
+ */
8
+ export declare function configureAlpacaTracing(configuration: EditorConfiguration): EditorConfiguration;
2
9
  export declare function configureAlpaca(configuration: EditorConfiguration): EditorConfiguration;
10
+ export { TracingWorkspace } from "./tracing/components/TracingWorkspace";
11
+ export { TracingTitlebar } from "./tracing/components/TracingTitlebar";
12
+ export { TraceSourcesSidebar } from "./tracing/components/TraceSourcesSidebar";
13
+ export type * from "./tracing/types";
3
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EAIpB,MAAM,gBAAgB,CAAC;AAwDxB,wBAAgB,eAAe,CAAC,aAAa,EAAE,mBAAmB,uBA6BjE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EAKpB,MAAM,gBAAgB,CAAC;AA4ExB,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAC5D,eAAO,MAAM,+BAA+B,yBAAyB,CAAC;AAkCtE;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,mBAAmB,uBAoBxE;AAED,wBAAgB,eAAe,CAAC,aAAa,EAAE,mBAAmB,uBA6BjE;AAED,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAC/E,mBAAmB,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -1,13 +1,21 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { lazy, Suspense } from "react";
3
- import { PanelsTopLeft } from "lucide-react";
3
+ import { Activity, PanelsTopLeft, Waypoints } from "lucide-react";
4
4
  import { GraphQLIcon, JsonIcon, } from "@parhelia/core";
5
5
  import { Layout } from "./Layout";
6
6
  import { alpacaComponentCommandProvider } from "./componentCommands";
7
+ import { TracingTitlebar } from "./tracing/components/TracingTitlebar";
8
+ import { TraceSourcesSidebar } from "./tracing/components/TraceSourcesSidebar";
7
9
  const GraphQL = lazy(() => import("./GraphQL").then((module) => ({ default: module.GraphQL })));
8
10
  function LazyGraphQLPanel() {
9
11
  return (_jsx(Suspense, { fallback: _jsx("div", { className: "p-4 text-sm text-muted-foreground", children: "Loading GraphQL editor..." }), children: _jsx(GraphQL, {}) }));
10
12
  }
13
+ const TracingWorkspaceComponent = lazy(() => import("./tracing/components/TracingWorkspace").then((module) => ({
14
+ default: module.TracingWorkspace,
15
+ })));
16
+ function LazyTracingWorkspace() {
17
+ return (_jsx(Suspense, { fallback: _jsx("div", { className: "p-4 text-sm text-muted-foreground", children: "Loading tracing\u2026" }), children: _jsx(TracingWorkspaceComponent, {}) }));
18
+ }
11
19
  const layoutDebugSidebar = {
12
20
  id: "layout",
13
21
  title: "Layout",
@@ -40,6 +48,58 @@ const graphqlSidebar = {
40
48
  },
41
49
  ],
42
50
  };
51
+ export const ALPACA_TRACING_WORKSPACE_ID = "alpaca-tracing";
52
+ export const ALPACA_TRACE_SOURCES_SIDEBAR_ID = "alpaca-trace-sources";
53
+ const traceSourcesSidebar = {
54
+ id: ALPACA_TRACE_SOURCES_SIDEBAR_ID,
55
+ title: "Trace sources",
56
+ icon: _jsx(Waypoints, { strokeWidth: 1 }),
57
+ position: "left",
58
+ sortOrder: 10,
59
+ defaultWidth: 300,
60
+ requiresModule: "alpaca",
61
+ requiresAdministrator: true,
62
+ panels: [
63
+ {
64
+ name: "alpaca-trace-sources",
65
+ title: "Sources",
66
+ content: _jsx(TraceSourcesSidebar, {}),
67
+ initialSize: 100,
68
+ },
69
+ ],
70
+ };
71
+ const tracingWorkspace = {
72
+ id: ALPACA_TRACING_WORKSPACE_ID,
73
+ title: "Tracing",
74
+ icon: _jsx(Activity, { strokeWidth: 1 }),
75
+ component: _jsx(LazyTracingWorkspace, {}),
76
+ titlebar: _jsx(TracingTitlebar, {}),
77
+ supportsSidebars: true,
78
+ defaultSidebars: [ALPACA_TRACE_SOURCES_SIDEBAR_ID],
79
+ sortOrder: 35,
80
+ requiresModule: "alpaca",
81
+ requiresAdministrator: true,
82
+ };
83
+ /**
84
+ * Registers the Alpaca Tracing workspace (administrators only, hidden unless the server advertises the
85
+ * "alpaca" module). Safe to call from any host app: it adds nothing else.
86
+ */
87
+ export function configureAlpacaTracing(configuration) {
88
+ const workspaces = configuration.editor?.workspaces || [];
89
+ const sidebars = configuration.editor?.sidebars || [];
90
+ const nextWorkspaces = workspaces.some((w) => w.id === tracingWorkspace.id)
91
+ ? workspaces
92
+ : [...workspaces, tracingWorkspace];
93
+ const nextSidebars = sidebars.some((s) => s.id === traceSourcesSidebar.id)
94
+ ? sidebars
95
+ : [...sidebars, traceSourcesSidebar];
96
+ configuration.editor = {
97
+ ...configuration.editor,
98
+ workspaces: nextWorkspaces,
99
+ sidebars: nextSidebars,
100
+ };
101
+ return configuration;
102
+ }
43
103
  export function configureAlpaca(configuration) {
44
104
  const sidebars = (configuration.editor?.sidebars || []).filter((s) => s.id !== "debug");
45
105
  const hasLayoutDebugSidebar = sidebars.some((s) => s.id === layoutDebugSidebar.id);
@@ -59,5 +119,8 @@ export function configureAlpaca(configuration) {
59
119
  providers: [...componentCommandProviders, alpacaComponentCommandProvider],
60
120
  };
61
121
  }
62
- return configuration;
122
+ return configureAlpacaTracing(configuration);
63
123
  }
124
+ export { TracingWorkspace } from "./tracing/components/TracingWorkspace";
125
+ export { TracingTitlebar } from "./tracing/components/TracingTitlebar";
126
+ export { TraceSourcesSidebar } from "./tracing/components/TraceSourcesSidebar";
@@ -0,0 +1,6 @@
1
+ import type { StoryRow, TraceSource } from "../types";
2
+ export declare function CaptureCompleteness({ source, story }: {
3
+ source: TraceSource | null;
4
+ story?: StoryRow;
5
+ }): import("react").JSX.Element | null;
6
+ //# sourceMappingURL=CaptureCompleteness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CaptureCompleteness.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/CaptureCompleteness.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEtD,wBAAgB,mBAAmB,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE,sCAwBtG"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function CaptureCompleteness({ source, story }) {
3
+ if (source?.kind !== "capture" && !source?.handoffPending)
4
+ return null;
5
+ const prior = story?.evictedBeforeCapture;
6
+ const lost = story?.lostDuringHandoff;
7
+ const notes = [
8
+ source.handoffPending ? `Stopping… waiting for ${source.writersInFlight ?? 0} writers` : null,
9
+ source.closePending ? "Closing capture… waiting for its last append" : null,
10
+ !source.finalized && !source.handoffPending ? `Finalizing… (${Math.ceil(source.finalizesInSeconds ?? 0)} s)` : null,
11
+ source.handoffIncomplete ? "Handoff incomplete: a writer exceeded the wait limit" : null,
12
+ source.releaseDeferred ? "Live capture data is still reserved until writers release it" : null,
13
+ source.truncated ? (source.droppedEntries || source.droppedStories
14
+ ? `Capture truncated: ${source.droppedEntries ?? 0} entries and ${source.droppedStories ?? 0} stories dropped`
15
+ : "Capture truncated: some text or metadata exceeded the configured limits") : null,
16
+ story?.incomplete ? "Request was still running at finalization" : null,
17
+ story?.endedAfterStop ? "Request ended after recording stopped" : null,
18
+ prior && (prior.logs || prior.timers) ? `Evicted before capture: ${prior.logs} logs, ${prior.timers} timers` : null,
19
+ lost && (lost.logs || lost.timers) ? `Lost during handoff: ${lost.logs} logs, ${lost.timers} timers` : null,
20
+ source.lossUncertain || story?.lossUncertain ? "Some eviction notifications could not be accounted for; loss is uncertain" : null,
21
+ source.lossIsUpperBound || story?.lossIsUpperBound ? "Reported loss is an upper bound" : null,
22
+ ].filter(Boolean);
23
+ if (!notes.length)
24
+ return null;
25
+ return _jsx("div", { className: "border-b border-border-default bg-feedback-orange-light px-4 py-2 text-xs text-neutral-grey-100", role: "status", children: notes.join(" · ") });
26
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Capture filter of one source as a modal: it is configuration for future stories, not a view of data,
3
+ * so it does not live next to logs and timers. Opened from the gear above the stories list or a source's menu.
4
+ */
5
+ export declare function CaptureFilterDialog(): import("react").JSX.Element;
6
+ //# sourceMappingURL=CaptureFilterDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CaptureFilterDialog.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/CaptureFilterDialog.tsx"],"names":[],"mappings":"AAMA;;;GAGG;AACH,wBAAgB,mBAAmB,gCAgClC"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Dialog, DialogContent, DialogDescription, StyledDialogTitle } from "@parhelia/core";
3
+ import { sourceKindLabel } from "../format";
4
+ import { traceActions, useTraceStore } from "../traceStore";
5
+ import { CaptureFilterForm } from "./CaptureFilterForm";
6
+ import { KindIcon } from "./shared";
7
+ /**
8
+ * Capture filter of one source as a modal: it is configuration for future stories, not a view of data,
9
+ * so it does not live next to logs and timers. Opened from the gear above the stories list or a source's menu.
10
+ */
11
+ export function CaptureFilterDialog() {
12
+ const state = useTraceStore();
13
+ const sourceId = state.captureFilterSourceId;
14
+ const source = sourceId ? state.status?.sources.find((s) => s.id === sourceId) ?? null : null;
15
+ const open = !!sourceId;
16
+ return (_jsx(Dialog, { open: open, onOpenChange: (next) => !next && traceActions.closeCaptureFilter(), children: _jsxs(DialogContent, { className: "flex max-h-[88vh] w-[min(94vw,720px)] max-w-none flex-col gap-0 p-0", "data-testid": "alpaca-trace-capture-filter-dialog", children: [_jsx(StyledDialogTitle, { icon: _jsx(KindIcon, { kind: source?.kind ?? "personal" }), title: "Capture filter", subtitle: source
17
+ ? `${sourceKindLabel(source)} · ${source.name} · ${source.kind === "capture" || source.kind === "restored" ? "what this source recorded" : "what this source records from now on"}`
18
+ : undefined }), _jsx(DialogDescription, { className: "sr-only", children: "What this source records from now on: log scopes, stack traces, skipped requests, bypassed caches and feature previews." }), source && sourceId ? (_jsx(CaptureFilterForm, { sessionId: sourceId, source: source, onSaved: () => traceActions.closeCaptureFilter() })) : (_jsx("div", { className: "p-6 text-xs text-neutral-grey-50", children: "This source is no longer available." }))] }) }));
19
+ }
@@ -0,0 +1,13 @@
1
+ import type { TraceSource } from "../types";
2
+ /**
3
+ * Body + footer of the capture filter dialog. Three sections: which log scopes are kept, which requests
4
+ * get a story at all, and the per-session overrides (bypassed caches, feature previews).
5
+ * Every list renders its "empty" meaning explicitly, so an empty box never reads as "nothing configured".
6
+ */
7
+ export declare function CaptureFilterForm({ sessionId, source, onSaved, }: {
8
+ sessionId: string;
9
+ source: TraceSource;
10
+ /** Called after a successful save (the dialog closes itself). */
11
+ onSaved?: () => void;
12
+ }): import("react").JSX.Element;
13
+ //# sourceMappingURL=CaptureFilterForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CaptureFilterForm.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/CaptureFilterForm.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAA4B,WAAW,EAAE,MAAM,UAAU,CAAC;AAGtE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,MAAM,EACN,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,CAAC;IACpB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,+BAyRA"}
@@ -0,0 +1,190 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { getTraceSnapshot, useTraceStore } from "../traceStore";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { Alert, AlertDescription, Button, Checkbox, DialogFooter, Input, Label, Popover, PopoverContent, PopoverTrigger, Switch, } from "@parhelia/core";
5
+ import { ChevronDown, Plus, X } from "lucide-react";
6
+ import { TracingApiError, forTracingInstance } from "../tracingService";
7
+ import { EmptyHint, InlineError, cls } from "./shared";
8
+ /**
9
+ * Body + footer of the capture filter dialog. Three sections: which log scopes are kept, which requests
10
+ * get a story at all, and the per-session overrides (bypassed caches, feature previews).
11
+ * Every list renders its "empty" meaning explicitly, so an empty box never reads as "nothing configured".
12
+ */
13
+ export function CaptureFilterForm({ sessionId, source, onSaved, }) {
14
+ const requestContext = useTraceStore();
15
+ const tracingService = forTracingInstance(requestContext.selectedInstance, requestContext.generation, requestContext.status?.globalRecording?.recordingId);
16
+ const [filter, setFilter] = useState(null);
17
+ const [saved, setSaved] = useState(null);
18
+ const [caches, setCaches] = useState([]);
19
+ const [features, setFeatures] = useState([]);
20
+ const [error, setError] = useState(null);
21
+ const [saving, setSaving] = useState(false);
22
+ useEffect(() => {
23
+ let cancelled = false;
24
+ setFilter(null);
25
+ setSaved(null);
26
+ tracingService
27
+ .filters(sessionId)
28
+ .then((result) => {
29
+ if (cancelled)
30
+ return;
31
+ setFilter(result.filter);
32
+ setSaved(result.filter);
33
+ setError(null);
34
+ })
35
+ .catch((e) => {
36
+ if (cancelled)
37
+ return;
38
+ setError(e instanceof TracingApiError && e.notFound
39
+ ? "Not in memory anymore."
40
+ : e instanceof Error
41
+ ? e.message
42
+ : String(e));
43
+ });
44
+ tracingService
45
+ .caches()
46
+ .then((r) => !cancelled && setCaches(r.caches))
47
+ .catch(() => undefined);
48
+ tracingService
49
+ .features()
50
+ .then((r) => !cancelled && setFeatures(r.features.map((f) => f.name)))
51
+ .catch(() => undefined);
52
+ return () => {
53
+ cancelled = true;
54
+ };
55
+ }, [sessionId, requestContext.selectedInstance, requestContext.epoch]);
56
+ const dirty = useMemo(() => JSON.stringify(filter) !== JSON.stringify(saved), [filter, saved]);
57
+ const readOnly = source.kind === "restored" || source.kind === "capture";
58
+ const save = async () => {
59
+ if (!filter || readOnly)
60
+ return;
61
+ const epoch = requestContext.epoch;
62
+ setSaving(true);
63
+ try {
64
+ const result = await tracingService.saveFilters(sessionId, filter);
65
+ if (getTraceSnapshot().epoch !== epoch)
66
+ return;
67
+ setFilter(result.filter);
68
+ setSaved(result.filter);
69
+ setError(null);
70
+ onSaved?.();
71
+ }
72
+ catch (e) {
73
+ if (getTraceSnapshot().epoch !== epoch)
74
+ return;
75
+ setError(e instanceof Error ? e.message : String(e));
76
+ }
77
+ finally {
78
+ if (getTraceSnapshot().epoch === epoch)
79
+ setSaving(false);
80
+ }
81
+ };
82
+ if (error && !filter)
83
+ return (_jsx("div", { className: "p-6", children: _jsx(InlineError, { message: error }) }));
84
+ if (!filter)
85
+ return _jsx(EmptyHint, { title: "Loading\u2026" });
86
+ const update = (patch) => setFilter((current) => (current ? { ...current, ...patch } : current));
87
+ const cacheOptions = caches.map((c) => ({
88
+ value: c.name,
89
+ label: c.name,
90
+ group: c.cacheType,
91
+ }));
92
+ const featureOptions = features.map((f) => ({ value: f, label: f }));
93
+ return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", "data-testid": "alpaca-trace-capture-filter", children: [_jsxs("div", { className: "min-h-0 flex-1 overflow-auto px-6 py-5", children: [readOnly && (_jsx(Alert, { variant: "info", className: "mb-5", children: _jsx(AlertDescription, { children: source.kind === "capture"
94
+ ? "This is a finished capture: a global recording window that has ended. Its stories are frozen and no new ones are added, so the filter below only documents what was recorded and cannot be changed."
95
+ : "This source was restored from trace storage and is read-only. The filter below documents what was recorded and cannot be changed." }) })), _jsxs("div", { className: "flex flex-col gap-7", children: [_jsx(Section, { title: "Log scopes", description: "Scopes are dotted prefixes; a prefix also covers everything below it, so Alpaca.Caching includes Alpaca.Caching.Html.", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(TokenField, { label: "Include", hint: "Only entries from these scopes are recorded.", emptyLabel: "Everything is recorded", addLabel: "Add a scope prefix", values: filter.includeScopes, onChange: (v) => update({ includeScopes: v }), disabled: readOnly, testId: "alpaca-trace-filter-include" }), _jsx(TokenField, { label: "Exclude", hint: "Entries from these scopes are dropped, even when included above.", emptyLabel: "Nothing is excluded", addLabel: "Add a scope prefix", values: filter.excludeScopes, onChange: (v) => update({ excludeScopes: v }), disabled: readOnly, testId: "alpaca-trace-filter-exclude" }), _jsx(TokenField, { label: "Keep Trace level", hint: "Trace-level entries are normally dropped. Keep them for these scopes.", emptyLabel: "Trace entries are dropped", addLabel: "Add a scope prefix", values: filter.includeScopesTraceLevel, onChange: (v) => update({ includeScopesTraceLevel: v }), disabled: readOnly, testId: "alpaca-trace-filter-trace-level" }), _jsx(TokenField, { label: "Capture stack traces", hint: "Every entry in these scopes records the full call stack. Expensive; use it on narrow scopes.", emptyLabel: "No stack traces", addLabel: "Add a scope prefix", values: filter.stackTraceScopes, onChange: (v) => update({ stackTraceScopes: v }), disabled: readOnly, testId: "alpaca-trace-filter-stack-traces", footer: _jsxs("label", { className: cls("flex items-center gap-2 text-2xs text-neutral-grey-100", filter.stackTraceScopes.length === 0 &&
96
+ "text-neutral-grey-50"), title: filter.stackTraceScopes.length === 0
97
+ ? "Add a stack trace scope first; counters are only traced inside those scopes."
98
+ : undefined, children: [_jsx(Switch, { checked: filter.stackTraceForCounters, disabled: readOnly || filter.stackTraceScopes.length === 0, onCheckedChange: (v) => update({ stackTraceForCounters: v }), "data-testid": "alpaca-trace-filter-counter-stacks" }), "Also record a stack for every counter increment in these scopes"] }) })] }) }), _jsx(Section, { title: "Stories", description: "A story is one request or background job with its own logs, timers and counters.", children: _jsx(TokenField, { label: "Skip request paths", hint: "Requests whose path starts with one of these prefixes never get a story.", emptyLabel: "Every request gets a story", addLabel: "Add a path prefix, e.g. /sitecore/shell/", values: filter.excludeStories, onChange: (v) => update({ excludeStories: v }), disabled: readOnly, testId: "alpaca-trace-filter-exclude-stories" }) }), _jsx(Section, { title: "Overrides for this source", description: "Only requests recorded by this source are affected. Nothing changes for other users or requests.", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(PickerField, { label: "Bypassed caches", hint: "Requests behave as if these caches were empty, without clearing them for everyone.", emptyLabel: "All caches active", addLabel: "Add cache", options: cacheOptions, values: filter.disabledCaches, onChange: (v) => update({ disabledCaches: v }), disabled: readOnly, action: caches.length > 0 ? (_jsx(ClearCacheButton, { caches: caches, onCleared: setCaches })) : undefined, testId: "alpaca-trace-filter-caches" }), _jsx(PickerField, { label: "Feature previews", hint: "Alpaca features treated as enabled for these requests.", emptyLabel: "No previews", addLabel: "Add feature", options: featureOptions, values: filter.enabledFeatures, onChange: (v) => update({ enabledFeatures: v }), disabled: readOnly, noOptionsHint: "No Alpaca features are registered on this instance.", testId: "alpaca-trace-filter-features" })] }) })] })] }), _jsxs(DialogFooter, { className: "items-center gap-3 border-t border-border-default bg-neutral-grey-5 px-6 py-3 sm:justify-between", children: [_jsx("div", { className: "min-w-0 flex-1 text-2xs text-neutral-grey-50", children: error ? (_jsx(InlineError, { message: error })) : readOnly ? (_jsx("span", { "data-testid": "alpaca-trace-filter-status", children: "Read-only: this source no longer records, so its filter cannot be changed." })) : dirty ? (_jsx("span", { "data-testid": "alpaca-trace-filter-status", children: "Unsaved changes. Saving applies to stories started from now on; existing stories keep the filter they were recorded with." })) : (_jsx("span", { "data-testid": "alpaca-trace-filter-status", children: "Changes apply to stories started after saving. Existing stories keep the filter they were recorded with." })) }), !readOnly && (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", disabled: !dirty || saving, onClick: () => setFilter(saved), "data-testid": "alpaca-trace-filter-discard", children: "Discard changes" }), _jsx(Button, { size: "sm", disabled: !dirty || saving || readOnly, onClick: () => void save(), "data-testid": "alpaca-trace-filter-save", children: saving ? "Saving…" : "Save capture filter" })] }))] })] }));
99
+ }
100
+ function Section({ title, description, children, }) {
101
+ return (_jsxs("section", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("h3", { className: "text-sm font-medium text-neutral-grey-100", children: title }), description && (_jsx("p", { className: "text-2xs leading-snug text-neutral-grey-50", children: description }))] }), children] }));
102
+ }
103
+ function FieldFrame({ label, hint, count, action, footer, children, }) {
104
+ return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Label, { className: "text-neutral-grey-100", children: label }), count > 0 && (_jsx("span", { className: "text-2xs tabular-nums text-neutral-grey-50", children: count })), action && _jsx("span", { className: "ml-auto", children: action })] }), _jsx("p", { className: "text-2xs leading-snug text-neutral-grey-50", children: hint }), children, footer] }));
105
+ }
106
+ const boxClass = "flex min-h-9 flex-wrap items-center gap-1 rounded-[4px] border border-neutral-grey-10 bg-neutral-grey-5 px-1.5 py-1 transition-colors";
107
+ function Chip({ value, onRemove, disabled, }) {
108
+ return (_jsxs("span", { className: "inline-flex max-w-full items-center gap-1 rounded border border-border-default bg-background px-1.5 py-0.5 font-mono text-2xs text-neutral-grey-100", title: value, "data-testid": "alpaca-trace-filter-chip", children: [_jsx("span", { className: "truncate", children: value }), !disabled && (_jsx("button", { type: "button", "aria-label": `Remove ${value}`, onClick: onRemove, className: "rounded text-neutral-grey-50 hover:text-feedback-red focus-visible:outline focus-visible:outline-neutral-grey-100", children: _jsx(X, { className: "h-3 w-3", strokeWidth: 1.5 }) }))] }));
109
+ }
110
+ function TokenField({ label, hint, emptyLabel, addLabel, values, onChange, disabled, footer, testId, }) {
111
+ const [draft, setDraft] = useState("");
112
+ const addMany = (raw) => {
113
+ const incoming = raw
114
+ .split(/[,\s]+/)
115
+ .map((v) => v.trim())
116
+ .filter(Boolean);
117
+ if (incoming.length === 0)
118
+ return;
119
+ const next = [...values];
120
+ for (const value of incoming)
121
+ if (!next.includes(value))
122
+ next.push(value);
123
+ if (next.length !== values.length)
124
+ onChange(next);
125
+ setDraft("");
126
+ };
127
+ return (_jsx(FieldFrame, { label: label, hint: hint, count: values.length, footer: footer, children: _jsxs("div", { className: cls(boxClass, !disabled &&
128
+ "focus-within:border-neutral-grey-100 focus-within:bg-background", disabled && "opacity-70"), "data-testid": testId, children: [values.map((value) => (_jsx(Chip, { value: value, disabled: disabled, onRemove: () => onChange(values.filter((v) => v !== value)) }, value))), disabled && values.length === 0 && (_jsx("span", { className: "px-1 text-2xs text-neutral-grey-50", children: emptyLabel })), !disabled && (_jsx("input", { value: draft, disabled: disabled, onChange: (e) => setDraft(e.target.value), onKeyDown: (e) => {
129
+ if (e.key === "Enter" || e.key === ",") {
130
+ e.preventDefault();
131
+ addMany(draft);
132
+ }
133
+ else if (e.key === "Backspace" &&
134
+ draft === "" &&
135
+ values.length > 0) {
136
+ onChange(values.slice(0, -1));
137
+ }
138
+ }, onBlur: () => addMany(draft), onPaste: (e) => {
139
+ const text = e.clipboardData.getData("text");
140
+ if (/[,\s]/.test(text.trim())) {
141
+ e.preventDefault();
142
+ addMany(draft + text);
143
+ }
144
+ }, placeholder: values.length === 0
145
+ ? `${emptyLabel} · ${addLabel.toLowerCase()}…`
146
+ : `${addLabel}…`, "aria-label": `${label}: ${addLabel}`, className: "h-6 min-w-[160px] flex-1 bg-transparent px-1 font-mono text-2xs text-neutral-grey-100 outline-none placeholder:font-sans placeholder:text-neutral-grey-50 disabled:cursor-not-allowed", "data-testid": testId ? `${testId}-input` : undefined }))] }) }));
147
+ }
148
+ function PickerField({ label, hint, emptyLabel, addLabel, options, values, onChange, disabled, action, noOptionsHint, testId, }) {
149
+ const [open, setOpen] = useState(false);
150
+ const [search, setSearch] = useState("");
151
+ const filtered = options.filter((o) => !search || o.label.toLowerCase().includes(search.toLowerCase()));
152
+ const grouped = useMemo(() => {
153
+ const map = new Map();
154
+ for (const option of filtered) {
155
+ const key = option.group ?? "";
156
+ const list = map.get(key) ?? [];
157
+ list.push(option);
158
+ map.set(key, list);
159
+ }
160
+ return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
161
+ }, [filtered]);
162
+ const noOptions = options.length === 0;
163
+ return (_jsx(FieldFrame, { label: label, hint: hint, count: values.length, action: action, children: _jsxs("div", { className: cls(boxClass, disabled && "opacity-70"), "data-testid": testId, children: [values.map((value) => (_jsx(Chip, { value: value, disabled: disabled, onRemove: () => onChange(values.filter((v) => v !== value)) }, value))), values.length === 0 && (_jsx("span", { className: "px-1 text-2xs text-neutral-grey-50", children: noOptions && noOptionsHint ? noOptionsHint : emptyLabel })), !disabled && !noOptions && (_jsxs(Popover, { open: open, onOpenChange: (next) => {
164
+ setOpen(next);
165
+ if (!next)
166
+ setSearch("");
167
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "xxs", className: "ml-auto gap-1", "data-testid": testId ? `${testId}-add` : undefined, children: [_jsx(Plus, { className: "h-3 w-3", strokeWidth: 1.5 }), addLabel] }) }), _jsxs(PopoverContent, { align: "end", className: "w-80 p-2", children: [_jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Filter\u2026", className: "mb-2 h-7 text-2xs", autoFocus: true }), _jsxs("div", { className: "max-h-64 overflow-auto", children: [filtered.length === 0 && (_jsx("div", { className: "p-2 text-2xs text-neutral-grey-50", children: "No matches." })), grouped.map(([group, items]) => (_jsxs("div", { children: [group && (_jsx("div", { className: "sticky top-0 bg-popover px-1 pt-1.5 pb-0.5 text-2xs font-medium uppercase tracking-wide text-neutral-grey-50", children: group })), items.map((option) => {
168
+ const checked = values.includes(option.value);
169
+ return (_jsxs("label", { className: cls("flex cursor-pointer items-center gap-2 rounded px-1 py-0.5 text-2xs hover:bg-neutral-grey-5", checked && "text-neutral-grey-100"), children: [_jsx(Checkbox, { checked: checked, onCheckedChange: (v) => onChange(v === true
170
+ ? [...values, option.value]
171
+ : values.filter((x) => x !== option.value)) }), _jsx("span", { className: "truncate font-mono", title: option.label, children: option.label })] }, option.value));
172
+ })] }, group || "_")))] }), _jsxs("div", { className: "mt-2 flex items-center justify-between border-t border-border-default pt-2 text-2xs text-neutral-grey-50", children: [_jsxs("span", { children: [values.length, " selected"] }), values.length > 0 && (_jsx("button", { type: "button", className: "hover:text-neutral-grey-100", onClick: () => onChange([]), children: "Clear selection" }))] })] })] }))] }) }));
173
+ }
174
+ function ClearCacheButton({ caches, onCleared, }) {
175
+ const requestContext = useTraceStore();
176
+ const tracingService = forTracingInstance(requestContext.selectedInstance, requestContext.generation, requestContext.status?.globalRecording?.recordingId);
177
+ const [busy, setBusy] = useState(null);
178
+ const types = Array.from(new Set(caches.map((c) => c.cacheType))).sort();
179
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "inline-flex items-center gap-0.5 text-2xs text-neutral-grey-50 hover:text-neutral-grey-100", title: "Clear caches for everyone on this instance", "data-testid": "alpaca-trace-clear-cache", children: ["Clear cache now ", _jsx(ChevronDown, { className: "h-3 w-3", strokeWidth: 1.5 })] }) }), _jsxs(PopoverContent, { align: "end", className: "w-64 p-1", children: [_jsx("div", { className: "px-2 pt-1 pb-1.5 text-2xs text-neutral-grey-50", children: "Clears the caches for every user, not only this source." }), _jsx("div", { className: "max-h-64 overflow-auto", children: types.map((type) => (_jsx("button", { type: "button", disabled: busy !== null, className: "block w-full rounded px-2 py-1 text-left text-2xs text-neutral-grey-100 hover:bg-neutral-grey-5 disabled:opacity-60", onClick: async () => {
180
+ setBusy(type);
181
+ try {
182
+ const result = await tracingService.clearCaches(type);
183
+ if (getTraceSnapshot().epoch === requestContext.epoch)
184
+ onCleared(result.caches);
185
+ }
186
+ finally {
187
+ setBusy(null);
188
+ }
189
+ }, children: busy === type ? "Clearing…" : `Clear all ${type} caches` }, type))) })] })] }));
190
+ }
@@ -0,0 +1,6 @@
1
+ export declare function CountersTab({ sessionId, storyId, available, }: {
2
+ sessionId: string;
3
+ storyId: string | null;
4
+ available: boolean;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=CountersTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CountersTab.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/CountersTab.tsx"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,OAAO,EACP,SAAS,GACV,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB,+BA6DA"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useTraceStore } from "../traceStore";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { SearchInput, SimpleTable } from "@parhelia/core";
5
+ import { TracingApiError, forTracingInstance } from "../tracingService";
6
+ import { EmptyHint, InlineError } from "./shared";
7
+ export function CountersTab({ sessionId, storyId, available, }) {
8
+ const requestContext = useTraceStore();
9
+ const tracingService = forTracingInstance(requestContext.selectedInstance, requestContext.generation, requestContext.status?.globalRecording?.recordingId);
10
+ const [rows, setRows] = useState(null);
11
+ const [error, setError] = useState(null);
12
+ const [search, setSearch] = useState("");
13
+ useEffect(() => {
14
+ let cancelled = false;
15
+ setRows(null);
16
+ tracingService
17
+ .counters(sessionId, storyId)
18
+ .then((result) => {
19
+ if (!cancelled) {
20
+ setRows(result.rows);
21
+ setError(null);
22
+ }
23
+ })
24
+ .catch((e) => {
25
+ if (cancelled)
26
+ return;
27
+ setError(e instanceof TracingApiError && e.notFound ? "Not in memory anymore." : e instanceof Error ? e.message : String(e));
28
+ });
29
+ return () => {
30
+ cancelled = true;
31
+ };
32
+ }, [requestContext.selectedInstance, sessionId, storyId]);
33
+ const filtered = useMemo(() => {
34
+ const needle = search.trim().toLowerCase();
35
+ return (rows ?? [])
36
+ .filter((r) => !needle || r.name.toLowerCase().includes(needle))
37
+ .sort((a, b) => b.value - a.value || a.name.localeCompare(b.name));
38
+ }, [rows, search]);
39
+ if (!available)
40
+ return _jsx(EmptyHint, { title: "Counters were not persisted for this story" });
41
+ if (error)
42
+ return _jsx("div", { className: "p-3", children: _jsx(InlineError, { message: error }) });
43
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "alpaca-trace-counters", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border-default px-3 py-2", children: [_jsx(SearchInput, { value: search, onChange: setSearch, placeholder: "Filter counters", size: "sm", className: "flex-1" }), _jsx("span", { className: "text-2xs text-neutral-grey-50", children: storyId ? "this story" : "whole source" })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: rows === null ? (_jsx(EmptyHint, { title: "Loading\u2026" })) : filtered.length === 0 ? (_jsx(EmptyHint, { title: "No counters", children: "Cache hit/miss counters appear here once caches are touched by a traced story." })) : (_jsx(SimpleTable, { items: filtered, rowKey: (r) => r.name, columns: [
44
+ { header: "Counter", body: (r) => _jsx("span", { className: "[overflow-wrap:anywhere] font-mono", title: r.name, children: r.name }) },
45
+ { header: "Value", align: "right", className: "w-[100px] whitespace-nowrap font-mono", body: (r) => r.value.toLocaleString() },
46
+ ] })) })] }));
47
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Which Sitecore instance's traces are shown. Lives in the Sources sidebar (sources belong to an instance)
3
+ * and in the source-switcher popover of the stories pane, so it stays reachable when the sidebar is closed.
4
+ * Rendered without a status guard: switching away from an unreachable instance must always be possible.
5
+ * Hidden, together with its "Instance" headings in both places, while there is nothing to switch between (hasInstanceChoice).
6
+ */
7
+ export declare function InstanceSwitcher({ className, showLabel }: {
8
+ className?: string;
9
+ showLabel?: boolean;
10
+ }): import("react").JSX.Element | null;
11
+ //# sourceMappingURL=InstanceSwitcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstanceSwitcher.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/InstanceSwitcher.tsx"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,SAAiB,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,sCAiD7G"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { hasInstanceChoice, traceActions, useTraceStore } from "../traceStore";
3
+ import { useEffect, useState } from "react";
4
+ import { RefreshCw } from "lucide-react";
5
+ import { formatCountdown } from "../format";
6
+ import { cls } from "./shared";
7
+ /**
8
+ * Which Sitecore instance's traces are shown. Lives in the Sources sidebar (sources belong to an instance)
9
+ * and in the source-switcher popover of the stories pane, so it stays reachable when the sidebar is closed.
10
+ * Rendered without a status guard: switching away from an unreachable instance must always be possible.
11
+ * Hidden, together with its "Instance" headings in both places, while there is nothing to switch between (hasInstanceChoice).
12
+ */
13
+ export function InstanceSwitcher({ className, showLabel = false }) {
14
+ const state = useTraceStore();
15
+ const [elapsed, setElapsed] = useState(0);
16
+ const [statusElapsed, setStatusElapsed] = useState(0);
17
+ useEffect(() => {
18
+ setStatusElapsed(0);
19
+ const observedAt = performance.now();
20
+ const timer = setInterval(() => setStatusElapsed((performance.now() - observedAt) / 1000), 1000);
21
+ return () => clearInterval(timer);
22
+ }, [state.status]);
23
+ useEffect(() => {
24
+ setElapsed(0);
25
+ const observedAt = performance.now();
26
+ const timer = setInterval(() => setElapsed((performance.now() - observedAt) / 1000), 1000);
27
+ return () => clearInterval(timer);
28
+ }, [state.instances]);
29
+ if (!hasInstanceChoice(state))
30
+ return null;
31
+ const entries = state.instances.length ? state.instances : [{ name: "local", label: "Local", reachable: true }];
32
+ return (_jsxs("div", { className: cls("flex min-w-0 shrink-0 items-center gap-2", className), "data-testid": "alpaca-tracing-instance-switcher", children: [showLabel && _jsx("label", { className: "text-xs text-neutral-grey-50", htmlFor: "trace-instance", children: "Instance" }), _jsxs("select", { id: "trace-instance", "aria-label": "Instance", className: "h-7 min-w-0 flex-1 rounded border border-border-default bg-background px-2 text-xs text-neutral-grey-100", value: state.selectedInstance, onChange: event => void traceActions.selectInstance(event.target.value), children: [!entries.some(entry => entry.name === state.selectedInstance) && _jsx("option", { value: state.selectedInstance, children: state.selectedInstance }), entries.map(entry => {
33
+ const selected = entry.name === state.selectedInstance && state.status != null;
34
+ const recording = selected ? state.status?.globalRecording : "globalRecording" in entry ? entry.globalRecording : undefined;
35
+ return _jsxs("option", { value: entry.name, children: [entry.reachable ? "●" : "○", " ", entry.label || entry.name, "kind" in entry && entry.kind ? ` · ${entry.kind}` : "", "version" in entry && entry.version ? ` · ${entry.version}` : "", recording?.enabled ? ` · recording${recording.remainingSeconds != null ? " " + formatCountdown(recording.remainingSeconds - (selected ? statusElapsed : elapsed)) : ""}` : ""] }, entry.name);
36
+ })] }), _jsx("button", { type: "button", className: "rounded p-1 text-neutral-grey-50 hover:bg-neutral-grey-10 hover:text-neutral-grey-100", title: "Refresh the instance list", "aria-label": "Refresh the instance list", onClick: () => void traceActions.refreshInstances(true), "data-testid": "alpaca-tracing-instance-refresh", children: _jsx(RefreshCw, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) })] }));
37
+ }
@@ -0,0 +1,6 @@
1
+ export declare function LogMessage({ message, truncated, highlight, }: {
2
+ message: string;
3
+ truncated?: boolean;
4
+ highlight?: string | null;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=LogMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogMessage.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/LogMessage.tsx"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,SAAS,EACT,SAAS,GACV,EAAE;IACD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,+BA8CA"}
@@ -0,0 +1,50 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ObjectInspector } from "react-inspector";
4
+ import { CopyButton } from "@parhelia/core";
5
+ import { ChevronDown, ChevronRight, ExternalLink } from "lucide-react";
6
+ import { isLongMessage, parseMessageTags } from "../messageTags";
7
+ export function LogMessage({ message, truncated, highlight, }) {
8
+ const [expanded, setExpanded] = useState(false);
9
+ const segments = parseMessageTags(message);
10
+ const long = isLongMessage(message);
11
+ const collapsedText = long && !expanded;
12
+ return (_jsxs("div", { className: "min-w-0 break-words whitespace-pre-wrap font-mono text-2xs leading-relaxed text-neutral-grey-100", children: [_jsx("span", { className: collapsedText ? "line-clamp-3" : undefined, children: segments.map((segment, index) => {
13
+ if (segment.type === "link") {
14
+ return (_jsxs("a", { href: segment.value, target: "_blank", rel: "noreferrer noopener", className: "inline-flex items-center gap-0.5 text-highlight-100 underline", children: [segment.value, _jsx(ExternalLink, { className: "h-3 w-3", strokeWidth: 1.5 })] }, index));
15
+ }
16
+ if (segment.type === "json") {
17
+ return _jsx(JsonBlock, { raw: segment.value, parsed: segment.parsed }, index);
18
+ }
19
+ return _jsx(Highlighted, { text: segment.value, highlight: highlight }, index);
20
+ }) }), (long || truncated) && (_jsxs("div", { className: "mt-0.5 flex items-center gap-2", children: [long && (_jsx("button", { type: "button", className: "text-2xs text-highlight-100 hover:underline", onClick: () => setExpanded((v) => !v), children: expanded ? "less" : "more" })), truncated && _jsx("span", { className: "text-2xs text-neutral-grey-50", children: "message truncated at 16 KB" })] }))] }));
21
+ }
22
+ function Highlighted({ text, highlight }) {
23
+ if (!highlight)
24
+ return _jsx(_Fragment, { children: text });
25
+ const needle = highlight.toLowerCase();
26
+ const parts = [];
27
+ let index = 0;
28
+ const lower = text.toLowerCase();
29
+ while (index < text.length) {
30
+ const found = lower.indexOf(needle, index);
31
+ if (found < 0) {
32
+ parts.push(text.slice(index));
33
+ break;
34
+ }
35
+ if (found > index)
36
+ parts.push(text.slice(index, found));
37
+ parts.push(_jsx("mark", { className: "rounded bg-feedback-orange-light px-0.5 text-neutral-grey-100", children: text.slice(found, found + needle.length) }, found));
38
+ index = found + needle.length;
39
+ }
40
+ return _jsx(_Fragment, { children: parts });
41
+ }
42
+ function JsonBlock({ raw, parsed }) {
43
+ const [open, setOpen] = useState(false);
44
+ const summary = parsed && typeof parsed === "object"
45
+ ? Array.isArray(parsed)
46
+ ? `array[${parsed.length}]`
47
+ : `{${Object.keys(parsed).length} keys}`
48
+ : "json";
49
+ return (_jsxs("span", { className: "my-0.5 inline-flex max-w-full flex-col rounded border border-border-default bg-neutral-grey-5 align-top", children: [_jsxs("span", { className: "flex items-center gap-1 px-1.5 py-0.5", children: [_jsxs("button", { type: "button", className: "inline-flex items-center gap-1 text-2xs text-neutral-grey-100", onClick: () => setOpen((v) => !v), children: [open ? _jsx(ChevronDown, { className: "h-3 w-3", strokeWidth: 1.5 }) : _jsx(ChevronRight, { className: "h-3 w-3", strokeWidth: 1.5 }), summary] }), _jsx(CopyButton, { textToCopy: raw, iconOnly: true, className: "ml-1" })] }), open && (_jsx("span", { className: "block max-h-64 overflow-auto border-t border-border-default px-1.5 py-1 text-left font-sans", children: parsed !== undefined ? (_jsx(ObjectInspector, { data: parsed, expandLevel: 1 })) : (_jsx("pre", { className: "whitespace-pre-wrap text-2xs", children: raw })) }))] }));
50
+ }
@@ -0,0 +1,12 @@
1
+ import { type StoryRow } from "../types";
2
+ /**
3
+ * Log entries of one story, or — with `story === null` — of the whole source merged by sequence
4
+ * (then a Story column links each row to its story).
5
+ */
6
+ export declare function LogsTab({ sessionId, story, jumpMs, onJumpConsumed, }: {
7
+ sessionId: string;
8
+ story: StoryRow | null;
9
+ jumpMs: number | null;
10
+ onJumpConsumed: () => void;
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=LogsTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LogsTab.d.ts","sourceRoot":"","sources":["../../../src/tracing/components/LogsTab.tsx"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,QAAQ,EACd,MAAM,UAAU,CAAC;AAalB;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,KAAK,EACL,MAAM,EACN,cAAc,GACf,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B,+BA+NA"}