From 843e6a7c4cd9b3d3ac37220efee696d8d96b0a32 Mon Sep 17 00:00:00 2001 From: Graham Barber Date: Thu, 16 Jul 2026 10:41:10 -0700 Subject: [PATCH] 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 --- .../2026-07-16-ui-polish}/.openspec.yaml | 0 .../2026-07-16-ui-polish}/design.md | 0 .../2026-07-16-ui-polish}/proposal.md | 0 .../specs/app-chrome/spec.md | 0 .../specs/dev-automation-server/spec.md | 0 .../specs/outline-editor/spec.md | 8 +- .../2026-07-16-ui-polish}/tasks.md | 0 openspec/specs/app-chrome/spec.md | 47 +++++++ openspec/specs/dev-automation-server/spec.md | 6 +- openspec/specs/outline-editor/spec.md | 126 +++++++++++++++++- 10 files changed, 182 insertions(+), 5 deletions(-) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/.openspec.yaml (100%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/design.md (100%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/proposal.md (100%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/specs/app-chrome/spec.md (100%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/specs/dev-automation-server/spec.md (100%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/specs/outline-editor/spec.md (96%) rename openspec/changes/{ui-polish => archive/2026-07-16-ui-polish}/tasks.md (100%) create mode 100644 openspec/specs/app-chrome/spec.md diff --git a/openspec/changes/ui-polish/.openspec.yaml b/openspec/changes/archive/2026-07-16-ui-polish/.openspec.yaml similarity index 100% rename from openspec/changes/ui-polish/.openspec.yaml rename to openspec/changes/archive/2026-07-16-ui-polish/.openspec.yaml diff --git a/openspec/changes/ui-polish/design.md b/openspec/changes/archive/2026-07-16-ui-polish/design.md similarity index 100% rename from openspec/changes/ui-polish/design.md rename to openspec/changes/archive/2026-07-16-ui-polish/design.md diff --git a/openspec/changes/ui-polish/proposal.md b/openspec/changes/archive/2026-07-16-ui-polish/proposal.md similarity index 100% rename from openspec/changes/ui-polish/proposal.md rename to openspec/changes/archive/2026-07-16-ui-polish/proposal.md diff --git a/openspec/changes/ui-polish/specs/app-chrome/spec.md b/openspec/changes/archive/2026-07-16-ui-polish/specs/app-chrome/spec.md similarity index 100% rename from openspec/changes/ui-polish/specs/app-chrome/spec.md rename to openspec/changes/archive/2026-07-16-ui-polish/specs/app-chrome/spec.md diff --git a/openspec/changes/ui-polish/specs/dev-automation-server/spec.md b/openspec/changes/archive/2026-07-16-ui-polish/specs/dev-automation-server/spec.md similarity index 100% rename from openspec/changes/ui-polish/specs/dev-automation-server/spec.md rename to openspec/changes/archive/2026-07-16-ui-polish/specs/dev-automation-server/spec.md diff --git a/openspec/changes/ui-polish/specs/outline-editor/spec.md b/openspec/changes/archive/2026-07-16-ui-polish/specs/outline-editor/spec.md similarity index 96% rename from openspec/changes/ui-polish/specs/outline-editor/spec.md rename to openspec/changes/archive/2026-07-16-ui-polish/specs/outline-editor/spec.md index 6bd3cf6..2ea8888 100644 --- a/openspec/changes/ui-polish/specs/outline-editor/spec.md +++ b/openspec/changes/archive/2026-07-16-ui-polish/specs/outline-editor/spec.md @@ -113,9 +113,11 @@ the caret rectangle's corners chase their target with direction-dependent lag (corners leading the direction of travel move faster than trailing ones), rendering as a visible stretch toward the destination that settles crisply. The glide SHALL follow the caret across block boundaries when -focus moves between blocks. Setting the caret animation knob to zero SHALL -disable the effect entirely (reduced motion, and the deterministic mode -used by UI tests). +focus moves between blocks, except when the destination row is above the +viewport top (where the row's painted position is synthetic until the +eased reveal lands, and the viewport's own motion carries the eye). +Setting the caret animation knob to zero SHALL disable the effect +entirely (reduced motion, and the deterministic mode used by UI tests). #### Scenario: Caret stretches toward its destination - **WHEN** the caret jumps within a block (e.g. Home from line end) or diff --git a/openspec/changes/ui-polish/tasks.md b/openspec/changes/archive/2026-07-16-ui-polish/tasks.md similarity index 100% rename from openspec/changes/ui-polish/tasks.md rename to openspec/changes/archive/2026-07-16-ui-polish/tasks.md diff --git a/openspec/specs/app-chrome/spec.md b/openspec/specs/app-chrome/spec.md new file mode 100644 index 0000000..f3b0d4a --- /dev/null +++ b/openspec/specs/app-chrome/spec.md @@ -0,0 +1,47 @@ +# app-chrome Specification + +## Purpose + +The application shell around the outline: client-drawn window chrome and the +sidebar that hosts auxiliary panels. + +## Requirements + +### Requirement: Client-drawn window chrome +The application window SHALL present a client-drawn titlebar integrated with +the application header — a drag region, window caption controls +(minimize/maximize/close), and the header's navigation controls in one row — +in place of the stock OS titlebar. Standard window gestures (drag to move, +double-click to maximize/restore) MUST keep working. + +#### Scenario: Window remains manageable +- **WHEN** the user drags the titlebar row or double-clicks its empty region +- **THEN** the window moves or toggles maximized exactly as with a native + titlebar, and the caption controls minimize, maximize/restore, and close + the window + +### Requirement: Collapsible right sidebar hosting panels +The application SHALL provide a right-hand sidebar that hosts named panels — +initially the calendar picker and the Similar-blocks panel — and SHALL be +collapsible via a dedicated sidebar-toggle icon button in the header/titlebar +row and a keyboard shortcut, and resizable by dragging its edge within +clamped bounds. The toggle icon MUST reflect the sidebar's current state +(collapsed vs. expanded). Collapsing the sidebar MUST NOT discard panel +state. Sidebar visibility and width SHALL be remembered for at least the +duration of the session. + +#### Scenario: Panels live in the sidebar +- **WHEN** the sidebar is open with a block focused +- **THEN** the calendar and the Similar-blocks list render as sidebar panels, + and the Similar panel no longer renders at the bottom of the outline + +#### Scenario: Toggle from the keyboard +- **WHEN** the user presses the sidebar toggle shortcut twice +- **THEN** the sidebar collapses and reopens at its prior width with its + panels intact, without moving editor focus + +#### Scenario: Calendar invocation targets the sidebar +- **WHEN** the user invokes the calendar (shortcut or button) while the + sidebar is collapsed +- **THEN** the sidebar opens revealing the calendar panel, and day selection + navigates to (or creates) that day's journal page exactly as before diff --git a/openspec/specs/dev-automation-server/spec.md b/openspec/specs/dev-automation-server/spec.md index 89bf88d..95aa503 100644 --- a/openspec/specs/dev-automation-server/spec.md +++ b/openspec/specs/dev-automation-server/spec.md @@ -48,12 +48,16 @@ A `keys` command SHALL parse its argument with gpui's `Keystroke::parse` syntax - **THEN** the server replies `{"ok":false,"error":...}` and dispatches nothing ### Requirement: Structured state dump -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. +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. #### Scenario: Dump reflects a completion popup - **WHEN** reference completion is open with candidates and a client sends `{"cmd":"dump"}` - **THEN** the response includes the open popup type and its candidate list, the focused block id, and the cursor position within it +#### Scenario: Dump reflects sidebar state +- **WHEN** the sidebar is open and a client sends `{"cmd":"dump"}` +- **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 + #### Scenario: Bounds available without a screenshot - **WHEN** a client sends `{"cmd":"bounds"}` (or reads bounds from a `dump`) - **THEN** the response contains the window's current position, size, and scale factor as reported by gpui diff --git a/openspec/specs/outline-editor/spec.md b/openspec/specs/outline-editor/spec.md index 8376214..bdfe561 100644 --- a/openspec/specs/outline-editor/spec.md +++ b/openspec/specs/outline-editor/spec.md @@ -14,7 +14,7 @@ The application SHALL maintain at most one live text input at any time — the f - **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 ### Requirement: Keyboard-complete outline manipulation -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. +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. #### Scenario: Indent under previous sibling - **WHEN** the cursor is in a block and the user presses Tab @@ -28,6 +28,14 @@ All outline operations SHALL be executable without the mouse: create sibling (En - **WHEN** the user folds a block with descendants - **THEN** descendants are hidden, a fold indicator is shown, and keyboard navigation skips the hidden blocks +#### Scenario: Merge follows the eye +- **WHEN** the cursor is at the start of a block whose previous sibling has visible descendants and the user presses Backspace +- **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 + +#### Scenario: First children are deletable +- **WHEN** the cursor is at the start of a first child block and the user presses Backspace +- **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 + ### Requirement: Markdown rendering of block content 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,3 +64,119 @@ The outline view SHALL virtualize rendering such that memory and frame time scal #### Scenario: Long journal page scrolls smoothly - **WHEN** a page contains 10,000 blocks and the user scrolls through it - **THEN** scrolling maintains 60fps and only visible blocks are materialized as UI elements + +### Requirement: Hierarchy guides and the focused-path thread +Rendered outline rows SHALL display two layers of threading. **Static +guides**: vertical hairlines in each indent column whose ancestor chain +continues below the row, starting slightly inset below the first child's row +top and, on a terminating line, running through the last child's bullet to +the bottom of its first text line. **Focused-path thread**: when a block is +focused, an accent-colored line SHALL thread the bullet icons from the +highest visible bulleted ancestor down to the focused block's bullet — +descending verticals through traversed columns, a rounded elbow into each +path node's bullet, the line visibly touching the bullets it connects — and +bullets on the path SHALL adopt the thread color. Where a thread elbow lands +on a terminating guide, the gray tail is suppressed in favor of the bend. +Both layers MUST render correctly under virtualization (computed per visible +row, no whole-tree pass) and respect the outline's named layout knobs. + +#### Scenario: Deep block is visually anchored +- **WHEN** a block nested four levels deep is visible +- **THEN** each of its four indent columns shows a guide segment exactly when + the corresponding ancestor chain continues at that level + +#### Scenario: Thread follows focus +- **WHEN** a block nested several levels deep is focused +- **THEN** an accent line runs from the highest visible bulleted ancestor + through each intermediate ancestor's bullet, bending into the focused + block's bullet, with every bullet on that path tinted the thread color — + and moving focus elsewhere re-threads accordingly + +### Requirement: Animated programmatic scrolling +Programmatic scroll changes (navigation history restore, follow-reference, +zoom, scroll-to-focused-block) SHALL animate to the target offset with a +short easing rather than jumping. User wheel/trackpad input MUST NOT be +altered and MUST cancel any in-flight animation. Setting the animation +duration knob to zero SHALL disable animation entirely (reduced motion, and +the deterministic mode used by UI tests). Scroll preservation during edits +MUST remain instantaneous, never animated. + +#### Scenario: Back-navigation glides +- **WHEN** the user navigates back to a view whose saved scroll offset + differs from the current one +- **THEN** the outline animates to the saved offset over the configured + duration, and a wheel event during the animation stops it at the user's + control + +### Requirement: The viewport follows focus with a typeahead margin +When the focused block's caret moves or its content changes, the outline +SHALL scroll to keep the caret's line visible, maintaining a typeahead +margin (a named knob, 120px) of content visible below the active line so +typing never rides the window's bottom edge. With scroll animation +enabled, the adjustment SHALL ease rather than jump, converging exactly +onto the margin position — including after bursts of movement that leave +the caret far outside the rendered range (bounded eased passes, then an +exact native fallback). With animation disabled (reduced motion; UI +tests), the reveal is the list's native synchronous autoscroll. The +margin SHALL be honored even on the document's last line (backed by a +trailing overscroll spacer at least as tall). A focused block scrolled out +of view by the user MUST remain interactive (keystrokes apply), and the +viewport MUST NOT scroll back until the caret or content next changes. + +#### Scenario: Typing past the fold +- **WHEN** the user splits blocks repeatedly until new blocks would fall + below the window's bottom edge +- **THEN** the outline scrolls so the active line stays visible with the + typeahead margin below it, and every keystroke lands in the focused + block with no stalls + +#### Scenario: The follow is eased, not instant +- **WHEN** scroll animation is enabled and the caret moves one line past + the margin (Enter or Down at the bottom edge) +- **THEN** the viewport glides to the new margin position over the scroll + animation duration instead of jumping, and a rapid run of such moves + (held arrow key) chases smoothly and still lands exactly at the margin + +#### Scenario: Wheel-scrolling away is respected +- **WHEN** the user wheel-scrolls the focused block out of view without + touching the caret +- **THEN** the viewport stays where the user put it until the next caret + movement or edit, which reveals the caret again + +### Requirement: Caret glide +The focused block's caret SHALL animate between positions Neovide-style: +the caret rectangle's corners chase their target with direction-dependent +lag (corners leading the direction of travel move faster than trailing +ones), rendering as a visible stretch toward the destination that settles +crisply. The glide SHALL follow the caret across block boundaries when +focus moves between blocks, except when the destination row is above the +viewport top (where the row's painted position is synthetic until the +eased reveal lands, and the viewport's own motion carries the eye). +Setting the caret animation knob to zero SHALL disable the effect +entirely (reduced motion, and the deterministic mode used by UI tests). + +#### Scenario: Caret stretches toward its destination +- **WHEN** the caret jumps within a block (e.g. Home from line end) or + focus moves to another block +- **THEN** the caret visibly glides from its previous position to the new + one, stretched along the direction of travel while in flight, arriving + as the normal caret rectangle + +### Requirement: Page root renders as a heading +A page's root SHALL render as a large heading — no bullet, no fold +affordance — with the page's children rendered from the first indent level +(bullets at the left margin). The heading remains the page's editable root +block: focus, editing, and rename behavior are unchanged from the current +root bullet. Tree depth semantics (including devtools dump depth) are +unaffected — this is rendering only. + +#### Scenario: Journal page reads as a titled document +- **WHEN** a journal date page is displayed +- **THEN** the date renders as a heading with the day's blocks starting + un-indented beneath it, one indent level shallower than the previous + title-as-bullet rendering + +#### Scenario: Heading is still the root block +- **WHEN** the user presses Up from the first child block +- **THEN** focus lands on the heading exactly as it previously landed on the + root bullet, and editing it behaves as editing the root block always has -- 2.51.2