flat blocks: what to do

decision doc · 2026-08-15 · follows the flat-blocks discussion + Lens Spec Draft 3 review · prototype: Flat Blocks Prototype.html

Position is layout; names are data. Where a block sits confers nothing a query can see. Structure a query can see must be written on the block itself.

1 · Schema — ship this, nothing more

blocks: id · page_id · order · stars · text · tags[] · metadata
span(i):      scan forward  while stars > stars[i]     // "subtree"
context(i):   scan backward, first block per smaller stars  // "ancestors"

Both run in the app layer on page order. The database never answers a hierarchy question.

2 · Operations — the collapsed-unit rule

Collapsed = acts as a unit. Expanded = acts as a single line. What you see is what you act on.

opexpanded blockcollapsed block
collapse spacehides its spanshows it again
indent tab / ⇧tab±1 star, this line only±1 star on every line in the span
move ⌥↑ ⌥↓this line hops over its neighborthe whole span hops as one slice
deletethis line onlythe whole span
dragdon't build it — swap-with-neighbor covers reordering; collapse-then-move covers subtree moves

Orphan spans (a ** with no * above after an edit) render as-is, indented. Never auto-normalize stars — indent is content, and the user typed it.

3 · Tags — no inheritance in storage, span context at read time

the pitch, twice:  don't organize by dragging into trees —
                   organize by naming.
                   indent for reading; tag for finding.

4 · Lens Spec Draft 3 — three sentences to edit

The lens grammar already contains no hierarchy predicate — no parent:, no under:; depth: is page nesting. Only the ownership wording assumes a tree:

Result rows are render output — they never occupy positions in the flat array, so “yours above (), borrowed below ()” costs nothing.

5 · One honest gotcha to design for

A user indents a block under * Project Lifelab and expects :project/lifelab: to find it. It won't — position confers nothing. Mitigations, in order:

6 · The one positional tree you keep

Pages nest (depth:, subpages) and that stays — pages are containers users deliberately file into, the one place traversal is paid for. The coherent story: pages nest · tags namespace · blocks just sit in order.

7 · Do / don't

dodon't
flat array + stars intparent_id, closure tables, recursive CTEs
span scans in the app layerhierarchy queries in the DB
collapsed-unit semantics everywheredrag & drop of subtrees
tags per block, span context at readmaterialized tag inheritance
explicit :project/*: prefix chipsimplicit ancestor-tag matching
render orphan spans as typedauto-normalizing stars