Commit graph

35 commits

Author SHA1 Message Date
Claude 4e8f7c4536
fix(tauri): reject "/" root path in workspace validation
trim_end_matches('/') collapses "/" to "", which then isn't matched by
the forbidden list, so a root-filesystem workspace slipped through. Keep
"/" as the canonical form when the stripped value is empty.
2026-04-19 07:08:42 +00:00
Claude 0fc1f16c9d
fix(new-task): attach date in a single create_task call to prevent loss
The new-task bottom sheet called createTask then, if a date was set,
made a follow-up updateTask to attach the date. If the update failed
(e.g. filesystem error between the two writes) the user was left with
a dateless task and, because transient sync errors are already
suppressed, often no visible error either.

Extend the create_task Tauri command to accept optional date/has_time
fields and pass them through. The frontend now creates the task in one
round-trip. No separate update path needed.
2026-04-17 16:23:51 +00:00
Claude f276233be5
fix(tauri): cascade delete must handle the full subtree, not just direct children
delete_task only collected direct children when a parent was deleted,
so grandchildren (and deeper descendants — the data model allows any
depth even though the UI is two-level today) would be left with a
parent_id pointing at a deleted task. Walk the parent-child graph to
collect the full descendant set and delete children before the parent
so a mid-cascade failure can't strand descendants.
2026-04-17 16:19:46 +00:00
Claude df66e7bc98
fix(tauri): add_workspace must initialise the target folder
The frontend currently calls init_workspace before add_workspace, but
the Tauri command itself is trivially breakable by any caller that
skips the pre-step or a future frontend refactor: add_workspace would
save the workspace entry pointing at a non-existent directory, and
every subsequent command would then fail with 'Path does not exist'
via TaskRepository::new. Call TaskRepository::init inside the command
so it is self-contained and idempotent.
2026-04-17 16:19:03 +00:00
Claude ac72955d23
fix: correct operator precedence in Windows path validation
The condition `len <= 3 && ends_with(":\\") || ends_with(":")` was
missing parentheses, causing the second ends_with check to run
regardless of path length due to && binding tighter than ||.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:21:16 +00:00
Claude 125f1e19ac
fix: audit — debounced save data loss, silent workspace errors, code duplication
- Fix debouncedSave in TaskDetailView losing edits when title and
  description are changed within 400ms (shared timer only saved the
  last-changed field)
- Return errors from Tauri commands when workspace ID doesn't exist
  instead of silently succeeding (set_webdav_config, set_workspace_theme,
  set_sync_interval, set_sync_interval_unfocused)
- Remove duplicate atomic_write_bytes in google_tasks.rs; reuse
  pub(crate) atomic_write from storage.rs
- Fix failing test using wrong frontmatter field name (due → date)
- Add Audit.md log

https://claude.ai/code/session_0186pnnUJxj2uv1KhHjWoAGA
2026-04-15 07:16:40 +00:00
Tristan Michael 6a4b79801b Add Google Tasks read-only workspace and sync
Introduce a new Google Tasks workspace mode that performs a one-way,
read-only pull from the Google Tasks API into the local
FileSystemStorage format. This adds a Google Tasks client (token
refresh, paginated list fetching, stable UUID v5 mapping), desktop
PKCE+loopback OAuth flow (Android stub), credential storage, workspace
creation and sync commands, UI flows for sign-in/creation, and guards to
disable write operations for Google Tasks workspaces. Changes also
include Cargo dependency updates and exports to wire the new module into
the app.
Hide editing UI for Google Tasks workspaces

Make Google Tasks workspaces read-only by hiding editing controls and
the FAB when app.isGoogleTasks is true. This prevents rename,
delete-completed, and new-task actions in read-only Google Tasks
workspaces and adjusts menu options (Rename removed, Group by due date
and Show subtasks remain with proper toggles). The change clarifies the
UX for Google Tasks by disabling interaction where appropriate and
conditionally showing the FAB only for editable workspaces.
Add Google Tasks workspace (read-only)

Introduce Google Tasks as a third workspace mode to allow one-way,
read-only sync from the Google Tasks API into local FileSystemStorage.
This includes a new WorkspaceMode::GoogleTasks, workspace google_account
metadata, a GoogleTasks client and sync logic (with stable UUID v5 task
IDs), OAuth PKCE+loopback flow for desktop (Android uses a
credential-manager stub), UI changes to mark Google Tasks workspaces as
read-only and disable write operations, and commands to create and sync
Google Tasks workspaces. Dependency updates add sha2/sha1_smol/reqwest
for OAuth and API calls.
when I go to create a google tasks workspace, I see the content associated with creating a webdav workspace as part of the interface

