refactor: simplify file reference interactions by removing unused validation logic

This commit is contained in:
Bohdan Triapitsyn
2026-04-01 12:25:58 +03:00
parent c38e00bd85
commit 81c41388ac
@@ -1022,23 +1022,15 @@ const getContextDirectory = (effectiveDirectory: string, resolvedPath: string):
const useFileReferenceInteractions = ({ const useFileReferenceInteractions = ({
containerRef, containerRef,
effectiveDirectory, effectiveDirectory,
statFile,
editor, editor,
preferRuntimeEditor, preferRuntimeEditor,
deferValidationUntilIdle = false,
}: { }: {
containerRef: React.RefObject<HTMLDivElement | null>; containerRef: React.RefObject<HTMLDivElement | null>;
effectiveDirectory: string; effectiveDirectory: string;
statFile?: (path: string) => Promise<{ path: string; isFile: boolean; size: number }>;
editor?: EditorAPI; editor?: EditorAPI;
preferRuntimeEditor?: boolean; preferRuntimeEditor?: boolean;
deferValidationUntilIdle?: boolean;
}) => { }) => {
const validationCacheRef = React.useRef<Map<string, boolean>>(new Map());
const inFlightValidationsRef = React.useRef<Map<string, Promise<boolean>>>(new Map());
const annotationPassRef = React.useRef(0);
const annotationDebounceRef = React.useRef<number | null>(null); const annotationDebounceRef = React.useRef<number | null>(null);
const isValidationSweepRunningRef = React.useRef(false);
React.useEffect(() => { React.useEffect(() => {
const container = containerRef.current; const container = containerRef.current;
@@ -1046,158 +1038,47 @@ const useFileReferenceInteractions = ({
return; return;
} }
let disposed = false; const annotateFileLinks = () => {
const isPathResolvable = async (resolvedPath: string): Promise<boolean> => {
const cache = validationCacheRef.current;
if (cache.has(resolvedPath)) {
return cache.get(resolvedPath) === true;
}
const inFlight = inFlightValidationsRef.current.get(resolvedPath);
if (inFlight) {
return inFlight;
}
const checkPromise = (async () => {
try {
if (!statFile) {
return false;
}
const stat = await statFile(resolvedPath);
if (!stat.isFile) {
cache.set(resolvedPath, false);
return false;
}
cache.set(resolvedPath, true);
return true;
} catch {
cache.set(resolvedPath, false);
return false;
} finally {
inFlightValidationsRef.current.delete(resolvedPath);
}
})();
inFlightValidationsRef.current.set(resolvedPath, checkPromise);
return checkPromise;
};
const clearCandidateLinkAttrs = (candidate: HTMLElement) => {
candidate.removeAttribute('data-openchamber-file-link');
candidate.removeAttribute('data-openchamber-file-ref');
candidate.removeAttribute('data-openchamber-file-path');
if (candidate.getAttribute('title') === 'Open file') {
candidate.removeAttribute('title');
}
if (candidate.tagName.toLowerCase() !== 'a') {
candidate.removeAttribute('role');
candidate.removeAttribute('tabindex');
}
};
const applyCandidateLinkAttrs = (candidate: HTMLElement, rawCandidate: string, resolvedPath: string) => {
candidate.setAttribute('data-openchamber-file-link', 'true');
candidate.setAttribute('data-openchamber-file-ref', rawCandidate);
candidate.setAttribute('data-openchamber-file-path', resolvedPath);
candidate.setAttribute('title', 'Open file');
if (candidate.tagName.toLowerCase() !== 'a') {
candidate.setAttribute('role', 'button');
candidate.setAttribute('tabindex', '0');
}
};
const runValidationSweep = async (paths: string[], expectedPassID: number) => {
if (deferValidationUntilIdle) {
return;
}
if (isValidationSweepRunningRef.current || paths.length === 0) {
return;
}
isValidationSweepRunningRef.current = true;
const maxConcurrent = 3;
let cursor = 0;
const worker = async () => {
while (!disposed && cursor < paths.length) {
const index = cursor;
cursor += 1;
const pathToCheck = paths[index];
if (!pathToCheck) {
continue;
}
await isPathResolvable(pathToCheck);
}
};
try {
await Promise.all(Array.from({ length: Math.min(maxConcurrent, paths.length) }, () => worker()));
} finally {
isValidationSweepRunningRef.current = false;
}
if (!disposed && annotationPassRef.current === expectedPassID) {
void annotateFileLinks();
}
};
const annotateFileLinks = async () => {
const passID = annotationPassRef.current + 1;
annotationPassRef.current = passID;
const candidates = container.querySelectorAll<HTMLElement>('[data-streamdown="inline-code"], a'); const candidates = container.querySelectorAll<HTMLElement>('[data-streamdown="inline-code"], a');
const unresolvedPaths = new Set<string>();
for (const candidate of Array.from(candidates)) { for (const candidate of Array.from(candidates)) {
const rawCandidate = extractPathCandidateFromElement(candidate); const rawCandidate = extractPathCandidateFromElement(candidate);
const resolved = getResolvedReference(rawCandidate, effectiveDirectory); const resolved = getResolvedReference(rawCandidate, effectiveDirectory);
if (!resolved) { if (!resolved) {
clearCandidateLinkAttrs(candidate); candidate.removeAttribute('data-openchamber-file-link');
candidate.removeAttribute('data-openchamber-file-ref');
candidate.removeAttribute('data-openchamber-file-path');
if (candidate.getAttribute('title') === 'Open file') {
candidate.removeAttribute('title');
}
if (candidate.tagName.toLowerCase() !== 'a') {
candidate.removeAttribute('role');
candidate.removeAttribute('tabindex');
}
continue; continue;
} }
if (annotationPassRef.current !== passID) { candidate.setAttribute('data-openchamber-file-link', 'true');
return; candidate.setAttribute('data-openchamber-file-ref', rawCandidate);
candidate.setAttribute('data-openchamber-file-path', resolved.resolvedPath);
candidate.setAttribute('title', 'Open file');
if (candidate.tagName.toLowerCase() !== 'a') {
candidate.setAttribute('role', 'button');
candidate.setAttribute('tabindex', '0');
} }
const cachedResult = validationCacheRef.current.get(resolved.resolvedPath);
if (cachedResult === true) {
applyCandidateLinkAttrs(candidate, rawCandidate, resolved.resolvedPath);
continue;
}
clearCandidateLinkAttrs(candidate);
if (cachedResult !== false) {
unresolvedPaths.add(resolved.resolvedPath);
}
}
if (unresolvedPaths.size > 0) {
void runValidationSweep(Array.from(unresolvedPaths), passID);
} }
}; };
const openFileReference = async (sourceElement: HTMLElement): Promise<boolean> => { const openFileReference = (sourceElement: HTMLElement) => {
const raw = sourceElement.getAttribute('data-openchamber-file-ref') || extractPathCandidateFromElement(sourceElement); const raw = sourceElement.getAttribute('data-openchamber-file-ref') || extractPathCandidateFromElement(sourceElement);
const resolved = getResolvedReference(raw, effectiveDirectory); const resolved = getResolvedReference(raw, effectiveDirectory);
if (!resolved) { if (!resolved) {
return false; return;
}
const isResolvable = await isPathResolvable(resolved.resolvedPath);
if (!isResolvable) {
sourceElement.removeAttribute('data-openchamber-file-link');
sourceElement.removeAttribute('data-openchamber-file-ref');
sourceElement.removeAttribute('data-openchamber-file-path');
if (sourceElement.getAttribute('title') === 'Open file') {
sourceElement.removeAttribute('title');
}
return false;
} }
const contextDirectory = getContextDirectory(effectiveDirectory, resolved.resolvedPath); const contextDirectory = getContextDirectory(effectiveDirectory, resolved.resolvedPath);
if (preferRuntimeEditor && editor) { if (preferRuntimeEditor && editor) {
await editor.openFile( void editor.openFile(
resolved.resolvedPath, resolved.resolvedPath,
Number.isFinite(resolved.line ?? Number.NaN) Number.isFinite(resolved.line ?? Number.NaN)
? Math.max(1, Math.trunc(resolved.line as number)) ? Math.max(1, Math.trunc(resolved.line as number))
@@ -1206,7 +1087,7 @@ const useFileReferenceInteractions = ({
? Math.max(1, Math.trunc(resolved.column as number)) ? Math.max(1, Math.trunc(resolved.column as number))
: undefined, : undefined,
); );
return true; return;
} }
const uiStore = useUIStore.getState(); const uiStore = useUIStore.getState();
@@ -1222,7 +1103,6 @@ const useFileReferenceInteractions = ({
} else { } else {
uiStore.openContextFile(contextDirectory, resolved.resolvedPath); uiStore.openContextFile(contextDirectory, resolved.resolvedPath);
} }
return true;
}; };
const handleClick = (event: MouseEvent) => { const handleClick = (event: MouseEvent) => {
@@ -1239,7 +1119,7 @@ const useFileReferenceInteractions = ({
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
void openFileReference(fileRefElement); openFileReference(fileRefElement);
}; };
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
@@ -1255,36 +1135,34 @@ const useFileReferenceInteractions = ({
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
void openFileReference(target); openFileReference(target);
}; };
void annotateFileLinks(); annotateFileLinks();
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
if (annotationDebounceRef.current !== null && typeof window !== 'undefined') { if (annotationDebounceRef.current !== null && typeof window !== 'undefined') {
window.clearTimeout(annotationDebounceRef.current); window.clearTimeout(annotationDebounceRef.current);
} }
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
void annotateFileLinks(); annotateFileLinks();
return; return;
} }
annotationDebounceRef.current = window.setTimeout(() => { annotationDebounceRef.current = window.setTimeout(() => {
annotationDebounceRef.current = null; annotationDebounceRef.current = null;
void annotateFileLinks(); annotateFileLinks();
}, deferValidationUntilIdle ? 280 : 120); }, 120);
}); });
observer.observe(container, { observer.observe(container, {
childList: true, childList: true,
subtree: true, subtree: true,
...(deferValidationUntilIdle ? {} : { characterData: true }), characterData: true,
}); });
container.addEventListener('click', handleClick); container.addEventListener('click', handleClick);
container.addEventListener('keydown', handleKeyDown); container.addEventListener('keydown', handleKeyDown);
return () => { return () => {
disposed = true;
annotationPassRef.current += 1;
if (annotationDebounceRef.current !== null && typeof window !== 'undefined') { if (annotationDebounceRef.current !== null && typeof window !== 'undefined') {
window.clearTimeout(annotationDebounceRef.current); window.clearTimeout(annotationDebounceRef.current);
} }
@@ -1293,7 +1171,7 @@ const useFileReferenceInteractions = ({
container.removeEventListener('click', handleClick); container.removeEventListener('click', handleClick);
container.removeEventListener('keydown', handleKeyDown); container.removeEventListener('keydown', handleKeyDown);
}; };
}, [containerRef, deferValidationUntilIdle, editor, effectiveDirectory, preferRuntimeEditor, statFile]); }, [containerRef, editor, effectiveDirectory, preferRuntimeEditor]);
}; };
const useMermaidInlineInteractions = ({ const useMermaidInlineInteractions = ({
@@ -1409,10 +1287,8 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
useFileReferenceInteractions({ useFileReferenceInteractions({
containerRef: streamdownContainerRef, containerRef: streamdownContainerRef,
effectiveDirectory, effectiveDirectory,
statFile: files.statFile,
editor, editor,
preferRuntimeEditor: runtime.isVSCode, preferRuntimeEditor: runtime.isVSCode,
deferValidationUntilIdle: isStreaming,
}); });
useExternalLinkInteractions({ containerRef: streamdownContainerRef }); useExternalLinkInteractions({ containerRef: streamdownContainerRef });
@@ -1495,7 +1371,6 @@ export const SimpleMarkdownRenderer: React.FC<{
useFileReferenceInteractions({ useFileReferenceInteractions({
containerRef: streamdownContainerRef, containerRef: streamdownContainerRef,
effectiveDirectory, effectiveDirectory,
statFile: files.statFile,
editor, editor,
preferRuntimeEditor: runtime.isVSCode, preferRuntimeEditor: runtime.isVSCode,
}); });