docs: sync markdown files with current codebase state
- Rename due_date → date and group_by_due_date → group_by_date throughout all docs to match the renamed Rust fields (Task.date, TaskList.group_by_date, repository set_group_by_date/get_group_by_date) - Update YAML frontmatter examples: due: → date:, add has_time field - Update .listdata.json examples: group_by_due_date → group_by_date - Update CLI flag in examples: --due → --date - Add WorkspaceMode::GoogleTasks variant and google_account / sync_interval_unfocused_secs fields to WorkspaceConfig in all docs - Document google_tasks.rs module (read-only Google Tasks API client, UUID v5 mapping) - Document Ink theme and window decorations selector in CLAUDE.md - Update PLAN.md Phase 7 Google Tasks checklist to reflect partial implementation - Update current state date in CLAUDE.md (2026-04-06 → 2026-04-15) - Update PLAN.md Last Updated date (2026-04-05 → 2026-04-15) - Add google_tasks.rs to DEVELOPMENT.md project structure https://claude.ai/code/session_01VmzpAB3PRY7bvSoVmzMRVm
This commit is contained in:
parent
068cc70443
commit
208032596c
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -37,13 +37,14 @@ 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), `webdav_url`, `webdav_path` (user-selected remote folder), `theme`, and `sync_interval_secs`. `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), `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.
|
||||||
|
|
||||||
### On-disk format
|
### On-disk format
|
||||||
|
|
||||||
Workspaces are plain folders. Each task list is a subfolder containing `.listdata.json` (metadata/ordering) and one `.md` file per task. The workspace root has `.onyx-workspace.json` for list ordering and workspace detection. Sync only processes files at expected depths: `.onyx-workspace.json` at root, `.listdata.json` and `*.md` inside list directories. Task frontmatter contains `id`, `status`, `version` (u64, increments via `saturating_add` on every write, defaults to 1 for legacy files), and optionally `due`, `has_time`, `parent` (omitted when false/null). `list_tasks` auto-deduplicates by UUID, keeping the highest-version file and deleting stale copies; when versions are equal, filesystem modification time is used as a tiebreaker.
|
Workspaces are plain folders. Each task list is a subfolder containing `.listdata.json` (metadata/ordering) and one `.md` file per task. The workspace root has `.onyx-workspace.json` for list ordering and workspace detection. Sync only processes files at expected depths: `.onyx-workspace.json` at root, `.listdata.json` and `*.md` inside list directories. Task frontmatter contains `id`, `status`, `version` (u64, increments via `saturating_add` on every write, defaults to 1 for legacy files), and optionally `date`, `has_time`, `parent` (omitted when false/null). `list_tasks` auto-deduplicates by UUID, keeping the highest-version file and deleting stale copies; when versions are equal, filesystem modification time is used as a tiebreaker.
|
||||||
|
|
||||||
### Tauri GUI structure
|
### Tauri GUI structure
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ The GUI uses Svelte 5 runes mode (`$state`, `$derived`, `$effect`, `$props()`).
|
||||||
|
|
||||||
Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to on-disk formats, config structure, or sync conventions are free — do not add migration logic.
|
Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to on-disk formats, config structure, or sync conventions are free — do not add migration logic.
|
||||||
|
|
||||||
### Current state (2026-04-06)
|
### Current state (2026-04-15)
|
||||||
|
|
||||||
- **Phase 1** (Core + CLI): Complete
|
- **Phase 1** (Core + CLI): Complete
|
||||||
- **Phase 2** (WebDAV sync): Complete — remote folder browsing, checksum-based conflict resolution, auto-sync lifecycle, per-workspace sync interval
|
- **Phase 2** (WebDAV sync): Complete — remote folder browsing, checksum-based conflict resolution, auto-sync lifecycle, per-workspace sync interval
|
||||||
|
|
@ -79,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) via CSS `data-theme` attribute
|
- Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, 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)
|
||||||
|
|
@ -96,7 +97,8 @@ Pre-alpha. No users, no released builds, no data to migrate. Breaking changes to
|
||||||
- Upload/download counts in drawer footer (hidden when zero)
|
- Upload/download counts in drawer footer (hidden when zero)
|
||||||
- Transient sync error suppression (connectivity issues update status dot only, no error banner)
|
- Transient sync error suppression (connectivity issues update status dot only, no error banner)
|
||||||
- File watcher (notify crate, 500ms debounce, auto-reloads on external changes, emits `watcher-error` event to frontend on failures)
|
- File watcher (notify crate, 500ms debounce, auto-reloads on external changes, emits `watcher-error` event to frontend on failures)
|
||||||
- WorkspaceMode enum (local/webdav) with per-workspace config, UUID-keyed workspaces
|
- WorkspaceMode enum (local/webdav/googletasks) with per-workspace config, UUID-keyed workspaces
|
||||||
|
- Window decorations selector (Custom/None/System) — persisted to localStorage, toggled via settings; Custom renders a Linux-style rounded border with `data-decorations` attribute
|
||||||
- Workspace rename (local folder rename + WebDAV MOVE for remote folders, with confirmation dialog)
|
- Workspace rename (local folder rename + WebDAV MOVE for remote folders, with confirmation dialog)
|
||||||
- Desktop packaging (Linux: AppImage + .deb; Windows: MSI)
|
- Desktop packaging (Linux: AppImage + .deb; Windows: MSI)
|
||||||
- Tauri desktop-only deps (notify, keyring) feature-gated for Android compilation
|
- Tauri desktop-only deps (notify, keyring) feature-gated for Android compilation
|
||||||
|
|
|
||||||
84
PLAN.md
84
PLAN.md
|
|
@ -62,7 +62,8 @@ Tasks are stored as individual `.md` files with YAML frontmatter:
|
||||||
id: 550e8400-e29b-41d4-a716-446655440000
|
id: 550e8400-e29b-41d4-a716-446655440000
|
||||||
status: backlog
|
status: backlog
|
||||||
version: 3
|
version: 3
|
||||||
due: 2026-11-15T14:00:00Z
|
date: 2026-11-15T14:00:00Z
|
||||||
|
has_time: true
|
||||||
parent: 550e8400-e29b-41d4-a716-446655440001
|
parent: 550e8400-e29b-41d4-a716-446655440001
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -84,8 +85,8 @@ Task {
|
||||||
title: String, // Derived from filename (without .md extension)
|
title: String, // Derived from filename (without .md extension)
|
||||||
description: String, // Markdown content
|
description: String, // Markdown content
|
||||||
status: TaskStatus, // Backlog or Completed
|
status: TaskStatus, // Backlog or Completed
|
||||||
due_date: Option<DateTime>,
|
date: Option<DateTime>,
|
||||||
has_time: bool, // Whether due_date includes a specific time
|
has_time: bool, // Whether date includes a specific time
|
||||||
version: u64, // Increments (saturating) on every write; used for sync dedup
|
version: u64, // Increments (saturating) on every write; used for sync dedup
|
||||||
parent_id: Option<Uuid>, // For subtasks
|
parent_id: Option<Uuid>, // For subtasks
|
||||||
}
|
}
|
||||||
|
|
@ -98,10 +99,10 @@ enum TaskStatus {
|
||||||
TaskList {
|
TaskList {
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
title: String, // Derived from folder name
|
title: String, // Derived from folder name
|
||||||
tasks: Vec<Task>, // Ordered by task_order, optionally grouped by due date
|
tasks: Vec<Task>, // Ordered by task_order, optionally grouped by date
|
||||||
created_at: DateTime,
|
created_at: DateTime,
|
||||||
updated_at: DateTime,
|
updated_at: DateTime,
|
||||||
group_by_due_date: bool, // If true, group by due date before applying task_order
|
group_by_date: bool, // If true, group by date before applying task_order
|
||||||
}
|
}
|
||||||
|
|
||||||
AppConfig {
|
AppConfig {
|
||||||
|
|
@ -109,14 +110,22 @@ AppConfig {
|
||||||
current_workspace: Option<String>, // UUID
|
current_workspace: Option<String>, // UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorkspaceMode {
|
||||||
|
Local,
|
||||||
|
Webdav,
|
||||||
|
GoogleTasks,
|
||||||
|
}
|
||||||
|
|
||||||
WorkspaceConfig {
|
WorkspaceConfig {
|
||||||
name: String, // Display name
|
name: String, // Display name
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
mode: WorkspaceMode, // Local or Webdav
|
mode: WorkspaceMode, // Local, Webdav, or GoogleTasks
|
||||||
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)
|
||||||
theme: Option<String>,
|
theme: Option<String>,
|
||||||
sync_interval_secs: Option<u64>,
|
sync_interval_secs: Option<u64>, // Auto-sync polling interval (focused)
|
||||||
|
sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -152,7 +161,7 @@ WorkspaceConfig {
|
||||||
"id": "list-uuid-1",
|
"id": "list-uuid-1",
|
||||||
"created_at": "2026-10-26T10:00:00Z",
|
"created_at": "2026-10-26T10:00:00Z",
|
||||||
"updated_at": "2026-10-27T14:30:00Z",
|
"updated_at": "2026-10-27T14:30:00Z",
|
||||||
"group_by_due_date": false,
|
"group_by_date": false,
|
||||||
"task_order": [
|
"task_order": [
|
||||||
"task-uuid-1",
|
"task-uuid-1",
|
||||||
"task-uuid-2",
|
"task-uuid-2",
|
||||||
|
|
@ -163,8 +172,8 @@ WorkspaceConfig {
|
||||||
|
|
||||||
**Task Ordering**:
|
**Task Ordering**:
|
||||||
- Tasks are always ordered according to the `task_order` array (manual ordering)
|
- Tasks are always ordered according to the `task_order` array (manual ordering)
|
||||||
- When `group_by_due_date` is `true`, tasks are first grouped by their due date, then sorted within each group by `task_order`
|
- When `group_by_date` is `true`, tasks are first grouped by their date, then sorted within each group by `task_order`
|
||||||
- Tasks without due dates appear at the end when grouping is enabled
|
- Tasks without dates appear at the end when grouping is enabled
|
||||||
|
|
||||||
**App Configuration** (separate from task data, supports multiple workspaces):
|
**App Configuration** (separate from task data, supports multiple workspaces):
|
||||||
- Windows: `%APPDATA%/onyx/config.json`
|
- Windows: `%APPDATA%/onyx/config.json`
|
||||||
|
|
@ -220,8 +229,8 @@ impl TaskRepository {
|
||||||
pub fn get_task_order(&self, list_id: Uuid) -> Result<Vec<Uuid>>;
|
pub fn get_task_order(&self, list_id: Uuid) -> Result<Vec<Uuid>>;
|
||||||
|
|
||||||
// Grouping preference (modifies .listdata.json)
|
// Grouping preference (modifies .listdata.json)
|
||||||
pub fn set_group_by_due_date(&mut self, list_id: Uuid, enabled: bool) -> Result<()>;
|
pub fn set_group_by_date(&mut self, list_id: Uuid, enabled: bool) -> Result<()>;
|
||||||
pub fn get_group_by_due_date(&self, list_id: Uuid) -> Result<bool>;
|
pub fn get_group_by_date(&self, list_id: Uuid) -> Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Storage {
|
pub trait Storage {
|
||||||
|
|
@ -351,9 +360,9 @@ $ onyx list create "Personal Projects"
|
||||||
$ onyx add "Buy groceries"
|
$ onyx add "Buy groceries"
|
||||||
✓ Created task "Buy groceries" (550e8400-e29b-41d4-a716-446655440000)
|
✓ Created task "Buy groceries" (550e8400-e29b-41d4-a716-446655440000)
|
||||||
|
|
||||||
$ onyx add "Review PR #123" --list "Work" --due "2026-11-15"
|
$ onyx add "Review PR #123" --list "Work" --date "2026-11-15"
|
||||||
✓ Created task "Review PR #123" (7f3a9c21-b8d2-4e5f-9a1c-3d8e7f6a2b1c)
|
✓ Created task "Review PR #123" (7f3a9c21-b8d2-4e5f-9a1c-3d8e7f6a2b1c)
|
||||||
Due: 2026-11-15
|
Date: 2026-11-15
|
||||||
|
|
||||||
# Or specify workspace explicitly
|
# Or specify workspace explicitly
|
||||||
$ onyx add "Team meeting" --workspace shared
|
$ onyx add "Team meeting" --workspace shared
|
||||||
|
|
@ -367,7 +376,7 @@ My Tasks (3 tasks)
|
||||||
[✓] Pay bills
|
[✓] Pay bills
|
||||||
|
|
||||||
Work (2 tasks)
|
Work (2 tasks)
|
||||||
[ ] Review PR #123 (due: 2026-11-15)
|
[ ] Review PR #123 (date: 2026-11-15)
|
||||||
[ ] Team meeting prep
|
[ ] Team meeting prep
|
||||||
|
|
||||||
# List tasks from specific workspace
|
# List tasks from specific workspace
|
||||||
|
|
@ -379,7 +388,7 @@ Shared Tasks (2 tasks)
|
||||||
# List tasks in specific list
|
# List tasks in specific list
|
||||||
$ onyx list show --list "Work"
|
$ onyx list show --list "Work"
|
||||||
Work (2 tasks)
|
Work (2 tasks)
|
||||||
[ ] Review PR #123 (due: 2026-11-15)
|
[ ] Review PR #123 (date: 2026-11-15)
|
||||||
[ ] Team meeting prep
|
[ ] Team meeting prep
|
||||||
|
|
||||||
# Complete a task
|
# Complete a task
|
||||||
|
|
@ -417,12 +426,12 @@ $ onyx workspace remove shared
|
||||||
Continue? (y/n): y
|
Continue? (y/n): y
|
||||||
✓ Removed workspace "shared"
|
✓ Removed workspace "shared"
|
||||||
|
|
||||||
# Toggle grouping by due date (tasks always use manual task_order within groups)
|
# Toggle grouping by date (tasks always use manual task_order within groups)
|
||||||
$ onyx group enable --list "Work"
|
$ onyx group enable --list "Work"
|
||||||
✓ Enabled group-by-due-date for list "Work"
|
✓ Enabled group-by-date for list "Work"
|
||||||
|
|
||||||
$ onyx group disable --list "Personal"
|
$ onyx group disable --list "Personal"
|
||||||
✓ Disabled group-by-due-date for list "Personal"
|
✓ Disabled group-by-date for list "Personal"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deliverables
|
### Deliverables
|
||||||
|
|
@ -468,12 +477,14 @@ Add WebDAV support to `onyx-core`:
|
||||||
WorkspaceConfig {
|
WorkspaceConfig {
|
||||||
name: String,
|
name: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
mode: WorkspaceMode, // Local or Webdav
|
mode: WorkspaceMode, // Local, Webdav, or GoogleTasks
|
||||||
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)
|
||||||
last_sync: Option<DateTime>,
|
last_sync: Option<DateTime>,
|
||||||
theme: Option<String>,
|
theme: Option<String>,
|
||||||
sync_interval_secs: Option<u64>,
|
sync_interval_secs: Option<u64>, // Auto-sync polling interval (focused)
|
||||||
|
sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig remains the same (workspaces + current_workspace)
|
// AppConfig remains the same (workspaces + current_workspace)
|
||||||
|
|
@ -688,14 +699,16 @@ AppConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkspaceConfig {
|
WorkspaceConfig {
|
||||||
name: String, // Display name
|
name: String, // Display name
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
mode: WorkspaceMode, // Local or Webdav
|
mode: WorkspaceMode, // Local, Webdav, or GoogleTasks
|
||||||
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)
|
||||||
last_sync: Option<DateTime>,
|
last_sync: Option<DateTime>,
|
||||||
theme: Option<String>, // Per-workspace theme
|
theme: Option<String>, // Per-workspace theme
|
||||||
sync_interval_secs: Option<u64>, // Auto-sync interval
|
sync_interval_secs: Option<u64>, // Auto-sync interval (focused)
|
||||||
|
sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -741,7 +754,7 @@ WorkspaceConfig {
|
||||||
- [x] Keyboard shortcuts (Escape closes settings → detail → drawer → menus in priority order)
|
- [x] Keyboard shortcuts (Escape closes settings → detail → drawer → menus in priority order)
|
||||||
- [x] Sync status indicators (last-sync time + upload/download counts chip in TasksScreen)
|
- [x] Sync status indicators (last-sync time + upload/download counts chip in TasksScreen)
|
||||||
- [x] Push/pull sync mode selection (session-only sync direction selector in SettingsScreen)
|
- [x] Push/pull sync mode selection (session-only sync direction selector in SettingsScreen)
|
||||||
- [x] Group-by-due-date toggle per list (checkmark toggle in list kebab menu)
|
- [x] Group-by-date toggle per list (checkmark toggle in list kebab menu)
|
||||||
- [x] Subtask hierarchy (expand/collapse, inline add, cascade toggle/delete)
|
- [x] Subtask hierarchy (expand/collapse, inline add, cascade toggle/delete)
|
||||||
- [ ] Search/filter tasks
|
- [ ] Search/filter tasks
|
||||||
- [x] Desktop packaging (Linux: AppImage + .deb; Windows: MSI; macOS not yet verified)
|
- [x] Desktop packaging (Linux: AppImage + .deb; Windows: MSI; macOS not yet verified)
|
||||||
|
|
@ -955,10 +968,13 @@ npm run tauri ios build
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
#### Google Tasks Importer
|
#### Google Tasks Importer
|
||||||
- [ ] **Import from Google Tasks** (via API or export)
|
- [x] `google_tasks.rs` module in `onyx-core` — client, UUID mapping, read-only sync (remote always wins)
|
||||||
- [ ] Migrate tasks, lists, due dates, notes
|
- [x] `GoogleTasks` workspace mode and `google_account` config field
|
||||||
|
- [x] Tauri commands: `google_tasks_authorize()`, `google_tasks_sync()`
|
||||||
|
- [ ] Complete OAuth flow (client ID/secret placeholders need real credentials)
|
||||||
|
- [ ] Migrate tasks, lists, due dates, notes with full UI integration
|
||||||
- [ ] Preserve task hierarchy and order
|
- [ ] Preserve task hierarchy and order
|
||||||
- [ ] Easy onboarding for Google Tasks users
|
- [ ] Easy onboarding flow for Google Tasks users
|
||||||
|
|
||||||
#### Advanced Task Management
|
#### Advanced Task Management
|
||||||
- [ ] **Recurring tasks** (tasks that automatically uncomplete and reschedule)
|
- [ ] **Recurring tasks** (tasks that automatically uncomplete and reschedule)
|
||||||
|
|
@ -1029,6 +1045,6 @@ This project is free and open-source software licensed under GPL v3.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2026-04-05
|
**Last Updated**: 2026-04-15
|
||||||
**Document Version**: 4.3
|
**Document Version**: 4.3
|
||||||
**Status**: Ready to Implement - Milestone-Driven Plan
|
**Status**: Ready to Implement - Milestone-Driven Plan
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,8 @@ cargo run -p onyx-cli -- init ~/Documents/Tasks --name personal
|
||||||
# Add a task
|
# Add a task
|
||||||
cargo run -p onyx-cli -- add "Buy groceries"
|
cargo run -p onyx-cli -- add "Buy groceries"
|
||||||
|
|
||||||
# Add a task with due date
|
# Add a task with a date
|
||||||
cargo run -p onyx-cli -- add "Review PR #123" --list "Work" --due "2026-11-15"
|
cargo run -p onyx-cli -- add "Review PR #123" --list "Work" --date "2026-11-15"
|
||||||
|
|
||||||
# List all tasks
|
# List all tasks
|
||||||
cargo run -p onyx-cli -- list show
|
cargo run -p onyx-cli -- list show
|
||||||
|
|
@ -168,7 +168,7 @@ Tasks are stored as markdown files with YAML frontmatter (Obsidian-compatible):
|
||||||
id: 550e8400-e29b-41d4-a716-446655440000
|
id: 550e8400-e29b-41d4-a716-446655440000
|
||||||
status: backlog
|
status: backlog
|
||||||
version: 3
|
version: 3
|
||||||
due: 2026-11-15T14:00:00Z
|
date: 2026-11-15T14:00:00Z
|
||||||
---
|
---
|
||||||
|
|
||||||
Task description and notes go here in **markdown** format.
|
Task description and notes go here in **markdown** format.
|
||||||
|
|
|
||||||
41
docs/API.md
41
docs/API.md
|
|
@ -18,8 +18,8 @@ pub struct Task {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub status: TaskStatus,
|
pub status: TaskStatus,
|
||||||
pub due_date: Option<DateTime<Utc>>,
|
pub date: Option<DateTime<Utc>>,
|
||||||
pub has_time: bool, // Whether due_date includes a specific time
|
pub has_time: bool, // Whether date includes a specific time
|
||||||
pub version: u64, // Increments (saturating) on every write; used for sync dedup
|
pub version: u64, // Increments (saturating) on every write; used for sync dedup
|
||||||
pub parent_id: Option<Uuid>,
|
pub parent_id: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
@ -38,10 +38,10 @@ use onyx_core::Task;
|
||||||
// Simple task
|
// Simple task
|
||||||
let task = Task::new("Buy groceries".to_string());
|
let task = Task::new("Buy groceries".to_string());
|
||||||
|
|
||||||
// Task with description and due date
|
// Task with description and date
|
||||||
let task = Task::new("Review PR #123".to_string())
|
let task = Task::new("Review PR #123".to_string())
|
||||||
.with_description("Check the authentication changes".to_string())
|
.with_description("Check the authentication changes".to_string())
|
||||||
.with_due_date(chrono::Utc::now() + chrono::Duration::days(2));
|
.with_date(chrono::Utc::now() + chrono::Duration::days(2));
|
||||||
```
|
```
|
||||||
|
|
||||||
#### TaskList
|
#### TaskList
|
||||||
|
|
@ -55,7 +55,7 @@ pub struct TaskList {
|
||||||
pub tasks: Vec<Task>,
|
pub tasks: Vec<Task>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
pub group_by_due_date: bool,
|
pub group_by_date: bool,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -108,15 +108,23 @@ config.save_to_file(&config_path)?;
|
||||||
Configuration for a single workspace.
|
Configuration for a single workspace.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
pub enum WorkspaceMode {
|
||||||
|
Local,
|
||||||
|
Webdav,
|
||||||
|
GoogleTasks,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WorkspaceConfig {
|
pub struct WorkspaceConfig {
|
||||||
pub name: String, // Display name
|
pub name: String, // Display name
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub mode: WorkspaceMode, // Local or Webdav
|
pub mode: WorkspaceMode, // Local, Webdav, or GoogleTasks
|
||||||
pub webdav_url: Option<String>,
|
pub webdav_url: Option<String>,
|
||||||
pub webdav_path: Option<String>, // User-selected remote folder path
|
pub webdav_path: Option<String>, // User-selected remote folder path
|
||||||
|
pub google_account: Option<String>, // Email/display name (GoogleTasks workspaces)
|
||||||
pub last_sync: Option<DateTime<Utc>>,
|
pub last_sync: Option<DateTime<Utc>>,
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
pub sync_interval_secs: Option<u64>, // Auto-sync polling interval
|
pub sync_interval_secs: Option<u64>, // Auto-sync polling interval (focused)
|
||||||
|
pub sync_interval_unfocused_secs: Option<u64>, // Auto-sync interval when unfocused
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -217,17 +225,17 @@ let order = repo.get_task_order(list_id)?;
|
||||||
|
|
||||||
### Grouping
|
### Grouping
|
||||||
|
|
||||||
#### Enable/Disable Group by Due Date
|
#### Enable/Disable Group by Date
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Enable grouping
|
// Enable grouping
|
||||||
repo.set_group_by_due_date(list_id, true)?;
|
repo.set_group_by_date(list_id, true)?;
|
||||||
|
|
||||||
// Disable grouping
|
// Disable grouping
|
||||||
repo.set_group_by_due_date(list_id, false)?;
|
repo.set_group_by_date(list_id, false)?;
|
||||||
|
|
||||||
// Check current setting
|
// Check current setting
|
||||||
let is_grouped = repo.get_group_by_due_date(list_id)?;
|
let is_grouped = repo.get_group_by_date(list_id)?;
|
||||||
```
|
```
|
||||||
|
|
||||||
## File Format
|
## File Format
|
||||||
|
|
@ -241,7 +249,8 @@ Tasks are stored as `.md` files with YAML frontmatter:
|
||||||
id: 550e8400-e29b-41d4-a716-446655440000
|
id: 550e8400-e29b-41d4-a716-446655440000
|
||||||
status: backlog
|
status: backlog
|
||||||
version: 3
|
version: 3
|
||||||
due: 2026-11-15T14:00:00Z
|
date: 2026-11-15T14:00:00Z
|
||||||
|
has_time: true
|
||||||
parent: 550e8400-e29b-41d4-a716-446655440001
|
parent: 550e8400-e29b-41d4-a716-446655440001
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -263,7 +272,7 @@ Each list folder contains a `.listdata.json` file:
|
||||||
"id": "list-uuid-1",
|
"id": "list-uuid-1",
|
||||||
"created_at": "2026-10-26T10:00:00Z",
|
"created_at": "2026-10-26T10:00:00Z",
|
||||||
"updated_at": "2026-10-27T14:30:00Z",
|
"updated_at": "2026-10-27T14:30:00Z",
|
||||||
"group_by_due_date": false,
|
"group_by_date": false,
|
||||||
"task_order": [
|
"task_order": [
|
||||||
"task-uuid-1",
|
"task-uuid-1",
|
||||||
"task-uuid-2",
|
"task-uuid-2",
|
||||||
|
|
@ -453,7 +462,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let task1 = repo.create_task(list.id, task1)?;
|
let task1 = repo.create_task(list.id, task1)?;
|
||||||
|
|
||||||
let task2 = Task::new("Call dentist".to_string())
|
let task2 = Task::new("Call dentist".to_string())
|
||||||
.with_due_date(chrono::Utc::now() + chrono::Duration::days(1));
|
.with_date(chrono::Utc::now() + chrono::Duration::days(1));
|
||||||
let task2 = repo.create_task(list.id, task2)?;
|
let task2 = repo.create_task(list.id, task2)?;
|
||||||
|
|
||||||
// List all tasks
|
// List all tasks
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,8 @@ onyx/
|
||||||
│ │ │ ├── repository.rs # Repository pattern (TaskRepository)
|
│ │ │ ├── repository.rs # Repository pattern (TaskRepository)
|
||||||
│ │ │ ├── error.rs # Error types
|
│ │ │ ├── error.rs # Error types
|
||||||
│ │ │ ├── sync.rs # Three-way sync engine with offline queue
|
│ │ │ ├── sync.rs # Three-way sync engine with offline queue
|
||||||
│ │ │ └── webdav.rs # WebDAV client and credential storage
|
│ │ │ ├── webdav.rs # WebDAV client and credential storage
|
||||||
|
│ │ │ └── google_tasks.rs # Google Tasks API client (read-only sync)
|
||||||
│ │ └── Cargo.toml
|
│ │ └── Cargo.toml
|
||||||
│ ├── onyx-cli/ # CLI application
|
│ ├── onyx-cli/ # CLI application
|
||||||
│ │ ├── src/
|
│ │ ├── src/
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue