architecture

Three TypeScript surfaces. Three sets of allowed APIs.

The three tiers.

RendererReact 18 · Vite 5 · DOM only

Components

  • ChatPanellegacy chat with provider controls + in-chat compile
  • ChatViewV2structured timeline backed by v2 messages + parts schema
  • PreviewPanelwebview on persist:uct-preview partition
  • TerminalTabs / TerminalPanemulti-tab xterm.js
  • BlocksLibrary / PromptBuildergovernance block toggling + final prompt
  • SkillsPanel / AgentsPanel / GitPanelproject-scoped discovery surfaces
  • MarkdownView / ReasoningBlockstreamdown renderer + collapsible thinking lane
  • CodeEditProposalper-file Apply / Reject with traversal + env-write guards
Preloadelectron/preload.ts · pass-through only

Contract

  • contextBridgeexposes window.uct.* to the renderer
  • ipcRenderer.invoketyped channel calls; no business logic
MainElectron 33 · Node + native modules

IPC namespaces

  • ipc/fsproject inspection, env masking, file read/write with size caps, skill / agent / external-tool / rule discovery
  • ipc/terminalnode-pty PTY sessions, data + exit channels, multi-tab
  • ipc/processspawned dev servers, destructive-command refusal, detached process-tree kill on dispose
  • ipc/qwenlocal open-source runtime chat + NDJSON stream + abort
  • ipc/modelsmulti-provider stream — local open-source runtime, cloud provider APIs (streaming), OAuth CLI adapter for session tokens
  • ipc/secretsElectron safeStorage-encrypted provider keys (Keychain / DPAPI / libsecret)
  • ipc/playwrightsmoke + step runner + screenshot + console-error capture
  • ipc/domAgentCDP attach to Preview webview, DOM eval, screenshots
  • ipc/networkCDP request capture from the Preview partition
  • ipc/codeIndexregex-based symbol extraction across 17 languages, SQLite snapshot cache
  • ipc/agentToolsread-oriented exec allowlist, hard-deny regex (shell metacharacters, sudo, chmod 777), file-system grep
  • ipc/mcpstdio MCP client, JSON-RPC 2.0 over newline-delimited JSON, per-request 30s timeout
  • ipc/gitstatus, branch, log, show, push, fetch, remote, rev-parse, blame, ls-files
  • ipc/capturewindow screenshot, data-URL save
  • ipc/authAI CLI detection, OAuth code-paste flow

Persistence: two stores, two purposes.

Both stores live under Electron userData. SQLite is the canonical chat store. JSON KV holds app-level preferences and the legacy migration shim.

canonical chat store

uct.sqlite (better-sqlite3)

WAL, foreign keys, in-process migrations on app start. Schema: v2 messages + parts (ULID-keyed), agent traces, code-index snapshots, project memory, notifications, tool / skill / MCP preferences, chat_embeddings (local open-source embedding model · 768d).

app-level KV

uct-store.json

Settings, custom prompt blocks, prompt history, recent projects, per-project presets, and a legacy chat.sessions.v1 shim retained only for migration from earlier versions.

Hard boundaries.

  • renderer

    No Node, no Electron, no native imports.

    The renderer never imports node:*, electron, better-sqlite3, node-pty, child_process, or fs. ESLint enforces it.

  • preload

    Pass-through only.

    Preload exposes typed contextBridge surfaces; no business logic, no state, no side effects beyond invoke.

  • main

    Validate at the IPC boundary.

    ipcMain handlers validate renderer input. Filesystem paths resolve against the project root; symlinks canonicalize through realpath.

  • agent exec

    Allowlist + hard-deny regex.

    Read-oriented binaries (git status|diff|log|…, npm run typecheck|test|lint|build:renderer, tsc, eslint, ruff, rg, grep, find, ls, cat). Deny: shell metacharacters (; | & $( \ >), sudo, chmod 777, chown, destructive patterns (rm -rf, git reset --hard, git push --force, drop table|database, supabase db reset).

  • secrets

    OS keychain at rest.

    Electron safeStorage. Keychain on macOS, DPAPI on Windows, libsecret on Linux. Plaintext fallback explicitly marked in Settings when keychain is unavailable. Keys never reach the renderer.

  • csp

    Strict in prod, relaxed in dev.

    Production CSP locks script-src; dev relaxes for Vite HMR and Monaco blob workers. connect-src allowlists localhost, 127.0.0.1, api.anthropic.com, api.openai.com.

  • permissions

    Default-deny on the main session.

    setPermissionRequestHandler refuses all permission requests by default. The Preview partition opens browser-grade permissions only for that webview.

  • external nav

    No in-app navigation.

    setWindowOpenHandler denies in-app navigation and routes through shell.openExternal. app:openExternal accepts only http(s):// URLs.

  • shutdown

    Group-kill spawned children.

    Detached spawn + negative-PID signal on SIGINT/SIGTERM/SIGHUP and app quit. Vite + esbuild + Next children clean up together.