Commit graph

279 commits

Author SHA1 Message Date
Claude 70fe7420cd
refactor(sync): remove dead .listdata.json guard in conflict path
The `.listdata.json` check was unreachable: the branch is already
gated on `parts[1].ends_with(".md")`, which `.listdata.json` fails.
2026-04-20 07:33:12 +00:00
SteelDynamite 6ae1006ab4
Merge pull request #56 from SteelDynamite/claude/serene-ride-XUY3D 2026-04-19 09:12:44 +01:00
SteelDynamite d8c6b9fc8e
Merge pull request #53 from SteelDynamite/claude/dreamy-brown-pFY5T 2026-04-19 09:12:08 +01:00
Claude 9a8a1a9f8e
style(sync): replace stray var with const in restartSyncInterval
Lone var in an otherwise let/const file — promote to const since the
value never gets reassigned. No behavior change.
2026-04-19 07:13:47 +00:00
Claude c952156491
refactor(date-picker): group selected-state declarations up top
selectedYear/selectedMonth were declared below selectDay, which writes
to them, and below isToday, which is declared nearby. Runtime worked
because the assignments only run on user click (after script init), but
the split made the initialization order confusing. Group all $state
fields at the top of the script.
2026-04-19 07:13:29 +00:00
Claude 62cf05480d
refactor(tauri): extract join_remote_path helper
Three call sites repeated the same "empty base -> child, otherwise
trim_end + slash + child" pattern. Pull it into a helper to keep the
join convention consistent across list_remote_folder, inspect, and
create_remote_workspace.
2026-04-19 07:12:37 +00:00
Claude e911ac1d94
refactor(tauri): extract credential_domain helper
Three call sites reproduced the same scheme://host parsing inline. Pull
it into a named helper so the domain-extraction convention lives in one
place.
2026-04-19 07:11:53 +00:00
Claude 937b6c2c7d
refactor(storage): read dedup mtimes once instead of in sort closure
sort_by may call the comparator many times, so the previous tiebreaker
re-read each duplicate file's metadata on every comparison. With N
duplicates that's O(N log N) stat calls, and the ordering could flip
mid-sort if a file was touched concurrently. Snapshot mtime per file up
front and sort on the cached values.
2026-04-19 07:09:49 +00:00
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 b977d275ba
docs: sync markdown docs with current codebase state
- CLAUDE.md: add `sync` to the CLI commands list (commands/sync.rs exists)
- PLAN.md: remove BottomSheet.svelte (deleted in efb4cca)
- DEVELOPMENT.md: add grouping.ts and paths.ts to the lib directory listing

https://claude.ai/code/session_01YbcpJqmwpEW5tCJFFkMSPZ
2026-04-18 08:53:10 +00:00
SteelDynamite 065118789f
Merge pull request #52 from SteelDynamite/claude/smoke-test-and-fixes-TwfSh 2026-04-18 09:49:21 +01:00
Claude a79dcc4617
test: cover CLI workspace resolver, date picker, saturating version
Add regression tests for the bugs found in this smoke test:

- resolve_workspace: by-name, by-UUID, unknown-identifier, current-fallback,
  actionable no-workspace message.
- DateTimePicker: selected-day highlight must be month-scoped; committing
  after navigating months uses the selected month, not the viewed one.
- create_task: version is saturating_add on u64::MAX (doesn't panic/wrap).

Also fixes the three pre-existing clippy warnings (WorkspaceMode now uses
#[derive(Default)] + #[default], repository test drops unused binding,
sync test uses struct-update syntax instead of field-reassign-default).
2026-04-17 16:32:22 +00:00
Claude efb4ccaaef
chore(cleanup): remove unused BottomSheet component and dead testConnection
BottomSheet.svelte is not imported anywhere — NewTaskInput hand-rolls
its own sheet. SetupScreen had a standalone testConnection() function
that was only ever reachable through connectAndBrowse which calls
test_webdav_connection directly; the standalone variant had no
callers.
2026-04-17 16:29:04 +00:00
Claude f6c8dfc951
fix(cli): create task-edit scratch file with mode 0600 on unix
onyx task edit wrote the task body to /tmp/onyx-<uuid>.md with the
default umask, leaving it world-readable on shared multi-user systems
for the duration of the editor session. Open with O_CREAT|O_TRUNC +
mode 0600 via OpenOptionsExt on unix; Windows keeps the existing
behaviour since unix-style mode bits don't apply.
2026-04-17 16:28:20 +00:00
Claude 3acc4c3f5d
fix(empty-state): replace misleading hint with an actual create button
The no-lists empty state said 'Tap the list name above to create one' —
but there is no list name above, just a static 'Tasks' label. The
actual affordance (+ New list) lives in the drawer, which may not be
open. Add a primary-button shortcut that opens the drawer and puts
focus in the new-list input in one click. Google Tasks workspaces are
read-only so they still get the explanatory text instead.
2026-04-17 16:27:18 +00:00
Claude 391c42aa18
fix(rename): imperatively focus + select rename inputs
Svelte's native autofocus attribute is unreliable for inputs rendered
via conditional blocks (prior smoke-test fixed this for the new-list
input). Apply the same bind:this + $effect pattern to the list-rename
input (TasksScreen) and the workspace-rename input (SettingsScreen),
and select() the existing text so typing replaces the old name
cleanly.
2026-04-17 16:26:29 +00:00
Claude 6283f9ab2c
fix(store): guard fs-changed listener against setup/missing screens
The module-scope fs-changed listener fired unconditionally, calling
loadLists even when the user was on the setup or missing-workspace
screens (where no current workspace exists). The invoke would fail
silently and a WebDAV debounced sync could kick off against an
incomplete state. Bail when there's no active workspace or the tasks
screen isn't mounted.
2026-04-17 16:25:39 +00:00
Claude 5869c305aa
fix(bulk-delete): snapshot targets and bail on first failure
executeDeleteCompleted and executeDeleteCompletedSubtasks iterated over
the reactive completedTasks/completedSubtasks lists with no error
handling: the array shrinks with every successful delete, skipping
subsequent entries, and a failed delete silently left a half-deleted
state. Snapshot the target list up front and abort as soon as a delete
returns false — matching the subtask-cascade path.
2026-04-17 16:25:03 +00:00
Claude d213e523ec
fix(sync): narrow transient-error detection so real errors aren't hidden
The connectivity-vs-real-error classifier tested the message against
/timeout|connect|network|unreachable|refused/i, matching any error
whose text happened to include one of those words. A server-side
permission error like 'network share access refused' was silently
classified as transient, updating only the status dot — the user
never saw the actual problem.

Tighten the regex to well-known connectivity phrases and lowercase
error codes (ENOTFOUND/ECONNREFUSED/etc), using word boundaries so
substrings in unrelated messages don't match.
2026-04-17 16:24:20 +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 d01bd9d280
fix(settings): stop clobbering WebDAV edits and save without a successful test
Two coupled issues in workspace settings:

1. The credentials-loading effect re-ran whenever ws.webdav_url changed,
   so any config mutation (e.g. changing sync interval) would trigger a
   re-load of the stored username/password, overwriting whatever the
   user was typing into those fields. Gate with a one-shot credsLoaded
   flag.

2. Save would persist whatever was in the URL input even if the user
   had never tested it — a typo'd host silently pointed the workspace
   at a dead server. Now saveWebdav auto-runs the connection test and
   bails if it fails; any edit to the three inputs clears the "ok"
   status via markDirty() so the next Save is forced to re-verify.

Also replaces the ASCII "Failed -- Retry" with an em dash.
2026-04-17 16:22:31 +00:00
Claude b437b0b7b2
fix(sync): use atomic_write for all payload file writes during sync
Sync's conflict-resolution and download paths wrote the local file with
plain fs::write. A crash or I/O error mid-write left a truncated .md
or .listdata.json that would then fail YAML/JSON parsing on the next
list_tasks. All other callers in this crate use atomic_write; route
the four sync call sites through it for consistency and crash safety.
2026-04-17 16:21:24 +00:00
Claude c134624839
fix(repository): saturating_add for in-memory version bump
create_task used a plain += on the in-memory version returned to the
caller while FileSystemStorage uses saturating_add when serialising
the frontmatter. The two would disagree at u64::MAX, and in debug
builds the + operator would panic on overflow. Match the storage
behaviour.
2026-04-17 16:20:11 +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 604a6058b8
fix(storage): atomic task-file writes
write_task used plain fs::write for the .md payload even though every
other write path in this module (metadata files, sync state, offline
queue, config) uses atomic_write. A crash mid-write left a truncated
.md file whose malformed YAML frontmatter then failed list_tasks for
the entire list. Route through atomic_write so a failed write either
leaves the old file intact or produces the full new file.
2026-04-17 16:18:09 +00:00
Claude a0e2bb214b
fix(date-picker): don't mark the same day in every month as selected
The day cell class used `selectedDay === day`, ignoring the currently
viewed month/year. After picking e.g. April 15, flipping to May still
painted May 15 as the selected day; committing with Done would shift
the task's date to whatever month the user happened to be viewing.

Track selectedYear/selectedMonth alongside selectedDay, update them
only on actual day click, and construct the committed ISO from the
selection (not the view). The pre-existing isSelected() helper is now
wired into the cell template.
2026-04-17 16:17:36 +00:00
Claude 8a81f05492
fix(cli): print clean error chain instead of anyhow Debug with backtrace
When RUST_BACKTRACE was set in the environment, every user-facing error
dumped a 20-line Rust backtrace at the user — e.g. running 'onyx list
show' with no workspace gave them a stack trace through anyhow, clap,
and libc start. Replace 'fn main() -> Result' with an explicit error
printer that walks the anyhow cause chain using Display, and exits 1.
Programming-bug panics still surface through the default panic handler.
2026-04-17 16:16:52 +00:00
Claude 433a950418
fix(cli): accept workspace name or UUID, auto-select on first add
Three related CLI bugs found during smoke testing:

1. `get_repository` used `config.get_workspace(name)` which expects the
   UUID string, so `onyx list create -w dev` or `onyx task add -w dev`
   always failed with "Workspace 'dev' not found". Unified CLI resolution
   into a single `resolve_workspace()` helper that accepts either the
   display name or the UUID; removed sync.rs's duplicated local copy.

2. `workspace switch`/`remove`/`retarget`/`migrate` only accepted the
   display name — the error message even suggested "Use the workspace ID
   instead" on ambiguous names, but IDs were then rejected. Updated
   `resolve_name` to try the map key first.

3. `onyx workspace add` never set `current_workspace`, so the very next
   command failed with "No workspace set. Use 'onyx init'..." even
   though a workspace was just created. Now sets the new workspace as
   current whenever none was previously selected, and reports the fact.
   Updated the error message to point at the correct `workspace add` /
   `workspace switch` commands instead of `init`.
2026-04-17 16:15:45 +00:00
Claude 855fa46a0e
refactor: simplify forgetMissingWorkspace now that removeWorkspace handles switch
removeWorkspace already switches to the next available workspace (or falls
back to setup). forgetMissingWorkspace can just delegate, dropping the
duplicate branch that previously never ran anyway because current_workspace
was always null after removal.
2026-04-17 16:13:46 +00:00
Claude cdef59fab4
fix: keep user on tasks screen when removing current workspace
When a user deletes the current workspace from settings, the backend
clears current_workspace and the frontend's hasWorkspace derived fell
through to the setup screen — even if the user still had other healthy
workspaces configured. Mirror the forgetMissingWorkspace flow: switch
to the next available workspace automatically.
2026-04-17 16:12:49 +00:00
SteelDynamite 92475483de
Merge pull request #51 from SteelDynamite/claude/dreamy-brown-YlW25 2026-04-17 16:36:33 +01:00
Claude 771e104486
Merge remote-tracking branch 'origin/main' into pr51-merge
# Conflicts:
#	README.md
2026-04-17 15:01:58 +00:00
Claude 7bef6b07bc
docs: sync markdown docs with actual codebase state
- README.md: update Phase 4 status to reflect Android preliminaries done
  (file-watcher gating, tauri-plugin-credentials, safe area insets, Android
  targets configured) but init/build not yet run; add tauri-plugin-credentials
  to project structure; expand docs/ tree; add newer GUI features (workspace
  rename, safe area insets, accessibility); add setup screen screenshot;
  update What's Next to note Phase 4 is in progress
- PLAN.md: fix Phase 4 checkboxes — android init and build-succeeds were
  marked [x] but gen/android/ does not exist; correct cfg gate annotation
  from #[cfg(not(mobile))] to #[cfg(not(target_os = "android"))]; update
  dependency snippet to reflect actual keyring/zeroize/sha2/quick-xml usage;
  bump Last Updated to 2026-04-17
- docs/DEVELOPMENT.md: add WEBKIT_DISABLE_DMABUF_RENDERER=1 Wayland note
  to tauri dev command

https://claude.ai/code/session_01MypN7wPNqeSgw8b5DYpMc1
2026-04-17 14:44:33 +00:00
SteelDynamite 0c2a218260
Merge pull request #50 from SteelDynamite/claude/run-app-screenshot-Z02aY 2026-04-17 15:39:16 +01:00
SteelDynamite 95b89b78e6
Merge pull request #49 from SteelDynamite/claude/dreamy-brown-d12z7 2026-04-17 15:36:21 +01:00
Claude 212e3d43d5
Merge main into claude/dreamy-brown-d12z7
Resolve conflicts against latest main:
- PLAN.md: keep main's updated Settings/theme list (window decorations,
  Black and Gold) while adopting PR's "Move to..." inline phrasing.
