[[ suggests titles and not-yet-created links first (#448) #564

Closed
kayg wants to merge 0 commits from issue/448-link-suggest into dev
Owner

Fixes the [[ picker from the production screenshot: [[Keshav Nayyar listed unrelated journal days and never "Keshav Nayyar" itself.

Cause

The picker showed whatever search.query returned, as titles. That search matches body text too, so days that only mention "Keshav" came back as if they were title matches. A link target that other notes already point at but nobody made is not a note, so search never knew it.

Order now

  1. Notes whose title matches: exact, starts with, a word starts with, holds it, letters in order (3+ letters, close together), then titles the search found with typos forgiven. Ties: what you link and open most (search.visit, now also sent when you choose a row), then newest. A day matches by its title and its ISO date. At most 6.
  2. Link targets referenced but never made (muted, note-and-+ icon, "linked from N notes"). Choosing one writes the same path, so it stays the same future note. Not offered: days, targets whose note is in the Trash, deleted or moved.
  3. Create "..." for exactly what was typed, unless a note (even this one) or a target already has that name.
  4. Mentioned in: notes holding the words in their text only, two lines each (title, then the snippet with the match marked). Only while fewer than 5 title/target matches, at most 4, never above titles.

Empty query is unchanged (what was used lately). Width, height, keyboard and the shared [[ @ / # picker are unchanged; # rows are untouched.

Where

  • crates/notes/src/missing/targets.rs, action notes.missing-targets {q, limit?}: reads the links index (covering links_target) into a short list of unmade targets kept at most 5 s and refreshed in the background (served stale meanwhile); a keystroke filters it in memory, checks the live note index and notes.link-states rules, and reads at most 6 linking notes to restore the real capitals. Covered by the isolation gate.
  • packages/editor/src/transforms/link-rank.ts: the ranking, pure, with test/link-rank.test.ts.
  • apps/web/src/lib/link-targets.ts, styles/cm.css: titles from the note list (ready-made folded titles), rows, section heading, snippet.
  • ADR 0040 notes the order.

Speed

Ranking at 10,000 notes (CPU ms per keystroke, node/V8): median 0.6-3.7, p95 <= 4.7 (worst query plans b 3.7 median). In Chromium on this busy host (load ~30): medians 0.3-3.2 ms, p95 <= 7; WebKit medians 1-5 ms. One-time cost to fold 10,000 titles: 35 ms (Chromium), 50 ms (WebKit), done in idle time after load. The server list at 550k links is one ~0.1-0.2 s pass at most every 5 s, never per keystroke.

Tests

  • cargo fmt --check, clippy --workspace --all-targets -D warnings, cargo test -p calternal-notes -p calternal (205 + isolation + contract): ok.
  • bun run check (action types + svelte-check): 0 errors.
  • vitest link-rank.test.ts: 14 passed (incl. the 10,000-note speed check).
  • Playwright on chromium-390, chromium-1440, webkit-390, webkit-1440 (--no-deps --workers=1): new link-suggest.spec.ts (5), links, links-look, missing-links, picker (existing spec now targets the note's own row), tag-picker: all green. Two timeouts in missing-links under host load passed on rerun.

Part of #448 / #449. Not for merging until kayg says so.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCcFhwwgMK66k9LMVqogNJ

Fixes the `[[` picker from the production screenshot: `[[Keshav Nayyar` listed unrelated journal days and never "Keshav Nayyar" itself. ## Cause The picker showed whatever `search.query` returned, as titles. That search matches body text too, so days that only mention "Keshav" came back as if they were title matches. A link target that other notes already point at but nobody made is not a note, so search never knew it. ## Order now 1. **Notes whose title matches**: exact, starts with, a word starts with, holds it, letters in order (3+ letters, close together), then titles the search found with typos forgiven. Ties: what you link and open most (`search.visit`, now also sent when you choose a row), then newest. A day matches by its title and its ISO date. At most 6. 2. **Link targets referenced but never made** (muted, note-and-+ icon, "linked from N notes"). Choosing one writes the same path, so it stays the same future note. Not offered: days, targets whose note is in the Trash, deleted or moved. 3. **Create "..."** for exactly what was typed, unless a note (even this one) or a target already has that name. 4. **Mentioned in**: notes holding the words in their text only, two lines each (title, then the snippet with the match marked). Only while fewer than 5 title/target matches, at most 4, never above titles. Empty query is unchanged (what was used lately). Width, height, keyboard and the shared `[[` `@` `/` `#` picker are unchanged; `#` rows are untouched. ## Where - `crates/notes/src/missing/targets.rs`, action `notes.missing-targets {q, limit?}`: reads the `links` index (covering `links_target`) into a short list of unmade targets kept at most 5 s and refreshed in the background (served stale meanwhile); a keystroke filters it in memory, checks the live note index and `notes.link-states` rules, and reads at most 6 linking notes to restore the real capitals. Covered by the isolation gate. - `packages/editor/src/transforms/link-rank.ts`: the ranking, pure, with `test/link-rank.test.ts`. - `apps/web/src/lib/link-targets.ts`, `styles/cm.css`: titles from the note list (ready-made folded titles), rows, section heading, snippet. - ADR 0040 notes the order. ## Speed Ranking at 10,000 notes (CPU ms per keystroke, node/V8): median 0.6-3.7, p95 <= 4.7 (worst query `plans b` 3.7 median). In Chromium on this busy host (load ~30): medians 0.3-3.2 ms, p95 <= 7; WebKit medians 1-5 ms. One-time cost to fold 10,000 titles: 35 ms (Chromium), 50 ms (WebKit), done in idle time after load. The server list at 550k links is one ~0.1-0.2 s pass at most every 5 s, never per keystroke. ## Tests - `cargo fmt --check`, `clippy --workspace --all-targets -D warnings`, `cargo test -p calternal-notes -p calternal` (205 + isolation + contract): ok. - `bun run check` (action types + svelte-check): 0 errors. - vitest `link-rank.test.ts`: 14 passed (incl. the 10,000-note speed check). - Playwright on chromium-390, chromium-1440, webkit-390, webkit-1440 (`--no-deps --workers=1`): new `link-suggest.spec.ts` (5), `links`, `links-look`, `missing-links`, `picker` (existing spec now targets the note's own row), `tag-picker`: all green. Two timeouts in `missing-links` under host load passed on rerun. Part of #448 / #449. Not for merging until kayg says so. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01WCcFhwwgMK66k9LMVqogNJ
[[ suggests titles and not-yet-created links first (#448)
Some checks failed
CI / server (pull_request) Successful in 25s
CI / rust (pull_request) Successful in 2m29s
CI / web (pull_request) Failing after 28s
523d10a419
Typing `[[Keshav Nayyar` listed unrelated journal days that only mention
the name in their text, and not "Keshav Nayyar" itself when other notes
already link to it but no note exists yet.

The picker now offers, in order: notes whose title matches (exact, starts
with, word starts with, holds, letters in order; ties by what you link
most, then newest; days also match by date), link targets other notes
point at that nobody made (muted, note-and-+ icon, "linked from N notes";
same path, so the same future note), a Create row for exactly what was
typed, and, only while fewer than five titles match, a "Mentioned in"
section with the matched words marked.

Server: notes.missing-targets reads the links index once into a short list
kept at most five seconds, so a keystroke only filters it in memory.
Client: the ranking is one pass over ready-made folded titles in
@calternal/editor (link-rank.ts), with unit tests and a speed check at
10,000 notes. Choosing a note now tells search (search.visit).

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

Landed in train z, #567.

Landed in train z, #567.
kayg closed this pull request 2026-10-18 02:42:26 +00:00
Some checks failed
CI / server (pull_request) Successful in 25s
CI / rust (pull_request) Successful in 2m29s
CI / web (pull_request) Failing after 28s

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!564
No description provided.