Frontend System
Overview
Section titled “Overview”The frontend is a single-page React application rendered inside a Tauri webview. It follows a strict “dumb pipe” pattern: all business logic lives in the Rust backend. The React layer handles only UI/UX behavior, delegating every data operation to the backend via @tauri-apps/api/core invoke() calls.
This means:
- No domain validation in TypeScript (validation happens in Rust use cases)
- No data transformation beyond display formatting
- No direct database or filesystem access
- Supabase JS client used only for auth transport (token bridge), not for data queries
Scope: React application shell, component tree, state management, editor integration, IPC patterns
Dependencies: Tauri framework (IPC bridge), all backend systems via invoke() calls
Architecture
Section titled “Architecture”Clean Architecture Placement
Section titled “Clean Architecture Placement”The frontend operates as an outer Framework layer, analogous to src-tauri/ on the Rust side:
| CA Layer | Frontend Role |
|---|---|
| Framework | React components, Zustand stores, TipTap setup |
| Command (Rust) | Accessed via invoke() IPC calls |
| Application | N/A — lives in Rust |
| Domain | N/A — lives in Rust |
| Infrastructure | N/A — lives in Rust |
File Structure
Section titled “File Structure”apps/desktop/src-react/├── main.tsx # Entry point (React root, Sentry init, ErrorBoundary)├── index.css # Global styles├── app/ # Application shell│ ├── App.tsx # Root component (workspace init, layout, dialog orchestration)│ └── SyncProvider.tsx # Auth token bridge + sync lifecycle (React context)├── stores/ # Zustand state management│ ├── appStore.ts # Global UI state (navigation, layout, dialogs, toasts)│ └── authStore.ts # Authentication state (user, token bridge)├── hooks/ # Custom React hooks (IPC wrappers)├── components/ # UI components organized by feature area├── lib/ # Utilities and non-React modules│ ├── errors.ts # parseCommandError() for Tauri structured errors│ ├── logger.ts # Structured frontend logging│ ├── loro-sync/ # TipTap ↔ Loro CRDT bridge│ └── auth-client.ts # Supabase JS client (transport only)└── styles/ └── tokens.css # Design token definitions (colors, spacing)Component Tree
Section titled “Component Tree”State Management
Section titled “State Management”appStore (Zustand)
Section titled “appStore (Zustand)”The primary UI store at stores/appStore.ts. Manages all cross-component state:
| Concern | State | Actions |
|---|---|---|
| Navigation | selectedPageSlug, selectedPageId | selectPage(), setSelectedPageId() |
| Layout | sidebarWidth, contextPanelWidth, contextPanelOpen | setSidebarWidth(), toggleContextPanel() |
| Refresh keys | pageTreeKey, pageDetailKey | refreshPageTree(), refreshPageDetail() |
| Dialogs | isCreatePageDialogOpen, deletePageSlug, movePageSlug, etc. | openCreatePageDialog(), closeDeletePageDialog(), etc. |
| Toasts | toasts[] | addToast(), removeToast() |
| Trash | isTrashViewOpen, trashRefreshKey | openTrashView(), refreshTrash() |
| Attachments | isAttachmentManagerOpen | openAttachmentManager() |
| Settings | isSettingsOpen, settingsSection | openSettings(), closeSettings() |
| Properties | highlightProperty | highlightPropertyInPanel() |
Layout state (sidebar/panel widths) is persisted to localStorage with debounced writes (200ms).
Refresh keys use an incrementing counter pattern: incrementing pageTreeKey causes any component subscribed to it to re-fetch data from the backend.
authStore (Zustand)
Section titled “authStore (Zustand)”Authentication state at stores/authStore.ts. The token bridge pattern:
- On mount,
initTokenBridge()fetches the current session from the Rust backend viainvoke('get_auth_session') - Session tokens are passed to the Supabase JS client via
authClient.auth.setSession() - Backend-initiated token refreshes are received via Tauri event listener (
auth-token-refreshed) - The Rust backend owns the session; the frontend is a mirror for Supabase JS client compatibility
WorkspaceProvider (React Context)
Section titled “WorkspaceProvider (React Context)”hooks/useWorkspace.tsx provides workspace state via React context. On mount, it calls invoke('initialize_workspace') to load or create the default workspace. Components access it via useWorkspace().
SyncProvider (React Context)
Section titled “SyncProvider (React Context)”app/SyncProvider.tsx wraps the app with sync lifecycle management:
- Initializes the auth token bridge
- Starts/stops the Rust sync engine based on workspace + auth state
- Exposes
isAuthenticated,isAuthLoading, andsyncStatusvia context
Component Organization
Section titled “Component Organization”Components live in components/ organized by feature area:
components/├── layout/ # App shell structure (AppShell, Sidebar)├── editor/ # Editor and related components│ ├── InklingsEditor.tsx # Main TipTap editor component│ ├── Toolbar.tsx # Editor toolbar│ ├── GridLayout.tsx # Multi-editor grid layout (per-cell LoroDoc)│ ├── extensions/ # TipTap extensions (WikiLink, PropertyRef, ImageBlock, etc.)│ ├── LayoutPicker.tsx # Layout selection UI│ └── InsertBlockMenu.tsx # Block type insertion menu├── context/ # Right-panel context components (backlinks, properties, graph)├── tags/ # Tag management├── settings/ # Settings panels├── attachments/ # Attachment browser├── auth/ # Auth UI (sign-in, sign-out)├── sync/ # Sync status indicators├── CommandPalette/ # Command palette (search + actions)├── IconPicker/ # Icon selection (code-split, lazy-loaded)├── FirstLaunch/ # Onboarding tour└── [Dialog components] # Create, Delete, Move, Rename, Import dialogsTipTap / Loro Editor Architecture
Section titled “TipTap / Loro Editor Architecture”The editor is the most complex frontend subsystem. It uses TipTap (ProseMirror-based) with Loro CRDT for persistent undo/redo and collaborative editing.
LoroSync Extension (lib/loro-sync/)
Section titled “LoroSync Extension (lib/loro-sync/)”The bridge between TipTap and Loro consists of three files:
LoroSyncExtension.ts— TipTap extension that registers the ProseMirror pluginsloroHelpers.ts— Utility functions for LoroDoc snapshot export/importindex.ts— Public API re-exports
The extension registers two ProseMirror plugins from loro-prosemirror:
- LoroSyncPlugin — Bidirectional sync: ProseMirror transactions update the LoroDoc, and LoroDoc changes update ProseMirror state
- LoroUndoPlugin — Loro-backed undo/redo that persists across sessions (replaces ProseMirror’s built-in history)
Editor Data Flow
Section titled “Editor Data Flow”Page load: Backend (LoroDoc BLOB) ──invoke──> InklingsEditor ├── Has snapshot? → Reuse existing LoroDoc (preserves CRDT history) └── No snapshot? → Create fresh LoroDoc from markdown text
Content change: ProseMirror transaction → LoroSyncPlugin updates LoroDoc → onContentChange(markdown, loroBytes) → Parent calls invoke('save_block_content', { markdown, loroBytes })
Page navigation: React key={slug} forces full editor remount → clean LoroDoc per pageGrid Layout (Multi-Editor)
Section titled “Grid Layout (Multi-Editor)”GridLayout.tsx renders a CSS Grid with one editor per cell. Each cell has its own independent LoroDoc — they never share a document. Cells load/save via get_block_content_snapshot_by_id / save_block_content_by_id.
Custom TipTap Extensions
Section titled “Custom TipTap Extensions”| Extension | Type | Purpose |
|---|---|---|
WikiLink | Inline node (atom) | [[Display|slug]] with optional #heading |
PropertyRef | Inline node (atom) | {{name:value}} property references |
ImageBlock | Block node | Image blocks with metadata |
ImageBlockDrop | Plugin | Drag-drop and paste handling for images |
AttachmentNode | Inline node | Generic attachment references |
AttachmentUpload | Plugin | File upload handling |
Tauri IPC Pattern
Section titled “Tauri IPC Pattern”Every data operation follows the same pattern:
import { invoke } from "@tauri-apps/api/core";
const result = await invoke<ReturnType>("command_name", { param1, param2 });Error handling uses the structured CommandError type from the Rust backend:
import { parseCommandError } from "../lib/errors";
try { await invoke("create_page", { title, parentSlug });} catch (err) { const message = parseCommandError(err); addToast(message);}The Rust backend serializes errors as { type: "Validation", data: { message: "..." } }, which parseCommandError() handles for all variants (NoWorkspace, Validation, Internal, NotFound, Permission, Unauthenticated).
Provider Hierarchy
Section titled “Provider Hierarchy”The app wraps components in a specific provider order:
<ErrorBoundary> — Top-level crash boundary <WorkspaceProvider> — Workspace initialization + context <TourProvider> — First-launch onboarding state <SyncProvider> — Auth token bridge + sync lifecycle <AppContent /> — Main application UI </SyncProvider> <TourOverlay /> — Onboarding overlay (outside SyncProvider) <ToastContainer /> — Toast notifications (outside SyncProvider) </TourProvider> </WorkspaceProvider></ErrorBoundary>Within a workspace, AppContent uses CommandPaletteProvider to scope keyboard shortcuts and palette state.
Error Handling
Section titled “Error Handling”Two levels of error boundaries provide graceful degradation:
ErrorBoundary(top-level) — Catches catastrophic failures, shows full-page errorFeatureErrorBoundary(per-feature) — Wraps sidebar, editor, and context panel individually. A crash in one panel does not take down the others
Key Design Decisions
Section titled “Key Design Decisions”- Dumb pipe principle: The frontend contains zero business logic. This eliminates an entire class of consistency bugs between frontend and backend validation.
- Zustand over Redux: Zustand provides simpler API with less boilerplate. Single store with action-grouped slices.
- Refresh key pattern: Rather than complex cache invalidation, incrementing a key causes React to re-fetch. Simple and reliable.
- Per-cell LoroDoc isolation: Grid layout cells each maintain independent CRDTs. Sharing a LoroDoc across cells would create cross-cell undo/redo interference.
- Token bridge pattern: The Rust backend owns the auth session. The frontend mirrors it solely for Supabase JS client compatibility (Realtime WebSocket).
Related
Section titled “Related”- Loro CRDT System — CRDT integration details
- Page System — Page CRUD and storage model
- Layout System — Grid layouts and multi-editor
- Wiki-Link System — Link syntax and resolution
- Auth System — Authentication and token management
Was this page helpful?
Thanks for your feedback!