Skip to main content

Overview

Time Capsule includes an automatic version management system that handles cache clearing and displays an authentic Unix-style package update sequence when the application version changes.
The system mimics real Unix package management, providing a seamless and authentic update experience without interrupting the user flow.

How It Works

Version Detection

When the application loads, the VersionManager checks if the stored version matches the current version:
// Stored in localStorage: 'cde-app-version'
const storedVersion = localStorage.getItem('cde-app-version');
const currentVersion = import.meta.env.PUBLIC_APP_VERSION;

if (storedVersion !== currentVersion) {
  // Trigger update sequence
}
export class VersionManager {
  private currentVersion: string;

  private constructor() {
    // Version from package.json (injected at build time)
    this.currentVersion = import.meta.env.PUBLIC_APP_VERSION || '1.0.0';
  }

  public async checkVersion(): Promise<void> {
    const storedVersion = localStorage.getItem('cde-app-version');

    if (!storedVersion) {
      // First time user
      localStorage.setItem('cde-app-version', this.currentVersion);
      return;
    }

    if (storedVersion !== this.currentVersion) {
      await this.performVersionUpdate(storedVersion, this.currentVersion);
    }
  }
}

Update Trigger

When a version mismatch is detected, the system performs these steps:
1

Clear localStorage

Remove all localStorage items except preserved keys:
const preserveKeys: string[] = [];
const allKeys = Object.keys(localStorage);

allKeys.forEach((key) => {
  if (!preserveKeys.includes(key) && key !== 'cde-app-version') {
    localStorage.removeItem(key);
  }
});
2

Clear IndexedDB

Clear the settings store:
const { indexedDBManager, STORES } = await import('./indexeddb-manager');
await indexedDBManager.clear(STORES.SETTINGS);
3

Clear Service Worker Caches

Remove all cached resources:
if ('caches' in window) {
  const cacheNames = await caches.keys();
  await Promise.all(
    cacheNames.map((cacheName) => caches.delete(cacheName))
  );
}
4

Set Pending Update Flag

Mark that an update sequence should be shown:
localStorage.setItem('cde-pending-update', 'true');
5

Reload Page

Force a page reload to show the update sequence:
window.location.reload();
The page reloads automatically after clearing caches. This ensures all changes take effect immediately.

Update Sequence Display

On the next boot, the system displays an authentic Unix package update sequence:
Shows boot-messages.json with:
  • Kernel initialization messages
  • Service startup logs
  • Desktop environment loading
const isUpdateMode = VersionManager.hasPendingUpdate();
window.debianBoot = new DebianRealBoot(isUpdateMode);

Boot Sequence Generalization

The DebianRealBoot class accepts a mode parameter:
class DebianRealBoot {
  constructor(isUpdateMode: boolean) {
    this.messagesFile = isUpdateMode 
      ? '/data/update-messages.json'
      : '/data/boot-messages.json';
  }
}
Both modes share:
  • Boot screen component
  • CSS classes and animations
  • Progress bar
  • Completion logic
This design allows code reuse while providing different content for each mode.

File Structure

Boot Messages

Defines the normal boot sequence:
{
  "phases": [
    {
      "name": "kernel",
      "min": 5,
      "max": 8,
      "messages": [
        {
          "text": "Linux version 2.0.36 (root@debian) (gcc version 2.7.2.3)",
          "type": "kernel"
        },
        {
          "text": "Console: colour VGA+ 80x25",
          "type": "kernel"
        }
      ]
    },
    {
      "name": "services",
      "min": 6,
      "max": 10,
      "messages": [
        {
          "text": "Starting CDE login manager...",
          "type": "service"
        }
      ]
    }
  ]
}
Each phase has min/max timing values to simulate realistic boot delays.

Update Messages

Defines the package update sequence:
{
  "phases": [
    {
      "name": "preparation",
      "min": 3,
      "max": 5,
      "messages": [
        {
          "text": "Reading package lists... Done",
          "type": "package"
        },
        {
          "text": "Building dependency tree",
          "type": "package"
        }
      ]
    },
    {
      "name": "packages",
      "min": 8,
      "max": 12,
      "messages": [
        {
          "text": "Get:1 http://archive.debian.org/debian bo/main libxpm4 3.4f-1",
          "type": "download"
        },
        {
          "text": "Get:2 http://archive.debian.org/debian bo/main cde-base 2.2.0-1",
          "type": "download"
        }
      ]
    },
    {
      "name": "installation",
      "min": 6,
      "max": 9,
      "messages": [
        {
          "text": "Unpacking libxpm4 (3.4f-1)...",
          "type": "install"
        },
        {
          "text": "Setting up cde-base (2.2.0-1)...",
          "type": "install"
        }
      ]
    }
  ]
}

Message Types and Colors

Boot Mode

kernel

