Skip to content

Builder Rulers: Improvements, Grid Overlay & Alignment Guides

Branch: add-builder-ruler Date: 2026-05-21

Background

The builder has a working ruler system (MultiRuler.vue) with 3 draggable rulers per axis and percentage-based snapping. This plan covers four areas of improvement:

  1. Ruler UX improvements (scrub inputs, design system alignment, cleanup)
  2. Grid overlay (pixel grid with adjustable density)
  3. Alignment guides (engine reports element bounding rects, frontend shows alignment lines)
  4. Element snap (rulers snap to actual block edges, not just fixed percentages)

The alignment guides and element snap features are designed to work with the upcoming live scrub-input values from the theming branch (CE PR #722), where users can manipulate block properties in real-time.

Scope

1. Ruler Improvements

Scrub inputs in control panel:

  • Replace the static percentage display in RulerControlPanel with scrub-enabled InputField components
  • Users can click the percentage label and scrub to set exact ruler position
  • Consistent with the rest of the builder where all number inputs support scrub

Design system alignment:

  • RulerControlPanel currently uses hardcoded colors (green buttons, dark background, custom border-radius)
  • Restyle to match BrandingToolbar/LabelToolbar pattern: $background-panel, $border-minus2, $border-radius-kilo
  • Ruler lines and handles should use CSS variables (var(--text-primary), var(--accent-primary)) instead of hardcoded hex
  • Active/selected state should use var(--accent-primary) instead of custom green

Cleanup:

  • Delete Ruler.vue (legacy, never imported)
  • Remove dead CSS class .ruler-dragging in PreviewPanel
  • Remove unused isRulerDragging state in PreviewPanel if confirmed dead

2. Grid Overlay

Pixel grid over iframe:

  • Semi-transparent grid rendered as an overlay on top of the iframe (same layer as rulers, z-index 100)
  • Default grid size: 8px (standard design convention)
  • Slider in control panel adjusts grid density: 4px to 64px range
  • Grid lines use very subtle styling: rgba(var(--text-primary-rgb), 0.08) or similar
  • Toggle on/off via button in control panel

Implementation approach:

  • Canvas element or CSS background pattern (repeating-linear-gradient) -- CSS is simpler and performs well for regular grids
  • Sized to match the iframe dimensions, positioned identically to rulers
  • Scales with panzoom (lives inside panZoomContainer)
  • Grid state (enabled, size) persisted to localStorage alongside ruler state

3. Alignment Guides (Engine Cooperation)

Engine side (Creative-Engine):

  • New postMessage type: cavai-devtools-element-rects
  • Engine sends bounding rects for all visible blocks when requested or when layout changes
  • Message payload:
    ts
    {
      type: 'cavai-devtools-element-rects',
      blocks: Array<{
        shortcode: string        // e.g. "t1", "b2", "g1"
        rect: { top: number, left: number, width: number, height: number }
      }>
    }
  • Exposed via __cavaiDevtools.getElementRects() for on-demand queries
  • Optionally: engine sends rects automatically during live value scrubbing (so guides update in real-time)

Frontend side (Application-Frontend):

  • New component: AlignmentGuides.vue (sibling to MultiRuler, same container)
  • Listens for cavai-devtools-element-rects messages
  • When a ruler is being dragged near a block edge, show a colored alignment line at that edge
  • When live scrub values change block positions, show alignment lines between blocks that share an edge or center

Alignment detection:

  • For each block, track: top, bottom, left, right, centerX, centerY
  • Show guide when two blocks share a value within a threshold (e.g. 2px)
  • Show distance indicator between nearby-but-not-aligned edges

Visual style:

  • Alignment lines: var(--accent-primary) with low opacity, 1px solid
  • Distance indicators: small label showing px distance, same accent color
  • Guides disappear when drag/scrub ends (transient, not persistent)

4. Element Snap for Rulers

Extended snap points:

  • In addition to the existing percentage snaps (0%, 25%, 33%, 50%, 66%, 75%, 100%), rulers also snap to block edges
  • When engine reports element rects, convert block edges to percentages relative to creative dimensions
  • Add these as dynamic snap points to the existing snap system in MultiRuler
  • Magnetic pull behavior identical to existing percentage snaps
  • Visual feedback: snap label shows block shortcode instead of percentage (e.g. "t1 top" instead of "25%")

Architecture

PreviewPanel.vue
  |-- panZoomContainer
  |     |-- iframe (creative engine)
  |     |-- GridOverlay.vue (CSS grid pattern, toggleable)
  |     |-- MultiRuler.vue (existing, enhanced with element snap points)
  |     |-- AlignmentGuides.vue (transient lines during drag/scrub)
  |
  |-- RulerControlPanel.vue (restyled, scrub inputs, grid toggle + slider)

Engine (postMessage):
  devtoolsBridge.ts --> cavai-devtools-element-rects
  flowBridge.ts --> __cavaiDevtools.getElementRects()

Implementation Order

Phase 1: Ruler cleanup and polish (frontend only)

  1. Delete Ruler.vue (dead code)
  2. Remove dead isRulerDragging / .ruler-dragging references in PreviewPanel
  3. Restyle RulerControlPanel to use design system tokens (BrandingToolbar pattern)
  4. Restyle MultiRuler lines and handles to use CSS variables
  5. Add scrub-input for ruler position in RulerControlPanel (replace static % label)

Phase 2: Grid overlay (frontend only)

  1. Create GridOverlay.vue -- CSS repeating-linear-gradient, sized to iframe
  2. Add grid toggle button and size slider to RulerControlPanel
  3. Persist grid state to localStorage
  4. Ensure grid scales correctly with panzoom

Phase 3: Engine element rect reporting (engine + frontend)

  1. Add getElementRects() to engine's devtoolsBridge
  2. Add cavai-devtools-element-rects postMessage type
  3. Frontend: listen for element rects in PreviewPanel, store in reactive state
  4. Pass element rect data to MultiRuler as dynamic snap points

Phase 4: Alignment guides (frontend, depends on Phase 3)

  1. Create AlignmentGuides.vue -- receives element rects, detects alignment
  2. Show guide lines when blocks share edges/centers
  3. Show distance indicators between nearby edges
  4. Integrate with live scrub events (guides update during value manipulation)

Phase 5: Element snap for rulers (frontend, depends on Phase 3)

  1. Extend MultiRuler snap system with dynamic element-edge snap points
  2. Update snap labels to show block shortcode context
  3. Test with various creative layouts and block configurations

Dependencies

  • Phase 1-2: No dependencies, can start immediately on add-builder-ruler branch
  • Phase 3-5: Depends on theming branch being merged (for live scrub infrastructure)
  • Phase 3: Requires changes in Creative-Engine repo

Files to Touch

Frontend (Application-Frontend):

  • Preview/MultiRuler.vue -- element snap points, CSS variable styling
  • Preview/RulerControlPanel.vue -- scrub inputs, grid controls, design system restyle
  • Preview/PreviewPanel.vue -- element rect listener, dead code cleanup
  • Preview/Ruler.vue -- DELETE
  • Preview/GridOverlay.vue -- NEW
  • Preview/AlignmentGuides.vue -- NEW

Engine (Creative-Engine):

  • src/devtools/devtoolsBridge.ts -- element rect reporting
  • src/devtools/flowBridge.ts -- getElementRects() on __cavaiDevtools
  • src/devtools/types.ts -- new types

Notes

  • Grid overlay uses CSS patterns (not canvas) for simplicity and performance
  • Alignment guides are transient (visible during interaction only), not persistent like rulers
  • Element rects from engine use block-local coordinates; frontend converts based on iframe position
  • All new styling must use SCSS variables and CSS custom properties, no hardcoded colors
  • Scrub inputs in control panel reuse the existing InputField component with scrub support
  • Phase 1-2 can be done now; Phase 3-5 should wait until theming branch lands

Internal documentation