Break out helper functions from the frontend's managers and stores#3920
Break out helper functions from the frontend's managers and stores#3920
Conversation
There was a problem hiding this comment.
7 issues found across 19 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="frontend/src/utility-functions/time.ts">
<violation number="1" location="frontend/src/utility-functions/time.ts:7">
P2: Timezone name is computed from the current date instead of the input timestamp, which can produce incorrect labels across DST boundaries.</violation>
</file>
<file name="frontend/src/utility-functions/clipboard.ts">
<violation number="1" location="frontend/src/utility-functions/clipboard.ts:101">
P1: Using `Promise.any()` here can return `false` early from the first unsupported item and incorrectly fail paste even when a later clipboard item is valid.</violation>
<violation number="2" location="frontend/src/utility-functions/clipboard.ts:105">
P1: The SVG branch requests `text/plain` instead of the detected SVG MIME type, which can fail when plain text is unavailable.</violation>
</file>
<file name="frontend/src/utility-functions/persistence.ts">
<violation number="1" location="frontend/src/utility-functions/persistence.ts:9">
P1: `createStore` opens a new IndexedDB connection on each call. It should be called once at module level and reused across all functions. Repeated calls waste connections and can cause race conditions (documented in idb-keyval#178) when concurrent operations overlap.</violation>
<violation number="2" location="frontend/src/utility-functions/persistence.ts:73">
P2: The `documentId` string→bigint migration block is duplicated in three functions. Extract it into a shared helper (e.g., `migrateDocumentIds`) so the TODO removal is a single-site change and the migration logic stays consistent.</violation>
</file>
<file name="frontend/src/utility-functions/input.ts">
<violation number="1" location="frontend/src/utility-functions/input.ts:250">
P1: The `async` `beforeunload` handler sets `e.returnValue` and calls `e.preventDefault()` after `await` statements. Browsers process `beforeunload` synchronously — code after `await` is deferred to the microtask queue and the browser may unload the page before the unsaved-changes dialog is triggered. Either make the checks synchronous or move `e.preventDefault()` before the first `await` and cancel it if the early-out conditions apply.</violation>
<violation number="2" location="frontend/src/utility-functions/input.ts:272">
P2: `forEach` does not await or catch async callbacks. If `pasteFile` rejects, the error becomes an unhandled promise rejection. Use a `for...of` loop instead, or collect the promises and handle them with `Promise.all`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| Array.from(clipboardItems).map(async (item) => { | ||
| // Read plain text and, if it is a layer, pass it to the editor | ||
| if (item.types.includes("text/plain")) { | ||
| const blob = await item.getType("text/plain"); |
There was a problem hiding this comment.
P1: The SVG branch requests text/plain instead of the detected SVG MIME type, which can fail when plain text is unavailable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/clipboard.ts, line 105:
<comment>The SVG branch requests `text/plain` instead of the detected SVG MIME type, which can fail when plain text is unavailable.</comment>
<file context>
@@ -0,0 +1,175 @@
+ Array.from(clipboardItems).map(async (item) => {
+ // Read plain text and, if it is a layer, pass it to the editor
+ if (item.types.includes("text/plain")) {
+ const blob = await item.getType("text/plain");
+ const reader = new FileReader();
+ reader.onload = () => {
</file context>
| const blob = await item.getType("text/plain"); | |
| const blob = await item.getType(imageType); |
| if (!clipboardItems) throw new Error("Clipboard API unsupported"); | ||
|
|
||
| // Read any layer data or images from the clipboard | ||
| const success = await Promise.any( |
There was a problem hiding this comment.
P1: Using Promise.any() here can return false early from the first unsupported item and incorrectly fail paste even when a later clipboard item is valid.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/clipboard.ts, line 101:
<comment>Using `Promise.any()` here can return `false` early from the first unsupported item and incorrectly fail paste even when a later clipboard item is valid.</comment>
<file context>
@@ -0,0 +1,175 @@
+ if (!clipboardItems) throw new Error("Clipboard API unsupported");
+
+ // Read any layer data or images from the clipboard
+ const success = await Promise.any(
+ Array.from(clipboardItems).map(async (item) => {
+ // Read plain text and, if it is a layer, pass it to the editor
</file context>
| @@ -0,0 +1,193 @@ | |||
| import * as idb from "idb-keyval"; | |||
There was a problem hiding this comment.
P1: createStore opens a new IndexedDB connection on each call. It should be called once at module level and reused across all functions. Repeated calls waste connections and can cause race conditions (documented in idb-keyval#178) when concurrent operations overlap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/persistence.ts, line 9:
<comment>`createStore` opens a new IndexedDB connection on each call. It should be called once at module level and reused across all functions. Repeated calls waste connections and can cause race conditions (documented in idb-keyval#178) when concurrent operations overlap.</comment>
<file context>
@@ -0,0 +1,193 @@
+import type { MessageBody } from "@graphite/subscription-router";
+
+export async function storeCurrentDocumentId(documentId: string) {
+ const indexedDbStorage = idb.createStore("graphite", "store");
+
+ await idb.set("current_document_id", String(documentId), indexedDbStorage);
</file context>
|
|
||
| // Window events | ||
|
|
||
| export async function onBeforeUnload(e: BeforeUnloadEvent, editor: Editor, portfolioStore: PortfolioStore) { |
There was a problem hiding this comment.
P1: The async beforeunload handler sets e.returnValue and calls e.preventDefault() after await statements. Browsers process beforeunload synchronously — code after await is deferred to the microtask queue and the browser may unload the page before the unsaved-changes dialog is triggered. Either make the checks synchronous or move e.preventDefault() before the first await and cancel it if the early-out conditions apply.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/input.ts, line 250:
<comment>The `async` `beforeunload` handler sets `e.returnValue` and calls `e.preventDefault()` after `await` statements. Browsers process `beforeunload` synchronously — code after `await` is deferred to the microtask queue and the browser may unload the page before the unsaved-changes dialog is triggered. Either make the checks synchronous or move `e.preventDefault()` before the first `await` and cancel it if the early-out conditions apply.</comment>
<file context>
@@ -0,0 +1,362 @@
+
+// Window events
+
+export async function onBeforeUnload(e: BeforeUnloadEvent, editor: Editor, portfolioStore: PortfolioStore) {
+ const activeDocument = get(portfolioStore).documents[get(portfolioStore).activeDocumentIndex];
+ if (activeDocument && !activeDocument.details.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
</file context>
| if (Number.isNaN(date.getTime())) return { timestamp: utc, year: `${new Date().getFullYear()}` }; | ||
|
|
||
| const timezoneName = Intl.DateTimeFormat(undefined, { timeZoneName: "longGeneric" }) | ||
| .formatToParts(new Date()) |
There was a problem hiding this comment.
P2: Timezone name is computed from the current date instead of the input timestamp, which can produce incorrect labels across DST boundaries.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/time.ts, line 7:
<comment>Timezone name is computed from the current date instead of the input timestamp, which can produce incorrect labels across DST boundaries.</comment>
<file context>
@@ -0,0 +1,14 @@
+ if (Number.isNaN(date.getTime())) return { timestamp: utc, year: `${new Date().getFullYear()}` };
+
+ const timezoneName = Intl.DateTimeFormat(undefined, { timeZoneName: "longGeneric" })
+ .formatToParts(new Date())
+ .find((part) => part.type === "timeZoneName");
+
</file context>
|
|
||
| const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage); | ||
|
|
||
| // TODO: Eventually remove this document upgrade code |
There was a problem hiding this comment.
P2: The documentId string→bigint migration block is duplicated in three functions. Extract it into a shared helper (e.g., migrateDocumentIds) so the TODO removal is a single-site change and the migration logic stays consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/persistence.ts, line 73:
<comment>The `documentId` string→bigint migration block is duplicated in three functions. Extract it into a shared helper (e.g., `migrateDocumentIds`) so the TODO removal is a single-site change and the migration logic stays consistent.</comment>
<file context>
@@ -0,0 +1,193 @@
+
+ const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage);
+
+ // TODO: Eventually remove this document upgrade code
+ // Migrate TriggerPersistenceWriteDocument.documentId from string to bigint if the browser is storing the old format as strings
+ if (previouslySavedDocuments) {
</file context>
| if (!dataTransfer || targetIsTextField(e.target || undefined)) return; | ||
| e.preventDefault(); | ||
|
|
||
| Array.from(dataTransfer.items).forEach(async (item) => { |
There was a problem hiding this comment.
P2: forEach does not await or catch async callbacks. If pasteFile rejects, the error becomes an unhandled promise rejection. Use a for...of loop instead, or collect the promises and handle them with Promise.all.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/utility-functions/input.ts, line 272:
<comment>`forEach` does not await or catch async callbacks. If `pasteFile` rejects, the error becomes an unhandled promise rejection. Use a `for...of` loop instead, or collect the promises and handle them with `Promise.all`.</comment>
<file context>
@@ -0,0 +1,362 @@
+ if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
+ e.preventDefault();
+
+ Array.from(dataTransfer.items).forEach(async (item) => {
+ if (item.type === "text/plain") item.getAsString((text) => editor.handle.pasteText(text));
+ await pasteFile(item, editor);
</file context>
|
/gemini summary |
Summary of ChangesThis pull request focuses on a significant frontend cleanup and refactoring effort. The primary goal was to enhance code organization and maintainability by extracting various utility functions and event handling logic from core manager and store files into dedicated Highlights
Changelog
Activity
|
No description provided.