# Multi-Platform Dyad: Web + Mobile Roadmap

> Generated by swarm planning session on 2026-02-14

## Summary

Turn Dyad from a desktop-only Electron app into a multi-platform product supporting web browsers and mobile devices (via PWA), while preserving the desktop app as the free, local, open-source flagship. The web version runs on server-side containers with a freemium business model. Data syncs between platforms via GitHub (code) and proprietary cloud sync (settings, chat history, metadata).

## Problem Statement

Dyad is currently limited to users who download and install a desktop app on macOS, Windows, or Linux. This creates three problems:

1. **High friction onboarding** -- competitors like v0, Bolt, Lovable, and Replit are browser-first, letting users try instantly from a link. Dyad's installation requirement kills conversion before users experience the product.
2. **No cross-device access** -- users can only interact with their projects from the machine where Dyad is installed. There's no way to check project status, send a quick prompt, or review changes from a phone or another computer.
3. **Limited viral distribution** -- browser-based tools can be shared via URL. Desktop apps require "download this app, install it, then try it." Every step loses users.

**Target users:**

- **Web:** Same developer/non-developer persona as desktop, but reached through lower-friction channels (links, embeds, social sharing)
- **Mobile (PWA):** Existing Dyad users who want lightweight access away from their desk -- reviewing, monitoring, quick chat interactions

## Scope

### In Scope (MVP -- Phase 0 + Phase 1)

- Backend service extraction from Electron main process into standalone Node.js server
- Server-side container infrastructure for live app preview (via E2B or Fly Machines)
- Web client with core chat + build + preview + deploy loop
- User authentication (GitHub OAuth, extending Dyad Pro infrastructure)
- Freemium tier (limited projects/compute on free, expanded on Pro)
- Responsive layout foundations (preparing for PWA mobile)
- Server-side database (better-sqlite3 or PostgreSQL with same Drizzle schema)
- Cloud-side secret storage for API keys (replacing Electron safeStorage)
- Basic usage tracking and rate limiting

### In Scope (Follow-up Phases)

- Full handler parity (all 40+ IPC handler categories via HTTP API)
- PWA mobile optimization (bottom tab nav, touch targets, service worker)
- GitHub-based code sync between desktop and web
- Proprietary cloud sync for settings, chat history, app metadata
- Billing/payment integration for Pro tier
- Offline messaging for PWA ("you're offline" states)

### Out of Scope

- Native mobile apps (iOS/Android via Capacitor) -- only if PWA hits hard walls
- Real-time collaboration / multi-user editing
- Desktop-to-web migration of local files (GitHub is the bridge)
- Local model support on web (Ollama/LM Studio -- desktop only)
- MCP server support on web (requires local process spawning -- desktop only)
- Capacitor/Xcode/Android Studio integration on web (desktop only)

## User Stories

### Web (Phase 1)

- As a new user, I want to try Dyad in my browser without installing anything, so I can evaluate it in under 3 minutes
- As a web user, I want to chat with the AI and see my generated app running in a live preview, so I get the same instant feedback loop as desktop
- As a web user, I want to deploy my app to Vercel from the browser, so I can ship without switching to desktop
- As a web user, I want to connect my own API keys (OpenAI, Anthropic, etc.), so I maintain the BYOK philosophy
- As a free-tier user, I want to see my usage limits clearly before I hit them, so I'm not surprised by a paywall mid-workflow
- As a free-tier user who hits limits, I want to be offered both "Upgrade to Pro" and "Download desktop (free, unlimited)" as options

### Mobile/PWA (Phase 3)

- As an existing Dyad user on my phone, I want to check my project status and recent chat history, so I can stay informed away from my desk
- As a mobile user, I want to send a quick prompt to my AI agent, so I can kick off work while commuting
- As a mobile user, I want to review generated code diffs and approve changes, so I don't block progress when away from desktop

### Sync (Phase 4)

- As a user with both desktop and web, I want my code synced via GitHub so I can work on either platform
- As a user, I want my settings, chat history, and preferences to sync across devices, so I don't have to reconfigure on each platform

## UX Design

### Platform-Specific Use Cases

