Skip to content

feat: add real editor-driven save post mechanism#445

Draft
jkmassel wants to merge 8 commits intotrunkfrom
jkmassel/real-save-post
Draft

feat: add real editor-driven save post mechanism#445
jkmassel wants to merge 8 commits intotrunkfrom
jkmassel/real-save-post

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Apr 9, 2026

Summary

Adds a window.editor.savePost() bridge method that delegates to Gutenberg's native savePost() action, replacing the previous fake save approach. This enables real POST/PUT persistence through the WordPress REST API from both iOS and Android host apps.

  • JS bridge: savePost() captures the server-assigned ID for new posts via __unstableFetch, records it as an entity edit (avoiding setEditedPost which causes title loss), and returns the saved entity to the native host
  • Pre-save hydration: editor.preSavePost filter round-trips the entity through the native host so it can inject metadata (categories, tags, featured media) before the REST request is sent. Meta fields are stripped to avoid Foundation↔JSON type coercion issues (e.g. integer meta returned as doubles)
  • CORS fix: Wraps globalThis.fetch to undo httpV1Middleware's X-HTTP-Method-Override header, which isn't in the CORS allow-list for WordPress.com. The middleware stays in the chain for servers that need it — the fix only strips the header at the network boundary
  • Save availability: useSyncSaveAvailability hook dispatches bridge events so native hosts can enable/disable the save button based on editor state
  • iOS/Android: Both platforms wire up savePost callbacks, save availability listeners, and latest-post/content providers in the demo apps

Test plan

  • Create a new post on iOS, add title + content, save — verify POST is sent, title remains visible, subsequent saves use PUT
  • Same flow on Android
  • Edit an existing post and save on both platforms
  • Verify no CORS errors in console on WordPress.com sites
  • Run npx vitest run src/components/editor/test/use-host-bridge.test.jsx — 26 tests pass
  • Run E2E save-post spec against wp-env

jkmassel added 3 commits April 8, 2026 21:06
Add `window.editor.savePost()` that delegates to Gutenberg's internal
save flow and returns the saved entity to the native host. For new posts
(id <= 0), a `__unstableFetch` wrapper captures the server-assigned ID
so the editor store can be switched to the created entity via
`setEditedPost`, preventing duplicate POST requests on subsequent saves.

A pre-save filter (`editor.preSavePost`) calls `hydratePost` to let the
native host enrich the entity record (e.g. categories, tags changed in
native UI) before the PUT is sent. Meta fields are stripped from the
hydrated response to avoid REST API type-validation failures caused by
the Foundation↔JSON round-trip.

Also adds `hydratePost` bridge method, allows write requests through
`filterEndpointsMiddleware`, and wires up `useSyncSaveAvailability` to
notify the native host of save-state changes.
Unit tests cover the new savePost bridge method including:
- __unstableFetch usage for new posts and setEditedPost with created ID
- No setEditedPost call for existing posts
- Pre-save filter hydration, meta stripping, and id stripping for new posts
- Save error surfacing via didPostSaveRequestFail

E2E test verifies the full POST→PUT flow: first save creates (POST),
editor store updates to server-assigned ID, second save updates (PUT)
to the correct endpoint. Uses X-HTTP-Method-Override header detection
since Gutenberg's httpV1Middleware converts PUT to POST.
Add `savePost()` method that calls the JS bridge and decodes the
returned entity into `EditorPost`. Introduces `EditorPersistenceDelegate`
protocol with `willSavePost` (pre-save hydration), `didSavePost`, and
`didFailToSavePost` callbacks. Registers `hydratePost` and
`requestLatestContent` WKWebView message handlers for the native↔JS
bridge communication.
@github-actions github-actions bot added the [Type] Enhancement A suggestion for improvement. label Apr 9, 2026
@jkmassel jkmassel force-pushed the jkmassel/real-save-post branch from db4971d to dab4ab8 Compare April 9, 2026 16:07
jkmassel added 5 commits April 9, 2026 10:59
Add `savePost()` method, `EditorSaveListener` interface with pre/post
save callbacks, and `hydratePost` / `requestLatestContent` JS interface
methods for the Android↔JS bridge. Include `restBase` and
`restNamespace` in the GBKit post payload.
…eware filtering

Call finishResolution('getEntityRecord', ...) after receiveEntityRecords in
useEditorSetup so Gutenberg's data layer knows the entity is already resolved.
This eliminates the need for filterEndpointsMiddleware, which previously blocked
the refetch by intercepting GET requests to the post endpoint.

Closes #434
Move the X-HTTP-Method-Override header stripping from a standalone
function in api-fetch.js into the existing fetch interceptor wrapper.
The interceptor now always wraps window.fetch (even when network
logging is disabled) so the CORS fix applies universally.
@jkmassel jkmassel force-pushed the jkmassel/real-save-post branch from 89fb199 to 7c3310a Compare April 9, 2026 16:59
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.

1 participant