fix: stop persisting the editor configuration in localStorage - #613
Merged
Merged
Conversation
XCFramework BuildThis PR's XCFramework is available for testing. Add the following to your .package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/613")Built from 8b27d20 |
dcalhoun
force-pushed
the
task/remove-gbkit-global-from-local-storage
branch
from
September 2, 2026 16:20
b7d738a to
f32075c
Compare
dcalhoun
changed the base branch from
task/stabilize-rest-request-relay
to
trunk
September 22, 2026 15:45
dcalhoun
force-pushed
the
task/remove-gbkit-global-from-local-storage
branch
3 times, most recently
from
September 24, 2026 13:35
1b3b90a to
407fcbc
Compare
dcalhoun
force-pushed
the
task/remove-gbkit-global-from-local-storage
branch
2 times, most recently
from
September 29, 2026 18:37
7cc97cf to
c139012
Compare
dcalhoun
changed the base branch from
trunk
to
fix/autosave-monitor-interval
September 29, 2026 18:37
dcalhoun
added this pull request to stack #745
September 29, 2026 18:38
`getGBKit` fell back to a copy of the configuration in `localStorage`. Boot waits for `window.GBKit` before anything reads the configuration, and outside `?dev_mode` aborts when it never arrives, so the fallback could only ever serve a previous session's values to a dev-mode page with no host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsMCoa3jNEuvnhxicDVRBV
`GBKit` carries the site credential and the local server's port and tokens, all valid only for the load that injected them, and iOS mirrored it into `localStorage`, which the default website data store keeps on disk across launches. The document-start user script replays the global on every navigation, including the reload after a WebContent process termination, so the copy had no reader. Remove the key as the configuration is injected so devices upgraded from an earlier version are scrubbed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsMCoa3jNEuvnhxicDVRBV
`GBKit` carries the site credential and the local server's port and token, all valid only for the load that injected them. The view re-injects the global on every page start and wipes web storage before each load, so the `localStorage` copy had no reader and nothing left to clear on detach. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsMCoa3jNEuvnhxicDVRBV
The CORS rationale on both platforms named `localStorage` alongside `window.GBKit` as where the editor holds the per-session bearer token. The token now lives in the injected global only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsMCoa3jNEuvnhxicDVRBV
The comment justified `*` partly by claiming the editor loads from file:// (Origin null) and so can't be allowlisted. That holds on iOS, but the Android editor has loaded from the site's own origin since #181, where echoing the origin would be perfectly possible. Name the document, not the origin, as what scopes the token: same-origin site pages exist on Android, and it is the per-document JS global that keeps them from reading it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
…script The trailing `"done";` dates from #14, when the configuration was injected with `evaluateJavaScript`, which needed a serializable trailing expression. #15 moved the script to a document-start `WKUserScript`, whose completion value WebKit discards, and the line has been inert since. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
`getGBKit` now ignores any persisted copy, but nothing held that contract in place: the suite only ever exercised it through `window.GBKit`, so restoring the storage fallback would have left every test green. Verified as a tripwire — reinstating the fallback fails the second case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
The iOS comment promised that an upgraded device "is scrubbed" without saying this only happens on the next editor load, and left no signal for when the migration can be dropped. Document `clearConfig` as teardown-only: with the storage fallback gone, `window.GBKit` is the editor's only source of configuration, so clearing it under a live editor now leaves that editor unusable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016zAv5Tqtqr1bcYWVDJHGAh
Both comments claimed no other document can read the editor's global, which overstated the guarantee and disagreed across platforms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEok5vK3cCWGfcQp8zEGWW
dcalhoun
force-pushed
the
task/remove-gbkit-global-from-local-storage
branch
from
September 30, 2026 02:07
c139012 to
8b27d20
Compare
dcalhoun
marked this pull request as ready for review
September 30, 2026 17:12
crazytonyli
approved these changes
Sep 30, 2026
jkmassel
added a commit
that referenced
this pull request
Oct 2, 2026
…g stops `revokeNativeUploadEndpoint()` cleared the endpoint from three places: the live page, the injected user script, and the `localStorage` copy of `GBKit` that `getGBKit()` fell back to. #613 removed that copy — `getGBKit()` reads `window.GBKit` alone, and the document-start script now removes the key so nothing session-scoped persists across launches. Merged together, the revoke no longer cleared anything there. It created the key instead: `JSON.parse(localStorage.getItem('GBKit') || '{}')` found nothing, so it stored `{"nativeUploadPort":null,"nativeUploadToken":null}`. No credential, and the next document start removed it again, but it is a write to storage #613 exists to keep empty, for a reader that is gone. Drop the block. Two copies hold the endpoint now, and the doc says so.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Stop persisting editor configuration in
localStorage.Why?
This fallback storage was originally introduced to survive events like editor reloading. However, it became unnecessary once we ensured the configuration globals were injected for these types of events.
The persisted values could also become stale mid-session—e.g., if the media upload server stopped, the port and token would no longer be valid.
How?
Remove the
localStorageset calls. Clear out existinglocalStoragevalues on editor launch.Testing Instructions
com.apple.WebKit.WebContentfor the Simulator). The editor reloads with the current post. The demo's "Trigger Editor Crash" action is not a substitute here — it throws in JS so the error boundary catches it, leaving the page and its process intact, where this step exercises the reload that replays the document-start script.EditorActivitydeclares noconfigChanges, so it is recreated and the editor reloads — it should render the post's saved title and content rather than the load-error view. Unsaved edits are not preserved across recreation in the demo, whoselatestContentusesremember; that is unrelated to this change.GBKitkey.Accessibility Testing Instructions
N/A, no user-facing changes.
Screenshots or screencast
N/A, no user-facing changes.
AI-generated details
Problem
window.GBKitcarries the site credential and the local server's port and token, all valid only for the load that injected them. Both platforms mirrored it intolocalStorage, which persists on disk across launches, andgetGBKitread that copy back whenever the global was missing.Impact
A copy of the configuration could outlive the session that issued it, and on Android it persisted under the site's own origin rather than an origin only the editor loads. #611 reads the relay's port and token through
getGBKittoo, so landing this first keeps a stale copy from pointing the relay at a stopped server.Nothing needed the copy. The persisted copy arrived with the global in #14, when iOS injected the configuration with
evaluateJavaScriptafter the page had loaded; #15 moved iOS to a document-startWKUserScript, which WebKit replays on every navigation, including the reload after a WebContent process termination. Android re-injects on every page start and wipes web storage before each load. Boot also waits forwindow.GBKitand, outside?dev_mode, aborts when it never arrives, so the fallback was unreachable in production.Mechanism
getGBKitreturnswindow.GBKit || {}; the storage fallback is gone.setItemin its document-startWKUserScriptfor aremoveItem, scrubbing an upgraded device the next time the editor loads. A test pins the script's shape.setItemand the matchingremoveIteminclearConfig, which is now documented as teardown-only sincewindow.GBKitis the editor's only source of configuration.Testing
The
getGBKittest is a tripwire rather than a tautology: verified by reinstating the storage fallback and confirming the case fails.Manual checks, each confirming the editor recovers without a persisted copy:
com.apple.WebKit.WebContentfor the Simulator). The editor reloads with the current post. The demo's "Trigger Editor Crash" action is not a substitute here — it throws in JS so the error boundary catches it, leaving the page and its process intact, where this step exercises the reload that replays the document-start script.Ctrl+F11).EditorActivitydeclares noconfigChanges, so it is recreated and the editor reloads — it should render the post's saved title and content rather than the load-error view. Unsaved edits are not preserved across recreation in the demo, whoselatestContentusesremember; that is unrelated to this change.GBKitkey.🤖 Generated with Claude Code
https://claude.ai/code/session_01JsMCoa3jNEuvnhxicDVRBV