| Platform   | Primary Use Case                                                  | Layout                                                     |
| ---------- | ----------------------------------------------------------------- | ---------------------------------------------------------- |
| Desktop    | Full creation (chat, code, preview, deploy, git, local models)    | Three-panel: sidebar + chat + preview                      |
| Web        | Full creation (cloud-backed, same loop minus local-only features) | Three-panel: sidebar + chat + preview (no custom titlebar) |
| Mobile/PWA | Monitoring, reviewing, lightweight chat                           | Single-panel with bottom tab navigation                    |

### User Flow (Web MVP)

1. User visits dyad.sh/app (or similar)
2. Sees landing page with "Try now" CTA -- no signup required for first experience
3. After 5 minutes or first meaningful action, prompted for GitHub OAuth signup
4. Creates or opens a project -> enters chat view
5. Sends prompt to AI -> streaming response with code generation
6. Server-side container spins up (5-15s, "Starting environment..." indicator)
7. Live preview appears in side panel once container is ready
8. User iterates via chat -> code updates -> preview hot-reloads
9. User deploys to Vercel via existing integration
10. Usage tracked against free tier limits (visible in sidebar)

### Key States

- **Container starting:** Progress indicator in preview panel ("Starting environment...") -- honest about 5-15s cold start
- **Container active:** Live preview iframe connected to remote container
- **Container disconnected:** "Preview disconnected -- Reconnecting..." overlay (not blank white screen)
- **Offline (PWA):** Cached project list and chat history (read-only) with "You're offline. Connect to continue building." banner
- **Free tier limit approaching:** Usage indicator in sidebar (similar to existing `AICreditStatus`)
- **Free tier limit reached:** Dual-CTA dialog: "Upgrade to Pro" or "Download desktop (free, unlimited)"
- **Build error (remote):** Same `DyadOutput`/`DyadLogs` components with "Cloud Build" label to indicate remote origin

### Interaction Details

| Interaction     | Desktop                                | Web                                        | Mobile/PWA                        |
| --------------- | -------------------------------------- | ------------------------------------------ | --------------------------------- |
| Navigation      | Sidebar + keyboard shortcuts           | Sidebar (collapsible)                      | Bottom tab bar (< 768px)          |
| File attachment | Drag-drop + native dialog              | Drag-drop + `<input file>`                 | Camera/photo library/file picker  |
| Context menus   | Right-click                            | Right-click                                | Long-press                        |
| Code editing    | Monaco editor                          | Monaco editor                              | Read-only code viewer             |
| Panel layout    | Resizable multi-panel                  | Resizable multi-panel                      | Single panel, swipe/tab to switch |
| Preview         | Local dev server in side panel         | Remote container in side panel             | Full-screen with toggle button    |
| Window controls | Custom titlebar (macOS/Windows)        | None (browser chrome)                      | None (OS chrome)                  |
| Settings        | Local file pickers, node path selector | Cloud storage, account management, billing | Drill-down navigation pattern     |

### Accessibility

- Touch targets: minimum 44x44pt on mobile breakpoints (current `h-7`/`h-8` buttons upscaled via responsive classes)
- Safe areas: `env(safe-area-inset-*)` for mobile notches/home indicators
- Motion sensitivity: Audit Framer Motion animations for `prefers-reduced-motion`
- Screen readers: Base UI ARIA support carries over; audit hidden radio input pattern
- Keyboard navigation: Standard browser Tab/Enter/Escape on web (no custom interception)

## Technical Design

### Architecture

**Option A: Backend Service Extraction (selected)**

Extract the Electron main process business logic into a standalone Node.js server. The existing IPC contracts (Zod schemas) become HTTP/WebSocket endpoints. Desktop Electron becomes a thin shell that spawns the local server. Web and mobile clients talk to the same API over HTTP.

```
Desktop:  Electron shell -> Local Node.js server -> Local SQLite + Local filesystem
Web/PWA:  Browser        -> Cloud Node.js server -> Cloud DB + Container filesystem
```

**Why this approach:**

- IPC contracts map mechanically to HTTP routes (channel name -> route, Zod input -> request body, Zod output -> response body)
- Stream contracts map to SSE/WebSocket
- React frontend has zero direct Electron references in components -- it's already web-ready
- Single codebase with `PlatformProvider` context for capability-based conditional rendering
- `createClient()` in `src/ipc/contracts/core.ts` is the single function to swap (IPC invoke -> HTTP fetch)

