Commit graph

335 commits

Author SHA1 Message Date
Danila Poyarkov 5e209eca1c Fix trackpad pinch-to-zoom on Safari macOS 2026-03-01 22:07:47 +03:00
Danila Poyarkov 13a95bf517 Release v0.2.1 2026-03-01 21:25:52 +03:00
Danila Poyarkov 091861f32a Update changelog for v0.2.1 2026-03-01 21:17:07 +03:00
Danila Poyarkov 3e81c15689 Center empty chat placeholder text 2026-03-01 21:15:32 +03:00
Danila Poyarkov 0ba1f00e74 Hide panels by default on mobile 2026-03-01 21:13:51 +03:00
Danila Poyarkov d4b8a2a310 Always show local user avatar in collab header 2026-03-01 21:12:22 +03:00
Danila Poyarkov ab658ac33f Add ⌘\ to toggle side panels, header with logo and editable filename
Header row with app icon, double-click-to-rename document name, and
sidebar toggle button. Shows in both Tauri and browser.
When panels are hidden, a floating bar on the canvas provides the same
controls to restore the UI.
2026-03-01 21:07:02 +03:00
Danila Poyarkov 575914b194 Add touch support for pan and pinch-zoom on iOS 2026-03-01 20:13:21 +03:00
Danila Poyarkov c3844db2bd Stub shiki to eliminate 9MB of unused language grammars
vue-stream-markdown bundles all 359 shiki grammar chunks by default.
We only need plain text code blocks in chat. Alias shiki to an empty
module — build drops from 20MB/377 files to 11MB/103 files.
2026-03-01 20:05:52 +03:00
Danila Poyarkov e8b685ad0a Update screenshot 2026-03-01 18:44:52 +03:00
Danila Poyarkov deac6e6019 Release v0.2.0 2026-03-01 18:43:09 +03:00
Danila Poyarkov 6e4332a6ba Release v0.2.0 2026-03-01 18:27:12 +03:00
Danila Poyarkov 56aa194785 Toast notifications via Reka UI Toast
- AppToast component: top-center blue pill (info), red pill (errors)
- Global error handler catches window.error + unhandledrejection
- Link copied toast on share and copy link button
- use-toast composable: toast.show(message, variant)
2026-03-01 18:25:04 +03:00
Danila Poyarkov f90e1400b7 P2P collaboration: cursors, follow mode, cleanup
- Broadcast cursor position from canvas mouse move via awareness
- Broadcast selection changes via reactive watcher on selectedIds
- Follow mode: click peer avatar to track their viewport (pan + zoom)
- Zoom broadcast via watcher on store.state.zoom, not just mouse move
- Figma-style cursor arrows: colored fill, white border, name pill
- Stale cursor cleanup: removeAwarenessStates on peer leave
- MQTT signaling (replace Nostr), STUN + TURN ICE servers
- Collab constants extracted to src/constants.ts
- crypto.getRandomValues() replaces Math.random() everywhere
- Provide/inject for collab composable (COLLAB_KEY)
- Update README (collab section, tech stack), CHANGELOG, AGENTS.md
- AGENTS.md: release process, CI workflows, documentation guidelines
2026-03-01 18:11:07 +03:00
Danila Poyarkov 7a2694b6bc Emphasize no subscription, bring your own API key 2026-03-01 17:26:30 +03:00
Danila Poyarkov 3427b7fd3c Replace Durable Object relay with Trystero P2P + y-indexeddb
- Remove packages/collab/ (CF Worker + Durable Object server)
- Replace WebSocket relay with Trystero (serverless WebRTC P2P)
  - Peers connect via Nostr relays for signaling (free, public)
  - All sync traffic flows directly peer-to-peer
  - Automatic chunking for large Yjs updates
- Add y-indexeddb for local persistence
  - Each client persists Y.Doc to IndexedDB
  - Room survives browser refresh, loads instantly
