Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/components/admin/review-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ import { Checkbox } from "@/components/ui/checkbox"
import type { SchemaNode } from "@/lib/schema-types"
import { DISPLAY_KEY_FALLBACKS, pickString } from "@/lib/node-display"
import { useUserStore } from "@/stores/user-store"

const IMAGE_FIELD_KEY = "image_url"

function ProposedChangeValue({ fieldKey, value }: { fieldKey: string; value: unknown }) {
const [imgError, setImgError] = useState(false)
const strValue = String(value)

if (fieldKey === IMAGE_FIELD_KEY && typeof value === "string" && value.length > 0 && !imgError) {
return (
<div className="col-span-2">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={strValue}
alt="image preview"
className="max-h-40 w-full rounded-md border border-border/50 object-contain bg-muted/20"
onError={() => setImgError(true)}
/>
</div>
)
}

return <span className="break-all text-foreground/90">{strValue}</span>
}
import { useGraphStore } from "@/stores/graph-store"

// ── Status badge ──────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -1001,7 +1024,7 @@ export function ReviewRow({
{changedEntries.map(([k, v]) => (
<>
<span key={`k-${k}`} className="text-muted-foreground">{k}</span>
<span key={`v-${k}`} className="break-all text-foreground/90">{String(v)}</span>
<ProposedChangeValue key={`v-${k}`} fieldKey={k} value={v} />
</>
))}
</div>
Expand Down
81 changes: 81 additions & 0 deletions src/lib/__tests__/reviews.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,87 @@ describe("ReviewRow", () => {
expect(getByText("Node Being Edited")).toBeTruthy()
})

// ── edit_node image_url preview ───────────────────────────────────────────

it("edit_node image_url change renders an img with the proposed URL as src", async () => {
const user = userEvent.setup()
const imgUrl = "https://example.com/photo.jpg"
const { getByText, container } = render(
<ReviewRow
schemas={[]}
review={makeReview({
action_name: "edit_node",
action_payload: {
ref_id: "n8",
node_type: "Person",
properties: { image_url: imgUrl },
},
subject_ids: ["n8"],
subject_nodes: [{ ref_id: "n8", node_type: "Person", properties: { name: "Alice", image_url: "https://old.com/old.jpg" } }],
display_label: "Edit Alice",
})}
onRefresh={noop}
/>
)
await user.click(getByText("Edit Alice"))
const img = container.querySelector("img[alt='image preview']") as HTMLImageElement
expect(img).toBeTruthy()
expect(img.src).toBe(imgUrl)
})

it("edit_node non-image properties still render as plain text", async () => {
const user = userEvent.setup()
const { getByText } = render(
<ReviewRow
schemas={[]}
review={makeReview({
action_name: "edit_node",
action_payload: {
ref_id: "n8",
node_type: "Person",
properties: { name: "Bob Updated" },
},
subject_ids: ["n8"],
subject_nodes: [{ ref_id: "n8", node_type: "Person", properties: { name: "Bob" } }],
display_label: "Edit Bob",
})}
onRefresh={noop}
/>
)
await user.click(getByText("Edit Bob"))
expect(getByText("Bob Updated")).toBeTruthy()
})

it("edit_node image_url falls back to URL string on image load error", async () => {
const user = userEvent.setup()
const brokenUrl = "https://example.invalid/content-c5-broken.jpg"
const { getByText, container } = render(
<ReviewRow
schemas={[]}
review={makeReview({
action_name: "edit_node",
action_payload: {
ref_id: "n8",
node_type: "Person",
properties: { image_url: brokenUrl },
},
subject_ids: ["n8"],
subject_nodes: [{ ref_id: "n8", node_type: "Person", properties: { name: "Charlie" } }],
display_label: "Edit Charlie",
})}
onRefresh={noop}
/>
)
await user.click(getByText("Edit Charlie"))
// Trigger the error handler
const img = container.querySelector("img[alt='image preview']") as HTMLImageElement
expect(img).toBeTruthy()
fireEvent.error(img)
// After error, the img should be replaced by the raw URL string
expect(getByText(brokenUrl)).toBeTruthy()
expect(container.querySelector("img[alt='image preview']")).toBeNull()
})

// ── add_schema_node_type ──────────────────────────────────────────────────

it("renders add_schema_node_type row without crashing with valid payload", () => {
Expand Down
Loading