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 && }
+
+ );
+}