diff --git a/openspec/changes/trawler-mvp/.openspec.yaml b/openspec/changes/archive/2026-07-12-trawler-mvp/.openspec.yaml similarity index 100% rename from openspec/changes/trawler-mvp/.openspec.yaml rename to openspec/changes/archive/2026-07-12-trawler-mvp/.openspec.yaml diff --git a/openspec/changes/trawler-mvp/README.md b/openspec/changes/archive/2026-07-12-trawler-mvp/README.md similarity index 100% rename from openspec/changes/trawler-mvp/README.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/README.md diff --git a/openspec/changes/trawler-mvp/design.md b/openspec/changes/archive/2026-07-12-trawler-mvp/design.md similarity index 100% rename from openspec/changes/trawler-mvp/design.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/design.md diff --git a/openspec/changes/trawler-mvp/proposal.md b/openspec/changes/archive/2026-07-12-trawler-mvp/proposal.md similarity index 100% rename from openspec/changes/trawler-mvp/proposal.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/proposal.md diff --git a/openspec/changes/trawler-mvp/specs/block-graph/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/block-graph/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/block-graph/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/block-graph/spec.md diff --git a/openspec/changes/trawler-mvp/specs/graph-navigation/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/graph-navigation/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/graph-navigation/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/graph-navigation/spec.md diff --git a/openspec/changes/trawler-mvp/specs/journal/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/journal/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/journal/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/journal/spec.md diff --git a/openspec/changes/trawler-mvp/specs/outline-editor/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/outline-editor/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/outline-editor/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/outline-editor/spec.md diff --git a/openspec/changes/trawler-mvp/specs/search/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/search/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/search/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/search/spec.md diff --git a/openspec/changes/trawler-mvp/specs/steel-queries/spec.md b/openspec/changes/archive/2026-07-12-trawler-mvp/specs/steel-queries/spec.md similarity index 100% rename from openspec/changes/trawler-mvp/specs/steel-queries/spec.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/specs/steel-queries/spec.md diff --git a/openspec/changes/trawler-mvp/tasks.md b/openspec/changes/archive/2026-07-12-trawler-mvp/tasks.md similarity index 100% rename from openspec/changes/trawler-mvp/tasks.md rename to openspec/changes/archive/2026-07-12-trawler-mvp/tasks.md diff --git a/openspec/specs/block-graph/spec.md b/openspec/specs/block-graph/spec.md new file mode 100644 index 0000000..a2e8bef --- /dev/null +++ b/openspec/specs/block-graph/spec.md @@ -0,0 +1,60 @@ +# block-graph Specification + +## Purpose + +The core data model: blocks, outline hierarchy, pages, references, tags, and properties, with Loro-backed persistence and derived in-memory indexes. + +## Requirements + +### Requirement: Everything is a node +The system SHALL represent blocks, pages, tags, and journal dates as nodes in a single graph. Pages SHALL be nodes that own an ordered tree of block nodes; tags and dates SHALL be referenceable nodes with no special-cased storage. + +#### Scenario: A tag is navigable like a page +- **WHEN** a block is tagged `#project` +- **THEN** `project` exists as a node whose backlinks include that block, exactly as a `[[project]]` page reference would + +### Requirement: Stable block identity +Every block SHALL have a globally unique, stable identifier that survives edits, moves, reparenting, application restarts, and (future) sync merges. + +#### Scenario: Identity survives restructuring +- **WHEN** a block is edited, indented under a new parent, and the application is restarted +- **THEN** the block retains its original identifier and all references to it resolve + +### Requirement: Outline tree operations +The system SHALL support creating, deleting, splitting, and merging blocks, and moving blocks (with their subtrees) to any position under any parent, preserving sibling order. + +#### Scenario: Subtree move +- **WHEN** a block with three descendant blocks is moved under a different parent +- **THEN** the block and all three descendants appear at the new location in their original relative order, and the source location no longer contains them + +### Requirement: Loro document is the source of truth +The system SHALL persist the graph exclusively as a Loro CRDT document (movable tree for hierarchy, text for block content, maps for metadata), stored as a snapshot plus incremental update blobs in the graph directory. No derived structure may hold data that cannot be rebuilt from the Loro document. + +#### Scenario: Derived indexes are disposable +- **WHEN** all derived index files are deleted and the application is restarted +- **THEN** the graph loads completely from the Loro document and all queries, backlinks, and search results are identical to their pre-deletion state + +#### Scenario: Crash safety +- **WHEN** the process is killed immediately after an edit is acknowledged in the UI +- **THEN** on next startup the edit is present + +### Requirement: Block references and backlinks +Block content SHALL support inline references to pages, tags, dates, and individual blocks. The system SHALL maintain a backlink index mapping every node to the set of blocks that reference it, updated within the same edit transaction. + +#### Scenario: Backlink appears immediately +- **WHEN** the user types a reference to `[[fishing]]` inside a block and confirms it +- **THEN** the `fishing` node's backlink set includes the referencing block without an application restart or manual reindex + +### Requirement: Lightweight properties +Blocks SHALL support key–value properties where values are typed as text, number, date, boolean, or node reference. Property keys SHALL be queryable across the graph. Property schemas, classes, and inheritance are explicitly out of scope for this change. + +#### Scenario: Property round-trip +- **WHEN** a block is given the property `due` with the date value `2026-07-10` +- **THEN** a query for blocks with a `due` property returns the block, and the value is retrieved as a date (not a string) + +### Requirement: Incremental index maintenance +The in-memory graph and all derived indexes SHALL be updated incrementally on every edit, and an incremental state SHALL be behaviorally indistinguishable from a full rebuild. + +#### Scenario: Incremental equals rebuild +- **WHEN** a randomized sequence of at least 1,000 edit operations (create, edit, move, delete, tag, reference) is applied +- **THEN** the incrementally-maintained indexes are equal to indexes rebuilt from scratch from the Loro document diff --git a/openspec/specs/graph-navigation/spec.md b/openspec/specs/graph-navigation/spec.md new file mode 100644 index 0000000..21caa1c --- /dev/null +++ b/openspec/specs/graph-navigation/spec.md @@ -0,0 +1,42 @@ +# graph-navigation Specification + +## Purpose + +Moving through the graph: following references, backlinks, block zoom, and keyboard hopping between linked nodes. + +## Requirements + +### Requirement: Follow reference from keyboard +The system SHALL provide a keyboard action that follows the reference at or nearest the cursor in the focused block, navigating to the referenced node ("go to definition" for thoughts). + +#### Scenario: Hop to a referenced page +- **WHEN** the cursor is on a `[[nets]]` reference and the user invokes follow-reference +- **THEN** the view navigates to the `nets` page with focus in its first block + +### Requirement: Backlinks are always visible +When viewing any node (page or zoomed block), the system SHALL display the blocks that reference it, grouped by their containing page, each rendered with enough surrounding context to be intelligible and navigable to its source. + +#### Scenario: Backlink panel navigation +- **WHEN** the user selects an entry in the backlinks section +- **THEN** the view navigates to that block in its home page with the block focused + +### Requirement: Block zoom +The system SHALL allow zooming into any block, making it the temporary root of the view (showing only its subtree plus a breadcrumb of ancestors), and zooming back out. + +#### Scenario: Zoom in and out +- **WHEN** the user zooms into a block three levels deep +- **THEN** the view shows only that block's subtree with an ancestor breadcrumb, and invoking zoom-out (or a breadcrumb entry) restores the wider view + +### Requirement: Navigation history +The system SHALL maintain a navigation history with keyboard-accessible back and forward actions covering page visits, zooms, and reference hops. + +#### Scenario: Retrace a hop chain +- **WHEN** the user follows references across three pages and invokes back three times +- **THEN** the view returns through the visited locations in reverse order, restoring scroll and focus position at each step + +### Requirement: Quick open +The system SHALL provide a keyboard-invoked switcher that fuzzy-matches page and tag names (including journal dates) and navigates to the selection. + +#### Scenario: Jump by name fragment +- **WHEN** the user invokes quick open and types `trawl` +- **THEN** nodes whose names fuzzy-match (e.g., `trawler-design`) are listed, and confirming navigates to the selected node diff --git a/openspec/specs/journal/spec.md b/openspec/specs/journal/spec.md new file mode 100644 index 0000000..5789075 --- /dev/null +++ b/openspec/specs/journal/spec.md @@ -0,0 +1,32 @@ +# journal Specification + +## Purpose + +Daily notes: automatic date pages, date references, and the journal timeline as the application's home view. + +## Requirements + +### Requirement: Automatic daily pages +The system SHALL ensure a journal page exists for the current local date whenever the application opens or the date rolls over while running. Journal pages SHALL be ordinary page nodes distinguished by a date attribute, created lazily and never duplicated. + +#### Scenario: First launch of the day +- **WHEN** the application opens on a date with no existing journal page +- **THEN** a journal page for that date exists and is focused with an empty first block ready for input + +#### 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 + +### Requirement: Journal timeline is the 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. + +#### Scenario: Opening the app +- **WHEN** the application starts +- **THEN** today's journal page is displayed with focus in its first block, and scrolling down reveals prior days in reverse-chronological order + +### Requirement: Date references resolve to journal pages +Date references in block content SHALL be node references to the corresponding journal page, participating in backlinks like any other reference. Typing support SHALL make date references cheap to produce (e.g., a date picker or natural-language completion). + +#### Scenario: Scheduling by reference +- **WHEN** a block on any page references `[[2026-07-10]]` +- **THEN** the journal page for 2026-07-10 lists that block in its backlinks, visible when that day is viewed diff --git a/openspec/specs/outline-editor/spec.md b/openspec/specs/outline-editor/spec.md new file mode 100644 index 0000000..8376214 --- /dev/null +++ b/openspec/specs/outline-editor/spec.md @@ -0,0 +1,58 @@ +# outline-editor Specification + +## Purpose + +The GPUI editing surface: one-hot block editing, keyboard-first outline manipulation, and rendered display of inactive blocks. + +## Requirements + +### Requirement: One-hot block editing +The application SHALL maintain at most one live text input at any time — the focused block. All other blocks SHALL be rendered as read-only styled views. Moving focus SHALL commit the current block's content, re-render it, and attach the editor to the target block. + +#### Scenario: Focus transition commits content +- **WHEN** the user edits a block and presses Down to focus the next block +- **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. + +#### Scenario: Indent under previous sibling +- **WHEN** the cursor is in a block and the user presses Tab +- **THEN** the block (with its subtree) becomes the last child of its previous sibling, and the cursor position within the text is preserved + +#### Scenario: Newline within a block vs. new block +- **WHEN** the user presses Shift+Enter mid-block, types a second paragraph, then presses Enter +- **THEN** the block contains both paragraphs, and a new empty sibling block is created and focused + +#### Scenario: Fold hides descendants +- **WHEN** the user folds a block with descendants +- **THEN** descendants are hidden, a fold indicator is shown, and keyboard navigation skips the hidden blocks + +### 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. + +#### Scenario: Reference renders as a link +- **WHEN** a block containing `[[gear-maintenance]]` is not focused +- **THEN** the reference renders as a styled link displaying the target page's name + +#### Scenario: Multi-paragraph block renders fully +- **WHEN** a block containing two paragraphs and a fenced code block is not focused +- **THEN** both paragraphs and the syntax-styled code fence render within the single block, which folds, moves, and is referenced as one node + +#### Scenario: References in code are literal +- **WHEN** a block's fenced code block contains the text `[[not-a-link]]` +- **THEN** it renders as literal code, creates no backlink, and `not-a-link` gains no referencing node + +### Requirement: Editing latency +Focus transitions between blocks SHALL complete within one frame at 60Hz (under ~16ms) on the development machine, measured from keypress to the new editor accepting input, for outlines of at least 10,000 visible-tree blocks. + +#### Scenario: Rapid navigation stays responsive +- **WHEN** the user holds Down to traverse 50 consecutive blocks +- **THEN** focus tracks the keypress rate with no perceptible lag or dropped keystrokes + +### Requirement: Large outlines render lazily +The outline view SHALL virtualize rendering such that memory and frame time scale with visible blocks, not total blocks in the page. + +#### 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 diff --git a/openspec/specs/search/spec.md b/openspec/specs/search/spec.md new file mode 100644 index 0000000..5c3d8d5 --- /dev/null +++ b/openspec/specs/search/spec.md @@ -0,0 +1,42 @@ +# search Specification + +## Purpose + +Full-text search and lexical similar-content suggestions over the block graph. + +## Requirements + +### Requirement: Full-text search over all blocks +The system SHALL index the text content of every block (tantivy) and provide keyboard-invoked search returning matching blocks ranked by relevance, with match highlighting and navigation to the source block. + +#### Scenario: Search and jump +- **WHEN** the user invokes search and types `otter trawl` +- **THEN** blocks containing the terms are listed ranked by relevance with matches highlighted, and confirming an entry navigates to that block in its page + +### Requirement: Index freshness +Newly created or edited block content SHALL be searchable within one second of the edit, without manual reindexing. + +#### Scenario: Just-typed content is findable +- **WHEN** the user writes a block containing a novel term and invokes search for it within a second +- **THEN** the block appears in the results + +### Requirement: Search is available to queries +Full-text search SHALL be exposed as a set-returning primitive in the Steel query API, composable with other set primitives. + +#### Scenario: Search composed with a tag filter +- **WHEN** a query intersects full-text results for `"bycatch"` with the `#research` tag set +- **THEN** only blocks matching both are returned + +### Requirement: Similar blocks (lexical) +The system SHALL suggest blocks lexically similar to a given block (shared distinctive terms, more-like-this style), excluding the block itself and its ancestors/descendants, ranked by similarity. The similarity provider SHALL be a swappable interface so a semantic (embedding) tier can be added later without changing consumers. + +#### Scenario: Related note resurfaces +- **WHEN** the user views similar blocks for a block about "mending the cod-end after the last haul" +- **THEN** previously written blocks sharing distinctive vocabulary (e.g., other cod-end or net-repair notes) are listed and navigable, and unrelated blocks are not + +### Requirement: Search index is disposable +The search index SHALL be entirely derivable from the block graph. Deleting the index directory SHALL trigger a transparent rebuild on next startup with no data loss. + +#### Scenario: Rebuild after deletion +- **WHEN** the search index directory is deleted and the application restarts +- **THEN** search returns the same results as before deletion once the rebuild completes diff --git a/openspec/specs/steel-queries/spec.md b/openspec/specs/steel-queries/spec.md new file mode 100644 index 0000000..3555b02 --- /dev/null +++ b/openspec/specs/steel-queries/spec.md @@ -0,0 +1,60 @@ +# steel-queries Specification + +## Purpose + +The Steel (Scheme) query integration: read-only query API, query blocks with live previews, and the in-app Lisp editing experience. + +## Requirements + +### Requirement: Query blocks live in the outline +The system SHALL support a query block: an outline block whose content is a Steel expression and which renders its evaluation result inline beneath the expression. Query blocks SHALL be ordinary blocks (movable, referenceable, foldable). + +#### Scenario: Results render in place +- **WHEN** a query block contains `(blocks (and (tag 'project) (ref "rust")))` +- **THEN** the matching blocks render beneath the query as a navigable outline slice, each entry linking to its source block + +### Requirement: Read-only capability scoping +Query evaluation SHALL run in a Steel context where only read-only graph primitives are registered. No mutation of the graph, filesystem access, or network access SHALL be reachable from a query. + +#### Scenario: Mutation is unavailable +- **WHEN** a query attempts to call any mutating or I/O operation +- **THEN** evaluation fails with an unknown-identifier error and the graph is unchanged + +### Requirement: Set-algebra query primitives +The query API SHALL expose native set-returning primitives — at minimum: blocks by tag, blocks referencing a node, blocks with a property (optionally matching a value), blocks within a date range, descendants of a node, and full-text search results — plus native `and`/`or`/`not` combinators that operate on sets without per-block Steel callbacks. + +#### Scenario: Native combination stays fast +- **WHEN** a query intersects a 20,000-block tag set with a 5,000-block date-range set on a 100,000-block graph +- **THEN** evaluation completes within 50ms, with the intersection performed natively rather than by iterating blocks in Scheme + +#### Scenario: Scheme predicates over narrowed sets +- **WHEN** a query applies a Steel lambda filter to a set already narrowed to under 1,000 blocks +- **THEN** the per-block predicate is applied and results are correct + +### Requirement: Live preview with debounce +While a query block is being edited, the system SHALL re-evaluate it on a debounce and update the rendered results without requiring the user to leave the block. + +#### Scenario: Editing refines results live +- **WHEN** the user extends `(tag 'project)` to `(and (tag 'project) (since "2026-06"))` and pauses typing +- **THEN** the preview updates to the narrowed result set without explicit re-run + +### Requirement: Runaway query containment +Query evaluation SHALL run off the UI thread and SHALL be cancellable. A query exceeding its time budget SHALL be terminated and reported as timed out; the UI SHALL remain responsive throughout. + +#### Scenario: Infinite loop cannot freeze the app +- **WHEN** a query block contains a non-terminating expression +- **THEN** the UI continues to respond, the evaluation is terminated at the time budget, and the query block displays a timeout error + +### Requirement: Lisp editing experience +When editing a query block, the editor SHALL provide Scheme syntax highlighting (tree-sitter), matching-paren indication, and inline display of evaluation errors with position information where available. + +#### Scenario: Error is shown at the block +- **WHEN** a query contains an unbalanced paren or unknown identifier +- **THEN** the query block displays the error message inline instead of results, and the rest of the outline is unaffected + +### Requirement: Table rendering for property queries +Query results SHALL render as an outline slice by default, and as a table when the query projects named properties. + +#### Scenario: Projected properties become columns +- **WHEN** a query projects each matching block's content and its `due` property +- **THEN** results render as a two-column table with one row per block, sortable by column