Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .cursor/commands/upgrade-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: >-
Upgrade Yarn deps (root + expo-example) aggressively to latest compatible
versions (majors OK), fix peers and native stack, verify build/lint, sync
README and docs
---

# upgrade-dependencies

Run **`.cursor/skills/upgrade-dependencies/SKILL.md`**.

**Strategy:** prefer **latest** versions **including majors** (e.g. `2.3.4` → `3.x`), not only minor bumps—then fix **peer/native conflicts** (Reanimated + worklets, Expo SDK matrix, Skia, `bob` types paths). Use **`yarn upgrade-interactive --latest`** and **`npx expo install --fix`** after Expo SDK changes. Finish with **`yarn build`**, ESLint, **`tsc`**, **`expo-doctor`**, and updates to **README.md** (install peers) and **docs/PROJECT_OVERVIEW.md** (versions + scripts).
30 changes: 30 additions & 0 deletions .cursor/rules/react-native-free-canvas.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
description: Conventions for react-native-free-canvas library (Skia, Reanimated, gestures)
globs: src/**/*.{ts,tsx}
alwaysApply: false
---

# react-native-free-canvas

## Stack

- Skia (`@shopify/react-native-skia`) for `Canvas` / `Path` / snapshots.
- Reanimated shared values and worklets; `react-native-gesture-handler` for pan/pinch.
- Use `scheduleOnRN` from `react-native-worklets` when calling React state setters from gesture/worklet code or animated reactions (not `runOnJS`, which is deprecated). See `drawing-canvas.tsx` and `index.tsx`.

## Style (match repo)

- Prettier: single quotes, trailing commas, avoid arrow parens when single arg.
- Prefer `StyleSheet` from `styles.ts` over new inline style objects on hot paths (README calls this out for `flex: 1`).
- Components: `forwardRef` + `memo` for canvas pieces; co-locate prop types in `types.ts` for public API.
- Use `import type` for type-only imports.
- Mark UI-thread callbacks with `'worklet'` where required.

## Architecture

- Do not edit `lib/` — run `yarn build` after changing `src/`.
- Two layers: `DrawingCanvas` (live stroke + gestures) over `DrawnCanvas` (committed paths). Context in `canvas-context.ts`; path completion sync uses `promises-delivery`.

## Types / API

- Extend `FreeCanvasProps` / `FreeCanvasRef` in `types.ts` when adding props or ref methods; export types from `index.tsx` as today.
51 changes: 51 additions & 0 deletions .cursor/skills/commit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: commit
description: >-
Prepares Conventional Commit messages, verifies scope, and commits changes in
react-native-free-canvas. Use when the user asks to commit, save work, or
finalize a change set before push or PR.
---

# Commit (react-native-free-canvas)

## When to apply

- User asks to **commit**, **stage**, or **write a commit message**.
- Wrapping up a feature/fix before **push** or **PR**.

## Workflow

1. **Inspect**
- Run `git status -sb` and `git diff` (and `git diff --staged` if anything is already staged).
- Confirm only intentional paths are included (no secrets, `.env`, local `.pack/` tarballs, or accidental `node_modules` edits).

2. **Verify (when library source or build output changed)**
- If `src/` changed: from repo root run `yarn build`, `yarn eslint src --max-warnings 0`, and `npx tsc --noEmit -p tsconfig.json`.
- If only docs, `.npmignore`, `.cursor/`, or metadata changed, skip the full build unless the user asks.

3. **Stage**
- `git add` only the paths that belong in this commit. Prefer **one logical commit** per request; split if the diff mixes unrelated concerns.

4. **Message format (Conventional Commits)**

```
type(scope): imperative subject under ~72 characters

Optional body: what changed and why; breaking changes noted here.
```

