13 KiB
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-dimat 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)
- Background fill
#0a0a0a - Track row alternating banding: even
#0a0a0a, odd#0c0c0c(2px brightness difference only) - Waveform peaks:
#1a3a3avertical bars behind blocks - Beat marks:
#00ffffat 40% opacity, 1px wide vertical lines at calibrated beat times - Animation blocks: filled rects
#0a2a2a, 1px border#1a4a4a; selected:#0d3a3afill,#00ffff1px border - Block labels: animation name in
#00ffff, 10px, uppercase, clipped to block width - Drag ghost: 1px border
#00ffff,rgba(0,255,255,0.15)fill - Track headers:
#0f0f0ffill, device name in--text-primary12px - Time axis:
#080808fill, time labels--text-dim10px, tick marks--border-dim - Playback cursor:
#00ffff1px 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)
- User drags from animation palette row in sidebar.
- Ghost block follows mouse across canvas at target track Y position.
- On
mouseupover a track: snap to nearest beat if within 100ms threshold; place block at snapped time. - On
mouseupoutside canvas: cancel drag, remove ghost. - Attach
mousemoveandmouseuptodocumentduring drag; remove onmouseup. (Pitfall 2 prevention.)
Block Move (TL-03)
mousedownon block center region (not within 8px edge): enter move mode.mousemove: update block start time live on canvas (visual only, no API call during drag).mouseup: snap to beat; fireMoveBlockCommand.execute()→ updates local state → async PATCH to backend.
Block Resize (TL-03)
mousedownwithin 8px of left or right block edge: enter resize mode; cursor changes toew-resize.mousemove: update duration or start time live on canvas.mouseup: snap to beat; fireResizeBlockCommand.execute().
Block Delete (TL-03)
Deletekey when block selected: fireDeleteBlockCommand.execute()immediately. No confirmation dialog for individual block delete.- Block remove button in inspector (
✕): same behavior as Delete key.
Block Selection
mousedownon block body (not dragging): select block, show inspector strip.mousedownon 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+ZorCtrl+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
scrollXoffset. - 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):
LABEL— animation type name,--text-accent, 14px, 600 weight, uppercase,flex-shrink: 0, min-width 120pxCOLOR— label +<input type="color">hidden, visible swatch 16×16px with 1px--border-dimborder; click swatch triggers color inputSPEED— label +<input type="number" step="0.1" min="0.1" max="10">, width 60pxDIR— label +<select>with FWD / REV optionsLEN— label +<input type="number" step="0.05" min="0.05" max="1">, width 60px- Spacer (
flex: 1) ✕— 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):
- Zoom:
ZOOMlabel +<input type="range" min="20" max="400" step="10" value="100">— updatespixelsPerSecond - Separator: 1px
--border-dimvertical line, 20px tall - Beat offset:
BEAT OFFSETlabel +<input type="number" step="0.005" min="-0.2" max="0.2" value="0.030">width 70px +sunit label - Separator
- Snap toggle:
SNAPbutton —.transport-btnstyle, 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