openpencil/CLAUDE.md
Kayshen Xu ca1b5370ae
V0.3.0 (#24)
* feat(boolean-operations): implement boolean operations in the editor

- Added a new BooleanToolbar component for union, subtract, and intersect operations.
- Integrated boolean operations into the layer context menu and keyboard shortcuts.
- Enhanced the editor layout to include the boolean toolbar for improved user interaction.
- Updated internationalization support with new translation keys for boolean operations.
- Bumped version to 0.3.0 to reflect the addition of these features.

* refactor(editor): update editor layout and panels for improved functionality

- Replaced the PropertyPanel with a new RightPanel that includes both Property and Code panels.
- Removed the CodePanel from the main editor layout and integrated it into the RightPanel.
- Updated keyboard shortcuts to switch the right panel to the code tab.
- Enhanced the LayerPanel with a resizable width feature for better user experience.
- Added internationalization support for new right panel labels and code panel features.
- Introduced new code generation capabilities for various frameworks in the CodePanel.
- Improved overall layout structure for better responsiveness and usability.

* feat(electron): implement .op file association and enhance file handling

- Added support for .op file association in electron-builder, allowing OpenPencil documents to be opened directly from the file system.
- Implemented IPC handlers for opening and reading .op files, ensuring proper loading of document content.
- Enhanced the main process to handle file opening events on macOS and single-instance locking on Windows/Linux.
- Updated the renderer to listen for file open events and load documents accordingly.
- Improved README to reflect new file association feature.

* fix(canvas): improve layout accuracy for AI-generated designs

- Unify lineHeight default via canonical defaultLineHeight() function
- Unify text measurement by removing duplicate estimators in generation-utils
- Fix optical centering formula to scale proportionally with fontSize
- Round layout positions to whole pixels to prevent sub-pixel artifacts
- Recursively sanitize nested x/y in streaming layout containers
- Fix input trailing icon alignment using fill_container instead of space_between

* feat(canvas): right-align agent badge and add breathing glow border

- Agent badge now right-aligned to frame's right edge instead of after label
- Added breathing glow border around agent-owned frames during generation
- Glow border uses same color and lifecycle as the agent badge
- Removed unused BADGE_GAP constant and useDocumentStore import

* feat(code-panel): enhance tab scrolling functionality and add scrollbar utility

- Introduced left and right scroll buttons for tab navigation in the CodePanel, improving user experience for navigating long tab lists.
- Added a custom utility to hide scrollbars for a cleaner interface.
- Updated styles for better responsiveness and usability in the CodePanel layout.

* fix(docs): update Discord invite links in multiple README files

- Replaced outdated Discord invite links with the new link across all language-specific README files.
- Ensured consistency in the documentation for community engagement.

* feat(code-panel): enhance system prompt for responsive design

- Updated the ENHANCE_SYSTEM_PROMPT to emphasize the importance of responsive design in code rewriting.
- Added detailed guidelines for converting fixed pixel widths to relative units and using responsive Tailwind breakpoints.
- Ensured that the output remains visually faithful on desktop while adapting gracefully across screen sizes.

* feat(docs): add WeChat group information to README.zh.md and include group image

- Introduced a new section in the Chinese README to provide details about the WeChat group for community engagement.
- Added an image representing the WeChat group for better visibility and user interaction.

* feat(electron): enhance theme management and title bar overlay for Windows/Linux

- Updated the `setTheme` method in the Electron API to accept custom colors for the title bar overlay, improving theme synchronization across platforms.
- Adjusted title bar overlay colors for Windows and Linux to ensure proper visibility and aesthetics.
- Enhanced the top bar component to read computed CSS colors and apply them dynamically, ensuring a consistent user interface.
- Improved handling of theme changes in the application to support background and foreground color customization.

* fix(screenshot): update screenshot image for improved clarity and quality

* fix(docs): update WeChat group image path in README.zh.md for consistency

* fix(ai): fix post-generation validation pipeline and text centering

- Fix Agent SDK validation: save temp screenshots inside project dir
  (.openpencil-tmp/) so Claude Code plan mode can read them, instead
  of /tmp/ which is outside the project sandbox
- Enrich validation tree dump with fill colors, stroke, fontSize,
  fontWeight, textAlign, cornerRadius, opacity for comprehensive
  visual analysis
- Add multi-round validation with quality scoring (threshold 8/10),
  500ms stabilization delay between rounds
- Add detailed debug logging to applyValidationFixes showing which
  nodes were found/skipped and property changes
- Fix canvas sync needsTextbox check to also account for textAlign
  (matching isFixedWidthText in factory), preventing IText↔Textbox
  thrashing on every sync tick
- Auto-center text in vertical+center layouts by expanding to full
  container width and injecting textAlign:'center'
- Force Textbox for non-left-aligned text so textAlign is respected
  (IText ignores width and computes its own)

* fix(canvas): use precise text width estimation for fit-content layout

Remove the 14% safety factor from text width estimation when computing
fit-content/natural-width text dimensions. IText auto-computes its own
width and ignores our setting, so the safety margin only inflated the
layout allocation, making text appear left-shifted within its container.

* fix(canvas): center fit-content text in horizontal layouts

For text nodes with fit-content width in horizontal layouts, set
textAlign:'center' to compensate for width estimation inaccuracy.
The estimated box is typically wider than the actual rendered text,
causing left-aligned text to appear visually shifted. Centering
distributes the estimation error evenly on both sides.

* feat(ai): show validation details in checklist panel

- Accumulate validation log (screenshot, analysis, fixes) instead of
  overwriting status messages, so the full process is visible
- Preserve step thinking content in buildFinalStepTags (was discarded)
- Add details field to pipeline items and render in checklist UI
- Each validation step now shows: screenshot captured, issues found,
  quality score, fixes applied

* feat(ai): add visual reference pipeline types and integration hooks

- Add DesignSystem and VisualReference types to ai-types
- Add 'visual-ref' mode to AIDesignRequest and SubTask.htmlReference
- Detect visual-ref candidates in chat handlers (landing pages, websites)
- Wire visual-ref mode in design-generator and orchestrator
- Inject HTML reference snippets into sub-agent prompts

* feat(ai): add modular design principles for sub-agent context

- Add design-principles module with topic files: color, typography,
  spacing, composition, components
- Selectively load relevant principles based on prompt content
- Inject design principles into sub-agent system prompts

* feat(ai): implement visual reference pipeline

- Add design-system-generator: generates color/typography/spacing tokens
- Add design-code-generator: generates HTML/CSS from design system
- Add html-renderer: renders HTML to screenshot via html2canvas
- Add visual-ref-orchestrator: coordinates the full pipeline
  (design system → HTML code → screenshot → enrich subtasks)
- Add html2canvas dependency for client-side HTML rendering

* feat(mcp): default filePath to live canvas and fix cross-platform issues

- Default all MCP tool filePath to live://canvas when omitted, so tools
  operate on the real-time canvas instead of stale files
- Remove filePath from required params in all tool schemas (21 interfaces)
- Fix mcp-server-manager.ts using process.cwd() which fails in Electron
  production on Linux — now checks ELECTRON_RESOURCES_PATH first
- Fix stopMcpHttpServer using SIGTERM on Windows — use taskkill instead
- Force new children reference in applyExternalDocument to ensure canvas
  sync subscriber always detects MCP-pushed document updates

* feat(mcp): enhance design prompt with semantic roles, CJK typography, and layout rules

Add comprehensive design knowledge to MCP design prompt for better
AI-generated designs: design type detection (mobile vs desktop), full
semantic role reference with context-aware defaults, CJK typography
rules, expanded text/layout/form guidelines, and detailed post-processing
documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ai): implement intent classification for chat handlers

