Skip to content

fix: /Link to note opens a live note picker instead of a dead [[ - #83

Merged
chasehuh merged 1 commit into
mainfrom
task/linkto-picker-ux
Aug 5, 2026
Merged

fix: /Link to note opens a live note picker instead of a dead [[#83
chasehuh merged 1 commit into
mainfrom
task/linkto-picker-ux

Conversation

@chasehuh

@chasehuh chasehuh commented Aug 5, 2026

Copy link
Copy Markdown
Member

Closes #82.

The bug

/Link to note was a dead end. It rewrote the slash trigger into [[
(optionally seeded with the typed query) and stopped there — the picker never
reopened, leaving a stranded [[dep in the user's prose with no popup and no
discoverable way forward.

Root cause

The obvious suspect is the empty-query guard in noteLinkSource:

if (!query && !context.explicit) return null;

That is only half of it, and fixing it alone would have changed nothing.

The apply dispatch used userEvent: "input" and set a selection.
@codemirror/autocomplete@6.20.3 getUpdateType reads that as neither
input.type nor input.complete"input" is not a parent event of
"input.type"; the dot-prefix relation runs the other way
— falls through to
tr.selection ? UpdateType.Reset, and ActiveSource.update forces every source
Inactive. An inactive source is never re-queried, so [[dep was just as dead
as bare [[. Only an explicit startCompletion — or a real keystroke, which is
why the bug read as flaky rather than deterministic — could bring the popup back.

The fix

Both handoffs (Link to note, and bare New note, which had the same dead
trigger) route through one openNotePicker helper that dispatches the trigger
and then calls startCompletion. The explicit flag that sets is also what
lets a query-less [[ list the whole catalog without weakening the guard
that keeps a stray bracket pair from popping the list open mid-sentence.

The Link handoff is additionally marked link-only via a StateField holding
the trigger's document offset. Link is a peer hyperlink, never a nesting create
(#80) — with no match, Create "…" would otherwise sit pre-selected one
Enter from filing the "linked" note as a sub-note of the one being
edited (#81). The offset (rather than a boolean) scopes the flag to the one
trigger it was set for, and it clears as soon as an edit overlaps the brackets
themselves, so deleting them and typing [[ by hand restores pick-or-create.

A zero-match link-only picker shows a non-actionable No note matches "…" row
rather than closing — an empty result closes the popup, and a closed popup over a
live [[ is exactly the dead trigger this is fixing. Keeping the source active
means backspacing narrows straight back to real notes.

Also excludes the active note from link candidates: self-linking is never useful.

Before / after

Gesture Before After
/ → Link to note [[, popup closed [[, picker open on the full catalog
/dep → Link to note [[dep, popup closed [[dep, picker open, filtered to "dep"
/dep → Link to note, no match Create "dep" pre-selected → sub-note No note matches "dep", nothing created
/ → New note (no title) [[, popup closed [[, picker open, Create appears once titled
hand-typed [[dep pick or create unchanged (#77)

Tests

New lib/editor/note-links.dom.test.ts drives the real autocompletion()
plugin in a mounted EditorView — a source-level test cannot see this bug class,
since the popup dies inside CodeMirror's transaction handling, not in what the
source returns. 9 of its 10 cases fail against the pre-fix code; the tenth is
the hand-typed [[ control, which passes both ways by design.

Covered: picker opens on bare and seeded handoff; narrowing across a space;
no Create in a link-only picker and createNote never called; recovery from a
non-matching query; the picked note inserting [Title](/n/{id}); New note
still offering Create; and link-only not leaking onto a hand-typed [[.

Verification

Notes

  • [[ remains a transient trigger; storage is still plain [Title](/n/{id})
    Markdown, so the CRDT, publish, and note_revisions paths are untouched. The
    state field is local editor state and never reaches Yjs.
  • agentnoteCompletion sits in the shared extension array used with and without
    yCollab, so the CRDT path gets the same behavior by construction.
  • The fix uses only public API (startCompletion); the RCA's dependence on
    getUpdateType internals cannot silently reintroduce the bug on an upgrade,
    and the DOM tests would catch it if it tried.

🤖 Generated with Claude Code

Picking `Link to note` rewrote the slash trigger into `[[` and stopped
there, so the note picker never reopened — the user was left with a
stranded `[[dep` in their prose and no popup.

The empty-query guard in `noteLinkSource` is only half of it. The apply
dispatch carries a selection but no `input.type` user event, and
`@codemirror/autocomplete`'s `getUpdateType` maps that to
`UpdateType.Reset`, forcing every source Inactive — so the source was
never re-queried even with a query seeded. Only an explicit
`startCompletion` (or a real keystroke, which is why the bug read as
flaky) can bring it back.

Both handoffs now route through `openNotePicker`, which dispatches the
trigger and re-arms completion. The `explicit` flag it sets is what lets
a query-less `[[` list the whole catalog without weakening the guard
that keeps stray brackets from popping the list open mid-sentence.

The Link handoff is additionally marked link-only through a state field
holding the trigger's offset, so its picker offers existing notes only.
Link is a peer hyperlink, never a nesting create (#80): with no match,
`Create "…"` would otherwise sit pre-selected one Enter from filing the
"linked" note as a sub-note. A zero-match link picker shows a
non-actionable row instead of closing, so backspacing narrows straight
back to real notes.

Also excludes the active note from link candidates — self-linking is
never useful.

`note-links.dom.test.ts` drives the real `autocompletion()` plugin,
because a source-level test cannot see a popup that dies in CodeMirror's
transaction handling. 9 of its 10 cases fail against the old code; the
tenth is the hand-typed `[[` control.

Closes #82

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
memo Ready Ready Preview Aug 5, 2026 7:37am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: Link to note must open the note picker (slash → [[ handoff UX)

1 participant