- SetupScreen.svelte: fixed template structure where WebDAV branches lacked mode === "webdav" guards, causing them to render when mode === "googletasks"; folded Google Tasks steps into the main if/else chain and removed the separate Google Tasks {#if} block that was appending below the WebDAV content
2026-04-14 07:02:50 -07:00
Tristan Michael 9ed84690ac Rename due_date to date across codebase
The kebab menu and docs referred to a task "due date" but the field was
just a date; this change renames due_date/group_by_due_date and related
identifiers to date/group_by_date across Rust, TypeScript, Svelte, CLI,
docs and tests to keep terminology consistent. Updates include
API/command names, storage/models, repository methods, UI text, JS
variables and builder/parse functions so code, tests and documentation
all use "date" semantics.
Preserve old "group_by_due_date" field name

Add serde alias to ListMetadata.group_by_date so older .listdata.json
files that used the previous field name (group_by_due_date) can still be
deserialized correctly. This fixes serialization/deserialization issues
encountered at /home/trztn/Documents/Onyx and
/var/home/trztn/Nextcloud/Onyx.
the frontmatter due should be date... I don't want due anywhere

- Renamed `TaskFrontmatter.due` → `TaskFrontmatter.date`; YAML key on disk is now `date:` instead of `due:`
- Added `#[serde(alias = "due")]` so existing task files with `due:` frontmatter still deserialize correctly
- Updated google_tasks.rs to write `date:` instead of `due:` in generated YAML
- Renamed CLI `--due` flag to `--date`; updated function signature and display string "Due:" → "Date:"
Remove serde aliases and rename due→date

Drop backwards-compat aliases and update frontmatter/metadata to use the
canonical "date"/"group_by_date" fields. The aliases for
group_by_due_date and due were removed to avoid maintaining
backward-compatibility and to reflect the current schema. Updated
storage types to rename the fields and adjusted serialization attributes
so YAML/JSON frontmatter and .listdata.json files now use group_by_date
and date consistently.
2026-04-14 07:36:10 -07:00
Tristan Michael 50800f0c2d feat: add separate sync interval for unfocused state
Introduce a new sync interval configuration for when the app is
in the background, allowing users to set a longer sync period to
reduce resource usage. Replace the fixed focus threshold with
dynamic interval switching based on app focus state. Add
syncIntervalUnfocused setting to the UI with a new background
sync interval selector. Refactor sync interval management into
a restartSyncInterval function to handle both focused and
unfocused intervals consistently.
2026-04-06 09:37:06 -07:00
Claude 6174836b7f
Fix critical and high-severity issues from project audit
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
2026-04-06 11:03:11 +00:00
Claude 85748f4c95
Audit fixes: panic safety, path hardening, code quality
- Replace 2 production unwrap() calls in workspace.rs with proper error
  handling to prevent panics on inconsistent state
- Add AppState::save_config() helper to eliminate 11 duplicated
  save_to_file patterns in Tauri lib.rs
- Log file watcher errors instead of silently swallowing them
- Harden path traversal check in storage.rs: re-verify after
  canonicalization to catch symlink escapes
- Add Windows reserved device name handling (CON, NUL, etc.) to
  sanitize_filename
- Clean up stale .tmp files from interrupted atomic writes on startup

All 107 core tests pass.

https://claude.ai/code/session_01EnSrQsowc64rAwzD9BnJpc
2026-04-06 10:41:03 +00:00
Claude a12deb5182
Harden codebase: fix security, quality, and maintainability issues
- Replace dangerous unwrap() with proper error handling (storage.rs, webdav.rs)
- Add atomic writes (temp + rename) for config, sync state, and metadata files
- Add path traversal validation in sync executor
- Add workspace path validation in Tauri commands
- Add input size limits for task titles, descriptions, and list names
- Add file download size limit (10MB) to WebDAV get_file
- Fix move_task rollback to log failures instead of silently ignoring
- Fix JSON serialization unwrap in Tauri create_remote_workspace
- Fix swallowed errors in sync queue backup, metadata writes, sync state load
- Extract hardcoded strings into named constants (filenames, extensions, limits)
- Use REQUEST_TIMEOUT/CONNECT_TIMEOUT constants in WebDAV client builder
- Fix frontend: clear taskStack when viewed task is deleted or list is switched
- Fix frontend: surface credential loading and focus listener errors

https://claude.ai/code/session_01F67yfLLmSaBtT7aKKNus1M
2026-04-06 10:17:30 +00:00
Tristan Michael 501f991f2c feat: replace custom WebDAV credential IO with pluginSwitch credential storage and retrieval the tauri_plugin_credentialsplugin and thread the AppHandle into handlers that need it.
- Import Credentials and initialize its plugin inuriBuilder.
- Replace blocking webdav::load_credentials/store calls with Credentials::load and Credentials::store to avoid spawn_blocking and centralize credential handling- AppHandle parameters to commands that need credential access:
 rename_workspace, add_webdav_workspace, store_credentials, load_credentials,
 and sync_workspace- Update save flow in add_webdav_workspace to use plugin storage after updating config.

This reduces manual blocking calls, unifies credential management andintegrates with Tauri app state for safer runtime access.
2026-04-05 17:30:07 -07:00
Tristan Michael 0ae0705331 Add per-workspace sync interval and fix download timestamp recording
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.
2026-04-05 16:35:22 -07:00
Tristan Michael 4c57851e15 Rename workspace and remote folders with confirmation
Add WebDAV MOVE support and update workspace rename flow to handle both
local and WebDAV-backed workspaces. The Tauri rename_workspace command
is made async and now performs filesystem rename for local workspaces
and issues a WebDAV MOVE (via a new WebDavClient::move_resource) for
remote workspaces, updating stored paths and credentials accordingly. A
confirmation dialog is added to SettingsScreen to prompt users before
renaming, and minor UI/default tweaks are included (SetupScreen default
name). This ensures renames update both local folders and remote WebDAV
folders reliably and with user confirmation.
2026-04-05 15:10:44 -07:00
Tristan Michael 50d859ef80 Refactor workspaces to use UUID keys
Change workspace identifiers from display names to UUID strings so
multiple workspaces can share the same display name. WorkspaceConfig now
stores a name field; add_workspace returns a generated UUID. Update all
CLI, Tauri commands, frontend stores and screens, WebDAV managed
directories, and tests to use/resolve workspace IDs; add helpers
find_by_name/resolve_name to map display names to IDs when needed. This
removes duplicate-name checks, avoids filesystem conflicts, and
preserves display names while using stable unique IDs internally.
2026-04-05 14:58:31 -07:00
Tristan Michael a709df609f Prevent workspace name collisions
Adding explicit duplicate-name checks to the Tauri commands that create
workspaces prevents a new workspace from silently overwriting an
existing one. The change returns an error if a workspace with the given
name already exists.

Also set sane defaults in the setup UI: default workspace name to "Onyx"
for both local and WebDAV flows, default the local folder to the user's
Documents directory via documentDir(), and ensure the create flow resets
the name to the "Onyx" default when starting creation.
2026-04-05 14:49:55 -07:00
Tristan Michael 095ac8fa97 Parallelize workspace detection in folder listing
Speed up initial folder listing by checking each subfolder for the
.onyx-workspace.json marker in parallel instead of sequentially. This
uses futures::future::join_all to run multiple PROPFIND/list_files calls
concurrently, reducing latency when detecting workspaces across many
subdirectories.

Also add the futures 0.3 dependency to the Tauri Cargo.toml and lockfile
so the async utilities are available.
2026-04-05 14:37:58 -07:00
Tristan Michael 753cb1cad5 Rename workspace metadata and add WebDAV folder browsing
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.
2026-04-05 14:30:22 -07:00
Tristan Michael bb735ecd4a Add workspace rename and restructure settings screen
Full-stack workspace rename: renames folder on disk, updates config
key/path, refreshes frontend. Restructure settings screen with generic
'Workspace Settings' header, workspace name row with kebab menu
(Rename + Delete). Replace per-workspace kebab dropdown in workspace
list with a direct settings gear button. Remove Appearance heading
and border box from theme section. Clean up unused wsMenuName state.
2026-04-05 14:30:22 -07:00
Tristan Michael 58f37b08d6 fix: harden WebDAV sync — async credentials, consolidated command, Onyx subfolder 2026-04-03 10:11:46 -07:00
Tristan Michael be6b8d0d90 security: update callers for hardened credential API
- 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>
2026-04-03 10:11:40 -07:00
Tristan Michael a60b1a997b feat: add WorkspaceMode (local/webdav) and per-workspace theme to config
Introduces WorkspaceMode enum with local and webdav variants, plus a
theme field on WorkspaceConfig. Adds set_workspace_theme and
add_webdav_workspace Tauri commands. WebDAV workspaces auto-manage
local files in app data dir.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 04:02:39 -07:00
Tristan Michael 3c11539f02 fix: harden Tauri backend — replace unwrap panics, fix watcher lifecycle, strengthen CSP
Replace all .unwrap() calls on repo with repo_ref()/repo_mut() helpers
that return error strings instead of panicking. Stop the old file watcher
before starting a new one on workspace switch to prevent accumulation.
Add object-src and base-uri directives to CSP.
2026-04-02 09:37:43 -07:00
Tristan Michael 3b2cb12272 fix: replace panicking unwrap/expect calls with graceful error handling
Replace .expect() in config path resolution with a fallback default.
Replace all .lock().unwrap() on Tauri AppState mutex with a helper that
converts poisoned locks into error strings. Replace .expect() on Android
app data dir with proper error propagation. Handle LAST_WRITE and WATCHER
global mutex locks gracefully. Propagate subtask cascade delete errors
instead of silently ignoring them.
2026-04-02 09:35:38 -07:00
Tristan Michael e709d6e4f1 Fix Android crash: resolve config path via Tauri app data dir 2026-04-02 07:51:38 -07:00
Claude 45b7cc57de
feat: add subtask hierarchy UI to Tauri GUI
Wire up the existing parent_id data model into a full subtask UI:
- Backend: create_task accepts parent_id, cascade delete/toggle for subtasks
- Store: subtask-aware derived state (root tasks only in pending/completed),
  childrenMap for O(1) subtask lookup, createTask accepts parentId
- TaskItem: depth-based indentation, subtask completion counter badge
- TasksScreen: expand/collapse chevron for parent tasks, indented subtask
  rendering with left border, subtasks shown under completed parents
- TaskDetailView: "Add subtask" inline input, subtask list with toggleable
  checkboxes, parent task breadcrumb for subtasks

https://claude.ai/code/session_01XWcSekaCAJ7qnr4hN3tgL8
2026-04-02 12:14:56 +00:00
Tristan Michael 326ebd83d8 Gate desktop-only deps for Tauri Android compilation
Make keyring optional behind keyring-storage feature in onyx-core.
Make notify/notify-debouncer-mini optional behind desktop feature in Tauri.
Gate all file watcher code behind #[cfg(not(target_os = "android"))].
Provide env-var-only credential fallbacks when keyring is disabled.
2026-04-01 17:35:57 -07:00
Tristan Michael 70d519de64 feat: sync status indicators and push/pull mode selection
Add workspace_name and mode params to sync_workspace Tauri command.
After each sync, persist last_sync to WorkspaceConfig and save config.
Frontend gains lastSyncResult and syncMode state; triggerSync reloads
config post-sync and stores the result. SettingsScreen shows a sync
direction selector (Full/Push/Pull) and a last-sync status line.
TasksScreen shows an upload/download count chip after sync completes.
2026-04-01 01:06:10 -07:00
Tristan Michael 5faf285d28 feat(tauri): add move, rename, grouping commands and file watcher
Add Tauri commands: move_task, rename_list, set/get_group_by_due_date,
watch_workspace. Implement file watcher using notify crate with 500ms
debounce and self-change suppression via mute_watcher().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 13:27:56 -07:00
Tristan Michael c536250072 rename Tauri app from bevy-tasks to onyx 2026-03-31 09:47:07 -07:00
Tristan Michael c138a8bcf6 fix(gui): wire sync credentials, fix memory leak, race condition, add delete confirmations
- Add load_credentials Tauri command and use it in triggerSync() instead
  of passing empty username/password strings
- Replace raw __TAURI_INTERNALS__.invoke() with proper invoke import in
  SettingsScreen
- Wrap window event listeners in $effect() with cleanup to prevent
  memory leak on component remount
- Return created Task from createTask() and use it directly in
  NewTaskInput instead of guessing from array index
- Add confirm() dialogs before deleting tasks, lists, and workspaces
2026-03-30 16:24:46 -07:00
Tristan Michael ea76f65579 Frameless transparent window with custom title bar
Remove native window decorations and add transparent background with
rounded corners, border, and drop shadow. Add custom close button
(Linux) and minimize/maximize/close (Windows) in the header. Add
programmatic window dragging via mousedown and double-click to maximize.
Install tauri-plugin-os for platform detection. Move sync spinner to
bottom-right corner. Convert drawer layout from vw to cqi units to
support the padding-based shadow approach.
2026-03-30 07:54:17 -07:00
Tristan Michael f810cfa2a3 Commit to Tauri GUI: remove Flutter/egui, redesign task UI
- Remove Flutter app and egui placeholder crate, commit to Tauri as sole GUI
- Update PLAN.md to replace egui with Tauri across all phases (v4.0)
- Redesign task screen: sliding drawer for list picker, floating FAB for new tasks,
  bottom sheet toast for task creation with title + description fields
- Add description support to create_task Tauri command
- Lighten dark theme to GNOME-style neutral grays, shift primary to cyan-blue
- Fix Wayland compatibility (dev port change)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 09:49:30 -07:00
Tristan Michael ec17d6274f tauri and flutter guis 2026-03-17 09:38:53 -07:00