Skip to content

Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend architecture#3871

Merged
Keavon merged 10 commits intomasterfrom
fix-hmr
Mar 19, 2026
Merged

Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend architecture#3871
Keavon merged 10 commits intomasterfrom
fix-hmr

Conversation

@Keavon
Copy link
Copy Markdown
Member

@Keavon Keavon commented Mar 9, 2026

No description provided.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural refactor to improve the frontend's modularity, stability, and hot module replacement (HMR) experience. It separates concerns by introducing managers and stores, ensures proper resource cleanup with destroy() methods, and fixes several bugs related to UI rendering and input handling. These changes collectively enhance the developer experience and application reliability.

Highlights

  • Manager and Store Architecture: Introduces a new architecture separating managers (for handling side effects and browser APIs) and stores (for managing reactive state), improving code organization and HMR.
  • Destructor Functions: Ensures proper cleanup of resources by implementing destroy() methods for managers and stores, preventing memory leaks and improving stability during development.
  • Hot Module Replacement (HMR) Improvements: Enhances the HMR experience by correctly tearing down and recreating modules, preserving application state, and preventing crashes during development.
  • Bug Fixes: Addresses several bugs, including a Safari rendering issue with floating menus and incorrect slider behavior in number inputs.
Changelog
  • frontend/src/App.svelte
    • Updated the onDestroy lifecycle hook to use the new editor.destroy() method for proper resource cleanup.
  • frontend/src/README.md
    • Updated the documentation to reflect the new managers and stores architecture, providing clear explanations of their roles and usage.
  • frontend/src/components/Editor.svelte
    • Refactored to use stores and managers, creating them at startup and calling their destroy() methods on unmount.
    • Stores are now passed to Svelte's setContext() for component access.
    • Managers are created as isolated systems linked to browser API functionality.
  • frontend/src/components/floating-menus/ColorPicker.svelte
    • Updated to use TooltipStore instead of TooltipState.
    • Inlined the clamp function.
  • frontend/src/components/floating-menus/Dialog.svelte
    • Updated to use DialogStore instead of DialogState.
    • Replaced githubUrl with crashReportUrl.
  • frontend/src/components/floating-menus/EyedropperPreview.svelte
    • Modified to initialize temporaryCanvas only when needed.
  • frontend/src/components/floating-menus/MenuList.svelte
    • Improved keydown event listener management to prevent multiple listeners and ensure proper cleanup.
  • frontend/src/components/floating-menus/NodeCatalog.svelte
    • Updated to use NodeGraphStore instead of NodeGraphState.
  • frontend/src/components/floating-menus/Tooltip.svelte
    • Updated to use TooltipStore instead of TooltipState.
  • frontend/src/components/layout/FloatingMenu.svelte
    • Added and called setSafariScrollableOverflow to address a Safari rendering bug.
    • Added a resize observer for dialogs to handle antialiasing issues.
    • Ensured proper disconnection of resize observers and removal of event listeners on destroy.
  • frontend/src/components/panels/Document.svelte
    • Updated to use AppWindowStore and DocumentStore instead of AppWindowState and DocumentState.
    • Removed cleanupViewportResizeObserver and integrated its functionality into onDestroy.
    • Added font face cleanup on destroy.
  • frontend/src/components/panels/Layers.svelte
    • Updated to use NodeGraphStore and TooltipStore instead of NodeGraphState and TooltipState.
  • frontend/src/components/views/Graph.svelte
    • Updated to use NodeGraphStore and DocumentStore instead of NodeGraphState and DocumentState.
    • Replaced direct calls to nodeGraph.closeContextMenu() with closeContextMenu().
  • frontend/src/components/widgets/WidgetSpan.svelte
    • Removed debouncers from widget value commit and update events.
  • frontend/src/components/widgets/inputs/CurveInput.svelte
    • Inlined the clamp function.
  • frontend/src/components/widgets/inputs/NumberInput.svelte
    • Moved PRESS_REPEAT_DELAY_MS and PRESS_REPEAT_INTERVAL_MS constants to managers/input.ts.
    • Added comprehensive cleanup of event listeners and timeouts on destroy.
    • Fixed slider abort behavior and Safari cursor hiding.
  • frontend/src/components/widgets/inputs/ScrollbarInput.svelte
    • Moved PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS, and PRESS_REPEAT_INTERVAL_RAPID_MS constants to managers/input.ts.
    • Added event listener cleanup on destroy.
  • frontend/src/components/widgets/inputs/SpectrumInput.svelte
    • Added onMount to ensure the keydown event listener is attached after the component is mounted.
    • Added event listener cleanup on destroy.
  • frontend/src/components/widgets/labels/TextLabel.svelte
    • Added handlePointerLeave to onDestroy.
  • frontend/src/components/window/MainWindow.svelte
    • Updated to use AppWindowStore, DialogStore, and TooltipStore instead of AppWindowState, DialogState, and TooltipState.
  • frontend/src/components/window/StatusBar.svelte
    • Added subscription cleanup on destroy.
  • frontend/src/components/window/TitleBar.svelte
    • Updated to use AppWindowStore, FullscreenStore, and TooltipStore instead of AppWindowState, FullscreenState, and TooltipState.
    • Added subscription cleanup on destroy.
  • frontend/src/components/window/Workspace.svelte
    • Updated to use PortfolioStore instead of PortfolioState.
    • Added active resize cleanup on destroy.
  • frontend/src/editor.ts
    • Added a destroy method to the Editor type to free WASM resources and abort demo artwork loading.
    • Added demoArtworkAbortController to abort demo artwork loading when destroying the editor.
  • frontend/src/main.ts
    • Ensured the old component tree is properly torn down during HMR so all onDestroy hooks fire.
  • frontend/src/managers/clipboard.ts
    • Created managers/clipboard.ts from io-managers/clipboard.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/managers/fonts.ts
    • Created managers/fonts.ts from io-managers/fonts.ts.
    • Added destroy function to unsubscribe from frontend messages, abort ongoing requests, and handle HMR.
  • frontend/src/managers/hyperlink.ts
    • Created managers/hyperlink.ts from io-managers/hyperlink.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/managers/input.ts
    • Created managers/input.ts from io-managers/input.ts.
    • Added destroy function to unsubscribe from frontend messages, clear timeouts, and remove event listeners.
    • Moved PRESS_REPEAT_DELAY_MS and PRESS_REPEAT_INTERVAL_MS constants to this file.
  • frontend/src/managers/localization.ts
    • Created managers/localization.ts from io-managers/localization.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/managers/panic.ts
    • Created managers/panic.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/managers/persistence.ts
    • Created managers/persistence.ts from io-managers/persistence.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/app-window.ts
    • Created stores/app-window.ts from state-providers/app-window.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/dialog.ts
    • Created stores/dialog.ts from state-providers/dialog.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/document.ts
    • Created stores/document.ts from state-providers/document.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/fullscreen.ts
    • Created stores/fullscreen.ts from state-providers/fullscreen.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/node-graph.ts
    • Created stores/node-graph.ts from state-providers/node-graph.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/portfolio.ts
    • Created stores/portfolio.ts from state-providers/portfolio.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/stores/tooltip.ts
    • Created stores/tooltip.ts from state-providers/tooltip.ts.
    • Added destroy function to unsubscribe from frontend messages and handle HMR.
  • frontend/src/subscription-router.ts
    • Refactored subscriptions and layoutCallbacks to use Maps instead of Records.
    • Added a check to prevent console errors after teardown during HMR.
  • frontend/src/utility-functions/crash-report.ts
    • Created utility-functions/crash-report.ts from io-managers/panic.ts.
    • Updated the crash report URL to point to the development version of Graphite.
Activity
  • The pull request involves a significant refactor of the frontend architecture, separating concerns into managers and stores.
  • Destructor functions were added to ensure proper cleanup of resources and prevent memory leaks.
  • Hot Module Replacement (HMR) was improved to provide a more stable and reliable development experience.
  • Several bugs were fixed, including a Safari rendering issue and incorrect slider behavior.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

6 issues found across 54 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/components/window/Workspace.svelte">

<violation number="1" location="frontend/src/components/window/Workspace.svelte:112">
P2: `abortResize` also calls `removeListeners()` but doesn't clear `activeResizeCleanup`. After a resize abort (via Escape or right-click), the `onDestroy` handler will still hold a stale reference. Add `activeResizeCleanup = undefined` in `abortResize` to keep the cleanup tracking consistent.</violation>
</file>

<file name="frontend/src/stores/fullscreen.ts">

<violation number="1" location="frontend/src/stores/fullscreen.ts:39">
P2: `update()` is used here only to read the current state — the callback returns `state` unmodified. Because Svelte's `safe_not_equal` treats all objects as changed, this triggers a spurious subscriber notification on every toggle. Use `get(store)` to read the value without notifying subscribers, which also eliminates the explicit `Promise` constructor antipattern.

Note: `get` needs to be added to the `svelte/store` import.</violation>
</file>

<file name="frontend/src/managers/fonts.ts">

