- Shift+Up/Down extends selection in task list
- Enter opens BulkAction popup with 3 options:
1. Mark as completed
2. Set due date to Today
3. Move to new list (creates list, copies tasks, deletes originals)
- Plain Up/Down clears selection, Escape clears it
- Selected items highlighted in Yellow
- calendar_scroll replaced by per-week calendar_scrolls[4] + active_week
- Tab cycles through weeks within Calendar, Left/Right switch week
- Up/Down scroll only the active week independently
- Sat/Sun rendered in Magenta, weekdays in Cyan, today in Yellow
- Moved Calendar from body left column (8 lines) to full-width
row between body and status bar (12 lines)
- Calendar splits into 4 horizontal panels, each showing one week
starting from Monday of the current week
- Day headers in Cyan (Yellow for today), events in White
- Removed old date-grouped event list rendering
- Body layout simplified to single horizontal split (Tasks | Detail)
- CalendarEvent model with summary, start, end, location
- New scope calendar.readonly in SCOPES
- fetch_upcoming_events() in ApiClient (15 events, next 7 days)
- Focus::Calendar variant, Tab cycle includes Calendar
- Body layout: left column split into Tasks (flex) + Calendar (8)
- render_calendar_panel with day headers and scroll
- refresh_calendar() called on initial sync and Ctrl+R
- Up/Down scroll Calendar panel when focused
- Increased popup height from 12 to 14 lines
- Notes constraint increased from Length(3) to Length(5)
- Notes Paragraph uses multi-line Text from notes.lines()
- Added Wrap { trim: false } to Notes Paragraph
- Use .chars().count() instead of .len() for accurate visual width
- Truncate title with … when title + due_text exceeds available width
- Compute padding from truncated title width for consistent alignment
- Replace absolute date with relative string (Overdue, Xh left, X days left)
- Overdue in Red, <24h in Yellow, >=24h in DarkGray
- Due string right-aligned via padding calculation
- Add 3 buttons (Today, Tomorrow, Next Week) below Notes
- Popup height increased from 10 to 12
- Tab/Up/Down cycle through 5 fields (Title, Notes, 3 buttons)
- Enter on a date button saves task and sets due date
- Text editing keys restricted to fields 0 and 1
Show 'X todo / Y done' in the Tasks panel title bar.
Also includes prior uncommitted work:
- Pagination in fetch_tasks (maxResults=100 + pageToken loop)
- fetch_tasks_since for incremental pull sync
- SyncStats struct with version/last_sync/last_pull/changed counts
- Periodic push (30s) and pull (5min) sync engine
- event::poll(100ms) for non-blocking UI refresh
- Ctrl+R full sync (push + pull)
- refresh_if_needed() to reload data after background sync
- Retry mechanism (MAX_SYNC_RETRIES=3) for sync queue items
- HTTP status code checks in fetch_lists/fetch_tasks/fetch_tasks_since
- Fix move_task URL to use reqwest query()
- Remove CASCADE via replace_all_lists (use insert_list instead)
- has_pending_sync() to prevent pull during pending push
- Device Flow only works with 'TV and Limited Input devices' OAuth client type
- Desktop app type requires Authorization Code flow with localhost redirect
- New flow: start local TCP server on random port, open browser with auth URL,
catch redirect containing authorization code, exchange for tokens
- Uses webbrowser crate to auto-open the browser
- Self-contained: no separate HTTP server framework needed, uses std::net
- Popup shows auth URL and waits for browser authorization
- Support for refresh_token for long-lived access
- Auth flow now waits for user's Enter before starting
- Start auth only when user presses Enter on DeviceAuth popup
- Proper error handling: missing GOOGLE_CLIENT_ID shows clear message
- Error messages displayed in popup with Retry option
- Popup shows instructions before auth, URL+code during auth
- Handle both verification_url and verification_uri field names from Google
- Check HTTP status code and show error_description on failures
- AuthError propagated to render function for display
- Popup border turns green when URL+code are ready