LifeLab · design charter

You're not lost because the app looks wrong. You're lost because the port kept the aesthetic and left the rule that generated it behind.

The web build's identity was never “monospace and org syntax.” Those were symptoms of one commitment: the document is the interface — structure, data, and program all made of the same stuff, editable in place. CodeMirror made that free, so the look and the rule were the same thing and you never had to state the rule.

Native gave you real windows, real menus, real tabs, real system pickers. Those are chrome — a second substance the web build didn't have. Two substances with no stated boundary is exactly what “lost” feels like: every decision becomes a coin flip, because there's no principle to lose the argument to. The fix isn't more mono. It's writing the rule down, then letting it decide the coin flips for you.

1 · the sentence if you can't fit it on one line, you'll keep drifting

LifeLab is a personal wiki where every surface is a block you can edit, query, and program — and the native app makes it a first-class citizen of your Mac.Two halves on purpose. The first half is what LifeLab is (unchanged from the web build — this is your identity). The second is what this app is for (new — this is its purpose, and the only reason to build native instead of shipping a URL).

Test it against your own screenshots: tabs pass (a tab set can be a block). Nested lenses pass (a query in the document). The `{filter}` placeholder in the title passes. A native date picker passes — it edits a block's property. A sidebar file tree would fail, which is why it never felt right.

2 · four commitments the identity. non-negotiable; everything else is style

01

Everything is a block

No privileged UI objects. Page titles, lenses, tab sessions, sidebar contents, settings — all blocks, in the same tree, with the same verbs.

→ so: `* Home` stays a block, not a title bar

02

Every view is a visible query

A view is never a hardcoded screen. It's a lens you can read, edit, nest, and save — including the views the app ships with.

→ so: “day/week/month” are saved lenses, not modes

03

Every behaviour is yours

Hooks and Lua aren't a power-user feature, they're the mechanism the app's own features are built from. Nothing the app does should be unreachable to the user.

→ so: every menu item maps to a callable command

04

Text is the truth

Plain text/org is the substrate; PowerSync, indexes and caches are derivatives. If the app died tomorrow the notes are still complete and readable.

→ so: no state that exists only in the UI layer

What this buys you when you're lost: a feature request now has a home or it doesn't. “Can we add a kanban view?” → commitment 02: it's a lens with a group-by, not a new screen. “Can tabs sync across devices?” → 04: sessions are blocks, so yes, automatically. “Can we add a formatting toolbar?” → 01 + 04: no, because it implies markup the text can't hold.

3 · seven laws the aesthetic, derived — each one testable in a screenshot

i
Mono is content. SF is app.
The one rule that regenerates the whole web-build feel without giving up a single native surface. Content-derived tokens stay mono even inside chrome — a tag name in a menu, a page slug in a tab, a count in the modeline.fails now: mono toolbar, sans sidebar header, blue system tint
ii
Position carries structure. Colour carries category.
Depth, nesting and containment are expressed by indent and rule; colour says what kind of thing this is. Ordinal information in a tint channel always clamps out.fails now: lens depth 1–4 as four near-identical greys
iii
One accent, one meaning.
Violet means “a query / a lens / the active view” — nothing else. Not decoration, not every hover. Set the system accent to it so native controls stop arriving in Apple blue.fails now: violet on active tabs, blue on date selection
iv
Borrow native surfaces; never imitate them.
Real menus, real pickers, real window management — get them free, tint them, put mono content inside them. But never draw a fake macOS control, and never re-implement one you could borrow.
v
Every row of chrome must change when you switch tabs.
If a bar shows the same thing on every page, it's navigation and belongs in the tab bar, the menu bar, or the palette. This law alone resolves the three-Homes problem.fails now: nav row is static across tabs
vi
Density is the feature; measure is the limit.
Stay tight and information-rich — but cap the text column (~72–80ch) and anchor metadata to the content edge, not the window edge. Density without measure is just a wide table.fails now: tags right-align to the window at any width
vii
Nothing is hidden that isn't sayable.
Any state a user can create by clicking must be visible as text and reachable from a command. Fold state, applied lens, pinned tabs, session — all inspectable, all scriptable. This is what makes the app feel yours rather than merely fast.

4 · purpose: why native at all the half of the story you haven't built yet

