Markdown import/export + opt-in note encryption#103
Open
atayozcan wants to merge 7 commits intocosmic-utils:mainfrom
Open
Markdown import/export + opt-in note encryption#103atayozcan wants to merge 7 commits intocosmic-utils:mainfrom
atayozcan wants to merge 7 commits intocosmic-utils:mainfrom
Conversation
Adds a Sync section to settings with server URL, username, password, Test Connection and Sync Now actions. New sync module implements a minimal CalDAV client (PROPFIND principal/home, REPORT VTODOs, PUT) and a sync engine that pulls remote VTODO calendars as lists and syncs tasks both ways using last-modified. Tested against the build; runtime sync needs a CalDAV server (e.g. Stalwart) to validate end-to-end.
Stalwart (and others) return <calendar-data> as CDATA so the iCal payload survives XML escaping. quick-xml fires that as Event::CData, which the multistatus parser ignored, causing fetch_todos to return zero items and sync to appear no-op. Listen for CData and accumulate text chunks across events.
- LocalStorage::update_task now bumps last_modified_date_time so the sync engine sees local edits as newer than remote. - Add LocalStorage::replace_task that preserves LMD; sync engine uses it on pull to avoid ping-pong. - Pages emit Output::Mutated on save-class events (add/complete/ delete/title submit/expand/sub-task ops; details: title/favorite/ priority/due-date). Keystroke-grade writes (TitleUpdate, Editor) are intentionally skipped to avoid spam — periodic sync picks them up. - App handles Mutated by dispatching SyncNow if configured and not already syncing. - Add 60s subscription emitting SyncTick to drive periodic sync.
Three fixes for CalDAV sync. - parse_ical_datetime: chrono's DateTime::parse_from_str rejects a literal 'Z' as a timezone specifier, so VTODO timestamps like 20260405T170617Z always failed to parse and fell back to Utc::now(). This made every local task's last_modified equal to the most recent pull time, which then equaled (or trailed) the remote's reparsed "now" in the push comparison, so push never fired. Strip trailing Z and parse via NaiveDateTime in UTC. Tests cover Zulu / floating / date-only forms. - Nav model duplicates after sync: PopulateLists appended without clearing the segmented_button model, so each sync re-added every list. Clear the model first, then restore the previously active list by id. - Password storage: move CalDAV password from cosmic-config (plaintext on disk) to libsecret via the keyring crate. Existing config-stored passwords migrate into the keyring on first launch and are cleared from the config file. Username/server URL stay in cosmic-config. - Surface PUT failures in the Sync status line (added `failed` count) and log response bodies on non-2xx PUTs.
Account / keyring
- Move CalDAV password to the system keyring (Secret Service /
cosmic-keyring); drop the legacy plaintext-password migration and
the now-unused sync_password field on TasksConfig.
- Replace the description-embedded "caldav:URL" marker with a proper
List::remote_url field; legacy lists migrate on first sync.
- Account settings panel gains a status row, helper text under each
input, last-synced relative timestamp, and a destructive Sign-out
button that wipes config and keyring entry.
Sync triggers
- Sync icon in the header bar (configured-only, disables while
running).
- "Sync now" entries in the View menu and per-list right-click menu.
UI polish
- Due-date badge on every task row ("Today" / "Tomorrow" /
"Yesterday" / weekday / YYYY-MM-DD), localized.
- Sort by due date (Earliest/Latest); completed tasks always sink to
the bottom regardless of sort.
Bug fixes
- Pulled VTODOs now appear immediately in the active list. SetList
short-circuited when the list id was unchanged, so post-sync the
view stayed stale until the user reselected. New
Message::ReloadTasks is dispatched after every successful sync.
- Date dialog Complete handler called details::update directly and
dropped RefreshTask/Mutated; routed through Message::Details(..) so
the in-memory task and sidebar refresh and a sync is triggered.
- "invalid SecondaryMap key used" panic: ReloadTasks rebuilds the
slotmap, so message handlers could arrive with stale DefaultKeys.
All hot-path SecondaryMap accesses now use .get() with bail-out.
- Date picker stored UTC midnight, which shifted to the previous day
for any UTC-negative offset. Stores local-midnight (as UTC) and
emits VALUE=DATE for all-day RFC encoding so other clients show the
same calendar day.
- Rename / Set-Icon dialogs now correctly target the entity passed in
from the nav context menu instead of the active list.
- CalDAV calendar URLs without a trailing slash had Url::join()
silently replace the last segment; trailing slashes are now
enforced at discovery.
- Removed unsafe impl Send for List (PathBuf is already Send).
- Dropped the dead sqlx dependency and Error::Sqlx variant.
iCalendar interop
- Use icalendar::Todo::get_due() so all RFC 5545 forms (DATE,
DATE-TIME UTC / floating / TZID) are accepted; textual fallback
parser also accepts ISO-8601 extended forms (with separators and
with offset).
- Always emit DTSTAMP, some servers refuse VTODOs without it.
- Use Todo::completed() for COMPLETED so it's a proper UTC date-time.
Release housekeeping
- 0.3.0 metainfo entry; flatpak finish-args gain --share=network and
--talk-name=org.freedesktop.secrets; <internet> changed from
offline-only to always.
- README gets a CalDAV section; new CHANGELOG.md
(Keep-a-Changelog).
- Reorganized .gitignore.
- About dialog reads version from CARGO_PKG_VERSION.
Tests
- 12 unit tests cover legacy-marker parsing, remote_url precedence,
marker stripping, ISO-8601 (UTC, offset, extended), garbage
rejection, and the all-day VALUE=DATE round-trip.
Adds a markdown-import flow and a save-to-file branch on the existing Export dialog, plus opt-in at-rest encryption of the Task::notes field using a key from the system keyring. Refs cosmic-utils#21 (markdown import), cosmic-utils#28 (clipboard-only export workaround). Markdown import (cosmic-utils#21): - File menu → Import from markdown… opens a file-path dialog. - Permissive parser: H1 → list name, H2/deeper → parent task, bullets (- / * / + / 1.) become tasks, [ ] / [x] checkboxes are honored, indentation maps to sub-tasks. ~ in paths is expanded. - 8 unit tests including a regression test built from a realistic multi-section TODO file. Markdown export (cosmic-utils#28): - Existing dialog gains a path input + "Save to file…" tertiary button alongside the original Copy button. Default destination is ~/Documents/<list>.md. Save creates parent dirs as needed. Notes-at-rest encryption: - New Settings → Privacy → "Encrypt notes at rest" toggle. - ChaCha20-Poly1305 with a 32-byte key in keyring service dev.edfloreshz.Tasks.notes; auto-generated on first use. - Encrypted payloads are wrapped with an `enc:v1:` magic prefix so reads auto-detect format. Toggling on/off requires no migration: writes follow the flag, reads always try to decrypt. - CalDAV roundtrips deliberately see plaintext (decryption happens at the storage read boundary) so the sync engine pushes the readable form, preserving interop with other CalDAV clients on the same calendar. - Storage gains Arc<AtomicBool> so all clones in the app observe the toggle without re-plumbing. Stacked on top of cosmic-utils#102 — the diff will look combined until that lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the import path-input dialog and the export save-path field with the XDG Desktop Portal file chooser via rfd's xdg-portal backend. ashpd was already a transitive dep; rfd is added directly with default-features off so we only pull in the portal path on Linux. Why: the previous path-input UX is fragile under Flatpak — the manifest only grants `--filesystem=xdg-config/cosmic:ro`, so any user-typed path into ~/ would have failed silently. The portal returns a sandbox-pierced fd per file, so no broader filesystem grant is needed. Changes: - File → Import from markdown… now opens the Open dialog directly; no intermediate DialogPage::Import is constructed. - Export dialog drops the path text_input. The "Save to file…" tertiary button opens the Save dialog with a slugified <list>.md as the pre-filled name. - Drop expand_user_path / DialogPage::Import / DialogAction::ExportSave — all dead code now. - New ApplicationAction futures: ImportFromFile / SaveExportToFile and their *Result variants. Cancel is a no-op (sentinel "cancelled" string). Errors go to tracing. Flatpak manifest is unchanged on purpose — the portal handles access.
Author
|
Pushed 985272d to address the path-input UX issue I caught after the initial push:
Tests still 23/23 green. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds markdown import, a save-to-file branch on the existing Export dialog, and opt-in at-rest encryption of
Task::notes.Note
Stacked on top of #102. The diff in this PR will look combined until that one lands. The new work itself lives in a single commit on
feat/markdown-io-and-encrypted-notes(branched offfeat/caldav-sync); rebasing onto a merged #102 will drop the CalDAV diff cleanly.Refs #21 (markdown import), #28 (clipboard-only export workaround).
Markdown import — closes #21
~/).# H1→ list name (first one wins; subsequent H1s are treated as parent tasks).## H2/ deeper → parent task whose children are the bullets that follow.-,*,+,1.,1). Checkboxes[ ]/[x]/[X]honored.Save markdown to file — refs #28
~/Documents/<list-slug>.md. Parent dirs are created on save.Opt-in note encryption
dev.edfloreshz.Tasks.notes(masteraccount). Generated on first use; never leaves the device.enc:v1:<base64(nonce|ciphertext)>. Reads auto-detect the magic prefix, so flipping the flag in either direction is non-destructive — old plaintext stays readable, old ciphertext stays readable, and writes follow the current flag.LocalStoragegained anArc<AtomicBool>so every clone in the app observes the toggle without re-plumbing.Why bundle these together
These three are the small surface area where I needed to touch the dialog/menu/storage layers anyway, and they share the import/export theme. The encryption piece is intentionally local-only and opt-in so it composes cleanly with #102's CalDAV path without changing what hits the wire.
New deps
chacha20poly1305— pure-Rust AEAD.rand— only for the 32-byte key + 12-byte nonce.Tests
23 total pass: the 8 markdown-import tests, 3 crypto round-trip tests, plus the 12 CalDAV/sync tests from #102.
Out of scope