Merge remote-tracking branch 'origin/master' into add-vitepress-docs
This commit is contained in:
commit
c31b0aa239
98
AGENTS.md
Normal file
98
AGENTS.md
Normal file
|
|
@ -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<string, SceneNode>`, 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<K>()` 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 `<style>` blocks
|
||||
- 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
|
||||
- ScrubInput (drag-to-change number) — cursor and pointerdown on outer container, not inner spans
|
||||
- Icons: use unplugin-icons with Iconify/Lucide (`<icon-lucide-*>`) — don't use raw SVG or Unicode symbols
|
||||
- Sections are draggable by title pill, not by the area to the right of the title
|
||||
|
||||
## File format
|
||||
|
||||
- .fig files use Kiwi binary codec — schema in `src/kiwi/codec.ts`
|
||||
- `NodeChange` is the central type for Kiwi encode/decode
|
||||
- Vector data uses reverse-engineered `vectorNetworkBlob` binary format — encoder/decoder in `src/engine/vector.ts`
|
||||
- showOpenFilePicker/showSaveFilePicker are File System Access API (Chrome/Edge), not Tauri-only — code has fallbacks
|
||||
- Tauri detection: `IS_TAURI` constant from `src/constants.ts` — don't use `'__TAURI_INTERNALS__' in window` inline
|
||||
- .fig export: compression with fflate (browser) or Tauri Rust commands
|
||||
- Test .fig round-trip by exporting and reimporting in Figma
|
||||
|
||||
## Tauri
|
||||
|
||||
- Tauri v2 with plugin-dialog, plugin-fs, plugin-opener
|
||||
- File system permissions must be configured in `desktop/tauri.conf.json` — "Internal error" on save means missing permissions
|
||||
- Dev tools: add a menu item to toggle, don't rely on keyboard shortcut
|
||||
|
||||
## Reference
|
||||
|
||||
[figma-use](https://github.com/dannote/figma-use) — our Figma toolkit. Use as reference for:
|
||||
- Kiwi binary format, schema, encode/decode (`packages/shared/src/kiwi/`)
|
||||
- Figma WebSocket multiplayer protocol (`packages/plugin/src/ws/`)
|
||||
- Vector network blob format (`packages/shared/src/vector/`)
|
||||
- Node types, paints, effects, layout fields (`packages/shared/src/types/`)
|
||||
- MCP tools / design operations (`packages/mcp/`)
|
||||
- JSX-to-design renderer (`packages/render/`)
|
||||
- Design linter rules (`packages/linter/`)
|
||||
|
||||
## Known issues
|
||||
|
||||
- Safari ew-resize/col-resize/ns-resize cursor bug (WebKit #303845) — fixed in Safari 26.3 Beta
|
||||
50
PLAN.md
50
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
|
||||
|
|
@ -1431,6 +1434,51 @@ This PoC validates the entire stack end-to-end in 4 weeks, before committing to
|
|||
|
||||
---
|
||||
|
||||
## CLI & Headless Mode
|
||||
|
||||
The editor should be fully controllable by AI agents and usable in CI without a GUI.
|
||||
|
||||
### Two modes
|
||||
|
||||
**Attached** — CLI connects to a running OpenPencil instance via WebSocket. The app starts a WS server on a configurable port. `eval` runs JS in the app's context with full access to the editor store, scene graph, renderer, and CanvasKit. This is how interactive AI workflows work (create, modify, screenshot, iterate).
|
||||
|
||||
**Headless** — CLI loads the engine directly in Bun/Node, no window, no Tauri, no WebGL. The engine (scene-graph, layout, codec) is pure TypeScript with no DOM dependencies. Rust zstd/zip is replaced with fflate (already bundled as browser fallback). This enables linting, analysis, .fig validation, and CI pipelines without a running app.
|
||||
|
||||
### Package structure
|
||||
|
||||
```
|
||||
packages/
|
||||
core/ — scene-graph, layout, codec, types (extracted from src/engine/)
|
||||
cli/ — open-pencil CLI (eval, lint, find, export, etc.)
|
||||
mcp/ — MCP server (wraps CLI commands for AI agents)
|
||||
```
|
||||
|
||||
`core` is the key extraction: the engine without rendering, importable by CLI, MCP, tests, and the app. The app's `src/engine/` imports from `core` instead of owning the types.
|
||||
|
||||
### CLI commands (matching figma-use where applicable)
|
||||
|
||||
| Command | Mode | Description |
|
||||
|---------|------|-------------|
|
||||
| `eval <code>` | attached | Run JS in editor context |
|
||||
| `find <query>` | both | Find nodes by name, type, XPath |
|
||||
| `lint` | both | Run design linter rules on .fig/.openpencil file |
|
||||
| `export <format>` | attached | Export selection/page as PNG/SVG/PDF |
|
||||
| `node get <id>` | both | Get node properties |
|
||||
| `node tree` | both | Print node tree |
|
||||
| `create <type>` | attached | Create a node |
|
||||
| `set <prop> <value>` | attached | Set node property |
|
||||
| `analyze colors` | both | Analyze color palette |
|
||||
| `analyze spacing` | both | Analyze spacing consistency |
|
||||
| `mcp` | both | Start MCP server |
|
||||
| `open <file>` | attached | Open .fig/.openpencil file |
|
||||
| `screenshot` | attached | Capture current viewport |
|
||||
|
||||
### Why not a Tauri CLI?
|
||||
|
||||
Tauri is a GUI framework — no headless mode. A separate Bun-based CLI that imports `core` directly is simpler, faster to start, and works in CI (Docker, GitHub Actions) without X11/Wayland.
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts Reference
|
||||
|
||||
Full Figma-compatible shortcut map. Implemented shortcuts marked with ✅.
|
||||
|
|
|
|||
|
|
@ -4,17 +4,21 @@ Open-source, AI-native design editor. Figma alternative built from scratch with
|
|||
|
||||
> **Status:** Active development. Not ready for production use.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Figma .fig file import** — open native Figma files directly
|
||||
- **Figma clipboard** — copy/paste between OpenPencil and Figma
|
||||
- **Vector networks** — complex boolean shapes and open paths, like Figma
|
||||
- **Auto-layout** — constraint-based layout matching Figma behavior
|
||||
- **Components & instances** — with live sync, overrides, component sets
|
||||
- **Pen tool** — bezier curves with tangent handles
|
||||
- **Inline text editing** — multi-line text with Inter font
|
||||
- **Inline text editing** — multi-line text with system fonts
|
||||
- **Undo/redo** — all operations are undoable
|
||||
- **Snap guides** — edge and center snapping
|
||||
- **Color picker** — HSV, hue/alpha sliders, hex input
|
||||
- **Color picker** — HSV, hue/alpha sliders, hex input, gradients
|
||||
- **~5 MB install, works fully offline** — no account, no server, no internet required
|
||||
|
||||
## Tech Stack
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "open-pencil-app",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.dannote.open-pencil-app",
|
||||
"identifier": "net.dannote.open-pencil",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
|
|
|
|||
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 878 KiB |
Loading…
Reference in a new issue