feat: unlinked references in the backlinks panel (slice 3a) #6

Merged
fiatcode merged 17 commits from feat/unlinked-references into main 2026-06-13 12:25:04 +07:00
Owner

Summary

Slice 3a of the connect roadmap (read-only half of "unlinked references + linkify"). When the backlinks panel (b) opens, peekseq now also lists unlinked references — bare-text mentions of the page elsewhere in pages/+journals/ that aren't yet [[linked]] — in a second section beneath the linked backlinks. enter on an unlinked row jumps to that page. Read-only; no files are written.

How it works

  • search.Mentions(graphPath, name) runs rg -w -F --ignore-case (whole-word, literal, case-insensitive) for the page name.
  • graph.FilterUnlinked (pure, unit-tested) drops hits that are inside an existing [[…]], inside a fenced code block, or in the page's own file.
  • The backlinks view moved to a flat-row model rendering linked refs + an "Unlinked references" section in one scroll; App.unlinkedRefs runs detection on panel-open and injects the result (view stays display-only).
  • graph imports search (verified no cycle); detection is by page name (no alias tracking exists).

Conscious deviations / notes

  • Match highlighting skipped. The spec mentioned Span-based emphasis of the matched text, but the existing linked backlink rows don't highlight either — emphasising only unlinked rows would make the two sections inconsistent. The Match span is still computed (it drives the inside-[[…]] filter and anchors the future linkify slice). Deferred for consistency.
  • Detection is synchronous on b-press — rg on one name over two dirs is fast; spec-sanctioned, with an async escape hatch noted if it ever drags.
  • ripgrep failure degrades gracefully to no unlinked section (an error hint would be invisible behind the overlay — the slice-1 hidden-hint lesson).

Scope

Out (future slice 3b → v1.4.0): one-key linkify (the first writer to files other than the current page). Also out: alias matching, namespaced-leaf matching, frequency/length caps.

Test plan

  • go vet ./... && go test ./... green
  • Unit: search.Mentions whole-word; FilterUnlinked (own-file / fenced / inside-[[]] / unreadable-file); App unlinkedRefs on a temp graph
  • teatest golden for the combined linked+unlinked panel; existing backlinks goldens unchanged (byte-identical)
  • Manual smoke on device: open b on a page with bare mentions; confirm the unlinked section + enter navigation
## Summary Slice 3a of the connect roadmap (read-only half of "unlinked references + linkify"). When the backlinks panel (`b`) opens, peekseq now also lists **unlinked references** — bare-text mentions of the page elsewhere in `pages/`+`journals/` that aren't yet `[[linked]]` — in a second section beneath the linked backlinks. `enter` on an unlinked row jumps to that page. Read-only; no files are written. ## How it works - `search.Mentions(graphPath, name)` runs `rg -w -F --ignore-case` (whole-word, literal, case-insensitive) for the page name. - `graph.FilterUnlinked` (pure, unit-tested) drops hits that are inside an existing `[[…]]`, inside a fenced code block, or in the page's own file. - The backlinks view moved to a flat-row model rendering linked refs + an "Unlinked references" section in one scroll; `App.unlinkedRefs` runs detection on panel-open and injects the result (view stays display-only). - `graph` imports `search` (verified no cycle); detection is by page **name** (no alias tracking exists). ## Conscious deviations / notes - **Match highlighting skipped.** The spec mentioned `Span`-based emphasis of the matched text, but the existing *linked* backlink rows don't highlight either — emphasising only unlinked rows would make the two sections inconsistent. The `Match` span is still computed (it drives the inside-`[[…]]` filter and anchors the future linkify slice). Deferred for consistency. - **Detection is synchronous** on `b`-press — `rg` on one name over two dirs is fast; spec-sanctioned, with an async escape hatch noted if it ever drags. - ripgrep failure degrades gracefully to no unlinked section (an error hint would be invisible behind the overlay — the slice-1 hidden-hint lesson). ## Scope Out (future **slice 3b → v1.4.0**): one-key linkify (the first writer to files other than the current page). Also out: alias matching, namespaced-leaf matching, frequency/length caps. ## Test plan - [x] `go vet ./... && go test ./...` green - [x] Unit: `search.Mentions` whole-word; `FilterUnlinked` (own-file / fenced / inside-`[[]]` / unreadable-file); App `unlinkedRefs` on a temp graph - [x] teatest golden for the combined linked+unlinked panel; existing backlinks goldens unchanged (byte-identical) - [x] Manual smoke on device: open `b` on a page with bare mentions; confirm the unlinked section + `enter` navigation
Introduces blRow flat-list model for the Backlinks overlay. NewBacklinks
now accepts an unlinked []graph.UnlinkedRef param so the App can supply
ripgrep-detected bare-text mentions alongside the linked backlinks. When
unlinked is nil/empty the rendered output is byte-identical to before.
Adds unlinkedFixture and three tests: navigation saturates at last
unlinked row, header rows are never selectable, and a golden that
verifies the full rendered output of the overlay with both linked and
unlinked sections present.
docs: document unlinked references in help, README, AGENTS, CHANGELOG
All checks were successful
CI / vet-and-test (pull_request) Successful in 2m28s
622a05bbec
feat(views): focus the back-reference link when jumping from a backlink
All checks were successful
CI / vet-and-test (pull_request) Successful in 26s
13f581c84b
When the user presses enter on a linked backlink, the destination page
now lands with its link cursor positioned on the first link back to the
source page (auto-scrolled and highlighted). Unlinked refs are
intentionally excluded (no link to anchor). The cursor is persisted in
the history entry so [ / ] history navigation restores it correctly.
fix(graph): ExtractWikiLinks skips [[..]] inside inline code
All checks were successful
CI / vet-and-test (pull_request) Successful in 2m27s
36fd0d498a
Sign in to join this conversation.
No reviewers
No labels
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
fiatcode/weft!6
No description provided.