Files
RUWIO/dev_docs/01_electron-plan.md
2026-06-07 10:46:34 +02:00

9.3 KiB

Electron Desktop Plan

Goal

Package RUWIO as a desktop app while keeping the existing Vue/Vite renderer as the primary application. The Electron layer should start small: host the built app, expose native filesystem dialogs, and later absorb the current server-side MP4 render workflow so desktop users do not need a separate render server.

Current App Shape

  • The UI is a Vue 3 + Vite single-page app in src/.
  • The production web build is produced by npm run build into dist/.
  • Project save/load is currently browser-driven:
    • JSON project export/import lives in src/lib/projectFile.ts.
    • .ruw bundle export/import lives in src/lib/ruwFile.ts.
    • User-triggered file downloads use Blob, object URLs, and anchor clicks.
  • Still/GIF/APNG style exports are implemented client-side.
  • MP4 rendering is currently split out into server/server.js, using Express, Puppeteer Core, Chromium, and ffmpeg.
  • The app already has PWA assets and static-host assumptions, which are compatible with an Electron renderer if paths are handled carefully.

Use a standard Electron split:

  • electron/main.ts
    • Creates the main BrowserWindow.
    • Loads the Vite dev server during development.
    • Loads dist/index.html in packaged builds.
    • Owns native dialogs, app menu, recent files, and privileged filesystem/process work.
  • electron/preload.ts
    • Exposes a small typed API on window.ruwioDesktop.
    • Uses contextBridge and IPC.
    • Does not expose Node primitives directly.
  • Vue renderer
    • Remains browser-compatible.
    • Detects the desktop API when available.
    • Falls back to current browser file picker/download behavior on the web.

Keep Electron as an optional target rather than replacing the browser build. The same app should continue to run with npm run dev, npm run build, and static hosting.

Package Additions

Add these development dependencies:

  • electron
  • electron-builder or electron-forge
  • concurrently for local dev convenience
  • wait-on if the Electron dev command waits for Vite

Recommended scripts:

{
  "dev": "vite",
  "desktop:dev": "concurrently \"vite\" \"wait-on http://localhost:5200 && electron .\"",
  "desktop:build": "npm run build && electron-builder",
  "desktop:pack": "npm run build && electron-builder --dir"
}

The exact script shape can be adjusted once the Electron entry files exist.

Phase 1: Minimal Desktop Shell

  1. Add Electron source files under electron/.
  2. Configure TypeScript output for the Electron main/preload files.
  3. Create a secure BrowserWindow:
    • contextIsolation: true
    • nodeIntegration: false
    • sandbox: true if preload needs stay small enough to support it
  4. Load http://localhost:5200 in development.
  5. Load the built dist/index.html in production.
  6. Set app name, icon, minimum window size, and basic lifecycle handling for macOS/Windows/Linux.
  7. Add a basic app menu with New/Open/Save/Save As/Export/Quit placeholders.

Acceptance check:

  • npm run desktop:dev opens the existing app.
  • npm run desktop:pack creates an unpacked app.
  • The normal browser dev/build flows still work.

Phase 2: Desktop File API

Add IPC-backed file operations instead of immediately rewriting the app's save/load model.

Expose a preload API like:

window.ruwioDesktop = {
  openProject(): Promise<{ name: string; bytes: Uint8Array } | null>,
  saveProject(options: { suggestedName: string; bytes: Uint8Array }): Promise<{ path: string } | null>,
  saveJson(options: { suggestedName: string; text: string }): Promise<{ path: string } | null>,
  exportBlob(options: { suggestedName: string; bytes: Uint8Array }): Promise<{ path: string } | null>
}

Renderer changes:

  • Add a small desktop adapter module, for example src/lib/desktopBridge.ts.
  • In downloadProject and downloadRuwFile, use the desktop bridge when present and fall back to browser downloads otherwise.
  • For loading, keep the current <input type="file"> path as the web fallback and add desktop open dialog support for Electron.
  • Preserve existing .ruw bundle logic. The desktop API should move bytes to and from disk, not duplicate parsing/packing rules.

Acceptance check:

  • Open .ruw and legacy .json projects from a native dialog.
  • Save .ruw projects through a native save dialog.
  • Browser import/export behavior remains unchanged.

Phase 3: Native Menus And App State

Wire menu actions to renderer commands through IPC.

