TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
People, tasks, notes, agenda, and ephemeral developer tools. Encrypted at rest. Encrypted backups between devices. AI when you want it.
ProductivityTypeScript · JavaScriptGitHub @sercancelenkOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗People, tasks, notes and agenda — encrypted on your device.AI when you want it, off when you don't.
🌐 Marketing site · 📖 User guide · 🚀 Try the web app · ⬇️ Download for macOS
A planner that doesn't ship your day to someone else's server. Four trade-offs we picked the unfashionable side on:
| 🔒 100% local-first | Every keystroke is written to your machine, not to a server. Atomic fsync'ed writes, 50 rolling auto-snapshots, refuse-to-overwrite guard, in-app recovery UI. A power loss or kernel panic can't lose acknowledged data. |
| 🛡️ Encrypted at rest | The desktop data file is wrapped in AES-256-GCM with a key derived from your account password via scrypt. Notes get an opt-in passphrase lockbox on top (PBKDF2-SHA-256, 200k iters → AES-256-GCM, non-extractable CryptoKey). |
| 🤖 AI is opt-in, BYO key | Bring your own API key for Anthropic Claude, OpenAI or Google Gemini, or point Cadence at your own gateway (OmniRoute, LiteLLM, Ollama, a company proxy) in any of four API formats. Calls go directly from your device to the provider — no Cadence proxy, no telemetry. Off completely until you pick a provider. |
| 📖 Open source, no upsell | The codebase is on GitHub under MIT. No accounts in the cloud, no premium tier, no waitlist. The desktop app auto-updates straight from GitHub Releases. |
About the name — Cadence is the new name for the project formerly called Leeadman. The desktop app auto-migrates legacy data on first launch (it copies
Leeadman/→Cadence/, renaming theleeadman-*files tocadence-*; the legacy folder stays as a safety net). See Migration notes at the bottom if you're coming from a pre-rename build.
A quick tour of the desktop app. Every page below is the macOS Electron build in dark mode — the light theme mirrors the same layouts.
Greets you with overdue counts, open to-dos and today's schedule. Stat cards for teams, tasks and people; quick-access tiles to Agenda, To-dos, Teams and Notes. The top bar carries a global search pill with a
⌘ K/Ctrl Kshortcut badge — clicking it (or pressing the shortcut) opens the command palette that fuzzy-searches across navigation targets, teams, people, items, to-dos and notes (titles + content).
Each row carries a status: To do · In progress · Done · Cancelled. Active · Archived segment for tasks and lists. Filter by status, sort by Manual / Priority / Due date / Status (Kanban order), and drag rows by the grip handle to reorder manually. Every task can carry optional Markdown details. Recurring reminders fire as desktop notifications.
Two-pane macOS-Notes-style view with Active · Archived in the sidebar. Drag the divider to resize (220–560 px). Five sort modes plus drag-reorder inside the pinned tier. Notes open in Preview by default; switching to Write reveals a full Markdown formatting toolbar. Optional per-note passphrase lockbox.
One unified view of every reminder, due task and personal to-do for the next seven days, plus an Overdue bucket up top.
Drag tasks from your personal to-do lists into Do first, Schedule, Delegate, or Eliminate. Pin up to three for today's focus strip. The matrix is a planning view — tasks stay linked to their original lists.
Cards are organised into named groups: Account & security (stay signed in, recovery codes, PIN), Data & backup (one Export / Import backup, daily backup folder, rolling snapshots), Integrations (AI assistant and OS notifications) and About (user guide, version, auto-update).
| 📝 Notes | macOS-Notes-style two-pane view, resizable sidebar (220–560 px), always-editable rich-text surface (no Preview/Edit toggle) with formatting toolbar (B / I / S / H1–H3 / lists / link / inline code / code block / divider, with ⌘B / ⌘I / ⌘K shortcuts). Double-click images to enlarge; ⌘/Ctrl+click links to open. Five sort modes plus drag-to-reorder inside the pinned tier. Active | Archived segment to shelve notes without deleting. Optional per-note passphrase lockbox. |
| ✅ To-dos | Lists grouped by project, each with its own priority (Urgent / High / Normal / Low) and per-row status (To do / In progress / Done / Cancelled). Optional Markdown details per task (Notes-grade editor). Sort by Manual / Priority / Due date / Status (Kanban order). Filter by status. Active | Archived for tasks and lists. Drag items between groups and within. Recurring reminders (daily / weekly / monthly) fire as desktop notifications. Hide / show closed, archive, search (title and details), bulk ops. |
| 📅 Agenda | Unified Today / This-week / Overdue view combining reminders + due tasks + personal to-dos. Lives offline; never asks for calendar permission. |
| 🎯 Planning | Personal Eisenhower matrix — drag tasks into Do first / Schedule / Delegate / Eliminate by urgency and importance. |
| 👥 Teams + People | Group people into teams, give each a private scratchpad and a running agenda. 1:1 mode with a persistent markdown meeting agenda + archive of past meetings; unchecked items carry over. Person Timeline for review prep. |
| 🔎 ⌘K command palette | Global search across notes, tasks, items, people and navigation targets. Indexes body text (not just titles), highlights matches with contextual snippets, deep-links straight to the result. Locked notes searchable by title only. Archived notes excluded from search. |
| 📊 Analytics | Local-only dashboard: completion rate, daily / weekly / monthly / yearly created-vs-completed SVG chart, per-team performance bars, top-contributors table. Activity report tab for a chronological change log. |
| 🧰 Utilities | Sidebar Document (rich-text, autosaved) and (CodeMirror: format, fold, Diff, compact/stringify). Plus — ephemeral encode/hash/text/time/codegen, lite ERD, and Excalidraw-style sketch. Tool inputs are session-only and never touch the workspace file. |
| 🔒 Encrypted at rest | AES-256-GCM data file keyed via scrypt(password). Notes get an additional PBKDF2 → AES-256-GCM lockbox with a non-extractable CryptoKey. |
| 🔑 Stay signed in (opt-out) | After your first login, Cadence wraps the data-encryption key with Electron's safeStorage and persists it in your OS keychain (macOS Keychain / Windows DPAPI / Linux libsecret). Restarts skip the password prompt; Logout always purges the cached key, and Settings → Stay signed in gives you a one-click "ask on every launch" toggle. On Linux without libsecret we refuse to cache rather than fall back to a hardcoded-obfuscation key — same security promise on every platform. |
| 🛟 Durable saves | Atomic open → write → fsync → close → rename cycle plus directory fsync. A power loss or kernel panic leaves either the old file or the new file — never a torn one. Worst case: ≤ 400 ms of unflushed typing. |
| 🗂️ Auto-backups | 50 rolling snapshots in backups/<userId>/ (labelled launch / post-login / pre-save / pre-pwchange / pre-restore) with a one-click in-app restore. Each snapshot shows teams / lists / tasks / notes counts; Reveal in Finder for manual inspection. Refuse-to-overwrite guard if the live file is undecipherable. Settings → Backup & recovery merges manual export/import and snapshot restore in one place — one Export backup and one Import backup (a single .zip with notes, tasks, images and — on desktop — note history; import shows what is inside and lets you add or replace), with folder and text-only JSON under Advanced. |
| 🔐 Recovery codes | Optional MetaMask-style codes to reset your account password on this device without data loss. Generate at sign-up or in Settings; use /recover if you forget your password. Existing accounts without codes behave exactly as before. |
| 📦 Workspace storage | Desktop app splits large workspaces into monthly files for efficiency while keeping a full copy in the base file for downgrade safety. Settings → Storage & cache shows a per-area size breakdown (notes, tasks, teams, archives). |
| 🛡️ |
| 🪶 Task extractor | Drop a wall of meeting notes / brain dump → get a clean ordered task list back, ready to file into any project group. |
| 🧭 Coaching on a task | One-line reframing + three concrete next actions ordered by leverage + one risk to avoid. Always under 200 words. |
| 🗺️ Visualize | Select text → a Mermaid flowchart, sequence or state diagram, checked with Mermaid's own parser before it is offered, inserted as a new block. |
| 🔍 Challenge my idea | Assumptions, risks, alternatives and open questions about a note, added as a separate block at the end. |
| 🪜 Break down | Split a task into editable steps: a checklist in its details, or new tasks in the same list. |
| 🧮 Classify with AI | Suggest important / urgent for unsorted tasks on the Planning matrix; undoable. |
| 🧩 Document, mock data and ER diagram | A structured copy of a document (outline, RFC, meeting summary or table, then drawn as Mermaid or Sketch), invented records shaped like your sample, and additive table suggestions; each lands as a new tab or only adds. |
| ✏️ Sketch and screenshot → Mermaid | Convert a sketch's labels and arrows, or a picture of a diagram, into Mermaid (or an HTML mock-up) as a new note. |
| ✏️ Draw from a description | Describe a diagram in words; get a Mermaid note or a Sketch board, then ask for changes. Added as new; nothing existing is touched. |
| 📅 Weekly digest | A retrospective or an upward status update from titles and counts of your week; streams in as it is written. |
| 🤝 Prepare a 1:1 | Follow-ups, open goals, feedback and questions from one person's records; people data. |
| 🔎 Ask your workspace | Answers from your own notes and tasks with [n] citations and links; optional search by meaning. |
| 🧱 Feedback → SBI | Rewrite feedback as Situation / Behavior / Impact using only what you wrote; people data, so only to connections you allow. |
| 🎛️ Three providers, one switch | Anthropic Claude, OpenAI ChatGPT, Google Gemini. One dropdown to swap. |
| 🖥️ macOS desktop | Universal signed + notarized DMG (Apple Silicon + Intel). Auto-updates via GitHub Releases with an in-app progress dialog. Optional PIN lock at launch with rate-limited account-password recovery. |
| 📱 PWA | Installable on iOS / Android / desktop browsers. Slide-in drawer sidebar, full-screen content, iOS safe-area aware. Offline-capable. Data lives in the browser's storage on that device. |
| 🐧 Windows / Linux | Code is fully cross-platform — encryption + backup + auto-update behave identically. No CI binaries yet; build locally when you need one. |
For users
/guide)For developers
Cadence-<version>-universal.dmg. This is a universal binary that runs natively on both Apple Silicon (M1/M2/M3/M4) and Intel Macs — one file for everyone.Cadence.app into Applications.Because the DMG is signed with a Developer ID Application certificate and notarized by Apple, you will not see any "damaged" or "unidentified developer" warning.
https://sercancelenk.github.io/cadence/ in Safari (iOS) or Chrome (Android).Mobile data is separate by default. The PWA stores its own data in the phone's
localStorage(independent of the desktop's encrypted file). To move data between devices move a portable backup file across (Settings → Backup → Export, then Import / merge on the other device).
If you build a DMG yourself without code signing, macOS quarantines it ("… is damaged"). Bypass once with:
xattr -dr com.apple.quarantine /Applications/Cadence.app
The official Releases DMG never needs this.
localStorage["cadence.tour.completed.v1"]) and won't show it again on this device. Returning devices (anything with an existing LAN pair or Drive tokens) skip the tour automatically.Tip: install Cadence on a second machine and use Settings → Backup → Export JSON / Import JSON to move your data.
Account
└── App data file (JSON)
├── Utilities (Document scratch pad, JSON/YAML editor)
└── Teams[]
├── Me (auto-created per team)
├── My leader (auto-created per team)
└── People[]
├── Profile + scratchpad (markdown)
├── 1:1 agenda (markdown, archivable)
└── Items[]
├── Task (due date, reminders)
├── Goal (status, start, deadline)
├── Note (markdown body)
├── Feedback (praise / coaching / concern)
└── Document (URL)
Personal
└── To-do lists (pin, archive, search, bulk ops)
The complete data model is in src/model.ts.
The top bar carries a global search pill ("Search teams, notes, tasks…") sitting next to the team-switcher — click it, focus it with /, or press ⌘ K (macOS) / Ctrl K (Windows / Linux) and the same palette opens. The button is both an entry point and a visual reminder of the keyboard shortcut (the trailing ⌘ K / Ctrl K badge auto-adapts to the platform).
The palette searches across:
Picking a Notes hit navigates to /notes?id=<id>; the page selects that note on mount and immediately strips the query string from the URL so a refresh doesn't keep fighting your manual navigation.
Arrow keys to move, Enter to open, Esc to close.
Notes, personal to-do details, team-item bodies, person scratchpads and 1:1 agendas all use GitHub-flavored markdown with a Write / Preview toggle:
- [ ] / - [x]).In Write mode the editor now also shows a formatting toolbar that operates on the current textarea selection (and falls back to a placeholder when nothing is selected, so the user can immediately type to replace it):
| Button | What it does | Shortcut |
|---|---|---|
| B | Wraps selection in **…** (bold) | ⌘ B / Ctrl B |
| I | Wraps selection in *…* (italic) | ⌘ I / Ctrl I |
| S | Wraps in ~~…~~ (strikethrough) | — |
| H ▾ | H1 / H2 / H3 — prepends #, ##, ### to every line in the selection | — |
| • / 1. / ☐ | Prepends - , 1. , or - [ ] to every line | — |
| 🔗 | Inserts [selection](https://) and parks the caret over the URL placeholder | ⌘ K when the textarea is focused |
</> | Wraps in single backticks (inline code) | — |
{ } | Inserts a fenced ```code block``` around the selection (smart-aware of line boundaries) | — |
| — | Inserts a --- horizontal divider | — |
The toolbar is line-aware (lists and headings always operate on whole lines, never mid-word) and focus-preserving (the caret lands inside the new markup so you can keep typing without picking up the mouse again).
Every person page has a Timeline tab that shows a chronological feed of every item attached to that person — tasks, goals, notes, feedback, documents — grouped by day (Today / Yesterday / Friday / …) and filterable by kind. Designed for review prep ("what happened with Alice in Q3?").
Every person also has a 1:1 Mode tab with two halves:
- [ ] for action items.1:1 · 15 May 2026) attached to that person, and the agenda resets. Any unchecked action items are carried over into the new agenda automatically.The archived meetings are regular notes, so they also show up in the Person Timeline.
The /agenda page is a global, unified view of:
It mixes team-item reminders + due dates with personal to-do dues, marks each entry with its origin team / person, and lets you mark-complete or jump to its workspace inline.
The /analytics page is a fully local, dependency-free dashboard that reads
straight from your workspace data:
/todos items.Nothing is sent off-device; the chart is rendered as inline SVG.
Sidebar Utilities — workspace tools that are not notes, todos, or team items:
/utilities/document) — a standalone rich-text scratch pad with the same always-edit chrome as notes (sticky toolbar, autosave indicator). Persisted as utilityDocument in your workspace JSON./utilities/structured) — a lazy-loaded CodeMirror editor for config and payload review:
content + diffContent).Both utilities autosave to your encrypted workspace file like everything else.
Any item reminder can be made daily / weekly / monthly. When the reminder fires, the next occurrence is computed and assigned automatically — you never have to recreate weekly 1:1 reminders.
Every person has a dedicated Feedback log alongside Tasks/Goals/Notes/Documents. Each entry is tagged with one of:
This makes performance-review prep and growth-conversation prep trivial — open the person's Timeline, filter to Feedback, and you have your talking points.
The /todos page scales as your lists grow:
Schedule chip that opens a popover with Today 5pm, Tomorrow 9am, +3h, Next Mon 9am presets, plus a custom datetime picker and a one-tap "Clear schedule" action.<open> / <total> at a glance.Every task row has an Ask AI button as soon as you connect a provider in Settings → AI Assistant. The assistant takes the task title + optional Markdown details and returns a structured next-action plan, rendered as Markdown in a side dialog.
claude-sonnet-5-5. Suggested: claude-sonnet-5-5, claude-haiku-5-5, claude-opus-5-5.gpt-5.4-mini. Suggested: gpt-5.4-mini, gpt-5.5, gpt-5.6. (gpt-4o-mini, gpt-4o and gpt-4.1-mini have an announced shutdown on 2027-01-14.)gemini-3.8-flash. Suggested: gemini-3.8-flash, gemini-3.6-flash, gemini-3.5-flash-lite, gemini-3.1-flash-lite. (Gemini 1.x and gemini-2.0-flash are retired and return HTTP 404; the Settings UI surfaces a one-click fix.)http:// is accepted only for localhost and private-network addresses. Failures offer copyable diagnostic details with secrets hidden.Beyond Ask AI and Extract tasks there are Visualize (text → Mermaid diagram), Challenge my idea, Break down (a task into steps), Feedback → SBI, Classify with AI, document / mock-data / ER-diagram helpers, Convert to Mermaid (sketch), Diagram from a screenshot, Weekly digest, Prepare a 1:1 and Ask your workspace; each previews its result and writes nothing until you press its button (see docs/AI-INTEGRATION.md). JSON, ZIP and full-backup exports leave out your AI keys.
The provider-agnostic transport layer lives in src/lib/ai.ts. Connection details and limits: docs/AI-INTEGRATION.md. The design behind these features and what was left out on purpose: AI features: foundation and roadmap.
Settings → Backups & recovery is your safety net when something goes sideways — for example after a major version upgrade, a password rotation hiccup, or registering a fresh account by mistake.
cadence-data-<userId>.json into backups/<userId>/data-<label>-<timestamp>.json. A rolling window of 50 snapshots is kept; oldest are pruned automatically (~115 KB for a typical workspace — disk use stays bounded).localStorage. On the next launch, if the loaded file is meaningfully smaller than that marker, an amber Looks like data may be missing banner appears app-wide with exact before/after numbers and a one-click jump here. Dismissing rebases the marker to the current state.leeadman-data.json, from the pre-accounts era — also picked up by the one-shot rename migration)cadence-data-<otherId>.json files (or pre-rename leeadman-data-<otherId>.json files left behind), useful when you registered twicejq, a text editor, etc.).Loaded: 1 team, 5 tasks, 2 notes). The current state is itself snapshotted as pre-restore first, so the operation is reversible.leeadman-data.json from pre-accounts builds) does, Cadence imports it into your account automatically. No more "I updated and my old data is gone" surprise.Existing users: recovery codes, ZIP backups, and note history are optional add-ons. Old JSON and folder backups import exactly as before; no forced migration.
Settings → Storage & cache is the honest, read-only picture of what Cadence occupies on this device — plus a safe way to reclaim disk when the browser engine has cached a lot of HTTP responses, V8 code or GPU shaders over months of use.
userData size.session.clearCache() / clearCodeCaches() / clearStorageData({ storages: ['cachestorage', 'shadercache'] }) APIs. Tasks, notes, AI keys, backups and account list are never touched. Caches repopulate naturally; you may see a one-time slower first request or a shader recompile on next launch.caches API entries on this origin, then hard-reloads. Use this if the PWA feels stuck on an older version; your localStorage (AI key, UI prefs, hashed account credentials) is preserved.This panel is purely diagnostic — you can ignore it forever and nothing degrades. The auto-prune logic already keeps backups at 50 slots and Chromium self-manages its caches; the buttons exist so you have the option, not as a maintenance chore.
/notes is a macOS-Notes-style two-pane view for free-form personal notes that don't belong to a team or a person:
sortOrder only within the pinned tier the row belongs to, so you can never accidentally cross the pin boundary.lastOpenedAt timestamp that powers the "Last opened" sort mode. The stamp explicitly does not bump updatedAt, so opening a note never bubbles it up in the "Last updated" view.Per-note lock with workspace master key. Click the lock button on any note and you'll be prompted, once, to set a Notes passphrase (≥ 6 chars). From then on:
AppData.notesLock) to derive a 256-bit master AES-GCM key. The key is created with extractable: false, so its raw bytes can't be retrieved even from JavaScript — only encrypt and decrypt calls work.CryptoKey lives in renderer memory for the rest of the session. A memory dump won't yield the passphrase. The pending-input fields in the dialog are cleared as soon as derivation succeeds."leeadman-notes-v2") — so a wrong passphrase is rejected before we touch a real note.NotesUnlockProvider lives below AuthGate and unmounts in any of those cases.Remove the passphrase. A dedicated Remove notes passphrase button (padlock-with-slash icon) in the Notes sidebar header always prompts you to re-enter the Notes passphrase before showing the destructive confirm dialog — even if the session master key is still cached in memory from an earlier unlock this session. Once you've re-authenticated and confirmed, Cadence decrypts every locked note up-front with the current master key; if any note fails (master key mismatch) it aborts the whole operation and leaves your data untouched. On success it converts every locked note back to plaintext and clears AppData.notesLock in one atomic save.
Strict per-view unlocking. Clicking Unlock to view on a locked note always opens the passphrase prompt, even if you unlocked the same note (or another locked note) a minute ago in the same session. Navigating away from a viewed locked note clears its in-memory plaintext, and the next view request drops the cached session key and re-prompts — so locking a note actually keeps it hidden every time you come back to it, not just until you next clicked the unlock button. Editing a note that's currently visible still uses the in-memory key (so each keystroke isn't gated on a prompt), but the moment you leave the note the strict-prompt rule re-engages.
What's stored where:
| Thing | Plain on disk? |
|---|---|
| Locked note's body | No — only { ivB64, cipherB64 } |
| Locked note's title, pin, timestamps | Yes (intentional — you can still find notes without unlocking) |
| Workspace verifier | Yes ({ saltB64, verifierIvB64, verifierCipherB64 }, no key material) |
| Notes passphrase | Never |
| Workspace master key | Renderer memory only, non-extractable, dropped on logout/lock/restart |
No reset path on purpose. Because the passphrase is never written down, anyone who steals your data file (including future-you with backups) cannot derive it. If you forget the passphrase your locked notes are unreadable forever. That's the trade we make for honest at-rest encryption; if you want recoverability instead, just don't lock the note.
/profile is a card-first, view-by-default page:
data: URL on your profile, so it shows up in the top-bar avatar everywhere.Companion guide: For the recommended one user, desktop + phone workflow, troubleshooting, and golden rules, see docs/LAN-SYNC.md.
When you don't want any cloud service involved, Cadence moves a workspace between devices with a portable backup file (plain ZIP, not encrypted — keep it private) — no network connection between the devices is ever required:
.zip that bundles the workspace, image attachments and, on desktop, note version history; unencrypted).There are two distinct import modes, and the difference matters for data safety:
The merge is implemented by mergeAppendWorkspace (src/core/model/mergeWorkspace.ts) and deduplicates by entity id first, then by a stable content signature, so the same logical note created independently on both devices doesn't import as a near-duplicate. Because there are no tombstones in the model, a merge can never delete anything — the documented trade-off is that re-importing an old file can resurrect an item you had deleted locally.
There is no sync service: Cadence is local-first, so a second device gets your data only when you carry a file across. Live LAN pairing and Google Drive sync were retired.
Cadence ships as a Progressive Web App in addition to the desktop Electron build. The same React bundle is deployed to GitHub Pages and installable on iOS/Android.
main. The pages.yml workflow runs, builds with CADENCE_PWA=1, and publishes to https://<user>.github.io/cadence/.localStorage, so reads and edits work fully offline.# Regenerate icons after editing public/icon.svg
npm run icons
# Build for GitHub Pages (sets base path to /cadence/)
npm run build:pwa
/ → /todos.public/
manifest.webmanifest # PWA metadata
sw.js # Service worker (offline cache, auto-update)
icon.svg # Source vector logo
icon-192.png # Manifest icon (any)
icon-512.png # Manifest icon (any) + splash
icon-maskable-512.png # Adaptive icon for Android (safe-zone padded)
apple-touch-icon.png # 180×180 iOS home-screen icon
favicon-32.png # Browser tab icon
scripts/
generate-pwa-icons.mjs # sharp-powered icon rasteriser
src/pwa.ts # SW registration (skipped under Electron/file://)
.github/workflows/pages.yml # Automated Pages deploy
Cadence ships with a native menu bar (English):
Standard shortcuts apply (⌘ Q, ⌘ W, ⌘ R, ⌘ F, ⌘ , …). The global ⌘ K opens the command palette from anywhere.
~/Library/Application Support/Cadence/. (Pre-rename installs originally wrote to ~/Library/Application Support/Leeadman/; that folder is consumed and converted on first launch — see Dev vs installed app data isolation.)
cadence-accounts.json — user list (email, salted scrypt password hash, per-user encSalt).cadence-session.json — id of the signed-in user.cadence-data-<userId>.json — your workspace data, per account, encrypted at rest (AI key, tasks + optional Markdown details per task, lists, people, notes, preferences — everything).leeadman-data.json — legacy single-user file from pre-accounts versions (only present if you upgraded from an old install). Auto-imported on first login.backups/<userId>/data-<label>-<timestamp>.json — rolling auto-snapshots (50 slots; labels include launch, post-login, pre-save, pre-pwchange, pre-restore).auth-lock.json — optional PIN hash.scrypt(password, encSalt) at login and lives only in main-process memory until logout. Changing your password atomically decrypts with the old key, derives a fresh encSalt, and re-encrypts under the new key. Legacy plaintext files from older versions are upgraded silently on the first save after login.localStorage for the Pages origin and is not encrypted by the app — rely on the device's keychain / disk encryption.The desktop build is engineered so a kernel panic, power loss or kill-9 cannot silently lose acknowledged writes:
.tmp file with an explicit open → write → fsync → close cycle, then atomically rename()s it over the live file, then fsyncs the containing directory. The bytes are on durable storage before saveData() returns. A crash mid-write leaves either the old file or the new file — never a torn half-write.beforeunload / pagehide flush the pending payload synchronously. The worst case (an instant SIGKILL with a partial debounce timer) loses ≤ 400 ms of typing, never a committed save.backups/<userId>/data-pre-save-<timestamp>.json. So even a logic bug that writes garbage cannot destroy your previous state — the immediate prior version is one click away in Settings → Backups & recovery.localStorage. If the next launch loads a meaningfully smaller workspace, an amber banner spells out the before/after numbers and links to Backups & recovery — catching "everything disappeared" before you assume the worst (common when every todo list was archived, or after a bad session resume).userData directory, not inside the app bundle. On macOS your data lives in ~/Library/Application Support/Cadence/, which is outside Cadence.app. Trashing the app does not delete your data. Reinstalling later picks the data right back up. The same is true on Linux (~/.config/cadence) and Windows (%APPDATA%\Cadence).userData/backups/<userId>/. As long as you don't manually delete , every save from the past N days is recoverable.Atomic writes + 50 rolling snapshots survive crashes, bad updates and accidental in-app deletes. They do not survive a rm -rf ~/Library/Application Support/Cadence, a wiped SSD, a stolen laptop, or a misconfigured Time Machine.
For total peace of mind, periodically run Settings → Backup → Export JSON and copy the resulting cadence-backup-YYYY-MM-DD.json to iCloud Drive, Dropbox, a USB stick, or another machine. The export is a fully decrypted, version-tagged JSON dump — diff-friendly and migration-friendly. Importing it later restores everything (and snapshots the existing state to pre-restore first, so the import itself is reversible).
A monthly export is plenty for typical use; weekly if you treat the app as a primary system of record.
If you're moving everything from another app into Cadence in one sitting, the engine handles you (atomic writes, 50 pre-save snapshots, refuse-to- overwrite, autosave-failed banner) — but a few habits make the import itself more forgiving:
cadence-backup-YYYY-MM-DD.json after the migration gives you a
clean "known good" reference and is the file you'd hand to Import
JSON if you ever need to fully redo a restore.If you're migrating from a previous Leeadman install, see also Migration notes (from Leeadman) — the data-folder rename is consumed automatically on first launch.
The packaged desktop app checks GitHub Releases on launch via electron-updater:
latest-mac.yml from the latest release.You can also force an interactive check from Settings → Auto updates → Check for updates. A dialog walks you through the full flow:
quitAndInstall so the app closes, swaps in the new binary, and relaunches without you having to manually quit. Later defers the install until next launch.You can also trigger the same check from the menu bar via Cadence → Check for Updates…. Development builds (npm run dev) short-circuit the check and the dialog shows a "disabled in development mode" notice.
Releases use CalVer: the build carries a YYYY.M.<build> version (e.g. 2026.7.72) where the year is the semver major, the month (1–12) is the minor, and the CI run number is the patch. This is still valid, strictly-increasing semver, so electron-updater's comparison stays trivially correct — every new release is greater than any previous one (including the legacy 0.3.x line, since 2026 > 0), meaning clients on older versions always see it as an upgrade.
In the UI you just see the friendly label — "July 2026" — with the exact build string available in the tooltip and under Settings → Application version. The version is produced in CI by scripts/compute-version.mjs and formatted for display by src/lib/appVersionLabel.ts. Before publishing, scripts/validate-release-config.mjs --check-version checks the new version against the latest published release and aborts the release if it isn't strictly greater — a guard that makes it impossible to ship a build that would freeze auto-updates for existing users. If GitHub can't be reached the guard retries and then fails open (never blocking a legitimate release for a transient network blip).
The PWA "updates" itself silently via the service worker — the next time the device is online and you reopen the app, the new build is fetched and applied on the following navigation. Bumping CACHE_VERSION in public/sw.js invalidates every old cache.
Everything below this line is implementation-detail territory — how to build from source, the on-disk layout, release plumbing, the architecture deep-dive. If you're using Cadence rather than hacking on it, you can stop here and head to Install instead.
git clone https://github.com/sercancelenk/cadence.git
cd cadence
npm install
| Script | What it does |
|---|---|
npm run dev | Starts Vite at http://localhost:5173 and launches Electron pointed at it with hot reload. Runs in an isolated userData directory (~/Library/Application Support/Cadence (Dev)/ on macOS), so a dev session can never read, write or corrupt the data of the installed app — see Dev vs installed app data isolation. |
npm run build:web | Builds the Electron-targeted React bundle (base: ./) into dist/. |
npm run build:pwa | Builds the GitHub-Pages-targeted PWA bundle (base: /cadence/) into dist/. |
npm run build | build:web + electron-builder (local desktop bundle). |
npm run build:release | build:web + electron-builder --publish always (used by the release workflow). |
npm run icons | Regenerates PWA icons from public/icon.svg (uses sharp). |
npm run preview | Vite preview server for the last build. |
npm test | Runs the Vitest unit + integration suite (87 tests today — snapshot crypto, sync algorithm, Drive backend with fetch mocked, OAuth token flow, LAN client primitives). Fast, no network, runs under jsdom. |
npm run test:watch | Same suite in watch mode, for TDD-style iteration. |
npm run check:env | Pre-deploy sanity check: Node version, required source files (README.md, PRIVACY.md, TERMS.md, .env.example), .gitignore safety (no .env leaks), and the shape of VITE_GOOGLE_OAUTH_CLIENT_ID. Returns non-zero if anything critical is wrong, useful as a release-pipeline gate. |
# One-shot
npm test
# Watch (re-runs on file change)
npm run test:watch
The suite covers the parts of the codebase that have the highest blast radius if they regress:
src/lib/snapshotCrypto.test.ts — round-trip encrypt/decrypt, wrong passphrase, tampering detection, unsupported-version branch.src/lib/syncSnapshotGuard.test.ts — parseRemoteSnapshot accepts every valid shape and refuses every invalid one (defends against an empty/garbled remote overwriting your local data).src/core/model/mergeWorkspace.test.ts — the additive "import items from another device" merge: additive-only, no overwrite, content dedupe, idempotency, singleton protection.src/lib/useSyncAutoSync.test.ts — the full sync state machine (first-time pull, first-time seed, dirty push, clean pull, not-modified, conflict, auth-required, corrupt-remote) against a scripted FakeBackend.src/lib/syncBackends/gdrive.test.ts — 19 integration scenarios against a fetch-mocked Drive (auth, pull-ok, no-snapshot, wrong-password, not-modified, file-create, file-update, conflict, 401, network-error, 5xx retry-loop, 413 too-large, status, disconnect).src/lib/syncBackends/gdriveAuth.test.ts — OAuth token persistence, silent refresh-on-expiry, signOut revoke, redirect broker handling, runtime client-ID override priority.Everything runs under jsdom, so no real network and no Drive credentials are needed — CI runs the same suite on every push.
CSC_IDENTITY_AUTO_DISCOVERY=false npm run build
GitHub Releases ship signed macOS DMG + ZIP only. The renderer and the Electron main process are platform-agnostic (atomic fsync'ed writes, scrypt + AES-256-GCM, Google Drive sync, auto-updater all work on Windows and Linux), but the project's electron-builder config and release workflow only target macOS by design — adding Windows code signing infrastructure and ongoing CI minutes isn't worth it until there's real demand.
If you want to run Cadence on Windows or Linux today, you can build locally:
Windows (portable .exe, unsigned) — fastest path; no installer, no SmartScreen reputation needed (still shows a "Windows protected your PC" warning the first time; click More info → Run anyway).
# Run on a Windows machine (or via Wine on macOS/Linux with electron-builder docs)
npm pkg set "build.win.target=portable"
npm run build
# Output: release/Cadence-<version>-portable.exe
Windows (NSIS installer, unsigned) — full Start-menu + uninstall integration.
npm pkg set "build.win.target=nsis"
npm pkg set "build.nsis.oneClick=false"
npm pkg set "build.nsis.allowToChangeInstallationDirectory=true"
npm run build
# Output: release/Cadence Setup <version>.exe
Linux (AppImage) — single executable file; works on most distros without root.
# Run on Linux (or macOS with Docker for cross-compile)
npm pkg set "build.linux.target=AppImage"
npm pkg set "build.linux.category=Office"
npm run build
# Output: release/Cadence-<version>.AppImage
Data lives in %APPDATA%\Cadence\ on Windows and ~/.config/cadence/ on Linux — app.getPath('userData') resolves per-OS automatically and the same encryption + backup rules apply.
When/if community demand justifies it, these targets will be promoted into the release workflow (with Azure Trusted Signing for Windows so SmartScreen stays quiet). Until then, treat the above as "build it yourself" instructions.
If you've already installed Cadence from a DMG and you want to run the
local checkout side-by-side to try out new features, the two builds could
otherwise share the same on-disk data — Electron derives userData from
app.getName(), and macOS APFS is case-insensitive, so two builds with
the same display name resolve to the same directory. Running a dev build
on prod data is risky because a dev build typically writes a newer
on-disk schema than the installed version understands, which can silently
strip new fields on the next save.
To prevent this, electron/main.cjs detects dev mode (the
VITE_DEV_SERVER_URL env var that npm run dev sets) and routes everything
through a separate folder:
| Build | macOS userData |
|---|---|
| Installed DMG (production) | ~/Library/Application Support/Cadence/ |
npm run dev | ~/Library/Application Support/Cadence (Dev)/ |
This project used to be called Leeadman. The very first launch after the
rename detects a ~/Library/Application Support/Leeadman/ (or Leeadman (Dev)/ for dev) folder, copies its contents into the new Cadence/ (or
Cadence (Dev)/) folder, and renames every leeadman-*.json to
cadence-*.json on the way. The legacy folder is left in place untouched
as a safety net — feel free to delete it manually once you've confirmed
your data made it across. The migration is guarded by the presence of
cadence-accounts.json in the new folder, so it never runs twice and
never overwrites a workspace you've already started using under the new
name.
Side effects you should know about:
app.getName() too, so the installed
app and a dev session can run at the same time. They'll appear as two
separate apps in the Dock; both windows are functional.app.isPackaged, so the dev session
never tries to update itself.cp -R "~/Library/Application Support/Cadence/"* \
"~/Library/Application Support/Cadence (Dev)/"
This includes accounts, encrypted data files, backups and sync config.
The dev session opens the next time you npm run dev.New to Electron? Have a look at the Electron deep dive below — it explains the main / renderer / preload split, the IPC patterns we use and the build pipeline this command actually runs.
Both the desktop release and the PWA deploy are manual, and nothing runs automatically on push or pull request. To publish, open the Actions tab and pick the workflow you want; CI runs first as a gate inside it.
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml | reusable (workflow_call) + manual (Run workflow) | npm run typecheck + npm test + npm run build:web. Invoked as the green-light gate by Release & Pages; you can also fire it on demand to sanity-check a branch. |
pages.yml | manual (Run workflow) | Calls CI → npm run build:pwa → publishes to GitHub Pages. |
release.yml | manual (Run workflow) | Calls CI → computes a CalVer version YYYY.M.<run_number> and guards it against the latest release (updater-safety) → electron-builder --publish always → signs + notarizes the .app → uploads DMG/ZIP/latest-mac.yml to a new GitHub Release. |
main).electron-updater.CACHE_VERSION invalidation makes existing installs pull the new shell on next visit.Required repository permissions: Settings → Actions → General → Workflow permissions = Read and write.
For DMGs to open without the "… is damaged and can't be opened" Gatekeeper error on someone else's Mac, the build must be:
notarytool service.This repo's release workflow does both automatically. You only need to set up the secrets once.
.cer, and double-click to install it into the login keychain..p12CSC_KEY_PASSWORD below).developer-id.p12.xxxx-xxxx-xxxx-xxxx password.In Settings → Secrets and variables → Actions, add:
| Secret name | Value |
|---|---|
CSC_LINK | base64 of developer-id.p12 — base64 -i developer-id.p12 | pbcopy |
CSC_KEY_PASSWORD | .p12 export password |
APPLE_ID | Apple Developer account email |
APPLE_APP_SPECIFIC_PASSWORD | the xxxx-xxxx-xxxx-xxxx password |
APPLE_TEAM_ID | 10-character Team ID (e.g. ME5ER9CA9Q) |
The Team ID is also configured in package.json under build.mac.notarize.teamId — update it there if you fork this project.
After installing a signed build:
codesign -dv --verbose=4 /Applications/Cadence.app
spctl -a -t exec -vv /Applications/Cadence.app
xcrun stapler validate /Applications/Cadence.app
spctl should respond with accepted source=Notarized Developer ID. If it does, every user can install your DMG without any warning.
The signed binary uses the entitlements at build/entitlements.mac.plist:
com.apple.security.cs.allow-jit — required by V8 in Electron.com.apple.security.cs.allow-unsigned-executable-memory — JIT support.com.apple.security.cs.disable-library-validation — load Electron framework dylibs.com.apple.security.cs.allow-dyld-environment-variables — used by Electron's bootstrap.com.apple.security.network.client / server — outbound update checks and IPC..
├── electron/
│ ├── main.cjs # Main process: window, menu, IPC, auth, CSP, updater
│ └── preload.cjs # contextBridge surface exposed as window.leeadman
├── src/
│ ├── App.tsx # Router + protected shells, PWA-launch redirect
│ ├── main.tsx # React entry; StrictMode + ErrorBoundary + SW
│ ├── pwa.ts # Service-worker registration (web build only)
│ ├── AccountContext.tsx # Account sign-in/up state
│ ├── AuthContext.tsx # PIN lock state
│ ├── AppDataContext.tsx # Workspace data, debounce-save, reminder watcher
│ ├── ThemeContext.tsx # Dark / light theme
│ ├── actions.ts # Pure reducers operating on AppData
│ ├── model.ts # Domain types + migrations + normalization
│ ├── components/
│ │ ├── AppSidebar.tsx
│ │ ├── CommandPalette.tsx # ⌘K palette
│ │ ├── ErrorBoundary.tsx
│ │ ├── Layout.tsx / TeamLayout.tsx / TopBar.tsx
│ │ ├── icons.tsx
│ │ ├── AIAssistantDialog.tsx # Markdown chat dialog for the per-task AI button
│ │ └── ui/
│ │ ├── Button.tsx
│ │ ├── AutoResizeTextarea.tsx # Multi-line task input (auto-grow + submitMode)
│ │ └── MarkdownEditor.tsx # GFM markdown editor + viewer
│ ├── lib/
│ │ ├── ai.ts # Provider-agnostic AI client (Anthropic / OpenAI / Gemini)
│ │ └── … # datetime, routes, sorting, categories, etc.
│ └── views/
│ ├── HomePage.tsx
│ ├── HomeTeams.tsx
│ ├── TodosPage.tsx # Drag-drop lists + items, priorities, hide-completed, AI button
│ ├── AgendaPage.tsx # Today / This-week unified agenda
│ ├── AnalyticsPage.tsx # Local analytics dashboard (SVG charts)
│ ├── People.tsx # Person workspace + Timeline + 1:1 Mode tabs
│ ├── ProfilePage.tsx # Avatar, view/edit toggle, change-password tab
│ ├── Settings.tsx # Theme, PIN, AI key, backups & recovery
│ └── LoginPage.tsx / RegisterPage.tsx
├── public/ # PWA static assets (manifest, sw.js, icons)
├── docs/
│ ├── LAN-SYNC.md # Offline device-to-device transfer (portable backup + merge)
│ ├── HEALTH-CHECK-AND-ROADMAP.md # Living health score + phased roadmap
│ ├── electron-guide.md # Practical Electron tutorial walking through this codebase
│ ├── DEPLOYMENT-AND-POLICY.md
│ └── ENTERPRISE.md
├── build/
│ └── entitlements.mac.plist
├── scripts/
│ ├── generate-pwa-icons.mjs # sharp-powered icon rasteriser
│ └── patch-publish.mjs # Rewrites build.publish.owner at build time
├── .github/workflows/
│ ├── ci.yml
│ ├── release.yml # macOS signed + notarized release pipeline
│ └── pages.yml # PWA → GitHub Pages
├── vite.config.ts # Env-aware base path (Electron vs Pages)
├── package.json
└── README.md
If you want to go beyond "build it and ship it" and actually understand how the desktop side of Cadence works under the hood, there's a dedicated guide that doubles as a hands-on Electron tutorial:
docs/electron-guide.md — a practical Electron tutorial walking through this codebase: the two-process model, the preload bridge, IPC patterns, the security checklist, AES-256-GCM encryption at rest, the auto-update flow, the build pipeline, debugging tips and the most common pitfalls.
It's written so a developer who has never touched Electron can read it
top-to-bottom and end up confidently extending electron/main.cjs and
electron/preload.cjs.
For official releases this should not happen — they are signed + notarized. If it does, the file was likely tampered with in transit; download again from the releases page. For DIY (unsigned) builds, run:
xattr -dr com.apple.quarantine /Applications/Cadence.app
That usually means the renderer crashed before painting. The ErrorBoundary should display the stack trace; please file an issue with that text. In development you can open DevTools with ⌘ ⌥ I.
You're offline or behind a captive portal. Auto-update will retry on next launch; nothing to fix.
The service worker uses network-first for navigation, so a second visit when online should fetch the new shell. If you want to force-refresh: from the installed PWA, pull-to-refresh or close-and-reopen the app. From a browser, hard-refresh and clear site data once.
Passwords are not recoverable (they're stored as salted scrypt hashes locally). You can edit cadence-accounts.json in ~/Library/Application Support/Cadence/ and remove the user entry, then sign up again. Your workspace JSON file is named cadence-data-<userId>.json — keep it if you want to import it into the new account.
The lock screen has a Forgot PIN? Reset with account password link. It asks for your account password (the one you sign in with), rate-limits attempts, and on success clears the PIN so you can set a new one. No terminal commands required.
Don't panic — your data is still on disk. Open Settings → Backups & recovery. The page lists every candidate source: the current file, the legacy single-user file (leeadman-data.json, from pre-accounts builds), every automatic snapshot (50 rolling slots labelled launch / post-login / pre-save / pre-pwchange / pre-restore), and any orphaned per-user file from a previous account UUID. Pick the one with the right item counts → Restore. The current state is itself snapshotted as pre-restore first, so the operation is reversible.
Google retired the Gemini 1.x family in late 2025 and gemini-2.0-flash / gemini-2.0-flash-lite on 2026-06-01. Open Settings → AI Assistant, click the inline gemini-3.8-flash suggestion (or any other current model from the list), Save, then run Test connection again. Google limits the 2.5 models to users who already use them, so use the 3.x models for anything new.
| # | Feature |
|---|---|
| 1.1 | Markdown editor for notes, scratchpads, item bodies and 1:1 agendas |
| 1.2 | Feedback log (praise / coaching / concern item kind) |
| 1.3 | Recurring reminders (daily / weekly / monthly cadence) |
| 1.4 | Person Timeline — chronological feed per person, filterable by kind |
| 1.5 | Today / This-week unified Agenda page |
| 1.6 | 1:1 Mode — persistent agenda + archive + carry-over |
| 1.7 | ⌘K command palette + global search |
| 1.8 | Smart to-do list management (pin, archive, move, bulk ops, search) |
| 1.9 | Mobile PWA build + GitHub Pages deploy |
| 1.10 | AES-256-GCM encryption of the data file with key derived from password |
| 1.11 | Profile redesign — avatar upload, view/edit toggle, change-password with old-password verification |
| 1.12 | Drag-and-drop reordering for to-do lists (handle + drop targets) |
| 1.13 | Quick-schedule presets for tasks (Today / Tomorrow / +3h / Next Mon) |
| 1.14 | Analytics dashboard with SVG bar chart, per-team and per-person stats |
| 1.15 | LAN sync — Electron HTTP server with token auth + pair UI in Settings |
| 1.16 | Mobile drawer sidebar + iOS-safe input sizing |
| 1.17 | AI Assistant with BYO API key for Claude / ChatGPT / Gemini, per-task button, Markdown chat dialog, append-to-task |
| 1.18 | Backups & recovery — rolling 50-snapshot backups, refuse-to-overwrite guard, in-app recovery UI with one-click restore |
| 1.19 | Universal macOS DMG — single Apple-Silicon + Intel build, simpler distribution |
| 1.20 | Task priorities on lists and items + sort-by-priority / sort-by-due-date toggle |
| 1.21 | Item drag-reorder, hide / show completed, delete confirmation, multi-line task textarea |
| # | Feature | Why it matters |
|---|---|---|
| 2.1 | Cycle / Quarter scope for goals + progress % | OKR support: parent/child (Objective → Key Result), quarter tagging, progress with history. |
| 2.2 | Templates (1:1, skip-level, perf review, onboarding) | Apply a template to a new note/meeting and get a structured outline instantly. |
| 2.3 | Person attributes | Start date, timezone, location, level, manager (dotted-line), pronouns, skills. Auto-computed tenure. |
| 2.4 | iCal (.ics) export | Read-only feed so macOS Calendar / Google Calendar can subscribe. |
| 2.5 | Field-level merge in sync (last-write-wins per item) | Today's Pull / Push replace the whole snapshot; per-item merge would let two devices edit in parallel — the ETag conflict guard already prevents data loss, this would prevent the conflict in the first place. |
| # | Feature |
|---|---|
| 3.1 | Cadence heatmap (1:1 / feedback frequency per person, 12-week strip) |
| 3.2 | Tags + saved filter views (#growth, #blocker, "All overdue tasks") |
| 3.3 | Decisions log (team-level ADRs) |
| 3.4 | Kanban view for tasks (Today / This week / Later / Done) |
| 3.5 | Watch-folder mode — write notes as real .md files for Obsidian compatibility |
| 3.6 | Bulk operations on items (multi-select → mass reschedule / move / delete) |
| 3.7 | Per-team / per-person filter on the Analytics page |
Small, well-scoped follow-ups surfaced by the v1 production-readiness review. None block the MVP — they're listed here so the audit trail doesn't get lost between releases.
| # | Item | Why it matters |
|---|---|---|
| 4.1 | Toast action button support (e.g. "Undo" after a destructive action) | The Toast provider in src/components/ui/Toast.tsx already owns the lifecycle, an action?: { label, onClick } slot would let destructive flows replace window.confirm while still being safely reversible. |
| 4.2 | Settings bundle split | dist/assets/Settings-*.js is ~68 kB / 20 kB gzip — Sync, Cloud Sync and AI sub-sections could be lazy-loaded as siblings of the main page, dropping the initial Settings chunk by roughly half. |
| 4.3 | Cloud sync "remember passphrase on this device" toggle | Cleans up the TODO in src/lib/syncSession.ts (line 45). The passphrase already lives in sessionStorage — exposing the policy as a Settings toggle just makes it discoverable. |
| 4.4 | Auto-updater TLS pinning (defence-in-depth) | electron-updater already verifies the Apple-signed binary; pinning a GitHub Releases certificate fingerprint at the IPC boundary would catch a hypothetical MITM at the download step even if the signature check were ever bypassed. |
| 4.5 | Toast queue position preference | Bottom-right is sensible default, but power users might want top-right (closer to where they typically focus). One-line addition to the toast container. |
| 4.6 | window.alert → toast migration sweep | Done for v1 in Settings + RegisterPage; spot-check other route surfaces (TodosPage, NotesPage, dialogs) for any remaining native dialogs that should be toasts. |
| 4.7 | Manual note ↔ task linking (picker) | v1.40 auto-links via AI extraction; a "🔗 Link to a note" button in todo edit mode (with a small note picker, fuzzy by title) would let users connect tasks they typed by hand to a relevant note. The data model already supports it — only the UI is missing. |
| 4.8 | Note checklist → inline task conversion | When a Markdown checklist line (- [ ] …) is detected in a note, surface a hover-only "Convert to task" affordance on that line. Creates a single todo (in the user's default or last-used list) with sourceNoteId set and replaces the checklist line with a reference. Faster than running the full extractor for one-off items. |
Issues and pull requests are welcome. Please:
npm install, and run npm run dev.npm run typecheck — strict type-check (same gate as CI).npm run check — typecheck + unit tests in one shot.npm run build:web — Electron-targeted Vite build.npm run build:pwa — Pages-targeted Vite build.This repo ships agent context and a reusable Cursor rule "team" (AGENTS.md plus
.cursor/rules/: developer-expert, code-review-expert, frontend-expert,
technical-architect, devops-expert, test-expert) that steer AI agents toward
production-ready, zero-data-loss work. See AI agent rules & project context for
how they're built, how to use them, and how to reuse the setup in other projects.
The project was previously called Leeadman. The rename to Cadence touches every user-visible surface: window title, Dock, menus, manifest, on-disk file names (cadence-data-<userId>.json, cadence-accounts.json, …), localStorage keys and macOS appId (com.cadence.app).
If you have data from a pre-rename build on the same machine:
~/Library/Application Support/Leeadman/ (or the equivalent on Windows / Linux) into the new Cadence/ userData folder, renaming every leeadman-* file to cadence-*. The legacy folder stays in place as a safety net — feel free to delete it after confirming the migration worked./cadence/?source=pwa. The marketing landing now lives at /cadence/ and the app moved to /cadence/app/; an inline redirect in the landing's <head> bounces ?source=pwa traffic so existing PWA installs keep working without manual reinstall.leeadman-backup-*.json exported from the old build), use Settings → Backup → Import JSON in the new app — the importer reads the file contents, not the filename.CADENCE_PWA=1 is the canonical flag now, but LEEADMAN_PWA=1 is still accepted for one more release so old CI workflows don't break.LICENSE.Cadence is not affiliated with or endorsed by Google, Anthropic, OpenAI, or Microsoft. Mentioned trademarks belong to their respective owners.
MIT © Sercan Çelenk
See LICENSE if present, otherwise refer to the MIT License.
| After every successful save, a per-device fingerprint records how much content you had. On the next boot, if the loaded file is meaningfully smaller, an amber banner spells out the before/after counts and links straight to Backups & recovery — so "my data vanished" is caught before you panic. |
| 📦 Offline device transfer | Move a workspace between devices with no cloud and no network link between them: export a portable backup (a plain ZIP — it is not encrypted, so keep it private), carry it across (AirDrop / Files / USB), and merge it in additively. The merge keeps every local item, dedupes by id + content signature, protects local config (passphrase, active team), and is idempotent — re-importing the same file is a no-op. |
| 🚫 No telemetry | Zero network calls outside of (a) the auto-updater hitting GitHub Releases, and (b) the AI assistant hitting whichever provider you configured. Nothing else dials home. |
| 🏢 Enterprise / shared-device ready | Three onboarding presets (Personal / Work-Standard / Work-Strict) the user picks at first launch, plus an admin-deployable policy.json (cross-platform, 5-layer search) that wins over the user preset and gates Sync / AI / Export / Updates with defence-in-depth in the Electron main process. A separate "Cadence for Work" build flavor (npm run build:enterprise) ships a locked binary with its own app ID and update channel. See docs/ENTERPRISE.md and docs/DEPLOYMENT-AND-POLICY.md (updates, fresh installs, self-restriction). |
| 🛑 Off completely | No provider configured, no AI — the rest of the app behaves identically. AI is gated behind "is a provider configured?", so an offline workspace is the default, not a degraded mode. |
cadence-data-<userId>.json, AES-256-GCM, keyed by your account password).localStorage only (not encrypted) — use a low-budget key with usage limits.~/Library/Application Support/Cadence/ if you want to copy a backup to iCloud / a USB stick..zip) — one file on every platform: workspace, images and, on desktop, note version history. Import shows what is inside first and offers Add to what's here or Replace what's here.userData| 1.22 | Manual CI/CD — Release & Pages workflows manual-only and CI-gated; automatic version bumps via run_number |
| 1.23 | Polished light & dark themes — proper input contrast, focus rings, accent-aware hovers everywhere |
| 1.24 | PIN reset with account password — in-app, rate-limited recovery so you can never lock yourself out |
| 1.25 | Notes — resizable sidebar — drag the divider between list and editor (Pointer Events, 220–560 px, works with mouse / touch / pen) |
| 1.26 | Markdown formatting toolbar — Bold / Italic / Strike / H1–H3 dropdown / Bullet / Numbered / Task / Link / Inline code / Code block / Divider, with selection-aware insertions and ⌘B/⌘I/⌘K shortcuts |
| 1.27 | Notes sort modes + manual drag-reorder — Last updated / Last opened / Created / Title / Manual; per-row drag handle in Manual mode that respects the pinned/unpinned tier boundary |
| 1.28 | Notes — preview-by-default reading mode — each note opens in rendered preview; one click on Write flips into the editor and the new toolbar |
| 1.29 | Notes — strict per-view unlocking — viewing a locked note always re-prompts for the passphrase; the Remove notes passphrase action requires re-auth even with a warm session key (and uses a clearer padlock-with-slash icon) |
| 1.30 | Top-bar global search pill — Bootstrap-style search button in the header (with auto-platform ⌘ K / Ctrl K badge) that opens the existing command palette; the palette now indexes standalone notes too and deep-links to /notes?id=<id> on click |
| 1.31 | LAN sync — HTTPS by default — Electron server now runs TLS 1.2+ over a self-signed cert (RSA 2048 / SHA-256) persisted in cadence-sync-tls.json; auto-regenerated when the LAN IP list changes or expiry nears, 800-day validity to stay within Apple's hard cap. Fixes mixed-content / HTTPS-Only blockers between the GitHub Pages PWA and the desktop host. |
| 1.32 | LAN sync — QR-code pairing — host card renders a scannable QR encoding https://<lan-ip>:9787/?pair=<token>; on the phone the camera URL opens the bundled PWA from the host, the ?pair= handler stores the URL + token in localStorage and the device is paired with zero typing |
| 1.33 | LAN sync — bidirectional auto-sync + ETag conflicts — paired devices keep both sides in step in the background (push first, pull after) on launch / focus / online / visibilitychange; If-None-Match gives a free 304 when nothing changed, If-Match triggers a 412 when the host moved on, and a "Connected · synced X ago" badge with a Disconnect button shows live state in Settings |
| 1.34 | Cloud sync — Google Drive (end-to-end encrypted) — OAuth 2.0 PKCE sign-in with the drive.appdata scope, working on both PWA (popup flow) and Electron (loopback HTTP server flow); snapshots wrapped with AES-256-GCM (PBKDF2-SHA-256 KDF, fresh salt + IV per upload) before they ever leave the device; Drive sees opaque ciphertext only; provider-agnostic SyncBackend interface lets LAN and Drive coexist with a per-user "Auto-sync provider" toggle; sync passphrase lives in sessionStorage (cleared on tab close); Drive version field used as the etag for optimistic concurrency (If-None-Match-style short-circuit on pull, conflict detection on push) |
| 1.35 | Cloud sync — production-ready hardening — runtime OAuth client ID config (paste your own client ID into Settings, no .env or rebuild needed); first-time pull always pulls a baseline (never silently overwrites remote with an empty workspace); parseRemoteSnapshot shape guard refuses to apply malformed / empty payloads; localFingerprint decouples local dirty-tracking from remote ETag scheme (fixes Drive auto-push loop); retry + full-jitter exponential backoff on 5xx / 429 / network errors; inline conflict resolver with "Pull first / Override remote"; auto-clear passphrase on wrong-password; Settings surfaces the last background sync error so silent failures can't pretend to be "synced N ago" |
| 1.36 | First-run welcome tour — three-card onboarding modal explains the workspace, the two sync paths and the optional setup steps; appears only on first authenticated launch per account; suppressed automatically on returning devices (any device with an existing LAN pair or Drive tokens skips it); dismiss persisted to localStorage["cadence.tour.completed.v1"] per account |
| 1.37 | Test suite + CI gating — Vitest suite under jsdom covering snapshot crypto, sync algorithm, Drive backend (fetch-mocked, every path), OAuth token lifecycle, LAN client primitives and the remote-snapshot shape guard; CI workflow runs npx tsc --noEmit + npm test + npm run build:web + npm run build:pwa on every push and PR with concurrency cancellation; npm run check:env validates the build environment (Node version, required files, .gitignore safety, OAuth client-ID shape) as a release gate |
| 1.38 | To-do Markdown details — optional body per task with the same editor + toolbar as Notes; Add details on create, 📝 toggle per row, search matches title + body, AI assistant reads/writes details |
| 1.39 | Data-loss prevention UX — boot-time integrity banner (fingerprint vs loaded shape), enriched backup viewer (per-snapshot counts, archived-list / orphan-lock warnings, Reveal in Finder, restore confirmation), sidebar badges when tasks are hidden by archive, explicit "all lists archived" alarm on the To-dos page, orphan notesLock auto-cleanup on load |
| 1.40 | Note ↔ Todo cross-linking — tasks created by the AI extractor from a specific note keep a sourceNoteId back-reference; ✨ Extract tasks on a note pre-fills the dialog with that note's body; each linked task shows a tap-able 📝 chip that deep-links to /notes?focus=<id>; the note surfaces a "Tasks from this note" backlinks panel that deep-links the other way to /todos?focus=<id>, with smooth-scroll + 1.6s focus flash (reduced-motion safe); orphaned references (note deleted) render as a muted non-interactive chip so backup-restore can re-attach them |
| 1.41 | Account recovery codes — optional MetaMask-style 8 codes for local password reset; /recover flow; welcome tour + Settings; envelope only persisted after user confirms they saved codes; changing password clears old codes |
| 1.42 | Portable ZIP backup — cross-platform export/import (data.json + plaintext attachment sidecars); platform-aware backup UI (desktop: folder + ZIP; web/mobile: ZIP primary); legacy JSON and folder backups unchanged |
| 1.43 | Note version history (desktop) — automatic revision capture; browse/restore panel; included in full backup folder |
| 1.44 | Notes sidebar collapse — hide list panel on desktop for full-width editing; preference persisted per account |
| 1.45 | In-app user guide — /guide route; Settings, ⌘K, Welcome tour, and Electron Help menu links |
| 1.46 | Cross-account attachment import — portable .bin sidecars in ZIP/folder backups; salvage path for same-machine JSON import |
[task ↗](…)| 4.9 | Cross-link integrity audit + duplicate-extraction guard | (a) On startup, re-validate every sourceNoteId against the notes list and flag (not delete) orphans in a Settings → Diagnostics panel — useful when a sync conflict drops a note. (b) When re-running the extractor on a note that already produced tasks, surface them as "Already extracted" rows instead of re-suggesting near-duplicates. Both are quality-of-life polish; current behaviour is safe (orphans render muted; duplicates are user-rejectable). |