**Types** (common in this repo): `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `build`, `ci`, `revert`.

**Scopes** (examples): `publish`, `deps`, `expo-example`, `canvas`, `eslint`, `cursor`, `bob`.

- Subject: **imperative**, no trailing period, no vague “update” without context.
- **Breaking changes**: add `!` after scope/type or a `BREAKING CHANGE:` footer.

5. **Commit**
- `git commit -m "type(scope): subject"` or use `-m` twice for subject + body.
- Do not amend or force-push unless the user explicitly asks.

## What not to do

- Do not commit generated `lib/` unless it is the deliberate outcome of `yarn build` for a release (this repo normally commits `lib/` when `src/` changes; follow existing practice on the branch).
- Do not bundle unrelated refactors with a focused fix in one commit without user approval.
55 changes: 55 additions & 0 deletions .cursor/skills/run-expo-example/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
name: run-expo-example
description: >-
Run the Expo example app that consumes react-native-free-canvas from ../src.
Use when manually testing the library UI, after dependency upgrades, or when
verifying the example still starts with Metro.
---

# Run Expo example (test the library)

## How the example depends on the library

`expo-example/package.json` uses **`"react-native-free-canvas": "../src"`** so Metro bundles **library source** from the repo. Run **`yarn build`** at the repo root when you need **`lib/`** artifacts (publish or type consumers); the example does not require the tarball for day-to-day UI work.

**Caveat:** If Metro ever resolves duplicate native copies of Skia/Reanimated, prefer a **packed install** (`npm pack` + `file:../.pack/…tgz`) for a consumer-faithful test (see **upgrade-dependencies** skill).

## Commands

**One command from repo root (install + start):**

```bash
yarn demo
```

**Platform shortcuts:**

```bash
yarn demo:ios
yarn demo:android
```

**Manual (same as `yarn demo` without the single alias):**

```bash
yarn --cwd expo-example install && yarn --cwd expo-example start
```

After **native** or **Babel** dependency changes:

```bash
cd expo-example && npx expo start --clear
```

## Verify library build (optional)

From repo root:

```bash
yarn build
yarn eslint src --max-warnings 0
```

## Outside this repo

From the library root: `yarn build && npm pack`, then in any app: `yarn add file:/absolute/path/to/react-native-free-canvas-<version>.tgz`.
119 changes: 119 additions & 0 deletions .cursor/skills/upgrade-dependencies/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
name: upgrade-dependencies
description: >-
Upgrades Yarn dependencies for the library root and expo-example, resolves
version skew (Skia / Reanimated / Worklets / RN / Expo), verifies build and
lint, and updates README and docs/PROJECT_OVERVIEW.md. Use when bumping
packages, fixing peer conflicts, after Expo SDK upgrades, or when the user
asks to upgrade or align dependencies.
---

# Upgrade dependencies (react-native-free-canvas)

## Upgrade strategy: **aggressive (latest first)**

Default to **latest stable** versions, **including majors** (e.g. `2.3.4` → `3.1.0`, not `2.5.0`), unless something in the stack forbids it.

- **Goal:** maximize freshness in one pass; avoid timid “minor-only” bumps when a newer major is compatible.
- **Constraint:** after each wave of bumps, the tree must have **no unresolved peer dependency errors** and **no broken native stack** (rules below). Fix conflicts by adjusting pins or upgrading the whole related group (e.g. Expo SDK + RN together), not by indefinitely staying on old majors “to be safe.”
- **Expo example caveat:** packages **managed by Expo** for a given SDK (RN, Skia, Reanimated, worklets, gesture-handler, etc.) must stay **compatible with that SDK** until you **upgrade the Expo SDK** (then go aggressive on the new SDK’s matrix). For everything else (navigation, dev tools, non-Expo-pinned libs), prefer **latest** like the root.
- **Library root:** devDependencies may sit **newer** than the example (e.g. newer RN / Skia for `bob` + types) as long as **`yarn build`**, **`eslint`**, and **`tsc`** pass and peer rules are satisfied.

## Scope

Two workspaces (run commands from **repo root** unless noted):

| Workspace | Path | Role |
|-----------|------|------|
| Library | `.` | `devDependencies` pin what `bob build` and ESLint typecheck against |
| Example | `expo-example/` | Expo app; native deps must match **Expo SDK** until SDK is upgraded |

Package manager: **Yarn 1** (`packageManager` in root `package.json`).

## Workflow (agent checklist)

1. **Inventory**
- Read root `package.json` (`devDependencies`, `peerDependencies`, `dependencies`, `scripts`).
- Read `expo-example/package.json` (`dependencies`, `devDependencies`).
- Note: library source imports **`react-native-worklets`** (`scheduleOnRN` in `src/drawing-canvas.tsx`); consumers need a compatible **worklets** version with **Reanimated**.

2. **Choose upgrade target (aggressive)**
- Run **`yarn outdated`** (root and `expo-example/`) and **`npm view <pkg> version`** / release notes when a major jump is risky.
- **Expo example:** either (a) bump **`expo`** to the **latest SDK** you want, then run **`npx expo install --fix`** so native modules align to that SDK in one shot, or (b) stay on the current SDK but still take **latest patch/minor** Expo allows for pinned modules. Do not leave the app on an old SDK only to avoid editing `package.json`—upgrade SDK when the goal is “latest everywhere” and conflicts are resolved.
- **Library root:** bump **direct** devDependencies to **latest** (majors OK): ESLint, TypeScript, Prettier, `@typescript-eslint/*`, Skia, Reanimated, Gesture Handler, RN, React, `react-native-builder-bob`, etc., subject only to **peer/native** rules below.
- After **`react-native-builder-bob`** majors, re-check **`package.json`** `exports` / `types` paths against actual **`lib/typescript/**`** layout from `yarn build`.

3. **Conflict rules (no broken native stack)**

These override “always latest”: if peers clash, **fix the set** (upgrade/downgrade together) until installs are clean.

- **Reanimated** ↔ **react-native-worklets:** follow **`peerDependencies`** on the chosen Reanimated version (pair must match; never bump one alone).
- **@shopify/react-native-skia:** JS version must match native; in the example, follow **`expo install`** for SDK-validated pins unless you also change SDK/native setup.
- **react-native-gesture-handler:** compatible with RN + Reanimated per upstream.
- **Do not** change native deps in `expo-example` without **`yarn install`** there and **`npx expo start --clear`** when natives change.

4. **Apply upgrades (prefer latest, including majors)**

```bash
# Root — see everything; then bump aggressively
yarn outdated
# Interactive: select latest (including red/major) where rules allow
yarn upgrade-interactive --latest
# Or direct latest for specific packages:
# yarn add -D some-package@latest other-package@latest
yarn install

# Example — same idea; use expo install after expo version changes
cd expo-example && yarn outdated
yarn upgrade-interactive --latest # for non-Expo-pinned deps
# When changing Expo SDK or core RN/Reanimated/Skia/worklets:
npx expo install expo@latest # or expo@~<sdk>.0 then:
npx expo install --fix
yarn install
cd ..
```

Resolve **peer dependency errors** (not mere warnings): adjust **`peerDependencies`** on the library root and/or example pins until **`yarn install`** exits successfully.

5. **Verify (must pass before committing)**

```bash
yarn install
yarn build
yarn eslint src --max-warnings 0
npx tsc --noEmit -p tsconfig.json

cd expo-example && yarn install && npx eslint app --max-warnings 0
cd ..
```

Run **`npx expo-doctor`** from `expo-example/` after Expo or native bumps.

6. **Update documents** (whenever versions or install story change)

| File | What to sync |
|------|----------------|
| `README.md` | **Install** code block: list every root **`peerDependency`** with correct lower bounds (include `react-native-worklets` if the library imports it). |
| `docs/PROJECT_OVERVIEW.md` | **Tech stack** table and **Version notes**: reflect pinned majors (RN, Expo SDK, Skia, Reanimated, worklets). **Scripts and workflows**: must match **root** `package.json` `scripts` (do not document scripts that do not exist). |
| `.cursor/skills/run-expo-example/SKILL.md` | If root scripts for packing/syncing the example change, update that skill’s command list. |

7. **Regenerate `lib/`** after `src/` or build config changes

```bash
yarn build
```

Do not hand-edit `lib/`.

## Common fixes

- **Duplicate native modules / wrong Reanimated instance**: example must not symlink monorepo root for the library in a way that pulls a second `node_modules` tree for Skia/Reanimated (see `run-expo-example` skill if using tarball workflow).
- **`scheduleOnRN` / worklets**: ensure `react-native-worklets` is a **dependency** (not only devDependency) of any app that imports it directly from JS (e.g. `expo-example` if it uses `scheduleOnRN`).
- **`bob` / TypeScript emit layout** changed on builder major: update root **`package.json`** `types` and **`exports`**.**`types`** to match **`lib/typescript/**`**; add **`exclude`** entries in **`tsconfig.json`** if stray roots (e.g. `eslint.config.js`, `scripts/`) get pulled into declaration emit.
- **Yarn resolutions**: only add `resolutions` in root or example as a last resort; prefer aligning direct dependencies.

## What not to do

- Do not bump only one of **Reanimated** / **worklets** without checking compatibility.
- Do not commit secrets or local `.pack/` tarballs if policy forbids it.
- Do not edit `lib/` manually.
29 changes: 29 additions & 0 deletions .github/workflows/verify-pack.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Verify pack and example

on:
push:
branches: [main, master]
pull_request:

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: yarn
cache-dependency-path: |
yarn.lock
expo-example/yarn.lock

- name: Install root dependencies
run: yarn install --frozen-lockfile

- name: Sync packed library into expo-example
run: yarn example:sync

- name: Typecheck example app
run: cd expo-example && npx tsc --noEmit
47 changes: 44 additions & 3 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,45 @@
expo-example
# Published tarball is driven by package.json "files" (lib, src). These rules
# exclude anything that must never ship to npm, and prune junk inside src/lib.

# --- Example app & repo-only trees (not library consumers) ---
expo-example/
docs/
scripts/
.cursor/
.github/

# --- Dependencies & package managers ---
node_modules/
.vscode
.yarn
.yarn/
.pnp.cjs
.pnp.loader.mjs

# --- Local / CI / editor ---
.vscode/
.idea/
.expo/
.pack/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
coverage/
.nyc_output/

# --- Dev config at repo root (not needed in node_modules install) ---
eslint.config.js
tsconfig.json
.editorconfig
.gitattributes
yarn.lock
package-lock.json

# --- Tests & snapshots (if added under src/ later) ---
**/__tests__/
**/__mocks__/
**/*.test.ts
**/*.test.tsx
**/*.spec.ts
**/*.spec.tsx
**/*.snap
Binary file added .pack/react-native-free-canvas-2.0.0.tgz
Binary file not shown.
Binary file added .pack/react-native-free-canvas.tgz
Binary file not shown.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@ Freehand sketch on canvas based on [@shopify/react-native-skia](https://github.

<img src="https://github.com/user-attachments/assets/240398a3-2ebf-4edc-bdf5-36812765e39b" width=200 />

## Demo app (Expo)

From the **repository root** (after `yarn install` here once, if you have not already):

```bash
yarn demo
```

That installs `expo-example` dependencies and starts Expo. Then press **`i`** (iOS), **`a`** (Android), or scan the QR code with Expo Go.

Shortcuts:

```bash
yarn demo:ios
yarn demo:android
```

## Install
You need to install following dependencies
```
"@shopify/react-native-skia": ">=2.0.0",
"react": ">=18.0.0",
"react-native": ">=0.72.0",
"react-native-gesture-handler": ">=2.0.0",
"react-native-reanimated": ">=4.0.0",
"react-native-worklets": "*",
"react-native-reanimated": ">=4.1.0",
"react-native-worklets": ">=0.5.0"
```

`react-native-worklets` is required at runtime (the library uses `scheduleOnRN` from gesture/worklet code). Match the **worklets** version range your **Reanimated** package declares (e.g. Reanimated **4.3.x** expects **worklets 0.8.x**; **Expo SDK 55** / **Reanimated ~4.2** uses **worklets 0.7.x** — always align with `peerDependencies` on the versions you install).

Dependency upgrades in this repo: follow **`.cursor/skills/upgrade-dependencies/SKILL.md`** (or run the **upgrade-dependencies** command).

## Usage
```ts
import FreeCanvas from 'react-native-free-canvas';
Expand Down
Loading
Loading