Files
gui-video-clipper/docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md

10 KiB
Raw Permalink Blame History

v0.1.3 Polish & Tweaks — Design Spec

Date: 2026-09-22 Version: 0.1.2 → 0.1.3

Overview

Three UI polish features for v0.1.3: adjustable clip-list layout, improved timeline timestamp labels with a mouse-proximity magnification effect, and a custom right-click context menu on the timeline.


Feature 1: Adjustable Layout (Clip List Position)

Current State

The main content area is a vertical stack:

┌──────────────────────────────┐
│ Toolbar                      │
├──────────────────────────────┤
│ Video Preview                │
├──────────────────────────────┤
│ Transport Controls           │
├──────────────────────────────┤
│ Timeline / Thumbs / Waveform │
├──────────────────────────────┤
│ Clip List                    │
├──────────────────────────────┤
│ Status Bar                   │
└──────────────────────────────┘

Proposed: Two Layout Modes

A new clipListPosition preference ('bottom' | 'left') controls where the clip list appears. Default: 'bottom' (current behavior, unchanged).

When set to 'left', the layout becomes:

┌──────────────────────────────────────────┐
│ Toolbar                                  │
├──────────┬───────────────────────────────┤
│ Clip     │  Video Preview                │
│ List     │                               │
│          │                               │
│          ├───────────────────────────────┤
│          │  Transport Controls           │
├──────────┴───────────────────────────────┤
│ Timeline / Thumbs / Waveform (full)      │
├──────────────────────────────────────────┤
│ Status Bar                               │
└──────────────────────────────────────────┘

Key details:

  • The clip list shares horizontal space only with the video preview and transport controls. The timeline remains full-width underneath — no horizontal space is sacrificed for timeline real estate.
  • A vertical drag-to-resize handle sits between the clip list and the video area, allowing the user to adjust the sidebar width. Default width: ~220px. Min: 150px. Max: 40% of window width.
  • The existing vertical resize handle between timeline and clip list only appears in 'bottom' mode. In 'left' mode, the timeline height is controlled by the existing timelineHeight state (still resizable via a horizontal drag handle between the upper section and the timeline).

Toggle Button

A button in the toolbar (alongside ⚙ and ℹ) toggles between 'bottom' and 'left' modes. The icon visually hints at the current layout — e.g. a stacked-rows icon for bottom mode, a sidebar icon for left mode. Clicking it switches immediately and persists the preference.

Persistence

  • Add clipListPosition: 'bottom' | 'left' to the Preferences interface in preferences.svelte.ts.
  • Add clipListWidth: number (default 220) for the sidebar width in left mode.
  • Both are saved/loaded via the existing Tauri store mechanism.

Implementation Notes

  • App.svelte is the only component that needs structural changes. ClipList.svelte, VideoPlayer.svelte, and Timeline.svelte remain unchanged — they just get placed in different containers.
  • In 'left' mode, the .content area becomes:
    • A flex column with two children:
      1. Upper section (flex row): [ClipList sidebar | flex column of [VideoPlayer, TransportControls]]
      2. Lower section (same as before): timeline pane with resize handle
    • The vertical resize handle between the upper and lower sections works the same as the existing one.
  • In 'bottom' mode: the current layout is preserved exactly.
  • The ClipList component needs no changes; it already fills its container. The only adjustment is removing the border-top when in left mode (the sidebar should have a border-right instead), which can be handled via a CSS class or prop.

Feature 2: Timeline Timestamp Labels

Current State

Timestamp labels are rendered at 10px font size in drawTimeTicks() in renderer.ts. They are small and hard to read.

Proposed Changes

  1. Increase base font size from 10px to 12px.
  2. Mouse-proximity magnification effect: As the cursor moves along the timeline, labels near the cursor smoothly scale up. This creates an invisible "magnifying lens" effect for timestamps.

Magnification Behavior

  • Radius: ~80px from cursor (in canvas pixel space).
  • Peak size: ~18px directly under the cursor.
  • Falloff: Smooth cosine interpolation from peak to base size. A label 80px away renders at base size; a label directly under the cursor renders at peak size.
  • Vertical alignment: Labels stay bottom-aligned as they grow — the text baseline remains constant, so larger labels grow upward. This prevents layout jitter.
  • Cursor leaves timeline: When the mouse leaves the canvas (onmouseleave), mouseX is set to null and all labels render at the base size. No magnification in this state.

