generated from eblume/project-template
heph.nvim: RPC client + buffer editing + wiki-links + journal (slice 11a)
Some checks failed
Build / validate (pull_request) Failing after 4s
Some checks failed
Build / validate (pull_request) Failing after 4s
The primary surface begins (tech-spec §8): a Neovim plugin that is a thin
client of the local hephd over its unix-socket JSON-RPC.
- node.resolve {title} → Node|null (heph-core Store + dispatch): exact,
owner-scoped, non-tombstoned alias-then-title match — the same mapping that
materializes wiki links, so follow-link jumps to the node the stored link
points at (never fuzzy search). Unit + rpc_socket integration tests.
- heph.nvim/: vim.uv unix-socket JSON-RPC client (blocking call via vim.wait,
id-demuxed, partial-line buffered, luanil so JSON null → Lua nil; isolated
Sessions for tests). Buffer-backed nodes (heph://node/<id>, acwrite;
BufReadCmd→node.get / BufWriteCmd→node.update, whole-buffer body round-trips
exactly through the CRDT). [[wiki-link]] follow on <CR>. Daily journal.
:Heph command surface + completion.
- Headless e2e (§9): a self-contained busted-style runner (tests/e2e/runner.lua)
— no external plugins, no network, deterministic CI exit codes. Specs: journal
round-trip, follow-link (+ unresolved no-op), link-two-docs/backlink.
`make -C heph.nvim test` builds hephd and runs it.
Docs: heph-nvim reference card, §14 tracker (11a done; 11b/11c/11d queued),
changelog fragment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87c76da659
commit
ee865e5635
29 changed files with 1240 additions and 10 deletions
|
|
@ -14,3 +14,4 @@ Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices:
|
|||
- Hub authentication (§13, slice 10a): the sync hub now verifies an OIDC bearer token on `/sync/*` and `/rpc` — RS256-pinned JWT validation with exact issuer/audience, expiry, and a required subject; JWKS discovered and cached, refetched on key rotation (`jsonwebtoken`). Enabled with `hephd --mode server --oidc-issuer <url> --oidc-audience <client-id>` (open when unset, for local dev). A single-tenant owner gate binds the hub to the first authenticated identity and rejects any other. Verification sits behind a `TokenVerifier` trait, so it's tested entirely offline (stub middleware + an adversarial battery against an in-process mock IdP).
|
||||
- Client authentication (§13, slice 10b): `heph auth login --hub-url <url> --issuer <url> --client-id <id>` runs the OAuth 2.0 device-code flow and caches the token in the OS keyring; spokes and `client` mode attach it to hub requests, refreshing on expiry (`--oidc-issuer`/`--oidc-client-id`). Offline-tested against a mock OAuth server and a full spoke-to-authenticated-hub loop. (Auth/proxy HTTP uses the runtime-free `ureq`, since `reqwest::blocking` is unsafe inside the async daemon.)
|
||||
- CI runs the Rust suite (fmt/clippy/test) via the project build hook.
|
||||
- `heph.nvim` slice 11a (§8) — the primary surface begins: a Neovim plugin that is a thin client of the local `hephd` over its unix socket. A `vim.uv` JSON-RPC client (blocking `call` via `vim.wait`, id-demuxed, partial-line buffered, JSON `null`→Lua `nil`); buffer-backed nodes (`heph://node/<id>` with `BufReadCmd`→`node.get` / `BufWriteCmd`→`node.update`, whole-buffer body round-tripping exactly through the CRDT); `[[wiki-link]]` follow on `<CR>` via a new exact `node.resolve {title}` RPC (alias-then-title, the same mapping that materializes `wiki` links — unresolved links allowed); the daily journal (`:Heph today`); and the `:Heph` command surface. Headless e2e (§9) drives the plugin against a real daemon over a temp socket with a self-contained busted-style runner (no external plugins, no network): journal round-trip, follow-link, and link-two-docs/backlink.
|
||||
|
|
|
|||
67
docs/reference/heph-nvim.md
Normal file
67
docs/reference/heph-nvim.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: heph.nvim
|
||||
modified: 2026-06-01
|
||||
tags:
|
||||
- reference
|
||||
- design
|
||||
---
|
||||
|
||||
# heph.nvim
|
||||
|
||||
The primary user surface (tech-spec §8): a Neovim plugin that replaces
|
||||
obsidian.nvim and is a **thin client of the local `hephd`** over its
|
||||
unix-socket JSON-RPC. Notes, journals, and tasks are edited as ordinary
|
||||
buffers; the daemon owns all storage and sync. Built in checkpointed slices on
|
||||
`feature/v1-prototype`; this card tracks the stable surface as it lands.
|
||||
|
||||
## Architecture
|
||||
|
||||
`heph.nvim/lua/heph/` modules, each small and single-purpose:
|
||||
|
||||
| Module | Responsibility |
|
||||
|---|---|
|
||||
| `rpc` | libuv (`vim.uv`) unix-socket JSON-RPC client. A blocking `call()` is built over the async pipe by pumping the loop with `vim.wait` until the matching id returns. Demuxes responses by id; partial lines are buffered; JSON `null` decodes to Lua `nil` (`luanil`). A `Session` is one connection — the module keeps a default singleton and lets tests open isolated sessions. |
|
||||
| `node` | Buffer-backed nodes. A node is a buffer named `heph://node/<id>` with `buftype=acwrite`; `BufReadCmd` loads the body via `node.get`, `BufWriteCmd` saves the whole buffer via `node.update`. |
|
||||
| `link` | Parse the `[[wiki-link]]` under the cursor (mirroring `extract.rs` grammar) and follow it via `node.resolve` (exact, never fuzzy `search`). Unresolved links are allowed. |
|
||||
| `journal` | Open/create a dated journal node (idempotent — deterministic id). |
|
||||
| `daemon` | Locate / spawn / readiness-poll `hephd` (shared with the e2e harness). |
|
||||
| `config` / `init` | `setup(opts)`, socket resolution, default keymaps. |
|
||||
| `command` | The `:Heph <subcommand>` dispatch + completion. |
|
||||
|
||||
Surfaces never touch SQLite — every operation is a daemon RPC (tech-spec §3).
|
||||
The plugin is **mode-agnostic**: Tactical/Strategic/Organizational are
|
||||
plugin-side compositions of daemon primitives, not daemon concepts.
|
||||
|
||||
## Daemon RPC dependencies
|
||||
|
||||
Beyond the existing methods (tech-spec §6), the plugin relies on
|
||||
**`node.resolve {title} → Node | null`**: an exact, owner-scoped,
|
||||
non-tombstoned alias-then-title match — the same mapping the store uses to
|
||||
materialize `wiki` links, so "follow link under cursor" jumps to the *same*
|
||||
node the stored link points at.
|
||||
|
||||
## Commands (as of slice 11a)
|
||||
|
||||
| Command | Action |
|
||||
|---|---|
|
||||
| `:Heph today` | Open today's journal |
|
||||
| `:Heph journal <YYYY-MM-DD>` | Open a dated journal |
|
||||
| `:Heph follow` (also `<CR>` in a node buffer) | Follow the `[[link]]` under the cursor |
|
||||
| `:Heph open <id>` | Open a node buffer by id |
|
||||
|
||||
Task/agenda views (`:Heph next`/`list`/`capture`, set-attention, done/drop),
|
||||
the per-task log, and context-item **promotion** arrive in slices 11b/11c.
|
||||
|
||||
## Testing (tech-spec §9)
|
||||
|
||||
The headless e2e suite drives the plugin in `nvim --headless` against a real
|
||||
`hephd` over a temp socket, asserting both buffer contents and resulting DB
|
||||
state (via an isolated RPC session). It uses a **self-contained busted-style
|
||||
runner** (`tests/e2e/runner.lua`) — no external plugins, no network — so CI is
|
||||
deterministic. `make test` builds the daemon and runs it; a deliberately
|
||||
failing spec exits non-zero (no false-green).
|
||||
|
||||
## Related
|
||||
|
||||
- [[tech-spec]] — §8 surface spec, §6 RPC API, §9 testing strategy
|
||||
- [[design]] — the mode model (Tactical/Strategic/Organizational) and rationale
|
||||
|
|
@ -13,6 +13,7 @@ Technical reference material for the repository tooling that ships with this pro
|
|||
## Project
|
||||
|
||||
- [[tech-spec]] — Hephaestus technical specification (data model, RPC API, "what is next?" ranking, recurrence, testing strategy, v1 scope)
|
||||
- [[heph-nvim]] — The Neovim plugin surface: architecture, buffer-backed editing, RPC dependencies, commands, and the headless e2e harness
|
||||
|
||||
## Template Surface Area
|
||||
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ See [[design]] §5–§7 for the constraints later phases impose on present choi
|
|||
|
||||
## 14. Implementation status (Phase 1 tracker)
|
||||
|
||||
> Cross-session resume tracker for the Phase 1 C1 (branch `feature/v1-prototype`, PR #1). Updated 2026-06-01 — **112 tests green** (`cargo test --all`), `clippy -D warnings` + `fmt` + `prek` clean. Workspace: `crates/heph-core`, `crates/hephd`, `crates/heph` (no `heph.nvim/` yet).
|
||||
> Cross-session resume tracker for the Phase 1 C1 (branch `feature/v1-prototype`, PR #1). Updated 2026-06-01 — **114 Rust tests green** (`cargo test --all`) + the heph.nvim headless e2e suite (`make -C heph.nvim test`), `clippy -D warnings` + `fmt` + `prek` clean. Workspace: `crates/heph-core`, `crates/hephd`, `crates/heph`, plus `heph.nvim/` (slice 11a).
|
||||
|
||||
**Done**
|
||||
|
||||
|
|
@ -345,14 +345,17 @@ See [[design]] §5–§7 for the constraints later phases impose on present choi
|
|||
- ✅ **Auth — client side (§13, slice 10b):** OAuth2 **device-code flow** (`hephd::oauth::DeviceFlow`: discover → start → poll handling `authorization_pending`/`slow_down`, + refresh). `TokenStore` (OS keyring via `keyring`, in-memory for tests); `current_bearer` refreshes on expiry. `heph auth login` runs the flow + caches the token; spokes (`sync_once`) and `client` mode (`RemoteStore`) attach the bearer, refreshing as needed (`--oidc-issuer`/`--oidc-client-id`). **All auth/proxy HTTP uses `ureq`** (runtime-free blocking) — `reqwest::blocking` panics inside the daemon's `spawn_blocking`; async `reqwest` remains only for `sync_once`. Tested offline against a mock OAuth server (device flow, refresh, store) + a full spoke⇄authed-hub loop.
|
||||
- ✅ **CLI (§1):** `heph` next/task/doc/get/export/search/journal/**auth login·logout**.
|
||||
- ✅ **CI (§9):** `.forgejo/scripts/build` runs fmt/clippy/test (self-bootstrapping rustup).
|
||||
- ✅ **`heph.nvim` slice 11a (§8) — the primary surface begins:** the Lua plugin (`heph.nvim/`) as a thin client of the `hephd` unix socket. **RPC client** over a `vim.uv` pipe (blocking `call` via `vim.wait`; id-demuxed; partial-line buffered; `luanil` so JSON `null`→`nil`; isolated `Session`s for tests). **Buffer-backed nodes** — `heph://node/<id>` buffers (`buftype=acwrite`), `BufReadCmd`→`node.get` / `BufWriteCmd`→`node.update` (whole-buffer body, CRDT-diffed; exact round-trip). **`[[wiki-link]]` follow** on `<CR>` via a new **`node.resolve {title}`** RPC (exact alias-then-title match, the same mapping that materializes `wiki` links — never fuzzy `search`; unresolved links allowed). **Daily journal** (`:Heph today`/`journal <date>`, idempotent). `:Heph` command surface + completion. **Headless e2e (§9):** drives the plugin in `nvim --headless` against a real daemon over a temp socket via a **self-contained busted-style runner** (`tests/e2e/runner.lua` — no external plugins/network, deterministic CI exit codes); specs cover journal round-trip, follow-link (+ unresolved no-op), and link-two-docs/backlink. `make -C heph.nvim test` builds the daemon and runs it.
|
||||
|
||||
**Not yet done (resume order)**
|
||||
|
||||
> The Rust backend is feature-complete; `heph.nvim` is the one remaining build slice. The rest are non-blocking polish + an end-of-v1 sweep (§11).
|
||||
> The Rust backend is feature-complete; `heph.nvim` is being built in checkpointed sub-slices (11a done). The rest are non-blocking polish + an end-of-v1 sweep (§11).
|
||||
|
||||
1. ⏳ **`heph.nvim` (§8) — the next slice, the primary surface:** obsidian.nvim parity + task/agenda views over the `hephd` unix socket; headless-nvim e2e (needs `neovim` + `plenary.nvim` on the CI runner). First non-Rust slice — likely wants its own short design pass (Lua layout, RPC client, CI runner setup) before coding.
|
||||
2. ⏳ **Adoption refinement + multi-tenant (§13) — non-blocking:** local→authed **adoption** currently rewrites `owner_id` (`adopt_owner`) but not yet the owner-embedded deterministic ids (journal/tag) + their links; and the hub is single-tenant (one owner per store) — owner-per-token storage is a future extension.
|
||||
3. ⏳ **Dependency-refresh pass (§11) — before declaring v1 done:** sweep all external deps to latest stable (e.g. `keyring` 3→4, which restructured to `keyring_core`), re-run the full suite.
|
||||
1. ⏳ **`heph.nvim` slice 11b (§8) — task views:** enrich `list` to titled rows; Tactical `next` + Organizational `list` views; task capture, set-attention, mark done/dropped; per-task log quick-append; `vim.ui.select` pickers (Telescope auto-upgrade when present). e2e: capture→next→context→checklist→done, and the recurring fresh-checklist workflow.
|
||||
2. ⏳ **`heph.nvim` slice 11c (§8) — promotion + CI runner:** add **`task.promote`** (mint a committed task from a `- [ ]` context-item line, rewrite it into a `[[link]]`; `item_ref` = 1-based code-fence-aware context-item index) + the in-buffer promote flow + its e2e; extend `.forgejo/scripts/build` to build `hephd` and run the nvim e2e suite (runner needs `neovim`; the self-contained busted runner needs **no** plenary).
|
||||
3. ⏳ **`heph.nvim` slice 11d (§6/§8) — DEFERRED, post-parity:** daemon **server-push** notification framing (no-`id` lines on the socket; the client read-loop already demuxes them) + the **dirty-buffer reconcile** (the §8 "known-hard" case) + the "update-arrives-while-open" e2e (§9).
|
||||
4. ⏳ **Adoption refinement + multi-tenant (§13) — non-blocking:** local→authed **adoption** currently rewrites `owner_id` (`adopt_owner`) but not yet the owner-embedded deterministic ids (journal/tag) + their links; and the hub is single-tenant (one owner per store) — owner-per-token storage is a future extension.
|
||||
5. ⏳ **Dependency-refresh pass (§11) — before declaring v1 done:** sweep all external deps to latest stable (e.g. `keyring` 3→4, which restructured to `keyring_core`), re-run the full suite.
|
||||
|
||||
## Related
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue