From 9752a6d6146ca9bd8d16c27667e896dfa3953ced Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 22 Sep 2026 16:32:17 -0400 Subject: [PATCH] docs: add v0.1.3 polish/tweaks design spec Co-authored-by: Cursor --- .../specs/2026-09-22-v013-polish-tweaks.md | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md diff --git a/docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md b/docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md new file mode 100644 index 0000000..a0bf4e2 --- /dev/null +++ b/docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md @@ -0,0 +1,198 @@ +# 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.