refactor: replace action log sheet with dialog component and update related references

This commit is contained in:
Anish Sarkar 2026-05-18 01:34:41 +05:30
parent c580addc04
commit 5bcda6b83b
11 changed files with 168 additions and 91 deletions

View file

@ -451,7 +451,7 @@ export default function NewChatPage() {
}, [params.search_space_id]); }, [params.search_space_id]);
// Unified store for agent-action rows (the same react-query cache // Unified store for agent-action rows (the same react-query cache
// the agent-actions sheet, the inline Revert button, and the // the agent-actions dialog, the inline Revert button, and the
// per-turn Revert button all read). Hydrates from // per-turn Revert button all read). Hydrates from
// ``GET /threads/{id}/actions`` and is updated incrementally by the // ``GET /threads/{id}/actions`` and is updated incrementally by the
// SSE handlers + revert-batch results below — no atom side-channel. // SSE handlers + revert-batch results below — no atom side-channel.

View file

@ -137,7 +137,7 @@ const FLAG_GROUPS: FlagGroup[] = [
{ {
id: "tier5", id: "tier5",
title: "Tier 5 — Audit + revert", title: "Tier 5 — Audit + revert",
subtitle: "Action log + revert route used by the Agent Actions sheet.", subtitle: "Action log + revert route used by the Agent Actions dialog.",
flags: [ flags: [
{ {
key: "enable_action_log", key: "enable_action_log",

View file

@ -0,0 +1,19 @@
import { atom } from "jotai";
interface ActionLogDialogState {
open: boolean;
threadId: number | null;
}
export const actionLogDialogAtom = atom<ActionLogDialogState>({
open: false,
threadId: null,
});
export const openActionLogDialogAtom = atom(null, (_get, set, threadId: number) => {
set(actionLogDialogAtom, { open: true, threadId });
});
export const closeActionLogDialogAtom = atom(null, (_get, set) => {
set(actionLogDialogAtom, { open: false, threadId: null });
});

View file

@ -1,19 +0,0 @@
import { atom } from "jotai";
interface ActionLogSheetState {
open: boolean;
threadId: number | null;
}
export const actionLogSheetAtom = atom<ActionLogSheetState>({
open: false,
threadId: null,
});
export const openActionLogSheetAtom = atom(null, (_get, set, threadId: number) => {
set(actionLogSheetAtom, { open: true, threadId });
});
export const closeActionLogSheetAtom = atom(null, (_get, set) => {
set(actionLogSheetAtom, { open: false, threadId: null });
});

View file

@ -3,7 +3,7 @@
import { useAtomValue, useSetAtom } from "jotai"; import { useAtomValue, useSetAtom } from "jotai";
import { Workflow } from "lucide-react"; import { Workflow } from "lucide-react";
import { useCallback } from "react"; import { useCallback } from "react";
import { openActionLogSheetAtom } from "@/atoms/agent/action-log-sheet.atom"; import { openActionLogDialogAtom } from "@/atoms/agent/action-log-dialog.atom";
import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom"; import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@ -13,7 +13,7 @@ interface ActionLogButtonProps {
} }
/** /**
* Header button that opens the agent action log sheet for the current * Header button that opens the agent action log dialog for the current
* thread. Renders nothing when: * thread. Renders nothing when:
* - the action log feature flag is off (graceful no-op for older * - the action log feature flag is off (graceful no-op for older
* deployments), OR * deployments), OR
@ -21,7 +21,7 @@ interface ActionLogButtonProps {
*/ */
export function ActionLogButton({ threadId }: ActionLogButtonProps) { export function ActionLogButton({ threadId }: ActionLogButtonProps) {
const { data: flags } = useAtomValue(agentFlagsAtom); const { data: flags } = useAtomValue(agentFlagsAtom);
const open = useSetAtom(openActionLogSheetAtom); const open = useSetAtom(openActionLogDialogAtom);
const enabled = !!flags?.enable_action_log && !flags?.disable_new_agent_stack; const enabled = !!flags?.enable_action_log && !flags?.disable_new_agent_stack;

View file

@ -2,19 +2,19 @@
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useAtom, useAtomValue } from "jotai"; import { useAtom, useAtomValue } from "jotai";
import { RefreshCcw, Workflow, } from "lucide-react"; import { RefreshCcw, Workflow } from "lucide-react";
import { useCallback } from "react"; import { useCallback } from "react";
import { actionLogSheetAtom } from "@/atoms/agent/action-log-sheet.atom"; import { actionLogDialogAtom } from "@/atoms/agent/action-log-dialog.atom";
import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom"; import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Sheet, Dialog,
SheetContent, DialogContent,
SheetDescription, DialogDescription,
SheetHeader, DialogTitle,
SheetTitle, } from "@/components/ui/dialog";
} from "@/components/ui/sheet"; import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { agentActionsQueryKey, useAgentActionsQuery } from "@/hooks/use-agent-actions-query"; import { agentActionsQueryKey, useAgentActionsQuery } from "@/hooks/use-agent-actions-query";
import { ActionLogItem } from "./action-log-item"; import { ActionLogItem } from "./action-log-item";
@ -25,7 +25,7 @@ function EmptyState() {
<div className="flex max-w-[260px] flex-col gap-1.5"> <div className="flex max-w-[260px] flex-col gap-1.5">
<p className="text-sm font-semibold tracking-tight">No actions logged yet</p> <p className="text-sm font-semibold tracking-tight">No actions logged yet</p>
<p className="text-xs leading-relaxed text-muted-foreground"> <p className="text-xs leading-relaxed text-muted-foreground">
A complete audit trail of every tool the agent uses in this thread will appear here A complete audit trail of every tool the agent uses in this thread will appear here
</p> </p>
</div> </div>
</div> </div>
@ -64,8 +64,8 @@ function LoadingState() {
); );
} }
export function ActionLogSheet() { export function ActionLogDialog() {
const [state, setState] = useAtom(actionLogSheetAtom); const [state, setState] = useAtom(actionLogDialogAtom);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: flags } = useAtomValue(agentFlagsAtom); const { data: flags } = useAtomValue(agentFlagsAtom);
@ -78,6 +78,13 @@ export function ActionLogSheet() {
{ enabled: state.open && actionLogEnabled } { enabled: state.open && actionLogEnabled }
); );
const handleOpenChange = useCallback(
(open: boolean) => {
setState((current) => (open ? { ...current, open } : { open: false, threadId: null }));
},
[setState]
);
const handleRevertSuccess = useCallback(() => { const handleRevertSuccess = useCallback(() => {
if (threadId !== null) { if (threadId !== null) {
queryClient.invalidateQueries({ queryKey: agentActionsQueryKey(threadId) }); queryClient.invalidateQueries({ queryKey: agentActionsQueryKey(threadId) });
@ -85,24 +92,22 @@ export function ActionLogSheet() {
}, [queryClient, threadId]); }, [queryClient, threadId]);
return ( return (
<Sheet open={state.open} onOpenChange={(open) => setState((s) => ({ ...s, open }))}> <Dialog open={state.open} onOpenChange={handleOpenChange}>
<SheetContent <DialogContent className="select-none flex h-[90vh] max-h-[640px] w-[95vw] max-w-[900px] flex-col gap-0 overflow-hidden p-0 [--card:var(--popover)] md:h-[80vh]">
side="right" <div className="shrink-0 px-6 pb-3 pt-6 pr-28">
className="flex h-full w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-md select-none" <div className="flex items-center gap-2">
> <DialogTitle className="text-lg font-semibold">Agent actions</DialogTitle>
<SheetHeader className="shrink-0 px-4 py-4"> {data?.total !== undefined && data.total > 0 ? (
<div className="flex items-center gap-2 pr-24">
<SheetTitle className="text-base font-semibold">Agent actions</SheetTitle>
{data?.total !== undefined && data.total > 0 && (
<Badge variant="secondary" className="text-[10px]"> <Badge variant="secondary" className="text-[10px]">
{data.total} {data.total}
</Badge> </Badge>
)} ) : null}
</div> </div>
<SheetDescription className="sr-only"> <DialogDescription className="sr-only">
Audit trail of every tool call the agent made in this thread. Audit trail of every tool call the agent made in this thread.
</SheetDescription> </DialogDescription>
</SheetHeader> <Separator className="mt-4" />
</div>
<Button <Button
size="sm" size="sm"
@ -135,7 +140,7 @@ export function ActionLogSheet() {
) : items.length === 0 ? ( ) : items.length === 0 ? (
<EmptyState /> <EmptyState />
) : ( ) : (
<div className="flex flex-col gap-2 p-3"> <div className="flex flex-col gap-2 px-4 pb-4">
{items.map((action) => ( {items.map((action) => (
<ActionLogItem <ActionLogItem
key={action.id} key={action.id}
@ -144,15 +149,15 @@ export function ActionLogSheet() {
onRevertSuccess={handleRevertSuccess} onRevertSuccess={handleRevertSuccess}
/> />
))} ))}
{data?.has_more && ( {data?.has_more ? (
<p className="py-2 text-center text-[11px] text-muted-foreground"> <p className="py-2 text-center text-[11px] text-muted-foreground">
Showing {items.length} of {data.total}. Older actions are paginated. Showing {items.length} of {data.total}. Older actions are paginated.
</p> </p>
)} ) : null}
</div> </div>
)} )}
</div> </div>
</SheetContent> </DialogContent>
</Sheet> </Dialog>
); );
} }

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { ChevronRight, RotateCcw, ShieldOff, Undo2 } from "lucide-react"; import { Check, ChevronRight, Copy, RotateCcw, Undo2 } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
@ -16,7 +16,6 @@ import {
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { getToolDisplayName, getToolIcon } from "@/contracts/enums/toolIcons"; import { getToolDisplayName, getToolIcon } from "@/contracts/enums/toolIcons";
import { type AgentAction, agentActionsApiService } from "@/lib/apis/agent-actions-api.service"; import { type AgentAction, agentActionsApiService } from "@/lib/apis/agent-actions-api.service";
import { AppError } from "@/lib/error"; import { AppError } from "@/lib/error";
@ -29,10 +28,55 @@ interface ActionLogItemProps {
onRevertSuccess: () => void; onRevertSuccess: () => void;
} }
function formatPrimitiveValue(value: unknown) {
if (value === null) return "null";
if (value === undefined) return "undefined";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return JSON.stringify(value, null, 2);
}
function ArgumentValue({ value }: { value: unknown }) {
const formatted = formatPrimitiveValue(value);
const isBlockValue =
typeof value === "object" ||
(typeof value === "string" && (value.includes("\n") || value.length > 120));
if (isBlockValue) {
return (
<pre className="mt-2 whitespace-pre-wrap break-words bg-popover px-4 py-3 text-[11px] leading-relaxed text-popover-foreground/80">
{formatted}
</pre>
);
}
return (
<p className="mt-1 break-words font-mono text-[11px] leading-relaxed text-popover-foreground/80">
{formatted}
</p>
);
}
function StructuredArguments({ args }: { args: Record<string, unknown> }) {
return (
<div className="divide-y divide-popover-border border-t border-popover-border">
{Object.entries(args).map(([key, value]) => (
<div key={key} className="bg-popover">
<div className="px-4 py-3">
<p className="font-mono text-[10px] font-medium text-muted-foreground">{key}</p>
<ArgumentValue value={value} />
</div>
</div>
))}
</div>
);
}
export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogItemProps) { export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogItemProps) {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [isReverting, setIsReverting] = useState(false); const [isReverting, setIsReverting] = useState(false);
const [confirmOpen, setConfirmOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false);
const [copiedSection, setCopiedSection] = useState<"arguments" | null>(null);
const isAlreadyReverted = action.reverted_by_action_id !== null; const isAlreadyReverted = action.reverted_by_action_id !== null;
const isRevertAction = action.is_revert_action; const isRevertAction = action.is_revert_action;
@ -42,11 +86,22 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
const displayName = getToolDisplayName(action.tool_name); const displayName = getToolDisplayName(action.tool_name);
const argsPreview = action.args ? JSON.stringify(action.args, null, 2) : null; const argsPreview = action.args ? JSON.stringify(action.args, null, 2) : null;
const truncatedArgs =
argsPreview && argsPreview.length > 600 ? `${argsPreview.slice(0, 600)}` : argsPreview;
const canRevert = action.reversible && !isAlreadyReverted && !isRevertAction && !hasError; const canRevert = action.reversible && !isAlreadyReverted && !isRevertAction && !hasError;
const handleCopyArguments = async () => {
if (!argsPreview) return;
try {
await navigator.clipboard.writeText(argsPreview);
setCopiedSection("arguments");
toast.success("Arguments copied");
window.setTimeout(() => setCopiedSection(null), 1200);
} catch {
toast.error("Failed to copy arguments.");
}
};
const handleRevert = async () => { const handleRevert = async () => {
setIsReverting(true); setIsReverting(true);
try { try {
@ -70,7 +125,7 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
return ( return (
<div <div
className={cn( className={cn(
"rounded-lg border bg-card transition-colors", "overflow-hidden rounded-lg border border-popover-border bg-popover text-popover-foreground transition-colors",
isAlreadyReverted && "opacity-70" isAlreadyReverted && "opacity-70"
)} )}
> >
@ -78,10 +133,10 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
type="button" type="button"
variant="ghost" variant="ghost"
onClick={() => setIsExpanded((v) => !v)} onClick={() => setIsExpanded((v) => !v)}
className="h-auto w-full items-start justify-start gap-3 p-3 text-left hover:bg-accent hover:text-accent-foreground" className="h-auto w-full items-start justify-start gap-3 rounded-none p-3 text-left hover:bg-accent hover:text-accent-foreground"
aria-expanded={isExpanded} aria-expanded={isExpanded}
> >
<div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-muted"> <div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-accent">
{isRevertAction ? ( {isRevertAction ? (
<Undo2 className="size-4 text-muted-foreground" /> <Undo2 className="size-4 text-muted-foreground" />
) : ( ) : (
@ -102,7 +157,10 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
</Badge> </Badge>
)} )}
{!isRevertAction && action.reversible && !isAlreadyReverted && ( {!isRevertAction && action.reversible && !isAlreadyReverted && (
<Badge variant="outline" className="text-[10px]"> <Badge
variant="secondary"
className="border-0 bg-neutral-200 px-1.5 py-0.5 text-[10px] text-neutral-700 dark:bg-neutral-700 dark:text-neutral-200"
>
Reversible Reversible
</Badge> </Badge>
)} )}
@ -116,55 +174,69 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
</div> </div>
<ChevronRight <ChevronRight
className={cn( className={cn(
"size-4 shrink-0 text-muted-foreground transition-transform", "size-4 shrink-0 self-center text-muted-foreground transition-transform",
isExpanded && "rotate-90" isExpanded && "rotate-90"
)} )}
/> />
</Button> </Button>
{isExpanded && ( {isExpanded && (
<div className="flex flex-col gap-3 border-t bg-muted/20 p-3"> <div className="flex flex-col border-t border-popover-border bg-accent/80">
{truncatedArgs && ( {action.args && argsPreview && (
<div> <div className="border-b border-popover-border">
<p className="mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <div className="flex items-center justify-between px-4 py-2">
Arguments <p className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
</p> Arguments
<pre className="max-h-48 overflow-auto rounded-md bg-background p-2 text-[11px] text-foreground/80"> </p>
{truncatedArgs} <Button
</pre> type="button"
size="sm"
variant="ghost"
onClick={handleCopyArguments}
className="size-6 rounded-lg p-0 text-muted-foreground hover:bg-popover hover:text-popover-foreground"
aria-label={
copiedSection === "arguments" ? "Arguments copied" : "Copy arguments"
}
>
{copiedSection === "arguments" ? (
<Check className="size-3" />
) : (
<Copy className="size-3" />
)}
</Button>
</div>
<StructuredArguments args={action.args} />
</div> </div>
)} )}
{action.error && ( {action.error && (
<div> <div className="border-b border-popover-border">
<p className="mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <p className="px-4 py-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
Error Error
</p> </p>
<pre className="max-h-32 overflow-auto rounded-md bg-destructive/10 p-2 text-[11px] text-destructive"> <pre className="max-h-32 overflow-auto border-t border-popover-border bg-destructive/10 px-4 py-3 text-[11px] text-destructive">
{JSON.stringify(action.error, null, 2)} {JSON.stringify(action.error, null, 2)}
</pre> </pre>
</div> </div>
)} )}
{action.reverse_descriptor && ( {action.reverse_descriptor && (
<div> <div className="border-b border-popover-border">
<p className="mb-1 text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <p className="px-4 py-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
Reverse plan Reverse plan
</p> </p>
<pre className="max-h-32 overflow-auto rounded-md bg-background p-2 text-[11px] text-foreground/80"> <pre className="max-h-32 overflow-auto border-t border-popover-border bg-popover px-4 py-3 text-[11px] text-popover-foreground/80">
{JSON.stringify(action.reverse_descriptor, null, 2)} {JSON.stringify(action.reverse_descriptor, null, 2)}
</pre> </pre>
</div> </div>
)} )}
<Separator /> <div className="flex items-center justify-between px-4 py-3">
<div className="flex items-center justify-between">
<p className="text-[10px] text-muted-foreground"> <p className="text-[10px] text-muted-foreground">
Action ID: <span className="font-mono">{action.id}</span> Action ID: <span className="font-mono">{action.id}</span>
</p> </p>
{canRevert ? ( {canRevert ? (
<AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}> <AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<AlertDialogTrigger asChild> <AlertDialogTrigger asChild>
<Button size="sm" variant="outline" className="gap-1.5"> <Button size="sm" variant="secondary" className="gap-1.5">
<RotateCcw className="size-3.5" /> <RotateCcw className="size-3.5" />
Revert Revert
</Button> </Button>
@ -186,6 +258,7 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
handleRevert(); handleRevert();
}} }}
disabled={isReverting} disabled={isReverting}
className="bg-secondary text-secondary-foreground hover:bg-secondary/80 focus-visible:ring-0"
> >
{isReverting ? "Reverting…" : "Revert"} {isReverting ? "Reverting…" : "Revert"}
</AlertDialogAction> </AlertDialogAction>
@ -194,7 +267,6 @@ export function ActionLogItem({ action, threadId, onRevertSuccess }: ActionLogIt
</AlertDialog> </AlertDialog>
) : ( ) : (
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground"> <div className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
<ShieldOff className="size-3.5" />
{isAlreadyReverted {isAlreadyReverted
? "Already reverted" ? "Already reverted"
: isRevertAction : isRevertAction

View file

@ -5,7 +5,7 @@
* assistant turn that has at least one reversible action. * assistant turn that has at least one reversible action.
* *
* The button reads from the unified ``useAgentActionsQuery`` cache * The button reads from the unified ``useAgentActionsQuery`` cache
* (the SAME react-query cache the agent-actions sheet and the inline * (the SAME react-query cache the agent-actions dialog and the inline
* Revert button consume) filtered by ``chat_turn_id``. It shows a * Revert button consume) filtered by ``chat_turn_id``. It shows a
* confirmation dialog summarising "N reversible / M total" and, on * confirmation dialog summarising "N reversible / M total" and, on
* confirm, calls ``POST /threads/{id}/revert-turn/{chat_turn_id}``. * confirm, calls ``POST /threads/{id}/revert-turn/{chat_turn_id}``.

View file

@ -22,7 +22,7 @@ import {
} from "@/atoms/settings/settings-dialog.atoms"; } from "@/atoms/settings/settings-dialog.atoms";
import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom"; import { removeChatTabAtom, syncChatTabAtom, type Tab } from "@/atoms/tabs/tabs.atom";
import { currentUserAtom } from "@/atoms/user/user-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { ActionLogSheet } from "@/components/agent-action-log/action-log-sheet"; import { ActionLogDialog } from "@/components/agent-action-log/action-log-dialog";
import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog"; import { AnnouncementsDialog } from "@/components/announcements/AnnouncementsDialog";
import { SearchSpaceSettingsDialog } from "@/components/settings/search-space-settings-dialog"; import { SearchSpaceSettingsDialog } from "@/components/settings/search-space-settings-dialog";
import { TeamDialog } from "@/components/settings/team-dialog"; import { TeamDialog } from "@/components/settings/team-dialog";
@ -893,8 +893,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
<TeamDialog searchSpaceId={Number(searchSpaceId)} /> <TeamDialog searchSpaceId={Number(searchSpaceId)} />
<AnnouncementsDialog /> <AnnouncementsDialog />
{/* Agent action log + revert sheet */} {/* Agent action log + revert dialog */}
<ActionLogSheet /> <ActionLogDialog />
</> </>
); );
} }

View file

@ -10,7 +10,7 @@ import { useAgentActionsQuery } from "@/hooks/use-agent-actions-query";
* Resolve the ``AgentActionLog`` row for a given tool-call card. Tries * Resolve the ``AgentActionLog`` row for a given tool-call card. Tries
* three lookup strategies, in priority order, against the unified * three lookup strategies, in priority order, against the unified
* ``useAgentActionsQuery`` cache (the same react-query cache the * ``useAgentActionsQuery`` cache (the same react-query cache the
* agent-actions sheet consumes keeps the card and the sheet in * agent-actions dialog consumes keeps the card and the dialog in
* lockstep across reload, navigation, live stream, post-stream * lockstep across reload, navigation, live stream, post-stream
* reversibility flips, and explicit revert clicks). * reversibility flips, and explicit revert clicks).
* *

View file

@ -35,12 +35,12 @@ const dbg = (...args: unknown[]) => {
* * the per-turn "Revert turn" button under each assistant message * * the per-turn "Revert turn" button under each assistant message
* * the edit-from-position pre-flight that decides whether to show * * the edit-from-position pre-flight that decides whether to show
* the confirmation dialog * the confirmation dialog
* * the agent-actions sheet * * the agent-actions dialog
* *
* The cache is hydrated by ``GET /threads/{id}/actions`` (sized to * The cache is hydrated by ``GET /threads/{id}/actions`` (sized to
* 200, the server max) and updated incrementally by helpers that turn * 200, the server max) and updated incrementally by helpers that turn
* SSE events / revert RPC responses into ``setQueryData`` mutations. * SSE events / revert RPC responses into ``setQueryData`` mutations.
* That keeps the card and the sheet in lockstep on every code path * That keeps the card and the dialog in lockstep on every code path
* page reload, navigation, live stream, post-stream reversibility flip, * page reload, navigation, live stream, post-stream reversibility flip,
* and explicit revert clicks. * and explicit revert clicks.
*/ */
@ -72,7 +72,7 @@ export interface ActionLogSseEvent {
* *
* The SSE payload is a strict subset of ``AgentAction``; missing * The SSE payload is a strict subset of ``AgentAction``; missing
* fields (``args``, ``reverse_descriptor``, ``user_id``) are filled * fields (``args``, ``reverse_descriptor``, ``user_id``) are filled
* with ``null`` placeholders. The next refetch (sheet open, user * with ``null`` placeholders. The next refetch (dialog open, user
* focus, route stale) backfills them but the inline Revert button * focus, route stale) backfills them but the inline Revert button
* only reads the fields the SSE payload carries, so it lights up * only reads the fields the SSE payload carries, so it lights up
* immediately. * immediately.
@ -251,7 +251,7 @@ export function applyRevertTurnResultsToCache(
} }
/** /**
* Read-side hook used by the card, the turn button, the sheet, and * Read-side hook used by the card, the turn button, the dialog, and
* the edit-from-position pre-flight. * the edit-from-position pre-flight.
* *
* Returns the raw query state plus convenience selectors so consumers * Returns the raw query state plus convenience selectors so consumers