### Components Affected

**Must Change (Infrastructure):**

| Component                           | Current                                | Target                                                                                          |
| ----------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `src/ipc/handlers/*.ts` (40+ files) | `ipcMain.handle()`                     | Extract pure service functions; thin handler wrappers for both IPC and HTTP                     |
| `src/ipc/contracts/core.ts`         | `window.electron.ipcRenderer.invoke()` | HTTP fetch adapter for web, keep IPC for desktop                                                |
| `src/preload.ts`                    | Electron contextBridge                 | HTTP client for web                                                                             |
| `src/db/index.ts`                   | `better-sqlite3` (local)               | Same for desktop; server-side DB for web                                                        |
| `src/main/settings.ts`              | File JSON + `safeStorage`              | Platform-agnostic `SettingsProvider` (safeStorage for desktop, encrypted cloud storage for web) |
| `src/paths/paths.ts`                | `electron.app.getPath()`               | Abstract path provider                                                                          |
| `src/main.ts`                       | Full Electron app lifecycle            | Thin Electron shell; business logic extracted                                                   |
| `src/ipc/utils/git_utils.ts`        | dugite + isomorphic-git                | isomorphic-git for web; keep dugite for desktop                                                 |
| `src/ipc/utils/simpleSpawn.ts`      | `child_process.spawn()`                | Remote container execution for web                                                              |
| `electron-log` (all handlers)       | Electron-specific logger               | Platform-agnostic logger (pino)                                                                 |

**Unchanged (Portable):**

- `src/components/**` -- React UI (zero Electron refs)
- `src/atoms/**` -- Jotai state
- `src/pages/**`, `src/routes/**` -- TanStack Router
- `src/db/schema.ts` -- Drizzle ORM schema (fully portable)
- `src/ipc/types/**` -- Zod schemas (pure validation)

### Data Model Changes

**Schema changes:**

- Add `userId` column to: `apps`, `chats`, `messages`, `versions`, `prompts`, `themes`, `languageModels` tables (for multi-tenant web)
- Add `WHERE userId = ?` to all queries in multi-tenant mode
- These are standard Drizzle migrations (`drizzle-kit generate`)
- Desktop: `userId` is always a fixed local-user ID (no behavior change)

**Database driver strategy:**

- Desktop: `better-sqlite3` (keep current, fast, synchronous)
- Web server: `better-sqlite3` per-user (simple) or PostgreSQL (scalable) -- same Drizzle schema either way
- Mobile/PWA: No local DB -- all data from web server API

**Settings storage:**

- Desktop: File JSON + `safeStorage` encryption (unchanged)
- Web: Server-side encrypted storage for API keys (AES-256-GCM with server key or secrets manager); non-sensitive settings in DB
- PWA: No local settings -- use web server

**File storage:**

- Desktop: Local filesystem (`~/dyad-apps/`) -- unchanged
- Web: Container filesystem (ephemeral per session) + persistent cloud storage per project (S3/GCS or container volumes)

### API Changes

**New HTTP/WebSocket API layer:**

IPC contracts transform mechanically:

```
IPC:  ipcMain.handle("create-app", handler)  ->  HTTP:  POST /api/create-app
IPC:  ipcRenderer.invoke("create-app", data)  ->  HTTP:  fetch("/api/create-app", {body: data})
```

Stream contracts transform to SSE:

```
StreamContract: onChunk/onEnd/onError  ->  SSE: event: chunk/end/error
```

**New endpoints (web-specific):**

- `POST /api/auth/login` -- GitHub OAuth flow
- `POST /api/auth/session` -- Session management
- `POST /api/containers/create` -- Provision container for project
- `DELETE /api/containers/:id` -- Teardown container
- `GET /api/containers/:id/proxy/*` -- Proxy to container dev server
- `POST /api/sync/upload` -- Upload local DB data to cloud
- `GET /api/sync/download` -- Download cloud data to local
- `GET /api/usage` -- Current usage vs. tier limits

**Authentication middleware:**

- All API endpoints require auth (except initial anonymous trial)
- Session-based (JWT or secure cookie)
- Built on existing Dyad Pro infrastructure

### Container Architecture