- Yjs sync protocol over Trystero actions:
  - yjs-update: incremental doc updates
  - sync-step1/sync-reply: full state sync on peer join
  - awareness: cursor, selection, presence
- Zero server cost at any scale
2026-03-01 17:25:46 +03:00
Danila Poyarkov 8695ba6d1b Emphasize no subscription, bring your own API key 2026-03-01 17:24:54 +03:00
Danila Poyarkov 76134ccc1a Collab UI polish, vue-router, extract HsvColorArea
- Move CollabPanel to right panel header (like Figma)
- /share/:roomId shows join dialog with name input before connecting
- Add vue-router with / and /share/:roomId routes
- Extract EditorView from App.vue
- Extract HsvColorArea component from ColorPicker + FillPicker
  (eliminates ~160 lines of duplicated HSV logic)
- Extract syncNodePropsToYMap in use-collab.ts
- Register Yjs update listener before opening WebSocket (fix race)
- Scrollable AppMenu without scrollbar (scrollbar-none utility)
- Overflow-x hidden on LayersPanel
- Gitignore .wrangler build artifacts
- SPA catch-all redirect for Cloudflare Pages
2026-03-01 17:15:08 +03:00
Danila Poyarkov 2add3ac4c0 Real-time collaboration with Yjs CRDT over Cloudflare Durable Objects
- Cloudflare Worker + Durable Object server (packages/collab/)
  - One DO per room with WebSocket Hibernation for near-zero idle cost
  - Yjs sync protocol for document state (CRDT merge)
  - Yjs awareness protocol for cursors, selection, presence
  - Persists doc state to DO storage
  - CORS headers for cross-origin WebSocket upgrade

- Client composable (src/composables/use-collab.ts)
  - Y.Doc ↔ SceneGraph bidirectional binding
  - Awareness: broadcasts cursor position, selection, name, color
  - Auto-join from /share/:roomId URL
  - Suppresses echo loops between Yjs and graph updates

- CollabPanel component (src/components/CollabPanel.vue)
  - Share button generates room link, copies to clipboard
  - Join flow: paste link or room ID
  - Avatar stack with colored initials and tooltips
  - Name input persisted to localStorage

- Remote cursor rendering in CanvasKit
  - Colored arrow cursor + name label pill per remote peer
  - Remote selection outlines in peer's color
  - Only shows cursors on the same page

