feat: show diff view for AI CV rewrites

The master-CV rewrite preview replaced text without showing what
changed -- the teardown flagged this as the biggest unmanaged AI risk
(a rewrite silently upgrading "assisted with migration" to "led
migration" was invisible). Add a "Show changes" toggle on the rewrite
preview panel that renders a word-level diff (before = current master
text or the targeted section's stored content, after = the AI's
rewrite) instead of the flat replacement text.

Defaults to off: an existing test proved diff-by-default breaks the
familiar plain-text read (word-fragmented spans aren't matchable as
one block), and it's a better UX default regardless -- read normally,
opt into the diff when you want the trust signal.

Uses the `diff` package (word-level diffWords) rather than hand-rolled
LCS; no existing dependency covers this, and it's a solved problem.
This commit is contained in:
cesnimda
2026-07-12 15:21:04 +02:00
parent 66384bda60
commit cbd045a0d3
7 changed files with 103 additions and 20 deletions
+26
View File
@@ -0,0 +1,26 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import TextDiff from "./components/TextDiff";
describe("TextDiff", () => {
it("renders unchanged text without strike-through or highlight styling", () => {
render(<TextDiff before="Backend engineer." after="Backend engineer." />);
expect(screen.getByText("Backend engineer.")).toBeInTheDocument();
});
it("marks removed words with a distinct style from unchanged text", () => {
render(<TextDiff before="Assisted with the migration." after="Led the migration." />);
const removed = screen.getByText("Assisted with", { selector: "span" });
const unchanged = screen.getByText(/the migration\./, { selector: "span" });
// jsdom doesn't resolve emotion's generated CSS cascade for getComputedStyle, so assert the
// component branched into a different (MUI-generated) class for removed vs. unchanged text
// rather than the literal computed decoration value.
expect(removed.className).not.toBe(unchanged.className);
});
it("treats an empty before as an entirely new addition", () => {
render(<TextDiff before="" after="Brand new summary." />);
expect(screen.getByText(/Brand new summary\./)).toBeInTheDocument();
});
});