From d33efa1228b934c64bc8266437c4ca5bc8d9b082 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 23:17:39 +0300 Subject: [PATCH 1/8] Update PLAN.md with Yoga CSS Grid PR tracking details --- PLAN.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index 9c1e998ff..9db319648 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1139,7 +1139,10 @@ bun run test:figma:report Based on our research, **Yoga** is the right choice now: -- **CSS Grid support is landing** — 9-part PR series active (Feb 2026) +- **CSS Grid support is landing** — [facebook/yoga#1893](https://github.com/facebook/yoga/pull/1893)–#1902 (9 PRs by @intergalacticspacehighway from Expo) + - PR 1/9 (style types & public API) under active review by NickGerleman (Meta), last activity Feb 28, 2026 + - Supported: `grid-template-columns/rows`, `grid-column/row-start/end`, `grid-auto-columns/rows`, `minmax()`, `auto`, `%`, `px`, `fr` + - Not yet: `repeat()`, `auto-fill`/`auto-fit`, `grid-template-areas`, `grid-auto-flow`, subgrid - Battle-tested in React Native (billions of devices) - ~45KB WASM, well-maintained by Meta - Flexbox + Grid covers everything a design tool needs From fdb3fec236c08c2a50c512c869d314c4fa3f021e Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 23:18:55 +0300 Subject: [PATCH 2/8] Add AGENTS.md --- AGENTS.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..94847ec41 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# OpenPencil + +Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop app, also runs in browser. + +## Commands + +- `bun run check` — lint + typecheck (run before committing) +- `bun run format` — oxfmt with import sorting +- `bun test ./tests/engine` — unit tests +- `bun run test` — Playwright visual regression + +## Rules + +- Use `@/` import alias for cross-directory imports, `./` for same directory +- No `any` — use proper types, generics, declaration merging +- No `!` non-null assertions — use guards, `?.`, `??` +- Shared types live in `src/types.ts` (GUID, Color, Vector, Matrix, Rect) +- Mac shortcuts: use `e.code` not `e.key` (Option transforms characters) +- UI components: use reka-ui +- `src/kiwi/kiwi-schema/` is vendored — don't modify From 199520ae86c324b52f31fbdd06e2d0dfc38a41ed Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 23:22:07 +0300 Subject: [PATCH 3/8] Expand AGENTS.md with lessons from all sessions --- AGENTS.md | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 94847ec41..fb935f273 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # OpenPencil -Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop app, also runs in browser. +Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also runs in browser. ## Commands @@ -9,12 +9,50 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop app, a - `bun test ./tests/engine` — unit tests - `bun run test` — Playwright visual regression -## Rules +## Code conventions -- Use `@/` import alias for cross-directory imports, `./` for same directory +- `@/` import alias for cross-directory imports, `./` for same directory - No `any` — use proper types, generics, declaration merging - No `!` non-null assertions — use guards, `?.`, `??` -- Shared types live in `src/types.ts` (GUID, Color, Vector, Matrix, Rect) -- Mac shortcuts: use `e.code` not `e.key` (Option transforms characters) -- UI components: use reka-ui +- Shared types (GUID, Color, Vector, Matrix, Rect) live in `src/types.ts` +- Window API extensions (showOpenFilePicker, queryLocalFonts) live in `src/global.d.ts` - `src/kiwi/kiwi-schema/` is vendored — don't modify + +## Rendering + +- Canvas is CanvasKit (Skia WASM) on a WebGL surface, not DOM +- Rendering is coalesced via rAF — call `scheduleRender()`, not `renderNow()` +- `renderNow()` is only for surface recreation and font loading (need immediate draw) +- Resize observer uses rAF throttle, not debounce — debounce causes canvas skew (old WebGL surface stretched into resized element) +- Viewport culling skips off-screen nodes; unclipped parents are NOT culled (children may extend beyond bounds) + +## Components & instances + +- Purple (#9747ff) for COMPONENT, COMPONENT_SET, INSTANCE — matches Figma +- Instance children map to component children via `componentId` for 1:1 sync +- Override key format: `"childId:propName"` in instance's `overrides` record +- Editing a component must call `syncIfInsideComponent()` to propagate to instances +- `SceneGraph.copyProp()` typed helper — uses `structuredClone` for arrays + +## Layout + +- `computeAllLayouts()` must be called after demo creation and after opening .fig files +- Yoga WASM handles flexbox; CSS Grid blocked on upstream (facebook/yoga#1893) + +## UI + +- Use reka-ui for UI components (Splitter, ContextMenu, etc.) +- Tailwind 4 for styling — no component-level CSS +- Mac keyboards: use `e.code` not `e.key` for shortcuts with modifiers (Option transforms characters) +- Splitter resize handles need inner div with `pointer-events-none` for sizing (zero-width handle collapses without it) +- Number input spinner hiding is global CSS in `app.css`, not per-component + +## File format + +- .fig files use Kiwi binary codec — schema in `src/kiwi/codec.ts` +- `NodeChange` is the central type for Kiwi encode/decode +- showOpenFilePicker/showSaveFilePicker are File System Access API (Chrome/Edge), not Tauri-only — code has fallbacks for other browsers + +## Known issues + +- Safari ew-resize/col-resize/ns-resize cursor bug (WebKit #303845) — fixed in Safari 26.3 Beta From 7a803772a5ab4c6eb9e3caa63d612cb9986602b3 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sat, 28 Feb 2026 23:23:46 +0300 Subject: [PATCH 4/8] Expand AGENTS.md with lessons from all project sessions Added from ~/Development sessions: scene graph behaviors, auto-layout gotchas, section/frame rendering rules, .fig round-trip testing, Tauri permissions, icon conventions, ScrubInput cursor handling, culori usage, vueuse hooks. --- AGENTS.md | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fb935f273..9b4778f3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,6 +8,7 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also - `bun run format` — oxfmt with import sorting - `bun test ./tests/engine` — unit tests - `bun run test` — Playwright visual regression +- `bun run tauri dev` — desktop app with hot reload ## Code conventions @@ -16,6 +17,8 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also - No `!` non-null assertions — use guards, `?.`, `??` - Shared types (GUID, Color, Vector, Matrix, Rect) live in `src/types.ts` - Window API extensions (showOpenFilePicker, queryLocalFonts) live in `src/global.d.ts` +- Use `culori` for color conversions — don't reimplement parseColor/colorToRgba +- Use `@vueuse/core` hooks (useEventListener, etc.) — don't do manual addEventListener/removeEventListener - `src/kiwi/kiwi-schema/` is vendored — don't modify ## Rendering @@ -25,6 +28,18 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also - `renderNow()` is only for surface recreation and font loading (need immediate draw) - Resize observer uses rAF throttle, not debounce — debounce causes canvas skew (old WebGL surface stretched into resized element) - Viewport culling skips off-screen nodes; unclipped parents are NOT culled (children may extend beyond bounds) +- Selection border width must be constant regardless of zoom — divide by scale +- Section/frame title text never scales — render at fixed font size, ellipsize to fit +- Rulers are rendered on the canvas (not DOM), with selection range badges that don't overlap tick numbers + +## Scene graph + +- Nodes live in flat `Map`, tree via `parentIndex` references +- Frames clip content by default is OFF (unlike what you'd assume) +- When creating auto-layout, sort children by geometric position first +- Dragging a child outside a frame should reparent it, not clip it +- Layer panel tree must react to reparenting — watch for stale children refs +- Groups: creating a group must preserve children's visual positions ## Components & instances @@ -38,20 +53,34 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also - `computeAllLayouts()` must be called after demo creation and after opening .fig files - Yoga WASM handles flexbox; CSS Grid blocked on upstream (facebook/yoga#1893) +- Auto-layout creation (Shift+A) must recompute layout immediately to update selection bounds ## UI -- Use reka-ui for UI components (Splitter, ContextMenu, etc.) -- Tailwind 4 for styling — no component-level CSS +- Use reka-ui for UI components (Splitter, ContextMenu, DropdownMenu, etc.) +- Tailwind 4 for styling — no inline CSS, no component-level `