mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
feat(review): migrate all-files code review to Pierre CodeView (#885)
Replaces the all-files renderer with Pierre's virtualized CodeView (one scroller, identical look, scales to huge diffs); deletes the legacy AllFilesDiffView/LazyFileDiff and their flag; single-file panels stay on FileDiff. @pierre/diffs pinned exact 1.2.8 (1.2.9's tree is broken on npm — see #880). Also ships: diff staleness detection ('Diff out of date · Refresh' toolbar notice backed by shared per-VCS fingerprints + GET /api/diff/fresh on both Bun and Pi servers), a stale-content guard so files edited mid-review degrade to raw-patch view instead of breaking virtualization, the Ask AI wrong-file fix (pre-existing), and toolbar/header UI polish (global settings cog, aligned sem badges, no all-files split dragger). Hardened through five review waves; tested with 1384 passing tests incl. real-git fingerprint and patch-consistency suites. Known minor gaps documented in the PR.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
const POLL_INTERVAL_MS = 5000;
|
||||
|
||||
export interface DiffFreshness {
|
||||
/** True when the underlying files changed since the current diff snapshot
|
||||
* was computed AND the user hasn't dismissed this particular staleness. */
|
||||
isStale: boolean;
|
||||
/** Hide the notice for the CURRENT staleness. A further change (different
|
||||
* server fingerprint) re-shows it; a diff refresh resets everything. */
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls `GET /api/diff/fresh` while the review is open. The server compares a
|
||||
* cheap VCS fingerprint captured when the diff snapshot was computed against
|
||||
* the repo's state NOW — files changing mid-review (the normal agent-editing-
|
||||
* while-you-review workflow) flips `fresh` to false.
|
||||
*
|
||||
* Polling is timer-based on purpose: in this product the files change while
|
||||
* the user is actively IN the review (an agent works underneath them), so a
|
||||
* focus/visibility trigger would miss the case that matters. Ticks are
|
||||
* skipped while the document is hidden, and the whole hook no-ops in demo
|
||||
* mode (`enabled: false`).
|
||||
*/
|
||||
export function useDiffFreshness({
|
||||
enabled,
|
||||
resetKey,
|
||||
}: {
|
||||
enabled: boolean;
|
||||
/** Identity of the current diff snapshot (e.g. the rawPatch string). A new
|
||||
* snapshot (refresh / switch) clears staleness + dismissal and resumes. */
|
||||
resetKey: string;
|
||||
}): DiffFreshness {
|
||||
const [staleFingerprint, setStaleFingerprint] = useState<string | null>(null);
|
||||
const [dismissedFingerprint, setDismissedFingerprint] = useState<string | null>(null);
|
||||
|
||||
// New snapshot → clean slate.
|
||||
useEffect(() => {
|
||||
setStaleFingerprint(null);
|
||||
setDismissedFingerprint(null);
|
||||
}, [resetKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
let cancelled = false;
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const schedule = () => {
|
||||
if (!cancelled) timer = setTimeout(tick, POLL_INTERVAL_MS);
|
||||
};
|
||||
|
||||
const tick = async () => {
|
||||
// Nobody is looking — don't burn VCS commands on a hidden window.
|
||||
if (document.hidden) {
|
||||
schedule();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch('/api/diff/fresh');
|
||||
if (!cancelled && res.ok) {
|
||||
const data = (await res.json()) as { fresh: boolean; fingerprint?: string };
|
||||
// Keep polling even while stale: a reverted edit flips back to
|
||||
// fresh, and a FURTHER change updates the fingerprint so a
|
||||
// dismissed notice can reappear.
|
||||
setStaleFingerprint(data.fresh ? null : data.fingerprint ?? 'stale');
|
||||
}
|
||||
} catch {
|
||||
// Transient/network/server-gone: ignore — staleness is best-effort.
|
||||
}
|
||||
schedule();
|
||||
};
|
||||
|
||||
schedule();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, [enabled, resetKey]);
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
setDismissedFingerprint(staleFingerprint);
|
||||
}, [staleFingerprint]);
|
||||
|
||||
return {
|
||||
isStale: staleFingerprint != null && staleFingerprint !== dismissedFingerprint,
|
||||
dismiss,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user