Skip to content

refactor(js): harden native inserter pattern payload shape#467

Merged
jkmassel merged 2 commits into
trunkfrom
jkmassel/native-inserter-pattern-shape
Apr 27, 2026
Merged

refactor(js): harden native inserter pattern payload shape#467
jkmassel merged 2 commits into
trunkfrom
jkmassel/native-inserter-pattern-shape

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented Apr 23, 2026

Summary

  • Extracts inline pattern/pattern-category serialization from NativeBlockInserterButton into named helpers in src/utils/blocks.js.
  • Flips array fields (blockTypes, categories, keywords) from ?? null to ?? [] to match the non-nullable List<String> contract in the Kotlin BlockPattern model (feat(android): add block inserter payload model #465). Emitting null there would throw SerializationException on Android.
  • Adds a JSON Schema at schemas/block-inserter-payload.schema.json that mirrors the Kotlin BlockInserterPayload contract, plus unit tests that validate the formatters against it via ajv.

Dependencies

Targets trunk directly. Self-contained — all files it touches exist on trunk and #465 (the Kotlin BlockInserterPayload model the schema mirrors) has merged.

Landing this early rather than stacked on #461 is deliberate: the formatter fix and shape-validating tests act as a defensive guardrail, so when the Android native inserter (#461) flips to ready nothing lands on trunk already broken. The ?? [] path has no consumer until #461 lands — technically dead code today — but the cost of landing early is low and the payoff (no broken-on-trunk intermediate state) is real.

Root Cause

NativeBlockInserterButton produced pattern.blockTypes ?? null, pattern.categories ?? null, and pattern.keywords ?? null. The Kotlin model declares those fields as non-nullable List<String> with emptyList() defaults, which means null on the wire fails strict deserialization. The bug only surfaces once the bridge is wired up end-to-end, so the shape tests here are the earliest place to catch it.

Fix

  1. src/utils/blocks.js: Add formatPatternsForNativeInserter and formatPatternCategoriesForNativeInserter. Array fields default to []; nullable scalars (description, source, viewportWidth) stay null.
  2. src/components/native-inserter/index.jsx: Replace ~20 lines of inline mapping with two function calls.
  3. schemas/block-inserter-payload.schema.json: Standalone JSON Schema (draft-07) with $id, field-level descriptions pulled from the Kotlin doc comments, and additionalProperties: false on every definition. Consumable by cross-language tooling (quicktype, kotlinx-serialization codegen) alongside the hand-written Kotlin BlockInserterPayload.
  4. src/utils/blocks.test.js: Imports the schema and validates payloads via ajv. When either side drifts — wrong type, missing field, or unknown field — the tests fail loudly with a JSON-Pointer path instead of failing silently at bridge-deserialization time. Upstream Gutenberg ships no JSON Schema for these shapes (only JSDoc typedefs on WPEditorInserterItem / WPBlockType), so the schema is hand-written but tracks the Kotlin model as the source of truth.
  5. package.json: Adds ajv as a direct devDependency (was transitive).

Test plan

  • npx vitest run src/utils/blocks.test.js — 17 tests pass
  • make lint-js-fix — clean

@github-actions github-actions Bot added the [Type] Task Issues or PRs that have been broken down into an individual action to take label Apr 23, 2026
@jkmassel jkmassel force-pushed the jkmassel/native-inserter-pattern-shape branch from 039d75c to 9dd9daa Compare April 23, 2026 17:35
@jkmassel jkmassel force-pushed the jkmassel/android-block-picker branch from 88d07a8 to 932e7ed Compare April 24, 2026 16:48
@jkmassel jkmassel force-pushed the jkmassel/native-inserter-pattern-shape branch from 9dd9daa to b225bcf Compare April 24, 2026 16:50
@jkmassel jkmassel force-pushed the jkmassel/native-inserter-pattern-shape branch from b225bcf to 85d870d Compare April 24, 2026 16:59
@jkmassel jkmassel changed the base branch from jkmassel/android-block-picker to trunk April 24, 2026 16:59
@jkmassel jkmassel marked this pull request as ready for review April 24, 2026 16:59
@jkmassel jkmassel force-pushed the jkmassel/native-inserter-pattern-shape branch from 85d870d to 4f01de9 Compare April 24, 2026 21:09
@jkmassel
Copy link
Copy Markdown
Contributor Author

e2e test failures will be resolved by #473

@jkmassel jkmassel requested a review from oguzkocer April 24, 2026 22:02
Pulls the inline pattern/pattern-category serialization in
`NativeBlockInserterButton` into named helpers in `src/utils/blocks.js`
and flips array fields (`blockTypes`, `categories`, `keywords`) from
`?? null` to `?? []` so the JSON shape matches the non-nullable
`List<String>` contract in the Kotlin `BlockPattern` model. Emitting
`null` for those fields would throw `SerializationException` on the
Android side.
Adds `schemas/block-inserter-payload.schema.json` mirroring the Kotlin
`BlockInserterPayload` contract at
android/Gutenberg/src/main/java/org/wordpress/gutenberg/model/BlockInserter.kt,
plus `src/utils/blocks.test.js` which validates the output of
`preprocessBlockTypesForNativeInserter`, `formatPatternsForNativeInserter`,
and `formatPatternCategoriesForNativeInserter` against it via `ajv`.

The schema is draft-07 with `additionalProperties: false` on every
definition, and is co-located at the repo root so it can be consumed by
cross-language tooling (`quicktype`, `kotlinx-serialization` codegen)
alongside the hand-written Kotlin model. Upstream Gutenberg ships no
JSON Schema for these shapes (only JSDoc typedefs on
`WPEditorInserterItem` / `WPBlockType`), so the schema is hand-written
and tracks the Kotlin model as the source of truth.

Catches the null-vs-array drift that motivated #465, plus drift in
required fields, types, and unknown-field leaks that Kotlin's
`ignoreUnknownKeys = true` would otherwise silently tolerate.

Adds `ajv` as a direct devDependency (was transitive).
@jkmassel jkmassel force-pushed the jkmassel/native-inserter-pattern-shape branch from 4f01de9 to 436b233 Compare April 27, 2026 19:11
@jkmassel jkmassel merged commit bfc85d9 into trunk Apr 27, 2026
22 checks passed
@jkmassel jkmassel deleted the jkmassel/native-inserter-pattern-shape branch April 27, 2026 20:15
jkmassel added a commit that referenced this pull request Apr 28, 2026
* refactor(js): extract native inserter pattern formatters

Pulls the inline pattern/pattern-category serialization in
`NativeBlockInserterButton` into named helpers in `src/utils/blocks.js`
and flips array fields (`blockTypes`, `categories`, `keywords`) from
`?? null` to `?? []` so the JSON shape matches the non-nullable
`List<String>` contract in the Kotlin `BlockPattern` model. Emitting
`null` for those fields would throw `SerializationException` on the
Android side.

* test(js): validate native inserter payload against JSON Schema

Adds `schemas/block-inserter-payload.schema.json` mirroring the Kotlin
`BlockInserterPayload` contract at
android/Gutenberg/src/main/java/org/wordpress/gutenberg/model/BlockInserter.kt,
plus `src/utils/blocks.test.js` which validates the output of
`preprocessBlockTypesForNativeInserter`, `formatPatternsForNativeInserter`,
and `formatPatternCategoriesForNativeInserter` against it via `ajv`.

The schema is draft-07 with `additionalProperties: false` on every
definition, and is co-located at the repo root so it can be consumed by
cross-language tooling (`quicktype`, `kotlinx-serialization` codegen)
alongside the hand-written Kotlin model. Upstream Gutenberg ships no
JSON Schema for these shapes (only JSDoc typedefs on
`WPEditorInserterItem` / `WPBlockType`), so the schema is hand-written
and tracks the Kotlin model as the source of truth.

Catches the null-vs-array drift that motivated #465, plus drift in
required fields, types, and unknown-field leaks that Kotlin's
`ignoreUnknownKeys = true` would otherwise silently tolerate.

Adds `ajv` as a direct devDependency (was transitive).
jkmassel added a commit that referenced this pull request Apr 29, 2026
#468 added `iconForeground: getBlockIconForeground(item)` to the block
payload, but the schema (added in #467, in trunk) still has
`additionalProperties: false` on `BlockType` with no `iconForeground`
entry. Trunk doesn't emit the field so trunk CI is fine; this branch
carries both pieces and AJV rejects the payload.

Mirrors the existing `icon` entry: nullable string.
jkmassel added a commit that referenced this pull request May 12, 2026
)

* fix(js): add iconForeground to block-inserter payload schema

#468 added `iconForeground: getBlockIconForeground(item)` to the block
payload, but the schema (added in #467, in trunk) still has
`additionalProperties: false` on `BlockType` with no `iconForeground`
entry. Trunk doesn't emit the field so trunk CI is fine; this branch
carries both pieces and AJV rejects the payload.

Mirrors the existing `icon` entry: nullable string.

* test(js): read AJV dataPath alongside instancePath in shape helper

Resolved AJV in node_modules is 6.12.6 (despite package.json declaring
^8.18.0 — pulled in transitively). AJV 6 surfaces the failing field as
`dataPath`; AJV 7+ uses `instancePath`. The helper only read
`instancePath`, so error messages came back as `/: should be array`
instead of `.patterns[0].blockTypes: should be array`, and the regex
matchers in the rejection tests failed to find the field name.

Read both, fall back across versions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Task Issues or PRs that have been broken down into an individual action to take

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants