@sentry/junior-dashboard 0.270.0 → 0.271.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 (32) hide show
  1. package/dist/app.js +2 -2
  2. package/dist/client.js +77 -77
  3. package/dist/index.js +2 -2
  4. package/dist/tailwind.css +1 -1
  5. package/package.json +4 -4
  6. package/src/client/App.tsx +89 -61
  7. package/src/client/components/AGENTS.md +30 -59
  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/AGENTS.md +12 -33
  12. package/src/client/conversations/ConversationPage.tsx +44 -8
  13. package/src/client/conversations/conversationRoutes.ts +30 -3
  14. package/src/client/pages/automations/AGENTS.md +6 -25
  15. package/src/client/pages/settings/ConnectionsPage.tsx +198 -0
  16. package/src/client/pages/settings/McpSettingsPage.tsx +77 -0
  17. package/src/client/pages/settings/PersonalTokensPage.tsx +198 -0
  18. package/src/client/pages/{SettingsPage.tsx → settings/SettingsPage.tsx} +54 -62
  19. package/src/client/pages/settings/SettingsPageLayout.tsx +45 -0
  20. package/src/client/primaryNav.ts +1 -1
  21. package/src/client/review-desk/ReviewDeskPage.tsx +202 -241
  22. package/src/client/review-desk/ReviewDeskSidebar.tsx +253 -0
  23. package/src/client/review-desk/ReviewItemContext.tsx +108 -0
  24. package/src/client/review-desk/ReviewItemDetails.tsx +153 -0
  25. package/src/client/review-desk/ReviewItemView.tsx +159 -0
  26. package/src/client/review-desk/queries.ts +4 -2
  27. package/src/client/review-desk/reviewItemFormat.ts +54 -0
  28. package/src/client/components/charts/AGENTS.md +0 -15
  29. package/src/client/pages/ConnectionsPage.tsx +0 -207
  30. package/src/client/pages/PersonalTokensPage.tsx +0 -205
  31. package/src/client/pages/system/AGENTS.md +0 -12
  32. package/src/client/review-desk/ReviewItemDetail.tsx +0 -123
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-dashboard",
3
- "version": "0.270.0",
3
+ "version": "0.271.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -39,10 +39,10 @@
39
39
  "remark-gfm": "^4.0.1",
40
40
  "shiki": "4.1.0",
41
41
  "zod": "^4.5.4",
42
- "@sentry/junior-plugin-api": "0.270.0"
42
+ "@sentry/junior-plugin-api": "0.271.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@sentry/junior": "0.270.0"
45
+ "@sentry/junior": "0.271.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@oxlint/plugins": "1.79.0",
@@ -63,7 +63,7 @@
63
63
  "tw-animate-css": "^1.4.0",
64
64
  "typescript": "^7.0.2",
65
65
  "vitest": "^4.1.12",
66
- "@sentry/junior": "0.270.0"
66
+ "@sentry/junior": "0.271.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "pnpm --filter @sentry/junior-plugin-api build && pnpm --filter @sentry/junior build && pnpm run build:client && pnpm run build:css && pnpm run build:server",
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from "react";
1
+ import { useEffect, useMemo, useState, type ReactNode } from "react";
2
2
  import { Navigate, Route, Routes, useLocation, useParams } from "react-router";
3
3
 
