LifeLab · model · answering the outliner question
Making the page name the root node is a genuine simplification — it deletes concepts rather than adding them, and it's the move that finally kills SUBPAGES for real. But copy is the wrong word for what a lens's children are, and if it survives into the codebase you'll get a divergence bug and a sync bug. The right word is mount: the lens borrows nodes that live elsewhere, one indent deeper, for the duration of the render.
01The vault is one outline. Every node has a parent, an order among its siblings, and children.
02A node is a page if it has a name, a block if it has content, and a lens if its content is an expression. Same struct; one field decides.
03A lens has no children of its own. It mounts its results as if they were children, one indent deeper, borrowed for the duration of the render.
Sentence 2 is the part that pays: "lens is a type of block" stops being a type. It's a block whose content happens to be an expression — the same way a block whose content starts with TODO is a task without being a Task class. One less node kind, one less migration, one less place to special-case. And sentence 3 is just the invariant from the previous doc, stated structurally: mounting ≠ owning.
/ in a page name is indentation · blocks and pages are siblings in the same treesolid rule owned children — this node is their parent
dotted rule mounted results — borrowed, parent is elsewhere
⏎ on any node makes it the root
What just happened: docs sits in the outline exactly where its author put it — between a lens and a folded sibling — instead of being herded into a footer section. Page-nodes and block-nodes are siblings, so tab on docs re-parents a page and tab on a block re-parents a block, with one implementation. And a page's own blocks are simply its children, so "the page river" isn't a concept anymore either.
✗ copy — semantic failure
Say mount, project, or borrow. Never copy, never contain.
✗ subtree — structural failure
![[:urgent:]]{todo} returns nodes from six parents at four depths — a forest, not a branch.So there are two mount shapes, and the lens's source decides which. ▾
One branch, borrowed whole. Real depth shows. Structural verbs work — tab, drag, reorder all act on the true parent, because here the render parent is the true parent.
A forest levelled into one rank, ordered by sort:. Structural verbs are off — you can't tab a node whose neighbours aren't its siblings. Drag still works, but it means re-home or make it match, never reorder.
This distinction is worth naming in the model, because it's the honest source of the rule users will feel: indent works inside a page lens and not inside a tag lens. Better to derive that from "is the render parent the real parent?" than to explain it case by case. It's also the same predicate that governs ×, add, and reorder — one question answers four affordances.
lens block type — a block whose content is an expression. No new kind, no migration.
SUBPAGES section — child page-nodes, in place, foldable, author-ordered.
PageRiver / PageFace — a page's children. That's the whole definition.
page header surface — the root node's own content, which is where its filter expression lives.
page view vs block zoom — one operation: set the root.
tabs — zoom is re-rooting, and the path back is the ancestor chain you're already storing.
LINKED survives, but demoted: it becomes an ordinary lens the root node happens to carry by default — ![[:this-page:]] — which means you can filter it, move it, fold it, or delete it like anything else, and it stops being a fixed footer. That's the same trick as the header filter: a default lens, not a special surface.
| edge | the question | proposal |
|---|---|---|
| sibling order | Page-nodes and block-nodes are siblings — who orders them? | One order, authored. A page-node takes a position in its parent's page_order like any block. Alphabetical becomes a view (sort:name), never the storage. |
| addressability | If they're all nodes, can I link to a block? | Named nodes are addressable; content nodes are addressable once they earn an id. Keep that asymmetry visible — it's the difference between a page and a block, and it's the only difference. |
| structural verbs | What does tab do inside a mount? | Enabled iff render parent == real parent. Faithful mounts: full outliner. Flat mounts: content editing and drag-to-rehome only. One predicate, four affordances. |
| cycles | A mounts B mounts A. | Already in the spec: budgeted, cycle-safe descent. In one outline it's cheaper to detect — you're walking an ancestor chain you already have. |
| occurrence vs node | The same block appears twice on screen. Whose cursor? Whose fold? | Content is per node; view state is per occurrence. Fold, selection, and scroll key off the render path, never the node id — and never persist. This is the one real implementation tax, and it's unavoidable in any transclusion system. |
The one I'd nail down first is occurrence vs node, because it's the thing that will be quietly wrong for months if the fold map is keyed by node id — you'll fold a block in one lens and watch it fold everywhere, and it'll feel like a haunting rather than a bug.