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
1 change: 1 addition & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default defineConfig({
"**/profile-active-turn.spec.ts",
"**/config-bridge-screenshots.spec.ts",
"**/observer-feed-screenshots.spec.ts",
"**/transcript-tool-run-quality.smoke.spec.ts",
"**/core-memory-screenshots.spec.ts",
"**/activity-scope-label-screenshots.spec.ts",
"**/welcome-agent-modal-screenshots.spec.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,16 @@ export function ToolItem({
agentPubkey,
item,
profiles,
expanded,
onExpansionChange,
}: AgentTranscriptIdentityProps & {
item: Extract<TranscriptItem, { type: "tool" }>;
profiles?: UserProfileLookup;
expanded?: boolean;
onExpansionChange?: (expanded: boolean) => void;
}) {
const [isExpanded, setIsExpanded] = React.useState(false);
const [localExpanded, setLocalExpanded] = React.useState(false);
const isExpanded = expanded ?? localExpanded;
const hasArgs = Object.keys(item.args).length > 0;
const hasResult = item.result.trim().length > 0;
const canonicalToolName = item.buzzToolName ?? item.toolName;
Expand All @@ -52,9 +57,14 @@ export function ToolItem({
const agentResolvedAvatarUrl = agentProfile?.avatarUrl ?? agentAvatarUrl;
const handleToggle = React.useCallback(
(event: React.SyntheticEvent<HTMLDetailsElement>) => {
setIsExpanded(event.currentTarget.open);
const nextExpanded = event.currentTarget.open;
if (onExpansionChange) {
onExpansionChange(nextExpanded);
} else {
setLocalExpanded(nextExpanded);
}
},
[],
[onExpansionChange],
);

if (compactSummary.presentation === "message") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { CircleHelp, Radio } from "lucide-react";

import { FuzzyLogo } from "@/shared/ui/buzz-logo/FuzzyLogo";
import {
getUncertainHistoryCopy,
type UncertainHistoryCertainty,
} from "./agentSessionHistoryCertainty";

/**
* An uncertain variant means history may exist but could not be read (no
* `owner_p` save subscription, or archive hydration incomplete). Those are
* deliberately distinct from `"idle"`: only `"idle"` may state that there was
* no activity. The specific variant is carried rather than a single `"unknown"`
* so the rendered copy can name the actual gap — see
* `getUncertainHistoryCopy`.
*/
export type AgentSessionTranscriptEmptyState =
| "idle"
| "loading"
| UncertainHistoryCertainty;

/**
* What a transcript with nothing to render says.
*
* Three outcomes, and the distinction between the last two is the point: an
* idle channel may state that no activity happened, while a channel whose
* archive could not be read may not. All uncertain wording comes from
* `getUncertainHistoryCopy` so this view cannot drift from the module that
* owns it.
*/
export function AgentSessionTranscriptEmptyBody({
emptyDescription,
isLoading,
state,
}: {
emptyDescription: string;
/** True while a turn is live or the caller is still loading. */
isLoading: boolean;
state: AgentSessionTranscriptEmptyState;
}) {
if (isLoading) {
return (
<FuzzyLogo
ariaLabel="Waiting for ACP activity"
className="mx-auto text-muted-foreground"
fuzz={false}
loop
/>
);
}

if (state !== "idle" && state !== "loading") {
const copy = getUncertainHistoryCopy(state);
return (
<>
<CircleHelp className="mx-auto h-4 w-4 text-muted-foreground" />
<p className="mt-3 text-sm font-medium">{copy.title}</p>
<p className="mt-1 text-sm text-muted-foreground">{copy.description}</p>
</>
);
}

return (
<>
<Radio className="mx-auto h-4 w-4 text-muted-foreground" />
<p className="mt-3 text-sm font-medium">No ACP activity yet</p>
<p className="mt-1 text-sm text-muted-foreground">{emptyDescription}</p>
</>
);
}
Loading
Loading