@sentry/junior-dashboard 0.279.0 → 0.281.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/dist/app.js +105 -43
- package/dist/client-manifest.json +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +105 -43
- package/dist/public/chunks/AdminPage-M5RBVZFN.js +1 -0
- package/dist/public/chunks/AutomationPage-WALCREDT.js +3 -0
- package/dist/public/chunks/AutomationRunsPage-CV47E4UX.js +1 -0
- package/dist/public/chunks/AutomationsPage-57L3AQVJ.js +1 -0
- package/dist/public/chunks/CodePage-XJUYMFJH.js +1 -0
- package/dist/public/chunks/ConnectionsPage-3QQAGYWF.js +1 -0
- package/dist/public/chunks/DrawerContent-RYYS57ZH.js +1 -0
- package/dist/public/chunks/LocationDetailPage-4IL5HQML.js +1 -0
- package/dist/public/chunks/LocationsPage-D2DUQAHJ.js +1 -0
- package/dist/public/chunks/McpSettingsPage-3RT7JPEK.js +1 -0
- package/dist/public/chunks/PeoplePage-6F5GPQ7C.js +1 -0
- package/dist/public/chunks/PersonProfilePage-SPAYQPTD.js +1 -0
- package/dist/public/chunks/PersonalTokensPage-SHAUJ74M.js +1 -0
- package/dist/public/chunks/PluginUserPage-64FIWD7M.js +1 -0
- package/dist/public/chunks/RepositoryPage-L7IHOXSN.js +1 -0
- package/dist/public/chunks/SettingsPage-U7WG4FHE.js +1 -0
- package/dist/public/chunks/SystemPage-ZBC43OJH.js +1 -0
- package/dist/public/chunks/WorkspaceFormPage-3NVIIZ4P.js +2 -0
- package/dist/public/chunks/WorkspacesPage-G73HPBDD.js +1 -0
- package/dist/public/chunks/chunk-2BCS5AQL.js +1 -0
- package/dist/public/chunks/chunk-2J3LGF5Q.js +1 -0
- package/dist/public/chunks/chunk-2NNKHSZE.js +1 -0
- package/dist/public/chunks/chunk-3Y3ICT6K.js +1 -0
- package/dist/public/chunks/chunk-4AKUZW6R.js +1 -0
- package/dist/public/chunks/chunk-4TQSTCIN.js +13 -0
- package/dist/public/chunks/chunk-4UGZNVJM.js +1 -0
- package/dist/public/chunks/chunk-564V7YHG.js +1 -0
- package/dist/public/chunks/chunk-5ISD3QSC.js +1 -0
- package/dist/public/chunks/chunk-5LNGNJ3P.js +1 -0
- package/dist/public/chunks/chunk-5ZA674OU.js +1 -0
- package/dist/public/chunks/chunk-64GPEGGE.js +1 -0
- package/dist/public/chunks/chunk-6FHY65EQ.js +1 -0
- package/dist/public/chunks/chunk-6FSJ3W2W.js +1 -0
- package/dist/public/chunks/chunk-6PHJZPWD.js +19 -0
- package/dist/public/chunks/chunk-B5ZN3SWQ.js +14 -0
- package/dist/public/chunks/chunk-BKOACADP.js +1 -0
- package/dist/public/chunks/chunk-CQY6BYUB.js +1 -0
- package/dist/public/chunks/chunk-CR2ICLSH.js +1 -0
- package/dist/public/chunks/chunk-DEEO4D4N.js +1 -0
- package/dist/public/chunks/chunk-DZB4LPI6.js +1 -0
- package/dist/public/chunks/chunk-FE522UEA.js +1 -0
- package/dist/public/chunks/chunk-FSGVY7HB.js +1 -0
- package/dist/public/chunks/chunk-FVX42XAD.js +1 -0
- package/dist/public/chunks/chunk-G5NWYJTV.js +1 -0
- package/dist/public/chunks/chunk-GMZIELFU.js +1 -0
- package/dist/public/chunks/chunk-GOTQ4D6W.js +1 -0
- package/dist/public/chunks/chunk-GYRM5ZIV.js +1 -0
- package/dist/public/chunks/chunk-GZBMHYYU.js +1 -0
- package/dist/public/chunks/chunk-H2ZU34CP.js +1 -0
- package/dist/public/chunks/chunk-H76B43OQ.js +1 -0
- package/dist/public/chunks/chunk-HCLRG2EC.js +1 -0
- package/dist/public/chunks/chunk-II4NXLSQ.js +2 -0
- package/dist/public/chunks/chunk-ILH47V7O.js +1 -0
- package/dist/public/chunks/chunk-IZWTFLCC.js +1 -0
- package/dist/public/chunks/chunk-J37MXUED.js +1 -0
- package/dist/public/chunks/chunk-JAZABOBA.js +0 -0
- package/dist/public/chunks/chunk-JPL2NRWK.js +1 -0
- package/dist/public/chunks/chunk-JVJ5UWIK.js +1 -0
- package/dist/public/chunks/chunk-K4TYMAA3.js +63 -0
- package/dist/public/chunks/chunk-KDUTK357.js +1 -0
- package/dist/public/chunks/chunk-L2H3ZWZN.js +1 -0
- package/dist/public/chunks/chunk-LBELRO5P.js +1 -0
- package/dist/public/chunks/chunk-LKW7MULF.js +1 -0
- package/dist/public/chunks/chunk-LZYAHJVQ.js +1 -0
- package/dist/public/chunks/chunk-MEHLQZDG.js +3 -0
- package/dist/public/chunks/chunk-MKKFXRYO.js +1 -0
- package/dist/public/chunks/chunk-MTBUV2GJ.js +1 -0
- package/dist/public/chunks/chunk-NGQRNS3C.js +1 -0
- package/dist/public/chunks/chunk-ONDKE72P.js +1 -0
- package/dist/public/chunks/chunk-PG7M7L24.js +1 -0
- package/dist/public/chunks/chunk-PQPAPPEI.js +1 -0
- package/dist/public/chunks/chunk-PXHLJIOT.js +1 -0
- package/dist/public/chunks/chunk-QOX5RMLM.js +1 -0
- package/dist/public/chunks/chunk-QPBLZSNX.js +1 -0
- package/dist/public/chunks/chunk-QQUFROWT.js +1 -0
- package/dist/public/chunks/chunk-RB5FBDEY.js +1 -0
- package/dist/public/chunks/chunk-RGXXD4DM.js +41 -0
- package/dist/public/chunks/chunk-RHMZNAJX.js +1 -0
- package/dist/public/chunks/chunk-SIOW7OOW.js +1 -0
- package/dist/public/chunks/chunk-SUX4QBVF.js +1 -0
- package/dist/public/chunks/chunk-T4M53QQT.js +1 -0
- package/dist/public/chunks/chunk-TC7TU4GK.js +1 -0
- package/dist/public/chunks/chunk-TFJOEF3Q.js +1 -0
- package/dist/public/chunks/chunk-UJD3MBMB.js +1 -0
- package/dist/public/chunks/chunk-UK2V4UBT.js +1 -0
- package/dist/public/chunks/chunk-UT6HZPGL.js +1 -0
- package/dist/public/chunks/chunk-UZ44J2LH.js +1 -0
- package/dist/public/chunks/chunk-VC6IS564.js +1 -0
- package/dist/public/chunks/chunk-WLIOZO3W.js +1 -0
- package/dist/public/chunks/chunk-WRXMQB7X.js +25 -0
- package/dist/public/chunks/chunk-XDUSY26N.js +55 -0
- package/dist/public/chunks/chunk-XNR4RZEC.js +1 -0
- package/dist/public/chunks/chunk-YPMUHXYT.js +147 -0
- package/dist/public/chunks/chunk-ZIJ6SJDN.js +1 -0
- package/dist/public/chunks/code-highlighter-Q7WC7AEF.js +12 -0
- package/dist/public/client-4OAAOHF4.js +68 -0
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +1 -1
- package/src/assets.ts +4 -1
- package/src/client/App.tsx +100 -23
- package/src/client/TracedRoutes.tsx +8 -4
- package/src/client/code-highlighter.ts +26 -0
- package/src/client/code.tsx +2 -22
- package/src/client/components/BottomSheet.tsx +1 -1
- package/src/client/components/DetailsPanel.tsx +2 -1
- package/src/client/components/Drawer.tsx +12 -71
- package/src/client/components/DrawerContent.tsx +76 -0
- package/src/client/components/ProfileMenu.tsx +12 -3
- package/src/client/components/UserInputForm.tsx +28 -5
- package/src/client/conversations/ConversationBrief.tsx +129 -65
- package/src/client/conversations/ConversationCatchUp.tsx +107 -0
- package/src/client/conversations/ConversationContextTags.tsx +110 -0
- package/src/client/conversations/ConversationDetails.tsx +13 -14
- package/src/client/conversations/ConversationEventLog.tsx +135 -70
- package/src/client/conversations/ConversationHeader.tsx +74 -65
- package/src/client/conversations/ConversationHeaderActions.tsx +36 -38
- package/src/client/conversations/ConversationHomeList.tsx +41 -3
- package/src/client/conversations/ConversationLinkedWork.tsx +30 -10
- package/src/client/conversations/ConversationMeta.tsx +21 -27
- package/src/client/conversations/ConversationOverflowMenu.tsx +15 -4
- package/src/client/conversations/ConversationPage.tsx +219 -157
- package/src/client/conversations/ConversationTranscript.tsx +234 -186
- package/src/client/conversations/ConversationUsageBreakdown.tsx +142 -64
- package/src/client/conversations/DetailsList.tsx +7 -1
- package/src/client/conversations/PendingMailboxStack.tsx +88 -44
- package/src/client/conversations/ShareConversationButton.tsx +5 -50
- package/src/client/conversations/SlackMark.tsx +16 -4
- package/src/client/conversations/TranscriptActivityGroup.tsx +7 -14
- package/src/client/conversations/TranscriptEventView.tsx +7 -3
- package/src/client/conversations/TranscriptReasoningView.tsx +4 -2
- package/src/client/conversations/TranscriptRows.tsx +61 -16
- package/src/client/conversations/TranscriptView.tsx +25 -13
- package/src/client/conversations/conversationArchive.ts +22 -0
- package/src/client/conversations/conversationCatchUpState.ts +96 -0
- package/src/client/conversations/conversationMemory.ts +8 -1
- package/src/client/conversations/queries.ts +85 -46
- package/src/client/conversations/{transcriptUnreadHistory.ts → transcriptAroundHistory.ts} +48 -42
- package/src/client/conversations/transcriptBottomPinning.ts +40 -8
- package/src/client/conversations/transcriptListRows.ts +215 -0
- package/src/client/conversations/transcriptReadPosition.ts +24 -2
- package/src/client/conversations/turnContext.ts +1 -1
- package/src/client/conversations/virtualRows.ts +168 -0
- package/src/client/http.ts +6 -5
- package/src/client/pages/admin/AdminPage.tsx +1 -1
- package/src/client/pages/admin/ServiceAccountsSection.tsx +1 -1
- package/src/client/pages/memory/memoryDashboard.ts +1 -1
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/settings/ConnectionsPage.tsx +1 -1
- package/src/client/pages/user/PluginUserPage.tsx +0 -6
- package/src/client/pages/user/pluginUserPageRoutes.ts +5 -0
- package/src/client/primaryNav.ts +1 -1
- package/src/client/review-desk/reviewItemFormat.ts +1 -1
- package/src/client/sentry.ts +53 -3
- package/src/conversation-cache.ts +8 -10
- package/src/mock-reporting/fixtures.ts +43 -8
- package/src/mock-reporting/routes.ts +40 -3
- package/src/public-assets.ts +28 -20
- package/src/shell.ts +12 -2
- package/dist/public/client-ZCZXZBTL.js +0 -460
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { ChevronRight } from "lucide-react";
|
|
2
|
-
import
|
|
1
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
2
|
+
import {
|
|
3
|
+
useId,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
3
9
|
import type {
|
|
4
10
|
ActorIdentity,
|
|
5
11
|
ConversationDetailReport,
|
|
@@ -20,6 +26,9 @@ type ConversationBriefReport = NonNullable<ConversationDetailReport["brief"]>;
|
|
|
20
26
|
|
|
21
27
|
type BriefContent = ConversationBriefReport["content"];
|
|
22
28
|
|
|
29
|
+
const SUMMARY_PREVIEW_LINES = 4;
|
|
30
|
+
const SUMMARY_FADE_LINES = 1.65;
|
|
31
|
+
|
|
23
32
|
const outcomeTones: Record<BriefContent["outcome"]["status"], StatusChipTone> =
|
|
24
33
|
{
|
|
25
34
|
abandoned: "neutral",
|
|
@@ -39,20 +48,9 @@ export function ConversationBrief(props: {
|
|
|
39
48
|
const mentions = props.brief.mentions;
|
|
40
49
|
if (props.variant === "summary") {
|
|
41
50
|
return (
|
|
42
|
-
<
|
|
51
|
+
<BriefSummary>
|
|
43
52
|
<DisplayText mentions={mentions} text={content.summary} />
|
|
44
|
-
|
|
45
|
-
<div className="grid gap-2">
|
|
46
|
-
<h4 className="m-0 text-sm font-medium text-dashboard-text">
|
|
47
|
-
Open decisions
|
|
48
|
-
</h4>
|
|
49
|
-
<BriefOpenDecisions
|
|
50
|
-
decisions={content.openDecisions}
|
|
51
|
-
mentions={mentions}
|
|
52
|
-
/>
|
|
53
|
-
</div>
|
|
54
|
-
) : null}
|
|
55
|
-
</div>
|
|
53
|
+
</BriefSummary>
|
|
56
54
|
);
|
|
57
55
|
}
|
|
58
56
|
return (
|
|
@@ -80,26 +78,9 @@ export function ConversationBrief(props: {
|
|
|
80
78
|
</Detail>
|
|
81
79
|
</DetailList>
|
|
82
80
|
</BriefSection>
|
|
83
|
-
{content.decisions.length
|
|
81
|
+
{content.decisions.length ? (
|
|
84
82
|
<BriefSection title="Decisions">
|
|
85
|
-
<
|
|
86
|
-
{content.decisions.length ? (
|
|
87
|
-
<Detail label="Decisions">
|
|
88
|
-
<BriefDecisions
|
|
89
|
-
decisions={content.decisions}
|
|
90
|
-
mentions={mentions}
|
|
91
|
-
/>
|
|
92
|
-
</Detail>
|
|
93
|
-
) : null}
|
|
94
|
-
{content.openDecisions.length ? (
|
|
95
|
-
<Detail label="Open decisions">
|
|
96
|
-
<BriefOpenDecisions
|
|
97
|
-
decisions={content.openDecisions}
|
|
98
|
-
mentions={mentions}
|
|
99
|
-
/>
|
|
100
|
-
</Detail>
|
|
101
|
-
) : null}
|
|
102
|
-
</DetailList>
|
|
83
|
+
<BriefDecisions decisions={content.decisions} mentions={mentions} />
|
|
103
84
|
</BriefSection>
|
|
104
85
|
) : null}
|
|
105
86
|
{content.facts.length ||
|
|
@@ -162,6 +143,120 @@ export function ConversationBrief(props: {
|
|
|
162
143
|
);
|
|
163
144
|
}
|
|
164
145
|
|
|
146
|
+
function BriefSummary(props: { children: ReactNode }) {
|
|
147
|
+
const id = useId();
|
|
148
|
+
const previewRef = useRef<HTMLDivElement>(null);
|
|
149
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
150
|
+
const toggleRef = useRef<HTMLButtonElement>(null);
|
|
151
|
+
const [hasMore, setHasMore] = useState(false);
|
|
152
|
+
const [expanded, setExpanded] = useState(false);
|
|
153
|
+
const collapsed = hasMore && !expanded;
|
|
154
|
+
|
|
155
|
+
useLayoutEffect(() => {
|
|
156
|
+
const preview = previewRef.current;
|
|
157
|
+
const content = contentRef.current;
|
|
158
|
+
if (!preview || !content) return;
|
|
159
|
+
|
|
160
|
+
const measure = () => {
|
|
161
|
+
const lineHeight = Number.parseFloat(
|
|
162
|
+
getComputedStyle(preview).lineHeight,
|
|
163
|
+
);
|
|
164
|
+
const height = lineHeight * SUMMARY_PREVIEW_LINES;
|
|
165
|
+
const overflows = content.getBoundingClientRect().height > height + 0.5;
|
|
166
|
+
const focused = document.activeElement;
|
|
167
|
+
if (
|
|
168
|
+
overflows &&
|
|
169
|
+
focused &&
|
|
170
|
+
content.contains(focused) &&
|
|
171
|
+
focused.getBoundingClientRect().bottom >
|
|
172
|
+
preview.getBoundingClientRect().top +
|
|
173
|
+
height -
|
|
174
|
+
lineHeight * SUMMARY_FADE_LINES
|
|
175
|
+
) {
|
|
176
|
+
setExpanded(true);
|
|
177
|
+
} else if (!overflows && focused && focused === toggleRef.current) {
|
|
178
|
+
preview.focus({ preventScroll: true });
|
|
179
|
+
}
|
|
180
|
+
setHasMore(overflows);
|
|
181
|
+
};
|
|
182
|
+
measure();
|
|
183
|
+
const observer = new ResizeObserver(measure);
|
|
184
|
+
observer.observe(content);
|
|
185
|
+
return () => observer.disconnect();
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
useLayoutEffect(() => {
|
|
189
|
+
const toggle = toggleRef.current;
|
|
190
|
+
if (!expanded && toggle && document.activeElement === toggle) {
|
|
191
|
+
toggle.scrollIntoView({ block: "nearest" });
|
|
192
|
+
}
|
|
193
|
+
}, [expanded]);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div className="relative min-w-0">
|
|
197
|
+
<div
|
|
198
|
+
className={cn("leading-6 md:leading-7", collapsed && "overflow-clip")}
|
|
199
|
+
id={id}
|
|
200
|
+
onFocusCapture={(event) => {
|
|
201
|
+
if (!collapsed) return;
|
|
202
|
+
// Keep focused links clear of both the clipping and the fade.
|
|
203
|
+
const preview = event.currentTarget;
|
|
204
|
+
const lineHeight = Number.parseFloat(
|
|
205
|
+
getComputedStyle(preview).lineHeight,
|
|
206
|
+
);
|
|
207
|
+
if (
|
|
208
|
+
event.target.getBoundingClientRect().bottom >
|
|
209
|
+
preview.getBoundingClientRect().bottom -
|
|
210
|
+
lineHeight * SUMMARY_FADE_LINES
|
|
211
|
+
) {
|
|
212
|
+
setExpanded(true);
|
|
213
|
+
}
|
|
214
|
+
}}
|
|
215
|
+
ref={previewRef}
|
|
216
|
+
style={
|
|
217
|
+
collapsed
|
|
218
|
+
? {
|
|
219
|
+
maskImage: `linear-gradient(black calc(100% - ${SUMMARY_FADE_LINES}lh), transparent)`,
|
|
220
|
+
maxHeight: `${SUMMARY_PREVIEW_LINES}lh`,
|
|
221
|
+
}
|
|
222
|
+
: undefined
|
|
223
|
+
}
|
|
224
|
+
tabIndex={-1}
|
|
225
|
+
>
|
|
226
|
+
<div ref={contentRef}>{props.children}</div>
|
|
227
|
+
</div>
|
|
228
|
+
{hasMore ? (
|
|
229
|
+
<button
|
|
230
|
+
aria-controls={id}
|
|
231
|
+
aria-expanded={expanded}
|
|
232
|
+
aria-label={
|
|
233
|
+
expanded ? "Collapse Brief summary" : "Expand Brief summary"
|
|
234
|
+
}
|
|
235
|
+
className={cn(
|
|
236
|
+
"group flex h-6 w-8 cursor-pointer items-center justify-center rounded-full focus-visible:outline-2 focus-visible:outline-dashboard-focus",
|
|
237
|
+
collapsed
|
|
238
|
+
? "absolute bottom-0 left-1/2 -translate-x-1/2"
|
|
239
|
+
: "mx-auto mt-1",
|
|
240
|
+
)}
|
|
241
|
+
onClick={() => setExpanded((open) => !open)}
|
|
242
|
+
ref={toggleRef}
|
|
243
|
+
type="button"
|
|
244
|
+
>
|
|
245
|
+
<span
|
|
246
|
+
aria-hidden="true"
|
|
247
|
+
className="grid size-5 place-items-center rounded-full border border-dashboard-border bg-dashboard-ink text-dashboard-text-solid transition-[filter] group-hover:brightness-95 dark:bg-dashboard-text-solid dark:text-dashboard-text-inverse"
|
|
248
|
+
>
|
|
249
|
+
<ChevronDown
|
|
250
|
+
className="size-4 group-aria-expanded:rotate-180"
|
|
251
|
+
strokeWidth={2.25}
|
|
252
|
+
/>
|
|
253
|
+
</span>
|
|
254
|
+
</button>
|
|
255
|
+
) : null}
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
165
260
|
function BriefSection(props: { children: ReactNode; title: string }) {
|
|
166
261
|
return (
|
|
167
262
|
<Collapsible className="min-w-0 border-t border-dashboard-border pt-3">
|
|
@@ -221,37 +316,6 @@ function BriefDecisions(props: {
|
|
|
221
316
|
);
|
|
222
317
|
}
|
|
223
318
|
|
|
224
|
-
function BriefOpenDecisions(props: {
|
|
225
|
-
decisions: BriefContent["openDecisions"];
|
|
226
|
-
mentions?: readonly ActorIdentity[];
|
|
227
|
-
}) {
|
|
228
|
-
return (
|
|
229
|
-
<ul className="m-0 grid list-none gap-2 p-0">
|
|
230
|
-
{props.decisions.map((decision, index) => (
|
|
231
|
-
<li key={`${decision.text}:${index}`}>
|
|
232
|
-
<div>
|
|
233
|
-
<DisplayText
|
|
234
|
-
inline
|
|
235
|
-
mentions={props.mentions}
|
|
236
|
-
text={decision.text}
|
|
237
|
-
/>
|
|
238
|
-
</div>
|
|
239
|
-
{decision.owner ? (
|
|
240
|
-
<div className="mt-1 text-xs text-dashboard-text-muted">
|
|
241
|
-
Owner:{" "}
|
|
242
|
-
<DisplayText
|
|
243
|
-
inline
|
|
244
|
-
mentions={props.mentions}
|
|
245
|
-
text={decision.owner}
|
|
246
|
-
/>
|
|
247
|
-
</div>
|
|
248
|
-
) : null}
|
|
249
|
-
</li>
|
|
250
|
-
))}
|
|
251
|
-
</ul>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
319
|
function BriefList(props: {
|
|
256
320
|
items: string[];
|
|
257
321
|
mentions?: readonly ActorIdentity[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef } from "react";
|
|
2
|
+
import { ArrowDown, Sparkles } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../components/ui/button";
|
|
5
|
+
import { Skeleton } from "../components/ui/skeleton";
|
|
6
|
+
import { InlineError } from "../components/InlineError";
|
|
7
|
+
import { DisplayText } from "../display-text/DisplayText";
|
|
8
|
+
import { useConversationCatchUpState } from "./conversationCatchUpState";
|
|
9
|
+
import { TranscriptJumpToLatestContext } from "./transcriptBottomPinning";
|
|
10
|
+
|
|
11
|
+
const cardClass =
|
|
12
|
+
"grid gap-2.5 rounded-lg border border-dashboard-accent/20 bg-dashboard-accent/10 px-3.5 py-3 font-sans text-sm leading-relaxed text-dashboard-text";
|
|
13
|
+
|
|
14
|
+
/** Offer the catch-up inside the "New" marker, so it takes no extra row. */
|
|
15
|
+
export function ConversationCatchUpButton() {
|
|
16
|
+
const state = useConversationCatchUpState();
|
|
17
|
+
if (!state || state.data || state.pending) return null;
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
className="inline-flex h-6 shrink-0 cursor-pointer items-center gap-1 rounded-full border border-dashboard-accent/30 bg-transparent px-2.5 font-sans text-xs font-medium text-dashboard-accent-text transition-colors hover:border-dashboard-accent/60 hover:bg-dashboard-accent/10 focus-visible:outline focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus/55"
|
|
21
|
+
onClick={state.request}
|
|
22
|
+
type="button"
|
|
23
|
+
>
|
|
24
|
+
<Sparkles aria-hidden="true" size={12} strokeWidth={2} />
|
|
25
|
+
Catch me up
|
|
26
|
+
</button>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Show the loading, loaded, or failed catch-up under the "New" marker. */
|
|
31
|
+
export function ConversationCatchUpCard() {
|
|
32
|
+
const state = useConversationCatchUpState();
|
|
33
|
+
const jumpToLatest = useContext(TranscriptJumpToLatestContext);
|
|
34
|
+
const cardRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
const shown = state?.data ? "data" : state?.pending ? "pending" : undefined;
|
|
36
|
+
// The transcript holds the row below the reader's place, so the card grows
|
|
37
|
+
// up, out of view. Bring it back after that scroll fix runs.
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!shown) return;
|
|
40
|
+
const frame = requestAnimationFrame(() => {
|
|
41
|
+
// Keep the "New" marker above the card in view too.
|
|
42
|
+
const marker = cardRef.current?.closest(
|
|
43
|
+
"[data-transcript-unread-marker]",
|
|
44
|
+
);
|
|
45
|
+
marker?.scrollIntoView({ block: "nearest" });
|
|
46
|
+
});
|
|
47
|
+
return () => cancelAnimationFrame(frame);
|
|
48
|
+
}, [shown]);
|
|
49
|
+
|
|
50
|
+
if (state?.data) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
aria-label="Catch-up"
|
|
54
|
+
className={cardClass}
|
|
55
|
+
ref={cardRef}
|
|
56
|
+
role="group"
|
|
57
|
+
>
|
|
58
|
+
<DisplayText text={state.data.summary} />
|
|
59
|
+
{state.data.needsYou ? (
|
|
60
|
+
<p className="m-0 border-t border-dashboard-accent/15 pt-2.5">
|
|
61
|
+
<span className="font-semibold text-dashboard-accent-text">
|
|
62
|
+
Needs you
|
|
63
|
+
</span>
|
|
64
|
+
<span
|
|
65
|
+
aria-hidden="true"
|
|
66
|
+
className="px-1.5 text-dashboard-text-muted"
|
|
67
|
+
>
|
|
68
|
+
·
|
|
69
|
+
</span>
|
|
70
|
+
{state.data.needsYou}
|
|
71
|
+
</p>
|
|
72
|
+
) : null}
|
|
73
|
+
{jumpToLatest ? (
|
|
74
|
+
<Button
|
|
75
|
+
className="-mb-1 h-7 justify-self-center gap-1.5 px-2 text-xs"
|
|
76
|
+
onClick={jumpToLatest}
|
|
77
|
+
variant="quiet"
|
|
78
|
+
>
|
|
79
|
+
<ArrowDown aria-hidden="true" size={14} strokeWidth={2} />
|
|
80
|
+
Jump to latest
|
|
81
|
+
</Button>
|
|
82
|
+
) : null}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
if (state?.error) {
|
|
87
|
+
return (
|
|
88
|
+
<InlineError className="text-center text-xs">
|
|
89
|
+
Catch-up failed. Try again.
|
|
90
|
+
</InlineError>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (state?.pending) {
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
aria-label="Summarizing new messages"
|
|
97
|
+
className={cardClass}
|
|
98
|
+
ref={cardRef}
|
|
99
|
+
role="status"
|
|
100
|
+
>
|
|
101
|
+
<Skeleton className="h-4 w-full" />
|
|
102
|
+
<Skeleton className="h-4 w-2/3" />
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { slackLocationLabel } from "../format";
|
|
4
|
+
import { cn } from "../styles";
|
|
5
|
+
import type { Conversation } from "../types";
|
|
6
|
+
import { SlackMark } from "./SlackMark";
|
|
7
|
+
|
|
8
|
+
function githubRepositoryFromUrl(href: string) {
|
|
9
|
+
const url = new URL(href);
|
|
10
|
+
if (url.origin !== "https://github.com") return undefined;
|
|
11
|
+
const [, owner, repo] = url.pathname.split("/");
|
|
12
|
+
if (!owner || !repo) return undefined;
|
|
13
|
+
return {
|
|
14
|
+
label: `${owner}/${repo}`,
|
|
15
|
+
href: `${url.origin}/${owner}/${repo}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function singleGitHubRepository(annotations: Conversation["annotations"]) {
|
|
20
|
+
let repository: ReturnType<typeof githubRepositoryFromUrl>;
|
|
21
|
+
for (const annotation of annotations ?? []) {
|
|
22
|
+
if (annotation.plugin !== "github" || !annotation.url) continue;
|
|
23
|
+
const candidate = githubRepositoryFromUrl(annotation.url);
|
|
24
|
+
if (!candidate) continue;
|
|
25
|
+
if (
|
|
26
|
+
repository &&
|
|
27
|
+
repository.href.toLowerCase() !== candidate.href.toLowerCase()
|
|
28
|
+
) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
repository ??= candidate;
|
|
32
|
+
}
|
|
33
|
+
return repository;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ContextTag(props: {
|
|
37
|
+
href?: string;
|
|
38
|
+
icon: ReactNode;
|
|
39
|
+
label: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
}) {
|
|
42
|
+
const className =
|
|
43
|
+
"group/context-tag inline-flex h-6 min-w-0 items-center gap-1 rounded-md border border-dashboard-border-subtle px-1.5 font-sans text-xs font-medium text-dashboard-text-muted";
|
|
44
|
+
const content = (
|
|
45
|
+
<>
|
|
46
|
+
{props.icon}
|
|
47
|
+
<span className="truncate">{props.label}</span>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return props.href ? (
|
|
52
|
+
<a
|
|
53
|
+
className={cn(
|
|
54
|
+
className,
|
|
55
|
+
"no-underline transition-colors hover:border-dashboard-border-strong hover:bg-dashboard-fill-soft hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus",
|
|
56
|
+
)}
|
|
57
|
+
href={props.href}
|
|
58
|
+
rel="noreferrer"
|
|
59
|
+
target="_blank"
|
|
60
|
+
title={props.title ?? props.label}
|
|
61
|
+
>
|
|
62
|
+
{content}
|
|
63
|
+
</a>
|
|
64
|
+
) : (
|
|
65
|
+
<span className={className} title={props.label}>
|
|
66
|
+
{content}
|
|
67
|
+
</span>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function GitHubMark() {
|
|
72
|
+
return (
|
|
73
|
+
<svg
|
|
74
|
+
aria-label="GitHub"
|
|
75
|
+
className="size-3 shrink-0 transition-colors group-hover/context-tag:text-dashboard-text-solid group-focus-visible/context-tag:text-dashboard-text-solid"
|
|
76
|
+
fill="currentColor"
|
|
77
|
+
role="img"
|
|
78
|
+
viewBox="0 0 24 24"
|
|
79
|
+
>
|
|
80
|
+
{/* Keep the geometry from packages/docs/public/integrations/github.svg. */}
|
|
81
|
+
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
|
82
|
+
</svg>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Show source context and a repository tag when linked work points to one repository. */
|
|
87
|
+
export function conversationContextTags(
|
|
88
|
+
conversation: Conversation | undefined,
|
|
89
|
+
) {
|
|
90
|
+
const location =
|
|
91
|
+
conversation && slackLocationLabel(conversation, { includeId: false });
|
|
92
|
+
const repository = singleGitHubRepository(conversation?.annotations);
|
|
93
|
+
if (!location && !repository) return null;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
97
|
+
{location ? (
|
|
98
|
+
<ContextTag
|
|
99
|
+
href={conversation?.locationUrl}
|
|
100
|
+
icon={
|
|
101
|
+
<SlackMark className="size-3 shrink-0 group-hover/context-tag:[&>path]:fill-(--slack-mark-color) group-focus-visible/context-tag:[&>path]:fill-(--slack-mark-color)" />
|
|
102
|
+
}
|
|
103
|
+
label={location}
|
|
104
|
+
title={`Open ${location} in Slack`}
|
|
105
|
+
/>
|
|
106
|
+
) : null}
|
|
107
|
+
{repository ? <ContextTag {...repository} icon={<GitHubMark />} /> : null}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -37,9 +37,7 @@ import type { SubagentTranscriptTarget } from "./SubagentTranscriptDrawer";
|
|
|
37
37
|
type Brief = NonNullable<ConversationDetailReport["brief"]>;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
* work is (the Brief), what needs them, what it changed or waits on, who did
|
|
42
|
-
* it, and what it cost. Each fact shows here only, not in the title bar.
|
|
40
|
+
* Surface work that needs the reader, then the Brief and its linked work.
|
|
43
41
|
*/
|
|
44
42
|
export function ConversationDetails(props: {
|
|
45
43
|
/** Viewer's auto-archive control. Null for non-participants. */
|
|
@@ -153,21 +151,22 @@ export function ConversationDetails(props: {
|
|
|
153
151
|
}
|
|
154
152
|
onClose={props.onClose}
|
|
155
153
|
>
|
|
156
|
-
<div className="grid min-w-0 content-start gap-
|
|
154
|
+
<div className="grid min-w-0 content-start gap-2 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3">
|
|
155
|
+
<ConversationReviewItems
|
|
156
|
+
conversationId={props.conversationId}
|
|
157
|
+
highlight={props.highlightReview}
|
|
158
|
+
/>
|
|
157
159
|
{props.brief ? (
|
|
158
160
|
<div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
|
|
159
161
|
<ConversationBrief brief={props.brief} variant="summary" />
|
|
160
162
|
</div>
|
|
161
163
|
) : null}
|
|
162
|
-
<ConversationReviewItems
|
|
163
|
-
conversationId={props.conversationId}
|
|
164
|
-
highlight={props.highlightReview}
|
|
165
|
-
/>
|
|
166
164
|
{props.links.length > 0 || watches.length > 0 ? (
|
|
167
165
|
// A Watch waits on something. It shows with linked work, so the
|
|
168
166
|
// work and what Junior waits on read as one list.
|
|
169
167
|
<DetailsCard
|
|
170
168
|
count={props.links.length || watches.length}
|
|
169
|
+
flat
|
|
171
170
|
title={props.links.length > 0 ? "Linked work" : "Waiting on"}
|
|
172
171
|
>
|
|
173
172
|
{props.links.length > 0 ? (
|
|
@@ -191,7 +190,7 @@ export function ConversationDetails(props: {
|
|
|
191
190
|
<DetailsCard>{props.autoArchive}</DetailsCard>
|
|
192
191
|
) : null}
|
|
193
192
|
{agents?.length ? (
|
|
194
|
-
<DetailsCard count={agents.length} title="Subagents">
|
|
193
|
+
<DetailsCard count={agents.length} flat title="Subagents">
|
|
195
194
|
<ConversationChildAgents
|
|
196
195
|
conversationId={props.conversationId}
|
|
197
196
|
onOpenAgent={(target) => {
|
|
@@ -203,13 +202,13 @@ export function ConversationDetails(props: {
|
|
|
203
202
|
</DetailsCard>
|
|
204
203
|
) : null}
|
|
205
204
|
{props.overview != null ? (
|
|
206
|
-
<DetailsCard>{props.overview}</DetailsCard>
|
|
207
|
-
) : null}
|
|
208
|
-
{props.usage != null ? (
|
|
209
|
-
<DetailsCard title="Usage">{props.usage}</DetailsCard>
|
|
205
|
+
<DetailsCard flat>{props.overview}</DetailsCard>
|
|
210
206
|
) : null}
|
|
207
|
+
{props.usage}
|
|
211
208
|
{props.identity != null ? (
|
|
212
|
-
<DetailsCard title="Participants">
|
|
209
|
+
<DetailsCard flat title="Participants">
|
|
210
|
+
{props.identity}
|
|
211
|
+
</DetailsCard>
|
|
213
212
|
) : null}
|
|
214
213
|
{memoryList?.events.length ? (
|
|
215
214
|
<ConversationMemoryCard
|