Files
PaperClipAI/ui/src/pages/StatusCards/CreateStatusCardDialog.tsx
T
DottaandClaude Fable 5 3a16b91217 feat(status-cards): single-message setup drives query and update prompt (#10202)
## Thinking Path

> - Paperclip is the open source control plane people use to manage
AI-agent companies and their ongoing work.
> - Status cards turn a standing question into recurring,
agent-generated summaries on the board.
> - The existing setup split intent across a watch prompt and separate
update instructions, which made creation and later behavior harder to
understand.
> - A status card should have one durable source of truth for both
deciding what to watch and telling the summarizer what each update must
contain.
> - This pull request makes the card prompt that source of truth,
simplifies creation to one step, and lets operators choose the running
agent immediately.
> - The benefit is a smaller mental model, fewer configuration modes,
and consistent update instructions throughout the card lifecycle.

## Linked Issues or Issue Description

Status cards currently require operators to express the same intent in
two places: the watch prompt and optional update instructions with
append/replace/none modes. This feature simplifies the experimental
status-card workflow so a single prompt defines both the watch query and
every generated update. The create flow must also support selecting the
responsible agent without a second setup step.

Related prior status-card work: #10101.

## What Changed

- Use the status card's single prompt to compile the watch query and
directly instruct every summary update.
- Add migration `0190_status_card_single_prompt` to remove
`status_cards.instructions_mode` and `status_cards.instructions`.
- Add `agentId` to `createStatusCardSchema`, validate company
membership, and default new cards to the built-in Summarizer.
- Replace the two-step create flow with one prompt-and-agent dialog and
extract a shared `SummarizerAgentSelect` for create/settings surfaces.
- Remove the extra-instructions settings section, reset incremental
history when the prompt changes, and rename the board page to "Status".
- Update the bundled `status-card-query` skill and board-operator
documentation, then regenerate the skills catalog manifest.

## Verification

- Server status-card suites: 29/29 passing.
- UI `StatusCards` suites: 22/22 passing.
- Skills catalog suite: 20/20 passing.
- `tsc -b` passes for server, UI, shared, and database packages.
- `pnpm check:migrations` passes.
- Light and dark mode screenshots cover the new create dialog and
settings tab.

## Risks

- Migration `0190` intentionally drops existing separate instruction
text. Existing card prompts remain and become the update instructions
under the new model; status cards are experimental and feature-flagged.
- Prompt edits now reset the incremental summary chain and trigger a
full rebuild, which is intentional because the prompt is also the update
contract.
- Agent selection is company-scoped; invalid agent ids return a
validation error rather than creating a misrouted card.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Implementation: Anthropic Claude via the `claude_local` adapter, agent
label "Claude Fable 5"; extended reasoning, tool use, and code
execution. The exact provider model id and context-window value were not
retained in the task metadata.
- PR preparation: OpenAI GPT-5.4 through Codex CLI, with reasoning,
repository inspection, GitHub CLI, and Paperclip API tool use.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [ ] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:06:14 -05:00

140 lines
4.8 KiB
TypeScript

import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { defaultStatusCardRefreshPolicy } from "@paperclipai/shared";
import { Loader2 } from "lucide-react";
import { statusCardsApi } from "@/api/statusCards";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { InlineBanner } from "@/components/InlineBanner";
import { queryKeys } from "@/lib/queryKeys";
import { SummarizerAgentSelect } from "./SummarizerAgentSelect";
const EXAMPLES = [
"issues about evals",
"everything blocked this week",
"is feature X live? if not, the exact next actions to ship it",
];
export function CreateStatusCardDialog({
companyId,
open,
onOpenChange,
}: {
companyId: string;
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const queryClient = useQueryClient();
const [prompt, setPrompt] = useState("");
// "" → the built-in Summarizer; otherwise the id of the override agent.
const [agentId, setAgentId] = useState("");
const [error, setError] = useState<string | null>(null);
function reset() {
setPrompt("");
setAgentId("");
setError(null);
}
function close() {
onOpenChange(false);
// Delay reset so the closing animation does not flash cleared fields.
window.setTimeout(reset, 200);
}
const createMutation = useMutation({
mutationFn: () =>
statusCardsApi.create(companyId, {
interestPrompt: prompt.trim(),
titlePinned: false,
agentId: agentId || null,
refreshPolicy: defaultStatusCardRefreshPolicy,
}),
onMutate: () => setError(null),
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: queryKeys.statusCards.list(companyId, false) }),
queryClient.invalidateQueries({ queryKey: queryKeys.statusCards.list(companyId, true) }),
]);
close();
},
onError: (err) => setError(err instanceof Error ? err.message : "Could not create the card."),
});
return (
<Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : close())}>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>New card</DialogTitle>
<DialogDescription>
One message sets up the whole card: say what you want to watch and what each update
should tell you. The agent builds the query from it and writes every update against it.
</DialogDescription>
</DialogHeader>
{error ? <InlineBanner tone="danger" title="Create failed">{error}</InlineBanner> : null}
<div className="space-y-3">
<label htmlFor="status-card-prompt" className="block pb-1 text-sm font-semibold">
What do you want to keep an eye on?
</label>
<Textarea
id="status-card-prompt"
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
rows={5}
autoFocus
placeholder="Keep an eye on the ID and Cloud projects. Tell me whether the service is live, and if not, the exact three actions needed to get it to production."
className="text-sm"
/>
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-medium text-muted-foreground">Examples</span>
{EXAMPLES.map((example) => (
<button
key={example}
type="button"
onClick={() => setPrompt(example)}
className="rounded-full border border-border px-3 py-1 text-xs text-muted-foreground transition-colors hover:bg-accent/40"
>
{example}
</button>
))}
</div>
</div>
<div className="space-y-2">
<label className="block text-sm font-semibold">Agent</label>
<SummarizerAgentSelect companyId={companyId} value={agentId} onChange={setAgentId} enabled={open} />
<p className="text-xs text-muted-foreground">
Runs this card's setup and updates. Leave on the default unless another agent should own it.
</p>
</div>
<DialogFooter>
<div className="flex gap-2">
<Button variant="outline" onClick={close} disabled={createMutation.isPending}>
Cancel
</Button>
<Button
onClick={() => createMutation.mutate()}
disabled={prompt.trim().length === 0 || createMutation.isPending}
>
{createMutation.isPending ? <Loader2 className="animate-spin" /> : null}
Create card
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
}