From 60d30970fddbfd8cc37ebee2302ba818c12caa9f Mon Sep 17 00:00:00 2001 From: olexndr <92177586+iamalexndr@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:01:34 +0200 Subject: [PATCH 1/2] Add files via upload --- ONBOARDING.md | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 ONBOARDING.md diff --git a/ONBOARDING.md b/ONBOARDING.md new file mode 100644 index 0000000..692be82 --- /dev/null +++ b/ONBOARDING.md @@ -0,0 +1,208 @@ +# Excalidraw Developer Onboarding + +## What is Excalidraw? + +Excalidraw is an open-source virtual whiteboard for creating hand-drawn style diagrams, wireframes, and sketches. It's designed to be collaborative, end-to-end encrypted, and works offline as a PWA. + +The project is both a **React component library** (`@excalidraw/excalidraw` on npm) and a **full web application** (excalidraw.com) — they live in the same monorepo. + +--- + +## Tech Stack + +| Layer | Technology | +|---|---| +| UI | React 19, TypeScript 5.9 (strict) | +| State | Jotai (atomic state) | +| Canvas | Canvas API + RoughJS (hand-drawn style) | +| Styling | SASS/SCSS | +| Build (app) | Vite 5 | +| Build (packages) | esbuild | +| Testing | Vitest + @testing-library/react | +| Linting | ESLint + Prettier | +| Collaboration | Socket.io + Firebase | +| Monorepo | Yarn Workspaces | + +--- + +## Repository Structure + +``` +excalidraw/ +├── packages/ +│ ├── excalidraw/ # @excalidraw/excalidraw — the npm library (main package) +│ ├── common/ # @excalidraw/common — shared constants, color utils +│ ├── element/ # @excalidraw/element — shape types, geometry, mutations +│ ├── math/ # @excalidraw/math — 2D math, vectors, transforms +│ └── utils/ # @excalidraw/utils — clipboard, file export/import +├── excalidraw-app/ # The excalidraw.com application +│ ├── App.tsx # App-level wrapper +│ ├── collab/ # Real-time collaboration +│ └── data/ # Firebase, local storage, file management +├── examples/ # Integration examples (Next.js, plain HTML) +├── scripts/ # Build and release scripts +└── dev-docs/ # Developer documentation +``` + +**Rule of thumb:** If it's a general editor feature, work in `packages/`. If it's specific to excalidraw.com, work in `excalidraw-app/`. + +**Package dependency order:** +``` +@excalidraw/excalidraw + ├── @excalidraw/common + ├── @excalidraw/element + │ ├── @excalidraw/math + │ └── @excalidraw/common + └── @excalidraw/utils +``` + +--- + +## Architecture + +### State Management + +State is managed with **Jotai** (atomic). There is no Redux or Context for editor state. + +- `packages/excalidraw/editor-jotai.ts` — editor atoms (library-level) +- `excalidraw-app/app-jotai.ts` — app atoms (collab, theme, etc.) +- The central `AppState` type lives in `packages/excalidraw/types.ts` and holds all UI + canvas state (tool, zoom, pan, selections, drawing properties, etc.) + +### Rendering + +Two-layer canvas rendering in `packages/excalidraw/scene/`: + +- **`interactiveScene.ts`** — live canvas with interactions (what the user sees while drawing) +- **`staticScene.ts`** — deterministic output used for export/snapshots +- **`staticSvgScene.ts`** — SVG export + +RoughJS applies the hand-drawn aesthetic on top of the canvas API. + +### Element System + +Elements (rectangles, arrows, text, images, etc.) are **immutable objects** with version numbers. Mutations always produce new element instances — this is critical for collaborative conflict resolution and undo/redo. + +Types are defined in `packages/element/types.ts`. Supported element types: `rectangle`, `diamond`, `ellipse`, `arrow`, `line`, `freedraw`, `text`, `image`, `frame`, `embeddable`. + +### Actions System + +Editor operations (copy, paste, align, delete, etc.) are declarative actions defined in `packages/excalidraw/actions/`. Each action file follows the pattern `action[FeatureName].ts(x)` and declares its keyboard shortcut, menu binding, and handler. + +### Data Flow + +``` +User interaction + → Action handler (packages/excalidraw/actions/) + → Jotai atom update (AppState / elements) + → React re-render + → Canvas redraw (interactiveScene.ts) +``` + +Serialization (`data/encode.ts`) produces compressed JSON (`.excalidraw` files). Deserialization (`data/restore.ts`) handles schema migrations across versions. + +--- + +## First Steps + +### 1. Prerequisites + +- Node.js >= 18.0.0 +- Yarn (installed globally or via corepack) + +### 2. Setup + +```bash +git clone https://github.com/excalidraw/excalidraw.git +cd excalidraw +yarn install +``` + +### 3. Start the dev server + +```bash +yarn start +# App runs at http://localhost:3000 +``` + +### 4. Run tests + +```bash +yarn test:typecheck # TypeScript check +yarn test:update # Run all tests (updates snapshots) +yarn fix # Auto-fix formatting and lint +``` + +--- + +## Development Commands + +```bash +yarn start # Dev server (excalidraw-app) +yarn test:typecheck # TypeScript compilation check +yarn test:update # Run all tests with snapshot updates +yarn test:app # Run Vitest in watch mode +yarn fix # Auto-fix ESLint + Prettier +yarn build # Production build (excalidraw-app) +yarn build:packages # Build all library packages +``` + +**Before every commit:** +```bash +yarn test:typecheck && yarn test:update && yarn fix +``` + +--- + +## Key Files to Know + +| File | Purpose | +|---|---| +| `packages/excalidraw/index.tsx` | Library entry point, `` component | +| `packages/excalidraw/types.ts` | Core types: `AppState`, `ExcalidrawProps` | +| `packages/excalidraw/components/App.tsx` | Main editor component | +| `packages/excalidraw/editor-jotai.ts` | Jotai store setup | +| `packages/excalidraw/scene/interactiveScene.ts` | Live canvas rendering | +| `packages/excalidraw/data/restore.ts` | Deserialize / migrate drawings | +| `packages/element/types.ts` | Element type definitions | +| `excalidraw-app/App.tsx` | Application wrapper | +| `excalidraw-app/collab/Collab.tsx` | Collaboration logic | + +--- + +## Conventions + +**Imports:** +- Use package aliases: `import { ... } from "@excalidraw/common"` +- `import type { ... }` for TypeScript-only imports (ESLint enforced) +- Import order: builtin → external → `@excalidraw/*` → parent → sibling + +**Naming:** +- Components: `PascalCase.tsx` +- Utilities: `camelCase.ts` +- Tests: `FileName.test.ts` alongside source files + +**Elements are immutable** — never mutate an element in place. Always create a new object with an incremented version. + +**Do not import directly from `jotai`** — use `editor-jotai` or `app-jotai` wrappers. + +--- + +## CI Checks (must pass on PRs) + +| Check | Command | +|---|---| +| TypeScript | `yarn test:typecheck` | +| Lint | `yarn test:code` | +| Formatting | `yarn test:other` | +| Tests | `yarn test:app` | +| Bundle size | automated via size-limit action | +| PR title | must follow semantic commit format | + +--- + +## Getting Help + +- **Docs:** https://docs.excalidraw.com +- **Contributing guide:** https://docs.excalidraw.com/docs/introduction/contributing +- **Discord:** https://discord.gg/UexuTaE +- **Issues:** https://github.com/excalidraw/excalidraw/issues From bf624edb359500b02645c7933ecdd61205ca9bfd Mon Sep 17 00:00:00 2001 From: alexndrzbgl Date: Thu, 26 Mar 2026 18:30:10 +0200 Subject: [PATCH 2/2] docs: add ONBOARDING.md for new developers --- ONBOARDING.md | 160 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 65 deletions(-) diff --git a/ONBOARDING.md b/ONBOARDING.md index 692be82..d21d4e3 100644 --- a/ONBOARDING.md +++ b/ONBOARDING.md @@ -2,9 +2,9 @@ ## What is Excalidraw? -Excalidraw is an open-source virtual whiteboard for creating hand-drawn style diagrams, wireframes, and sketches. It's designed to be collaborative, end-to-end encrypted, and works offline as a PWA. +Excalidraw is an open-source virtual whiteboard for sketching diagrams, wireframes, and ideas in a hand-drawn aesthetic. It runs entirely in the browser, works offline as a PWA, and supports real-time collaboration with end-to-end encryption. -The project is both a **React component library** (`@excalidraw/excalidraw` on npm) and a **full web application** (excalidraw.com) — they live in the same monorepo. +The project serves two purposes at once: it's a **publishable React component library** (`@excalidraw/excalidraw` on npm) that other products can embed, and it's the full **excalidraw.com web application** — both living in the same monorepo. This dual nature shapes almost every structural decision in the codebase. --- @@ -27,6 +27,8 @@ The project is both a **React component library** (`@excalidraw/excalidraw` on n ## Repository Structure +The repo is cleanly split between the library packages and the application: + ``` excalidraw/ ├── packages/ @@ -44,16 +46,17 @@ excalidraw/ └── dev-docs/ # Developer documentation ``` -**Rule of thumb:** If it's a general editor feature, work in `packages/`. If it's specific to excalidraw.com, work in `excalidraw-app/`. +The most important mental model: **`packages/` is the library, `excalidraw-app/` is the product**. Any feature that belongs in the editor itself — a new tool, a rendering fix, a new element type — lives under `packages/`. Anything that's specific to excalidraw.com — collaboration, Firebase, PWA registration, analytics — lives in `excalidraw-app/`. When in doubt, ask whether the change would make sense if someone embedded the library in their own app. If yes, it belongs in `packages/`. + +The packages form a layered dependency chain. Lower-level packages know nothing about higher-level ones: -**Package dependency order:** ``` -@excalidraw/excalidraw - ├── @excalidraw/common - ├── @excalidraw/element - │ ├── @excalidraw/math +@excalidraw/excalidraw ← main library, depends on everything below + ├── @excalidraw/common ← foundational constants and utilities + ├── @excalidraw/element ← shape types and geometry + │ ├── @excalidraw/math ← pure 2D math │ └── @excalidraw/common - └── @excalidraw/utils + └── @excalidraw/utils ← export/import, clipboard ``` --- @@ -62,43 +65,47 @@ excalidraw/ ### State Management -State is managed with **Jotai** (atomic). There is no Redux or Context for editor state. +The editor's entire runtime state — the active tool, zoom level, pan offset, selected elements, current stroke color, whether the grid is on, and dozens of other properties — lives in a single `AppState` object defined in [packages/excalidraw/types.ts](packages/excalidraw/types.ts). This object is exposed as Jotai atoms, not passed through props or stored in a Redux slice. + +The two relevant store files are: +- [packages/excalidraw/editor-jotai.ts](packages/excalidraw/editor-jotai.ts) — atoms for the library-level editor state +- [excalidraw-app/app-jotai.ts](excalidraw-app/app-jotai.ts) — atoms for app-specific state like collaboration and theme -- `packages/excalidraw/editor-jotai.ts` — editor atoms (library-level) -- `excalidraw-app/app-jotai.ts` — app atoms (collab, theme, etc.) -- The central `AppState` type lives in `packages/excalidraw/types.ts` and holds all UI + canvas state (tool, zoom, pan, selections, drawing properties, etc.) +Jotai was chosen over Redux because it allows fine-grained subscriptions — a component that only cares about the zoom level won't re-render when the stroke color changes. ### Rendering -Two-layer canvas rendering in `packages/excalidraw/scene/`: +The canvas is rendered in two separate layers, both in [packages/excalidraw/scene/](packages/excalidraw/scene/): -- **`interactiveScene.ts`** — live canvas with interactions (what the user sees while drawing) -- **`staticScene.ts`** — deterministic output used for export/snapshots -- **`staticSvgScene.ts`** — SVG export +**[interactiveScene.ts](packages/excalidraw/scene/interactiveScene.ts)** handles the live canvas the user interacts with. It redraws on every state change and includes selection handles, hover states, and in-progress drawing feedback. -RoughJS applies the hand-drawn aesthetic on top of the canvas API. +**[staticScene.ts](packages/excalidraw/scene/staticScene.ts)** produces a deterministic, interaction-free render of the elements. This is what gets used when exporting to PNG. **[staticSvgScene.ts](packages/excalidraw/scene/staticSvgScene.ts)** does the same for SVG exports. + +RoughJS sits between the canvas API and the element geometry — it intercepts draw calls and adds the hand-drawn jitter. This means the aesthetic is applied uniformly across all shape types without each one needing to implement it. ### Element System -Elements (rectangles, arrows, text, images, etc.) are **immutable objects** with version numbers. Mutations always produce new element instances — this is critical for collaborative conflict resolution and undo/redo. +Every shape on the canvas is an *element* — a plain TypeScript object with a type, geometry, style properties, and a version number. Elements are **immutable**: you never mutate one in place. Any change produces a new object with an incremented version. This constraint is load-bearing — the version number is how the collaboration layer detects and resolves conflicts, and how the undo/redo history tracks changes. -Types are defined in `packages/element/types.ts`. Supported element types: `rectangle`, `diamond`, `ellipse`, `arrow`, `line`, `freedraw`, `text`, `image`, `frame`, `embeddable`. +Element types (`rectangle`, `diamond`, `ellipse`, `arrow`, `line`, `freedraw`, `text`, `image`, `frame`, `embeddable`) and their shared and specific properties are all defined in [packages/element/types.ts](packages/element/types.ts). ### Actions System -Editor operations (copy, paste, align, delete, etc.) are declarative actions defined in `packages/excalidraw/actions/`. Each action file follows the pattern `action[FeatureName].ts(x)` and declares its keyboard shortcut, menu binding, and handler. +Rather than scattering editor logic across components, all discrete editor operations are defined as *actions* in [packages/excalidraw/actions/](packages/excalidraw/actions/). Each action file (e.g. `actionAlign.tsx`, `actionClipboard.tsx`) declares what it does, what keyboard shortcut triggers it, where it appears in menus, and the handler function that executes it. This makes it straightforward to add a new editor operation without touching component code — you define the action and register it. ### Data Flow +Understanding how a user interaction becomes a canvas update is useful early on: + ``` -User interaction - → Action handler (packages/excalidraw/actions/) - → Jotai atom update (AppState / elements) +User interaction (click, keypress, drag) + → Action handler (packages/excalidraw/actions/) + → Jotai atom update (AppState and/or elements array) → React re-render - → Canvas redraw (interactiveScene.ts) + → Canvas redraw (interactiveScene.ts) ``` -Serialization (`data/encode.ts`) produces compressed JSON (`.excalidraw` files). Deserialization (`data/restore.ts`) handles schema migrations across versions. +Saving a drawing serializes the elements and AppState to compressed JSON via [data/encode.ts](packages/excalidraw/data/encode.ts), producing a `.excalidraw` file. Loading runs the reverse through [data/restore.ts](packages/excalidraw/data/restore.ts), which also handles schema migrations so old files continue to open correctly. --- @@ -106,10 +113,20 @@ Serialization (`data/encode.ts`) produces compressed JSON (`.excalidraw` files). ### 1. Prerequisites -- Node.js >= 18.0.0 -- Yarn (installed globally or via corepack) +You need **Node.js >= 18.0.0** and **Yarn**. If you don't have Yarn, the easiest way is via corepack which ships with Node: + +```bash +corepack enable +``` + +Verify your setup: + +```bash +node --version # should be >= 18.0.0 +yarn --version # should print a version number +``` -### 2. Setup +### 2. Clone and install ```bash git clone https://github.com/excalidraw/excalidraw.git @@ -117,6 +134,8 @@ cd excalidraw yarn install ``` +`yarn install` from the root installs dependencies for all workspaces at once. You don't need to `cd` into individual packages — Yarn Workspaces handles everything from the root. This step also sets up the symlinks between packages so that, for example, `excalidraw-app` can import `@excalidraw/excalidraw` directly from source without a build step. + ### 3. Start the dev server ```bash @@ -124,26 +143,41 @@ yarn start # App runs at http://localhost:3000 ``` -### 4. Run tests +This starts the `excalidraw-app` Vite dev server with hot module replacement. Because the packages are symlinked, any change you make in `packages/excalidraw/` or any other package is reflected in the browser immediately — no separate package build step needed during development. + +### 4. Explore the codebase + +A good starting path through the code: + +1. [packages/excalidraw/index.tsx](packages/excalidraw/index.tsx) — see what the library exposes publicly +2. [packages/excalidraw/types.ts](packages/excalidraw/types.ts) — read `AppState` and `ExcalidrawProps` to understand what drives the editor +3. [packages/excalidraw/components/App.tsx](packages/excalidraw/components/App.tsx) — the main editor component where most interaction logic lives +4. [packages/excalidraw/actions/](packages/excalidraw/actions/) — pick any action file to see the pattern for adding editor operations + +### 5. Make a change and verify it + +Before touching anything real, do a quick smoke test to confirm your environment works end-to-end: ```bash -yarn test:typecheck # TypeScript check -yarn test:update # Run all tests (updates snapshots) -yarn fix # Auto-fix formatting and lint +yarn test:typecheck # TypeScript compiles cleanly +yarn test:update # All tests pass (updates snapshots if needed) +yarn fix # No formatting or lint issues ``` +All three should exit cleanly. If they do, your setup is correct and you're ready to contribute. Run the same three commands before every commit — the CI enforces all of them on every PR. + --- ## Development Commands ```bash -yarn start # Dev server (excalidraw-app) +yarn start # Dev server for excalidraw-app (localhost:3000) yarn test:typecheck # TypeScript compilation check -yarn test:update # Run all tests with snapshot updates +yarn test:update # Run all tests, update snapshots yarn test:app # Run Vitest in watch mode -yarn fix # Auto-fix ESLint + Prettier -yarn build # Production build (excalidraw-app) -yarn build:packages # Build all library packages +yarn fix # Auto-fix ESLint + Prettier issues +yarn build # Production build of excalidraw-app +yarn build:packages # Compile all library packages with esbuild ``` **Before every commit:** @@ -155,48 +189,44 @@ yarn test:typecheck && yarn test:update && yarn fix ## Key Files to Know -| File | Purpose | +| File | What it does | |---|---| -| `packages/excalidraw/index.tsx` | Library entry point, `` component | -| `packages/excalidraw/types.ts` | Core types: `AppState`, `ExcalidrawProps` | -| `packages/excalidraw/components/App.tsx` | Main editor component | -| `packages/excalidraw/editor-jotai.ts` | Jotai store setup | -| `packages/excalidraw/scene/interactiveScene.ts` | Live canvas rendering | -| `packages/excalidraw/data/restore.ts` | Deserialize / migrate drawings | -| `packages/element/types.ts` | Element type definitions | -| `excalidraw-app/App.tsx` | Application wrapper | -| `excalidraw-app/collab/Collab.tsx` | Collaboration logic | +| [packages/excalidraw/index.tsx](packages/excalidraw/index.tsx) | Library entry point — exports the `` component | +| [packages/excalidraw/types.ts](packages/excalidraw/types.ts) | Defines `AppState`, `ExcalidrawProps`, and all core types | +| [packages/excalidraw/components/App.tsx](packages/excalidraw/components/App.tsx) | The main editor component — most interaction logic lives here | +| [packages/excalidraw/editor-jotai.ts](packages/excalidraw/editor-jotai.ts) | Jotai store setup for the library | +| [packages/excalidraw/scene/interactiveScene.ts](packages/excalidraw/scene/interactiveScene.ts) | Live canvas rendering | +| [packages/excalidraw/data/restore.ts](packages/excalidraw/data/restore.ts) | Deserializes and migrates saved drawings | +| [packages/element/types.ts](packages/element/types.ts) | All element type definitions | +| [excalidraw-app/App.tsx](excalidraw-app/App.tsx) | Application-level wrapper around the library | +| [excalidraw-app/collab/Collab.tsx](excalidraw-app/collab/Collab.tsx) | Real-time collaboration logic | --- ## Conventions -**Imports:** -- Use package aliases: `import { ... } from "@excalidraw/common"` -- `import type { ... }` for TypeScript-only imports (ESLint enforced) -- Import order: builtin → external → `@excalidraw/*` → parent → sibling +**Imports:** Always use package aliases rather than relative paths across package boundaries — `import { ... } from "@excalidraw/common"`, not `../../common/src/...`. Use `import type { ... }` for TypeScript-only imports (ESLint enforces this). Import order goes: builtins → external packages → `@excalidraw/*` packages → parent directories → siblings. -**Naming:** -- Components: `PascalCase.tsx` -- Utilities: `camelCase.ts` -- Tests: `FileName.test.ts` alongside source files +**Do not import directly from `jotai`** — always go through `editor-jotai` or `app-jotai`. These wrappers configure the correct store instance. -**Elements are immutable** — never mutate an element in place. Always create a new object with an incremented version. +**Naming:** React components use `PascalCase.tsx`, utility modules use `camelCase.ts`, and test files sit alongside their source as `FileName.test.ts`. -**Do not import directly from `jotai`** — use `editor-jotai` or `app-jotai` wrappers. +**Elements are immutable.** Never mutate an element object in place. Always create a new object and increment its `version` field. Code that violates this will subtly break undo/redo and collaboration. --- -## CI Checks (must pass on PRs) +## CI Checks + +All of these must pass before a PR can merge: -| Check | Command | +| Check | What it runs | |---|---| | TypeScript | `yarn test:typecheck` | -| Lint | `yarn test:code` | -| Formatting | `yarn test:other` | -| Tests | `yarn test:app` | -| Bundle size | automated via size-limit action | -| PR title | must follow semantic commit format | +| Lint | `yarn test:code` (ESLint) | +| Formatting | `yarn test:other` (Prettier) | +| Tests | `yarn test:app` (Vitest) | +| Bundle size | size-limit action — flags regressions automatically | +| PR title | must follow the semantic commit format (e.g. `feat:`, `fix:`) | ---