From 8339bcc7e7be7442a868d19692d4b888e813c715 Mon Sep 17 00:00:00 2001 From: Dotta <34892728+cryppadotta@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:08:36 -0500 Subject: [PATCH] fix(ui): keep decision training under decisions (#10181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Operators review decisions and turn useful outcomes into training examples > - The training library belongs to the Decisions surface, but its routes and entry point appeared as a separate top-level destination > - That mismatch made the training icon feel disconnected and left internal links pointing at legacy `/training` URLs > - This pull request nests the training library and inspector under `/decisions/training`, keeps legacy URLs working through redirects, and uses one route helper everywhere > - The benefit is a clearer Decisions workflow without breaking existing bookmarks or links ## Linked Issues or Issue Description Refs: #9718 Refs: #9779 **Problem:** The Decisions page exposed training as a labeled header action while the training library lived at the unrelated top-level `/training` route. Links across the UI duplicated that legacy path. **Expected behavior:** Training should be an icon action alongside the other Decisions controls, and training library/record URLs should live under `/decisions/training` while old URLs continue to redirect. **Steps to reproduce:** 1. Open the Decisions page. 2. Observe the separate labeled Training button in the page heading. 3. Open a training record and note the top-level `/training/...` URL. **Version/commit:** Current `master` before this PR. **Deployment mode:** All UI deployment modes. ## What Changed - Move the training library and inspector routes under `/decisions/training`. - Redirect legacy `/training` and `/training/:id` URLs to the new locations. - Add `decisionTrainingHref()` and use it for training links and navigation. - Place the Training icon action with the Decisions filter and sort controls. - Add focused tests for library and record URL generation. ## Verification - `pnpm exec vitest run ui/src/lib/decisionTraining.test.ts` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm --filter @paperclipai/ui build` - `pnpm check:token-gates` ## Risks - Low risk: the change is limited to client-side routes and navigation. - Existing `/training` bookmarks remain supported through replacement redirects. - No API, database, migration, dependency, or workflow changes. > 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 using GPT-5.4 with reasoning, repository tool use, shell execution, and code/test verification. ## 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 --- ui/src/App.tsx | 11 +++++++++-- ui/src/components/DecisionTrainingDrawer.tsx | 12 ++++++------ ui/src/lib/decisionTraining.test.ts | 12 ++++++++++++ ui/src/lib/decisionTraining.ts | 4 ++++ ui/src/pages/Training.tsx | 7 ++++--- ui/src/pages/WhatNeedsMe.tsx | 19 +++++++++++++------ 6 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 ui/src/lib/decisionTraining.test.ts diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 7c941d2615..1011501dfe 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -269,8 +269,10 @@ function boardRoutes() { } /> } /> - } /> - } /> + } /> + } /> + } /> + } /> } /> } /> } /> @@ -298,6 +300,11 @@ function InboxRootRedirect() { return ; } +function LegacyTrainingRedirect() { + const { id } = useParams<{ id: string }>(); + return ; +} + function LegacySkillStudioRedirect() { const location = useLocation(); const { companies, selectedCompany, loading } = useCompany(); diff --git a/ui/src/components/DecisionTrainingDrawer.tsx b/ui/src/components/DecisionTrainingDrawer.tsx index 5b5c436290..7e31f83b1e 100644 --- a/ui/src/components/DecisionTrainingDrawer.tsx +++ b/ui/src/components/DecisionTrainingDrawer.tsx @@ -18,7 +18,7 @@ import { Link } from "@/lib/router"; import { decisionTrainingApi, type DecisionTrainingTarget } from "../api/decisionTraining"; import { useToastActions } from "../context/ToastContext"; import { queryKeys } from "../lib/queryKeys"; -import { codeResolutionLabel, trainingTargetForItem } from "../lib/decisionTraining"; +import { codeResolutionLabel, decisionTrainingHref, trainingTargetForItem } from "../lib/decisionTraining"; import { relativeTime } from "../lib/utils"; import { Button } from "./ui/button"; import { Textarea } from "./ui/textarea"; @@ -385,7 +385,7 @@ function SavedState({

{decisionTitle(example)}

{issueIdentifier} · {outcomeLabel(example.decisionOutcome)} · cutoff {formatDateTime(example.cutoffAt)}

+

{decisionTitle(example)}

{issueIdentifier} · {outcomeLabel(example.decisionOutcome)} · cutoff {formatDateTime(example.cutoffAt)}

Training notes

Last edited {formatDateTime(example.updatedAt)} · edits are versioned

{!editing ? : null}
{editing ?