ryOS ryOS / Docs
GitHub Launch

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

StorePurposePersistence
useFilesStoreFile/folder metadata, paths, UUIDs, statuslocalStorage
useFinderStoreFinder window instances, navigation history, view preferenceslocalStorage
IndexedDBFile content (text, images, applets)Browser storage

Directory Structure

PathTypeDescription
/RootRoot directory
/ApplicationsVirtualApps from registry (non-Finder apps)
/DocumentsPhysicalUser text documents (.txt, .md)
/ImagesPhysicalUser images (PNG, JPG, GIF, WebP, BMP)
/MusicVirtualiPod library (organized by artist)
/VideosVirtualVideo library (organized by artist)
/SitesVirtualInternet Explorer favorites
/AppletsPhysicalHTML applets (.app, .html files)
/TrashSpecialDeleted items (restorable)
/DownloadsPhysicalUser downloads (AirDrop and similar)
/DesktopPhysicalShortcuts 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 StoreContent TypeKey
documentsText files (strings)UUID
imagesBinary images (Blobs)UUID
appletsHTML applet contentUUID
trashDeleted file contentUUID
custom_wallpapersUser wallpapersUUID

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

  1. Mark item as status: "trashed" in metadata
  2. Store originalPath and deletedAt timestamp
  3. For Documents/Images files, move content from original store to trash store in IndexedDB
  4. Update Trash folder icon (trash-full.png)

Restore from Trash Flow

  1. Reset status to "active", clear originalPath and deletedAt
  2. For Documents/Images files, move content back from trash store to original store
  3. 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

ExtensionTypeOpens WithStorage
.mdMarkdownTextEditIndexedDB (documents)
.txtPlain textTextEditIndexedDB (documents)
.png, .jpg, .jpeg, .gif, .webp, .bmpImagePaintIndexedDB (images)
.app, .htmlHTML AppletApplet ViewerIndexedDB (applets)
.mp3AudioiPodVirtual (iPod store)
.movVideoVideosVirtual (Video store)
.weblocWeb linkInternet ExplorerVirtual (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:

DomainTriggers
files-metadataDocument save, format filesystem
files-imagesImage save, move, trash/restore
files-trashMove to trash, restore, empty trash
files-appletsApplet 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 }
}));