Skip to content

Commit 9b016da

Browse files
authored
feat(review): diff display options with ConfigStore integration (#428)
* feat(config): add DiffOptions to PlannotatorConfig with deep-merge support Extends the server-side config system to support diff display options namespaced under `diffOptions` in ~/.plannotator/config.json. The saveConfig deep-merge ensures partial writes (e.g. changing only overflow) don't clobber sibling keys. For provenance purposes, this commit was AI assisted. * feat(server): accept diffOptions in POST /api/config handlers All six server handlers (plan, review, annotate x Bun + Pi) now whitelist diffOptions alongside displayName, forwarding it to saveConfig for deep-merged disk persistence. For provenance purposes, this commit was AI assisted. * feat(config): register diff display settings with deep-merge write batching Adds 6 diff option settings to the ConfigStore registry (diffStyle, diffOverflow, diffIndicators, diffLineDiffType, diffShowLineNumbers, diffShowBackground), all synced to disk under the diffOptions namespace. Fixes write batching for nested server keys — replaces Object.assign with deepMerge so rapid changes to multiple diff options within the 300ms debounce window don't clobber each other. Includes legacy cookie migration for the old review-diff-style key. For provenance purposes, this commit was AI assisted. * feat(review): wire diff display options through to @pierre/diffs Migrates diffStyle from raw cookie storage to ConfigStore. Reads all 6 diff options via useConfigValue and forwards them to FileDiff: overflow, diffIndicators, lineDiffType, disableLineNumbers, disableBackground. Adds a Scroll/Wrap toolbar toggle alongside the existing Split/Unified toggle. Disables the custom resizable split dragger in wrap mode since Pierre manages its own grid layout. Fixes the unsafeCSS grid override to only target scroll mode, preventing per-character wrapping in split view. For provenance purposes, this commit was AI assisted. * feat(review): add Display tab to review-mode Settings dialog Adds a Display tab when Settings is in review mode, exposing all 6 diff options: Diff Style, Line Overflow, Change Indicators, Inline Diff Granularity, Show Line Numbers, and Show Diff Background. Changes take effect immediately and persist via ConfigStore (cookie + disk). Extracts reusable SegmentedControl and ToggleSwitch helpers for the review display controls. For provenance purposes, this commit was AI assisted. * chore: update bun.lock For provenance purposes, this commit was AI assisted. * fix(review): reapply search highlights after display option changes Add new diff display props to the search highlight and scroll-to-match effect dependency arrays so highlights are restored after Pierre re-renders. For provenance purposes, this commit was AI assisted.
1 parent a0a6edd commit 9b016da

14 files changed

Lines changed: 412 additions & 52 deletions

File tree

apps/pi-extension/server/serverAnnotate.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ export async function startAnnotateServer(options: {
7979
});
8080
} else if (url.pathname === "/api/config" && req.method === "POST") {
8181
try {
82-
const body = (await parseBody(req)) as { displayName?: string };
83-
if (body.displayName !== undefined) {
84-
saveConfig({ displayName: body.displayName });
85-
}
82+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
83+
const toSave: Record<string, unknown> = {};
84+
if (body.displayName !== undefined) toSave.displayName = body.displayName;
85+
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
86+
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
8687
json(res, { ok: true });
8788
} catch {
8889
json(res, { error: "Invalid request" }, 400);

apps/pi-extension/server/serverPlan.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,11 @@ export async function startPlanReviewServer(options: {
210210
}
211211
} else if (url.pathname === "/api/config" && req.method === "POST") {
212212
try {
213-
const body = (await parseBody(req)) as { displayName?: string };
214-
if (body.displayName !== undefined) {
215-
saveConfig({ displayName: body.displayName });
216-
}
213+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
214+
const toSave: Record<string, unknown> = {};
215+
if (body.displayName !== undefined) toSave.displayName = body.displayName;
216+
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
217+
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
217218
json(res, { ok: true });
218219
} catch {
219220
json(res, { error: "Invalid request" }, 400);

apps/pi-extension/server/serverReview.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,11 @@ export async function startReviewServer(options: {
463463
json(res, result);
464464
} else if (url.pathname === "/api/config" && req.method === "POST") {
465465
try {
466-
const body = (await parseBody(req)) as { displayName?: string };
467-
if (body.displayName !== undefined) {
468-
saveConfig({ displayName: body.displayName });
469-
}
466+
const body = (await parseBody(req)) as { displayName?: string; diffOptions?: Record<string, unknown> };
467+
const toSave: Record<string, unknown> = {};
468+
if (body.displayName !== undefined) toSave.displayName = body.displayName;
469+
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
470+
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
470471
json(res, { ok: true });
471472
} catch {
472473
json(res, { error: "Invalid request" }, 400);

bun.lock

Lines changed: 38 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/review-editor/App.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,12 @@ const ReviewApp: React.FC = () => {
9393
const [showExportModal, setShowExportModal] = useState(false);
9494
const [showNoAnnotationsDialog, setShowNoAnnotationsDialog] = useState(false);
9595
const [isLoading, setIsLoading] = useState(true);
96-
const [diffStyle, setDiffStyle] = useState<'split' | 'unified'>(() => {
97-
return (storage.getItem('review-diff-style') as 'split' | 'unified') || 'split';
98-
});
96+
const diffStyle = useConfigValue('diffStyle');
97+
const diffOverflow = useConfigValue('diffOverflow');
98+
const diffIndicators = useConfigValue('diffIndicators');
99+
const diffLineDiffType = useConfigValue('diffLineDiffType');
100+
const diffShowLineNumbers = useConfigValue('diffShowLineNumbers');
101+
const diffShowBackground = useConfigValue('diffShowBackground');
99102
const [isPanelOpen, setIsPanelOpen] = useState(true);
100103
const [copyFeedback, setCopyFeedback] = useState<string | null>(null);
101104
const [viewedFiles, setViewedFiles] = useState<Set<string>>(new Set());
@@ -438,10 +441,8 @@ const ReviewApp: React.FC = () => {
438441
.finally(() => setIsLoading(false));
439442
}, []);
440443

441-
// Handle diff style change
442444
const handleDiffStyleChange = useCallback((style: 'split' | 'unified') => {
443-
setDiffStyle(style);
444-
storage.setItem('review-diff-style', style);
445+
configStore.set('diffStyle', style);
445446
}, []);
446447

447448
// Handle line selection from diff viewer
@@ -1058,6 +1059,32 @@ const ReviewApp: React.FC = () => {
10581059
</button>
10591060
</div>
10601061

1062+
{/* Overflow toggle */}
1063+
<div className="flex items-center gap-1 bg-muted rounded-lg p-0.5">
1064+
<button
1065+
onClick={() => configStore.set('diffOverflow', 'scroll')}
1066+
className={`px-2 py-1 text-xs rounded-md transition-colors ${
1067+
diffOverflow === 'scroll'
1068+
? 'bg-background text-foreground shadow-sm'
1069+
: 'text-muted-foreground hover:text-foreground'
1070+
}`}
1071+
title="Scroll long lines horizontally"
1072+
>
1073+
Scroll
1074+
</button>
1075+
<button
1076+
onClick={() => configStore.set('diffOverflow', 'wrap')}
1077+
className={`px-2 py-1 text-xs rounded-md transition-colors ${
1078+
diffOverflow === 'wrap'
1079+
? 'bg-background text-foreground shadow-sm'
1080+
: 'text-muted-foreground hover:text-foreground'
1081+
}`}
1082+
title="Wrap long lines"
1083+
>
1084+
Wrap
1085+
</button>
1086+
</div>
1087+
10611088
{/* Primary actions */}
10621089
<button
10631090
onClick={handleCopyDiff}
@@ -1377,6 +1404,11 @@ const ReviewApp: React.FC = () => {
13771404
filePath={activeFile.path}
13781405
oldPath={activeFile.oldPath}
13791406
diffStyle={diffStyle}
1407+
diffOverflow={diffOverflow}
1408+
diffIndicators={diffIndicators}
1409+
lineDiffType={diffLineDiffType}
1410+
disableLineNumbers={!diffShowLineNumbers}
1411+
disableBackground={!diffShowBackground}
13801412
annotations={activeFileAnnotations}
13811413
selectedAnnotationId={selectedAnnotationId}
13821414
pendingSelection={pendingSelection}

0 commit comments

Comments
 (0)