**Platform recommendation:** Start with E2B (purpose-built AI code execution sandboxes)

**Container lifecycle:**

1. User opens/creates project -> `ContainerManager.provision(projectId, userId)`
2. Container initializes with project files from cloud storage
3. Runs `npm install && npm run dev` inside container
4. Preview URL proxied to browser via `/api/containers/:id/proxy/*`
5. Container idle timeout: 15 min (free) / 60 min (Pro)
6. Container shutdown: files persisted to cloud storage, container destroyed
7. Next session: new container, files restored from cloud storage

**Cost controls:**

- Free tier: 1 concurrent container, 30 min/day active time
- Pro tier: 3+ concurrent containers, extended active time
- Container auto-sleep after idle timeout
- Resource limits per container (CPU, memory, disk)

### Sync Architecture

**Code sync (GitHub -- already built):**

- Existing `github_handlers.ts` with push/pull/clone/fetch/rebase
- `apps` table already stores `githubOrg`, `githubRepo`, `githubBranch`
- Desktop user pushes to GitHub; web user clones from GitHub
- No new infrastructure needed

**Everything-else sync (new proprietary service):**

- Simple export/import for MVP: user clicks "Sync to cloud" on desktop, uploads relevant table data via API
- Tables synced: `apps` (metadata only, not `path`), `chats`, `messages`, `prompts`, `themes`, `languageModels`, settings
- Conflict resolution: last-write-wins for settings/preferences (with "Settings updated from your other device" toast); explicit conflict picker for project metadata
- Lazy-load old chat histories (only sync recent chats; `messages.aiMessagesJson` can be large)

## Implementation Plan

### Phase 0: Service Extraction (Foundation)

Extract business logic from Electron main process into pure, testable service functions. No abstract interfaces -- just separation of concerns.

- [ ] Extract service functions from the 5 most critical handler files: `chat_stream_handlers.ts`, `chat_handlers.ts`, `app_handlers.ts`, `github_handlers.ts`, `settings_handlers.ts`
- [ ] Replace `electron-log` imports with platform-agnostic logger (pino) across all handler files
- [ ] Create `PlatformProvider` context and expand `useSystemPlatform` hook to detect desktop/web/mobile
- [ ] Ensure all existing desktop tests still pass (zero user-facing changes)

### Phase 1: Web MVP with Server-Side Containers

Build the web version with the full chat + build + preview + deploy experience.

**Backend:**

- [ ] Create standalone Node.js HTTP server (Hono/Express) with routes generated from IPC contracts
- [ ] Implement HTTP client adapter to replace `ipcRenderer.invoke()` for web
- [ ] Set up server-side database (better-sqlite3 or PostgreSQL with Drizzle)
- [ ] Add `userId` columns to all user-facing tables; add tenant isolation middleware
- [ ] Implement authentication (GitHub OAuth, session management, extending Dyad Pro)
- [ ] Replace `safeStorage` with server-side encrypted secret storage for API keys
- [ ] Implement cloud file storage for projects (S3/GCS)
- [ ] Set up E2B (or Fly Machines) container infrastructure
- [ ] Build `ContainerManager`: provision, proxy, idle timeout, shutdown, file persistence
- [ ] Implement streaming chat via SSE/WebSocket
- [ ] Add usage tracking middleware and rate limiting
- [ ] Security hardening: container sandboxing, input validation, resource limits

**Frontend:**

- [ ] Remove custom titlebar on web (relocate chat tabs, title bar actions)
- [ ] Replace Electron file dialogs with web `<input type="file">`
- [ ] Replace `shell.openExternal()` with `window.open()`
- [ ] Add login/signup flow (GitHub OAuth)
- [ ] Add container status widget in preview panel ("Starting environment..." indicator)
- [ ] Add "Preview disconnected -- Reconnecting..." overlay
- [ ] Add freemium usage indicator in sidebar
- [ ] Add free tier limit dialog with dual CTA (upgrade / download desktop)
- [ ] Web-compatible onboarding flow (replace YouTube link with interactive walkthrough)
- [ ] Error messaging adapted for remote builds ("Cloud Build" label on `DyadOutput`)

### Phase 2: Backend Convergence + Full Handler Parity

Unify desktop and web backends to prevent codebase divergence.

- [ ] Port remaining 35+ IPC handler categories to HTTP endpoints
- [ ] Desktop Electron app spawns local Node.js server (same server as web, running locally)
- [ ] Unified feature development: new features work on both desktop and web from a single implementation
- [ ] Audit isomorphic-git coverage vs. dugite -- implement web-compatible git for all critical operations
- [ ] Unify Dyad Pro subscription: one account, one Pro status, works on desktop + web
- [ ] Billing/payment integration (Stripe or similar)
- [ ] Feature flags for platform-specific capabilities (`hasFilesystem`, `hasShell`, `hasLocalModels`, etc.)

### Phase 3: PWA Mobile Optimization

Make the web version excellent on mobile devices.

- [ ] Responsive layout: sidebar collapses to bottom tab bar at < 768px viewport
- [ ] Touch target audit: all interactive elements >= 44x44pt on mobile breakpoints
- [ ] Safe area support: `env(safe-area-inset-*)` for notches/home indicators
- [ ] Service worker for PWA installability and offline shell
- [ ] Web app manifest for install-to-homescreen
- [ ] Mobile-specific input adaptations: camera/photo picker for file attachments, virtual keyboard handling
- [ ] Offline state: cached project list + chat history (read-only) with offline banner
- [ ] Chat/preview as swipeable tabs instead of side-by-side panels on mobile
- [ ] Code viewer (read-only) replacing Monaco editor on mobile
- [ ] Long-press for context menus (replacing right-click)

### Phase 4: Cross-Device Sync

Enable seamless work across desktop and web.

- [ ] GitHub code sync: prominent "Push to GitHub" / "Pull from GitHub" UI flows
- [ ] Sync API: export/import for chat history, messages, prompts, themes, settings, language model configs
- [ ] Sync status indicator in sidebar footer (checkmark/spinner/warning)
- [ ] Conflict resolution: last-write-wins for settings (with toast notification), explicit picker for project metadata
- [ ] First-time sync setup flow: "Sign in -> Choose apps to sync -> Done"
- [ ] Lazy-load old chat histories during sync (skip large `aiMessagesJson` for old chats)
- [ ] Desktop "Connect to cloud" opt-in flow

### Phase 5: Polish + Advanced Features

- [ ] Audit and optimize container cold start times
- [ ] Advanced offline support (queued operations that sync when back online)
- [ ] Evaluate native mobile (Capacitor) if PWA hits hard walls (push notifications, performance)
- [ ] Collaboration features (share project link, real-time co-editing)
- [ ] Advanced sync (CRDT-based or operational transforms if last-write-wins proves insufficient)

## Testing Strategy

- [ ] **Unit tests:** Extract service functions are tested independently with mocked platform APIs (Vitest, existing test infrastructure)
- [ ] **Integration tests:** HTTP API layer tested with supertest -- verify Zod contract compatibility between IPC and HTTP transports
- [ ] **E2E tests (Desktop):** Keep existing Playwright Electron tests; verify Electron shell + extracted backend still works
- [ ] **E2E tests (Web):** Standard Playwright browser tests against the web app; cover auth flow, chat+preview loop, deploy, freemium limits
- [ ] **Container tests:** Integration tests for container provisioning, preview proxying, idle timeout, crash recovery
- [ ] **Sync tests:** Round-trip tests for GitHub code sync and proprietary data sync; conflict resolution scenarios
- [ ] **Responsive tests:** Playwright at mobile viewport sizes for PWA breakpoints, touch target sizes
- [ ] **Contract tests:** Automated verification that IPC contracts and HTTP API contracts stay in sync (they share Zod schemas)
- [ ] **Security tests:** Container escape testing, API key storage encryption verification, tenant isolation verification

## Risks & Mitigations

| Risk                                                            | Likelihood | Impact | Mitigation                                                                                                                |
| --------------------------------------------------------------- | ---------- | ------ | ------------------------------------------------------------------------------------------------------------------------- |
| Container infrastructure complexity delays Phase 1              | H          | H      | Start with managed platform (E2B) to minimize ops; self-host only at scale                                                |
| Container costs exceed freemium revenue                         | M          | H      | Aggressive idle timeouts, free tier time limits, monitor cost-per-user closely                                            |
| Two-codebase divergence (desktop vs. web backend)               | H          | M      | Timebox: Phase 2 convergence must start within 6 months of web MVP launch                                                 |
| User trust concerns storing API keys in cloud                   | M          | H      | Clear trust indicators in UI, key rotation support, consider browser-side key storage with proxy pattern                  |
| isomorphic-git missing critical git features for web            | M          | M      | Audit git usage early; fall back to server-side git proxy for unsupported operations                                      |
| Freemium conversion < 5% makes web unsustainable                | M          | H      | Track conversion from day one; adjust free tier limits; desktop download as fallback CTA                                  |
| Container security (arbitrary npm install, user code execution) | M          | H      | Use gVisor/Firecracker sandboxing; network isolation; resource limits; security audit before public launch                |
| Sync conflicts cause data loss                                  | L          | H      | Last-write-wins with confirmation toast for low-stakes data; explicit conflict UI for high-stakes data; never auto-delete |
| PWA limitations frustrate mobile users                          | M          | L      | Monitor PWA capability gaps; Capacitor wrapping is the escape hatch if needed                                             |
| Desktop development slows due to web investment                 | M          | M      | Run as parallel tracks; shared component library (Storybook already set up); convergence in Phase 2                       |

## Open Questions

1. **Container platform selection:** E2B vs. Fly Machines vs. self-hosted. Recommend E2B proof-of-concept spike as first action to validate latency, cost, and reliability.
2. **Exact freemium tier limits:** How many free projects? How much active container time per day? What storage limits? These directly control infrastructure burn rate and need data from the E2B spike to set.
3. **Web version URL/branding:** Is it dyad.sh/app? app.dyad.sh? A separate domain? Affects auth cookies, CORS, and SEO.
4. **Desktop-only feature messaging:** When a web user encounters a feature that only works on desktop (local models, MCP servers, Capacitor builds), what's the UX? Banner? Tooltip? Redirect to download?
5. **Existing DYAD_ENGINE_URL pattern:** The codebase already has a remote engine URL concept. How much of the backend extraction has already been contemplated or partially built? This could accelerate Phase 0-1 significantly.

## Decision Log

| Decision                      | Options Considered                                               | Chosen                                                     | Reasoning                                                                                                                                                                 |
| ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Web preview approach          | WebContainers, server-side containers, deploy-only (no preview)  | Server-side containers                                     | Preserves Dyad's core value prop (instant preview). WebContainers is Chromium-only and can't run shell commands. Deploy-only is too degraded.                             |
| Business model                | Free web, paid-only web, freemium                                | Freemium                                                   | Desktop stays free/local (preserves open-source identity). Web Pro is the upsell. Freemium maximizes reach while generating revenue to cover infrastructure.              |
| Mobile approach               | Native (Capacitor), PWA, defer entirely                          | PWA first                                                  | Lowest cost, instant updates, no App Store friction. Mobile use case is lightweight (review, chat) which PWA handles well. Capacitor is the escape hatch if needed.       |
| Data sync model               | GitHub only, proprietary sync only, hybrid, no sync              | Hybrid (GitHub for code + proprietary for everything else) | GitHub for code leverages existing infrastructure and aligns with "Bridge, Don't Replace." Proprietary sync for settings/metadata fills the gap GitHub can't cover.       |
| Backend architecture          | Cloud-only, local-only-everywhere, backend extraction (Option A) | Backend extraction                                         | Preserves local-first for desktop. Same business logic serves both local (Electron) and cloud (web). IPC contracts map mechanically to HTTP.                              |
| Phase ordering                | Abstraction first, web backend first, simultaneous               | Service extraction first, then web backend                 | PM argued abstraction-first is premature. Eng agreed to drop TransportAdapter but keep service extraction. Build working web backend, then converge.                      |
| Database for web              | sql.js (WASM in browser), server-side SQLite, PostgreSQL         | Server-side DB                                             | Web version is inherently cloud-based. No reason to run SQLite in browser when there's a server. Same Drizzle schema works with any driver.                               |
| Single vs. separate frontends | Single React codebase, separate frontends per platform           | Single codebase                                            | Frontend has zero direct Electron references. Platform differences handled via PlatformProvider context and responsive CSS. Separate frontends would diverge immediately. |

---

_Generated by dyad:swarm-to-plan_
