Add onyx theme as a new dark theme option. Register it in the dark
themes set, add the theme option to the settings dropdown, and define
complete color variables and scrollbar styling for the onyx theme.
Also improve sync error handling to gracefully skip upload and conflict
resolution actions when files are missing, preventing sync failures due
to files moved or deleted between action computation and execution.
- CLAUDE.md: Fix drawer width unit (80vw -> 80cqi), clarify group-by-due-date
is toggle-only (sorting not yet implemented), clarify Phase 4 status
- PLAN.md: Fix conflict resolution label (last-write-wins -> remote wins),
add username to keyring key format, mark fallback credential storage as
not yet implemented
- sync.rs: Fix comment table to say "conflict" instead of "last-write-wins"
for both-modified and both-added cases (matches actual Conflict action)
- config.rs: Add temp file cleanup on atomic write rename failure to match
the pattern already used in storage.rs
https://claude.ai/code/session_01YWvxpzeT3hEUxD9aNvfNuL
Security:
- Fix path traversal via backslash bypass in sync validate_sync_path()
- Replace silent HTTP client fallback with proper error propagation
- Add 64KB YAML frontmatter size limit to prevent DoS via crafted files
Data integrity:
- Reorder delete operations: update metadata before removing files to
prevent orphaned metadata entries on crash
- Fix task deduplication to use file mtime as tiebreaker when versions
are equal, preventing non-deterministic data loss
- Add rollback on conflict recovery failure (remove orphaned duplicate
files when metadata update fails)
- Clean up temp files on atomic write rename failure
- Add file-based sync lock to prevent concurrent sync operations
- Use saturating_add for task version to prevent overflow
Error handling:
- Surface move_task rollback failures as structured errors instead of
silent warnings
- Log WebDAV parallel request failures instead of silently swallowing
- Emit watcher-error events to frontend instead of only printing to stderr
Frontend:
- Fix focus listener leak in auto-sync (clean up if stopAutoSync called
while promise pending)
- Add prefers-reduced-motion CSS media query for accessibility
- Add ARIA labels, roles, and keyboard handlers to TaskItem, BottomSheet,
and ConfirmDialog components
- Replace BottomSheet children: any with Snippet type
https://claude.ai/code/session_01AJoK28N4vqLqzskq6ybGri
Add a configurable per-workspace sync interval (sync_interval_secs) with
Tauri command set_sync_interval, UI dropdown in Settings, and store
support to restart auto-sync when changed. Remove the redundant
main-panel sync status chip and surface upload/download counts in the
drawer footer and Tasks screen. Fix the sync logic to record the remote
file's last_modified timestamp when downloading (instead of local mtime)
so subsequent diffs don’t falsely trigger downloads.
These changes were needed to allow users to control auto-sync frequency
per workspace, simplify the UI by moving sync counts to the drawer
footer, and correct a bug where downloads were always considered new
because the local file mtime was used instead of the authoritative
remote timestamp.
Rework sync conflict handling to stop using timestamp-based
last-write-wins and use a single Conflict action. The conflict handler
now downloads the remote file and compares SHA-256 checksums: identical
content is treated as a false conflict and skipped; when different, the
remote version wins and the local task file is recovered as a duplicate
with a new UUID and a "[RECOVERED FROM CONFLICT]" prefix. Duplicates are
inserted adjacent to the original in .listdata.json; non-task files
still use remote-wins without duplication. Removed local_wins() and its
tests, simplified action variants to a single Conflict, and updated
conflict-related tests and reporting accordingly.
Fix compute_sync_actions to properly treat the case where a file has
been deleted on the remote but remains unchanged locally: previously the
code always emitted an Upload (re-uploading the local copy). Now it
checks the base checksum and emits DeleteLocal when the local copy
matches the base (unchanged), and emits Upload only when the local copy
differs (modified).
Updated tests: adjusted test_remote_deleted_local_unchanged to assert
DeleteLocal, and added test_remote_deleted_local_modified to verify the
upload behavior when the local file is modified while remote was
deleted.
Rework WebDAV workspace setup to use .onyx-workspace.json instead of
.metadata.json and to let users pick a remote folder instead of forcing
an Onyx/ subfolder. This updates storage, sync, config types, tests, and
CLI/Tauri commands to store a webdav_path in WorkspaceConfig and to
combine webdav_url + webdav_path for sync.
Changes include:
- Rename .metadata.json → .onyx-workspace.json across storage, sync, and tests so workspace detection and root metadata use the new filename.
- Remove hardcoded automatic "Onyx/" subfolder in sync and use the user-selected remote path directly.
- Add webdav_path field to WorkspaceConfig (Rust and TypeScript types) and thread it through add_webdav_workspace and frontend addWebdavWorkspace.
- Add three Tauri commands (list_remote_folder, inspect_remote_workspace, create_remote_workspace) to support remote folder browsing, workspace preview, and remote workspace creation.
- Rewrite SetupScreen WebDAV flow to Connect → Browse (lazy folder explorer) → Preview or Create, and wire UI state/handlers to the new commands.
- Update CLAUDE.md to document the new on-disk filename and note development phase allowing breaking changes.
- Handle Result from WebDavClient::new in CLI sync, core sync, and Tauri
- Unwrap Zeroizing<String> at Tauri serialization boundary
- Use .as_str() for basic_auth calls with Zeroizing<String> fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Back up local files before overwriting during ConflictRemoteWins so data
is never silently lost. Fix false-positive change detection by parsing
timestamps before comparing (different formats like RFC3339 vs HTTP date
were never equal as strings). Add zeroize crate to zero WebDAV credentials
in memory on drop, preventing exposure in core dumps.
Restrict is_syncable() to validate path depth: .md files and .listdata.json
must be at depth 2 (inside list dirs), .metadata.json only at depth 1 (root).
Prevents syncing arbitrary files at unexpected depths. Back up corrupted
sync queue files before resetting, and log warnings on parse failures
instead of silently dropping queued operations.