fix(app): stabilize canvas diff rendering

Keep diff headers and painted content on the same scroll clock while preserving native scrolling and independent per-file horizontal offsets.

Complete review, selection, typography, tree, and palette parity without returning line rendering to React.
This commit is contained in:
Mohamed Boudra
2026-08-17 21:14:38 +02:00
parent 4d23905587
commit c7b103a2cd
50 changed files with 2180 additions and 580 deletions
@@ -11,6 +11,7 @@ interface FileHeaderInteractionInput {
onSelect?: (path: string) => void;
onActivate?: (path: string) => void;
onLayout?: (height: number) => void;
stickyPressFallback?: boolean;
}
interface PressPointEvent {
@@ -27,14 +28,14 @@ interface StickyPressReleaseInput {
enabled: boolean;
native: boolean;
pressHandled: boolean;
layoutY: number | null;
stickyPressFallback: boolean;
pressOrigin: PressOrigin | null;
releasedAt: number;
pageX: number;
pageY: number;
}
interface FileHeaderInteraction {
export interface FileHeaderInteraction {
activate: () => void;
select: () => void;
onLayout: (event: LayoutChangeEvent) => void;
@@ -59,9 +60,12 @@ export function reduceFileHeaderPress(
if (event.type === "press-in") return { phase: "pressed", effect: "none" };
if (event.type === "long-press") return { phase: "long", effect: "select" };
if (event.type === "press-out") {
if (phase === "released" || phase === "handled" || phase === "long") {
return { phase, effect: "none" };
}
return event.stickyFallbackEligible && phase === "pressed"
? { phase: "released", effect: "defer-activate" }
: { phase: phase === "long" ? "long" : "idle", effect: "none" };
: { phase: "idle", effect: "none" };
}
if (event.type === "press") {
return phase === "long" || phase === "handled"
@@ -78,7 +82,7 @@ export function shouldActivateStickyHeaderPress(input: StickyPressReleaseInput):
!input.enabled ||
!input.native ||
input.pressHandled ||
input.layoutY !== 0 ||
!input.stickyPressFallback ||
!input.pressOrigin
) {
return false;
@@ -92,8 +96,14 @@ export function shouldActivateStickyHeaderPress(input: StickyPressReleaseInput):
}
export function useFileHeaderInteraction(input: FileHeaderInteractionInput): FileHeaderInteraction {
const { path, enabled, onSelect, onActivate, onLayout: notifyLayout } = input;
const layoutYRef = useRef<number | null>(null);
const {
path,
enabled,
onSelect,
onActivate,
onLayout: notifyLayout,
stickyPressFallback = false,
} = input;
const pressHandledRef = useRef(false);
const pressOriginRef = useRef<PressOrigin | null>(null);
const phaseRef = useRef<FileHeaderPressPhase>("idle");
@@ -123,7 +133,6 @@ export function useFileHeaderInteraction(input: FileHeaderInteractionInput): Fil
const onLayout = useCallback(
(event: LayoutChangeEvent) => {
layoutYRef.current = event.nativeEvent.layout.y;
notifyLayout?.(event.nativeEvent.layout.height);
},
[notifyLayout],
@@ -157,7 +166,7 @@ export function useFileHeaderInteraction(input: FileHeaderInteractionInput): Fil
enabled,
native: isNative,
pressHandled: pressHandledRef.current,
layoutY: layoutYRef.current,
stickyPressFallback,
pressOrigin: pressOriginRef.current,
releasedAt: Date.now(),
pageX: event.nativeEvent.pageX,
@@ -182,7 +191,7 @@ export function useFileHeaderInteraction(input: FileHeaderInteractionInput): Fil
}, 0);
}
},
[clearFallback, enabled, onActivate, path, select],
[clearFallback, enabled, onActivate, path, select, stickyPressFallback],
);
return { activate, select, onLayout, onPressIn, onPressOut, onLongPress };