Skip to content
juevPublic

About

Local-first to-do list for macOS and Android with a self-hosted web interface and sync over WebDAV or CalDAV

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

Lists

CI Release

A local-first to-do list with native apps for macOS and Android, a self-hosted web interface, and sync through storage you already own: a WebDAV folder, a CalDAV server or a plain directory. No account, no service to subscribe to.

Lists covers the part of 2Do that a single person uses every day. A task has a list, dates, a priority, tags, a repeat, a reminder, notes in Markdown, attachments and subtasks, which are plain lines with a title; tasks group into projects, and saved filters cut across the lists. A task is closed as done or as "won't do", and stays in view for a few seconds before it leaves. Attachments open inside the app, can be saved or shared, and work in the web interface. The apps for macOS and Android show the calendar events of the day in Today, remind with a sound you choose and keep automatic local backups. Everything works offline; edits made on two devices to different fields of the same task are both kept.

The interface is in English with a Russian localization and follows the system language.

Screenshots

The pictures show the same demo data on every platform; cargo run --example demo -- <empty folder> in core/ makes it.

macOS

The Today view on macOS: the tasks that are overdue and the tasks of the day

A list on macOS with the card of a task open: note, dates, tags, a file and subtasks

The quick-entry window on macOS with a note taken from the clipboard

Android

Today A list A task
The Today view on Android A list on Android The card of a task on Android
Quick entry Lists and views A filter Settings
Quick entry on Android: the date and the priority are read from the text The panel with the views, lists, projects and filters on Android A saved filter of the next seven days on Android The settings on Android

Web

The web interface: a list with the card of a task open

The web interface in English: the Today view

A saved filter in the web interface

Today on a phone A task on a phone
The web interface on a phone: the Today view The web interface on a phone: the card of a task

Install

Every release on the Releases page carries the macOS disk image, the Android APK and a SHA256SUMS file; the web server is published as a Docker image.

macOS

Needs macOS 14 or newer on Apple silicon.

  1. Download Lists-<version>-macos-arm64.dmg and open it.
  2. Drag Lists to Applications and start it from there.

A release whose notes say the build is not notarized is refused by macOS on first start. Remove the quarantine flag once:

xattr -dr com.apple.quarantine /Applications/Lists.app

The share extension works only in notarized builds and in builds made with your own certificate.

Android

Needs Android 8.0 or newer on an arm64 device, which is every current phone.

  1. Download Lists-<version>-android.apk on the phone.
  2. Open the file and allow the browser or file manager to install apps when Android asks.

Every release is signed with the same key, so a newer APK installs over the older one and keeps the data. The SHA-256 fingerprint of the signing certificate is 4aca5869f8c3b2ffc45fa2b62530d5c15fb6790fcc37bcc9c94e3b170cf63c09; apksigner verify --print-certs shows it.

Web interface

The image ghcr.io/juev/lists is built for amd64 and arm64. The server speaks plain HTTP, so the password and the session cookie travel in clear text: publish the port on the loopback address only and reach it from elsewhere through a reverse proxy that terminates TLS.

docker run -d --name lists \
  -p 127.0.0.1:8080:8080 \
  -v lists-data:/data \
  -e LISTS_WEB_PASSWORD='choose a password' \
  -e LISTS_TRUST_PROXY=1 \
  ghcr.io/juev/lists:0.8.1

Open http://127.0.0.1:8080 on the same machine and sign in with the password. The reverse proxy, sync, OpenID Connect and a Compose file are covered in docs/self-hosting.md. Run the version of the latest release, the same as the apps: a server older than 0.5.1 stops syncing with a storage the current apps write, as Sync explains. Attachments are streamed to and from the disk and served with byte ranges; the server takes files up to 512 MiB and 2 GiB per task by default, which LISTS_MAX_FILE_MB and LISTS_MAX_TASK_MB change. Scripts reach it with API tokens made in the web interface; the API is described in docs/api.md.

First steps

  1. Add a task: ⌘N on macOS, the "+" button on Android, the New task field in the web interface. Type it and press Enter. One line is enough: report friday 10:00 !! #work @Projects sets the due date, a medium priority, a tag and the list. A tag is one word of up to 196 bytes (196 Latin letters or 98 Cyrillic ones); a longer tag is refused with an error.
  2. Open Settings → Sync on each device and point them all at the same WebDAV folder or CalDAV server. Until then the data stays on the device, and the app is fully usable.
  3. Coming from another task manager? Import a 2Do backup, a Todoist CSV, a TickTick backup, an OmniFocus CSV, a Things database, a Trello board or Microsoft To Do lists: File → Import… on macOS, Settings on Android, the sidebar in the web interface. Leaving goes the other way: everything exports to JSON in a documented format, or to Markdown for reading.

The user guide describes lists, tasks and attachments, subtasks and projects, repeats, quick entry, filters, sync, notifications, calendar events, keyboard shortcuts, backups, export and import.

