From 90d557487fd8aea4bcf8424bb2833681ef484ff3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 17 Feb 2026 21:15:41 +0200 Subject: [PATCH] docs: add UI polish & UX upgrade design doc Covers amber accent system, charcoal background lift, button hierarchy, toast notifications, rich empty states, UI zoom, portable storage, and window state persistence. --- docs/plans/2026-02-17-ui-polish-design.md | 371 ++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 docs/plans/2026-02-17-ui-polish-design.md diff --git a/docs/plans/2026-02-17-ui-polish-design.md b/docs/plans/2026-02-17-ui-polish-design.md new file mode 100644 index 0000000..2e6651b --- /dev/null +++ b/docs/plans/2026-02-17-ui-polish-design.md @@ -0,0 +1,371 @@ +# ZeroClock UI Polish & UX Upgrade — Design + +## Problem + +The first redesign pass established a Swiss/Dieter Rams foundation but went too far into monochrome territory. The app is a wall of near-black grey with zero personality. The signature amber accent color is completely absent. All buttons look identical. Empty states are sad grey text. `alert()` calls for all feedback. No visual hierarchy for primary actions. The app also stores data in AppData (not portable) and doesn't remember window position/size. + +## Goals + +1. Reintroduce warm amber (#D97706) as the strategic accent color +2. Lift the entire background palette from near-black to charcoal +3. Establish clear button hierarchy (primary/secondary/ghost/danger) +4. Replace all `alert()` calls with a toast notification system +5. Design rich empty states with icons, copy, and CTA buttons +6. Add amber focus states on all inputs +7. Add UI zoom control in Settings (persistent) +8. Make the app fully portable (data next to exe) +9. Persist window position and size between runs + +--- + +## Design System + +### Color Palette (Revised) + +``` +Background layers (lifted from near-black to charcoal): + --bg-base: #1A1A18 app background + --bg-surface: #222220 cards, panels, navRail, titlebar + --bg-elevated: #2C2C28 hover, active, raised elements + --bg-inset: #141413 inputs, recessed areas + +Text (warm whites — unchanged): + --text-primary: #F5F5F0 headings, active items + --text-secondary: #8A8A82 body, descriptions + --text-tertiary: #5A5A54 disabled, placeholders (bumped from #4A4A45) + +Borders (bumped to match brighter backgrounds): + --border-subtle: #2E2E2A dividers, card borders + --border-visible: #3D3D38 input borders, focused + +Accent (NEW — amber): + --accent: #D97706 button fills, active indicators + --accent-hover: #B45309 hover/pressed amber + --accent-muted: rgba(217,119,6,0.12) subtle glows, backgrounds + --accent-text: #FBBF24 amber text on dark backgrounds + +Status (semantic — unchanged): + --status-running: #34D399 timer active, success, toggle on + --status-warning: #EAB308 pending, caution + --status-error: #EF4444 destructive, overdue + --status-info: #3B82F6 informational +``` + +Remove all legacy alias colors (--color-amber mapped to white, --color-background, --color-surface, etc.). + +### Button Hierarchy + +**Primary** — amber fill, for the ONE main action per view: +- `bg-accent text-[#1A1A18] font-medium rounded` +- Hover: `bg-accent-hover` +- Used: Start timer, Create Project, Save Settings, Generate Report, Apply Filters, Create Invoice + +**Secondary** — outlined, for supporting actions: +- `border border-border-visible text-text-primary rounded` +- Hover: `bg-bg-elevated` +- Used: Export CSV, Export Data, Cancel buttons, Clear filters + +**Ghost** — text only, for low-priority actions: +- `text-text-secondary hover:text-text-primary` +- No border, no background +- Used: "View all" links, tab navigation inactive + +**Danger** — destructive actions: +- `border border-status-error text-status-error rounded` +- Hover: `bg-status-error/10` +- Used: Clear Data, Delete buttons in confirmation dialogs + +### Input Focus States + +All inputs, selects, and textareas: +- Current: `focus:border-border-visible` (grey, barely visible) +- New: `focus:border-accent focus:outline-none` with `box-shadow: 0 0 0 2px rgba(217,119,6,0.12)` (amber border + subtle amber glow) + +--- + +## Shell + +### TitleBar + +- "ZEROCLOCK" wordmark: `text-accent-text` (amber #FBBF24) instead of `text-text-secondary` +- Running timer section: unchanged (green dot + project + time + stop) +- Window controls: unchanged + +### NavRail + +- Active indicator: 2px left border in `bg-accent` (#D97706) instead of `bg-text-primary` (white) +- Active icon color: stays `text-text-primary` (white) +- Tooltip: add subtle caret/triangle pointing left for polish +- Timer dot at bottom: stays green (semantic) + +--- + +## View Designs + +### Dashboard + +**Header:** +- Greeting: "Good morning/afternoon/evening" in `text-lg text-text-secondary` +- Date: "Monday, February 17, 2026" in `text-xs text-text-tertiary` + +**Stats row (4 stats):** +- This Week | This Month | Today | Active Projects +- Labels: `text-xs text-text-tertiary uppercase tracking-[0.08em]` +- Values: `text-[1.25rem] font-mono text-accent-text` (amber) + +**Weekly chart:** +- Bar fill: `#D97706` (amber), today's bar: `#FBBF24` (lighter amber) +- Grid: `#2E2E2A`, ticks: `#5A5A54` + +**Recent entries:** +- Flat list as current +- "View all" ghost link bottom-right, navigates to Entries + +**Empty state:** +- Centered vertically in available space +- Lucide `Clock` icon, 48px, `text-text-tertiary` +- "Start tracking your time" — `text-text-secondary` +- "Your dashboard will come alive with stats, charts, and recent activity once you start logging hours." — `text-xs text-text-tertiary` +- Primary amber button: "Go to Timer" + +### Timer + +**Hero display:** +- `text-[3rem] font-mono text-text-primary` centered +- When running: colon separators pulse amber (`text-accent-text` with opacity animation) +- When stopped: all white, static + +**Start/Stop button:** +- Start: `bg-accent text-[#1A1A18] px-10 py-3 text-sm font-medium rounded` +- Stop: `bg-status-error text-white px-10 py-3 text-sm font-medium rounded` (filled red) +- 150ms color transition between states + +**Inputs:** +- `max-w-[36rem] mx-auto` (centered) +- Amber focus states +- Project select: small colored dot preview of selected project color + +**Recent entries:** +- Scoped to today's entries +- Most recent entry: subtle amber left border +- Max 5, "View all" ghost link + +**Empty state:** +- Lucide `Timer` icon, 40px, `text-text-tertiary` +- "No entries today" — `text-text-secondary` +- "Select a project and hit Start to begin tracking" — `text-xs text-text-tertiary` + +### Projects + +**Header:** +- Title "Projects" left +- "+ Add" becomes small amber primary button: `bg-accent text-[#1A1A18] px-3 py-1.5 text-xs font-medium rounded` + +**Cards:** +- 2px left border in project color +- `bg-bg-surface hover:bg-bg-elevated` transition +- Hover: left border widens from 2px to 3px +- Rate and client inline: "ClientName · $50.00/hr" + +**Create/Edit dialog:** +- Submit: amber primary +- Cancel: secondary +- Color picker: row of 8 preset swatches above hex input + - Presets: #D97706, #3B82F6, #8B5CF6, #EC4899, #10B981, #EF4444, #06B6D4, #6B7280 + +**Empty state:** +- Lucide `FolderKanban` icon, 48px, `text-text-tertiary` +- "No projects yet" — `text-text-secondary` +- "Projects organize your time entries and set billing rates for clients." — `text-xs text-text-tertiary` +- Primary amber button: "Create Project" (opens dialog) + +### Entries + +**Filter bar:** +- Wrapped in `bg-bg-surface rounded p-4` container +- "Apply": amber primary +- "Clear": ghost button +- `mb-6` gap between filter bar and table + +**Table:** +- Header row: `bg-bg-surface` background +- Duration column: `text-accent-text font-mono` (amber) +- Edit/delete hover reveal: unchanged + +**Empty state (below filter bar):** +- Lucide `List` icon, 48px, `text-text-tertiary` +- "No entries found" — `text-text-secondary` +- "Time entries will appear here as you track your work. Try adjusting the date range if you have existing entries." — `text-xs text-text-tertiary` +- Primary amber button: "Go to Timer" + +### Reports + +**Filter bar:** +- Same `bg-bg-surface rounded p-4` container +- "Generate": amber primary +- "Export CSV": secondary + +**Stats:** +- `mt-6` spacing after filter bar +- Values: `text-accent-text font-mono` (amber) + +**Chart:** +- Bars use each project's own assigned color +- Fallback for no-color projects: #D97706 +- Grid: `#2E2E2A`, ticks: `#5A5A54` + +**Breakdown:** +- Hours value: `text-accent-text font-mono` +- Earnings: `text-text-secondary font-mono` + +**Empty state:** +- Lucide `BarChart3` icon, 48px, `text-text-tertiary` +- "Generate a report to see your data" — `text-text-secondary` +- No CTA button (Generate button is right there) + +### Invoices + +**Tabs:** +- Active: `border-b-2 border-accent text-text-primary` (amber underline) +- Inactive: `text-text-tertiary hover:text-text-secondary` + +**List table:** +- Header row: `bg-bg-surface` +- Amount: `text-accent-text font-mono` +- Status colors: unchanged (semantic) + +**Create form:** +- Submit: amber primary +- Cancel: secondary +- Total line: `text-accent-text font-mono` + +**Invoice detail dialog:** +- Export PDF: amber primary +- Total: `text-accent-text` + +**Empty state:** +- Lucide `FileText` icon, 48px, `text-text-tertiary` +- "No invoices yet" — `text-text-secondary` +- "Create invoices from your tracked time to bill clients." — `text-xs text-text-tertiary` +- Primary amber button: "Create Invoice" (switches to Create tab) + +### Settings + +**Buttons:** +- "Save Settings": amber primary +- "Export": secondary +- "Clear Data": danger (red) + +**Toggle:** stays green when active (semantic "on" state) + +**All `alert()` calls:** replaced with toasts + +**UI Zoom control (NEW):** +- New section "Appearance" between Timer and Data sections +- Label: "UI Scale" +- Minus button [-] | value display "100%" | Plus button [+] +- Steps: 80%, 90%, 100%, 110%, 120%, 130%, 150% +- Implementation: CSS `zoom` property on the `#app` root element +- Persisted via `update_settings('ui_zoom', '100')` in the settings SQLite table +- Applied on app startup before first paint (read from settings store in App.vue onMounted) + +--- + +## Toast Notification System + +### Component: `ToastNotification.vue` + +- Fixed position, top-center of the main content area, `top-4` +- Max width 320px +- `bg-bg-surface border border-border-subtle rounded shadow-lg` +- 3px left border colored by type: + - Success: `border-status-running` (green) + - Error: `border-status-error` (red) + - Info: `border-accent` (amber) +- Content: Lucide icon (Check/X/Info, 16px) + message in `text-sm text-text-primary` +- Enter animation: slide down from -20px + fade, 200ms +- Exit: fade out 150ms +- Auto-dismiss: 3 seconds +- Click to dismiss early +- Stack with 8px gap, max 3 visible + +### Store: `useToastStore` + +- `addToast(message: string, type: 'success' | 'error' | 'info')` +- Auto-generates unique ID +- Auto-removes after 3s timeout +- Max 3 toasts visible, oldest removed first + +### Replacements + +All `alert()` calls across the app become toast calls: +- Settings save success/failure +- Clear data success/failure +- Export data success/failure +- Timer "Please select a project" +- Reports "Please select a date range" +- Reports "No data to export" +- Invoices "Please select a client" + +--- + +## Portable App + +### Problem + +Currently uses `directories::ProjectDirs` to store the SQLite database in the OS app data directory (e.g., `C:\Users\\AppData\Roaming\ZeroClock\`). This is not portable. + +### Solution + +Change `get_data_dir()` in `lib.rs` to always resolve relative to the executable: + +```rust +fn get_data_dir() -> PathBuf { + let exe_path = std::env::current_exe().unwrap(); + let data_dir = exe_path.parent().unwrap().join("data"); + std::fs::create_dir_all(&data_dir).ok(); + data_dir +} +``` + +This stores `data/timetracker.db` next to the `.exe`. The `directories` crate dependency can be removed from Cargo.toml. + +--- + +## Window State Persistence + +### Plugin: `tauri-plugin-window-state` + +Add the Tauri window-state plugin to save/restore: +- Window position (x, y) +- Window size (width, height) +- Maximized state + +### Implementation + +1. Add dependency: `tauri-plugin-window-state = "2"` to Cargo.toml +2. Add `"window-state"` to plugins in tauri.conf.json +3. Register plugin: `.plugin(tauri_plugin_window_state::Builder::new().build())` in lib.rs +4. The plugin auto-saves state to a `.window-state` file. Since we're making the app portable, configure the plugin to store state in our `data/` directory next to the exe. + +### Config + +In `tauri.conf.json`, add to plugins: +```json +"window-state": { + "all": true +} +``` + +Remove `"center": true` from the window config so the saved position is respected on subsequent launches. + +--- + +## Transitions & Motion + +Unchanged from first redesign, plus: +- Toast enter: translateY(-20px) + opacity 0 to 0+1, 200ms ease-out +- Toast exit: opacity 1 to 0, 150ms +- Button hover: 150ms background-color transition +- Project card left-border width: 150ms transition on hover +- Timer colon amber pulse: opacity 0.4 to 1.0 on accent-text color, 1s ease-in-out infinite (only when running)