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
}
version-manager.ts
Location
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 );
}
}
}
File : /src/scripts/core/version-manager.ts:32The version check runs on application initialization before the desktop loads.
Update Trigger
When a version mismatch is detected, the system performs these steps:
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 );
}
});
Clear IndexedDB
Clear the settings store: const { indexedDBManager , STORES } = await import ( './indexeddb-manager' );
await indexedDBManager . clear ( STORES . SETTINGS );
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 ))
);
}
Set Pending Update Flag
Mark that an update sequence should be shown: localStorage . setItem ( 'cde-pending-update' , 'true' );
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 );
Shows update-messages.json with:
Package list reading
Package downloads
Installation progress
Configuration updates
More authentic Unix package management experience.
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:
Open Browser Console
Press F12 or right-click → Inspect → Console
Change Version
Run this command in the console: localStorage . setItem ( 'cde-app-version' , '0.0.1' );
Reload Page
Press F5 or refresh the page
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:
Update package.json
Increment the version number: Current version: 1.0.31
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
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
Update Messages File
Add to update-messages.json or boot-messages.json: {
"text" : "My new message" ,
"type" : "newtype"
}
Add CSS Class
In public/css/desktop/boot-screen.css: .boot-newtype {
color : #ff00ff ;
font-weight : bold ;
}
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 orchestration init.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 startup update-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 modes boot-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
Breaking Changes
New Features
Bug Fixes
When to use : Major architectural changes, data structure changes
Clear all caches
Show update sequence
Consider data migration
When to use : Adding applications, new themes, major features
May clear caches if needed
Show update sequence
Preserve compatible data
When to use : Bug fixes, minor improvements, CSS tweaks
Usually no cache clear
No update sequence
Service worker handles updates
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.