- Replace hardcoded keyword matching with a lightweight LLM call to classify user intent in chat messages.
- Introduce a new function `classifyIntent` to determine if the request is for design generation or conversation.
- Update design request handling in `useChatHandlers` to utilize the new classification method.
- Enhance design prompt documentation to reflect changes in design type detection based on intent rather than keywords.

* fix(ai): handle string qualityScore in validation response parsing

The LLM sometimes returns qualityScore as a string (e.g. "8" instead of 8),
causing it to fall through to 0. Also hide misleading "quality: 0/10" display
when the score couldn't be determined, and log raw response for debugging.

* fix(ai): increase validation timeout to 90s and fix quality score parsing

Agent SDK validation requires spawning a process, reading the image, and
analyzing it — 30s was consistently timing out. Also handle string
qualityScore values from LLM responses and hide misleading 0/10 display.

* fix(ai): fix validation timeout and response parsing

- Increase validation timeout from 30s to 180s (Agent SDK needs time
  for subprocess spawn + OAuth auth + multi-turn image reading)
- Strip <tool_use> XML blocks from Agent SDK response before extracting
  JSON — the tool call XML was confusing the regex, causing qualityScore
  to parse as 0 despite valid JSON being present
- Handle string qualityScore values and hide misleading "quality: 0/10"
- Revert unnecessary direct API key approach for validation

* fix(ai): prevent node ID collisions between generations