Planned menu commands:

  • New Project
  • Open
  • Save
  • Save As
  • Export HTML/JS bundle
  • Export PNG/GIF/APNG/MP4 as available
  • Undo/Redo if the existing history store can expose commands
  • Toggle fullscreen/preview/test overlay where appropriate

Implementation notes:

  • The main process should not mutate project state directly.
  • The renderer should own project state and respond to menu events.
  • Track the current file path in the desktop bridge or a small Pinia/composable integration.
  • Add a dirty-state signal so the main process can confirm before closing a window with unsaved work.

Acceptance check:

  • Menu shortcuts trigger the same code paths as toolbar buttons.
  • Closing the app warns when there are unsaved changes.
  • Recent files can be added after the open/save path is stable.

Phase 4: Desktop MP4 Rendering

Replace the external render server requirement for desktop builds.

Preferred approach:

  1. Move the core render job orchestration out of server/server.js into a reusable Node module.
  2. Keep the existing Express server as a thin wrapper around that module for web/server deployments.
  3. Call the same render module from Electron main via IPC.
  4. Stream progress back to the renderer.
  5. Save the resulting MP4 through a native save dialog.

Rendering dependencies:

  • Bundle or locate Chromium. Electron already ships Chromium, but Puppeteer against Electron's browser runtime needs careful validation. The safer first version is to keep Puppeteer or Playwright with an explicit executable path.
  • Bundle or locate ffmpeg. For a consumer desktop app, prefer an embedded ffmpeg binary per platform or document a clear install requirement until bundling is solved.
  • Keep temporary frames/pipes in the OS temp directory, as the current server does.

Acceptance check:

  • MP4 export works offline in the packaged desktop app.
  • Progress, cancel, and failure states match the current server export UX.
  • The existing server/ deployment still runs.

Phase 5: Packaging And Release

Configure packaging metadata:

  • App id, product name, copyright.
  • Icons for macOS, Windows, and Linux.
  • File associations for .ruw.
  • Build targets:
    • macOS: .dmg or .zip
    • Windows: nsis
    • Linux: AppImage first, optionally .deb
  • Include dist/, Electron compiled files, static assets, and any render binaries.
  • Exclude source maps and development-only files unless needed for debugging.

Later release tasks:

  • Code signing and notarization for macOS.
  • Windows signing.
  • Auto-update channel if RUWIO will ship outside app stores.
  • CI builds for all target platforms.

Acceptance check:

  • A clean checkout can produce a packaged desktop artifact.
  • The artifact launches without a local dev server.
  • File associations open .ruw projects when double-clicked.

Security Rules

  • Keep Node access out of the Vue renderer.
  • Use contextBridge with a narrow allowlist.
  • Validate IPC payloads in the main process.
  • Do not pass arbitrary file paths from renderer to main without checks.
  • Restrict navigation to the app origin in development and local files in production.
  • Disable or guard window.open.
  • Keep the render process isolated from arbitrary project content where possible, especially if imported SVG/HTML assets can contain script-like payloads.

Risk Areas

  • Asset paths under file:// may behave differently than Vite dev paths. Test fonts, icons, manifest assets, imported images, audio, and exported runtime files in packaged builds.
  • Browser download code is synchronous from the user's perspective, while native save dialogs are async and cancellable. UI states should handle cancellation cleanly.
  • MP4 export is the largest unknown because Chromium and ffmpeg packaging varies by platform.
  • Large .ruw files may put pressure on IPC if sent as huge arrays. If this becomes a problem, switch to main-process file handles or temporary files.
  • Existing in-browser tests need an Electron smoke layer because packaged-app failures may not appear in Vite.

Suggested Implementation Order

  1. Add Electron shell and dev/build scripts.
  2. Package the current app without native integrations.
  3. Add preload typings and a tiny desktopBridge.
  4. Move project open/save to native dialogs.
  5. Add native export save paths for existing browser-generated blobs.
  6. Add app menu and unsaved-change handling.
  7. Refactor server/server.js render logic into a reusable Node module.
  8. Add desktop MP4 export through Electron main.
  9. Configure packaging icons, file associations, and platform targets.
  10. Add CI/package smoke checks.

First Pull Request Scope

Keep the first PR intentionally small:

  • Electron main/preload files.
  • Desktop dev and pack scripts.
  • Production loading of dist/index.html.
  • Basic secure window configuration.
  • No changes to project save/load or rendering behavior yet.

That gives RUWIO a launchable desktop shell before any app behavior changes, making the rest of the migration easier to test in small increments.