From 52432febb4e838e7161128f3e0d8a06b068180ea Mon Sep 17 00:00:00 2001 From: Michael Matloka Date: Wed, 1 Apr 2026 04:31:22 +0200 Subject: [PATCH] feat(inbox): show research task logs inline in report detail pane - Add signal_report field to Task type (FK from backend's sig/add-signal-report-task branch) - Add signalReportId to TaskCreationInput and pass signal_report through API - Add ReportTaskLogs component that finds the backend research task (origin_product: "signal_report") for a report and embeds TaskLogsPanel - Show live-streaming task progress in the report detail pane during research - Only matches backend research tasks, not user-created "Run cloud" tasks Depends on PostHog/posthog sig/add-signal-report-task branch. --- apps/code/src/renderer/api/posthogClient.ts | 9 +- .../inbox/components/InboxSignalsTab.tsx | 11 + .../inbox/components/ReportTaskLogs.tsx | 211 ++++++++++++++++++ .../sessions/components/SessionView.tsx | 5 +- .../task-detail/components/TaskLogsPanel.tsx | 14 +- .../src/renderer/sagas/task/task-creation.ts | 3 + apps/code/src/shared/types.ts | 1 + 7 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 apps/code/src/renderer/features/inbox/components/ReportTaskLogs.tsx diff --git a/apps/code/src/renderer/api/posthogClient.ts b/apps/code/src/renderer/api/posthogClient.ts index 8bab9f34c..c66ef1217 100644 --- a/apps/code/src/renderer/api/posthogClient.ts +++ b/apps/code/src/renderer/api/posthogClient.ts @@ -440,7 +440,14 @@ export class PostHogAPIClient { async createTask( options: Pick & Partial< - Pick + Pick< + Task, + | "title" + | "repository" + | "json_schema" + | "origin_product" + | "signal_report" + > > & { github_integration?: number | null; }, diff --git a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx index 432b9637c..72e82d1a7 100644 --- a/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx +++ b/apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx @@ -59,6 +59,7 @@ import { useRendererWindowFocusStore } from "@stores/rendererWindowFocusStore"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; import { ReportCard } from "./ReportCard"; +import { ReportTaskLogs } from "./ReportTaskLogs"; import { SignalCard } from "./SignalCard"; import { SignalReportPriorityBadge } from "./SignalReportPriorityBadge"; import { SignalReportSummaryMarkdown } from "./SignalReportSummaryMarkdown"; @@ -601,6 +602,7 @@ export function InboxSignalsTab() { style={{ flex: 1 }} > + {/* ── Description ─────────────────────────────────────── */} )} + {/* ── Signals ─────────────────────────────────────────── */} {signals.length > 0 && ( )} + {/* ── Evidence ────────────────────────────────────────── */} + {/* ── Research task logs (bottom preview + overlay) ─────── */} + ); } else { @@ -910,6 +920,7 @@ export function InboxSignalsTab() { flex: 1, minWidth: 0, height: "100%", + position: "relative", }} > {rightPaneContent} diff --git a/apps/code/src/renderer/features/inbox/components/ReportTaskLogs.tsx b/apps/code/src/renderer/features/inbox/components/ReportTaskLogs.tsx new file mode 100644 index 000000000..e8ce73713 --- /dev/null +++ b/apps/code/src/renderer/features/inbox/components/ReportTaskLogs.tsx @@ -0,0 +1,211 @@ +import { TaskLogsPanel } from "@features/task-detail/components/TaskLogsPanel"; +import { useAuthenticatedQuery } from "@hooks/useAuthenticatedQuery"; +import { + CaretUpIcon, + CheckCircleIcon, + CircleNotchIcon, + XCircleIcon, +} from "@phosphor-icons/react"; +import { Flex, Spinner, Text } from "@radix-ui/themes"; +import type { SignalReportStatus, Task } from "@shared/types"; +import { useState } from "react"; + +function useReportTask(reportId: string) { + return useAuthenticatedQuery( + ["inbox", "report-task", reportId], + async (client) => { + const tasks = (await client.getTasks({ + originProduct: "signal_report", + })) as unknown as Task[]; + return tasks.find((t) => t.signal_report === reportId) ?? null; + }, + { + enabled: !!reportId, + staleTime: 10_000, + }, + ); +} + +function getTaskStatusSummary(task: Task): { + label: string; + color: string; + icon: React.ReactNode; +} { + const status = task.latest_run?.status; + switch (status) { + case "started": + case "in_progress": + return { + label: task.latest_run?.stage + ? `Running — ${task.latest_run.stage}` + : "Running…", + color: "var(--amber-9)", + icon: , + }; + case "completed": + return { + label: "Completed", + color: "var(--green-9)", + icon: , + }; + case "failed": + return { + label: "Failed", + color: "var(--red-9)", + icon: , + }; + case "cancelled": + return { + label: "Cancelled", + color: "var(--gray-9)", + icon: , + }; + default: + return { + label: "Queued", + color: "var(--gray-9)", + icon: , + }; + } +} + +const BAR_HEIGHT = 38; + +interface ReportTaskLogsProps { + reportId: string; + reportStatus: SignalReportStatus; +} + +export function ReportTaskLogs({ + reportId, + reportStatus, +}: ReportTaskLogsProps) { + const { data: task, isLoading } = useReportTask(reportId); + const [expanded, setExpanded] = useState(false); + + const showBar = + isLoading || + !!task || + reportStatus === "in_progress" || + reportStatus === "ready"; + + if (!showBar) { + return null; + } + + const hasTask = !isLoading && !!task; + + // No task — simple in-flow status bar + if (!hasTask) { + return ( + + + + {isLoading + ? "Loading task…" + : reportStatus === "in_progress" + ? "Research is running…" + : "No research task yet."} + + + ); + } + + const status = getTaskStatusSummary(task); + + return ( + <> + {/* In-flow spacer — same height as the bar */} +
+ + {/* Scrim — biome-ignore: scrim is a non-semantic dismissal target */} + {/* biome-ignore lint/a11y/useKeyWithClickEvents: scrim dismiss */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: scrim dismiss */} +
setExpanded(false) : undefined} + style={{ + position: "absolute", + inset: 0, + zIndex: 10, + background: "rgba(0, 0, 0, 0.32)", + opacity: expanded ? 1 : 0, + transition: "opacity 0.2s ease", + pointerEvents: expanded ? "auto" : "none", + }} + /> + + {/* Sliding card — animates `top` to avoid a Chromium layout + bug with `transform` on absolute elements in flex+scroll. */} +
+ + +
+ +
+
+ + ); +} diff --git a/apps/code/src/renderer/features/sessions/components/SessionView.tsx b/apps/code/src/renderer/features/sessions/components/SessionView.tsx index 3f32f8be6..e9086a3b6 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionView.tsx @@ -61,6 +61,8 @@ interface SessionViewProps { slackThreadUrl?: string; compact?: boolean; isActiveSession?: boolean; + /** Hide the message input and permission UI — log-only view. */ + hideInput?: boolean; } const DEFAULT_ERROR_MESSAGE = @@ -90,6 +92,7 @@ export function SessionView({ slackThreadUrl, compact = false, isActiveSession = true, + hideInput = false, }: SessionViewProps) { const showRawLogs = useShowRawLogs(); const { setShowRawLogs } = useSessionViewActions(); @@ -502,7 +505,7 @@ export function SessionView({ )} - ) : firstPendingPermission ? ( + ) : hideInput ? null : firstPendingPermission ? ( - {isCloud && ( + {isCloud && !hideCloudStatus && ( | null; + signal_report?: string | null; latest_run?: TaskRun; }