Sync

Kind What is stored When to pick it
WebDAV or folder the app's own change log, full fidelity any WebDAV server, a mounted share, a folder synced by another tool
CalDAV one VTODO per task, a calendar per list tasks should also be visible in other CalDAV clients

With CalDAV what the app knows about a task travels in a custom property next to the standard ones (the shared settings and attachments over 5 MB do not travel), edits made by other clients are picked up, and their own properties are preserved. Servers that strip unknown properties reduce it to the standard fields. Details: docs/specs/caldav.md.

Lists 0.5.0 moves a WebDAV or folder storage to format 2, with compressed snapshots; earlier versions stop syncing with such a storage and say so. Lists 0.5.1 and later move the storage from lists/v1/ to lists/, and 0.5.0 stops syncing with a moved storage in the same way. Update every device and the web server together. CalDAV is not affected.

A WebDAV or folder storage can be encrypted with a passphrase from Settings → Sync on a Mac or a phone (usage); earlier versions stop syncing with an encrypted storage and say so, so update every device first. CalDAV is not encrypted. The storage password and the passphrase are kept in the macOS keychain and, on Android, encrypted with a key from the Android Keystore; they are never written to the database.

Documentation

Document What it covers
docs/usage.md using the apps and the web interface
docs/self-hosting.md running the web server: Docker, settings, sign-in, reverse proxy
docs/releasing.md cutting a release, signing keys and repository secrets
docs/architecture.md design decisions and their reasons
docs/android-design.md Android theme, measures, colours and screenshot tests
docs/android-visual-direction.md the look of the Android app: the directions compared and the one chosen
docs/specs requirements, data model, sync rules, CalDAV mapping, import

Building from source

Path What
core/ Rust crate: storage (SQLite), merge of concurrent edits, sync, recurrence, quick-entry parsing
apple/ macOS app, share extension and the helper of quick entry: SwiftUI, with the task list, the cards and the sidebar in AppKit
android/ Android app, Kotlin and Jetpack Compose
web/ lists-web: a small server with the same core and a web interface
scripts/ builds the core for each platform and generates the bindings

The apps reach the core through UniFFI bindings generated at build time; generated files are not committed. The Rust version is pinned in rust-toolchain.toml.

Core and web server, on any machine with Rust:

make test    # unit tests, behaviour tests, sync tests, sync over a local WebDAV server
make lint    # rustfmt and clippy
make deny    # advisories and licenses of the dependencies (brew install cargo-deny)
make web     # web/target/release/lists-web

macOS app (Xcode 16 or newer, brew install xcodegen):

make apple        # apple/build/Build/Products/Debug/Lists.app
make apple-run
make apple-test   # the tests in apple/Tests; they do not start the app
make apple-lint   # SwiftLint, settings in apple/.swiftlint.yml (brew install swiftlint)

The share extension and the app share one database through an App Group, which needs a signing team. The build takes the team from LISTS_TEAM_ID or from the first "Apple Development" certificate in the keychain. Without a team the app is signed ad hoc and keeps its data in Application Support; the share extension then has no access to it and says so in its form instead of saving.

Android app (JDK 17 or newer, Android SDK with platform 36 and an NDK, rustup target add aarch64-linux-android x86_64-linux-android, cargo install cargo-ndk):

make android           # unit tests, then android/app/build/outputs/apk/debug/app-debug.apk
make android-install   # onto the connected device or emulator
make android-lint      # ktlint, settings in .editorconfig (brew install ktlint)

JAVA_HOME and ANDROID_HOME default to the Homebrew and Android Studio locations on macOS; override them in the environment.

Docker image, from the repository root:

docker build -t lists-web .

Trying sync without a server

cd core
cargo run --example webdav -- /tmp/lists-dav 8765

This serves a folder over WebDAV and CalDAV with user user and password secret. Point the macOS app at http://127.0.0.1:8765 and the Android emulator at http://10.0.2.2:8765. cargo run --example lists is a small command-line tool for looking into a data folder; it takes the password from LISTS_PASSWORD.

To check CalDAV sync against a real server, point the ignored test at it. It creates and removes its own tasks and one calendar, and leaves the inbox calendar behind:

cd core
LISTS_CALDAV_URL=https://example.org/dav/calendars/me/ LISTS_CALDAV_USER=me LISTS_CALDAV_PASSWORD=… \
  cargo test --test caldav real_server -- --ignored

Status

Version 0.8.1. What is implemented and how each part was checked is listed at the end of docs/specs/product.md.

Known gaps:

  • no iOS app; the web interface covers the phone;
  • a CalDAV storage is not encrypted, and neither is a WebDAV or folder storage until a passphrase is set (Sync);
  • the macOS app is built for Apple silicon only, the released APK for arm64 only.

Author

Denis Evsyukov.

License

MIT.

About

Local-first to-do list for macOS and Android with a self-hosted web interface and sync over WebDAV or CalDAV

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages