Skip to content

Conversation

@akirk
Copy link
Member

@akirk akirk commented Jan 21, 2026

Based on #3157.

Motivation for the change, related issues

Personal Playgrounds store data in the browser's Origin Private File System (OPFS), which can be cleared unexpectedly by the browser. Users need a gentle reminder to back up their work periodically to avoid data loss.

This PR adds a backup status indicator that tracks usage days since the last backup and prompts users to download backups based on urgency.

Screenshots

Screenshot 2026-01-22 at 04 11 46 Screenshot 2026-01-22 at 04 18 24 Screenshot 2026-01-22 at 04 18 32 Screenshot 2026-01-22 at 04 18 07 Screenshot 2026-01-22 at 04 18 45

(No button when the backup is up to date)

Implementation details

New files:

  • backup-status-indicator.tsx - Component showing days since last backup with color-coded urgency (green ≤1 day, yellow 2-4 days, red 5+ days)
  • backup-status-indicator.module.css - Styles for the indicator button
  • use-backup.ts - Hook that handles backup creation using zipWpContent and file-saver

Modified files:

  • slice-sites.ts - Added metadata fields:
    • backupHistory - Array of recent backups (filename + timestamp)
    • lastAccessDate - Timestamp for tracking usage days
    • daysUsedSinceLastBackup - Counter reset on backup
  • persistent-browser-chrome/index.tsx - Added BackupStatusIndicator to toolbar

Behavior:

  • Indicator only appears after the user returns on a different day than creation
  • Tracks "days used" (not calendar days) - only increments when user actively uses the site
  • Clicking the button downloads a self-contained zip backup
  • Counter resets to 0 after successful backup

Testing Instructions (or ideally a Blueprint)

  1. Create a new persistent Playground
  2. Verify the backup indicator does NOT appear on the first day
  3. Open browser DevTools and use the snippet below to fake the last backup date
  4. The indicator should appear in yellow ("2 days since backup")
  5. Click the indicator button
  6. Verify a zip file downloads with format: {site-name}-backup-{date}-{time}.zip
  7. Verify the indicator disappears (counter reset to 0)
  8. Set daysUsedSinceLastBackup to 5 - indicator should appear in red
  // Fake old backup for testing (could be added to your snippets)
  window.fakeOldBackup = function(days = 3) {                                                                                             
    const store = window.__PLAYGROUND_STORE__;                                                                                            
    if (!store) {                                                                                                                         
      console.error('Store not available. Make sure you are in dev mode.');                                                               
      return;                                                                                                                             
    }                                                                                                                                     
                                                                                                                                          
    const state = store.getState();                                                                                                       
    const slug = state.ui.activeSite?.slug;                                                                                               
    if (!slug) {                                                                                                                          
      console.error('No active site');                                                                                                    
      return;                                                                                                                             
    }                                                                                                                                     
                                                                                                                                          
    const site = state.sites.entities[slug];                                                                                              
    if (!site) {                                                                                                                          
      console.error('Site not found');                                                                                                    
      return;                                                                                                                             
    }                                                                                                                                     
                                                                                                                                          
    const daysAgo = Date.now() - days * 24 * 60 * 60 * 1000;                                                                              
    store.dispatch({                                                                                                                      
      type: 'sites/updateSite',                                                                                                           
      payload: {                                                                                                                          
        id: slug,                                                                                                                         
        changes: {                                                                                                                        
          metadata: {                                                                                                                     
            ...site.metadata,                                                                                                             
            whenCreated: daysAgo - 24 * 60 * 60 * 1000,                                                                                   
            lastAccessDate: Date.now(),                                                                                                   
            daysUsedSinceLastBackup: days,                                                                                                
            backupHistory: [],                                                                                                            
          },                                                                                                                              
        },                                                                                                                                
      },                                                                                                                                  
    });                                                                                                                                   
                                                                                                                                          
    console.log(`Faked ${days} days since backup for site "${slug}"`);                                                                    
  };                                                                                                                                      
                                                                                                                                          
  console.log('Run fakeOldBackup(3) to simulate 3 days since backup');                                                                    

@akirk akirk force-pushed the persistent/health-check-recovery branch from de5b08e to 1baf5fd Compare January 21, 2026 18:14
@akirk akirk force-pushed the persistent/backup-reminder branch from 73357f6 to 6db275d Compare January 21, 2026 18:14
@akirk akirk force-pushed the persistent/health-check-recovery branch from 1baf5fd to 9676f6f Compare January 22, 2026 03:10
@akirk akirk force-pushed the persistent/backup-reminder branch from 6db275d to 4cff483 Compare January 22, 2026 03:10
@akirk akirk force-pushed the persistent/health-check-recovery branch from 6192b58 to 30ade19 Compare January 22, 2026 14:41
@akirk akirk force-pushed the persistent/backup-reminder branch from ccebb0e to 1555eba Compare January 22, 2026 14:41
akirk added 4 commits January 23, 2026 06:02
When a plugin breaks the persistent WordPress site, users can now
recover by installing the Health Check plugin in troubleshooting mode.

- Add health-check-recovery.ts with blueprint that installs Health Check
  and an MU-plugin to bypass hash verification
- Add Recovery section to PersistentPlaygroundOverlay with toggle button
- getBlueprintUrl encodes blueprints as base64 data URLs for easy sharing
Adds a backup reminder indicator for persistent sites that tracks
usage days since the last backup. Shows color-coded urgency
(green/yellow/red) and allows one-click backup downloads.
@akirk akirk force-pushed the persistent/health-check-recovery branch from 30ade19 to 6ee19d9 Compare January 23, 2026 05:03
@akirk akirk force-pushed the persistent/backup-reminder branch from 1555eba to 4da5ad5 Compare January 23, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants