@sentry/junior-dashboard 0.267.0 → 0.269.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 (59) hide show
  1. package/README.md +7 -105
  2. package/dist/app.js +47 -8
  3. package/dist/client.js +74 -78
  4. package/dist/index.js +47 -8
  5. package/dist/tailwind.css +1 -1
  6. package/package.json +4 -4
  7. package/src/api/schema.ts +2 -1
  8. package/src/app.ts +11 -7
  9. package/src/client/App.tsx +20 -4
  10. package/src/client/api.ts +4 -19
  11. package/src/client/components/AGENTS.md +65 -0
  12. package/src/client/components/ActorAvatar.tsx +1 -1
  13. package/src/client/components/ParticipantAvatarStack.tsx +3 -1
  14. package/src/client/components/ProfileMenu.tsx +88 -29
  15. package/src/client/components/UserInputForm.tsx +283 -0
  16. package/src/client/components/charts/AGENTS.md +15 -0
  17. package/src/client/components/layout/DashboardHeader.tsx +13 -0
  18. package/src/client/components/ui/button.tsx +11 -1
  19. package/src/client/conversations/AGENTS.md +42 -0
  20. package/src/client/conversations/ConversationHomeList.tsx +6 -0
  21. package/src/client/conversations/ConversationListStatusIcon.tsx +15 -1
  22. package/src/client/conversations/ConversationPage.tsx +4 -27
  23. package/src/client/conversations/ConversationSidebar.tsx +6 -0
  24. package/src/client/conversations/ConversationWorkspace.tsx +4 -0
  25. package/src/client/conversations/TranscriptActivityGroup.tsx +5 -0
  26. package/src/client/conversations/TranscriptMessageContextView.tsx +12 -1
  27. package/src/client/conversations/TranscriptMessageView.tsx +3 -0
  28. package/src/client/conversations/TranscriptToolView.tsx +13 -1
  29. package/src/client/conversations/TranscriptTurnContextView.tsx +142 -144
  30. package/src/client/conversations/turnContext.ts +15 -0
  31. package/src/client/format.ts +28 -28
  32. package/src/client/pages/SettingsPage.tsx +103 -3
  33. package/src/client/pages/automations/AGENTS.md +34 -0
  34. package/src/client/pages/automations/AutomationActions.tsx +18 -20
  35. package/src/client/pages/automations/AutomationEditor.tsx +3 -4
  36. package/src/client/pages/automations/AutomationOverview.tsx +3 -3
  37. package/src/client/pages/automations/AutomationStatus.tsx +2 -2
  38. package/src/client/pages/code/RepositorySections.tsx +1 -0
  39. package/src/client/pages/code/RepositoryTabs.tsx +3 -0
  40. package/src/client/pages/system/AGENTS.md +12 -0
  41. package/src/client/primaryNav.ts +15 -2
  42. package/src/client/review-desk/ConversationReviewBar.tsx +68 -0
  43. package/src/client/review-desk/ReviewDeskPage.tsx +297 -0
  44. package/src/client/review-desk/ReviewItemActions.tsx +165 -0
  45. package/src/client/review-desk/ReviewItemCard.tsx +47 -0
  46. package/src/client/review-desk/ReviewItemDetail.tsx +123 -0
  47. package/src/client/review-desk/queries.ts +173 -0
  48. package/src/client/review-desk/reviewItemFormat.ts +61 -0
  49. package/src/mock-reporting/fixtures.ts +10 -0
  50. package/src/mock-reporting/routes.ts +20 -0
  51. package/src/shell.ts +3 -0
  52. package/src/tailwind.css +1 -1
  53. package/src/client/components/README.md +0 -215
  54. package/src/client/components/charts/README.md +0 -38
  55. package/src/client/conversations/README.md +0 -159
  56. package/src/client/conversations/UserInputRequestCard.tsx +0 -244
  57. package/src/client/conversations/userInputRequest.ts +0 -56
  58. package/src/client/pages/automations/README.md +0 -97
  59. package/src/client/pages/system/README.md +0 -23
@@ -0,0 +1,173 @@
1
+ import { useMemo } from "react";
2
+ import {
3
+ keepPreviousData,
4
+ useInfiniteQuery,
5
+ useMutation,
6
+ useQueries,
7
+ useQuery,
8
+ useQueryClient,
9
+ } from "@tanstack/react-query";
10
+ import {
11
+ automationListSchema,
12
+ conversationDetailReportSchema,
13
+ reviewItemCountSchema,
14
+ reviewItemListSchema,
15
+ reviewItemSchema,
16
+ reviewItemWaitingConversationsSchema,
17
+ type ReviewItemActionBody,
18
+ } from "@sentry/junior/api/schema";
19
+
20
+ import { conversationDetailTitle } from "../format";
21
+ import { fetchDashboardJson, post } from "../http";
22
+
23
+ const REVIEW_ITEMS_QUERY_KEY = ["dashboard", "review-items"] as const;
24
+ const COUNT_REFRESH_MS = 60_000;
25
+
26
+ /**
27
+ * List the open Review items that the viewer can see, one page at a time.
28
+ * A search also finds archived items, with the first page. `items` holds
29
+ * the items of every loaded page, and `query` loads more.
30
+ */
31
+ export function useReviewItemsData(filters: {
32
+ conversationId?: string;
33
+ q?: string;
34
+ }) {
35
+ const params = new URLSearchParams();
36
+ if (filters.conversationId) {
37
+ params.set("conversationId", filters.conversationId);
38
+ }
39
+ if (filters.q) params.set("q", filters.q);
40
+ const search = params.toString();
41
+ const query = useInfiniteQuery({
42
+ initialPageParam: undefined as string | undefined,
43
+ queryKey: [...REVIEW_ITEMS_QUERY_KEY, "list", search],
44
+ queryFn: ({ pageParam, signal }) => {
45
+ const pageParams = new URLSearchParams(search);
46
+ if (pageParam) pageParams.set("cursor", pageParam);
47
+ return fetchDashboardJson(
48
+ reviewItemListSchema,
49
+ `/api/review-items?${pageParams.toString()}`,
50
+ signal,
51
+ );
52
+ },
53
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
54
+ placeholderData: keepPreviousData,
55
+ retry: false,
56
+ });
57
+ const pages = query.data?.pages;
58
+ const items = useMemo(
59
+ () => (pages ?? []).flatMap((page) => page.items),
60
+ [pages],
61
+ );
62
+ return { items, moreArchived: Boolean(pages?.[0]?.moreArchived), query };
63
+ }
64
+
65
+ /** Read one Review item, for a link to an item outside the current list. */
66
+ export function useReviewItemData(id: string | undefined) {
67
+ return useQuery({
68
+ enabled: Boolean(id),
69
+ queryKey: [...REVIEW_ITEMS_QUERY_KEY, "detail", id],
70
+ queryFn: ({ signal }) =>
71
+ fetchDashboardJson(
72
+ reviewItemSchema,
73
+ `/api/review-items/${encodeURIComponent(id!)}`,
74
+ signal,
75
+ ),
76
+ retry: false,
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Read the titles of the Conversations linked to an item. Each read asks for
82
+ * one event only. A Conversation that the viewer cannot read has no title.
83
+ */
84
+ export function useLinkedConversationTitles(
85
+ conversationIds: readonly string[],
86
+ ): (string | undefined)[] {
87
+ return useQueries({
88
+ queries: conversationIds.map((conversationId) => ({
89
+ queryKey: ["dashboard", "conversation-title", conversationId],
90
+ queryFn: async ({ signal }: { signal: AbortSignal }) =>
91
+ conversationDetailTitle(
92
+ await fetchDashboardJson(
93
+ conversationDetailReportSchema,
94
+ `/api/conversations/${encodeURIComponent(conversationId)}?limit=1`,
95
+ signal,
96
+ ),
97
+ ) ?? null,
98
+ retry: false,
99
+ staleTime: 5 * 60_000,
100
+ })),
101
+ }).map((query) => query.data ?? undefined);
102
+ }
103
+
104
+ /** Count the open action items assigned to the viewer, for the nav. */
105
+ export function useReviewItemCountData(enabled: boolean) {
106
+ return useQuery({
107
+ enabled,
108
+ queryKey: [...REVIEW_ITEMS_QUERY_KEY, "count"],
109
+ queryFn: ({ signal }) =>
110
+ fetchDashboardJson(
111
+ reviewItemCountSchema,
112
+ "/api/review-items/count",
113
+ signal,
114
+ ),
115
+ refetchInterval: COUNT_REFRESH_MS,
116
+ retry: false,
117
+ });
118
+ }
119
+
120
+ /**
121
+ * List the viewer's blocked Automations. They show on the review desk, but
122
+ * their state stays derived from Automation status.
123
+ */
124
+ export function useBlockedAutomationsData(enabled: boolean) {
125
+ return useQuery({
126
+ enabled,
127
+ queryKey: ["dashboard", "automations", "scope=mine&state=blocked"],
128
+ queryFn: ({ signal }) =>
129
+ fetchDashboardJson(
130
+ automationListSchema,
131
+ "/api/automations?scope=mine&state=blocked&pageSize=100",
132
+ signal,
133
+ ),
134
+ retry: false,
135
+ });
136
+ }
137
+
138
+ /** Take, submit, complete, or dismiss one Review item. */
139
+ export function useReviewItemAction() {
140
+ const queryClient = useQueryClient();
141
+ return useMutation({
142
+ mutationFn: (args: { body: ReviewItemActionBody; id: string }) =>
143
+ post(
144
+ reviewItemSchema,
145
+ `/api/review-items/${encodeURIComponent(args.id)}/actions`,
146
+ args.body,
147
+ ),
148
+ onSettled: () =>
149
+ queryClient.invalidateQueries({ queryKey: REVIEW_ITEMS_QUERY_KEY }),
150
+ });
151
+ }
152
+
153
+ const NO_CONVERSATIONS: ReadonlySet<string> = new Set();
154
+
155
+ /**
156
+ * Return the Conversations that wait on the viewer. Today only the review
157
+ * desk sets this: a linked open action item assigned to the viewer.
158
+ */
159
+ export function useWaitingConversationIds(): ReadonlySet<string> {
160
+ const query = useQuery({
161
+ queryKey: [...REVIEW_ITEMS_QUERY_KEY, "waiting-conversations"],
162
+ queryFn: ({ signal }) =>
163
+ fetchDashboardJson(
164
+ reviewItemWaitingConversationsSchema,
165
+ "/api/review-items/waiting-conversations",
166
+ signal,
167
+ ),
168
+ refetchInterval: COUNT_REFRESH_MS,
169
+ retry: false,
170
+ });
171
+ const ids = query.data?.conversationIds;
172
+ return useMemo(() => (ids ? new Set(ids) : NO_CONVERSATIONS), [ids]);
173
+ }
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import type { ReviewItemPayload } from "@sentry/junior/api/schema";
3
+
4
+ /** Return the review desk route that selects one item. */
5
+ export function reviewItemPath(id: string): string {
6
+ return `/review?item=${encodeURIComponent(id)}`;
7
+ }
8
+
9
+ /** Name the state of an item: what it needs while open, or how it closed. */
10
+ export function reviewItemStatusLabel(item: ReviewItemPayload): string {
11
+ if (item.status === "open") {
12
+ return item.kind === "action" ? "Needs an answer" : "Notice";
13
+ }
14
+ return item.status === "done" ? "Done" : "Dismissed";
15
+ }
16
+
17
+ /** Name the assignees of an item in one short line. */
18
+ export function assigneeLabel(item: ReviewItemPayload): string {
19
+ const names = item.assignees.map(
20
+ (person) => person.displayName ?? person.email,
21
+ );
22
+ if (item.assignedToEveryone) names.unshift("Everyone");
23
+ return names.length > 0 ? names.join(", ") : "Unassigned";
24
+ }
25
+
26
+ const ADD_REVIEW_ITEM_TOOL_NAME = "addReviewItem";
27
+ const executeToolInputSchema = z.object({ tool_name: z.string() });
28
+ const addedItemSchema = z.object({ item: z.object({ id: z.string().min(1) }) });
29
+
30
+ /** Read the item id from a tool result, which can be JSON text. */
31
+ function addedItemId(output: unknown): string | undefined {
32
+ let value = output;
33
+ if (typeof output === "string") {
34
+ try {
35
+ value = JSON.parse(output);
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ }
40
+ return addedItemSchema.safeParse(value).data?.item.id;
41
+ }
42
+
43
+ /**
44
+ * Return the id of the Review item that one completed tool call added. The
45
+ * agent calls `addReviewItem` directly or through `executeTool`. Returns
46
+ * undefined when the transcript hides the tool result.
47
+ */
48
+ export function reviewItemIdFromToolCall(call: {
49
+ input?: unknown;
50
+ name: string;
51
+ output?: unknown;
52
+ status: string;
53
+ }): string | undefined {
54
+ if (call.status !== "completed") return undefined;
55
+ const toolName =
56
+ call.name === "executeTool"
57
+ ? executeToolInputSchema.safeParse(call.input).data?.tool_name
58
+ : call.name;
59
+ if (toolName !== ADD_REVIEW_ITEM_TOOL_NAME) return undefined;
60
+ return addedItemId(call.output);
61
+ }
@@ -602,6 +602,16 @@ function activeConversation(nowMs: number): ConversationDetailReport {
602
602
  },
603
603
  ],
604
604
  }),
605
+ reportEvent(10, iso(Date.parse(startedAt), 46_000), {
606
+ type: "turn_context",
607
+ turnId: "active-turn",
608
+ pluginName: "junior",
609
+ kind: "user_customization",
610
+ version: 1,
611
+ content: {
612
+ text: "Keep replies short and simple.\nLead with the answer, then the evidence.",
613
+ },
614
+ }),
605
615
  ],
