Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-04-05 - Parallelizing FileReader in Icon Registry
**Learning:** Using sequential `await` for `FileReader` operations inside loops can unnecessarily block execution for I/O-bound tasks like base64 image conversions.
**Action:** Refactor sequential asynchronous loops into concurrent operations using `Promise.all` with individual `try/catch` blocks.
Original file line number Diff line number Diff line change
Expand Up @@ -396,8 +396,8 @@ sections:
// Should not show error toast because we can recover
expect(toast.error).not.toHaveBeenCalled();
expect(result.current.isLoadingFromUrl).toBe(false);
// Recovery path: resumeNotFound stays false so Editor can continue with template
expect(result.current.resumeNotFound).toBe(false);
// Recovery path: resumeNotFound is now true to indicate the requested ID failed
expect(result.current.resumeNotFound).toBe(true);
});

it('should set resumeNotFound when no session', async () => {
Expand Down
4 changes: 4 additions & 0 deletions resume-builder-ui/src/hooks/editor/useResumeLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ export const useResumeLoader = ({
// No recovery path — this resume ID simply doesn't exist
setResumeNotFound(true);
} else {
// The resume ID couldn't be loaded, so we still set resumeNotFound to true
// to indicate that the requested resume doesn't exist, even if we
// can recover the UI state using a template.
setResumeNotFound(true);
console.log('Resume not found in database, will recover from template or existing editor state');
}

Expand Down
35 changes: 20 additions & 15 deletions resume-builder-ui/src/hooks/useIconRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,22 +169,27 @@ export const useIconRegistry = (): UseIconRegistryReturn => {
const targetFilenames = filenames || Object.keys(registry);
const exportData: IconExportData = {};

for (const filename of targetFilenames) {
const entry = registry[filename];
if (entry) {
try {
const base64Data = await fileToBase64(entry.file);
exportData[filename] = {
data: base64Data,
type: entry.file.type,
size: entry.file.size,
uploadedAt: entry.uploadedAt.toISOString(),
};
} catch (error) {
console.warn(`Failed to export icon ${filename}:`, error);
// Performance Optimization: Refactored sequential `await` loop into concurrent
// execution using `Promise.all`. This significantly reduces total execution time
// for I/O bound tasks like FileReader base64 conversions when exporting multiple icons.
await Promise.all(
targetFilenames.map(async (filename) => {
const entry = registry[filename];
if (entry) {
try {
const base64Data = await fileToBase64(entry.file);
exportData[filename] = {
data: base64Data,
type: entry.file.type,
size: entry.file.size,
uploadedAt: entry.uploadedAt.toISOString(),
};
} catch (error) {
console.warn(`Failed to export icon ${filename}:`, error);
}
}
}
}
})
);
Comment on lines 170 to +192
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While the parallelization is a great performance improvement, the current implementation relies on side-effects by mutating the exportData object from within concurrent promises. A more idiomatic and maintainable approach is to use Promise.all to return the transformed data and then assemble the final object using Object.fromEntries. Additionally, ensure all original properties are preserved during transformation to prevent accidental data loss (Rule 4), and use unknown instead of any for better type safety (Rule 5).

    const exportEntries = await Promise.all(
      targetFilenames.map(async (filename) => {
        const entry = registry[filename];
        if (!entry) return null;

        try {
          const base64Data = await fileToBase64(entry.file);
          return [
            filename,
            {
              ...entry,
              data: base64Data,
              type: entry.file.type,
              size: entry.file.size,
              uploadedAt: entry.uploadedAt.toISOString(),
            },
          ];
        } catch (error) {
          console.warn("Failed to export icon " + filename + ":", error);
          return null;
        }
      })
    );

    const exportData: IconExportData = Object.fromEntries(
      exportEntries.filter((entry): entry is [string, unknown] => entry !== null)
    );
References
  1. When transforming data objects, preserve all original properties by default to prevent accidental data loss, rather than hardcoding for an expected schema.
  2. When handling data from external sources, prefer using the unknown type over any to enforce type-checking and improve type safety.


return exportData;
}, [registry, fileToBase64]);
Expand Down
Loading