# 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(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 `
` 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): ```typescript 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 All three 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 --- ## 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.