Feature/v2/filewithid implementation (#4369)

Added Filewithid type
Updated code where file was being used to use filewithid
Updated places we identified files by name or composite keys to use UUID
Updated places we should have been using quickkey
Updated pageeditor issue where we parsed pagenumber from pageid instead
of using pagenumber directly

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
Reece Browne
2025-09-05 11:33:03 +01:00
committed by GitHub
parent 5caec41d96
commit 87c63efcec
37 changed files with 493 additions and 339 deletions

View File

@@ -19,7 +19,10 @@ import {
FileContextStateValue,
FileContextActionsValue,
FileContextActions,
FileRecord
FileId,
StirlingFileStub,
StirlingFile,
createStirlingFile
} from '../types/fileContext';
// Import modular components
@@ -29,7 +32,6 @@ import { AddedFile, addFiles, consumeFiles, undoConsumeFiles, createFileActions
import { FileLifecycleManager } from './file/lifecycle';
import { FileStateContext, FileActionsContext } from './file/contexts';
import { IndexedDBProvider, useIndexedDB } from './IndexedDBContext';
import { FileId } from '../types/file';
const DEBUG = process.env.NODE_ENV === 'development';
@@ -79,7 +81,7 @@ function FileContextInner({
}
// File operations using unified addFiles helper with persistence
const addRawFiles = useCallback(async (files: File[], options?: { insertAfterPageId?: string; selectFiles?: boolean }): Promise<File[]> => {
const addRawFiles = useCallback(async (files: File[], options?: { insertAfterPageId?: string; selectFiles?: boolean }): Promise<StirlingFile[]> => {
const addedFilesWithIds = await addFiles('raw', { files, ...options }, stateRef, filesRef, dispatch, lifecycleManager);
// Auto-select the newly added files if requested
@@ -98,15 +100,15 @@ function FileContextInner({
}));
}
return addedFilesWithIds.map(({ file }) => file);
return addedFilesWithIds.map(({ file, id }) => createStirlingFile(file, id));
}, [indexedDB, enablePersistence]);
const addProcessedFiles = useCallback(async (filesWithThumbnails: Array<{ file: File; thumbnail?: string; pageCount?: number }>): Promise<File[]> => {
const addProcessedFiles = useCallback(async (filesWithThumbnails: Array<{ file: File; thumbnail?: string; pageCount?: number }>): Promise<StirlingFile[]> => {
const result = await addFiles('processed', { filesWithThumbnails }, stateRef, filesRef, dispatch, lifecycleManager);
return result.map(({ file }) => file);
return result.map(({ file, id }) => createStirlingFile(file, id));
}, []);
const addStoredFiles = useCallback(async (filesWithMetadata: Array<{ file: File; originalId: FileId; metadata: any }>, options?: { selectFiles?: boolean }): Promise<File[]> => {
const addStoredFiles = useCallback(async (filesWithMetadata: Array<{ file: File; originalId: FileId; metadata: any }>, options?: { selectFiles?: boolean }): Promise<StirlingFile[]> => {
const result = await addFiles('stored', { filesWithMetadata }, stateRef, filesRef, dispatch, lifecycleManager);
// Auto-select the newly added files if requested
@@ -114,7 +116,7 @@ function FileContextInner({
selectFiles(result);
}
return result.map(({ file }) => file);
return result.map(({ file, id }) => createStirlingFile(file, id));
}, []);
// Action creators
@@ -122,11 +124,11 @@ function FileContextInner({
// Helper functions for pinned files
const consumeFilesWrapper = useCallback(async (inputFileIds: FileId[], outputFiles: File[]): Promise<FileId[]> => {
return consumeFiles(inputFileIds, outputFiles, stateRef, filesRef, dispatch, indexedDB);
return consumeFiles(inputFileIds, outputFiles, filesRef, dispatch, indexedDB);
}, [indexedDB]);
const undoConsumeFilesWrapper = useCallback(async (inputFiles: File[], inputFileRecords: FileRecord[], outputFileIds: FileId[]): Promise<void> => {
return undoConsumeFiles(inputFiles, inputFileRecords, outputFileIds, stateRef, filesRef, dispatch, indexedDB);
const undoConsumeFilesWrapper = useCallback(async (inputFiles: File[], inputStirlingFileStubs: StirlingFileStub[], outputFileIds: FileId[]): Promise<void> => {
return undoConsumeFiles(inputFiles, inputStirlingFileStubs, outputFileIds, stateRef, filesRef, dispatch, indexedDB);
}, [indexedDB]);
// Helper to find FileId from File object
@@ -140,24 +142,14 @@ function FileContextInner({
});
}, []);
// File-to-ID wrapper functions for pinning
const pinFileWrapper = useCallback((file: File) => {
const fileId = findFileId(file);
if (fileId) {
baseActions.pinFile(fileId);
} else {
console.warn('File not found for pinning:', file.name);
}
}, [baseActions, findFileId]);
// File pinning functions - use StirlingFile directly
const pinFileWrapper = useCallback((file: StirlingFile) => {
baseActions.pinFile(file.fileId);
}, [baseActions]);
const unpinFileWrapper = useCallback((file: File) => {
const fileId = findFileId(file);
if (fileId) {
baseActions.unpinFile(fileId);
} else {
console.warn('File not found for unpinning:', file.name);
}
}, [baseActions, findFileId]);
const unpinFileWrapper = useCallback((file: StirlingFile) => {
baseActions.unpinFile(file.fileId);
}, [baseActions]);
// Complete actions object
const actions = useMemo<FileContextActions>(() => ({
@@ -178,8 +170,8 @@ function FileContextInner({
}
}
},
updateFileRecord: (fileId: FileId, updates: Partial<FileRecord>) =>
lifecycleManager.updateFileRecord(fileId, updates, stateRef),
updateStirlingFileStub: (fileId: FileId, updates: Partial<StirlingFileStub>) =>
lifecycleManager.updateStirlingFileStub(fileId, updates, stateRef),
reorderFiles: (orderedFileIds: FileId[]) => {
dispatch({ type: 'REORDER_FILES', payload: { orderedFileIds } });
},
@@ -303,7 +295,7 @@ export {
useFileSelection,
useFileManagement,
useFileUI,
useFileRecord,
useStirlingFileStub,
useAllFiles,
useSelectedFiles,
// Primary API hooks for tools

View File

@@ -6,7 +6,7 @@ import { FileId } from '../../types/file';
import {
FileContextState,
FileContextAction,
FileRecord
StirlingFileStub
} from '../../types/fileContext';
// Initial state
@@ -29,7 +29,7 @@ export const initialFileContextState: FileContextState = {
function processFileSwap(
state: FileContextState,
filesToRemove: FileId[],
filesToAdd: FileRecord[]
filesToAdd: StirlingFileStub[]
): FileContextState {
// Only remove unpinned files
const unpinnedRemoveIds = filesToRemove.filter(id => !state.pinnedFiles.has(id));
@@ -70,11 +70,11 @@ function processFileSwap(
export function fileContextReducer(state: FileContextState, action: FileContextAction): FileContextState {
switch (action.type) {
case 'ADD_FILES': {
const { fileRecords } = action.payload;
const { stirlingFileStubs } = action.payload;
const newIds: FileId[] = [];
const newById: Record<FileId, FileRecord> = { ...state.files.byId };
const newById: Record<FileId, StirlingFileStub> = { ...state.files.byId };
fileRecords.forEach(record => {
stirlingFileStubs.forEach(record => {
// Only add if not already present (dedupe by stable ID)
if (!newById[record.id]) {
newIds.push(record.id);
@@ -233,13 +233,13 @@ export function fileContextReducer(state: FileContextState, action: FileContextA
}
case 'CONSUME_FILES': {
const { inputFileIds, outputFileRecords } = action.payload;
return processFileSwap(state, inputFileIds, outputFileRecords);
const { inputFileIds, outputStirlingFileStubs } = action.payload;
return processFileSwap(state, inputFileIds, outputStirlingFileStubs);
}
case 'UNDO_CONSUME_FILES': {
const { inputFileRecords, outputFileIds } = action.payload;
return processFileSwap(state, outputFileIds, inputFileRecords);
const { inputStirlingFileStubs, outputFileIds } = action.payload;
return processFileSwap(state, outputFileIds, inputStirlingFileStubs);
}
case 'RESET_CONTEXT': {

View File

@@ -3,10 +3,10 @@
*/
import {
FileRecord,
StirlingFileStub,
FileContextAction,
FileContextState,
toFileRecord,
toStirlingFileStub,
createFileId,
createQuickKey
} from '../../types/fileContext';
@@ -109,8 +109,8 @@ export async function addFiles(
await addFilesMutex.lock();
try {
const fileRecords: FileRecord[] = [];
const addedFiles: AddedFile[] = [];
const stirlingFileStubs: StirlingFileStub[] = [];
const addedFiles: AddedFile[] = [];
// Build quickKey lookup from existing files for deduplication
const existingQuickKeys = buildQuickKeySet(stateRef.current.files.byId);
@@ -163,7 +163,7 @@ export async function addFiles(
}
// Create record with immediate thumbnail and page metadata
const record = toFileRecord(file, fileId);
const record = toStirlingFileStub(file, fileId);
if (thumbnail) {
record.thumbnailUrl = thumbnail;
// Track blob URLs for cleanup (images return blob URLs that need revocation)
@@ -184,7 +184,7 @@ export async function addFiles(
}
existingQuickKeys.add(quickKey);
fileRecords.push(record);
stirlingFileStubs.push(record);
addedFiles.push({ file, id: fileId, thumbnail });
}
break;
@@ -205,7 +205,7 @@ export async function addFiles(
const fileId = createFileId();
filesRef.current.set(fileId, file);
const record = toFileRecord(file, fileId);
const record = toStirlingFileStub(file, fileId);
if (thumbnail) {
record.thumbnailUrl = thumbnail;
// Track blob URLs for cleanup (images return blob URLs that need revocation)
@@ -226,7 +226,7 @@ export async function addFiles(
}
existingQuickKeys.add(quickKey);
fileRecords.push(record);
stirlingFileStubs.push(record);
addedFiles.push({ file, id: fileId, thumbnail });
}
break;
@@ -254,7 +254,7 @@ export async function addFiles(
filesRef.current.set(fileId, file);
const record = toFileRecord(file, fileId);
const record = toStirlingFileStub(file, fileId);
// Generate processedFile metadata for stored files
let pageCount: number = 1;
@@ -301,7 +301,7 @@ export async function addFiles(
}
existingQuickKeys.add(quickKey);
fileRecords.push(record);
stirlingFileStubs.push(record);
addedFiles.push({ file, id: fileId, thumbnail: metadata.thumbnail });
}
@@ -310,9 +310,9 @@ export async function addFiles(
}
// Dispatch ADD_FILES action if we have new files
if (fileRecords.length > 0) {
dispatch({ type: 'ADD_FILES', payload: { fileRecords } });
if (DEBUG) console.log(`📄 addFiles(${kind}): Successfully added ${fileRecords.length} files`);
if (stirlingFileStubs.length > 0) {
dispatch({ type: 'ADD_FILES', payload: { stirlingFileStubs } });
if (DEBUG) console.log(`📄 addFiles(${kind}): Successfully added ${stirlingFileStubs.length} files`);
}
return addedFiles;
@@ -328,7 +328,7 @@ export async function addFiles(
async function processFilesIntoRecords(
files: File[],
filesRef: React.MutableRefObject<Map<FileId, File>>
): Promise<Array<{ record: FileRecord; file: File; fileId: FileId; thumbnail?: string }>> {
): Promise<Array<{ record: StirlingFileStub; file: File; fileId: FileId; thumbnail?: string }>> {
return Promise.all(
files.map(async (file) => {
const fileId = createFileId();
@@ -347,7 +347,7 @@ async function processFilesIntoRecords(
if (DEBUG) console.warn(`📄 Failed to generate thumbnail for file ${file.name}:`, error);
}
const record = toFileRecord(file, fileId);
const record = toStirlingFileStub(file, fileId);
if (thumbnail) {
record.thumbnailUrl = thumbnail;
}
@@ -365,10 +365,10 @@ async function processFilesIntoRecords(
* Helper function to persist files to IndexedDB
*/
async function persistFilesToIndexedDB(
fileRecords: Array<{ file: File; fileId: FileId; thumbnail?: string }>,
stirlingFileStubs: Array<{ file: File; fileId: FileId; thumbnail?: string }>,
indexedDB: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any> }
): Promise<void> {
await Promise.all(fileRecords.map(async ({ file, fileId, thumbnail }) => {
await Promise.all(stirlingFileStubs.map(async ({ file, fileId, thumbnail }) => {
try {
await indexedDB.saveFile(file, fileId, thumbnail);
} catch (error) {
@@ -383,7 +383,6 @@ async function persistFilesToIndexedDB(
export async function consumeFiles(
inputFileIds: FileId[],
outputFiles: File[],
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any> } | null
@@ -391,11 +390,11 @@ export async function consumeFiles(
if (DEBUG) console.log(`📄 consumeFiles: Processing ${inputFileIds.length} input files, ${outputFiles.length} output files`);
// Process output files with thumbnails and metadata
const outputFileRecords = await processFilesIntoRecords(outputFiles, filesRef);
const outputStirlingFileStubs = await processFilesIntoRecords(outputFiles, filesRef);
// Persist output files to IndexedDB if available
if (indexedDB) {
await persistFilesToIndexedDB(outputFileRecords, indexedDB);
await persistFilesToIndexedDB(outputStirlingFileStubs, indexedDB);
}
// Dispatch the consume action
@@ -403,21 +402,21 @@ export async function consumeFiles(
type: 'CONSUME_FILES',
payload: {
inputFileIds,
outputFileRecords: outputFileRecords.map(({ record }) => record)
outputStirlingFileStubs: outputStirlingFileStubs.map(({ record }) => record)
}
});
if (DEBUG) console.log(`📄 consumeFiles: Successfully consumed files - removed ${inputFileIds.length} inputs, added ${outputFileRecords.length} outputs`);
if (DEBUG) console.log(`📄 consumeFiles: Successfully consumed files - removed ${inputFileIds.length} inputs, added ${outputStirlingFileStubs.length} outputs`);
// Return the output file IDs for undo tracking
return outputFileRecords.map(({ fileId }) => fileId);
return outputStirlingFileStubs.map(({ fileId }) => fileId);
}
/**
* Helper function to restore files to filesRef and manage IndexedDB cleanup
*/
async function restoreFilesAndCleanup(
filesToRestore: Array<{ file: File; record: FileRecord }>,
filesToRestore: Array<{ file: File; record: StirlingFileStub }>,
fileIdsToRemove: FileId[],
filesRef: React.MutableRefObject<Map<FileId, File>>,
indexedDB?: { deleteFile: (fileId: FileId) => Promise<void> } | null
@@ -440,7 +439,7 @@ async function restoreFilesAndCleanup(
if (DEBUG) console.warn(`📄 Skipping empty file ${file.name}`);
return;
}
// Restore the file to filesRef
if (DEBUG) console.log(`📄 Restoring file ${file.name} with id ${record.id} to filesRef`);
filesRef.current.set(record.id, file);
@@ -455,7 +454,7 @@ async function restoreFilesAndCleanup(
throw error; // Re-throw to trigger rollback
})
);
// Execute all IndexedDB operations
await Promise.all(indexedDBPromises);
}
@@ -466,28 +465,28 @@ async function restoreFilesAndCleanup(
*/
export async function undoConsumeFiles(
inputFiles: File[],
inputFileRecords: FileRecord[],
inputStirlingFileStubs: StirlingFileStub[],
outputFileIds: FileId[],
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>,
dispatch: React.Dispatch<FileContextAction>,
indexedDB?: { saveFile: (file: File, fileId: FileId, existingThumbnail?: string) => Promise<any>; deleteFile: (fileId: FileId) => Promise<void> } | null
): Promise<void> {
if (DEBUG) console.log(`📄 undoConsumeFiles: Restoring ${inputFileRecords.length} input files, removing ${outputFileIds.length} output files`);
if (DEBUG) console.log(`📄 undoConsumeFiles: Restoring ${inputStirlingFileStubs.length} input files, removing ${outputFileIds.length} output files`);
// Validate inputs
if (inputFiles.length !== inputFileRecords.length) {
throw new Error(`Mismatch between input files (${inputFiles.length}) and records (${inputFileRecords.length})`);
if (inputFiles.length !== inputStirlingFileStubs.length) {
throw new Error(`Mismatch between input files (${inputFiles.length}) and records (${inputStirlingFileStubs.length})`);
}
// Create a backup of current filesRef state for rollback
const backupFilesRef = new Map(filesRef.current);
try {
// Prepare files to restore
const filesToRestore = inputFiles.map((file, index) => ({
file,
record: inputFileRecords[index]
record: inputStirlingFileStubs[index]
}));
// Restore input files and clean up output files
@@ -502,13 +501,13 @@ export async function undoConsumeFiles(
dispatch({
type: 'UNDO_CONSUME_FILES',
payload: {
inputFileRecords,
inputStirlingFileStubs,
outputFileIds
}
});
if (DEBUG) console.log(`📄 undoConsumeFiles: Successfully undone consume operation - restored ${inputFileRecords.length} inputs, removed ${outputFileIds.length} outputs`);
if (DEBUG) console.log(`📄 undoConsumeFiles: Successfully undone consume operation - restored ${inputStirlingFileStubs.length} inputs, removed ${outputFileIds.length} outputs`);
} catch (error) {
// Rollback filesRef to previous state
if (DEBUG) console.error('📄 undoConsumeFiles: Error during undo, rolling back filesRef', error);

View File

@@ -9,7 +9,7 @@ import {
FileContextStateValue,
FileContextActionsValue
} from './contexts';
import { FileRecord } from '../../types/fileContext';
import { StirlingFileStub, StirlingFile } from '../../types/fileContext';
import { FileId } from '../../types/file';
/**
@@ -38,13 +38,13 @@ export function useFileActions(): FileContextActionsValue {
/**
* Hook for current/primary file (first in list)
*/
export function useCurrentFile(): { file?: File; record?: FileRecord } {
export function useCurrentFile(): { file?: File; record?: StirlingFileStub } {
const { state, selectors } = useFileState();
const primaryFileId = state.files.ids[0];
return useMemo(() => ({
file: primaryFileId ? selectors.getFile(primaryFileId) : undefined,
record: primaryFileId ? selectors.getFileRecord(primaryFileId) : undefined
record: primaryFileId ? selectors.getStirlingFileStub(primaryFileId) : undefined
}), [primaryFileId, selectors]);
}
@@ -87,7 +87,7 @@ export function useFileManagement() {
addFiles: actions.addFiles,
removeFiles: actions.removeFiles,
clearAllFiles: actions.clearAllFiles,
updateFileRecord: actions.updateFileRecord,
updateStirlingFileStub: actions.updateStirlingFileStub,
reorderFiles: actions.reorderFiles
}), [actions]);
}
@@ -111,24 +111,24 @@ export function useFileUI() {
/**
* Hook for specific file by ID (optimized for individual file access)
*/
export function useFileRecord(fileId: FileId): { file?: File; record?: FileRecord } {
export function useStirlingFileStub(fileId: FileId): { file?: File; record?: StirlingFileStub } {
const { selectors } = useFileState();
return useMemo(() => ({
file: selectors.getFile(fileId),
record: selectors.getFileRecord(fileId)
record: selectors.getStirlingFileStub(fileId)
}), [fileId, selectors]);
}
/**
* Hook for all files (use sparingly - causes re-renders on file list changes)
*/
export function useAllFiles(): { files: File[]; records: FileRecord[]; fileIds: FileId[] } {
export function useAllFiles(): { files: StirlingFile[]; records: StirlingFileStub[]; fileIds: FileId[] } {
const { state, selectors } = useFileState();
return useMemo(() => ({
files: selectors.getFiles(),
records: selectors.getFileRecords(),
records: selectors.getStirlingFileStubs(),
fileIds: state.files.ids
}), [state.files.ids, selectors]);
}
@@ -136,12 +136,12 @@ export function useAllFiles(): { files: File[]; records: FileRecord[]; fileIds:
/**
* Hook for selected files (optimized for selection-based UI)
*/
export function useSelectedFiles(): { files: File[]; records: FileRecord[]; fileIds: FileId[] } {
export function useSelectedFiles(): { files: StirlingFile[]; records: StirlingFileStub[]; fileIds: FileId[] } {
const { state, selectors } = useFileState();
return useMemo(() => ({
files: selectors.getSelectedFiles(),
records: selectors.getSelectedFileRecords(),
records: selectors.getSelectedStirlingFileStubs(),
fileIds: state.ui.selectedFileIds
}), [state.ui.selectedFileIds, selectors]);
}

View File

@@ -4,9 +4,11 @@
import { FileId } from '../../types/file';
import {
FileRecord,
StirlingFileStub,
FileContextState,
FileContextSelectors
FileContextSelectors,
StirlingFile,
createStirlingFile
} from '../../types/fileContext';
/**
@@ -17,16 +19,24 @@ export function createFileSelectors(
filesRef: React.MutableRefObject<Map<FileId, File>>
): FileContextSelectors {
return {
getFile: (id: FileId) => filesRef.current.get(id),
getFile: (id: FileId) => {
const file = filesRef.current.get(id);
return file ? createStirlingFile(file, id) : undefined;
},
getFiles: (ids?: FileId[]) => {
const currentIds = ids || stateRef.current.files.ids;
return currentIds.map(id => filesRef.current.get(id)).filter(Boolean) as File[];
return currentIds
.map(id => {
const file = filesRef.current.get(id);
return file ? createStirlingFile(file, id) : undefined;
})
.filter(Boolean) as StirlingFile[];
},
getFileRecord: (id: FileId) => stateRef.current.files.byId[id],
getStirlingFileStub: (id: FileId) => stateRef.current.files.byId[id],
getFileRecords: (ids?: FileId[]) => {
getStirlingFileStubs: (ids?: FileId[]) => {
const currentIds = ids || stateRef.current.files.ids;
return currentIds.map(id => stateRef.current.files.byId[id]).filter(Boolean);
},
@@ -35,11 +45,14 @@ export function createFileSelectors(
getSelectedFiles: () => {
return stateRef.current.ui.selectedFileIds
.map(id => filesRef.current.get(id))
.filter(Boolean) as File[];
.map(id => {
const file = filesRef.current.get(id);
return file ? createStirlingFile(file, id) : undefined;
})
.filter(Boolean) as StirlingFile[];
},
getSelectedFileRecords: () => {
getSelectedStirlingFileStubs: () => {
return stateRef.current.ui.selectedFileIds
.map(id => stateRef.current.files.byId[id])
.filter(Boolean);
@@ -52,26 +65,21 @@ export function createFileSelectors(
getPinnedFiles: () => {
return Array.from(stateRef.current.pinnedFiles)
.map(id => filesRef.current.get(id))
.filter(Boolean) as File[];
.map(id => {
const file = filesRef.current.get(id);
return file ? createStirlingFile(file, id) : undefined;
})
.filter(Boolean) as StirlingFile[];
},
getPinnedFileRecords: () => {
getPinnedStirlingFileStubs: () => {
return Array.from(stateRef.current.pinnedFiles)
.map(id => stateRef.current.files.byId[id])
.filter(Boolean);
},
isFilePinned: (file: File) => {
// Find FileId by matching File object properties
const fileId = (Object.keys(stateRef.current.files.byId) as FileId[]).find(id => {
const storedFile = filesRef.current.get(id);
return storedFile &&
storedFile.name === file.name &&
storedFile.size === file.size &&
storedFile.lastModified === file.lastModified;
});
return fileId ? stateRef.current.pinnedFiles.has(fileId) : false;
isFilePinned: (file: StirlingFile) => {
return stateRef.current.pinnedFiles.has(file.fileId);
},
// Stable signature for effects - prevents unnecessary re-renders
@@ -90,9 +98,9 @@ export function createFileSelectors(
/**
* Helper for building quickKey sets for deduplication
*/
export function buildQuickKeySet(fileRecords: Record<FileId, FileRecord>): Set<string> {
export function buildQuickKeySet(stirlingFileStubs: Record<FileId, StirlingFileStub>): Set<string> {
const quickKeys = new Set<string>();
Object.values(fileRecords).forEach(record => {
Object.values(stirlingFileStubs).forEach(record => {
if (record.quickKey) {
quickKeys.add(record.quickKey);
}
@@ -119,7 +127,7 @@ export function buildQuickKeySetFromMetadata(metadata: Array<{ name: string; siz
export function getPrimaryFile(
stateRef: React.MutableRefObject<FileContextState>,
filesRef: React.MutableRefObject<Map<FileId, File>>
): { file?: File; record?: FileRecord } {
): { file?: File; record?: StirlingFileStub } {
const primaryFileId = stateRef.current.files.ids[0];
if (!primaryFileId) return {};

View File

@@ -3,7 +3,7 @@
*/
import { FileId } from '../../types/file';
import { FileContextAction, FileRecord, ProcessedFilePage } from '../../types/fileContext';
import { FileContextAction, StirlingFileStub, ProcessedFilePage } from '../../types/fileContext';
const DEBUG = process.env.NODE_ENV === 'development';
@@ -166,7 +166,7 @@ export class FileLifecycleManager {
/**
* Update file record with race condition guards
*/
updateFileRecord = (fileId: FileId, updates: Partial<FileRecord>, stateRef?: React.MutableRefObject<any>): void => {
updateStirlingFileStub = (fileId: FileId, updates: Partial<StirlingFileStub>, stateRef?: React.MutableRefObject<any>): void => {
// Guard against updating removed files (race condition protection)
if (!this.filesRef.current.has(fileId)) {
if (DEBUG) console.warn(`🗂️ Attempted to update removed file (filesRef): ${fileId}`);