When generating new content on a canvas with existing nodes, AI-generated
IDs (e.g. brand-spacer) would collide with previous generations. Now
captures pre-existing node IDs at generation start and checks against
them during upsert sanitization. Remapped IDs are tracked in
generationRemappedIds so progressive streaming updates can still find
their nodes.

* fix(ai): require styleGuide in orchestrator plan and fix validation detail icons

- Add fallback default styleGuide when orchestrator LLM omits it
- Strengthen prompt to mark styleGuide as REQUIRED
- Replace emoji icons in validation details with [done]/[pending]/[error]
  markers for consistent styling with the checklist design system

* feat(server): add port file plugin for server instance discovery

- Introduce a new Nitro plugin that writes a port file on server startup to allow the MCP server to discover the running instance, whether it's a development server or Electron.
- Implement error handling in the Electron main process for writing the port file, logging any failures.
- Update Vite configuration to include additional external dependencies in the rollup configuration.

* feat(electron): implement IPC for retrieving pending file paths

- Added a new IPC handler `file:getPending` to retrieve and clear the pending file path when the React app mounts.
- Updated the Electron API to include `getPendingFile` for renderer access.
- Enhanced the `useElectronMenu` hook to load any pending file on application startup.
- Updated UI components to reflect changes in file handling and improved user experience.

* fix(panels): replace emoji icons with styled icons in validation checklist

- Parse [done]/[pending]/[error] prefixes in detail lines and render as
  styled circle icons matching the parent checklist design system
- Replace remaining emoji markers in design-validation.ts with text prefixes
- Fix isApplied detection to recognize new [done] Applied marker

* refactor(electron): update settings path to use platform-standard app data directory

- Changed the settings file path to utilize Electron's user data directory for better cross-platform compatibility.
- Updated the settings writing function to ensure the user data directory is created if it doesn't exist.
- Added comments to clarify the storage location for different operating systems.
- Implemented a fixed partition for localStorage/cookies to maintain data across server port changes.

* feat(ai): enhance validation with pre-checks, structural fixes, and border detection

- Add design-pre-validation.ts: pure code checks before LLM validation
  - Invisible container detection (same fill as parent → auto-add border)
  - Sibling consistency (majority-rule for height/cornerRadius)
- Add structural fixes to validation: addChild/removeNode operations
  - Icon injection via lookupIconByName with server fallback
  - autoFixParentLayout with child count guard to prevent layout breakage
- Add strokeColor/strokeWidth to safe fix properties for border fixes
- Simplify intent classification: all design requests use visual-ref pipeline
- Fix checklist: "Found N issues" now shows [done] instead of [pending]
- Fix qualityScore: only update when > 0 to preserve valid round scores

* fix(ai): cherry-pick safe validation improvements, drop aggressive pre-checks

Keep: stroke tree dump bug fix (object not array), qualityScore=0 false
positive detection, fit_content→fixed safety guard, empty path removal,
type-specific sibling consistency, repeated fix filtering, screenshot
extraction to design-screenshot.ts.

Drop: detectForcedFixedHeight (destroyed input/button heights),
MAX_VALIDATION_ROUNDS 5 (too many rounds), removal of quality threshold
early stop, section regeneration phase.

---------

Co-authored-by: Fini <fini.yang@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 11:55:35 +08:00

25 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

  • Dev server: bun --bun run dev (runs on port 3000)
  • Build: bun --bun run build
  • Preview production build: bun --bun run preview
  • Run all tests: bun --bun run test (Vitest)
  • Run a single test: bun --bun vitest run path/to/test.ts
  • Type check: npx tsc --noEmit
  • Install dependencies: bun install
  • Electron dev: bun run electron:dev (starts Vite + Electron together)
  • Electron compile: bun run electron:compile (esbuild electron/ to electron-dist/)
  • Electron build: bun run electron:build (full web build + compile + electron-builder package)

Architecture

OpenPencil is an open-source vector design tool (alternative to Pencil.dev) with a Design-as-Code philosophy. Built as a TanStack Start full-stack React application with Bun runtime. Server API powered by Nitro. Also ships as an Electron desktop app for macOS, Windows, and Linux.

Key technologies: React 19, Fabric.js v7 (canvas engine), Paper.js (boolean path operations), Zustand v5 (state management), TanStack Router (file-based routing), Tailwind CSS v4, shadcn/ui (UI primitives), Vite 7, Nitro (server), Electron 35 (desktop), TypeScript (strict mode).

Data Flow

React Components (Toolbar, LayerPanel, PropertyPanel)
        │ Zustand hooks
        ▼
