Editor + web: the full @ / [[ picker: embeds, New note, instant (#165) #262

Closed
kayg wants to merge 1 commit from issue/165-picker-2 into dev
Owner

Part 2 of #165: the full @ / [[ picker. Replaces #234, rebased on dev. Stacked on the fast-fix PR (its first commit is that PR's). Merge that first; this PR then shows only its own commit.

  • Embeds: ![[ and !@ use the same picker and write ![Title](<Note.md>) for a note, or ![](<file>) for a picture or file. A hand-typed ![[Title]] becomes an embed too. (The card that draws an embed comes in part 3.)
  • Instant: the first answer comes on the same frame as the keystroke, from the note list already in memory: titles that contain the words, newest first, the top 8 kept as it goes (no sort of thousands of matches). About 60 ms after typing pauses, the one search (search.query, the same client as ⌘K) replaces it in place. Offline, the first answer stays. The editor no longer waits CodeMirror's default 100 ms before asking.
  • Never a dead end: if no note has exactly that title, the last choice is “New note “…””. It writes the link at once and makes the note with its H1. If the server names the file differently, the link is corrected.
  • Keyboard first: ↑ ↓ move, Enter or Tab chooses, Esc closes. Tab only chooses while the list is open; otherwise it still nests list items.
  • Look: each choice shows the app's own stroke icon (note, daily note, picture, file, and a plus for New note in the accent colour). Choosing a note ranks it higher next time (search.visit).
  • Attachments: the picker already handles image and file targets, but no attachment search provider exists on the server yet, so today it lists notes only.

Perf (10k-note fixture Home, real server, production web build, Chromium, frame.mjs in the issue thread). We count a keystroke when the picker's list content changes, then check whether that happened before the frame that paints the typed letter:

  • #220 (search only, 100 ms activation): 9 of 31 keystrokes; the list changed a median of 297 ms after the letter (p95 552).
  • This PR: 28 of 39 in Chromium and 15 of 17 in WebKit. In the rest, the instant answer was the same list as before, so only the later search answer changed it.
  • Note: this machine was under load average ~50 during these runs, so absolute times are inflated. Plain typing latency in the same run was p50 481 ms.

Tests: packages/editor/test/picker.test.ts (8) and e2e/picker.spec.ts ([[, @, ![[ + Tab, New note) pass in Chromium and WebKit at 390 and 1440.

🤖 Generated with Claude Code

Part 2 of #165: the full `@` / `[[` picker. Replaces #234, rebased on dev. **Stacked on the fast-fix PR** (its first commit is that PR's). Merge that first; this PR then shows only its own commit. - **Embeds:** `![[` and `!@` use the same picker and write `![Title](<Note.md>)` for a note, or `![](<file>)` for a picture or file. A hand-typed `![[Title]]` becomes an embed too. (The card that draws an embed comes in part 3.) - **Instant:** the first answer comes on the same frame as the keystroke, from the note list already in memory: titles that contain the words, newest first, the top 8 kept as it goes (no sort of thousands of matches). About 60 ms after typing pauses, the one search (`search.query`, the same client as ⌘K) replaces it in place. Offline, the first answer stays. The editor no longer waits CodeMirror's default 100 ms before asking. - **Never a dead end:** if no note has exactly that title, the last choice is “New note “…””. It writes the link at once and makes the note with its H1. If the server names the file differently, the link is corrected. - **Keyboard first:** ↑ ↓ move, Enter or Tab chooses, Esc closes. Tab only chooses while the list is open; otherwise it still nests list items. - **Look:** each choice shows the app's own stroke icon (note, daily note, picture, file, and a plus for New note in the accent colour). Choosing a note ranks it higher next time (`search.visit`). - Attachments: the picker already handles `image` and `file` targets, but no attachment search provider exists on the server yet, so today it lists notes only. **Perf** (10k-note fixture Home, real server, production web build, Chromium, `frame.mjs` in the issue thread). We count a keystroke when the picker's list content changes, then check whether that happened before the frame that paints the typed letter: - #220 (search only, 100 ms activation): 9 of 31 keystrokes; the list changed a median of 297 ms after the letter (p95 552). - This PR: 28 of 39 in Chromium and 15 of 17 in WebKit. In the rest, the instant answer was the same list as before, so only the later search answer changed it. - Note: this machine was under load average ~50 during these runs, so absolute times are inflated. Plain typing latency in the same run was p50 481 ms. Tests: `packages/editor/test/picker.test.ts` (8) and `e2e/picker.spec.ts` (`[[`, `@`, `![[` + Tab, New note) pass in Chromium and WebKit at 390 and 1440. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Web: @ and [[ offer notes again, from search (#165)
Some checks failed
CI / web (pull_request) Failing after 43s
CI / rust (pull_request) Successful in 2m45s
33c702bae5
Why they showed nothing: the editor's link shortcuts (@ and [[) only
offer notes when the app hands them a search, and Editor.svelte never
did. The completion source returned nothing on every keystroke.

- One search client (lib/search.ts) for ⌘K and the link picker, on
  search.query, so ranking, typo fixes and "used lately" match ⌘K.
- lib/link-targets.ts turns hits into link targets; offline or on an
  error it falls back to notes whose title matches. Never offers the
  note itself. A typed [[Title]] links the note with that title.
- The picker sits on <body> with the palette's glass and the app font,
  a set width, 12px from the screen's sides, so it never clips or
  overflows at 320.
- e2e/picker.spec.ts: [[ and @ find a note by search and Enter writes a
  standard [Title](<Title.md>) link, Chromium and WebKit, 390 and 1440.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Editor + web: the full @ / [[ picker: embeds, New note, instant (#165)
Some checks failed
CI / web (pull_request) Failing after 29s
CI / rust (pull_request) Successful in 2m48s
cc4c0499d0
- ![[ and !@ use the same picker and write an embed: ![Title](<Note.md>)
  for a note, ![](<file>) for a picture or file. A hand-typed
  ![[Title]] becomes an embed too.
- The first answer comes on the keystroke's own frame, from the note
  list already in memory (titles that contain the words, newest first,
  no sort of thousands of matches); a moment later the one search
  (same as ⌘K) replaces it in place. Offline the first answer stays.
  The editor no longer waits 100 ms before asking.
- "New note “…”" is the last choice when no note has exactly that
  title: it links at once and makes the note (with its H1); if the
  server names the file differently, the link is corrected.
- Tab chooses like Enter while the list is open; otherwise Tab keeps
  nesting lists.
- Each choice shows the app's own icon (note, daily note, picture,
  file, plus for New note). Choosing ranks the note higher next time
  (search.visit).
- Tests: packages/editor/test/picker.test.ts (links, embeds, pictures,
  instant then better answers, addresses aren't triggers, New note and
  its correction); e2e picker.spec.ts adds ![[ + Tab and New note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Author
Owner

Closed by kayg (2026-10-16). Most of it is now covered by the picker and links work (#465). Embeds get their own issue.

Closed by kayg (2026-10-16). Most of it is now covered by the picker and links work (#465). Embeds get their own issue.
kayg closed this pull request 2026-10-16 03:57:36 +00:00
Some checks failed
CI / web (pull_request) Failing after 29s
CI / rust (pull_request) Successful in 2m48s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!262
No description provided.