Files

13 KiB
Raw Permalink Blame History

phase, slug, status, shadcn_initialized, preset, created
phase slug status shadcn_initialized preset created
4 timeline-editor draft false none 2026-04-06

Phase 4 — UI Design Contract

Visual and interaction contract for the Timeline Editor phase. Generated by gsd-ui-researcher. Stack is plain HTML5/CSS/JS served by FastAPI — no React, no shadcn, no build step.


Design System

Property Value
Tool none — hand-rolled CSS variables
Preset not applicable
Component library none — plain DOM elements
Icon library Unicode glyphs inline (▶, ■, ▼, ✕, >) — no icon font
Font JetBrains Mono, Fira Code, Consolas, monospace (system fallback chain)

Source: lightsync/frontend/style.css — CSS variable declarations at :root.


Spacing Scale

Declared values (must be multiples of 4):

Token Value Usage
xs 4px Gap between inline inspector fields, scrollbar width
sm 8px Panel content padding, element gaps
md 16px Default spacing between grouped elements
lg 24px Section-level padding
xl 32px Layout gaps
2xl 48px TRACK_HEIGHT — per-device track row height on canvas
3xl 64px Reserved; not used in this phase

Exceptions:

  • TIME_AXIS_HEIGHT: 24px — time ruler height above tracks
  • HEADER_WIDTH: 140px — track label column width (device names)
  • Timeline toolbar height: 32px — compact single row above canvas
  • Block inspector strip height: 40px — compact horizontal strip (D-02)
  • Resize handle hit zone: 8px on left/right edge of each canvas block
  • Snap threshold: 100ms (not a pixel dimension, but a time threshold for beat snap)

Source: RESEARCH.md Pattern 1 (coordinate system constants); CONTEXT.md D-02.


Typography

Role Size Weight Line Height Usage
Body 13px 400 1.4 Default text, form inputs, device names
Label 10px 400 1.2 Panel headers (DEVICES, ANIMATIONS, TIMELINE), toolbar labels, canvas track names
Compact 12px 400 1.3 Transport controls, animation names, inspector field values, beat tick labels
Heading 14px 600 1.2 Header title (LIGHTSYNC), block animation type label in inspector

All text: font-family var(--font-mono) — monospace throughout. Text transform: uppercase for all panel headers, labels, and inspector field names. Letter spacing: 0.14em on panel headers; 0.06em on animation names; 0.15em on header title.

Source: style.css.panel-header (10px/0.14em), .header-title (14px/600), .transport-time (12px), :root (13px base).


Color

Role Value Usage
Dominant (60%) #0a0a0a Body background, main-area background, canvas background
Secondary (30%) #0f0f0f (--bg-panel) / #080808 (--bg-panel-dark) Sidebar, header, transport bar, block inspector strip, timeline toolbar
Accent (10%) #00ffff See reserved list below
Destructive #ff3333 Delete block hover state on remove controls only
Dim border #1a4a4a (--border-dim) All panel borders, track separator lines, time axis tick marks
Waveform background #1a3a3a Waveform peaks drawn behind blocks (very dim cyan — not accent)
Beat marks #00ffff at 40% opacity Thin vertical beat tick marks on timeline canvas
Playback cursor #00ffff 1px vertical line spanning full canvas height
Selected block #00ffff border at 1px Selected block outline
Block fill (default) #0a2a2a Unselected animation block fill
Block fill (selected) #0d3a3a Selected animation block fill
Ghost block (drag) #00ffff at 20% opacity Block preview during drag-and-place

