Skip to content

feat: align iOS third-party blocks setting with Android's Site Settings toggle - #25889

Draft
dcalhoun wants to merge 9 commits into
trunkfrom
feat/cmm-2265-third-party-blocks-site-setting
Draft

feat: align iOS third-party blocks setting with Android's Site Settings toggle#25889
dcalhoun wants to merge 9 commits into
trunkfrom
feat/cmm-2265-third-party-blocks-site-setting

Conversation

@dcalhoun

Copy link
Copy Markdown
Member

Description

Fixes CMM-2265.

The GutenbergKit third-party blocks ("plugins") feature is surfaced differently on each platform. Android gives users a per-site toggle in Site Settings; on iOS the only toggle lives in the internal-only Debug Menu, so no end user can enable it. This adopts Android's user-facing model.

Three things compose as AND — the editor receives plugins = true only when all agree:

Order Condition State Setting row Editor
1 gutenberg_kit_plugins off hidden Not shown Off
2 Site can't support the feature unsupported Shown, disabled Off
3 All pass available(userEnabled) Shown, interactive Follows user

The remote flag stays as the outer kill switch and rollout control; the new setting is a per-site opt-in within an enabled rollout. Because the preference persists independently, toggling the flag off and on restores each site's prior choice.

Commits

Reviewable in order — each builds on the last:

  1. style: — swift-format on GutenbergSettings alone, so the following diffs read cleanly
  2. Per-site preference storage (local-only, defaults to false)
  3. ThirdPartyBlocksCapability resolver + @objc bridge
  4. Wire the resolver into shouldEnablePlugins, replacing the site-type check
  5. The Site Settings row
  6. Release note

Notes for reviewers

The capability probe previously had no effect. EditorDependencyManager writes .setSupports(.plugins, …) but nothing outside tests ever read it. This PR makes it load-bearing.

Un-probed sites resolve as supported, deliberately. fetchEditorCapabilities is fire-and-forget and gated behind newGutenberg, so on a fresh install it may not have run when the setting is first read. getSupports returns false for both "server said no" and "never asked", so I added hasProbedSupport(for:blog:) to separate them. Treating un-probed as unsupported would disable the row on perfectly capable sites; treating it as supported degrades to an editor without plugin blocks, which is today's behavior anyway.

The WP.com REST API requirement is retained. The old check required isAccessibleThroughWPCom. The editor-assets endpoint is served through that API, so a purely self-hosted site can't serve third-party blocks even with an application password — and the probe can't stand in for it given the point above.

configureThirdPartyBlocksSelectorCell restores the disabled state, unlike the Theme Styles equivalent it's modeled on, which only ever enables the cell. On a recycled cell that can strand an interactive switch on an unsupported site. I didn't fix the Theme Styles version here to keep the diff scoped, but it looks like a real latent bug worth a follow-up.

Known limitation: because the probe is async, the row can render before the true capability is known and settles on the next pull-to-refresh or revisit. This matches existing Theme Styles behavior; making it update live would be a deliberate change beyond this scope.

Tests: the previous 7 tests asserted the old site-type behavior throughout and were rewritten rather than extended. The suite now covers flag-off, both unsupported reasons, un-probed, default-off, opted-in, per-site isolation, the flag off→on cycle, and the shouldEnablePlugins entry point.

Two test-infrastructure fixes were needed, both of which caused confusing failures:

  • The flag value is injected rather than overriding the shared FeatureFlagOverrideStore, which tests running in parallel would clobber.
  • Builder-made blogs get obtainPermanentIDs. Capability support is keyed on Blog.locallyUniqueId, which hashes the Core Data object ID — a write made before Core Data promotes a temporary ID is read back under a different key.

Testing instructions

