Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions apps/code/src/renderer/features/inbox/components/InboxReportCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useInboxReportById } from "@features/inbox/hooks/useInboxReports";
import { useInboxReportSelectionStore } from "@features/inbox/stores/inboxReportSelectionStore";
import { useInboxSignalsFilterStore } from "@features/inbox/stores/inboxSignalsFilterStore";
import {
ArrowSquareOut,
CaretDown,
CaretUp,
Tray,
} from "@phosphor-icons/react";
import { Box, Flex, Spinner, Text } from "@radix-ui/themes";
import { navigateToInbox } from "@renderer/navigationBridge";
import { useCallback, useState } from "react";
import { SignalReportActionabilityBadge } from "./utils/SignalReportActionabilityBadge";
import { SignalReportPriorityBadge } from "./utils/SignalReportPriorityBadge";
import { SignalReportStatusBadge } from "./utils/SignalReportStatusBadge";
import { SignalReportSummaryMarkdown } from "./utils/SignalReportSummaryMarkdown";

interface InboxReportCardProps {
reportId: string;
}

/**
* Compact, expandable card for the inbox report a task is associated with.
* Rendered under the initial prompt so the report can be read inline instead
* of navigating away to the inbox view. Reads the report from the same query
* cache the inbox uses (`useInboxReportById`), so it stays in sync and an
* "Open in inbox" action can reuse the warmed cache for the detail pane.
*/
export function InboxReportCard({ reportId }: InboxReportCardProps) {
const [expanded, setExpanded] = useState(false);
const { data: report, isLoading } = useInboxReportById(reportId, {
staleTime: 60_000,
});

const setSelectedReportIds = useInboxReportSelectionStore(
(s) => s.setSelectedReportIds,
);
const resetFilters = useInboxSignalsFilterStore((s) => s.resetFilters);

const handleOpenInInbox = useCallback(() => {
// Reset inbox-local filters first so the report isn't hidden by an active
// filter, then navigate and select it (mirrors the deep-link open path).
resetFilters();
navigateToInbox();
setSelectedReportIds([reportId]);
}, [reportId, resetFilters, setSelectedReportIds]);

if (isLoading && !report) {
return (
<Flex
align="center"
gap="2"
className="mt-2 rounded-md border border-gray-5 bg-gray-1 px-2.5 py-2"
>
<Spinner size="1" />
<Text color="gray" className="text-[12px]">
Loading inbox report...
</Text>
</Flex>
);
}

if (!report) return null;

return (
<Box className="mt-2 overflow-hidden rounded-md border border-gray-5 bg-gray-1">
<button
type="button"
onClick={() => setExpanded((prev) => !prev)}
aria-expanded={expanded}
className="flex w-full items-center gap-2 px-2.5 py-2 text-left hover:bg-gray-2"
>
<Tray size={14} weight="duotone" className="shrink-0 text-gray-10" />
<Text className="min-w-0 flex-1 truncate font-medium text-[13px]">
{report.title ?? "Inbox report"}
</Text>
<Box className="shrink-0">
<SignalReportStatusBadge status={report.status} />
</Box>
{expanded ? (
<CaretUp size={12} className="shrink-0 text-gray-10" />
) : (
<CaretDown size={12} className="shrink-0 text-gray-10" />
)}
</button>

{expanded && (
<Flex
direction="column"
gap="2"
className="border-gray-5 border-t px-2.5 py-2"
>
<SignalReportSummaryMarkdown
content={report.summary}
fallback="No summary available."
variant="detail"
/>

{(report.priority || report.actionability) && (
<Flex align="center" gap="1" wrap="wrap">
<SignalReportPriorityBadge priority={report.priority} />
<SignalReportActionabilityBadge
actionability={report.actionability}
/>
</Flex>
)}

<button
type="button"
onClick={handleOpenInInbox}
className="inline-flex items-center gap-1 self-start text-[12px] text-accent-11 hover:text-accent-12"
>
<ArrowSquareOut size={12} />
<span>Open in inbox</span>
</button>
</Flex>
)}
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ export function ConversationView({
? slackThreadUrl
: undefined
}
signalReportId={
item.id === firstUserMessageId
? (task?.signal_report ?? undefined)
: undefined
}
/>
);
case "git_action":
Expand Down Expand Up @@ -245,7 +250,14 @@ export function ConversationView({
);
}
},
[repoPath, taskId, slackThreadUrl, firstUserMessageId, initialItemIds],
[
repoPath,
taskId,
slackThreadUrl,
firstUserMessageId,
initialItemIds,
task?.signal_report,
],
);

const getItemKey = useCallback((item: ConversationItem) => item.id, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Tooltip } from "@components/ui/Tooltip";
import { MarkdownRenderer } from "@features/editor/components/MarkdownRenderer";
import { InboxReportCard } from "@features/inbox/components/InboxReportCard";
import {
CaretDown,
CaretUp,
Expand Down Expand Up @@ -28,6 +29,9 @@ interface UserMessageProps {
content: string;
timestamp?: number;
sourceUrl?: string;
/** Inbox report this message's task is associated with — rendered as an
* expandable card under the message (first user message only). */
signalReportId?: string;
attachments?: UserMessageAttachment[];
animate?: boolean;
}
Expand All @@ -52,6 +56,7 @@ export const UserMessage = memo(function UserMessage({
content,
timestamp,
sourceUrl,
signalReportId,
attachments = [],
animate = true,
}: UserMessageProps) {
Expand Down Expand Up @@ -160,6 +165,7 @@ export const UserMessage = memo(function UserMessage({
<span>View Slack thread</span>
</a>
)}
{signalReportId && <InboxReportCard reportId={signalReportId} />}
<Box className="absolute top-1 right-1 flex select-none items-center gap-1.5 rounded-md bg-gray-2 py-0.5 pr-1 pl-2 opacity-0 shadow-sm transition-opacity group-hover/msg:opacity-100">
{timestamp != null && (
<span aria-hidden className="text-[11px] text-gray-10">
Expand Down
Loading