Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ const dirtyContextBannerSection =

const contextBannerElement: ReactNode = dirtyContextBannerSection ? (
<ThreadPromptContextBanner
todoSection={null}
archivedSection={null}
environmentGoneSection={null}
gitSection={{
Expand All @@ -342,34 +341,35 @@ const contextBannerElement: ReactNode = dirtyContextBannerSection ? (
gitSectionPending={false}
parentThreadSection={null}
childThreadsSection={null}
pullRequestSection={null}
expandedSection={null}
onToggleSection={noop}
/>
) : null;

const archivedContextBannerElement: ReactNode = (
<ThreadPromptContextBanner
todoSection={null}
archivedSection={{ archivedAt: 1_731_456_000_000 }}
environmentGoneSection={null}
gitSection={null}
gitSectionPending={false}
parentThreadSection={null}
childThreadsSection={null}
pullRequestSection={null}
expandedSection={null}
onToggleSection={noop}
/>
);

const environmentGoneContextBannerElement: ReactNode = (
<ThreadPromptContextBanner
todoSection={null}
archivedSection={null}
environmentGoneSection={{ status: "destroyed" }}
gitSection={null}
gitSectionPending={false}
parentThreadSection={null}
childThreadsSection={null}
pullRequestSection={null}
expandedSection={null}
onToggleSection={noop}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,6 @@ function ReorderableQueuedMessagesList() {
);
}

export function Reorderable() {
return (
<PromptStage>
<ReorderableQueuedMessagesList />
</PromptStage>
);
}

export function Overview() {
return (
<StoryCard>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useState } from "react";
import type { ThreadTimelineGoal } from "@bb/domain";
import { StoryCard, StoryRow } from "../../../../.ladle/story-card";
import { ThreadGoalCard } from "./ThreadGoalCard";

export default {
title: "promptbox/banner/Goal Card",
};

function Stage({ children }: { children: React.ReactNode }) {
return <div className="w-full max-w-[760px]">{children}</div>;
}

const activeGoal: ThreadTimelineGoal = {
sourceSeq: 42,
updatedAt: Date.now() - 12_000,
objective:
"Ship the prompt context banner cleanup: keep active state segments first, compact metadata into icon-only controls, and preserve quick actions for single-segment states.",
status: "active",
tokenBudget: 80_000,
tokensUsed: 27_450,
timeUsedSeconds: 1_238,
};

const unboundedGoal: ThreadTimelineGoal = {
...activeGoal,
sourceSeq: 43,
objective:
"Audit the composer stack and identify the smallest set of follow-up UI fixes needed before merging.",
tokenBudget: null,
tokensUsed: 9_820,
timeUsedSeconds: 412,
};

function ToggleableGoalCard({
goal,
initiallyExpanded = false,
}: {
goal: ThreadTimelineGoal;
initiallyExpanded?: boolean;
}) {
const [expanded, setExpanded] = useState(initiallyExpanded);
return (
<ThreadGoalCard
goal={goal}
isExpanded={expanded}
onToggle={() => setExpanded((value) => !value)}
/>
);
}

function FauxComposer() {
return (
<div className="rounded-lg border border-border bg-popover p-3">
<div className="pb-3 text-sm text-subtle-foreground">
Reply to the agent...
</div>
<div className="flex items-center gap-2">
<span className="rounded-full border border-border px-2.5 py-1 text-xs text-muted-foreground">
gpt-5.5
</span>
</div>
</div>
);
}

export function Overview() {
return (
<StoryCard>
<StoryRow
label="prompt stack"
hint="goal card above the composer; click to expand"
>
<Stage>
<div className="flex flex-col gap-2">
<ToggleableGoalCard goal={activeGoal} />
<FauxComposer />
</div>
</Stage>
</StoryRow>
<StoryRow
label="expanded"
hint="full objective with token budget and elapsed time"
>
<Stage>
<ToggleableGoalCard goal={activeGoal} initiallyExpanded />
</Stage>
</StoryRow>
<StoryRow label="no token budget" hint="unbounded goal usage summary">
<Stage>
<ToggleableGoalCard goal={unboundedGoal} initiallyExpanded />
</Stage>
</StoryRow>
</StoryCard>
);
}
29 changes: 13 additions & 16 deletions apps/app/src/components/promptbox/banner/ThreadGoalCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ import { cn } from "@/lib/utils";

const GOAL_CARD_ROW_HEIGHT = 32;

function GoalActiveBadge() {
return (
<span className="inline-flex shrink-0 items-center gap-1.5 text-xs leading-none text-success">
<span className="size-1.5 shrink-0 rounded-full bg-success" />
Active
</span>
);
}

function formatDuration(seconds: number): string {
if (seconds < 60) {
return `${Math.round(seconds)}s`;
Expand Down Expand Up @@ -48,10 +39,10 @@ const TOGGLE_ID = "thread-goal-card-toggle";
/**
* Collapsible goal card for the prompt stack above the composer. Surfaces the
* provider's current durable objective (Codex `thread/goal/*` events projected
* onto the timeline). Collapsed: objective + Active badge. Expanded: full
* objective + token/time usage. Mirrors the ThreadPromptContextBanner section
* chrome. Only rendered while the goal is active — once the provider marks it
* complete (or paused / budget-limited) it drops out of the prompt stack.
* onto the timeline). Collapsed: objective. Expanded: full objective +
* token/time usage. Mirrors the ThreadPromptContextBanner section chrome. Only
* rendered while the goal is active — once the provider marks it complete (or
* paused / budget-limited) it drops out of the prompt stack.
*/
export function ThreadGoalCard({
goal,
Expand Down Expand Up @@ -82,7 +73,10 @@ export function ThreadGoalCard({
className="size-3.5 shrink-0 text-muted-foreground"
aria-hidden="true"
/>
<span className="min-w-0 flex-1 truncate text-left" title={goal.objective}>
<span
className="min-w-0 flex-1 truncate text-left"
title={goal.objective}
>
{goal.objective}
</span>
<Icon
Expand All @@ -94,7 +88,6 @@ export function ThreadGoalCard({
aria-hidden="true"
/>
</button>
<GoalActiveBadge />
</div>
<section
id={BODY_ID}
Expand All @@ -115,7 +108,11 @@ export function ThreadGoalCard({
</p>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span className="inline-flex items-center gap-1.5">
<Icon name="Zap" className="size-3.5 shrink-0" aria-hidden="true" />
<Icon
name="Zap"
className="size-3.5 shrink-0"
aria-hidden="true"
/>
{formatTokenUsage(goal)}
</span>
<span className="inline-flex items-center gap-1.5">
Expand Down
Loading
Loading