# UI Improvement Pass — Design Spec **Date:** 2026-03-19 **Scope:** All 6 pages — visual polish + UX usability, equal weight **Direction:** Darker, cooler blue-grey tone ("monitoring dashboard" feel), lifted nav branding --- ## Goals 1. Replace the generic default-Vuetify color palette with a distinctive monitoring-dashboard identity 2. Give the nav drawer meaningful visual structure and branding 3. Add a WebSocket connection indicator in the app bar 4. Improve Dashboard information hierarchy and action bar layout 5. Fix dark-mode-breaking hardcoded colour classes across all pages 6. Consistent empty states and loading treatment throughout 7. Per-page UX improvements (Downloads filter, Credentials visual state, Subscriptions dark mode) --- ## Section 1: Color System (`frontend/src/plugins/vuetify.js`) Replace both theme palettes entirely. ### Dark theme (primary use case) ```js dark: { dark: true, colors: { background: '#0F172A', // slate-900 — deep navy surface: '#1E293B', // slate-800 — card surfaces primary: '#60A5FA', // blue-400 — cooler accent secondary: '#94A3B8', // slate-400 error: '#F87171', // red-400 warning: '#FBBF24', // amber-400 success: '#34D399', // emerald-400 info: '#38BDF8', // sky-400 }, } ``` ### Light theme ```js light: { dark: false, colors: { background: '#F1F5F9', // slate-100 — cool grey workspace surface: '#FFFFFF', primary: '#2563EB', // blue-600 — deeper, more serious secondary: '#475569', // slate-600 error: '#EF4444', warning: '#F59E0B', success: '#10B981', info: '#0EA5E9', }, } ``` ### Global component defaults (add to existing `defaults`) ```js VCard: { elevation: 0, border: true }, VBtn: { variant: 'flat' }, VChip: { rounded: 'md' }, ``` Removing shadow elevation in favour of a subtle border gives cards a cleaner, more modern feel at both themes. The border adapts to theme colours automatically. --- ## Section 2: Nav Drawer & App Bar (`frontend/src/App.vue`) ### Nav drawer brand header Replace the plain `v-list-item` brand block with a styled header: - Container: `pa-4` padding, `bg-surface` background to visually separate from nav list - Icon: `mdi-download-network` at size 32, `color="primary"` *(icon intentionally changed from current `mdi-image-multiple`)* - Title: `"GallerySubscriber"` in `font-weight-bold text-subtitle-1` *(title intentionally changed from `"Gallery Subscriber"` — one word, no space)* - Subtitle: `"Download Manager"` in `text-caption text-medium-emphasis` - Followed by a `v-divider` ### Nav list items Change from `density="compact"` to `density="comfortable"`. Add `rounded="lg"` and `active-color="primary"` to each `v-list-item`. The built-in Vuetify active state renders a tinted pill highlight on the current route. ### App bar - Change `elevation="1"` to `elevation="0"` with `border="b"` — flat bar with bottom border - Add a WebSocket status indicator to the right of the title, before the theme toggle: - A small icon button showing two states (the store exposes `isConnected: Ref` only — no "connecting" state is tracked): - **Connected:** `mdi-circle` at size 10, `color="success"` + `v-tooltip` "Real-time updates active" - **Disconnected:** `mdi-circle` at size 10, `color="error"` + `v-tooltip` "Disconnected — real-time updates paused" - Reads from `useWebSocketStore().isConnected` - Wrapped in a `v-btn variant="text" icon` so it is unobtrusive and the tooltip activates on hover --- ## Section 3: Dashboard (`frontend/src/views/Dashboard.vue`) ### Stat cards (number-forward layout) Replace the `v-avatar` + side-by-side number layout with a vertical number-forward design: - Large `text-h3 font-weight-bold` number at top - `text-caption text-medium-emphasis` label below - A 4px left border accent stripe using inline style `border-left: 4px solid ` matching the card's semantic colour (primary, success, error, warning) - Remove the `v-avatar` entirely — the border provides the colour signal ### Action bar (split into two rows) **Row 1** (actions): The three action buttons unchanged — Check All Sources, Retry All Failed, Reset Stuck. **Row 2** (status): Two groups separated by a `v-divider vertical`: - Left: credential status chips (the existing platform chips, unchanged) - Right: storage stats (existing display, unchanged) Each row is its own `v-card-text`. This removes the single-line cramming. ### Active downloads list Running items get a subtle animated left border: ```css .download-running { border-left: 3px solid rgb(var(--v-theme-primary)); animation: pulse-border 2s ease-in-out infinite; } @keyframes pulse-border { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } ``` ### Consistent empty states All three empty panels (active downloads, recent activity, sources needing attention) use the same pattern: ```html
mdi-[relevant-icon]
[Primary message]
[Helper text]
``` - Active downloads empty: icon `mdi-check-circle-outline`, "No active downloads", "All sources are idle" - Recent activity empty: icon `mdi-history`, "No recent activity", "Failures and new downloads appear here" - Sources needing attention empty: icon `mdi-check-all`, "All sources healthy", "No failures in the last 7 days" --- ## Section 4: Per-Page Fixes ### Subscriptions (`frontend/src/views/Subscriptions.vue`) - Replace `class="bg-grey-lighten-4"` on the expanded inner table with `class="bg-surface"`. This is the only dark-mode-breaking class in the file. - Add `class="border-t"` to the inner table container for visual separation. ### Downloads (`frontend/src/views/Downloads.vue`) - Add an `exclude_superseded` filter toggle below the existing four filter columns (the current row is already full-width with four `md="3"` columns). Add a new `v-row` directly below the existing filter row containing a single `v-col cols="12"` with a `v-switch` labelled "Hide superseded failures", `density="compact"`, `hide-details`, defaulting to `true`. When true, adds `exclude_superseded: true` to the `loadDownloads()` params. - The existing four filter columns are unchanged. ### Credentials (`frontend/src/views/Credentials.vue`) Two visual states for platform cards: **Configured:** Card gets `border-color` set to the success colour via inline style or a CSS class. Existing content unchanged. **Not configured:** Card gets a dashed border via `:style="{ border: '2px dashed var(--v-border-color)' }"` and `:border="false"` to override the global VCard `border: true` default. (`--v-border-color` is a hex value, not an RGB triplet, so it must be used directly without `rgb()` wrapping.) The `v-alert` info block is replaced with a concise empty-state message, and the "Add Credentials" button is promoted to `color="primary" variant="flat"` (currently `variant="text"`), making the call to action visually prominent. ### Settings & Logs No structural changes — both pages inherit the new palette and card border style from the vuetify.js update automatically. --- ## Files Changed | File | Change | |------|--------| | `frontend/src/plugins/vuetify.js` | Full palette replacement, component defaults update | | `frontend/src/App.vue` | Nav branding, nav item density/rounding, app bar border+ws indicator | | `frontend/src/views/Dashboard.vue` | Stat card layout, action bar split, running item pulse, empty states | | `frontend/src/views/Subscriptions.vue` | Replace `bg-grey-lighten-4` with `bg-surface` | | `frontend/src/views/Downloads.vue` | Add `exclude_superseded` filter toggle | | `frontend/src/views/Credentials.vue` | Configured/unconfigured card visual states | Settings.vue and Logs.vue are **not modified** — they benefit from the theme update without code changes. --- ## Non-Goals - No new pages or routes - No layout restructuring (grid columns, page structure unchanged) - No new API endpoints - No component extraction / shared component library - Settings.vue and Logs.vue: no structural changes