From 124542b4c4cbcc514da49d4bd4063d691ac75645 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 22 Sep 2026 17:15:28 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20post-smoke-test=20polish=20=E2=80=94=20p?= =?UTF-8?q?reserve=20playhead,=20clip=20list=20redesign,=20play-clip=20act?= =?UTF-8?q?ion,=201100x750=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- ...-09-22_16-55-v013-polish-tweaks-summary.md | 56 ++++ src-tauri/tauri.conf.json | 4 +- src/lib/components/ClipList.svelte | 251 +++++++++++++++--- src/lib/components/Timeline.svelte | 9 + src/lib/components/TimelineContextMenu.svelte | 9 + src/lib/components/VideoPlayer.svelte | 4 + 6 files changed, 290 insertions(+), 43 deletions(-) create mode 100644 chat-summaries/2026-09-22_16-55-v013-polish-tweaks-summary.md diff --git a/chat-summaries/2026-09-22_16-55-v013-polish-tweaks-summary.md b/chat-summaries/2026-09-22_16-55-v013-polish-tweaks-summary.md new file mode 100644 index 0000000..a77fe46 --- /dev/null +++ b/chat-summaries/2026-09-22_16-55-v013-polish-tweaks-summary.md @@ -0,0 +1,56 @@ +# v0.1.3 Polish/Tweaks Implementation Summary + +**Date:** 2026-09-22 16:55 ET +**Version:** 0.1.2 → 0.1.3 + +## Task Description + +Implemented three UI polish features for v0.1.3 of the GUI Video Clipper (Tauri + Svelte 5): + +1. **Adjustable clip list layout** — clip list can appear below the timeline (default) or as a left sidebar next to the video preview, with a toolbar toggle button and persisted preference +2. **Timeline timestamp magnification** — increased base font from 10px to 12px, with a mouse-proximity magnification effect using cosine interpolation (~18px peak, 80px radius) +3. **Timeline right-click context menu** — custom context menu replacing the default web menu, with Mark In/Out, Go to Clip Start/End, and Delete Clip actions + +## Changes Made + +### Commits (oldest → newest) +- `0b8bec2` — chore: bump version to 0.1.3 +- `b371ec6` — feat: adjustable clip list layout (bottom/left sidebar) +- `923c7b0` — fix: clip list width edge cases (falsy check, narrow-window clamp) +- `27c199e` — feat: larger timeline timestamps with mouse-proximity magnification +- `8c27177` — feat: timeline right-click context menu with mark/seek/delete actions +- `279e27a` — docs: user edits to v0.1.3 spec (version bump section) + +### Files Modified +- `VERSION` — new file, version 0.1.3 +- `package.json` — version bump +- `package-lock.json` — lockfile sync +- `src-tauri/tauri.conf.json` — version bump +- `src-tauri/Cargo.toml` — version bump +- `src-tauri/Cargo.lock` — lockfile sync +- `src/lib/stores/preferences.svelte.ts` — added `clipListPosition` and `clipListWidth` preferences +- `src/App.svelte` — dual layout modes (bottom/left), toggle button, sidebar resize +- `src/lib/components/ClipList.svelte` — added `position` prop for sidebar styling +- `src/lib/timeline/renderer.ts` — larger base font, magnification math in `drawTimeTicks()` +- `src/lib/components/Timeline.svelte` — mouseX tracking, context menu wiring +- `src/lib/components/TimelineContextMenu.svelte` — new context menu component + +### Design & Plan Docs +- `docs/superpowers/specs/2026-09-22-v013-polish-tweaks.md` — design spec +- `docs/superpowers/plans/2026-09-22-v013-polish-tweaks.md` — implementation plan + +## Process + +Used subagent-driven development: 5 tasks dispatched as subagents with task reviews between each. Two reviewer findings on Task 2 (falsy width check, narrow-window clamp) were fixed before proceeding. + +## Lessons Learned + +- The layout restructure required careful conditional rendering in App.svelte — the `{#if}/{:else}` approach creates two separate DOM trees. This means TransportControls gets remounted on layout toggle, which should be smoke-tested. +- Context menu `$effect` for viewport clamping can flash position (0,0) for one frame before adjustment — initializing adjusted positions from the raw x/y props would eliminate this. +- The bump-version.sh script doesn't sync package-lock.json — needed manual `npm install --package-lock-only`. + +## Follow-Up Items + +- [ ] Manual smoke test with `npm run tauri dev` — layout toggle, sidebar resize, timestamp magnification feel, context menu flow +- [ ] Consider fixing first-frame context menu position flash +- [ ] Consider adding `onresize` clamp for sidebar width when window shrinks below saved width diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7a5e090..63c6a6d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -13,8 +13,8 @@ "windows": [ { "title": "GUI Video Clipper", - "width": 800, - "height": 600 + "width": 1100, + "height": 750 } ], "security": { diff --git a/src/lib/components/ClipList.svelte b/src/lib/components/ClipList.svelte index c2c0a40..6ba693e 100644 --- a/src/lib/components/ClipList.svelte +++ b/src/lib/components/ClipList.svelte @@ -4,9 +4,11 @@ selectClip, removeClip, updateClip, + type Clip, } from '$lib/stores/clips.svelte'; import { formatTime, parseTime } from '$lib/utils/time'; import { session } from '$lib/stores/videoSession.svelte'; + import { seekTo } from '$lib/transport/playback'; let { onExport, @@ -15,14 +17,13 @@ let clips = $derived(clipStore.clips); let selectedId = $derived(clipStore.selectedClipId); + let isSidebar = $derived(position === 'left'); function handleSelect(id: string) { selectClip(id); const clip = clips.find((c) => c.id === id); if (clip) { - session.currentTime = clip.startTime; - const videoEl = document.querySelector('video'); - if (videoEl) videoEl.currentTime = clip.startTime; + seekTo(clip.startTime); } } @@ -39,16 +40,29 @@ function handleContainerClick(e: MouseEvent) { const target = e.target as HTMLElement; - // Only deselect if the click landed on the container itself, not a child if (target.classList.contains('clip-list')) { selectClip(null); } } + + function handlePlayClip(clip: Clip) { + seekTo(clip.startTime); + const videoEl = document.querySelector('video'); + if (videoEl?.paused) videoEl.play(); + } + + function handleGoToStart(clip: Clip) { + seekTo(clip.startTime); + } + + function handleGoToEnd(clip: Clip) { + seekTo(clip.endTime); + }
- - handleLabelEdit(clip.id, (e.target as HTMLInputElement).value)} - onclick={(e) => e.stopPropagation()} - /> - - handleTimeEdit(clip.id, 'startTime', (e.target as HTMLInputElement).value)} - onclick={(e) => e.stopPropagation()} - /> - → - - handleTimeEdit(clip.id, 'endTime', (e.target as HTMLInputElement).value)} - onclick={(e) => e.stopPropagation()} - /> - + {#if isSidebar} + +
+
+ + handleLabelEdit(clip.id, (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> +
+
+ + handleTimeEdit(clip.id, 'startTime', (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> + → + + handleTimeEdit(clip.id, 'endTime', (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> +
+ {#if clip.id === selectedId} +
+ + + + +
+ {/if} +
+ {:else} + + + handleLabelEdit(clip.id, (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> + + handleTimeEdit(clip.id, 'startTime', (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> + → + + handleTimeEdit(clip.id, 'endTime', (e.target as HTMLInputElement).value)} + onclick={(e) => e.stopPropagation()} + /> + {#if clip.id === selectedId} +
+ + + + +
+ {:else} + + {/if} + {/if}
{/each} -
+
{#if selectedId} {/if} @@ -139,6 +236,7 @@ .clip-list.sidebar { border-top: none; height: 100%; + padding: 8px 6px; } .empty { @@ -155,7 +253,7 @@ margin-bottom: 4px; } - .action-buttons { + .list-actions { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); @@ -163,7 +261,7 @@ gap: 8px; } - .action-buttons button { + .list-actions button { font-size: 13px; padding: 6px 12px; } @@ -173,6 +271,7 @@ background: var(--bg-tertiary); } + /* --- Bottom (wide) row layout --- */ .clip-row { display: flex; align-items: center; @@ -195,6 +294,32 @@ background: var(--bg-tertiary); } + /* --- Sidebar (vertical card) layout --- */ + .clip-row.sidebar { + flex-direction: column; + align-items: stretch; + gap: 4px; + padding: 6px; + } + + .clip-card { + display: flex; + flex-direction: column; + gap: 4px; + width: 100%; + } + + .card-row { + display: flex; + align-items: center; + gap: 6px; + } + + .card-row.times { + justify-content: center; + } + + /* --- Shared elements --- */ .color-swatch { width: 12px; height: 12px; @@ -211,6 +336,12 @@ padding: 2px 4px; } + .label-input.sidebar-label { + flex: 1; + width: auto; + min-width: 0; + } + .label-input:focus { background: var(--bg-primary); outline: 1px solid var(--accent); @@ -239,6 +370,44 @@ font-size: 12px; } + /* --- Clip action buttons --- */ + .clip-actions { + display: flex; + gap: 4px; + justify-content: center; + padding-top: 4px; + } + + .clip-actions.inline { + padding-top: 0; + margin-left: auto; + flex-shrink: 0; + } + + .action-btn { + font-size: 12px; + padding: 2px 6px; + color: var(--text-secondary); + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 3px; + cursor: pointer; + line-height: 1.2; + } + + .action-btn:hover { + background: var(--accent); + color: var(--bg-primary); + border-color: var(--accent); + } + + .action-btn.danger:hover { + background: var(--danger); + color: var(--bg-primary); + border-color: var(--danger); + } + + /* Legacy delete button for unselected clips in bottom mode */ .delete-btn { font-size: 14px; padding: 2px 6px; diff --git a/src/lib/components/Timeline.svelte b/src/lib/components/Timeline.svelte index 7424b7d..1424015 100644 --- a/src/lib/components/Timeline.svelte +++ b/src/lib/components/Timeline.svelte @@ -279,6 +279,15 @@ case 'delete-clip': if (clipStore.selectedClipId) removeClip(clipStore.selectedClipId); break; + case 'play-clip': { + const clip = clipStore.clips.find((c) => c.id === clipStore.selectedClipId); + if (clip) { + seekTo(clip.startTime); + const videoEl = document.querySelector('video'); + if (videoEl?.paused) videoEl.play(); + } + break; + } default: { const _exhaustive: never = action; void _exhaustive; diff --git a/src/lib/components/TimelineContextMenu.svelte b/src/lib/components/TimelineContextMenu.svelte index f49e10b..f00effb 100644 --- a/src/lib/components/TimelineContextMenu.svelte +++ b/src/lib/components/TimelineContextMenu.svelte @@ -6,6 +6,7 @@ | { type: 'mark-out' } | { type: 'go-to-clip-start' } | { type: 'go-to-clip-end' } + | { type: 'play-clip' } | { type: 'delete-clip' }; let { @@ -126,6 +127,14 @@ {#if selectedClipId}
+