You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There's no way to organize blocks into collapsible sections. Long notes become hard to scan, and related blocks can't be visually grouped. Users want to bundle blocks under a named group, collapse it to hide contents, and move both the group and its children naturally.
Context
Blocks are currently stored as a flat []block.Block list in the editor model (internal/editor/editor.go:49). There is no nesting — every block is a peer. Block operations (insert, delete, merge, swap) all operate on flat indices. The parser (internal/block/parse.go) and serializer (internal/block/serialize.go) process blocks linearly. The gutter system (internal/editor/render.go:347-361) renders a fixed-width label per block type.
Add Group to BlockType. Introduce a Children []Block field on Block (only populated for groups). The editor's flat []block.Block stays flat at the top level — a group block's children live inside it, not as siblings.
typeBlockstruct {
TypeBlockTypeContentstring// group title for Group blocksChildren []Block// only for Group typeCheckedboolCollapsedbool// only for Group type — persisted in markdown
}
Markdown format
Use HTML <details> / <summary> — it's valid markdown, renders in GitHub/most renderers, and round-trips cleanly:
<details>
<summary>Group title</summary>
- item one
- item two
</details>
Collapsed state: <details> (closed by default) vs <details open> (expanded).
Editor behavior
Rendering: Group header rendered with a distinct gutter label ("gr") and visual container (left border bar for children, dimmed ▸/▾ collapse indicator). Collapsed groups show ▸ Group title (N items).
Collapse toggle: Keybinding on group header (e.g., Enter or Tab when cursor is on group header, or Ctrl+E to mirror preview toggle).
Cursor navigation: Collapsed children are skipped — Up/Down jumps over them. Expanding restores normal traversal.
Move within group (Alt+Up/Down): Reorders children among siblings. Moving past first/last child promotes the block out of the group.
Move group (Alt+Up/Down on group header): Moves the entire group (header + all children) as a unit.
Creating groups: Via / command palette → "Group". Starts as empty group; blocks can be moved into it.
Adding blocks to group: Moving a block down into a group's last position (or up into its first) adopts it. Also support creating new blocks inside a group via Enter.
Deleting groups: Backspace on empty group header deletes the group. If group has children, unwrap them (promote to siblings).
Nesting limit: 1 level for v1 — groups cannot contain groups.
Palette
Add paletteItem{Label: "Group", Type: block.Group, Icon: "▸"} to the palette items list.
Tasks
Add Group to BlockType, add Children, Collapsed fields to Block
Update parser to detect <details>/<summary> and produce Group blocks with nested children
Update serializer to emit <details> format, preserving collapsed state
Add round-trip tests for group blocks (empty, with children, collapsed, nested content types)
Add group to / command palette
Render group header with gutter label gr, collapse indicator (▸/▾), and title
Render expanded group children with left border bar indent
Render collapsed group as single line with item count
Implement collapse/expand toggle keybinding
Skip collapsed children during cursor navigation (Up/Down)
Problem
There's no way to organize blocks into collapsible sections. Long notes become hard to scan, and related blocks can't be visually grouped. Users want to bundle blocks under a named group, collapse it to hide contents, and move both the group and its children naturally.
Context
Blocks are currently stored as a flat
[]block.Blocklist in the editor model (internal/editor/editor.go:49). There is no nesting — every block is a peer. Block operations (insert, delete, merge, swap) all operate on flat indices. The parser (internal/block/parse.go) and serializer (internal/block/serialize.go) process blocks linearly. The gutter system (internal/editor/render.go:347-361) renders a fixed-width label per block type.Key files:
internal/block/block.go— BlockType enum, Block structinternal/block/parse.go— markdown → blocksinternal/block/serialize.go— blocks → markdowninternal/editor/editor.go— model, block ops, move up/down (swapBlocks), backspace merge (mergeBlockUp)internal/editor/render.go— gutter labels, block renderinginternal/editor/palette.go—/command palette itemsProposed approach
Data model
Add
GrouptoBlockType. Introduce aChildren []Blockfield onBlock(only populated for groups). The editor's flat[]block.Blockstays flat at the top level — a group block's children live inside it, not as siblings.Markdown format
Use HTML
<details>/<summary>— it's valid markdown, renders in GitHub/most renderers, and round-trips cleanly:Collapsed state:
<details>(closed by default) vs<details open>(expanded).Editor behavior
"gr") and visual container (left border bar for children, dimmed▸/▾collapse indicator). Collapsed groups show▸ Group title (N items).EnterorTabwhen cursor is on group header, orCtrl+Eto mirror preview toggle).Alt+Up/Down): Reorders children among siblings. Moving past first/last child promotes the block out of the group.Alt+Up/Downon group header): Moves the entire group (header + all children) as a unit./command palette → "Group". Starts as empty group; blocks can be moved into it.Palette
Add
paletteItem{Label: "Group", Type: block.Group, Icon: "▸"}to the palette items list.Tasks
GrouptoBlockType, addChildren,Collapsedfields toBlock<details>/<summary>and produceGroupblocks with nested children<details>format, preserving collapsed state/command palettegr, collapse indicator (▸/▾), and titleTest plan
<details><summary>Title</summary>with children → Group block with correct children<details>markdownSerialize(Parse(md))preserves group structure and collapsed state<details>parses normally (no groups)Scope
Type: feature
Size: large