From 6098c1f928adb2126fe6b6da1972128e60eefa18 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 14 Oct 2026 11:42:12 +0200 Subject: [PATCH] feat: add a change preview to the plugin UI kit --- lib/__tests__/plugin-ui-kit.test.tsx | 9 ++++++++ lib/plugin-sandbox/ui-kit/components.tsx | 27 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/lib/__tests__/plugin-ui-kit.test.tsx b/lib/__tests__/plugin-ui-kit.test.tsx index b4c4fb50e..81be9cbd2 100644 --- a/lib/__tests__/plugin-ui-kit.test.tsx +++ b/lib/__tests__/plugin-ui-kit.test.tsx @@ -77,10 +77,19 @@ describe('plugin UI kit components', () => { Sent + ); for (const child of Array.from(container.children)) expect(child.classList.contains('bk'), child.outerHTML.slice(0, 60)).toBe(true); }); + it('marks removed and added words in a change preview', () => { + const { container } = render(); + expect(container.querySelector('del')).toHaveTextContent('Bob'); + expect(container.querySelector('del')!.className).toContain('line-through'); + expect(container.querySelector('ins')).toHaveTextContent('Robert'); + expect(container.textContent).toBe('Hi BobRobert'); + }); + it('draws a checkbox the user can toggle by its label', () => { const onChange = vi.fn(); render(); diff --git a/lib/plugin-sandbox/ui-kit/components.tsx b/lib/plugin-sandbox/ui-kit/components.tsx index 670738f8a..690bae0c2 100644 --- a/lib/plugin-sandbox/ui-kit/components.tsx +++ b/lib/plugin-sandbox/ui-kit/components.tsx @@ -792,3 +792,30 @@ export function SidebarRow({ icon, label, detail, onClick, title }: { icon?: str export function SidebarEmpty({ children }: Children) { return

{children}

; } + +// ─── Changes ─────────────────────────────────────────────────── + +export interface DiffPart { + type: 'same' | 'add' | 'del'; + text: string; +} + +/** + * A text with its changes marked: removed words struck through in red, + * added ones highlighted. For previews of a rewrite before it is applied. + * `streaming` shows a caret at the end while the text is still coming in. + */ +export function DiffView({ parts, streaming = false, label }: { parts: DiffPart[]; streaming?: boolean; label?: string }) { + return ( +
+ {parts.map((p, i) => ( + p.type === 'del' + ? {p.text} + : p.type === 'add' + ? {p.text} + : {p.text} + ))} + {streaming &&
+ ); +}