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.
This commit is contained in:
371
docs/plans/2026-02-17-ui-polish-design.md
Normal file
371
docs/plans/2026-02-17-ui-polish-design.md
Normal file
@@ -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\<user>\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)
|
||||||
Reference in New Issue
Block a user