Personal outliner built with Rust.
3

Configure Feed

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

archive add-dev-automation: sync delta specs to openspec/specs, move change to archive/2026-07-14-add-dev-automation

graham.systems (Jul 14, 2026, 10:54 AM -0700) 4fd4496c 0b9dae0f

+166
openspec/changes/add-dev-automation/.openspec.yaml openspec/changes/archive/2026-07-14-add-dev-automation/.openspec.yaml
openspec/changes/add-dev-automation/design.md openspec/changes/archive/2026-07-14-add-dev-automation/design.md
openspec/changes/add-dev-automation/proposal.md openspec/changes/archive/2026-07-14-add-dev-automation/proposal.md
openspec/changes/add-dev-automation/specs/dev-automation-server/spec.md openspec/changes/archive/2026-07-14-add-dev-automation/specs/dev-automation-server/spec.md
openspec/changes/add-dev-automation/specs/fixture-graphs/spec.md openspec/changes/archive/2026-07-14-add-dev-automation/specs/fixture-graphs/spec.md
openspec/changes/add-dev-automation/specs/ui-test-harness/spec.md openspec/changes/archive/2026-07-14-add-dev-automation/specs/ui-test-harness/spec.md
openspec/changes/add-dev-automation/tasks.md openspec/changes/archive/2026-07-14-add-dev-automation/tasks.md
+70
openspec/specs/dev-automation-server/spec.md
··· 1 + # dev-automation-server Specification 2 + 3 + ## Purpose 4 + 5 + 6 + A feature-gated local automation endpoint on the running app: keystroke/text injection through GPUI's own dispatch, structured UI state dumps (including window bounds), and window screenshot capture, over a JSON-lines TCP protocol on localhost. Gives agents and contributors on any platform an identical, Rust-native way to drive and observe trawler during development. 7 + 8 + ## Requirements 9 + 10 + ### Requirement: Server is doubly gated and localhost-only 11 + The automation server and its dependencies SHALL compile only under a `devtools` cargo feature that is off by default, and even in a devtools build SHALL start only when explicitly enabled at launch (`TRAWLER_DEVTOOLS=1`). The listener MUST bind to `127.0.0.1` only. Release builds MUST NOT contain the server. 12 + 13 + #### Scenario: Devtools build without opt-in 14 + - **WHEN** a binary built with `--features devtools` is launched without `TRAWLER_DEVTOOLS=1` 15 + - **THEN** no listener is started and no automation port file is written 16 + 17 + #### Scenario: Default build 18 + - **WHEN** the crate is built without the `devtools` feature 19 + - **THEN** the server module, `xcap`, and the wire-protocol dependencies are not compiled into the binary 20 + 21 + ### Requirement: Port discovery 22 + When the server starts it SHALL bind an OS-assigned port, print the port to stdout, and write it to a `devtools.port` file in the graph directory, so clients can discover the endpoint without configuration. 23 + 24 + #### Scenario: Client discovers the endpoint 25 + - **WHEN** the app starts with the server enabled using graph directory `G` 26 + - **THEN** `G/devtools.port` contains the bound port number, and connecting to `127.0.0.1:<port>` succeeds 27 + 28 + ### Requirement: JSON-lines request/response protocol 29 + The server SHALL accept one JSON request object per line and reply with exactly one JSON response object per line, in request order. Failures (unknown command, malformed JSON, command error) MUST produce `{"ok":false,"error":...}` responses and MUST NOT crash or otherwise affect the app. 30 + 31 + #### Scenario: Malformed request 32 + - **WHEN** a client sends a line that is not valid JSON or names an unknown `cmd` 33 + - **THEN** the server replies `{"ok":false,"error":...}` on one line and continues serving subsequent requests, with the app unaffected 34 + 35 + ### Requirement: Keystroke injection through GPUI dispatch 36 + A `keys` command SHALL parse its argument with gpui's `Keystroke::parse` syntax (e.g. `"ctrl-k"`) and dispatch it on the app window via `Window::dispatch_keystroke` on the main thread, replying only after the dispatch completes. A `type` command SHALL insert literal text into the focused editor as real typing would. Injection MUST NOT depend on OS input APIs or on the window having OS focus. 37 + 38 + #### Scenario: Driving quick-open without OS focus 39 + - **WHEN** the trawler window is unfocused or occluded and a client sends `{"cmd":"keys","keys":"ctrl-k"}` 40 + - **THEN** the quick-open switcher opens exactly as if the user pressed Ctrl+K, and the reply `{"ok":true}` arrives after the action has run 41 + 42 + #### Scenario: Typing text 43 + - **WHEN** a block is focused and a client sends `{"cmd":"type","text":"hello [["}` 44 + - **THEN** the focused block receives the text through the same input path as real typing, including triggering reference completion for `[[` 45 + 46 + #### Scenario: Invalid keystroke string 47 + - **WHEN** a client sends a `keys` command whose string `Keystroke::parse` rejects 48 + - **THEN** the server replies `{"ok":false,"error":...}` and dispatches nothing 49 + 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. 52 + 53 + #### Scenario: Dump reflects a completion popup 54 + - **WHEN** reference completion is open with candidates and a client sends `{"cmd":"dump"}` 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: Bounds available without a screenshot 58 + - **WHEN** a client sends `{"cmd":"bounds"}` (or reads bounds from a `dump`) 59 + - **THEN** the response contains the window's current position, size, and scale factor as reported by gpui 60 + 61 + ### Requirement: Window screenshot capture 62 + A `screenshot` command SHALL capture the app's own window (located by process id) to a PNG at the requested path using cross-platform Rust capture (`xcap`), running the capture off the GPUI main thread. Screenshot support is best-effort per platform: on platforms or compositors where capture fails (notably Linux Wayland), the command MUST fail soft with `{"ok":false,"error":...}` while all other commands remain functional. 63 + 64 + #### Scenario: Successful capture 65 + - **WHEN** a client sends `{"cmd":"screenshot","path":"shot.png"}` on a platform with working capture 66 + - **THEN** a PNG image of the trawler window is written to that path and the reply includes `{"ok":true}` with the resolved path 67 + 68 + #### Scenario: Capture unavailable 69 + - **WHEN** the same command runs where window capture is unsupported 70 + - **THEN** the reply is `{"ok":false,"error":...}` and subsequent `keys`/`type`/`dump` commands still work
+44
openspec/specs/fixture-graphs/spec.md
··· 1 + # fixture-graphs Specification 2 + 3 + ## Purpose 4 + 5 + 6 + Deterministic seeded graph construction in `trawler-core`, shared by the gpui test harness and interactive dev-server sessions, so state assertions and screenshots run against identical, reproducible content on every machine. 7 + 8 + ## Requirements 9 + 10 + ### Requirement: Fixture builder in trawler-core behind a feature 11 + `trawler-core` SHALL provide a fixture-graph builder, compiled only under a `fixtures` cargo feature (off by default), that constructs graph content through the real storage and mutation APIs — not by writing storage files directly. 12 + 13 + #### Scenario: Builder uses real APIs 14 + - **WHEN** the fixture builder seeds a graph directory 15 + - **THEN** the resulting directory is a valid trawler graph (openable by `GraphStorage`), with references, tags, and properties indexed exactly as if the content had been entered through the app 16 + 17 + #### Scenario: Feature-gated compilation 18 + - **WHEN** `trawler-core` is built without the `fixtures` feature 19 + - **THEN** the fixture module and its code are not compiled 20 + 21 + ### Requirement: Fixture content is deterministic 22 + Seeding the standard fixture SHALL produce equivalent graph content on every invocation and every machine: fixed journal dates (no clock reads), stable block ordering, and no randomness, so dumps and screenshots of a fixture graph are comparable across sessions. 23 + 24 + #### Scenario: Two seeds are equivalent 25 + - **WHEN** the standard fixture is seeded into two different directories, on the same or different machines 26 + - **THEN** both graphs contain the same pages, block tree shapes, block contents, tags, references, and properties 27 + 28 + ### Requirement: Standard fixture exercises core features 29 + The standard fixture SHALL include at minimum: a journal page on a fixed date, at least two named pages, a nested outline several levels deep, blocks carrying tags, page references, and typed properties, and one query block — small enough to inspect in a single screenshot, rich enough to exercise references, tags, properties, and query rendering. 30 + 31 + #### Scenario: Fixture supports a query-block screenshot 32 + - **WHEN** the app opens a freshly seeded standard fixture and navigates to the page holding the query block 33 + - **THEN** the query block evaluates against fixture content and renders a non-empty result 34 + 35 + ### Requirement: Seeding available to both consumers 36 + The fixture builder SHALL be callable in-process by gpui tests (against a tempdir) and invocable from the command line to seed a scratch directory for dev-server sessions (a `devtools`-gated flag on the trawler binary, e.g. `--seed-fixtures <dir>`), producing the same standard fixture through both paths. 37 + 38 + #### Scenario: Test consumes fixture in-process 39 + - **WHEN** a `#[gpui::test]` requests a seeded fixture graph in a tempdir 40 + - **THEN** the builder returns a ready graph directory the test opens `TrawlerApp` against, with no external process involved 41 + 42 + #### Scenario: Dev session consumes fixture via CLI 43 + - **WHEN** a contributor runs the seed command against a scratch directory and launches trawler with `TRAWLER_GRAPH_DIR` pointing at it 44 + - **THEN** the app opens the same standard fixture content the tests use
+52
openspec/specs/ui-test-harness/spec.md
··· 1 + # ui-test-harness Specification 2 + 3 + ## Purpose 4 + 5 + 6 + Headless gpui integration testing of the real `TrawlerApp` view: simulated keyboard input through the real keymap, deterministic assertions on outline/editor entity state, running identically on all platforms and in CI. 7 + 8 + ## Requirements 9 + 10 + ### Requirement: Tests drive the real app view headlessly 11 + The test harness SHALL construct the real `TrawlerApp` view (after the same `editor::init` and keymap initialization as `main()`) inside a gpui test window backed by the fake test platform, against a temporary fixture graph directory, with no OS window, GPU, or display required. 12 + 13 + #### Scenario: Smoke test on a fresh fixture graph 14 + - **WHEN** a `#[gpui::test]` opens a test window hosting `TrawlerApp` over a fixture graph in a tempdir and simulates typing a character 15 + - **THEN** the focused block's content contains that character, and the test passes without any real window or GPU being created 16 + 17 + #### Scenario: Platform-independent execution 18 + - **WHEN** `cargo test --workspace` runs on any supported development platform (including CI without a display) 19 + - **THEN** the gpui integration tests execute and produce the same results as on any other platform 20 + 21 + ### Requirement: Keyboard input goes through the real keymap 22 + Simulated input in tests MUST be dispatched as keystrokes through gpui's keymap dispatch (`simulate_keystrokes`/`simulate_input`), not by calling action handlers or editor methods directly, so that a passing test implies the user-visible key binding works. 23 + 24 + #### Scenario: A rebound key would fail the test 25 + - **WHEN** a test simulates `tab` on a focused block and the `tab` binding were removed from the keymap 26 + - **THEN** the test fails, because no action fires through dispatch 27 + 28 + ### Requirement: Documented editor keyboard behaviors are covered 29 + The harness SHALL include passing tests for each documented while-editing keyboard behavior: Enter splits a block at the cursor into a new sibling; Shift+Enter inserts a newline within the block; Tab indents under the previous sibling and Shift+Tab outdents; Backspace at the start of a block merges into the end of the previous sibling; Alt+Up/Alt+Down move a block among its siblings; Up/Down at a content boundary move focus to the previous/next visible block; `[[` and `#` open reference/tag completion; Escape dismisses an open completion popup. 30 + 31 + #### Scenario: Enter splits a block 32 + - **WHEN** a test places the cursor mid-content in a focused block and simulates `enter` 33 + - **THEN** the block tree contains a new following sibling holding the content after the cursor, and focus is on the new sibling 34 + 35 + #### Scenario: Backspace at start merges blocks 36 + - **WHEN** a test focuses a block with a previous sibling, places the cursor at offset 0, and simulates `backspace` 37 + - **THEN** the block's content is appended to the previous sibling, the block is removed from the tree, and focus moves to the merged block 38 + 39 + #### Scenario: Tab indents under previous sibling 40 + - **WHEN** a test focuses a block that has a previous sibling and simulates `tab` 41 + - **THEN** the block becomes the last child of that previous sibling and remains focused 42 + 43 + #### Scenario: Completion popup opens and dismisses 44 + - **WHEN** a test types `[[` in a focused block, then simulates `escape` 45 + - **THEN** the completion popup state is open with candidates after `[[`, and closed after `escape`, with block content preserved 46 + 47 + ### Requirement: Assertions read entity state, not pixels 48 + Tests SHALL assert on the app's entity state (block tree shape and content, focused block, cursor offset, selection, popup state) rather than on rendered output, and this asserted state MUST be the same state the dev-automation-server `dump` command serializes. 49 + 50 + #### Scenario: Structural assertion after a mutation 51 + - **WHEN** a test performs an indent and asserts the result 52 + - **THEN** the assertion inspects the block tree parent/child relationships from entity state, not rendered element geometry