From 22fea65ee4d0a747d9122d2008c9c00ca81b8d16 Mon Sep 17 00:00:00 2001 From: tomsmith8 Date: Thu, 21 May 2026 09:04:35 +0000 Subject: [PATCH] Generated with Hive: Add UI support for add_source reviews with source details and mock data --- src/components/admin/review-row.tsx | 28 +++++- src/lib/__tests__/reviews.test.tsx | 135 ++++++++++++++++++++++++++++ src/lib/mock-data.ts | 35 ++++++++ 3 files changed, 196 insertions(+), 2 deletions(-) diff --git a/src/components/admin/review-row.tsx b/src/components/admin/review-row.tsx index 2489950..3c276b2 100644 --- a/src/components/admin/review-row.tsx +++ b/src/components/admin/review-row.tsx @@ -3,7 +3,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react" import { createPortal } from "react-dom" import { useRouter } from "next/navigation" -import { ArrowRight, ArrowRightLeft, ChevronRight, GitMerge, Trash2, type LucideIcon } from "lucide-react" +import { ArrowRight, ArrowRightLeft, ChevronRight, GitMerge, PlusCircle, Trash2, type LucideIcon } from "lucide-react" import { formatDateRelative } from "@/lib/date-format" import type { Review, ReviewStatus } from "@/lib/graph-api" import { approveReview, dismissReview } from "@/lib/graph-api" @@ -229,6 +229,11 @@ const ACTION_LABELS: Record = { rowLabel: (s) => `Replace ${s.displayName ?? s.typeLabel}`, approvePrompt: () => "Replace the old node with the new one?", }, + add_source: { + approve: "Add", + rowLabel: () => "Add new source", + approvePrompt: () => "Add this source to the radar?", + }, } // ── Confirm action popover (used for both Approve and Dismiss) ──────────────── @@ -408,6 +413,7 @@ const ICON_MAP: Record = { "git-merge": GitMerge, "trash-2": Trash2, "arrow-right-left": ArrowRightLeft, + "plus-circle": PlusCircle, } // ── Main ReviewRow ──────────────────────────────────────────────────────────── @@ -553,7 +559,9 @@ export function ReviewRow({ ) : ( - {labels.rowLabel(subjectSummary)} + {(!direction && review.subject_nodes.length === 0 && review.display_label) + ? review.display_label + : labels.rowLabel(subjectSummary)} )} @@ -646,6 +654,22 @@ export function ReviewRow({ /> + ) : review.action_name === "add_source" && review.action_payload && typeof review.action_payload === "object" ? ( +
+
+ Suggested Source +
+
+
+ Type: + {String((review.action_payload as Record).source_type ?? "—")} +
+
+ Source: + {String((review.action_payload as Record).source ?? "—")} +
+
+
) : (
diff --git a/src/lib/__tests__/reviews.test.tsx b/src/lib/__tests__/reviews.test.tsx index 26b9505..4f79241 100644 --- a/src/lib/__tests__/reviews.test.tsx +++ b/src/lib/__tests__/reviews.test.tsx @@ -386,6 +386,141 @@ describe("ReviewRow", () => { expect(getByText(/Hide Mock Episode/)).toBeTruthy() }) + // ── add_source / new_source_candidate ───────────────────────────────────── + + it("collapsed row shows display_label when subject_nodes is empty and display_label is set", () => { + const { getByText } = render( + + ) + expect(getByText("Add Youtube Channel: https://www.youtube.com/@lexfridman")).toBeTruthy() + }) + + it("collapsed row renders plus-circle icon for add_source action", () => { + const { container } = render( + + ) + expect(container.querySelector("svg")).toBeTruthy() + }) + + it("approve button label shows 'Add' for add_source action", () => { + const { getByText } = render( + + ) + expect(getByText("Add")).toBeTruthy() + }) + + it("expanded section shows 'Suggested Source' heading with source_type and source for add_source", async () => { + const user = userEvent.setup() + const { getByText } = render( + + ) + // Click the row to expand + await user.click(getByText("Add Youtube Channel: https://www.youtube.com/@lexfridman")) + expect(getByText("Suggested Source")).toBeTruthy() + expect(getByText("youtube_channel")).toBeTruthy() + expect(getByText("https://www.youtube.com/@lexfridman")).toBeTruthy() + }) + + it("expanded section does NOT show 'Subjects (0)' for add_source", async () => { + const user = userEvent.setup() + const { getByText, queryByText } = render( + + ) + await user.click(getByText("Add Youtube Channel: https://www.youtube.com/@lexfridman")) + expect(queryByText("Subjects (0)")).toBeNull() + }) + + it("shows error_message inline for failed new_source_candidate review", () => { + const { getByText } = render( + + ) + expect(getByText(/add_source failed: Source already exists/)).toBeTruthy() + }) + it("renders topic_review_candidate row without errors", () => { const { container, getByText } = render(