From fd25ea7d0510a7c9e4dfa336ffb8fe49b2e79e4b Mon Sep 17 00:00:00 2001 From: xpltd Date: Wed, 18 Mar 2026 23:01:36 -0500 Subject: [PATCH] =?UTF-8?q?M002/S04:=20UX=20review=20fixes=20=E2=80=94=20r?= =?UTF-8?q?ound=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move Video/Audio toggle to same row as Download button - Auto-condense toggle to icon-only below 540px - Move gear icon to right of Download button - Fix file download URLs: normalize filenames to relative paths in progress hook - Display filename with visible extension (truncate middle, preserve ext) - Remove border/box from dark mode toggle — glyph only - Fix light/dark theme fonts: use monospace display font across all themes --- .gsd/milestones/M002/slices/S04/S04-PLAN.md | 23 ++++ backend/app/services/download.py | 12 ++ frontend/src/components/DarkModeToggle.vue | 6 +- frontend/src/components/DownloadTable.vue | 20 ++- frontend/src/components/UrlInput.vue | 143 +++++++++++--------- frontend/src/themes/dark.css | 2 +- frontend/src/themes/light.css | 2 +- 7 files changed, 132 insertions(+), 76 deletions(-) create mode 100644 .gsd/milestones/M002/slices/S04/S04-PLAN.md diff --git a/.gsd/milestones/M002/slices/S04/S04-PLAN.md b/.gsd/milestones/M002/slices/S04/S04-PLAN.md new file mode 100644 index 0000000..6f7ceaf --- /dev/null +++ b/.gsd/milestones/M002/slices/S04/S04-PLAN.md @@ -0,0 +1,23 @@ +# S04: UX Review + Live Tweaks + +**Goal:** Walk through the entire app as a user, identify UX issues, and fix them in real time. This is a guided review session — the user drives the walkthrough and calls out issues, the agent fixes them immediately. +**Demo:** All issues identified during the walkthrough are resolved. App feels polished for a v1.0 release. + +## Approach + +1. Start backend + frontend dev servers +2. Walk through every user flow in the browser at desktop and mobile viewports +3. User identifies issues — agent fixes each one before moving on +4. Run tests after all fixes to confirm no regressions +5. Commit + +## Verification + +- `cd frontend && npx vitest run` — all tests pass +- `cd backend && source .venv/Scripts/activate && python -m pytest tests/ -q -m "not integration"` — no regressions +- Browser: all flows verified during the walkthrough + +## Tasks + +- [ ] **T01: Live UX review and fixes** `est:variable` + - Iterative — tasks emerge from the walkthrough diff --git a/backend/app/services/download.py b/backend/app/services/download.py index 7c4b37e..f30ec23 100644 --- a/backend/app/services/download.py +++ b/backend/app/services/download.py @@ -203,6 +203,18 @@ class DownloadService: try: event = ProgressEvent.from_yt_dlp(job_id, d) + # Normalize filename to be relative to the output directory + # so the frontend can construct download URLs correctly. + if event.filename: + from pathlib import PurePosixPath, Path + abs_path = Path(event.filename).resolve() + out_dir = Path(self._config.downloads.output_dir).resolve() + try: + event.filename = str(abs_path.relative_to(out_dir)) + except ValueError: + # Not under output_dir — use basename as fallback + event.filename = abs_path.name + # Always publish to SSE broker (cheap, in-memory) self._broker.publish(session_id, event) diff --git a/frontend/src/components/DarkModeToggle.vue b/frontend/src/components/DarkModeToggle.vue index 42cdd2d..aaae9c9 100644 --- a/frontend/src/components/DarkModeToggle.vue +++ b/frontend/src/components/DarkModeToggle.vue @@ -39,16 +39,14 @@ const theme = useThemeStore() height: 40px; background: transparent; color: var(--color-text-muted); - border: 1px solid var(--color-border); + border: none; border-radius: var(--radius-sm); cursor: pointer; - transition: all var(--transition-normal); + transition: color var(--transition-normal); padding: 0; } .dark-mode-toggle:hover { color: var(--color-accent); - border-color: var(--color-accent); - background: color-mix(in srgb, var(--color-accent) 8%, transparent); } diff --git a/frontend/src/components/DownloadTable.vue b/frontend/src/components/DownloadTable.vue index f81cb63..b2af378 100644 --- a/frontend/src/components/DownloadTable.vue +++ b/frontend/src/components/DownloadTable.vue @@ -55,7 +55,17 @@ const sortedJobs = computed(() => { function displayName(job: Job): string { if (job.filename) { const parts = job.filename.replace(/\\/g, '/').split('/') - return parts[parts.length - 1] + const name = parts[parts.length - 1] + // Ensure extension is visible: if name is long, truncate the middle + if (name.length > 60) { + const ext = name.lastIndexOf('.') + if (ext > 0) { + const extension = name.slice(ext) + const base = name.slice(0, 55 - extension.length) + return `${base}…${extension}` + } + } + return name } try { const u = new URL(job.url) @@ -92,11 +102,13 @@ function isCompleted(job: Job): boolean { return job.status === 'completed' } -// File download URL +// File download URL — filename is relative to the output directory +// (normalized by the backend). May contain subdirectories for source templates. function downloadUrl(job: Job): string { if (!job.filename) return '#' - const name = job.filename.replace(/\\/g, '/').split('/').pop() || '' - return `/api/downloads/${encodeURIComponent(name)}` + const normalized = job.filename.replace(/\\/g, '/') + // Encode each path segment separately to preserve directory structure + return `/api/downloads/${normalized.split('/').map(encodeURIComponent).join('/')}` } // Copy download link to clipboard diff --git a/frontend/src/components/UrlInput.vue b/frontend/src/components/UrlInput.vue index b211346..980549f 100644 --- a/frontend/src/components/UrlInput.vue +++ b/frontend/src/components/UrlInput.vue @@ -84,16 +84,40 @@ function toggleOptions(): void {