A local-first, end-to-end-encrypted, cross-platform task workflow. Your to-do list doesn't have to live on someone else's server.
English · 中文
Most task managers pick one of two lanes: pure-local (great on one device, useless the moment you switch laptops) or cloud-synced (convenient, but every plan, habit and note you keep ends up on someone else's disk). TaskFlow takes the third path — data lives on your devices by default, and when it syncs across devices it's end-to-end encrypted. The relay server only ever sees ciphertext frames.
I started it because I wanted a todo app I could actually use on the train (no network), on my laptop (full keyboard), and on my phone (swipe to dismiss) without maintaining three codebases or mailing my life to a SaaS. The interesting parts, in my opinion:
- Four components, one data model. Mobile (React Native), desktop
(Electron), an optional Python backend, and a self-hostable relay.
Every persisted object carries
updatedAt, so the same record flows across all of them. - E2EE sync that's actually byte-compatible cross-platform. Mobile uses Web Crypto, desktop uses Node crypto. The wire format differs in one place (AES-GCM authTag position) so we reshuffle it explicitly — a frame encrypted on mobile decrypts on desktop, and vice versa. 668 tests include bidirectional interop cases.
- Local-first by default, not as a marketing tag. No TaskFlow account exists. The relay can't read your data. If you never pair a second device, nothing ever leaves the first one.
- AI suggestions are statistical, not magical. They look at your completion history (which hours you actually work in, which category you default to, which tasks you keep deferring) and surface that as a one-tap action. No model weights, no API, no network.
| Component | Stack | What it does |
|---|---|---|
| Mobile | React Native 0.86 · Expo 56 · TypeScript 6 · Zustand 5 | 15 screens, 6 views, focus mode / pomodoro / white noise. Private keys in Keychain / Keystore. |
| Desktop | Electron · React · Vite · better-sqlite3-multiple-ciphers | SQLCipher file-level encryption + field-level AES-GCM. Biometric unlock, screenshot protection, privacy shell. |
| Relay | Node.js 18+ · WebSocket · express-rate-limit · Docker | Forwards ciphertext frames only, with an offline queue (7-day TTL). LAN-first, relay as fallback. |
| Backend | Python 3.11+ · FastAPI · PyGit2 · langchain-core | Optional: task API, git management, LLM, plugin system. Decoupled from the sync path. |
Mobile ⇄ desktop syncs over an encrypted P2P session; the relay is just a NAT-traversal porter that never decrypts. See ARCHITECTURE.md for the full layering.
Fifteen screens on mobile (Home, Calendar, Analytics, Search, Goals, Habits, Notes, Projects, Categories, Tags, Views, Templates, Automation, TaskDetail, Settings) and six non-list views over the same data:
- Kanban — drag between columns, PanResponder + LayoutAnimation
- Gantt — dependencies on a timeline
- Timeline / TimeBlock / Table / MindMap — one view per way of looking
- Focus mode — full-screen Forest + pomodoro + Web Audio white noise
Draggable reordering is done without react-native-reanimated —
PanResponder + LayoutAnimation is enough for the lists a todo app
actually has, and the bundle is ~30% smaller for it. Task dependencies
(blockedBy / blocks) stop you marking a task done when something
it's waiting on is still open.
This is where most of the time went. Two devices handshake before syncing, verify each other's identity, derive a pair of direction-isolated session keys, then every record flows under AES-256-GCM. The relay sees bytes, not content.
identity Ed25519 long-term keypair per device, private key in Keychain/Keystore
deviceId = sha256(raw SPKI pubkey), first 16 hex
handshake X25519 ephemeral ECDH, both sides Ed25519-sign the transcript
derive HKDF-SHA256 → sendKey / receiveKey, info bound by role direction
records Sync Master Key (SMK) does AES-256-GCM, wire = iv[12]‖tag[16]‖ct
conflict updatedAt (LWW) first, version vectors break same-ms ties
transport 9 message types, frame = mode[1]‖length[4 BE]‖payload
sequence number + sliding window for replay protection
Pairing is an 8-digit code, 5-minute TTL, one-shot. SMK is generated by the host and transferred to the joiner over the encrypted pairing session, then compared in constant time — if it doesn't match, pairing is rejected rather than silently continuing into a state where every later sync would fail to decrypt. Device revocation is a four-step orchestration: kill the runtime session, drop the device record, clear its outbox, and only reset the SMK when no paired devices remain.
The gory details (incl. 7 places where the implementation diverged from the design spec, and why) live in docs/superpowers/specs/.
npm install
npm run web # fastest path — opens http://localhost:8081
npm run android # needs Android Studio or a device
npm run ios # needs macOS + XcodeFor a deployable web bundle:
npm run build:web # writes dist/, ready for Netlify / Vercel / S3cd desktop
npm install
npm run dev # Vite + Electron in dev mode
npm run build # produces platform installer via electron-builderSystem requirements and the full desktop user manual (master password, biometric unlock, vault, sync, backup) are in docs/desktop-user-guide.md.
cd relay
docker compose up -d # uses relay/docker-compose.ymlTLS, Nginx reverse proxy and ACME notes are in docs/relay-deployment.md. The relay only needs to reach the public internet; it never sees plaintext.
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reloadOpenAPI spec is auto-generated and CI-validated — see backend/docs/openapi.json (36 endpoints).
App.tsx # mobile entry, wraps everything in an ErrorBoundary
screens/ # 15 mobile screens, one file each
src/shared/
components/common/ # ~20 reusable bits (Button, TaskCard, ...)
components/views/ # the six non-list views (Kanban, Gantt, ...)
components/sync/ # mobile sync UI (pairing dialog, device list, ...)
hooks/ # useBulkSelection, useKeyboardShortcuts, useUndo, useSyncRuntime
store/index.ts # Zustand store entry; state lives in slices/
sync/ # mobile E2EE stack (Web Crypto based, byte-compatible with desktop)
types/index.ts # every persisted shape lives here
utils/ # natural-language date parser, secure storage
desktop/ # Electron app — main / preload / renderer
src/main/services/sync/ # desktop E2EE stack (Node crypto based)
src/renderer/ # React UI (vault, sync settings, task pages)
relay/ # self-hostable WebSocket relay + Docker
backend/ # optional FastAPI service
docs/ # user guide, roadmap, security tracker, relay deployment
The mobile src/shared/sync/ and desktop desktop/src/main/services/sync/
directories are deliberately parallel — same protocol, different crypto
backends. The .interop-check.js script at the repo root verifies
byte-level compatibility between the two.
# mobile / web
npm run typecheck # tsc --noEmit, 0 errors
npm run lint # ESLint v9 flat config, 0 errors
npm run test # vitest
# desktop
cd desktop && npm run typecheck && npm run lint && npm test
# backend
cd backend && python -m ruff check app && python -m mypy app && python -m pytest tests/ -q
# relay
cd relay && npx tsc --noEmit && npm testI aim for "0 errors" on all four. The test baseline as of v1.2.0 is
668 tests (mobile 339 · desktop 256 · backend 65 · relay 8), all
green on main. The 5000-record end-to-end sync benchmark lands at
649 ms (7699 rec/s) — that figure used to be 52 s before fixing a
REQUEST-chunking bug and batching the inserts into one transaction.
Two independent audits (TF-001019 and TF2-001017, 36 findings total)
are tracked in a single
SECURITY_TRACKER.md. Most are
fixed; the rest are tagged in the roadmap. Highlights of what's in
place: branch protection with required review, CodeQL, gitleaks with
push-protection, dependency review (rejects GPL-3.0/AGPL-3.0), OSSF
Scorecard, cosign-signed releases, property-based fuzzing with
Hypothesis. Vulnerability reporting is private — see
SECURITY.md.
The repo ships these GitHub Actions:
verify.yml— typecheck on every push (lint runs inci.yml)build-android.yml— debug APK build artifacteas-build.yml— EAS Cloud Build (manual trigger, needsEXPO_TOKEN)pages-intro.yml— publishes the static project intro indocs/to GitHub Pagesdesktop-build.yml— builds the Electron installers (three platforms)release.yml— cosign-keyless signed release + SHA256SUMS
The GitHub Pages site is only an introduction page; the actual apps must be run locally or built via the workflows above.
| Doc | What it covers |
|---|---|
| ARCHITECTURE.md | Layering, state model, sync protocol stack |
| QUICK_START.md | Three paths to a running app |
| docs/desktop-user-guide.md | Desktop manual: install, vault, biometric, sync, backup (442 lines) |
| docs/ROADMAP.md | What's done (Phase 1–8) and what's next |
| docs/DEVELOPER_GUIDE.md | Dev workflow, commands, sync strategy |
| docs/relay-deployment.md | Self-hosting the relay, TLS, Nginx |
| docs/fuzzing.md | Property-based fuzzing with Hypothesis |
| CHANGELOG.md | Per-phase change log |
| FAQ.md | Stuff people actually run into |
| SECURITY.md | Supported versions, private disclosure |
Two mirrors, identical content. GitHub is the primary repo (CI, releases, issues); GitCode is a mirror for reach from inside the GFW.
| Host | Repo |
|---|---|
| GitHub | github.com/MS33834/taskflow |
| GitCode | gitcode.com/badhope/taskflow |
File issues and PRs on GitHub. The GitCode mirror receives the same pushes but doesn't run CI.
- The web build is a single SPA bundle — fine for GitHub Pages, too big
for anything that cares about TTFB. Run
npm run build:weband checkdist/for the current size rather than trusting a stale number here. - Voice input is Web-only. The native side would need a separate speech-recognition library, which would balloon the APK.
- Screenshot protection on Linux is best-effort. X11/Wayland can't reliably intercept screenshots, so the settings page says so plainly instead of pretending.
- Mobile ↔ desktop sync requires a self-hosted relay for pairing. Once paired, LAN-direct is preferred when reachable; the relay is fallback.
MIT. See LICENSE.
If you're reviewing this for a job, ARCHITECTURE.md is the best place to start — it walks through the state model, the rendering pipeline, and the sync protocol stack.