Skip to content

Instantly share code, notes, and snippets.

@akirk
Last active January 22, 2026 14:56
Show Gist options
  • Select an option

  • Save akirk/38b86075aeaf01252ca55e60d0e98b9c to your computer and use it in GitHub Desktop.

Select an option

Save akirk/38b86075aeaf01252ca55e60d0e98b9c to your computer and use it in GitHub Desktop.

Personal WordPress Playground Fork Summary

This document summarizes the changes in this fork of wordpress-playground, intended to help with PR review.


Table of Contents

  1. The Big Picture
  2. Core Features by PR
  3. Feature Details
  4. Stats
  5. PR Strategy Considerations
  6. Upstream PR Strategy (includes recommended separate package approach)
  7. Scratched & Evolved Features
  8. Tips for Reviewers
  9. Architecture Notes
  10. Risk Analysis: Impact on Standard Playground (includes bundle size concern)
  11. Changes That Benefit Standard Playground
  12. Testing Checklist
  13. Future Outlook: SharedWorker Support
  14. Questions / Discussion Points

The Big Picture

This fork creates a personal WordPress Playground deployment - a version of Playground where users get their own WordPress site that persists in the browser, with proper onboarding, backup guidance, and recovery tools.

Live deployment: my.wordpress.net

Goal: Make this deployable as "just another environment" from the main repo, not a permanent fork.


Core Features by PR

PR Feature Purpose
#1 Personal deployment infrastructure New build mode, deploy workflow, OPFS storage by default
#3 Welcome plugin & boot blueprint Onboarding experience for new users
#9 Automatic user language i18n support, detects browser language
#10 App catalog Pre-configured apps/blueprints users can install
#2, #4, #11 Backup reminder system Guides users to backup their personal data
#7, #13 Recovery tools Health Check integration for crash recovery
#6, #8 UI polish & overlay refactor Improved UX for personal sites
#12, #14 Multi-tab coordination Makes personal sites work reliably across tabs

Feature Details

1. Personal Deployment Infrastructure (PR #1)

The foundation: a new build mode that configures Playground for personal use.

  • Build script: build-personalwp.sh
  • Deploy workflow: .github/workflows/deploy-my-wordpress-net.yml
  • Configuration via virtual:website-defaults:
    • defaultBlueprintUrl - Boot blueprint for new sites
    • defaultStorageType - 'opfs' for personal, 'none' for temporary
    • defaultSiteSlug - Fixed site slug (e.g., 'my-wordpress')

This allows the main repo to support multiple deployment targets with different defaults.

2. User Onboarding (PRs #3, #9, #10)

  • Welcome plugin with localized content
  • Automatic language detection from browser settings
  • App catalog - pre-configured blueprints users can install (e.g., "CRM", "Friends plugin")

3. Data Safety (PRs #2, #4, #8, #11)

  • Backup reminder component that tracks days since last backup
  • Backup history synced across tabs
  • Export/import UI integrated into the overlay
  • Less intrusive prompts that respect user workflow

4. Recovery Tools (PRs #7, #13)

  • Health Check integration for recovery mode
  • MU-plugin bypass for recovering from fatal errors
  • Guidance for users on how to fix broken sites
  • Links to AI Assistant for code fixing (if installed)

5. Multi-Tab Coordination (PRs #12, #14)

Necessary for personal sites where users may have multiple tabs open:

  • One worker per site - only one tab runs the PHP worker ("main" tab)
  • Dependent mode - other tabs use the main tab's worker
  • Takeover protocol - a dependent tab can request to become main
  • Cross-tab sync - backup history and metadata stay in sync
  • Stale tab handling - tabs older than 1 day gracefully shut down

Key files:


Stats

Net Changes (Final Diff vs Trunk)

Metric Count
Commits ahead of trunk 52
Files changed 325
Lines added 6,491
Lines removed 5,134
New files 39
Modified files 239
Deleted files 25
Open PRs 13

Churn Analysis (Cumulative vs Net)

Metric Cumulative Net Hidden Work
Lines added 8,642 6,491 2,151 (25%)
Lines deleted 4,523 5,134 -

25% of code written was later undone - typical of rapid prototyping. A clean PR should only include the net result.

Scratched Files (Added Then Removed)

Files that were created during development but no longer exist in the final diff:

File What Happened
public/blueprints/boot.json Replaced by personalwp-boot.json
public/plugins/playground-welcome/* Moved to git submodule
components/plugin-list/ Scratched component
saved-playgrounds-overlay/personalwp-overlay.tsx Moved to personalwp-overlay/

Most Churned Files (Touched Multiple Times)

File Times Modified Notes
boot-site-client.ts 16 Core boot logic, most iterated
personalwp-overlay/index.tsx 13 Main overlay UI
backup-status-indicator.tsx 10 Backup UI iterations
personalwp-boot.json 10 Blueprint tweaks
slice-sites.ts 9 Redux state changes
backup-reminder/index.tsx 8 Backup reminder iterations
tab-coordinator.ts 6 Multi-tab coordination
vite.config.ts 6 Build config

Commit Hygiene

Category Count Percentage
Total commits 52 100%
Fix/Update/Iterate/Refactor commits 23 44%
Feature commits 29 56%

The high percentage of iteration commits is typical of rapid prototyping. A clean PR would squash these into logical units.


PR Strategy Considerations

The Dilemma

This fork was developed rapidly as a prototype. Now it needs to be merged cleanly. Options:

Approach Pros Cons
Separate personal-wp package Ships fast, zero risk to standard, simple review Code duplication, changes need two places
One large PR Shows complete vision, less coordination Intimidating, all-or-nothing
Multiple clean PRs Easier review, incremental merge More work to slice, dependency management
Squash + clean single PR Clean history, one review Still large

⭐ Recommended: The separate package approach (see Upstream PR Strategy) allows shipping quickly while avoiding risk to the standard Playground. Code duplication is an acceptable trade-off that may prompt extraction of shared components later.

Alternative Approach: Clean Branch (If Single Codebase Preferred)

If the team prefers a single codebase over the separate package approach, create a fresh branch and manually apply only the final working code:

git checkout trunk
git checkout -b personal-playground

# Apply changes in logical commits:
# 1. "Add virtual:website-defaults configuration"
# 2. "Add multi-tab coordination for personal sites"
# 3. "Add backup reminder UI"
# 4. "Add Health Check recovery integration"
# 5. "Add personal build mode and deployment workflow"

What to Exclude From the Clean PR

See Scratched & Evolved Features below for code that should not be included.

Files to Cherry-Pick vs. Skip

Include Skip
Final tab-coordinator.ts Intermediate iterations
Final backup-reminder/ component Early backup UI attempts
health-check-recovery.ts Custom plugin deactivation UI (scratched)
Final welcome plugin state 9 iterations of welcome text

Upstream PR Strategy (for WordPress/wordpress-playground)

Recommended: Separate personal-wp Package

Alternative approach: Instead of refactoring the website package to support both modes, copy the entire packages/playground/website package as packages/playground/personal-wp.

Why this might be better:

Consideration Separate Package Refactored Website
Time to ship Could ship today Requires careful refactoring
Review burden Blanket merge possible Needs careful per-file review
Risk to standard Playground Zero - completely isolated Medium - shared code paths
Code duplication Yes - changes need two places No - shared components
Future maintenance May prompt extracting shared code Single codebase

Trade-offs:

  • Pro: Ships faster, no risk to playground.wordpress.net, simpler review
  • Pro: Natural pressure to extract truly shared components into a common package later
  • Con: Bug fixes and improvements need to be applied to both packages
  • Con: Divergence over time if not actively maintained

Implementation:

# Copy the entire website package
cp -r packages/playground/website packages/playground/personal-wp

# Update package.json name
# Update build scripts and workflows
# Deploy from personal-wp instead of website

This approach acknowledges that getting everything polished for the main website package may take longer than desired, while a separate package could potentially ship immediately.


Alternative: Gradual Integration (Original Plan)

The following sections describe the original gradual integration approach, which remains an option if the team prefers a single codebase.

Complete Diff Segmentation

All 37 website source files organized by upstream PR.

Base path: packages/playground/website/src/

Upstream PR 1: Overlay Refactor

Pure refactor, no feature flags, benefits all deployments

File Change Type
components/overlay/index.tsx NEW
components/overlay/style.module.css NEW
components/saved-playgrounds-overlay/index.tsx MODIFIED
components/saved-playgrounds-overlay/style.module.css MODIFIED

Upstream PR 2: Enable Personal Deployment ⭐

Browser chrome refactor + website defaults + basic personal mode (combined for testability)

Currently browser-chrome/index.tsx has conditionals like:

{defaultStorageType === 'opfs' ? <BackupStatusIndicator /> : <SaveStatusIndicator />}
{defaultStorageType === 'opfs' ? <PersonalWPOverlay /> : <SavedPlaygroundsOverlay />}

Goal: Extract shared base components, create separate entry points, add website defaults, and enable personal build mode - all in one testable unit.

File Change Type
components/browser-chrome/index.tsx REFACTOR → base
components/browser-chrome/standard-browser-chrome.tsx NEW
components/browser-chrome/personalwp-browser-chrome.tsx NEW
components/address-bar/index.tsx REFACTOR
vite.config.ts MODIFIED
lib/types.d.ts MODIFIED
components/personalwp-overlay/index.tsx NEW (minimal)
components/personalwp-overlay/style.module.css NEW
lib/personalwp/index.ts NEW
lib/state/url/router.ts MODIFIED
lib/state/url/resolve-blueprint-from-url.ts MODIFIED
lib/state/opfs/opfs-site-storage.ts MODIFIED
components/ensure-playground-site/ensure-playground-site-is-selected.tsx MODIFIED

Architecture after refactor:

browser-chrome/
├── browser-chrome-base.tsx      ← Shared shell (header, content area)
├── standard-browser-chrome.tsx  ← SaveStatusIndicator, SavedPlaygroundsOverlay
├── personalwp-browser-chrome.tsx  ← BackupStatusIndicator, PersonalWPOverlay
└── index.tsx                    ← Re-exports based on build mode

Upstream PR 3: Health Check Recovery

Can now test against PR #2's personal deployment

File Change Type
lib/health-check-recovery.ts NEW
components/site-error-modal/get-site-error-view.tsx MODIFIED
components/site-error-modal/style.module.css MODIFIED

Upstream PR 4: Backup Reminder UI

Can now test against PR #2's personal deployment

File Change Type
components/backup-reminder/index.tsx NEW
components/backup-reminder/style.module.css NEW
components/browser-chrome/backup-status-indicator.tsx NEW
components/browser-chrome/save-status-indicator.module.css MODIFIED
lib/hooks/use-backup.ts NEW

Upstream PR 5: Multi-Tab Coordination

Can now test against PR #2's personal deployment, highest risk

File Change Type
lib/state/redux/tab-coordinator.ts NEW
lib/state/redux/cross-tab-sync.ts NEW
lib/state/redux/boot-site-client.ts MODIFIED (major)
lib/state/redux/slice-sites.ts MODIFIED (major)
lib/state/redux/slice-clients.ts MODIFIED
lib/state/redux/slice-ui.ts MODIFIED
lib/state/redux/store.ts MODIFIED
lib/hooks/use-takeover.ts NEW
components/address-bar/worker-status-indicator.tsx NEW
components/address-bar/worker-status-indicator.module.css NEW
components/address-bar/index.tsx MODIFIED
components/address-bar/style.module.css MODIFIED
components/tab-info-window/index.tsx NEW
components/tab-info-window/style.module.css NEW

Upstream PR 6: i18n & App Catalog

Polish for personal deployment

File Change Type
lib/personalwp/i18n.ts NEW
components/playground-viewport/index.tsx MODIFIED
components/toolbar-buttons/download-as-zip.tsx MODIFIED
App catalog components NEW

Files to Exclude (deployment-specific or scratched)

File Reason
.github/workflows/deploy-my-wordpress-net.yml Deployment-specific
build-personalwp.sh Deployment-specific
blueprints/personalwp-boot.json Deployment-specific
public/plugins/playground-welcome/ (submodule) Deployment-specific
Docs translations (i18n/*) Unrelated sync from trunk

Recommended PR Sequence

Submit PRs in this order to enable testing at each phase:

Order PR Dependencies Risk Why This Order
1 Overlay refactor None Low Pure refactor, cleaner code
2 Enable personal deployment #1 Low Browser chrome refactor + website defaults + basic personal mode. Unblocks testing of all personal features
3 Health Check recovery #2 Low Can now test with personal site
4 Backup reminder UI #2 Low Can now test with personal site
5 Multi-tab coordination #2 Medium Can now test with personal site
6 i18n & App catalog #2 Low Polish for personal deployment

Key insight: PR 2 combines browser chrome refactor, website defaults, and basic personal deployment into one testable unit. You can't integration-test the infrastructure without a personal deployment to use it.

PR 1: Overlay Refactor

What: Extract shared overlay components (Overlay, OverlayHeader, OverlayBody, OverlaySection)

Files:

Why first: Pure refactor, no feature flags, improves code quality for everyone. Can be tested immediately on standard Playground.

PR 2: Enable Personal Deployment ⭐

What: Combined PR that enables and allows testing of personal deployment:

  1. Browser chrome refactor - Extract shared base, create Standard/Personal variants
  2. Website defaults infrastructure - Add virtual:website-defaults Vite plugin
  3. Basic personal mode - Add --mode personalwp build with minimal overlay

Files:

Why combined: The browser chrome refactor and website defaults can't be fully integration-tested without a personal deployment to use them. Combining them creates one testable unit.

Testing:

  • Standard Playground still works (uses StandardBrowserChrome, defaultStorageType = 'none')
  • Personal mode works (npm run build -- --mode personalwp, uses PersonalWPBrowserChrome, defaultStorageType = 'opfs')

PR 3: Health Check Recovery

What: Add recovery flow for crashed personal sites using Health Check plugin

Files:

Why: Useful for existing OPFS sites in standard Playground. Can now be tested against PR 2's personal deployment.

Depends on: PR #2 (for testing)

PR 4: Backup Reminder

What: Add backup reminder UI for personal sites

Files:

Why: Can now be tested against PR 2's personal deployment.

Depends on: PR #2 (for testing)

PR 5: Multi-Tab Coordination

What: Add tab coordinator for personal sites to prevent OPFS conflicts

Files:

Why: Can now be tested against PR 2's personal deployment. Open two tabs with the personal site to verify coordination works.

Depends on: PR #2 (for testing)

This is the riskiest PR - touches core boot logic. Needs careful review.

PR 6: i18n & App Catalog

What: Add automatic language detection and app catalog for personal deployment

Files:

Why: Polish for the personal deployment. Can be merged after core functionality is working.

Depends on: PR #2

Alternative: Single PR with Logical Commits

If the team prefers one PR, structure it with clear commits:

commit 1: "Refactor overlay into reusable base components"
commit 2: "Enable personal deployment mode" (browser chrome refactor + website defaults + basic personal)
commit 3: "Add Health Check recovery for personal sites"
commit 4: "Add backup reminder UI"
commit 5: "Add multi-tab coordination for personal sites"
commit 6: "Add i18n and app catalog for personal sites"

Each commit should pass tests independently. Commit 2 should be testable as a minimal personal deployment.

Keep Fork as Integration Branch

While upstreaming:

  1. Keep worker-handover branch as the working integration
  2. Cherry-pick clean code into upstream PRs
  3. Rebase fork on trunk as PRs merge
  4. my.wordpress.net continues running from fork until all PRs merge

What NOT to Submit Upstream

Item Reason
Welcome plugin submodule Deployment-specific, keep in fork or separate repo
deploy-my-wordpress-net.yml Deployment-specific workflow
App catalog content Deployment-specific (Friends plugin, CRM, etc.)
Remote logging code Was scratched
Custom plugin deactivation UI Was scratched, replaced by Health Check

These can live in a separate "playground-deployments" repo or stay in the fork.


Scratched & Evolved Features

Features that were tried and abandoned or significantly reworked. These should be excluded from clean PRs.

1. Custom Plugin Deactivation UI (Scratched)

Original commit: dce5716ad - "Add a recovery section to deactivate plugins"

What it was: A custom UI in the overlay to list and deactivate plugins when WordPress crashed.

Why scratched: Replaced with Health Check plugin integration, which:

  • Is a standard WordPress solution
  • Handles more edge cases
  • Provides troubleshooting mode that disables all plugins
  • Self-cleans when user exits troubleshooting

Current approach: Error modal links to Health Check recovery blueprint (health-check-recovery.ts)

Files affected:

2. Remote Logging (Scratched)

Commit: f25fc7fc7 - "Remove remote logging"

What it was: 27 lines in boot-playground-remote.ts for sending logs to a remote endpoint.

Why scratched: Likely for privacy/debugging purposes during development. Not needed in production.

3. Welcome Plugin Iterations (9 commits)

Commit Change
8c2ca0896 Basic welcome blueprint
e23061fd2 Add the playground welcome plugin
98113fc7a Update welcome text
ffaebbb45 Update welcome
f68fe986d Iterate on the welcome message
78761b74c Update playground-welcome submodule
dc3c32aed Make welcome plugin less prominent
af58b714b Update welcome
2a3795804 Update welcome translations

For clean PR: Only include the final state of the welcome plugin submodule.

4. Backup Reminder Iterations (6 commits)

Commit Change
1d200a03b Add backup reminder
c9f3404dd Make backup reminder less intrusive
bd0b9ca45 Refactor the overlay and improve the backup component
d258455c3 Fix backup button opening the sidebar
740c35dd9 Fix backup button filename, show button on new day
023b7976d Warn about unbackuped revert, notify other tabs

For clean PR: Squash into single "Add backup reminder system" commit.

5. Features That Evolved Significantly

Feature Original Final
Recovery Custom plugin deactivation Health Check integration
Overlay Modified saved-playgrounds-overlay New personalwp-overlay
Backup UI Intrusive prompt Subtle indicator with overlay section

Tips for Reviewers

Suggested Review Order

  1. Start with PR 1 - Pure overlay refactor, easy to review
  2. Review PR 2 (Enable personal deployment) - Larger PR but enables testing everything else
  3. Review PRs 3, 4 - Health Check recovery and backup reminder (can test immediately)
  4. Review PR 5 last - Multi-tab coordination is the most complex and highest risk
  5. PR 6 - Polish (i18n, app catalog)

Key Questions for Review

  1. Does this make sense as a deployment configuration? The goal is that this could be merged into the main repo and deployed as one of several environments.

  2. Is the virtual:website-defaults approach appropriate? This is how environment-specific configuration is injected at build time.

  3. Are the multi-tab coordination tradeoffs acceptable? The alternative would be to spawn a new worker per tab (more resource-intensive) or block multiple tabs entirely.

  4. Is the backup reminder UX appropriate? It needs to encourage backups without being annoying.

Files to Focus On

File Why
vite.config.ts Build configuration and virtual modules
boot-site-client.ts Core boot logic changes for personal/multi-tab
tab-coordinator.ts Multi-tab coordination (most complex new code)
slice-sites.ts Redux state changes for personal sites
backup-reminder/index.tsx Main backup UX component

What's NOT Changed

  • Core Playground PHP/WASM runtime
  • Blueprint execution engine
  • OPFS storage implementation (just configuration)
  • Existing temporary playground functionality

Testing Suggestions

  1. Basic personal flow: Open my.wordpress.net, make changes, close tab, reopen - changes should persist.
  2. Multi-tab: Open site in two tabs, make changes in one, see if the other stays in sync.
  3. Takeover: Open site in tab A, then open with ?plugin=something in tab B - tab B should take over.
  4. Recovery: Install a plugin that crashes WordPress, verify recovery UI appears.
  5. Backup: Use the site for a day, verify backup reminder appears, test export/import.

Architecture Notes

Why Multi-Tab Coordination?

Personal sites live in OPFS (Origin Private File System). Unlike temporary playgrounds:

  • Users expect them to "just work" across tabs
  • Data corruption is possible if two workers write simultaneously
  • Users may leave tabs open for days

The coordination system ensures only one worker writes to OPFS at a time while still allowing multiple tabs to view the site.

Why Dependent Mode?

Instead of blocking additional tabs or showing an error, dependent tabs can:

  • Navigate the WordPress site (read-only from their perspective)
  • Request backups from the main tab
  • Request takeover if they need to make changes

This provides a better UX than "site is open in another tab" errors.

Browser UI Architecture

The browser chrome uses if/else conditionals based on defaultStorageType to render different components:

// browser-chrome/index.tsx

// Status indicator slot
{defaultStorageType === 'opfs' ? (
  <BackupStatusIndicator />      // NEW - personal mode
) : (
  <SaveStatusIndicator />        // EXISTING - standard mode
)}

// Overlay
{defaultStorageType === 'opfs' ? (
  <PersonalWPOverlay />    // NEW - personal mode
) : (
  <SavedPlaygroundsOverlay />      // EXISTING - standard mode
)}

Overlay Component Architecture

Both overlays share a new base component extracted in the refactor:

overlay/                         ← NEW: shared base (PR #8 refactor)
├── index.tsx
│   ├── Overlay                  - Container, escape key, animation
│   ├── OverlayHeader            - Logo, title, close/back buttons
│   ├── OverlayBody              - Body container
│   └── OverlaySection           - Section with title + description
└── style.module.css             - Shared styles
        ↑                ↑
        │                │
saved-playgrounds-       personalwp-
overlay/                 overlay/
(EXISTING content)       (NEW content)

What each overlay contains:

SavedPlaygroundsOverlay (Standard) PersonalWPOverlay (Personal)
Site list with thumbnails Backup reminder section
Blueprint gallery from GitHub App catalog (plugin blueprints)
Import from file/URL/GitHub Tab info window (other tabs)
Site rename/delete options Delete entire site option
Create new temporary site Health Check recovery link

New components for personal mode:

Component Purpose
personalwp-overlay/ Main overlay for personal sites
backup-reminder/ Backup reminder with history
backup-status-indicator.tsx Toolbar indicator showing backup status
worker-status-indicator.tsx Shows Main/Dependent badge in address bar
tab-info-window/ Shows info about other tabs using same site

Risk Analysis: Impact on Standard Playground

Note: If the separate personal-wp package approach is used, most risks in this section become irrelevant since the standard Playground remains untouched.

Bundle Size Concern

Problem: Personal-mode code is statically imported, meaning it ships in the standard playground.wordpress.net bundle even though it never executes.

Affected imports:

// browser-chrome/index.tsx - ships unused components
import { PersonalWPOverlay } from '../personalwp-overlay';
import { BackupStatusIndicator } from './backup-status-indicator';

// boot-site-client.ts - ships unused coordinator
import { initTabCoordinator, ... } from './tab-coordinator';

// slice-sites.ts - ships unused sync
import { broadcastMetadataUpdate } from './cross-tab-sync';

Bundle bloat estimate: ~1,861 lines of personal-only code

File Lines
personalwp-overlay/index.tsx 332
backup-reminder/index.tsx 261
tab-coordinator.ts 519
cross-tab-sync.ts 151
tab-info-window/index.tsx 227
use-takeover.ts 141
use-backup.ts 112
health-check-recovery.ts 118
Total 1,861

Solutions for Bundle Size

Option 1: Browser Chrome Refactor + Separate Entry Points (Recommended)

PR 1b's browser chrome refactor naturally solves this by creating separate entry points:

browser-chrome/
├── browser-chrome-base.tsx       ← Shared (always bundled)
├── standard-browser-chrome.tsx   ← Only bundled for playground.wordpress.net
└── personalwp-browser-chrome.tsx   ← Only bundled for my.wordpress.net

With separate entry points, Vite/Rollup can tree-shake unused code at build time. Standard builds won't include personal components at all.

Option 2: Lazy Loading (Fallback)

Use React.lazy() for components and dynamic import() for modules:

// browser-chrome/index.tsx
const PersonalWPOverlay = React.lazy(
  () => import('../personalwp-overlay')
);
const BackupStatusIndicator = React.lazy(
  () => import('./backup-status-indicator')
);

// Only loads when defaultStorageType === 'opfs'
{defaultStorageType === 'opfs' && (
  <Suspense fallback={null}>
    <BackupStatusIndicator />
  </Suspense>
)}
// boot-site-client.ts
if (site.metadata.storage !== 'none') {
  const { initTabCoordinator } = await import('./tab-coordinator');
  initTabCoordinator(...);
}

Pros: Standard playground ships zero personal code Cons: Slightly more complex code, small latency on first load

Option 3: Build-Time Code Elimination

Configure Vite to eliminate dead code based on defaultStorageType:

// vite.config.ts
define: {
  'import.meta.env.STORAGE_TYPE': JSON.stringify(defaultStorageType)
}

Then use compile-time constants:

if (import.meta.env.STORAGE_TYPE === 'opfs') {
  // This entire block is eliminated in standard builds (personal-only code)
}

Pros: Zero runtime overhead Cons: Requires careful setup, may not work with all patterns

Option 4: Separate Entry Points (Full App)

Create completely separate entry points for each deployment:

src/
  entry-standard.tsx    → playground.wordpress.net
  entry-personalwp.tsx    → my.wordpress.net

Pros: Complete separation Cons: More build complexity, potential code duplication

Recommendation

Use Option 1 (Browser Chrome Refactor) as the primary strategy:

  • PR 1b already does this work
  • Allows UIs to diverge naturally
  • Tree-shaking handles bundle size automatically
  • No runtime overhead, no lazy loading complexity

Option 2 (Lazy Loading) can be used as a fallback for components that are harder to split cleanly.

The browser chrome refactor should be done in Upstream PR 1b before the personal-specific features are added.


How Changes Are Guarded

Most personal-specific behavior is guarded by build-time configuration:

// vite.config.ts sets these at build time:
const defaultStorageType = isPersonalWPMode ? 'opfs' : 'none';
const defaultSiteSlug = isPersonalWPMode ? 'default' : undefined;

For standard (temporary) playground builds:

  • defaultStorageType = 'none'
  • defaultSiteSlug = undefined

Risk Areas (Changes That Touch Shared Code)

File Lines Changed Risk Level Why
boot-site-client.ts ~407 Medium Core boot logic; multi-tab code runs conditionally on storage !== 'none'
slice-sites.ts ~158 Medium Site management logic; personal checks guard most changes
router.ts ~27 Low URL handling; defaultSiteSlug check guards personal-specific paths
browser-chrome/index.tsx Small Low Conditional render: BackupStatusIndicator vs SaveStatusIndicator

Specific Risks to Standard Deployment

  1. boot-site-client.ts changes

    • Multi-tab coordinator init is guarded by if (site.metadata.storage !== 'none')
    • Should not affect temporary sites, but this is the riskiest area
    • Mitigation: Test temporary playgrounds with multiple tabs open
  2. slice-sites.ts changes

    • broadcastMetadataUpdate() is called on all updateSiteMetadata calls
    • For temporary sites this should be a no-op (no other tabs care), but adds overhead
    • Mitigation: Verify broadcastMetadataUpdate gracefully handles non-personal sites
  3. Removed page-title URL parameter (router.ts)

    • This parameter was removed from the preserved params list
    • Could break existing URLs that use ?page-title=...
    • Mitigation: Check if this is intentional or should be kept for backwards compatibility
  4. Import of virtual:website-defaults

    • Several files now import from this virtual module
    • If the module isn't properly defined in standard builds, it would break
    • Mitigation: Vite config always defines these, but verify build works

Low-Risk Areas (Additive or Isolated)

These are new files/components that don't affect standard deployment:

No-Risk Areas (Isolated to Personal Deployment)


Changes That Benefit Standard Playground

These changes would naturally improve the standard deployment if merged:

Change Benefit
Recovery tools (PRs #7, #13) Users of saved OPFS sites in standard Playground could recover from broken plugins
Health Check integration Better error recovery for any personal site
Overlay refactor (PR #8) Cleaner component structure benefits maintainability
virtual:website-defaults pattern Makes the codebase more configurable for different deployments
Documentation updates Architecture docs apply to both deployments

Recommended for Upstreaming First

These PRs have the cleanest separation and broadest benefit:

  1. PR 1 (Overlay refactor) - Pure refactor, no feature flags needed
  2. PR 2 (Enable personal deployment) - Browser chrome refactor + website defaults + basic personal mode, all testable together

Testing Checklist for Standard Deployment

Before merging, verify these scenarios work on playground.wordpress.net:

  • Temporary site boots correctly
  • Multiple tabs with temporary sites work independently
  • Saved OPFS sites still work (site manager)
  • Blueprint URL parameters work (?plugin=..., ?blueprint-url=...)
  • URL routing doesn't break (no 404s)
  • Build completes without errors in standard mode
  • No console errors related to virtual:website-defaults

Future Outlook: SharedWorker Support

Current State (2025-2026)

Safari has reinstated SharedWorker support (Safari 16+, including iOS). The only major holdout is Chrome on Android.

Browser SharedWorker Support
Chrome (Desktop) Yes
Firefox (Desktop/Mobile) Yes
Safari (Desktop/iOS) Yes (16+)
Chrome (Android) No

How SharedWorker Could Change the Architecture

Current approach (BroadcastChannel + Main/Dependent tabs):

Tab A (Main)          Tab B (Dependent)      Tab C (Dependent)
    │                      │                      │
    ▼                      │                      │
┌─────────┐                │                      │
│ Worker  │◄───────────────┴──────────────────────┘
│ (PHP)   │         BroadcastChannel coordination
└─────────┘
    │
    ▼
  OPFS

SharedWorker approach:

Tab A                 Tab B                 Tab C
    │                    │                    │
    └────────────────────┼────────────────────┘
                         │
                         ▼
                  ┌─────────────┐
                  │SharedWorker │
                  │   (PHP)     │
                  └─────────────┘
                         │
                         ▼
                       OPFS

Would SharedWorker Make Tab Management Unnecessary?

Partially yes, but not entirely.

What SharedWorker Would Eliminate

  1. Main/Dependent mode distinction - All tabs connect to the same worker equally
  2. Takeover protocol - No need to transfer "ownership" between tabs
  3. BroadcastChannel coordination - SharedWorker is the natural coordination point
  4. Stale tab detection - Worker stays alive as long as any tab is connected

What Would Still Be Needed

  1. UI coordination - Which tab shows the "active" indicator? Which responds to keyboard shortcuts?
  2. Backup reminders - Still need to track days since backup across sessions
  3. Cross-tab metadata sync - Backup history, last access date (though simpler via SharedWorker)
  4. Graceful degradation - Chrome Android users still need the current approach

Implementation Considerations

  1. PHP/WASM in SharedWorker

    • The Playground PHP worker would need to run inside a SharedWorker
    • All tabs would communicate via MessagePort instead of direct iframe postMessage
    • This is a significant architectural change to @wp-playground/web
  2. OPFS Access

    • SharedWorkers can access OPFS, so this would work
    • Single writer eliminates corruption risk entirely
  3. Iframe Coordination

    • Each tab still has its own iframe showing WordPress
    • SharedWorker would need to route responses to the correct tab's iframe
    • More complex message routing than current single-tab-owns-worker model
  4. Lifecycle

    • SharedWorker stays alive while any tab is connected
    • If all tabs close, worker terminates (and state in memory is lost, but OPFS persists)
    • On reopen, worker restarts and reloads from OPFS

Migration Path

Phase Approach Chrome Android
Current BroadcastChannel + Main/Dependent Works
Future SharedWorker with fallback Falls back to current approach

A hybrid approach could detect SharedWorker support and use it when available:

if (typeof SharedWorker !== 'undefined') {
  // Use SharedWorker approach
} else {
  // Fall back to BroadcastChannel coordination
}

Recommendation

The current BroadcastChannel approach is not wasted work because:

  1. Chrome Android - Still needs it (significant mobile market share)
  2. Fallback - Even with SharedWorker, you'd want a fallback
  3. Simpler first step - SharedWorker requires deeper architectural changes to how Playground boots workers

However, SharedWorker should be on the roadmap as the preferred approach once:

  • Chrome Android adds support, OR
  • The team decides Chrome Android can use the fallback

The current tab coordination code could eventually become the "legacy fallback" path.


Questions / Discussion Points

  1. Should the welcome plugin be a submodule or bundled differently?
  2. Is the 1-day threshold for "stale" tabs appropriate?
  3. Should backup reminders be configurable per deployment?
  4. How should this integrate with the existing Playground site management UI?
  5. Is the page-title parameter removal intentional? (Could break existing URLs)
  6. Should broadcastMetadataUpdate be a no-op for non-personal sites? (Minor performance consideration)
  7. Should SharedWorker support be planned as a future optimization? (Would simplify multi-tab significantly)
  8. What's the cleanest way to handle the welcome plugin? (Submodule vs. bundled vs. external blueprint)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment