@sentry/junior-dashboard 0.264.0 → 0.265.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.
- package/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +145 -136
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +12 -2
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +15 -24
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +86 -77
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +13 -3
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +147 -62
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +55 -88
- package/src/client/conversations/ConversationPage.tsx +220 -101
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +86 -13
- package/src/client/conversations/ShareConversationButton.tsx +25 -13
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +0 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/components/ui/sheet.LICENSE +0 -21
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { Globe2, Lock, Trash2 } from "lucide-react";
|
|
2
|
+
import { useState, type ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { DetailsPanel } from "../../components/DetailsPanel";
|
|
5
|
+
import { PageContentSkeleton } from "../../components/PageContentSkeleton";
|
|
6
|
+
import {
|
|
7
|
+
AlertDialog,
|
|
8
|
+
AlertDialogAction,
|
|
9
|
+
AlertDialogCancel,
|
|
10
|
+
AlertDialogContent,
|
|
11
|
+
AlertDialogDescription,
|
|
12
|
+
AlertDialogFooter,
|
|
13
|
+
AlertDialogTitle,
|
|
14
|
+
} from "../../components/ui/alert-dialog";
|
|
15
|
+
import { Button } from "../../components/ui/button";
|
|
16
|
+
import {
|
|
17
|
+
Tooltip,
|
|
18
|
+
TooltipContent,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
} from "../../components/ui/tooltip";
|
|
21
|
+
import { TranscriptText } from "../../conversations/TranscriptText";
|
|
22
|
+
import type {
|
|
23
|
+
PluginUserPageRecord,
|
|
24
|
+
PluginUserPageRecordAction,
|
|
25
|
+
} from "../user/pluginUserPageData";
|
|
26
|
+
|
|
27
|
+
// The memory plugin API does not resolve mentions yet, so memory text shows
|
|
28
|
+
// Slack ids instead of names.
|
|
29
|
+
const NO_MEMORY_MENTIONS: readonly [] = [];
|
|
30
|
+
|
|
31
|
+
// The heading, origin, and scope rows already show these values.
|
|
32
|
+
const SUMMARIZED_METADATA = new Set([
|
|
33
|
+
"Learned",
|
|
34
|
+
"Memory ID",
|
|
35
|
+
"Source",
|
|
36
|
+
"Type",
|
|
37
|
+
"Visibility",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/** Show one memory beside the library, or in a bottom sheet on small screens. */
|
|
41
|
+
export function MemoryDetails(props: {
|
|
42
|
+
forgetPending: boolean;
|
|
43
|
+
onClose(): void;
|
|
44
|
+
onForget(action: PluginUserPageRecordAction): void;
|
|
45
|
+
record: PluginUserPageRecord | undefined;
|
|
46
|
+
}) {
|
|
47
|
+
const { record } = props;
|
|
48
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
49
|
+
const heading = record ? memoryHeading(record) : "Memory details";
|
|
50
|
+
const isPublic = record && metadataValue(record, "Visibility") === "Public";
|
|
51
|
+
const VisibilityIcon = isPublic ? Globe2 : Lock;
|
|
52
|
+
const forgetAction = record?.actions?.find(
|
|
53
|
+
(recordAction) => recordAction.tone === "danger",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<DetailsPanel
|
|
58
|
+
label="Memory details"
|
|
59
|
+
onClose={props.onClose}
|
|
60
|
+
sheetTitle={heading}
|
|
61
|
+
title={
|
|
62
|
+
<h2 className="m-0 flex items-center gap-2 font-display text-base font-medium text-dashboard-text">
|
|
63
|
+
{record ? (
|
|
64
|
+
<VisibilityIcon
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
className="shrink-0 text-dashboard-text-muted"
|
|
67
|
+
size={14}
|
|
68
|
+
/>
|
|
69
|
+
) : null}
|
|
70
|
+
<span className="truncate">{heading}</span>
|
|
71
|
+
</h2>
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
<div className="grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto]">
|
|
75
|
+
<div
|
|
76
|
+
aria-label="Memory content"
|
|
77
|
+
role="region"
|
|
78
|
+
tabIndex={0}
|
|
79
|
+
className="grid min-h-0 min-w-0 content-start gap-4 overflow-y-auto overscroll-contain p-4 outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus [overflow-wrap:anywhere]"
|
|
80
|
+
>
|
|
81
|
+
{record ? (
|
|
82
|
+
<>
|
|
83
|
+
<div className="grid gap-3 text-sm leading-relaxed">
|
|
84
|
+
<TranscriptText
|
|
85
|
+
mentions={NO_MEMORY_MENTIONS}
|
|
86
|
+
text={record.title}
|
|
87
|
+
/>
|
|
88
|
+
{record.description ? (
|
|
89
|
+
<TranscriptText
|
|
90
|
+
mentions={NO_MEMORY_MENTIONS}
|
|
91
|
+
text={record.description}
|
|
92
|
+
/>
|
|
93
|
+
) : null}
|
|
94
|
+
</div>
|
|
95
|
+
<MemoryFacts record={record} />
|
|
96
|
+
</>
|
|
97
|
+
) : (
|
|
98
|
+
<PageContentSkeleton label="Loading memory" variant="panel" />
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
{record && forgetAction ? (
|
|
102
|
+
// Keep the destructive action away from the title and close control.
|
|
103
|
+
<footer
|
|
104
|
+
aria-label="Memory actions"
|
|
105
|
+
className="flex items-center gap-2 border-t border-dashboard-border bg-dashboard-bg-elevated p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
|
|
106
|
+
>
|
|
107
|
+
<Tooltip>
|
|
108
|
+
<TooltipTrigger
|
|
109
|
+
render={
|
|
110
|
+
<Button
|
|
111
|
+
aria-label="Forget this memory"
|
|
112
|
+
className="min-h-11 min-w-11"
|
|
113
|
+
disabled={props.forgetPending}
|
|
114
|
+
onClick={() => {
|
|
115
|
+
if (forgetAction.confirmation) setConfirmOpen(true);
|
|
116
|
+
else props.onForget(forgetAction);
|
|
117
|
+
}}
|
|
118
|
+
size="icon"
|
|
119
|
+
variant="danger"
|
|
120
|
+
>
|
|
121
|
+
<Trash2 aria-hidden="true" size={16} />
|
|
122
|
+
</Button>
|
|
123
|
+
}
|
|
124
|
+
/>
|
|
125
|
+
<TooltipContent size="compact">Forget this memory</TooltipContent>
|
|
126
|
+
</Tooltip>
|
|
127
|
+
</footer>
|
|
128
|
+
) : null}
|
|
129
|
+
</div>
|
|
130
|
+
{forgetAction?.confirmation && record ? (
|
|
131
|
+
<AlertDialog onOpenChange={setConfirmOpen} open={confirmOpen}>
|
|
132
|
+
<AlertDialogContent>
|
|
133
|
+
<AlertDialogTitle>{forgetAction.confirmation}</AlertDialogTitle>
|
|
134
|
+
<AlertDialogDescription>{record.title}</AlertDialogDescription>
|
|
135
|
+
<AlertDialogFooter>
|
|
136
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
137
|
+
<AlertDialogAction
|
|
138
|
+
onClick={() => {
|
|
139
|
+
setConfirmOpen(false);
|
|
140
|
+
props.onForget(forgetAction);
|
|
141
|
+
}}
|
|
142
|
+
variant="danger"
|
|
143
|
+
>
|
|
144
|
+
{forgetAction.label}
|
|
145
|
+
</AlertDialogAction>
|
|
146
|
+
</AlertDialogFooter>
|
|
147
|
+
</AlertDialogContent>
|
|
148
|
+
</AlertDialog>
|
|
149
|
+
) : null}
|
|
150
|
+
</DetailsPanel>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function MemoryFacts(props: { record: PluginUserPageRecord }) {
|
|
155
|
+
const { record } = props;
|
|
156
|
+
const learned = metadataValue(record, "Learned");
|
|
157
|
+
const source = metadataValue(record, "Source");
|
|
158
|
+
const visibility = metadataValue(record, "Visibility");
|
|
159
|
+
const isPublic = visibility === "Public";
|
|
160
|
+
const origin =
|
|
161
|
+
learned === "Automatic"
|
|
162
|
+
? `From a ${source ?? "past"} conversation`
|
|
163
|
+
: learned === "Explicit"
|
|
164
|
+
? isPublic
|
|
165
|
+
? "Someone asked Junior"
|
|
166
|
+
: "You asked Junior"
|
|
167
|
+
: learned
|
|
168
|
+
? "Before origin tracking"
|
|
169
|
+
: undefined;
|
|
170
|
+
const remembered = metadataValue(record, "Remembered");
|
|
171
|
+
// Most memories are saved when Junior sees them. Show one date in that case.
|
|
172
|
+
const dates = (record.metadata ?? []).filter(
|
|
173
|
+
(item) =>
|
|
174
|
+
!SUMMARIZED_METADATA.has(item.label) &&
|
|
175
|
+
!(item.label === "Observed" && item.value === remembered),
|
|
176
|
+
);
|
|
177
|
+
return (
|
|
178
|
+
<dl className="m-0 grid min-w-0 gap-2 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3 text-xs leading-snug">
|
|
179
|
+
{origin ? <MemoryFact label="Learned">{origin}</MemoryFact> : null}
|
|
180
|
+
{visibility ? (
|
|
181
|
+
<MemoryFact label="Used in">
|
|
182
|
+
{isPublic ? "Future workspace channels" : "Your future conversations"}
|
|
183
|
+
</MemoryFact>
|
|
184
|
+
) : null}
|
|
185
|
+
{dates.map((item) => (
|
|
186
|
+
<MemoryFact key={item.label} label={item.label}>
|
|
187
|
+
{item.value}
|
|
188
|
+
</MemoryFact>
|
|
189
|
+
))}
|
|
190
|
+
</dl>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function MemoryFact(props: { children: ReactNode; label: string }) {
|
|
195
|
+
return (
|
|
196
|
+
<div className="flex min-w-0 items-baseline justify-between gap-4">
|
|
197
|
+
<dt className="shrink-0 text-dashboard-text-muted">{props.label}</dt>
|
|
198
|
+
<dd className="m-0 min-w-0 break-words text-right tabular-nums text-dashboard-text">
|
|
199
|
+
{props.children}
|
|
200
|
+
</dd>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Name a memory by visibility and type, such as "Private preference". */
|
|
206
|
+
function memoryHeading(record: PluginUserPageRecord): string {
|
|
207
|
+
const kind = metadataValue(record, "Type");
|
|
208
|
+
const visibility = metadataValue(record, "Visibility");
|
|
209
|
+
if (!kind) return visibility ? `${visibility} memory` : "Memory";
|
|
210
|
+
return visibility ? `${visibility} ${kind.toLowerCase()}` : kind;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function metadataValue(
|
|
214
|
+
record: PluginUserPageRecord,
|
|
215
|
+
label: string,
|
|
216
|
+
): string | undefined {
|
|
217
|
+
return record.metadata?.find((item) => item.label === label)?.value;
|
|
218
|
+
}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
Sparkles,
|
|
9
9
|
UserRound,
|
|
10
10
|
} from "lucide-react";
|
|
11
|
-
import { useEffect, useState } from "react";
|
|
11
|
+
import { useEffect, useRef, useState } from "react";
|
|
12
12
|
import { Navigate, useLocation, useNavigate, useParams } from "react-router";
|
|
13
13
|
import type { PluginUserPageLink } from "@sentry/junior-plugin-api";
|
|
14
14
|
|
|
@@ -25,8 +25,10 @@ import {
|
|
|
25
25
|
} from "../../components/controls/TimeRangeSelector";
|
|
26
26
|
import { Card } from "../../components/layout/Card";
|
|
27
27
|
import { PageHeader } from "../../components/layout/PageHeader";
|
|
28
|
+
import { PageLayout } from "../../components/layout/PageLayout";
|
|
28
29
|
import {
|
|
29
30
|
type PluginUserPageRecord,
|
|
31
|
+
type PluginUserPageRecordAction,
|
|
30
32
|
usePluginUserPageData,
|
|
31
33
|
} from "../user/pluginUserPageData";
|
|
32
34
|
import { pathWithSearch } from "../../searchParams";
|
|
@@ -36,7 +38,7 @@ import {
|
|
|
36
38
|
useMemoryDashboardData,
|
|
37
39
|
useMemorySearchCounts,
|
|
38
40
|
} from "./memoryDashboard";
|
|
39
|
-
import {
|
|
41
|
+
import { MemoryDetails } from "./MemoryDetails";
|
|
40
42
|
import { MemoryPageLayout } from "./MemoryPageLayout";
|
|
41
43
|
import { MemorySearchHighlight } from "./MemorySearchHighlight";
|
|
42
44
|
import { MemoryTimeline } from "./MemoryTimeline";
|
|
@@ -64,13 +66,16 @@ export function MemoryPage(props: { page: PluginUserPageLink }) {
|
|
|
64
66
|
|
|
65
67
|
return (
|
|
66
68
|
<MemoryPageLayout>
|
|
67
|
-
<PageHeader
|
|
68
|
-
description={props.page.description}
|
|
69
|
-
{...(overview ? { onRangeChange: setRange, range } : {})}
|
|
70
|
-
title={props.page.label}
|
|
71
|
-
/>
|
|
72
69
|
{overview ? (
|
|
73
|
-
|
|
70
|
+
<>
|
|
71
|
+
<PageHeader
|
|
72
|
+
description={props.page.description}
|
|
73
|
+
onRangeChange={setRange}
|
|
74
|
+
range={range}
|
|
75
|
+
title={props.page.label}
|
|
76
|
+
/>
|
|
77
|
+
<MemoryOverview range={range} />
|
|
78
|
+
</>
|
|
74
79
|
) : (
|
|
75
80
|
<MemoryLibrary libraryPath={libraryPath} page={props.page} />
|
|
76
81
|
)}
|
|
@@ -161,11 +166,34 @@ function MemoryLibrary(props: {
|
|
|
161
166
|
const navigate = useNavigate();
|
|
162
167
|
const location = useLocation();
|
|
163
168
|
const { memoryId } = useParams();
|
|
169
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
164
170
|
const memoryQuery = useMemoryRecord(memoryId);
|
|
165
171
|
const selectedRecord =
|
|
166
172
|
memoryQuery.data ?? records.find((record) => record.id === memoryId);
|
|
167
173
|
const memoryPath = (pathname: string) =>
|
|
168
174
|
pathWithSearch(pathname, location.search);
|
|
175
|
+
const closeDetails = () => {
|
|
176
|
+
const trigger = memoryId
|
|
177
|
+
? listRef.current?.querySelector<HTMLButtonElement>(
|
|
178
|
+
`button[data-memory-id="${CSS.escape(memoryId)}"]`,
|
|
179
|
+
)
|
|
180
|
+
: undefined;
|
|
181
|
+
navigate(memoryPath(props.libraryPath));
|
|
182
|
+
(trigger ?? listRef.current)?.focus();
|
|
183
|
+
};
|
|
184
|
+
const forget = (recordAction: PluginUserPageRecordAction) => {
|
|
185
|
+
// The forgotten row leaves the list, so focus the list instead.
|
|
186
|
+
navigate(memoryPath(props.libraryPath));
|
|
187
|
+
listRef.current?.focus();
|
|
188
|
+
runAction(recordAction);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
// Forget leaves the shared mutation in success or error. Clear it when
|
|
193
|
+
// another memory opens, so old action feedback does not stay visible.
|
|
194
|
+
if (memoryId) action.reset();
|
|
195
|
+
// oxlint-disable-next-line react/exhaustive-deps -- reset only on open id change
|
|
196
|
+
}, [memoryId]);
|
|
169
197
|
|
|
170
198
|
useEffect(() => {
|
|
171
199
|
// Drop stale permalinks after a forget/archive or unknown id once the
|
|
@@ -198,116 +226,143 @@ function MemoryLibrary(props: {
|
|
|
198
226
|
const loading = !query.data && !query.error;
|
|
199
227
|
|
|
200
228
|
return (
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
value={searchText}
|
|
229
|
+
<div
|
|
230
|
+
className={cn(
|
|
231
|
+
"grid h-full min-h-0 min-w-0",
|
|
232
|
+
memoryId && "xl:grid-cols-[minmax(0,1fr)_25rem]",
|
|
233
|
+
)}
|
|
234
|
+
>
|
|
235
|
+
<div
|
|
236
|
+
aria-label="Memories list"
|
|
237
|
+
className="min-h-0 overflow-y-auto overscroll-contain outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
238
|
+
ref={listRef}
|
|
239
|
+
role="region"
|
|
240
|
+
tabIndex={0}
|
|
241
|
+
>
|
|
242
|
+
<PageLayout className="gap-6 sm:gap-8">
|
|
243
|
+
<PageHeader
|
|
244
|
+
description={props.page.description}
|
|
245
|
+
title={props.page.label}
|
|
219
246
|
/>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
/>
|
|
247
|
+
<section
|
|
248
|
+
className="grid gap-4"
|
|
249
|
+
aria-labelledby="memory-library-title"
|
|
250
|
+
>
|
|
251
|
+
<div className="flex flex-wrap items-end justify-between gap-4">
|
|
252
|
+
<div>
|
|
253
|
+
<h2
|
|
254
|
+
className="m-0 font-display text-xl font-medium tracking-[-0.02em] text-dashboard-text"
|
|
255
|
+
id="memory-library-title"
|
|
256
|
+
>
|
|
257
|
+
{searchQuery ? "Search results" : "What Junior remembers"}
|
|
258
|
+
</h2>
|
|
259
|
+
</div>
|
|
260
|
+
{content?.searchPlaceholder ? (
|
|
261
|
+
<SearchInput
|
|
262
|
+
className="w-full max-w-md sm:w-auto"
|
|
263
|
+
label={content.searchPlaceholder}
|
|
264
|
+
onChange={setSearchText}
|
|
265
|
+
placeholder={content.searchPlaceholder}
|
|
266
|
+
size="default"
|
|
267
|
+
value={searchText}
|
|
268
|
+
/>
|
|
269
|
+
) : null}
|
|
270
|
+
</div>
|
|
245
271
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
272
|
+
<FilterTabList
|
|
273
|
+
ariaLabel="Memory collections"
|
|
274
|
+
items={[
|
|
275
|
+
{
|
|
276
|
+
count: counts?.active,
|
|
277
|
+
label: "All",
|
|
278
|
+
value: "",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
count: counts?.personal,
|
|
282
|
+
label: "Private",
|
|
283
|
+
value: "private",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
count: counts?.public,
|
|
287
|
+
label: "Public",
|
|
288
|
+
value: "public",
|
|
289
|
+
},
|
|
290
|
+
]}
|
|
291
|
+
onChange={setFilter}
|
|
292
|
+
value={filter}
|
|
260
293
|
/>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
294
|
+
|
|
295
|
+
{loading ? (
|
|
296
|
+
<PageContentSkeleton label="Loading memories" variant="panel" />
|
|
297
|
+
) : query.error ? (
|
|
298
|
+
<Card className="flex items-center gap-3 border-rose-300/20 p-5 text-sm text-rose-200">
|
|
299
|
+
<CircleAlert aria-hidden="true" size={18} />
|
|
300
|
+
{query.error.message}
|
|
301
|
+
</Card>
|
|
302
|
+
) : records.length === 0 ? (
|
|
303
|
+
<Card className="grid min-h-56 place-items-center p-8 text-center">
|
|
304
|
+
<div>
|
|
305
|
+
<Database
|
|
306
|
+
aria-hidden="true"
|
|
307
|
+
className="mx-auto text-dashboard-text-muted"
|
|
308
|
+
size={26}
|
|
309
|
+
/>
|
|
310
|
+
<p className="mt-4 mb-0 text-sm text-dashboard-text-muted">
|
|
311
|
+
{content?.emptyText ?? "No memories yet."}
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
314
|
+
</Card>
|
|
315
|
+
) : (
|
|
316
|
+
<div className="grid gap-3">
|
|
317
|
+
<Card padding="none">
|
|
318
|
+
<MemoryListHeader />
|
|
319
|
+
{records.map((record, index) => (
|
|
320
|
+
<MemoryRow
|
|
321
|
+
first={index === 0}
|
|
322
|
+
key={record.id}
|
|
323
|
+
onSelect={() =>
|
|
324
|
+
navigate(
|
|
325
|
+
memoryPath(
|
|
326
|
+
memoryId === record.id
|
|
327
|
+
? props.libraryPath
|
|
328
|
+
: `/memories/${encodeURIComponent(record.id)}`,
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
record={record}
|
|
333
|
+
searchQuery={searchQuery}
|
|
334
|
+
selected={record.id === memoryId}
|
|
335
|
+
/>
|
|
336
|
+
))}
|
|
337
|
+
</Card>
|
|
338
|
+
<LoadMorePagination
|
|
339
|
+
className="mt-2"
|
|
340
|
+
hasMore={
|
|
341
|
+
!query.isPlaceholderData && Boolean(query.hasNextPage)
|
|
342
|
+
}
|
|
343
|
+
loading={query.isFetchingNextPage}
|
|
344
|
+
onLoadMore={() => void query.fetchNextPage()}
|
|
345
|
+
/>
|
|
346
|
+
{action.error ? (
|
|
347
|
+
<InlineError className="text-center">
|
|
348
|
+
Could not complete this action. Try again.
|
|
349
|
+
</InlineError>
|
|
350
|
+
) : null}
|
|
351
|
+
</div>
|
|
352
|
+
)}
|
|
353
|
+
</section>
|
|
354
|
+
</PageLayout>
|
|
355
|
+
</div>
|
|
356
|
+
{memoryId ? (
|
|
357
|
+
<MemoryDetails
|
|
358
|
+
forgetPending={action.isPending}
|
|
359
|
+
key={memoryId}
|
|
360
|
+
onClose={closeDetails}
|
|
361
|
+
onForget={forget}
|
|
307
362
|
record={selectedRecord}
|
|
308
363
|
/>
|
|
309
364
|
) : null}
|
|
310
|
-
</
|
|
365
|
+
</div>
|
|
311
366
|
);
|
|
312
367
|
}
|
|
313
368
|
|
|
@@ -500,6 +555,7 @@ function MemoryRow(props: {
|
|
|
500
555
|
<button
|
|
501
556
|
aria-expanded={props.selected}
|
|
502
557
|
aria-label={`View memory details: ${props.record.title}`}
|
|
558
|
+
data-memory-id={props.record.id}
|
|
503
559
|
className={cn(
|
|
504
560
|
"grid min-w-0 flex-1 cursor-pointer items-center gap-3 border-0 bg-transparent px-4 py-3 text-left",
|
|
505
561
|
MEMORY_GRID,
|
|
@@ -10,7 +10,8 @@ const MEMORY_BASE_PATH = "/memories";
|
|
|
10
10
|
const MEMORY_LIBRARY_PATH = `${MEMORY_BASE_PATH}/library`;
|
|
11
11
|
const RESERVED_MEMORY_SEGMENTS = new Set(["library"]);
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
/** Identify library and memory routes that keep the list beside its details. */
|
|
14
|
+
export function isMemoryLibraryPath(pathname: string): boolean {
|
|
14
15
|
if (
|
|
15
16
|
pathname === MEMORY_LIBRARY_PATH ||
|
|
16
17
|
pathname.startsWith(`${MEMORY_LIBRARY_PATH}/`)
|
|
@@ -21,21 +22,34 @@ function isMemoriesLibraryPath(pathname: string): boolean {
|
|
|
21
22
|
return Boolean(match?.[1] && !RESERVED_MEMORY_SEGMENTS.has(match[1]));
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Place the shared secondary navigation above one Memories page. Library
|
|
27
|
+
* children fill the remaining height and own their scroll frames.
|
|
28
|
+
*/
|
|
25
29
|
export function MemoryPageLayout(props: { children: ReactNode }) {
|
|
30
|
+
const library = isMemoryLibraryPath(useLocation().pathname);
|
|
26
31
|
return (
|
|
27
32
|
<SecondaryNavigation
|
|
28
33
|
ariaLabel="Memory navigation"
|
|
34
|
+
className={
|
|
35
|
+
library
|
|
36
|
+
? "grid h-full min-h-0 grid-rows-[auto_minmax(0,1fr)] md:grid-rows-[minmax(0,1fr)]"
|
|
37
|
+
: undefined
|
|
38
|
+
}
|
|
29
39
|
items={[
|
|
30
40
|
{ end: true, label: "Overview", to: MEMORY_BASE_PATH },
|
|
31
41
|
{
|
|
32
|
-
isActive:
|
|
42
|
+
isActive: isMemoryLibraryPath,
|
|
33
43
|
label: "Memories",
|
|
34
44
|
to: MEMORY_LIBRARY_PATH,
|
|
35
45
|
},
|
|
36
46
|
]}
|
|
37
47
|
>
|
|
38
|
-
|
|
48
|
+
{library ? (
|
|
49
|
+
<div className="min-h-0">{props.children}</div>
|
|
50
|
+
) : (
|
|
51
|
+
<PageLayout className="gap-6 sm:gap-8">{props.children}</PageLayout>
|
|
52
|
+
)}
|
|
39
53
|
</SecondaryNavigation>
|
|
40
54
|
);
|
|
41
55
|
}
|
|
@@ -46,10 +60,9 @@ export function MemoryRouteLoading(props: {
|
|
|
46
60
|
label: string;
|
|
47
61
|
title?: string;
|
|
48
62
|
}) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
<MemoryPageLayout>
|
|
63
|
+
const library = isMemoryLibraryPath(useLocation().pathname);
|
|
64
|
+
const content = (
|
|
65
|
+
<>
|
|
53
66
|
<PageHeader
|
|
54
67
|
description={
|
|
55
68
|
props.description ??
|
|
@@ -59,8 +72,19 @@ export function MemoryRouteLoading(props: {
|
|
|
59
72
|
/>
|
|
60
73
|
<PageContentSkeleton
|
|
61
74
|
label={props.label}
|
|
62
|
-
variant={
|
|
75
|
+
variant={library ? "library" : "overview"}
|
|
63
76
|
/>
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
return (
|
|
80
|
+
<MemoryPageLayout>
|
|
81
|
+
{library ? (
|
|
82
|
+
<div className="h-full overflow-y-auto">
|
|
83
|
+
<PageLayout className="gap-6 sm:gap-8">{content}</PageLayout>
|
|
84
|
+
</div>
|
|
85
|
+
) : (
|
|
86
|
+
content
|
|
87
|
+
)}
|
|
64
88
|
</MemoryPageLayout>
|
|
65
89
|
);
|
|
66
90
|
}
|