Skip to content

[PARKED] Upgrade block apiVersion from 2 to 3#118

Draft
gsarig wants to merge 16 commits into
masterfrom
feature/upgrade-block-apiversion-from-2-to-3
Draft

[PARKED] Upgrade block apiVersion from 2 to 3#118
gsarig wants to merge 16 commits into
masterfrom
feature/upgrade-block-apiversion-from-2-to-3

Conversation

@gsarig
Copy link
Copy Markdown
Owner

@gsarig gsarig commented Mar 28, 2026

What

Upgrades the block's apiVersion from 2 to 3 in block.json.

Status: Parked — not targeting 2.12.0
This PR is complete and all tests pass, but has been deliberately parked and is not being merged into release/2.12.0. See the decision rationale below.

Brief

apiVersion 3 was introduced in WordPress 6.3. The key difference: the block editor canvas is now rendered inside a blob: URL iframe. The block's JavaScript (including Leaflet and React) continues to run in the parent frame's execution context — not inside the iframe.

For this block, three regressions were introduced and fixed:

HTML5 DnD hijack — useBlockProps sets draggable="true" on the block wrapper in the iframe DOM. Fixed by cancelling dragstart in capture phase before Gutenberg's block-drag handler.

Coordinate mismatch (drag jump) — Gutenberg's useBubbleEvents adds the iframe's getBoundingClientRect() offset to forwarded mousemove events, but Leaflet's _startPoint is captured from the native mousedown in iframe-relative coords. Fixed by correcting _startPoint after Leaflet's _onDown runs.

mouseup not forwarded — Gutenberg's useBubbleEvents does not forward mouseup. Fixed by manually forwarding it from the iframe document to the parent document so Leaflet's drag _onUp handler fires.

Additionally: tile elements are inserted into the iframe DOM, so the browser sends no Referer for tile requests (blob: URL context). Fixed by injecting into the iframe . This resolves the 403 on production; on localhost, OSM's own policy blocks localhost origins regardless.

Why it is parked

The fix for regression #2 accesses Leaflet's private internal API:

map.dragging._draggable._startPoint.x += rect.left;
This is a documented fragile point: any Leaflet version that renames or restructures _draggable._startPoint would silently reintroduce the drag-jump regression with no error. The coordinate correction is also coupled to exactly how Gutenberg's useBubbleEvents translates mouse coordinates — a change in that mechanism would require revisiting the fix.

Meanwhile, apiVersion 2 has no announced deprecation timeline. WordPress's backward compatibility commitments are strong and apiVersion 2 blocks are expected to keep working for the foreseeable future.

Forcing functions to revisit

Pick this up when one of the following occurs:

The pending react-leaflet upgrade (currently blocked by @wordpress/scripts Node constraints) lands — at that point, verify _draggable._startPoint still exists at that path in the new Leaflet version and merge together
WordPress announces a deprecation notice for apiVersion 2 (editor warnings, etc.)
A future WordPress release changes useBubbleEvents in a way that breaks the current fixes anyway

What is on the branch

src/block/block.json — "apiVersion": 2 → 3
src/block/Elements/MapEvents.js — iframe drag fixes + referrer meta tag injection
src/block/Elements/TileProvider.js — removed non-functional referrerPolicy="origin" prop (Leaflet ignores it)
tests/playwright/block-editor.spec.ts — new editor E2E tests, updated for apiVersion 3 iframe architecture
CLAUDE.md + docs/tests/README.md — documented the localhost OSM tile 403 limitation
All tests pass: lint ✓ · PHPUnit ✓ · Playwright 6/7 ✓ (1 skipped: Mapbox, no key)


Old Brief

What

enables the latest block lifecycle guarantees; needs its own PR with a targeted regression test run against saved block content

Brief

Source: current src/block/block.json has "apiVersion": 2

apiVersion 3 was introduced in WordPress 6.3. The key difference: the block wrapper
element (<div class="wp-block-…">) is now rendered by the editor automatically via
useBlockProps, so the save function should not add the outer element itself. For
blocks with render_callback (server-side rendering), the save function already
returns null — which means the wrapper is currently not in saved post content and the
upgrade path is simpler than for static-save blocks.

Rough scope:

  • Update "apiVersion": 2 to "apiVersion": 3 in block.json
  • Verify the editor's edit function uses useBlockProps correctly (it should already)
  • Verify the save function returns null (server-rendered block — it should)
  • Run snapshot tests and Playwright E2E to confirm no regressions in rendered output or
    block editor behaviour
  • Check that any existing saved blocks load without a block validation error in the editor

Known risk: If any legacy saved blocks have a wrapper div written by the save function
from a prior version, upgrading apiVersion can cause block validation failures on those
posts. Check the save function history to determine whether this has ever not been null.

