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
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).
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
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