From 5f8a24fbb35de554667f81b3f23b080526fe54ae Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Wed, 8 Apr 2026 22:30:07 +0200
Subject: [PATCH 1/6] Rules and commands added
---
.cursor/commands/add-locale-string.md | 34 ++
.cursor/commands/app-only-feature.md | 32 ++
.cursor/commands/debug-collab.md | 30 ++
.cursor/commands/element-schema-change.md | 50 +++
.cursor/commands/new-action.md | 30 ++
.cursor/commands/new-vitest-excalidraw.md | 38 ++
.cursor/commands/pre-pr-check.md | 33 ++
.cursor/commands/rendering-change.md | 39 +++
.cursor/commands/restore-migration.md | 30 ++
.cursor/commands/size-budget.md | 30 ++
.cursor/commands/update-state-flow.md | 110 ++++++
.cursor/rules/excalidraw-actions-state.mdc | 21 ++
.cursor/rules/excalidraw-app-integration.mdc | 20 ++
.../rules/excalidraw-canvas-components.mdc | 17 +
.cursor/rules/excalidraw-data-collab.mdc | 20 ++
.cursor/rules/excalidraw-element-model.mdc | 28 ++
.cursor/rules/excalidraw-monorepo.mdc | 21 ++
.cursor/rules/excalidraw-rendering.mdc | 20 ++
.cursor/rules/excalidraw-testing.mdc | 23 ++
.../rules/excalidraw-typescript-imports.mdc | 31 ++
.cursor/rules/excalidraw-ui-app.mdc | 17 +
.cursor/rules/excalidraw-ui.mdc | 18 +
.../docs/a-docs/01-architecture-overview.md | 210 +++++++++++
dev-docs/docs/a-docs/02-local-setup.md | 183 ++++++++++
.../docs/a-docs/03-codebase-navigation.md | 176 ++++++++++
dev-docs/docs/a-docs/04-key-business-flows.md | 330 ++++++++++++++++++
.../docs/a-docs/05-important-components.md | 277 +++++++++++++++
dev-docs/docs/a-docs/06-testing-guide.md | 262 ++++++++++++++
dev-docs/docs/a-docs/07-common-pitfalls.md | 177 ++++++++++
dev-docs/docs/a-docs/08-adding-features.md | 273 +++++++++++++++
dev-docs/docs/a-docs/09-tldr-new-devs.md | 130 +++++++
dev-docs/docs/a-docs/README.md | 31 ++
32 files changed, 2741 insertions(+)
create mode 100644 .cursor/commands/add-locale-string.md
create mode 100644 .cursor/commands/app-only-feature.md
create mode 100644 .cursor/commands/debug-collab.md
create mode 100644 .cursor/commands/element-schema-change.md
create mode 100644 .cursor/commands/new-action.md
create mode 100644 .cursor/commands/new-vitest-excalidraw.md
create mode 100644 .cursor/commands/pre-pr-check.md
create mode 100644 .cursor/commands/rendering-change.md
create mode 100644 .cursor/commands/restore-migration.md
create mode 100644 .cursor/commands/size-budget.md
create mode 100644 .cursor/commands/update-state-flow.md
create mode 100644 .cursor/rules/excalidraw-actions-state.mdc
create mode 100644 .cursor/rules/excalidraw-app-integration.mdc
create mode 100644 .cursor/rules/excalidraw-canvas-components.mdc
create mode 100644 .cursor/rules/excalidraw-data-collab.mdc
create mode 100644 .cursor/rules/excalidraw-element-model.mdc
create mode 100644 .cursor/rules/excalidraw-monorepo.mdc
create mode 100644 .cursor/rules/excalidraw-rendering.mdc
create mode 100644 .cursor/rules/excalidraw-testing.mdc
create mode 100644 .cursor/rules/excalidraw-typescript-imports.mdc
create mode 100644 .cursor/rules/excalidraw-ui-app.mdc
create mode 100644 .cursor/rules/excalidraw-ui.mdc
create mode 100644 dev-docs/docs/a-docs/01-architecture-overview.md
create mode 100644 dev-docs/docs/a-docs/02-local-setup.md
create mode 100644 dev-docs/docs/a-docs/03-codebase-navigation.md
create mode 100644 dev-docs/docs/a-docs/04-key-business-flows.md
create mode 100644 dev-docs/docs/a-docs/05-important-components.md
create mode 100644 dev-docs/docs/a-docs/06-testing-guide.md
create mode 100644 dev-docs/docs/a-docs/07-common-pitfalls.md
create mode 100644 dev-docs/docs/a-docs/08-adding-features.md
create mode 100644 dev-docs/docs/a-docs/09-tldr-new-devs.md
create mode 100644 dev-docs/docs/a-docs/README.md
diff --git a/.cursor/commands/add-locale-string.md b/.cursor/commands/add-locale-string.md
new file mode 100644
index 0000000..c0fa4fd
--- /dev/null
+++ b/.cursor/commands/add-locale-string.md
@@ -0,0 +1,34 @@
+# Add locale string (i18n)
+
+Use for **user-visible** text in the **library** (`packages/excalidraw`), not hardcoded English.
+
+## 1. Pick a key
+
+- Prefer existing namespaces: **`labels.*`**, **`toolBar.*`**, **`hints.*`**, etc. — follow keys near similar UI in `packages/excalidraw/locales/` (e.g. `en.json`).
+
+## 2. Add English source
+
+- Edit the appropriate locale JSON under **`packages/excalidraw/locales/`** (start from **`en.json`** or the pattern used for your language).
+- Keep placeholders consistent with existing messages (`{variable}` style if the codebase uses interpolation — match siblings).
+
+## 3. Use in UI
+
+- Import **`useI18n`** from `../i18n` (or the established relative path from the component).
+- Call **`const { t } = useI18n()`** and use **`t("your.key")`** for labels, titles, buttons, tooltips.
+
+## 4. Actions
+
+- Action **`label`** (and **`keywords`**) should use **translation keys**, not raw English strings, when exposed in menus/command palette.
+
+## 5. Crowdin
+
+- Project translations are managed via **Crowdin** (per project docs); adding keys in English is the usual first step; translators pick up new keys there.
+
+## Do not
+
+- Hardcode user-facing strings in **`packages/excalidraw/components/`** for production UI.
+- For **`excalidraw-app/`**-only UI, follow existing app patterns (some strings may be app-specific — mirror nearby files).
+
+## See also
+
+`dev-docs/docs/a-docs/08-adding-features.md`, `.cursor/rules/excalidraw-ui.mdc`.
diff --git a/.cursor/commands/app-only-feature.md b/.cursor/commands/app-only-feature.md
new file mode 100644
index 0000000..7495a68
--- /dev/null
+++ b/.cursor/commands/app-only-feature.md
@@ -0,0 +1,32 @@
+# App-only feature (excalidraw.com)
+
+Use when the change targets **`excalidraw-app/`** only and must **not** bloat **`@excalidraw/excalidraw`**.
+
+## Boundaries
+
+- **App shell:** routing, welcome, share dialogs, Plus hooks, Firebase, collab wiring — **`excalidraw-app/`**.
+- **Reusable editor:** canvas, actions, element model — **`packages/excalidraw/`** (published npm package).
+
+Prefer **props and callbacks** into `` rather than forking core editor logic into the app.
+
+## State
+
+- **App-level Jotai:** **`excalidraw-app/app-jotai.ts`** — not raw `jotai`.
+- **Editor state:** still **`AppState` + elements** inside the package; app listens via **`onChange`** / APIs as today.
+
+## Config & services
+
+- **Env:** `VITE_APP_*` in `.env.development` / `.env.production`.
+- **Firebase / WebSocket URLs:** see app `data/` and `collab/`; reuse **`packages/excalidraw/data`** for encode/decode and reconcile — do not duplicate JSON formats.
+
+## UI placement
+
+- Components in **`excalidraw-app/components/`** with colocated **`.scss`** when needed (match **`AppSidebar.scss`** patterns).
+
+## Finish
+
+`yarn test:typecheck`, tests under **`excalidraw-app/tests/`** if applicable, `yarn fix`.
+
+## See also
+
+`dev-docs/docs/a-docs/03-codebase-navigation.md` (app-level data), `.cursor/rules/excalidraw-app-integration.mdc`, `excalidraw-ui-app.mdc`.
diff --git a/.cursor/commands/debug-collab.md b/.cursor/commands/debug-collab.md
new file mode 100644
index 0000000..2a168ac
--- /dev/null
+++ b/.cursor/commands/debug-collab.md
@@ -0,0 +1,30 @@
+# Debug collaboration
+
+Use when **live collaboration**, **shared rooms**, or **remote cursors** misbehave (drift, lost updates, duplicates, stale scenes).
+
+## Mental model
+
+- **Socket.io** (`excalidraw-app/collab/Portal.tsx`) carries realtime updates.
+- **Firebase** (`excalidraw-app/data/firebase.ts`) persists encrypted scene + files for rooms.
+- **Merge** uses **`reconcileElements`** in **`packages/excalidraw/data/reconcile.ts`** (version-based; ties / tombstones matter).
+- **Encryption key** is in the URL **hash** — not sent to HTTP servers; do not log full share URLs in production debug.
+
+## Investigation order
+
+1. **Repro** — two browsers, same room, minimal steps (draw, delete, reorder, image paste).
+2. **Local vs remote** — does the bug appear offline? If only online, suspect **reconcile**, **socket**, or **Firebase** timing.
+3. **`Collab.tsx`** — `startCollaboration`, `syncElements`, `handleRemoteSceneUpdate`, save queues.
+4. **`reconcile.ts`** — same element id, **version** / **isDeleted** / ordering after merge.
+5. **Element mutations** — direct mutation or missing **version** bumps break reconciliation and undo.
+6. **Images** — `FileManager`, Firebase file prefix, lazy fetch paths for peers’ assets.
+
+## Files to read first
+
+- `excalidraw-app/collab/Collab.tsx`
+- `excalidraw-app/collab/Portal.tsx`
+- `packages/excalidraw/data/reconcile.ts`
+- `packages/excalidraw/data/encryption.ts`
+
+## See also
+
+`dev-docs/docs/a-docs/04-key-business-flows.md` (collaboration section), `.cursor/rules/excalidraw-data-collab.mdc`, `excalidraw-app-integration.mdc`.
diff --git a/.cursor/commands/element-schema-change.md b/.cursor/commands/element-schema-change.md
new file mode 100644
index 0000000..626c464
--- /dev/null
+++ b/.cursor/commands/element-schema-change.md
@@ -0,0 +1,50 @@
+# Element schema change
+
+Use when adding or changing fields on **drawing elements** (shapes, text, arrows, frames, images, etc.).
+
+## 1. Types
+
+- **`packages/element/src/types.ts`** — extend the correct element interface or shared base type.
+
+## 2. Creation defaults
+
+- **`packages/element/src/newElement.ts`** (and any specialized factories) — set defaults for **new** elements.
+
+## 3. Load / paste / migrate
+
+- **`packages/excalidraw/data/restore.ts`** — defaults and migrations for **old** JSON, clipboard, and shared links so older files still open.
+
+## 4. Mutation
+
+- Use **`mutateElement`** (or established helpers). **Never** mutate element objects in place without proper **version** / **versionNonce** behavior.
+
+## 5. Ordering / scene
+
+- If z-order or structure changes: **`packages/element/src/Scene.ts`** and **fractional `index`** rules — prefer Scene APIs over hand-rolled indices.
+
+## 6. Rendering & export
+
+- Canvas: **`packages/excalidraw/renderer/staticScene.ts`**
+- SVG: **`packages/excalidraw/renderer/staticSvgScene.ts`**
+- PNG / export pipeline: **`packages/excalidraw/scene/export.ts`** as needed
+
+## 7. Serialization
+
+- **`packages/excalidraw/data/json.ts`** (and related types) if the field must round-trip in `.excalidraw` JSON.
+
+## 8. Collaboration
+
+- **`packages/excalidraw/data/reconcile.ts`** — merging uses **version**; deleted elements stay as tombstones for a reason.
+- Test **two clients** editing the same scene when the change affects concurrent updates.
+
+## 9. Tests
+
+- Add or extend tests under **`packages/element/tests/`** and **`packages/excalidraw/tests/`** as appropriate.
+
+## Finish
+
+`yarn test:typecheck`, `yarn test:update`, `yarn fix`.
+
+## See also
+
+`restore-migration.md`, `.cursor/rules/excalidraw-element-model.mdc`, `excalidraw-data-collab.mdc`.
diff --git a/.cursor/commands/new-action.md b/.cursor/commands/new-action.md
new file mode 100644
index 0000000..87a2e70
--- /dev/null
+++ b/.cursor/commands/new-action.md
@@ -0,0 +1,30 @@
+# New editor action
+
+Add a **user-triggered** state change via the action system (`ActionManager`).
+
+## Steps
+
+1. **Create** `packages/excalidraw/actions/actionYourFeature.ts` or `actionYourFeature.tsx`.
+2. **Import** `register` from `./register`, `CaptureUpdateAction` from `@excalidraw/element`, and types with `import type` from `./types` and `../types` as needed.
+3. **Implement** `export const actionYourFeature = register({ name, label, perform, captureUpdate, … })`:
+ - `perform(elements, appState, formData, app)` → `{ elements?, appState?, files?, captureUpdate }` or `false` to no-op.
+ - Choose **`captureUpdate`** to match the closest existing action (`IMMEDIATELY`, `EVENTUALLY`, `NEVER`, etc.).
+4. **Export** from `packages/excalidraw/actions/index.ts` (required for registration).
+5. **Shortcut** (optional): `keyTest` on the action and/or `packages/excalidraw/actions/shortcuts.ts`.
+6. **UI** (optional): toolbar / menu / command palette — follow patterns in `Actions.tsx`, `MainMenu.tsx`, or command palette registrations.
+7. **Label / i18n:** use a translation key for `label` (e.g. `"labels.myFeature"`) and add the string under `packages/excalidraw/locales/` (English + structure for Crowdin).
+8. **Tests:** `packages/excalidraw/actions/actionYourFeature.test.tsx` with `render` / `unmountComponent` from `../tests/test-utils`, `API`, `Keyboard` where relevant.
+
+## Conventions
+
+- Do not import from `packages/excalidraw/index.tsx` inside the package; use concrete module paths.
+- Prefer keeping pure state transforms in **`perform`**; use `App.tsx` only when pointer/tool lifecycle requires it.
+- Mirror **`actionToggleStats.tsx`**, **`actionDeleteSelected`**, or a similar action for structure.
+
+## Finish
+
+Run `yarn test:typecheck`, `yarn test:update` if snapshots change, `yarn fix`.
+
+## See also
+
+`update-state-flow.md`, `dev-docs/docs/a-docs/08-adding-features.md`, `.cursor/rules/excalidraw-actions-state.mdc`.
diff --git a/.cursor/commands/new-vitest-excalidraw.md b/.cursor/commands/new-vitest-excalidraw.md
new file mode 100644
index 0000000..9550448
--- /dev/null
+++ b/.cursor/commands/new-vitest-excalidraw.md
@@ -0,0 +1,38 @@
+# New Vitest test (Excalidraw editor)
+
+Add or extend tests using the project’s **Vitest + jsdom + Testing Library** setup.
+
+## Setup (global)
+
+- **`setupTests.ts`** (repo root) — canvas, IndexedDB, fonts, RAF mocks apply automatically.
+- **`vitest.config.mts`** — path aliases **`@excalidraw/*`** must match imports in tests.
+
+## Component / integration tests
+
+1. Import **`render`**, **`unmountComponent`** from **`packages/excalidraw/tests/test-utils`** (adjust relative path from test file).
+2. **`beforeEach`:** `await render()` (or the minimal wrapper used in sibling tests).
+3. **`afterEach`:** `unmountComponent()` when using that helper.
+4. Use **`API`** (`../tests/helpers/api`), **`Keyboard`**, **`Mouse`** from **`../tests/helpers/ui`** when simulating editor actions — prefer existing helpers over brittle DOM queries.
+
+## Pure logic tests
+
+- **`packages/element/tests/`**, **`packages/math/tests/`**, **`packages/common/`** colocated `*.test.ts` — no React unless needed.
+
+## Running
+
+```bash
+yarn test:app -- path/to/file.test.tsx
+yarn test:app -- -t "partial test name"
+```
+
+## Snapshots
+
+- If output is correct, run **`yarn test:update`** and **review diffs** before committing.
+
+## Finish
+
+`yarn test:typecheck`, `yarn fix`.
+
+## See also
+
+`dev-docs/docs/a-docs/06-testing-guide.md`, `.cursor/rules/excalidraw-testing.mdc`.
diff --git a/.cursor/commands/pre-pr-check.md b/.cursor/commands/pre-pr-check.md
new file mode 100644
index 0000000..a59d0d2
--- /dev/null
+++ b/.cursor/commands/pre-pr-check.md
@@ -0,0 +1,33 @@
+# Pre-PR check
+
+Run this before opening a PR or pushing a feature branch.
+
+## Commands (repo root)
+
+```bash
+yarn test:update
+yarn test:typecheck
+yarn fix
+```
+
+1. **`yarn test:update`** — runs Vitest and updates snapshots where intended. **Review every snapshot diff**; do not commit accidental UI regressions.
+2. **`yarn test:typecheck`** — full TypeScript check for the monorepo configuration.
+3. **`yarn fix`** — Prettier + ESLint auto-fix (`fix:other` + `fix:code`).
+
+## CI parity (optional but thorough)
+
+```bash
+yarn test:all
+```
+
+Equivalent to typecheck + eslint + prettier check + tests (non-watch).
+
+## Manual sanity
+
+- [ ] New user-facing strings go through **`locales/`** (library UI).
+- [ ] Element changes: **`mutateElement`** + version / collab implications considered.
+- [ ] New deps: bundle / **size-limit** CI (`.github/workflows/size-limit.yml`) if the change affects published packages.
+
+## If something fails
+
+Fix in order: **types** → **lint** → **tests** → **snapshots** (only if output is correct).
diff --git a/.cursor/commands/rendering-change.md b/.cursor/commands/rendering-change.md
new file mode 100644
index 0000000..4167fb6
--- /dev/null
+++ b/.cursor/commands/rendering-change.md
@@ -0,0 +1,39 @@
+# Rendering / canvas change
+
+Use when changing **how elements are drawn** or how **canvas layers** behave.
+
+## Architecture
+
+- **Static canvas** — committed elements, **Rough.js**, should **not** redraw every pointer move.
+- **Interactive canvas** — selection, handles, snaps, remote cursors.
+- **New element canvas** — in-progress shape before commit.
+
+Orchestration lives under **`packages/excalidraw/components/canvases/`**; core drawing in **`packages/excalidraw/renderer/`**.
+
+## Files
+
+| Concern | Typical files |
+|--------|----------------|
+| Static bitmap | `renderer/staticScene.ts` |
+| Selection overlay | `renderer/interactiveScene.ts` |
+| SVG export | `renderer/staticSvgScene.ts` |
+| Export to PNG / clipboard | `scene/export.ts` |
+
+## Performance
+
+- Preserve **caching** of Rough drawables (tied to element version — do not recreate all drawables on every frame).
+- Avoid pushing static-layer updates from **`pointerMove`** unless necessary.
+- Stress-test with **many elements** (hundreds+) after changes.
+
+## Consistency
+
+- If canvas appearance changes, check **SVG export** matches for the same scene where applicable.
+
+## Tests / checks
+
+- Relevant renderer tests or visual snapshots; `yarn test:app` for affected areas.
+- `yarn test:typecheck`
+
+## See also
+
+`dev-docs/docs/a-docs/01-architecture-overview.md` (rendering), `.cursor/rules/excalidraw-rendering.mdc`, `excalidraw-canvas-components.mdc`.
diff --git a/.cursor/commands/restore-migration.md b/.cursor/commands/restore-migration.md
new file mode 100644
index 0000000..5b6cb1a
--- /dev/null
+++ b/.cursor/commands/restore-migration.md
@@ -0,0 +1,30 @@
+# Restore & migration (`restore.ts`)
+
+Use when **saved files**, **clipboard**, or **shared links** must keep working after changing element or app state shape.
+
+## Primary file
+
+- **`packages/excalidraw/data/restore.ts`** — `restoreElements`, `restoreAppState`, `restoreLibraryItems`, version bumps where required.
+
+## Goals
+
+1. **Old JSON** still parses — add defaults for new fields; rename fields with explicit migration steps if needed.
+2. **Invalid / partial data** — normalize defensively; match style of existing restore branches.
+3. **Collaboration** — after restore, **element versions** must remain consistent with reconciliation expectations (see **`bumpElementVersions`** and related helpers in this file / callers).
+
+## Related
+
+- **`packages/element/src/types.ts`** — source of truth for element shape.
+- **`packages/excalidraw/data/json.ts`** — serialization / types for file format.
+- **Tests** — add fixtures or extend tests under **`packages/excalidraw/tests/`** or **`packages/element/tests/`** for old-format samples when possible.
+
+## Checklist
+
+- [ ] New field has a default in **`restore.ts`**
+- [ ] No regression loading a **minimal** and a **real** saved `.excalidraw` file
+- [ ] Clipboard paste from an **old** build still works if users mix versions
+- [ ] `yarn test:typecheck` and targeted tests pass
+
+## See also
+
+`element-schema-change.md`, `dev-docs/docs/a-docs/05-important-components.md` (restore / migration), `.cursor/rules/excalidraw-data-collab.mdc`.
diff --git a/.cursor/commands/size-budget.md b/.cursor/commands/size-budget.md
new file mode 100644
index 0000000..7ce3206
--- /dev/null
+++ b/.cursor/commands/size-budget.md
@@ -0,0 +1,30 @@
+# Size budget & bundle impact
+
+Use before adding **heavy dependencies** or **large imports** to **`packages/excalidraw`** or the app entry.
+
+## Why
+
+- The published editor is sensitive to bundle size; CI may enforce limits.
+- **`@size-limit/preset-big-lib`** is a dev dependency of **`packages/excalidraw`**; workflow automation lives under **`.github/workflows/size-limit.yml`**.
+
+## What to do
+
+1. **Prefer** existing utilities in **`@excalidraw/common`**, tree-shakeable imports, and **dynamic import** for rare code paths when the codebase already does so for similar features.
+2. **Avoid** importing whole libraries when only a small API is needed.
+3. After significant dependency adds, run a **production build** locally and compare size if unsure:
+
+ ```bash
+ yarn build:packages
+ yarn build
+ ```
+
+4. Open a PR early if the change is large — CI **size-limit** will report regressions.
+
+## Library vs app
+
+- **`packages/excalidraw`** — stricter; affects all embedders.
+- **`excalidraw-app`** — still matters for first load; be deliberate with new chunks.
+
+## See also
+
+`packages/excalidraw/package.json` (size-limit scripts if present), `.github/workflows/size-limit.yml`.
diff --git a/.cursor/commands/update-state-flow.md b/.cursor/commands/update-state-flow.md
new file mode 100644
index 0000000..c4ce0b5
--- /dev/null
+++ b/.cursor/commands/update-state-flow.md
@@ -0,0 +1,110 @@
+# Update editor state (actions & related touchpoints)
+
+Use this flow when adding or changing **how the Excalidraw editor state is updated** (elements, `AppState`, files, undo/redo, shortcuts, or reactive UI). Follow branches that apply; skip the rest.
+
+## 0. Classify the change
+
+| Need | Primary path |
+|------|----------------|
+| User-triggered change (menu, shortcut, toolbar, command palette) | **Action** + `ActionManager` |
+| New/changed field on a **drawing element** | **`@excalidraw/element`** types + factories + **`restore`** + often renderer/export |
+| New/changed **editor chrome** state (tool, zoom, selection flags, …) | **`AppState`** / `types.ts` + **`appState.ts`** defaults + readers (hooks / UI) |
+| Shared **reactive** UI state (panels, library, collab handles) | **Jotai** via `editor-jotai.ts` or `excalidraw-app/app-jotai.ts` |
+| Behavior during **pointer / drag / tool** lifecycle | **`App.tsx`** handlers (keep small; prefer actions for pure state transforms) |
+| **Binary attachments** (images, …) | `ActionResult.files`, `replaceFiles`; align with existing file APIs |
+
+State is **not** a single Redux store: it combines **`AppState` + elements**, **Jotai** atoms, **`Scene`**, and **History** deltas.
+
+---
+
+## 1. New or changed **action** (most common)
+
+`ActionResult` shape (see `packages/excalidraw/actions/types.ts`):
+
+- `elements` — new element array or omit/null per existing patterns
+- `appState` — partial `AppState` updates
+- `files` / `replaceFiles` — when binary files change
+- `captureUpdate` — **required**; controls **undo/redo** (`CaptureUpdateAction` from `@excalidraw/element`). Match a similar existing action.
+- Return **`false`** to no-op.
+
+**Steps:**
+
+1. Add `packages/excalidraw/actions/actionYourFeature.ts` (or `.tsx`) using `register()` like sibling `action*.ts` files.
+2. Implement `perform(elements, appState, formData, app)` → `ActionResult` or `false`.
+3. Optional: `keyTest`, `predicate`, `PanelComponent`, `trackEvent`, icons — mirror nearby actions.
+4. **Export** the action from `packages/excalidraw/actions/index.ts` (required for registration).
+5. If it needs a **shortcut**, update `packages/excalidraw/actions/shortcuts.ts` and/or wire UI in the relevant component (`Actions.tsx`, menus, command palette).
+6. **Tests:** `packages/excalidraw/actions/actionYourFeature.test.tsx` using `tests/test-utils`, `API`, `Keyboard`, etc.
+7. Run `yarn test:typecheck`, `yarn test:update` if snapshots change, `yarn fix`.
+
+**Imports:** `import type` for types; inside `packages/excalidraw` do not import from the package barrel `index.tsx`. Jotai only from `editor-jotai.ts` / `app-jotai.ts`.
+
+---
+
+## 2. New **element** field or behavior
+
+1. **`packages/element/src/types.ts`** — extend the right element / base type.
+2. **`packages/element/src/newElement.ts`** (and any focused factories) — defaults for new elements.
+3. **`packages/excalidraw/data/restore.ts`** — migrations / defaults for loaded or pasted data.
+4. If it affects appearance or export: **`packages/excalidraw/renderer/staticScene.ts`**, **`staticSvgScene.ts`**, and **`scene/export.ts`** as needed.
+5. **Immutability:** use **`mutateElement`** (and version discipline); never mutate element objects in place.
+6. **Collaboration:** reconcile uses **version**; test concurrent edits if z-order or structure changes (`packages/excalidraw/data/reconcile.ts`).
+
+---
+
+## 3. New **`AppState` / UI editor state** field
+
+1. **`packages/excalidraw/types.ts`** — `AppState` / `UIAppState` (or relevant subsection).
+2. **`packages/excalidraw/appState.ts`** — default value and any reset paths.
+3. Update **readers** (hooks like `useAppStateValue`, components, `App.tsx`) and any **serialization** if the field must persist (JSON / localStorage / share link) — follow how similar fields are saved in `data/json.ts` and app `LocalData` if applicable.
+
+---
+
+## 4. **Jotai** (reactive UI, not the canvas element array)
+
+- Library/editor scope: **`packages/excalidraw/editor-jotai.ts`**.
+- Hosted app scope: **`excalidraw-app/app-jotai.ts`**.
+- Do not import `jotai` directly in feature code.
+
+Use atoms for UI that many components subscribe to; keep **elements** and core **`AppState`** updates flowing through **actions** when the change must be **undoable** and consistent with the rest of the editor.
+
+---
+
+## 5. **Pointer / tool** wiring
+
+If the change starts from canvas interaction:
+
+- **`packages/excalidraw/components/App.tsx`** — `handleCanvasPointerDown` / `Move` / `Up` (and related). Prefer delegating the final state commit to an **action** or shared helper so `perform` stays testable.
+- New **tool type**: `ActiveTool` in `types.ts`, toolbar UI, shortcuts, and `App.tsx` tool branches (see `dev-docs/docs/a-docs/08-adding-features.md`).
+
+---
+
+## 6. **History (undo/redo)**
+
+- Incorrect **`captureUpdate`** breaks undo/redo or records noise.
+- History uses **deltas**; avoid assumptions that break after **collab reconcile**.
+
+---
+
+## 7. **Persistence, export, collab**
+
+- **JSON / clipboard / share:** `packages/excalidraw/data/json.ts`, `blob.ts`, app `data/` for backend.
+- **Collaboration:** `excalidraw-app/collab/` + **`reconcileElements`**; encryption keys stay in URL **hash**.
+
+---
+
+## 8. Checklist before PR
+
+- [ ] Types and defaults updated (`types.ts`, `appState.ts`, `newElement.ts`, `restore.ts` as needed)
+- [ ] Action exported in `actions/index.ts` with correct `captureUpdate`
+- [ ] Shortcuts / UI entry points wired
+- [ ] Renderer / export updated if visual or SVG/PNG output changes
+- [ ] Tests added or updated; `yarn test:update` if snapshots intentional
+- [ ] `yarn test:typecheck` and `yarn fix`
+- [ ] User-visible strings use **`locales/`** (library UI)
+
+## Reference docs & rules
+
+- `dev-docs/docs/a-docs/08-adding-features.md`
+- `dev-docs/docs/a-docs/04-key-business-flows.md`
+- `.cursor/rules/excalidraw-actions-state.mdc`, `excalidraw-element-model.mdc`, `excalidraw-data-collab.mdc`, `excalidraw-typescript-imports.mdc`
diff --git a/.cursor/rules/excalidraw-actions-state.mdc b/.cursor/rules/excalidraw-actions-state.mdc
new file mode 100644
index 0000000..159a5dc
--- /dev/null
+++ b/.cursor/rules/excalidraw-actions-state.mdc
@@ -0,0 +1,21 @@
+---
+description: ActionManager, ActionResult, captureUpdate, and registration
+globs:
+ - packages/excalidraw/actions/**/*.ts
+ - packages/excalidraw/actions/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-actions-state APPLIED ⚡
+
+# Actions (`packages/excalidraw/actions`)
+
+- Prefer **actions** for user-triggered, undoable editor changes. Wire keyboard shortcuts via **`keyTest`** and register with **`register()`** like existing `action*.ts(x)` files.
+- **`perform`** should return an **`ActionResult`**: `{ elements, appState, captureUpdate, … }`, or **`false`** to no-op.
+- Set **`captureUpdate`** correctly for **undo/redo** (e.g. `CaptureUpdateAction.IMMEDIATELY` when the change should be recorded — match nearby actions).
+- After adding an action file, **export** it from **`actions/index.ts`** so it is registered with **`ActionManager`**.
+- **`App.tsx`** holds pointer/tool orchestration; if an action only needs state transformation, keep logic in the action and keep `App.tsx` changes minimal.
diff --git a/.cursor/rules/excalidraw-app-integration.mdc b/.cursor/rules/excalidraw-app-integration.mdc
new file mode 100644
index 0000000..d9d989d
--- /dev/null
+++ b/.cursor/rules/excalidraw-app-integration.mdc
@@ -0,0 +1,20 @@
+---
+description: excalidraw-app collab, persistence, and boundaries with the library
+globs:
+ - excalidraw-app/**/*.ts
+ - excalidraw-app/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-app-integration APPLIED 🌐
+
+# App shell (`excalidraw-app`)
+
+- **`collab/`:** `Collab.tsx` orchestrates sessions; **`Portal.tsx`** wraps Socket.io. On remote updates, run **`reconcileElements`** (from `@excalidraw/excalidraw` data layer) before applying scene updates.
+- **`data/`:** Local persistence (`LocalData`, IndexedDB), Firebase, file manager, tab sync — app-specific. Reuse **`packages/excalidraw/data`** for encode/decode, restore, and reconcile.
+- **Env:** Vite exposes `VITE_APP_*` variables; don’t hardcode production URLs in new code without checking existing `.env.*` patterns.
+- **Jotai:** app-level atoms live in **`excalidraw-app/app-jotai.ts`**, not in raw `jotai`.
diff --git a/.cursor/rules/excalidraw-canvas-components.mdc b/.cursor/rules/excalidraw-canvas-components.mdc
new file mode 100644
index 0000000..9fe836c
--- /dev/null
+++ b/.cursor/rules/excalidraw-canvas-components.mdc
@@ -0,0 +1,17 @@
+---
+description: Static, interactive, and new-element canvas components
+globs: packages/excalidraw/components/canvases/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-canvas-components APPLIED 🖼️
+
+# Canvas components (`components/canvases`)
+
+- Keep the **multi-canvas** split: static layer for committed geometry, interactive layer for selection/handles/cursors, new-element layer for in-progress drawing.
+- Avoid wiring **pointer move** handlers that unnecessarily bump static-canvas props or force full static re-renders; prefer updating the interactive or preview layer when possible.
+- Coordinate with **`packages/excalidraw/renderer/`** — canvas components should orchestrate, not duplicate rendering logic that belongs in `renderStaticScene` / `renderInteractiveScene`.
diff --git a/.cursor/rules/excalidraw-data-collab.mdc b/.cursor/rules/excalidraw-data-collab.mdc
new file mode 100644
index 0000000..8344c38
--- /dev/null
+++ b/.cursor/rules/excalidraw-data-collab.mdc
@@ -0,0 +1,20 @@
+---
+description: reconcile, restore, encryption, and shared serialization
+globs:
+ - packages/excalidraw/data/**/*.ts
+ - packages/excalidraw/data/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-data-collab APPLIED 🔐
+
+# Data layer (`packages/excalidraw/data`)
+
+- **`reconcileElements`:** merging uses **element version**; be careful with **ties** and **deleted** elements (`isDeleted`). Do not remove deleted elements casually — reconciliation and tombstones depend on them.
+- **`restore.ts`:** add **defaults and migrations** when the element or appState schema changes so older JSON/clipboard data still works.
+- **Encryption / share links:** sensitive material belongs in the URL **hash** (fragment), which is not sent to the server. Never log full share URLs or move keys into query strings.
+- Keep **serialization** concerns here; **`excalidraw-app`** should orchestrate Firebase/network, not reimplement core JSON/binary formats.
diff --git a/.cursor/rules/excalidraw-element-model.mdc b/.cursor/rules/excalidraw-element-model.mdc
new file mode 100644
index 0000000..5c5fdf4
--- /dev/null
+++ b/.cursor/rules/excalidraw-element-model.mdc
@@ -0,0 +1,28 @@
+---
+description: Immutable elements, versions, Scene ordering, and schema changes
+globs:
+ - packages/element/**/*.ts
+ - packages/element/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-element-model APPLIED 🧱
+
+# Element model (`@excalidraw/element`)
+
+- Treat elements as **immutable**. Do not assign to element fields directly; use **`mutateElement`** (and follow project rules for **`version`** / **`versionNonce`** so render, history, and collab stay consistent).
+- **Z-order** uses fractional **`index`** strings. Prefer **`Scene`** APIs for reordering; avoid inventing index strings by hand unless you follow the same invariants as `Scene.ts`.
+- **New element properties:** update types in **`types.ts`**, defaults in **`newElement.ts`** (or equivalent factories), and **migration / defaults** in **`packages/excalidraw/data/restore.ts`** so old files still load.
+- **Collaboration:** reconciliation uses **version**; bugs in versioning here cause dropped updates or bad merges.
+
+```typescript
+// ❌ BAD
+element.x = 100;
+
+// ✅ GOOD
+mutateElement(element, { x: 100 });
+```
diff --git a/.cursor/rules/excalidraw-monorepo.mdc b/.cursor/rules/excalidraw-monorepo.mdc
new file mode 100644
index 0000000..bdc9d97
--- /dev/null
+++ b/.cursor/rules/excalidraw-monorepo.mdc
@@ -0,0 +1,21 @@
+---
+description: Yarn workspaces, package layers, and where to read architecture docs
+alwaysApply: true
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-monorepo APPLIED 🏗️
+
+# Excalidraw monorepo
+
+- Use **Yarn Classic (v1)** workspaces. Do not switch to npm or Yarn Berry unless the team explicitly decides to.
+- **Dependency direction** (only depend downward):
+ - `packages/common` and `packages/math` — no internal `@excalidraw/*` deps.
+ - `packages/element` → `common`, `math`.
+ - `packages/excalidraw` → `element`, `common`, `math`, `utils`.
+ - `excalidraw-app` → `@excalidraw/excalidraw` plus app-only services (Firebase, Socket.io, etc.).
+- New utilities belong in the **lowest** package that can own them: math → `math`, element ops → `element`, shared non-geometry → `common`, export/bounds for consumers → `utils`.
+- For architecture, flows, and pitfalls, prefer **`dev-docs/docs/a-docs/`** (onboarding guide) before guessing.
diff --git a/.cursor/rules/excalidraw-rendering.mdc b/.cursor/rules/excalidraw-rendering.mdc
new file mode 100644
index 0000000..bb036e7
--- /dev/null
+++ b/.cursor/rules/excalidraw-rendering.mdc
@@ -0,0 +1,20 @@
+---
+description: Multi-canvas layers, static vs interactive redraws, Rough.js caching
+globs:
+ - packages/excalidraw/renderer/**/*.ts
+ - packages/excalidraw/renderer/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-rendering APPLIED 🎨
+
+# Rendering and canvases
+
+- Architecture splits **static** (committed elements), **interactive** (handles, selection, remote cursors), and **new element** preview. Do not force **static** scene redraws on every pointer move unless unavoidable.
+- **`renderStaticScene`:** Rough.js drawables are **cached** per element/version; avoid recreating drawable objects every frame when only the interactive overlay should change.
+- SVG export paths (`staticSvgScene`, etc.) should stay consistent with canvas rendering for the same elements.
+- Before large renderer changes, consider **performance** with many elements (hundreds+); keep hot loops allocation-light.
diff --git a/.cursor/rules/excalidraw-testing.mdc b/.cursor/rules/excalidraw-testing.mdc
new file mode 100644
index 0000000..c3538f8
--- /dev/null
+++ b/.cursor/rules/excalidraw-testing.mdc
@@ -0,0 +1,23 @@
+---
+description: Vitest, test-utils, snapshots, path aliases, and setupTests
+globs:
+ - "**/*.test.ts"
+ - "**/*.test.tsx"
+ - setupTests.ts
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+
+`RULE excalidraw-testing APPLIED 🧪`
+
+# Testing
+
+- Framework: **Vitest** + **jsdom** + **@testing-library/react**. Global mocks live in root **`setupTests.ts`** (canvas, IndexedDB, fonts, etc.).
+- For editor tests, use **`packages/excalidraw/tests/test-utils`** (`render`, `unmountComponent`) and helpers (**`API`**, **`Keyboard`**, etc.) instead of ad-hoc mounting.
+- Path aliases **`@excalidraw/*`** must match **`vitest.config.mts`** and **`tsconfig.json`** — use the same import style as production code.
+- When snapshots change intentionally, run **`yarn test:update`** and review diffs before committing.
+- Prefer **`*.test.ts`** / **`*.test.tsx`** naming; colocate small unit tests or use package **`tests/`** directories per existing layout.
+- In **`.tsx`** tests: use **`unmountComponent()`** in `afterEach` when using the shared `render` helper.
diff --git a/.cursor/rules/excalidraw-typescript-imports.mdc b/.cursor/rules/excalidraw-typescript-imports.mdc
new file mode 100644
index 0000000..b3ab239
--- /dev/null
+++ b/.cursor/rules/excalidraw-typescript-imports.mdc
@@ -0,0 +1,31 @@
+---
+description: TypeScript imports, Jotai entry points, and cross-package paths
+globs:
+ - "**/*.ts"
+ - "**/*.tsx"
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
+
+RULE excalidraw-typescript-imports APPLIED 📎
+
+# TypeScript and imports
+
+- Use `import type { … }` for **type-only** imports (ESLint enforces this).
+- **Inside `packages/excalidraw`**, do not import from the package barrel **`index.tsx`**. Import the **specific module** path instead.
+- **Jotai:** import atoms/helpers from `packages/excalidraw/editor-jotai.ts` or `excalidraw-app/app-jotai.ts`. Do not import from the `jotai` package directly in app/editor code.
+- Cross-package: use **`@excalidraw/common`**, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils` — not long relative paths into another package’s `src`.
+- Avoid new **browser-only** APIs in code paths used by **SSR** (e.g. Next.js example) unless there is a safe guard or fallback.
+
+```typescript
+// ❌ BAD (inside packages/excalidraw)
+import { foo } from "../index";
+import { atom } from "jotai";
+
+// ✅ GOOD
+import { foo } from "../components/some-module";
+import { atom } from "../editor-jotai";
+```
diff --git a/.cursor/rules/excalidraw-ui-app.mdc b/.cursor/rules/excalidraw-ui-app.mdc
new file mode 100644
index 0000000..3a23fbd
--- /dev/null
+++ b/.cursor/rules/excalidraw-ui-app.mdc
@@ -0,0 +1,17 @@
+---
+description: excalidraw.com-only UI and styling
+globs: excalidraw-app/components/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+
+`RULE excalidraw-ui-app APPLIED 📱`
+
+# App UI (`excalidraw-app/components`)
+
+- These components are **not** part of **`@excalidraw/excalidraw`**; they wrap or extend the editor for the hosted app (share, welcome, account, etc.).
+- Prefer reusing **`packages/excalidraw`** APIs and props rather than duplicating editor internals.
+- Colocate **`.scss`** with components; follow patterns from sibling files in this folder.
diff --git a/.cursor/rules/excalidraw-ui.mdc b/.cursor/rules/excalidraw-ui.mdc
new file mode 100644
index 0000000..f304b20
--- /dev/null
+++ b/.cursor/rules/excalidraw-ui.mdc
@@ -0,0 +1,18 @@
+---
+description: React UI, SCSS, i18n, and library vs app component placement
+globs: packages/excalidraw/components/**/*.tsx
+alwaysApply: false
+---
+
+## Important (debug)
+
+For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+
+`RULE excalidraw-ui APPLIED 🎯`
+
+# UI components (`packages/excalidraw/components`)
+
+- Colocate **`.scss`** (or SCSS modules) with the component; follow existing spacing and BEM-like naming in sibling files.
+- **User-visible strings** go through **`packages/excalidraw/locales/`** (i18n); do not hardcode English for labels, menus, or toasts.
+- Use **Jotai** via **`editor-jotai.ts`** and **`useAppStateValue`** (and related hooks) for reactive editor state — not raw `jotai`.
+- **`App.tsx`** is high-risk: pointer, keyboard, tools, and canvas behavior intersect here; keep changes focused and test multiple tools and inputs when touching it.
diff --git a/dev-docs/docs/a-docs/01-architecture-overview.md b/dev-docs/docs/a-docs/01-architecture-overview.md
new file mode 100644
index 0000000..4bffafa
--- /dev/null
+++ b/dev-docs/docs/a-docs/01-architecture-overview.md
@@ -0,0 +1,210 @@
+# Architecture Overview
+
+## High-Level Summary
+
+Excalidraw is an open-source virtual whiteboard built with **React** and **TypeScript**. The codebase is organized as a **Yarn workspaces monorepo** with a clear separation between the **reusable library** (published to npm as `@excalidraw/excalidraw`) and the **web application** (excalidraw.com).
+
+### Tech Stack
+
+| Layer | Technology |
+|-------|-----------|
+| UI Framework | React 19 + TypeScript 5.9 |
+| State Management | Jotai (atomic state) + imperative `AppState` |
+| Build System | Vite (app), esbuild (packages) |
+| Testing | Vitest + jsdom + @testing-library/react |
+| Canvas Rendering | HTML Canvas 2D + [Rough.js](https://roughjs.com/) (hand-drawn style) |
+| Real-time Collaboration | Socket.io (WebSocket) |
+| Backend Storage | Firebase Firestore + Firebase Storage |
+| Styling | SCSS modules |
+| Package Manager | Yarn (workspaces) |
+| CI/CD | GitHub Actions |
+| Containerization | Docker + nginx |
+
+---
+
+## Monorepo Architecture
+
+```mermaid
+graph TD
+ subgraph "Web Application"
+ APP["excalidraw-app/"]
+ end
+
+ subgraph "Core Packages"
+ EXC["@excalidraw/excalidraw (Main React Component)"]
+ ELE["@excalidraw/element (Element Logic)"]
+ COM["@excalidraw/common (Shared Utilities)"]
+ MAT["@excalidraw/math (Geometry)"]
+ UTL["@excalidraw/utils (Export & Helpers)"]
+ end
+
+ subgraph "External Services"
+ FB["Firebase (Firestore + Storage)"]
+ WS["WebSocket Server (Socket.io)"]
+ end
+
+ APP --> EXC
+ APP --> FB
+ APP --> WS
+ EXC --> ELE
+ EXC --> COM
+ EXC --> MAT
+ EXC --> UTL
+ ELE --> COM
+ ELE --> MAT
+```
+
+---
+
+## Package Responsibilities
+
+### `packages/excalidraw/` — Main Editor Component
+
+The heart of the project. Published to npm as `@excalidraw/excalidraw`.
+
+- **`components/App.tsx`** — Main editor: canvas rendering, pointer/keyboard events, tool management, state orchestration
+- **`actions/`** — Atomic operations (copy, paste, delete, style changes, zoom, etc.)
+- **`renderer/`** — Canvas rendering pipeline (static scenes, interactive overlays, SVG export)
+- **`scene/`** — Scene export (PNG, SVG, clipboard), render configurations
+- **`data/`** — Serialization, deserialization, encryption, compression, reconciliation
+- **`components/`** — All UI: menus, sidebars, color picker, font picker, library, command palette
+- **`hooks/`** — Custom React hooks
+- **`fonts/`** — Font loading and subsetting (Virgil, Excalifont, Nunito, etc.)
+- **`locales/`** — i18n translations (managed via Crowdin)
+
+### `packages/element/` — Element System
+
+All logic for manipulating drawing elements (shapes, text, arrows, images, frames).
+
+- **`types.ts`** — Element type definitions (`ExcalidrawRectangleElement`, `ExcalidrawArrowElement`, etc.)
+- **`Scene.ts`** — Scene state management with fractional indexing
+- Modules: binding, bounds, collision detection, crop, delta, duplicate, groups, transform, z-index
+
+### `packages/common/` — Shared Utilities
+
+Cross-package utilities used by all other packages.
+
+- Constants (`APP_NAME`, `CURSOR_TYPE`, `POINTER_BUTTON`, `THEME`, `MIME_TYPES`)
+- Binary heap, color utilities, key constants, event emitter, queue
+- Font metadata, URL helpers, random ID generation
+
+### `packages/math/` — Geometry Primitives
+
+Pure mathematical operations with no UI dependencies.
+
+- Points, vectors, angles, segments, lines
+- Curves, ellipses, polygons, rectangles, triangles
+
+### `packages/utils/` — Export & Bounds Utilities
+
+Higher-level utilities for consumers of the library.
+
+- `exportToCanvas`, `exportToSvg`, `exportToBlob`
+- `withinBounds`, shape utilities, bounding box calculations
+
+### `excalidraw-app/` — Web Application
+
+The full-featured application at excalidraw.com, built on top of `@excalidraw/excalidraw`.
+
+- **`App.tsx`** — App shell: scene initialization, URL routing, localStorage, collaboration wiring
+- **`collab/`** — Real-time collaboration (Socket.io + Firebase)
+- **`data/`** — Firebase integration, local persistence, file management, tab sync
+- **`components/`** — App-specific UI (menus, share dialog, welcome screen, AI features)
+
+---
+
+## Dependency Graph
+
+```
+@excalidraw/excalidraw
+├── @excalidraw/element
+│ ├── @excalidraw/common
+│ └── @excalidraw/math
+├── @excalidraw/common
+├── @excalidraw/math
+└── @excalidraw/utils
+
+excalidraw-app
+└── @excalidraw/excalidraw (+ Firebase, Socket.io, Sentry)
+```
+
+Key rule: packages only depend **downward**. `common` and `math` have no internal dependencies. `element` depends on `common` and `math`. `excalidraw` depends on everything.
+
+---
+
+## State Management Architecture
+
+```mermaid
+graph LR
+ subgraph "Jotai Atoms"
+ EA["Editor Atoms (editor-jotai.ts)"]
+ AA["App Atoms (app-jotai.ts)"]
+ end
+
+ subgraph "AppState"
+ AS["appState (theme, tool, zoom, selection...)"]
+ end
+
+ subgraph "Elements"
+ EL["elements[] (shapes, text, arrows...)"]
+ end
+
+ subgraph "History"
+ H["History (undo/redo stacks)"]
+ end
+
+ EA --> AS
+ AA --> AS
+ AS --> EL
+ EL --> H
+```
+
+State is managed through two complementary systems:
+
+1. **Jotai atoms** — Fine-grained reactive state for UI components. The editor uses `EditorJotaiProvider` (scoped per editor instance) and the app adds `AppJotaiProvider`.
+2. **`AppState`** — A large flat object containing all editor state (current tool, theme, zoom, selection, collaborators, etc.). Defined in `packages/excalidraw/appState.ts`.
+3. **Elements array** — The source of truth for all drawing content. Each element is immutable; changes create new objects with incremented `version`.
+
+---
+
+## Rendering Architecture
+
+```mermaid
+graph TD
+ EL["Elements Array"] --> SC["StaticCanvas (committed shapes)"]
+ EL --> IC["InteractiveCanvas (selection, handles, cursors)"]
+ EL --> NC["NewElementCanvas (shape being drawn)"]
+ SC --> RS["renderStaticScene() (Rough.js)"]
+ IC --> RI["renderInteractiveScene()"]
+ NC --> RN["renderNewElement()"]
+```
+
+The editor uses a **multi-canvas architecture**:
+
+- **StaticCanvas** — Renders all committed elements using Rough.js for the hand-drawn aesthetic
+- **InteractiveCanvas** — Overlays selection boxes, resize handles, remote cursors
+- **NewElementCanvas** — Renders the element currently being drawn (before commit)
+
+This separation allows the static layer to be cached and only re-rendered when elements change, while the interactive layer updates on every pointer move.
+
+---
+
+## Data Flow: User Action → State Update
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant App as App.tsx
+ participant AM as ActionManager
+ participant Action
+ participant State as AppState + Elements
+
+ User->>App: Keyboard/pointer event
+ App->>AM: handleKeyDown() / handlePointerDown()
+ AM->>Action: action.perform(elements, appState)
+ Action-->>AM: ActionResult {elements, appState, captureUpdate}
+ AM->>State: updater(ActionResult)
+ State-->>App: Re-render
+```
+
+Every user interaction flows through the **Action system**. Actions are pure functions that receive current state and return new state, making them predictable and testable.
diff --git a/dev-docs/docs/a-docs/02-local-setup.md b/dev-docs/docs/a-docs/02-local-setup.md
new file mode 100644
index 0000000..c2f17a6
--- /dev/null
+++ b/dev-docs/docs/a-docs/02-local-setup.md
@@ -0,0 +1,183 @@
+# Local Setup Guide
+
+## Prerequisites
+
+| Tool | Version | How to Verify |
+|------|---------|---------------|
+| **Node.js** | >= 18.0.0 | `node --version` |
+| **Yarn** | 1.x (Classic) | `yarn --version` |
+| **Git** | Any recent | `git --version` |
+
+> The project uses Yarn Classic (v1) workspaces. Do **not** use npm or Yarn Berry.
+
+## Step 1: Clone the Repository
+
+```bash
+git clone https://github.com/excalidraw/excalidraw.git
+cd excalidraw
+```
+
+## Step 2: Install Dependencies
+
+```bash
+yarn
+```
+
+This installs dependencies for all workspaces: `excalidraw-app/`, `packages/*`, and `examples/*`.
+
+If you run into issues, try a clean install:
+
+```bash
+yarn clean-install
+```
+
+This removes all `node_modules` directories and reinstalls everything.
+
+## Step 3: Start the Development Server
+
+```bash
+yarn start
+```
+
+This starts the Vite dev server for `excalidraw-app/` on **http://localhost:3001** (configured via `VITE_APP_PORT` in `.env.development`).
+
+The app hot-reloads on changes to both `excalidraw-app/` and `packages/` source files thanks to the path aliases in the Vite config.
+
+## Step 4: Verify It Works
+
+1. Open **http://localhost:3001** in your browser
+2. You should see the Excalidraw whiteboard editor
+3. Try drawing a rectangle — if the hand-drawn style renders, fonts and canvas are working
+
+---
+
+## Environment Variables
+
+Environment variables are defined in two files at the project root:
+
+| File | Purpose |
+|------|---------|
+| `.env.development` | Used during `yarn start` |
+| `.env.production` | Used during `yarn build` |
+
+All variables are prefixed with `VITE_APP_` (Vite convention).
+
+### Key Variables
+
+| Variable | Purpose | Default (dev) |
+|----------|---------|---------------|
+| `VITE_APP_BACKEND_V2_GET_URL` | Backend for loading shared drawings | `https://json.excalidraw.com/api/v2/` |
+| `VITE_APP_BACKEND_V2_POST_URL` | Backend for saving shared drawings | `https://json.excalidraw.com/api/v2/post/` |
+| `VITE_APP_WS_SERVER_URL` | WebSocket server for collaboration | `https://oss-collab.excalidraw.com` |
+| `VITE_APP_FIREBASE_CONFIG` | Firebase config (JSON string) | Pre-configured for dev |
+| `VITE_APP_PORT` | Dev server port | `3001` |
+| `VITE_APP_DISABLE_TRACKING` | Disable analytics | (not set in dev) |
+| `VITE_APP_ENABLE_TRACKING` | Enable analytics | (not set in dev) |
+
+> For local-only development, the defaults work out of the box. You do **not** need to configure Firebase or WebSocket for basic editor work.
+
+---
+
+## Building the Project
+
+### Build the web app
+
+```bash
+yarn build
+```
+
+Output goes to `excalidraw-app/build/`. You can serve it:
+
+```bash
+yarn start:production
+```
+
+This builds and serves on **http://localhost:5001**.
+
+### Build only the packages (for library development)
+
+```bash
+yarn build:packages
+```
+
+This builds all packages in order: `common` → `math` → `element` → `excalidraw`.
+
+### Build individual packages
+
+```bash
+yarn build:common
+yarn build:math
+yarn build:element
+yarn build:excalidraw
+```
+
+---
+
+## Running with Docker
+
+```bash
+docker-compose up --build -d
+```
+
+This builds the app in a Node 18 container and serves it via nginx on **http://localhost:3000**.
+
+---
+
+## Running Examples
+
+### Browser script example
+
+```bash
+yarn start:example
+```
+
+Runs the Vite-based example at `examples/with-script-in-browser/` on port 5002.
+
+### Next.js example
+
+```bash
+cd examples/with-nextjs
+yarn dev
+```
+
+Runs on port 3005.
+
+---
+
+## Common Issues and Fixes
+
+### `canvas` module errors in tests
+
+The project uses `vitest-canvas-mock` to mock the Canvas API. If you see canvas-related errors, ensure `setupTests.ts` is being picked up (configured in `vitest.config.mts`).
+
+### Font loading failures
+
+Fonts are loaded from local files during development. The `setupTests.ts` file mocks font fetches. If fonts don't render in the browser, check that `public/` assets are being served correctly.
+
+### Port already in use
+
+If port 3001 is occupied, either kill the existing process or change `VITE_APP_PORT` in `.env.development`.
+
+### `yarn start` fails with module resolution errors
+
+Run `yarn` again to ensure all workspace symlinks are correct. If that doesn't help:
+
+```bash
+yarn clean-install
+```
+
+### TypeScript errors after pulling new changes
+
+```bash
+yarn test:typecheck
+```
+
+This runs `tsc` across the entire project and will show you what needs fixing.
+
+### Snapshot test failures after pulling
+
+```bash
+yarn test:update
+```
+
+This updates all Vitest snapshots.
diff --git a/dev-docs/docs/a-docs/03-codebase-navigation.md b/dev-docs/docs/a-docs/03-codebase-navigation.md
new file mode 100644
index 0000000..f47d443
--- /dev/null
+++ b/dev-docs/docs/a-docs/03-codebase-navigation.md
@@ -0,0 +1,176 @@
+# Codebase Navigation Guide
+
+## Top-Level Structure
+
+```
+excalidraw/
+├── excalidraw-app/ # Web application (excalidraw.com)
+├── packages/
+│ ├── excalidraw/ # Main React editor component (@excalidraw/excalidraw)
+│ ├── element/ # Element types and manipulation (@excalidraw/element)
+│ ├── common/ # Shared utilities (@excalidraw/common)
+│ ├── math/ # Geometry primitives (@excalidraw/math)
+│ └── utils/ # Export and bounds helpers (@excalidraw/utils)
+├── examples/
+│ ├── with-script-in-browser/ # Vite integration example
+│ └── with-nextjs/ # Next.js integration example
+├── dev-docs/ # Docusaurus documentation site
+├── firebase-project/ # Firebase configuration
+├── public/ # Static assets (favicon, service worker)
+├── scripts/ # Build and release scripts
+├── .github/ # CI/CD workflows
+├── .env.development # Dev environment variables
+├── .env.production # Prod environment variables
+├── vitest.config.mts # Test configuration
+├── tsconfig.json # Root TypeScript config with path aliases
+└── package.json # Root workspace config
+```
+
+---
+
+## Where to Find Things
+
+### Business Logic
+
+| What | Where |
+|------|-------|
+| Element types and definitions | `packages/element/src/types.ts` |
+| Element creation | `packages/element/src/newElement.ts` |
+| Element mutation | `packages/element/src/mutateElement.ts` |
+| Element transforms (resize, rotate) | `packages/element/src/transform.ts` |
+| Arrow binding logic | `packages/element/src/binding.ts` |
+| Text wrapping and sizing | `packages/element/src/textElement.ts` |
+| Grouping | `packages/element/src/groups.ts` |
+| Frame containment | `packages/element/src/frame.ts` |
+| Z-index ordering | `packages/element/src/zindex.ts` |
+| Collision detection | `packages/element/src/collision.ts` |
+| Scene state + fractional indexing | `packages/element/src/Scene.ts` |
+
+### User Actions
+
+| What | Where |
+|------|-------|
+| Action type definitions | `packages/excalidraw/actions/types.ts` |
+| Action manager (registration, dispatch) | `packages/excalidraw/actions/manager.tsx` |
+| All action implementations | `packages/excalidraw/actions/action*.ts(x)` |
+| Keyboard shortcuts | `packages/excalidraw/actions/shortcuts.ts` |
+
+Key actions: `actionDeleteSelected`, `actionDuplicateSelection`, `actionCopy`, `actionPaste`, `actionGroup`, `actionChangeStrokeColor`, `actionChangeFontSize`, `actionZoomIn`, `actionToggleTheme`, `actionSaveToActiveFile`, `actionLoadScene`.
+
+### Data Layer (Serialization, Persistence)
+
+| What | Where |
+|------|-------|
+| JSON serialization/deserialization | `packages/excalidraw/data/json.ts` |
+| Binary file loading (PNG/SVG/JSON) | `packages/excalidraw/data/blob.ts` |
+| Data compression/encoding | `packages/excalidraw/data/encode.ts` |
+| Encryption for collaboration | `packages/excalidraw/data/encryption.ts` |
+| Element reconciliation (collab merge) | `packages/excalidraw/data/reconcile.ts` |
+| State restoration and migration | `packages/excalidraw/data/restore.ts` |
+| Library persistence | `packages/excalidraw/data/library.ts` |
+| PNG metadata embedding | `packages/excalidraw/data/image.ts` |
+| File System Access API | `packages/excalidraw/data/filesystem.ts` |
+| Exported data types | `packages/excalidraw/data/types.ts` |
+
+### App-Level Data (excalidraw.com specific)
+
+| What | Where |
+|------|-------|
+| Firebase integration | `excalidraw-app/data/firebase.ts` |
+| Local storage persistence | `excalidraw-app/data/LocalData.ts` |
+| File upload/download management | `excalidraw-app/data/FileManager.ts` |
+| Cross-tab synchronization | `excalidraw-app/data/tabSync.ts` |
+| Text-to-diagram storage | `excalidraw-app/data/TTDStorage.ts` |
+
+### UI Components
+
+| What | Where |
+|------|-------|
+| Main editor (canvas, events, state) | `packages/excalidraw/components/App.tsx` |
+| Layer controls | `packages/excalidraw/components/LayerUI.tsx` |
+| Static canvas rendering | `packages/excalidraw/components/canvases/StaticCanvas.tsx` |
+| Interactive canvas (selection, handles) | `packages/excalidraw/components/canvases/InteractiveCanvas.tsx` |
+| Main menu | `packages/excalidraw/components/main-menu/MainMenu.tsx` |
+| Sidebar | `packages/excalidraw/components/Sidebar/Sidebar.tsx` |
+| Color picker | `packages/excalidraw/components/ColorPicker/` |
+| Font picker | `packages/excalidraw/components/FontPicker/` |
+| Library panel | `packages/excalidraw/components/LibraryMenu.tsx` |
+| Command palette | `packages/excalidraw/components/CommandPalette/CommandPalette.tsx` |
+| Welcome screen | `packages/excalidraw/components/welcome-screen/` |
+| Text-to-diagram dialog | `packages/excalidraw/components/TTDDialog/TTDDialog.tsx` |
+| Element stats inspector | `packages/excalidraw/components/Stats/` |
+
+### Rendering
+
+| What | Where |
+|------|-------|
+| Static scene rendering | `packages/excalidraw/renderer/staticScene.ts` |
+| Interactive scene rendering | `packages/excalidraw/renderer/interactiveScene.ts` |
+| SVG export rendering | `packages/excalidraw/renderer/staticSvgScene.ts` |
+| Scene export (PNG/SVG/clipboard) | `packages/excalidraw/scene/export.ts` |
+| Render config types | `packages/excalidraw/scene/types.ts` |
+
+### Collaboration
+
+| What | Where |
+|------|-------|
+| Collaboration manager | `excalidraw-app/collab/Collab.tsx` |
+| WebSocket portal | `excalidraw-app/collab/Portal.tsx` |
+| Collab error handling | `excalidraw-app/collab/CollabError.tsx` |
+| Live collaboration trigger UI | `packages/excalidraw/components/live-collaboration/` |
+
+### State Management
+
+| What | Where |
+|------|-------|
+| Default app state | `packages/excalidraw/appState.ts` |
+| Editor Jotai provider | `packages/excalidraw/editor-jotai.ts` |
+| App Jotai provider | `excalidraw-app/app-jotai.ts` |
+| App state hooks | `packages/excalidraw/hooks/useAppStateValue.ts` |
+| Core types (AppState, Props, etc.) | `packages/excalidraw/types.ts` |
+
+---
+
+## Naming Conventions
+
+### Files
+
+- **React components**: PascalCase (e.g., `LayerUI.tsx`, `ColorPicker.tsx`)
+- **Utilities and modules**: camelCase (e.g., `mutateElement.ts`, `reconcile.ts`)
+- **Action files**: `action` prefix (e.g., `actionDeleteSelected.ts`, `actionZoomToFit.ts`)
+- **Test files**: `.test.ts` or `.test.tsx` suffix, either colocated or in a `tests/` directory
+- **Style files**: `.scss` next to their component (e.g., `App.scss`, `LayerUI.scss`)
+- **Type-only files**: `types.ts` for shared type definitions
+
+### Code
+
+- **Element types**: string literals (`"rectangle"`, `"arrow"`, `"text"`, `"freedraw"`)
+- **Actions**: objects conforming to the `Action` interface with a unique `name` string
+- **Jotai atoms**: suffixed with `Atom` (e.g., `collabAPIAtom`, `libraryItemsAtom`)
+- **Constants**: UPPER_SNAKE_CASE in `@excalidraw/common` constants
+- **TypeScript**: Strict mode, `consistent-type-imports` enforced via ESLint
+
+### Imports
+
+The project uses **path aliases** for cross-package imports:
+
+```typescript
+import { something } from "@excalidraw/common";
+import { ExcalidrawElement } from "@excalidraw/element";
+import { pointFrom } from "@excalidraw/math";
+```
+
+These are resolved via `tsconfig.json` paths and duplicated in `vitest.config.mts` for tests.
+
+> **Important**: ESLint enforces that you do NOT import from barrel `index.ts` files within `@excalidraw/excalidraw` when you're inside that package. Import from the specific module instead.
+
+---
+
+## Key Entry Points
+
+| Entry Point | File | Description |
+|-------------|------|-------------|
+| npm package | `packages/excalidraw/index.tsx` | Exports `Excalidraw` component and all public APIs |
+| Web app | `excalidraw-app/index.tsx` → `App.tsx` | Bootstraps the full application |
+| Browser example | `examples/with-script-in-browser/index.tsx` | Minimal integration example |
+| Next.js example | `examples/with-nextjs/src/app/page.tsx` | SSR integration example |
diff --git a/dev-docs/docs/a-docs/04-key-business-flows.md b/dev-docs/docs/a-docs/04-key-business-flows.md
new file mode 100644
index 0000000..22fffbd
--- /dev/null
+++ b/dev-docs/docs/a-docs/04-key-business-flows.md
@@ -0,0 +1,330 @@
+# Key Business Flows
+
+This document covers the most important data flows in Excalidraw. Understanding these will help you reason about how user interactions translate to state changes, persistence, and collaboration.
+
+---
+
+## 1. Drawing an Element
+
+When a user clicks and drags on the canvas to draw a shape:
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant App as App.tsx
+ participant NE as newElement.ts
+ participant NC as NewElementCanvas
+ participant Scene as Scene
+ participant SC as StaticCanvas
+
+ User->>App: pointerDown (canvas)
+ App->>App: Determine active tool (e.g., "rectangle")
+ App->>NE: newElement({type, x, y, ...styleProps})
+ NE-->>App: ExcalidrawElement (initial)
+ App->>NC: Render in-progress element
+
+ loop While dragging
+ User->>App: pointerMove
+ App->>App: Update element dimensions (width, height)
+ App->>NC: Re-render preview
+ end
+
+ User->>App: pointerUp
+ App->>Scene: Commit element to elements array
+ Scene->>SC: Re-render static canvas
+ App->>App: captureUpdate → push to History
+```
+
+**Key files:**
+- `packages/excalidraw/components/App.tsx` — pointer event handlers
+- `packages/element/src/newElement.ts` — element factory functions
+- `packages/excalidraw/components/canvases/NewElementCanvas.tsx` — preview rendering
+- `packages/element/src/Scene.ts` — scene state management
+
+---
+
+## 2. App Bootstrap & Scene Loading
+
+When the app first loads:
+
+```mermaid
+sequenceDiagram
+ participant Browser
+ participant Index as index.tsx
+ participant App as ExcalidrawWrapper
+ participant Init as initializeScene()
+ participant LS as localStorage
+ participant Backend as Backend API
+ participant Collab as Collab
+
+ Browser->>Index: Load page
+ Index->>App: Render ExcalidrawApp
+ App->>Init: useEffect → initializeScene()
+
+ alt URL has #room=...
+ Init->>Collab: startCollaboration(roomLinkData)
+ else URL has #json=...
+ Init->>Backend: importFromBackend(id, key)
+ Backend-->>Init: Decrypted scene data
+ else URL has #url=...
+ Init->>Backend: fetch(url) → loadFromBlob()
+ else No URL params
+ Init->>LS: importFromLocalStorage()
+ LS-->>Init: Saved scene + appState
+ end
+
+ Init-->>App: initialData
+ App->>App: Render Excalidraw with initialData
+```
+
+**Key files:**
+- `excalidraw-app/App.tsx` — `initializeScene()` function (lines ~215–370)
+- `excalidraw-app/data/index.ts` — `importFromBackend()`
+- `excalidraw-app/data/localStorage.ts` — `importFromLocalStorage()`
+- `packages/excalidraw/data/blob.ts` — `loadFromBlob()`
+
+---
+
+## 3. Real-Time Collaboration
+
+When a user starts or joins a collaborative session:
+
+```mermaid
+sequenceDiagram
+ participant User as User A
+ participant App as App.tsx
+ participant Collab as Collab.tsx
+ participant Portal as Portal (Socket.io)
+ participant WS as WebSocket Server
+ participant Firebase as Firebase
+ participant UserB as User B
+
+ User->>App: Click "Live Collaboration"
+ App->>Collab: startCollaboration()
+ Collab->>Collab: generateRoomId() + encryptionKey
+ Collab->>Portal: open(socket, roomId)
+ Portal->>WS: Connect + join room
+ Collab->>Firebase: loadFromFirebase(roomId)
+ Firebase-->>Collab: Existing scene (if any)
+
+ User->>App: Draw / modify elements
+ App->>Collab: onChange → syncElements()
+ Collab->>Collab: reconcileElements(local, remote)
+ Collab->>Portal: broadcastScene(elements)
+ Portal->>WS: Emit encrypted scene data
+ WS->>UserB: Forward to other clients
+
+ UserB->>WS: Send their changes
+ WS->>Portal: Receive remote update
+ Portal->>Collab: handleRemoteSceneUpdate()
+ Collab->>Collab: reconcileElements()
+ Collab->>App: updateScene(reconciledElements)
+
+ par Background save
+ Collab->>Firebase: queueSaveToFirebase()
+ end
+```
+
+**Key concepts:**
+- **Room ID + encryption key** are encoded in the URL hash (never sent to server)
+- **Reconciliation** merges local and remote elements by comparing `version` numbers — the higher version wins
+- **Cursor sync** is a separate channel: `broadcastMouseLocation()` sends pointer position + username
+- **Firebase** acts as durable storage; Socket.io is for real-time relay only
+
+**Key files:**
+- `excalidraw-app/collab/Collab.tsx` — orchestrates the collaboration lifecycle
+- `excalidraw-app/collab/Portal.tsx` — Socket.io wrapper
+- `packages/excalidraw/data/reconcile.ts` — `reconcileElements()` merge algorithm
+- `packages/excalidraw/data/encryption.ts` — end-to-end encryption
+
+---
+
+## 4. Save & Load
+
+### Local Persistence (Auto-save)
+
+Every change triggers a debounced save to `localStorage` + `IndexedDB`:
+
+```mermaid
+sequenceDiagram
+ participant App as App.tsx
+ participant LD as LocalData
+ participant LS as localStorage
+ participant IDB as IndexedDB
+
+ App->>LD: onChange → LocalData.save(elements, appState)
+ LD->>LS: Save appState (JSON)
+ LD->>IDB: Save elements + files (binary data)
+```
+
+### Export to File
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Action as actionSaveToActiveFile
+ participant JSON as json.ts
+ participant FS as filesystem.ts
+
+ User->>Action: Ctrl+S
+ Action->>JSON: serializeAsJSON(elements, appState, files)
+ JSON-->>Action: JSON string
+ Action->>FS: fileSave(blob, filename)
+ FS->>FS: File System Access API or download fallback
+```
+
+### Share via Link
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Export as exportToBackend()
+ participant Backend as Backend V2 API
+ participant Firebase as Firebase Storage
+
+ User->>Export: Click "Share" → export link
+ Export->>Export: encryptData(elements + appState)
+ Export->>Backend: POST encrypted data
+ Backend-->>Export: {id}
+ Export->>Firebase: saveFilesToFirebase(files)
+ Export-->>User: URL with #json=id,encryptionKey
+```
+
+**Key files:**
+- `excalidraw-app/data/LocalData.ts` — auto-save logic
+- `packages/excalidraw/data/json.ts` — `serializeAsJSON()`, `saveAsJSON()`, `loadFromJSON()`
+- `packages/excalidraw/data/filesystem.ts` — File System Access API wrapper
+- `excalidraw-app/data/index.ts` — `exportToBackend()`, `importFromBackend()`
+
+---
+
+## 5. Export to Image (PNG/SVG)
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Action as exportCanvas()
+ participant Scene as scene/export.ts
+ participant Renderer as renderStaticScene()
+ participant Clipboard as Clipboard API
+
+ User->>Action: Export as PNG / Copy as PNG
+ Action->>Scene: exportToCanvas(elements, appState)
+ Scene->>Renderer: renderStaticScene() on offscreen canvas
+ Renderer-->>Scene: Canvas with rendered elements
+
+ alt Export to file
+ Scene->>Scene: canvas.toBlob("image/png")
+ Scene-->>User: Download PNG file
+ else Copy to clipboard
+ Scene->>Clipboard: copyBlobToClipboardAsPng(blob)
+ else Export as SVG
+ Scene->>Scene: renderSceneToSvg()
+ Scene-->>User: SVG string / file
+ end
+```
+
+**Key files:**
+- `packages/excalidraw/scene/export.ts` — `exportToCanvas()`, `exportToSvg()`
+- `packages/excalidraw/renderer/staticScene.ts` — `renderStaticScene()`
+- `packages/excalidraw/renderer/staticSvgScene.ts` — `renderSceneToSvg()`
+- `packages/excalidraw/data/image.ts` — PNG metadata embedding (scene data inside PNG)
+
+---
+
+## 6. Undo/Redo
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant App as App.tsx
+ participant History as History
+ participant Delta as HistoryDelta
+
+ User->>App: Make a change (draw, move, delete)
+ App->>History: record(delta)
+ History->>History: Push inverse delta to undoStack
+
+ User->>App: Ctrl+Z (Undo)
+ App->>History: undo()
+ History->>Delta: undoStack.pop()
+ Delta->>Delta: applyTo(currentState)
+ Delta-->>History: New state + inverse delta
+ History->>History: Push inverse to redoStack
+ History-->>App: Updated elements + appState
+```
+
+**Key concepts:**
+- History uses **deltas**, not full snapshots — this is memory-efficient
+- Each `HistoryDelta` extends `StoreDelta` from `@excalidraw/element`
+- `captureUpdate` in `ActionResult` controls whether the action is recorded in history
+
+**Key files:**
+- `packages/excalidraw/history.ts` — `History` class with `undoStack`/`redoStack`
+- `packages/excalidraw/actions/actionHistory.tsx` — undo/redo action definitions
+
+---
+
+## 7. Clipboard (Copy/Paste)
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant Action as actionCopy/actionPaste
+ participant Clipboard as clipboard.ts
+ participant System as System Clipboard
+
+ User->>Action: Ctrl+C
+ Action->>Clipboard: copyToClipboard(elements)
+ Clipboard->>Clipboard: serializeAsClipboardJSON(elements)
+ Clipboard->>System: navigator.clipboard.writeText(json)
+
+ User->>Action: Ctrl+V
+ Action->>Clipboard: parseClipboard(event)
+ Clipboard->>System: navigator.clipboard.read()
+ System-->>Clipboard: ClipboardItems
+
+ alt Excalidraw JSON detected
+ Clipboard-->>Action: Parsed elements
+ Action->>Action: Insert elements at cursor
+ else Plain text
+ Clipboard-->>Action: Create text element
+ else Image
+ Clipboard-->>Action: Create image element
+ end
+```
+
+**Key files:**
+- `packages/excalidraw/clipboard.ts` — `copyToClipboard()`, `parseClipboard()`
+- `packages/excalidraw/actions/actionClipboard.tsx` — copy/paste/cut actions
+
+---
+
+## 8. Library (Reusable Components)
+
+```mermaid
+sequenceDiagram
+ participant User
+ participant LibMenu as LibraryMenu
+ participant Library as library.ts
+ participant IDB as IndexedDB
+ participant Scene as Scene
+
+ User->>LibMenu: Open library panel
+ LibMenu->>Library: Load libraryItemsAtom
+ Library->>IDB: LibraryPersistenceAdapter.load()
+ IDB-->>Library: LibraryItem[]
+ Library-->>LibMenu: Render items
+
+ User->>LibMenu: Click item to insert
+ LibMenu->>Scene: onInsertLibraryItems(elements)
+ Scene->>Scene: Add elements to canvas
+
+ User->>LibMenu: Select elements → "Add to Library"
+ LibMenu->>Library: Save new LibraryItem
+ Library->>IDB: LibraryPersistenceAdapter.save()
+```
+
+**Key files:**
+- `packages/excalidraw/components/LibraryMenu.tsx` — UI
+- `packages/excalidraw/data/library.ts` — persistence adapter and Jotai atom
diff --git a/dev-docs/docs/a-docs/05-important-components.md b/dev-docs/docs/a-docs/05-important-components.md
new file mode 100644
index 0000000..8bd62df
--- /dev/null
+++ b/dev-docs/docs/a-docs/05-important-components.md
@@ -0,0 +1,277 @@
+# Important Components
+
+This document covers the most critical classes, services, and modules in the codebase. Understanding these is essential for making safe, effective changes.
+
+---
+
+## Core Components
+
+### `App.tsx` — The Editor Core
+
+**Path:** `packages/excalidraw/components/App.tsx`
+
+This is the largest and most critical file in the project. It is the main React component for the Excalidraw editor.
+
+**Responsibilities:**
+- Canvas rendering orchestration (static + interactive + new element canvases)
+- All pointer event handling (click, drag, resize, rotate, pan, zoom)
+- All keyboard event handling (shortcuts, text input)
+- Tool state machine (selection, drawing, eraser, laser pointer, etc.)
+- Drag-and-drop handling (files, library items)
+- Gesture handling (pinch-to-zoom on mobile)
+- Collaboration state (remote cursors, selections)
+
+**Side effects:**
+- Registers global event listeners (`window.addEventListener`)
+- Interacts with clipboard API
+- Triggers history recording via `captureUpdate`
+
+**When you'll touch it:** Adding new tools, changing pointer behavior, modifying canvas interactions.
+
+> **Warning:** Changes to `App.tsx` can have wide-reaching effects. Test thoroughly with different tools and input devices.
+
+---
+
+### `ActionManager` — Action Dispatch System
+
+**Path:** `packages/excalidraw/actions/manager.tsx`
+
+Manages the registry of all user actions and dispatches them.
+
+**Responsibilities:**
+- Registers action objects at startup
+- Routes keyboard events to matching actions via `keyTest`
+- Executes `action.perform()` and feeds `ActionResult` to the state updater
+- Provides `renderAction()` for toolbar/panel rendering
+
+**How it works:**
+```
+User presses Ctrl+D
+ → ActionManager.handleKeyDown(event)
+ → Finds actionDuplicateSelection (keyTest matches)
+ → action.perform(elements, appState, formData, app)
+ → Returns ActionResult {elements, appState, captureUpdate}
+ → updater(result) → state update → re-render
+```
+
+**When you'll touch it:** Adding new keyboard shortcuts or action infrastructure.
+
+---
+
+### `Scene` — Element State Container
+
+**Path:** `packages/element/src/Scene.ts`
+
+Holds the array of all elements and manages their ordering.
+
+**Responsibilities:**
+- Stores elements with `getNonDeletedElements()` for rendering
+- Manages **fractional indexing** for element ordering (`syncMovedIndices`, `syncInvalidIndices`)
+- Provides element lookup by ID
+- Throttled validation of element indices
+
+**Key concept — Fractional Indexing:**
+Elements have an `index` field (a fractional string like `"a0"`, `"a1"`, `"a0V"`) that determines their z-order. This allows inserting elements between others without re-indexing the entire array — critical for collaboration where multiple users may reorder simultaneously.
+
+---
+
+### `History` — Undo/Redo Engine
+
+**Path:** `packages/excalidraw/history.ts`
+
+Manages undo and redo stacks using delta-based state tracking.
+
+**Responsibilities:**
+- Records state deltas when actions are performed
+- Applies inverse deltas for undo, forward deltas for redo
+- Manages stack size and memory
+
+**Key detail:** Uses `HistoryDelta` (extends `StoreDelta` from `@excalidraw/element`) — these are diffs, not full snapshots. Each delta knows how to apply itself to current state and produce an inverse.
+
+**Risk:** If a delta is applied to state it wasn't computed against (e.g., after a collaboration merge), results may be unexpected. The reconciliation logic handles this, but be aware when modifying either system.
+
+---
+
+## Data Layer Components
+
+### `reconcileElements()` — Collaboration Merge
+
+**Path:** `packages/excalidraw/data/reconcile.ts`
+
+The critical function that merges local and remote element arrays during collaboration.
+
+**Algorithm:**
+1. Iterate through both local and remote element arrays
+2. For each element ID, compare `version` numbers
+3. Higher version wins; ties go to remote (server authority)
+4. Deleted elements (`isDeleted: true`) are preserved for conflict resolution
+5. Returns merged array maintaining correct ordering
+
+**Risk:** Bugs here cause data loss or duplication in collaborative sessions. Any changes must be tested with concurrent editing scenarios.
+
+---
+
+### `restore.ts` — Data Migration
+
+**Path:** `packages/excalidraw/data/restore.ts`
+
+Restores and migrates saved data to the current format.
+
+**Responsibilities:**
+- `restoreElements()` — normalizes element data, applies defaults, migrates old formats
+- `restoreAppState()` — normalizes app state, applies defaults
+- `restoreLibraryItems()` — normalizes library data
+- `bumpElementVersions()` — increments versions after restoration
+
+**When it matters:** When the element schema changes (new properties, renamed fields), migration logic goes here. This ensures old saved files continue to work.
+
+---
+
+### Encryption Module
+
+**Path:** `packages/excalidraw/data/encryption.ts`
+
+Handles end-to-end encryption for shared scenes and collaboration.
+
+**Functions:**
+- `generateEncryptionKey()` — creates an AES-GCM key
+- `encryptData(key, data)` — encrypts scene data
+- `decryptData(key, iv, ciphertext)` — decrypts scene data
+
+**Key design:** The encryption key is stored in the URL **hash** (fragment), which browsers do not send to servers. This means the server storing the encrypted data cannot read it.
+
+---
+
+## Collaboration Components
+
+### `Collab.tsx` — Collaboration Orchestrator
+
+**Path:** `excalidraw-app/collab/Collab.tsx`
+
+Manages the full lifecycle of a collaborative session.
+
+**Responsibilities:**
+- Start/stop collaboration sessions
+- Sync elements between local state and remote participants
+- Manage file (image) uploads to Firebase Storage
+- Handle connection/disconnection events
+- Queue saves to Firebase Firestore
+
+**Key methods:**
+- `startCollaboration()` — generates room, connects socket, loads existing data
+- `syncElements()` — called on every local change, broadcasts to peers
+- `handleRemoteSceneUpdate()` — receives remote changes, reconciles, updates local state
+- `fetchImageFilesFromFirebase()` — lazy-loads images that other participants added
+
+---
+
+### `Portal.tsx` — WebSocket Transport
+
+**Path:** `excalidraw-app/collab/Portal.tsx`
+
+Low-level Socket.io wrapper for real-time communication.
+
+**Responsibilities:**
+- Establish/close WebSocket connections
+- Broadcast scene data (`broadcastScene`)
+- Broadcast mouse positions (`broadcastMouseLocation`)
+- Broadcast visible scene bounds (`broadcastVisibleSceneBounds`)
+
+**Protocol subtypes:** `INIT`, `UPDATE`, `MOUSE_LOCATION`, `USER_VISIBLE_SCENE_BOUNDS`, `IDLE_STATUS`
+
+---
+
+## Firebase Integration
+
+**Path:** `excalidraw-app/data/firebase.ts`
+
+Firebase is used for durable storage in collaborative sessions and for shared links.
+
+**Functions:**
+- `loadFromFirebase(roomId)` — reads encrypted scene from Firestore `scenes` collection
+- `saveToFirebase(roomId, elements)` — writes encrypted scene to Firestore
+- `loadFilesFromFirebase(prefix, ids)` — reads image files from Firebase Storage
+- `saveFilesToFirebase(prefix, files)` — writes image files to Firebase Storage
+
+**Configuration:** Loaded from `VITE_APP_FIREBASE_CONFIG` env var (JSON string parsed at runtime).
+
+---
+
+## Rendering Components
+
+### `renderStaticScene()`
+
+**Path:** `packages/excalidraw/renderer/staticScene.ts`
+
+Renders all committed elements onto the static canvas.
+
+**How it works:**
+1. Clears canvas
+2. Applies zoom and scroll transforms
+3. Iterates visible elements
+4. For each element, generates Rough.js drawable (cached) and renders
+5. Handles special cases: images, text, frames, embeds
+
+### `renderInteractiveScene()`
+
+**Path:** `packages/excalidraw/renderer/interactiveScene.ts`
+
+Renders the overlay layer with selection UX.
+
+**What it renders:**
+- Selection rectangles and handles
+- Rotation handles
+- Binding indicators (arrow snap points)
+- Remote user cursors and selections
+- Snap guidelines
+- Lasso selection path
+
+---
+
+## Background Services
+
+### `LocalData` — Auto-Persistence
+
+**Path:** `excalidraw-app/data/LocalData.ts`
+
+Runs debounced saves to `localStorage` (for `appState`) and `IndexedDB` (for elements and binary files).
+
+**Trigger:** Called from `App.tsx`'s `onChange` callback on every state change.
+
+### `FileManager` — Binary File Management
+
+**Path:** `excalidraw-app/data/FileManager.ts`
+
+Manages image file uploads, downloads, and caching for collaboration.
+
+### `tabSync` — Cross-Tab Sync
+
+**Path:** `excalidraw-app/data/tabSync.ts`
+
+Synchronizes state across multiple browser tabs using `BroadcastChannel` or `localStorage` events.
+
+---
+
+## Library System
+
+### `library.ts`
+
+**Path:** `packages/excalidraw/data/library.ts`
+
+Manages the reusable element library (templates/components users can save and reuse).
+
+**Architecture:**
+- `LibraryPersistenceAdapter` — interface for storage backends (IndexedDB by default)
+- `libraryItemsAtom` — Jotai atom holding the library state
+- Items are arrays of elements with metadata (`id`, `name`, `status`, `created`)
+
+### Font System
+
+**Path:** `packages/excalidraw/fonts/`
+
+Manages loading and subsetting of fonts (Virgil, Excalifont, Nunito, etc.).
+
+- `ExcalidrawFontFace.ts` — font face loading abstraction
+- `index.ts` — font registration and loading orchestration
+- `fonts.css` — `@font-face` declarations
+- Font subsetting in `subset/` for optimized export
diff --git a/dev-docs/docs/a-docs/06-testing-guide.md b/dev-docs/docs/a-docs/06-testing-guide.md
new file mode 100644
index 0000000..e5c2a7f
--- /dev/null
+++ b/dev-docs/docs/a-docs/06-testing-guide.md
@@ -0,0 +1,262 @@
+# Testing Guide
+
+## Overview
+
+Excalidraw uses **Vitest** with **jsdom** as the test environment and **@testing-library/react** for component testing. Tests run in a simulated browser environment with mocked Canvas API, IndexedDB, clipboard, and fonts.
+
+---
+
+## Running Tests
+
+| Command | Purpose |
+|---------|---------|
+| `yarn test:app` | Run tests in watch mode |
+| `yarn test:update` | Run all tests + update snapshots (use before committing) |
+| `yarn test:all` | Full suite: typecheck + lint + prettier + tests |
+| `yarn test:typecheck` | TypeScript type checking only |
+| `yarn test:code` | ESLint only |
+| `yarn test:other` | Prettier check only |
+| `yarn test:coverage` | Run tests with coverage report |
+| `yarn test:ui` | Vitest UI with coverage visualization |
+
+### Running a specific test file
+
+```bash
+yarn test:app -- packages/excalidraw/tests/excalidraw.test.tsx
+```
+
+### Running tests matching a pattern
+
+```bash
+yarn test:app -- -t "should render toolbar"
+```
+
+---
+
+## Test Structure
+
+### Test file locations
+
+Tests are found in two patterns:
+
+1. **Dedicated `tests/` directories** — most common
+ - `packages/excalidraw/tests/`
+ - `packages/element/tests/`
+ - `packages/math/tests/`
+ - `excalidraw-app/tests/`
+
+2. **Colocated with source** — for small utility modules
+ - `packages/common/src/utils.test.ts`
+
+### Test file naming
+
+- `*.test.ts` — pure logic tests
+- `*.test.tsx` — component/integration tests that render React
+
+---
+
+## Test Setup
+
+### Global setup file: `setupTests.ts`
+
+Located at the project root, this file runs before all tests and sets up:
+
+| Mock | Purpose |
+|------|---------|
+| `vitest-canvas-mock` | Mocks HTML Canvas 2D API |
+| `fake-indexeddb/auto` | Mocks IndexedDB |
+| `throttleRAF` mock | Replaces `requestAnimationFrame` throttle with immediate execution |
+| `setPointerCapture` | Mocked to no-op |
+| `window.matchMedia` | Returns mock MediaQueryList |
+| `FontFace` | Mocked constructor |
+| `document.fonts` | Mocked FontFaceSet |
+| Font fetch | Intercepts font file requests and returns empty responses |
+| `#root` div | Creates root element for React rendering |
+
+### Vitest configuration: `vitest.config.mts`
+
+Key settings:
+- **Path aliases**: Maps `@excalidraw/*` to source directories (not build output)
+- **Environment**: jsdom
+- **Globals**: `describe`, `it`, `expect`, `vi` available without imports
+- **Coverage thresholds**: lines 60%, branches 70%, functions 63%, statements 60%
+
+---
+
+## Test Helpers
+
+The project has a rich set of test utilities. Learn these before writing tests.
+
+### `test-utils.ts`
+
+**Path:** `packages/excalidraw/tests/test-utils.ts`
+
+| Export | Purpose |
+|--------|---------|
+| `render()` | Renders the editor in a test container, waits for initialization |
+| `unmountComponent()` | Cleanly unmounts the rendered component |
+| `GlobalTestState` | Shared state accessible across test helpers |
+| `toggleMenu(name)` | Opens/closes menus by test ID |
+
+### `helpers/api.ts`
+
+**Path:** `packages/excalidraw/tests/helpers/api.ts`
+
+The `API` object provides programmatic control of the editor in tests:
+
+| Method | Purpose |
+|--------|---------|
+| `API.updateScene({elements, appState})` | Directly set scene state |
+| `API.setAppState(patch)` | Update app state |
+| `API.setElements(elements)` | Replace all elements |
+| `API.createElement({type, ...})` | Create a test element |
+| `API.getAppState()` | Read current app state |
+| `API.getElements()` | Read current elements |
+
+### `helpers/ui.ts`
+
+**Path:** `packages/excalidraw/tests/helpers/ui.ts`
+
+Simulates user interactions:
+
+| Helper | Purpose |
+|--------|---------|
+| `UI.clickTool(toolName)` | Click a tool button in the toolbar |
+| `Keyboard.keyDown(key)` | Simulate keyboard events |
+| `Keyboard.withModifierKeys({ctrl: true}, () => {...})` | Simulate modifier combinations |
+| `Pointer` / mouse helpers | Simulate pointer events at specific coordinates |
+
+### `helpers/mocks.ts`
+
+**Path:** `packages/excalidraw/tests/helpers/mocks.ts`
+
+| Mock | Purpose |
+|------|---------|
+| `mockThrottleRAF()` | Makes RAF-throttled functions execute synchronously |
+| `mockMermaidToExcalidraw()` | Mocks the mermaid-to-excalidraw integration |
+| `mockHTMLImageElement()` | Mocks Image loading |
+
+---
+
+## Mocking Strategy
+
+### General approach
+
+- **Canvas API**: Fully mocked via `vitest-canvas-mock` (no real rendering in tests)
+- **IndexedDB**: Mocked via `fake-indexeddb` for persistence tests
+- **Network**: Use `vi.mock()` for Firebase, fetch, and Socket.io
+- **requestAnimationFrame**: Mocked to execute synchronously via `throttleRAF` mock
+- **Fonts**: Mocked at the fetch level to avoid loading real font files
+- **Crypto**: Override `window.crypto` in tests that need encryption
+
+### Mocking imports
+
+Use Vitest's `vi.mock()` for module-level mocks:
+
+```typescript
+vi.mock("../data/firebase", () => ({
+ loadFromFirebase: vi.fn().mockResolvedValue(null),
+ saveToFirebase: vi.fn().mockResolvedValue(undefined),
+}));
+```
+
+### Snapshot testing
+
+Some tests use snapshot assertions for rendered output. When you change UI, snapshots may break:
+
+```bash
+yarn test:update
+```
+
+Review the snapshot diffs in `__snapshots__/` directories before committing.
+
+---
+
+## Writing a New Test
+
+Here's a template for a typical component/integration test:
+
+```typescript
+import { vi } from "vitest";
+import { render, unmountComponent } from "../tests/test-utils";
+import { API } from "../tests/helpers/api";
+import { UI, Keyboard } from "../tests/helpers/ui";
+import { Excalidraw } from "../index";
+
+describe("my feature", () => {
+ beforeEach(async () => {
+ localStorage.clear();
+ await render();
+ });
+
+ afterEach(() => {
+ unmountComponent();
+ });
+
+ it("should do something when user clicks", async () => {
+ // Arrange: set up initial state
+ const rect = API.createElement({ type: "rectangle", x: 100, y: 100 });
+ API.setElements([rect]);
+
+ // Act: simulate user interaction
+ UI.clickTool("select");
+ // ... simulate clicks, drags, etc.
+
+ // Assert: check the result
+ const elements = API.getElements();
+ expect(elements).toHaveLength(1);
+ expect(elements[0].type).toBe("rectangle");
+ });
+
+ it("should handle keyboard shortcuts", async () => {
+ const rect = API.createElement({ type: "rectangle" });
+ API.setElements([rect]);
+
+ // Select all
+ Keyboard.withModifierKeys({ ctrl: true }, () => {
+ Keyboard.keyDown("a");
+ });
+
+ const appState = API.getAppState();
+ expect(appState.selectedElementIds[rect.id]).toBe(true);
+ });
+});
+```
+
+### For pure unit tests (no React):
+
+```typescript
+import { pointFrom, pointRotateRads } from "@excalidraw/math";
+
+describe("point rotation", () => {
+ it("should rotate point around origin", () => {
+ const result = pointRotateRads(
+ pointFrom(10, 0),
+ pointFrom(0, 0),
+ Math.PI / 2,
+ );
+ expect(result[0]).toBeCloseTo(0);
+ expect(result[1]).toBeCloseTo(10);
+ });
+});
+```
+
+---
+
+## CI Integration
+
+Tests run automatically in GitHub Actions:
+
+| Workflow | Trigger | What it checks |
+|----------|---------|----------------|
+| `test.yml` | Push to `master` | `yarn test:app` |
+| `lint.yml` | Pull requests | `yarn test:other` + `yarn test:code` + `yarn test:typecheck` |
+| `test-coverage-pr.yml` | Pull requests | Coverage report via `davelosert/vitest-coverage-report-action` |
+
+### Before submitting a PR, always run:
+
+```bash
+yarn test:update # Tests + snapshot updates
+yarn test:typecheck # Type checking
+yarn fix # Auto-fix lint and formatting
+```
diff --git a/dev-docs/docs/a-docs/07-common-pitfalls.md b/dev-docs/docs/a-docs/07-common-pitfalls.md
new file mode 100644
index 0000000..2bdd434
--- /dev/null
+++ b/dev-docs/docs/a-docs/07-common-pitfalls.md
@@ -0,0 +1,177 @@
+# Common Pitfalls
+
+Real risks and gotchas that can cause bugs, data loss, or production issues.
+
+---
+
+## 1. Element Immutability
+
+**Risk:** Mutating elements directly instead of creating new versions.
+
+Elements in Excalidraw are treated as **immutable**. Every modification must create a new object with an incremented `version` and new `versionNonce`.
+
+```typescript
+// WRONG — direct mutation
+element.x = 100;
+
+// CORRECT — use mutateElement or create a new element
+mutateElement(element, { x: 100 });
+```
+
+**Why it matters:**
+- The rendering pipeline uses reference equality to skip unchanged elements
+- The collaboration reconciliation algorithm uses `version` to determine which copy wins
+- History (undo/redo) relies on deltas computed from version changes
+
+If you mutate an element without incrementing its version, collaboration will silently drop the change, undo won't work correctly, and the canvas may not re-render.
+
+---
+
+## 2. Collaboration Reconciliation Edge Cases
+
+**Risk:** Data loss when local and remote changes conflict.
+
+The `reconcileElements()` function merges element arrays by comparing versions. The higher version wins; **ties go to remote** (server authority). This means:
+
+- If two users modify the same element in the same frame, one change will be lost
+- Deleted elements (`isDeleted: true`) must be kept in the array for a period to prevent "resurrection" by stale remote updates
+- Adding new properties to elements requires careful handling in `restoreElements()` to ensure old saved data gets sensible defaults
+
+**What to watch for:**
+- Never remove deleted elements from arrays during reconciliation
+- Always test concurrent editing scenarios when changing element structure
+- Be aware that `version` increments happen per-mutation, not per-action
+
+---
+
+## 3. Fractional Indexing Corruption
+
+**Risk:** Ordering bugs or crashes from invalid fractional indices.
+
+Elements use **fractional indexing** (the `index` field) for z-ordering. This avoids the need to re-number all elements when one is moved, which is critical for collaboration.
+
+**Pitfalls:**
+- Inserting an element without computing a valid fractional index between its neighbors
+- Bulk operations that don't maintain monotonically increasing indices
+- `Scene.ts` has validation (`syncMovedIndices`, `syncInvalidIndices`) but it's throttled — bugs may not surface immediately
+
+**Safe approach:** Use the `Scene` methods for reordering; don't manually assign `index` values.
+
+---
+
+## 4. `App.tsx` Side Effects
+
+**Risk:** Breaking seemingly unrelated features by changing `App.tsx`.
+
+`App.tsx` is the largest file in the project and handles many cross-cutting concerns. Event handler changes can have unexpected effects:
+
+- Changing `pointerDown` handling may break drawing, selection, AND resize
+- Modifying keyboard handlers may break shortcuts, text editing, AND command palette
+- Gesture handling code affects both desktop and mobile
+
+**Approach:**
+- Test on both desktop and mobile (or at least with touch event simulation)
+- Test with multiple tools active (selection, rectangle, arrow, text, freedraw)
+- Test with collaboration active (remote cursors, selections)
+
+---
+
+## 5. Canvas Rendering Performance
+
+**Risk:** Rendering lag or jank from unnecessary re-renders.
+
+The multi-canvas architecture is specifically designed to avoid re-rendering the static canvas on every pointer move. Breaking this optimization can make the editor feel sluggish.
+
+**Don'ts:**
+- Don't force static canvas re-render on pointer move events
+- Don't add expensive computations inside render loops
+- Don't create new Rough.js drawables on every render (they're cached per element version)
+
+**Check:** If your change affects rendering, test with 500+ elements on the canvas.
+
+---
+
+## 6. Import Path Restrictions
+
+**Risk:** Build failures or circular dependencies from wrong import paths.
+
+ESLint enforces strict import rules:
+
+- **Inside `@excalidraw/excalidraw`**: Do NOT import from the barrel `index.tsx` — import from the specific module
+- **Jotai imports**: Must come from `@excalidraw/excalidraw/editor-jotai` or `excalidraw-app/app-jotai`, NOT directly from `jotai`
+- **Cross-package imports**: Only go through the `@excalidraw/*` aliases, never relative paths to other packages
+
+```typescript
+// WRONG (inside packages/excalidraw/)
+import { something } from "./index";
+import { atom } from "jotai";
+
+// CORRECT
+import { something } from "./specific-module";
+import { atom } from "../editor-jotai";
+```
+
+---
+
+## 7. Encryption Key in URL Hash
+
+**Risk:** Accidentally exposing encryption keys.
+
+Shared scene encryption keys are stored in the URL **hash** (fragment). Browsers don't send the hash to servers, which is the security model. But:
+
+- Logging the full URL (e.g., in error tracking) may leak the key
+- Redirects to other domains may include the hash
+- Browser extensions have access to the full URL
+
+**Rule:** Never log or transmit the full URL including hash in analytics or error reporting.
+
+---
+
+## 8. State Update Ordering
+
+**Risk:** Stale state bugs from async state updates.
+
+The action system returns `ActionResult` objects that update state synchronously. But some operations (font loading, image processing, file I/O) are async. Mixing sync and async state updates can cause:
+
+- Reading stale `appState` after an async operation
+- Lost updates when two async operations complete out of order
+- React re-render timing issues
+
+**Approach:** Use `captureUpdate` in `ActionResult` to ensure history records the correct state boundary. For async operations, consider whether the state might have changed by the time the promise resolves.
+
+---
+
+## 9. Font Loading and Text Measurement
+
+**Risk:** Incorrect text bounding boxes when fonts aren't loaded.
+
+Text elements compute their dimensions based on font metrics. If a font hasn't loaded when `measureText()` runs, the measurements will be wrong, causing:
+
+- Overlapping text
+- Incorrect auto-sizing of text containers
+- Wrong bounding boxes for selection/export
+
+**The project handles this** via the `InitializeApp` component which loads fonts at startup and the font mock in tests. But if you add a new font or change text measurement logic, verify that measurements are correct after fonts load.
+
+---
+
+## 10. `isDeleted` Flag vs Array Removal
+
+**Risk:** Breaking undo, collaboration, or references by removing elements from arrays.
+
+Elements are **soft-deleted** by setting `isDeleted: true`. They are NOT removed from the elements array. This is by design:
+
+- Undo needs deleted elements to restore them
+- Collaboration needs deleted elements to prevent "ghost" resurrections
+- Bound elements (arrows to shapes) reference IDs that may point to deleted elements
+
+**Rule:** Use `isDeleted: true` for deletion. Use `getNonDeletedElements()` when you need the visible set. Never filter deleted elements out of the source-of-truth array.
+
+---
+
+## 11. Testing Pitfalls
+
+- **Forgetting `await` with `render()`**: The test `render()` function is async. Forgetting to `await` it causes timing-related flaky tests.
+- **Not cleaning up**: Always call `unmountComponent()` in `afterEach`. Leaking mounted components causes state pollution between tests.
+- **Snapshot drift**: If you see unexpected snapshot changes, don't blindly update. Review the diffs — they may indicate a real regression.
+- **Canvas mocking limitations**: `vitest-canvas-mock` doesn't implement all Canvas 2D methods. Some rendering logic cannot be tested in unit tests.
diff --git a/dev-docs/docs/a-docs/08-adding-features.md b/dev-docs/docs/a-docs/08-adding-features.md
new file mode 100644
index 0000000..1b9f923
--- /dev/null
+++ b/dev-docs/docs/a-docs/08-adding-features.md
@@ -0,0 +1,273 @@
+# How to Add New Features
+
+A practical guide to extending Excalidraw while following existing patterns.
+
+---
+
+## Adding a New Action
+
+Actions are the standard way to add user-triggerable operations (keyboard shortcuts, toolbar buttons, menu items).
+
+### Step 1: Create the action file
+
+Create `packages/excalidraw/actions/actionMyFeature.ts`:
+
+```typescript
+import { register } from "./register";
+import type { ActionResult } from "./types";
+
+export const actionMyFeature = register({
+ name: "myFeature",
+ label: "My Feature",
+ icon: MyFeatureIcon, // optional, for toolbar
+ trackEvent: { category: "element" },
+
+ // Keyboard shortcut (optional)
+ keyTest: (event) =>
+ event.key === "m" && event[KEYS.CTRL_OR_CMD],
+
+ // Whether the action is available in current state
+ predicate: (elements, appState) => {
+ return appState.selectedElementIds.length > 0;
+ },
+
+ // The actual logic
+ perform: (elements, appState, formData, app) => {
+ // Your logic here
+ const newElements = /* transform elements */;
+
+ return {
+ elements: newElements,
+ appState: { ...appState /* state changes */ },
+ captureUpdate: CaptureUpdateAction.IMMEDIATELY,
+ };
+ },
+
+ // Panel component for the toolbar (optional)
+ PanelComponent: ({ elements, appState, updateData }) => (
+
+ ),
+});
+```
+
+### Step 2: Register the action
+
+Add the export to `packages/excalidraw/actions/index.ts`:
+
+```typescript
+export { actionMyFeature } from "./actionMyFeature";
+```
+
+### Step 3: Write tests
+
+Create `packages/excalidraw/actions/actionMyFeature.test.tsx`:
+
+```typescript
+import { render, unmountComponent } from "../tests/test-utils";
+import { API } from "../tests/helpers/api";
+import { Keyboard } from "../tests/helpers/ui";
+import { Excalidraw } from "../index";
+
+describe("actionMyFeature", () => {
+ beforeEach(async () => {
+ await render();
+ });
+ afterEach(() => unmountComponent());
+
+ it("should do the thing", () => {
+ // Arrange
+ API.setElements([API.createElement({ type: "rectangle" })]);
+
+ // Act
+ Keyboard.withModifierKeys({ ctrl: true }, () => {
+ Keyboard.keyDown("m");
+ });
+
+ // Assert
+ expect(API.getElements()).toSatisfyMyCondition();
+ });
+});
+```
+
+### Key points for actions:
+
+- `captureUpdate` controls undo/redo: use `CaptureUpdateAction.IMMEDIATELY` for undoable actions
+- Return `false` from `perform` to no-op (don't update state)
+- `formData` comes from `PanelComponent`'s `updateData()` call
+- `app` provides access to `AppClassProperties` for imperative operations
+
+---
+
+## Adding a New Element Property
+
+When you need to add a new visual or behavioral property to elements.
+
+### Step 1: Update the type
+
+In `packages/element/src/types.ts`, add the property to the relevant element type or base type:
+
+```typescript
+type _ExcalidrawElementBase = {
+ // ... existing properties
+ myNewProp?: string;
+};
+```
+
+### Step 2: Set the default
+
+In `packages/element/src/newElement.ts`, include the default value:
+
+```typescript
+export const newElement = (opts) => ({
+ // ... existing defaults
+ myNewProp: opts.myNewProp ?? "default",
+});
+```
+
+### Step 3: Add migration logic
+
+In `packages/excalidraw/data/restore.ts`, handle old elements that don't have the property:
+
+```typescript
+// Inside restoreElement()
+element.myNewProp = element.myNewProp ?? "default";
+```
+
+### Step 4: Wire up the UI
+
+If the property is user-editable, create a panel component in `packages/excalidraw/components/` and register an action that changes it.
+
+### Step 5: Update rendering (if visual)
+
+If the property affects how elements look, update the rendering in:
+- `packages/excalidraw/renderer/staticScene.ts` — for canvas rendering
+- `packages/excalidraw/renderer/staticSvgScene.ts` — for SVG export
+
+---
+
+## Adding a New Tool
+
+Tools determine what happens when the user interacts with the canvas.
+
+### Step 1: Register the tool type
+
+Add the tool type to `packages/excalidraw/types.ts` in the `ActiveTool` definition.
+
+### Step 2: Add the toolbar button
+
+Update `packages/excalidraw/components/Actions.tsx` or the relevant toolbar component.
+
+### Step 3: Handle canvas interactions
+
+The main event handlers are in `packages/excalidraw/components/App.tsx`:
+- `handleCanvasPointerDown()` — start of interaction
+- `handleCanvasPointerMove()` — during interaction
+- `handleCanvasPointerUp()` — end of interaction
+
+Add your tool's behavior in the appropriate switch/if blocks.
+
+### Step 4: Add keyboard shortcut
+
+Add the shortcut key in `packages/excalidraw/actions/shortcuts.ts`.
+
+---
+
+## Adding a New UI Component
+
+### For library-level components (part of the npm package):
+
+Place it in `packages/excalidraw/components/`:
+
+```
+packages/excalidraw/components/
+├── MyComponent.tsx
+└── MyComponent.scss (if needed)
+```
+
+Follow existing patterns:
+- Use Jotai atoms from `editor-jotai.ts` for state
+- Use `useAppStateValue()` to read app state
+- Use SCSS modules for styling (or plain SCSS with BEM-like naming)
+- Use `useDevice()` from `@excalidraw/common` for responsive behavior
+
+### For app-level components (excalidraw.com only):
+
+Place it in `excalidraw-app/components/`:
+
+```
+excalidraw-app/components/
+├── MyAppComponent.tsx
+└── MyAppComponent.scss
+```
+
+---
+
+## Adding a New Package Utility
+
+If adding a utility function to one of the core packages:
+
+1. **Determine the right package:**
+ - Pure math/geometry → `packages/math/`
+ - Element manipulation → `packages/element/`
+ - Shared utility (no element/UI deps) → `packages/common/`
+ - Export/bounds helpers → `packages/utils/`
+
+2. **Add to the appropriate source file** (or create a new module)
+
+3. **Export from the package index** (`src/index.ts`)
+
+4. **Respect the dependency graph:** `common` and `math` cannot import from other `@excalidraw` packages
+
+---
+
+## Patterns to Follow
+
+### State updates via actions
+
+All user-initiated state changes should go through the action system. Don't directly modify state from event handlers when an action would be more appropriate.
+
+### Element immutability
+
+Always use `mutateElement()` or create new element objects. Never modify element properties directly.
+
+### Consistent type imports
+
+Use `import type { ... }` for type-only imports (enforced by ESLint):
+
+```typescript
+import type { ExcalidrawElement } from "@excalidraw/element/types";
+```
+
+### Jotai atom scoping
+
+Editor-level atoms go through `editor-jotai.ts`. App-level atoms through `app-jotai.ts`. Never import `jotai` directly.
+
+---
+
+## What to Avoid
+
+- **Don't bypass the action system** for state changes that should be undoable
+- **Don't add dependencies** to `packages/common/` or `packages/math/` on other `@excalidraw` packages
+- **Don't import from barrel files** (`index.ts`) when inside the same package
+- **Don't add large dependencies** without discussing — the bundle size is monitored (`size-limit.yml`)
+- **Don't add browser-specific APIs** without fallbacks — the library runs in SSR contexts (Next.js)
+- **Don't skip snapshot updates** — run `yarn test:update` and review diffs
+- **Don't hardcode strings** — use the i18n system for user-facing text (see `packages/excalidraw/locales/`)
+
+---
+
+## Checklist for New Features
+
+- [ ] Types defined (in appropriate `types.ts`)
+- [ ] Default values set (in `newElement.ts` or `appState.ts`)
+- [ ] Migration logic added (in `restore.ts`) if changing element schema
+- [ ] Action created with `keyTest` if it has a shortcut
+- [ ] UI wired up (toolbar, panel, menu)
+- [ ] Rendering updated if it's visual
+- [ ] Tests written (unit + integration)
+- [ ] Snapshots updated (`yarn test:update`)
+- [ ] Type checking passes (`yarn test:typecheck`)
+- [ ] Linting passes (`yarn fix` then `yarn test:code`)
+- [ ] Works on both desktop and mobile
+- [ ] Works with collaboration (if it changes elements)
+- [ ] Export/import handles the new data (PNG, SVG, JSON)
diff --git a/dev-docs/docs/a-docs/09-tldr-new-devs.md b/dev-docs/docs/a-docs/09-tldr-new-devs.md
new file mode 100644
index 0000000..2d4ebe6
--- /dev/null
+++ b/dev-docs/docs/a-docs/09-tldr-new-devs.md
@@ -0,0 +1,130 @@
+# TL;DR for New Developers
+
+Your quick-start reference. Bookmark this.
+
+---
+
+## Day 1 Checklist
+
+- [ ] Clone the repo: `git clone https://github.com/excalidraw/excalidraw.git`
+- [ ] Ensure Node.js >= 18 and Yarn Classic are installed
+- [ ] Run `yarn` to install all dependencies
+- [ ] Run `yarn start` and open http://localhost:3001
+- [ ] Draw a few shapes, export a PNG, try undo/redo — confirm everything works
+- [ ] Run `yarn test:app --watch=false` to verify tests pass
+- [ ] Run `yarn test:typecheck` to verify TypeScript compiles
+- [ ] Read this TL;DR page fully
+
+---
+
+## Key Commands
+
+| Command | What it does |
+|---------|-------------|
+| `yarn start` | Start dev server (port 3001) |
+| `yarn test:update` | Run tests + update snapshots (**always run before committing**) |
+| `yarn test:typecheck` | TypeScript type check |
+| `yarn fix` | Auto-fix formatting and linting |
+| `yarn test:all` | Full CI-equivalent check |
+| `yarn build` | Build the web app |
+| `yarn build:packages` | Build only the npm packages |
+
+---
+
+## Files to Read First
+
+In order of priority:
+
+1. **`packages/excalidraw/index.tsx`** — main entry point, see what's exported
+2. **`packages/element/src/types.ts`** — element type definitions (the data model)
+3. **`packages/excalidraw/types.ts`** — `AppState` and component props
+4. **`packages/excalidraw/appState.ts`** — default app state (all the knobs)
+5. **`packages/excalidraw/actions/types.ts`** — how the action system works
+6. **`packages/excalidraw/actions/manager.tsx`** — action dispatch logic
+7. **`excalidraw-app/App.tsx`** — app bootstrap and scene loading
+8. **`packages/excalidraw/data/reconcile.ts`** — collaboration merge logic
+9. **`setupTests.ts`** — test environment setup
+
+---
+
+## The Mental Model
+
+```
+User draws a shape
+ → App.tsx handles pointer events
+ → Creates a new element via newElement()
+ → ActionManager processes it
+ → Returns ActionResult {elements, appState, captureUpdate}
+ → State updates → Canvas re-renders
+ → History records the delta
+ → LocalData auto-saves
+ → If collaborating: sync to peers via Socket.io + Firebase
+```
+
+---
+
+## Project Structure in 30 Seconds
+
+```
+excalidraw/
+├── excalidraw-app/ ← The website (excalidraw.com)
+│ ├── collab/ ← Real-time collaboration
+│ └── data/ ← Firebase, localStorage, file management
+├── packages/
+│ ├── excalidraw/ ← The npm library (editor component)
+│ │ ├── actions/ ← User actions (copy, paste, delete, etc.)
+│ │ ├── components/ ← React UI components
+│ │ ├── renderer/ ← Canvas rendering
+│ │ ├── data/ ← Serialization, encryption, import/export
+│ │ └── scene/ ← Export helpers, render configs
+│ ├── element/ ← Element types, mutation, transforms
+│ ├── common/ ← Shared constants and utilities
+│ ├── math/ ← Geometry (points, vectors, curves)
+│ └── utils/ ← Export and bounds helpers
+└── examples/ ← Integration examples (Next.js, Vite)
+```
+
+---
+
+## The 5 Most Important Concepts
+
+1. **Elements are immutable** — use `mutateElement()`, never modify properties directly
+2. **Actions are the state change API** — all user operations go through `ActionManager`
+3. **Jotai for reactive state** — atoms from `editor-jotai.ts`, not raw `jotai`
+4. **Multi-canvas rendering** — static (cached), interactive (selection), new element (preview)
+5. **Collaboration via reconciliation** — `reconcileElements()` merges by version numbers
+
+---
+
+## Before Your First PR
+
+```bash
+yarn test:update # Tests + snapshot updates
+yarn test:typecheck # TypeScript
+yarn fix # Formatting + linting
+```
+
+All three must pass. CI will check them.
+
+---
+
+## When You're Stuck
+
+| Problem | Where to look |
+|---------|---------------|
+| "How does X tool work?" | `packages/excalidraw/components/App.tsx` — pointer handlers |
+| "How is Y element created?" | `packages/element/src/newElement.ts` |
+| "How does Z action work?" | `packages/excalidraw/actions/actionZ.ts` |
+| "Why is my element not rendering?" | `packages/excalidraw/renderer/staticScene.ts` |
+| "How does collab work?" | `excalidraw-app/collab/Collab.tsx` |
+| "Where is state defined?" | `packages/excalidraw/appState.ts` + `types.ts` |
+| "How do tests work?" | `setupTests.ts` + `packages/excalidraw/tests/helpers/` |
+| "How is data saved?" | `packages/excalidraw/data/json.ts` + `excalidraw-app/data/LocalData.ts` |
+
+---
+
+## Quick Links
+
+- [Excalidraw Docs](https://docs.excalidraw.com/) — official documentation
+- [Contributing Guide](https://docs.excalidraw.com/docs/introduction/contributing) — PR guidelines
+- [Development Guide](https://docs.excalidraw.com/docs/introduction/development) — official setup guide
diff --git a/dev-docs/docs/a-docs/README.md b/dev-docs/docs/a-docs/README.md
new file mode 100644
index 0000000..1b816f4
--- /dev/null
+++ b/dev-docs/docs/a-docs/README.md
@@ -0,0 +1,31 @@
+# Excalidraw — Developer Onboarding Guide
+
+Welcome to the Excalidraw project! This guide will help you get productive quickly.
+
+## Table of Contents
+
+| # | Document | What You'll Learn |
+|---|----------|-------------------|
+| 1 | [Architecture Overview](./01-architecture-overview.md) | Monorepo structure, package responsibilities, how components interact |
+| 2 | [Local Setup](./02-local-setup.md) | Prerequisites, installation, running the app, environment variables |
+| 3 | [Codebase Navigation](./03-codebase-navigation.md) | Folder structure, where to find things, naming conventions |
+| 4 | [Key Business Flows](./04-key-business-flows.md) | Drawing, collaboration, save/load, export, undo/redo |
+| 5 | [Important Components](./05-important-components.md) | Core classes, managers, event handlers, integration services |
+| 6 | [Testing Guide](./06-testing-guide.md) | Test structure, running tests, mocking strategy, writing new tests |
+| 7 | [Common Pitfalls](./07-common-pitfalls.md) | Race conditions, data consistency, things that break production |
+| 8 | [Adding New Features](./08-adding-features.md) | Patterns to follow, where to add logic, what to avoid |
+| 9 | [TL;DR for New Developers](./09-tldr-new-devs.md) | Day-1 checklist, key commands, files to read first |
+
+There is also a printable combined version:
+
+- [**new-employee-guide.html**](./new-employee-guide.html) — styled HTML combining key content, optimized for printing/PDF export
+
+## How to Use This Guide
+
+**Day 1:** Start with the [TL;DR](./09-tldr-new-devs.md), then follow the [Local Setup](./02-local-setup.md).
+
+**Day 2–3:** Read the [Architecture Overview](./01-architecture-overview.md) and [Codebase Navigation](./03-codebase-navigation.md).
+
+**First week:** Study the [Key Business Flows](./04-key-business-flows.md) and [Important Components](./05-important-components.md).
+
+**Before your first PR:** Read the [Testing Guide](./06-testing-guide.md), [Common Pitfalls](./07-common-pitfalls.md), and [Adding New Features](./08-adding-features.md).
From bd21c486549c54700b1bc5e91cc3e406fa8bc489 Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Wed, 8 Apr 2026 22:36:21 +0200
Subject: [PATCH 2/6] Added how to verify section
---
.cursor/rules/excalidraw-actions-state.mdc | 6 ++++++
.cursor/rules/excalidraw-app-integration.mdc | 6 ++++++
.cursor/rules/excalidraw-canvas-components.mdc | 6 ++++++
.cursor/rules/excalidraw-data-collab.mdc | 7 +++++++
.cursor/rules/excalidraw-element-model.mdc | 7 +++++++
.cursor/rules/excalidraw-monorepo.mdc | 7 +++++++
.cursor/rules/excalidraw-rendering.mdc | 6 ++++++
.cursor/rules/excalidraw-testing.mdc | 10 ++++++++--
.cursor/rules/excalidraw-typescript-imports.mdc | 6 ++++++
.cursor/rules/excalidraw-ui-app.mdc | 10 ++++++++--
.cursor/rules/excalidraw-ui.mdc | 10 ++++++++--
11 files changed, 75 insertions(+), 6 deletions(-)
diff --git a/.cursor/rules/excalidraw-actions-state.mdc b/.cursor/rules/excalidraw-actions-state.mdc
index 159a5dc..a31ab20 100644
--- a/.cursor/rules/excalidraw-actions-state.mdc
+++ b/.cursor/rules/excalidraw-actions-state.mdc
@@ -19,3 +19,9 @@ RULE excalidraw-actions-state APPLIED ⚡
- Set **`captureUpdate`** correctly for **undo/redo** (e.g. `CaptureUpdateAction.IMMEDIATELY` when the change should be recorded — match nearby actions).
- After adding an action file, **export** it from **`actions/index.ts`** so it is registered with **`ActionManager`**.
- **`App.tsx`** holds pointer/tool orchestration; if an action only needs state transformation, keep logic in the action and keep `App.tsx` changes minimal.
+
+## How to verify
+
+- Confirm the new action is **exported** in **`packages/excalidraw/actions/index.ts`** and appears in UI/shortcuts as intended.
+- **Manual:** trigger the action from menu, keyboard, and (if wired) command palette; confirm **undo/redo** matches expectations for the chosen **`captureUpdate`**.
+- Run **`yarn test:typecheck`** and add or extend **`packages/excalidraw/actions/*.test.tsx`**; run **`yarn test:app --`** that file path.
diff --git a/.cursor/rules/excalidraw-app-integration.mdc b/.cursor/rules/excalidraw-app-integration.mdc
index d9d989d..0ca89fc 100644
--- a/.cursor/rules/excalidraw-app-integration.mdc
+++ b/.cursor/rules/excalidraw-app-integration.mdc
@@ -18,3 +18,9 @@ RULE excalidraw-app-integration APPLIED 🌐
- **`data/`:** Local persistence (`LocalData`, IndexedDB), Firebase, file manager, tab sync — app-specific. Reuse **`packages/excalidraw/data`** for encode/decode, restore, and reconcile.
- **Env:** Vite exposes `VITE_APP_*` variables; don’t hardcode production URLs in new code without checking existing `.env.*` patterns.
- **Jotai:** app-level atoms live in **`excalidraw-app/app-jotai.ts`**, not in raw `jotai`.
+
+## How to verify
+
+- Run **`yarn start`** and exercise the feature (localStorage / share / collab as relevant).
+- Run **`yarn test:typecheck`** and **`yarn test:app -- excalidraw-app`** (or specific tests under **`excalidraw-app/tests/`**).
+- **Collab / Firebase:** test with dev **`VITE_APP_*`** defaults; confirm no new hardcoded prod URLs without env alignment.
diff --git a/.cursor/rules/excalidraw-canvas-components.mdc b/.cursor/rules/excalidraw-canvas-components.mdc
index 9fe836c..f4451dc 100644
--- a/.cursor/rules/excalidraw-canvas-components.mdc
+++ b/.cursor/rules/excalidraw-canvas-components.mdc
@@ -15,3 +15,9 @@ RULE excalidraw-canvas-components APPLIED 🖼️
- Keep the **multi-canvas** split: static layer for committed geometry, interactive layer for selection/handles/cursors, new-element layer for in-progress drawing.
- Avoid wiring **pointer move** handlers that unnecessarily bump static-canvas props or force full static re-renders; prefer updating the interactive or preview layer when possible.
- Coordinate with **`packages/excalidraw/renderer/`** — canvas components should orchestrate, not duplicate rendering logic that belongs in `renderStaticScene` / `renderInteractiveScene`.
+
+## How to verify
+
+- **Manual:** exercise tools that use each canvas layer (draw, select, resize, multi-select); confirm static content does not “flash” or rebuild unnecessarily on every move.
+- Compare **export PNG/SVG** with on-screen appearance after your change.
+- Run **`yarn test:typecheck`** and integration tests that hit the canvas (**`yarn test:app --`** relevant `*.test.tsx` under **`packages/excalidraw/tests/`**).
diff --git a/.cursor/rules/excalidraw-data-collab.mdc b/.cursor/rules/excalidraw-data-collab.mdc
index 8344c38..467269f 100644
--- a/.cursor/rules/excalidraw-data-collab.mdc
+++ b/.cursor/rules/excalidraw-data-collab.mdc
@@ -18,3 +18,10 @@ RULE excalidraw-data-collab APPLIED 🔐
- **`restore.ts`:** add **defaults and migrations** when the element or appState schema changes so older JSON/clipboard data still works.
- **Encryption / share links:** sensitive material belongs in the URL **hash** (fragment), which is not sent to the server. Never log full share URLs or move keys into query strings.
- Keep **serialization** concerns here; **`excalidraw-app`** should orchestrate Firebase/network, not reimplement core JSON/binary formats.
+
+## How to verify
+
+- **Round-trip:** save → reload → export JSON; paste from clipboard; open a **share link** / old file fixture after **`restore`** changes.
+- **Collab:** two clients, same room — concurrent edits, deletes, and reordering; watch for duplicates or vanishing elements after **`reconcileElements`** changes.
+- Run **`yarn test:typecheck`** and tests touching **`packages/excalidraw/data/`** (e.g. **`yarn test:app -- packages/excalidraw/data`** or specific `*.test.ts`).
+- **Encryption:** ensure keys stay in the URL **hash** only; grep for accidental logging of full share URLs in new code.
diff --git a/.cursor/rules/excalidraw-element-model.mdc b/.cursor/rules/excalidraw-element-model.mdc
index 5c5fdf4..5d57b98 100644
--- a/.cursor/rules/excalidraw-element-model.mdc
+++ b/.cursor/rules/excalidraw-element-model.mdc
@@ -26,3 +26,10 @@ element.x = 100;
// ✅ GOOD
mutateElement(element, { x: 100 });
```
+
+## How to verify
+
+- Run **`yarn test:typecheck`**.
+- Run targeted tests: **`yarn test:app -- packages/element`** (or a specific file under **`packages/element/tests/`**).
+- **Manual:** draw, move, undo/redo, and (if you changed structure) **collaborate with two browsers** on the same room to confirm versions and ordering.
+- **Regression:** load an **old saved `.excalidraw` file** after schema changes to confirm **`restore.ts`** paths still work.
diff --git a/.cursor/rules/excalidraw-monorepo.mdc b/.cursor/rules/excalidraw-monorepo.mdc
index bdc9d97..b83f675 100644
--- a/.cursor/rules/excalidraw-monorepo.mdc
+++ b/.cursor/rules/excalidraw-monorepo.mdc
@@ -19,3 +19,10 @@ RULE excalidraw-monorepo APPLIED 🏗️
- `excalidraw-app` → `@excalidraw/excalidraw` plus app-only services (Firebase, Socket.io, etc.).
- New utilities belong in the **lowest** package that can own them: math → `math`, element ops → `element`, shared non-geometry → `common`, export/bounds for consumers → `utils`.
- For architecture, flows, and pitfalls, prefer **`dev-docs/docs/a-docs/`** (onboarding guide) before guessing.
+
+## How to verify
+
+- Run **`yarn test:typecheck`** — catches bad cross-package imports and missing types after refactors.
+- Run **`yarn test:code`** (ESLint) — enforces import rules and dependency boundaries in many cases.
+- After changing **`package.json`** / workspaces: **`yarn install`** from repo root; **`yarn build:packages`** then **`yarn build`** for a full compile smoke test.
+- If you added a dependency to **`packages/common`** or **`packages/math`**, confirm it does not import other **`@excalidraw/*`** packages (review `package.json` + imports).
diff --git a/.cursor/rules/excalidraw-rendering.mdc b/.cursor/rules/excalidraw-rendering.mdc
index bb036e7..5324d67 100644
--- a/.cursor/rules/excalidraw-rendering.mdc
+++ b/.cursor/rules/excalidraw-rendering.mdc
@@ -18,3 +18,9 @@ RULE excalidraw-rendering APPLIED 🎨
- **`renderStaticScene`:** Rough.js drawables are **cached** per element/version; avoid recreating drawable objects every frame when only the interactive overlay should change.
- SVG export paths (`staticSvgScene`, etc.) should stay consistent with canvas rendering for the same elements.
- Before large renderer changes, consider **performance** with many elements (hundreds+); keep hot loops allocation-light.
+
+## How to verify
+
+- **Manual:** draw, select, drag with **many elements** on canvas; confirm no obvious jank on pointer move.
+- **Export:** PNG and SVG for the same scene; visuals should match expectations vs on-screen canvas.
+- Run **`yarn test:typecheck`** and renderer-related tests if present (**`yarn test:app -- packages/excalidraw/renderer`** or **`export.test`** under **`packages/excalidraw/tests/`**).
diff --git a/.cursor/rules/excalidraw-testing.mdc b/.cursor/rules/excalidraw-testing.mdc
index c3538f8..2a6f817 100644
--- a/.cursor/rules/excalidraw-testing.mdc
+++ b/.cursor/rules/excalidraw-testing.mdc
@@ -9,9 +9,9 @@ alwaysApply: false
## Important (debug)
-For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
-`RULE excalidraw-testing APPLIED 🧪`
+RULE excalidraw-testing APPLIED 🧪
# Testing
@@ -21,3 +21,9 @@ For transcript debugging: **when this rule is in your context for the user’s m
- When snapshots change intentionally, run **`yarn test:update`** and review diffs before committing.
- Prefer **`*.test.ts`** / **`*.test.tsx`** naming; colocate small unit tests or use package **`tests/`** directories per existing layout.
- In **`.tsx`** tests: use **`unmountComponent()`** in `afterEach` when using the shared `render` helper.
+
+## How to verify
+
+- Run the test file you changed: **`yarn test:app -- path/to/file.test.tsx`**.
+- Run **`yarn test:app --watch=false`** before commit; use **`yarn test:update`** only when snapshot changes are intentional, then **review diffs**.
+- Run **`yarn test:typecheck`** — ensures test imports and **`@excalidraw/*`** aliases resolve like CI.
diff --git a/.cursor/rules/excalidraw-typescript-imports.mdc b/.cursor/rules/excalidraw-typescript-imports.mdc
index b3ab239..7f1a6f2 100644
--- a/.cursor/rules/excalidraw-typescript-imports.mdc
+++ b/.cursor/rules/excalidraw-typescript-imports.mdc
@@ -29,3 +29,9 @@ import { atom } from "jotai";
import { foo } from "../components/some-module";
import { atom } from "../editor-jotai";
```
+
+## How to verify
+
+- Run **`yarn test:code`** — ESLint flags **`import/type`** and many forbidden import paths.
+- Run **`yarn test:typecheck`** — resolves **`@excalidraw/*`** aliases the same as CI; catches wrong relative hops between packages.
+- For **Next.js / SSR**: build or run **`examples/with-nextjs`** (or your SSR path) after changes that might touch `window` / `document` without guards.
diff --git a/.cursor/rules/excalidraw-ui-app.mdc b/.cursor/rules/excalidraw-ui-app.mdc
index 3a23fbd..e4cd6a6 100644
--- a/.cursor/rules/excalidraw-ui-app.mdc
+++ b/.cursor/rules/excalidraw-ui-app.mdc
@@ -6,12 +6,18 @@ alwaysApply: false
## Important (debug)
-For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
-`RULE excalidraw-ui-app APPLIED 📱`
+RULE excalidraw-ui-app APPLIED 📱
# App UI (`excalidraw-app/components`)
- These components are **not** part of **`@excalidraw/excalidraw`**; they wrap or extend the editor for the hosted app (share, welcome, account, etc.).
- Prefer reusing **`packages/excalidraw`** APIs and props rather than duplicating editor internals.
- Colocate **`.scss`** with components; follow patterns from sibling files in this folder.
+
+## How to verify
+
+- Run **`yarn start`** and walk through the app flows that use the component (share, welcome, sidebar, etc.).
+- Run **`yarn test:typecheck`** and **`yarn test:app -- excalidraw-app`** when tests cover the area.
+- Confirm **`excalidraw-app`** styling does not break the embedded editor layout (regression pass on main canvas).
diff --git a/.cursor/rules/excalidraw-ui.mdc b/.cursor/rules/excalidraw-ui.mdc
index f304b20..4861e0a 100644
--- a/.cursor/rules/excalidraw-ui.mdc
+++ b/.cursor/rules/excalidraw-ui.mdc
@@ -6,9 +6,9 @@ alwaysApply: false
## Important (debug)
-For transcript debugging: **when this rule is in your context for the user’s message**, output this exact line once at the **start** of your reply (plain text, its own line), then continue:
+For transcript debugging: **when this rule is in your context for the user’s message**, print this exact line once at the **start** of your reply as **plain text** (no backticks, no code fence), then continue:
-`RULE excalidraw-ui APPLIED 🎯`
+RULE excalidraw-ui APPLIED 🎯
# UI components (`packages/excalidraw/components`)
@@ -16,3 +16,9 @@ For transcript debugging: **when this rule is in your context for the user’s m
- **User-visible strings** go through **`packages/excalidraw/locales/`** (i18n); do not hardcode English for labels, menus, or toasts.
- Use **Jotai** via **`editor-jotai.ts`** and **`useAppStateValue`** (and related hooks) for reactive editor state — not raw `jotai`.
- **`App.tsx`** is high-risk: pointer, keyboard, tools, and canvas behavior intersect here; keep changes focused and test multiple tools and inputs when touching it.
+
+## How to verify
+
+- **Manual:** open menus, dialogs, and toolbars you touched; test **keyboard** and **pointer** paths; check **mobile** or narrow viewport if the component is responsive.
+- **i18n:** confirm new strings exist in **`packages/excalidraw/locales/`** and render correctly via **`t(...)`** (switch language if you test locales).
+- Run **`yarn test:typecheck`**, **`yarn test:code`**, and relevant **`yarn test:app --`** UI tests; update snapshots only when expected (**`yarn test:update`**).
From 9189ceaed5154c56b47c894092b49ce9e2f07cd4 Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Wed, 8 Apr 2026 22:50:33 +0200
Subject: [PATCH 3/6] Agent.md added
---
AGENT.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 145 insertions(+)
create mode 100644 AGENT.md
diff --git a/AGENT.md b/AGENT.md
new file mode 100644
index 0000000..6be53df
--- /dev/null
+++ b/AGENT.md
@@ -0,0 +1,145 @@
+# AGENT.md
+
+Persistent context for AI coding agents and developers working in this repository. Aligns with **`dev-docs/docs/a-docs/`**, **`.cursor/rules/`**, and project tooling.
+
+---
+
+## Project
+
+**Excalidraw monorepo** — open-source collaborative virtual whiteboard: hand-drawn style canvas, real-time collaboration, end-to-end encrypted sharing, export (PNG, SVG, JSON).
+
+| Layer | Role |
+|-------|------|
+| **`packages/excalidraw`** | Published npm package **`@excalidraw/excalidraw`** — embeddable React editor |
+| **`excalidraw-app`** | Full web app (excalidraw.com) — Vite app, Firebase, Socket.io collab, local persistence |
+| **`packages/element`** | Element model, scene, transforms, bindings, z-order (`@excalidraw/element`) |
+| **`packages/common`** | Shared constants and utilities (`@excalidraw/common`) |
+| **`packages/math`** | Geometry only, no UI deps (`@excalidraw/math`) |
+| **`packages/utils`** | Export and bounds helpers for consumers (`@excalidraw/utils`) |
+| **`examples/*`** | Integration samples (e.g. Next.js, Vite) |
+| **`dev-docs/`** | Docusaurus site; deep onboarding lives in **`dev-docs/docs/a-docs/`** |
+
+**Tech (high level):** React 19, TypeScript (strict), Yarn Classic workspaces, Vite (app), esbuild (packages), Vitest + jsdom + Testing Library, HTML Canvas 2D + Rough.js, SCSS, Jotai (scoped providers), Socket.io + Firebase in the app.
+
+---
+
+## Teams & audiences (who cares about what)
+
+| Audience | Focus |
+|----------|--------|
+| **Library consumers** | Stable **`@excalidraw/excalidraw`** API, bundle size, SSR (e.g. Next.js example), minimal breaking changes |
+| **Product / web app** | **`excalidraw-app`**: collab, auth-adjacent flows, env-specific config, hosting |
+| **Core editor contributors** | **`packages/excalidraw`** + **`packages/element`**: actions, rendering, data format, undo/redo |
+| **Infrastructure / release** | `scripts/`, `.github/workflows`, Docker/nginx as used by the project |
+
+When unsure whether code belongs in the **library** or the **app**, default to: reusable editor behavior → **`packages/excalidraw`** (or lower packages); excalidraw.com-only → **`excalidraw-app`**.
+
+---
+
+## Architecture (mental model)
+
+- **State:** Hybrid — large **`AppState`** object + immutable **`elements`** array; **Jotai** atoms for fine-grained UI (`editor-jotai.ts`, `app-jotai.ts`); **`Scene`** for element ordering (fractional indices); **History** uses deltas for undo/redo.
+- **User operations:** Prefer the **action** system — `ActionManager` dispatches **`perform` → `ActionResult`** (`elements`, `appState`, `files`, **`captureUpdate`** for history). Not a single Redux-style store.
+- **Rendering:** **Multi-canvas** — static (committed shapes, Rough.js, cache-friendly), interactive (selection, handles, cursors), new-element preview. Avoid redrawing the static layer on every pointer move.
+- **Collaboration (app):** Socket.io for realtime; Firebase for durable storage; **`reconcileElements`** merges by element **version**; encryption key in URL **hash** (fragment), not query string.
+- **Dependencies:** Flow **downward** — `common` / `math` have no internal `@excalidraw/*` deps; `element` → `common`, `math`; `excalidraw` → all; `excalidraw-app` → `@excalidraw/excalidraw` + app services.
+
+**Heavy-touch files:** `packages/excalidraw/components/App.tsx` (class-based editor core — pointer, keyboard, tools). Treat changes there as high blast radius.
+
+---
+
+## Commands
+
+Use **Yarn Classic v1** from the **repository root** (`packageManager: yarn@1.22.22`). Node **>= 18**.
+
+| Command | Purpose |
+|---------|---------|
+| `yarn` | Install all workspace dependencies |
+| `yarn start` | Dev server for **`excalidraw-app`** (default port from `.env.development`, often `3001`) |
+| `yarn build` | Production build of the web app |
+| `yarn build:packages` | Build npm packages only (`common`, `math`, `element`, `excalidraw`) |
+| `yarn test` / `yarn test:app` | Vitest (watch mode by default for `test:app`) |
+| `yarn test:update` | Tests + update snapshots — run before commit when snapshots are intentional |
+| `yarn test:typecheck` | `tsc` for the monorepo |
+| `yarn test:code` | ESLint |
+| `yarn test:other` | Prettier check |
+| `yarn test:all` | CI-like: typecheck + lint + prettier + tests (no watch) |
+| `yarn fix` | Prettier write + ESLint fix |
+| `yarn clean-install` | Remove workspace `node_modules` and reinstall |
+
+**Examples:** `yarn start:example` — build packages + run browser example. **`VITE_APP_*`** env vars live in `.env.development` / `.env.production`.
+
+---
+
+## Repository map (where to look)
+
+| Topic | Location |
+|-------|-----------|
+| Actions | `packages/excalidraw/actions/` — `manager.tsx`, `types.ts`, `action*.ts(x)` |
+| Main editor | `packages/excalidraw/components/App.tsx` |
+| Canvases | `packages/excalidraw/components/canvases/` |
+| Renderers | `packages/excalidraw/renderer/` |
+| Serialization / reconcile | `packages/excalidraw/data/` — `json.ts`, `restore.ts`, `reconcile.ts`, `encryption.ts` |
+| Collab | `excalidraw-app/collab/` |
+| App persistence | `excalidraw-app/data/` |
+| i18n (library UI) | `packages/excalidraw/locales/` + `useI18n` |
+| Tests setup | Root `setupTests.ts`, `vitest.config.mts` |
+| Onboarding docs | `dev-docs/docs/a-docs/README.md` (index) |
+| Agent rules | `.cursor/rules/*.mdc` |
+| Slash-command prompts | `.cursor/commands/*.md` |
+
+---
+
+## Code style & conventions
+
+- **TypeScript:** **`strict: true`** (root `tsconfig.json`). Use **`import type { … }`** for type-only imports (ESLint enforced).
+- **React:** Prefer **functional components** and hooks for **new UI**; the core **`App`** remains a **class** — follow existing patterns when touching it.
+- **Exports:** Prefer **named exports** for new modules; match neighboring files when editing legacy code.
+- **Imports:** Use path aliases **`@excalidraw/common`**, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils`. **Inside `packages/excalidraw`**, do **not** import from the package barrel **`index.tsx`** — import the **specific module**.
+- **Jotai:** Use **`packages/excalidraw/editor-jotai.ts`** or **`excalidraw-app/app-jotai.ts`** — do not import **`jotai`** directly in feature code.
+- **Styling:** SCSS, often colocated (e.g. `Component.scss`). Library styles frequently nest under **`.excalidraw`**.
+- **Naming:** Components **PascalCase**; utilities/modules **camelCase**; actions **`actionName.ts`**; tests **`*.test.ts(x)`**.
+- **i18n:** No hardcoded English for **library** user-visible strings — use **`locales/`** and **`t(...)`**.
+
+---
+
+## State & data rules
+
+- **Elements are immutable** — use **`mutateElement`** (and correct **version** / **versionNonce** behavior). Direct field assignment breaks render optimization, history, and collaboration.
+- **Z-order:** Fractional **`index`** on elements; prefer **`Scene`** APIs for reordering.
+- **Schema changes:** Update **`types.ts`**, **`newElement.ts`**, **`restore.ts`**, and rendering/export as needed.
+- **Collaboration:** Be careful with **`reconcileElements`** and **tombstones** (`isDeleted`); test concurrent editing when changing element structure or versioning.
+
+---
+
+## Testing
+
+- **Vitest** + **jsdom** + **@testing-library/react**; **`packages/excalidraw/tests/test-utils`** (`render`, `unmountComponent`) and helpers **`API`**, **`Keyboard`**, etc.
+- Run **`yarn test:update`** when snapshots change intentionally; **review snapshot diffs** before committing.
+- Path aliases in tests must match **`vitest.config.mts`**.
+
+---
+
+## Constraints & non-goals
+
+- **Package manager:** Yarn Classic workspaces — do not switch to npm or Yarn Berry without an explicit team decision.
+- **Dependencies:** Avoid new **heavy** dependencies in **`packages/excalidraw`** without considering **bundle size** and **`.github/workflows/size-limit.yml`**. Discuss significant additions.
+- **No direct mutation** of element objects or ad-hoc editor state that bypasses **actions** when the change should be **undoable** and consistent.
+- **SSR / embeds:** Avoid browser-only APIs on code paths used by **Next.js** (or other SSR) without guards or dynamic import patterns consistent with the codebase.
+- **Security:** Do not log full **share URLs** or move **encryption keys** from the URL **hash** to query strings.
+
+---
+
+## Contributing & external docs
+
+- **`CONTRIBUTING.md`** points to [Excalidraw contributing docs](https://docs.excalidraw.com/docs/introduction/contributing).
+- First-time deep dive: start with **`dev-docs/docs/a-docs/09-tldr-new-devs.md`**, then **`01-architecture-overview.md`**, **`03-codebase-navigation.md`**, **`08-adding-features.md`**.
+
+---
+
+## Cursor integration (this repo)
+
+- **Rules:** `.cursor/rules/` — monorepo layers, imports, element model, actions, data/collab, app, rendering, testing, UI (each includes **How to verify** where relevant).
+- **Commands:** `.cursor/commands/` — e.g. **`update-state-flow`**, **`new-action`**, **`pre-pr-check`**, **`element-schema-change`**, **`add-locale-string`**, **`debug-collab`**, **`rendering-change`**, etc. Invoke via **`/`** in Cursor chat.
+
+When answering in this project, prefer citing **real paths** and **existing patterns** over generic React advice.
From 82f00c2bf5806f69d1541a24205eda5999c0c983 Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Wed, 8 Apr 2026 23:05:34 +0200
Subject: [PATCH 4/6] Added documented result of validation
---
.../excalidraw-element-model-ab-validation.md | 82 +++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 dev-docs/docs/rule-validation/excalidraw-element-model-ab-validation.md
diff --git a/dev-docs/docs/rule-validation/excalidraw-element-model-ab-validation.md b/dev-docs/docs/rule-validation/excalidraw-element-model-ab-validation.md
new file mode 100644
index 0000000..f07cfd3
--- /dev/null
+++ b/dev-docs/docs/rule-validation/excalidraw-element-model-ab-validation.md
@@ -0,0 +1,82 @@
+# A/B rule validation: `excalidraw-element-model.mdc`
+
+This document records a **Rule Validation: A/B method** check for the Cursor rule
+`.cursor/rules/excalidraw-element-model.mdc`.
+
+**Method (summary):**
+
+1. **A — Rule ON:** Same prompt with the rule active (`.mdc` present, globs match).
+2. **B — Rule OFF:** Rename rule to `.mdc.off` (or otherwise disable), **same prompt**.
+3. **Compare** outputs → conclude **rule works** vs **rewrite rule**.
+
+---
+
+## Rule under test
+
+| Field | Value |
+|-------|--------|
+| File | `.cursor/rules/excalidraw-element-model.mdc` |
+| Globs | `packages/element/**/*.ts`, `packages/element/**/*.tsx` |
+| `alwaysApply` | `false` |
+
+**Intent of the rule:** Immutable elements, use `mutateElement`, Scene for z-order, schema → `types` / `newElement` / `restore`, collaboration versioning.
+
+---
+
+## Documented test scenario
+
+| Item | Value |
+|------|--------|
+| **Context** | User attached `@packages/element/src/mutateElement.ts` and had element-package context (glob match). |
+| **Prompt** | “How should I move a rectangle in Excalidraw’s element package?” |
+
+---
+
+## Result A (rule ON)
+
+- **Debug line:** Reply began with
+ `RULE excalidraw-element-model APPLIED 🧱`
+ (per **Important (debug)** section in the rule).
+- **Framing:** Opened with not relying on raw `element.x = …` alone; tied behavior to **collaboration, history, and version / versionNonce / updated** early.
+- **Structure:** Numbered sections — (1) core `mutateElement`, (2) `Scene.mutateElement` for React updates, (3) `newElementWith`.
+- **Technical content:** `mutateElement(element, elementsMap, { x, y })`, cited warning about re-renders, cited `Scene.mutateElement` + `triggerUpdate`, cited `newElementWith`, code references to `packages/element/src/mutateElement.ts` and `Scene.ts`.
+
+---
+
+## Result B (rule OFF)
+
+- **Debug line:** **Absent**; reply noted that **`excalidraw-element-model`** appeared disabled (e.g. `excalidraw-element-model.off`) and that the rule’s debug line was therefore skipped.
+- **Framing:** Went straight into **how** to move (x/y, `mutateElement`), then re-render caveat, then immutable alternative.
+- **Structure:** Headings “Move a rectangle”, “Re-renders in the editor”, “Immutable alternative”, closing “Avoid …”.
+- **Technical content:** **Same** APIs, same files, same citations — `mutateElement`, `elementsMap`, `Scene.mutateElement`, `newElementWith`, avoid `rectangle.x = …` without versioning.
+
+---
+
+## Comparison
+
+| Dimension | A (rule ON) | B (rule OFF) |
+|-----------|-------------|--------------|
+| **RULE … APPLIED line** | Yes | No |
+| **Upfront immutability / collab story** | Stronger, earlier | Present but later / lighter |
+| **Correct move API (`mutateElement`, `Scene`)** | Yes | Yes |
+| **Substantive technical difference** | **Small** for this prompt | — |
+
+---
+
+## Conclusion
+
+- **The rule clearly “fires” from a validation perspective** when the **debug line** appears in A and not in B — that is a reliable **A/B signal**.
+- For **this specific prompt**, the **substantive** answers were **largely the same**, because **`mutateElement.ts`** (attached / in context) already documents the correct pattern, version bumps, and the “use `scene.mutateElement` for component updates” warning. The model does not need the `.mdc` text to give a correct low-level answer in that situation.
+- **Recommendation for future A/B tests of this rule:** use a prompt **not fully answered by a single open file**, for example:
+ - *“I’m adding a new optional field on frame elements; list every file to touch and in what order.”*
+ Here the rule’s **`restore.ts`**, **`types.ts`**, **`newElement.ts`**, rendering/export, and collab/version checklist should create a **larger** gap between rule ON vs OFF.
+
+**Verdict:** **Rule works** for **enforcement visibility** (debug banner + framing). For **content-only** checks, pair with prompts where the rule adds information **beyond** the currently focused source file.
+
+---
+
+## Related
+
+- Other project rules: `.cursor/rules/*.mdc`
+- Onboarding / element conventions: `dev-docs/docs/a-docs/` (e.g. `08-adding-features.md`, `07-common-pitfalls.md`)
+- Agent overview: `AGENT.md` (repo root)
From adeafbe363854d147aef458a076d4d594cb5c48c Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Thu, 9 Apr 2026 10:10:26 +0200
Subject: [PATCH 5/6] Rename AGENT.md to AGENTS.md
---
AGENT.md => AGENTS.md | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename AGENT.md => AGENTS.md (100%)
diff --git a/AGENT.md b/AGENTS.md
similarity index 100%
rename from AGENT.md
rename to AGENTS.md
From eec5b434b92a24222990211d7a45871036295a9b Mon Sep 17 00:00:00 2001
From: andriiyanchak <97438890+andriiyanchak@users.noreply.github.com>
Date: Thu, 9 Apr 2026 10:11:01 +0200
Subject: [PATCH 6/6] Fix filename from AGENT.md to AGENTS.md
---
AGENTS.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/AGENTS.md b/AGENTS.md
index 6be53df..20be8d0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -1,4 +1,4 @@
-# AGENT.md
+# AGENTS.md
Persistent context for AI coding agents and developers working in this repository. Aligns with **`dev-docs/docs/a-docs/`**, **`.cursor/rules/`**, and project tooling.