Sync, files, and backup

Sync is never required. Everything works with Wi-Fi off. When you want history, backup, or a second machine, keep ~/devnote as plain Markdown and share it through a private git repository.

Applies to v0.3+Desktop app onlyManual sync — no background uploads

Setup in 3 steps

1
Create an empty private repository, for example git@github.com:you/devnote.git. Do not add a README, license, or other initial commit; an empty remote makes the first sync clean.
2
In devnote, open Preferences → Sync. If ~/devnote does not exist yet, click Export now first. Paste the remote URL, then press Enter or move focus away from the field. Invalid URLs show Not a git remote.
3
Click Sync now. devnote exports notes, creates a commit, pulls, resolves conflicts if needed, pushes, then imports the result.
Accepted remote formats: HTTPS (https://host/user/repo.git), SSH (ssh://git@host/user/repo.git), and SSH shorthand (git@host:user/repo.git). Local filesystem paths are rejected: sync is meant for a private hosted repository.

Authentication

devnote shells out to the git already installed on your machine. It does not store SSH keys, passwords, or tokens itself; it uses your existing SSH agent and git credential helper.

SSH

ssh -T git@github.com
# success looks like: Hi you! You've successfully authenticated…

Use SSH when possible. Keep the private key in ~/.ssh, protect it with a passphrase, and back up ~/.ssh separately from notes.

HTTPS

Use HTTPS with a credential helper so git can remember the login without devnote ever seeing it:

# macOS
git config --global credential.helper osxkeychain

# Windows
git config --global credential.helper manager

# Linux (example; needs libsecret)
git config --global credential.helper libsecret
Keep the sync repository private. Notes may contain personal plans, credentials, or client work. Also back up SSH keys and recovery codes: losing access to both a device and its keys can lock you out of the remote.

How Sync now works

  1. Export: writes changed, moved, renamed, trashed, and permanently deleted notes to ~/devnote.
  2. Commit: stages everything with git add -A and commits only when something changed. The message looks like devnote: sync 2026-09-14T18:30:00.000Z (laptop).
  3. Pull: runs git pull --no-rebase --no-edit when an upstream branch exists. First-time repositories report no upstream and continue.
  4. Resolve or push: on conflict, devnote applies the per-file rule below and finishes the merge; otherwise it pushes, using push -u origin HEAD the first time.
  5. Import: reads ~/devnote back into the app, including remote changes and preserved conflict notes.
Manual means manual. There is no auto-commit on idle, pull on launch, push timer, or background upload. The watcher below imports disk changes into the app, but it never commits, pulls from the remote, or pushes.

Files on disk

Each note is one Markdown file:

~/devnote/<notebook-path>/<slug>-<id8>.md
~/devnote/.attachments/<note>-<nonce>-<name>.png

Note files carry YAML frontmatter with id, title, notebook, tags, status, pinned, trashed, createdAt, and updatedAt. Unknown notebooks import under Unsorted. Files without an id are adopted and written back with one. When the same note exists in both places, newer updatedAt wins; imports never delete notes.

Trash in the app, not in Finder. Deleting a .md file externally does not delete the note: the next export recreates the missing file. Trash and restore notes inside devnote; only Trash → Delete forever prunes the file.

Automatic disk imports

The desktop app watches ~/devnote recursively. After about 750 milliseconds without filesystem activity, plus a short app-side debounce, devnote imports changed Markdown files. A notice appears only when notes were created or updated, for example:

Mirror changed on disk: 1 new, 2 updated

The watcher skips imports while a sync is running and while a merge conflict is waiting for review. It never commits, pulls remote history, or pushes.

Attachments

Paste or drop an image into the editor on the desktop app. devnote inserts an Uploading… placeholder at the cursor, saves the file, then replaces it with a stable reference such as:

![diagram.png](.attachments/a1b2c3d4-m3xk2q-diagram.png)
Backup gap: one-click zip backup currently skips dot-directories, including ~/devnote/.attachments. Until that changes, keep image backups in git or copy .attachments separately.

Conflicts

Never silently drop text. Conflicts are resolved per file. Newer frontmatter updatedAt wins; a missing timestamp loses, and an exact tie keeps the local version.

The losing version is preserved next to the winner with a fresh note id, for example:

~/devnote/engineering/sync-engine-a1b2c3d4.md
~/devnote/engineering/sync-engine-a1b2c3d4.conflict-laptop.md

The preserved note is titled Sync engine design (conflict laptop) and appears in the app for review. The red banner offers Review loser and Resolve & sync. Device names come from DEVNOTE_DEVICE, then HOSTNAME, then the machine hostname; set DEVNOTE_DEVICE for readable conflict suffixes.

Recovery

  1. Open the preserved (conflict …) note and copy anything still needed into the winner.
  2. Trash the preserved note only when you are sure nothing remains.
  3. Click Resolve & sync to export, commit, and push the cleanup.

If a sync error leaves git mid-merge, do not hand-edit conflict markers. Run Sync now again first. As a last resort in Terminal:

cd ~/devnote
git status
git merge --abort
Restore points: revision history keeps prior note text, tombstones preserve trashed notes across machines, and zip backup plus git history provide two independent recovery paths.

Status

StateMeaningNext step
SyncedEverything is committed and pushed.None.
Uncommitted changesLocal files differ from the last commit.Click Sync now.
Unpushed commitsLocal commits are ahead of the remote.Click Sync now.
Remote updates availableThe remote is ahead of this machine.Click Sync now.
Diverged — needs attentionBoth sides have new commits.Click Sync now; review conflicts if any appear.
Conflict — review neededA merge conflict is waiting.Review the loser, then Resolve & sync.
No sync repo (Export first)~/devnote/.git does not exist yet.Click Export now, then set the remote.
Git not installedThe desktop app cannot find the git CLI.Install git and restart the app.

Backup & restore

Preferences → Sync → Export backup (.zip) creates ~/devnote-backup-YYYY-MM-DD.zip from visible mirror files. Restore from backup is desktop-only: pick a zip, confirm the destructive overwrite, and devnote writes it over ~/devnote, then imports the result. Notes already open stay on screen until the next export.

Restore overwrites the mirror. Export a fresh backup first if the current disk state might contain anything worth keeping.

iOS

There is no mobile app yet. The supported path is manual: clone the private repository in Working Copy, edit Markdown files, commit, and push. On desktop, click Sync now to pull those changes. Expect manual commits, no background sync, and possible clock skew in updatedAt comparisons. Passphrase-based git encryption remains a later roadmap item, not a built-in feature.

Errors

MessageLikely causeFix
git-not-foundNo git CLI is installed.Install git, restart devnote, then refresh sync state.
not-a-repo~/devnote/.git is missing.Click Export now before setting a remote or syncing.
Not a git remoteURL has whitespace or an unsupported/local form.Use HTTPS, ssh://, or git@host:path.
empty-remoteThe saved remote URL is blank.Paste a remote URL and press Enter.
Push failed: …Authentication, permission, upstream, or non-fast-forward push.Check SSH/HTTPS credentials, repository access, and whether the remote has commits your machine lacks.
no upstreamNormal on first sync; no tracking branch exists yet.Continue: devnote pushes with -u origin HEAD.
mirror not initializedBackup ran before ~/devnote existed.Click Export now, then back up again.
File mirror needs the DevNote desktop appMirror, sync, attachments, or zip restore was attempted in a browser.Use the desktop app for those operations.