The feature is behind the gutenberg_kit_plugins remote flag, off by default. Enable it in Me → App Settings → Debug → Feature Flags → "Experimental Block Editor Plugins" (also requires "Experimental Block Editor").

  1. Flag off → Site Settings shows no "Use Third-Party Blocks" row.
  2. Flag on, WP.com Simple site → the row appears in the Editor section, off by default. Toggle it on, open the editor, and confirm blocks from installed plugins appear in the inserter. Toggle off → they're gone.
  3. Jetpack/Atomic site with an application password → same as above.
  4. Self-hosted site with no application password → the row appears disabled, with the footer explaining an application password is required.
  5. Per-site persistence → enable on one site, confirm a second site is still off.
  6. Flag cycle → with the toggle on, turn the remote flag off (row disappears), then on again; the site's previous choice is restored.
  7. Header check → the "Editor" section header should appear exactly once, not duplicated above the new row.

Worth a pass in dark mode and at larger dynamic type sizes, since the row uses an explicit lightGrayColor for its disabled state (inherited from the Theme Styles pattern).

dcalhoun and others added 6 commits August 11, 2026 13:22
Formatting-only change, no behavior difference. Committed separately so the
following functional change reads cleanly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Store the user's per-site opt-in for third-party (plugin-provided) blocks,
mirroring the Theme Styles preference pattern.

The preference is local-only and never posted to the server, matching
Android's SiteSettingsModel.useThirdPartyBlocks. It defaults to false so the
capability stays opt-in, unlike Theme Styles which defaults to true.

This is storage only. Resolving the preference against the remote feature
flag and the site's advertised capability comes next.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Introduce ThirdPartyBlocksCapability with the three states Android's
EditorCapabilityResolver uses — hidden, unsupported, available — plus
resolveThirdPartyBlocks(for:) to derive them from the remote feature flag,
the site's authentication, and the site's advertised capability.

Treat a site whose capability has not been probed yet as supported. The
probe is fire-and-forget from MySiteViewController and Site Settings, and
only runs when newGutenberg is enabled, so it may not have completed — or
on a fresh install, run at all — when the setting is read. getSupports
reports false for both 'server said no' and 'never asked', so add
hasProbedSupport(for:blog:) to tell those apart. Assuming support keeps the
setting reachable on capable sites; the editor degrades gracefully if the
route is genuinely absent.

Also expose the @objc bridge methods the Objective-C Site Settings screen
needs to show, enable, and toggle the row.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the site-type check in shouldEnablePlugins with the resolver, so the
editor loads third-party blocks only when the remote flag, the site's
capability, and the user's per-site preference all agree. The preference
defaults to false, so no site enables plugins until the user opts in.

Keep the WP.com REST API requirement the old check enforced via
isAccessibleThroughWPCom. The editor assets endpoint is served through that
API, so a purely self-hosted site can't serve third-party blocks even with an
application password, and the capability probe can't stand in for it — an
unprobed site resolves as supported by design.

Rewrite the tests around the new model: the previous suite asserted the
site-type behavior throughout. They now cover the flag-off, unsupported,
unprobed, default-off, opted-in, per-site, and flag-cycle cases, plus the
shouldEnablePlugins entry point.

Pass the flag value in rather than overriding the shared
FeatureFlagOverrideStore, which tests running in parallel would clobber, and
obtain permanent Core Data IDs for builder-made blogs. Capability support is
keyed on Blog.locallyUniqueId, which hashes the object ID, so a write made
before Core Data promotes a temporary ID is read back under a different key.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a per-site 'Use Third-Party Blocks (Beta)' switch to the Editor section,
following the Theme Styles row's pattern. The section is omitted entirely
when the resolver reports .hidden, and the row renders disabled with an
explanatory footer when the site can't support the feature — matching
Android, which shows the row rather than hiding it.

The footer distinguishes the two unsupported reasons: a site missing the
editor-assets capability gets different wording than one missing an
application password.

Extend the 'Editor' section header chaining to the new section, so the
header still appears exactly once when earlier editor sections are absent.

Unlike configureThemeStylesSelectorCell, the configure method here also
restores the disabled state, so a recycled cell can't strand an interactive
switch on a site that doesn't support the feature.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dangermattic

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

