- CLAUDE.md: add last_sync field to WorkspaceConfig description - README.md: update Phase 4 status, replace dark mode with multi-theme system, add has_time/parent fields to data format example - PLAN.md: add last_sync to Phase 1 WorkspaceConfig, update dark mode entries to reflect theme selector, fix Google Tasks Tauri command names, add rename_list/move_task to Core Library API, fix "Move to..." description (inline, not submenu) - docs/API.md: document rename_list and move_task repository methods - docs/DEVELOPMENT.md: add dateFormat.ts to frontend file structure https://claude.ai/code/session_01NCtJ5PNhaDh21kYnDZXYsN
12 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Onyx is a local-first, cross-platform task management app built in Rust. Tasks are stored as markdown files with YAML frontmatter in user-selected folders. The GUI uses Tauri v2 (Svelte 5 + Tailwind CSS 4) in apps/tauri/.
Build & Test Commands
cargo build # Build all crates
cargo build -p onyx-cli # Build CLI only
cargo test # Run all tests
cargo test -p onyx-core # Run core library tests only
cargo run -p onyx-cli -- <args> # Run CLI with arguments
# Tauri GUI
cd apps/tauri && npm install # Install frontend dependencies
WEBKIT_DISABLE_DMABUF_RENDERER=1 npm run tauri dev # Run Tauri in dev mode (Wayland)
npm run tauri build # Build for production
The CLI binary is named onyx (from the onyx-cli crate).
The Tauri dev server runs on port 1422 (vite.config.ts and tauri.conf.json).
Architecture
Two-crate workspace (resolver = "2", edition 2021) plus a Tauri app:
- onyx-core — Pure Rust library. Storage trait with
FileSystemStorageimplementation,TaskRepository(main API), data models, config, error types. No CLI/UI dependencies.keyringfeature-gated behindkeyring-storage(default on) for Android compatibility. - onyx-cli — CLI frontend using clap. Commands are in
src/commands/(init, workspace, list, task, group). Output formatting insrc/output.rs. - apps/tauri/ — Tauri v2 GUI. Svelte 5 frontend in
src/, Rust backend insrc-tauri/with Tauri commands that call intoonyx-core.notifycrate feature-gated for Android.tauri-plugin-credentials/provides cross-platform credential storage (Android Keystore via EncryptedSharedPreferences, desktop via keyring crate).
Key patterns
- Storage trait (
storage.rs): Strategy pattern for task persistence.FileSystemStoragereads/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):TaskRepositorywraps aStorageimpl and provides the public API for task/list CRUD, ordering, and grouping. Tests live here. - Config (
config.rs):AppConfigmanages workspaces keyed by UUID string.WorkspaceConfigstoresname,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), andsync_interval_unfocused_secs(lower-frequency polling when window loses focus, for mobile battery optimization).add_workspacereturns a generated UUID. Stored in platform-specific config dirs via thedirectoriescrate. Atomic writes (temp file + rename) prevent corruption on crash. - Sync (
sync.rs): Three-way diff sync with offline queue. File-based.sync.lockprevents 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 intokio::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_resourcemethod for WebDAV MOVE (workspace rename). 10MB cap on both PROPFIND responses and file downloads. Desktop credentials viakeyringcrate (feature-gated); Tauri GUI usestauri-plugin-credentialsfor 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 viaFileSystemStorage. Remote always wins (read-only workspace mode). OAuth flow is partially implemented — client ID/secret are placeholders pending real credentials.
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 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
The GUI uses Svelte 5 runes mode ($state, $derived, $effect, $props()). Key UI patterns:
- Sliding drawer: Left panel (lists) slides with main content as one piece via
translateX. 80cqi wide (80% container query inline size). List items show checkmark for active list and chevron on hover. - Three-panel slide: Main content area is 300% wide with three panels (task list, task detail, subtask detail) that slide via
translateXusing ataskStackarray. Stack depth 0 = list, 1 = task detail, 2 = subtask detail. - Settings modal: Per-workspace settings opened from workspace kebab menu. Shows WebDAV config (for webdav workspaces), sync controls, and theme selector.
- Workspace switcher: Custom drop-up menu in drawer footer (left), kebab menu per workspace (right) with Settings option.
- Task animations: Grid-rows
0fr/1frtrick for smooth collapse/expand. Module-levelanimateInIdsSet coordinates expand-in after toggle. - Inline editing: Click task to edit, auto-save on blur.
debouncedSavesnapshots task before timer to prevent stale-reference errors on component destroy. - Kebab menus: Tasks and lists use kebab menus with custom
ConfirmDialogcomponent (not nativeconfirm()). "Move to..." is inline in the menu (not a submenu) to avoid overflow. - Main panel header: Hamburger + window controls in top bar; list name (large, bold) + kebab below divider (matching task detail layout). Kebab has Rename, Group by date, Delete completed, Delete list.
- New task: FAB button opens bottom toast sheet (outside sliding container for fixed positioning).
Development phase
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-15)
- 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 3 (GUI MVP): Complete
- Phase 4 (Mobile): In progress — Android preliminaries done (file-watcher gating, tauri-plugin-credentials, safe area insets, Android targets configured); needs build verification and iOS setup
GUI features done
- Task CRUD (create, read, update, delete)
- Task completion/restoration with animated transitions
- Drag-and-drop task reordering
- Inline task editing (auto-save on blur)
- Sliding lists drawer with checkmark selection
- Settings popup overlay
- Workspace switcher drop-up with add/remove
- Per-workspace theme system (System default, Light, Dark, Nord, Dracula, Solarized Dark, Ink) via CSS
data-themeattribute - Completed tasks section with animated show/hide
- Date picker/editor (DateTimePicker in new task + task detail);
has_time: boolfield tracks whether time is set - Move task between lists (inline list in kebab menu, no submenu)
- List rename (inline input in main panel header via kebab)
- Group-by-date toggle per list (main panel kebab; persists flag but display sorting not yet implemented)
- Delete completed tasks (main panel kebab + subtask kebab, with confirmation dialogs)
- Keyboard shortcuts (Escape priority chain: settings → detail → list menu → drawer → menus)
- Setup screen with 2-step mode selection (Local Folder vs WebDAV Server), window dragging, "Open Existing Folder" option, remote folder browsing
- WebDAV setup flow with connection test, credential storage via tauri-plugin-credentials (Android Keystore + desktop keychain)
- WebDAV sync: user-selected remote folder, 60s hard timeout, checksum-based conflict resolution (remote wins, local recovered as duplicate)
- Auto-sync lifecycle: periodic polling (configurable interval), debounced file-change (5s), window-focus (30s stale threshold); sync status dot (idle/synced/error/offline) and manual sync button in drawer
- Initial sync loading screen for new WebDAV workspaces
- Per-workspace sync interval (configurable in settings)
- Upload/download counts in drawer footer (hidden when zero)
- 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-errorevent to frontend on failures) - 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-decorationsattribute - Workspace rename (local folder rename + WebDAV MOVE for remote folders, with confirmation dialog)
- Desktop packaging (Linux: AppImage + .deb; Windows: MSI)
- Tauri desktop-only deps (notify, keyring) feature-gated for Android compilation
- Safe area insets for mobile (CSS variables --safe-top/--safe-bottom, viewport-fit=cover)
- Task deduplication on load (handles sync conflict duplicates)
- Subtask hierarchy: subtask count shown on parent tasks in list, subtask detail via three-panel slide navigation, inline add at top of subtask list (new subtasks prepend), collapsible completed subtasks section, cascade delete (parent deletion removes all subtasks with confirmation warning)
- Custom confirmation dialogs (ConfirmDialog component replaces native confirm())
- Workspace path validation (rejects system directories)
- Task detail auto-cleanup (taskStack clears when viewed task is deleted or list switches)
- Accessibility: ARIA labels/roles on interactive components, keyboard handlers,
prefers-reduced-motionCSS support
GUI features NOT yet done
- Search/filter tasks
- Desktop packaging for macOS
Roadmap
See PLAN.md for the 7-phase roadmap. Detailed API docs in docs/API.md, development practices in docs/DEVELOPMENT.md.
GitButler
If you generate code or modify files, run the gitbutler update branches MCP tool.