- README.md: keep main's theme list including Black and Gold.
- docs/API.md: keep main's atomic move_task documentation.

https://claude.ai/code/session_01NCtJ5PNhaDh21kYnDZXYsN
2026-04-17 14:35:39 +00:00
Claude 67ac43e527
Add Vitest suite covering the smoke-test fixes
Extracts two pure helpers out of Svelte components so they can be
exercised without the reactive runtime, and adds component tests for
ConfirmDialog's Escape-handling behavior.

- apps/tauri/src/lib/grouping.ts (new): `groupTasksByDate` lifted out of
  the `groupedPendingTasks` $derived in the app store.
- apps/tauri/src/lib/paths.ts (new): `workspaceNameFromPath` lifted out
  of SetupScreen.handleOpen.
- apps/tauri/src/lib/grouping.test.ts: 8 cases — "No Date" placed last
  (regression), full bucket ordering, empty input, within-bucket
  stable sort, earlier-today stays in Today, multi-task same-day,
  No Date preserves insertion order.
- apps/tauri/src/lib/paths.test.ts: 8 cases — POSIX/Windows/mixed
  separators, trailing slash regression ("…/Tasks/" → "Tasks"), empty
  and root-only fallback, names with spaces.
- apps/tauri/src/lib/components/ConfirmDialog.test.ts: 6 cases —
  renders message/detail/custom confirm text, Cancel/Confirm fire the
  right callbacks, Escape calls oncancel and does NOT reach an outer
  window listener (regression), non-Escape keys are ignored, and the
  module-level open-count increments/decrements correctly (including
  when two dialogs are mounted at once).

Test harness: Vitest + jsdom + @testing-library/svelte. `npm test`
runs the suite; `resolve.conditions` is set to "browser" under VITEST
so Svelte resolves its client entry and mount() works.

23/23 tests pass. cargo check, cargo test -p onyx-core (162/162),
and npm run build all still green.
2026-04-17 14:33:12 +00:00
Claude 8a04895270
Fix nine GUI bugs found during local-workspace smoke test
- crates/onyx-core/src/webdav.rs: rename `getpassword`/`setpassword`
  (7 call sites) to `get_password`/`set_password` so `cargo build`
  and the CLI compile again under the default `keyring-storage` feature.
- ConfirmDialog.svelte: intercept Escape at window capture phase and
  expose a module-level open-count so TasksScreen's Escape handler can
  defer; previously Escape on a dialog both dismissed the dialog AND
  popped the task-detail view behind it. Cancel is also focused on
  mount for keyboard users.
- TasksScreen.svelte: extend the taskStack cleanup effect to collapse
  back to parent detail when only the subtask is gone (was leaving a
  blank third panel); focus the new-list input when it appears; reset
  the Completed section's expand state when switching lists.
- TaskDetailView.svelte: re-sync local title/description state when
  the task prop's content changes (unless the user is editing), so a
  sync pull doesn't get silently overwritten on next save. Bail out of
  the parent delete if a subtask delete fails instead of orphaning.
- app.svelte.ts: deleteTask now returns a success boolean; move the
  "No Date" group to the end of the grouped-by-date view so Overdue
  and Today surface first.
- SetupScreen.svelte: strip trailing separators before splitting the
  picked folder path so "…/MyTasks/" yields "MyTasks" instead of the
  literal fallback "workspace".

Verified live under Xvfb for the three user-visible cases (ConfirmDialog
Escape, orphan subtask collapse, new-list autofocus). Screenshots in
screenshots/smoke-test/. cargo test --lib -p onyx-core is green
(162/162); npm run build succeeds.
2026-04-17 14:24:59 +00:00
Claude 3b65dc4216
Add smoke-test screenshots demonstrating GUI bugs
Screenshots captured from a seeded local workspace loaded under Xvfb.
Includes working flows (task list, drawer, detail view, group by date)
and four bug demonstrations: Escape on ConfirmDialog pops navigation,
subtask panel orphaned after external delete, new-list input lacks
autofocus.
2026-04-17 13:57:02 +00:00
Claude 9f40061b07
Add screenshot of Tauri app setup screen
Captured by running the Tauri GUI under Xvfb (1024x768x24) with
WEBKIT_DISABLE_DMABUF_RENDERER=1 and WEBKIT_DISABLE_COMPOSITING_MODE=1,
then using ImageMagick `import` against the Onyx X window id.
2026-04-17 12:06:35 +00:00
Claude 4cc15a96fe
docs: sync markdown documentation with codebase
- 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
2026-04-16 08:34:32 +00:00
SteelDynamite aceeac0442
Merge pull request #48 from SteelDynamite/claude/jolly-mendel-Hwl4L 2026-04-16 09:29:57 +01:00
SteelDynamite 707e1ac2e2
Merge pull request #47 from SteelDynamite/claude/dreamy-brown-AVqxJ 2026-04-16 09:28:17 +01:00
Claude 85400b68bc
refactor: extract find_task helper to deduplicate CLI task search
The complete(), delete(), and edit() functions each had an identical
loop searching for a task by ID across all lists. Extract a shared
find_task() helper that returns the list ID and task.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:27:16 +00:00
Claude 1ad6fddda6
refactor: deduplicate CLI group enable/disable into single function
The enable() and disable() functions were identical except for the
boolean parameter and output message. Extract shared set_grouping()
helper.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:26:30 +00:00
Claude a313a6e270
refactor: extract shared date formatting utilities in frontend
Three components had duplicate date formatting functions. Extract
formatDateChip (for detail/input views with optional time) and
formatDateLabel (for compact list items) to a shared dateFormat module.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:26:06 +00:00
Claude e470e79e78
refactor: deduplicate filename sanitization logic
storage.rs and google_tasks.rs had near-identical sanitize_filename
implementations. Extract the shared logic to a crate-level function
so both modules reuse it. The google_tasks version also gains Windows
reserved device name handling it previously lacked.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:23:49 +00:00
Claude 5c04e50956
refactor: remove misleading underscore prefixes from WebDavClient fields
The fields _client, _base_url, _username, _password were all actively
used throughout the struct's methods. The underscore prefix convention
signals unused fields, which was misleading for readers.

https://claude.ai/code/session_013ooJht2HrZUTXgNJFU79cV
2026-04-16 07:22:45 +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