hephaestus/docs/reference/heph-nvim.md
Erich Blume 9d84eb7427
Some checks failed
Build / validate (pull_request) Failing after 1m17s
feat(nvim): do/late date chip (+ ↻) on task-view rows (§8)
`:Heph next`/`list` rows now render a compact relative do/late date chip
(today/tomorrow/yesterday/MM-DD/YYYY-MM-DD, mirroring heph-tui's fmt) and
a recurrence ↻, so scheduling is visible at a glance. `<CR>` already jumps
to a row's canonical-context doc. e2e: a do-date-chip render assertion.

Completes the §14 item-2 task-list UX wave.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 11:05:02 -07:00

6.8 KiB

title modified tags
heph.nvim 2026-06-03
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).
config / init setup(opts), socket resolution, default keymaps. The plugin is connect-only — it never spawns a daemon (see Daemon lifecycle).
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 lifecycle

The plugin is connect-only: it never spawns or supervises a hephd. The daemon is an explicit, OS-managed service started once with heph daemon start (a launchd agent on macOS, a systemd user service on Linux — see run-the-daemon); every surface (CLI, TUI, this plugin) is a pure client of that one daemon. On setup({}) the plugin resolves the socket and connects; if nothing is serving it, it notifies once with guidance to run heph daemon start (and a dropped connection is retried with a plain reconnect — never a spawn). The $HEPH_SOCKET / $HEPH_DB env knobs (and mise run dev) point a dev Neovim at a separate daemon + DB so real data is never touched.

History: earlier iterations had the plugin auto-spawn and supervise its own daemon (autostart, self-heal, kill-on-exit). That was removed once the CLI became a first-class surface — a daemon owned by one surface can't be shared, so lifecycle moved to an explicit service (design §4).

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. When the target doesn't resolve, follow creates a doc with that title (the zettelkasten follow-or-create gesture) and materializes the source's backlink (saving the source if it has unsaved edits, else adding the wiki link directly).

Commands (as of slice 11c)

Command Action
:Heph home Open the home / index landing page (created on first use; title via opts.home)
:Heph today / :Heph journal <YYYY-MM-DD> Open today's / a dated journal
:Heph journals Pick among recent days (preview existing, @create for new); count via opts.journal_days
:Heph follow (also <CR> in a node buffer) Follow the [[link]] under the cursor — creating the target doc if it doesn't exist yet
:Heph doc <title> Create (and open) a new wiki doc
:Heph open <id> Open a node buffer by id
:Heph search <query> Full-text search; pick a result to open
:Heph next [scope] Tactical "what is next?" view (<CR> opens a task's context)
:Heph list [attention] Organizational survey of the outstanding set
:Heph view <name> Run a built-in filter view (tom|ondeck|chores|work|tasks, tech-spec §8.2)
:Heph capture <title> Capture a committed task (pick attention)
:Heph attention [color] Set the current task's attention
:Heph done / :Heph drop / :Heph skip State change on the current task
:Heph promote [attention] Promote the - [ ] line under the cursor to a committed task
:Heph log <text> Append a breadcrumb to the current task's log

"Current task" is resolved from the buffer: a task node, or a canonical-context doc whose owning task is followed via its canonical-context backlink. The next/list views render the titled rows the daemon returns (list enriched to carry titles + the context id, so no N+1 node.get) and are interactive: <CR> opens a task's context, a adds a task (prompt title + attention), d marks the task under the cursor done, r refreshes. Each row also shows a compact do/late date chip (and a recurrence ). Pickers use built-in vim.ui.select, auto-upgrading to Telescope when installed.

Promotion (:Heph promote) mints a committed task from the - [ ] line under the cursor (the daemon's task.promote, item_ref = the cursor item's 1-based index among context items, computed code-fence-aware to mirror extract.rs) and rewrites that line into a [[link]] to the new task. To keep that link unambiguous, wiki-link resolution excludes canonical-context docs, so [[Task Title]] resolves to the task, not its identically-titled context doc.

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 it is deterministic. mise run test-nvim builds the daemon and runs the suite against system-installed Neovim; a deliberately failing spec exits non-zero (no false-green; the runner also fails if it discovers zero specs). CI runs the same suite through the test-nvim Dagger function (.dagger/, invoked by build.yaml as dagger call test-nvim), which bakes a pinned, arch-detected Neovim onto a Rust image, builds hephd, and runs the suite — reproducible, and identical to the native mise run test-nvim path.

  • tech-spec — §8 surface spec, §6 RPC API, §9 testing strategy
  • design — the mode model (Tactical/Strategic/Organizational) and rationale