diff --git a/openspec/changes/create-standalone-page/.openspec.yaml b/openspec/changes/create-standalone-page/.openspec.yaml new file mode 100644 --- /dev/null +++ b/openspec/changes/create-standalone-page/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-31 diff --git a/openspec/changes/create-standalone-page/design.md b/openspec/changes/create-standalone-page/design.md new file mode 100644 --- /dev/null +++ b/openspec/changes/create-standalone-page/design.md @@ -0,0 +1,40 @@ +# Design: create-standalone-page + +## Context + +Pages are tree roots (`Outline::create_block(None, …)`); the page heading is the editable root block itself (ui-polish design D6), and renames already happen in place by editing it. `CreatePage` (ctrl-enter) exists solely to materialize a page for the Tag/Date view being displayed. Quick-open lists pages by name. Decisions settled in the 2026-07-30 explore session alongside `home-document`. + +## Goals / Non-Goals + +**Goals:** +- One keystroke from anywhere to a fresh, nameless page ready to type into. +- Zero litter from abandoned creations. + +**Non-Goals:** +- A name-first dialog (D6 makes name-in-place strictly better). +- Templates, page types, or default content. +- Cleanup of pre-existing empty pages in old graphs. + +## Decisions + +### D1: Name-in-place via the heading, no dialog + +`ctrl-n` creates an empty root with one empty child block, navigates to `View::Node(root)`, and focuses the root heading. Typing names the page; Enter moves into the first block (existing heading→child editor flow). Alternative (modal name prompt) rejected: it duplicates quick-open chrome and breaks the type-first flow D6 already enables. + +### D2: Sweep on navigate-away, not create-on-first-keystroke + +An abandoned new page (still no name, no non-empty children, no inbound references) is deleted when navigation leaves it. Create-on-first-keystroke was considered — it avoids ever persisting an empty root — but it splits creation across the editor and navigation layers and complicates the focus handoff; the sweep is a small check in one place (`navigate_to`) with the same end state. The sweep condition is deliberately conservative (name OR content OR reference keeps the page) so it can never eat real work. + +### D3: Sweep scope is the departed view only + +Only the specific page being navigated away from is examined — no background scanning for empties. Keeps the sweep O(1), predictable, and safe for old graphs that may contain historical empty pages the user hasn't asked to delete. + +## Risks / Trade-offs + +- [Relaunch while sitting on an empty new page] → the page persists (sweep only runs on navigation). Acceptable: it appears in quick-open as a nameless page and is swept next time the user visits and leaves it. +- [User intentionally wants an empty named-later page] → giving it any character of name or content keeps it; only the fully-untouched case is swept. +- [Sweep racing a reference typed elsewhere] → the reference check runs at sweep time against current backlinks; a referenced page is kept. + +## Open Questions + +None. diff --git a/openspec/changes/create-standalone-page/proposal.md b/openspec/changes/create-standalone-page/proposal.md new file mode 100644 --- /dev/null +++ b/openspec/changes/create-standalone-page/proposal.md @@ -0,0 +1,26 @@ +# Proposal: create-standalone-page + +## Why + +There is no way to create a page from nothing: `CreatePage` (ctrl-enter) only materializes a page for the Tag/Date view currently displayed, and otherwise pages are born by being referenced from an existing block. Users have asked for arbitrary pages without a parental backlink — and the companion `home-document` change makes the need concrete: a pinned home opens with nothing focused, and starting a new page should be one keystroke away. + +## What Changes + +- New `NewPage` action bound to `ctrl-n`: creates an empty root page, navigates to it, and focuses its heading — the heading is the editable root block (ui-polish design D6), so typing names the page in place. No name dialog. +- **Sweep on navigate-away**: leaving a page that is still empty — no name, no children with content, and unreferenced — deletes it, so abandoned `ctrl-n` presses leave no litter (mirrors the journal's no-empty-page-litter behavior). + +## Capabilities + +### New Capabilities + +- `page-creation`: creating standalone pages and sweeping abandoned empty ones. + +### Modified Capabilities + +None — `CreatePage`'s existing Tag/Date materialization is untouched, and no existing requirement changes behavior. + +## Impact + +- `crates/trawler/src/main.rs`: `NewPage` action + keymap, create-and-navigate flow, sweep hook in the navigation path (`navigate_to`), quick-open interaction with empty untitled pages. +- Uses existing `Outline::create_block(None, …)` for root creation; no storage changes. +- Sequencing: independent, but designed as the companion to `home-document` (the unfocused pinned-home flow); referenced from that change's design. diff --git a/openspec/changes/create-standalone-page/tasks.md b/openspec/changes/create-standalone-page/tasks.md new file mode 100644 --- /dev/null +++ b/openspec/changes/create-standalone-page/tasks.md @@ -0,0 +1,17 @@ +# Tasks: create-standalone-page + +## 1. Creation flow + +- [ ] 1.1 Add `NewPage` action bound to `ctrl-n`; handler creates an empty root page with one empty child (`Outline::create_block(None, …)`), persists, and navigates to `View::Node(root)`. +- [ ] 1.2 Focus the new page's heading (root block editor) on arrival; verify Enter from the empty heading moves into the first block (existing D6 heading flow). +- [ ] 1.3 UI tests: `ctrl-n` then typing names the page in place; Enter lands in the first block; works from journal, from a page, and from an unfocused home view. + +## 2. Sweep + +- [ ] 2.1 Implement the sweep check in the navigation path: on leaving `View::Node(root)` where the root has an empty heading, no non-empty children, and no inbound references, delete the page (and its empty child) and persist. +- [ ] 2.2 UI tests: abandoned `ctrl-n` page is gone after navigate-back and absent from quick-open; named page survives; still-empty page with an inbound reference survives; pre-existing empty pages elsewhere in the graph are untouched. + +## 3. Verification + +- [ ] 3.1 Search-index and backlinks stay consistent through create + sweep (no dangling index entries); cover with a storage-level test. +- [ ] 3.2 Full check: `cargo fmt`, clippy `-D warnings`, `cargo test --workspace`; dev-loop pass exercising the create/name/sweep flow. diff --git a/openspec/changes/home-document/.openspec.yaml b/openspec/changes/home-document/.openspec.yaml new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-31 diff --git a/openspec/changes/home-document/design.md b/openspec/changes/home-document/design.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/design.md @@ -0,0 +1,57 @@ +# Design: home-document + +## Context + +`TrawlerApp::new` hardcodes `view: View::Journal`, calls `ensure_today_journal_page` unconditionally, and focuses today's first block (journal spec "Opening the app"). `View::Node(NodeId)` already renders any node — page root, zoomed block, tag, date — so a home document needs no new document view, only a startup decision, chrome, and persistence. The dump/devtools layer already serializes node ids as `tree:` / `tag:` strings. Fold state established the precedent that UI state belonging to the graph lives in the Loro document. + +Decisions here were made in an explore session (2026-07-30) with the maintainer; alternatives noted per decision. + +## Goals / Non-Goals + +**Goals:** +- Open the app on a chosen page or tag; journal remains the default. +- Journal timeline reachable in one click/keystroke at all times. +- First settings surface, cheap to extend later. +- No empty-journal accumulation when the journal isn't visited. + +**Non-Goals:** +- Pinning nodes to the sidebar (separate `pinned-sidebar` change; "pin" vocabulary reserved for it). +- Context-menu affordance for setting home (explicitly rejected: home changes rarely; the settings screen is the only mechanism). +- Per-device home (the setting is a graph property and syncs with it). +- Startup auto-focus for a non-journal home (see `create-standalone-page` for the companion flow). +- Migration/cleanup of empty journal pages that already exist in old graphs. + +## Decisions + +### D1: Home is a doc-level LWW key, not a node property + +A single key (e.g. `home` in a doc-level settings map) holding the node's serialized id (`tree:…` / `tag:…`); absent means journal. A per-node boolean flag was rejected: two replicas could each flag a different node and both survive the merge, leaving two "homes"; a single LWW register resolves concurrent sets deterministically. Follows the fold-state precedent for doc-persisted UI state, and syncs with the graph under the planned Loro-native sync. No format version bump — old builds ignore the key, new builds treat absence as journal. + +### D2: Settings is a `View` variant, not a modal + +`View::Settings` alongside `Journal` and `Node`. Rationale: views already carry navigation history for free (back/forward just work), no new overlay/dismiss plumbing, and the screen renders like any other main-column content. Opened from a new sidebar footer button (the footer is new chrome; the sidebar previously had only stacked panels). A modal overlay was considered and rejected as building a second navigation idiom for one setting. + +The home picker inside Settings reuses the quick-open machinery (type-ahead over pages and tags) with a "Journal (default)" row that clears the key — "unset" must read as the default, not as a broken state. + +### D3: Titlebar swaps Calendar for Home + Journal + +The Calendar titlebar button toggled a sidebar panel and read as disconnected from it; it is removed (calendar remains via sidebar toggle and `ctrl-shift-c`). Home navigates to the home document — journal when unset, so the button is never dead. Journal always navigates to the timeline; without it, setting a home would make the journal timeline unreachable (calendar day clicks navigate to individual date pages, not the timeline). `GoHome` action bound to `ctrl-h` (free; `ctrl-j` noted as the natural Journal binding if wanted later, unbound in this change). + +### D4: Today-page creation moves to journal-open + +`ensure_today_journal_page` leaves the startup path and runs when the journal view is entered (startup-with-journal-home included, which keeps the default experience byte-identical to today). The rollover timer and render-time check are already gated on `view == View::Journal` and keep working unchanged; a rollover while the journal is open still materializes the new day. Days on which the journal was never opened simply have no page — the timeline already renders sparse day sequences (fixture graphs demonstrate this). + +### D5: Deleted home falls back and self-heals + +At startup (and on `GoHome`), a `tree:` home whose node no longer exists resolves to the journal and the stored key is cleared — silently; the next Settings visit shows "Journal (default)". Tags are virtual (no lifecycle), so the fallback path only concerns tree nodes. + +## Risks / Trade-offs + +- [Settings picker over a large graph] → reuses quick-open's existing search index path; no new scaling surface. +- [Home key written by newer builds, opened by older] → older builds never read the key; behavior degrades to journal-home. Acceptable, no format bump. +- [User sets home, forgets journal exists] → Journal button is permanent titlebar chrome, not conditional. +- [Concurrent set-home on two replicas] → LWW register; last writer wins, deterministic, both replicas converge. + +## Open Questions + +None — decisions above were settled in the explore session. diff --git a/openspec/changes/home-document/proposal.md b/openspec/changes/home-document/proposal.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/proposal.md @@ -0,0 +1,33 @@ +# Proposal: home-document + +## Why + +Users have asked to open the app on a document of their choosing — a wiki index, an `#inbox` tag — instead of the daily journal. Today `View::Journal` is hardcoded as the startup view, there is no way back to the journal timeline other than already being in it, and the app has no settings surface at all. + +## What Changes + +- A graph can designate one node (a page or a tag) as its **home document**, stored in the Loro document as a single last-write-wins key (absent = journal, today's behavior). +- New **Settings screen** — a full view (`View::Settings`, participating in navigation history), reached from a new **sidebar footer** button. v1 contains one setting: the home document, changed via a quick-open-style picker over pages and tags, with "Journal" as the default/unset option. +- The titlebar's **Calendar button is replaced by Home and Journal buttons**: Home navigates to the home document (journal when unset), Journal always navigates to the journal timeline. New `GoHome` action bound to `ctrl-h`. The calendar stays reachable via the sidebar and `ctrl-shift-c`. +- **Today's journal page is created when the journal is opened**, not at application startup — so a graph whose home is elsewhere doesn't accumulate empty journal pages for days the journal was never visited. +- Opening a pinned home at startup does **not** auto-focus a block (unlike the journal's focus-first-block behavior); the companion `create-standalone-page` change gives that flow a purpose (`ctrl-n` to start a new page). +- A home document whose tree node was deleted falls back to the journal at startup and clears the stored setting. + +## Capabilities + +### New Capabilities + +- `home-document`: designating, storing, navigating to, and falling back from the home document; the Settings screen that manages it. + +### Modified Capabilities + +- `journal`: "Journal timeline is the home view" becomes "Journal timeline is the default home view" (startup honors the home document); "Automatic daily pages" creation moves from application-open to journal-open. +- `app-chrome`: titlebar gains Home and Journal buttons (Calendar button removed); sidebar gains a footer with a Settings button. +- `graph-navigation`: new `GoHome` action (`ctrl-h`) and Journal-button navigation; Settings participates in navigation history. + +## Impact + +- `crates/trawler/src/main.rs`: `View` enum (new `Settings` variant), `TrawlerApp::new` startup path, titlebar row, sidebar footer, keymap, `check_journal_rollover`/`ensure_today_journal_page` call sites, settings screen rendering, home picker. +- `crates/trawler-core`: storage accessor for the home key (doc-level map alongside the fold-state precedent). +- No graph format version bump: the home key is optional, absent in old graphs, ignored by old builds. +- Sequencing: independent of `create-standalone-page` and `pinned-sidebar`, but designed alongside them ("pin" vocabulary is reserved for the sidebar pin list; the home document is "set", not "pinned"). diff --git a/openspec/changes/home-document/tasks.md b/openspec/changes/home-document/tasks.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/tasks.md @@ -0,0 +1,30 @@ +# Tasks: home-document + +## 1. Storage + +- [ ] 1.1 Add a doc-level settings map accessor to trawler-core with a `home` LWW key storing a serialized node id (`tree:…`/`tag:…`); absent = journal. Unit-test set/clear/read round-trip and LWW convergence of concurrent sets. +- [ ] 1.2 Add resolve-home logic: parse the stored id, verify a `tree:` node still exists; on a dangling node, report fallback so the caller can clear the key. + +## 2. Startup and journal creation + +- [ ] 2.1 Move `ensure_today_journal_page` out of `TrawlerApp::new` into journal-view entry (a shared "opening the journal" path used by startup-default, the Journal control, and rollover-while-displayed). +- [ ] 2.2 Startup resolves the home document: unset → journal with today's first block focused (existing behavior); set → `View::Node` without focusing; dangling → journal plus key cleared. +- [ ] 2.3 UI tests: default startup unchanged; startup with home page set opens it unfocused; startup with dangling home falls back and clears; several launches with non-journal home create no journal pages. + +## 3. Header controls and actions + +- [ ] 3.1 Replace the Calendar titlebar button with Home and Journal buttons; add `GoHome` and `GoToJournal` actions, bind `ctrl-h` to `GoHome`. +- [ ] 3.2 Both actions navigate via `navigate_to` (history push); Home resolves the home document with the same fallback path as startup. +- [ ] 3.3 UI tests: `ctrl-h` from an arbitrary page lands on home and back returns; Journal control reaches the timeline when a home is set. + +## 4. Settings screen + +- [ ] 4.1 Add `View::Settings` variant rendering a full main-column settings screen; wire history/breadcrumb behavior like other views. +- [ ] 4.2 Add the sidebar footer with a Settings button opening `View::Settings`. +- [ ] 4.3 Home setting row: shows current home ("Journal (default)" when unset); a type-ahead picker over pages and tags (reusing quick-open machinery) plus a "Journal (default)" row that clears the key. +- [ ] 4.4 UI tests: open settings from footer; set home via picker and confirm persistence across relaunch (storage-level); unset shows default label; back from Settings returns to prior view. + +## 5. Polish and verification + +- [ ] 5.1 Devtools dump: expose the current view kind for Settings and the resolved home so UI tests and the dev-loop can assert on them. +- [ ] 5.2 Full check: `cargo fmt`, clippy `-D warnings`, `cargo test --workspace`; dev-loop screenshot pass over the new chrome (titlebar buttons, footer, settings screen). diff --git a/openspec/changes/pinned-sidebar/.openspec.yaml b/openspec/changes/pinned-sidebar/.openspec.yaml new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-31 diff --git a/openspec/changes/pinned-sidebar/design.md b/openspec/changes/pinned-sidebar/design.md new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/design.md @@ -0,0 +1,46 @@ +# Design: pinned-sidebar + +## Context + +The sidebar hosts stacked panels via `SIDEBAR_PANELS` (Calendar, Similar). No right-click interaction exists anywhere in the app; the completion popup already demonstrates the overlay pattern the menu needs (`anchored()` + `deferred()`, painted above list rows, dismissed explicitly). Fold state and (with `home-document`) the home key establish doc-persisted UI state; pins add the first *ordered* doc-persisted list. Decisions settled in the 2026-07-30 explore session. + +## Goals / Non-Goals + +**Goals:** +- Stable, ordered, graph-persisted waypoints one click from anywhere. +- Reusable context-menu infrastructure with a deliberately tiny v1 surface. + +**Non-Goals:** +- "Set as home" in the menu (explicitly rejected — Settings is the only home mechanism). +- Drag-reorder of pins (the storage choice keeps it open; UI later). +- Pinning dates or non-root blocks in v1 (pages and tags only, matching what home accepts). +- Any other menu items (copy-reference, delete, toggle-query are future candidates, not scope). + +## Decisions + +### D1: Pins are a doc-level ordered list + +A Loro list of serialized node ids (`tree:…`/`tag:…` — same helper as `home-document`'s key). A list, not a map/set: order is user-meaningful (append order in v1) and Loro's movable list makes future drag-reorder a UI-only change. Duplicates are prevented at the pin site (menu shows Unpin when already pinned); concurrent pin of the same node on two replicas can yield a duplicate entry, which the load path dedupes — cheaper than inventing a CRDT set with order. + +### D2: Context menu as anchored/deferred overlay + +Right-click (mouse-down with the right button) on an outline bullet, a page heading, or a Pinned panel row opens a menu at the cursor: an `anchored()` + `deferred()` div (completion-popup precedent), dismissed on escape, on any click outside, and on selection. One menu open at a time, tracked app-side. The menu is populated per-target: Pin to sidebar / Unpin, by current state. Building this generically (a target id + item list) is deliberate — the menu is the intended home for future per-node actions — but only the pin items ship. + +### D3: Bullets and headings are the right-click targets + +They are the existing per-node handles (click-to-zoom, click-to-focus live there already) and sidestep per-view-header chrome, which was rejected for the home feature and stays rejected here. Right-click on row *text* is left to the editor/selection behaviors. + +### D4: Dangling pins are dropped on load + +A `tree:` pin whose node no longer exists is silently removed from the list when the panel loads it — no fallback navigation is involved (unlike home), since nothing auto-navigates to a pin. Tags are virtual and cannot dangle. + +## Risks / Trade-offs + +- [Right-click conflicts with future text-level context menus in the editor] → targets are bullets/headings/panel rows only; editor text right-click stays unbound. +- [Concurrent pins produce duplicate entries] → deduped on load; order of survivors is deterministic (first occurrence wins). +- [Menu overlay z-order over virtualized list rows] → `deferred()` with priority, same mechanism the completion popup already uses successfully. +- [Panel row identity for tags vs pages] → rows render the node's display name via the same resolution quick-open uses; renamed pages reflect immediately since pins store ids, not names. + +## Open Questions + +None. diff --git a/openspec/changes/pinned-sidebar/proposal.md b/openspec/changes/pinned-sidebar/proposal.md new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/proposal.md @@ -0,0 +1,29 @@ +# Proposal: pinned-sidebar + +## Why + +Frequently-visited nodes have no persistent surface — reaching them means quick-open every time. A pinned list in the sidebar gives the graph a stable set of waypoints (`#inbox`, an index page, an active project), distinct from the single home document introduced by the `home-document` change. + +## What Changes + +- New **Pinned sidebar panel** between Calendar and Similar: an ordered list of pinned nodes (pages or tags), each row navigating on click, with a placeholder line when empty (matching Similar's style). +- The pin list is stored in the Loro document as an **ordered list** of serialized node ids, so it persists and travels with the graph. +- New **right-click context-menu infrastructure** — the first in the app — on outline bullets and page headings, and on Pinned panel rows. v1 menu contains exactly Pin-to-sidebar / Unpin (toggled by state). "Set as home" is deliberately excluded (home changes rarely; Settings is its only mechanism), but the menu is the intended landing spot for future per-node actions. +- Pins whose tree node no longer exists are dropped from the list when loaded. + +## Capabilities + +### New Capabilities + +- `pinned-nodes`: the pin list, its persistence and ordering, the Pinned panel, and the context-menu affordance that manages it. + +### Modified Capabilities + +- `app-chrome`: the sidebar's panel set gains the Pinned panel between Calendar and Similar. + +## Impact + +- `crates/trawler/src/main.rs`: `SIDEBAR_PANELS` (new panel + rendering), context-menu overlay component (anchored/deferred pattern, per the completion popup precedent), right-click handlers on bullet/heading/panel rows, dismiss handling (escape, click-away). +- `crates/trawler-core`: doc-level ordered list accessor for pins (Loro list; movable list keeps future drag-reorder open). +- No graph format version bump: the list is optional and ignored by old builds. +- Sequencing: independent of `home-document` and `create-standalone-page`; shares the `tree:…`/`tag:…` id serialization with `home-document`, so whichever lands first introduces the shared helper. Note: the app-chrome delta text includes `home-document`'s sidebar-footer sentence — archive `home-document` first, or trim that sentence from this change's delta if this one lands first. diff --git a/openspec/changes/pinned-sidebar/tasks.md b/openspec/changes/pinned-sidebar/tasks.md new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/tasks.md @@ -0,0 +1,23 @@ +# Tasks: pinned-sidebar + +## 1. Storage + +- [ ] 1.1 Add a doc-level ordered pin list accessor to trawler-core (Loro list of `tree:…`/`tag:…` ids; reuse or introduce the shared node-id serialization helper with `home-document`). Append, remove, load-with-dedup. +- [ ] 1.2 Drop dangling `tree:` entries on load; unit-test append/remove/round-trip, dedup of duplicate entries, and dangling-node removal. + +## 2. Context-menu infrastructure + +- [ ] 2.1 Build a generic context-menu overlay (anchored + deferred, completion-popup pattern): open at cursor with a target node id and item list; dismiss on escape, outside click, and selection; at most one open, tracked app-side. +- [ ] 2.2 Wire right-click (right mouse-down) on outline bullets and page headings to open the menu with Pin-to-sidebar/Unpin per the node's current state. +- [ ] 2.3 UI tests: menu opens on right-click, escape and outside-click dismiss without action, selection pins and closes. + +## 3. Pinned panel + +- [ ] 3.1 Add the Pinned panel to `SIDEBAR_PANELS` between Calendar and Similar: rows show display names (same resolution as quick-open), click navigates via `navigate_to`, empty state renders a placeholder line matching Similar's style. +- [ ] 3.2 Right-click on a panel row opens the same menu with Unpin. +- [ ] 3.3 UI tests: pin → row appears in order; unpin from panel → row gone and outline menu offers Pin again; row click navigates and back returns; renamed page's row updates; pins survive relaunch (storage-level). + +## 4. Verification + +- [ ] 4.1 Devtools dump: expose the pinned list and whether a context menu is open, for UI-test and dev-loop assertions. +- [ ] 4.2 Full check: `cargo fmt`, clippy `-D warnings`, `cargo test --workspace`; dev-loop screenshot pass (panel with entries, empty state, open menu). diff --git a/openspec/changes/create-standalone-page/specs/page-creation/spec.md b/openspec/changes/create-standalone-page/specs/page-creation/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/create-standalone-page/specs/page-creation/spec.md @@ -0,0 +1,29 @@ +# page-creation Specification (delta) + +## ADDED Requirements + +### Requirement: Standalone page creation +The system SHALL provide a keyboard-bound New-Page action (default `ctrl-n`) that creates a new root page with no parental backlink, navigates to it, and focuses its heading for naming in place. The new page SHALL start with an empty heading and one empty first block, and creation MUST NOT require any existing block to reference it. + +#### Scenario: Create and name in one flow +- **WHEN** the user presses `ctrl-n` from any view and types `reading-notes` +- **THEN** a new page exists whose heading reads `reading-notes`, the text landed in the heading without any intermediate dialog, and pressing Enter moves the caret into the page's first block + +#### Scenario: Creation from an unfocused home +- **WHEN** the application opened on a home document with no block focused and the user presses `ctrl-n` +- **THEN** a fresh page opens with its heading focused, ready to type + +### Requirement: Abandoned new pages are swept +The system SHALL delete a page when navigation leaves it if it is still completely untouched — empty heading, no non-empty child blocks, and no inbound references. A page with any name, content, or reference SHALL be kept. Only the page being navigated away from SHALL be examined. + +#### Scenario: Abandoned creation leaves no litter +- **WHEN** the user presses `ctrl-n` and then navigates back without typing anything +- **THEN** the created page no longer exists and does not appear in quick-open + +#### Scenario: Named page survives +- **WHEN** the user presses `ctrl-n`, types a name, and navigates away +- **THEN** the page persists and is findable in quick-open by that name + +#### Scenario: Referenced page survives +- **WHEN** a still-empty new page gains an inbound reference from another block before the user navigates away from it +- **THEN** the page persists diff --git a/openspec/changes/home-document/specs/app-chrome/spec.md b/openspec/changes/home-document/specs/app-chrome/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/specs/app-chrome/spec.md @@ -0,0 +1,48 @@ +# app-chrome Specification (delta) + +## ADDED Requirements + +### Requirement: Home and Journal header controls +The header/titlebar row SHALL present a Home control and a Journal control in place of the previous Calendar button. Home SHALL navigate to the designated home document (the journal timeline when none is designated); Journal SHALL always navigate to the journal timeline. The calendar SHALL remain reachable via the sidebar and its keyboard shortcut. + +#### Scenario: Journal always one click away +- **WHEN** a home document is designated and the user clicks the Journal header control +- **THEN** the journal timeline opens + +#### Scenario: Home with nothing designated +- **WHEN** no home document is designated and the user clicks the Home header control +- **THEN** the journal timeline opens (the control is never inert) + +## MODIFIED Requirements + +### 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. The sidebar SHALL end in a footer containing a +Settings button that opens the Settings screen. + +#### 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 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 + +#### Scenario: Settings lives in the footer +- **WHEN** the sidebar is open +- **THEN** a footer row at the bottom of the sidebar shows a Settings button, + and clicking it opens the Settings screen in the main column diff --git a/openspec/changes/home-document/specs/graph-navigation/spec.md b/openspec/changes/home-document/specs/graph-navigation/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/specs/graph-navigation/spec.md @@ -0,0 +1,14 @@ +# graph-navigation Specification (delta) + +## ADDED Requirements + +### Requirement: Home and Journal navigation actions +The system SHALL provide a keyboard-bound Go-Home action (default `ctrl-h`) that navigates to the designated home document — the journal timeline when none is designated — and a Journal navigation action invocable from the header. Both SHALL push onto navigation history like any other navigation, and the Settings screen SHALL participate in history the same way. + +#### Scenario: Go home from anywhere +- **WHEN** the user presses `ctrl-h` while viewing an arbitrary page +- **THEN** the view navigates to the home document, and navigate-back returns to the prior page + +#### Scenario: Settings is a history entry +- **WHEN** the user opens Settings, navigates to the journal via the Journal control, and invokes navigate-back twice +- **THEN** the view returns first to Settings, then to whatever preceded it diff --git a/openspec/changes/home-document/specs/home-document/spec.md b/openspec/changes/home-document/specs/home-document/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/specs/home-document/spec.md @@ -0,0 +1,47 @@ +# home-document Specification (delta) + +## ADDED Requirements + +### Requirement: Home document designation +The system SHALL allow designating exactly one node — a page (tree root) or a tag — as the graph's home document, stored in the Loro document as a single last-write-wins value. An absent value SHALL mean the journal timeline is the home. The designation SHALL persist across relaunches and travel with the graph. + +#### Scenario: Home survives relaunch +- **WHEN** the user sets a page as the home document and relaunches the application +- **THEN** the application opens on that page + +#### Scenario: Tag as home +- **WHEN** the user sets the tag `#inbox` as the home document and relaunches +- **THEN** the application opens on the `#inbox` tag view (header plus backlinks), exactly as if navigated to + +### Requirement: Startup honors the home document +On startup the application SHALL open the designated home document; with no designation it SHALL open the journal timeline exactly as before. Opening a non-journal home SHALL NOT auto-focus any block. + +#### Scenario: Unset home preserves today's behavior +- **WHEN** the application starts with no home document designated +- **THEN** the journal timeline opens with today's page focused in its first block, identical to the pre-change behavior + +#### Scenario: Pinned home opens unfocused +- **WHEN** the application starts with a page designated as home +- **THEN** that page is displayed with no block focused and no editor attached + +### Requirement: Deleted home falls back and clears +If the designated home is a tree node that no longer exists, resolving the home (at startup or via the Home action) SHALL open the journal timeline and clear the stored designation. + +#### Scenario: Home node was deleted +- **WHEN** the application starts and the designated home page has been deleted from the graph +- **THEN** the journal timeline opens, and the Settings screen subsequently shows the home as "Journal (default)" + +### Requirement: Settings screen +The application SHALL provide a Settings screen rendered as a full main-column view that participates in navigation history, opened from a Settings button in a sidebar footer. It SHALL show the current home document and allow changing it via a type-ahead picker over pages and tags, including a "Journal (default)" option that clears the designation. + +#### Scenario: Open settings and set home +- **WHEN** the user clicks the sidebar footer's Settings button, opens the home picker, and selects a page +- **THEN** the Settings screen shows that page as the home document, and the change is persisted + +#### Scenario: Settings participates in history +- **WHEN** the user opens Settings from a page and invokes navigate-back +- **THEN** the view returns to that page + +#### Scenario: Unset reads as default +- **WHEN** the user opens Settings on a graph with no home designated +- **THEN** the home setting displays "Journal (default)" rather than an empty or error state diff --git a/openspec/changes/home-document/specs/journal/spec.md b/openspec/changes/home-document/specs/journal/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/home-document/specs/journal/spec.md @@ -0,0 +1,29 @@ +# journal Specification (delta) + +## MODIFIED Requirements + +### Requirement: Automatic daily pages +The system SHALL ensure a journal page exists for the current local date whenever the journal timeline is opened, or the date rolls over while the journal timeline is displayed. Journal pages SHALL be ordinary page nodes distinguished by a date attribute, created lazily and never duplicated. Days on which the journal timeline was never displayed SHALL NOT accumulate journal pages. + +#### Scenario: First journal visit of the day +- **WHEN** the journal timeline is opened on a date with no existing journal page +- **THEN** a journal page for that date exists in the timeline + +#### Scenario: No empty-page litter +- **WHEN** a journal page was auto-created but never received content +- **THEN** it is not shown in the timeline on subsequent days + +#### Scenario: Unvisited days create nothing +- **WHEN** the application runs for several days with a non-journal home document and the journal timeline is never opened +- **THEN** no journal pages exist for those days + +### Requirement: Journal timeline is the default home view +The application SHALL open to a chronological timeline with today's journal page at the top and previous non-empty journal pages loading below it as the user scrolls, unless a home document is designated (see the home-document capability), in which case the timeline remains reachable via the Journal header control and renders identically when visited. + +#### Scenario: Opening the app +- **WHEN** the application starts with no home document designated +- **THEN** today's journal page is displayed with focus in its first block, and scrolling down reveals prior days in reverse-chronological order + +#### Scenario: Timeline unchanged when reached from a home document +- **WHEN** a home document is designated and the user invokes the Journal header control +- **THEN** the journal timeline renders with today's page at the top exactly as it does at startup without a home document diff --git a/openspec/changes/pinned-sidebar/specs/app-chrome/spec.md b/openspec/changes/pinned-sidebar/specs/app-chrome/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/specs/app-chrome/spec.md @@ -0,0 +1,36 @@ +# app-chrome Specification (delta) + +## MODIFIED Requirements + +### Requirement: Collapsible right sidebar hosting panels +The application SHALL provide a right-hand sidebar that hosts named panels — +the calendar picker, the Pinned panel, and the Similar-blocks panel, in that +order — 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. The sidebar SHALL end in a footer +containing a Settings button that opens the Settings screen. + +#### Scenario: Panels live in the sidebar +- **WHEN** the sidebar is open with a block focused +- **THEN** the calendar, the Pinned panel, and the Similar-blocks list render + as sidebar panels in that order, 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 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 + +#### Scenario: Settings lives in the footer +- **WHEN** the sidebar is open +- **THEN** a footer row at the bottom of the sidebar shows a Settings button, + and clicking it opens the Settings screen in the main column diff --git a/openspec/changes/pinned-sidebar/specs/pinned-nodes/spec.md b/openspec/changes/pinned-sidebar/specs/pinned-nodes/spec.md new file mode 100644 --- /dev/null +++ b/openspec/changes/pinned-sidebar/specs/pinned-nodes/spec.md @@ -0,0 +1,44 @@ +# pinned-nodes Specification (delta) + +## ADDED Requirements + +### Requirement: Pinned list is graph state +The system SHALL maintain an ordered list of pinned nodes (pages or tags) in the Loro document, persisting across relaunches and traveling with the graph. Pinning SHALL append to the list; unpinning SHALL remove the entry. A pinned tree node that no longer exists SHALL be dropped from the list when it is loaded. Duplicate entries (e.g. from concurrent pinning) SHALL be deduplicated on load, keeping the first occurrence. + +#### Scenario: Pins survive relaunch in order +- **WHEN** the user pins page A, then tag `#inbox`, then page B, and relaunches +- **THEN** the Pinned panel lists A, `#inbox`, B in that order + +#### Scenario: Deleted node's pin disappears +- **WHEN** a pinned page is deleted from the graph +- **THEN** the Pinned panel no longer shows it after the list next loads, without error + +### Requirement: Pinned sidebar panel +The sidebar SHALL host a Pinned panel between the Calendar and Similar panels, listing each pinned node by display name. Clicking a row SHALL navigate to that node (pushing navigation history). An empty list SHALL render a placeholder line in the style of the Similar panel's empty state. + +#### Scenario: One click to a pinned tag +- **WHEN** `#inbox` is pinned and the user clicks its Pinned panel row from any view +- **THEN** the `#inbox` tag view opens, and navigate-back returns to the prior view + +#### Scenario: Empty state +- **WHEN** nothing is pinned +- **THEN** the Pinned panel shows a placeholder line (e.g. "Nothing pinned yet") rather than disappearing + +#### Scenario: Rename reflects immediately +- **WHEN** a pinned page is renamed +- **THEN** its Pinned panel row shows the new name (pins store node identity, not names) + +### Requirement: Context menu manages pins +The system SHALL open a context menu on right-click of an outline bullet, a page heading, or a Pinned panel row, offering exactly one pin action for that node: "Pin to sidebar" when unpinned, "Unpin" when pinned. The menu SHALL dismiss on selection, on escape, and on any click outside it, and at most one menu SHALL be open at a time. + +#### Scenario: Pin from the outline +- **WHEN** the user right-clicks a page heading and selects "Pin to sidebar" +- **THEN** the menu closes and the page appears at the end of the Pinned panel + +#### Scenario: Unpin from the panel +- **WHEN** the user right-clicks a Pinned panel row and selects "Unpin" +- **THEN** the row disappears from the panel and the node's outline context menu offers "Pin to sidebar" again + +#### Scenario: Dismiss without action +- **WHEN** the user right-clicks a bullet and then clicks elsewhere or presses escape +- **THEN** the menu closes and nothing is pinned