Files
PaperClipAI/ui/src/components/DecisionShelf.tsx
T
DottaandPaperclip 145d86911b Remove decision training UI (#11225)
## 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>
2026-08-11 14:23:57 -04:00

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>
);
}