Commit Graph

18 Commits

Author SHA1 Message Date
Ruben Rosario 9649ca96b0 Fix list deletion sync: SyncAction::DeleteList
- Added DeleteList variant to SyncAction enum
- Added ApiClient::delete_list() calling DELETE /users/@me/lists/{id}
- List deletion uses DeleteList action (not Delete/delete_task)
- Sync engine handles DeleteList calling api.delete_list()
2026-06-21 18:27:53 +01:00
Ruben Rosario a35eab35af Fix sync: CreateList action + server ID mapping
- 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
2026-06-21 18:14:24 +01:00
Ruben Rosario 3379cbd057 n+l leader key for new list creation
- 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)
2026-06-21 18:04:37 +01:00
Ruben Rosario 0c142f1f67 Auto-detect missing scopes and re-auth
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.
2026-06-21 17:23:56 +01:00
Ruben Rosario 7946b0f102 Feature 3: Read-only Google Calendar panel
- 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
2026-06-21 17:18:22 +01:00
Ruben Rosario fa03a30a31 Add vertical scroll to Notes field in EditTask popup
- Added notes_scroll to App and AppView
- Up/Down in Notes field scrolls (Tab cycles fields instead)
- Reset scroll on opening EditTask popup
- Notes Paragraph uses .scroll((notes_scroll, 0))
2026-06-21 17:05:01 +01:00
Ruben Rosario 7cb3c6efd6 Feature 4: sort tasks - incomplete first by position, completed by updated_at
- 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
2026-06-21 16:38:54 +01:00
Ruben Rosario 747d40b1e9 Feature 1: date buttons in EditTask popup
- 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
2026-06-21 16:07:16 +01:00
Ruben Rosario b3dcefcd65 Add created_at/updated_at to Task model, DB, and API
- 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
2026-06-21 16:03:40 +01:00
Ruben Rosario 11bdb712f6 chore: checkpoint before removing POPUP_BG 2026-06-21 15:45:14 +01:00
Ruben Rosario 3035859dcb Replace Ctrl+Tab with Ctrl+Left/Right for cycling lists
Ctrl+Right selects next list, Ctrl+Left selects previous list,
works from any Focus (Tabs/TaskList/Detail).
2026-06-21 14:34:01 +01:00
Ruben Rosario 6eee90f128 Add task count to task list panel header
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
2026-06-21 14:21:14 +01:00
Ruben Rosario a64fdea005 refactor: simplify auth process in App using start_and_wait_for_auth 2026-06-21 10:04:08 +01:00
Ruben Rosario 0cbf9262c7 fix: replace device flow with loopback ip redirect flow (RFC 8252)
- 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
2026-06-20 20:55:08 +01:00
Ruben Rosario 985e8c9bc9 fix: show oauth url and code properly on device auth popup
- 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
2026-06-20 19:56:41 +01:00
Ruben Rosario 320a9c2572 fix: wire initial sync, oauth flow, and eliminate warnings
- 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)
2026-06-20 19:51:10 +01:00
Ruben Rosario 71befdf9f8 feat(api): google tasks oauth, sync engine, and background worker
- 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
2026-06-20 19:41:47 +01:00
Ruben Rosario 3b6726a726 feat(app): keyboard event handling with panel switching and CRUD
- 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
2026-06-20 19:38:12 +01:00