From 0db696c898d14b89624eea8ba8559fa254cc2d64 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 6 Nov 2026 00:52:35 +0200 Subject: [PATCH] feat(giellie): show stock quantity approvals as a table A 40-line stocktake in raw JSON is unreviewable. The stock quantity tool gets a table with the mode, SKU, description and quantity. Every other tool keeps its JSON exactly as it was. --- src/components/giellie/approval-card.test.tsx | 79 +++++++++++++++++++ src/components/giellie/approval-card.tsx | 57 ++++++++++++- 2 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/components/giellie/approval-card.test.tsx b/src/components/giellie/approval-card.test.tsx index 529ed67..8946233 100644 --- a/src/components/giellie/approval-card.test.tsx +++ b/src/components/giellie/approval-card.test.tsx @@ -94,3 +94,82 @@ describe("ApprovalCard", () => { expect((screen.getByRole("button", { name: /reject/i }) as HTMLButtonElement).disabled).toBe(true); }); }); + + +describe("the stock quantity tool renders a table", () => { + it("lists the sku, description and qty", () => { + render( + , + ); + expect(screen.getByText("ABC-123")).toBeTruthy(); + expect(screen.getByText("Bolt M12")).toBeTruthy(); + expect(screen.getByText("50")).toBeTruthy(); + expect(screen.getByText(/new quantity on hand/i)).toBeTruthy(); + expect(document.body.textContent).not.toMatch(/"lines"/); + }); + + it("says when the quantities are being added instead of set", () => { + render( + , + ); + expect(screen.getByText(/adding these quantities/i)).toBeTruthy(); + }); + + it("leaves other tools as JSON", () => { + render( + , + ); + expect(document.body.textContent).toMatch(/"status"/); + }); +}); diff --git a/src/components/giellie/approval-card.tsx b/src/components/giellie/approval-card.tsx index 7e7fcf8..300afce 100644 --- a/src/components/giellie/approval-card.tsx +++ b/src/components/giellie/approval-card.tsx @@ -1,9 +1,62 @@ "use client"; import { Check, Mail, X } from "lucide-react"; +import type { ReactNode } from "react"; import { Button } from "@/components/ui/button"; import type { PendingAction } from "@/lib/giellie-types"; +type StockQuantityLine = { sku?: string; description?: string; qty?: number }; + +/** + * The stock quantity tool is the one tool whose input is a list the + * operator has to read line by line. JSON is not reviewable at that + * size, so it gets a table. Every other tool keeps raw JSON. + */ +export function renderToolInput(toolName: string, input: unknown): ReactNode { + if (toolName === "stock_items.set_quantities" && input && typeof input === "object") { + const payload = input as { mode?: unknown; lines?: unknown }; + const mode = typeof payload.mode === "string" ? payload.mode : ""; + const lines = Array.isArray(payload.lines) ? (payload.lines as StockQuantityLine[]) : []; + if (lines.length > 0) { + return ( +
+

+ {mode === "add" + ? "Adding these quantities to the current stock." + : "Setting these as the new quantity on hand."} +

+
+ + + + + + + + + + {lines.map((line, i) => ( + + + + + + ))} + +
SKUDescriptionQty
{line.sku ?? "—"}{line.description ?? "—"}{line.qty ?? "—"}
+
+
+ ); + } + } + // Every other tool keeps its raw JSON exactly as it was. + return ( +
+      {JSON.stringify(input, null, 2)}
+    
+ ); +} + interface Props { pending: PendingAction; busy: boolean; @@ -39,9 +92,7 @@ export function ApprovalCard({ pending, busy, onDecide }: Props) { )} -
-              {JSON.stringify(tu.input, null, 2)}
-            
+ {renderToolInput(tu.name, tu.input)} ))}