From a2476bb79a05bb82009002a1404fd088ff5e2b4a Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 24 Apr 2026 19:20:01 -0500 Subject: [PATCH] Thread externalReferences through issue-scoped markdown surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the Phase 6 plumbing so every `MarkdownBody` rendered inside an issue context receives `externalObjectsState.markdownReferences`. Comments, documents, chat, continuation handoffs, interaction details, and run transcripts now decorate resolved external URLs with the ยง2 status chip. Co-Authored-By: Paperclip --- ui/src/components/CommentThread.test.tsx | 62 +++++++++++++++- ui/src/components/CommentThread.tsx | 13 +++- ui/src/components/IssueChatThread.tsx | 23 +++++- .../components/IssueContinuationHandoff.tsx | 10 ++- .../components/IssueDocumentsSection.test.tsx | 73 ++++++++++++++++++- ui/src/components/IssueDocumentsSection.tsx | 24 ++++-- .../components/IssueThreadInteractionCard.tsx | 20 ++++- .../transcript/RunTranscriptView.tsx | 36 ++++++++- ui/src/pages/IssueDetail.tsx | 15 +++- 9 files changed, 249 insertions(+), 27 deletions(-) diff --git a/ui/src/components/CommentThread.test.tsx b/ui/src/components/CommentThread.test.tsx index ed791baa2c6..f42a4112763 100644 --- a/ui/src/components/CommentThread.test.tsx +++ b/ui/src/components/CommentThread.test.tsx @@ -9,8 +9,22 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { CommentThread } from "./CommentThread"; vi.mock("./MarkdownBody", () => ({ - MarkdownBody: ({ children, className }: { children: ReactNode; className?: string }) => ( -
{children}
+ MarkdownBody: ({ + children, + className, + externalReferences, + }: { + children: ReactNode; + className?: string; + externalReferences?: Record; + }) => ( +
+ {children} +
), })); @@ -395,4 +409,48 @@ describe("CommentThread", () => { root.unmount(); }); }); + + it("passes externalReferences to MarkdownBody for comment bodies", () => { + const root = createRoot(container); + + act(() => { + root.render( + + {}} + /> + , + ); + }); + + const commentRow = container.querySelector("#comment-comment-ref") as HTMLDivElement | null; + expect(commentRow).not.toBeNull(); + const markdownBody = commentRow?.querySelector('[data-testid="markdown-body"]') as HTMLElement | null; + expect(markdownBody?.getAttribute("data-external-reference-keys")) + .toContain("https://github.com/example/repo/pull/42"); + + act(() => { + root.unmount(); + }); + }); }); diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index cf0576abb1a..9026b051064 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -14,7 +14,7 @@ import { ArrowRight, Check, Copy, Paperclip } from "lucide-react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Identity } from "./Identity"; import { InlineEntitySelector, type InlineEntityOption } from "./InlineEntitySelector"; -import { MarkdownBody } from "./MarkdownBody"; +import { MarkdownBody, type MarkdownExternalReferenceMap } from "./MarkdownBody"; import { MarkdownEditor, type MarkdownEditorRef, type MentionOption } from "./MarkdownEditor"; import { OutputFeedbackButtons } from "./OutputFeedbackButtons"; import { ApprovalCard } from "./ApprovalCard"; @@ -105,6 +105,7 @@ interface CommentThreadProps { onInterruptQueued?: (runId: string) => Promise; interruptingQueuedRunId?: string | null; composerDisabledReason?: string | null; + externalReferences?: MarkdownExternalReferenceMap; } const DRAFT_DEBOUNCE_MS = 800; @@ -324,6 +325,7 @@ function CommentCard({ voting = false, highlightCommentId, queued = false, + externalReferences, }: { comment: CommentWithRunMeta; agentMap?: Map; @@ -339,6 +341,7 @@ function CommentCard({ voting?: boolean; highlightCommentId?: string | null; queued?: boolean; + externalReferences?: MarkdownExternalReferenceMap; }) { const isHighlighted = highlightCommentId === comment.id; const isPending = comment.clientStatus === "pending"; @@ -412,7 +415,7 @@ function CommentCard({ {isDeleted ? (
Comment deleted
) : ( - {comment.body} + {comment.body} )} {companyId && !isPending && !isDeleted ? (
@@ -575,6 +578,7 @@ const TimelineList = memo(function TimelineList({ onVote, votingTargetId, highlightCommentId, + externalReferences, }: { timeline: TimelineItem[]; agentMap?: Map; @@ -597,6 +601,7 @@ const TimelineList = memo(function TimelineList({ ) => Promise; votingTargetId?: string | null; highlightCommentId?: string | null; + externalReferences?: MarkdownExternalReferenceMap; }) { if (timeline.length === 0) { return

No timeline entries yet.

; @@ -718,6 +723,7 @@ const TimelineList = memo(function TimelineList({ onVote={onVote ? (vote, options) => onVote(comment.id, vote, options) : undefined} voting={votingTargetId === comment.id} highlightCommentId={highlightCommentId} + externalReferences={externalReferences} /> ); })} @@ -756,6 +762,7 @@ export function CommentThread({ onInterruptQueued, interruptingQueuedRunId = null, composerDisabledReason = null, + externalReferences, }: CommentThreadProps) { const [body, setBody] = useState(""); const [submitting, setSubmitting] = useState(false); @@ -974,6 +981,7 @@ export function CommentThread({ votingTargetId={votingTargetId} highlightCommentId={highlightCommentId} feedbackTermsUrl={feedbackTermsUrl} + externalReferences={externalReferences} /> {liveRunSlot} @@ -1006,6 +1014,7 @@ export function CommentThread({ projectId={projectId} highlightCommentId={highlightCommentId} queued + externalReferences={externalReferences} /> ))}
diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index aa215ac4fc4..48b4d92244d 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -101,7 +101,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { MarkdownBody } from "./MarkdownBody"; +import { MarkdownBody, type MarkdownExternalReferenceMap } from "./MarkdownBody"; import { WorkspaceFileMarkdownBody } from "./WorkspaceFileMarkdownBody"; import { MarkdownEditor, type MentionOption, type MarkdownEditorRef } from "./MarkdownEditor"; import { Identity } from "./Identity"; @@ -217,6 +217,7 @@ interface IssueChatMessageContext { onUploadImage?: (file: File) => Promise; issueStatus?: string; successfulRunHandoff?: SuccessfulRunHandoffState | null; + externalReferences?: MarkdownExternalReferenceMap; } const IssueChatCtx = createContext({ @@ -450,6 +451,7 @@ interface IssueChatThreadProps { * comment is in the loaded set before we scroll to it. */ onRefreshLatestComments?: () => Promise | void; + externalReferences?: MarkdownExternalReferenceMap; } type IssueChatErrorBoundaryProps = { @@ -457,6 +459,7 @@ type IssueChatErrorBoundaryProps = { messages: readonly ThreadMessage[]; emptyMessage: string; variant: "full" | "embedded"; + externalReferences?: MarkdownExternalReferenceMap; children: ReactNode; }; @@ -491,6 +494,7 @@ class IssueChatErrorBoundary extends Component ); } @@ -555,10 +559,12 @@ function IssueChatFallbackThread({ messages, emptyMessage, variant, + externalReferences, }: { messages: readonly ThreadMessage[]; emptyMessage: string; variant: "full" | "embedded"; + externalReferences?: MarkdownExternalReferenceMap; }) { return (
@@ -599,7 +605,9 @@ function IssueChatFallbackThread({
{lines.length > 0 ? lines.map((line, index) => ( - {line} + + {line} + )) : (

No message content.

)} @@ -705,7 +713,7 @@ function commentDateLabel(date: Date | string | undefined): string { } const IssueChatTextPart = memo(function IssueChatTextPart({ text, recessed, onAccent }: { text: string; recessed?: boolean; onAccent?: boolean }) { - const { onImageClick } = useContext(IssueChatCtx); + const { onImageClick, externalReferences } = useContext(IssueChatCtx); if (isSuccessfulRunHandoffComment(text)) { return ; } @@ -717,6 +725,7 @@ const IssueChatTextPart = memo(function IssueChatTextPart({ text, recessed, onAc style={recessed ? { opacity: 0.55 } : undefined} softBreaks onImageClick={onImageClick} + externalReferences={externalReferences} > {text} @@ -2113,6 +2122,7 @@ function ExpiredRequestConfirmationActivity({ onRejectInteraction, onCancelInteraction, onUploadImage, + externalReferences, } = useContext(IssueChatCtx); const [expanded, setExpanded] = useState(false); const hasResolvedActor = Boolean(interaction.resolvedByAgentId || interaction.resolvedByUserId); @@ -2193,6 +2203,7 @@ function ExpiredRequestConfirmationActivity({ onRejectInteraction={onRejectInteraction} onCancelInteraction={onCancelInteraction} onUploadImage={onUploadImage} + externalReferences={externalReferences} />
) : null} @@ -2620,6 +2631,7 @@ function IssueChatSystemMessage({ message }: { message: ThreadMessage }) { onSubmitInteractionAnswers, onCancelInteraction, onUploadImage, + externalReferences, } = useContext(IssueChatCtx); const custom = message.metadata.custom as Record; const anchorId = typeof custom.anchorId === "string" ? custom.anchorId : undefined; @@ -2677,6 +2689,7 @@ function IssueChatSystemMessage({ message }: { message: ThreadMessage }) { onSubmitInteractionAnswers={onSubmitInteractionAnswers} onCancelInteraction={onCancelInteraction} onUploadImage={onUploadImage} + externalReferences={externalReferences} /> @@ -4094,6 +4107,7 @@ export function IssueChatThread({ assigneeUserId = null, onResumeFromBacklog, resumeFromBacklogPending = false, + externalReferences, }: IssueChatThreadProps) { const location = useLocation(); const lastScrolledHashRef = useRef(null); @@ -4618,6 +4632,7 @@ export function IssueChatThread({ onUploadImage: stableOnUploadImage, issueStatus, successfulRunHandoff, + externalReferences, }), [ feedbackDataSharingPreference, @@ -4643,6 +4658,7 @@ export function IssueChatThread({ stableOnUploadImage, issueStatus, successfulRunHandoff, + externalReferences, ], ); @@ -4680,6 +4696,7 @@ export function IssueChatThread({ messages={messages} emptyMessage={resolvedEmptyMessage} variant={variant} + externalReferences={externalReferences} >
{expanded ? (
- + {document.body}
diff --git a/ui/src/components/IssueDocumentsSection.test.tsx b/ui/src/components/IssueDocumentsSection.test.tsx index 0ce5cb6627d..4884beea5d3 100644 --- a/ui/src/components/IssueDocumentsSection.test.tsx +++ b/ui/src/components/IssueDocumentsSection.test.tsx @@ -43,8 +43,22 @@ vi.mock("@/lib/router", () => ({ })); vi.mock("./MarkdownBody", () => ({ - MarkdownBody: ({ children, className }: { children: string; className?: string }) => ( -
{children}
+ MarkdownBody: ({ + children, + className, + externalReferences, + }: { + children: string; + className?: string; + externalReferences?: Record; + }) => ( +
+ {children} +
), })); @@ -684,6 +698,61 @@ describe("IssueDocumentsSection", () => { queryClient.clear(); }); + it("forwards externalReferences to the rendered document body so URL decoration applies", async () => { + const issue = createIssue(); + const root = createRoot(container); + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, + }); + + mockIssuesApi.listDocuments.mockResolvedValue([ + createIssueDocument({ + body: "Linked work: https://github.com/example/repo/pull/99", + }), + ]); + + await act(async () => { + root.render( + + + , + ); + }); + await flush(); + await flush(); + + const markdownBodies = Array.from( + container.querySelectorAll('[data-testid="markdown-body"]'), + ) as HTMLElement[]; + expect(markdownBodies.length).toBeGreaterThan(0); + const rendered = markdownBodies.find((element) => + (element.textContent ?? "").includes("Linked work"), + ); + expect(rendered?.getAttribute("data-external-reference-keys")) + .toContain("https://github.com/example/repo/pull/99"); + + await act(async () => { + root.unmount(); + }); + queryClient.clear(); + }); + it("wraps the documents header actions so mobile layouts do not overflow", async () => { const issue = createIssue(); const root = createRoot(container); diff --git a/ui/src/components/IssueDocumentsSection.tsx b/ui/src/components/IssueDocumentsSection.tsx index 4b329cfeab1..2e7e858e356 100644 --- a/ui/src/components/IssueDocumentsSection.tsx +++ b/ui/src/components/IssueDocumentsSection.tsx @@ -20,7 +20,7 @@ import { queryKeys } from "../lib/queryKeys"; import { cn, relativeTime } from "../lib/utils"; import { FoldCurtain } from "./FoldCurtain"; import { DocumentAnnotationsCountChip, IssueDocumentAnnotations } from "./IssueDocumentAnnotations"; -import { MarkdownBody } from "./MarkdownBody"; +import { MarkdownBody, type MarkdownExternalReferenceMap } from "./MarkdownBody"; import { MarkdownEditor, type MentionOption } from "./MarkdownEditor"; import { OutputFeedbackButtons } from "./OutputFeedbackButtons"; import { Button } from "@/components/ui/button"; @@ -75,10 +75,16 @@ function saveFoldedDocumentKeys(issueId: string, keys: string[]) { window.localStorage.setItem(getFoldedDocumentsStorageKey(issueId), JSON.stringify(keys)); } -function renderFoldableBody(body: string, className?: string) { +function renderFoldableBody( + body: string, + className?: string, + externalReferences?: MarkdownExternalReferenceMap, +) { return ( - {body} + + {body} + ); } @@ -161,6 +167,7 @@ export function IssueDocumentsSection({ defaultAnnotationPanelOpenKeys, defaultAnnotationFocusedThreadIds, forceEditDocumentKey, + externalReferences, }: { issue: Issue; canDeleteDocuments: boolean; @@ -187,6 +194,7 @@ export function IssueDocumentsSection({ defaultAnnotationFocusedThreadIds?: Readonly>; /** Force a doc into edit mode on mount (Storybook-only). */ forceEditDocumentKey?: string | null; + externalReferences?: MarkdownExternalReferenceMap; }) { const queryClient = useQueryClient(); const location = useLocation(); @@ -878,7 +886,9 @@ export function IssueDocumentsSection({ PLAN
- {renderFoldableBody(issue.legacyPlanDocument.body, documentBodyContentClassName)} +
+ {renderFoldableBody(issue.legacyPlanDocument.body, documentBodyContentClassName, externalReferences)} +
) : null} @@ -1197,7 +1207,7 @@ export function IssueDocumentsSection({ {!isPlanKey(doc.key) && activeConflict.serverDocument.title ? (

{activeConflict.serverDocument.title}

) : null} - {renderFoldableBody(activeConflict.serverDocument.body, "text-[14px] leading-7")} + {renderFoldableBody(activeConflict.serverDocument.body, "text-[14px] leading-7", externalReferences)} )} @@ -1235,7 +1245,7 @@ export function IssueDocumentsSection({ defaultFocusedThreadId={defaultAnnotationFocusedThreadIds?.[doc.key]} > {isHistoricalPreview ? ( - renderFoldableBody(displayedBody, documentBodyContentClassName) + renderFoldableBody(displayedBody, documentBodyContentClassName, externalReferences) ) : activeDraft ? ( void commitDraft(activeDraft ?? draft, { clearAfterSave: false, trackAutosave: true })} /> ) : ( - renderFoldableBody(displayedBody, documentBodyContentClassName) + renderFoldableBody(displayedBody, documentBodyContentClassName, externalReferences) )} diff --git a/ui/src/components/IssueThreadInteractionCard.tsx b/ui/src/components/IssueThreadInteractionCard.tsx index 49e4e4a5619..3ccaa9f0a3d 100644 --- a/ui/src/components/IssueThreadInteractionCard.tsx +++ b/ui/src/components/IssueThreadInteractionCard.tsx @@ -21,7 +21,7 @@ import { type SuggestedTaskTreeNode, } from "../lib/issue-thread-interactions"; import { cn, formatDateTime, formatShortDate } from "../lib/utils"; -import { MarkdownBody } from "./MarkdownBody"; +import { MarkdownBody, type MarkdownExternalReferenceMap } from "./MarkdownBody"; import { Button } from "./ui/button"; import { Checkbox } from "./ui/checkbox"; import { PriorityIcon } from "./PriorityIcon"; @@ -58,6 +58,7 @@ interface IssueThreadInteractionCardProps { interaction: AskUserQuestionsInteraction, ) => Promise | void; onUploadImage?: (file: File) => Promise; + externalReferences?: MarkdownExternalReferenceMap; } function resolveActorLabel(args: { @@ -706,6 +707,7 @@ function AskUserQuestionsCard({ interaction, onSubmitInteractionAnswers, onCancelInteraction, + externalReferences, }: { interaction: AskUserQuestionsInteraction; onSubmitInteractionAnswers?: ( @@ -715,6 +717,7 @@ function AskUserQuestionsCard({ onCancelInteraction?: ( interaction: AskUserQuestionsInteraction, ) => Promise | void; + externalReferences?: MarkdownExternalReferenceMap; }) { const [draftAnswers, setDraftAnswers] = useState>(() => Object.fromEntries( @@ -1013,7 +1016,7 @@ function AskUserQuestionsCard({
Submitted summary
- {interaction.result.summaryMarkdown} + {interaction.result.summaryMarkdown} ) : null} @@ -1173,6 +1176,7 @@ function RequestConfirmationCard({ onAcceptInteraction, onRejectInteraction, onUploadImage, + externalReferences, }: { interaction: RequestConfirmationInteraction; isPlan?: boolean; @@ -1184,6 +1188,7 @@ function RequestConfirmationCard({ reason?: string, ) => Promise | void; onUploadImage?: (file: File) => Promise; + externalReferences?: MarkdownExternalReferenceMap; }) { const [rejecting, setRejecting] = useState(false); const [working, setWorking] = useState<"accept" | "reject" | null>(null); @@ -1283,7 +1288,7 @@ function RequestConfirmationCard({ {interaction.payload.detailsMarkdown ? (
- {interaction.payload.detailsMarkdown} + {interaction.payload.detailsMarkdown}
) : null} Promise | void; + externalReferences?: MarkdownExternalReferenceMap; }) { const options = interaction.payload.options; const optionIds = useMemo(() => options.map((option) => option.id), [options]); @@ -1721,7 +1728,7 @@ function RequestCheckboxConfirmationCard({
{interaction.payload.prompt}
{interaction.payload.detailsMarkdown ? (
- {interaction.payload.detailsMarkdown} + {interaction.payload.detailsMarkdown}
) : null} ) : interaction.kind === "request_checkbox_confirmation" ? ( ) : ( )} diff --git a/ui/src/components/transcript/RunTranscriptView.tsx b/ui/src/components/transcript/RunTranscriptView.tsx index a9130f8cde8..52e9f6bfc85 100644 --- a/ui/src/components/transcript/RunTranscriptView.tsx +++ b/ui/src/components/transcript/RunTranscriptView.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef, useState } from "react"; import type { TranscriptEntry } from "../../adapters"; -import { MarkdownBody } from "../MarkdownBody"; +import { MarkdownBody, type MarkdownExternalReferenceMap } from "../MarkdownBody"; import { cn, formatTokens } from "../../lib/utils"; import { Check, @@ -31,6 +31,7 @@ interface RunTranscriptViewProps { emptyMessage?: string; className?: string; thinkingClassName?: string; + externalReferences?: MarkdownExternalReferenceMap; } type TranscriptBlock = @@ -636,9 +637,11 @@ export function normalizeTranscript(entries: TranscriptEntry[], streaming: boole function TranscriptMessageBlock({ block, density, + externalReferences, }: { block: Extract; density: TranscriptDensity; + externalReferences?: MarkdownExternalReferenceMap; }) { const isAssistant = block.role === "assistant"; const compact = density === "compact"; @@ -656,6 +659,7 @@ function TranscriptMessageBlock({ "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0", compact ? "text-xs leading-5 text-foreground/85" : "text-sm", )} + externalReferences={externalReferences} > {block.text} @@ -676,10 +680,12 @@ function TranscriptThinkingBlock({ block, density, className, + externalReferences, }: { block: Extract; density: TranscriptDensity; className?: string; + externalReferences?: MarkdownExternalReferenceMap; }) { return ( {block.text} @@ -1090,9 +1097,11 @@ function TranscriptActivityRow({ function TranscriptEventRow({ block, density, + externalReferences, }: { block: Extract; density: TranscriptDensity; + externalReferences?: MarkdownExternalReferenceMap; }) { const compact = density === "compact"; const toneClasses = @@ -1121,6 +1130,7 @@ function TranscriptEventRow({ "[&>*:first-child]:mt-0 [&>*:last-child]:mb-0 text-sky-700 dark:text-sky-300", compact ? "text-[11px] leading-5" : "text-xs leading-5", )} + externalReferences={externalReferences} > {block.text} @@ -1473,6 +1483,7 @@ export function RunTranscriptView({ emptyMessage = "No transcript yet.", className, thinkingClassName, + externalReferences, }: RunTranscriptViewProps) { const blocks = useMemo( () => (mode === "raw" ? [] : normalizeTranscript(entries, streaming)), @@ -1504,9 +1515,20 @@ export function RunTranscriptView({ key={`${block.type}-${block.ts}-${index}`} className={cn(index === visibleBlocks.length - 1 && streaming && "animate-in fade-in slide-in-from-bottom-1 duration-300")} > - {block.type === "message" && } + {block.type === "message" && ( + + )} {block.type === "thinking" && ( - + )} {block.type === "tool" && } {block.type === "command_group" && } @@ -1518,7 +1540,13 @@ export function RunTranscriptView({ )} {block.type === "activity" && } - {block.type === "event" && } + {block.type === "event" && ( + + )} ))} diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 8817f5334e5..54042b1c22f 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -79,6 +79,7 @@ import { IssueOutputSection } from "../components/issue-output/IssueOutputSectio import { isImageAttachment } from "../lib/issue-attachments"; import { getPromotedOutputAttachmentIds } from "../lib/issue-output"; import { IssueSiblingNavigation } from "../components/IssueSiblingNavigation"; +import type { MarkdownExternalReferenceMap } from "../components/MarkdownBody"; import { IssuesList } from "../components/IssuesList"; import { AgentIcon } from "../components/AgentIconPicker"; import { IssueReferenceActivitySummary } from "../components/IssueReferenceActivitySummary"; @@ -744,6 +745,7 @@ type IssueDetailChatTabProps = { assigneeUserId: string | null; onResumeFromBacklog?: () => Promise | void; resumeFromBacklogPending?: boolean; + externalReferences?: MarkdownExternalReferenceMap; }; const IssueDetailChatTab = memo(function IssueDetailChatTab({ @@ -805,6 +807,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({ assigneeUserId, onResumeFromBacklog, resumeFromBacklogPending, + externalReferences, }: IssueDetailChatTabProps) { // Conference Room Chat experimental flag (PAP-136/PAP-139): ON renders the // NUX thread (bubbles, metadata rows, composer chrome); OFF renders the @@ -1029,6 +1032,7 @@ const IssueDetailChatTab = memo(function IssueDetailChatTab({ onResumeFromBacklog={onResumeFromBacklog} resumeFromBacklogPending={resumeFromBacklogPending} footer={footer} + externalReferences={externalReferences} /> ); @@ -1049,6 +1053,7 @@ type IssueDetailActivityTabProps = { onCheckMonitorNow: () => void; checkingMonitorNow: boolean; handoffFocusSignal?: number; + externalReferences?: MarkdownExternalReferenceMap; }; function IssueDetailActivityTab({ @@ -1066,6 +1071,7 @@ function IssueDetailActivityTab({ onCheckMonitorNow, checkingMonitorNow, handoffFocusSignal = 0, + externalReferences, }: IssueDetailActivityTabProps) { const { data: activity, isLoading: activityLoading } = useQuery({ queryKey: queryKeys.issues.activity(issueId), @@ -1267,6 +1273,11 @@ function IssueDetailActivityTab({ }} /> + {linkedApprovals && linkedApprovals.length > 0 && (
{linkedApprovals.map((approval) => ( @@ -1287,7 +1298,6 @@ function IssueDetailActivityTab({ ))}
)} - { const attachment = await uploadAttachment.mutateAsync(file); return attachment.contentPath; @@ -4251,6 +4262,7 @@ export function IssueDetail() { resumeFromBacklogPending={ updateIssue.isPending && updateIssue.variables?.status === "todo" } + externalReferences={externalObjectsState.markdownReferences} /> ) : null} @@ -4274,6 +4286,7 @@ export function IssueDetail() { }} onCheckMonitorNow={() => checkIssueMonitorNow.mutate()} checkingMonitorNow={checkIssueMonitorNow.isPending} + externalReferences={externalObjectsState.markdownReferences} /> ) : null}