Problem
There's no way to change a block's type once it has content. The / palette only opens on empty blocks (ta.Value() == ""), so users who want to turn a paragraph into a heading — or a bullet into a quote — must clear the block, change its type, then re-type the content.
This is a common editing operation (realize a paragraph should be a heading, promote/demote headings, convert to a list after drafting). The infrastructure already supports it — applyPaletteSelection preserves content for all non-Divider types — but the opening gate blocks it.
Context
- Palette open condition (
internal/editor/editor.go:1184-1188): requires ta.Value() == "", ta.Line() == 0, ta.LineInfo().ColumnOffset == 0
applyPaletteSelection (internal/editor/editor.go:729-743): already changes type and preserves content; only clears content for Divider
- Backspace at pos 0 already converts lists → paragraph, showing the pattern exists for non-empty type changes
- Palette filtering, navigation, and selection all work independently of content state
Possible approaches
Approach A: Extend / trigger to position-0 on non-empty blocks
- How: Remove the
ta.Value() == "" gate. Keep the position-0 requirement so / mid-sentence still types a slash.
- Pros: Single mental model ("go to start of block, type
/, pick a type"). Mirrors Notion. Minimal code change.
- Cons: Need to ensure
/ isn't inserted into the block text when triggering the palette.
- Files:
internal/editor/editor.go (open condition), internal/editor/palette.go (maybe hide Divider when content exists)
Approach B: Separate shortcut (e.g., Ctrl+/) for non-empty blocks
- How: Add a dedicated keybind that opens the palette regardless of content or cursor position.
- Pros: No ambiguity about when
/ is a character vs. a command.
- Cons: Two ways to do the same thing. More to document. Conflicts with macOS terminal remapping concerns.
- Files:
internal/editor/editor.go
Recommended: Approach A — simpler, consistent, less to learn.
Tasks
Test plan
Scope
Type: enhancement
Size: small–medium
Problem
There's no way to change a block's type once it has content. The
/palette only opens on empty blocks (ta.Value() == ""), so users who want to turn a paragraph into a heading — or a bullet into a quote — must clear the block, change its type, then re-type the content.This is a common editing operation (realize a paragraph should be a heading, promote/demote headings, convert to a list after drafting). The infrastructure already supports it —
applyPaletteSelectionpreserves content for all non-Divider types — but the opening gate blocks it.Context
internal/editor/editor.go:1184-1188): requiresta.Value() == "",ta.Line() == 0,ta.LineInfo().ColumnOffset == 0applyPaletteSelection(internal/editor/editor.go:729-743): already changes type and preserves content; only clears content for DividerPossible approaches
Approach A: Extend
/trigger to position-0 on non-empty blocksta.Value() == ""gate. Keep the position-0 requirement so/mid-sentence still types a slash./, pick a type"). Mirrors Notion. Minimal code change./isn't inserted into the block text when triggering the palette.internal/editor/editor.go(open condition),internal/editor/palette.go(maybe hide Divider when content exists)Approach B: Separate shortcut (e.g.,
Ctrl+/) for non-empty blocks/is a character vs. a command.internal/editor/editor.goRecommended: Approach A — simpler, consistent, less to learn.
Tasks
ta.Value() == ""from palette open condition (keep pos-0 check)/character is not inserted into block content when palette opens/still types normally mid-textTest plan
/— palette opens/— literal slash inserted, no paletteScope
Type: enhancement
Size: small–medium