Skip to content

fix: normalize data store attributes to plain strings in JS bridge#429

Draft
jkmassel wants to merge 1 commit intotrunkfrom
jkmassel/fix-title-object-unwrap
Draft

fix: normalize data store attributes to plain strings in JS bridge#429
jkmassel wants to merge 1 commit intotrunkfrom
jkmassel/fix-title-object-unwrap

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Apr 6, 2026

Summary

Fixes a bug where editor.getTitleAndContent() could return JavaScript objects instead of plain strings to the native host app, corrupting the content in the host app.

Root Cause

WordPress's getEditedPostContent() selector uses getEditedEntityRecord() internally, which preserves the raw entity record shape. When a post is loaded, title and content are stored as { raw: "..." } objects in the entity record. Most selectors (like getEditedPostAttribute('title')) go through getRawEntityRecord(), which extracts .raw automatically. But getEditedPostContent() does not — it hits a fallback path (else if (record.content) { return record.content; }) that returns the { raw } wrapper object as-is.

This means:

  • Title: getEditedPostAttribute('title') → already returns a plain string (goes through getRawEntityRecord + getPostRawValue)
  • Content: getEditedPostContent() → may return { raw: "..." } object (goes through getEditedEntityRecord, which does not unwrap)

The { raw } object would then cross the JS-to-native bridge and arrive as a dictionary/map where the native side expected a string.

What We Explored

1. Making native types match the {raw, rendered} shape

We initially updated iOS (EditorTitle struct) and Android (TitleValue data class) to parse {raw, rendered} objects for both title and content. This worked mechanically, but:

  • rendered is useless client-side: It's only available from the server's initial REST API response. Once the user edits a field, the data store replaces the object with a plain string, losing rendered. The server-side rendering pipeline (render_block(), the_title filters) cannot be reproduced in the client.
  • rendered was identical to raw for content: getEditedPostContent() returns serialized block markup — there's no separate rendered form.
  • The {raw, rendered} shape only appeared in the exact states that triggered the bug — making it doubly useless to expose to the host app.

2. Hoisting the {raw, rendered} wrapper into native input types

We considered having the native EditorConfiguration accept {raw, rendered} objects (matching the WP REST API shape) instead of plain strings, so the JS bridge wouldn't need to wrap/unwrap. But:

  • The data store replaces {raw, rendered} with a plain string after any edit — so the output type would still be inconsistent regardless of input.
  • We'd still need output normalization, making the input change pure overhead.
  • It would couple the library's public API to the WordPress REST API response format.

3. Using getEditedPostAttribute('content') instead of getEditedPostContent()

getEditedPostAttribute goes through getRawEntityRecord which extracts .raw. But for 'content' specifically, it delegates to getEditedPostContent() — same code path, same bug.

Fix

Add a normalizeAttribute() function at the JS bridge boundary that extracts .raw from objects or passes strings through unchanged. Applied to getTitleAndContent() for both title and content.

Also removed the redundant getContent() bridge method (and its iOS public API) — getTitleAndContent() is now the single accessor for reading editor state.

Changes

  • src/components/editor/use-host-bridge.js: Add normalizeAttribute(), apply to getTitleAndContent(), remove getContent()
  • src/components/editor/test/use-host-bridge.test.jsx: Add unit tests for normalization with proper mocks
  • e2e/get-title-and-content.spec.js: New E2E test covering initial load (the bug case), post-edit, and empty state
  • e2e/editor-page.js: Add getTitleAndContent() helper to page object
  • ios/.../EditorViewController.swift: Remove getContent() public API
  • ios/.../EditorViewControllerDelegate.swift: Update doc reference

Test Plan

  • JS unit tests pass (115 tests)
  • ESLint passes
  • iOS Swift package tests pass
  • E2E tests pass — including new get-title-and-content.spec.js that verifies plain strings are returned before edits (the bug case), after title edits, after content edits, and with empty initial state

@github-actions github-actions bot added the [Type] Bug An existing feature does not function as intended label Apr 6, 2026
@jkmassel jkmassel changed the title fix: unwrap title object in getTitleAndContent bridge method fix: return title as {raw, rendered} object from getTitleAndContent Apr 6, 2026
@jkmassel jkmassel force-pushed the jkmassel/fix-title-object-unwrap branch 7 times, most recently from 974d1bb to abb6056 Compare April 7, 2026 00:01
The WordPress data store's `getEditedPostContent()` may return
`{raw, rendered}` objects instead of plain strings because it uses
`getEditedEntityRecord` (which preserves the object shape) rather than
`getRawEntityRecord` (which extracts `.raw`).

Add `normalizeAttribute()` to always extract the raw string before
returning values to the native host via `getTitleAndContent()`.

Also remove the redundant `getContent()` bridge method and its iOS
public API — `getTitleAndContent()` is the single accessor for editor
state.
@jkmassel jkmassel force-pushed the jkmassel/fix-title-object-unwrap branch from abb6056 to 9c68308 Compare April 7, 2026 00:06
@jkmassel jkmassel changed the title fix: return title as {raw, rendered} object from getTitleAndContent fix: normalize data store attributes to plain strings in JS bridge Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant