Skip to content

feat: surface editor crashes natively and offer a reload - #642

Draft
dcalhoun wants to merge 2 commits into
fix/android-reset-readiness-on-editor-crashfrom
fix/surface-editor-crash-and-offer-reload
Draft

feat: surface editor crashes natively and offer a reload#642
dcalhoun wants to merge 2 commits into
fix/android-reset-readiness-on-editor-crashfrom
fix/surface-editor-crash-and-offer-reload

Conversation

@dcalhoun

@dcalhoun dcalhoun commented Sep 9, 2026

Copy link
Copy Markdown
Member

What?

A crashed editor left Gutenberg's own error fallback on screen, and the user had no way back other than closing the post. GutenbergKit now covers the editor with a native notice and offers to reload.

Why?

Fix the second half of CMM-2008. #638 and #640 stop the bridge failures; this makes the resulting state legible and recoverable.

Gutenberg's fallback is built for the desktop editor and neither of its actions helps on a phone:

  • "Copy contents" reads the post through getEditedPostContent() at click time, but the boundary sits above EditorProvider, whose unmount runs setEditedPost( null, null ). It always copies an empty string.
  • "Copy error" hands the user a stack trace.

GutenbergKit already owns the equivalent UI for the states either side of this one — displayError / EditorErrorView for load failures, progress and spinner views for loading. GutenbergView's own documentation states the contract: "This view manages its own loading UI internally… Consumers do not need to implement loading UI." A crash is the same kind of state, so it belongs here rather than being rebuilt in every host.

Native rather than web, specifically: the web fallback is rendered by a React tree that just crashed, and anything built there has to survive the same unmount that broke the copy button. The native layer is healthy by definition and, after #638/#640, knows authoritatively.

How?

  • iOSContentUnavailableView covering the editor, matching displayError, plus a public reloadEditor().
  • Android — the same, reusing EditorErrorView, which gains an optional action button. reloadEditor() clears didFireEditorLoaded so the reloaded editor re-announces itself, and returns to the spinner until it does.
  • JS — drop canCopyContent, removing the button that never worked.

Reload restores content from the host's editorDidRequestLatestContent / LatestContentProvider, so work up to the host's last autosave survives. Readiness is restored only when onEditorLoaded arrives again, so bridge calls stay refused until the editor is genuinely usable.

The description makes no promise about saved work

The copy reads "Reload the editor to continue editing." and deliberately stops there.

Whether anything was persisted is entirely the host's business. WordPress-iOS mirrors content to Core Data about a second after typing stops; CustomPostEditorViewController and the comment editor have no mirror at all and read content only when the user saves. GutenbergKit cannot tell which it is embedded in, so it must not claim the work is safe — on the unmirrored hosts that would be false, and would encourage someone to close a post and lose the lot.

Hosts that do mirror content should override editorCrashedDescription (iOS) / gbk_editor_crashed_description (Android) to say so. Both are annotated with why.

Reload is user-initiated, not automatic

An automatic retry would recover transient crashes invisibly, but risks a loop when the crash comes from the post's own content, and silently discards anything since the last autosave. An explicit button keeps the user in control and matches how displayError already behaves. Worth revisiting if telemetry shows most crashes are transient.

Note for review

GutenbergViewTest drops manifest = Config.NONE and the module opts into isIncludeAndroidResources, because Robolectric needs the merged resources to construct the error view now that it reads a string resource. The pre-existing HttpServerAuthenticationTests failures are unrelated — they fail identically on clean trunk here (a local proxy returning 407).

Testing Instructions

Trip the boundary from the web inspector (iOS) or chrome://inspect (Android), on a post with at least one block:

const be = wp.data.select( 'core/block-editor' );
be.getBlockOrder = () => { throw new Error( 'repro' ); };
wp.data.dispatch( 'core/block-editor' ).updateSettings( {} );
  1. Confirm the editor is covered by "The editor stopped working" with a Reload Editor button — not Gutenberg's "Copy contents / Copy error" notice.
  2. Tap Reload Editor. The editor reloads and content up to the host's last autosave is intact.
  3. Confirm editing works normally after the reload — typing, undo/redo, block insertion, save.
  4. Without triggering a crash, confirm loading, load failures, and normal editing are unchanged.

Unit tests: ./android/gradlew -p ./android :Gutenberg:testDebugUnitTest --tests "org.wordpress.gutenberg.GutenbergViewTest"

Accessibility Testing Instructions

The crash view is standard ContentUnavailableView / TextView + Button content, so VoiceOver and TalkBack read the title, description, and button label, and the button is reachable by keyboard and switch control. Worth confirming the reload button is announced with its label and that focus moves into the notice when it appears.

Screenshots or screencast

To add — before/after of the crash state on both platforms.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VuxMbKtUsaUF8nUVgKdxwK

dcalhoun and others added 2 commits September 9, 2026 12:32
A crashed editor left Gutenberg's own fallback on screen — a desktop notice
offering to copy the post contents and a stack trace. Neither helps here: the
copy button reads the post through `getEditedPostContent()` at click time, and
the boundary sits above `EditorProvider`, whose unmount clears the post
pointer, so it always copies an empty string.

Cover the editor with a `ContentUnavailableView`, matching the existing
`displayError` treatment for load failures, and offer `reloadEditor()`. The
reloaded editor starts from whatever the host returns from
`editorDidRequestLatestContent`, so work up to the host's last autosave
survives, and readiness is restored only once `onEditorLoaded` arrives again.

The copy button is dropped rather than kept alongside the native UI, since it
never worked.

The description deliberately makes no claim about saved work. Whether anything
was persisted is the host's business — some mirror content continuously,
others read it only when the user saves — so a host that does mirror should
override that key to reassure the user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuxMbKtUsaUF8nUVgKdxwK
Mirrors the iOS treatment: cover the editor with a notice offering to reload,
rather than leaving Gutenberg's desktop fallback on screen.

`EditorErrorView` gains an optional action button so the crash state can offer
`reloadEditor()`, which clears `didFireEditorLoaded` so the reloaded editor
re-announces itself, and returns to the spinner until it does.

The description deliberately makes no claim about saved work, since whether
anything was persisted is the host's business.

Robolectric needs the merged resources to construct the error view now that it
reads a string resource, so unit tests opt into Android resources and
`GutenbergViewTest` drops `manifest = Config.NONE`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuxMbKtUsaUF8nUVgKdxwK
@dcalhoun
dcalhoun added this pull request to stack #641 September 9, 2026 16:33
@dcalhoun dcalhoun changed the title fix/surface editor crash and offer reload feat: surface editor crashes natively and offer a reload Sep 9, 2026
@github-actions github-actions Bot added the [Type] Enhancement A suggestion for improvement. label Sep 9, 2026
@wpmobilebot

Copy link
Copy Markdown

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/642")

Built from 6ccfb7b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants