diff --git a/.env.development b/.env.development
index 387ab6204..bf641c34c 100644
--- a/.env.development
+++ b/.env.development
@@ -1,3 +1,5 @@
+MODE="development"
+
VITE_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/
diff --git a/.env.production b/.env.production
index 11e9fd84b..72dd24d6e 100644
--- a/.env.production
+++ b/.env.production
@@ -1,3 +1,5 @@
+MODE="production"
+
VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
diff --git a/.eslintrc.json b/.eslintrc.json
index 8263b08a9..89f822736 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -32,6 +32,12 @@
"name": "jotai",
"message": "Do not import from \"jotai\" directly. Use our app-specific modules (\"editor-jotai\" or \"app-jotai\")."
}
+ ],
+ "react/jsx-no-target-blank": [
+ "error",
+ {
+ "allowReferrer": true
+ }
]
}
}
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 000000000..aebac52a0
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,45 @@
+# Project coding standards
+
+## Generic Communication Guidelines
+
+- Be succint and be aware that expansive generative AI answers are costly and slow
+- Avoid providing explanations, trying to teach unless asked for, your chat partner is an expert
+- Stop apologising if corrected, just provide the correct information or code
+- Prefer code unless asked for explanation
+- Stop summarizing what you've changed after modifications unless asked for
+
+## TypeScript Guidelines
+
+- Use TypeScript for all new code
+- Where possible, prefer implementations without allocation
+- When there is an option, opt for more performant solutions and trade RAM usage for less CPU cycles
+- Prefer immutable data (const, readonly)
+- Use optional chaining (?.) and nullish coalescing (??) operators
+
+## React Guidelines
+
+- Use functional components with hooks
+- Follow the React hooks rules (no conditional hooks)
+- Keep components small and focused
+- Use CSS modules for component styling
+
+## Naming Conventions
+
+- Use PascalCase for component names, interfaces, and type aliases
+- Use camelCase for variables, functions, and methods
+- Use ALL_CAPS for constants
+
+## Error Handling
+
+- Use try/catch blocks for async operations
+- Implement proper error boundaries in React components
+- Always log errors with contextual information
+
+## Testing
+
+- Always attempt to fix #problems
+- Always offer to run `yarn test:app` in the project root after modifications are complete and attempt fixing the issues reported
+
+## Types
+
+- Always include `packages/math/src/types.ts` in the context when your write math related code and always use the Point type instead of { x, y}
diff --git a/.github/workflows/autorelease-excalidraw.yml b/.github/workflows/autorelease-excalidraw.yml
index 5ff5690eb..6e2c0d00e 100644
--- a/.github/workflows/autorelease-excalidraw.yml
+++ b/.github/workflows/autorelease-excalidraw.yml
@@ -24,4 +24,4 @@ jobs:
- name: Auto release
run: |
yarn add @actions/core -W
- yarn autorelease
+ yarn release --tag=next --non-interactive
diff --git a/.github/workflows/autorelease-preview.yml b/.github/workflows/autorelease-preview.yml
deleted file mode 100644
index a40ed3c43..000000000
--- a/.github/workflows/autorelease-preview.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-name: Auto release excalidraw preview
-on:
- issue_comment:
- types: [created, edited]
-
-jobs:
- Auto-release-excalidraw-preview:
- name: Auto release preview
- if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request
- runs-on: ubuntu-latest
- steps:
- - name: React to release comment
- uses: peter-evans/create-or-update-comment@v1
- with:
- token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
- comment-id: ${{ github.event.comment.id }}
- reactions: "+1"
- - name: Get PR SHA
- id: sha
- uses: actions/github-script@v4
- with:
- result-encoding: string
- script: |
- const { owner, repo, number } = context.issue;
- const pr = await github.pulls.get({
- owner,
- repo,
- pull_number: number,
- });
- return pr.data.head.sha
- - uses: actions/checkout@v2
- with:
- ref: ${{ steps.sha.outputs.result }}
- fetch-depth: 2
- - name: Setup Node.js 18.x
- uses: actions/setup-node@v2
- with:
- node-version: 18.x
- - name: Set up publish access
- run: |
- npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
- env:
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- - name: Auto release preview
- id: "autorelease"
- run: |
- yarn add @actions/core -W
- yarn autorelease preview ${{ github.event.issue.number }}
- - name: Post comment post release
- if: always()
- uses: peter-evans/create-or-update-comment@v1
- with:
- token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
- issue-number: ${{ github.event.issue.number }}
- body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}"
diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml
index a4a8a4c5f..68eee2775 100644
--- a/.github/workflows/publish-docker.yml
+++ b/.github/workflows/publish-docker.yml
@@ -17,9 +17,14 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
- name: Build and push
- uses: docker/build-push-action@v3
+ uses: docker/build-push-action@v5
with:
context: .
push: true
tags: excalidraw/excalidraw:latest
+ platforms: linux/amd64, linux/arm64, linux/arm/v7
diff --git a/.gitignore b/.gitignore
index 6f9407fad..6f3a62bba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,5 @@ packages/excalidraw/types
coverage
dev-dist
html
-meta*.json
\ No newline at end of file
+meta*.json
+.claude
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..4faf291ec
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,34 @@
+# CLAUDE.md
+
+## Project Structure
+
+Excalidraw is a **monorepo** with a clear separation between the core library and the application:
+
+- **`packages/excalidraw/`** - Main React component library published to npm as `@excalidraw/excalidraw`
+- **`excalidraw-app/`** - Full-featured web application (excalidraw.com) that uses the library
+- **`packages/`** - Core packages: `@excalidraw/common`, `@excalidraw/element`, `@excalidraw/math`, `@excalidraw/utils`
+- **`examples/`** - Integration examples (NextJS, browser script)
+
+## Development Workflow
+
+1. **Package Development**: Work in `packages/*` for editor features
+2. **App Development**: Work in `excalidraw-app/` for app-specific features
+3. **Testing**: Always run `yarn test:update` before committing
+4. **Type Safety**: Use `yarn test:typecheck` to verify TypeScript
+
+## Development Commands
+
+```bash
+yarn test:typecheck # TypeScript type checking
+yarn test:update # Run all tests (with snapshot updates)
+yarn fix # Auto-fix formatting and linting issues
+```
+
+## Architecture Notes
+
+### Package System
+
+- Uses Yarn workspaces for monorepo management
+- Internal packages use path aliases (see `vitest.config.mts`)
+- Build system uses esbuild for packages, Vite for the app
+- TypeScript throughout with strict configuration
diff --git a/Dockerfile b/Dockerfile
index 2716803fa..c08385d65 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:18 AS build
+FROM --platform=${BUILDPLATFORM} node:18 AS build
WORKDIR /opt/node_app
@@ -6,13 +6,14 @@ COPY . .
# do not ignore optional dependencies:
# Error: Cannot find module @rollup/rollup-linux-x64-gnu
-RUN yarn --network-timeout 600000
+RUN --mount=type=cache,target=/root/.cache/yarn \
+ npm_config_target_arch=${TARGETARCH} yarn --network-timeout 600000
ARG NODE_ENV=production
-RUN yarn build:app:docker
+RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
-FROM nginx:1.27-alpine
+FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine
COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
diff --git a/README.md b/README.md
index 9458779ce..f1cf03053 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,9 @@
+
+
+
@@ -63,7 +66,7 @@ The Excalidraw editor (npm package) supports:
- ποΈ Customizable.
- π· Image support.
- π Shape libraries support.
-- π
Localization (i18n) support.
+- π Localization (i18n) support.
- πΌοΈ Export to PNG, SVG & clipboard.
- πΎ Open format - export drawings as an `.excalidraw` json file.
- βοΈ Wide range of tools - rectangle, circle, diamond, arrow, line, free-draw, eraser...
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx
index 3831268f0..e7852cee9 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/children-components/footer.mdx
@@ -2,7 +2,7 @@
Earlier we were using `renderFooter` prop to render custom footer which was removed in [#5970](https://github.com/excalidraw/excalidraw/pull/5970). Now you can pass a `Footer` component instead to render the custom UI for footer.
-You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should a valid React Node.
+You will need to import the `Footer` component from the package and wrap your component with the Footer component. The `Footer` should be a valid React Node.
**Usage**
@@ -25,7 +25,7 @@ function App() {
}
```
-This will only for `Desktop` devices.
+This will only work for `Desktop` devices.
For `mobile` you will need to render it inside the [MainMenu](#mainmenu). You can use the [`useDevice`](#useDevice) hook to check the type of device, this will be available only inside the `children` of `Excalidraw` component.
@@ -65,4 +65,4 @@ const App = () => (
// Need to render when code is span across multiple components
// in Live Code blocks editor
render();
-```
\ No newline at end of file
+```
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
index b7a3bab5f..c9580b66b 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
@@ -363,13 +363,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
```ts
(
tool: (
- | (
- | { type: Exclude }
- | {
- type: Extract;
- insertOnCanvasDirectly?: boolean;
- }
- )
+ | { type: ToolType }
| { type: "custom"; customType: string }
) & { locked?: boolean },
) => {};
@@ -377,7 +371,7 @@ This API has the below signature. It sets the `tool` passed in param as the acti
| Name | Type | Default | Description |
| --- | --- | --- | --- |
-| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool. When setting `image` as active tool, the insertion onto canvas when using image tool is disabled by default, so you can enable it by setting `insertOnCanvasDirectly` to `true` |
+| `type` | [ToolType](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/types.ts#L91) | `selection` | The tool type which should be set as active tool |
| `locked` | `boolean` | `false` | Indicates whether the the active tool should be locked. It behaves the same way when using the `lock` tool in the editor interface |
## setCursor
diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
index 5c2a5501b..607e97182 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/props.mdx
@@ -31,6 +31,7 @@ All `props` are _optional_.
| [`generateIdForFile`](#generateidforfile) | `function` | \_ | Allows you to override `id` generation for files added on canvas |
| [`validateEmbeddable`](#validateembeddable) | `string[]` \| `boolean` \| `RegExp` \| `RegExp[]` \| ((link: string) => boolean | undefined) | \_ | use for custom src url validation |
| [`renderEmbeddable`](/docs/@excalidraw/excalidraw/api/props/render-props#renderEmbeddable) | `function` | \_ | Render function that can override the built-in `