diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..91e72a6fa --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,98 @@ +# OpenPencil + +Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, 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 +- `bun run tauri dev` — desktop app with hot reload + +## Code conventions + +- `@/` 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 (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 + +- 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) +- 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 + +- 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) +- Auto-layout creation (Shift+A) must recompute layout immediately to update selection bounds + +## UI + +- Use reka-ui for UI components (Splitter, ContextMenu, DropdownMenu, etc.) +- Tailwind 4 for styling — no inline CSS, no component-level `