WP Manifestindependent plugin directory
manifest / editor / game-mode

Game Mode

Choose a difficulty level (Easy / Medium / Hard) for the WordPress Site Editor experience.

★ 0stars
11release downloads
playgroundlatest · never

Install

Download game-mode.zip From the playground release on GitHub, May 15, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/annezazu/game-mode/releases/download/playground/game-mode.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Choose a difficulty level for the WordPress Site Editor. Simple locks layout and shows only the basics. Intermediate gives you balanced editing. Advanced unlocks every block-support control for full theme design.

A persistent switcher in the bottom-right of the editor lets you change levels at any time.

Status: experimental. This plugin uses several __experimental and __unstable Gutenberg APIs and depends on DOM-text matching against editor labels. It may break with Gutenberg releases.

What each level does

Simple Intermediate Advanced
Patterns content-only locked unlocked unlocked
Block-support controls color + font size only core defaults every control expanded by default
Focus mode (focusMode core preference) on off off
Distraction-free minimal chrome (helpers off) normal normal
Theme blocks (Query Loop, Post Title, …) hidden from inserter available available
Inserter tabs Patterns only all tabs all tabs
Choose-pattern modal on new pages (enableChoosePatternModal) enabled enabled disabled
Block-directory installs disabled disabled enabled
RichText toolbar formats bold / italic / underline / strike / link / inline-code / text-color full full
Code Editor (codeEditingEnabled) disabled disabled enabled
Styles sidebar Browse styles + Colors + Typography + Background only full full
Pattern editing UX locked unless "Edit pattern" clicked auto-unlocked auto-unlocked
Lock-removal on root blocks locked (Backspace can't wipe a section) unlocked unlocked

Try it in WordPress Playground

No install needed — open the latest build in a throwaway WordPress site in your browser:

▶ Launch Game Mode in Playground

You'll be auto-logged-in as admin and dropped straight into the Site Editor with the plugin active. Each push to main triggers a rebuild, so the link always reflects the latest code.

Install

This repo ships source only. To use the plugin you need to build it once, then drop the directory into wp-content/plugins/.

git clone https://github.com/annezazu/game-mode.git
cd game-mode
npm install
npm run build

Then either copy the directory into your WordPress install:

cp -R . /path/to/wordpress/wp-content/plugins/game-mode

…or symlink it:

ln -s "$(pwd)" /path/to/wordpress/wp-content/plugins/game-mode

Activate Game Mode in Plugins → Installed Plugins and open the Site Editor. The first time it runs you'll see a "Choose your difficulty" modal.

Local development with wp-env

npm run start          # webpack watch
npx wp-env start       # boots a local WP at http://localhost:8898
npx wp-env run cli wp core version

The bundled .wp-env.json pins WordPress to a recent release and mounts this directory as a plugin via a no-space symlink at /tmp/game-mode-link/game-mode (wp-env tokenizes plugin paths on whitespace).

Tests

npm test

Jest unit tests cover the pure block-support helpers (minimizeSupports, expandDefaultControls).

Reproducing first-run for testing

The "Choose your difficulty" picker fires once, when all of the following are true: no game_mode_level user meta, no core/preferences['game-mode']['level'], and the Site Editor's welcome guide has been dismissed. To force a fresh first-run experience without uninstalling/reinstalling, run this in the browser console while on site-editor.php:

// 1. Clear the per-user meta (level lives on /wp/v2/users/me).
await wp.apiFetch( {
    path: '/wp/v2/users/me',
    method: 'POST',
    data: { meta: { game_mode_level: '' } },
} );

// 2. Clear the in-store preference.
wp.data.dispatch( 'core/preferences' ).set( 'game-mode', 'level', undefined );

// 3. Dismiss the Site Editor welcome guide so it doesn't block our picker.
wp.data.dispatch( 'core/preferences' ).set( 'core/edit-site', 'welcomeGuide', false );

location.reload();

After reload, the picker should appear. If it doesn't, check wp.data.select('core').getCurrentUser().meta.game_mode_level — it should be empty.

Architecture

  • PHP (game-mode.php): bootstrap, asset enqueue gated to site-editor.php, registered user meta (game_mode_level) exposed through /wp/v2/users/me, and three server-side curation layers — allowed_block_types_all for theme-block hiding on Simple, block_editor_settings_all for per-level editor settings (codeEditingEnabled, disableContentOnlyForUnsyncedPatterns), and wp_theme_json_data_user for Simple's declarative settings constraints (appearance-tools off, advanced typography keys off, etc.). The theme.json layer is belt-and-braces on top of minimizeSupports — settings can't gate the ToolsPanel "more" menu, only the default-visible set.
  • JS bundle (src/): React UI for the picker modal + bottom-right switcher (@wordpress/components), level configuration (levels.js), and a stack of MutationObserver/subscribe()-driven filters in src/filters/ that hide-or-modify parts of the editor based on the active level.

Stability — experimental Gutenberg APIs in use

This plugin reaches into a number of __experimental* and __unstable* APIs from the Gutenberg packages. Those carry no compatibility guarantee — they can be renamed or removed in any release without a deprecation cycle. The list below is the surface area to watch when bumping @wordpress/* deps:

API File What breaks if it goes away
__experimentalGetDirtyEntityRecords (core store) src/index.js Unsaved-changes guard before reload — falls back to no warning
__experimentalConfirmDialog (@wordpress/components) src/index.js The "Save before switching?" dialog stops rendering
__unstableMotion / __unstableAnimatePresence (@wordpress/components) src/components/LevelSwitcher.jsx Switcher icon swap goes from animated → instant
__unstableMarkNextChangeAsNotPersistent (core/block-editor dispatch) src/filters/easy-lock-remove.js Lock-remove writes get marked as user edits, dirtying the post
__experimentalGetAllowedPatterns (core/block-editor selector) n/a directly today, but used by patterns observer Pattern list filtering can no-op
__experimentalText / __experimentalHeading / __experimentalVStack / __experimentalHStack (@wordpress/components) LevelCard, LevelPickerModal Build error — these are not optional, swap with stable Text/Heading/Flex if removed

In addition, several filters depend on DOM-text matching against editor labels (e.g. matching panel headers by their visible "Layout" / "Edit pattern" text). Those break in non-English locales and on label changes:

  • src/filters/easy-block-inspector.js — "Edit pattern", "Styles", "Layout", "Dimensions", "Border", "Position", "Shadow", "Background", "Advanced"
  • src/filters/easy-styles-menu.js — "Browse styles", "Colors", "Typography", "Background", + the "customize the appearance of specific blocks" description
  • src/filters/easy-patterns-only-inserter.js — "Patterns"

Also worth noting: showSimpleTopbar and showBlockHelpers in src/levels.js mirror an unmerged Gutenberg PR (#74546). On stock WordPress they are no-ops; the visual effect is provided by the CSS in src/filters/distraction-free-config.js.

Known limitations

Root-block lock attributes persist after an explicit save on Simple. src/filters/easy-lock-remove.js writes lock.remove: true to root blocks while on Simple so Backspace can't wipe a whole section. The write is marked non-persistent via __unstableMarkNextChangeAsNotPersistent, but if the user explicitly saves the template/page while on Simple, the attribute goes along for the ride into the saved post_content. After switching to Intermediate or Advanced, those blocks still render with the lock icon until they're re-saved at the higher level.

Workaround: open the affected block on Advanced, click the toolbar lock icon to unlock, and save. Or wait until you next edit and re-save the template — easy-lock-remove's cleanup pass strips the attribute in memory on level switch, so the next save will write a clean copy.

This isn't fixed in code because every clean fix path (filtering on render, rewriting on save, intercepting canRemoveBlock) carries enough Core-coupling and edge-case risk to outweigh the cost of a niche visual nuisance.

License

GPL-2.0-or-later.

Releases

1 release. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
playground latest
May 15, 2026 5mo ago
game-mode.zip
11