This document summarizes the changes in this fork of wordpress-playground, intended to help with PR review.
- The Big Picture
- Core Features by PR
- Feature Details
- Stats
- PR Strategy Considerations
- Upstream PR Strategy (includes recommended separate package approach)
- Scratched & Evolved Features
- Tips for Reviewers
- Architecture Notes
- Risk Analysis: Impact on Standard Playground (includes bundle size concern)
- Changes That Benefit Standard Playground
- Testing Checklist
- Future Outlook: SharedWorker Support
- Questions / Discussion Points
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.
| 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 |
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 sitesdefaultStorageType-'opfs'for personal,'none'for temporarydefaultSiteSlug- Fixed site slug (e.g.,'my-wordpress')
This allows the main repo to support multiple deployment targets with different defaults.
- Welcome plugin with localized content
- Automatic language detection from browser settings
- App catalog - pre-configured blueprints users can install (e.g., "CRM", "Friends plugin")
- 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
- 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)
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:
packages/playground/website/src/lib/state/redux/tab-coordinator.tspackages/playground/website/src/lib/state/redux/cross-tab-sync.ts
| 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 |
| 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.
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/ |
| 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 |
| 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.
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.
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"See Scratched & Evolved Features below for code that should not be included.
| Include | Skip |
|---|---|
Final tab-coordinator.ts |
Intermediate iterations |
Final backup-reminder/ component |
Early backup UI attempts |
health-check-recovery.ts |
|
| Final welcome plugin state | 9 iterations of welcome text |
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 websiteThis approach acknowledges that getting everything polished for the main website package may take longer than desired, while a separate package could potentially ship immediately.
The following sections describe the original gradual integration approach, which remains an option if the team prefers a single codebase.
All 37 website source files organized by upstream PR.
Base path: packages/playground/website/src/
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 |
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
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 |
Can now test against PR #2's personal deployment
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 |
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 |
| 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 |
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.
What: Extract shared overlay components (Overlay, OverlayHeader, OverlayBody, OverlaySection)
Files:
- New:
components/overlay/index.tsx,style.module.css - Modified:
components/saved-playgrounds-overlay/index.tsx(use new base)
Why first: Pure refactor, no feature flags, improves code quality for everyone. Can be tested immediately on standard Playground.
What: Combined PR that enables and allows testing of personal deployment:
- Browser chrome refactor - Extract shared base, create Standard/Personal variants
- Website defaults infrastructure - Add
virtual:website-defaultsVite plugin - Basic personal mode - Add
--mode personalwpbuild with minimal overlay
Files:
- Refactor:
components/browser-chrome/index.tsx→browser-chrome-base.tsx - New:
components/browser-chrome/standard-browser-chrome.tsx - New:
components/browser-chrome/personalwp-browser-chrome.tsx - Refactor:
components/address-bar/index.tsx - Modified:
vite.config.ts - New:
build-personalwp.sh - New:
components/personalwp-overlay/(minimal version) - New:
blueprints/personalwp-boot.json
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, usesPersonalWPBrowserChrome,defaultStorageType = 'opfs')
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)
What: Add backup reminder UI for personal sites
Files:
- New:
components/backup-reminder/ - New:
components/browser-chrome/backup-status-indicator.tsx - Modified:
components/browser-chrome/personalwp-browser-chrome.tsx(uses BackupStatusIndicator)
Why: Can now be tested against PR 2's personal deployment.
Depends on: PR #2 (for testing)
What: Add tab coordinator for personal sites to prevent OPFS conflicts
Files:
- New:
lib/state/redux/tab-coordinator.ts - New:
lib/state/redux/cross-tab-sync.ts - Modified:
lib/state/redux/boot-site-client.ts - Modified:
lib/state/redux/slice-sites.ts
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.
What: Add automatic language detection and app catalog for personal deployment
Files:
- New:
lib/personalwp/i18n.ts - New: App catalog components
- Submodule: welcome plugin
Why: Polish for the personal deployment. Can be merged after core functionality is working.
Depends on: PR #2
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.
While upstreaming:
- Keep
worker-handoverbranch as the working integration - Cherry-pick clean code into upstream PRs
- Rebase fork on trunk as PRs merge
- my.wordpress.net continues running from fork until all PRs merge
| 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.
Features that were tried and abandoned or significantly reworked. These should be excluded from clean PRs.
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:
personalwp-overlay.tsxhad 208 lines added, later removedsaved-playgrounds-overlay/style.module.csshad 63 lines added for this UI
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.
| 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.
| 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.
| 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 |
- Start with PR 1 - Pure overlay refactor, easy to review
- Review PR 2 (Enable personal deployment) - Larger PR but enables testing everything else
- Review PRs 3, 4 - Health Check recovery and backup reminder (can test immediately)
- Review PR 5 last - Multi-tab coordination is the most complex and highest risk
- PR 6 - Polish (i18n, app catalog)
-
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.
-
Is the
virtual:website-defaultsapproach appropriate? This is how environment-specific configuration is injected at build time. -
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.
-
Is the backup reminder UX appropriate? It needs to encourage backups without being annoying.
| 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 |
- Core Playground PHP/WASM runtime
- Blueprint execution engine
- OPFS storage implementation (just configuration)
- Existing temporary playground functionality
- Basic personal flow: Open my.wordpress.net, make changes, close tab, reopen - changes should persist.
- Multi-tab: Open site in two tabs, make changes in one, see if the other stays in sync.
- Takeover: Open site in tab A, then open with
?plugin=somethingin tab B - tab B should take over. - Recovery: Install a plugin that crashes WordPress, verify recovery UI appears.
- Backup: Use the site for a day, verify backup reminder appears, test export/import.
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.
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.
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
)}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 |
Note: If the separate
personal-wppackage approach is used, most risks in this section become irrelevant since the standard Playground remains untouched.
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 |
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.
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
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
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
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.
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
| 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 |
-
boot-site-client.tschanges- 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
- Multi-tab coordinator init is guarded by
-
slice-sites.tschangesbroadcastMetadataUpdate()is called on allupdateSiteMetadatacalls- For temporary sites this should be a no-op (no other tabs care), but adds overhead
- Mitigation: Verify
broadcastMetadataUpdategracefully handles non-personal sites
-
Removed
page-titleURL 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
-
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
These are new files/components that don't affect standard deployment:
tab-coordinator.ts- New file, only used whenstorage !== 'none'cross-tab-sync.ts- New file, only used for personal sitesbackup-reminder/- New component, only rendered whendefaultStorageType === 'opfs'personalwp-overlay/- New component, conditionally renderedpersonalwp/- New directory, only imported in personal code pathshealth-check-recovery.ts- New file, recovery feature
.github/workflows/deploy-my-wordpress-net.yml- Separate workflowbuild-personalwp.sh- Separate build scriptblueprints/personalwp-boot.json- Only used in personal mode- Welcome plugin submodule - Only loaded by personal blueprint
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 |
These PRs have the cleanest separation and broadest benefit:
- PR 1 (Overlay refactor) - Pure refactor, no feature flags needed
- PR 2 (Enable personal deployment) - Browser chrome refactor + website defaults + basic personal mode, all testable together
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
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 |
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
Partially yes, but not entirely.
- Main/Dependent mode distinction - All tabs connect to the same worker equally
- Takeover protocol - No need to transfer "ownership" between tabs
- BroadcastChannel coordination - SharedWorker is the natural coordination point
- Stale tab detection - Worker stays alive as long as any tab is connected
- UI coordination - Which tab shows the "active" indicator? Which responds to keyboard shortcuts?
- Backup reminders - Still need to track days since backup across sessions
- Cross-tab metadata sync - Backup history, last access date (though simpler via SharedWorker)
- Graceful degradation - Chrome Android users still need the current approach
-
PHP/WASM in SharedWorker
- The Playground PHP worker would need to run inside a SharedWorker
- All tabs would communicate via
MessagePortinstead of direct iframe postMessage - This is a significant architectural change to
@wp-playground/web
-
OPFS Access
- SharedWorkers can access OPFS, so this would work
- Single writer eliminates corruption risk entirely
-
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
-
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
| 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
}The current BroadcastChannel approach is not wasted work because:
- Chrome Android - Still needs it (significant mobile market share)
- Fallback - Even with SharedWorker, you'd want a fallback
- 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.
- Should the welcome plugin be a submodule or bundled differently?
- Is the 1-day threshold for "stale" tabs appropriate?
- Should backup reminders be configurable per deployment?
- How should this integrate with the existing Playground site management UI?
- Is the
page-titleparameter removal intentional? (Could break existing URLs) - Should
broadcastMetadataUpdatebe a no-op for non-personal sites? (Minor performance consideration) - Should SharedWorker support be planned as a future optimization? (Would simplify multi-tab significantly)
- What's the cleanest way to handle the welcome plugin? (Submodule vs. bundled vs. external blueprint)