51 Commits

Author SHA1 Message Date
rubenrosario 5cfad78ef8 feat: melhorar popup delete com contexto e adicionar logs CRUD 2026-06-22 21:42:56 +01:00
rubenrosario 6fd5b82941 docs: adicionar README.md e melhorar .gitignore 2026-06-22 21:15:17 +01:00
Ruben Rosario 66b833b7ac Refactor bulk move to use Move API + Fix update_list_id UNIQUE violation + File-only logging
- Bulk move (new/existing list) now uses SyncAction::Move + move_task API
  instead of create+delete clone, preserving task IDs and history on Google
- Add SyncAction::Move variant and MovePayload struct to domain models
- Fix api.move_task parameter: destinationTaskList -> destinationTasklist
- Add update_task_list_id method to Db for local DB update
- Fix update_list_id: DELETE+INSERT instead of UPDATE for task_lists to
  avoid UNIQUE constraint violation (duplicate PK on re-sync)
- Add list_id_map table and resolve_list_id for UUID->server ID mapping
- Replace eprintln! with file-only log_msg to avoid TUI screen corruption
- Add debug logging throughout sync engine (push_sync items, CreateList,
  Move resolution, etc.)
- Skip pushing Delete sync for single tasks whose list_id is local UUID
- Use trigger_full_sync() instead of trigger_sync() after bulk move
  operations, called after load_tasks() for clean ordering
2026-06-22 12:15:11 +01:00
Ruben Rosario 83762720a1 Fix move to existing list: sync_queue list_id update + PickList filter
- update_list_id now also updates sync_queue table
- PickList filters out local-only UUID lists (only shows server IDs)
2026-06-21 19:26:22 +01:00
Ruben Rosario d669ca5c05 Enhanced BulkAction: 7 options, arrow navigation, PickList, visual selection
- Added: Mark as uncomplete, Set due Tomorrow, Set due Next Week
- Added: Move to existing list (PickList popup)
- ↑/↓ navigate options, Enter executes, 1-7 for number shortcuts
- Selected tasks now show bg(DarkGray) for clearer visual feedback
- BulkAction popup with 7 options, PickList popup for list selection
2026-06-21 19:21:04 +01:00
Ruben Rosario 2fb550229e Multi-select tasks with Shift+Arrows and bulk actions
- 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
2026-06-21 18:59:27 +01:00
Ruben Rosario 10a8d1d75e Calendar: independent week scrolling and weekend colors
- 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
2026-06-21 18:57:07 +01:00
Ruben Rosario 00fec516ac Tolerate 404 on delete operations
- delete_task returns Ok(()) on 404 (already deleted)
- delete_list returns Ok(()) on 404 (already deleted)
2026-06-21 18:29:59 +01:00
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 7ebafec3c0 Calendar panel: full-width layout with 4 weekly columns
- 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)
2026-06-21 18:01:49 +01:00
Ruben Rosario 822c335864 Fix token_has_all_scopes for yup-oauth2 v8 array format
yup-oauth2 v8 stores tokens as a JSON array of entries, each
with a 'scopes' array field. Updated the parser to iterate
over entries and check for matching scopes.
2026-06-21 17:35:31 +01:00
Ruben Rosario 320a1823f3 Increase calendar fetch window to 30 days 2026-06-21 17:31:43 +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 915f0a3197 Feature 2: Notes field in EditTask with wrap and 5-line minimum
- 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
2026-06-21 16:57:53 +01:00
Ruben Rosario 1ff594e9b7 Feature 1: Wrap text in Details panel
Add ratatui Wrap widget to Paragraph in render_detail so
long titles and notes wrap within the detail area.
2026-06-21 16:57:28 +01:00
Ruben Rosario 028c43d49b Fix 411 Length Required on move_task API call
Google Tasks move endpoint requires Content-Length header
even on POST requests with no body.
2026-06-21 16:41:46 +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 c9e99cddd0 Fix task list alignment: use char count not byte count, truncate title
- 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
2026-06-21 16:33:22 +01:00
Ruben Rosario aa91fb7a9d Feature 3: relative due dates right-aligned in task list
- 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
2026-06-21 16:24:54 +01:00
Ruben Rosario 5cb8d0cd4e Fix update_task: always set updated_at to now()
Local edits must always update updated_at to the current time.
The task.updated_at field is only relevant for insert_task
(API-synced tasks have a real server timestamp).
2026-06-21 16:20:30 +01:00
Ruben Rosario 27b42d7836 Fix created_at > updated_at for API-synced tasks
Use updated_at as fallback for created_at when no existing row,
so API tasks have created_at <= updated_at.
2026-06-21 16:15:16 +01:00
Ruben Rosario 98409ff88b Feature 2: show created_at and updated_at in detail panel 2026-06-21 16:11:58 +01:00
Ruben Rosario 9839ebe4de Fix ALTER TABLE migration for created_at column
SQLite ALTER TABLE ADD COLUMN does not support DEFAULT expressions,
only literal values. Use DEFAULT '' then UPDATE existing rows.
2026-06-21 16:09:05 +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 e45631b235 Remove POPUP_BG from EditTask popup, rely on Clear widget 2026-06-21 15:48:11 +01:00
Ruben Rosario 11bdb712f6 chore: checkpoint before removing POPUP_BG 2026-06-21 15:45:14 +01:00
Ruben Rosario 6254395570 Remove auto-sync timers, keep only manual sync
Sync engine no longer has push (30s) or pull (5min) intervals.
Sync only happens on launch (InitialSync), manual Ctrl+R (FullSync),
or after create/edit/delete (TriggerSync).
2026-06-21 14:38:23 +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 ae9910bcbc refactor: read client_secret.json from disk instead of env vars 2026-06-21 10:23:25 +01:00
Ruben Rosario 9da086b7be fix: correct yup-oauth2 types (Authenticator, AccessToken, ApplicationSecret fields) 2026-06-21 10:10:22 +01:00
Ruben Rosario 532e13caef refactor: update main.rs for async ApiClient creation and use has_token 2026-06-21 10:04:41 +01:00
Ruben Rosario 1c95f5f6be refactor: simplify auth popup to show status instead of URL 2026-06-21 10:04:27 +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 f3e5ac0789 feat: replace manual OAuth with yup-oauth2 InstalledFlowAuthenticator 2026-06-21 10:03:34 +01:00
Ruben Rosario 469c30084d feat: add yup-oauth2 dependency, remove url and webbrowser 2026-06-21 10:03:11 +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 64993b127c fix: improve invalid_client error message with setup steps
- Remove client_secret from initial /device/code request (desktop app type)
- Update error message with full Google Cloud Console setup checklist:
  1. Enable Google Tasks API
  2. Configure OAuth consent screen (Testing mode + test users + scopes)
  3. Create Desktop app OAuth client
- Copy credentials exactly without extra whitespace
2026-06-20 20:36:12 +01:00
Ruben Rosario df1c5f5c2a fix: include client_secret in device code request and improve error guidance
- Include client_secret in the initial POST to /device/code endpoint
- Add check for empty GOOGLE_CLIENT_SECRET with clear error message
- Improve 'Invalid client type' error to suggest creating Desktop app OAuth client
- Include error code in addition to description for better diagnostics
2026-06-20 20:30:26 +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
Ruben Rosario efc3c1c84c feat(ui): render layout, tabs, panes, popups, and status bar
- ui/mod.rs: AppView struct, Focus/Popup/NetworkStatus enums, draw() layout function
- Top-Tabs + Bottom-Split layout (50/50 left/right)
- TabsBar: list selector with highlight on active
- TaskListPane: checkbox + title + due date per task
- DetailPane: title, status, due, notes of selected task
- InputPopup: centered modal with cursor
- DatePickerPopup: date/time edit modal with instructions
- ConfirmDeletePopup: confirmation dialog
- DeviceAuthPopup: OAuth URL + code display
- StatusBar: ONLINE/OFFLINE/SYNCING with color coding
2026-06-20 19:37:13 +01:00
Ruben Rosario 3626331a70 feat(infra): add sqlite storage with position-based ordering
- Db struct with rusqlite Connection (WAL mode, foreign keys)
- Tables: task_lists, tasks (with position column), sync_queue
- CRUD: get/insert/update/delete for lists and tasks
- reorder_task shifts positions of sibling tasks
- replace_all_lists and replace_all_tasks for sync import
- push_sync and drain_sync for offline queue management
- All reads sorted by position ASC
2026-06-20 19:35:56 +01:00