mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 04:02:18 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The Decisions desk shows work that needs an operator response. > - It also exposed decision-training actions and a separate training library. > - Paperclip does not plan to use these training surfaces now. > - Keeping inactive controls makes the Decisions workflow harder to scan. > - This pull request removes the training UI and keeps the backend snapshot contract unchanged. > - The benefit is a smaller and clearer Decisions workflow without a data migration. ## Linked Issues or Issue Description **What existing behavior does this improve?** The Decisions desk currently exposes training controls, training state, and a separate training library route. **Subsystem affected** `ui/` — React and Vite board UI. **Current behavior** Operators can open a training library from the Decisions toolbar. They can also mark a decision for training from rows and inspect the result in a drawer. **Proposed behavior** Remove the training controls, badges, drawer, library pages, and routes from the Decisions UI. Keep the server APIs and stored training examples unchanged. **Reason and benefit** The product does not plan to use decision training now. Removing the unused surfaces reduces Decisions UI noise and avoids presenting a workflow that operators should not use. **Breaking changes** The `/decisions/training` UI routes are no longer registered. Existing server endpoints and stored decision-training data remain compatible. ## What Changed - Removed decision-training controls and state from Decisions toolbars, rows, queue pages, and shelves. - Removed the training drawer, library, inspector, API client, helpers, query keys, and routes. - Added route and row regressions that assert training UI does not return. - Updated the Decisions Storybook description to match the available controls. ## Verification - `pnpm exec vitest run ui/src/App.test.tsx ui/src/components/AttentionQueueRow.test.tsx` — 32 tests passed. - `pnpm check:token-gates` — all gates clean. - `pnpm -r typecheck` — passed. - `pnpm build` — passed. - `pnpm test:run` — server and UI partitions passed. The CLI partition had one environment-only failure because this agent runtime injects static AWS credentials. The exact CLI file passed all 8 tests when those credential variables were unset. - Searched `ui/src` and `ui/storybook` for the removed training routes, drawer, library, badges, and actions. Only negative regression assertions remain. ## Risks - Low implementation risk. This change deletes UI-only entry points and does not change the database or server APIs. - Saved training-page URLs no longer render a board route. This is the intended behavior. > 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 - OpenAI Codex, model `gpt-5.6-sol`, with `xhigh` reasoning. The runtime did not expose the context-window size. The agent used repository tools, shell execution, and automated tests. ## 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) - [x] 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: Paperclip <noreply@paperclip.ing>
124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
import { type ReactNode } from "react";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { Loader2, Sun } from "lucide-react";
|
|
import type { Agent, AttentionItem } from "@paperclipai/shared";
|
|
import { decisionQueuesApi } from "../api/decisionQueues";
|
|
import { useToastActions } from "../context/ToastContext";
|
|
import { queryKeys } from "../lib/queryKeys";
|
|
import { attentionIdleDays } from "../lib/attention";
|
|
import { AttentionQueueRow } from "./AttentionQueueRow";
|
|
import { IssueGroupHeader } from "./IssueGroupHeader";
|
|
import { Button } from "./ui/button";
|
|
|
|
/**
|
|
* A collapsible shelf header + body (snoozed / dismissed / aging / decided /
|
|
* expired). Shared by the desk and the per-queue page so both collapse the same
|
|
* way across both decision surfaces.
|
|
*/
|
|
export function Curtain({
|
|
label,
|
|
count,
|
|
open,
|
|
onToggle,
|
|
children,
|
|
}: {
|
|
label: string;
|
|
count?: number | string;
|
|
open: boolean;
|
|
onToggle: () => void;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<section className="space-y-2">
|
|
<IssueGroupHeader
|
|
label={count == null ? label : `${label} (${count})`}
|
|
collapsible
|
|
collapsed={!open}
|
|
onToggle={onToggle}
|
|
className="text-muted-foreground"
|
|
/>
|
|
{open && <div className="space-y-4">{children}</div>}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* An aging-shelf row (§4.4): the standard card, prefaced by an idle-duration
|
|
* label and a "Keep on desk" affordance that clears the shelf flag server-side
|
|
* (P1 retention `keep`). Shared by the desk and the per-queue page.
|
|
*/
|
|
export function AgingItemRow({
|
|
item,
|
|
companyId,
|
|
now,
|
|
agentMap,
|
|
agents,
|
|
currentUserId,
|
|
expanded,
|
|
onToggleExpand,
|
|
onDismiss,
|
|
onSnooze,
|
|
}: {
|
|
item: AttentionItem;
|
|
companyId: string;
|
|
now: number;
|
|
agentMap: Map<string, Agent>;
|
|
agents: Agent[] | undefined;
|
|
currentUserId: string | null;
|
|
expanded: boolean;
|
|
onToggleExpand: (item: AttentionItem) => void;
|
|
onDismiss: (item: AttentionItem) => void;
|
|
onSnooze: (item: AttentionItem, snoozedUntil: string) => void;
|
|
}) {
|
|
const queryClient = useQueryClient();
|
|
const { pushToast } = useToastActions();
|
|
const idleDays = attentionIdleDays(item, now);
|
|
const keep = useMutation({
|
|
mutationFn: () => decisionQueuesApi.setKeep(companyId, item.sourceKind, item.subject.id, true),
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.attention(companyId) });
|
|
pushToast({ title: "Kept on desk", body: item.subject.title ?? undefined, tone: "success" });
|
|
},
|
|
onError: (error) =>
|
|
pushToast({
|
|
title: "Could not keep this decision",
|
|
body: error instanceof Error ? error.message : "Please try again.",
|
|
tone: "error",
|
|
}),
|
|
});
|
|
|
|
return (
|
|
<div className="space-y-1.5">
|
|
<div className="flex items-center justify-between gap-2 px-1">
|
|
<span className="text-(length:--text-nano) text-muted-foreground">
|
|
Idle {idleDays} {idleDays === 1 ? "day" : "days"}
|
|
</span>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="xs"
|
|
className="h-7 gap-1"
|
|
disabled={keep.isPending || item.keep}
|
|
onClick={() => keep.mutate()}
|
|
>
|
|
{keep.isPending && <Loader2 className="h-3 w-3 animate-spin" />}
|
|
<Sun className="h-3.5 w-3.5" />
|
|
{item.keep ? "Kept" : "Keep on desk"}
|
|
</Button>
|
|
</div>
|
|
<AttentionQueueRow
|
|
item={item}
|
|
companyId={companyId}
|
|
expanded={expanded}
|
|
onToggleExpand={onToggleExpand}
|
|
onDismiss={onDismiss}
|
|
onSnooze={onSnooze}
|
|
agentMap={agentMap}
|
|
agents={agents}
|
|
showTriage
|
|
currentUserId={currentUserId}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|