┌─────────────────┐    ┌───────────────────┐
│  canvas-store   │    │  document-store   │ ← single source of truth
│  (UI state:     │    │  (PenDocument)    │
│   tool/selection │    │  CRUD / tree ops  │
│   /viewport)    │    │                   │
└────────┬────────┘    └────────┬──────────┘
         │                      │
         ▼                      ▼
   Fabric.js Canvas      canvas-sync-lock
   (imperative render)   (prevents circular sync)
  • document-store is the single source of truth. Fabric.js only renders.
  • User edits on canvas → Fabric events → update document-store (with sync lock)
  • User edits in panels → update document-store → use-canvas-sync updates Fabric
  • canvas-sync-lock.ts prevents circular updates when Fabric events write to the store

Multi-Page Architecture

PenDocument
  ├── pages?: PenPage[]   (id, name, children)
  └── children: PenNode[] (default/single-page fallback)
  • document-store-pages.ts — page CRUD actions: addPage, removePage, renamePage, reorderPage, duplicatePage
  • canvas-store.tsactivePageId state, setActivePageId action
  • canvas-sync-utils.tsforcePageResync() triggers page-aware canvas re-sync
  • page-tabs.tsx — tab bar UI for multi-page navigation with context menu

Design Variables Architecture

PenDocument (source of truth)
  ├── variables: Record<string, VariableDefinition>   ($color-1, $spacing-md, ...)
  ├── themes: Record<string, string[]>                ({Theme-1: ["Default","Dark"]})
  └── children: PenNode[]                             (nodes with $variable refs)
                │
     ┌──────────┴──────────┐
     ▼                      ▼
  Canvas Sync             Code Generation
  resolveNodeForCanvas()  $ref → var(--name)
  $ref → concrete value   CSS Variables block
  • $variable references are preserved in the document store (e.g. $color-1 in fill color)
  • normalize-pen-file.ts does NOT resolve $refs — only fixes format issues
  • resolveNodeForCanvas() resolves $refs on-the-fly before Fabric.js rendering
  • Code generators output var(--name) for $ref values
  • Multiple theme axes supported (e.g. Theme-1 with Light/Dark, Theme-2 with Compact/Comfortable)
  • Each theme axis has variants; variables can have per-variant values (ThemedValue[])

