Skip to content

fix(android): derive inserter tabs from payload sections#524

Draft
jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/issue-523
Draft

fix(android): derive inserter tabs from payload sections#524
jkmassel wants to merge 1 commit into
trunkfrom
jkmassel/issue-523

Conversation

@jkmassel
Copy link
Copy Markdown
Contributor

@jkmassel jkmassel commented May 28, 2026

What?

  • Replaced BlockPickerTab's hardcoded block-ID allowlist with tabs derived from BlockInserterPayload.sections.
  • Tab labels come from each section's name (already localized by the JS bridge via getBlockCategories()); Recent keeps its hardcoded string resource.

Fixes #523.

Why?

BlockPickerTab carried a hand-curated Set<String> of block IDs per tab and filtered the inserter to that allowlist:

val ids = tab.blockIds ?: return allBlocks
return allBlocks.filter { it.name.substringAfterLast('/') in ids }

Anything outside the union of those sets disappeared — every plugin block, plus core blocks (core/group, core/details, core/footnotes, core/social-links, …) that didn't make the design handoff. Now that consumers are starting to enable plugins in production (e.g. setPlugins(true) in wordpress-mobile/WordPress-Android), this gate is the visible reason Jetpack blocks never appeared in the inserter despite being correctly registered, returned by getInserterItems(), and serialized into the payload. The comment above the enum already acknowledged the allowlist was a stub:

Hardcoded block-id groupings from the design handoff. Production will likely derive these from the payload, but the design freezes the taxonomy shown in the tabs so we pin it here to match Variation B exactly.

This PR makes production match what the comment described.

How?

The JS bridge already ships exactly what we need: payload.sections carries one section per WordPress block category, each with a localized name and the blocks in that category (core categories first, plugin categories appended). The Kotlin side just had to use it.

1. New BlockPickerTabs.kt

Pure helpers, no Compose, fully unit-testable:

  • BlockPickerTab is now a sealed class — Recent (special-cased) and Category(section).
  • browsableSections(payload): strips every section whose category starts with gbk- (the prefix the JS bridge uses for synthetic sections — gbk-most-used, gbk-contextual, gbk-search-only). Forward-compatible with any new synthetic section.
  • buildTabs(sections): Recent followed by one Category per browsable section. Payload order is preserved, so the JS side controls the tab taxonomy.
  • blocksForTab(tab, recentBlocks): recent for Recent, the section's blocks for Category. No allowlist, no substringAfterLast('/') namespace stripping.
  • recentBlocks and dedupeById moved here unchanged so they're testable alongside the rest.

2. BlockPickerDialog.kt

  • Removed the BlockPickerTab enum, filterByTab, flattenBlocks, dedupeById, and the SEARCH_ONLY_CATEGORY / MOST_USED_CATEGORY private constants.
  • BlockPickerSheet derives the tab list from the payload and threads it through SheetContentCategoryTabs.
  • tabLabel renders Recent from a string resource and Category labels from section.name (defensive fallback to section.category, though the JS bridge always populates name for non-synthetic sections).

3. strings.xml

Removed unused per-tab labels (gbk_block_inserter_tab_text, …_media, …_design, …_widgets, …_theme, …_embeds). Tab labels come from the payload now. gbk_block_inserter_tab_recent stays.

What We Explored

  • A catch-all "More" tab (option 2 in Native inserter tabs hide plugin and several core blocks via a hardcoded ID allowlist #523) — option 1 makes it unnecessary. The JS bridge buckets every block into a category section (blocks with no category fall back to 'common', unknown categories get appended with name = capitalize(slug)), so every browsable block lives in some non-synthetic section. No overflow to catch.
  • Dropping the stale core/table entry from BLOCK_ORDER_BY_CATEGORY.text in src/utils/blocks.js — was bundled in earlier, then split out. Tracked as a separate cleanup; the Android fix stands alone without it.
  • Surfacing gbk-search-only blocks in search — pre-existing behavior outside the scope of this fix.

Testing Instructions

Run the Android demo app against jetpack.wpmt.co, open the inserter, and confirm the AI block is reachable.

Related issues

@github-actions github-actions Bot added the [Type] Bug An existing feature does not function as intended label May 28, 2026
@jkmassel jkmassel force-pushed the jkmassel/issue-523 branch from fe8d066 to f96c3be Compare May 28, 2026 05:32
@wpmobilebot
Copy link
Copy Markdown

wpmobilebot commented May 28, 2026

XCFramework Build

This PR's XCFramework is available for testing. Add the following to your Package.swift:

.package(url: "https://github.com/wordpress-mobile/GutenbergKit", branch: "pr-build/524")

Built from 2825055

Replaces the hardcoded `BlockPickerTab` enum and its per-tab block-ID
allowlist with tabs built from `BlockInserterPayload.sections`. Tab
labels come from each section's `name` (already localized by the JS
bridge); `Recent` keeps its hardcoded string resource.

Plugin blocks (e.g. `jetpack/ai-assistant`) and core blocks not in the
original design handoff (`core/group`, `core/details`, `core/footnotes`,
`core/social-links`, …) are reachable from the inserter again.

Fixes #523
@jkmassel jkmassel force-pushed the jkmassel/issue-523 branch from f96c3be to 2825055 Compare May 28, 2026 05:40
@jkmassel jkmassel self-assigned this May 28, 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.

Native inserter tabs hide plugin and several core blocks via a hardcoded ID allowlist

2 participants