<violation number="1" location="frontend/src/managers/fonts.ts:17">
P2: Missing `response.ok` check. If the API returns a non-2xx status, `response.json()` may succeed with an error body that lacks `items`, producing a confusing `TypeError` instead of a clear HTTP error.</violation>

<violation number="2" location="frontend/src/managers/fonts.ts:44">
P1: Missing `response.ok` check before consuming the response body. If the font URL returns an HTTP error (e.g., 404), the error page body will be silently passed to `onFontLoad` as font data, corrupting the backend state.</violation>
</file>

<file name="frontend/src/stores/dialog.ts">

<violation number="1" location="frontend/src/stores/dialog.ts:80">
P2: `fetch` can reject on network errors, skipping the `requestLicensesThirdPartyDialogWithLicenseText` call entirely. Wrap the fetch in a try/catch so the pre-defined fallback `licenseText` is actually used.</violation>
</file>

<file name="frontend/src/stores/document.ts">

<violation number="1" location="frontend/src/stores/document.ts:73">
P2: Missing `await tick()` before the store update — every other `subscribeLayoutUpdate` handler in this file awaits `tick()` first to let Svelte flush pending DOM updates before patching the layout. This one appears to be an accidental omission.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread frontend/src/managers/fonts.ts
Comment thread frontend/src/components/window/Workspace.svelte
Comment thread frontend/src/stores/fullscreen.ts
Comment thread frontend/src/managers/fonts.ts
Comment thread frontend/src/stores/dialog.ts Outdated
Comment thread frontend/src/stores/document.ts Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request refactors the frontend architecture by introducing a new "managers" and "stores" pattern, replacing the previous "I/O managers" and "state providers." This change involves renaming modules, updating imports and type definitions across many Svelte components, and implementing explicit destroy() methods for resource cleanup, which are integrated with Svelte's onDestroy and Hot Module Replacement (HMR) for improved lifecycle management. A high-severity path traversal vulnerability was identified in frontend/src/editor.ts, where unsanitized URL hash data could lead to arbitrary file access. Additionally, a medium-severity performance concern was raised in frontend/src/components/widgets/WidgetSpan.svelte due to the removal of a debouncer, potentially causing excessive messages during drag interactions.

Comment thread frontend/src/editor.ts
Comment thread frontend/src/components/widgets/WidgetSpan.svelte
@github-actions github-actions bot temporarily deployed to graphite-dev (Preview) March 9, 2026 22:09 Inactive
@github-actions github-actions bot temporarily deployed to graphite-dev (Preview) March 9, 2026 22:53 Inactive
@Keavon Keavon force-pushed the master branch 2 times, most recently from 5bb6104 to 52d2b38 Compare March 9, 2026 23:35
@github-actions github-actions bot temporarily deployed to graphite-dev (Preview) March 10, 2026 02:00 Inactive
@Keavon Keavon force-pushed the master branch 5 times, most recently from e58c1de to df8001f Compare March 17, 2026 00:10
@Keavon Keavon force-pushed the fix-hmr branch 2 times, most recently from 9dc0c5f to 7be6803 Compare March 19, 2026 06:09
@Keavon Keavon force-pushed the master branch 3 times, most recently from 05c5187 to fcc53f5 Compare March 19, 2026 09:26
@github-actions github-actions bot temporarily deployed to graphite-dev (Preview) March 19, 2026 10:44 Inactive
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 54 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/managers/hyperlink.ts">

<violation number="1" location="frontend/src/managers/hyperlink.ts:11">
P2: Opening a new tab with `"_blank"` should include `noopener`/`noreferrer` to prevent reverse-tabnabbing via `window.opener`.</violation>
</file>

<file name="frontend/src/managers/persistence.ts">

<violation number="1" location="frontend/src/managers/persistence.ts:190">
P2: `findIndex` can return `-1` when `currentDocumentId` is not in `orderedSavedDocuments` (e.g., stale IDB state). In that case the code opens all documents via the second loop but then calls `selectDocument` with an ID that was never opened. Guard against `currentIndex === -1` by falling through to the `else` branch.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread frontend/src/managers/hyperlink.ts Outdated
Comment thread frontend/src/managers/persistence.ts Outdated
@github-actions github-actions bot temporarily deployed to graphite-dev (Preview) March 19, 2026 20:20 Inactive
@Keavon Keavon changed the title Fix frontend HMR so the page doesn't break upon saving TS files Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend Mar 19, 2026
@Keavon Keavon changed the title Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend architecture Mar 19, 2026
@Keavon Keavon merged commit 124b17f into master Mar 19, 2026
10 checks passed
@Keavon Keavon deleted the fix-hmr branch March 19, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant