LifeLab · model · answering the outliner question

Yes to one outline. No to "subtree copy".

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.

the restatementthree sentences · replaces "pages, blocks, and a lens block type"

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.

one outlinethe / in a page name is indentation · blocks and pages are siblings in the same tree

projectpagenamed, addressable, no content
LifeLabpageproject/LifeLab — the name is its path
*Finalize release notesblockcontent, ordered, owned here
*Sign the macOS buildblock
*![[:urgent:]]{todo}lenscontent is an expression
*Verify the Windows installermounted · lives in journal/2026-07-22
*Renew renter insurancemounted · lives in home
docspagea child node — not a "subpage section"
*Replace the README heroblock
frontendpagefolded · 6 inside
solid 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.

why not "copy"two failures — one semantic, one structural

✗ copy — semantic failure

  • Copy implies a second thing that can diverge. But editing inside a lens must write straight through to the original — there is no second thing.
  • Copy implies a moment. Results are recomputed every paint; there's no snapshot to be stale.
  • Anyone implementing "copy" will build reconciliation, and reconciliation is where the sync bugs live.
  • It breaks the invariant: a copy is owned by the lens. Deleting the lens would then delete something.

Say mount, project, or borrow. Never copy, never contain.

✗ subtree — structural failure

  • A lens's result is usually not a subtree. ![[:urgent:]]{todo} returns nodes from six parents at four depths — a forest, not a branch.
  • Mounting a forest as children of the lens fabricates a parent-child relationship that doesn't exist in the document.
  • That's fine as render — but it means the mounted level is flat, and each row must carry its real home. Which is exactly the rail.

So there are two mount shapes, and the lens's source decides which. ▾

faithful mountsource is a page · depth preserved
*![[project/LifeLab/docs]]
*Replace the README hero
*pick a screenshotreal child
docs/apireal child page

One branch, borrowed whole. Real depth shows. Structural verbs worktab, drag, reorder all act on the true parent, because here the render parent is the true parent.

flat mountsource is a predicate · depth is lost, home is shown
*![[:urgent:]]{todo}
*Verify the installerjournal/07-22 · depth 3
*Renew renter insurancehome · depth 1

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.

what this deletesthe test of a good re-framing

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.

the five things to decide before writing it downeach has a proposal · none is a blocker

edgethe questionproposal
sibling orderPage-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.
addressabilityIf 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 verbsWhat 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.
cyclesA 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 nodeThe 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.