File System
Browser-based hierarchical virtual file system with two-layer architecture for metadata and content separation.
Two-Layer Architecture
- Metadata Layer (Zustand + localStorage): File paths, names, types, UUIDs, timestamps, and status
- Content Layer (IndexedDB): Actual file content indexed by UUID for efficient storage
graph TB
subgraph Application["Application Layer"]
App[React Components]
Hook[useFileSystem Hook]
end
subgraph Metadata["Metadata Layer"]
FilesStore[(useFilesStore)]
FinderStore[(useFinderStore)]
LocalStorage[(localStorage)]
end
subgraph Content["Content Layer"]
IDB[(IndexedDB)]
subgraph Stores["Object Stores"]
Docs[documents]
Imgs[images]
Apps[applets]
Trash[trash]
Wall[custom_wallpapers]
end
end
App --> Hook
Hook --> FilesStore
Hook --> FinderStore
FilesStore <--> LocalStorage
FinderStore <--> LocalStorage
Hook -->|"UUID lookup"| IDB
IDB --> Stores
Key Stores
| Store | Purpose | Persistence |
|---|---|---|
useFilesStore | File/folder metadata, paths, UUIDs, status | localStorage |
useFinderStore | Finder window instances, navigation history, view preferences | localStorage |
| IndexedDB | File content (text, images, applets) | Browser storage |
Directory Structure
| Path | Type | Description |
|---|---|---|
/ | Root | Root directory |
/Applications | Virtual | Apps from registry (non-Finder apps) |
/Documents | Physical | User text documents (.txt, .md) |
/Images | Physical | User images (PNG, JPG, GIF, WebP, BMP) |
/Music | Virtual | iPod library (organized by artist) |
/Videos | Virtual | Video library (organized by artist) |
/Sites | Virtual | Internet Explorer favorites |
/Applets | Physical | HTML applets (.app, .html files) |
/Trash | Special | Deleted items (restorable) |
/Downloads | Physical | User downloads (AirDrop and similar) |
/Desktop | Physical | Shortcuts and aliases |
graph TD
Root["/"] --> Apps["/Applications
Virtual"]
Root --> Docs["/Documents
Physical"]
Root --> Imgs["/Images
Physical"]
Root --> Music["/Music
Virtual"]
Root --> Videos["/Videos
Virtual"]
Root --> Sites["/Sites
Virtual"]
Root --> Applets["/Applets
Physical"]
Root --> Trash["/Trash
Special"]
Root --> Downloads["/Downloads
Physical"]
Root --> Desktop["/Desktop
Physical"]
Apps -.->|"from appRegistry"| AppReg[(App Registry)]
Music -.->|"from useIpodStore"| iPod[(iPod Library)]
Videos -.->|"from useVideoStore"| VidLib[(Video Library)]
Sites -.->|"from useInternetExplorerStore"| IEFav[(IE Favorites)]
Docs -->|"stored in"| IDB[(IndexedDB)]
Imgs -->|"stored in"| IDB
Applets -->|"stored in"| IDB
File Metadata
interface FileSystemItem {
// Core properties
path: string; // Full path, unique identifier (e.g., "/Documents/note.md")
name: string; // File/folder name
isDirectory: boolean;
type?: string; // File type (markdown, text, png, jpg, html, etc.)
icon?: string; // Icon path or emoji
appId?: string; // Associated application ID
// Content reference
uuid?: string; // UUID for IndexedDB content lookup (files only)
// File properties
size?: number; // File size in bytes
createdAt?: number; // Creation timestamp
modifiedAt?: number; // Last modified timestamp
// Status
status: "active" | "trashed";
originalPath?: string; // Path before moving to trash
deletedAt?: number; // When moved to trash
// Applet sharing
shareId?: string; // Share ID for shared applets (from Redis)
createdBy?: string; // Creator username
storeCreatedAt?: number;
// Window dimensions
windowWidth?: number;
windowHeight?: number;
// Alias/shortcut properties
aliasTarget?: string; // Target path or appId
aliasType?: "file" | "app";
hiddenOnThemes?: OsThemeId[]; // Hide on specific OS themes
}
IndexedDB Storage
Database: ryOS (version 7)
| Object Store | Content Type | Key |
|---|---|---|
documents | Text files (strings) | UUID |
images | Binary images (Blobs) | UUID |
applets | HTML applet content | UUID |
trash | Deleted file content | UUID |
custom_wallpapers | User wallpapers | UUID |
Content structure stored in IndexedDB:
interface StoredContent {
name: string; // Original filename
content: string | Blob; // File content
}
CRUD Operations
File Operations
// useFileSystem hook provides these operations
const {
// Navigation
currentPath,
navigateToPath,
navigateUp,
navigateBack,
navigateForward,
// File listing
files,
isLoading,
error,
// Selection (supports multi-select with Ctrl/Cmd+click and Shift+click)
selectedFiles,
selectionAnchorPath,
handleFileSelect,
handleFileOpen,
// File operations
saveFile, // Create or update file
renameFile, // Rename file/folder
createFolder, // Create new folder
moveFile, // Move file to different folder
// Trash operations
moveToTrash,
restoreFromTrash,
emptyTrash,
trashItemsCount,
// System
formatFileSystem, // Reset entire filesystem
} = useFileSystem(initialPath, options);
Save File Example
await saveFile({
path: "/Documents/note.md",
name: "note.md",
content: "# My Note\nContent here",
type: "markdown",
icon: "/icons/file-text.png",
});
Move to Trash Flow
- Mark item as
status: "trashed"in metadata - Store
originalPathanddeletedAttimestamp - For Documents/Images files, move content from original store to
trashstore in IndexedDB - Update Trash folder icon (
trash-full.png)
Restore from Trash Flow
- Reset
statusto"active", clearoriginalPathanddeletedAt - For Documents/Images files, move content back from
trashstore to original store - Update Trash folder icon if empty
Lazy Loading
Default file content is lazy-loaded on first access:
// Register files for lazy loading during initialization
registerFilesForLazyLoad(files, items);
// Load content when file is opened
await ensureFileContentLoaded(filePath, uuid);
Files with assetPath in public/data/filesystem.json are fetched on-demand, not during app initialization. This improves startup performance.
preloadFileSystemData() starts fetching filesystem.json and applets.json during bootstrap so metadata is warm before Finder mounts, while binary file assets still load on first open.
Finder App Integration
Multi-Window Support
Each Finder window is an instance with its own state:
interface FinderInstance {
instanceId: string;
currentPath: string;
navigationHistory: string[];
navigationIndex: number;
viewType: ViewType; // "small" | "large" | "list"
sortType: SortType; // "name" | "date" | "size" | "kind"
selectedFiles: string[]; // Multi-select support
selectionAnchorPath: string | null; // Anchor for range selection
}
Undo/Redo for File Operations
Finder maintains a per-instance undo/redo stack for file operations:
type FinderUndoAction =
| { type: "moveToTrash"; fileName: string; originalPath: string }
| { type: "rename"; basePath: string; oldName: string; newName: string };
Supported undo/redo actions:
- Move to Trash: Undo restores the trashed item; redo re-trashes it
- Rename: Undo reverts to the old name; redo reapplies the new name
The stack is capped at 20 entries. Any new action clears the redo stack.
Reactive File View
Finder automatically refreshes when the file store's items reference changes (e.g., after move-to-trash, restore, empty-trash, or cloud sync). This is achieved via a Zustand subscribe() call that triggers loadFiles() whenever state.items changes. Cloud sync updates for images are also detected through useCloudSyncStore subscription.
Sorting with Localized Names
Finder sorts items using locale-aware comparison via compareFinderItemsByDisplayName() and compareFinderSortText() from src/utils/finderDisplay.ts. These utilities use Intl.Collator with the current UI language so that translated folder names (e.g., Japanese, Korean) sort correctly alongside Latin names.
View Type Preferences
Per-path view type preferences persist across sessions:
// Default view types by path
getDefaultViewTypeForPath(path) {
if (path === "/" || path.startsWith("/Images") ||
path.startsWith("/Videos") || path.startsWith("/Applications") ||
path.startsWith("/Applets") || path.startsWith("/Trash") ||
path.startsWith("/Music"))
return "large";
if (path.startsWith("/Documents"))
return "list";
return "list";
}
File Type Detection
function getFileTypeFromExtension(fileName: string): string {
const ext = fileName.split(".").pop()?.toLowerCase();
switch (ext) {
case "md": return "markdown";
case "txt": return "text";
case "png": case "gif": case "webp": case "bmp": return ext;
case "jpg": case "jpeg": return "jpg";
case "app": return "application";
default: return "unknown";
}
}
Supported File Types
| Extension | Type | Opens With | Storage |
|---|---|---|---|
.md | Markdown | TextEdit | IndexedDB (documents) |
.txt | Plain text | TextEdit | IndexedDB (documents) |
.png, .jpg, .jpeg, .gif, .webp, .bmp | Image | Paint | IndexedDB (images) |
.app, .html | HTML Applet | Applet Viewer | IndexedDB (applets) |
.mp3 | Audio | iPod | Virtual (iPod store) |
.mov | Video | Videos | Virtual (Video store) |
.webloc | Web link | Internet Explorer | Virtual (IE store) |
Desktop Shortcuts
Aliases/shortcuts on Desktop:
// Create alias from context menu or drag
fileStore.createAlias(
targetPath, // Original file path or app ID
aliasName, // Display name
aliasType, // "file" | "app"
targetAppId // For app shortcuts
);
Desktop shortcuts support:
- App shortcuts (launches the app)
- File shortcuts (opens the target file)
- Theme-specific visibility (
hiddenOnThemes)
Cloud Sync
The cloud sync system (/api/sync/*) persists file metadata and content (documents, images, applets, trash) across devices. It also syncs calendar events, contacts, stickies, songs, videos, custom wallpapers, and settings from their respective stores, enabling backup and real-time sync via Pusher or local WebSocket.
Deletion Markers
When items are deleted (trash, empty trash, etc.), deletion markers (path/id → timestamp) are stored and synced. This enables correct handling of deletes across devices and safe recreation of items after deletion, using marker timestamps to resolve conflicts.
Merge-on-Conflict
Redis sync domains use merge-on-conflict instead of last-write-wins. On upload or 409 Conflict, mergeRedisStateConflict() merges local and remote snapshots per domain, reducing destructive overwrites. Domain-specific mergers handle files-metadata, stickies, calendar, contacts, songs, and videos.
Domain-Based Change Tracking
File operations emit domain-specific change events via emitCloudSyncDomainChange() and emitCloudSyncDomainChanges() so the sync system knows which data stores have been modified:
| Domain | Triggers |
|---|---|
files-metadata | Document save, format filesystem |
files-images | Image save, move, trash/restore |
files-trash | Move to trash, restore, empty trash |
files-applets | Applet save, fetch from share |
Individual file operations (save, rename, move, trash, restore) emit the appropriate domain events to enable incremental sync rather than full-store uploads.
Migration System
Store version migrations handle schema changes:
// Version history
// v5: Added UUID-based content keys
// v6: Added timestamps (createdAt, modifiedAt)
// v7: Added file size tracking
// v8: One-time sync for existing file sizes/timestamps
// v10: Updated Applets folder icon
Events
File system changes emit custom events:
// File saved/updated
window.dispatchEvent(new CustomEvent("fileUpdated", {
detail: { name, path }
}));
// File renamed
window.dispatchEvent(new CustomEvent("fileRenamed", {
detail: { oldPath, newPath, oldName, newName }
}));
// File saved (for applets)
window.dispatchEvent(new CustomEvent("saveFile", {
detail: { name, path, content, icon }
}));