Right now the native app's answer to “why not just the web app?” is “it's faster and right-click works.” That's a reason to build it, not a reason to want it. Native's real gift is that your wiki stops being a place you visit and becomes something the rest of the Mac can talk to. Every item below is impossible in the web build and cheap for you now:

capture — get things in without arriving

  • Global hotkey quick-capture panel that writes a block to today, closes, and never shows the app. The single highest-value native feature for a life-logging wiki.
  • Services / Share extension: select text anywhere → “Add to LifeLab”, with the source URL as a property.
  • Drop anything on a tab — file, URL, image, mail message — becomes a block with metadata attached.
  • Menu bar item showing today's TODO count; click to capture.

reach — let the OS call your Lua

  • Shortcuts actions + AppleScript: “append block”, “run lens”, “open page”. Your Lua layer already is the API; expose it.
  • Real files: a folder on disk, visible in Finder, versionable in git. Commitment 04 made literal.
  • Notifications from hooks: a scheduled block can actually notify you. Org agenda that reaches out.
  • Multi-window + splits — the thing you built tabs for. Two windows, different sessions, both synced.

Pick one and ship it next. Quick-capture is the answer. It's a day of work, it's the feature people describe to friends, and it proves the purpose half of the sentence: LifeLab is where your day goes, without you going anywhere.

5 · say no to these each one is a plausible good idea that would cost you the identity

  • A persistent file-tree sidebar. Breaks 02 (structure becomes a fixed hierarchy instead of a query) and eats the density you're known for. Search + lenses + tabs already cover it.
  • A rich-text formatting toolbar. Breaks 04 — implies markup the text can't round-trip. Org syntax typed inline is the feature, not the friction.
  • A free-canvas / whiteboard mode. Positions aren't text. It's the most common way apps like this lose their thesis.
  • Theme marketplace / 12 accent colours. Breaks 03. One accent with one meaning is worth more than infinite customisation.
  • An AI chat sidebar. If AI belongs here it's a lens or a Lua function that writes blocks, inspectable and re-runnable — not a chat pane bolted to the right edge.
  • Hand-drawn context menus, pickers, or tooltips. Breaks 04. You already made the right call; don't regress it for pixel control.
  • Collaboration / sharing / multiplayer. Not now. “Personal” is in the first sentence; PowerSync is for your devices.
  • Onboarding tour. The cheatsheet and the menu bar are your teaching surfaces. A wiki that needs a tour has a modeline problem, not a tour problem.

6 · the next four weeks in order. don't reorder — each phase makes the next cheaper

week 1coherence
Apply laws i–iii and vi as a pure styling pass: violet system accent, mono/SF boundary, capped measure with metadata on the content edge, lens indentation with tint clamped at depth 2, hide unfilled {filter}. No architecture, no new features.goal: the app looks decided rather than ported
week 2chrome
Apply law v — collapse the three Homes. Ship option C (slug tabs · path modeline · prose title) on day one, then move to option A (nav row merged into pinned tabs, second row becomes a real modeline showing lens + counts + sync). Prototype the bottom-modeline variant behind a preference.goal: three bars, three jobs, one word each
week 3purpose
Global quick-capture, drop-onto-tab, menu-bar TODO count. This is the week the app stops being a port and starts being a Mac app.goal: something you can only do because it's native
week 4compounding
Sessions as blocks + the tabs.* Lua API + on_tab_focus. Tabs become data, which means workspaces become notes — commitment 01 applied to your own chrome, and the story that makes tabs interesting rather than merely expected.goal: the new UI feature obeys the old thesis

7 · two questions to keep on a sticky note

“Would this still make sense if the whole app were a text file?”Catches drift toward chrome-only state, positional UI, and hidden magic. If the answer is no, either it's wrong, or you're missing the text form of it.

“Could the user have built this themselves with a lens and a hook?”Catches special-cased features. If yes, ship it as a lens and a hook — then it's an example, a template, and a feature at once.

You said the native app “traded the styling away for practicality.” It didn't have to be a trade, and mostly it wasn't — the buffer is intact. What you actually traded away was the explanation. Write these two questions and the four commitments into the repo's README, and the next fifty decisions stop feeling like coin flips.