openpencil/openspec/specs/components/spec.md
Anton A S c03f605a22 Sync specs and docs with master: live sync, effects panel, independent corners, CI
Sync 10 commits from master (68425a0..af3db9f):
- Live component-instance sync with override preservation
- Effects section in properties panel (shadow/blur controls)
- Independent corner radius controls (per-corner toggle)
- GitHub Actions CI/CD for Windows and macOS builds
- Mac shortcut fixes

Updated: 5 openspec specs, 3 VitePress docs pages, Figma comparison matrix
2026-02-28 22:20:16 +03:00

5.7 KiB

components Specification

Purpose

Components, component sets, instances — create, instantiate, detach, go-to-main component, component labels, opaque container hit testing.

Requirements

Requirement: Create component from selection

The editor SHALL convert selected frames/groups to COMPONENT type, or wrap multiple selected nodes in a new COMPONENT node (⌥⌘K). Single frame/group converts in-place; multiple nodes wraps in a bounding component.

Scenario: Convert frame to component

  • WHEN user selects a single frame and presses ⌥⌘K
  • THEN the frame's type changes to COMPONENT

Scenario: Wrap multiple nodes in component

  • WHEN user selects three rectangles and presses ⌥⌘K
  • THEN a COMPONENT node wraps them, positioned at their bounding box

Requirement: Create component set from components

The editor SHALL combine multiple selected COMPONENT nodes into a COMPONENT_SET container (⇧⌘K). The set gets a dashed purple border and a 40px padding around its children.

Scenario: Create component set

  • WHEN user selects two COMPONENT nodes and presses ⇧⌘K
  • THEN a COMPONENT_SET wraps them with dashed purple border

Requirement: Create instance from component

The editor SHALL create an INSTANCE node from a COMPONENT via context menu, copying its visual properties and deep-cloning children with componentId mapping. The instance is placed 40px to the right of the source component. Instance creation is available only through the context menu (no button in properties panel).

Scenario: Create instance via context menu

  • WHEN user right-clicks a component and selects "Create instance"
  • THEN an INSTANCE appears to the right, visually identical to the component

Scenario: Instance children have componentId mapping

  • WHEN an instance is created from a component with children [A, B]
  • THEN instance children have componentId pointing to A and B respectively

Requirement: Detach instance

The editor SHALL convert an INSTANCE back to a regular FRAME, clearing its componentId and overrides (⌥⌘B).

Scenario: Detach instance

  • WHEN user selects an instance and presses ⌥⌘B
  • THEN the instance becomes a FRAME with no component link

Requirement: Go to main component

The editor SHALL navigate to and select the main COMPONENT for a selected INSTANCE, switching pages if needed.

Scenario: Navigate to main component

  • WHEN user right-clicks an instance and selects "Go to main component"
  • THEN the main component is selected and centered in the viewport

Requirement: Component labels

The renderer SHALL draw always-visible purple labels above COMPONENT and INSTANCE nodes (or inside COMPONENT_SET children). Labels show the node name with a diamond icon.

Scenario: Component label visible

  • WHEN a COMPONENT node exists on canvas
  • THEN a purple label with the component name is rendered above it

Requirement: Component set visual treatment

COMPONENT_SET nodes SHALL render with a dashed purple border (6px dash, 4px gap, 1.5px width) instead of a solid border.

Scenario: Component set border

  • WHEN a COMPONENT_SET is on canvas
  • THEN it renders with a dashed purple border

Requirement: Opaque container hit testing

COMPONENT and INSTANCE nodes SHALL behave as opaque containers for hit testing — clicking selects the component/instance itself, not its children. Children are accessible only via double-click (deep hit test).

Scenario: Click on component child

  • WHEN user clicks a rectangle inside a component
  • THEN the component is selected, not the rectangle

Scenario: Double-click into component

  • WHEN user double-clicks a child inside a component
  • THEN the child is selected (deep selection)

Requirement: Live component-instance sync

The scene graph SHALL propagate property changes from a COMPONENT to all its INSTANCE nodes. Synced properties include: width, height, fills, strokes, effects, opacity, corner radii, layout properties, and clipsContent. The store SHALL auto-trigger sync after updateNode, commitMove, and commitResize when the edited node is inside a COMPONENT.

Scenario: Edit component updates instances

  • WHEN user changes the fill color of a main component
  • THEN all instances of that component update to the new fill color

Scenario: Resize component syncs to instances

  • WHEN user resizes a main component
  • THEN all instances resize to match

Requirement: Override preservation during sync

Instances SHALL maintain an overrides record. When syncing, properties marked in overrides are skipped. Child-level overrides use ${childId}:${propertyKey} keys. Overridable child properties include name, text, fontSize, fontWeight, fontFamily, plus all synced visual/layout properties.

Scenario: Override preserved during sync

  • WHEN an instance child has text overridden to "Custom" and the component child text changes to "New Default"
  • THEN the instance child's text remains "Custom" while non-overridden properties sync

Requirement: New children propagate to instances

When a new child is added to a COMPONENT, sync SHALL clone the new child into all existing instances.

Scenario: Add child to component

  • WHEN user adds a new rectangle inside a component that has two instances
  • THEN both instances gain a cloned copy of the new rectangle

Requirement: Instance child order matches component

After sync, instance children SHALL be reordered to match the component's child order.

Scenario: Reorder component children

  • WHEN component children are reordered from [A, B] to [B, A]
  • THEN after sync, instance children reflect the new order