diff --git a/.codesandbox/Dockerfile b/.codesandbox/Dockerfile
new file mode 100644
index 0000000000..fd5b38d1e8
--- /dev/null
+++ b/.codesandbox/Dockerfile
@@ -0,0 +1,5 @@
+FROM node:18-bullseye
+
+# Vite wants to open the browser using `open`, so we
+# need to install those utils.
+RUN apt update -y && apt install -y xdg-utils
diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json
index 360636c4c0..51c6e4e16f 100644
--- a/.codesandbox/tasks.json
+++ b/.codesandbox/tasks.json
@@ -27,7 +27,10 @@
"start": {
"name": "Start Excalidraw",
"command": "yarn start",
- "runAtStart": true
+ "runAtStart": true,
+ "preview": {
+ "port": 3000
+ }
},
"test": {
"name": "Run Tests",
@@ -37,7 +40,11 @@
"install-deps": {
"name": "Install Dependencies",
"command": "yarn install",
- "restartOn": { "files": ["yarn.lock"] }
+ "restartOn": {
+ "files": ["yarn.lock"],
+ "branch": false,
+ "resume": false
+ }
}
}
}
diff --git a/.dockerignore b/.dockerignore
index 7a01509475..6472839a78 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -4,8 +4,16 @@
!.eslintrc.json
!.npmrc
!.prettierrc
+!excalidraw-app/
!package.json
!public/
-!src/
+!packages/
+!scripts/
!tsconfig.json
!yarn.lock
+
+# keep (sub)sub directories at the end to exclude from explicit included
+# e.g. ./packages/excalidraw/{dist,node_modules}
+**/build
+**/dist
+**/node_modules
diff --git a/.env.development b/.env.development
index c56b62b360..bf641c34c8 100644
--- a/.env.development
+++ b/.env.development
@@ -1,30 +1,55 @@
-REACT_APP_BACKEND_V2_GET_URL=https://json-dev.excalidraw.com/api/v2/
-REACT_APP_BACKEND_V2_POST_URL=https://json-dev.excalidraw.com/api/v2/post/
+MODE="development"
-REACT_APP_LIBRARY_URL=https://libraries.excalidraw.com
-REACT_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
+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/
+
+VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
+VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
# collaboration WebSocket server (https://github.com/excalidraw/excalidraw-room)
-REACT_APP_WS_SERVER_URL=http://localhost:3002
+VITE_APP_WS_SERVER_URL=http://localhost:3002
-# set this only if using the collaboration workflow we use on excalidraw.com
-REACT_APP_PORTAL_URL=
+VITE_APP_PLUS_LP=https://plus.excalidraw.com
+VITE_APP_PLUS_APP=http://localhost:3000
-REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
+VITE_APP_AI_BACKEND=http://localhost:3015
+
+VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyCMkxA60XIW8KbqMYL7edC4qT5l4qHX2h8","authDomain":"excalidraw-oss-dev.firebaseapp.com","projectId":"excalidraw-oss-dev","storageBucket":"excalidraw-oss-dev.appspot.com","messagingSenderId":"664559512677","appId":"1:664559512677:web:a385181f2928d328a7aa8c"}'
# put these in your .env.local, or make sure you don't commit!
# must be lowercase `true` when turned on
#
-# whether to enable Service Workers in development
-REACT_APP_DEV_ENABLE_SW=
# whether to disable live reload / HMR. Usuaully what you want to do when
# debugging Service Workers.
-REACT_APP_DEV_DISABLE_LIVE_RELOAD=
-REACT_APP_DISABLE_TRACKING=true
+VITE_APP_DEV_DISABLE_LIVE_RELOAD=
+VITE_APP_ENABLE_TRACKING=true
FAST_REFRESH=false
+# The port the run the dev server
+VITE_APP_PORT=3000
+
#Debug flags
# To enable bounding box for text containers
-REACT_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=
+VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=
+
+# Set this flag to false if you want to open the overlay by default
+VITE_APP_COLLAPSE_OVERLAY=true
+
+# Set this flag to false to disable eslint
+VITE_APP_ENABLE_ESLINT=true
+
+# Enable PWA in dev server
+VITE_APP_ENABLE_PWA=false
+
+VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm2g5T+Rub6Kbf1Mf57t0
+7r2zeHuVg4dla3r5ryXMswtzz6x767octl6oLThn33mQsPSy3GKglFZoCTXJR4ij
+ba8SxB04sL/N8eRrKja7TFWjCVtRwTTfyy771NYYNFVJclkxHyE5qw4m27crHF1y
+UNWEjuqNMi/lwAErS9fFa2oJlWyT8U7zzv/5kQREkxZI6y9v0AF3qcbsy2731FnD
+s9ChJvOUW9toIab2gsIdrKW8ZNpu084ZFVKb6LNjvIXI1Se4oMTHeszXzNptzlot
+kdxxjOoaQMAyfljFSot1F1FlU6MQlag7UnFGvFjRHN1JI5q4K+n3a67DX+TMyRqS
+HQIDAQAB'
+
+# set to true in .env.development.local to disable the prevent unload dialog
+VITE_APP_DISABLE_PREVENT_UNLOAD=
diff --git a/.env.production b/.env.production
index b86aa4bcc5..72dd24d6ec 100644
--- a/.env.production
+++ b/.env.production
@@ -1,15 +1,34 @@
-REACT_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
-REACT_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
+MODE="production"
-REACT_APP_LIBRARY_URL=https://libraries.excalidraw.com
-REACT_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
+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/
-REACT_APP_PORTAL_URL=https://portal.excalidraw.com
-# Fill to set socket server URL used for collaboration.
-# Meant for forks only: excalidraw.com uses custom REACT_APP_PORTAL_URL flow
-REACT_APP_WS_SERVER_URL=
+VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
+VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
-REACT_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
+VITE_APP_PLUS_LP=https://plus.excalidraw.com
+VITE_APP_PLUS_APP=https://app.excalidraw.com
+
+VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
+
+# socket server URL used for collaboration
+VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
+
+VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
+
+VITE_APP_ENABLE_TRACKING=false
+
+VITE_APP_PLUS_EXPORT_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApQ0jM9Qz8TdFLzcuAZZX
+/WvuKSOJxiw6AR/ZcE3eFQWM/mbFdhQgyK8eHGkKQifKzH1xUZjCxyXcxW6ZO02t
+kPOPxhz+nxUrIoWCD/V4NGmUA1lxwHuO21HN1gzKrN3xHg5EGjyouR9vibT9VDGF
+gq6+4Ic/kJX+AD2MM7Yre2+FsOdysrmuW2Fu3ahuC1uQE7pOe1j0k7auNP0y1q53
+PrB8Ts2LUpepWC1l7zIXFm4ViDULuyWXTEpUcHSsEH8vpd1tckjypxCwkipfZsXx
+iPszy0o0Dx2iArPfWMXlFAI9mvyFCyFC3+nSvfyAUb2C4uZgCwAuyFh/ydPF4DEE
+PQIDAQAB'
+
+# Set the below flags explicitly to false in production mode since vite loads and merges .env.local vars when running the build command
+VITE_APP_DEBUG_ENABLE_TEXT_CONTAINER_BOUNDING_BOX=false
+VITE_APP_COLLAPSE_OVERLAY=false
+# Enable eslint in dev server
+VITE_APP_ENABLE_ESLINT=false
-REACT_APP_PLUS_APP=https://app.excalidraw.com
-REACT_APP_DISABLE_TRACKING=
diff --git a/.eslintignore b/.eslintignore
index b238ce5f7b..8b4f458dee 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -5,4 +5,7 @@ package-lock.json
firebase/
dist/
public/workbox
-src/packages/excalidraw/types
+packages/excalidraw/types
+examples/**/public
+dev-dist
+coverage
diff --git a/.eslintrc.json b/.eslintrc.json
index fbb12f59d4..89f8227361 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,43 @@
{
"extends": ["@excalidraw/eslint-config", "react-app"],
"rules": {
+ "import/order": [
+ "warn",
+ {
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"],
+ "pathGroups": [
+ {
+ "pattern": "@excalidraw/**",
+ "group": "external",
+ "position": "after"
+ }
+ ],
+ "newlines-between": "always-and-inside-groups",
+ "warnOnUnassignedImports": true
+ }
+ ],
"import/no-anonymous-default-export": "off",
- "no-restricted-globals": "off"
+ "no-restricted-globals": "off",
+ "@typescript-eslint/consistent-type-imports": [
+ "error",
+ {
+ "prefer": "type-imports",
+ "disallowTypeAnnotations": false,
+ "fixStyle": "separate-type-imports"
+ }
+ ],
+ "no-restricted-imports": [
+ "error",
+ {
+ "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/assets/logo.png b/.github/assets/logo.png
deleted file mode 100644
index d9b8953ebd..0000000000
Binary files a/.github/assets/logo.png and /dev/null differ
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000000..aebac52a08
--- /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 ad0a0a7e9c..6e2c0d00e0 100644
--- a/.github/workflows/autorelease-excalidraw.yml
+++ b/.github/workflows/autorelease-excalidraw.yml
@@ -12,10 +12,10 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 2
- - name: Setup Node.js 14.x
+ - name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
- node-version: 14.x
+ node-version: 18.x
- name: Set up publish access
run: |
npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
@@ -23,5 +23,5 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Auto release
run: |
- yarn add @actions/core
- yarn autorelease
+ yarn add @actions/core -W
+ 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 8fe7f40b58..0000000000
--- 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 14.x
- uses: actions/setup-node@v2
- with:
- node-version: 14.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
- 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/lint.yml b/.github/workflows/lint.yml
index 77d2ef4d2c..82f826361c 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -9,14 +9,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- - name: Setup Node.js 14.x
+ - name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
- node-version: 14.x
+ node-version: 18.x
- name: Install and lint
run: |
- yarn --frozen-lockfile
+ yarn install
yarn test:other
yarn test:code
yarn test:typecheck
diff --git a/.github/workflows/locales-coverage.yml b/.github/workflows/locales-coverage.yml
index 924dc9e973..957e9bc37c 100644
--- a/.github/workflows/locales-coverage.yml
+++ b/.github/workflows/locales-coverage.yml
@@ -10,23 +10,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
- - name: Setup Node.js 14.x
+ - name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
- node-version: 14.x
+ node-version: 18.x
- name: Create report file
run: |
yarn locales-coverage
- FILE_CHANGED=$(git diff src/locales/percentages.json)
+ FILE_CHANGED=$(git diff packages/excalidraw/locales/percentages.json)
if [ ! -z "${FILE_CHANGED}" ]; then
git config --global user.name 'Excalidraw Bot'
git config --global user.email 'bot@excalidraw.com'
- git add src/locales/percentages.json
+ git add packages/excalidraw/locales/percentages.json
git commit -am "Auto commit: Calculate translation coverage"
git push
fi
diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml
index a4a8a4c5ff..68eee27755 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/.github/workflows/semantic-pr-title.yml b/.github/workflows/semantic-pr-title.yml
index 969d236407..34a6413fe2 100644
--- a/.github/workflows/semantic-pr-title.yml
+++ b/.github/workflows/semantic-pr-title.yml
@@ -11,6 +11,6 @@ jobs:
semantic:
runs-on: ubuntu-latest
steps:
- - uses: amannn/action-semantic-pull-request@v3.0.0
+ - uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/sentry-production.yml b/.github/workflows/sentry-production.yml
index 6f53f91eb7..cea4cf63d6 100644
--- a/.github/workflows/sentry-production.yml
+++ b/.github/workflows/sentry-production.yml
@@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - name: Setup Node.js 14.x
+ - name: Setup Node.js 18.x
uses: actions/setup-node@v2
with:
- node-version: 14.x
+ node-version: 18.x
- name: Install and build
run: |
yarn --frozen-lockfile
diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml
index 8ced8ee037..5bd3c0d92b 100644
--- a/.github/workflows/size-limit.yml
+++ b/.github/workflows/size-limit.yml
@@ -15,16 +15,14 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18.x
- - name: Install
- run: yarn --frozen-lockfile
- - name: Install in src/packages/excalidraw
- run: yarn --frozen-lockfile
- working-directory: src/packages/excalidraw
+ - name: Install in packages/excalidraw
+ run: yarn
+ working-directory: packages/excalidraw
env:
CI: true
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- build_script: build:umd
+ build_script: build:esm
skip_step: install
- directory: src/packages/excalidraw
+ directory: packages/excalidraw
diff --git a/.github/workflows/test-coverage-pr.yml b/.github/workflows/test-coverage-pr.yml
new file mode 100644
index 0000000000..7ff40ad5d2
--- /dev/null
+++ b/.github/workflows/test-coverage-pr.yml
@@ -0,0 +1,26 @@
+name: Test Coverage PR
+on:
+ pull_request:
+
+jobs:
+ coverage:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: "Install Node"
+ uses: actions/setup-node@v2
+ with:
+ node-version: "18.x"
+ - name: "Install Deps"
+ run: yarn install
+ - name: "Test Coverage"
+ run: yarn test:coverage
+ - name: "Report Coverage"
+ if: always() # Also generate the report if tests are failing
+ uses: davelosert/vitest-coverage-report-action@v2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b64ea47352..7d454ecfc4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,17 +1,19 @@
name: Tests
-on: pull_request
+on:
+ push:
+ branches: master
jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Setup Node.js 14.x
- uses: actions/setup-node@v2
+ - uses: actions/checkout@v4
+ - name: Setup Node.js 18.x
+ uses: actions/setup-node@v4
with:
- node-version: 14.x
+ node-version: 18.x
- name: Install and test
run: |
- yarn --frozen-lockfile
+ yarn install
yarn test:app
diff --git a/.gitignore b/.gitignore
index e637a8c0f9..6f3a62bba3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,8 +21,9 @@ npm-debug.log*
package-lock.json
yarn-debug.log*
yarn-error.log*
-src/packages/excalidraw/types
-src/packages/excalidraw/example/public/bundle.js
-src/packages/excalidraw/example/public/excalidraw-assets-dev
-src/packages/excalidraw/example/public/excalidraw.development.js
+packages/excalidraw/types
coverage
+dev-dist
+html
+meta*.json
+.claude
diff --git a/.nvmrc b/.nvmrc
index 8351c19397..3c032078a4 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-14
+18
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index a6506e9a0f..0000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## 2020-10-13
-
-- Added ability to embed scene source into exported PNG/SVG files so you can import the scene from them (open via `Load` button or drag & drop). #2219
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..4faf291ec8
--- /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 d1fa424e53..c08385d654 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,20 @@
-FROM node:14-alpine AS build
+FROM --platform=${BUILDPLATFORM} node:18 AS build
WORKDIR /opt/node_app
-COPY package.json yarn.lock ./
-RUN yarn --ignore-optional --network-timeout 600000
+COPY . .
+
+# do not ignore optional dependencies:
+# Error: Cannot find module @rollup/rollup-linux-x64-gnu
+RUN --mount=type=cache,target=/root/.cache/yarn \
+ npm_config_target_arch=${TARGETARCH} yarn --network-timeout 600000
ARG NODE_ENV=production
-COPY . .
-RUN yarn build:app:docker
+RUN npm_config_target_arch=${TARGETARCH} yarn build:app:docker
-FROM nginx:1.21-alpine
+FROM --platform=${TARGETPLATFORM} nginx:1.27-alpine
-COPY --from=build /opt/node_app/build /usr/share/nginx/html
+COPY --from=build /opt/node_app/excalidraw-app/build /usr/share/nginx/html
HEALTHCHECK CMD wget -q -O /dev/null http://localhost || exit 1
diff --git a/README.md b/README.md
index 48529165e6..f1cf030539 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
+