@sentry/junior-dashboard 0.270.0 → 0.272.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 (48) hide show
  1. package/dist/app.js +44 -3
  2. package/dist/client.js +77 -77
  3. package/dist/index.js +40 -3
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +4 -4
  6. package/src/app.ts +2 -0
  7. package/src/client/App.tsx +89 -61
  8. package/src/client/components/ProfileMenu.tsx +0 -24
  9. package/src/client/components/UserInputForm.tsx +38 -7
  10. package/src/client/components/layout/PageHeader.tsx +25 -10
  11. package/src/client/conversations/ConversationPage.tsx +55 -8
  12. package/src/client/conversations/ConversationTranscript.tsx +74 -5
  13. package/src/client/conversations/TranscriptRows.tsx +23 -0
  14. package/src/client/conversations/TranscriptView.tsx +22 -0
  15. package/src/client/conversations/conversationRoutes.ts +30 -3
  16. package/src/client/conversations/queries.ts +30 -5
  17. package/src/client/conversations/transcriptBottomPinning.ts +84 -13
  18. package/src/client/conversations/transcriptReadPosition.ts +267 -0
  19. package/src/client/conversations/transcriptRenderModel.ts +16 -2
  20. package/src/client/pages/automations/AutomationActions.tsx +2 -3
  21. package/src/client/pages/automations/AutomationEditor.tsx +18 -30
  22. package/src/client/pages/automations/AutomationOverview.tsx +2 -2
  23. package/src/client/pages/automations/AutomationVersionsTab.tsx +1 -1
  24. package/src/client/pages/settings/ConnectionsPage.tsx +198 -0
  25. package/src/client/pages/settings/McpSettingsPage.tsx +77 -0
  26. package/src/client/pages/settings/PersonalTokensPage.tsx +198 -0
  27. package/src/client/pages/{SettingsPage.tsx → settings/SettingsPage.tsx} +115 -61
  28. package/src/client/pages/settings/SettingsPageLayout.tsx +45 -0
  29. package/src/client/primaryNav.ts +1 -1
  30. package/src/client/review-desk/ReviewDeskPage.tsx +185 -242
  31. package/src/client/review-desk/ReviewDeskSidebar.tsx +259 -0
  32. package/src/client/review-desk/ReviewItemActions.tsx +1 -10
  33. package/src/client/review-desk/ReviewItemContext.tsx +108 -0
  34. package/src/client/review-desk/ReviewItemDetails.tsx +153 -0
  35. package/src/client/review-desk/ReviewItemView.tsx +159 -0
  36. package/src/client/review-desk/queries.ts +3 -3
  37. package/src/client/review-desk/reviewItemFormat.ts +52 -0
  38. package/src/mock-reporting/read-positions.ts +39 -0
  39. package/src/mock-reporting/routes.ts +26 -1
  40. package/src/tailwind.css +1 -1
  41. package/src/client/components/AGENTS.md +0 -65
  42. package/src/client/components/charts/AGENTS.md +0 -15
  43. package/src/client/conversations/AGENTS.md +0 -38
  44. package/src/client/pages/ConnectionsPage.tsx +0 -207
  45. package/src/client/pages/PersonalTokensPage.tsx +0 -205
  46. package/src/client/pages/automations/AGENTS.md +0 -34
  47. package/src/client/pages/system/AGENTS.md +0 -12
  48. package/src/client/review-desk/ReviewItemDetail.tsx +0 -123