Implementation Notes

  • Timeline.svelte already handles onmousemove. Add tracking of the mouse X coordinate (a simple let mouseX = $state<number | null>(null)), updated on mousemove and cleared on mouseleave.
  • Pass mouseX into drawTimeline() → drawTimeTicks().
  • drawTimeTicks() computes the per-label font size based on distance from mouseX:
    distance = abs(labelX - mouseX)
    if distance > MAGNIFY_RADIUS: scale = 0
    else: scale = (1 + cos(PI * distance / MAGNIFY_RADIUS)) / 2
    fontSize = BASE_FONT_SIZE + BONUS_FONT_SIZE * scale
    
  • Trigger a redraw whenever mouseX changes. Since requestDraw() already coalesces via requestAnimationFrame, this adds no extra frame overhead — it simply piggybacks on the next scheduled frame.

Feature 3: Timeline Right-Click Context Menu

Current State

Right-clicking the timeline shows the default web/Tauri context menu (Reload, Inspect Element), which is not useful.

Proposed: Custom Context Menu

A styled context menu appears on right-click anywhere on the timeline canvas. Right-clicking also moves the playhead to the clicked position.

Menu Items

# Label Behavior Availability
1 Mark In Sets in-point at the right-clicked time (calls markInPoint) Always
2 Mark Out Sets out-point at the right-clicked time (calls markOutPoint) Always
— (separator)
3 Go to Clip Start Seeks playhead to selectedClip.startTime Disabled if no clip selected
4 Go to Clip End Seeks playhead to selectedClip.endTime Disabled if no clip selected
— (separator)
5 Delete Clip Removes the selected clip Hidden if no clip selected

Behavior

  • Right-click on canvas: contextmenu event is intercepted, default is prevented. The playhead moves to the clicked time position. The context menu appears at the cursor's screen coordinates.
  • Selecting an item: Executes the action and closes the menu.
  • Closing without action: Click anywhere outside the menu, press Escape, or scroll — the menu closes.
  • Positioning: The menu is rendered as a positioned <div> appended at the App.svelte level (or uses a portal pattern) so it isn't clipped by any overflow: hidden containers. If the menu would extend beyond the window edge, it flips direction (e.g. opens leftward or upward).

Component: TimelineContextMenu.svelte

Props:

  • x: number — screen X position
  • y: number — screen Y position
  • time: number — the timeline time at the right-click position
  • selectedClipId: string | null — to determine which items are enabled/hidden
  • onAction: (action: ContextMenuAction) => void — callback for menu item selection
  • onClose: () => void — callback to close the menu

Actions (discriminated union):

type ContextMenuAction =
  | { type: 'mark-in' }
  | { type: 'mark-out' }
  | { type: 'go-to-clip-start' }
  | { type: 'go-to-clip-end' }
  | { type: 'delete-clip' };

Styling

  • Dark theme matching the app's existing style (dark background, light text, hover highlights).
  • Subtle border/shadow to float above the timeline.
  • Disabled items: dimmed text, no hover effect, not clickable.
  • Separators: thin horizontal lines using var(--border).

Implementation Notes

  • Timeline.svelte adds an oncontextmenu handler on the canvas element.
  • The handler computes the time from the click X coordinate (using existing computeClickTime), seeks the playhead, and emits an event / sets state to show the context menu.
  • The context menu component is rendered conditionally in Timeline.svelte (or App.svelte if clipping is an issue).
  • Keyboard shortcut hints can optionally be shown in the menu items (e.g. "Mark In I") for discoverability.

Version Bump

Execute scripts/bump-version.sh after incrementing VERSION.

Confirm all locations updated from 0.1.2 → 0.1.3:

  • package.json — version field
  • src-tauri/tauri.conf.json — version field
  • src-tauri/Cargo.toml — version field
  • grep/find for remaining 0.1.2 references, fix any, and add to scripts/bump-version.sh

Scope Boundaries

  • No changes to the backend (Rust). All three features are purely frontend.
  • No changes to clip store logic — existing markInPoint, markOutPoint, removeClip, selectClip functions are reused as-is.
  • No new dependencies.
  • The context menu does NOT appear on the minimap — only on the main timeline canvas.