Frontend Architecture
Maintenance note: Update this file when frontend state management patterns, client APIs, entry point initialization, or major feature areas change.
Package: packages/frontend Build: cd packages/static && pnpm build-dev (do NOT run pnpm build in packages/frontend) Typecheck: cd packages/frontend && pnpm tsc --noEmit
App Initialization
Entry point: packages/frontend/entry-point.ts
Startup Sequence
jQuery plugins initialization
Core store/action registration (account, app, projects, file-use)
Optional features (notifications, markdown, customization)
React rendering
React Tree
Render entry: packages/frontend/app/render.tsx Main layout: packages/frontend/app/page.tsx
State Management
CoCalc uses a custom Redux-style framework in packages/frontend/app-framework/.
Key Files
app-framework/index.ts— GlobalAppReduxsingleton,rclasswrapperapp-framework/actions-and-stores.ts— Type registry for all global storesapp-framework/redux-hooks.ts— React hooks:useRedux,useStore,useActions,useTypedRedux,useEditorReduxapp-framework/Table.ts— Database synctable integrationapp-framework/syncdb/—useSyncdbRecord,useSyncdbContexthooks
Feature Store Pattern
Each feature area has a standard structure:
Registration:
Accessing Stores from Components
Major Global Stores
| Store | Registration | Purpose |
|---|---|---|
AccountStore | account/init.ts | User profile, settings, preferences, auth state |
ProjectsStore | projects/init.ts | Project listing, open projects, search/filter |
BillingStore | billing/init.ts | Invoices, subscriptions, payment methods |
PageStore | app/init.ts | Page navigation, layout |
CustomizeStore | customize/init.ts | Site customization settings |
Per-Project and Per-Editor Stores
ProjectStore— one per active project (project_store.ts)ProjectActions— one per active project (project_actions.ts)EditorStore/EditorActions— one per open file (created dynamically)
Access:
Immutable Data
Stores use Immutable.js (Map, List, Set). State changes are detected by reference comparison. All store state is deeply immutable.
Client Layer
packages/frontend/client/ provides the webapp_client singleton that all frontend code uses to communicate with the backend.
Key Files
| File | Sub-client | Purpose |
|---|---|---|
client/client.ts | WebappClient | Main client, initializes all sub-clients |
client/query.ts | QueryClient | Database queries and changefeeds via conat |
client/project.ts | ProjectClient | Project operations |
client/account.ts | AccountClient | Account/auth operations |
client/admin.ts | AdminClient | Admin-only operations |
client/llm.ts | LLMClient | LLM/AI integration |
client/purchases.ts | PurchasesClient | Billing operations |
client/users.ts | UsersClient | User lookup/tracking |
client/api.ts | — | api() for REST calls to /api/v2/ |
client/messages.ts | Messages | WebSocket messaging |
client/time.ts | TimeClient | Server time sync |
Conat Client
packages/frontend/conat/client.ts manages the conat connection:
Connection Layer
Feature Areas
The frontend is organized into feature directories under packages/frontend/:
| Directory | Purpose |
|---|---|
account/ | Account settings, preferences, SSH keys |
admin/ | Site administration panel |
billing/ | Billing, subscriptions, invoices |
chat/ | Side chat and standalone chat |
codemirror/ | CodeMirror editor integration |
collaborators/ | Project collaborator management |
components/ | Shared React components (300+) |
conat/ | Conat client integration |
course/ | Course management (instructor tools) |
customize/ | Site customization |
editors/ | File editor registry and routing |
frame-editors/ | Frame-based editor system (28 editor types) |
i18n/ | Internationalization (react-intl, 19+ languages) |
jupyter/ | Jupyter notebook frontend |
messages/ | User messaging system |
notifications/ | Notification system |
project/ | Project page, file explorer, settings |
projects/ | Project listing and creation |
purchases/ | Purchase flow and management |
sagews/ | Sage worksheet frontend |
search/ | Search functionality |
share/ | Public sharing features |
site-licenses/ | License management |
Frame Editors
packages/frontend/frame-editors/ provides the split-pane editor system used for all file editing in CoCalc.
Architecture Overview
Binary Tree Model
The frame layout is a binary tree stored as an Immutable.js Map. Each node is either an internal "node" (split into two panes) or a leaf with a specific editor type.
direction: "col"— vertical split (side by side)direction: "row"— horizontal split (top and bottom)pos— drag bar position as fraction (0 to 1)
Tree operations in frame-tree/tree-ops.ts:
split_leaf(tree, id, direction, type)— Split a leaf into two panesdelete_node(tree, id)— Remove a frame, replacing parent with siblingget_node(tree, id)— Find a node by IDget_leaf_ids(tree)— Get all leaf node IDsassign_ids(tree)— Assign UUID-based IDs (8 chars) to nodes without themnew_frame(tree, direction, type)— Create new root with existing tree + new leaf
Editor Registration
Editors are registered by file extension via frame-tree/register.ts:
When a file is opened:
get_file_editor(ext, is_public)looks up the registered editorinit(path, redux, project_id)creates a Redux store + actions for this fileThe store name is
redux_name(project_id, path)(deterministic)Reference counting tracks open instances; cleanup on last close
Editor Spec Pattern
Each editor type declares an EditorSpec — a map of frame type names to EditorDescription objects:
The createEditor() function (in frame-tree/editor.tsx) wraps the spec into a FrameTreeEditor component that manages the full frame tree.
Complex editors override _raw_default_frame_tree() in their Actions class to specify the initial layout:
Component Hierarchy
Each leaf gets a FrameContext.Provider with id, project_id, path, actions, desc, font_size, isFocused, isVisible, and redux.
Persistence: Where State Is Stored
Frame editors persist state at three levels:
1. File content — SyncString/SyncDB (via conat, server-side)
The primary file content is synced in real-time via conat:
SyncString— for plain text files (code, markdown, etc.)SyncDB— for structured data (Jupyter notebooks, whiteboards)Changes sync to all collaborators and persist to disk on the project daemon
Initialized in
_init_syncstring()which creates the sync object and registers change handlers
2. Frame layout + editor state — localStorage (per-browser)
The local_view_state stores everything about the editor's visual layout:
Stored in localStorage under the key redux_name(project_id, path). Loaded on open via _load_local_view_state(), saved (debounced 1500ms) via _save_local_view_state() on every change. Survives page reloads but is per-browser (not synced between devices).
Key methods:
set_local_view_state(obj)— Update fields in local_view_statesave_editor_state(id, state)— Save per-frame state (e.g., scroll position)reset_local_view_state()— Delete from localStorage and rebuild defaults
3. Account settings — Redux global store (synced via database)
Font size defaults, editor preferences, terminal settings come from the account store and are passed down as props:
Data Flow: Opening a File
Key Files
Browser Console Debugging
In dev mode (DEBUG=true), the global cc (or cocalc) object is available in the browser console. Defined in packages/frontend/client/console.ts.
Note: If cc is undefined in Chrome DevTools, make sure the console context is set to top (not an iframe).
Authentication
Wait for auth to complete before accessing account data:
The AccountStore tracks user_type: "public" → "signing_in" → "signed_in".
Styling
SASS files in
packages/frontend/_*.sassColors: always use
COLORSfrom@cocalc/util/theme— never hardcodeAnt Design components with custom overrides