mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 05:16:50 +01:00
## 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>
140 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
}
|