@sentry/junior-dashboard 0.268.0 → 0.270.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 +12 -9
- package/dist/client.js +81 -81
- package/dist/index.js +12 -9
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +2 -1
- package/src/app.ts +11 -7
- package/src/client/App.tsx +15 -9
- 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/AutomationCard.tsx +2 -1
- package/src/client/components/ParticipantAvatarStack.tsx +1 -0
- package/src/client/components/ProfileMenu.tsx +88 -29
- package/src/client/components/UserInputForm.tsx +110 -37
- package/src/client/components/charts/AGENTS.md +15 -0
- package/src/client/conversations/AGENTS.md +38 -0
- package/src/client/conversations/ConversationBrief.tsx +55 -36
- package/src/client/conversations/ConversationComposer.tsx +27 -8
- package/src/client/conversations/ConversationEventLog.tsx +1 -0
- package/src/client/conversations/ConversationHomeList.tsx +43 -26
- package/src/client/conversations/ConversationPage.tsx +90 -56
- package/src/client/conversations/ConversationSectionHeading.tsx +44 -0
- package/src/client/conversations/ConversationSidebar.tsx +55 -25
- package/src/client/conversations/ConversationTranscript.tsx +3 -2
- package/src/client/conversations/ConversationWatches.tsx +4 -1
- package/src/client/conversations/ConversationWorkspace.tsx +43 -25
- package/src/client/conversations/EventDetails.tsx +6 -9
- package/src/client/conversations/PendingMailboxStack.tsx +5 -2
- package/src/client/conversations/TranscriptContextEventView.tsx +3 -4
- package/src/client/conversations/TranscriptEventView.tsx +9 -3
- package/src/client/conversations/TranscriptMessageView.tsx +6 -5
- package/src/client/conversations/TranscriptReasoningView.tsx +4 -5
- package/src/client/conversations/TranscriptRows.tsx +6 -1
- package/src/client/conversations/TranscriptStructuredEventView.tsx +5 -4
- package/src/client/conversations/TranscriptToolView.tsx +2 -1
- package/src/client/conversations/TranscriptView.tsx +6 -6
- package/src/client/conversations/conversationOutbox.ts +13 -3
- package/src/client/conversations/conversationSections.ts +36 -8
- package/src/client/conversations/eventTranscript.ts +1 -0
- package/src/client/conversations/transcriptBottomPinning.ts +171 -81
- package/src/client/conversations/transcriptScrollAnchor.ts +101 -0
- package/src/client/conversations/useCollapsedConversationSections.ts +48 -0
- package/src/client/display-text/DisplayText.tsx +68 -0
- package/src/client/display-text/emoji.ts +54 -0
- package/src/client/{conversations/transcriptMarkdownLinks.ts → display-text/links.ts} +16 -3
- package/src/client/{conversations/TranscriptMarkdown.tsx → display-text/markdown.tsx} +83 -19
- package/src/client/display-text/mentions.tsx +148 -0
- package/src/client/display-text/plainText.ts +29 -0
- package/src/client/format.ts +7 -23
- 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/AutomationFilters.tsx +6 -6
- package/src/client/pages/automations/AutomationOverview.tsx +5 -5
- package/src/client/pages/automations/AutomationStatus.tsx +2 -2
- package/src/client/pages/automations/AutomationVersionsTab.tsx +12 -8
- package/src/client/pages/code/RepositoryTabs.tsx +6 -1
- package/src/client/pages/memory/MemoryDetails.tsx +5 -13
- package/src/client/pages/system/AGENTS.md +12 -0
- package/src/client/review-desk/ConversationReviewBar.tsx +14 -9
- package/src/client/review-desk/ReviewDeskPage.tsx +99 -79
- package/src/client/review-desk/ReviewItemActions.tsx +6 -2
- package/src/client/review-desk/ReviewItemCard.tsx +58 -17
- package/src/client/review-desk/ReviewItemDetail.tsx +17 -9
- package/src/client/review-desk/UnsentAnswers.tsx +148 -0
- package/src/client/review-desk/queries.ts +25 -16
- package/src/client/review-desk/reviewItemFormat.ts +29 -1
- package/src/mock-reporting/routes.ts +1 -1
- package/src/tailwind.css +1 -1
- package/src/client/components/README.md +0 -219
- package/src/client/components/charts/README.md +0 -38
- package/src/client/conversations/README.md +0 -159
- package/src/client/conversations/TranscriptText.tsx +0 -47
- package/src/client/conversations/UserInputRequestCard.tsx +0 -39
- package/src/client/conversations/conversationActivityPreview.ts +0 -16
- package/src/client/conversations/transcriptMentions.tsx +0 -134
- 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,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useId,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
12
|
+
import type { ReviewItemPayload } from "@sentry/junior/api/schema";
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
AlertDialog,
|
|
16
|
+
AlertDialogAction,
|
|
17
|
+
AlertDialogCancel,
|
|
18
|
+
AlertDialogContent,
|
|
19
|
+
AlertDialogDescription,
|
|
20
|
+
AlertDialogFooter,
|
|
21
|
+
AlertDialogTitle,
|
|
22
|
+
} from "../components/ui/alert-dialog";
|
|
23
|
+
|
|
24
|
+
type UnsentAnswers = {
|
|
25
|
+
conversationId: string;
|
|
26
|
+
/** Item title for each mounted answer form that has unsent answers. */
|
|
27
|
+
titles: Map<string, string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const UnsentAnswersContext = createContext<UnsentAnswers | undefined>(
|
|
31
|
+
undefined,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Track the answer forms in one Conversation that have unsent answers. A
|
|
36
|
+
* message in the Conversation closes some open action items, and the
|
|
37
|
+
* answers on those forms are then lost. The reply footer reads this to ask
|
|
38
|
+
* first.
|
|
39
|
+
*/
|
|
40
|
+
export function UnsentAnswersProvider(props: {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
conversationId: string;
|
|
43
|
+
}) {
|
|
44
|
+
const value = useMemo(
|
|
45
|
+
() => ({ conversationId: props.conversationId, titles: new Map() }),
|
|
46
|
+
[props.conversationId],
|
|
47
|
+
);
|
|
48
|
+
return (
|
|
49
|
+
<UnsentAnswersContext.Provider value={value}>
|
|
50
|
+
{props.children}
|
|
51
|
+
</UnsentAnswersContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Report that the answer form of an item has unsent answers. It counts only
|
|
57
|
+
* when a message from the viewer closes the item. This follows
|
|
58
|
+
* `closeReviewItemsAnsweredByReply`: an open action item that the agent
|
|
59
|
+
* added in this Conversation, not assigned to Everyone, that the viewer can
|
|
60
|
+
* act on. Outside a Conversation it does nothing.
|
|
61
|
+
*/
|
|
62
|
+
export function useReportUnsentAnswer(
|
|
63
|
+
item: ReviewItemPayload,
|
|
64
|
+
unsent: boolean,
|
|
65
|
+
) {
|
|
66
|
+
const context = useContext(UnsentAnswersContext);
|
|
67
|
+
const key = useId();
|
|
68
|
+
const closesOnReply =
|
|
69
|
+
item.kind === "action" &&
|
|
70
|
+
item.status === "open" &&
|
|
71
|
+
item.canAct &&
|
|
72
|
+
!item.assignedToEveryone &&
|
|
73
|
+
item.addedByConversationId === context?.conversationId;
|
|
74
|
+
const title = item.title;
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!context || !closesOnReply || !unsent) return;
|
|
77
|
+
context.titles.set(key, title);
|
|
78
|
+
return () => {
|
|
79
|
+
context.titles.delete(key);
|
|
80
|
+
};
|
|
81
|
+
}, [closesOnReply, context, key, title, unsent]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Ask the reader before a message drops unsent answers. `confirmSubmit`
|
|
86
|
+
* returns undefined when nothing would be lost, so the send goes at once.
|
|
87
|
+
* Otherwise it opens `dialog` and resolves true when the reader sends anyway.
|
|
88
|
+
*/
|
|
89
|
+
export function useConfirmUnsentAnswers() {
|
|
90
|
+
const context = useContext(UnsentAnswersContext);
|
|
91
|
+
const [open, setOpen] = useState(false);
|
|
92
|
+
// Keep the titles through the close animation.
|
|
93
|
+
const [titles, setTitles] = useState<string[]>([]);
|
|
94
|
+
const resolveRef = useRef<(send: boolean) => void>(undefined);
|
|
95
|
+
|
|
96
|
+
const confirmSubmit = useCallback(() => {
|
|
97
|
+
// The transcript card and the side panel can show the same item.
|
|
98
|
+
const unsent = [...new Set(context?.titles.values())];
|
|
99
|
+
if (unsent.length === 0) return undefined;
|
|
100
|
+
setTitles(unsent);
|
|
101
|
+
setOpen(true);
|
|
102
|
+
return new Promise<boolean>((resolve) => {
|
|
103
|
+
resolveRef.current = resolve;
|
|
104
|
+
});
|
|
105
|
+
}, [context]);
|
|
106
|
+
|
|
107
|
+
// Resolve on close, so a dismissal by Escape also keeps the draft.
|
|
108
|
+
const close = (send: boolean) => {
|
|
109
|
+
resolveRef.current?.(send);
|
|
110
|
+
resolveRef.current = undefined;
|
|
111
|
+
setOpen(false);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const dialog = (
|
|
115
|
+
<AlertDialog
|
|
116
|
+
onOpenChange={(open) => {
|
|
117
|
+
if (!open) close(false);
|
|
118
|
+
}}
|
|
119
|
+
open={open}
|
|
120
|
+
>
|
|
121
|
+
<AlertDialogContent>
|
|
122
|
+
<AlertDialogTitle>Send without your answers?</AlertDialogTitle>
|
|
123
|
+
<AlertDialogDescription>
|
|
124
|
+
{titles.length === 1 ? (
|
|
125
|
+
<>
|
|
126
|
+
Your answers to{" "}
|
|
127
|
+
<strong className="font-medium text-dashboard-text">
|
|
128
|
+
{titles[0]}
|
|
129
|
+
</strong>{" "}
|
|
130
|
+
are not sent. This message closes that review item, and the
|
|
131
|
+
answers are lost.
|
|
132
|
+
</>
|
|
133
|
+
) : (
|
|
134
|
+
`Your answers to ${titles.length} review items are not sent. This message closes those items, and the answers are lost.`
|
|
135
|
+
)}
|
|
136
|
+
</AlertDialogDescription>
|
|
137
|
+
<AlertDialogFooter>
|
|
138
|
+
<AlertDialogCancel>Go back</AlertDialogCancel>
|
|
139
|
+
<AlertDialogAction onClick={() => close(true)} variant="danger">
|
|
140
|
+
Send message
|
|
141
|
+
</AlertDialogAction>
|
|
142
|
+
</AlertDialogFooter>
|
|
143
|
+
</AlertDialogContent>
|
|
144
|
+
</AlertDialog>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
return { confirmSubmit, dialog };
|
|
148
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import {
|
|
3
3
|
keepPreviousData,
|
|
4
|
+
useInfiniteQuery,
|
|
4
5
|
useMutation,
|
|
5
6
|
useQueries,
|
|
6
7
|
useQuery,
|
|
@@ -19,38 +20,46 @@ import {
|
|
|
19
20
|
import { conversationDetailTitle } from "../format";
|
|
20
21
|
import { fetchDashboardJson, post } from "../http";
|
|
21
22
|
|
|
22
|
-
/** Review desk views. Each shows open items, or archived ones. */
|
|
23
|
-
export const REVIEW_DESK_VIEWS = ["mine", "everyone", "all"] as const;
|
|
24
|
-
export type ReviewDeskView = (typeof REVIEW_DESK_VIEWS)[number];
|
|
25
|
-
|
|
26
23
|
const REVIEW_ITEMS_QUERY_KEY = ["dashboard", "review-items"] as const;
|
|
27
24
|
const COUNT_REFRESH_MS = 60_000;
|
|
28
25
|
|
|
29
|
-
/**
|
|
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
|
+
*/
|
|
30
31
|
export function useReviewItemsData(filters: {
|
|
31
|
-
archived: boolean;
|
|
32
32
|
conversationId?: string;
|
|
33
33
|
q?: string;
|
|
34
|
-
view: ReviewDeskView;
|
|
35
34
|
}) {
|
|
36
|
-
const params = new URLSearchParams(
|
|
37
|
-
if (filters.archived) params.set("archived", "true");
|
|
35
|
+
const params = new URLSearchParams();
|
|
38
36
|
if (filters.conversationId) {
|
|
39
37
|
params.set("conversationId", filters.conversationId);
|
|
40
38
|
}
|
|
41
39
|
if (filters.q) params.set("q", filters.q);
|
|
42
40
|
const search = params.toString();
|
|
43
|
-
|
|
41
|
+
const query = useInfiniteQuery({
|
|
42
|
+
initialPageParam: undefined as string | undefined,
|
|
44
43
|
queryKey: [...REVIEW_ITEMS_QUERY_KEY, "list", search],
|
|
45
|
-
queryFn: ({ signal }) =>
|
|
46
|
-
|
|
44
|
+
queryFn: ({ pageParam, signal }) => {
|
|
45
|
+
const pageParams = new URLSearchParams(search);
|
|
46
|
+
if (pageParam) pageParams.set("cursor", pageParam);
|
|
47
|
+
return fetchDashboardJson(
|
|
47
48
|
reviewItemListSchema,
|
|
48
|
-
`/api/review-items?${
|
|
49
|
+
`/api/review-items?${pageParams.toString()}`,
|
|
49
50
|
signal,
|
|
50
|
-
)
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
51
54
|
placeholderData: keepPreviousData,
|
|
52
55
|
retry: false,
|
|
53
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 };
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
/** Read one Review item, for a link to an item outside the current list. */
|
|
@@ -92,7 +101,7 @@ export function useLinkedConversationTitles(
|
|
|
92
101
|
}).map((query) => query.data ?? undefined);
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
/** Count the open
|
|
104
|
+
/** Count the open action items assigned to the viewer, for the nav. */
|
|
96
105
|
export function useReviewItemCountData(enabled: boolean) {
|
|
97
106
|
return useQuery({
|
|
98
107
|
enabled,
|
|
@@ -145,7 +154,7 @@ const NO_CONVERSATIONS: ReadonlySet<string> = new Set();
|
|
|
145
154
|
|
|
146
155
|
/**
|
|
147
156
|
* Return the Conversations that wait on the viewer. Today only the review
|
|
148
|
-
* desk sets this:
|
|
157
|
+
* desk sets this: a linked open action item assigned to the viewer.
|
|
149
158
|
*/
|
|
150
159
|
export function useWaitingConversationIds(): ReadonlySet<string> {
|
|
151
160
|
const query = useQuery({
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ConversationReportEvent,
|
|
4
|
+
ReviewItemPayload,
|
|
5
|
+
} from "@sentry/junior/api/schema";
|
|
3
6
|
|
|
4
7
|
/** Return the review desk route that selects one item. */
|
|
5
8
|
export function reviewItemPath(id: string): string {
|
|
@@ -59,3 +62,28 @@ export function reviewItemIdFromToolCall(call: {
|
|
|
59
62
|
if (toolName !== ADD_REVIEW_ITEM_TOOL_NAME) return undefined;
|
|
60
63
|
return addedItemId(call.output);
|
|
61
64
|
}
|
|
65
|
+
|
|
66
|
+
/** Return the id of the last Review item that a tool call in the events added. */
|
|
67
|
+
export function latestReviewItemId(
|
|
68
|
+
events: readonly ConversationReportEvent[] | undefined,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
for (let index = (events?.length ?? 0) - 1; index >= 0; index -= 1) {
|
|
71
|
+
const data = events![index]!.data;
|
|
72
|
+
if (data.type !== "tool_calls") continue;
|
|
73
|
+
for (let call = data.calls.length - 1; call >= 0; call -= 1) {
|
|
74
|
+
const id = reviewItemIdFromToolCall(data.calls[call]!);
|
|
75
|
+
if (id) return id;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** True when an open item asks a question that the viewer can answer. */
|
|
82
|
+
export function reviewItemWaitsForAnswer(item: ReviewItemPayload): boolean {
|
|
83
|
+
return (
|
|
84
|
+
item.status === "open" &&
|
|
85
|
+
item.kind === "action" &&
|
|
86
|
+
Boolean(item.request) &&
|
|
87
|
+
item.canAct
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -303,7 +303,7 @@ export function createMockReportingApi(): Hono<{
|
|
|
303
303
|
jsonResponse(reviewItemListSchema, { items: [] }),
|
|
304
304
|
);
|
|
305
305
|
app.get("/review-items/count", () =>
|
|
306
|
-
jsonResponse(reviewItemCountSchema, {
|
|
306
|
+
jsonResponse(reviewItemCountSchema, { needsViewer: 0 }),
|
|
307
307
|
);
|
|
308
308
|
app.get("/review-items/waiting-conversations", () =>
|
|
309
309
|
jsonResponse(reviewItemWaitingConversationsSchema, { conversationIds: [] }),
|
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,219 +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
|
-
- Use `Button variant="quiet"` for a secondary action beside the primary one,
|
|
94
|
-
such as Dismiss. It has no border, so the primary action stays dominant.
|
|
95
|
-
- In a form, show each choice as a whole bordered row around `RadioGroupItem`
|
|
96
|
-
or `Checkbox`. The row shows hover and selected state. See `UserInputForm`.
|
|
97
|
-
|
|
98
|
-
## Foundations
|
|
99
|
-
|
|
100
|
-
Tokens live in the `@theme` block of `src/tailwind.css`. Keep every color in a
|
|
101
|
-
token so each theme can redefine it.
|
|
102
|
-
|
|
103
|
-
- **Themes.** Dark is the default. The light theme redefines every token under
|
|
104
|
-
`:root[data-theme="light"]`. It also mirrors the light status and chart
|
|
105
|
-
shades (50-400) to their dark pair, so `text-rose-300` stays readable on the
|
|
106
|
-
light canvas. The head script in `src/theme.ts` sets `data-theme` before first
|
|
107
|
-
paint from the saved choice or the system color scheme. `ThemeSwitcher` saves
|
|
108
|
-
the choice for this browser. Use the `light:` variant only for one-off values
|
|
109
|
-
that no token covers, such as provider brand colors.
|
|
110
|
-
- **Charts.** Use `var(--color-*)` for series colors, not hex values, so they
|
|
111
|
-
follow the theme. Name series colors in the `ChartContainer` config and fill
|
|
112
|
-
each series with `var(--color-<key>)`.
|
|
113
|
-
- **Surfaces.** Use the surface ladder, darkest to lightest: `bg`,
|
|
114
|
-
`bg-elevated` (drawers), `surface-panel` (sidebar, cards, bubbles),
|
|
115
|
-
`surface-raised` (buttons, tooltips), `control`, `surface-hover`,
|
|
116
|
-
`surface-active` (selected rows). Use `fill-*` for translucent fills and
|
|
117
|
-
`overlay-*` for scrims. Do not use `white/*` or `black/*` fills, borders, or
|
|
118
|
-
rings, or hex surface colors. Shadows may use black.
|
|
119
|
-
- **Borders.** Use `border-subtle` for hairlines inside a surface,
|
|
120
|
-
`border` for card edges, `border-strong` for shell edges, and
|
|
121
|
-
`border-emphasis` or `border-interactive` for controls and hover.
|
|
122
|
-
- **Text.** Brightest to dimmest: `text-solid`, `text`, `text-subtle`,
|
|
123
|
-
`text-muted`, `text-faint`.
|
|
124
|
-
- **Accent.** Lavender is the one brand accent. Use `accent` for icons,
|
|
125
|
-
active markers, and tinted fills, such as `bg-dashboard-accent/10`. Use
|
|
126
|
-
`accent-text` for links and accent text. Keep Tailwind palette colors for
|
|
127
|
-
status (info, success, warning, error) and for categorical chart, event, and
|
|
128
|
-
subagent colors.
|
|
129
|
-
- **Focus.** Use `outline-dashboard-focus` for keyboard focus. Do not use other
|
|
130
|
-
colors for focus rings.
|
|
131
|
-
- **Type faces.** `font-sans` is the UI face for prose, controls, navigation,
|
|
132
|
-
labels, and names. `font-display` is for headings, record titles, and large
|
|
133
|
-
metric values. `font-mono` is for machine values: code, ids, paths, file
|
|
134
|
-
names, tool names, and raw payloads. Show counts, times, and prices in
|
|
135
|
-
`font-sans tabular-nums`.
|
|
136
|
-
- **Type scale.** Use the named steps `text-2xs` through `text-4xl`. Keep
|
|
137
|
-
`text-2xs` for compact badges and chart annotations.
|
|
138
|
-
- **Labels.** Section, card, field, and column labels use
|
|
139
|
-
`font-sans text-xs font-medium text-dashboard-text-muted` in sentence case.
|
|
140
|
-
Do not use uppercase or letter spacing for labels.
|
|
141
|
-
- **Radius.** Controls and chips use `rounded-md`. Cards, list rows, and panels
|
|
142
|
-
use `rounded-lg`. The composer, menus, and transcript object cards use
|
|
143
|
-
`rounded-xl`. Avatars and count badges use `rounded-full`.
|
|
144
|
-
|
|
145
|
-
Shared class strings live in `src/client/styles.ts`.
|
|
146
|
-
|
|
147
|
-
## App components
|
|
148
|
-
|
|
149
|
-
- **Status.** Use `StatusDot` for dense table or list markers. Use `StatusChip`
|
|
150
|
-
when the label must stay visible. `StatusChip` is not a control.
|
|
151
|
-
- **Messages.** Use `Notice` for compact transient messages and `FormNotice`
|
|
152
|
-
for wrapping form recovery text.
|
|
153
|
-
- **Disclosure.** Use `ui/collapsible` for one show-or-hide section. Put a
|
|
154
|
-
`ChevronRight` with `dashboardDisclosureChevronClass` in a `group` trigger.
|
|
155
|
-
Set `hiddenUntilFound` on the content when browser find must reach closed
|
|
156
|
-
text. Closed content then keeps its box, so put margins and padding on a
|
|
157
|
-
child. Use `ui/accordion` for a set of sections with headings.
|
|
158
|
-
- **Details.** `DetailsPanel` docks an aside at 1280px and above, and uses a
|
|
159
|
-
`BottomSheet` below that width. It owns Escape and the scroll frame for
|
|
160
|
-
docked panels. Docked actions sit beside the close control, so their
|
|
161
|
-
position does not change with the title width.
|
|
162
|
-
- **Drawer.** `Drawer` is a right-side `ui/drawer`. A `Drawer` opened from
|
|
163
|
-
inside another `Drawer` stacks in front of it. The parent scales back,
|
|
164
|
-
shows a thin edge behind the child, and hides its content. Set `flush` when
|
|
165
|
-
the content owns its insets and sticky headers, such as the event log.
|
|
166
|
-
- **Identity.** `ObjectIcon` owns the native Ticket, Code change, Automation,
|
|
167
|
-
Deployment, and Item icons. Do not replace it with a provider logo or a
|
|
168
|
-
status-only icon.
|
|
169
|
-
- **Loading.** Use `ui/skeleton` for placeholder leaves and `PageContentSkeleton`
|
|
170
|
-
variants for page bodies. Use `PageRouteLoading` when a page keeps its real
|
|
171
|
-
header above a body skeleton. Use a feature-owned loading component for
|
|
172
|
-
distinct geometry, such as conversations. Use `ui/spinner` for an inline
|
|
173
|
-
pending icon.
|
|
174
|
-
|
|
175
|
-
## Navigation
|
|
176
|
-
|
|
177
|
-
The dashboard has three navigation levels. Each has its own look, so the
|
|
178
|
-
levels stay distinct.
|
|
179
|
-
|
|
180
|
-
- **App navigation** in `layout/DashboardHeader` uses pills.
|
|
181
|
-
- **Page links** use `SegmentedNav`, through `layout/SecondaryNavigation`. They
|
|
182
|
-
are underline tabs that sit on the header border.
|
|
183
|
-
- **Panel switches** use `ui/tabs`. Give `TabsList` an `aria-label`. Set
|
|
184
|
-
`activateOnFocus` so arrow keys select the tab.
|
|
185
|
-
|
|
186
|
-
Keep collection filters in `FilterTabList`. It uses `TabsList variant="line"`
|
|
187
|
-
and adds the count badges.
|
|
188
|
-
|
|
189
|
-
## Patterns
|
|
190
|
-
|
|
191
|
-
- **Layout.** `layout/` holds the shell chrome, page headers, cards, and
|
|
192
|
-
section titles. `DashboardChrome` owns the sticky header block and its one
|
|
193
|
-
bottom border.
|
|
194
|
-
- **Charts.** Put `role="img"` and the chart `aria-label` on the Recharts
|
|
195
|
-
chart, so the tooltip live region stays outside the image. Day and hour
|
|
196
|
-
charts compose the `charts/ActivityChart` axis, average line, and tooltip
|
|
197
|
-
helpers. They do not format UTC bucket keys inline.
|
|
198
|
-
- **Conversation mobile geometry** is a hard ownership edge:
|
|
199
|
-
- `layout/VisualViewportShell` owns the fixed shell, visualViewport CSS
|
|
200
|
-
variables, and body lock.
|
|
201
|
-
- `conversations/ChatLayout` owns the scroll-above-dock frame for reply
|
|
202
|
-
threads.
|
|
203
|
-
- `conversations/ComposerDock` owns the reply bottom pad and dock chrome.
|
|
204
|
-
|
|
205
|
-
Pages pass children only. Home and create are one landing surface, not a
|
|
206
|
-
dock, and must not use `ComposerDock`. Do not add viewport height, offset, or
|
|
207
|
-
bottom safe-area math in page modules.
|
|
208
|
-
|
|
209
|
-
Feature surfaces stay under `conversations/` or `pages/` until two real callers
|
|
210
|
-
share them. See [Transcript components](../conversations/README.md) for
|
|
211
|
-
transcript ownership.
|
|
212
|
-
|
|
213
|
-
## Rules
|
|
214
|
-
|
|
215
|
-
- Prefer a named component on the second real use.
|
|
216
|
-
- Keep Tailwind classes on the owning component.
|
|
217
|
-
- Check new or changed shared components on a real page with mock data and in
|
|
218
|
-
the dashboard E2E screenshots.
|
|
219
|
-
- 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.
|