- 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
- Added SyncAction::CreateList variant
- create_task returns server Task, added create_list API
- Sync engine processes CreateList first, updates IDs in batch
- After Create/CreateList success, local IDs updated to server IDs
- n is now a leader key (like t for dates)
- n + l opens Input popup to name and create a new list
- n + n opens EditTask popup to create a new task
- Popup::Input Enter now creates a list regardless of focus
- Removed immediate n behavior (list/task creation)
Added token_has_all_scopes() to ApiClient — reads stored
token.json and checks if all requested scopes are present.
If token exists but lacks calendar.readonly, the auth popup
is shown instead of silently failing in the sync engine.
- 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
- sort_tasks() extracts the sort logic into a standalone function
- Applied in load_tasks, App::new, check_initial_load, refresh_if_needed
- reorder_task guards: only NeedsAction tasks, never cross group boundary
- 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
- Add created_at and updated_at fields to Task struct
- Preserve existing created_at on upsert in insert_task
- Parse updated field from Google Tasks API response
- Add created_at column to DB schema with migration
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
- Add token_file_exists() to ApiClient for sync token check
- App::new now checks for token on startup; shows DeviceAuth popup if missing
- Background thread starts OAuth Device Flow automatically when no token
- App::poll_auth() called each frame to detect auth completion
- Auth completion triggers SyncCommand::InitialSync
- run_initial_sync fetches all lists and tasks via Google Tasks API
- Stores results in local DB via replace_all_lists / replace_all_tasks
- App::check_initial_load() refreshes UI from DB after initial sync
- Removed all compile warnings (dead_code annotations)
- ApiClient with manual OAuth2 Device Flow (no yup-oauth2 dependency)
- Devide auth: POST device/code -> show URL+code -> poll token endpoint
- Token persistence in ~/.config/task_app/token.json
- CRUD: create_task, update_task, delete_task, move_task via Google Tasks API
- fetch_lists and fetch_tasks for initial sync import
- Db wraps Connection in std::sync::Mutex for thread-safe sharing via Arc
- Sync engine: background thread with tokio runtime, processes queue every 30s
- process_sync_queue drains sync_queue and calls API methods
- trigger_sync() called after every local mutation (create/update/delete/reorder)
- Network status propagated to UI (Online/Offline/Syncing)
- Initial sync skeleton ready for full import flow
- App struct with full state (lists, tasks, focus, popups, DB)
- Tab cycles focus: Tabs -> TaskList -> Detail -> Tabs
- Left/Right arrows switch lists when focus on Tabs
- Up/Down navigate tasks (TaskList) or scroll (Detail)
- Alt+Up/Down reorder tasks with position persistence
- n: create new list or task, d: delete, e: edit title
- Enter on Detail opens DatePicker popup
- InputPopup with full text editing (navigation, insert, delete)
- ConfirmDelete popup before destructive actions
- DatePicker adjusts draft_date with Up/Down
- main.rs: terminal setup, event loop, raw mode, alternate screen