Markdown import/export + opt-in note encryption #103

Open
atayozcan wants to merge 7 commits from atayozcan/feat/markdown-io-and-encrypted-notes into main
atayozcan commented 2026-04-29 20:43:37 +00:00 (Migrated from github.com)

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 off feat/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

  • File → Import from markdown… opens a file-path dialog (supports ~/).
  • Permissive parser:
    • # H1 → list name (first one wins; subsequent H1s are treated as parent tasks).
    • ## H2 / deeper → parent task whose children are the bullets that follow.
    • Bullets accepted: -, *, +, 1., 1). Checkboxes [ ] / [x] / [X] honored.
    • Indentation (2 spaces or 1 tab) → sub-task nesting, arbitrarily deep.
  • Imports always create a new list, so existing CalDAV-bound lists are never overwritten.
  • 8 unit tests, including one built from a realistic multi-section TODO file structure.

Save markdown to file — refs #28

  • Existing Export dialog gains a path input + Save to file… tertiary button next to the original Copy button.
  • Default suggested path: ~/Documents/<list-slug>.md. Parent dirs are created on save.
  • Doesn't fix the underlying clipboard bug from #28, but gives users a working alternative when their clipboard backend can't accept the copy.

Opt-in note encryption

  • New Settings → Privacy → Encrypt notes at rest toggle.
  • ChaCha20-Poly1305 with a 32-byte key stored in the system keyring under service dev.edfloreshz.Tasks.notes (master account). Generated on first use; never leaves the device.
  • Encrypted payloads are wrapped 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.
  • CalDAV roundtrips deliberately see plaintext. Decryption happens at the storage read boundary, so the sync engine pushes the readable form to the server, preserving interop with other clients sharing the same calendar.
  • The toggle warms the keyring entry up front so the user gets the unlock prompt at toggle time, not the first time they edit a note.
  • Implementation note: LocalStorage gained an Arc<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.

test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured

Out of scope

  • A "re-encrypt all existing notes now" one-shot — auto-detection on read makes this unnecessary day-to-day, and it would just be churn.
  • Encrypting notes on the wire for CalDAV — would break interop; that's a different feature (and would need a per-list opt-in on top of this).
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 off `feat/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 - **File → Import from markdown…** opens a file-path dialog (supports `~/`). - Permissive parser: - `# H1` → list name (first one wins; subsequent H1s are treated as parent tasks). - `## H2` / deeper → parent task whose children are the bullets that follow. - Bullets accepted: `-`, `*`, `+`, `1.`, `1)`. Checkboxes `[ ]` / `[x]` / `[X]` honored. - Indentation (2 spaces or 1 tab) → sub-task nesting, arbitrarily deep. - Imports always create a *new* list, so existing CalDAV-bound lists are never overwritten. - 8 unit tests, including one built from a realistic multi-section TODO file structure. ## Save markdown to file — refs #28 - Existing Export dialog gains a path input + **Save to file…** tertiary button next to the original **Copy** button. - Default suggested path: `~/Documents/<list-slug>.md`. Parent dirs are created on save. - Doesn't fix the underlying clipboard bug from #28, but gives users a working alternative when their clipboard backend can't accept the copy. ## Opt-in note encryption - New **Settings → Privacy → Encrypt notes at rest** toggle. - ChaCha20-Poly1305 with a 32-byte key stored in the system keyring under service `dev.edfloreshz.Tasks.notes` (`master` account). Generated on first use; never leaves the device. - Encrypted payloads are wrapped `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. - **CalDAV roundtrips deliberately see plaintext.** Decryption happens at the storage read boundary, so the sync engine pushes the readable form to the server, preserving interop with other clients sharing the same calendar. - The toggle warms the keyring entry up front so the user gets the unlock prompt at toggle time, not the first time they edit a note. - Implementation note: `LocalStorage` gained an `Arc<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. ```text test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured ``` ## Out of scope - A "re-encrypt all existing notes now" one-shot — auto-detection on read makes this unnecessary day-to-day, and it would just be churn. - Encrypting notes *on the wire* for CalDAV — would break interop; that's a different feature (and would need a per-list opt-in on top of this).
atayozcan commented 2026-04-29 20:51:05 +00:00 (Migrated from github.com)

Pushed 985272d to address the path-input UX issue I caught after the initial push:

  • The path field would have failed silently in Flatpak — the manifest only grants `--filesystem=xdg-config/cosmic:ro`, so any user-typed path under `~/` would have hit a permission error.
  • Switched both dialogs to the XDG Desktop Portal file chooser via `rfd` with the `xdg-portal` backend. `ashpd` was already a transitive dep; `rfd` is the one new direct dep, default-features off so only the portal path is pulled in.
  • The portal hands back a sandbox-pierced fd per file, so the Flatpak manifest stays untouched (no broader `--filesystem=home` grant needed).
  • Import dialog dropped entirely — the picker is the dialog. Export dialog keeps its preview but the path-input is replaced by a "Save to file…" tertiary button that opens the portal Save dialog with a slugified `.md` pre-filled.

Tests still 23/23 green.

Pushed 985272d to address the path-input UX issue I caught after the initial push: - The path field would have failed silently in Flatpak — the manifest only grants \`--filesystem=xdg-config/cosmic:ro\`, so any user-typed path under \`~/\` would have hit a permission error. - Switched both dialogs to the **XDG Desktop Portal file chooser** via \`rfd\` with the \`xdg-portal\` backend. \`ashpd\` was already a transitive dep; \`rfd\` is the one new direct dep, default-features off so only the portal path is pulled in. - The portal hands back a sandbox-pierced fd per file, so the Flatpak manifest stays untouched (no broader \`--filesystem=home\` grant needed). - Import dialog dropped entirely — the picker is the dialog. Export dialog keeps its preview but the path-input is replaced by a "Save to file…" tertiary button that opens the portal Save dialog with a slugified \`<list>.md\` pre-filled. Tests still 23/23 green.
edfloreshz commented 2026-05-01 20:21:01 +00:00 (Migrated from github.com)

Could you rebase this branch? There are conflicts.

Could you rebase this branch? There are conflicts.
atayozcan commented 2026-05-02 01:50:25 +00:00 (Migrated from github.com)

Started the rebase and hit a wall — the upstream feat: new store system commit isn't a content change, it's a structural rewrite. The files this PR adds to (src/app/markdown.rs, the src/storage/ module including the new notes_crypto.rs) have been deleted or replaced — src/app/markdown.rssrc/app/ui/markdown.rs, and src/storage/src/services/store.rs with a different API. Layered on top of the CalDAV branch in #102, which has its own structural conflicts, a git rebase would mostly produce markers in files that no longer exist.

Right move is probably to re-apply this PR as a port on top of the new architecture rather than force-push a rebase. Happy to do that, but wanted to check first:

  1. Same question as on #102 — fresh PR on the new arch (closing this one), or rebase here?
  2. Should the markdown import/export flow extend the new src/app/ui/markdown.rs, or live as a separate module (e.g. src/services/markdown_io.rs)?
  3. Note encryption touches the store; is the new services::store the right seam to add an encrypted-note flag, or would you prefer a wrapper layer?
Started the rebase and hit a wall — the upstream `feat: new store system` commit isn't a content change, it's a structural rewrite. The files this PR adds to (`src/app/markdown.rs`, the `src/storage/` module including the new `notes_crypto.rs`) have been deleted or replaced — `src/app/markdown.rs` → `src/app/ui/markdown.rs`, and `src/storage/` → `src/services/store.rs` with a different API. Layered on top of the CalDAV branch in #102, which has its own structural conflicts, a `git rebase` would mostly produce markers in files that no longer exist. Right move is probably to re-apply this PR as a port on top of the new architecture rather than force-push a rebase. Happy to do that, but wanted to check first: 1. Same question as on #102 — fresh PR on the new arch (closing this one), or rebase here? 2. Should the markdown import/export flow extend the new `src/app/ui/markdown.rs`, or live as a separate module (e.g. `src/services/markdown_io.rs`)? 3. Note encryption touches the store; is the new `services::store` the right seam to add an encrypted-note flag, or would you prefer a wrapper layer?
edfloreshz commented 2026-05-03 20:02:08 +00:00 (Migrated from github.com)

I'd appreciate it if you could respond directly, I understand you're using AI for this PR and I'm happy to discuss the changes, but I'd prefer to hear from a human.

  1. You can create a new PR if you need to and close this one.
  2. Extend src/app/ui/markdown.rs.
  3. Why are we encrypting notes?
I'd appreciate it if you could respond directly, I understand you're using AI for this PR and I'm happy to discuss the changes, but I'd prefer to hear from a human. 1. You can create a new PR if you need to and close this one. 2. Extend `src/app/ui/markdown.rs`. 3. Why are we encrypting notes?
This pull request has changes conflicting with the target branch.
  • Cargo.lock
  • Cargo.toml
  • dev.edfloreshz.Tasks.json
  • i18n/en/tasks.ftl
  • res/dev.edfloreshz.Tasks.metainfo.xml
  • src/app.rs
  • src/app/actions.rs
  • src/app/dialog.rs
  • src/app/error.rs
  • src/app/markdown.rs
  • src/app/menu.rs
  • src/config.rs
  • src/core/localize.rs
  • src/core/settings/app.rs
  • src/core/settings/error.rs
  • src/core/style/segmented_control.rs
  • src/main.rs
  • src/pages/content.rs
  • src/pages/details.rs
  • src/storage/migration.rs
  • src/storage/mod.rs
  • src/storage/models/list.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin atayozcan/feat/markdown-io-and-encrypted-notes:atayozcan/feat/markdown-io-and-encrypted-notes
git switch atayozcan/feat/markdown-io-and-encrypted-notes

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff atayozcan/feat/markdown-io-and-encrypted-notes
git switch atayozcan/feat/markdown-io-and-encrypted-notes
git rebase main
git switch main
git merge --ff-only atayozcan/feat/markdown-io-and-encrypted-notes
git switch atayozcan/feat/markdown-io-and-encrypted-notes
git rebase main
git switch main
git merge --no-ff atayozcan/feat/markdown-io-and-encrypted-notes
git switch main
git merge --squash atayozcan/feat/markdown-io-and-encrypted-notes
git switch main
git merge --ff-only atayozcan/feat/markdown-io-and-encrypted-notes
git switch main
git merge atayozcan/feat/markdown-io-and-encrypted-notes
git push origin main
Sign in to join this conversation.
No description provided.