(() => ({
- q: q.trim() || undefined,
- project: project === "all" ? undefined : project,
- kind: kind === "all" ? undefined : kind as DecisionTrainingSourceKind,
- author: author === "all" ? undefined : author,
- }), [author, kind, project, q]);
- const recordsQuery = useQuery({
- queryKey: [...queryKeys.decisionTraining.list(selectedCompanyId ?? ""), filters],
- queryFn: ({ signal }) => decisionTrainingApi.list(selectedCompanyId!, filters, { signal }),
- enabled: Boolean(selectedCompanyId),
- });
- const projectsQuery = useQuery({
- queryKey: queryKeys.projects.list(selectedCompanyId ?? "", { includeArchived: true }),
- queryFn: () => projectsApi.list(selectedCompanyId!, { includeArchived: true }),
- enabled: Boolean(selectedCompanyId),
- });
- const records = recordsQuery.data ?? [];
- const projects = projectsQuery.data ?? [];
- const projectNames = new Map(projects.map((item: Project) => [item.id, item.name]));
- const authors = [...new Set(records.map((row) => row.example.createdByUserId))];
-
- return (
-
-
-
-
-
-
- setQ(event.target.value)} placeholder="Search notes, tasks…" className="pl-9" />
-
-
-
-
-
-
- {recordsQuery.isLoading ?
Loading training examples…
: null}
- {recordsQuery.isError ?
Could not load training examples.
: null}
- {!recordsQuery.isLoading && !recordsQuery.isError && records.length === 0 ?
No training examples match these filters.
: null}
-
-
-
- DecisionOutcomeSnapshotAuthorCreatedEdited
-
- {records.map(({ example, issueIdentifier, issueTitle }) => {
- const issue = example.snapshot.issue;
- const projectName = projectNames.get(stringValue(issue, "projectId", "project_id") ?? "") ?? "No project";
- const edited = example.updatedAt !== example.createdAt;
- return (
-
- );
- })}
-
-
- );
-}
-
-function JsonPanel({ value }: { value: unknown }) {
- return {JSON.stringify(value, null, 2)};
-}
-
-export function TrainingThreadPanel({ example, liveComments }: { example: DecisionTrainingExample; liveComments: IssueComment[] }) {
- const { included, excluded } = partitionTrainingThread(example.snapshot.comments, liveComments, example.cutoffAt);
- const renderComment = (comment: SnapshotRecord, ghosted = false) => (
-
-
{stringValue(comment, "authorType", "author_type") ?? "Comment"} · {recordDate(comment) ? formatDateTime(recordDate(comment)) : "Unknown time"} · {recordId(comment).slice(0, 10)}
-
{stringValue(comment, "body") ?? ""}
- {ghosted ?
Excluded from snapshot · after cutoff
: null}
-
- );
- return {included.map((comment) => renderComment(comment))}
CUTOFF · {formatDateTime(example.cutoffAt)}
{excluded.map((comment) => renderComment(comment as SnapshotRecord, true))}
;
-}
-
-export function TrainingInspector() {
- const { id = "" } = useParams();
- const navigate = useNavigate();
- const queryClient = useQueryClient();
- const { setBreadcrumbs } = useBreadcrumbs();
- const { pushToast } = useToastActions();
- const [notes, setNotes] = useState("");
- const [editing, setEditing] = useState(false);
- const recordQuery = useQuery({ queryKey: queryKeys.decisionTraining.detail(id), queryFn: ({ signal }) => decisionTrainingApi.get(id, { signal }), enabled: Boolean(id) });
- const example = recordQuery.data;
- const commentsQuery = useQuery({ queryKey: ["issues", example?.issueId, "comments", "training-audit"], queryFn: () => issuesApi.listComments(example!.issueId, { order: "asc" }), enabled: Boolean(example?.issueId) });
- useEffect(() => {
- if (example && !editing) setNotes(example.notes);
- }, [editing, example]);
- useEffect(() => setBreadcrumbs([{ label: "Decisions", href: "/decisions" }, { label: "Training", href: decisionTrainingHref() }, { label: example ? decisionTitle(example) : "Example" }]), [example, setBreadcrumbs]);
- const saveMutation = useMutation({
- mutationFn: () => decisionTrainingApi.updateNotes(id, notes.trim()),
- onSuccess: (updated) => {
- queryClient.setQueryData(queryKeys.decisionTraining.detail(id), updated);
- queryClient.invalidateQueries({ queryKey: queryKeys.decisionTraining.list(updated.companyId) });
- pushToast({ title: "Notes updated", tone: "success" });
- setEditing(false);
- },
- onError: (error) => {
- pushToast({
- title: "Could not update notes",
- body: error instanceof Error ? error.message : "Please try again.",
- tone: "error",
- });
- },
- });
-
- if (recordQuery.isLoading) return Loading training example…
;
- if (recordQuery.isError) return Could not load training example.
;
- if (!example) return Training example not found.
;
- const issueIdentifier = stringValue(example.snapshot.issue, "identifier") ?? example.issueId.slice(0, 8);
- return (
-
-
-
-
Training notes
Last edited {formatDateTime(example.updatedAt)} · edits are versioned
{!editing ?
: null}
{editing ? : {example.notes || "No notes recorded."}
}
-
Frozen state
read-onlyThreadIssueRunsCodeDecision
-
-
- );
-}
diff --git a/ui/src/pages/WhatNeedsMe.tsx b/ui/src/pages/WhatNeedsMe.tsx
index 3d04199c05..0e51fc88bf 100644
--- a/ui/src/pages/WhatNeedsMe.tsx
+++ b/ui/src/pages/WhatNeedsMe.tsx
@@ -38,7 +38,6 @@ import {
type AttentionGroupBy,
type AttentionSortOrder,
} from "../lib/attention";
-import { decisionTrainingHref } from "../lib/decisionTraining";
import { hasBlockingShortcutDialog, resolveAttentionQueueKeyAction } from "../lib/keyboardShortcuts";
import { PageSkeleton } from "../components/PageSkeleton";
import { AttentionQueueRow } from "../components/AttentionQueueRow";
@@ -47,7 +46,6 @@ import { Curtain, AgingItemRow } from "../components/DecisionShelf";
import { DecisionQueueRail } from "../components/DecisionQueueRail";
import { DecisionDateChips, type AttentionCustomRange } from "../components/DecisionDateChips";
import { DecisionResolver } from "../components/DecisionResolver";
-import { DecisionTrainingDrawer } from "../components/DecisionTrainingDrawer";
import { IssueGroupHeader } from "../components/IssueGroupHeader";
/** Curtain rows never expand; module-level so memoized rows see one identity. */
@@ -100,9 +98,6 @@ export function WhatNeedsMe() {
// still follows a click, so keyboard actions target the row you just used.
const [selectionFromKeyboard, setSelectionFromKeyboard] = useState(false);
const [autoExpandDone, setAutoExpandDone] = useState(false);
- // Decision-training drawer target. `null` when closed.
- const [trainingItem, setTrainingItem] = useState(null);
-
// Toolbar preferences (persisted to localStorage, Inbox pattern).
const [groupBy, setGroupBy] = useState(() => loadAttentionGroupBy());
const [sortOrder, setSortOrder] = useState(() => loadAttentionSortOrder());
@@ -466,10 +461,6 @@ export function WhatNeedsMe() {
setSelectionFromKeyboard(false);
setExpandedId((prev) => (prev === item.id ? null : item.id));
}, []);
- const handleTrain = useCallback((item: AttentionItem) => {
- setTrainingItem(item);
- }, []);
-
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
const action = resolveAttentionQueueKeyAction({
@@ -538,7 +529,6 @@ export function WhatNeedsMe() {
onGroupByChange={updateGroupBy}
sortOrder={sortOrder}
onSortOrderChange={updateSortOrder}
- onOpenTraining={() => navigate(decisionTrainingHref())}
/>
@@ -621,7 +611,6 @@ export function WhatNeedsMe() {
onToggleExpand={handleToggleExpand}
onDismiss={handleDismiss}
onSnooze={handleSnooze}
- onTrain={handleTrain}
agentMap={agentMap}
agents={agents}
showTriage
@@ -712,7 +701,6 @@ export function WhatNeedsMe() {
onToggleExpand={handleToggleExpand}
onDismiss={handleDismiss}
onSnooze={handleSnooze}
- onTrain={handleTrain}
/>
))}
@@ -768,16 +756,6 @@ export function WhatNeedsMe() {
)}
-
- {
- if (!next) setTrainingItem(null);
- }}
- companyId={selectedCompanyId}
- item={trainingItem}
- currentUserId={currentUserId}
- />
);
}
diff --git a/ui/storybook/stories/decisions-desk.stories.tsx b/ui/storybook/stories/decisions-desk.stories.tsx
index c0a0928541..acda732786 100644
--- a/ui/storybook/stories/decisions-desk.stories.tsx
+++ b/ui/storybook/stories/decisions-desk.stories.tsx
@@ -438,7 +438,7 @@ export const AgingShelf: Story = {
/**
* Screen 2 — a queue page. The queue carries the same toolbar as
- * the desk (filter / group / sort / training), the date-range chips, the arrival
+ * the desk (filter / group / sort), the date-range chips, the arrival
* timeline groupings ("Decide now" / "New today" / "Earlier") and the aging
* shelf, above the seed-rules card (with its rewritten copy) and the per-item
* Exclude-with-reason affordance. Each source-native decision still resolves per