@@ -1,205 +0,0 @@
1
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
- import { KeyRound, Trash2 } from "lucide-react";
3
- import { useRef, useState } from "react";
4
- import {
5
- createdPersonalTokenSchema,
6
- personalTokenListSchema,
7
- type PersonalTokenMetadata,
8
- } from "@sentry/junior/api/schema";
9
- import { deleteDashboardResource, fetchDashboardJson, post } from "../http";
10
- import { dashboardContainerClass } from "../styles";
11
- import { getDashboardAgentName } from "../agentName";
12
- import { useDocumentTitle } from "../documentTitle";
13
- import { Button } from "../components/ui/button";
14
- import { Input } from "../components/ui/input";
15
- import { InlineError } from "../components/InlineError";
16
- import { Card } from "../components/layout/Card";
17
-
18
- const personalTokensQueryKey = ["dashboard", "personal-tokens"] as const;
19
-
20
- /** Create and revoke personal API tokens for local clients. */
21
- export function PersonalTokensPage() {
22
- useDocumentTitle("API tokens");
23
- const queryClient = useQueryClient();
24
- const createTokenStarted = useRef(false);
25
- const [name, setName] = useState("Local agent");
26
- const cancelTokenListRefetch = () =>
27
- queryClient.cancelQueries({
28
- exact: true,
29
- queryKey: personalTokensQueryKey,
30
- });
31
- const tokensQuery = useQuery({
32
- queryKey: personalTokensQueryKey,
33
- queryFn: ({ signal }) =>
34
- fetchDashboardJson(
35
- personalTokenListSchema,
36
- "/api/personal-tokens",
37
- signal,
38
- ),
39
- retry: false,
40
- });
41
- const createTokenMutation = useMutation({
42
- gcTime: 0,
43
- mutationFn: (tokenName: string) =>
44
- post(createdPersonalTokenSchema, "/api/personal-tokens", {
45
- name: tokenName,
46
- }),
47
- onMutate: () => cancelTokenListRefetch(),
48
- onSuccess: async (created) => {
49
- const { token: _token, ...metadata } = created;
50
- await cancelTokenListRefetch();
51
- queryClient.setQueryData<{ tokens: PersonalTokenMetadata[] }>(
52
- personalTokensQueryKey,
53
- (current) => ({
54
- tokens: [
55
- metadata,
56
- ...(current?.tokens ?? []).filter(
57
- (token) => token.id !== metadata.id,
58
- ),
59
- ],
60
- }),
61
- );
62
- },
63
- onSettled: () => {
64
- createTokenStarted.current = false;
65
- },
66
- });
67
- const revokeTokenMutation = useMutation({
68
- mutationFn: (token: PersonalTokenMetadata) =>
69
- deleteDashboardResource(
70
- `/api/personal-tokens/${encodeURIComponent(token.id)}`,
71
- ),
72
- onMutate: () => cancelTokenListRefetch(),
73
- onSuccess: async (_result, token) => {
74
- await cancelTokenListRefetch();
75
- queryClient.setQueryData<{ tokens: PersonalTokenMetadata[] }>(
76
- personalTokensQueryKey,
77
- (current) => ({
78
- tokens: (current?.tokens ?? []).filter(
79
- (item) => item.id !== token.id,
80
- ),
81
- }),
82
- );
83
- if (createTokenMutation.data?.id === token.id) {
84
- createTokenMutation.reset();
85
- }
86
- },
87
- });
88
- const createdToken = createTokenMutation.data;
89
- const tokens = tokensQuery.data?.tokens ?? [];
90
- const loading = tokensQuery.isPending;
91
- const busy = createTokenMutation.isPending || revokeTokenMutation.isPending;
92
- const displayedError =
93
- (createTokenMutation.isError
94
- ? "Could not create the API token. Try again."
95
- : revokeTokenMutation.isError
96
- ? "Could not revoke the API token. Try again."
97
- : undefined) ??
98
- (!tokensQuery.data && tokensQuery.isError
99
- ? "Could not load API tokens. Try again."
100
- : undefined);
101
-
102
- return (
103
- <div className={`${dashboardContainerClass} px-4 py-8 md:px-8`}>
104
- <section className="mx-auto w-full max-w-3xl">
105
- <h1 className="m-0 text-2xl font-bold">Personal API Tokens</h1>
106
- <p className="mt-2 mb-0 max-w-2xl text-sm text-dashboard-text-muted">
107
- Use a token to read {getDashboardAgentName()} APIs from a local agent
108
- or script. Tokens expire after 90 days.
109
- </p>
110
-
111
- <Card className="mb-0 mt-6" padding="md" variant="raised">
112
- {createdToken ? (
113
- <div className="mt-5 rounded-md border border-emerald-400/40 bg-emerald-400/5 p-3">
114
- <p className="mt-0 mb-2 text-sm font-semibold">
115
- Copy this token now. It won't be shown again.
116
- </p>
117
- <code className="block overflow-x-auto rounded-md bg-dashboard-ink p-2 text-xs text-emerald-300">
118
- {createdToken.token}
119
- </code>
120
- <Button
121
- className="mt-3"
122
- onClick={() => {
123
- createTokenMutation.reset();
124
- setName("Local agent");
125
- }}
126
- >
127
- Create another token
128
- </Button>
129
- </div>
130
- ) : (
131
- <div className="mt-5 flex gap-2">
132
- <Input
133
- aria-label="Token name"
134
- className="min-w-0 flex-1"
135
- maxLength={80}
136
- onChange={(event) => setName(event.target.value)}
137
- value={name}
138
- />
139
- <Button
140
- disabled={loading || busy || !name.trim()}
141
- onClick={() => {
142
- if (createTokenStarted.current) return;
143
- revokeTokenMutation.reset();
144
- createTokenStarted.current = true;
145
- createTokenMutation.mutate(name);
146
- }}
147
- >
148
- Create token
149
- </Button>
150
- </div>
151
- )}
152
-
153
- {displayedError ? (
154
- <InlineError className="mt-4">{displayedError}</InlineError>
155
- ) : null}
156
-
157
- <div className="mt-5 space-y-2">
158
- {loading ? (
159
- <p className="text-sm text-dashboard-text-muted">
160
- Loading API tokens…
161
- </p>
162
- ) : tokens.length === 0 ? (
163
- <p className="text-sm text-dashboard-text-muted">
164
- No active API tokens.
165
- </p>
166
- ) : (
167
- tokens.map((token) => (
168
- <div
169
- className="flex items-center gap-3 rounded-md border border-dashboard-border-strong p-3"
170
- key={token.id}
171
- >
172
- <KeyRound className="text-dashboard-accent" size={16} />
173
- <div className="min-w-0 flex-1">
174
- <p className="m-0 truncate text-sm font-semibold">
175
- {token.name}
176
- </p>
177
- <p className="mt-1 mb-0 text-xs text-dashboard-text-muted">
178
- Ends in {token.tokenSuffix} · Expires{" "}
179
- {new Date(token.expiresAt).toLocaleDateString()}
180
- </p>
181
- </div>
182
- <Button
183
- aria-label={`Revoke ${token.name}`}
184
- disabled={busy}
185
- onClick={() => {
186
- if (createTokenMutation.isError) {
187
- createTokenMutation.reset();
188
- }
189
- revokeTokenMutation.mutate(token);
190
- }}
191
- size="icon"
192
- variant="danger"
193
- type="button"
194
- >
195
- <Trash2 size={16} />
196
- </Button>
197
- </div>
198
- ))
199
- )}
200
- </div>
201
- </Card>
202
- </section>
203
- </div>
204
- );
205
- }
@@ -1,34 +0,0 @@
1
- # Automation pages
2
-
3
- ## Boundaries
4
-
5
- - Core owns validation, edit access, creator-only rules, revision checks,
6
- Schedule previews, and the Event catalog. The page shows core errors. It
7
- does not repeat these rules.
8
- - Preview requests never save or run work.
9
- - Completed scheduled Automations are read-only.
10
-
11
- ## Editor rules
12
-
13
- - Send only changed fields. Keep unsupported triggers and stored
14
- Destinations unless the user replaces them. Keep outcome order.
15
- - New messages can target only the current Destination or the creator.
16
- - A public reader sees outcomes as read-only and cannot turn on creator
17
- credentials. Warn that an instruction or event selector change switches to
18
- system credentials.
19
- - Pause, Resume, and Delete are creator-only. Hide them from other viewers.
20
- - Query refreshes must not replace an unsaved draft. After a stale save, let
21
- the user keep their changes or load the latest version.
22
- - Block navigation with unsaved changes.
23
- - Lifecycle actions and "Make active" read the latest revision first.
24
-
25
- ## Page rules
26
-
27
- - Each Automation has one page at `/automations/:id`. Keep the list query
28
- string on the way back. Drop tab-only params: `result`, `run`, `version`.
29
- - `AutomationRecentRuns` shows terminal runs only. Do not infer queued or
30
- running work from terminal records.
31
- - Delete keeps errors in the dialog and blocks dismissal while pending.
32
-
33
- Browser journeys: `e2e/automation-editor.spec.ts` and
34
- `e2e/automations.spec.ts`.
@@ -1,12 +0,0 @@
1
- # System pages
2
-
3
- - The URL selects a Workspace recipe: `/system/workspaces/:id` opens
4
- `WorkspaceDetails` beside the list.
5
- - The list report has no snapshots. The detail read adds the current
6
- snapshot. Keep snapshot loading and errors inside the Snapshot row.
7
- - Edit and Delete live in the panel header. List rows have no actions.
8
- - `WorkspaceDeleteDialog` keeps errors in the dialog and blocks dismissal
9
- while the request runs.
10
- - Close and Escape return focus to the selected row.
11
-
12
- Browser coverage: `e2e/system.spec.ts`.
@@ -1,123 +0,0 @@
1
- import { Archive } from "lucide-react";
2
- import type { ReactNode } from "react";
3
- import { Link } from "react-router";
4
- import type { ReviewItemPayload } from "@sentry/junior/api/schema";
5
-
6
- import { StatusChip } from "../components/StatusChip";
7
- import { DisplayText } from "../display-text/DisplayText";
8
- import { conversationPath } from "../conversations/conversationRoutes";
9
- import { automationPath, formatRelativeTime } from "../format";
10
- import { ReviewItemActions } from "./ReviewItemActions";
11
- import { useLinkedConversationTitles } from "./queries";
12
- import { assigneeLabel, reviewItemStatusLabel } from "./reviewItemFormat";
13
-
14
- function Fact(props: { children: ReactNode; label: string }) {
15
- return (
16
- <div className="grid content-start gap-0.5">
17
- <dt className="text-xs font-medium text-dashboard-text-muted">
18
- {props.label}
19
- </dt>
20
- <dd className="m-0 min-w-0 break-words text-sm">{props.children}</dd>
21
- </div>
22
- );
23
- }
24
-
25
- /** Show one Review item with its facts and the viewer's actions. */
26
- export function ReviewItemDetail(props: { item: ReviewItemPayload }) {
27
- const { item } = props;
28
- const subject = item.subject;
29
- const conversationTitles = useLinkedConversationTitles(item.conversationIds);
30
- const closed = item.status !== "open";
31
- return (
32
- <article className="grid min-w-0 content-start gap-5">
33
- {closed ? (
34
- // Search mixes open and archived items, so a closed item must be
35
- // easy to tell apart at the top of the detail.
36
- <p className="m-0 flex items-center gap-2 rounded-md bg-dashboard-fill-soft px-3 py-2 text-xs font-medium text-dashboard-text-muted">
37
- <Archive aria-hidden="true" size={14} />
38
- Archived · {reviewItemStatusLabel(item)}
39
- </p>
40
- ) : null}
41
- <header className="grid gap-1.5">
42
- <div className="flex min-w-0 items-start justify-between gap-3">
43
- <h2 className="m-0 min-w-0 break-words font-display text-xl font-medium leading-snug">
44
- {item.title}
45
- </h2>
46
- {closed ? null : (
47
- <StatusChip className="mt-1 shrink-0" size="compact" tone="accent">
48
- {reviewItemStatusLabel(item)}
49
- </StatusChip>
50
- )}
51
- </div>
52
- <p className="m-0 text-xs text-dashboard-text-muted">
53
- Added {formatRelativeTime(item.createdAt)}
54
- {item.visibility === "private" ? " · Private" : null}
55
- </p>
56
- </header>
57
- <DisplayText text={item.body} />
58
- <section
59
- aria-label={item.status === "open" ? "Actions" : "Outcome"}
60
- className="border-t border-dashboard-border-subtle pt-5"
61
- >
62
- <ReviewItemActions item={item} />
63
- </section>
64
- <dl className="m-0 grid gap-4 border-t border-dashboard-border-subtle pt-5 sm:grid-cols-2">
65
- <Fact label="Assignees">
66
- {item.assignees.length > 0 || item.assignedToEveryone ? (
67
- assigneeLabel(item)
68
- ) : (
69
- <StatusChip size="compact" tone="warning">
70
- Unassigned
71
- </StatusChip>
72
- )}
73
- </Fact>
74
- {subject ? (
75
- <Fact label="Subject">
76
- {subject.url ? (
77
- <a href={subject.url} rel="noreferrer" target="_blank">
78
- {subject.title ?? subject.url}
79
- </a>
80
- ) : (
81
- (subject.title ?? subject.id)
82
- )}
83
- </Fact>
84
- ) : null}
85
- <Fact label="Added by">
86
- <Link to={conversationPath(item.addedByConversationId)}>
87
- Conversation
88
- </Link>
89
- {item.automationId ? (
90
- <>
91
- {" · "}
92
- <Link to={automationPath(item.automationId)}>Automation</Link>
93
- </>
94
- ) : null}
95
- </Fact>
96
- {item.conversationIds.length > 0 ? (
97
- <Fact label="Linked Conversations">
98
- <ul className="m-0 grid list-none gap-0.5 p-0">
99
- {item.conversationIds.map((conversationId, index) => {
100
- const title = conversationTitles[index];
101
- return (
102
- <li
103
- className={
104
- title ? "truncate" : "truncate font-mono text-xs"
105
- }
106
- key={conversationId}
107
- >
108
- <Link
109
- title={conversationId}
110
- to={conversationPath(conversationId)}
111
- >
112
- {title ?? conversationId}
113
- </Link>
114
- </li>
115
- );
116
- })}
117
- </ul>
118
- </Fact>
119
- ) : null}
120
- </dl>
121
- </article>
122
- );
123
- }