LifeLab · lens ↔ outliner

They're the same tree. The only difference is how a node gets its children.

You're right, and it's worth stating precisely: an outline node is a lens whose query is "the blocks I contain" — implicit, positional, single-parent. A lens is that same container with the query written down, so membership is decided by a predicate instead of by where you pressed tab. Outliners do containment by position; lenses do containment by criteria. Everything else — fold, zoom, drag, indent — is shared, and should stay shared.

one tree · three child-producersstored · referenced · computed — a node's badge is its contract

*Launch weekstoredtyped here · 3 children
*Finalize release notesposition 1
*Sign the macOS buildposition 2
*project/LifeLab{todo}referenceda window · 6 children
*Record the Lens walkthroughlives in project/LifeLab
*:blocked:computeda predicate · 2 children
*Wait on notarisationlives in person/Ada
*Screen recording rightslives in …/docs
storedindent
children
Literally nested in the file. Order is authored.
drag in
Moves the text. This is the outliner's only verb.
×
Deletes.
is
An ordinary outline node.
referenced![[page]]
children
Another page's blocks. Order is that page's.
drag in
Re-homes — the block moves to that page, seen from here.
×
Removes the window. The page survives.
is
An outline node whose subtree lives elsewhere.
computed{todo :tag:}
children
Whatever matches, from anywhere. Order is sorted.
drag in
Make it match — edits attributes until it qualifies. Never moves it.
×
Removes the query. Nothing is deleted.
is
An outline node whose subtree is derived.

This is the whole model. One tree, one set of gestures, three answers to "where do my children come from" — and the badge/gutter on each node tells you which, which is exactly the information that makes × and drag unambiguous. The nesting-darker idea was trying to encode depth; what actually needs encoding is which of these three a node is.

every outliner verb, translatednothing new to learn — the same keys, one step more general

outlineron a stored nodeon a referenced / computed node
fold tabHide the subtree.Identical. Fold is fold; a lens is just a node with children.
zoom That node becomes the root of the view.That lens becomes the whole view — same lens, new frame. This is the tab-killer: zoom is already the outliner's answer to "open in a new window", and it works unchanged on a query.
indent tabReparent onto the sibling above.Under a computed node there is no position to take, so indent means acquire the predicate — the block gets the tag/status that puts it in the set. Same keystroke, same intent ("belong to that"), different mechanism.
move ⌥↑↓Reorder siblings.Disabled inside a computed node — order is the lens's sort:, not yours. The lens header is where you change it. This is the one place the metaphor genuinely breaks, and it should be visible: no drag handles on computed rows.
new sibling Insert a block after this one.The ghost-add row, pre-filled to qualify — typing under {todo :urgent:} gives you a TODO tagged :urgent:. The predicate becomes a template.
breadcrumbPath of stored ancestors.Path of frames — the lenses you zoomed through. Mixed freely: journal/07-22 › project/LifeLab › :blocked:.

The translation table is the argument. Five of six verbs survive untouched, which is the sign the abstraction is right rather than clever. The sixth — manual reordering — fails for a good reason, and failing loudly (no handle, no drop line) teaches the model better than any label would.

where this leaves the designconsequences worth acting on

01

A lens header is a bullet. Not a panel, not a card, not a section rule — a node with a badge. That settles the aesthetics question: lenses shouldn't look like containers, they should look like outline nodes that declare their source. Same indent, same fold, same hit target.

02

Indent already encodes depth, so the darker-nesting idea has nothing left to say. Spend the visual channel on the badge/gutter — stored, referenced, computed — because that changes what your keys do.

03

The page view is the root node. Every outliner has an implicit root; here it's a lens like any other, which is why the title can carry filter chips and why reframing is an edit rather than navigation.

04

Naming is the actual upgrade. An outline node's query is unwritten and unnameable, so it can't be reused, shared, or reasoned about. ![[page]]{todo} is the same containment made quotable — that's the whole delta, and it's why lenses compose and folders never did.

the honest tension

A block can now appear in many places, so "where is it?" stops having one answer. Outliners are lovable partly because position is identity. The rule that saves it: exactly one producer is stored — that's home, it's always shown in the rail when you're not looking at it from home, and every other appearance is explicitly a window or a match. Multiplicity is fine as long as home is never in doubt.

what I'd build first

Make the three producers a real union in the model, not three renderers — one node with a children_source of stored | ref | query. Then fold, zoom, drag, breadcrumb and keyboard nav are written once, and the lens stops being a feature bolted onto the outliner and becomes the thing the outliner was a special case of.