Skip to content

OUT-4154: guard image insert against destroyed editor - #237

Merged
SandipBajracharya merged 1 commit into
mainfrom
OUT-4154
Sep 4, 2026
Merged

OUT-4154: guard image insert against destroyed editor#237
SandipBajracharya merged 1 commit into
mainfrom
OUT-4154

Conversation

@SandipBajracharya

Copy link
Copy Markdown
Collaborator

Changes

  • Fix TypeError: Cannot read properties of null (reading 'chain') (Sentry CLIENT-HOME-V3-1W / OUT-4154) in the editor's file-insertion flow.

Root cause: fileReader.onload in useFileHandlers.tsx is async. It inserts a placeholder image, awaits uploadFileToSupabase(file) (a network round-trip), then calls currentEditor.chain()... to swap the blob for the proxy URL. If the TipTap editor is destroyed during that window (component unmount, navigation, or useEditor re-init when the content prop changes), the resumed continuation calls .chain() on a torn-down editor whose internal commandManager is null → the crash. Because onload is async with no try/catch, it surfaced as an unhandled promise rejection.

Fix:

  • Bail on currentEditor.isDestroyed at the top of onload (guards the synchronous chain()onload fires on a later tick, so the editor may already be gone) and again after the upload await.
  • Wrap the upload + src-swap in try/catch so a failed/aborted upload keeps the blob placeholder instead of throwing an unhandled rejection.
  • Log a console.warn breadcrumb when the destroyed-editor guard trips, to retain visibility (the base64 placeholder stays in content, same state as before the crash — just no longer fatal).

Testing Criteria

  • Insert an image into the editor; confirm it uploads and the src swaps from the base64 blob to the /api/media/image proxy URL (happy path unchanged). (Loom to be added)
  • Insert an image, then unmount/navigate away before the upload completes — confirm no crash and no unhandled rejection in the console (warn breadcrumb instead).
  • Simulate an upload failure (e.g. offline) — confirm the blob placeholder remains and the error is logged, no unhandled rejection.

Notes

  • No dependencies. TipTap Editor.isDestroyed is the canonical "editor still usable" check.

Impact & Surface Area of Change

  • Scope is limited to src/features/editor/hooks/useFileHandlers.tsx (image/file insertion). Happy path is unchanged; the new guards only short-circuit when the editor is already destroyed or the upload rejects. Watch the editor image-insert flow for regressions.

🤖 Generated with Claude Code

The async image upload in useFileHandlers could resume after the TipTap
editor was torn down (unmount / re-init), calling chain() on an editor
whose commandManager is null and throwing an unhandled rejection
(Sentry CLIENT-HOME-V3-1W).

- Bail on currentEditor.isDestroyed at the top of onload and after the
  Supabase upload await
- Wrap the upload + src swap in try/catch so a failed upload no longer
  surfaces as an unhandled rejection

Fixes CLIENT-HOME-V3-1W

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 4, 2026

Copy link
Copy Markdown

OUT-4154

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
client-home-v3 Ready Ready Preview Sep 4, 2026 9:52am UTC

Request Review

@SandipBajracharya SandipBajracharya changed the title fix(OUT-4154): guard image insert against destroyed editor OUT-4154: guard image insert against destroyed editor Sep 4, 2026
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR hardens asynchronous editor image insertion against editor teardown and rejected uploads.

  • Checks the captured TipTap editor before inserting the placeholder and again after the upload completes.
  • Keeps the temporary image when upload or editor lifecycle handling prevents the proxy-URL swap.
  • Converts upload rejections into handled, logged failures instead of unhandled promise rejections.

Confidence Score: 5/5

The PR appears safe to merge, with the asynchronous teardown and upload-failure paths handled without changing successful image insertion.

The changed callback checks editor viability before each lifecycle-sensitive phase and catches upload failures, while the normal path still uploads the file, replaces the temporary source, and synchronizes the resulting HTML.

Important Files Changed

Filename Overview
src/features/editor/hooks/useFileHandlers.tsx Adds lifecycle guards and rejection handling around asynchronous image upload and source replacement without changing the active-editor success path.

Sequence Diagram

sequenceDiagram
  participant User
  participant Reader as FileReader
  participant Editor
  participant Storage
  User->>Reader: Select or paste image
  Reader-->>Editor: onload(data URL)
  alt Editor already destroyed
    Editor-->>User: Stop without insertion
  else Editor active
    Editor->>Editor: Insert temporary image
    Editor->>Storage: Upload file
    alt Upload fails
      Storage-->>Editor: Reject
      Editor->>Editor: Log error and retain placeholder
    else Upload succeeds
      Storage-->>Editor: Return file path
      alt Editor destroyed during upload
        Editor->>Editor: Log warning and retain placeholder
      else Editor active
        Editor->>Editor: Replace src with proxy URL
        Editor->>Editor: Synchronize serialized HTML
      end
    end
  end
Loading

Reviews (1): Last reviewed commit: "fix(OUT-4154): guard image insert agains..." | Re-trigger Greptile

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

@SandipBajracharya
SandipBajracharya merged commit 4be6d96 into main Sep 4, 2026
8 checks passed
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.

2 participants