606
616
  });
607
617
  }
@@ -28,6 +28,9 @@ import {
28
28
  personalSpendReportSchema,
29
29
  personParamsSchema,
30
30
  pluginOperationalReportFeedSchema,
31
+ reviewItemCountSchema,
32
+ reviewItemListSchema,
33
+ reviewItemWaitingConversationsSchema,
31
34
  automationExecutionListSchema,
32
35
  automationListSchema,
33
36
  automationParamsSchema,
@@ -294,6 +297,23 @@ export function createMockReportingApi(): Hono<{
294
297
  }
295
298
  return errorResponse("Attachment not found.", 404);
296
299
  });
300
+ // Mock reporting has no Review items. Answer the reads here so the
301
+ // dashboard does not query a database that has no review item tables.
302
+ app.get("/review-items", () =>
303
+ jsonResponse(reviewItemListSchema, { items: [] }),
304
+ );
305
+ app.get("/review-items/count", () =>
306
+ jsonResponse(reviewItemCountSchema, { needsViewer: 0 }),
307
+ );
308
+ app.get("/review-items/waiting-conversations", () =>
309
+ jsonResponse(reviewItemWaitingConversationsSchema, { conversationIds: [] }),
310
+ );
311
+ app.get("/review-items/:id", () =>
312
+ errorResponse("Review item not found.", 404),
313
+ );
314
+ app.post("/review-items/:id/actions", () =>
315
+ errorResponse("Review item not found.", 404),
316
+ );
297
317
  app.get("/automations", (c) => {
298
318
  const report = readMockAutomationList();
299
319
  return jsonResponse(
package/src/shell.ts CHANGED
@@ -131,6 +131,9 @@ export function dashboardPagePaths(
131
131
  nested: true,
132
132
  path: basePath === "/" ? "/conversations" : `${basePath}/conversations`,
133
133
  },
134
+ {
135
+ path: basePath === "/" ? "/review" : `${basePath}/review`,
136
+ },
134
137
  {
135
138
  nested: true,
136
139
  path: basePath === "/" ? "/people" : `${basePath}/people`,
package/src/tailwind.css CHANGED
@@ -4,7 +4,7 @@
4
4
  @import "shadcn/tailwind.css";
5
5
 
6
6
  /*
7
- * Dashboard design tokens. components/README.md explains each role.
7
+ * Dashboard design tokens. components/AGENTS.md has the token rules.
8
8
  * Keep structural UI colors here so a second theme can redefine them.
9
9
  * Prefer roles over opacity ladders. Status accents stay as Tailwind palette utilities.
10
10
  */
@@ -1,215 +0,0 @@
1
- # Dashboard UI kit
2
-
3
- Shared dashboard components live here. Feature pages compose these surfaces
4
- instead of copying Tailwind blocks. See `policies/frontend-components.md` for
5
- the policy. `pnpm dashboard-style:check` enforces the token rules below.
6
-
7
- ## shadcn/ui
8
-
9
- `packages/junior-dashboard/components.json` configures shadcn/ui for this package.
10
- It uses Base UI (`base-nova`), Tailwind 4, and Lucide. Add components from the
11
- repository root:
12
-
13
- ```sh
14
- pnpm --filter @sentry/junior-dashboard exec shadcn add <component>
15
- ```
16
-
17
- The CLI writes components to `src/client/components/ui/`. Its `@dashboard/*`
18
- alias points to `src/client/*`. The existing `@/*` alias still points to core.
19
- The `ui/` files join classes with the merging `cn` from
20
- `src/client/lib/utils.ts`, so a caller's `className` wins over a base class.
21
-
22
- The shadcn color tokens in `src/tailwind.css` use the dashboard tokens in both
23
- themes. The `dark:` variant follows `data-theme`. Keep the existing Tailwind
24
- radius scale. Do not apply the CLI's global border or body resets. Check each
25
- added component against the rules below before use.
26
-
27
- Each `ui/` file is shadcn source that uses dashboard tokens. Remove exports
28
- that no app code uses. Use `ui/` components directly. Do not add an app
29
- component that only renames a `ui/` component or its props. Change the `ui/`
30
- file when every caller needs the same style or variant. App components compose
31
- `ui/` source only when they add structure or behavior:
32
-
33
- | App component | shadcn source |
34
- | ------------------------------ | ----------------- |
35
- | `Drawer` | `ui/drawer` |
36
- | `BottomSheet` | `ui/sheet` |
37
- | `layout/DashboardHeader` (nav) | `ui/sheet` |
38
- | `ProfileMenu` (desktop) | `ui/popover` |
39
- | `ActorAvatar` | `ui/avatar` |
40
- | `ParticipantAvatarStack` | `ui/avatar` |
41
- | `SearchInput` | `ui/input-group` |
42
- | `PagePagination` | `ui/pagination` |
43
- | `EmptyTelemetry` | `ui/empty` |
44
- | `TimeRangeSelector` | `ui/toggle-group` |
45
- | `ThemeSwitcher` | `ui/toggle-group` |
46
- | `TranscriptViewToggle` | `ui/toggle-group` |
47
- | `FilterTabList` | `ui/tabs` |
48
- | `charts/ActivityChartTooltip` | `ui/tooltip` |
49
- | `conversations/ToolFrame` | `ui/trace` |
50
- | `TranscriptReasoningView` | `ui/trace` |
51
- | `ImageAttachment` (preview) | `ui/dialog` |
52
- | `Notice`, `InlineError` | `ui/alert` |
53
- | App banners | `ui/alert` |
54
- | Recharts activity charts | `ui/chart` |
55
-
56
- Rules for adopted components:
57
-
58
- - Use `ui/trace` for transcript activity rows. It composes `ui/collapsible`.
59
- A closed tool row unmounts its panel, so it does not run Shiki. Reasoning
60
- sets `hiddenUntilFound`, so browser find reaches closed text. Transcript
61
- search holds rows open and turns off the row button. Put only inline
62
- elements, such as `span`, inside the row button.
63
- - Use `ui/alert-dialog` for every confirmation. Do not use `window.confirm`.
64
- Keep the confirmed item in state after close, so the closing dialog keeps its
65
- text.
66
- - Use `ui/dialog` for a short task form, such as the fork message. Do not put
67
- a form in a `Drawer` or a `DetailsPanel`. On small screens the dialog sits
68
- near the top, so the on-screen keyboard does not cover its fields.
69
- - Use `useSheet` for each app `ui/dialog`, as for sheets. It shares the
70
- document scroll lock. Only the front modal closes on Escape or a backdrop
71
- tap. Mount the dialog only while it is open.
72
- - `ui/alert` sets `role="alert"`. Pass `role="status"` for a message that is
73
- not an error.
74
- - `ui/toast` has no global notification manager. Features call
75
- `useToastManager` inside the mounted `Toaster` provider.
76
- - Put a checkbox or radio inside its `<label>`. Give each `RadioGroup` an
77
- `aria-label`.
78
- - Give an icon-only control its label with `TooltipContent size="compact"`.
79
- Do not use the native `title` attribute. Tooltips open on mouse hover and
80
- keyboard focus only, so a tap still runs the control.
81
- - When a `ui/button` tooltip trigger can be disabled, set
82
- `focusableWhenDisabled`. A native disabled button can drop hover and focus,
83
- so Firefox closes the tooltip and hides pending or result labels.
84
- `ui/button` styles `data-disabled`, so both disabled modes look the same.
85
- - Build a form field from `ui/field`. Give `FieldError` the id
86
- `<control id>-error` and `FieldDescription` the id `<control id>-help`. The
87
- control sets `aria-invalid` and `aria-describedby`.
88
- - Use `Button size="menu"` for a full-width icon and label row in a mobile
89
- overflow menu. A pressed row adds `bg-dashboard-fill-strong`.
90
- - For a link that looks like a button, put `buttonVariants()` on the router
91
- `Link`. A form page has one `Button variant="primary"` for its main save
92
- action.
93
-
94
- ## Foundations
95
-
96
- Tokens live in the `@theme` block of `src/tailwind.css`. Keep every color in a
97
- token so each theme can redefine it.
98
-
99
- - **Themes.** Dark is the default. The light theme redefines every token under
100
- `:root[data-theme="light"]`. It also mirrors the light status and chart
101
- shades (50-400) to their dark pair, so `text-rose-300` stays readable on the
102
- light canvas. The head script in `src/theme.ts` sets `data-theme` before first
103
- paint from the saved choice or the system color scheme. `ThemeSwitcher` saves
104
- the choice for this browser. Use the `light:` variant only for one-off values
105
- that no token covers, such as provider brand colors.
106
- - **Charts.** Use `var(--color-*)` for series colors, not hex values, so they
107
- follow the theme. Name series colors in the `ChartContainer` config and fill
108
- each series with `var(--color-<key>)`.
109
- - **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
110
- `bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
111
- `surface-raised` (buttons, tooltips), `control`, `surface-hover`,
112
- `surface-active` (selected rows). Use `fill-*` for translucent fills and
113
- `overlay-*` for scrims. Do not use `white/*` or `black/*` fills, borders, or
114
- rings, or hex surface colors. Shadows may use black.
115
- - **Borders.** Use `border-subtle` for hairlines inside a surface,
116
- `border` for card edges, `border-strong` for shell edges, and
117
- `border-emphasis` or `border-interactive` for controls and hover.
118
- - **Text.** Brightest to dimmest: `text-solid`, `text`, `text-subtle`,
119
- `text-muted`, `text-faint`.
120
- - **Accent.** Lavender is the one brand accent. Use `accent` for icons,
121
- active markers, and tinted fills, such as `bg-dashboard-accent/10`. Use
122
- `accent-text` for links and accent text. Keep Tailwind palette colors for
123
- status (info, success, warning, error) and for categorical chart, event, and
124
- subagent colors.
125
- - **Focus.** Use `outline-dashboard-focus` for keyboard focus. Do not use other
126
- colors for focus rings.
127
- - **Type faces.** `font-sans` is the UI face for prose, controls, navigation,
128
- labels, and names. `font-display` is for headings, record titles, and large
129
- metric values. `font-mono` is for machine values: code, ids, paths, file
130
- names, tool names, and raw payloads. Show counts, times, and prices in
131
- `font-sans tabular-nums`.
132
- - **Type scale.** Use the named steps `text-2xs` through `text-4xl`. Keep
133
- `text-2xs` for compact badges and chart annotations.
134
- - **Labels.** Section, card, field, and column labels use
135
- `font-sans text-xs font-medium text-dashboard-text-muted` in sentence case.
136
- Do not use uppercase or letter spacing for labels.
137
- - **Radius.** Controls and chips use `rounded-md`. Cards, list rows, and panels
138
- use `rounded-lg`. The composer, menus, and transcript object cards use
139
- `rounded-xl`. Avatars and count badges use `rounded-full`.
140
-
141
- Shared class strings live in `src/client/styles.ts`.
142
-
143
- ## App components
144
-
145
- - **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
146
- when the label must stay visible. `StatusChip` is not a control.
147
- - **Messages.** Use `Notice` for compact transient messages and `FormNotice`
148
- for wrapping form recovery text.
149
- - **Disclosure.** Use `ui/collapsible` for one show-or-hide section. Put a
150
- `ChevronRight` with `dashboardDisclosureChevronClass` in a `group` trigger.
151
- Set `hiddenUntilFound` on the content when browser find must reach closed
152
- text. Closed content then keeps its box, so put margins and padding on a
153
- child. Use `ui/accordion` for a set of sections with headings.
154
- - **Details.** `DetailsPanel` docks an aside at 1280px and above, and uses a
155
- `BottomSheet` below that width. It owns Escape and the scroll frame for
156
- docked panels. Docked actions sit beside the close control, so their
157
- position does not change with the title width.
158
- - **Drawer.** `Drawer` is a right-side `ui/drawer`. A `Drawer` opened from
159
- inside another `Drawer` stacks in front of it. The parent scales back,
160
- shows a thin edge behind the child, and hides its content. Set `flush` when
161
- the content owns its insets and sticky headers, such as the event log.
162
- - **Identity.** `ObjectIcon` owns the native Ticket, Code change, Automation,
163
- Deployment, and Item icons. Do not replace it with a provider logo or a
164
- status-only icon.
165
- - **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
166
- variants for page bodies. Use `PageRouteLoading` when a page keeps its real
167
- header above a body skeleton. Use a feature-owned loading component for
168
- distinct geometry, such as conversations. Use `ui/spinner` for an inline
169
- pending icon.
170
-
171
- ## Navigation
172
-
173
- The dashboard has three navigation levels. Each has its own look, so the
174
- levels stay distinct.
175
-
176
- - **App navigation** in `layout/DashboardHeader` uses pills.
177
- - **Page links** use `SegmentedNav`, through `layout/SecondaryNavigation`. They
178
- are underline tabs that sit on the header border.
179
- - **Panel switches** use `ui/tabs`. Give `TabsList` an `aria-label`. Set
180
- `activateOnFocus` so arrow keys select the tab.
181
-
182
- Keep collection filters in `FilterTabList`. It uses `TabsList variant="line"`
183
- and adds the count badges.
184
-
185
- ## Patterns
186
-
187
- - **Layout.** `layout/` holds the shell chrome, page headers, cards, and
188
- section titles. `DashboardChrome` owns the sticky header block and its one
189
- bottom border.
190
- - **Charts.** Put `role="img"` and the chart `aria-label` on the Recharts
191
- chart, so the tooltip live region stays outside the image. Day and hour
192
- charts compose the `charts/ActivityChart` axis, average line, and tooltip
193
- helpers. They do not format UTC bucket keys inline.
194
- - **Conversation mobile geometry** is a hard ownership edge:
195
- - `layout/VisualViewportShell` owns the fixed shell, visualViewport CSS
196
- variables, and body lock.
197
- - `conversations/ChatLayout` owns the scroll-above-dock frame for reply
198
- threads.
199
- - `conversations/ComposerDock` owns the reply bottom pad and dock chrome.
200
-
201
- Pages pass children only. Home and create are one landing surface, not a
202
- dock, and must not use `ComposerDock`. Do not add viewport height, offset, or
203
- bottom safe-area math in page modules.
204
-
205
- Feature surfaces stay under `conversations/` or `pages/` until two real callers
206
- share them. See [Transcript components](../conversations/README.md) for
207
- transcript ownership.
208
-
209
- ## Rules
210
-
211
- - Prefer a named component on the second real use.
212
- - Keep Tailwind classes on the owning component.
213
- - Check new or changed shared components on a real page with mock data and in
214
- the dashboard E2E screenshots.
215
- - One-off page glue may stay inline when extraction only adds indirection.
@@ -1,38 +0,0 @@
1
- # System cache reporting
2
-
3
- `InputCacheChart` owns the cache trend, summary, and largest writes.
4
- `SystemActivity` selects its time buckets with the page reporting control.
5
- `SystemMetricCharts` keeps spend and runtime beside the cache view.
6
-
7
- ## Input counters
8
-
9
- `ConversationMetricDay` comes from stored Run metrics, not Sentry spans.
10
- Its ordinary input, cached input, and cache-creation counters are disjoint.
11
- Add these three counters once to get total input. Do not include output.
12
- Sentry span input totals use a different rule and must not use this transform.
13
-
14
- `cache-input.ts` owns this calculation. Period shares use summed token counts,
15
- not the mean of bucket shares. A missing counter is not a reported zero.
16
- If an active bucket lacks a counter, omit its share and the whole-period share.
17
- Token mode can still show reported counts. Gray marks identify incomplete
18
- buckets. Empty buckets do not lower shares.
19
-
20
- Field presence is not call-level coverage. SQL sums can combine complete and
21
- incomplete Runs in one bucket. Do not describe these values as a cache hit rate
22
- or a cache failure rate.
23
-
24
- ## Investigation
25
-
26
- `CacheWrites` lists the three largest write counts in the page time window.
27
- The list and chart consume the same selected buckets. There is no local time
28
- selector or comparison window. The list is collapsed until needed.
29
-
30
- The chart fills the available width. It keeps a fixed, compact height rather
31
- than growing taller on wide screens.
32
-
33
- This report has no per-call model, release, environment, trace, or write-cost
34
- fields. Add a reporting contract before adding those filters or drilldowns.
35
- A large write alone cannot identify cache expiry, a cold start, or prefix loss.
36
-
37
- The System browser scenario covers display modes and verifies that the page
38
- time control also filters the list.