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
9 changes: 8 additions & 1 deletion apps/code/src/renderer/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,14 @@ export class PostHogAPIClient {
async createTask(
options: Pick<Task, "description"> &
Partial<
Pick<Task, "title" | "repository" | "json_schema" | "origin_product">
Pick<
Task,
| "title"
| "repository"
| "json_schema"
| "origin_product"
| "signal_report"
>
> & {
github_integration?: number | null;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -601,6 +602,7 @@ export function InboxSignalsTab() {
style={{ flex: 1 }}
>
<Flex direction="column" gap="2" p="2" className="min-w-0">
{/* ── Description ─────────────────────────────────────── */}
<SignalReportSummaryMarkdown
content={selectedReport.summary}
fallback="No summary available."
Expand Down Expand Up @@ -652,6 +654,7 @@ export function InboxSignalsTab() {
</Box>
)}

{/* ── Signals ─────────────────────────────────────────── */}
{signals.length > 0 && (
<Box>
<Text
Expand All @@ -675,6 +678,7 @@ export function InboxSignalsTab() {
</Text>
)}

{/* ── Evidence ────────────────────────────────────────── */}
<Box>
<Text
size="1"
Expand Down Expand Up @@ -743,6 +747,12 @@ export function InboxSignalsTab() {
</Box>
</Flex>
</ScrollArea>
{/* ── Research task logs (bottom preview + overlay) ─────── */}
<ReportTaskLogs
key={selectedReport.id}
reportId={selectedReport.id}
reportStatus={selectedReport.status}
/>
</>
);
} else {
Expand Down Expand Up @@ -910,6 +920,7 @@ export function InboxSignalsTab() {
flex: 1,
minWidth: 0,
height: "100%",
position: "relative",
}}
>
{rightPaneContent}
Expand Down
211 changes: 211 additions & 0 deletions apps/code/src/renderer/features/inbox/components/ReportTaskLogs.tsx
Original file line number Diff line number Diff line change
@@ -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<Task | null>(
["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: <CircleNotchIcon size={14} className="animate-spin" />,
};
case "completed":
return {
label: "Completed",
color: "var(--green-9)",
icon: <CheckCircleIcon size={14} weight="fill" />,
};
case "failed":
return {
label: "Failed",
color: "var(--red-9)",
icon: <XCircleIcon size={14} weight="fill" />,
};
case "cancelled":
return {
label: "Cancelled",
color: "var(--gray-9)",
icon: <XCircleIcon size={14} />,
};
default:
return {
label: "Queued",
color: "var(--gray-9)",
icon: <Spinner size="1" />,
};
}
}

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 (
<Flex
align="center"
gap="2"
px="3"
py="2"
className="shrink-0 border-gray-5 border-t"
style={{ height: BAR_HEIGHT }}
>
<Spinner size="1" />
<Text size="1" color="gray" className="text-[12px]">
{isLoading
? "Loading task…"
: reportStatus === "in_progress"
? "Research is running…"
: "No research task yet."}
</Text>
</Flex>
);
}

const status = getTaskStatusSummary(task);

return (
<>
{/* In-flow spacer — same height as the bar */}
<div
className="shrink-0 border-gray-5 border-t"
style={{ height: BAR_HEIGHT }}
/>

{/* Scrim — biome-ignore: scrim is a non-semantic dismissal target */}
{/* biome-ignore lint/a11y/useKeyWithClickEvents: scrim dismiss */}
{/* biome-ignore lint/a11y/noStaticElementInteractions: scrim dismiss */}
<div
onClick={expanded ? () => 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. */}
<div
style={{
position: "absolute",
left: 0,
right: 0,
bottom: 0,
zIndex: 11,
display: "flex",
flexDirection: "column",
borderTop: "1px solid var(--gray-6)",
background: "var(--color-background)",
pointerEvents: "none",
top: expanded ? "15%" : `calc(100% - ${BAR_HEIGHT}px)`,
transition: "top 0.25s cubic-bezier(0.32, 0.72, 0, 1)",
}}
>
<button
type="button"
onClick={() => setExpanded((v) => !v)}
style={{ pointerEvents: "auto" }}
className="flex w-full shrink-0 cursor-pointer items-center gap-2 border-gray-5 border-b bg-transparent px-3 py-2 text-left transition-colors hover:bg-gray-2"
>
<span style={{ color: status.color }}>{status.icon}</span>
<Text size="1" weight="medium" className="flex-1 text-[12px]">
Research task
</Text>
<Text
size="1"
className="text-[11px]"
style={{ color: status.color }}
>
{status.label}
</Text>
<span
className="inline-flex text-gray-9"
style={{
transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
transition: "transform 0.2s ease",
}}
>
<CaretUpIcon size={12} />
</span>
</button>

<div
style={{
flex: 1,
minHeight: 0,
overflow: "hidden",
pointerEvents: expanded ? "auto" : "none",
}}
>
<TaskLogsPanel
taskId={task.id}
task={task}
hideInput={reportStatus !== "ready"}
hideCloudStatus
/>
</div>
</div>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -90,6 +92,7 @@ export function SessionView({
slackThreadUrl,
compact = false,
isActiveSession = true,
hideInput = false,
}: SessionViewProps) {
const showRawLogs = useShowRawLogs();
const { setShowRawLogs } = useSessionViewActions();
Expand Down Expand Up @@ -502,7 +505,7 @@ export function SessionView({
)}
</Flex>
</Flex>
) : firstPendingPermission ? (
) : hideInput ? null : firstPendingPermission ? (
<Box className="border-gray-4 border-t">
<Box className="mx-auto max-w-[750px] p-2">
<PermissionSelector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@ import { useCallback, useEffect, useMemo } from "react";
interface TaskLogsPanelProps {
taskId: string;
task: Task;
/** Hide the message input — log-only view. */
hideInput?: boolean;
/** Hide the cloud status footer bar. */
hideCloudStatus?: boolean;
}

export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
export function TaskLogsPanel({
taskId,
task,
hideInput,
hideCloudStatus,
}: TaskLogsPanelProps) {
const isWorkspaceLoaded = useWorkspaceLoaded();
const { isPending: isCreatingWorkspace } = useCreateWorkspace();
const repoKey = getTaskRepository(task);
Expand Down Expand Up @@ -158,14 +167,15 @@ export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
hasError={hasError}
errorTitle={errorTitle}
errorMessage={errorMessage}
hideInput={hideInput}
onRetry={isCloud ? undefined : handleRetry}
onNewSession={isCloud ? undefined : handleNewSession}
isInitializing={isInitializing}
slackThreadUrl={slackThreadUrl}
/>
</ErrorBoundary>
</Box>
{isCloud && (
{isCloud && !hideCloudStatus && (
<Flex
align="center"
justify="center"
Expand Down
3 changes: 3 additions & 0 deletions apps/code/src/renderer/sagas/task/task-creation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface TaskCreationInput {
reasoningLevel?: string;
environmentId?: string;
sandboxEnvironmentId?: string;
signalReportId?: string;
}

export interface TaskCreationOutput {
Expand Down Expand Up @@ -388,6 +389,8 @@ export class TaskCreationSaga extends Saga<
input.workspaceMode === "cloud"
? input.githubIntegrationId
: undefined,
origin_product: input.signalReportId ? "signal_report" : undefined,
signal_report: input.signalReportId ?? undefined,
});
return result as unknown as Task;
},
Expand Down
1 change: 1 addition & 0 deletions apps/code/src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface Task {
repository?: string | null; // Format: "organization/repository" (e.g., "posthog/posthog-js")
github_integration?: number | null;
json_schema?: Record<string, unknown> | null;
signal_report?: string | null;
latest_run?: TaskRun;
}

Expand Down
Loading