.Ember (DotEmber) puts the whole day in one Mac app. Write the journal,
plan the day beside it, and start the timer without leaving the window.
Everything is saved as Markdown files in a folder you choose.
The page below is about the design decisions and why they were made.
Why .Ember
Journal
.Ember started as an app for my own use. I had a separate app for
each part of the day, and the switching itself was the cost: I would
open three of them in the morning and have no interest left for the
fourth.
I didn’t need a more sophisticated single app. I needed one that fit
my day, and my own way of working through it.
Local-first
Sync settings
The first decision was that nothing lives anywhere but your own disk.
Everything .Ember writes is a Markdown file in a folder you pick. There
is no account to make and nothing to sign into, and if the app
disappears tomorrow the folder still opens in any editor.
The Markdown files are always the source of truth. The SQLite search
index lives outside the vault, in the app’s own container, and is a
cache the app can rebuild at any time.
iCloud sync is optional and runs through your own iCloud account.
There is no server of mine in between, and what syncs stays inside
that account too. RSS feeds come straight from
the publishers, and AI features use your own API key and are off by
default.
If you keep notes in another Markdown app. You can open the .Ember
vault in that app directly: the entries are plain Markdown files, and the
hidden folder the other app writes is one .Ember skips. To keep a folder
you already use as it is, there is an optional Markdown folder sync, and
the two folders have to be in different places. The engine mirrors Markdown
between them on a schedule you set, previews the first run before changing
anything, and preserves the losing side of any conflict. The other
direction - pointing .Ember at a folder another app already manages - is
not supported. See Can I use it with other Markdown
editors?.
ADHD in mind
Start your day
Because of my own ADHD tendencies, what I needed most was focus with
as little friction as possible. So there is one core feature, Start
your day, and everything else exists to support it. It is off by
default. Once turned on, the first thing that opens each day is a
full-screen journal and todo wizard. No sidebar, no tabs, no decision
about which view to be in: write first, then list what today is for.
Blocks group todos by context: Urgent, Routine, Project and Admin to
start, plus whatever you make, and a whole block runs straight into
the timer. Repeating todos come back each morning, and .Ember still
knows the difference between a renewed habit and something you typed
in yourself. The command palette (⌘K) is one keystroke from
anywhere.
Design
Todo Calendar
One screen, and the same blocks everywhere else. The left pane
holds the todo lists, the right pane is the week or month, and
scheduling is dragging a todo onto a day. There is no separate planner
view to keep in sync.
Timer and the menu bar
Those same lists carry over. A block you made here shows up in the
timer and the menu bar unchanged, with the same dot, name and
“done/total” count, so moving between features never means re-reading
the screen. Only the density differs between the Mac and the phone.
A todo's labelled options, open in the list
How a todo’s options are laid out. Every field has its own labelled
row. It was a single line first, the values trailing the title like a
sentence, which read well at a glance and worse every time after that.
So the labels came back.
The first shape on the left, the labelled rows that replaced it on the right
Light theme
Themes and accents. Dark, light or following the system, seven
accent presets, and a custom colour if none of them fits. Every preset
keeps a separate value per theme, so the accent clears WCAG AA contrast
in both, and each device keeps its own choice.
Mood tracker
Mood tracking. Each journal entry can carry a mood, set in one click
as you write it. Reports plots them across the month, so the trend is
there later without anything extra to fill in.
Notebooks are folders. A notebook is a directory, nested as deep as
you like, and a page is a file named for its subject, so Finder,
Spotlight and any Markdown editor show the same tree the sidebar does.
Rows drag into the order you want, and a deleted notebook comes back
whole.
Markdown that stays out of the way. The marks fade as you write,
and with Hide Markdown on they stay hidden until the caret returns to
them. Paste keeps the formatting it was copied with (bold, links,
lists) on the Mac and the phone alike.
Stack
This is my first Mac app, so the first version was built on the
stack I was already used to: Electron, React 19 + TypeScript,
Zustand, better-sqlite3, CloudKit JS. It reached 1.0.
Two things made me rewrite it in Swift. Packaging: sandbox-aware
code signing, CloudKit JS with no fetchRecordChanges so every sync
was a full query, Liquid Glass out of reach from a Chromium
renderer, and Swift code re-wrapped as Node modules just to be
callable. And the phone: with Electron, iOS means a second codebase;
with Swift, it is one more target on the same core.
The rewrite:
- SwiftUI, with AppKit where the system demands it: the
menu bar extra, the calendar grids, the sidebar, drag-and-drop.
Markdown editing via CodeMirror 6.
- SwiftData - an auxiliary cache; the Markdown files are still
the source of truth.
- SQLite + FTS5 - full-text search; the index rebuilds itself if
the cache is ever lost.
- CloudKit (native) - delta sync through the private DB. Vault
files ride iCloud Drive; the optional Markdown folder mirror
covers any other folder.
- StoreKit 2 - the 14-day trial and the one-time purchase.
- EventKit, plus a Google Calendar adapter - multi-account,
with Google Tasks.
- Liquid Glass on macOS 26+, with an
NSVisualEffectView
fallback down to macOS 15.
- 19 local Swift Package Manager modules in a tiered graph - a
UI-free domain core, enforced at compile time. The iOS app
consumes the same packages.
App Store only, sandboxed. And the foundation the iPhone and iPad app
sits on, without a second codebase.