storybook: treat work mode as the task type in Jev routing

Drop the separate bug/feature/research type classification. Jev now
answers three choice questions: work mode (Auto, Plan, or Ask), assignee,
and project. The mode chip leads the row, is always marked as suggested,
shows a one-line description and Jev's probability per mode, and supports
undo. Owner signals remain internal to the simulation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SWn6etZjfGz6p8NWYkCQve
This commit is contained in:
Claude committed 2026-09-24 21:43:19 +00:00
1 parent 39be9300d8
commit a9327563d5
4 files changed
+119 -128

No files matched your search

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import type { IssueWorkMode } from "@paperclipai/shared";
import { AlertTriangle, Check, ChevronDown, FolderKanban, Pencil, RotateCcw, Sparkles, Tag, Undo2 } from "lucide-react";
import { AlertTriangle, Check, ChevronDown, FolderKanban, Pencil, RotateCcw, Sparkles, Undo2 } from "lucide-react";
import { AgentAvatar } from "@/components/AgentAvatar";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
@@ -15,13 +15,11 @@ import {
JEV_NAME,
JEV_NO_PROJECT,
JEV_PROJECTS,
JEV_TASK_TYPES,
buildJevDecisionRequest,
draftTaskTitle,
routeTaskWithJev,
type JevField,
type JevRouting,
type JevTaskType,
} from "./jev-classifier";
export type JevTaskComposerProps = {
@@ -47,7 +45,7 @@ export type JevTaskComposerProps = {
mobile?: boolean;
};
type Overrides = Partial<{ title: string; type: JevTaskType; assignee: string; project: string | null; workMode: IssueWorkMode }>;
type Overrides = Partial<{ title: string; workMode: IssueWorkMode; assignee: string; project: string | null }>;
type OverrideField = keyof Overrides;
type Status = "idle" | "thinking" | "ready" | "error";
@@ -157,7 +155,6 @@ export function JevTaskComposer({
const effective = useMemo(() => ({
title: overrides.title ?? suggestedTitle ?? "",
type: overrides.type ?? routing?.type ?? null,
assignee: overrides.assignee ?? routing?.assigneeId ?? null,
project: overrides.project !== undefined ? overrides.project : routing?.projectId ?? null,
workMode: overrides.workMode ?? routing?.workMode ?? "standard",
@@ -241,7 +238,7 @@ export function JevTaskComposer({
}}
placeholder={flow === "instant"
? "What should get done? It gets a title and an owner once you start."
: "What should get done? A title, type, and owner are suggested as you write."}
: "What should get done? A title, mode, and owner are suggested as you write."}
className="min-h-36 resize-none border-0 bg-transparent px-0 text-base shadow-none focus-visible:ring-0 dark:bg-transparent"
/>
</div>
@@ -379,7 +376,6 @@ function ComposerHeader({
type Effective = {
title: string;
type: JevTaskType | null;
assignee: string | null;
project: string | null;
workMode: IssueWorkMode;
@@ -427,7 +423,7 @@ function RoutingPanel({
const why = fallbackOwner.reportsTo === null ? "who reports to the board" : "the first agent in your org";
return `${JEV_NAME} wasn't sure who should own this (${percent(routing!.confidence.assignee)}), so it goes to ${fallbackOwner.name}, ${why}.`;
}
if (status === "idle" && flow === "instant" && !created) return `${JEV_NAME} picks the type and owner after you start. You can change anything later.`;
if (status === "idle" && flow === "instant" && !created) return `${JEV_NAME} picks the mode and owner after you start. You can change anything later.`;
if (status === "idle" && promptLength > 0 && promptLength < JEV_MIN_PROMPT_LENGTH) return "Keep going. Suggestions start once there's a bit more to go on.";
return null;
})();
@@ -458,28 +454,14 @@ function RoutingPanel({
{showChips ? (
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
<PropertyChip
label="Type"
<WorkModeChip
loading={loading}
suggested={fromJev("type")}
onReset={resetFor("type")}
display={<><Tag aria-hidden className="size-3.5" />{JEV_TASK_TYPES.find((option) => option.value === effective.type)?.label ?? "Type"}</>}
>
{(close) => JEV_TASK_TYPES.map((option) => (
<MenuItem
key={option.value}
selected={option.value === effective.type}
probability={probabilities?.type[option.value]}
onClick={() => { setField("type", option.value); close(); }}
>
<span className="flex flex-col">
<span>{option.label}</span>
<span className="text-xs text-muted-foreground">{option.hint}</span>
</span>
</MenuItem>
))}
</PropertyChip>
mode={effective.workMode}
suggested={fromJev("workMode")}
probabilities={probabilities?.workMode}
onChange={(mode) => setField("workMode", mode)}
onReset={resetFor("workMode")}
/>
<PropertyChip
label="Assignee"
loading={loading}
@@ -534,13 +516,6 @@ function RoutingPanel({
]}
</PropertyChip>
<WorkModeChip
loading={loading}
mode={effective.workMode}
suggested={fromJev("workMode") && effective.workMode !== "standard"}
probabilities={probabilities?.workMode}
onChange={(mode) => setField("workMode", mode)}
/>
</div>
) : null}
@@ -569,10 +544,9 @@ function RoutingPanel({
);
}
const FIELD_LABELS: Record<JevField, string> = { type: "Type", assignee: "Assignee", project: "Project", workMode: "Mode" };
const FIELD_LABELS: Record<JevField, string> = { workMode: "Mode", assignee: "Assignee", project: "Project" };
function choiceLabel(field: JevField, routing: JevRouting): string {
if (field === "type") return JEV_TASK_TYPES.find((option) => option.value === routing.type)?.label ?? routing.type;
if (field === "assignee") {
const name = (id: string) => JEV_AGENTS.find((agent) => agent.id === id)?.name ?? id;
return routing.assigneeSource === "fallback"
@@ -584,7 +558,7 @@ function choiceLabel(field: JevField, routing: JevRouting): string {
}
function ConfidenceDetails({ routing, prompt, agents }: { routing: JevRouting; prompt: string; agents: typeof JEV_AGENTS }) {
const fields: JevField[] = ["type", "assignee", "project", "workMode"];
const fields: JevField[] = ["workMode", "assignee", "project"];
return (
<div className="mt-2 flex flex-col gap-2 pl-4">
<dl className="grid grid-cols-[auto_1fr_auto] items-center gap-x-3 gap-y-1.5">
@@ -699,53 +673,72 @@ function MenuItem({
);
}
const WORK_MODE_HINTS: Partial<Record<IssueWorkMode, string>> = {
standard: "The agent does the work",
planning: "The agent writes a plan for your review first",
ask: "The agent answers without changing anything",
};
/** The task's type. Uses the shipped work-mode labels, icons, and colors. */
function WorkModeChip({
mode,
loading,
suggested,
probabilities,
onChange,
onReset,
}: {
mode: IssueWorkMode;
loading: boolean;
suggested: boolean;
probabilities?: Record<string, number>;
onChange: (mode: IssueWorkMode) => void;
onReset?: () => void;
}) {
const [open, setOpen] = useState(false);
const meta = workModeMetaFor(mode);
const Icon = meta.icon;
if (loading) return <span aria-label="Mode loading" className="h-7 w-20 animate-pulse rounded-md border border-border bg-muted/60" />;
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
aria-label={`Mode${suggested ? ` (suggested by ${JEV_NAME})` : ""}`}
className={cn("inline-flex h-7 items-center gap-1.5 rounded-md border px-2 text-sm transition-colors", meta.classes.chip)}
>
{suggested ? <Sparkles aria-hidden className="size-3" /> : null}
<Icon aria-hidden className="size-3.5" />
{meta.label}
<ChevronDown aria-hidden className="size-3 opacity-60" />
</button>
</PopoverTrigger>
<PopoverContent align="start" className="w-56 p-1">
{workModeMetaList().map((option) => {
const OptionIcon = option.icon;
return (
<MenuItem
key={option.value}
selected={option.value === mode}
probability={probabilities?.[option.value]}
onClick={() => { onChange(option.value); setOpen(false); }}
>
<OptionIcon aria-hidden className={cn("size-3.5", option.classes.menuItem)} />
{option.label}
</MenuItem>
);
})}
</PopoverContent>
</Popover>
<span className="inline-flex items-center">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
aria-label={`Mode${suggested ? ` (suggested by ${JEV_NAME})` : ""}`}
className={cn("inline-flex h-7 items-center gap-1.5 rounded-md border px-2 text-sm transition-colors", meta.classes.chip)}
>
{suggested ? <Sparkles aria-hidden className="size-3" /> : null}
<Icon aria-hidden className="size-3.5" />
{meta.label}
<ChevronDown aria-hidden className="size-3 opacity-60" />
</button>
</PopoverTrigger>
<PopoverContent align="start" className="w-72 p-1">
{workModeMetaList().map((option) => {
const OptionIcon = option.icon;
return (
<MenuItem
key={option.value}
selected={option.value === mode}
probability={probabilities?.[option.value]}
onClick={() => { onChange(option.value); setOpen(false); }}
>
<OptionIcon aria-hidden className={cn("size-3.5 shrink-0", option.classes.menuItem)} />
<span className="flex flex-col">
<span>{option.label}</span>
<span className="text-xs text-muted-foreground">{WORK_MODE_HINTS[option.value]}</span>
</span>
</MenuItem>
);
})}
</PopoverContent>
</Popover>
{onReset ? (
<Button variant="ghost" size="icon-xs" className="text-muted-foreground" onClick={onReset} title={`Use ${JEV_NAME}'s suggestion`}>
<Undo2 />
</Button>
) : null}
</span>
);
}
@@ -16,8 +16,9 @@ after you start it.
Two models do the work, because they are good at different things:
- **[Jev](https://openrouter.ai/docs/guides/community/jev)** (`typesafe/jev-1.13`,
TypeSafe's decision model on OpenRouter) answers typed `choice` questions:
task type, assignee, project, and work mode. It returns a choice, a
TypeSafe's decision model on OpenRouter) answers typed `choice` questions for
the task's **mode**, which is its type (Auto, Plan, or Ask), plus its
**assignee** and **project**. It returns a choice, a
confidence, and a probability for every option. It is fast and cheap: you pay
for input tokens only, and output tokens are free. It does not generate text or
explain its answers.
@@ -28,8 +29,12 @@ Two models do the work, because they are good at different things:
- The **title** types into the header breadcrumb (`PAP › Fix login redirect
loop…`) when it arrives. Click it to edit.
- **Type**, **assignee**, **project**, and **mode** appear as chips under the
prompt, usually before the title, because Jev answers faster.
- **Mode**, **assignee**, and **project** appear as chips under the prompt,
usually before the title, because Jev answers faster. Mode is the task's type
and uses the shipped `IssueWorkMode` values and labels:
- **Auto** (`standard`): the agent does the work.
- **Plan** (`planning`): the agent writes a plan for review first.
- **Ask** (`ask`): the agent answers without changing anything.
- Suggested values carry a sparkle. When you change a value, the sparkle goes
away and the value is never overwritten. The undo icon restores the suggestion.
- Every chip menu shows Jev's probability for each option.
@@ -83,7 +88,5 @@ too (`draftTitle`). No API is called and no task is persisted.
## Open questions
- Does task **type** need to be a real field? Today `IssueWorkMode` and labels
cover part of it.
- Should Jev's criteria for each agent come from agent instructions or role
metadata, rather than a hand-written "owns" line?
@@ -8,7 +8,8 @@ import type { IssueWorkMode } from "@paperclipai/shared";
* does not generate text or explain itself. So routing is split in two:
*
* - Jev (`POST /api/alpha/decisions`, model `typesafe/jev-1.13`) answers `choice`
* questions for task type, assignee, project, and work mode.
* questions for the task's mode (its type: Auto, Plan, or Ask), assignee, and
* project.
* - A small text model drafts the title, the way Claude Code names a session.
*
* `buildJevDecisionRequest` produces the real request body. The response is
@@ -48,17 +49,6 @@ export type JevDecisionResponse = {
// ---------------------------------------------------------------------------
// Company fixtures (match the Storybook company)
export type JevTaskType = "bug" | "feature" | "research" | "design" | "qa" | "ops";
export const JEV_TASK_TYPES: { value: JevTaskType; label: string; hint: string }[] = [
{ value: "bug", label: "Bug", hint: "Something is broken or regressed" },
{ value: "feature", label: "Feature", hint: "Build or change product behavior" },
{ value: "research", label: "Research", hint: "Investigate, compare, or answer a question" },
{ value: "design", label: "Design", hint: "UI, UX, or visual work" },
{ value: "qa", label: "QA", hint: "Verify, test, or reproduce" },
{ value: "ops", label: "Ops", hint: "Admin, release, or coordination work" },
];
export type JevAgent = {
id: string;
name: string;
@@ -103,10 +93,14 @@ export function buildJevDecisionRequest(prompt: string, agents: JevAgent[] = JEV
projects: JEV_PROJECTS.map(({ id, name, description }) => ({ id, name, description })),
},
questions: {
task_type: {
work_mode: {
type: "choice",
instructions: "What kind of work does the task prompt describe?",
criteria: Object.fromEntries(JEV_TASK_TYPES.map((option) => [option.value, option.hint])),
instructions: "What type of task is this: should the agent do the work, plan it first, or answer a question?",
criteria: {
standard: "Auto: a clear, bounded request the agent can carry out directly.",
planning: "Plan: large, risky, or multi-step work that needs a reviewed plan before anything changes.",
ask: "Ask: a question or investigation to answer without changing anything.",
},
},
assignee: {
type: "choice",
@@ -121,23 +115,17 @@ export function buildJevDecisionRequest(prompt: string, agents: JevAgent[] = JEV
[JEV_NO_PROJECT]: "None of the projects fit.",
},
},
work_mode: {
type: "choice",
instructions: "How should the assignee approach this task?",
criteria: {
standard: "Do the work directly.",
planning: "Large or risky scope: write a plan for review before doing the work.",
ask: "A question: answer it without changing anything.",
},
},
},
};
}
// ---------------------------------------------------------------------------
// Local simulation of Jev's answers
// Local simulation of Jev's answers. Work areas are internal signals for
// picking an owner; they are not shown or stored.
const TYPE_KEYWORDS: Record<JevTaskType, string[]> = {
type WorkArea = "bug" | "feature" | "research" | "design" | "qa" | "ops";
const AREA_KEYWORDS: Record<WorkArea, string[]> = {
bug: ["bug", "broken", "fix", "crash", "error", "regress", "fails", "failing", "500", "stuck", "doesn't", "does not", "not working", "wrong"],
feature: ["add", "build", "implement", "support", "create", "new", "allow", "enable", "endpoint", "integrate", "make", "improve", "faster", "performance"],
research: ["research", "investigate", "compare", "evaluate", "why", "how does", "figure out", "explore", "options", "should we"],
@@ -146,8 +134,8 @@ const TYPE_KEYWORDS: Record<JevTaskType, string[]> = {
ops: ["release", "deploy", "rotate", "invoice", "schedule", "onboard", "hire", "budget report", "coordinate", "follow up", "email"],
};
/** Which agent usually owns each type. Assignee probabilities follow type probabilities. */
const TYPE_OWNER: Record<JevTaskType, string> = {
/** Which agent usually owns each work area. */
const AREA_OWNER: Record<WorkArea, string> = {
bug: "agent-codex",
feature: "agent-codex",
research: "agent-cto",
@@ -191,17 +179,17 @@ export function simulateJevDecisions(request: JevDecisionRequest): JevDecisionRe
const prompt = String(request.state.task_prompt ?? "");
const text = prompt.toLowerCase();
const typeScores = Object.fromEntries(
(Object.keys(TYPE_KEYWORDS) as JevTaskType[]).map((type) => [type, score(text, TYPE_KEYWORDS[type])]),
) as Record<JevTaskType, number>;
const areaScores = Object.fromEntries(
(Object.keys(AREA_KEYWORDS) as WorkArea[]).map((area) => [area, score(text, AREA_KEYWORDS[area])]),
) as Record<WorkArea, number>;
// No signal at all reads as an open question.
if (Object.values(typeScores).every((value) => value === 0)) typeScores.research = 0.6;
const typeProbabilities = softmax(typeScores);
if (Object.values(areaScores).every((value) => value === 0)) areaScores.research = 0.6;
const areaProbabilities = softmax(areaScores);
const assigneeQuestion = request.questions.assignee as JevChoiceQuestion;
const assigneeProbabilities: Record<string, number> = Object.fromEntries(Object.keys(assigneeQuestion.criteria).map((id) => [id, 0.02]));
for (const [type, probability] of Object.entries(typeProbabilities)) {
const owner = TYPE_OWNER[type as JevTaskType];
for (const [area, probability] of Object.entries(areaProbabilities)) {
const owner = AREA_OWNER[area as WorkArea];
if (owner in assigneeProbabilities) assigneeProbabilities[owner]! += probability;
}
const assigneeTotal = Object.values(assigneeProbabilities).reduce((sum, value) => sum + value, 0);
@@ -214,16 +202,16 @@ export function simulateJevDecisions(request: JevDecisionRequest): JevDecisionRe
const isQuestion = /\?\s*$/.test(prompt.trim()) || (/^(how|why|what|which|should|is|are|can|does)\b/.test(text) && !/^(can|could|would) you\b/.test(text));
const isLarge = /\b(plan|roadmap|migrate|migration|redesign|rewrite|overhaul|multi-step|phases?)\b/.test(text);
const modeScores = { standard: 1, planning: isLarge ? 3 : 0, ask: isQuestion ? 3 : 0 };
const isInvestigation = /^(investigate|research|find out|figure out|look into|compare|evaluate)\b/.test(text);
const modeScores = { standard: 1, planning: isLarge ? 3 : 0, ask: isQuestion ? 3 : isInvestigation ? 1.6 : 0 };
const inputTokens = 380 + Math.round(prompt.length / 4);
return {
model: request.model,
answers: {
task_type: choiceAnswer(typeProbabilities),
work_mode: choiceAnswer(softmax(modeScores)),
assignee: choiceAnswer(assigneeProbabilities),
project: choiceAnswer(softmax(projectScores, 2.4)),
work_mode: choiceAnswer(softmax(modeScores)),
},
// Output tokens are free on Jev; $0.042 per million input tokens.
usage: { input_tokens: inputTokens, output_tokens: 0, cost: (inputTokens * 0.042) / 1_000_000 },
@@ -233,7 +221,7 @@ export function simulateJevDecisions(request: JevDecisionRequest): JevDecisionRe
// ---------------------------------------------------------------------------
// What the composer consumes
export type JevField = "type" | "assignee" | "project" | "workMode";
export type JevField = "workMode" | "assignee" | "project";
/**
* Who gets a task when Jev isn't confident about the owner: the org's first
@@ -245,14 +233,14 @@ export function fallbackAssigneeId(agents: JevAgent[]): string | null {
}
export type JevRouting = {
type: JevTaskType;
/** The task's type. */
workMode: IssueWorkMode;
/** The owner to use: Jev's pick, or the fallback when Jev is unsure. */
assigneeId: string;
assigneeSource: "jev" | "fallback";
/** Jev's own top pick, kept even when the fallback is used. */
jevAssigneeId: string;
projectId: string | null;
workMode: IssueWorkMode;
confidence: Record<JevField, number>;
probabilities: Record<JevField, Record<string, number>>;
/** Jev's likely owners other than the one assigned, offered when Jev is unsure. */
@@ -261,7 +249,7 @@ export type JevRouting = {
};
export function routingFromJev(response: JevDecisionResponse, agents: JevAgent[] = JEV_AGENTS): JevRouting {
const { task_type: type, assignee, project, work_mode: mode } = response.answers as Record<string, JevChoiceAnswer>;
const { work_mode: mode, assignee, project } = response.answers as Record<string, JevChoiceAnswer>;
const fallbackId = assignee!.confidence < JEV_CONFIDENCE_THRESHOLD ? fallbackAssigneeId(agents) : null;
const assigneeId = fallbackId ?? assignee!.choice;
const alternateAssigneeIds = Object.entries(assignee!.probabilities)
@@ -269,14 +257,13 @@ export function routingFromJev(response: JevDecisionResponse, agents: JevAgent[]
.slice(0, 2)
.map(([id]) => id);
return {
type: type!.choice as JevTaskType,
workMode: mode!.choice as IssueWorkMode,
assigneeId,
assigneeSource: fallbackId ? "fallback" : "jev",
jevAssigneeId: assignee!.choice,
projectId: project!.choice === JEV_NO_PROJECT ? null : project!.choice,
workMode: mode!.choice as IssueWorkMode,
confidence: { type: type!.confidence, assignee: assignee!.confidence, project: project!.confidence, workMode: mode!.confidence },
probabilities: { type: type!.probabilities, assignee: assignee!.probabilities, project: project!.probabilities, workMode: mode!.probabilities },
confidence: { workMode: mode!.confidence, assignee: assignee!.confidence, project: project!.confidence },
probabilities: { workMode: mode!.probabilities, assignee: assignee!.probabilities, project: project!.probabilities },
alternateAssigneeIds,
usage: response.usage,
};
@@ -21,7 +21,7 @@ const meta = {
description: {
component:
"Prototype: prompt-first task creation. You write what you need. Jev (`typesafe/jev-1.13`, TypeSafe's decision model on OpenRouter) answers " +
"`choice` questions for task type, assignee, project, and work mode, with a confidence and per-option probabilities. Jev doesn't generate text, so a separate small text model drafts the title, like Claude Code naming a session. " +
"`choice` questions for the task's mode, which is its type (Auto, Plan, or Ask), plus its assignee and project, each with a confidence and per-option probabilities. Jev doesn't generate text, so a separate small text model drafts the title, like Claude Code naming a session. " +
"Suggested values carry a sparkle. Anything you change is yours and is never overwritten; the undo icon restores the suggestion. " +
"Below the confidence threshold the task goes to the org's first active agent that reports to the board (else its first active agent), and Jev's best guesses appear as one-click buttons. **Instant** stories create first; the title and routing arrive a moment later. " +
"Jev's answers are simulated locally in the documented response shape (`prototypes/jev-task-creation/jev-classifier.ts`); **How sure Jev is → Request sent to Jev** shows the real request body. No API is called and no task is persisted.",
@@ -44,12 +44,12 @@ export const WatchItSuggest: Story = {
name: "02 · Watch Jev suggest while you type",
args: { initialPrompt: PROMPTS.bug, typeOnMount: true },
};
export const Bug: Story = { name: "03 · Bug → CodexCoder", args: { initialPrompt: PROMPTS.bug } };
export const Feature: Story = { name: "04 · Feature → CodexCoder", args: { initialPrompt: PROMPTS.feature } };
export const Research: Story = { name: "05 · Question → CTO in Ask mode", args: { initialPrompt: PROMPTS.research } };
export const Design: Story = { name: "06 · Design → DesignSystemCoder", args: { initialPrompt: PROMPTS.design } };
export const QA: Story = { name: "07 · QA → QAChecker", args: { initialPrompt: PROMPTS.qa } };
export const LargeScope: Story = { name: "08 · Large scope → Plan mode", args: { initialPrompt: PROMPTS.planning } };
export const Bug: Story = { name: "03 · Login bug → Auto mode, CodexCoder", args: { initialPrompt: PROMPTS.bug } };
export const Feature: Story = { name: "04 · CSV export → Auto mode, CodexCoder", args: { initialPrompt: PROMPTS.feature } };
export const Research: Story = { name: "05 · Question → Ask mode, CTO", args: { initialPrompt: PROMPTS.research } };
export const Design: Story = { name: "06 · Spacing pass → Auto mode, DesignSystemCoder", args: { initialPrompt: PROMPTS.design } };
export const QA: Story = { name: "07 · Release checks → Auto mode, QAChecker", args: { initialPrompt: PROMPTS.qa } };
export const LargeScope: Story = { name: "08 · Phased migration → Plan mode, CodexCoder", args: { initialPrompt: PROMPTS.planning } };
export const LowConfidence: Story = {
name: "09 · Unsure · Goes to the agent reporting to the board",
args: { initialPrompt: PROMPTS.ambiguous },
@@ -78,6 +78,14 @@ export const AssigneeProbabilities: Story = {
await userEvent.click(chip);
},
};
export const ModeProbabilities: Story = {
name: "11c · Mode menu shows Jev's probabilities",
args: { initialPrompt: PROMPTS.planning },
play: async ({ canvasElement }) => {
const chip = await within(canvasElement).findByRole("button", { name: /^Mode/ }, { timeout: 4000 });
await userEvent.click(chip);
},
};
export const InstantEmpty: Story = {
name: "12 · Instant · Start first, name and route after",
args: { flow: "instant" },