is the page a lens?
Half yes, and the half that's no is load-bearing. Split the word "page" into the two jobs it's doing:
| page as home | page as face | |
|---|---|---|
| what it is | where blocks live — page_id | what you see when you open it |
| total? | yes — every block has exactly one | no — it can show foreign refs, hide sections |
| order | manual, stored (page_order) | whatever the view says |
| a lens? | ✕ never. It's identity, not a query. | ✓ already is one — §3 defines it as a set expression |
The page's face is a lens. The page's home is what lenses bottom out in.
You're not discovering a coincidence — Draft 3 §3 already writes the face as a query (PageFace = PageRiver + SUBPAGES + LINKED) and already lets you edit it as one (![[mcp]]{-linked -subpages} is in the spec's own examples). The face has been a lens since Draft 3. What's missing is admitting it in the UI, and refusing to let it swallow the home.
1 · The root lens is already on screen
That {filter} chip next to "Home" in the current build is a collapsed lens line. Unfold it and the page header is a lens line whose base is locked.
The base is the one part you can't edit in place — editing it is navigation (:e other-page). That single distinction is what keeps this from collapsing into nonsense: a page whose base is a query is a saved search, and a saved search cannot be a home, because the blocks inside it live somewhere else. Keep the base literal and the whole idea is safe.
2 · What it buys, concretely
- Mod+\ stops being special. Grid is no longer a page-level mode with its own plumbing; it's the word
gridin the root lens line. Which means table, board and timeline become page-level views for free — the exact thing I said was awkward two docs ago is now one word. - Journal views collapse into grain. "Cycle journal view" = day/week/month =
grain:on the root lens. Your monthly aggregation and Month Lenses stop being a separate feature and become the root lens reduced. One less subsystem. - SUBPAGES and LINKED stop being chrome. They're two sibling lenses over the same base, which is why
-linkedalready works on them. Their headers can carry the same chip rail as any region. - The write rule finally terminates. Lens writes are hard because a lens has no home; the root lens's base is a home. So: "a lens writes to its base; the root lens's base is the page." That's the base case §6 has been missing — pages are the recursion's ground, not another rung.
- One chip rail, one fold, one filter grammar, at page level and region level. The header's
{filter}and a region lens line become the same component with different bases.
3 · The layering that makes it coherent
page_id and one manual order key. Storage fact, not a view. Not a lens.Read upward and the recursion is well-founded: views draw lenses, lenses resolve against homes, homes partition the notebook. Read the other direction and it's the mistake to avoid — if a home can be a lens, a block can have no home, and the invariant that every block is findable in exactly one place is gone.
4 · Three invariants this must not break
Spec rule §filters says an invalid predicate must never broaden a result. At the root the danger runs the other way: a typo that returns nothing looks exactly like a page that lost its contents. So the root lens's failure mode is the full river, loudly marked — never an empty page.
The single biggest UX risk of a filterable page: someone leaves a filter on, opens the page tomorrow, and concludes their notes are gone. Hidden count in the header, always. And the root lens's filter should default to per-visit (the summon rung), persisting only when explicitly pinned.
Third: the root lens may narrow, reorder and redraw — it may never move a block's home. Dragging inside a filtered or grouped page view writes keys (board) or the manual order key (river), never page_id, unless the drop target is another page's face. Same discipline as the grid's position problem.
5 · Where it genuinely doesn't hold
- Manual order isn't a query result. The river's order is stored, not derived, so the root lens needs
sort:manual— the one sort Draft 3 defers, and the only one that reads from a stored overlay. It's not a lens feature; it's the home leaking through the lens, and it should be spelled as such. - Totality. Lenses are partial and overlapping by design; a home is a partition. Those are different maths and no amount of syntax makes them the same object.
- Perf. "Every page render is a query" is fine only if
base = this pagehas a fast path (apage_idindex), not the general filter pipeline. Otherwise you've made opening a page as slow as running a lens — and lens being slow is already on your TODO list. - Creation. ⏎ on an empty page must work before any lens resolves. The root lens can't be on the critical path for typing.
6 · Verdict
| adopt | The page face is the root lens. Unfold the {filter} chip into a real lens line with a locked base. One grammar from page to region. |
| adopt | Views are words in that line. grid/table/board/timeline/grain: at page level for free; Mod+\ becomes sugar for an edit. |
| never | A page's home is not a lens. The base stays literal; a page based on a query is a saved search and cannot own blocks. |
| guard | Root failure = full river, marked. Hidden count always visible. Root filters per-visit by default. |
The satisfying part: this makes Lens Is Not a Container literally true at every level. The page is the container. The lens — including the root one — is only ever the window.
7 · Open
- Is the root lens line stored in the page, or is it visit state? The commitment ladder already answers this for regions; the root probably wants the same rungs, with pin being the rare case.
- Do journal pages get a default root lens? A day page wants
grain:none, a month pagegrain:day— is that a page property, or derived from the page's name/type? - Can the root lens have a base of two pages? That's the workspace-panes question in disguise, and I'd keep it a no (§3.5 already forbids it for regions).
- Does
-subpagesat the root mean anything different from hiding a section? If not, the two toggles should be one.