diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8789a1c4f..0a5fed4be 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,8 @@
- Mobile UI cleanup: extract shared `colorToCSS` util to core, `initials` to `src/utils/text`, `toolIcons` to `src/utils/tools`; replace hand-rolled dropdowns with reka-ui Popover/DropdownMenu; narrow `mobileDrawerSnap` type to string union; move magic numbers to constants; disable PWA service worker in dev mode
- 83 new E2E tests (57 → 140): design panel, code panel, components, copy/paste, multi-page, text editing, keyboard shortcuts, context menu
- 150 new unit tests (588 → 738): color, undo, snap, vector, style-runs, text-editor
+- 48 new E2E tests (9 spec files) + 26 mutation unit tests + store/canvas test helpers
+- Add `data-test-id` attributes to AppearanceSection, LayoutSection, TypographySection, VariablesDialog, EditorView
## 0.7.0 — 2026-03-05
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/.openspec.yaml b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/.openspec.yaml
new file mode 100644
index 000000000..f1842c5f1
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-03-07
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/design.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/design.md
new file mode 100644
index 000000000..2183f4eec
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/design.md
@@ -0,0 +1,45 @@
+## Context
+
+The project has 17 Playwright E2E spec files covering ~140 test cases, but 12 of 20 manual test plan sections are sparsely covered. All existing tests share a common pattern: `window.__OPEN_PENCIL_STORE__` for state assertions, `CanvasHelper` for canvas interactions, `test.describe.configure({ mode: 'serial' })` with a shared page per file, and page-level event forwarding for error tracking. New tests follow the same conventions.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Cover every gap identified in the 20-section manual test plan
+- Use `window.__OPEN_PENCIL_STORE__` for deterministic state assertions (no pixel-matching for non-visual tests)
+- Keep each spec file self-contained: `beforeAll` creates a fresh page, draws needed fixtures, `afterAll` closes
+- Use `canvas.waitForRender()` after every interaction, never arbitrary `waitForTimeout`
+- Tests that need visual confirmation (snap guides, hover highlight) use `toHaveScreenshot` with existing snapshot infra
+
+**Non-Goals:**
+- Cross-browser testing (Chromium only, matching existing suite)
+- Collaboration/P2P tests (require two browser instances and signaling — separate effort)
+- Export file content validation (OS dialog interception is too brittle; test UI state only)
+- Font picker rendering (system fonts vary across CI environments)
+
+## Decisions
+
+**1. CanvasHelper extension over ad-hoc helpers**
+Add `marquee()`, `hover()`, `dragScrubInput()`, and `shiftDrag()` to the existing `CanvasHelper` class. Alternative: inline helpers per test file. Rejected because inline helpers duplicate boilerplate and drift from each other.
+
+**2. Store assertions for invisible/locked nodes**
+Nodes that are invisible or locked cannot be hit-tested on canvas. Use `page.evaluate(() => window.__OPEN_PENCIL_STORE__!.graph.getNode(id))` to assert state directly. Consistent with how `context-menu.spec.ts` already handles visibility toggle.
+
+**3. Snap guide tests use screenshot comparison**
+Snap guide lines are rendered on the canvas (CanvasKit), not DOM. The only way to assert their presence is `toHaveScreenshot`. Use `--update-snapshots` once on a known-good run; CI then catches regressions. Alternative: expose a `debugSnapLines` store getter — rejected as production API pollution.
+
+**4. ScrubInput drag via `mouse.move` with delta**
+ScrubInput responds to `pointerdown` + `pointermove` on the outer container. Simulate with `page.mouse.move(x, y)` → `page.mouse.down()` → `page.mouse.move(x + delta, y)` → `page.mouse.up()`. A `dragScrubInput(locator, delta)` helper encapsulates this.
+
+**5. Variables dialog accessed via store page.evaluate**
+The dialog can be opened by clicking the settings icon in `VariablesSection` (only visible without selection). Rather than pixel-hunting for the icon, use `page.locator('[data-test-id="variables-settings-btn"]')`. If that attribute doesn't exist, fall back to `page.evaluate(() => window.__OPEN_PENCIL_STORE__!.openVariablesDialog())` — check during implementation.
+
+**6. Export tests assert UI state, not file content**
+`showSaveFilePicker` is a native dialog; Playwright cannot intercept it in Chrome without mocking. Tests verify: export rows are added/removed, preview image appears, format selector value changes. No file download assertion.
+
+## Risks / Trade-offs
+
+- [ScrubInput pointer simulation] → ScrubInput uses `setPointerCapture` which may not behave identically in headless Chromium. Mitigation: test with `--enable-unsafe-swiftshader` (already in playwright.config.ts).
+- [Snap guide snapshots] → Guide rendering depends on node positions set up in `beforeAll`. If canvas DPI/viewport changes, snapshots break. Mitigation: pin `deviceScaleFactor: 2` and `viewport: 1280×800` (already in config).
+- [Variables dialog test-id] → `data-test-id` may not exist on the settings button. Mitigation: check during implementation; add attribute if missing (1-line change in `VariablesSection.vue`).
+- [Font picker test] → Excluded from scope due to CI font availability variance.
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/proposal.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/proposal.md
new file mode 100644
index 000000000..42594bf23
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/proposal.md
@@ -0,0 +1,39 @@
+## Why
+
+The existing E2E suite covers core happy paths but leaves 12 of 20 manual test plan sections with significant uncovered scenarios. Gaps include canvas manipulation (marquee select, resize handles, rotation, Space+drag pan), toolbar flyouts (Polygon/Star/Pen drawing), properties panel interactions (ScrubInput drag, corner radius, gradient, variable binding, alignment), text editing (cursor positioning, formatting shortcuts, font picker), auto-layout controls (direction, gap, padding, alignment grid), snap guides, panel resizing, variables dialog, export, and collaboration UI.
+
+## What Changes
+
+- Add `tests/e2e/canvas-manipulation.spec.ts` — marquee select, resize handles, rotation, Alt+drag duplicate, Shift+Arrow nudge, hover highlight
+- Add `tests/e2e/toolbar.spec.ts` — flyout chevrons, Polygon, Star tools, Pen drawing (corner, curve, close, Escape, preview), active tracking
+- Add `tests/e2e/properties-panel.spec.ts` — ScrubInput drag-to-change, corner radius (uniform + independent), fill gradient, variable bind/unbind, flip/rotate buttons, alignment buttons, clip content
+- Add `tests/e2e/text-formatting.spec.ts` — cursor positioning, drag select, word/line select, word/line navigation shortcuts, bold/italic/underline via keyboard and buttons, font picker search
+- Add `tests/e2e/auto-layout.spec.ts` — Shift+A on selection, direction toggle (H/V/wrap), gap ScrubInput, padding (uniform + per-side), alignment grid 3×3, sizing modes
+- Add `tests/e2e/snap-guides.spec.ts` — edge snap, center snap (visual: guide lines appear during drag)
+- Add `tests/e2e/panels.spec.ts` — layers panel resize, panel size after reload, ⌘\ toggle UI
+- Add `tests/e2e/variables-dialog.spec.ts` — open dialog, search filter, edit value, color variable picker
+- Add `tests/e2e/export.spec.ts` — PNG/JPG/WEBP/SVG export settings, multi-export rows, preview toggle
+- Extend `tests/helpers/canvas.ts` — add `marquee()`, `hover()`, `rightDrag()`, `dragScrubInput()` helpers
+
+## Capabilities
+
+### New Capabilities
+- `e2e-canvas-manipulation`: E2E tests for marquee selection, resize, rotation, duplicate, nudge, hover highlight
+- `e2e-toolbar`: E2E tests for toolbar flyouts and Pen tool drawing flows
+- `e2e-properties-panel`: E2E tests for ScrubInput drag, corner radius, gradient, variable binding, panel interactions
+- `e2e-text-formatting`: E2E tests for text cursor, selection, formatting shortcuts and buttons
+- `e2e-auto-layout`: E2E tests for auto-layout controls: direction, gap, padding, alignment grid, sizing
+- `e2e-snap-guides`: E2E tests for snap guide visibility during drag
+- `e2e-panels`: E2E tests for resizable panels and UI toggle
+- `e2e-variables-dialog`: E2E tests for variables dialog open, search, edit, color picker
+- `e2e-export`: E2E tests for export format settings and preview
+
+### Modified Capabilities
+- `testing`: Add new E2E requirements for all 9 new spec files and CanvasHelper extension
+
+## Impact
+
+- `tests/e2e/` — 9 new spec files (~1800 lines)
+- `tests/helpers/canvas.ts` — 4 new helper methods
+- No production code changes
+- All tests target `http://localhost:1420` with `window.__OPEN_PENCIL_STORE__` for state assertions
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-auto-layout/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-auto-layout/spec.md
new file mode 100644
index 000000000..dc50cee82
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-auto-layout/spec.md
@@ -0,0 +1,43 @@
+## ADDED Requirements
+
+### Requirement: Auto-layout Shift+A on selection E2E
+The E2E suite SHALL verify that Shift+A on multiple selected nodes wraps them in an auto-layout frame.
+
+#### Scenario: Shift+A wraps nodes in auto-layout frame
+- **WHEN** two rectangles are selected and Shift+A is pressed
+- **THEN** a single FRAME node exists on the page with `layoutMode` set to `HORIZONTAL` or `VERTICAL` and the rectangles are its children
+
+### Requirement: Auto-layout direction toggle E2E
+The E2E suite SHALL verify that clicking the horizontal/vertical direction buttons changes `layoutMode`.
+
+#### Scenario: Direction button toggles layoutMode
+- **WHEN** an auto-layout frame is selected and the vertical direction button is clicked in LayoutSection
+- **THEN** `frame.layoutMode` equals `VERTICAL`
+
+### Requirement: Auto-layout gap E2E
+The E2E suite SHALL verify that changing the gap ScrubInput updates `itemSpacing`.
+
+#### Scenario: Gap ScrubInput changes itemSpacing
+- **WHEN** an auto-layout frame is selected and the gap ScrubInput value is changed to 20
+- **THEN** `frame.itemSpacing` equals 20
+
+### Requirement: Auto-layout padding uniform E2E
+The E2E suite SHALL verify that changing the uniform padding input updates all four padding properties.
+
+#### Scenario: Uniform padding updates all sides
+- **WHEN** an auto-layout frame is selected and padding is set to 16
+- **THEN** `frame.paddingTop`, `frame.paddingRight`, `frame.paddingBottom`, and `frame.paddingLeft` all equal 16
+
+### Requirement: Auto-layout alignment grid E2E
+The E2E suite SHALL verify that clicking a cell in the 3×3 alignment grid updates `primaryAxisAlign` and `counterAxisAlign`.
+
+#### Scenario: Center-center alignment set
+- **WHEN** the center-center cell of the alignment grid is clicked
+- **THEN** `frame.primaryAxisAlign` equals `CENTER` and `frame.counterAxisAlign` equals `CENTER`
+
+### Requirement: Auto-layout remove E2E
+The E2E suite SHALL verify that clicking the remove (−) button in the auto-layout section sets `layoutMode` to NONE.
+
+#### Scenario: Remove auto-layout resets layoutMode
+- **WHEN** an auto-layout frame is selected and the remove auto-layout button is clicked
+- **THEN** `frame.layoutMode` equals `NONE`
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-canvas-manipulation/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-canvas-manipulation/spec.md
new file mode 100644
index 000000000..d8f970e0d
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-canvas-manipulation/spec.md
@@ -0,0 +1,47 @@
+## ADDED Requirements
+
+### Requirement: Marquee selection E2E
+The E2E suite SHALL verify that dragging from an empty canvas area selects all nodes whose bounds intersect the drag rectangle.
+
+#### Scenario: Marquee selects intersected nodes
+- **WHEN** two rectangles are drawn and mouse is dragged from a point before both to a point after both
+- **THEN** both nodes appear in `store.state.selectedIds`
+
+#### Scenario: Marquee on empty area deselects
+- **WHEN** a node is selected and mouse is dragged across empty canvas
+- **THEN** `store.state.selectedIds.size` equals 0
+
+### Requirement: Resize handles E2E
+The E2E suite SHALL verify that dragging a resize handle changes the node's width and height.
+
+#### Scenario: Corner resize increases dimensions
+- **WHEN** a rectangle is selected and its bottom-right corner handle is dragged 50px right and 50px down
+- **THEN** node width and height each increase by approximately 50px
+
+### Requirement: Rotation handle E2E
+The E2E suite SHALL verify that the rotation handle rotates a node.
+
+#### Scenario: Rotation changes node rotation
+- **WHEN** a rectangle is selected and the cursor is positioned just outside a corner, then dragged in an arc
+- **THEN** `node.rotation` is non-zero
+
+### Requirement: Alt+drag duplicate E2E
+The E2E suite SHALL verify that Alt+drag creates a copy of the node.
+
+#### Scenario: Alt drag creates duplicate
+- **WHEN** a rectangle is selected and dragged with Alt held
+- **THEN** the page has one more node than before the drag
+
+### Requirement: Shift+Arrow nudge E2E
+The E2E suite SHALL verify that Shift+Arrow nudges a node by 10px.
+
+#### Scenario: Shift+ArrowRight nudges 10px
+- **WHEN** a node is selected and Shift+ArrowRight is pressed
+- **THEN** `node.x` increases by 10
+
+### Requirement: Hover highlight E2E
+The E2E suite SHALL verify that hovering over a node (without clicking) triggers a visible highlight outline on the canvas.
+
+#### Scenario: Hover renders highlight
+- **WHEN** the mouse moves over a rectangle without clicking
+- **THEN** a screenshot taken during hover differs from a screenshot taken with the mouse away (highlight outline visible)
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-export/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-export/spec.md
new file mode 100644
index 000000000..d10cbd266
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-export/spec.md
@@ -0,0 +1,33 @@
+## ADDED Requirements
+
+### Requirement: Export format selection E2E
+The E2E suite SHALL verify that changing the export format selector updates the format value in the export section.
+
+#### Scenario: Format selector changes to JPG
+- **WHEN** a node with an export setting is selected and the format selector is changed to JPG
+- **THEN** the export row displays JPG as the selected format
+
+### Requirement: Export multi-row add/remove E2E
+The E2E suite SHALL verify that clicking + adds a new export row and − removes it.
+
+#### Scenario: Add export row
+- **WHEN** a node is selected and the + button in ExportSection is clicked
+- **THEN** one more export setting row is visible than before
+
+#### Scenario: Remove export row
+- **WHEN** a node has two export rows and the − button on one row is clicked
+- **THEN** only one export row remains
+
+### Requirement: Export preview toggle E2E
+The E2E suite SHALL verify that clicking the Preview button shows a preview image element.
+
+#### Scenario: Preview button shows image
+- **WHEN** a node with an export setting is selected and the Preview button is clicked
+- **THEN** an image element (checkerboard preview) becomes visible in the export section
+
+### Requirement: SVG format hides scale selector E2E
+The E2E suite SHALL verify that when SVG format is selected, the scale (multiplier) selector is hidden.
+
+#### Scenario: SVG hides scale input
+- **WHEN** an export row format is set to SVG
+- **THEN** no scale/multiplier input is visible for that row
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-panels/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-panels/spec.md
new file mode 100644
index 000000000..57c053d4b
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-panels/spec.md
@@ -0,0 +1,26 @@
+## ADDED Requirements
+
+### Requirement: Layers panel resize E2E
+The E2E suite SHALL verify that dragging the resize handle between the left panel and canvas changes the panel width.
+
+#### Scenario: Panel resize handle changes width
+- **WHEN** the splitter handle on the left panel is dragged 80px to the right
+- **THEN** the layers panel DOM element has a wider width than before the drag
+
+### Requirement: UI toggle persistence across reload E2E
+The E2E suite SHALL verify that panel sizes are preserved after a page reload.
+
+#### Scenario: Panel width persists after reload
+- **WHEN** the layers panel is resized and the page is reloaded
+- **THEN** the panel width after reload matches the width set before reload (within 2px)
+
+### Requirement: ⌘\\ hides and shows panels E2E
+The E2E suite SHALL verify that pressing ⌘\\ toggles the visibility of the left and right panels.
+
+#### Scenario: Cmd+Backslash hides panels
+- **WHEN** ⌘\\ is pressed while panels are visible
+- **THEN** the layers panel and properties panel elements are not visible in the DOM
+
+#### Scenario: Cmd+Backslash shows panels again
+- **WHEN** ⌘\\ is pressed while panels are hidden
+- **THEN** the layers panel and properties panel elements are visible again
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-properties-panel/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-properties-panel/spec.md
new file mode 100644
index 000000000..8f9bcf527
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-properties-panel/spec.md
@@ -0,0 +1,57 @@
+## ADDED Requirements
+
+### Requirement: ScrubInput drag-to-change E2E
+The E2E suite SHALL verify that dragging horizontally on a ScrubInput changes the node property value.
+
+#### Scenario: Drag X ScrubInput moves node
+- **WHEN** a rectangle is selected and the X ScrubInput is dragged 50px to the right
+- **THEN** `node.x` increases by approximately 50
+
+### Requirement: Corner radius uniform E2E
+The E2E suite SHALL verify that typing a corner radius value in the appearance section applies it to all corners.
+
+#### Scenario: Uniform corner radius applied
+- **WHEN** a rectangle is selected and a corner radius value is typed into the appearance input
+- **THEN** `node.cornerRadius` equals the entered value
+
+### Requirement: Corner radius independent E2E
+The E2E suite SHALL verify that toggling to independent corners splits the input into four separate fields.
+
+#### Scenario: Independent corners toggle shows four fields
+- **WHEN** the independent corners button is clicked
+- **THEN** four corner radius inputs are visible in the panel
+
+### Requirement: Fill gradient switch E2E
+The E2E suite SHALL verify that switching fill type to Linear Gradient updates the node fill.
+
+#### Scenario: Fill type switches to gradient
+- **WHEN** a rectangle with a solid fill is selected and fill type is changed to LINEAR
+- **THEN** `node.fills[0].type` equals `GRADIENT_LINEAR`
+
+### Requirement: Variable bind E2E
+The E2E suite SHALL verify that binding a color variable to a fill displays a variable badge.
+
+#### Scenario: Variable badge appears after bind
+- **WHEN** a color variable exists and is bound to a node's fill
+- **THEN** a variable badge element is visible in the fill section
+
+### Requirement: Alignment buttons E2E
+The E2E suite SHALL verify that clicking alignment buttons aligns multiple selected nodes.
+
+#### Scenario: Align left aligns nodes
+- **WHEN** two rectangles at different X positions are selected and Align Left is clicked
+- **THEN** both nodes have the same `x` coordinate (leftmost of the two)
+
+### Requirement: Flip horizontal E2E
+The E2E suite SHALL verify that clicking the flip horizontal button sets `flipX` on the node.
+
+#### Scenario: Flip horizontal toggles
+- **WHEN** a rectangle is selected and flip horizontal button is clicked
+- **THEN** `node.flipX` is true
+
+### Requirement: Clip content toggle E2E
+The E2E suite SHALL verify that the clip content checkbox toggles `clipsContent` on a frame.
+
+#### Scenario: Clip content checkbox toggles
+- **WHEN** a frame is selected and the clip content checkbox is clicked
+- **THEN** `node.clipsContent` changes to the opposite boolean value
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-snap-guides/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-snap-guides/spec.md
new file mode 100644
index 000000000..8719fbbd6
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-snap-guides/spec.md
@@ -0,0 +1,15 @@
+## ADDED Requirements
+
+### Requirement: Edge snap guide visual E2E
+The E2E suite SHALL verify that dragging a node to align its edge with another node causes snap guide lines to appear on the canvas.
+
+#### Scenario: Edge snap guide renders during drag
+- **WHEN** two rectangles are positioned such that dragging one aligns its left edge with the other's left edge
+- **THEN** a screenshot taken mid-drag (while edges are aligned) contains red guide line pixels not present in the pre-drag screenshot
+
+### Requirement: Center snap guide visual E2E
+The E2E suite SHALL verify that dragging a node to align its center with another node causes a center snap guide line to appear.
+
+#### Scenario: Center snap guide renders during drag
+- **WHEN** two rectangles are positioned such that dragging one aligns its horizontal center with the other's center
+- **THEN** a screenshot taken mid-drag contains guide line pixels indicating center alignment
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-text-formatting/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-text-formatting/spec.md
new file mode 100644
index 000000000..cb26fa6e3
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-text-formatting/spec.md
@@ -0,0 +1,43 @@
+## ADDED Requirements
+
+### Requirement: Text cursor positioning E2E
+The E2E suite SHALL verify that clicking inside a text node in edit mode positions the cursor near the clicked glyph.
+
+#### Scenario: Click positions cursor
+- **WHEN** a text node is double-clicked to enter edit mode and then a position inside the text is clicked
+- **THEN** the text node remains in edit mode without errors
+
+### Requirement: Text word selection E2E
+The E2E suite SHALL verify that double-clicking on a word selects that word.
+
+#### Scenario: Double-click selects word
+- **WHEN** a text node is in edit mode and a word is double-clicked
+- **THEN** the canvas screenshot shows the word highlighted (differs from no-selection screenshot)
+
+### Requirement: Bold shortcut E2E
+The E2E suite SHALL verify that ⌘B toggles bold formatting on selected text.
+
+#### Scenario: Cmd+B toggles bold
+- **WHEN** a text node is in edit mode, all text is selected, and ⌘B is pressed
+- **THEN** `node.fontWeight` changes to 700 (bold) or back to 400 (normal)
+
+### Requirement: Italic shortcut E2E
+The E2E suite SHALL verify that ⌘I toggles italic formatting on selected text.
+
+#### Scenario: Cmd+I toggles italic
+- **WHEN** a text node is in edit mode with all text selected and ⌘I is pressed
+- **THEN** `node.italic` toggles to the opposite boolean
+
+### Requirement: Word navigation shortcut E2E
+The E2E suite SHALL verify that Alt+ArrowRight moves the cursor by one word.
+
+#### Scenario: Alt+ArrowRight moves by word
+- **WHEN** a text node is in edit mode with cursor at position 0 and Alt+ArrowRight is pressed
+- **THEN** no JavaScript errors are thrown and the editor remains in text mode
+
+### Requirement: Typography section buttons E2E
+The E2E suite SHALL verify that the Bold button in the typography section applies formatting.
+
+#### Scenario: Bold button in panel toggles bold
+- **WHEN** a text node is selected (not in edit mode) and the Bold button in DesignPanel is clicked
+- **THEN** `node.fontWeight` changes
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-toolbar/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-toolbar/spec.md
new file mode 100644
index 000000000..903dc59d3
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-toolbar/spec.md
@@ -0,0 +1,50 @@
+## ADDED Requirements
+
+### Requirement: Shapes flyout E2E
+The E2E suite SHALL verify that the shapes flyout chevron opens a menu with Polygon and Star tools.
+
+#### Scenario: Shapes flyout opens
+- **WHEN** the shapes flyout trigger is clicked
+- **THEN** the flyout menu is visible with Polygon and Star items
+
+### Requirement: Polygon tool E2E
+The E2E suite SHALL verify that selecting Polygon from the flyout and dragging creates a POLYGON node.
+
+#### Scenario: Polygon created
+- **WHEN** Polygon is selected from flyout and drag is performed on canvas
+- **THEN** a node with type POLYGON exists on the current page
+
+### Requirement: Star tool E2E
+The E2E suite SHALL verify that selecting Star from the flyout and dragging creates a STAR node.
+
+#### Scenario: Star created
+- **WHEN** Star is selected from flyout and drag is performed on canvas
+- **THEN** a node with type STAR exists on the current page
+
+### Requirement: Pen tool corner points E2E
+The E2E suite SHALL verify that three clicks with the Pen tool create a VECTOR node with 3 vertices.
+
+#### Scenario: Three clicks create three vertices
+- **WHEN** Pen tool is active and three distinct canvas positions are clicked, then Enter pressed (commits open path)
+- **THEN** a VECTOR node exists with `vectorNetwork.vertices.length === 3`
+
+### Requirement: Pen tool close path E2E
+The E2E suite SHALL verify that clicking the first point of a Pen path closes it.
+
+#### Scenario: Click first point closes path
+- **WHEN** Pen tool creates 3 vertices and the user clicks on the first vertex position
+- **THEN** the resulting VECTOR node has a closed segment (no open endpoint)
+
+### Requirement: Pen tool Escape cancels path E2E
+The E2E suite SHALL verify that pressing Escape during Pen drawing cancels the current path without creating a node.
+
+#### Scenario: Escape cancels open path
+- **WHEN** Pen tool creates 2 vertices and Escape is pressed
+- **THEN** no new VECTOR node is created on the page (penCancel discards in-progress path)
+
+### Requirement: Frame flyout E2E
+The E2E suite SHALL verify that the Frame flyout chevron shows Frame and Section options.
+
+#### Scenario: Frame flyout shows both options
+- **WHEN** the frame flyout trigger is clicked
+- **THEN** Frame (F) and Section (S) items are visible in the flyout
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-variables-dialog/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-variables-dialog/spec.md
new file mode 100644
index 000000000..47ec0ad67
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/e2e-variables-dialog/spec.md
@@ -0,0 +1,29 @@
+## ADDED Requirements
+
+### Requirement: Variables dialog open E2E
+The E2E suite SHALL verify that the variables dialog can be opened when no node is selected.
+
+#### Scenario: Variables dialog opens
+- **WHEN** no node is selected and the variables settings button is activated
+- **THEN** a dialog element with variables table is visible
+
+### Requirement: Variables dialog search E2E
+The E2E suite SHALL verify that typing in the search field filters the variables table.
+
+#### Scenario: Search filters variable rows
+- **WHEN** variables exist with different names and a search term is typed
+- **THEN** only rows whose names match the search term are visible
+
+### Requirement: Variables dialog edit value E2E
+The E2E suite SHALL verify that clicking a variable value cell makes it editable.
+
+#### Scenario: Click cell to edit
+- **WHEN** a variable value cell is clicked in the dialog
+- **THEN** an input field is focused in that cell
+
+### Requirement: Color variable picker E2E
+The E2E suite SHALL verify that clicking a color variable swatch opens a color picker.
+
+#### Scenario: Color swatch opens picker
+- **WHEN** a color variable's swatch is clicked in the dialog
+- **THEN** a color picker element becomes visible
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/testing/spec.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/testing/spec.md
new file mode 100644
index 000000000..2a2dcde36
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/specs/testing/spec.md
@@ -0,0 +1,71 @@
+## ADDED Requirements
+
+### Requirement: Canvas manipulation E2E tests
+The E2E suite SHALL include tests for `tests/e2e/canvas-manipulation.spec.ts` covering: marquee selection, resize handle drag, rotation handle drag, Alt+drag duplicate, Shift+Arrow nudge (10px), and hover highlight (visual screenshot comparison).
+
+#### Scenario: Canvas manipulation tests pass
+- **WHEN** `bun run test` executes `canvas-manipulation.spec.ts`
+- **THEN** all 6+ tests pass verifying selection, resize, rotation, duplicate, nudge, and hover
+
+### Requirement: Toolbar E2E tests
+The E2E suite SHALL include tests for `tests/e2e/toolbar.spec.ts` covering: shapes flyout chevron, Polygon tool, Star tool, Pen tool (corner, close path, Escape), and Frame flyout.
+
+#### Scenario: Toolbar tests pass
+- **WHEN** `bun run test` executes `toolbar.spec.ts`
+- **THEN** all 7+ tests pass verifying flyout menus and shape creation
+
+### Requirement: Properties panel E2E tests
+The E2E suite SHALL include tests for `tests/e2e/properties-panel.spec.ts` covering: ScrubInput drag, corner radius (uniform + independent), fill gradient switch, variable bind badge, alignment buttons, flip horizontal, and clip content checkbox.
+
+#### Scenario: Properties panel tests pass
+- **WHEN** `bun run test` executes `properties-panel.spec.ts`
+- **THEN** all 8+ tests pass verifying property changes via store assertions
+
+### Requirement: Text formatting E2E tests
+The E2E suite SHALL include tests for `tests/e2e/text-formatting.spec.ts` covering: cursor positioning, double-click word select, ⌘B bold, ⌘I italic, Alt+ArrowRight word navigation, and Bold button in typography section.
+
+#### Scenario: Text formatting tests pass
+- **WHEN** `bun run test` executes `text-formatting.spec.ts`
+- **THEN** all 6+ tests pass verifying text edit interactions
+
+### Requirement: Auto-layout E2E tests
+The E2E suite SHALL include tests for `tests/e2e/auto-layout.spec.ts` covering: Shift+A on selection, direction toggle, gap ScrubInput, uniform padding, alignment grid, and remove auto-layout.
+
+#### Scenario: Auto-layout tests pass
+- **WHEN** `bun run test` executes `auto-layout.spec.ts`
+- **THEN** all 6+ tests pass verifying auto-layout frame state via store
+
+### Requirement: Snap guides E2E tests
+The E2E suite SHALL include tests for `tests/e2e/snap-guides.spec.ts` covering: edge snap guide and center snap guide, both verified via screenshot comparison.
+
+#### Scenario: Snap guide tests pass
+- **WHEN** `bun run test` executes `snap-guides.spec.ts`
+- **THEN** 2 screenshot comparison tests pass confirming guide line visibility
+
+### Requirement: Panel resize E2E tests
+The E2E suite SHALL include tests for `tests/e2e/panels.spec.ts` covering: left panel drag resize, width persistence after reload, and ⌘\\ UI toggle (hide + show).
+
+#### Scenario: Panel tests pass
+- **WHEN** `bun run test` executes `panels.spec.ts`
+- **THEN** all 4+ tests pass verifying panel DOM dimensions and visibility
+
+### Requirement: Variables dialog E2E tests
+The E2E suite SHALL include tests for `tests/e2e/variables-dialog.spec.ts` covering: dialog open, search filter, cell edit, and color swatch picker.
+
+#### Scenario: Variables dialog tests pass
+- **WHEN** `bun run test` executes `variables-dialog.spec.ts`
+- **THEN** all 4+ tests pass verifying dialog interactions
+
+### Requirement: Export E2E tests
+The E2E suite SHALL include tests for `tests/e2e/export.spec.ts` covering: format selector, add/remove rows, preview toggle, and SVG hiding scale input.
+
+#### Scenario: Export tests pass
+- **WHEN** `bun run test` executes `export.spec.ts`
+- **THEN** all 5+ tests pass verifying export section UI state
+
+### Requirement: CanvasHelper extended helpers
+The `CanvasHelper` class SHALL provide `marquee()`, `hover()`, `dragScrubInput()`, and `shiftDrag()` helper methods usable across all spec files.
+
+#### Scenario: Helpers available in all specs
+- **WHEN** a spec file imports `CanvasHelper`
+- **THEN** `canvas.marquee()`, `canvas.hover()`, `canvas.dragScrubInput()`, and `canvas.shiftDrag()` are callable without TypeScript errors
diff --git a/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/tasks.md b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/tasks.md
new file mode 100644
index 000000000..8c11eb139
--- /dev/null
+++ b/openspec/changes/archive/2026-03-07-e2e-coverage-gaps/tasks.md
@@ -0,0 +1,96 @@
+# Tasks: e2e-coverage-gaps
+
+## 0. Prerequisites — data-test-id audit
+
+- [x] 0.1 Add `data-test-id="corner-radius-input"` to the uniform corner radius ScrubInput in `AppearanceSection.vue`
+- [x] 0.2 Add `data-test-id="independent-corners-toggle"` to the independent corners toggle button in `AppearanceSection.vue`
+- [x] 0.3 Add `data-test-id="corner-tl-input"`, `corner-tr-input`, `corner-br-input`, `corner-bl-input` to the four independent corner ScrubInputs in `AppearanceSection.vue`
+- [x] 0.4 Add `data-test-id="clip-content-checkbox"` to the clip content checkbox in `LayoutSection.vue`
+- [x] 0.5 Add `data-test-id="left-splitter-handle"` to the first `SplitterResizeHandle` in `EditorView.vue`
+- [x] 0.6 Add `data-test-id="typography-bold-button"` to the Bold button in `TypographySection.vue`
+- [x] 0.7 Add `data-test-id="variables-search-input"` to the search input in `VariablesDialog.vue`
+
+## 1. Extend CanvasHelper
+
+- [x] 1.1 Add `marquee(x1, y1, x2, y2, steps = 10)` method to `tests/helpers/canvas.ts` — mouse.move to start → mouse.down → mouse.move to end with steps → mouse.up, then waitForRender
+- [x] 1.2 Add `hover(x, y)` method — mouse.move to canvas-relative position, waitForRender (uses existing `waitForRender`)
+- [x] 1.3 Add `dragScrubInput(locator, deltaX)` method — scroll element into view, getBoundingBox, mouse.move to center, mouse.down, mouse.move +deltaX, mouse.up
+- [x] 1.4 Add `altDrag(fromX, fromY, toX, toY)` method — keyboard.down('Alt') → drag → keyboard.up('Alt'), then waitForRender
+
+## 2. Canvas Manipulation spec
+
+- [x] 2.1 Create `tests/e2e/canvas-manipulation.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit + clearCanvas), afterAll (close)
+- [x] 2.2 Test: marquee selects two rectangles — draw rect at (100,100,80,80), draw rect at (250,100,80,80), marquee from (80,80) to (360,220), assert `store.state.selectedIds.size === 2`
+- [x] 2.3 Test: marquee on empty area deselects — press Escape to select none, draw rect at (100,300,80,80), click to select it, marquee from (500,500) to (600,600), assert `store.state.selectedIds.size === 0`
+- [x] 2.4 Test: Alt+drag duplicate — clearCanvas, draw rect at (100,100,80,80), click to select, altDrag from (140,140) to (280,140), assert page child count increased by 1
+- [x] 2.5 Test: Shift+ArrowRight nudge 10px — clearCanvas, draw rect at (200,200,80,80), record `node.x`, press Shift+ArrowRight, assert `node.x === initial + 10`
+- [x] 2.6 Test: hover highlight visual — clearCanvas, draw rect at (200,200,100,100), take no-hover screenshot, `canvas.hover(250, 250)`, take hover screenshot, assert they differ using `expect(hoverShot).not.toEqual(noHoverShot)` (Buffer comparison, not toHaveScreenshot — avoids baseline management)
+
+## 3. Toolbar spec
+
+- [x] 3.1 Create `tests/e2e/toolbar.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit)
+- [x] 3.2 Test: shapes flyout opens — click `[data-test-id="toolbar-flyout-rectangle"]`, assert `[data-test-id="toolbar-flyout-item-polygon"]` is visible
+- [x] 3.3 Test: Polygon created — click `[data-test-id="toolbar-flyout-item-polygon"]`, drag on canvas (300,200) to (400,300), assert POLYGON node in store page children
+- [x] 3.4 Test: Star created — click `[data-test-id="toolbar-flyout-rectangle"]`, click `[data-test-id="toolbar-flyout-item-star"]`, drag (150,150) to (250,250), assert STAR node in store page children
+- [x] 3.5 Test: Pen creates VECTOR on Enter — press P, click canvas at (100,100), click at (200,100), click at (200,200), press Enter (commits open path), assert VECTOR node with `vectorNetwork.vertices.length === 3`
+- [x] 3.6 Test: Frame flyout shows Frame and Section — click `[data-test-id="toolbar-flyout-frame"]`, assert `[data-test-id="toolbar-flyout-item-frame"]` and `[data-test-id="toolbar-flyout-item-section"]` are both visible
+
+## 4. Properties Panel spec
+
+- [x] 4.1 Create `tests/e2e/properties-panel.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit)
+- [x] 4.2 Test: ScrubInput drag changes X — clearCanvas, draw rect at (100,100,80,80), record `node.x`, dragScrubInput(`[data-test-id="position-section"] input` first scrub, +50), assert `node.x` changed from initial
+- [x] 4.3 Test: corner radius uniform — clearCanvas, draw rect at (200,200,80,80), select, triple-click `[data-test-id="corner-radius-input"]` input, type "12", press Enter, assert `node.cornerRadius === 12`
+- [x] 4.4 Test: independent corners shows four fields — click `[data-test-id="independent-corners-toggle"]`, assert `[data-test-id="corner-tl-input"]`, `corner-tr-input`, `corner-br-input`, `corner-bl-input` are all visible
+- [x] 4.5 Test: fill gradient switch — clearCanvas, draw rect at (200,200,80,80), click `[data-test-id="fill-item"]` swatch to open picker, click `[data-test-id="fill-picker-tab-gradient"]`, assert `node.fills[0].type === 'GRADIENT_LINEAR'`
+- [x] 4.6 Test: alignment buttons align nodes — clearCanvas, draw rect at (50,200,60,60), draw rect at (250,200,60,60), select both (Meta+A), click `[data-test-id="position-align-left"]`, assert both nodes have same `node.x`
+- [x] 4.7 Test: flip horizontal — clearCanvas, draw rect at (200,200,80,80), click `[data-test-id="position-flip-horizontal"]`, assert `node.flipX === true`
+- [x] 4.8 Test: clip content checkbox — clearCanvas, press F, drag (100,100) to (300,300) to create frame, select it, click `[data-test-id="clip-content-checkbox"]`, assert `node.clipsContent` changed
+
+## 5. Text Formatting spec
+
+- [x] 5.1 Create `tests/e2e/text-formatting.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit)
+- [x] 5.2 Test: double-click enters text edit mode — create text node via store at (200,200,150,30), press Escape to deselect, double-click canvas at (275,215), waitForRender, assert `store.state.editingTextId` is not null, assert no canvas errors
+- [x] 5.3 Test: Cmd+B toggles bold — (continuing from text edit mode) Ctrl+A, Meta+b, press Escape, assert `node.fontWeight === 700`
+- [x] 5.4 Test: Cmd+I toggles italic — double-click text node to re-enter edit, Ctrl+A, Meta+i, press Escape, assert `node.italic === true`
+- [x] 5.5 Test: Alt+ArrowRight word navigation — double-click text node to enter edit, press Alt+ArrowRight, assert no canvas errors, assert `store.state.editingTextId` is still set
+- [x] 5.6 Test: Bold button in panel — press Escape to exit edit mode and stay selected, click `[data-test-id="typography-bold-button"]`, assert `node.fontWeight` changed
+
+## 6. Auto-Layout spec
+
+- [x] 6.1 Create `tests/e2e/auto-layout.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit + clearCanvas)
+- [x] 6.2 Test: Shift+A wraps selection — draw rect at (100,100,60,60), draw rect at (220,100,60,60), Meta+A, Shift+A, assert FRAME node with `layoutMode !== 'NONE'` and 2 children
+- [x] 6.3 Test: direction button toggles VERTICAL — select the auto-layout frame, click `[data-test-id="layout-direction-vertical"]`, assert `frame.layoutMode === 'VERTICAL'`
+- [x] 6.4 Test: gap ScrubInput sets itemSpacing — select frame, dragScrubInput(`[data-test-id="layout-section"]` gap scrub input, +20), assert `frame.itemSpacing >= 20`
+- [x] 6.5 Test: alignment grid center — select frame, click 5th button (index 4) inside `[data-test-id="layout-alignment-grid"]`, assert `frame.primaryAxisAlign === 'CENTER'` and `frame.counterAxisAlign === 'CENTER'`
+- [x] 6.6 Test: remove auto-layout — select frame, click `[data-test-id="layout-remove-auto"]`, assert `frame.layoutMode === 'NONE'`
+
+## 7. Snap Guides spec
+
+- [x] 7.1 Create `tests/e2e/snap-guides.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit + clearCanvas). Tag with `test.skip` on Linux CI (snap guides are visual-only and X11 Alt+drag interference makes setup unreliable; these are run manually or on macOS CI).
+- [x] 7.2 Test: edge snap guide visual — create rect A at (100,100,80,80) and rect B at (300,100,80,80) via store. Take baseline screenshot. Slow-drag rect B from (340,140) toward (182,140) with steps=30 using mouse.down/move (do NOT release). Take mid-drag screenshot. Assert screenshots differ (guide pixels appeared). Release mouse.
+- [x] 7.3 Test: center snap guide visual — reset rects, slow-drag rect B toward center alignment with rect A with steps=30 mid-drag. Assert screenshots differ.
+
+## 8. Panels spec
+
+- [x] 8.1 Create `tests/e2e/panels.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit)
+- [x] 8.2 Test: layers panel resize — get bounding box of `[data-test-id="layers-panel"]`, record width, drag `[data-test-id="left-splitter-handle"]` 80px right, assert panel width increased by at least 40px
+- [x] 8.3 Test: panel width persists after reload — record panel width after resize, reload page, waitForInit, assert new panel width is within 5px of recorded width (Reka SplitterGroup uses `auto-save-id` localStorage)
+- [x] 8.4 Test: Cmd+Backslash hides panels — press Meta+Backslash, assert `[data-test-id="layers-panel"]` not visible
+- [x] 8.5 Test: Cmd+Backslash shows panels — press Meta+Backslash again, assert layers-panel visible
+
+## 9. Variables Dialog spec
+
+- [x] 9.1 Create `tests/e2e/variables-dialog.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit)
+- [x] 9.2 Test: dialog opens — create a color variable collection via `store.graph` evaluate, click `[data-test-id="variables-section-open"]`, assert `[data-test-id="variables-dialog"]` is visible
+- [x] 9.3 Test: search filters rows — create 2 variables with names "alpha-color" and "beta-spacing" via store, type "alpha" into `[data-test-id="variables-search-input"]`, assert only 1 `tr` row visible in the table body
+- [x] 9.4 Test: click cell to edit — click the EditablePreview in the first name cell, assert EditableInput is focused (use `page.locator('[data-test-id="variables-dialog"] tbody tr:first-child [contenteditable], input').first()`)
+- [x] 9.5 Test: color swatch opens picker — click `[data-test-id="color-picker-swatch"]` in first color variable row, assert `[data-test-id="color-picker-popover"]` is visible
+
+## 10. Export spec
+
+- [x] 10.1 Create `tests/e2e/export.spec.ts` with serial describe, shared page/CanvasHelper, beforeAll (goto + waitForInit + drawRect + select)
+- [x] 10.2 Test: add export row — count `[data-test-id="export-item"]`, click `[data-test-id="export-section-add"]`, assert count +1
+- [x] 10.3 Test: remove export row — click add again (now 2 rows), click `[data-test-id="export-item"]:first-child button` (the − button), assert count back to 1
+- [x] 10.4 Test: format selector changes to JPG — click the format AppSelect in the export row, select "JPG", assert AppSelect displays "JPG"
+- [x] 10.5 Test: SVG hides scale input — change format to SVG, assert the scale AppSelect is not visible (ExportSection hides scale for SVG via `v-if="setting.format !== 'SVG'"`)
+- [x] 10.6 Test: preview toggle shows image — click `[data-test-id="export-preview-toggle"]`, wait for `[data-test-id="export-section"] img` to be visible, assert `img.src` starts with `blob:` (confirms actual render, not empty src)
diff --git a/openspec/specs/e2e-auto-layout/spec.md b/openspec/specs/e2e-auto-layout/spec.md
new file mode 100644
index 000000000..e349b72d8
--- /dev/null
+++ b/openspec/specs/e2e-auto-layout/spec.md
@@ -0,0 +1,47 @@
+# e2e-auto-layout Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Auto-layout Shift+A on selection E2E
+The E2E suite SHALL verify that Shift+A on multiple selected nodes wraps them in an auto-layout frame.
+
+#### Scenario: Shift+A wraps nodes in auto-layout frame
+- **WHEN** two rectangles are selected and Shift+A is pressed
+- **THEN** a single FRAME node exists on the page with `layoutMode` set to `HORIZONTAL` or `VERTICAL` and the rectangles are its children
+
+### Requirement: Auto-layout direction toggle E2E
+The E2E suite SHALL verify that clicking the horizontal/vertical direction buttons changes `layoutMode`.
+
+#### Scenario: Direction button toggles layoutMode
+- **WHEN** an auto-layout frame is selected and the vertical direction button is clicked in LayoutSection
+- **THEN** `frame.layoutMode` equals `VERTICAL`
+
+### Requirement: Auto-layout gap E2E
+The E2E suite SHALL verify that changing the gap ScrubInput updates `itemSpacing`.
+
+#### Scenario: Gap ScrubInput changes itemSpacing
+- **WHEN** an auto-layout frame is selected and the gap ScrubInput value is changed to 20
+- **THEN** `frame.itemSpacing` equals 20
+
+### Requirement: Auto-layout padding uniform E2E
+The E2E suite SHALL verify that changing the uniform padding input updates all four padding properties.
+
+#### Scenario: Uniform padding updates all sides
+- **WHEN** an auto-layout frame is selected and padding is set to 16
+- **THEN** `frame.paddingTop`, `frame.paddingRight`, `frame.paddingBottom`, and `frame.paddingLeft` all equal 16
+
+### Requirement: Auto-layout alignment grid E2E
+The E2E suite SHALL verify that clicking a cell in the 3×3 alignment grid updates `primaryAxisAlign` and `counterAxisAlign`.
+
+#### Scenario: Center-center alignment set
+- **WHEN** the center-center cell of the alignment grid is clicked
+- **THEN** `frame.primaryAxisAlign` equals `CENTER` and `frame.counterAxisAlign` equals `CENTER`
+
+### Requirement: Auto-layout remove E2E
+The E2E suite SHALL verify that clicking the remove (−) button in the auto-layout section sets `layoutMode` to NONE.
+
+#### Scenario: Remove auto-layout resets layoutMode
+- **WHEN** an auto-layout frame is selected and the remove auto-layout button is clicked
+- **THEN** `frame.layoutMode` equals `NONE`
+
diff --git a/openspec/specs/e2e-canvas-manipulation/spec.md b/openspec/specs/e2e-canvas-manipulation/spec.md
new file mode 100644
index 000000000..312b1f525
--- /dev/null
+++ b/openspec/specs/e2e-canvas-manipulation/spec.md
@@ -0,0 +1,51 @@
+# e2e-canvas-manipulation Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Marquee selection E2E
+The E2E suite SHALL verify that dragging from an empty canvas area selects all nodes whose bounds intersect the drag rectangle.
+
+#### Scenario: Marquee selects intersected nodes
+- **WHEN** two rectangles are drawn and mouse is dragged from a point before both to a point after both
+- **THEN** both nodes appear in `store.state.selectedIds`
+
+#### Scenario: Marquee on empty area deselects
+- **WHEN** a node is selected and mouse is dragged across empty canvas
+- **THEN** `store.state.selectedIds.size` equals 0
+
+### Requirement: Resize handles E2E
+The E2E suite SHALL verify that dragging a resize handle changes the node's width and height.
+
+#### Scenario: Corner resize increases dimensions
+- **WHEN** a rectangle is selected and its bottom-right corner handle is dragged 50px right and 50px down
+- **THEN** node width and height each increase by approximately 50px
+
+### Requirement: Rotation handle E2E
+The E2E suite SHALL verify that the rotation handle rotates a node.
+
+#### Scenario: Rotation changes node rotation
+- **WHEN** a rectangle is selected and the cursor is positioned just outside a corner, then dragged in an arc
+- **THEN** `node.rotation` is non-zero
+
+### Requirement: Alt+drag duplicate E2E
+The E2E suite SHALL verify that Alt+drag creates a copy of the node.
+
+#### Scenario: Alt drag creates duplicate
+- **WHEN** a rectangle is selected and dragged with Alt held
+- **THEN** the page has one more node than before the drag
+
+### Requirement: Shift+Arrow nudge E2E
+The E2E suite SHALL verify that Shift+Arrow nudges a node by 10px.
+
+#### Scenario: Shift+ArrowRight nudges 10px
+- **WHEN** a node is selected and Shift+ArrowRight is pressed
+- **THEN** `node.x` increases by 10
+
+### Requirement: Hover highlight E2E
+The E2E suite SHALL verify that hovering over a node (without clicking) triggers a visible highlight outline on the canvas.
+
+#### Scenario: Hover renders highlight
+- **WHEN** the mouse moves over a rectangle without clicking
+- **THEN** a screenshot taken during hover differs from a screenshot taken with the mouse away (highlight outline visible)
+
diff --git a/openspec/specs/e2e-export/spec.md b/openspec/specs/e2e-export/spec.md
new file mode 100644
index 000000000..fcebdb592
--- /dev/null
+++ b/openspec/specs/e2e-export/spec.md
@@ -0,0 +1,37 @@
+# e2e-export Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Export format selection E2E
+The E2E suite SHALL verify that changing the export format selector updates the format value in the export section.
+
+#### Scenario: Format selector changes to JPG
+- **WHEN** a node with an export setting is selected and the format selector is changed to JPG
+- **THEN** the export row displays JPG as the selected format
+
+### Requirement: Export multi-row add/remove E2E
+The E2E suite SHALL verify that clicking + adds a new export row and − removes it.
+
+#### Scenario: Add export row
+- **WHEN** a node is selected and the + button in ExportSection is clicked
+- **THEN** one more export setting row is visible than before
+
+#### Scenario: Remove export row
+- **WHEN** a node has two export rows and the − button on one row is clicked
+- **THEN** only one export row remains
+
+### Requirement: Export preview toggle E2E
+The E2E suite SHALL verify that clicking the Preview button shows a preview image element.
+
+#### Scenario: Preview button shows image
+- **WHEN** a node with an export setting is selected and the Preview button is clicked
+- **THEN** an image element (checkerboard preview) becomes visible in the export section
+
+### Requirement: SVG format hides scale selector E2E
+The E2E suite SHALL verify that when SVG format is selected, the scale (multiplier) selector is hidden.
+
+#### Scenario: SVG hides scale input
+- **WHEN** an export row format is set to SVG
+- **THEN** no scale/multiplier input is visible for that row
+
diff --git a/openspec/specs/e2e-panels/spec.md b/openspec/specs/e2e-panels/spec.md
new file mode 100644
index 000000000..ce09a9799
--- /dev/null
+++ b/openspec/specs/e2e-panels/spec.md
@@ -0,0 +1,30 @@
+# e2e-panels Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Layers panel resize E2E
+The E2E suite SHALL verify that dragging the resize handle between the left panel and canvas changes the panel width.
+
+#### Scenario: Panel resize handle changes width
+- **WHEN** the splitter handle on the left panel is dragged 80px to the right
+- **THEN** the layers panel DOM element has a wider width than before the drag
+
+### Requirement: UI toggle persistence across reload E2E
+The E2E suite SHALL verify that panel sizes are preserved after a page reload.
+
+#### Scenario: Panel width persists after reload
+- **WHEN** the layers panel is resized and the page is reloaded
+- **THEN** the panel width after reload matches the width set before reload (within 2px)
+
+### Requirement: ⌘\\ hides and shows panels E2E
+The E2E suite SHALL verify that pressing ⌘\\ toggles the visibility of the left and right panels.
+
+#### Scenario: Cmd+Backslash hides panels
+- **WHEN** ⌘\\ is pressed while panels are visible
+- **THEN** the layers panel and properties panel elements are not visible in the DOM
+
+#### Scenario: Cmd+Backslash shows panels again
+- **WHEN** ⌘\\ is pressed while panels are hidden
+- **THEN** the layers panel and properties panel elements are visible again
+
diff --git a/openspec/specs/e2e-properties-panel/spec.md b/openspec/specs/e2e-properties-panel/spec.md
new file mode 100644
index 000000000..d526817fc
--- /dev/null
+++ b/openspec/specs/e2e-properties-panel/spec.md
@@ -0,0 +1,61 @@
+# e2e-properties-panel Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: ScrubInput drag-to-change E2E
+The E2E suite SHALL verify that dragging horizontally on a ScrubInput changes the node property value.
+
+#### Scenario: Drag X ScrubInput moves node
+- **WHEN** a rectangle is selected and the X ScrubInput is dragged 50px to the right
+- **THEN** `node.x` increases by approximately 50
+
+### Requirement: Corner radius uniform E2E
+The E2E suite SHALL verify that typing a corner radius value in the appearance section applies it to all corners.
+
+#### Scenario: Uniform corner radius applied
+- **WHEN** a rectangle is selected and a corner radius value is typed into the appearance input
+- **THEN** `node.cornerRadius` equals the entered value
+
+### Requirement: Corner radius independent E2E
+The E2E suite SHALL verify that toggling to independent corners splits the input into four separate fields.
+
+#### Scenario: Independent corners toggle shows four fields
+- **WHEN** the independent corners button is clicked
+- **THEN** four corner radius inputs are visible in the panel
+
+### Requirement: Fill gradient switch E2E
+The E2E suite SHALL verify that switching fill type to Linear Gradient updates the node fill.
+
+#### Scenario: Fill type switches to gradient
+- **WHEN** a rectangle with a solid fill is selected and fill type is changed to LINEAR
+- **THEN** `node.fills[0].type` equals `GRADIENT_LINEAR`
+
+### Requirement: Variable bind E2E
+The E2E suite SHALL verify that binding a color variable to a fill displays a variable badge.
+
+#### Scenario: Variable badge appears after bind
+- **WHEN** a color variable exists and is bound to a node's fill
+- **THEN** a variable badge element is visible in the fill section
+
+### Requirement: Alignment buttons E2E
+The E2E suite SHALL verify that clicking alignment buttons aligns multiple selected nodes.
+
+#### Scenario: Align left aligns nodes
+- **WHEN** two rectangles at different X positions are selected and Align Left is clicked
+- **THEN** both nodes have the same `x` coordinate (leftmost of the two)
+
+### Requirement: Flip horizontal E2E
+The E2E suite SHALL verify that clicking the flip horizontal button sets `flipX` on the node.
+
+#### Scenario: Flip horizontal toggles
+- **WHEN** a rectangle is selected and flip horizontal button is clicked
+- **THEN** `node.flipX` is true
+
+### Requirement: Clip content toggle E2E
+The E2E suite SHALL verify that the clip content checkbox toggles `clipsContent` on a frame.
+
+#### Scenario: Clip content checkbox toggles
+- **WHEN** a frame is selected and the clip content checkbox is clicked
+- **THEN** `node.clipsContent` changes to the opposite boolean value
+
diff --git a/openspec/specs/e2e-snap-guides/spec.md b/openspec/specs/e2e-snap-guides/spec.md
new file mode 100644
index 000000000..bfcc665aa
--- /dev/null
+++ b/openspec/specs/e2e-snap-guides/spec.md
@@ -0,0 +1,19 @@
+# e2e-snap-guides Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Edge snap guide visual E2E
+The E2E suite SHALL verify that dragging a node to align its edge with another node causes snap guide lines to appear on the canvas.
+
+#### Scenario: Edge snap guide renders during drag
+- **WHEN** two rectangles are positioned such that dragging one aligns its left edge with the other's left edge
+- **THEN** a screenshot taken mid-drag (while edges are aligned) contains red guide line pixels not present in the pre-drag screenshot
+
+### Requirement: Center snap guide visual E2E
+The E2E suite SHALL verify that dragging a node to align its center with another node causes a center snap guide line to appear.
+
+#### Scenario: Center snap guide renders during drag
+- **WHEN** two rectangles are positioned such that dragging one aligns its horizontal center with the other's center
+- **THEN** a screenshot taken mid-drag contains guide line pixels indicating center alignment
+
diff --git a/openspec/specs/e2e-text-formatting/spec.md b/openspec/specs/e2e-text-formatting/spec.md
new file mode 100644
index 000000000..54f156804
--- /dev/null
+++ b/openspec/specs/e2e-text-formatting/spec.md
@@ -0,0 +1,47 @@
+# e2e-text-formatting Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Text cursor positioning E2E
+The E2E suite SHALL verify that clicking inside a text node in edit mode positions the cursor near the clicked glyph.
+
+#### Scenario: Click positions cursor
+- **WHEN** a text node is double-clicked to enter edit mode and then a position inside the text is clicked
+- **THEN** the text node remains in edit mode without errors
+
+### Requirement: Text word selection E2E
+The E2E suite SHALL verify that double-clicking on a word selects that word.
+
+#### Scenario: Double-click selects word
+- **WHEN** a text node is in edit mode and a word is double-clicked
+- **THEN** the canvas screenshot shows the word highlighted (differs from no-selection screenshot)
+
+### Requirement: Bold shortcut E2E
+The E2E suite SHALL verify that ⌘B toggles bold formatting on selected text.
+
+#### Scenario: Cmd+B toggles bold
+- **WHEN** a text node is in edit mode, all text is selected, and ⌘B is pressed
+- **THEN** `node.fontWeight` changes to 700 (bold) or back to 400 (normal)
+
+### Requirement: Italic shortcut E2E
+The E2E suite SHALL verify that ⌘I toggles italic formatting on selected text.
+
+#### Scenario: Cmd+I toggles italic
+- **WHEN** a text node is in edit mode with all text selected and ⌘I is pressed
+- **THEN** `node.italic` toggles to the opposite boolean
+
+### Requirement: Word navigation shortcut E2E
+The E2E suite SHALL verify that Alt+ArrowRight moves the cursor by one word.
+
+#### Scenario: Alt+ArrowRight moves by word
+- **WHEN** a text node is in edit mode with cursor at position 0 and Alt+ArrowRight is pressed
+- **THEN** no JavaScript errors are thrown and the editor remains in text mode
+
+### Requirement: Typography section buttons E2E
+The E2E suite SHALL verify that the Bold button in the typography section applies formatting.
+
+#### Scenario: Bold button in panel toggles bold
+- **WHEN** a text node is selected (not in edit mode) and the Bold button in DesignPanel is clicked
+- **THEN** `node.fontWeight` changes
+
diff --git a/openspec/specs/e2e-toolbar/spec.md b/openspec/specs/e2e-toolbar/spec.md
new file mode 100644
index 000000000..9ad476f72
--- /dev/null
+++ b/openspec/specs/e2e-toolbar/spec.md
@@ -0,0 +1,54 @@
+# e2e-toolbar Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Shapes flyout E2E
+The E2E suite SHALL verify that the shapes flyout chevron opens a menu with Polygon and Star tools.
+
+#### Scenario: Shapes flyout opens
+- **WHEN** the shapes flyout trigger is clicked
+- **THEN** the flyout menu is visible with Polygon and Star items
+
+### Requirement: Polygon tool E2E
+The E2E suite SHALL verify that selecting Polygon from the flyout and dragging creates a POLYGON node.
+
+#### Scenario: Polygon created
+- **WHEN** Polygon is selected from flyout and drag is performed on canvas
+- **THEN** a node with type POLYGON exists on the current page
+
+### Requirement: Star tool E2E
+The E2E suite SHALL verify that selecting Star from the flyout and dragging creates a STAR node.
+
+#### Scenario: Star created
+- **WHEN** Star is selected from flyout and drag is performed on canvas
+- **THEN** a node with type STAR exists on the current page
+
+### Requirement: Pen tool corner points E2E
+The E2E suite SHALL verify that three clicks with the Pen tool create a VECTOR node with 3 vertices.
+
+#### Scenario: Three clicks create three vertices
+- **WHEN** Pen tool is active and three distinct canvas positions are clicked, then Enter pressed (commits open path)
+- **THEN** a VECTOR node exists with `vectorNetwork.vertices.length === 3`
+
+### Requirement: Pen tool close path E2E
+The E2E suite SHALL verify that clicking the first point of a Pen path closes it.
+
+#### Scenario: Click first point closes path
+- **WHEN** Pen tool creates 3 vertices and the user clicks on the first vertex position
+- **THEN** the resulting VECTOR node has a closed segment (no open endpoint)
+
+### Requirement: Pen tool Escape cancels path E2E
+The E2E suite SHALL verify that pressing Escape during Pen drawing cancels the current path without creating a node.
+
+#### Scenario: Escape cancels open path
+- **WHEN** Pen tool creates 2 vertices and Escape is pressed
+- **THEN** no new VECTOR node is created on the page (penCancel discards in-progress path)
+
+### Requirement: Frame flyout E2E
+The E2E suite SHALL verify that the Frame flyout chevron shows Frame and Section options.
+
+#### Scenario: Frame flyout shows both options
+- **WHEN** the frame flyout trigger is clicked
+- **THEN** Frame (F) and Section (S) items are visible in the flyout
+
diff --git a/openspec/specs/e2e-variables-dialog/spec.md b/openspec/specs/e2e-variables-dialog/spec.md
new file mode 100644
index 000000000..3bb17b8f6
--- /dev/null
+++ b/openspec/specs/e2e-variables-dialog/spec.md
@@ -0,0 +1,33 @@
+# e2e-variables-dialog Specification
+
+## Purpose
+TBD - created by archiving change e2e-coverage-gaps. Update Purpose after archive.
+## Requirements
+### Requirement: Variables dialog open E2E
+The E2E suite SHALL verify that the variables dialog can be opened when no node is selected.
+
+#### Scenario: Variables dialog opens
+- **WHEN** no node is selected and the variables settings button is activated
+- **THEN** a dialog element with variables table is visible
+
+### Requirement: Variables dialog search E2E
+The E2E suite SHALL verify that typing in the search field filters the variables table.
+
+#### Scenario: Search filters variable rows
+- **WHEN** variables exist with different names and a search term is typed
+- **THEN** only rows whose names match the search term are visible
+
+### Requirement: Variables dialog edit value E2E
+The E2E suite SHALL verify that clicking a variable value cell makes it editable.
+
+#### Scenario: Click cell to edit
+- **WHEN** a variable value cell is clicked in the dialog
+- **THEN** an input field is focused in that cell
+
+### Requirement: Color variable picker E2E
+The E2E suite SHALL verify that clicking a color variable swatch opens a color picker.
+
+#### Scenario: Color swatch opens picker
+- **WHEN** a color variable's swatch is clicked in the dialog
+- **THEN** a color picker element becomes visible
+
diff --git a/openspec/specs/testing/spec.md b/openspec/specs/testing/spec.md
index f957b7f80..b9426d0ce 100644
--- a/openspec/specs/testing/spec.md
+++ b/openspec/specs/testing/spec.md
@@ -96,7 +96,6 @@ Unit tests SHALL cover the variables system: add and resolve color/number variab
- **WHEN** `bun test ./tests/engine` is run
- **THEN** all variable tests pass (add/resolve color, resolve number, alias chain, circular alias, mode switching, bind/unbind, removal cleanup)
-
### Requirement: .fig roundtrip tests
The test suite SHALL include roundtrip tests for real .fig files: parsing property invariants, encode/decode cycle fidelity. Test fixtures (material3.fig, nuxtui.fig) SHALL be tracked via Git LFS.
@@ -136,6 +135,7 @@ The test suite SHALL include tests for sceneNodeToJsx() covering shapes, text, l
- **WHEN** sceneNodeToJsx is called on a rectangle with blue fill
- **THEN** the output includes Rectangle component with bg prop
+
### Requirement: FigmaAPI unit tests
The project SHALL provide comprehensive unit tests for the Figma Plugin API in `tests/engine/figma-api.test.ts`.
@@ -311,3 +311,74 @@ The test suite SHALL expand from original coverage to include 2571 LOC of new te
#### Scenario: Total new test lines
- **WHEN** new test files are counted
- **THEN** 2571 lines of tests are added
+
+### Requirement: Canvas manipulation E2E tests
+The E2E suite SHALL include tests for `tests/e2e/canvas-manipulation.spec.ts` covering: marquee selection, resize handle drag, rotation handle drag, Alt+drag duplicate, Shift+Arrow nudge (10px), and hover highlight (visual screenshot comparison).
+
+#### Scenario: Canvas manipulation tests pass
+- **WHEN** `bun run test` executes `canvas-manipulation.spec.ts`
+- **THEN** all 6+ tests pass verifying selection, resize, rotation, duplicate, nudge, and hover
+
+### Requirement: Toolbar E2E tests
+The E2E suite SHALL include tests for `tests/e2e/toolbar.spec.ts` covering: shapes flyout chevron, Polygon tool, Star tool, Pen tool (corner, close path, Escape), and Frame flyout.
+
+#### Scenario: Toolbar tests pass
+- **WHEN** `bun run test` executes `toolbar.spec.ts`
+- **THEN** all 7+ tests pass verifying flyout menus and shape creation
+
+### Requirement: Properties panel E2E tests
+The E2E suite SHALL include tests for `tests/e2e/properties-panel.spec.ts` covering: ScrubInput drag, corner radius (uniform + independent), fill gradient switch, variable bind badge, alignment buttons, flip horizontal, and clip content checkbox.
+
+#### Scenario: Properties panel tests pass
+- **WHEN** `bun run test` executes `properties-panel.spec.ts`
+- **THEN** all 8+ tests pass verifying property changes via store assertions
+
+### Requirement: Text formatting E2E tests
+The E2E suite SHALL include tests for `tests/e2e/text-formatting.spec.ts` covering: cursor positioning, double-click word select, ⌘B bold, ⌘I italic, Alt+ArrowRight word navigation, and Bold button in typography section.
+
+#### Scenario: Text formatting tests pass
+- **WHEN** `bun run test` executes `text-formatting.spec.ts`
+- **THEN** all 6+ tests pass verifying text edit interactions
+
+### Requirement: Auto-layout E2E tests
+The E2E suite SHALL include tests for `tests/e2e/auto-layout.spec.ts` covering: Shift+A on selection, direction toggle, gap ScrubInput, uniform padding, alignment grid, and remove auto-layout.
+
+#### Scenario: Auto-layout tests pass
+- **WHEN** `bun run test` executes `auto-layout.spec.ts`
+- **THEN** all 6+ tests pass verifying auto-layout frame state via store
+
+### Requirement: Snap guides E2E tests
+The E2E suite SHALL include tests for `tests/e2e/snap-guides.spec.ts` covering: edge snap guide and center snap guide, both verified via screenshot comparison.
+
+#### Scenario: Snap guide tests pass
+- **WHEN** `bun run test` executes `snap-guides.spec.ts`
+- **THEN** 2 screenshot comparison tests pass confirming guide line visibility
+
+### Requirement: Panel resize E2E tests
+The E2E suite SHALL include tests for `tests/e2e/panels.spec.ts` covering: left panel drag resize, width persistence after reload, and ⌘\\ UI toggle (hide + show).
+
+#### Scenario: Panel tests pass
+- **WHEN** `bun run test` executes `panels.spec.ts`
+- **THEN** all 4+ tests pass verifying panel DOM dimensions and visibility
+
+### Requirement: Variables dialog E2E tests
+The E2E suite SHALL include tests for `tests/e2e/variables-dialog.spec.ts` covering: dialog open, search filter, cell edit, and color swatch picker.
+
+#### Scenario: Variables dialog tests pass
+- **WHEN** `bun run test` executes `variables-dialog.spec.ts`
+- **THEN** all 4+ tests pass verifying dialog interactions
+
+### Requirement: Export E2E tests
+The E2E suite SHALL include tests for `tests/e2e/export.spec.ts` covering: format selector, add/remove rows, preview toggle, and SVG hiding scale input.
+
+#### Scenario: Export tests pass
+- **WHEN** `bun run test` executes `export.spec.ts`
+- **THEN** all 5+ tests pass verifying export section UI state
+
+### Requirement: CanvasHelper extended helpers
+The `CanvasHelper` class SHALL provide `marquee()`, `hover()`, `dragScrubInput()`, and `shiftDrag()` helper methods usable across all spec files.
+
+#### Scenario: Helpers available in all specs
+- **WHEN** a spec file imports `CanvasHelper`
+- **THEN** `canvas.marquee()`, `canvas.hover()`, `canvas.dragScrubInput()`, and `canvas.shiftDrag()` are callable without TypeScript errors
+
diff --git a/src/components/VariablesDialog.vue b/src/components/VariablesDialog.vue
index 52c6cbdd7..d839b6151 100644
--- a/src/components/VariablesDialog.vue
+++ b/src/components/VariablesDialog.vue
@@ -447,6 +447,7 @@ const table = useVueTable({