- SPA redirect for /share/* routes (Cloudflare Pages _redirects)
2026-03-01 16:44:37 +03:00
Danila Poyarkov 9f8242bce5 Add download links to README, docs landing, and getting started 2026-03-01 16:32:45 +03:00
Danila Poyarkov fb8891c431 Fix case-sensitive filename: ApiKeySetup.vue → APIKeySetup.vue 2026-03-01 16:29:54 +03:00
Danila Poyarkov 0acfd50a67 Add npm publish with trusted publishing to release workflow 2026-03-01 16:27:41 +03:00
Danila Poyarkov 1e873d64b1 Add repository field and provenance to package configs 2026-03-01 16:23:30 +03:00
Danila Poyarkov c4e6514ea2 Update repo references to open-pencil/open-pencil 2026-03-01 16:18:11 +03:00
Danila Poyarkov 74d4b6862e Use app favicon for docs site (symlink to public/favicon-32.png) 2026-03-01 16:14:06 +03:00
Danila Poyarkov baee65e599 Add CHANGELOG.md, update build workflow for v0.1.0-alpha
- Add Linux x64 build target
- Add patchelf for Linux AppImage builds
- Release body links to CHANGELOG.md
- Release name includes 'OpenPencil' prefix
2026-03-01 16:09:25 +03:00
Danila Poyarkov 515c8125ed Add app and docs links to README 2026-03-01 16:03:25 +03:00
Danila Poyarkov 10848d8319 Add app.openpencil.dev links to docs 2026-03-01 16:02:08 +03:00
Danila Poyarkov 268c4029e9 Add CI workflow for app.openpencil.dev deploy 2026-03-01 16:00:19 +03:00
Danila Poyarkov 0f0ca8db4b Update screenshot, remove duplicate from repo root
Single screenshot lives in packages/docs/public/. README references it
from there. Custom VitePress layout places it between hero and features.
2026-03-01 15:57:01 +03:00
Danila Poyarkov 503a561753 Symlink favicons from desktop/icons, add all sizes for Safari/Chrome
.ico (any), 32px PNG, 128px PNG, apple-touch-icon (512px).
Symlinks instead of copies so icons stay in sync.
2026-03-01 15:46:31 +03:00
Danila Poyarkov f8f85970e1 Add favicon, persist banner dismissal in localStorage 2026-03-01 15:44:25 +03:00
Danila Poyarkov c8af1c35e7 Improve Save for browsers without File System Access API
- Save As prompts for filename via prompt() before downloading
- Save reuses the filename from last Save As (no repeated prompts)
- Banner in Safari/Firefox explaining the limitation with Chrome link
- Banner is dismissible
2026-03-01 15:41:27 +03:00
Danila Poyarkov c886daded2 Fix .fig export: map COMPONENT/COMPONENT_SET to SYMBOL (Kiwi enum)
The Kiwi binary schema uses SYMBOL (15) for components, not COMPONENT.
Import already mapped SYMBOL→COMPONENT; export now reverses it.
2026-03-01 15:35:20 +03:00
Danila Poyarkov 1e1feb9cb0 Merge branch 'add-vitepress-docs' 2026-03-01 15:32:45 +03:00
Danila Poyarkov 22f8599408 Fix Save in Safari: append <a> to DOM, defer revokeObjectURL
Safari requires the <a> element to be in the document for .click()
downloads to work. Also defers URL.revokeObjectURL by 100ms so the
browser has time to start the download. Extracted shared downloadBlob
helper used by both file save and image export fallbacks.
2026-03-01 15:31:56 +03:00
Danila Poyarkov ca20bfb2ac Fix layers-panel tests: match current demo shapes (Components, App Preview) 2026-03-01 15:28:13 +03:00
Danila Poyarkov 20290f61b5 Add integration tests for app menu and autosave
9 app-menu tests: menu visibility, all 6 submenus content, Undo/Duplicate/
Zoom to fit via menu actions.
2 autosave tests: write triggers after scene change with mock file handle,
no write without file handle.
2026-03-01 15:26:10 +03:00
Danila Poyarkov 90e47e07ab Add autosave: debounced write 3s after last scene change
Triggers only when a file handle (browser File System Access API) or
file path (Tauri) exists. Tracks savedVersion to skip redundant writes.
Silent failure — user can always save manually.
2026-03-01 15:17:03 +03:00
Danila Poyarkov 96d8ab5789 Move docs to packages/docs, add Cloudflare Pages deploy
- Move docs/ → packages/docs/ as @open-pencil/docs workspace package
- Add screenshot.png as hero image on landing page
- Add GitHub Actions workflow for Cloudflare Pages deploy (openpencil.dev)
- Remove vitepress from root devDependencies
- Root docs:* scripts delegate via bun --filter
2026-03-01 15:16:16 +03:00
Danila Poyarkov 7809fb60f9 Add app menu bar for browser mode (File, Edit, View, Object, Text, Arrange)
Reka-ui Menubar in the left panel header, hidden when running in Tauri
(native menu handles it). Includes Open, Save, Save As, Export, Undo/Redo,
Zoom, Group/Ungroup, Component creation, Bring to front/Send to back,
Auto layout, Text formatting, Alignment.
2026-03-01 15:09:08 +03:00
Danila Poyarkov 3849872b32 Unify tool definitions: define once, adapt for AI/CLI/MCP
Move tool logic to @open-pencil/core/tools/schema.ts as framework-agnostic
ToolDef objects. AI adapter generates valibot schemas + Vercel AI tool()
wrappers automatically.

26 tools (was 10): create_shape, render (JSX), set_fill, set_stroke,
set_effects, set_layout, set_constraints, update_node, delete, clone,
rename, reparent, group/ungroup, find_nodes, get_node, get_page_tree,
get_selection, select, list_pages, switch_page, list_variables,
list_collections, create_component, create_instance, eval.

src/ai/tools.ts: 269→28 lines (adapter only).

Tests for all 3 interfaces:
- 26 core tool tests (FigmaAPI directly)
- 11 AI adapter tests (valibot + Vercel AI SDK tool())
- 12 CLI integration tests (eval command on .fig fixture)
323 total, all passing.
2026-03-01 15:02:05 +03:00
Anton A S 5b7de9fe99 Sync docs with README motivation: landing, features, comparison, user guide 2026-03-01 13:58:54 +03:00
Danila Poyarkov f22bd2e133 Merge eval-command: Figma Plugin API + eval CLI command
FigmaAPI wraps SceneGraph with Figma-compatible interface (Symbol-hidden internals).
CLI eval command runs Plugin API scripts headless on .fig files.
106 new tests (60 figma-api + 17 cli integration + 29 gap-filling).
2026-03-01 13:55:39 +03:00
Danila Poyarkov 123dbe491b Extract DEFAULT_STROKE_MITER_LIMIT constant 2026-03-01 13:53:25 +03:00
Anton A S 2b5f6d58c0 Merge remote-tracking branch 'origin/master' into add-vitepress-docs 2026-03-01 13:51:56 +03:00
Anton A S 5eb1b30390 Separate User Guide sidebar scope, remove redundant shortcut notes 2026-03-01 13:51:31 +03:00
Danila Poyarkov 3e9cd236e9 Fix bogus fig-import mappings: expanded and strokeMiterLimit
expanded: was mapped from frameMaskDisabled (unrelated Kiwi field),
now defaults to true. strokeMiterLimit: was mapped from speculative
strokeMiterAngle field that doesn't exist in Kiwi codec, now uses
hardcoded default 4.
2026-03-01 13:48:48 +03:00
Danila Poyarkov 48219af2fb Fill Figma Plugin API gaps: 29 new tests, 30+ new properties and methods
SceneNode: add minWidth/maxWidth/minHeight/maxHeight, isMask, maskType,
counterAxisAlignContent, itemReverseZIndex, strokesIncludedInLayout,
expanded, textTruncation, autoRename, strokeMiterLimit. All with defaults
and .fig import mapping.

FigmaNodeProxy: add clone(), strokeCap, strokeJoin, strokeMiterLimit,
strokeTopWeight/BottomWeight/LeftWeight/RightWeight, minWidth/maxWidth/
minHeight/maxHeight, isMask, maskType, expanded, textTruncation,
autoRename, insertCharacters, deleteCharacters, primaryAxisSizingMode,
counterAxisSizingMode, counterAxisAlignContent, itemReverseZIndex,
strokesIncludedInLayout, layoutAlign, findAllWithCriteria.

FigmaAPI: add createComponentFromNode, getVariableById, getLocalVariables,
getLocalVariableCollections, getVariableCollectionById.

Fix layoutSizingHorizontal/Vertical for self-framing auto-layout nodes
with no auto-layout parent — now checks the node's own layoutMode.
2026-03-01 13:35:02 +03:00
Danila Poyarkov 2a3e848e3b Deduplicate AI tools: use FigmaAPI.toJSON() and core parseColor
- get_page_tree: replace hand-rolled nodeToJSON with FigmaAPI.wrapNode().toJSON()
- get_selection: same — use FigmaAPI proxy serialization
- resolveColor: delegate hex parsing to core parseColor() (culori) instead of manual parseInt
2026-03-01 13:20:01 +03:00