feat: wiki-links by id — id-first resolution + heph.nvim [[ picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 6m34s

Backend: `links::resolve_id` now checks for an exact live node id before
alias/title, so a canonical `[[NODEID]]` link resolves to its node and
can't be shadowed by a like-named node. Legacy `[[Name]]` links still
resolve by name (until the migration), so this is additive.

heph.nvim: `link.insert` (bound to insert-mode `[[` and `:Heph link`)
searches via the `search` RPC and inserts `[[NODEID]]`, with a "+ Create
new doc" entry; `<CR>` follow resolves the id directly. e2e covers
search→insert→materialize and the create path.

Remaining (§8.4): read-expansion/conceal display + the one-time
[[Title]]→[[NODEID]] migration (then retire name-resolution + the hack).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-06-03 12:07:46 -07:00
commit 4e8f6743cf
7 changed files with 139 additions and 9 deletions

View file

@ -0,0 +1,73 @@
-- Wiki-links by node id (§8.4): the `[[` picker searches nodes and inserts a
-- canonical `[[NODEID]]` link; a "Create" entry mints a new doc. The inserted
-- id resolves on follow and materializes as a `wiki` link on save.
local h = require("e2e.helpers")
describe("link insert picker", function()
local ctx
before_each(function()
ctx = h.start()
end)
after_each(function()
h.stop(ctx)
end)
-- Drive `link.insert` with a stubbed query + choice.
local function with_picker(query, pick, fn)
vim.g.heph_force_ui_select = true
local oi, os = vim.ui.input, vim.ui.select
vim.ui.input = function(_o, cb)
cb(query)
end
vim.ui.select = function(items, _o, cb)
cb(pick(items))
end
local ok, err = pcall(fn)
vim.ui.input, vim.ui.select, vim.g.heph_force_ui_select = oi, os, nil
assert.is_true(ok, tostring(err))
end
it("inserts a canonical [[NODEID]] for a searched node and materializes the link", function()
local target = h.create_doc("Roofing", "the roofing doc")
local src = h.create_doc("Daily", "")
local buf = h.open(src.id)
-- Put the cursor on an empty body line below the frontmatter.
vim.api.nvim_buf_set_lines(buf, -1, -1, false, { "" })
vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(buf), 0 })
with_picker("roofing", function(items)
return items[1] -- the search hit (FTS matches the "roofing" token)
end, function()
require("heph.link").insert()
end)
-- The buffer now carries `[[<target id>]]`, and saving materializes the link.
assert.is_truthy(h.find(buf, "%[%[" .. target.id), "[[id]] not inserted")
h.save(buf)
local linked = false
for _, l in ipairs(ctx.q:call("links.backlinks", { id = target.id })) do
if l.src_id == src.id and l.link_type == "wiki" then
linked = true
end
end
assert.is_true(linked, "expected a wiki link from src to the picked node")
end)
it("creates a new doc when the Create entry is chosen", function()
local src = h.create_doc("Notes", "")
local buf = h.open(src.id)
vim.api.nvim_buf_set_lines(buf, -1, -1, false, { "" })
vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(buf), 0 })
with_picker("Brand New Topic", function(items)
return items[#items] -- the "+ Create" sentinel is last
end, function()
require("heph.link").insert()
end)
local created = ctx.q:call("node.resolve", { title = "Brand New Topic" })
assert.is_truthy(created, "create entry should mint the doc")
assert.is_truthy(h.find(buf, "%[%[" .. created.id), "[[new id]] not inserted")
end)
end)