4
4
  import {
@@ -11,7 +11,10 @@ import {
11
11
  import { ConnectionBanner } from "./components/ConnectionBanner";
12
12
  import { LoadingView } from "./components/LoadingView";
13
13
  import { VersionDriftBanner } from "./components/VersionDriftBanner";
14
- import { PageRouteLoading } from "./components/PageRouteLoading";
14
+ import {
15
+ PageContentSkeleton,
16
+ type PageContentSkeletonVariant,
17
+ } from "./components/PageContentSkeleton";
15
18
  import { ProfileMenu } from "./components/ProfileMenu";
16
19
  import {
17
20
  DashboardChrome,
@@ -36,10 +39,12 @@ import { CodePage } from "./pages/code/CodePage";
36
39
  import { LocationDetailPage } from "./pages/locations/LocationDetailPage";
37
40
  import { LocationsPage } from "./pages/locations/LocationsPage";
38
41
  import { PeoplePage } from "./pages/people/PeoplePage";
39
- import { ConnectionsPage } from "./pages/ConnectionsPage";
40
- import { PersonalTokensPage } from "./pages/PersonalTokensPage";
41
42
  import { PersonProfilePage } from "./pages/people/PersonProfilePage";
42
- import { SettingsPage } from "./pages/SettingsPage";
43
+ import { ConnectionsPage } from "./pages/settings/ConnectionsPage";
44
+ import { McpSettingsPage } from "./pages/settings/McpSettingsPage";
45
+ import { PersonalTokensPage } from "./pages/settings/PersonalTokensPage";
46
+ import { SettingsPage } from "./pages/settings/SettingsPage";
47
+ import { SettingsPageLayout } from "./pages/settings/SettingsPageLayout";
43
48
  import { SystemPage } from "./pages/system/SystemPage";
44
49
  import {
45
50
  SystemPageLayout,
@@ -65,7 +70,10 @@ import {
65
70
  } from "./pages/user/PluginUserPage";
66
71
  import { AdminPage } from "./pages/admin/AdminPage";
67
72
  import { buildPrimaryNavItems } from "./primaryNav";
68
- import { ReviewDeskPage } from "./review-desk/ReviewDeskPage";
73
+ import {
74
+ ReviewDeskLoading,
75
+ ReviewDeskPage,
76
+ } from "./review-desk/ReviewDeskPage";
69
77
  import { useReviewItemCountData } from "./review-desk/queries";
70
78
  import type { DashboardCoreData } from "./types";
71
79
 
@@ -74,6 +82,56 @@ const dashboardNoise = {
74
82
  "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitchTiles'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.7'/%3E%3C/svg%3E\")",
75
83
  };
76
84
 
85
+ const settingsRoutes: {
86
+ loading: {
87
+ description: string;
88
+ label: string;
89
+ title: string;
90
+ variant?: PageContentSkeletonVariant;
91
+ };
92
+ path: string;
93
+ render(data: DashboardCoreData): ReactNode;
94
+ }[] = [
95
+ {
96
+ loading: {
97
+ description: "Manage your profile and preferences.",
98
+ label: "Loading settings",
99
+ title: "General",
100
+ },
101
+ path: "/settings",
102
+ render: (data) => <SettingsPage identity={data.me} />,
103
+ },
104
+ {
105
+ loading: {
106
+ description: "Create and revoke personal API tokens.",
107
+ label: "Loading API tokens",
108
+ title: "API Tokens",
109
+ variant: "list",
110
+ },
111
+ path: "/settings/api-tokens",
112
+ render: () => <PersonalTokensPage />,
113
+ },
114
+ {
115
+ loading: {
116
+ description: "Connect and disconnect your accounts.",
117
+ label: "Loading identities",
118
+ title: "Identities",
119
+ variant: "list",
120
+ },
121
+ path: "/settings/connections",
122
+ render: () => <ConnectionsPage />,
123
+ },
124
+ {
125
+ loading: {
126
+ description: "Connect an MCP client to your conversations.",
127
+ label: "Loading MCP",
128
+ title: "MCP",
129
+ },
130
+ path: "/settings/mcp",
131
+ render: () => <McpSettingsPage />,
132
+ },
133
+ ];
134
+
77
135
  /** Render the dashboard SPA shell and route-level loading states. */
78
136
  export function DashboardShell() {
79
137
  const location = useLocation();
@@ -147,6 +205,7 @@ export function DashboardShell() {
147
205
  className="bg-dashboard-bg"
148
206
  enabled={
149
207
  workspace ||
208
+ location.pathname === "/review" ||
150
209
  hasAutomationDetailsFrame(location.pathname) ||
151
210
  isMemoryLibraryPath(location.pathname)
152
211
  }
@@ -375,11 +434,7 @@ export function DashboardShell() {
375
434
  <Route
376
435
  element={
377
436
  loading ? (
378
- <PageRouteLoading
379
- description="Items that need a person, and who can act on them."
380
- label="Loading review desk"
381
- title="Review desk"
382
- />
437
+ <ReviewDeskLoading />
383
438
  ) : loggedIn ? (
384
439
  <ReviewDeskPage />
385
440
  ) : (
@@ -523,56 +578,29 @@ export function DashboardShell() {
523
578
  }
524
579
  path="/system/*"
525
580
  />
526
- <Route
527
- element={
528
- loading ? (
529
- <PageRouteLoading
530
- description="Manage your Junior profile and preferences."
531
- label="Loading settings"
532
- title="Settings"
533
- />
534
- ) : loggedIn ? (
535
- <SettingsPage identity={data!.me} />
536
- ) : (
537
- <Navigate replace to="/" />
538
- )
539
- }
540
- path="/settings"
541
- />
542
- <Route
543
- element={
544
- loading ? (
545
- <PageRouteLoading
546
- description="Create and revoke personal API tokens."
547
- label="Loading API tokens"
548
- title="API tokens"
549
- variant="list"
550
- />
551
- ) : loggedIn ? (
552
- <PersonalTokensPage />
553
- ) : (
554
- <Navigate replace to="/" />
555
- )
556
- }
557
- path="/settings/api-tokens"
558
- />
559
- <Route
560
- element={
561
- loading ? (
562
- <PageRouteLoading
563
- description="Connect and disconnect your accounts."
564
- label="Loading connected accounts"
565
- title="Connected accounts"
566
- variant="list"
567
- />
568
- ) : loggedIn ? (
569
- <ConnectionsPage />
570
- ) : (
571
- <Navigate replace to="/" />
572
- )
573
- }
574
- path="/settings/connections"
575
- />
581
+ {settingsRoutes.map((route) => (
582
+ <Route
583
+ element={
584
+ loading ? (
585
+ <SettingsPageLayout
586
+ description={route.loading.description}
587
+ title={route.loading.title}
588
+ >
589
+ <PageContentSkeleton
590
+ label={route.loading.label}
591
+ variant={route.loading.variant}
592
+ />
593
+ </SettingsPageLayout>
594
+ ) : loggedIn ? (
595
+ route.render(data!)
596
+ ) : (
597
+ <Navigate replace to="/" />
598
+ )
599
+ }
600
+ key={route.path}
601
+ path={route.path}
602
+ />
603
+ ))}
576
604
  <Route
577
605
  element={
578
606
  loading || userPagesQuery.isPending ? (
@@ -1,65 +1,36 @@
1
- # Dashboard UI kit
2
-
3
- Shared dashboard components live here. Feature pages compose them instead of
4
- copying Tailwind blocks. Follow `policies/frontend-components.md`.
5
- `pnpm dashboard-style:check` enforces the token rules.
6
-
7
- ## shadcn/ui
8
-
9
- - Add components with
10
- `pnpm --filter @sentry/junior-dashboard exec shadcn add <component>`. They go
11
- to `ui/`.
12
- - `ui/` files are shadcn source on dashboard tokens. Remove unused exports.
13
- Change a `ui/` file when every caller needs the same style.
14
- - Use `ui/` components directly. Add an app component only when it adds
15
- structure or behavior, not to rename a `ui/` component or its props.
16
- - Do not apply the CLI's global border or body resets. Keep the Tailwind
17
- radius scale.
18
-
19
- ## Component rules
20
-
21
- - Use `ui/alert-dialog` for every confirmation. Never use `window.confirm`.
22
- - Use `ui/dialog` for a short task form. Never put a form in a `Drawer` or
23
- `DetailsPanel`. Use `useSheet` for each dialog and mount it only while open.
24
- - Use `ui/trace` for transcript activity rows. Put only inline elements in
25
- the row button.
26
- - Give an icon-only control a label with `TooltipContent size="compact"`.
27
- Never use the native `title` attribute.
28
- - Set `focusableWhenDisabled` on a `ui/button` tooltip trigger that can be
1
+ # Dashboard UI rules
2
+
3
+ These apply to all dashboard UI. `pnpm dashboard-style:check` enforces the
4
+ token rules.
5
+
6
+ - Add shadcn components with
7
+ `pnpm --filter @sentry/junior-dashboard exec shadcn add <component>`.
8
+ - Change a `ui/` file only when every caller needs the same style. Do not
9
+ wrap a `ui/` component just to rename it or its props.
10
+ - Do not apply the shadcn CLI's global border or body resets. Keep the
11
+ Tailwind radius scale.
12
+ - Confirmations use `ui/alert-dialog`, not `window.confirm`.
13
+ - Short task forms use `ui/dialog`, never a `Drawer` or `DetailsPanel`. Mount
14
+ each dialog through `useSheet`, only while open.
15
+ - Put only inline elements in a `ui/trace` row button.
16
+ - Label icon-only controls with `TooltipContent size="compact"`, not the
17
+ native `title` attribute.
18
+ - Set `focusableWhenDisabled` on a tooltip trigger `ui/button` that can be
29
19
  disabled.
30
- - Build form fields from `ui/field`. Use the ids `<control id>-error` and
31
- `<control id>-help`.
20
+ - Field ids are `<control id>-error` and `<control id>-help`.
32
21
  - Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
33
22
  `aria-label`.
34
- - `ui/alert` sets `role="alert"`. Pass `role="status"` for a message that is
35
- not an error.
36
23
  - A form page has one `Button variant="primary"`.
37
- - `ObjectIcon` owns native object icons. Do not replace it with a provider
38
- logo.
39
-
40
- ## Tokens
41
-
42
- - Keep every color in a token in `src/tailwind.css`, so each theme can
43
- redefine it. Use `light:` only for values that no token covers.
44
- - Use `var(--color-*)` for chart series. Never use hex values.
45
- - Do not use `white/*` or `black/*` fills, borders, or rings, or hex surface
46
- colors. Shadows may use black.
24
+ - Do not replace `ObjectIcon` with a provider logo.
25
+ - Use `light:` only for values that no token covers. Chart series use
26
+ `var(--color-*)`, not hex.
47
27
  - Use `outline-dashboard-focus` for keyboard focus only.
48
- - Use the named type scale. Keep `text-2xs` for compact badges and chart
49
- annotations.
50
- - Labels use sentence case. Do not use uppercase or letter spacing.
51
-
52
- ## Navigation
53
-
28
+ - Labels use sentence case, with no uppercase or letter spacing.
54
29
  - App navigation uses pills, page links use `SegmentedNav`, and panel
55
- switches use `ui/tabs`. Keep the three levels distinct.
56
-
57
- ## Ownership
58
-
59
- - Conversation mobile geometry has hard owners: `layout/VisualViewportShell`,
60
- `conversations/ChatLayout`, and `conversations/ComposerDock`. Pages pass
61
- children only. Do not add viewport, offset, or safe-area math in pages.
62
- - Home and create must not use `ComposerDock`.
63
- - Feature surfaces stay under `conversations/` or `pages/` until two real
64
- callers share them. Add a named component on the second real use.
65
- - Keep Tailwind classes on the owning component.
30
+ switches use `ui/tabs`.
31
+ - Do not add viewport, offset, or safe-area math in pages.
32
+ `layout/VisualViewportShell`, `conversations/ChatLayout`, and
33
+ `conversations/ComposerDock` own it. Home and create do not use
34
+ `ComposerDock`.
35
+ - Keep a feature component under `conversations/` or `pages/` until a second
36
+ real caller needs it.
@@ -1,9 +1,7 @@
1
1
  import {
2
2
  Boxes,
3
3
  ChevronDown,
4
- KeyRound,
5
4
  LogOut,
6
- Plug,
7
5
  Settings,
8
6
  ShieldCheck,
9
7
  UserRound,
@@ -197,22 +195,6 @@ export function ProfileMenu({
197
195
  <Settings aria-hidden="true" size={16} strokeWidth={2} />
198
196
  Settings
199
197
  </Link>
200
- <Link
201
- className={profileLinkClass}
202
- onClick={close}
203
- to="/settings/api-tokens"
204
- >
205
- <KeyRound aria-hidden="true" size={16} strokeWidth={2} />
206
- API tokens
207
- </Link>
208
- <Link
209
- className={profileLinkClass}
210
- onClick={close}
211
- to="/settings/connections"
212
- >
213
- <Plug aria-hidden="true" size={16} strokeWidth={2} />
214
- Connected accounts
215
- </Link>
216
198
  {identity.user.isAdmin ? (
217
199
  <Link className={profileLinkClass} onClick={close} to="/admin">
218
200
  <ShieldCheck aria-hidden="true" size={16} strokeWidth={2} />
@@ -282,12 +264,6 @@ export function ProfileMenu({
282
264
  <Link className={sheetItemClass} to="/settings">
283
265
  Settings
284
266
  </Link>
285
- <Link className={sheetItemClass} to="/settings/api-tokens">
286
- API tokens
287
- </Link>
288
- <Link className={sheetItemClass} to="/settings/connections">
289
- Connected accounts
290
- </Link>
291
267
  {identity.user.isAdmin ? (
292
268
  <Link className={sheetItemClass} to="/admin">
293
269
  Admin
@@ -35,10 +35,31 @@ const optionClass =
35
35
  const choiceButtonClass =
36
36
  "group flex w-full cursor-pointer items-center gap-3 rounded-md border border-dashboard-border bg-dashboard-surface-raised px-3 py-2.5 text-left font-sans text-sm transition-colors hover:border-dashboard-border-emphasis hover:bg-dashboard-surface-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-dashboard-focus disabled:cursor-not-allowed disabled:opacity-60 data-[picked]:border-dashboard-accent/60 data-[picked]:bg-dashboard-accent/10 data-[picked]:opacity-100";
37
37
 
38
- function OptionText(props: { description?: string; label: string }) {
38
+ // Marks the choice that Junior recommends, next to its label.
39
+ function SuggestedLabel(props: { className?: string }) {
40
+ return (
41
+ <span
42
+ className={cn(
43
+ "ml-2 text-xs font-normal text-dashboard-accent-text/80",
44
+ props.className,
45
+ )}
46
+ >
47
+ Suggested
48
+ </span>
49
+ );
50
+ }
51
+
52
+ function OptionText(props: {
53
+ description?: string;
54
+ label: string;
55
+ recommended?: boolean;
56
+ }) {
39
57
  return (
40
58
  <span className="grid min-w-0 gap-0.5 break-words">
41
- <span className="font-medium text-dashboard-text">{props.label}</span>
59
+ <span className="font-medium text-dashboard-text">
60
+ {props.label}
61
+ {props.recommended ? <SuggestedLabel /> : null}
62
+ </span>
42
63
  {props.description ? (
43
64
  <span className="text-xs text-dashboard-text-muted">
44
65
  {props.description}
@@ -56,13 +77,15 @@ function ChoiceField(props: {
56
77
  }) {
57
78
  const id = useId();
58
79
  const { field } = props;
59
- // Say how many choices count, so a reader knows when a field is done.
80
+ // Radios already say that one choice counts. Say it for checkboxes only.
60
81
  const legend = (
61
82
  <legend className="mb-2 p-0 text-sm font-semibold text-dashboard-text">
62
83
  {field.label}
63
- <span className="ml-2 text-xs font-normal text-dashboard-text-muted">
64
- {field.kind === "choice" ? "Pick one" : "Pick any"}
65
- </span>
84
+ {field.kind === "multi_choice" ? (
85
+ <span className="ml-2 text-xs font-normal text-dashboard-text-muted">
86
+ Pick any
87
+ </span>
88
+ ) : null}
66
89
  </legend>
67
90
  );
68
91
  const description = field.description ? (
@@ -90,6 +113,7 @@ function ChoiceField(props: {
90
113
  <OptionText
91
114
  description={option.description}
92
115
  label={option.label}
116
+ recommended={option.recommended}
93
117
  />
94
118
  </label>
95
119
  ))}
@@ -119,7 +143,11 @@ function ChoiceField(props: {
119
143
  )
120
144
  }
121
145
  />
122
- <OptionText description={option.description} label={option.label} />
146
+ <OptionText
147
+ description={option.description}
148
+ label={option.label}
149
+ recommended={option.recommended}
150
+ />
123
151
  </label>
124
152
  ))}
125
153
  </div>
@@ -261,6 +289,7 @@ export function UserInputForm(props: {
261
289
  id={`${choicesId}-${index}-label`}
262
290
  >
263
291
  {option.label}
292
+ {option.recommended ? <SuggestedLabel /> : null}
264
293
  </span>
265
294
  {option.description ? (
266
295
  <span
@@ -292,6 +321,8 @@ export function UserInputForm(props: {
292
321
  onClick={() => pick(buttonField.id, option.label)}
293
322
  >
294
323
  {option.label}
324
+ {/* The button gap already spaces the label. */}
325
+ {option.recommended ? <SuggestedLabel className="ml-0" /> : null}
295
326
  </Button>
296
327
  ))}
297
328
  {secondary}
@@ -5,17 +5,20 @@ import {
5
5
  type TimeRangeDays,
6
6
  } from "../controls/TimeRangeSelector";
7
7
  import { useDocumentTitle } from "../../documentTitle";
8
+ import { cn } from "../../styles";
8
9
 
9
10
  /**
10
11
  * Render a spacious analytics page heading with optional controls.
11
12
  *
12
13
  * Also names the browser tab after the page. Pass `documentTitle` when the tab
13
- * needs more context than the visible heading.
14
+ * needs more context than the visible heading. Pass `navigation` to put page
15
+ * links under the heading, with their underline on the header border.
14
16
  */
15
17
  export function PageHeader(props: {
16
18
  actions?: ReactNode;
17
- description: ReactNode;
19
+ description?: ReactNode;
18
20
  documentTitle?: string;
21
+ navigation?: ReactNode;
19
22
  onRangeChange?(value: TimeRangeDays): void;
20
23
  range?: TimeRangeDays;
21
24
  rangeOptions?: TimeRangeDays[];
@@ -36,16 +39,28 @@ export function PageHeader(props: {
36
39
  const actions = props.actions ?? rangeActions;
37
40
 
38
41
  return (
39
- <header className="flex min-w-0 items-center justify-between gap-6 border-b border-dashboard-border-subtle pb-4 sm:pb-6 max-md:flex-col max-md:items-stretch">
40
- <div className="min-w-0">
41
- <h2 className="m-0 font-display text-3xl font-light leading-none tracking-[-0.03em] text-dashboard-text sm:text-4xl">
42
- {props.title}
43
- </h2>
44
- <div className="mt-2 max-w-2xl font-sans text-xs leading-relaxed text-dashboard-text-muted sm:text-sm">
45
- {props.description}
42
+ <header
43
+ className={cn(
44
+ "min-w-0 border-b border-dashboard-border-subtle",
45
+ !props.navigation && "pb-4 sm:pb-6",
46
+ )}
47
+ >
48
+ <div className="flex min-w-0 items-center justify-between gap-6 max-md:flex-col max-md:items-stretch">
49
+ <div className="min-w-0">
50
+ <h2 className="m-0 font-display text-3xl font-light leading-none tracking-[-0.03em] text-dashboard-text sm:text-4xl">
51
+ {props.title}
52
+ </h2>
53
+ {props.description ? (
54
+ <div className="mt-2 max-w-2xl font-sans text-xs leading-relaxed text-dashboard-text-muted sm:text-sm">
55
+ {props.description}
56
+ </div>
57
+ ) : null}
46
58
  </div>
59
+ {actions ? <div className="shrink-0">{actions}</div> : null}
47
60
  </div>
48
- {actions ? <div className="shrink-0">{actions}</div> : null}
61
+ {props.navigation ? (
62
+ <div className="mt-4 sm:mt-6">{props.navigation}</div>
63
+ ) : null}
49
64
  </header>
50
65
  );
51
66
  }
@@ -1,38 +1,17 @@
1
1
  # Transcript components
2
2
 
3
- Keep transcript layout in these feature components. Use the UI kit in
4
- `../components/` for shared controls and overlays.
5
-
6
- ## Ownership
7
-
8
- - `TranscriptRows` and `TranscriptRow` own row gaps, padding, and indent.
9
- Keep horizontal padding inside each row, so focus outlines are not clipped.
10
- - `TranscriptMessageShell` owns the avatar column and message gutter.
11
- - `TranscriptSummaryTrigger` owns disclosure padding and focus. Do not repeat
12
- negative margins at call sites.
13
- - `ChatLayout` keeps the Conversation header outside the transcript scroll.
14
- - `eventLogRows` and `filterEventLogRows` own row text and search match. The
15
- page step controls use them too.
16
-
17
- ## Rules
18
-
19
- - The Automation trigger card shows the credentials from the dispatch record
20
- of the run, not the current setting. Keep the run input collapsed, because
21
- it is not from a person.
22
- - Each `Transcript` sets the panel for its own rows. A nested transcript never
23
- uses the panel of the page behind it.
24
- - The page has one right panel. Conversation details, event details, and turn
25
- context never show at the same time. Closing a panel returns focus to the
26
- control that opened it.
3
+ - Keep horizontal padding inside each transcript row, so focus outlines are
4
+ not clipped. Do not add negative margins at `TranscriptSummaryTrigger` call
5
+ sites.
6
+ - The Automation trigger card shows the credentials from the run's dispatch
7
+ record, not the current setting. Keep the run input collapsed, because it
8
+ is not from a person.
9
+ - A nested `Transcript` never uses the panel of the page behind it.
10
+ - The page shows one right panel at a time. Closing a panel returns focus to
11
+ the control that opened it.
27
12
  - The URL owns the selected event in the main Conversation. Standalone
28
- transcripts keep selection in `ConversationEventLog`.
29
- - A pending exit must not close a new panel or another Conversation.
30
- - A pending fork blocks dismissal of its dialog.
13
+ transcripts keep it in `ConversationEventLog`.
14
+ - A pending exit must not close a new panel or another Conversation. A
15
+ pending fork blocks dismissal of its dialog.
31
16
  - Raw JSON keeps the original ISO values. Persist the UTC/local mode, not the
32
17
  timezone.
33
-
34
- ## Checks
35
-
36
- - Review with `JUNIOR_DASHBOARD_MOCK_CONVERSATIONS=true pnpm dev`.
37
- - Keep visual checks out of automated assertions. Browser tests cover
38
- opening, closing, and keyboard interaction.