Key Modules

  • src/canvas/ — Fabric.js integration (30 files):
    • fabric-canvas.tsx — Canvas component initialization
    • use-fabric-canvas.ts — Canvas initialization hook
    • canvas-object-factory.ts — Creates Fabric objects from PenNodes (rect, ellipse, line, polygon, path, text, image, frame, group)
    • canvas-object-sync.ts — Syncs individual object properties between Fabric and store
    • canvas-sync-lock.ts — Prevents circular sync loops
    • canvas-sync-utils.tsforcePageResync() utility for page-aware canvas re-sync
    • canvas-controls.ts — Custom rotation controls and cursor styling
    • canvas-constants.ts — Default colors, zoom limits, stroke widths
    • use-canvas-events.ts — Drawing events, shape creation, smart guides activation, tool-based skipTargetFind management
    • canvas-node-creator.tscreateNodeForTool, isDrawingTool, toScene helpers extracted from use-canvas-events
    • canvas-object-modified.tssyncObjToStore, syncSelectionToStore, handleObjectModified — Fabric object:modified handler logic
    • use-canvas-sync.ts — Bidirectional PenDocument ↔ Fabric.js sync, node flattening with parent offsets, variable resolution via resolveNodeForCanvas()
    • canvas-layout-engine.ts — Auto-layout computation: resolvePadding, getNodeWidth/Height, computeLayoutPositions, Padding interface
    • canvas-text-measure.ts — Text width/height estimation, CJK detection, parseSizing, getTextOpticalCenterYOffset
    • use-canvas-viewport.ts — Wheel zoom, space+drag panning, tool cursor switching, selection toggling per tool
    • use-canvas-selection.ts — Selection sync between Fabric objects and canvas-store
    • use-canvas-hover.ts — Hover state management for objects
    • use-canvas-guides.ts — Smart alignment guides with snapping
    • guide-utils.ts — Guide calculation and rendering
    • pen-tool.ts — Bezier pen tool: anchor points, control handles, path closure, preview rendering
    • parent-child-transform.ts — Propagates parent transforms (move/scale/rotate) to children proportionally
    • use-dimension-label.ts — Shows size/position labels during object manipulation
    • use-frame-labels.ts — Renders frame names and boundaries on canvas
    • use-entered-frame-overlay.ts — Visual overlay when entering a frame for editing
    • use-layout-indicator.ts — Layout indicator rendering during drag operations
    • insertion-indicator.ts — Insertion point indicator for layout drop targets
    • drag-into-layout.ts — Drag-and-drop into auto-layout frames with insertion detection
    • drag-reparent.ts — Reparenting nodes during drag operations
    • layout-reorder.ts — Reorder children within layout frames during drag
    • selection-context.ts — Selection context management for multi-select operations
  • src/variables/ — Design variables system (2 files):
    • resolve-variables.ts — Core resolution utilities: resolveVariableRef, resolveNodeForCanvas, getDefaultTheme, isVariableRef; resolves $variable references to concrete values for canvas rendering with circular reference guards
    • replace-refs.tsreplaceVariableRefsInTree: recursively walk node tree to replace/resolve $refs when renaming or deleting variables (covers opacity, gap, padding, fills, strokes, effects, text)
  • src/stores/ — Zustand stores (8 files):
    • canvas-store.ts — UI/tool/selection/viewport/clipboard/interaction state, variablesPanelOpen toggle, activePageId, figmaImportDialogOpen
    • document-store.ts — PenDocument tree CRUD: addNode, updateNode, removeNode, moveNode, reorderNode, duplicateNode, groupNodes, ungroupNode, toggleVisibility, toggleLock, scaleDescendantsInStore, rotateDescendantsInStore, getNodeById, getParentOf, getFlatNodes, isDescendantOf; Variable CRUD: setVariable, removeVariable, renameVariable, setThemes (all with history support)
    • document-store-pages.ts — Page actions extracted from document-store: addPage, removePage, renamePage, reorderPage, duplicatePage
    • document-tree-utils.ts — Pure tree helpers extracted from document-store: findNodeInTree, findParentInTree, removeNodeFromTree, updateNodeInTree, flattenNodes, insertNodeInTree, isDescendantOf, getNodeBounds, findClearX, scaleChildrenInPlace, rotateChildrenInPlace, createEmptyDocument, DEFAULT_FRAME_ID
    • history-store.ts — Undo/redo (max 300 states), batch mode for grouped operations
    • ai-store.ts — Chat messages, streaming state, generated code, model selection, pendingAttachments for image uploads
    • agent-settings-store.ts — AI provider config (Anthropic/OpenAI/OpenCode/Copilot), MCP CLI integrations (Claude Code, Codex CLI, Gemini CLI, OpenCode CLI, Kiro CLI, Copilot CLI), localStorage persistence
    • uikit-store.ts — UIKit management: imported kits, component browser state (search, category filters), localStorage persistence
  • src/types/ — Type system (8 files):
    • pen.ts — PenDocument/PenNode (frame, group, rectangle, ellipse, line, polygon, path, text, image, ref), ContainerProps, PenPage; PenDocument.variables, PenDocument.themes, PenDocument.pages
    • canvas.ts — ToolType (select, frame, rectangle, ellipse, line, polygon, path, text, hand), ViewportState, SelectionState, CanvasInteraction
    • styles.ts — PenFill (solid, linear_gradient, radial_gradient), PenStroke, PenEffect (shadow, blur), BlendMode, StyledTextSegment
    • variables.tsVariableDefinition (type + value), ThemedValue (value per theme), VariableValue
    • uikit.ts — UIKit, KitComponent, ComponentCategory types for reusable component organization and browsing
    • agent-settings.ts — AI provider config types (AIProviderType: anthropic/openai/opencode/copilot, AIProviderConfig, MCPCliIntegration, GroupedModel)
    • electron.d.ts — Electron IPC bridge types (file dialogs, save operations, updater: UpdaterState/UpdaterStatus, getState/checkForUpdates/quitAndInstall/onStateChange)
    • opencode-sdk.d.ts — Type declarations for @opencode-ai/sdk
  • src/components/editor/ — Editor UI (9 files): editor-layout, toolbar (with variables panel toggle), boolean-toolbar (contextual floating toolbar for union/subtract/intersect, shown when 2+ compatible shapes selected), tool-button, shape-tool-dropdown (rectangle/ellipse/line/path + icon picker + image import), top-bar (with AgentStatusButton), status-bar, page-tabs (multi-page navigation with context menu), update-ready-banner (Electron auto-updater notification)
  • src/components/panels/ — Panels (26 files):
    • layer-panel.tsx / layer-item.tsx / layer-context-menu.tsx — Tree view with drag-and-drop reordering and drop-into-children (above/below/inside), visibility/lock toggles, context menu, rename
    • property-panel.tsx — Unified property panel
    • fill-section.tsx — Solid + gradient fill, variable picker integration for color binding
    • stroke-section.tsx — Stroke color/width/dash, variable picker for stroke color binding
    • corner-radius-section.tsx — Unified or 4-point corner radius
    • size-section.tsx — Position, size, rotation
    • text-section.tsx — Font, size, weight, spacing, alignment
    • text-layout-section.tsx — Text node layout controls (auto/fixed-width/fixed-height modes, fill width/height)
    • icon-section.tsx — Icon property panel section: current icon name, library dropdown, icon picker
    • effects-section.tsx — Shadow and blur
    • export-section.tsx — Per-layer export to PNG/SVG with scale options (1x/2x/3x)
    • layout-section.tsx — Auto-layout (none/vertical/horizontal), gap, padding, justify, align; variable picker for gap/padding binding
    • layout-padding-section.tsx — Extracted padding controls: single/axis/T-R-B-L modes with popover mode switcher
    • appearance-section.tsx — Opacity, visibility, lock, flip; variable picker for opacity binding
    • ai-chat-panel.tsx / chat-message.tsx — AI chat with markdown, design block collapse, apply design, image attachment upload (paperclip button, preview strip, 5MB/4-image limit)
    • ai-chat-handlers.tsuseChatHandlers hook, isDesignRequest, buildContextString helpers extracted from ai-chat-panel
    • ai-chat-checklist.tsxFixedChecklist component for AI generation progress display
    • code-panel.tsx — Code generation output (React/Tailwind, HTML/CSS, CSS Variables)
    • component-browser-panel.tsx / component-browser-grid.tsx / component-browser-card.tsx — Resizable floating panel for browsing, importing, and inserting UIKit components with category tabs and search
    • variables-panel.tsx — Design variables management: theme axes as tabs, variant columns, resizable floating panel, add/rename/delete themes and variants
    • variable-row.tsx — Individual variable row: type icon, editable name, per-theme-variant value cells (color picker, number input, text input), context menu
  • src/components/shared/ — Reusable UI (9 files): ColorPicker, NumberInput, SectionHeader, ExportDialog, SaveDialog, AgentSettingsDialog, IconPickerDialog, VariablePicker, FigmaImportDialog
  • src/components/icons/ — Provider/brand logos: ClaudeLogo, OpenAILogo, OpenCodeLogo, CopilotLogo, FigmaLogo
  • src/components/ui/ — shadcn/ui primitives: Button, Select, Separator, Slider, Switch, Toggle, Tooltip
  • src/services/ai/ — AI services (20 files):
    • ai-service.ts — Main AI chat API wrapper, model negotiation, provider selection
    • ai-prompts.ts — System prompts for design generation, context building
    • ai-types.tsChatMessage (with attachments?: ChatAttachment[]), ChatAttachment (id, name, mediaType, data, size), AIDesignRequest, OrchestratorPlan, streaming response types
    • ai-runtime-config.ts — Configuration constants for AI timeouts, thinking modes, effort levels, prompt length limits
    • design-generator.ts — Top-level generateDesign/generateDesignModification with orchestrator fallback, re-exports from design-parser and design-canvas-ops
    • design-parser.ts — Pure JSON/JSONL parsing: extractJsonFromResponse, extractStreamingNodes, parseJsonlToTree, node validation and scoring
    • design-canvas-ops.ts — Canvas mutation operations: insertStreamingNode, applyNodesToCanvas, upsertNodesToCanvas, animateNodesToCanvas, generation state management, sanitization and heuristics
    • design-node-sanitization.ts — Node cloning and merging utilities: deepCloneNode, setNodeChildren, mergeNodeForProgressiveUpsert (extracted from design-canvas-ops)
    • design-animation.ts — Fade-in animation coordination for generated design nodes
    • design-validation.ts — Post-generation screenshot validation using vision API to detect and auto-fix visual issues
    • generation-utils.ts — Pure utilities for text measurement, size/padding parsing, phone placeholder generation, color extraction
    • icon-resolver.ts — Auto-resolves AI-generated icon path nodes by name to verified Lucide SVG paths
    • role-resolver.ts — Registry-based system for applying role-specific defaults (button padding, card gaps) and tree post-pass fixes
    • role-definitions/ — Modular role definition files: index, content, display, interactive, layout, navigation, media, typography, table
    • orchestrator.ts — Orchestrator entry point: executeOrchestration, callOrchestrator, plan parsing
    • orchestrator-sub-agent.ts — Sub-agent execution: executeSubAgentsSequentially, executeSubAgent, prompt building, retry/fallback logic
    • orchestrator-progress.tsemitProgress, buildFinalStepTags for streaming progress updates
    • orchestrator-prompts.ts — Ultra-lightweight orchestrator prompt for spatial decomposition
    • orchestrator-prompt-optimizer.ts — Prompt preparation, compression, timeout calculation, fallback plan generation
    • context-optimizer.ts — Chat history trimming, sliding window to prevent unbounded context growth
  • src/services/figma/ — Figma .fig file import pipeline (11 files):
    • fig-parser.ts — Binary .fig file parser
    • figma-types.ts — Figma internal type definitions
    • figma-node-mapper.ts — Maps Figma nodes to PenNodes
    • figma-fill-mapper.ts — Converts Figma fills to PenFill
    • figma-stroke-mapper.ts — Converts Figma strokes to PenStroke
    • figma-effect-mapper.ts — Converts Figma effects to PenEffect
    • figma-layout-mapper.ts — Maps Figma auto-layout to PenNode layout props
    • figma-text-mapper.ts — Converts Figma text styles
    • figma-vector-decoder.ts — Decodes Figma vector geometry
    • figma-color-utils.ts — Color space conversion utilities
    • figma-image-resolver.ts — Resolves image blob references
  • src/services/codegen/ — React+Tailwind and HTML+CSS code generators (output var(--name) for $variable refs), CSS variables generator
  • src/hooks/ — Hooks (2 files):
    • use-keyboard-shortcuts.ts — Global keyboard event handling: tools, clipboard, undo/redo, save, select all, delete, arrow nudge, z-order, boolean operations (Cmd+Alt+U/S/I)
    • use-electron-menu.ts — Electron native menu IPC listener: dispatches menu actions (new, open, save, save-as, undo, redo, etc.) to Zustand stores; also handles onOpenFile for .op file association
  • src/lib/ — Utility functions (utils.ts with cn() for class merging)
  • src/uikit/ — UI kit system (3 files + kits/ subdir):
    • built-in-registry.ts — Default built-in UIKit with standard UI components
    • kit-import-export.ts — Import/export UIKits from .pen files with variable reference collection
    • kit-utils.ts — UIKit utilities: extract components from documents, find reusable nodes, deep clone
    • kits/ — Default kit data: default-kit.ts, default-kit-meta.ts
  • src/mcp/ — MCP server integration (2 files + tools/ and utils/ subdirs):
    • server.ts — MCP server entry point, tool registration
    • document-manager.ts — MCP utility for reading, writing, and caching PenDocuments from disk
    • tools/ — Individual MCP tool implementations: batch-design.ts, batch-get.ts, find-empty-space.ts, open-document.ts, snapshot-layout.ts, variables.ts, pages.ts (page CRUD: add/remove/rename/reorder/duplicate)
    • utils/ — Shared utilities: id.ts, node-operations.ts (page-aware getDocChildren/setDocChildren)
  • src/utils/ — File operations (save/open .pen), export (PNG/SVG), node clone, pen file normalization (format fixes only, preserves $variable refs), SVG parser (import SVG to editable PenNodes), syntax highlight, boolean operations (union/subtract/intersect via Paper.js)
  • server/api/ai/ — Nitro server API (7 files): chat.ts (streaming SSE with thinking state, multimodal image attachments per provider), generate.ts (non-streaming generation), connect-agent.ts (Claude Code/Codex CLI/OpenCode/Copilot connection), models.ts (model definitions), validate.ts (vision-based post-generation validation), mcp-install.ts (MCP server install/uninstall into CLI tool configs), icon.ts (icon name → SVG path resolution via local Iconify sets). Supports Anthropic API key or Claude Agent SDK (local OAuth) as dual providers
  • server/utils/ — Server utilities (5 files):
    • resolve-claude-cli.ts — Resolves standalone claude binary path (handles Nitro bundling issues with SDK's import.meta.url)
    • resolve-claude-agent-env.ts — Builds Claude Agent SDK environment: merges ~/.claude/settings.json env, validates ANTHROPIC_CUSTOM_HEADERS, handles auth token compat
    • opencode-client.ts — Shared OpenCode client manager, reuses server on port 4096 with random port fallback
    • codex-client.ts — Codex CLI client wrapper with JSON streaming, thinking mode support, timeout handling, optional imageFiles for vision queries
    • copilot-client.ts — Resolves standalone copilot binary path to avoid Bun's node:sqlite incompatibility with bundled CLI

Fabric.js v7 Gotchas

  • Default origin is center/center — always set originX: 'left', originY: 'top' on objects so left/top means top-left corner
  • Pointer capture — Fabric captures pointers on upperCanvasEl; attach pointer listeners there, not on document
  • Coordinate conversion — use canvas.getScenePoint(e) with canvas.calcOffset() for accurate pointer-to-scene mapping
  • Default strokeWidth is 1 — explicitly set strokeWidth: 0 when no stroke is desired
  • Tool isolation — when a drawing tool is active, set both canvas.selection = false and canvas.skipTargetFind = true to prevent Fabric from selecting existing objects during draw. Restore both when switching back to select tool.
  • Parent-child transforms — nodes are flattened to absolute coordinates for Fabric; nodeRenderInfo stores parent offsets for converting back to relative coordinates. parent-child-transform.ts handles propagating transforms to descendants during drag/scale/rotate.

Canvas Tool State Management

When switching tools, two subscribers manage canvas state:

  • use-canvas-events.ts — sets selection/skipTargetFind based on drawing vs select tool
  • use-canvas-viewport.ts — also manages selection/skipTargetFind for tool switches and space-key panning

Both must stay consistent: only select tool (without space pressed) should have selection = true and skipTargetFind = false.

Routing

File-based routing via TanStack Router. Routes in src/routes/, auto-generated tree in src/routeTree.gen.ts (do not edit).

  • / — Landing page
  • /editor — Main design editor

Path Aliases

@/* maps to ./src/* (configured in tsconfig.json and vite.config.ts).

Styling

Tailwind CSS v4 imported via src/styles.css. UI primitives from shadcn/ui (src/components/ui/). Icons from lucide-react. shadcn/ui config in components.json.

Electron Desktop App

  • electron/main.ts — Main process: window creation, Nitro server fork, IPC for native file dialogs, native application menu, auto-updater, macOS traffic-light padding (auto-hidden in fullscreen), .op file association handling (open-file event on macOS, CLI args + single-instance lock on Windows/Linux)
  • electron/preload.ts — Context bridge for renderer ↔ main IPC (file dialogs, menu actions, updater state, onOpenFile/readFile for file association)
  • electron-builder.yml — Packaging config: macOS (dmg/zip), Windows (nsis/portable), Linux (AppImage/deb), .op file association (fileAssociations)
  • scripts/electron-dev.ts — Dev workflow: starts Vite → waits for port 3000 → compiles electron/ with esbuild → launches Electron
  • Build flow: BUILD_TARGET=electron bun run buildbun run electron:compilenpx electron-builder
  • In production, Nitro server is forked as a child process on a random port; Electron loads http://127.0.0.1:{port}/editor
  • Auto-updater checks GitHub Releases on startup and every hour; update-ready-banner.tsx shows download progress and "Restart & Install" prompt
  • File association: .op files are registered as OpenPencil documents via fileAssociations in electron-builder.yml. On macOS the open-file app event handles double-click/drag; on Windows/Linux requestSingleInstanceLock + second-instance event forwards CLI args to the existing window. Pending file paths are queued until the renderer is ready, then sent via file:open IPC channel. The renderer (use-electron-menu.ts) listens via onOpenFile, reads the file through file:read IPC, and calls loadDocument.

CI / CD

  • .github/workflows/ci.yml — Push/PR: type check (tsc --noEmit), tests (vitest), web build
  • .github/workflows/build-electron.yml — Tag push (v*) or manual: builds Electron for macOS, Windows, Linux in parallel, creates draft GitHub Release with all artifacts

Code Style

  • Single files must not exceed 800 lines. Split into smaller modules when they grow beyond this limit.
  • One component per file, each with a single responsibility.
  • .ts and .tsx files use kebab-case naming, e.g. canvas-store.ts, use-keyboard-shortcuts.ts.
  • UI components must use shadcn/ui design tokens (bg-card, text-foreground, border-border, etc.). No hardcoded Tailwind colors like gray-*, blue-*.
  • Toolbar button active state uses isActive conditional className (bg-primary text-primary-foreground), not Radix Toggle's data-[state=on]: selector (has twMerge conflicts).

Git Commit Convention

Use Conventional Commits format:

<type>(<scope>): <subject>

<body>

Type

  • feat — New feature
  • fix — Bug fix
  • refactor — Refactoring (no behavior change)
  • perf — Performance optimization
  • style — Code formatting (no logic change)
  • docs — Documentation
  • test — Tests
  • chore — Build / tooling / dependency changes

Scope

By module: editor, canvas, panels, history, ai, codegen, store, types, variables, figma, mcp, electron.

Rules

  • Subject in English, lowercase start, no period, imperative mood (e.g. add, fix, remove).
  • Body is optional; explain why not what.
  • One commit per change. Do not mix unrelated changes in a single commit.

License

MIT License. See LICENSE for details.