Skip to content

Architecture

Snowy edited this page Feb 15, 2026 · 1 revision

Architecture

SnCode is an Electron application with a clear separation between the main process (Node.js) and the renderer process (React).

Directory Structure

sncode/
├── .github/
│   ├── workflows/
│   │   ├── ci.yml                  # CI: lint, typecheck, test, build
│   │   └── release.yml             # Release: package + GitHub Release
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── feature_request.yml
│   │   └── config.yml
│   └── pull_request_template.md
├── build/                          # App icons for electron-builder
├── scripts/
│   └── generate-icons.mjs          # Icon generation from SVG
├── src/
│   ├── main/                       # Electron main process
│   │   ├── main.ts                 # Window, IPC handlers, agent orchestration
│   │   ├── agent.ts                # AI agent loop, streaming, tool calling
│   │   ├── store.ts                # JSON file persistence
│   │   ├── project-tools.ts        # Sandboxed file/command tools
│   │   ├── mcp.ts                  # MCP (Model Context Protocol) client
│   │   ├── skills.ts               # Skills discovery and management
│   │   ├── oauth.ts                # OAuth flows (Anthropic PKCE, OpenAI device code)
│   │   ├── credentials.ts          # OS keychain via keytar
│   │   ├── preload.ts              # Context bridge (IPC API)
│   │   └── project-tools.test.ts   # Tool tests
│   ├── renderer/                   # React frontend
│   │   ├── main.tsx                # React entry point
│   │   ├── App.tsx                 # Main UI (~2470 lines)
│   │   ├── SettingsModal.tsx       # Settings panel (~800 lines)
│   │   ├── ErrorBoundary.tsx       # React error boundary
│   │   ├── styles.css              # CSS variables & theme system
│   │   └── vite-env.d.ts           # Vite type declarations
│   └── shared/                     # Shared types & utilities
│       ├── types.ts                # All TypeScript interfaces
│       ├── schema.ts               # Zod validation schemas
│       ├── models.ts               # Model catalog & helpers
│       ├── schema.test.ts          # Schema tests
│       └── models.test.ts          # Model tests
├── package.json                    # Dependencies & electron-builder config
├── vite.config.ts                  # Vite bundler config
├── vitest.config.ts                # Test runner config
├── tsconfig.json                   # Renderer TypeScript config
├── tsconfig.node.json              # Main process TypeScript config
├── eslint.config.js                # ESLint config
└── index.html                      # Electron entry HTML (CSP)

Process Architecture

┌─────────────────────────────────────────────────────┐
│                  Main Process (Node.js)              │
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌────────────────────┐ │
│  │  main.ts │──│ agent.ts │──│ project-tools.ts   │ │
│  │ (window, │  │ (AI loop,│  │ (file ops, shell,  │ │
│  │  IPC)    │  │ streaming│  │  glob, grep)       │ │
│  └──────────┘  └──────────┘  └────────────────────┘ │
│       │              │                               │
│  ┌──────────┐  ┌──────────┐  ┌────────────────────┐ │
│  │ store.ts │  │ oauth.ts │  │   credentials.ts   │ │
│  │ (JSON    │  │ (OAuth   │  │   (keytar/OS       │ │
│  │  state)  │  │  flows)  │  │    keychain)       │ │
│  └──────────┘  └──────────┘  └────────────────────┘ │
│       │                                              │
│  ┌──────────┐  ┌──────────┐                         │
│  │skills.ts │  │  mcp.ts  │                         │
│  │(skill    │  │(MCP JSON │                         │
│  │ loader)  │  │ RPC)     │                         │
│  └──────────┘  └──────────┘                         │
└─────────────────┬───────────────────────────────────┘
                  │ IPC (contextBridge via preload.ts)
┌─────────────────┴───────────────────────────────────┐
│               Renderer Process (React)               │
│                                                      │
│  ┌──────────────────────────────────────────────┐   │
│  │                  App.tsx                       │   │
│  │  ┌─────────┐ ┌──────────┐ ┌───────────────┐ │   │
│  │  │Sidebar  │ │Chat area │ │Right sidebar  │ │   │
│  │  │(projects│ │(messages,│ │(file preview, │ │   │
│  │  │ threads)│ │ input)   │ │ diff, tasks)  │ │   │
│  │  └─────────┘ └──────────┘ └───────────────┘ │   │
│  │  ┌─────────┐ ┌──────────┐ ┌───────────────┐ │   │
│  │  │FileTree │ │SearchBar │ │OnboardingModal│ │   │
│  │  └─────────┘ └──────────┘ └───────────────┘ │   │
│  └──────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────┐   │
│  │           SettingsModal.tsx                    │   │
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

IPC Communication

All communication between main and renderer processes goes through Electron's contextBridge (defined in preload.ts). The renderer accesses the API via window.sncode.*.

Request/Response (renderer → main)

The renderer calls methods like window.sncode.sendMessage(...), which trigger ipcMain.handle(...) handlers in main.ts.

Events (main → renderer)

The agent emits events during execution via webContents.send(...):

Channel Purpose
agent:status Agent status changes (running, idle, error, cancelled)
agent:chunk Streaming text chunks
agent:tool Tool invocation notifications
agent:message Complete messages (tool results, final assistant text)

The renderer listens with window.sncode.on(channel, callback).

Agent Loop

The agent runs in the main process (agent.ts):

  1. Build messages — Convert thread history to provider-specific format
  2. API call — Stream response from Anthropic or OpenAI
  3. Parse response — Extract text and tool calls
  4. Execute tools — Run tool calls sequentially (except spawn_task which runs in parallel)
  5. Loop — If tool calls were made, feed results back and repeat (up to maxToolSteps)
  6. Complete — Save final assistant message

Streaming

Both providers use native streaming:

  • Anthropic: client.messages.stream() with content_block_delta events
  • OpenAI: client.responses.create({ stream: true }) with delta events

Text chunks are emitted in real-time to the renderer for immediate display.

Data Persistence

State Store (store.ts)

Application state is persisted as a JSON file at:

  • Windows: %APPDATA%/sncode/sncode-state.json
  • macOS: ~/Library/Application Support/sncode/sncode-state.json
  • Linux: ~/.config/sncode/sncode-state.json

The store uses structuredClone for immutable state snapshots and supports legacy migration.

Credentials (credentials.ts)

Stored in the OS keychain under the service name sncode.providers. Each provider has its credential stored as either a plain API key string or an oauth: prefixed JSON string.

Security

  • Sandboxed renderer: contextIsolation: true, nodeIntegration: false, sandbox: true
  • Content Security Policy: Strict CSP in index.html
  • Path traversal protection: All file operations validate paths stay within project root
  • Navigation prevention: will-navigate event is blocked
  • External links: Only https:// URLs via system browser
  • Application menu: Disabled entirely
  • Command safety: Auto-injects exclusion flags for recursive search commands

Technology Stack

Component Technology
Desktop framework Electron 37
Frontend React 19, TypeScript 5.9
Styling Tailwind CSS 4
Bundler Vite 7
Markdown react-markdown + remark-gfm
Syntax highlighting highlight.js
Validation Zod 4
Credential storage keytar (OS keychain)
AI SDKs @anthropic-ai/sdk, openai
Testing Vitest
Linting ESLint 9
Packaging electron-builder 26

Clone this wiki locally