This is a contained change if the save function is confirmed as null. Give it its own
PR so the diff is reviewable in isolation.

Risk: Low if save returns null (server-rendered). Medium if any static-save era exists
in the block's history — snapshot tests and a manual editor check will surface this.


Planning proposal: 2026-03-28 · Task 8

@gsarig gsarig added this to the 2.12.0 milestone Mar 28, 2026
@gsarig gsarig added the enhancement New feature or request label Mar 28, 2026
gsarig and others added 14 commits March 28, 2026 13:36
* Register ability category for Abilities API compliance

* Remove unintended Kratt-specific meta block from ability registration

* Add description to ability category registration

* Fire wp_abilities_api_categories_init before abilities in test

* Clarify lat/lng and markers descriptions to prevent misuse by ability consumers
* Add unit tests for createMapboxStyleUrl and wire up test:unit script

* Add Playwright test for Mapbox tile loading

* Wire up MAPBOX_ACCESS_TOKEN for local .env and CI secret

* Fix playwright target to source .env before running setup

* Fix URL host check in Mapbox test to use hostname comparison

* Add test:unit to CI and disable Playwright traces to prevent token leaking
- Include src/block/block.json in the artifact-playwright version
  consistency check so all four version locations are validated
- Use ${RSYNC_EXCLUDE:+"$RSYNC_EXCLUDE"} in build-zip.sh to prevent
  word splitting if the variable contains spaces
* Harden OpenAI REST endpoint validation and error handling

* Add prompt length constraints and rename response body variable

- Add minLength/maxLength to the prompt REST arg schema to reject empty or
  overly-large inputs before the callback runs
- Rename $body to $response_body when retrieving the AI API response to
  avoid shadowing the outbound request payload variable

* Use get_param() for validated prompt arg and check upstream HTTP status

- Replace get_json_params()['prompt'] with get_param('prompt') so the
  validated/sanitized route arg is used regardless of how the client
  encodes the request body
- Check wp_remote_retrieve_response_code() before decoding the response
  so non-2xx upstream errors are returned as WP_Error 502 rather than
  silently forwarded as 200 OK
* Fix JS error handling, race condition, and edge cases

- Add .catch() and response.ok check to both SearchBox fetch calls
- Wrap renderMap in try/catch so one corrupt map doesn't break the page
- Return fetch promise from openai findMarkers to fix race condition
- Fix centerMap returning [undefined, undefined] for empty markers
- Replace for...in with findIndex in getMarkerIndex

* Normalize parsed AI response to array before mapping

JSON.parse(resultsRaw) may return a non-array value if the AI provider
returns an unexpected shape. Guard against this so .map() never throws
and Promise.all always receives a valid array.

* Guard Promise.all cleanup against empty results and marker fetch errors

* Skip remaining marker tasks once a fetch error has occurred

Once markerError is set, already-scheduled timeout callbacks can still
call findMarkers and have addMarker overwrite the error state with
'working'. Guard each task callback so it resolves immediately when
markerError is true, preventing later successful markers from clobbering
the error UI before its 3-second timeout fires.

* Guard addMarker against in-flight responses after a marker error

The markerError check before each task prevents new fetches from
starting, but a concurrent in-flight request can still resolve and
call addMarker after markerError is set. Skip addMarker when
markerError is already true so a racing success can't overwrite
the error UI state.

* Guard addMarker against empty Nominatim responses

Nominatim can return an empty array for an unknown place, which would
pass undefined to addMarker and either throw or silently produce a
broken marker. Only call addMarker when data is a non-empty array.

* Clear error state after JSON parse failure

The catch block for JSON.parse errors set openAImode to 'error' but
never scheduled a reset, leaving the error alert stuck indefinitely.
Add the same 3-second timeout cleanup used by the other error branches.
* Start: Consolidate Dependabot dependency bumps into a single PR

* Consolidate 14 Dependabot dependency bumps into a single PR

- npm: flatted 3.4.1→3.4.2, yaml 1.10.2→1.10.3, picomatch 2.3.1→2.3.2
  (4.0.3→4.0.4 in nested jest/tinyglobby entries)
- composer: phpstan/phpstan 2.1.40→2.1.44
- All other bumps (braces, form-data, tar-fs, on-headers, compression,
  playwright, js-yaml, preact, diff, lodash, lodash-es) were already at
  or above the Dependabot target versions after merging release/2.12.0
* Start: Add WordPress Playground blueprint

* Add WordPress Playground blueprint for live plugin preview

