Frontend Architecture

React 19 + TypeScript + Vite, organized around 9 panels, a small set of cross-cutting hooks, and Zustand stores. All data flows through src/lib/ipc.ts — the single source of truth for Rust calls.

App shell

App.tsx is the shell. It owns:

  • the allotment layout (resizable panes) and pane-size persistence
  • view routing between the 9 panels
  • dark / accent theming via CSS custom properties

main.tsx is the React entry point — no logic.

Header (44-48px) → Project tree (left) | Active view

The body uses Allotment-style resizable splits with 1px gutters. Canvas views (Workflows, Flows) pan/zoom on a dotted grid; generation views (Screens, Components, Design) put the chat left and the preview right.

9 panels

View Component Purpose
Wizard WizardPanel Full-app generator with ask_user Q&A, live preview, visual annotations
Screens ScreensPanel Chat + AI generation + device preview (embeds flow canvas)
Components ComponentsPanel Prompt → component code + live preview
Design (Themes) ThemesPanel Prompt → CSS theme generation
Workflows WorkflowsView Node-based execution canvas (React Flow)
APIs APIsPanel HTTP request/response testing
Runner RunnerPanel File tree, terminal (xterm.js), live preview
Library LibraryPanel Searchable library of components, themes, screens, workflows, APIs
Assets AssetsPanel AI image generation (Bonsai), asset gallery

The Wizard is the only panel that uses ask_user — it’s a thin panel-level wrapper over useChat with four optional callbacks (onAskUser, onAskUserForm, onToolCall, onToolResult).

Cross-cutting hooks

src/hooks/
  useSettings.ts              # Tauri Store persistence (thin re-export)
  useChat.ts                   # Chat session + streaming — used by all panel-level UIs
  useProjectFiles.ts          # Project file operations + React Query keys
  useModelCapabilities.ts     # Model capability detection (vision, tools, etc.)
  useAllotmentLayout.ts       # Pane size persistence
  useToast.ts                  # Toast notifications
  useBonsai.ts                 # Bonsai server + asset gallery lifecycle
  useScreenCode.ts             # Screen code save/load
  useHotspotTracking.ts        # Hotspot tracking (Wizard annotations)
  use-mobile.ts                # Mobile breakpoint detection

useChat is the heaviest hook. It wraps the streaming Channel<CompletionEvent> and exposes callbacks for the 8 event variants. The Wizard subscribes to all of them; simpler panels subscribe to a subset.

State

Five Zustand stores, each with a clear domain:

Store What it holds
appStore Global app settings (theme, accent, model picker)
chatStore Chat state (messages, streaming status)
projectSettingsStore Per-project settings
bonsaiStore Bonsai server state + asset gallery
uiStore UI state (panel visibility, layout)

Stays out of stores: derived data (use useMemo or selectors) and component-local state (useState).

The IPC single-source-of-truth

src/lib/ipc.ts wraps every invoke() call. If you find yourself writing invoke('foo', ...) anywhere else, move it to ipc.ts.

// src/lib/ipc.ts
import { invoke } from '@tauri-apps/api/core';
import type { CompletionEvent, ModelPreset, ... } from './types';

export async function generateCompletionStream(
  model: string,
  messages: Message[],
  host: string,
  apiKey: string,
  onEvent: (event: CompletionEvent) => void,
): Promise<void> {
  return invoke('generate_completion_stream', { model, messages, host, apiKey, onEvent });
}

Components import the wrapper, not invoke. This gives one place to add logging, error handling, type checking, and migration shims.

Styling

Tailwind v4 + shadcn/ui. Tokens in src/styles/globals.css (@theme inline block). All domain-specific CSS lives in globals.css. The shell is greyscale; color is reserved for node types, run status, and the user’s generated output (see Design Language for the full token system).

Package manager

Always bun and bunx — never npm, npx, or yarn:

bun install          # install deps
bun add <pkg>        # add package
bunx shadcn@latest   # run package binaries
bunx tsc --noEmit    # type-check

What next