Skip to content

Architecture

This is the architecture reference. For a friendlier introduction, see How Mosayic Works.

┌─────────────────────────────────┐
│ Mosayic Dashboard (mosayue) │
│ https://app.mosayic.io │
│ Vue 3 + Vite SPA │
└──────────────┬──────────────────┘
│ HTTPS
│ Authorization: Bearer <supabase JWT>
┌─────────────────────────────────┐
│ Mosayic Backend API │
│ mosayic-api │
│ FastAPI on Google Cloud Run │
└────┬─────────────────┬──────────┘
│ WebSocket │ REST
│ Bearer auth │ (CRUD on projects,
│ │ variables, etc.)
▼ ▼
┌──────────┐ ┌──────────────────┐
│ VS Code │ │ Supabase │
│ Extension│ │ (Mosayic's own) │
│ (yours) │ │ │
└──────────┘ └──────────────────┘
  • Stack: Vue 3 (Composition API) + Vite + Tailwind CSS 4
  • Routing: Vue Router 5, file-based
  • Auth: Supabase JS SDK (Google OAuth via PKCE)
  • HTTP client: native fetch with a thin wrapper that injects the Supabase JWT

The dashboard is a static SPA. It doesn’t run any backend code of its own — every operation either:

  • Hits the Mosayic API directly (POST /projects/.../scaffold, GET /commands/status, etc.)
  • Or kicks off a flow that the backend relays to your VS Code extension via WebSocket
  • Stack: FastAPI 0.119 + Python 3.10-3.12
  • Server: Uvicorn on port 8080 (in container; published as 443 by Cloud Run)
  • Database: Supabase Postgres, accessed via supabase Python client (no SQLAlchemy — direct PostgREST queries)
  • Auth: validates Supabase JWTs via supabase.auth.get_user(jwt=token)
  • WebSocket: one persistent connection per user, used to relay commands to and from the user’s VS Code extension
  • External integrations: none (relays to the user’s CLIs via the extension; doesn’t talk to GitHub/GCP/Expo on the user’s behalf except through the extension running their CLIs)

Key endpoints:

PathPurpose
GET /healthHealth check (no auth)
GET /auth/vscode/loginOAuth PKCE entrypoint for the extension
GET /auth/vscode/callbackOAuth callback that redirects to vscode://
POST /auth/vscode/refreshToken refresh
WS /wsPersistent socket for an authenticated extension
GET /commands/statusIs the user’s extension connected?
POST /commands/runRun a one-shot command (sync)
POST /commands/streamRun a command, stream output as SSE
POST /commands/terminalOpen a VS Code terminal and run a command
POST /dev-server/start / stop / inputManage long-running dev servers
POST /projects/:id/scaffoldBackground task that scaffolds a new project
POST /projects/:id/github/create-reposCreate user’s repos via gh
POST /projects/:id/secrets/...Persist secrets via gcloud / gh
POST /projects/:id/release/...Pre-flight, create release, deploy API, sync EAS
POST /projects/:id/supabase/setupStart local Supabase via the CLI
  • Stack: TypeScript on the VS Code extension API
  • Activation: onStartupFinished
  • Connection: single WebSocket to the backend, authenticated with the user’s Supabase JWT in the upgrade headers
  • UI: a status bar item and the standard Output channel — no webviews, no custom panels
  • Storage: tokens in VS Code secretStorage (OS keychain)

Message types it handles from the backend:

TypeAction
commandRun shell command, stream output, return result
terminal_commandOpen a VS Code terminal and run a command
start_dev_serverOpen a managed pseudoterminal, run a long-running command, stream output
stop_dev_serverKill a managed pseudoterminal
terminal_inputForward keystrokes to a managed pseudoterminal
dev_server_statusReport whether a managed session is alive
pick_folderOpen a native folder picker
open_folderOpen a folder as the workspace
send_to_terminalSend text to a named existing terminal
pingReply with pong
DataLives inMosayic accesses?
Your Google profileSupabase Auth (Mosayic’s project)Yes (read)
Project metadata (name, paths, URLs)Supabase Postgres (Mosayic’s project)Yes
Mobile / API source codeYour machine + your GitHubNo
Local Supabase dataYour machine (Docker volume)No
Production Supabase dataYour hosted Supabase projectNo
Secrets (Supabase service key, etc.)Your Google Cloud Secret ManagerNo (transits the relay; not persisted)
GitHub Actions secretsYour GitHub repoNo
Production API codeYour Google Cloud Cloud RunNo
Your users’ dataYour hosted SupabaseNo

The blunt summary: Mosayic owns the project list and the Google Sign-In session. Everything else belongs to you, on your services.

Standard HTTP requests with Authorization: Bearer <token> and JSON bodies. 30-second timeout on the client.

Server-Sent Events (backend → dashboard)

Section titled “Server-Sent Events (backend → dashboard)”

Used for streaming long-running command output to the dashboard. The dashboard’s fetch consumes the response as a stream and parses data: { ... }\n\n events.

JSON messages over a persistent socket. Each message has a type and (for request/response pairs) a request_id. The backend tracks pending request IDs in an in-memory dict; when the response comes back, it resolves the matching asyncio.Future.

URI scheme (dashboard → extension, indirectly)

Section titled “URI scheme (dashboard → extension, indirectly)”

The dashboard renders links like <a href="vscode://mosayic.vscode-mosayic/wake">. Clicking such a link causes VS Code to fire a URI handler in the extension, which can then trigger commands like reconnect or focus.

ComponentWhere
DashboardStatic SPA hosted on (likely) Cloud Run/Vercel/similar at app.mosayic.io
BackendGoogle Cloud Run (mosayic-api-service-...run.app)
ExtensionVisual Studio Marketplace, distributed to users
Mosayic’s databaseSupabase (separate from any user’s project)

CI/CD lives in .github/workflows/ of the mosayic-api repo: gcp-deploy.yaml (Cloud Run), supabase-deploy-migrations.yaml, scheduled-backups.yaml.

  • Extension WebSocket drops — exponential backoff (1s, 2s, 5s, 10s, 30s) for up to 10 attempts. After that, manual reconnect.
  • Backend restarts — all in-flight WebSocket sessions drop. Extensions reconnect within seconds.
  • Two extensions per user — newer wins, older closed with code 4001. By design.
  • Token expiry mid-session — extension auto-refreshes via /auth/vscode/refresh before expiry.
  • Scaffold mid-flight — backend tracks scaffold state in-process. If the backend container restarts, the dashboard sees the scaffold as “stuck” — there’s no resumption today (this is on the roadmap).