@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.
- package/README.md +7 -105
- package/dist/app.js +47 -8
- package/dist/client.js +74 -78
- package/dist/index.js +47 -8
- package/dist/tailwind.css +1 -1
- package/package.json +4 -4
- package/src/api/schema.ts +2 -1
- package/src/app.ts +11 -7
- package/src/client/App.tsx +20 -4
- package/src/client/api.ts +4 -19
- package/src/client/components/AGENTS.md +65 -0
- package/src/client/components/ActorAvatar.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +3 -1
- package/src/client/components/ProfileMenu.tsx +88 -29
- package/src/client/components/UserInputForm.tsx +283 -0
- package/src/client/components/charts/AGENTS.md +15 -0
- package/src/client/components/layout/DashboardHeader.tsx +13 -0
- package/src/client/components/ui/button.tsx +11 -1
- package/src/client/conversations/AGENTS.md +42 -0
- package/src/client/conversations/ConversationHomeList.tsx +6 -0
- package/src/client/conversations/ConversationListStatusIcon.tsx +15 -1
- package/src/client/conversations/ConversationPage.tsx +4 -27
- package/src/client/conversations/ConversationSidebar.tsx +6 -0
- package/src/client/conversations/ConversationWorkspace.tsx +4 -0
- package/src/client/conversations/TranscriptActivityGroup.tsx +5 -0
- package/src/client/conversations/TranscriptMessageContextView.tsx +12 -1
- package/src/client/conversations/TranscriptMessageView.tsx +3 -0
- package/src/client/conversations/TranscriptToolView.tsx +13 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +142 -144
- package/src/client/conversations/turnContext.ts +15 -0
- package/src/client/format.ts +28 -28
- package/src/client/pages/SettingsPage.tsx +103 -3
- package/src/client/pages/automations/AGENTS.md +34 -0
- package/src/client/pages/automations/AutomationActions.tsx +18 -20
- package/src/client/pages/automations/AutomationEditor.tsx +3 -4
- package/src/client/pages/automations/AutomationOverview.tsx +3 -3
- package/src/client/pages/automations/AutomationStatus.tsx +2 -2
- package/src/client/pages/code/RepositorySections.tsx +1 -0
- package/src/client/pages/code/RepositoryTabs.tsx +3 -0
- package/src/client/pages/system/AGENTS.md +12 -0
- package/src/client/primaryNav.ts +15 -2
- package/src/client/review-desk/ConversationReviewBar.tsx +68 -0
- package/src/client/review-desk/ReviewDeskPage.tsx +297 -0
- package/src/client/review-desk/ReviewItemActions.tsx +165 -0
- package/src/client/review-desk/ReviewItemCard.tsx +47 -0
- package/src/client/review-desk/ReviewItemDetail.tsx +123 -0
- package/src/client/review-desk/queries.ts +173 -0
- package/src/client/review-desk/reviewItemFormat.ts +61 -0
- package/src/mock-reporting/fixtures.ts +10 -0
- package/src/mock-reporting/routes.ts +20 -0
- package/src/shell.ts +3 -0
- package/src/tailwind.css +1 -1
- package/src/client/components/README.md +0 -215
- package/src/client/components/charts/README.md +0 -38
- package/src/client/conversations/README.md +0 -159
- package/src/client/conversations/UserInputRequestCard.tsx +0 -244
- package/src/client/conversations/userInputRequest.ts +0 -56
- package/src/client/pages/automations/README.md +0 -97
- 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/
|
|
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.
|