Personal outliner built with Rust.
4

Configure Feed

Select the types of activity you want to include in your feed.

archive ui-polish: sync delta specs to openspec/specs (new app-chrome capability; outline-editor gains threading, animated scrolling, viewport-follows-focus with typeahead margin, caret glide, page-heading rendering, visually-previous merge; dev-automation-server dump gains sidebar state), move change to archive/2026-07-16-ui-polish

graham.systems (Jul 16, 2026, 10:41 AM -0700) 843e6a7c fdde9df5

+182 -5
openspec/changes/ui-polish/.openspec.yaml openspec/changes/archive/2026-07-16-ui-polish/.openspec.yaml
openspec/changes/ui-polish/design.md openspec/changes/archive/2026-07-16-ui-polish/design.md
openspec/changes/ui-polish/proposal.md openspec/changes/archive/2026-07-16-ui-polish/proposal.md
openspec/changes/ui-polish/specs/app-chrome/spec.md openspec/changes/archive/2026-07-16-ui-polish/specs/app-chrome/spec.md
openspec/changes/ui-polish/specs/dev-automation-server/spec.md openspec/changes/archive/2026-07-16-ui-polish/specs/dev-automation-server/spec.md
+5 -3
openspec/changes/ui-polish/specs/outline-editor/spec.md openspec/changes/archive/2026-07-16-ui-polish/specs/outline-editor/spec.md
··· 113 113 lag (corners leading the direction of travel move faster than trailing 114 114 ones), rendering as a visible stretch toward the destination that settles 115 115 crisply. The glide SHALL follow the caret across block boundaries when 116 - focus moves between blocks. Setting the caret animation knob to zero SHALL 117 - disable the effect entirely (reduced motion, and the deterministic mode 118 - used by UI tests). 116 + focus moves between blocks, except when the destination row is above the 117 + viewport top (where the row's painted position is synthetic until the 118 + eased reveal lands, and the viewport's own motion carries the eye). 119 + Setting the caret animation knob to zero SHALL disable the effect 120 + entirely (reduced motion, and the deterministic mode used by UI tests). 119 121 120 122 #### Scenario: Caret stretches toward its destination 121 123 - **WHEN** the caret jumps within a block (e.g. Home from line end) or
openspec/changes/ui-polish/tasks.md openspec/changes/archive/2026-07-16-ui-polish/tasks.md
+47
openspec/specs/app-chrome/spec.md
··· 1 + # app-chrome Specification 2 + 3 + ## Purpose 4 + 5 + The application shell around the outline: client-drawn window chrome and the 6 + sidebar that hosts auxiliary panels. 7 + 8 + ## Requirements 9 + 10 + ### Requirement: Client-drawn window chrome 11 + The application window SHALL present a client-drawn titlebar integrated with 12 + the application header — a drag region, window caption controls 13 + (minimize/maximize/close), and the header's navigation controls in one row — 14 + in place of the stock OS titlebar. Standard window gestures (drag to move, 15 + double-click to maximize/restore) MUST keep working. 16 + 17 + #### Scenario: Window remains manageable 18 + - **WHEN** the user drags the titlebar row or double-clicks its empty region 19 + - **THEN** the window moves or toggles maximized exactly as with a native 20 + titlebar, and the caption controls minimize, maximize/restore, and close 21 + the window 22 + 23 + ### Requirement: Collapsible right sidebar hosting panels 24 + The application SHALL provide a right-hand sidebar that hosts named panels — 25 + initially the calendar picker and the Similar-blocks panel — and SHALL be 26 + collapsible via a dedicated sidebar-toggle icon button in the header/titlebar 27 + row and a keyboard shortcut, and resizable by dragging its edge within 28 + clamped bounds. The toggle icon MUST reflect the sidebar's current state 29 + (collapsed vs. expanded). Collapsing the sidebar MUST NOT discard panel 30 + state. Sidebar visibility and width SHALL be remembered for at least the 31 + duration of the session. 32 + 33 + #### Scenario: Panels live in the sidebar 34 + - **WHEN** the sidebar is open with a block focused 35 + - **THEN** the calendar and the Similar-blocks list render as sidebar panels, 36 + and the Similar panel no longer renders at the bottom of the outline 37 + 38 + #### Scenario: Toggle from the keyboard 39 + - **WHEN** the user presses the sidebar toggle shortcut twice 40 + - **THEN** the sidebar collapses and reopens at its prior width with its 41 + panels intact, without moving editor focus 42 + 43 + #### Scenario: Calendar invocation targets the sidebar 44 + - **WHEN** the user invokes the calendar (shortcut or button) while the 45 + sidebar is collapsed 46 + - **THEN** the sidebar opens revealing the calendar panel, and day selection 47 + navigates to (or creates) that day's journal page exactly as before
+5 -1
openspec/specs/dev-automation-server/spec.md
··· 48 48 - **THEN** the server replies `{"ok":false,"error":...}` and dispatches nothing 49 49 50 50 ### Requirement: Structured state dump 51 - A `dump` command SHALL return a versioned JSON document (top-level `"v":1`) of semantic UI state derived from entity state, including at minimum: the current view (journal date, page name, or search), the visible outline as a nested block list (id, content, depth, collapsed), the focused block id, cursor offset and selection, any open popup with its contents (completion candidates, quick-open items, calendar month), and the window bounds with scale factor. 51 + A `dump` command SHALL return a versioned JSON document (top-level `"v":1`) of semantic UI state derived from entity state, including at minimum: the current view (journal date, page name, or search), the visible outline as a nested block list (id, content, depth, collapsed), the focused block id, cursor offset and selection, any open popup with its contents (completion candidates, quick-open items), the sidebar's state (open flag, width, panel titles in display order, and the calendar's displayed month when the sidebar is open), and the window bounds with scale factor. 52 52 53 53 #### Scenario: Dump reflects a completion popup 54 54 - **WHEN** reference completion is open with candidates and a client sends `{"cmd":"dump"}` 55 55 - **THEN** the response includes the open popup type and its candidate list, the focused block id, and the cursor position within it 56 + 57 + #### Scenario: Dump reflects sidebar state 58 + - **WHEN** the sidebar is open and a client sends `{"cmd":"dump"}` 59 + - **THEN** the response includes the sidebar's open flag, width, panel titles, and the calendar's displayed month; collapsing the sidebar and dumping again reflects the closed state 56 60 57 61 #### Scenario: Bounds available without a screenshot 58 62 - **WHEN** a client sends `{"cmd":"bounds"}` (or reads bounds from a `dump`)
+125 -1
openspec/specs/outline-editor/spec.md
··· 14 14 - **THEN** the edited block displays its new rendered content and the next block becomes the sole editable input, with no intermediate state where zero or two editors exist 15 15 16 16 ### Requirement: Keyboard-complete outline manipulation 17 - All outline operations SHALL be executable without the mouse: create sibling (Enter), insert a newline within the block (Shift+Enter), split block at cursor, indent/outdent (Tab/Shift+Tab), move block up/down among siblings, delete/merge with previous (Backspace at start), and fold/unfold subtree. 17 + All outline operations SHALL be executable without the mouse: create sibling (Enter), insert a newline within the block (Shift+Enter), split block at cursor, indent/outdent (Tab/Shift+Tab), move block up/down among siblings, delete/merge with previous (Backspace at start), and fold/unfold subtree. Backspace at the start of a childless block SHALL merge it into the *visually previous* row — a previous sibling's deepest visible descendant, or the parent when the block is a first child — with the exception that merging into a page root is permitted only when the block is empty (pure deletion), never appending content into a page title. 18 18 19 19 #### Scenario: Indent under previous sibling 20 20 - **WHEN** the cursor is in a block and the user presses Tab ··· 27 27 #### Scenario: Fold hides descendants 28 28 - **WHEN** the user folds a block with descendants 29 29 - **THEN** descendants are hidden, a fold indicator is shown, and keyboard navigation skips the hidden blocks 30 + 31 + #### Scenario: Merge follows the eye 32 + - **WHEN** the cursor is at the start of a block whose previous sibling has visible descendants and the user presses Backspace 33 + - **THEN** the block's content joins the end of the deepest visible descendant — the row rendered directly above — and focus lands at the join point 34 + 35 + #### Scenario: First children are deletable 36 + - **WHEN** the cursor is at the start of a first child block and the user presses Backspace 37 + - **THEN** the block merges into its parent (an empty block simply disappears), except when the parent is a page root and the block still has content, which is refused rather than renaming the page 30 38 31 39 ### Requirement: Markdown rendering of block content 32 40 Inactive blocks SHALL render their content as full Markdown — multiple paragraphs, emphasis, code spans, fenced code blocks, quotes, embedded lists, and links — plus node references as an inline extension. Node references SHALL be visually distinct, SHALL identify their target, and SHALL NOT be parsed inside code spans or fenced code blocks. Markdown structure within a block is content only: it SHALL NOT create outline nodes. ··· 56 64 #### Scenario: Long journal page scrolls smoothly 57 65 - **WHEN** a page contains 10,000 blocks and the user scrolls through it 58 66 - **THEN** scrolling maintains 60fps and only visible blocks are materialized as UI elements 67 + 68 + ### Requirement: Hierarchy guides and the focused-path thread 69 + Rendered outline rows SHALL display two layers of threading. **Static 70 + guides**: vertical hairlines in each indent column whose ancestor chain 71 + continues below the row, starting slightly inset below the first child's row 72 + top and, on a terminating line, running through the last child's bullet to 73 + the bottom of its first text line. **Focused-path thread**: when a block is 74 + focused, an accent-colored line SHALL thread the bullet icons from the 75 + highest visible bulleted ancestor down to the focused block's bullet — 76 + descending verticals through traversed columns, a rounded elbow into each 77 + path node's bullet, the line visibly touching the bullets it connects — and 78 + bullets on the path SHALL adopt the thread color. Where a thread elbow lands 79 + on a terminating guide, the gray tail is suppressed in favor of the bend. 80 + Both layers MUST render correctly under virtualization (computed per visible 81 + row, no whole-tree pass) and respect the outline's named layout knobs. 82 + 83 + #### Scenario: Deep block is visually anchored 84 + - **WHEN** a block nested four levels deep is visible 85 + - **THEN** each of its four indent columns shows a guide segment exactly when 86 + the corresponding ancestor chain continues at that level 87 + 88 + #### Scenario: Thread follows focus 89 + - **WHEN** a block nested several levels deep is focused 90 + - **THEN** an accent line runs from the highest visible bulleted ancestor 91 + through each intermediate ancestor's bullet, bending into the focused 92 + block's bullet, with every bullet on that path tinted the thread color — 93 + and moving focus elsewhere re-threads accordingly 94 + 95 + ### Requirement: Animated programmatic scrolling 96 + Programmatic scroll changes (navigation history restore, follow-reference, 97 + zoom, scroll-to-focused-block) SHALL animate to the target offset with a 98 + short easing rather than jumping. User wheel/trackpad input MUST NOT be 99 + altered and MUST cancel any in-flight animation. Setting the animation 100 + duration knob to zero SHALL disable animation entirely (reduced motion, and 101 + the deterministic mode used by UI tests). Scroll preservation during edits 102 + MUST remain instantaneous, never animated. 103 + 104 + #### Scenario: Back-navigation glides 105 + - **WHEN** the user navigates back to a view whose saved scroll offset 106 + differs from the current one 107 + - **THEN** the outline animates to the saved offset over the configured 108 + duration, and a wheel event during the animation stops it at the user's 109 + control 110 + 111 + ### Requirement: The viewport follows focus with a typeahead margin 112 + When the focused block's caret moves or its content changes, the outline 113 + SHALL scroll to keep the caret's line visible, maintaining a typeahead 114 + margin (a named knob, 120px) of content visible below the active line so 115 + typing never rides the window's bottom edge. With scroll animation 116 + enabled, the adjustment SHALL ease rather than jump, converging exactly 117 + onto the margin position — including after bursts of movement that leave 118 + the caret far outside the rendered range (bounded eased passes, then an 119 + exact native fallback). With animation disabled (reduced motion; UI 120 + tests), the reveal is the list's native synchronous autoscroll. The 121 + margin SHALL be honored even on the document's last line (backed by a 122 + trailing overscroll spacer at least as tall). A focused block scrolled out 123 + of view by the user MUST remain interactive (keystrokes apply), and the 124 + viewport MUST NOT scroll back until the caret or content next changes. 125 + 126 + #### Scenario: Typing past the fold 127 + - **WHEN** the user splits blocks repeatedly until new blocks would fall 128 + below the window's bottom edge 129 + - **THEN** the outline scrolls so the active line stays visible with the 130 + typeahead margin below it, and every keystroke lands in the focused 131 + block with no stalls 132 + 133 + #### Scenario: The follow is eased, not instant 134 + - **WHEN** scroll animation is enabled and the caret moves one line past 135 + the margin (Enter or Down at the bottom edge) 136 + - **THEN** the viewport glides to the new margin position over the scroll 137 + animation duration instead of jumping, and a rapid run of such moves 138 + (held arrow key) chases smoothly and still lands exactly at the margin 139 + 140 + #### Scenario: Wheel-scrolling away is respected 141 + - **WHEN** the user wheel-scrolls the focused block out of view without 142 + touching the caret 143 + - **THEN** the viewport stays where the user put it until the next caret 144 + movement or edit, which reveals the caret again 145 + 146 + ### Requirement: Caret glide 147 + The focused block's caret SHALL animate between positions Neovide-style: 148 + the caret rectangle's corners chase their target with direction-dependent 149 + lag (corners leading the direction of travel move faster than trailing 150 + ones), rendering as a visible stretch toward the destination that settles 151 + crisply. The glide SHALL follow the caret across block boundaries when 152 + focus moves between blocks, except when the destination row is above the 153 + viewport top (where the row's painted position is synthetic until the 154 + eased reveal lands, and the viewport's own motion carries the eye). 155 + Setting the caret animation knob to zero SHALL disable the effect 156 + entirely (reduced motion, and the deterministic mode used by UI tests). 157 + 158 + #### Scenario: Caret stretches toward its destination 159 + - **WHEN** the caret jumps within a block (e.g. Home from line end) or 160 + focus moves to another block 161 + - **THEN** the caret visibly glides from its previous position to the new 162 + one, stretched along the direction of travel while in flight, arriving 163 + as the normal caret rectangle 164 + 165 + ### Requirement: Page root renders as a heading 166 + A page's root SHALL render as a large heading — no bullet, no fold 167 + affordance — with the page's children rendered from the first indent level 168 + (bullets at the left margin). The heading remains the page's editable root 169 + block: focus, editing, and rename behavior are unchanged from the current 170 + root bullet. Tree depth semantics (including devtools dump depth) are 171 + unaffected — this is rendering only. 172 + 173 + #### Scenario: Journal page reads as a titled document 174 + - **WHEN** a journal date page is displayed 175 + - **THEN** the date renders as a heading with the day's blocks starting 176 + un-indented beneath it, one indent level shallower than the previous 177 + title-as-bullet rendering 178 + 179 + #### Scenario: Heading is still the root block 180 + - **WHEN** the user presses Up from the first child block 181 + - **THEN** focus lands on the heading exactly as it previously landed on the 182 + root bullet, and editing it behaves as editing the root block always has