@dcalhoun dcalhoun added Site Settings Gutenberg Editing and display of Gutenberg blocks. [Type] Enhancement labels Aug 12, 2026
@dcalhoun dcalhoun added this to the 27.2 milestone Aug 12, 2026
@wpmobilebot

wpmobilebot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
App NameWordPress
ConfigurationRelease-Alpha
Build Number33733
VersionPR #25889
Bundle IDorg.wordpress.alpha
Commit7f42dc5
Installation URL4u99qjv2q0spg
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@wpmobilebot

wpmobilebot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
App Icon📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
App NameJetpack
ConfigurationRelease-Alpha
Build Number33733
VersionPR #25889
Bundle IDcom.jetpack.alpha
Commit7f42dc5
Installation URL034n4ddn3atg0
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

dcalhoun and others added 3 commits August 12, 2026 10:36
The capability probe checked WordPressClient.Feature.plugins, which resolves
to the /wp/v2/plugins route — the plugin *management* API. Third-party blocks
are served by /wpcom/v2/editor-assets, an unrelated route. WP.com Simple
sites don't expose /wp/v2/plugins, so the probe recorded false and the
resolver correctly reported .unsupported for sites that support the feature
perfectly well. That left the Site Settings row permanently disabled and no
third-party blocks in the editor.

Add Feature.editorAssets, checking the same routes GutenbergKit itself uses
to decide whether it can load plugin-provided blocks, including the
site-namespaced form WP.com sites expose. Point the probe and the resolver at
it. Feature.plugins is left in place for genuine plugin-management checks,
and its stringValue is unchanged so no stored settings are invalidated.

Also use sentence case for the Site Settings row to match the neighboring
'Use block editor' and 'Use theme styles' labels.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A WordPress.com site starts out proxied through the WP.com REST API and
switches to addressing its own API root once an application password is
created. The two roots key their discovery documents differently: the proxy
namespaces every route under /sites/{id}/, so it lists
/wpcom/v2/sites/123/editor-assets while the site's own root lists
/wpcom/v2/editor-assets.

WordPressClientFactory cached clients by blog alone, and WordPressClient
fetches its API root once in init and keeps it for the process lifetime.
When the application password arrived mid-session the transport flipped, so
fetchEditorCapabilities started computing siteId = nil and checking
non-namespaced routes against a still-cached proxy document. Every lookup
missed, which read as 'this site supports nothing' and disabled both the
theme styles and third-party blocks rows until the app was reinstalled.

Key the cache on the transport as well, and evict any client built for the
site's previous transport, so the next use fetches the matching document.

Also give SwitchTableViewCell a real isEnabled property. Disabling
flipSwitch alone left the row's tap gesture active, so the value still
toggled when the label was tapped, and WPStyleGuide.configureTableViewCell
calls sizeToFit() on layout, which restored the label's default color and
made a disabled row look enabled. Adopt it for both editor rows.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The capability probe is asynchronous, but Site Settings built its rows once
and never rebuilt them. viewDidLoad ran before the probe completed, so the
editor rows kept whatever state they were built with — on first load, before
any capability was known. The rows only corrected themselves when something
else forced a re-layout, such as cancelling a back swipe.

Await the probe and rebuild the section list when it lands. The rows now
render from the last-known capabilities and reconcile once the fresh values
arrive, rather than blocking the screen on a network round trip.

Also invalidate the prefetched editor when either editor preference is
toggled. The editor is warmed up ahead of time from an EditorConfiguration
built with these values, so opening it after a toggle reused a cached editor
that still had the old setting — third-party blocks stayed missing after
switching the setting on. This applies to theme styles for the same reason.

Part of CMM-2265.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wpmobilebot

Copy link
Copy Markdown
Contributor

🤖 Build Failure Analysis

This build has failures. Claude has analyzed them - check the build annotations for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gutenberg Editing and display of Gutenberg blocks. Site Settings [Type] Enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants