feat: node.linkable — first-class link targets for the [[ picker (§8.4)
Some checks failed
Build / validate (pull_request) Failing after 8s

The picker listed every node, so each task showed up twice (itself + its
same-titled canonical-context doc) plus tag/log noise — and the new
preview made the duplicates look identical. New `Store::list_linkable_nodes`
/ `node.linkable` returns non-tombstoned nodes minus `tag`s and the docs
that are a task's canonical-context or log attachment (you link the task,
not its body). The Telescope picker now sources from it.

Tests: a socket test (5 nodes → 2 linkable: task + standalone doc).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-06-03 13:15:29 -07:00
commit 2fc48a1aa9
9 changed files with 57 additions and 3 deletions

View file

@ -216,6 +216,10 @@ impl Store for RemoteStore {
self.call_as("node.list", json!({ "kind": kind.map(|k| k.as_str()) }))
}
fn list_linkable_nodes(&self) -> Result<Vec<Node>> {
self.call_as("node.linkable", json!({}))
}
fn journal_open_or_create(&mut self, date: &str) -> Result<Node> {
self.call_as("journal.open_or_create", json!({ "date": date }))
}

View file

@ -324,6 +324,7 @@ pub fn dispatch(store: &mut dyn Store, method: &str, params: Value) -> Result<Va
let p: NewNode = parse(params)?;
json!(store.create_node(p)?)
}
"node.linkable" => json!(store.list_linkable_nodes()?),
"node.update" => {
let p: UpdateParams = parse(params)?;
json!(store.update_node(&p.id, p.title, p.body)?)

View file

@ -274,6 +274,28 @@ fn tag_add_list_remove_over_socket() {
);
}
#[test]
fn node_linkable_excludes_context_docs_logs_and_tags() {
let (socket, _dir) = spawn_daemon();
let mut c = client(&socket);
// task.create → a task node + a same-titled canonical-context doc.
let task = c.call("task.create", json!({ "title": "Fix roof" })).unwrap();
let task_id = task["node_id"].as_str().unwrap().to_string();
// a standalone doc, a tag node, and a log doc (first append).
c.call("node.create", json!({ "kind": "doc", "title": "Notes" })).unwrap();
c.call("tag.add", json!({ "node_id": task_id, "tag": "house" })).unwrap();
c.call("log.append", json!({ "task_id": task_id, "text": "started" })).unwrap();
// Only first-class targets: the task and the standalone doc — not the
// context doc, the log doc, or the tag (5 nodes total ⇒ 2 linkable).
let nodes = c.call("node.linkable", json!({})).unwrap();
let arr = nodes.as_array().unwrap();
assert_eq!(arr.len(), 2, "expected just the task + standalone doc:\n{arr:#?}");
assert!(arr.iter().any(|n| n["title"] == "Fix roof" && n["kind"] == "task"));
assert!(arr.iter().any(|n| n["title"] == "Notes" && n["kind"] == "doc"));
}
#[test]
fn wikilinks_expand_on_read_and_collapse_on_write_over_socket() {
let (socket, _dir) = spawn_daemon();