Electron + SvelteKit + TypeScript desktop frontend for Intent that talks to
the intentd daemon. It is consumed as the packages/cloudlands-fe git
submodule of intent-hq/intent.
The renderer is a SvelteKit application running inside Electron. It never talks to the backend directly. Instead, all backend access flows through a single seam:
SvelteKit renderer <-> AppClient (JSON-RPC boundary) <-> intentd daemon
- AppClient (
src/lib/client) is the only boundary the renderer uses to reach "the backend". Each domain exposes async query methods, reactivesubscribe()streams, and mutation methods. It ships with two implementations behind the same contract:- a live implementation (
src/lib/client/live) that speaks JSON-RPC to theintentddaemon over the IPC bridge, and - a mock implementation (
src/lib/client/mock) backed by in-memory fixtures, so the app runs standalone without a daemon.
- a live implementation (
- Shared / domain state uses Redux + redux-saga under
src/store/renderer. Svelte 5 runes are used for UI rendering only; they are not a home for shared or durable application state. - Node-bound code lives under feature
main/subtrees. The renderer and the Electron main process are a strict boundary: renderer code never imports from a feature'smain/subtree, and vice versa.
cloudlands-fe is normally developed as the packages/cloudlands-fe submodule
of the monorepo:
git clone https://github.com/intent-hq/intent.git
cd intent
# Initialize this submodule (some sibling submodules are still private,
# so initialize selectively rather than cloning with --recurse-submodules)
git submodule update --init --recursive packages/cloudlands-fe
# Fast browser-only component work
make dev-ui
# Full app with an intentd sidecar
make devmake dev-ui installs the locked frontend dependencies when needed and opens no
Electron or daemon process. make dev-ui DEV_PORT=5290 gives a concurrent workspace
its own strict Vite port. For the full stack, make dev builds intentd and launches
the app with it as a sidecar (see intentd sidecar pin).
You can also clone and run this repository on its own (the app falls back to the mock AppClient when no daemon is present):
git clone https://github.com/intent-hq/cloudlands-fe.git
cd cloudlands-fe
corepack pnpm install --frozen-lockfile
corepack pnpm run dev:uiUse corepack pnpm run dev instead when you need the complete Electron app. Without a
staged intentd sidecar, it falls back to the mock AppClient.
Use the repository-pinned pnpm through Corepack, not npm. The following scripts are
defined in package.json:
corepack pnpm install --frozen-lockfile # Install locked dependencies
corepack pnpm run dev:ui # Fast named-state component preview
corepack pnpm run dev:web # Complete plain-browser renderer
corepack pnpm run dev # Complete Vite + Electron app
corepack pnpm run dev:cdp # Electron app with CDP support
corepack pnpm run build # Production build (renderer, main, preload)
corepack pnpm run build:web # Build static plain-browser output in dist/web
corepack pnpm run check # Svelte + TypeScript diagnostics
corepack pnpm run lint # ESLint
corepack pnpm run format # Prettier write pass
corepack pnpm run test:unit # Vitest unit suite
corepack pnpm run test:playwright # Playwright testsUse dev:ui for isolated component work, dev:web for the complete browser renderer
and its client connection, and dev:cdp for Electron main, preload, native, window, or
shell work. A direct preview URL controls state, theme, width, and motion:
http://127.0.0.1:5190/sandbox/button?state=loading&theme=dark&width=420&motion=reduced
http://127.0.0.1:5190/sandbox/mention-agent-avatar?state=waiting&theme=dark&width=420&motion=reduced
Button provides default, loading, disabled, and destructive. Mention agent
avatar provides idle, waiting, and error. See the monorepo
Developer Guide for every
working URL, browser discovery and readiness calls, HMR and hidden-tab guidance, and a
targeted component-test command.
dev:web accepts VITE_INTENTD_WS_URL as a local-development convenience.
build:web deliberately does not compile that value into static JavaScript,
because a full WebSocket URL can contain userinfo, query credentials, or a
fragment. Production hosting should replace /runtime-config.js at response or
deployment time and set globalThis.__INTENT_RUNTIME_CONFIG__.intentdWsUrl to a
wss:// URL. Serve that asset over authenticated HTTPS with Cache-Control: no-store, and use a short-lived, per-user credential rather than a shared
secret. The URL is necessarily visible to that browser session; this separation
prevents it from leaking through versioned bundles, source maps, and long-lived
CDN caches. The committed empty runtime config preserves the standalone mock
fallback when no live daemon is configured.
The bundled intentd sidecar is pinned to an exact released version in
intentd.version: a bare semver with no leading v,
matching a vX.Y.Z / vX.Y.Z-beta.N tag on
intent-hq/intentd.
- Bumping the pin: edit
intentd.versionin a normal reviewable PR, then runnode scripts/fetch-sidecar.cjsto confirm the release assets exist and verify. - Fetching the pinned sidecar:
node scripts/fetch-sidecar.cjsdownloads the cargo-dist release asset for the current platform/arch, verifies its sha256 against the release's.sha256asset, and stages the binary atresources/sidecar/intentd[.exe]. A GitHub token with read access (INTENTD_READ_PAT, orGH_TOKEN/GITHUB_TOKEN) is only needed while the intentd repo is private. The script is idempotent; use--forceto re-fetch. - Local dev builds:
scripts/copy-sidecar.cjsstill stages a locally built binary frompackages/intentd/target/release(make build-sidecar/make devin the monorepo).
The platform/arch → release-asset mapping lives in
scripts/fetch-sidecar-lib.mjs (unit-tested in
scripts/fetch-sidecar-lib.test.ts); update it there if the intentd release
target list changes.
Desktop builds are distributed through three rolling releases (alpha, beta,
stable) on
intent-hq/cloudlands-releases,
each pointing the auto-updater at the latest build for that channel:
- alpha — every
vX.Y.Ztag (cut by merging the release-please PR) builds the app and publishes both an immutable versioned release and the rollingalpharelease (.github/workflows/release-alpha.yml). - beta — a manual promotion of an existing versioned release (no new
build): dispatch
.github/workflows/promote-beta.ymlwith theversioninput to copy that release's assets and updater feeds into the rollingbetarelease. - stable — same promotion model via
.github/workflows/release-stable.yml; it additionally marks the promoted version as Latest.
src/
├── shared/ # Cross-process utilities (main + renderer)
├── lib/ # Renderer-only shared utilities & components
│ └── client/ # AppClient JSON-RPC boundary (live + mock)
├── main/ # Electron main-process code & utilities
├── preload/ # Electron preload scripts
├── features/ # Feature-first renderer modules
│ └── <name>/
│ ├── utils/ # Feature-local utilities
│ └── main/ # Feature's main-process (Node-bound) code
├── routes/ # SvelteKit pages
├── store/
│ └── renderer/ # Redux slices + sagas (shared/domain state)
└── test/
See AGENTS.md for the full conventions and routing guide.
Frontend documentation lives in the monorepo's docs/fe/
(the relative links resolve in a monorepo checkout, where this repo mounts at
packages/cloudlands-fe/). Key references:
- STATE_MANAGEMENT.md — Redux + saga state policy and the Svelte-store migration.
- MODULE_BOUNDARY_GUIDE.md — renderer↔main and feature boundaries.
- TYPE_SYSTEM_GUIDE.md — TypeScript and type safety conventions.
- EVENT_SYSTEM.md — the unified event system.
Bug reports and feature requests for all Intent components — including this frontend — are tracked centrally on the intent-hq/intent issue tracker, not on this repository.
The desktop app is local-first and ships no telemetry or analytics. The only network calls the app itself makes go to public GitHub Releases:
- Auto-updates & release notes — the built app checks for and downloads
updates from GitHub Releases on
intent-hq/cloudlands-releases,
and fetches release notes for the installed version from the same repo's
GitHub Releases API
(
src/features/release-notes/main/release-notes.service.ts).AUTO_UPDATE_URLinsrc/shared/constants.tsis the release-download base URL; thepublishURL inelectron-builder.ymlappends the release channel (e.g./stable).
Everything else goes through the intentd daemon the app is connected to —
local by default; connecting to a remote daemon makes that daemon traffic go
to the host you chose. If you connect the GitHub, Linear, or Sentry
integrations, the daemon calls those services' APIs on your behalf — nothing
is sent to them unless you configure the integration — and the daemon is
likewise what probes an MCP server when you test its connection. Daemon-side
network calls — provider OAuth sign-ins, the user-configured integrations
(GitHub, Linear, Sentry), user-configured MCP servers, and the sitter
self-update — are documented in the
monorepo README's Network & privacy section.
This repository is the frontend ported from the prior Electron app and
wired to the intentd daemon through the AppClient JSON-RPC boundary. It
replaces the earlier Tauri v2 prototype that previously occupied this repo.
Licensed under the Apache License 2.0. See NOTICE for attribution and lineage details.