Color: Gray (#cccccc)Kernel initialization messages

cpu

Color: Light blue (#88aaff)CPU detection and info

memory

Color: Orange (#ffaa88)Memory detection and allocation

fs

Color: Yellow (#ffff88)Filesystem mounting

systemd

Color: Cyan (#88ffff)Init system messages

service

Color: Green (#00ff00)Service startup

drm

Color: Red (#ff8888)Graphics subsystem

desktop

Color: Bright cyan (#00ffaa)Desktop ready

Update Mode

package

Color: Light blue (#88aaff)Package operations (reading lists, building dependencies)

download

Color: Orange (#ffaa88)Package downloads from repository

install

Color: Green (#00ff00)Package installation and unpacking

service

Color: Green (#00ff00)Service restarts and reloads

CSS Implementation

Colors are defined in /public/css/desktop/boot-screen.css:
.boot-kernel { color: #cccccc; }
.boot-cpu { color: #88aaff; }
.boot-memory { color: #ffaa88; }
.boot-fs { color: #ffff88; }
.boot-systemd { color: #88ffff; }
.boot-service { color: #00ff00; }
.boot-drm { color: #ff8888; }
.boot-desktop { color: #00ffaa; }

.boot-package { color: #88aaff; }
.boot-download { color: #ffaa88; }
.boot-install { color: #00ff00; }

Testing Updates

You can manually test the update sequence for development:
1

Open Browser Console

Press F12 or right-click → Inspect → Console
2

Change Version

Run this command in the console:
localStorage.setItem('cde-app-version', '0.0.1');
3

Reload Page

Press F5 or refresh the page
4

Observe Update Sequence

You’ll see the package update sequence instead of the normal boot
After the update sequence completes, the version will be updated to the current version automatically.

Triggering Updates in Production

To trigger updates for users in production:
1

Update package.json

Increment the version number:
{
  "version": "1.0.32"
}
Current version: 1.0.31
2

Commit and Deploy

Commit the change and deploy to production:
git add package.json
git commit -m "Bump version to 1.0.32"
git push origin main
3

Users See Update

When users visit the site, they’ll automatically see the update sequence
The version from package.json is injected at build time via Vite as import.meta.env.PUBLIC_APP_VERSION.

Customization

Adding New Message Types

1

Update Messages File

Add to update-messages.json or boot-messages.json:
{
  "text": "My new message",
  "type": "newtype"
}
2

Add CSS Class

In public/css/desktop/boot-screen.css:
.boot-newtype {
  color: #ff00ff;
  font-weight: bold;
}
3

Update Type Map

In src/scripts/boot/init.ts:
private getLineClass(type: string): string {
  const map: Record<string, string> = {
    kernel: 'boot-kernel',
    cpu: 'boot-cpu',
    // ... existing types
    newtype: 'boot-newtype',
  };
  return map[type] || 'boot-default';
}

Preserving User Data During Updates

By default, the update system clears all localStorage. To preserve specific keys:
// src/scripts/core/version-manager.ts:86
const preserveKeys: string[] = [
  'cde-system-settings',    // User preferences
  'cde_high_contrast',      // Accessibility settings
  'user-custom-theme',      // Custom themes
  // Add more keys to preserve
];
Only preserve keys that are forward-compatible. Breaking changes in data structure should not be preserved.

Custom Update Logic

For version-specific migrations, add logic in the update method:
private async performVersionUpdate(
  oldVersion: string, 
  newVersion: string
): Promise<void> {
  
  // Version-specific migrations
  if (oldVersion === '1.0.30' && newVersion === '1.0.31') {
    await this.migrateSettingsFormat();
  }
  
  if (this.isBreakingChange(oldVersion, newVersion)) {
    await this.fullCacheClear();
  } else {
    await this.partialCacheClear();
  }
  
  // ... rest of update process
}

Architecture Benefits

No Modal Interruption

Updates feel like a natural system operation rather than an intrusive popup

Authentic Experience

Mimics real Unix package management for period-accurate feel

Code Reuse

Same boot screen component handles both normal boot and update modes

Flexible

Easy to add new message types, phases, or customize timing

Testable

Can trigger updates manually in console for testing

Automatic

No user intervention required - just bump version and deploy
version-manager.ts - /src/scripts/core/version-manager.tsVersion detection, cache clearing, and update orchestrationinit.ts - /src/scripts/boot/init.tsBoot sequence orchestration and message display
boot-messages.json - /src/data/boot-messages.jsonNormal boot sequence messages with kernel, services, and desktop startupupdate-messages.json - /src/data/update-messages.jsonPackage update sequence with downloads and installation
BootSequence.astro - /src/components/desktop/BootSequence.astroBoot screen component used for both modesboot-screen.css - /public/css/desktop/boot-screen.cssStyling for boot messages, colors, and animations

Version History

Current Version: 1.0.31

The project uses semantic versioning:
  • Major: Breaking changes requiring user action
  • Minor: New features, backward compatible
  • Patch: Bug fixes and minor improvements
Version bumps that require cache clearing should increment at least the minor version.

Version Update Best Practices

When to use: Major architectural changes, data structure changes
{ "version": "2.0.0" }
  • Clear all caches
  • Show update sequence
  • Consider data migration

Future Enhancements

Add version-specific migration scripts:
class MigrationManager {
  private migrations = new Map<string, Migration>();
  
  register(fromVersion: string, toVersion: string, fn: Migration) {
    this.migrations.set(`${fromVersion}->${toVersion}`, fn);
  }
  
  async migrate(from: string, to: string) {
    const key = `${from}->${to}`;
    const migration = this.migrations.get(key);
    if (migration) {
      await migration();
    }
  }
}
Show changelog after update completes:
async showChangelog(version: string) {
  const changes = await fetch(`/changelogs/${version}.md`);
  const content = await changes.text();
  this.displayInWindow(content);
}
Add ability to rollback failed updates:
class VersionManager {
  async rollback() {
    const previousVersion = this.getPreviousVersion();
    await this.restoreBackup(previousVersion);
    window.location.reload();
  }
}
Track update history in IndexedDB:
interface UpdateRecord {
  fromVersion: string;
  toVersion: string;
  timestamp: number;
  success: boolean;
}

await indexedDBManager.add(STORES.UPDATE_HISTORY, record);

Debugging

The VersionManager is globally accessible for debugging:
// Check current version
window.VersionManager.getVersion();
// Returns: "1.0.31"

// Check if update is pending
window.VersionManager.hasPendingUpdate();
// Returns: true or false

// Force an update
await window.VersionManager.forceUpdate();
// Clears caches and reloads

// Clear pending update flag
window.VersionManager.clearPendingUpdate();
These are debugging methods. Do not use in production code.