docs: add v0.1.3 polish/tweaks design spec
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
198
docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md
Normal file
198
docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md
Normal file
@@ -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<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):
|
||||
```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.
|
||||
Reference in New Issue
Block a user