Adds .wordpress-org/blueprints/blueprint.json so the WordPress.org
plugin page shows a "Preview" button that launches a Playground instance
with the plugin installed and a demo map page open in the block editor.

The blueprint installs the plugin, then writes and navigates to a PHP
setup script that creates an "OpenStreetMap Demo" page with three
pre-configured markers (Athens, Thessaloniki, Heraklion) at zoom 7,
then redirects directly into the block editor.

The setup script mirrors save.js precisely — plugin_dir_url(), the same
JSON encoding flags, and the getBoundsCenter arithmetic — so the block
passes editor validation without a recovery prompt.

* Remove invalid activate property from installPlugin step

* Fix marker shape: use text/textRaw instead of title/content/icon

* Add blueprint lint: schema property check and PHP syntax validation

* Add blueprint validation to js-build CI job

* Skip PHP lint in validate-blueprint.js when php is not in PATH
…rt search (#117)

* Start: WordPress 7.0 compatibility + WordPress AI Client integration for smart search

* Add WordPress 7.0 compatibility and WP AI Client integration for smart search

- Bump "Tested up to" to 7.0 in readme.txt
- Route smart search through wp_ai_client_prompt() when available and no plugin API key is set; plugin key always wins (backward compat)
- Show a settings notice when a site-level AI connector is detected
- Add PHPStan stub for wp_ai_client_prompt() so static analysis passes

* Add integration tests for OpenAI callback routing and WP AI Client notice

* Update documentation to mention WP 7.0 site-level AI connector

* Differentiate connector notice when plugin API key is also set

* Fix missing HTTP status in openai_error and pass through upstream status from WP AI Client errors

* Add rules for REST WP_Error status and Copilot filter callback false positive

* Fix stub detection and add priority-3 test with process isolation

* Add Copilot rule: Tested up to belongs only in readme.txt, not plugin PHP header

* Wrap pre_http_request filter in try/finally to prevent test pollution on assertion failure
- Bump apiVersion from 2 to 3 in block.json
- Add referrerPolicy="origin" to TileLayer to fix 403 Referer errors on
  OSM tiles when the block renders inside Gutenberg's editor iframe
  (blob: URLs carry no Referer header by default)
- Add useEffect in MapEvents.js to handle three apiVersion 3 iframe quirks:
  1. HTML5 DnD: useBlockProps sets draggable="true" on the block wrapper;
     cancel dragstart in capture phase before Gutenberg's block-drag handler
     can reposition the DOM
  2. Coordinate mismatch: Gutenberg's useBubbleEvents translates forwarded
     mousemove coords from iframe-relative to parent-relative; Leaflet's
     _startPoint is captured from the native mousedown in iframe coords;
     correct _startPoint after Leaflet's _onDown runs so both coordinate
     systems match
  3. mouseup not forwarded: forward mouseup from iframeDoc to parent document
     so Leaflet's drag _onUp handler fires correctly
- Remove non-functional referrerPolicy=origin from TileProvider.js
  (Leaflet ignores this prop; it is not applied to the img elements
  TileLayer creates, so it was silently doing nothing)
- Fix block-editor.spec.ts for apiVersion 3: the editor canvas is now
  rendered inside an iframe, so block-content locators must go through
  frameLocator(); also update beforeEach to check a parent-document
  element instead of .block-editor-writing-flow which is inside the iframe

Made-with: Cursor
The button label changed between WP 6.6 (Toggle block inserter) and
WP 7.0+ (Block Inserter). Use the same dual-selector already used in
the insertBlock helper so the beforeEach check passes on both versions.

Made-with: Cursor
In apiVersion 3 the block editor canvas is a blob: URL iframe. Tile
images are inserted into that iframe's DOM, so the browser uses the
iframe document URL when determining the Referer header for tile
requests. Blob: URLs carry no Referer by default, causing OSM tile
servers to return 403.

Inject <meta name=referrer content=origin> into the iframe head
so the browser sends the page's real origin (https://example.com) as
the Referer. On localhost the origin is http://localhost which OSM
also rejects — that is an OSM policy limitation, not a code issue.

Made-with: Cursor
Add an Architecture Notes entry to CLAUDE.md explaining the OSM tile
403 on localhost: cause, the meta referrer fix, why production works,
and why localhost will always 403 (OSM policy). Add a matching Known
local limitations section to docs/tests/README.md for developers
running the editor locally.

Made-with: Cursor
@gsarig gsarig removed this from the 2.12.0 milestone Mar 29, 2026
@gsarig gsarig changed the title Upgrade block apiVersion from 2 to 3 [PARKED] Upgrade block apiVersion from 2 to 3 Mar 29, 2026
@gsarig gsarig changed the base branch from release/2.12.0 to master March 29, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant