Merge pull request #47 from SteelDynamite/claude/dreamy-brown-AVqxJ

This commit is contained in:
SteelDynamite 2026-04-16 09:28:17 +01:00 committed by GitHub
commit 707e1ac2e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 26 additions and 8 deletions

View file

@ -37,7 +37,7 @@ Two-crate workspace (`resolver = "2"`, edition 2021) plus a Tauri app:
- **Storage trait** (`storage.rs`): Strategy pattern for task persistence. `FileSystemStorage` reads/writes markdown files with YAML frontmatter and JSON metadata files. Atomic writes (temp file + rename, with temp cleanup on failure) for all metadata files. Input validation: task titles max 500 chars, descriptions max 1MB, list names max 255 chars, YAML frontmatter max 64KB. Delete operations update metadata before removing files to prevent orphaned metadata on crash. - **Storage trait** (`storage.rs`): Strategy pattern for task persistence. `FileSystemStorage` reads/writes markdown files with YAML frontmatter and JSON metadata files. Atomic writes (temp file + rename, with temp cleanup on failure) for all metadata files. Input validation: task titles max 500 chars, descriptions max 1MB, list names max 255 chars, YAML frontmatter max 64KB. Delete operations update metadata before removing files to prevent orphaned metadata on crash.
- **Repository** (`repository.rs`): `TaskRepository` wraps a `Storage` impl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here. - **Repository** (`repository.rs`): `TaskRepository` wraps a `Storage` impl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here.
- **Config** (`config.rs`): `AppConfig` manages workspaces keyed by UUID string. `WorkspaceConfig` stores `name`, `path`, `mode` (Local/Webdav/GoogleTasks), `webdav_url`, `webdav_path` (user-selected remote folder), `google_account` (display name/email for GoogleTasks workspaces), `theme`, `sync_interval_secs` (focused polling interval), and `sync_interval_unfocused_secs` (lower-frequency polling when window loses focus, for mobile battery optimization). `add_workspace` returns a generated UUID. Stored in platform-specific config dirs via the `directories` crate. Atomic writes (temp file + rename) prevent corruption on crash. - **Config** (`config.rs`): `AppConfig` manages workspaces keyed by UUID string. `WorkspaceConfig` stores `name`, `path`, `mode` (Local/Webdav/GoogleTasks), `webdav_url`, `webdav_path` (user-selected remote folder), `google_account` (display name/email for GoogleTasks workspaces), `last_sync` (timestamp of last successful sync), `theme`, `sync_interval_secs` (focused polling interval), and `sync_interval_unfocused_secs` (lower-frequency polling when window loses focus, for mobile battery optimization). `add_workspace` returns a generated UUID. Stored in platform-specific config dirs via the `directories` crate. Atomic writes (temp file + rename) prevent corruption on crash.
- **Sync** (`sync.rs`): Three-way diff sync with offline queue. File-based `.sync.lock` prevents concurrent sync operations (auto-cleaned after 5 minutes if stale). Checksum-based conflict resolution: downloads remote, compares SHA-256 — identical content is a false conflict (skipped); when different, remote wins and local is recovered as a duplicate with a new UUID and `[RECOVERED FROM CONFLICT]` prefix (duplicate file cleaned up if metadata update fails). Auto-sync lifecycle: periodic polling (configurable interval, default 60s), debounced file-change (5s), window-focus (30s stale threshold). Wrapped in `tokio::time::timeout` (60s) to handle unreachable servers on Windows. Path traversal validation rejects `..` components and backslashes anywhere in sync paths. Atomic writes for sync state and queue files (temp cleanup on failure). - **Sync** (`sync.rs`): Three-way diff sync with offline queue. File-based `.sync.lock` prevents concurrent sync operations (auto-cleaned after 5 minutes if stale). Checksum-based conflict resolution: downloads remote, compares SHA-256 — identical content is a false conflict (skipped); when different, remote wins and local is recovered as a duplicate with a new UUID and `[RECOVERED FROM CONFLICT]` prefix (duplicate file cleaned up if metadata update fails). Auto-sync lifecycle: periodic polling (configurable interval, default 60s), debounced file-change (5s), window-focus (30s stale threshold). Wrapped in `tokio::time::timeout` (60s) to handle unreachable servers on Windows. Path traversal validation rejects `..` components and backslashes anywhere in sync paths. Atomic writes for sync state and queue files (temp cleanup on failure).
- **WebDAV** (`webdav.rs`): reqwest client with rustls-tls, 30s request timeout, 10s connect timeout. Rejects non-HTTPS URLs. `Zeroizing<String>` for credential fields. `move_resource` method for WebDAV MOVE (workspace rename). 10MB cap on both PROPFIND responses and file downloads. Desktop credentials via `keyring` crate (feature-gated); Tauri GUI uses `tauri-plugin-credentials` for cross-platform support (Android Keystore + desktop keychain). - **WebDAV** (`webdav.rs`): reqwest client with rustls-tls, 30s request timeout, 10s connect timeout. Rejects non-HTTPS URLs. `Zeroizing<String>` for credential fields. `move_resource` method for WebDAV MOVE (workspace rename). 10MB cap on both PROPFIND responses and file downloads. Desktop credentials via `keyring` crate (feature-gated); Tauri GUI uses `tauri-plugin-credentials` for cross-platform support (Android Keystore + desktop keychain).
- **Google Tasks** (`google_tasks.rs`): Read-only Google Tasks API client using reqwest with Bearer auth. `gt_id_to_uuid()` converts Google Task IDs to stable UUID v5 values for consistent cross-sync identity. Fetches all task lists and tasks from the Google Tasks REST API and writes them locally via `FileSystemStorage`. Remote always wins (read-only workspace mode). OAuth flow is partially implemented — client ID/secret are placeholders pending real credentials. - **Google Tasks** (`google_tasks.rs`): Read-only Google Tasks API client using reqwest with Bearer auth. `gt_id_to_uuid()` converts Google Task IDs to stable UUID v5 values for consistent cross-sync identity. Fetches all task lists and tasks from the Google Tasks REST API and writes them locally via `FileSystemStorage`. Remote always wins (read-only workspace mode). OAuth flow is partially implemented — client ID/secret are placeholders pending real credentials.
@ -80,7 +80,7 @@ Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to
- Sliding lists drawer with checkmark selection - Sliding lists drawer with checkmark selection
- Settings popup overlay - Settings popup overlay
- Workspace switcher drop-up with add/remove - Workspace switcher drop-up with add/remove
- Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, Ink) via CSS `data-theme` attribute - Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, Black and Gold, Ink) via CSS `data-theme` attribute
- Completed tasks section with animated show/hide - Completed tasks section with animated show/hide
- Date picker/editor (DateTimePicker in new task + task detail); `has_time: bool` field tracks whether time is set - Date picker/editor (DateTimePicker in new task + task detail); `has_time: bool` field tracks whether time is set
- Move task between lists (inline list in kebab menu, no submenu) - Move task between lists (inline list in kebab menu, no submenu)
@ -108,6 +108,7 @@ Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to
- Custom confirmation dialogs (ConfirmDialog component replaces native confirm()) - Custom confirmation dialogs (ConfirmDialog component replaces native confirm())
- Workspace path validation (rejects system directories) - Workspace path validation (rejects system directories)
- Task detail auto-cleanup (taskStack clears when viewed task is deleted or list switches) - Task detail auto-cleanup (taskStack clears when viewed task is deleted or list switches)
- Swipe gestures on mobile: swipe left/right on a task to toggle completion (swipe direction depends on current status)
- Accessibility: ARIA labels/roles on interactive components, keyboard handlers, `prefers-reduced-motion` CSS support - Accessibility: ARIA labels/roles on interactive components, keyboard handlers, `prefers-reduced-motion` CSS support
### GUI features NOT yet done ### GUI features NOT yet done

12
PLAN.md
View file

@ -123,6 +123,7 @@ WorkspaceConfig {
webdav_url: Option<String>, webdav_url: Option<String>,
webdav_path: Option<String>, // User-selected remote folder webdav_path: Option<String>, // User-selected remote folder
google_account: Option<String>, // Email/display name (GoogleTasks workspaces) google_account: Option<String>, // Email/display name (GoogleTasks workspaces)
last_sync: Option<DateTime>, // Timestamp of last successful sync
theme: Option<String>, theme: Option<String>,
sync_interval_secs: Option<u64>, // Auto-sync polling interval (focused) sync_interval_secs: Option<u64>, // Auto-sync polling interval (focused)
sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused
@ -750,10 +751,10 @@ WorkspaceConfig {
- [x] Mark tasks complete/incomplete with animated transitions - [x] Mark tasks complete/incomplete with animated transitions
- [x] Drag-and-drop task reordering - [x] Drag-and-drop task reordering
- [x] Sliding lists drawer (80cqi wide, left side) - [x] Sliding lists drawer (80cqi wide, left side)
- [x] Settings popup overlay (WebDAV config, dark mode toggle) - [x] Settings popup overlay (WebDAV config, theme selector, window decorations)
- [x] Dark mode (GNOME-style neutral theme, cyan-blue accent) - [x] Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, Black and Gold, Ink)
- [x] Animated completed section show/hide - [x] Animated completed section show/hide
- [x] Move task between lists (kebab menu → "Move to..." submenu in task detail view) - [x] Move task between lists (inline list in task kebab menu, no submenu)
- [x] Optional time on due dates (`has_time: bool` field on Task with `#[serde(default)]` for backward compat; replaces the hours==0 heuristic) - [x] Optional time on due dates (`has_time: bool` field on Task with `#[serde(default)]` for backward compat; replaces the hours==0 heuristic)
- [x] Due date picker/editor (DateTimePicker component in both new task toast + task detail view) - [x] Due date picker/editor (DateTimePicker component in both new task toast + task detail view)
- [x] WebDAV setup flow with credentials (settings auto-populates URL/username/password from config + keychain on open) - [x] WebDAV setup flow with credentials (settings auto-populates URL/username/password from config + keychain on open)
@ -908,7 +909,8 @@ npm run tauri ios build
- [ ] Multiple windows (optional) - [ ] Multiple windows (optional)
#### Mobile-Specific #### Mobile-Specific
- [x] Swipe gestures (swipe to complete, swipe to delete) - [x] Swipe gestures (swipe to toggle completion; direction depends on current task status)
- [ ] Swipe to delete
- [ ] Pull-to-refresh - [ ] Pull-to-refresh
- [ ] Touch-optimized UI elements - [ ] Touch-optimized UI elements
- [ ] Larger touch targets - [ ] Larger touch targets
@ -977,7 +979,7 @@ npm run tauri ios build
#### Google Tasks Importer #### Google Tasks Importer
- [x] `google_tasks.rs` module in `onyx-core` — client, UUID mapping, read-only sync (remote always wins) - [x] `google_tasks.rs` module in `onyx-core` — client, UUID mapping, read-only sync (remote always wins)
- [x] `GoogleTasks` workspace mode and `google_account` config field - [x] `GoogleTasks` workspace mode and `google_account` config field
- [x] Tauri commands: `google_tasks_authorize()`, `google_tasks_sync()` - [x] Tauri commands: `start_google_oauth()`, `add_google_tasks_workspace()`, `sync_google_tasks_workspace()`
- [ ] Complete OAuth flow (client ID/secret placeholders need real credentials) - [ ] Complete OAuth flow (client ID/secret placeholders need real credentials)
- [ ] Migrate tasks, lists, due dates, notes with full UI integration - [ ] Migrate tasks, lists, due dates, notes with full UI integration
- [ ] Preserve task hierarchy and order - [ ] Preserve task hierarchy and order

View file

@ -55,7 +55,7 @@ onyx/
- Drag-and-drop reordering - Drag-and-drop reordering
- Sliding lists drawer, settings popup - Sliding lists drawer, settings popup
- Workspace switcher with add/remove - Workspace switcher with add/remove
- Dark mode (GNOME-style neutral grays, cyan-blue accent) - Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, Black and Gold, Ink)
- Due date picker/editor with optional time - Due date picker/editor with optional time
- Subtask hierarchy with three-panel slide navigation - Subtask hierarchy with three-panel slide navigation
- Move tasks between lists - Move tasks between lists
@ -64,6 +64,7 @@ onyx/
- WebDAV setup flow with credential auto-population - WebDAV setup flow with credential auto-population
- File watcher (auto-reloads on external changes) - File watcher (auto-reloads on external changes)
- Auto-sync with configurable interval, status indicators - Auto-sync with configurable interval, status indicators
- Swipe gestures on mobile (swipe to toggle completion)
- Custom confirmation dialogs - Custom confirmation dialogs
- Desktop packaging (Linux: AppImage + .deb; Windows: MSI) - Desktop packaging (Linux: AppImage + .deb; Windows: MSI)

View file

@ -207,6 +207,20 @@ let list = repo.get_list(list_id)?;
repo.delete_list(list_id)?; repo.delete_list(list_id)?;
``` ```
#### Rename List
```rust
repo.rename_list(list_id, "New Name".to_string())?;
```
#### Move Task Between Lists
```rust
// Atomically moves a task from one list to another.
// If the delete-from-source step fails, the copy in the destination is rolled back.
repo.move_task(from_list_id, to_list_id, task_id)?;
```
### Task Ordering ### Task Ordering
#### Reorder Task #### Reorder Task