Accent (#00ffff) reserved for:

  • Playback cursor line
  • Selected block border
  • Active animation type label in inspector (.inspector-label)
  • Panel header text (--text-accent)
  • Transport button hover state (accent fills button bg, primary bg for text)
  • Beat indicator flash in header
  • Timeline toolbar labels in focus/active state
  • > cursor glyph in active animation palette row

Accent is NOT used for:

  • Block fills (blocks use #0a2a2a / #0d3a3a)
  • Waveform strokes (use #1a3a3a)
  • Time axis background ticks (use --border-dim at reduced opacity)

Source: style.css CSS variables; CONTEXT.md "terminal aesthetic carries forward"; RESEARCH.md Pattern 6 (waveform color #1a3a3a).


Component Inventory

New in Phase 4

Component Type Description
#timeline-toolbar HTML div (flex row) 32px strip above canvas: zoom control, beat offset input, snap toggle
#timeline-canvas HTML5 Canvas Fills remaining flex space in main-area; custom hit-testing, virtual scroll
#block-inspector HTML div (flex row) 40px horizontal strip between canvas and transport-bar; hidden when no block selected
Animation palette rows HTML buttons in sidebar Compact single-line rows below DEVICES panel; drag source for placing blocks

Modified in Phase 4

Component Change
.main-area Replaces animation tile grid with: toolbar + canvas + inspector (flex column)
.sidebar Adds ANIMATIONS panel below DEVICES; items are compact drag-source rows

Retained Unchanged

Transport bar, header, device panel, all CSS variables, all form element styles.


Canvas Rendering Contract

All canvas rendering uses HTML5 Canvas 2D API. No third-party canvas library.

Coordinate System

HEADER_WIDTH = 140px   // track label column
TIME_AXIS_HEIGHT = 24px
TRACK_HEIGHT = 48px
PIXELS_PER_SECOND = 100  // default zoom; adjustable via toolbar

Layer Draw Order (back to front)

  1. Background fill #0a0a0a
  2. Track row alternating banding: even #0a0a0a, odd #0c0c0c (2px brightness difference only)
  3. Waveform peaks: #1a3a3a vertical bars behind blocks
  4. Beat marks: #00ffff at 40% opacity, 1px wide vertical lines at calibrated beat times
  5. Animation blocks: filled rects #0a2a2a, 1px border #1a4a4a; selected: #0d3a3a fill, #00ffff 1px border
  6. Block labels: animation name in #00ffff, 10px, uppercase, clipped to block width
  7. Drag ghost: 1px border #00ffff, rgba(0,255,255,0.15) fill
  8. Track headers: #0f0f0f fill, device name in --text-primary 12px
  9. Time axis: #080808 fill, time labels --text-dim 10px, tick marks --border-dim
  10. Playback cursor: #00ffff 1px line, full height

devicePixelRatio Scaling

Canvas must call resizeCanvas() on init and on window resize. Scale ctx by window.devicePixelRatio to prevent blur on HiDPI.


Interaction Contract

Block Placement (TL-02)

  1. User drags from animation palette row in sidebar.
  2. Ghost block follows mouse across canvas at target track Y position.
  3. On mouseup over a track: snap to nearest beat if within 100ms threshold; place block at snapped time.
  4. On mouseup outside canvas: cancel drag, remove ghost.
  5. Attach mousemove and mouseup to document during drag; remove on mouseup. (Pitfall 2 prevention.)

Block Move (TL-03)

  1. mousedown on block center region (not within 8px edge): enter move mode.
  2. mousemove: update block start time live on canvas (visual only, no API call during drag).
  3. mouseup: snap to beat; fire MoveBlockCommand.execute() → updates local state → async PATCH to backend.

Block Resize (TL-03)

  1. mousedown within 8px of left or right block edge: enter resize mode; cursor changes to ew-resize.
  2. mousemove: update duration or start time live on canvas.
  3. mouseup: snap to beat; fire ResizeBlockCommand.execute().

Block Delete (TL-03)

  • Delete key when block selected: fire DeleteBlockCommand.execute() immediately. No confirmation dialog for individual block delete.
  • Block remove button in inspector (): same behavior as Delete key.

Block Selection

  • mousedown on block body (not dragging): select block, show inspector strip.
  • mousedown on canvas background: deselect, hide inspector strip.
  • Only one block selected at a time (no multi-select in Phase 4).

Undo/Redo (TL-06)

  • Ctrl+Z: CommandHistory.undo()
  • Ctrl+Shift+Z or Ctrl+Y: CommandHistory.redo()
  • Minimum 50 undo steps (CommandHistory maxSteps = 50).
  • All mutations (place, move, resize, delete, param change) must go through CommandHistory.execute().

Beat Snap (TL-08)

  • Snap active by default; toggle via "SNAP" button in toolbar (lit accent when on, dim when off).
  • Snap threshold: 100ms.
  • Apply calibration offset before snap: displayBeat = rawBeat - calibrationOffset.

Timeline Scroll

  • Horizontal scroll via mouse wheel on canvas: updates scrollX offset.
  • No native CSS overflow scrollbar on canvas — virtual scroll only.
  • Auto-scroll: when playback cursor would exit the visible window, scroll to keep cursor at 20% from left edge.

Inspector Strip Contract (D-01, D-02, D-03)

Height: 40px. Display: flex row. Background: --bg-panel-dark. Border-top: 1px solid var(--border-dim). Hidden (display: none) when no block selected. Shown (display: flex) when block selected.

Field order (left to right):

  1. LABEL — animation type name, --text-accent, 14px, 600 weight, uppercase, flex-shrink: 0, min-width 120px
  2. COLOR — label + <input type="color"> hidden, visible swatch 16×16px with 1px --border-dim border; click swatch triggers color input
  3. SPEED — label + <input type="number" step="0.1" min="0.1" max="10">, width 60px
  4. DIR — label + <select> with FWD / REV options
  5. LEN — label + <input type="number" step="0.05" min="0.05" max="1">, width 60px
  6. Spacer (flex: 1)
  7. — remove button, --text-dim, hover: #ff3333

All field labels: 10px, uppercase, --text-dim, letter-spacing 0.1em. All inputs: var(--font-mono), 12px, --bg-panel, --border-dim border, no border-radius.


Timeline Toolbar Contract

Height: 32px. Background: --bg-panel-dark. Border-bottom: 1px solid var(--border-dim). Display: flex row, align-center, gap 16px, padding 0 12px.

Fields (left to right):

  1. Zoom: ZOOM label + <input type="range" min="20" max="400" step="10" value="100"> — updates pixelsPerSecond
  2. Separator: 1px --border-dim vertical line, 20px tall
  3. Beat offset: BEAT OFFSET label + <input type="number" step="0.005" min="-0.2" max="0.2" value="0.030"> width 70px + s unit label
  4. Separator
  5. Snap toggle: SNAP button — .transport-btn style, lit (accent bg, primary color text) when active, dim when inactive

All labels: 10px, uppercase, --text-dim, letter-spacing 0.1em.


Animation Palette Panel (Sidebar)

Placed below DEVICES panel in sidebar. Panel header: ANIMATIONS — same .panel-header style. Items: one row per animation type (7 total). Each row is a draggable element styled as .anim-tile. Compact layout: > glyph + animation name + brief description, same as existing .anim-tile pattern. Drag cursor: grab on hover, grabbing during drag.


Copywriting Contract

Element Copy
Primary CTA (place block) PLACE BLOCK
Animation palette panel header ANIMATIONS
Timeline toolbar label TIMELINE
Empty timeline state (no devices) NO DEVICES — add a device in the sidebar to create tracks
Empty timeline state (devices, no audio) LOAD AUDIO — use the transport bar to load a file
Empty track state (device exists, no blocks) (no label — empty track row is visually self-evident)
Beat offset field label BEAT OFFSET
Snap toggle SNAP
Beat marks loading state DETECTING BEATS... (shown as dim text in time axis area)
Beat marks load error BEAT DETECTION FAILED — check audio file
Block delete (no confirmation dialog needed — undo covers it) (immediate, no confirm)
Inspector: block removed (inspector hides; no toast)
Undo available indicator (Ctrl+Z visible in toolbar tooltip only — no persistent UI)

No modal confirmations for any Phase 4 action. All destructive actions are undoable via Ctrl+Z.


Error States

Scenario Visual Treatment Copy
Beat detection API call fails Dim status text in time axis BEAT DETECTION FAILED
Block PATCH/DELETE API call fails Console warning only (silent in UI — client is source of truth)
Block placed over existing block on same track Placement rejected; ghost turns #ff3333 briefly OVERLAP — cannot place here
Audio not loaded when timeline renders Dim placeholder text in canvas center LOAD AUDIO — transport bar
No devices registered Dim placeholder text in canvas center NO DEVICES — add a device

Registry Safety

Registry Blocks Used Safety Gate
shadcn official none not applicable — no shadcn
third-party none not applicable

No third-party component registries. All UI is hand-rolled HTML/CSS/JS.


Checker Sign-Off

  • Dimension 1 Copywriting: PASS
  • Dimension 2 Visuals: PASS
  • Dimension 3 Color: PASS
  • Dimension 4 Typography: PASS
  • Dimension 5 Spacing: PASS
  • Dimension 6 Registry Safety: PASS

Approval: pending