diff --git a/apps/web/src/components/NoActiveThreadState.tsx b/apps/web/src/components/NoActiveThreadState.tsx index cd1f76ed2c..4b9f58ab3d 100644 --- a/apps/web/src/components/NoActiveThreadState.tsx +++ b/apps/web/src/components/NoActiveThreadState.tsx @@ -1,9 +1,12 @@ import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from "./ui/empty"; -import { SidebarInset, SidebarTrigger } from "./ui/sidebar"; +import { SidebarInset, SidebarTrigger, useSidebar } from "./ui/sidebar"; import { isElectron } from "../env"; import { cn } from "~/lib/utils"; export function NoActiveThreadState() { + const { isMobile, open, openMobile } = useSidebar(); + const sidebarOpen = isMobile ? openMobile : open; + return (
@@ -21,7 +24,7 @@ export function NoActiveThreadState() { ) : (
- + {!sidebarOpen ? : null} No active thread diff --git a/apps/web/src/components/ProjectScriptsControl.tsx b/apps/web/src/components/ProjectScriptsControl.tsx index 11b08cc2cf..a20c9e34db 100644 --- a/apps/web/src/components/ProjectScriptsControl.tsx +++ b/apps/web/src/components/ProjectScriptsControl.tsx @@ -271,7 +271,7 @@ export default function ProjectScriptsControl({ {primaryScript ? ( - )} -
-

- {formatTimestamp(row.message.createdAt, ctx.timestampFormat)} -

-
+ +
+ {displayedUserMessage.copyText && ( + + )} + {canRevertAgentWork && ( + + )} +

+ {formatTimestamp(row.message.createdAt, ctx.timestampFormat)} +

); @@ -379,7 +407,9 @@ function TimelineRowContent({ row }: { row: TimelineRow }) { {row.kind === "message" && row.message.role === "assistant" && (() => { - const messageText = row.message.text || (row.message.streaming ? "" : "(empty response)"); + const messageText = + row.message.text || + (row.message.streaming ? "" : "(empty response)"); const assistantTurnStillInProgress = ctx.activeTurnInProgress && ctx.activeTurnId !== null && @@ -396,7 +426,9 @@ function TimelineRowContent({ row }: { row: TimelineRow }) {
- {ctx.completionSummary ? `Response • ${ctx.completionSummary}` : "Response"} + {ctx.completionSummary + ? `Response • ${ctx.completionSummary}` + : "Response"}
@@ -424,7 +456,10 @@ function TimelineRowContent({ row }: { row: TimelineRow }) { ) : ( formatMessageMeta( row.message.createdAt, - formatElapsed(row.durationStart, row.message.completedAt), + formatElapsed( + row.durationStart, + row.message.completedAt, + ), ctx.timestampFormat, ) )} @@ -434,8 +469,7 @@ function TimelineRowContent({ row }: { row: TimelineRow }) { ) : null} @@ -493,7 +527,9 @@ function WorkingTimer({ createdAt }: { createdAt: string }) { const id = setInterval(() => setNowMs(Date.now()), 1000); return () => clearInterval(id); }, [createdAt]); - return <>{formatWorkingTimer(createdAt, new Date(nowMs).toISOString()) ?? "0s"}; + return ( + <>{formatWorkingTimer(createdAt, new Date(nowMs).toISOString()) ?? "0s"} + ); } /** Live timestamp + elapsed duration for a streaming assistant message. */ @@ -527,7 +563,10 @@ function LiveMessageMeta({ const WorkGroupSection = memo(function WorkGroupSection({ groupedEntries, }: { - groupedEntries: Extract["groupedEntries"]; + groupedEntries: Extract< + MessagesTimelineRow, + { kind: "work" } + >["groupedEntries"]; }) { const { workspaceRoot } = use(TimelineRowCtx); const [isExpanded, setIsExpanded] = useState(false); @@ -537,7 +576,9 @@ const WorkGroupSection = memo(function WorkGroupSection({ ? groupedEntries.slice(-MAX_VISIBLE_WORK_LOG_ENTRIES) : groupedEntries; const hiddenCount = groupedEntries.length - visibleEntries.length; - const onlyToolEntries = groupedEntries.every((entry) => entry.tone === "tool"); + const onlyToolEntries = groupedEntries.every( + (entry) => entry.tone === "tool", + ); const showHeader = hasOverflow || !onlyToolEntries; const groupLabel = onlyToolEntries ? "Tool calls" : "Work log"; @@ -574,31 +615,33 @@ const WorkGroupSection = memo(function WorkGroupSection({ /** Subscribes directly to the UI state store for expand/collapse state, * so toggling re-renders only this component — not the entire list. */ -const AssistantChangedFilesSection = memo(function AssistantChangedFilesSection({ - turnSummary, - routeThreadKey, - resolvedTheme, - onOpenTurnDiff, -}: { - turnSummary: TurnDiffSummary | undefined; - routeThreadKey: string; - resolvedTheme: "light" | "dark"; - onOpenTurnDiff: (turnId: TurnId, filePath?: string) => void; -}) { - if (!turnSummary) return null; - const checkpointFiles = turnSummary.files; - if (checkpointFiles.length === 0) return null; +const AssistantChangedFilesSection = memo( + function AssistantChangedFilesSection({ + turnSummary, + routeThreadKey, + resolvedTheme, + onOpenTurnDiff, + }: { + turnSummary: TurnDiffSummary | undefined; + routeThreadKey: string; + resolvedTheme: "light" | "dark"; + onOpenTurnDiff: (turnId: TurnId, filePath?: string) => void; + }) { + if (!turnSummary) return null; + const checkpointFiles = turnSummary.files; + if (checkpointFiles.length === 0) return null; - return ( - - ); -}); + return ( + + ); + }, +); /** Inner component that only mounts when there are actual changed files, * so the store subscription is unconditional (no hooks after early return). */ @@ -616,9 +659,14 @@ function AssistantChangedFilesSectionInner({ onOpenTurnDiff: (turnId: TurnId, filePath?: string) => void; }) { const allDirectoriesExpanded = useUiStateStore( - (store) => store.threadChangedFilesExpandedById[routeThreadKey]?.[turnSummary.turnId] ?? true, + (store) => + store.threadChangedFilesExpandedById[routeThreadKey]?.[ + turnSummary.turnId + ] ?? true, + ); + const setExpanded = useUiStateStore( + (store) => store.setThreadChangedFilesExpanded, ); - const setExpanded = useUiStateStore((store) => store.setThreadChangedFilesExpanded); const summaryStat = summarizeTurnDiffStats(checkpointFiles); const changedFileCountLabel = String(checkpointFiles.length); @@ -630,7 +678,10 @@ function AssistantChangedFilesSectionInner({ {hasNonZeroStat(summaryStat) && ( <> - + )}

@@ -640,7 +691,13 @@ function AssistantChangedFilesSectionInner({ size="xs" variant="outline" data-scroll-anchor-ignore - onClick={() => setExpanded(routeThreadKey, turnSummary.turnId, !allDirectoriesExpanded)} + onClick={() => + setExpanded( + routeThreadKey, + turnSummary.turnId, + !allDirectoriesExpanded, + ) + } > {allDirectoriesExpanded ? "Collapse all" : "Expand all"} @@ -648,7 +705,9 @@ function AssistantChangedFilesSectionInner({ type="button" size="xs" variant="outline" - onClick={() => onOpenTurnDiff(turnSummary.turnId, checkpointFiles[0]?.path)} + onClick={() => + onOpenTurnDiff(turnSummary.turnId, checkpointFiles[0]?.path) + } > View diff @@ -671,13 +730,20 @@ function AssistantChangedFilesSectionInner({ // --------------------------------------------------------------------------- const UserMessageTerminalContextInlineLabel = memo( - function UserMessageTerminalContextInlineLabel(props: { context: ParsedTerminalContextEntry }) { + function UserMessageTerminalContextInlineLabel(props: { + context: ParsedTerminalContextEntry; + }) { const tooltipText = props.context.body.length > 0 ? `${props.context.header}\n${props.context.body}` : props.context.header; - return ; + return ( + + ); }, ); @@ -705,7 +771,9 @@ const UserMessageBody = memo(function UserMessageBody(props: { } if (matchIndex > cursor) { inlineNodes.push( - + {props.text.slice(cursor, matchIndex)} , ); @@ -744,14 +812,21 @@ const UserMessageBody = memo(function UserMessageBody(props: { />, ); inlineNodes.push( -