linting and frontend validation and change requests to remove any types and clean up functinos

This commit is contained in:
EthanHealy01 2025-11-06 17:31:05 +00:00
parent fbb077d2fd
commit a99dd444c7
11 changed files with 86 additions and 115 deletions

View File

@ -165,7 +165,7 @@ const CompareDocumentPane = ({
offsetPixels: OFFSET_PIXELS,
});
const { highlightOffset, baseWidth, baseHeight, containerWidth, containerHeight, innerScale } = metrics;
const { highlightOffset, containerWidth, containerHeight, innerScale } = metrics;
// Compute clamped pan for current zoom so content always touches edges when in bounds
const storedPan = pagePanRef.current.get(page.pageNumber) || { x: 0, y: 0 };

View File

@ -45,8 +45,8 @@ const CompareWorkbenchView = ({ data }: CompareWorkbenchViewProps) => {
const baseFile = getFileFromSelection(data?.baseLocalFile, baseFileId, selectors);
const comparisonFile = getFileFromSelection(data?.comparisonLocalFile, comparisonFileId, selectors);
const baseStub = getStubFromSelection(baseFileId, selectors) as any;
const comparisonStub = getStubFromSelection(comparisonFileId, selectors) as any;
const baseStub = getStubFromSelection(baseFileId, selectors);
const comparisonStub = getStubFromSelection(comparisonFileId, selectors);
const processedAt = result?.totals.processedAt ?? null;

View File

@ -1,6 +1,6 @@
import type { TokenBoundingBox, WordHighlightEntry } from '@app/types/compare';
import type { FileId } from '@app/types/file';
import type { StirlingFile } from '@app/types/fileContext';
import type { StirlingFile, StirlingFileStub } from '@app/types/fileContext';
import type { PagePreview } from '@app/types/compare';
/** Convert hex color (#rrggbb) to rgba() string with alpha; falls back to input if invalid. */
@ -175,8 +175,8 @@ export const getFileFromSelection = (
export const getStubFromSelection = (
fileId: FileId | null,
selectors: { getStirlingFileStub: (id: FileId) => unknown }
): unknown | null => {
selectors: { getStirlingFileStub: (id: FileId) => StirlingFileStub | undefined }
): StirlingFileStub | null => {
if (!fileId) return null;
const stub = selectors.getStirlingFileStub(fileId);
return stub ?? null;

View File

@ -160,7 +160,7 @@ export const useComparePagePreviews = ({
};
}
const key = `${(file as any).name || 'file'}:${(file as any).size || 0}:${cacheKey ?? 'none'}`;
const key = `${file.name || 'file'}:${file.size || 0}:${cacheKey ?? 'none'}`;
const refreshVersion = Symbol(key);
latestVersionMap.set(key, refreshVersion);
const entry = getOrCreateEntry(key);

View File

@ -440,6 +440,63 @@ export const useComparePanZoom = ({
return () => { cleanupBase(); cleanupComp(); };
}, []);
// Helpers for clearer pan-edge overscroll behavior
const getVerticalOverflow = useCallback((rawY: number, maxY: number): number => {
if (rawY < 0) return rawY; // negative -> scroll up
if (rawY > maxY) return rawY - maxY; // positive -> scroll down
return 0;
}, []);
const normalizeApplyCandidate = useCallback((overflowY: number): number => {
const DEADZONE = 32; // pixels
if (overflowY < -DEADZONE) return overflowY + DEADZONE;
if (overflowY > DEADZONE) return overflowY - DEADZONE;
return 0;
}, []);
const applyIncrementalScroll = useCallback((container: HTMLDivElement, isBase: boolean, applyCandidate: number) => {
const STEP = 48; // pixels per incremental scroll
const key = isBase ? 'base' : 'comparison';
const deltaSinceLast = applyCandidate - edgeOverscrollRef.current[key];
const magnitude = Math.abs(deltaSinceLast);
if (magnitude < STEP) return;
const stepDelta = Math.sign(deltaSinceLast) * Math.floor(magnitude / STEP) * STEP;
edgeOverscrollRef.current[key] += stepDelta;
const prevTop = container.scrollTop;
const nextTop = Math.max(0, Math.min(container.scrollHeight - container.clientHeight, prevTop + stepDelta));
if (nextTop === prevTop) return;
container.scrollTop = nextTop;
if (isScrollLinked) {
const sourceIsBase = isBase;
const target = isBase ? comparisonScrollRef.current : baseScrollRef.current;
if (target) {
const targetVerticalRange = Math.max(1, target.scrollHeight - target.clientHeight);
const mappedTop = mapScrollTopBetweenPanes(nextTop, sourceIsBase);
const deltaPx = sourceIsBase
? scrollLinkAnchorsRef.current.deltaPixelsBaseToComp
: scrollLinkAnchorsRef.current.deltaPixelsCompToBase;
const desiredTop = Math.max(0, Math.min(targetVerticalRange, mappedTop + deltaPx));
target.scrollTop = desiredTop;
}
}
}, [isScrollLinked, mapScrollTopBetweenPanes]);
const handlePanEdgeOverscroll = useCallback((rawY: number, boundsMaxY: number, isBase: boolean) => {
const container = isBase ? baseScrollRef.current : comparisonScrollRef.current;
if (!container) return;
const overflowY = getVerticalOverflow(rawY, boundsMaxY);
const applyCandidate = normalizeApplyCandidate(overflowY);
if (applyCandidate !== 0) {
applyIncrementalScroll(container, isBase, applyCandidate);
} else {
// Reset accumulator when back within deadzone
edgeOverscrollRef.current[isBase ? 'base' : 'comparison'] = 0;
}
}, [applyIncrementalScroll, getVerticalOverflow, normalizeApplyCandidate]);
const beginPan = useCallback(
(pane: Pane, event: ReactMouseEvent<HTMLDivElement>) => {
if (!isPanMode) return;
@ -489,47 +546,7 @@ export const useComparePanZoom = ({
};
// On vertical overscroll beyond pan bounds, scroll the page (with deadzone + incremental steps)
const container = isBase ? baseScrollRef.current : comparisonScrollRef.current;
if (container) {
const DEADZONE = 32; // pixels
const STEP = 48; // pixels per incremental scroll
let overflowY = 0;
if (rawY < 0) overflowY = rawY; // negative -> scroll up
else if (rawY > bounds.maxY) overflowY = rawY - bounds.maxY; // positive -> scroll down
let applyCandidate = 0;
if (overflowY < -DEADZONE) applyCandidate = overflowY + DEADZONE;
else if (overflowY > DEADZONE) applyCandidate = overflowY - DEADZONE;
if (applyCandidate !== 0) {
const key = isBase ? 'base' : 'comparison';
const deltaSinceLast = applyCandidate - edgeOverscrollRef.current[key];
const magnitude = Math.abs(deltaSinceLast);
if (magnitude >= STEP) {
const stepDelta = Math.sign(deltaSinceLast) * Math.floor(magnitude / STEP) * STEP;
edgeOverscrollRef.current[key] += stepDelta;
const prevTop = container.scrollTop;
const nextTop = Math.max(0, Math.min(container.scrollHeight - container.clientHeight, prevTop + stepDelta));
if (nextTop !== prevTop) {
container.scrollTop = nextTop;
if (isScrollLinked) {
const sourceIsBase = isBase;
const target = isBase ? comparisonScrollRef.current : baseScrollRef.current;
if (target) {
const targetVerticalRange = Math.max(1, target.scrollHeight - target.clientHeight);
const mappedTop = mapScrollTopBetweenPanes(nextTop, sourceIsBase);
const deltaPx = sourceIsBase
? scrollLinkAnchorsRef.current.deltaPixelsBaseToComp
: scrollLinkAnchorsRef.current.deltaPixelsCompToBase;
const desiredTop = Math.max(0, Math.min(targetVerticalRange, mappedTop + deltaPx));
target.scrollTop = desiredTop;
}
}
}
}
} else {
// Reset accumulator when back within deadzone
edgeOverscrollRef.current[isBase ? 'base' : 'comparison'] = 0;
}
}
handlePanEdgeOverscroll(rawY, bounds.maxY, isBase);
if (isBase) {
setBasePan(desired);
@ -729,45 +746,7 @@ export const useComparePanZoom = ({
y: Math.max(0, Math.min(bounds.maxY, rawY)),
};
const container = isBase ? baseScrollRef.current : comparisonScrollRef.current;
if (container) {
const DEADZONE = 32;
const STEP = 48;
let overflowY = 0;
if (rawY < 0) overflowY = rawY; else if (rawY > bounds.maxY) overflowY = rawY - bounds.maxY;
let applyCandidate = 0;
if (overflowY < -DEADZONE) applyCandidate = overflowY + DEADZONE;
else if (overflowY > DEADZONE) applyCandidate = overflowY - DEADZONE;
if (applyCandidate !== 0) {
const key = isBase ? 'base' : 'comparison';
const deltaSinceLast = applyCandidate - edgeOverscrollRef.current[key];
const magnitude = Math.abs(deltaSinceLast);
if (magnitude >= STEP) {
const stepDelta = Math.sign(deltaSinceLast) * Math.floor(magnitude / STEP) * STEP;
edgeOverscrollRef.current[key] += stepDelta;
const prevTop = container.scrollTop;
const nextTop = Math.max(0, Math.min(container.scrollHeight - container.clientHeight, prevTop + stepDelta));
if (nextTop !== prevTop) {
container.scrollTop = nextTop;
if (isScrollLinked) {
const sourceIsBase = isBase;
const target = isBase ? comparisonScrollRef.current : baseScrollRef.current;
if (target) {
const targetVerticalRange = Math.max(1, target.scrollHeight - target.clientHeight);
const mappedTop = mapScrollTopBetweenPanes(nextTop, sourceIsBase);
const deltaPx = sourceIsBase
? scrollLinkAnchorsRef.current.deltaPixelsBaseToComp
: scrollLinkAnchorsRef.current.deltaPixelsCompToBase;
const desiredTop = Math.max(0, Math.min(targetVerticalRange, mappedTop + deltaPx));
target.scrollTop = desiredTop;
}
}
}
}
} else {
edgeOverscrollRef.current[isBase ? 'base' : 'comparison'] = 0;
}
}
handlePanEdgeOverscroll(rawY, bounds.maxY, isBase);
if (isBase) {
setBasePan(desired);
} else {

View File

@ -36,7 +36,7 @@ export interface CompareOperationHook extends ToolOperationHook<CompareParameter
export const useCompareOperation = (): CompareOperationHook => {
const { t } = useTranslation();
const { selectors, actions: fileActions } = useFileContext();
const { selectors } = useFileContext();
const workerRef = useRef<Worker | null>(null);
const previousUrl = useRef<string | null>(null);
const activeRunIdRef = useRef(0);
@ -59,7 +59,7 @@ export const useCompareOperation = (): CompareOperationHook => {
const ensureWorker = useCallback(() => {
if (!workerRef.current) {
workerRef.current = new Worker(
new URL('../../../../workers/compareWorker.ts', import.meta.url),
new URL('/@app/workers/compareWorker.ts', import.meta.url),
{ type: 'module' }
);
}
@ -297,8 +297,12 @@ export const useCompareOperation = (): CompareOperationHook => {
expandable: false,
buttonText: t('compare.earlyDissimilarity.stopButton', 'Stop comparison'),
buttonCallback: () => {
try { cancelOperation(); } catch {}
try { window.dispatchEvent(new CustomEvent('compare:clear-selected')); } catch {}
try { cancelOperation(); } catch {
console.error('Failed to cancel operation');
}
try { window.dispatchEvent(new CustomEvent('compare:clear-selected')); } catch {
console.error('Failed to dispatch clear selected event');
}
if (dissimilarityToastIdRef.current) {
dismissToast(dissimilarityToastIdRef.current);
dissimilarityToastIdRef.current = null;

View File

@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
import type { PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
import { PagePreview } from '@app/types/compare';
const DISPLAY_SCALE = 1;
@ -36,11 +37,11 @@ export const useProgressivePagePreviews = ({
loadingPages: new Set(),
});
const pdfRef = useRef<any>(null);
const pdfRef = useRef<PDFDocumentProxy | null>(null);
const abortControllerRef = useRef<AbortController | null>(null);
const renderPageBatch = useCallback(async (
pdf: any,
pdf: PDFDocumentProxy,
pageNumbers: number[],
signal: AbortSignal
): Promise<PagePreview[]> => {
@ -114,7 +115,9 @@ export const useProgressivePagePreviews = ({
}));
try {
const previews = await renderPageBatch(pdfRef.current, pagesToLoad, signal);
const pdfDoc = pdfRef.current;
if (!pdfDoc) return;
const previews = await renderPageBatch(pdfDoc, pagesToLoad, signal);
if (!signal.aborted) {
setState(prev => {

View File

@ -80,7 +80,7 @@ const Compare = (props: BaseToolProps) => {
// Use a static label at registration time to avoid re-registering on i18n changes
label: 'Compare view',
icon: compareIcon,
component: CompareWorkbenchView as any,
component: CompareWorkbenchView,
});
return () => {

View File

@ -47,7 +47,7 @@ const ValidateSignature = (props: BaseToolProps) => {
workbenchId: REPORT_WORKBENCH_ID,
label: t('validateSignature.report.shortTitle', 'Signature Report'),
icon: reportIcon,
component: ValidateSignatureReportView as any,
component: ValidateSignatureReportView,
});
return () => {

View File

@ -1,7 +1,5 @@
// Shared text diff and normalization utilities for compare tool
export const PARAGRAPH_SENTINEL = '\uE000¶';
export const shouldConcatWithoutSpace = (word: string) => {
return /^[.,!?;:)\]}]/.test(word) || word.startsWith("'") || word === "'s";
};
@ -11,17 +9,6 @@ export const appendWord = (existing: string, word: string) => {
if (shouldConcatWithoutSpace(word)) return `${existing}${word}`;
return `${existing} ${word}`;
};
export const normalizeToken = (s: string) =>
s
.normalize('NFKC')
.replace(/[\u00AD\u200B-\u200F\u202A-\u202E]/g, '') // soft hyphen + zero width controls
.replace(/[“”]/g, '"')
.replace(/[]/g, "'")
.replace(/[–—]/g, '-')
.replace(/\s+/g, ' ')
.trim();
export const tokenize = (text: string): string[] => text.split(/\s+/).filter(Boolean);
type TokenType = 'unchanged' | 'removed' | 'added';

View File

@ -264,8 +264,7 @@ const chunkedDiff = (
if (unchangedRatio < runtimeStop.minUnchangedRatio) {
// Signal early termination for extreme dissimilarity
const err = new Error('EARLY_STOP_TOO_DISSIMILAR');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(err as any).__earlyStop = true;
(err as Error & { __earlyStop?: boolean }).__earlyStop = true;
throw err;
}
}
@ -423,10 +422,9 @@ self.onmessage = (event: MessageEvent<CompareWorkerRequest>) => {
},
{ maxProcessedTokens: runtimeMaxProcessedTokens, minUnchangedRatio: runtimeMinUnchangedRatio }
);
} catch (err) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const anyErr = err as any;
if (anyErr && (anyErr.__earlyStop || anyErr?.message === 'EARLY_STOP_TOO_DISSIMILAR')) {
} catch (err) {
const error = err as Error & { __earlyStop?: boolean };
if (error && (error.__earlyStop || error.message === 'EARLY_STOP_TOO_DISSIMILAR')) {
const response: CompareWorkerResponse = {
type: 'error',
message: