Skip to content

ENG-1249 Implement Discourse context overlay in Live Preview - #1414

Closed
trangdoan982 wants to merge 4 commits into
eng-2248-add-a-cached-relations-index-and-link-resolutionfrom
eng-1249-live-preview-overlay
Closed

ENG-1249 Implement Discourse context overlay in Live Preview#1414
trangdoan982 wants to merge 4 commits into
eng-2248-add-a-cached-relations-index-and-link-resolutionfrom
eng-1249-live-preview-overlay

Conversation

@trangdoan982

@trangdoan982 trangdoan982 commented Sep 7, 2026

Copy link
Copy Markdown
Member

Second of three stacked PRs for ENG-1249. Stack: #1413this#1415. Based on #1413 — review that first; this diff is against it.

https://www.loom.com/share/a7a58ec7bfd54d34b702c395acffe819

Reviewer brief

Result: In Live Preview, every link to a discourse node carries an inline badge with that node's relation count. Selecting it opens a popover listing those relations, with the option to add one. A node with no relations still badges, reading 0, and its popover says "No discourse relation found". Toggled by Show discourse context overlay in General settings, applied without a reload. Reading view is unchanged and follows in #1415.

Review focus: how a badge learns its count changed, and the two CodeMirror flags whose meaning is easy to invert. Both expanded below.

How a badge gets on screen
flowchart LR
  DOC["editor text<br>see [[Claim A|this]]"] --> RE["extractLinktext<br>→ 'Claim A'"]
  RE --> RES["resolveDiscourseLinkTarget"]
  RES --> IDX[("RelationsIndex")]
  RES --> OUT["file, nodeType, count 4"]
  OUT --> W["CM6 widget decoration"]
  W --> BADGE["shared badge element"]
Loading

The badge is plain DOM, not React. Neither render surface has a React root where a badge is inserted, and mounting one per link — dozens per note — would be far heavier than the badge deserves. #1415 reuses this same element, which is what keeps the two surfaces from drifting apart visually.

React appears exactly once, in the popover, which renders RelationshipSection — the same component the sidebar panel uses, so the two cannot disagree about a node's relations.

How a badge learns its count changed — the subtle part

Nothing about the document changes when a relation is added. The count lives entirely outside the text being rendered.

sequenceDiagram
  participant U as User
  participant F as relations.json
  participant I as RelationsIndex
  participant V as ViewPlugin
  U->>F: add a relation
  F->>I: vault "modify"
  I->>I: mark stale, reload<br>keep old snapshot
  I->>I: version 7 → 8
  I->>V: notify → empty transaction
  V->>V: update: version changed?
  V->>U: rebuild, badge now 5
Loading

The trap is in the last two steps. update() early-returns when nothing relevant changed, and an empty transaction changes neither the document nor the viewport — so the guard swallowed exactly the signal the refresh was sending:

// before — the redraw ran and did nothing
if (!update.docChanged && !update.viewportChanged) return;

The fix gives update() something it can observe: the index exposes a monotonic version, compared alongside the setting.

[!NOTE]
This is why the index carries a version counter in #1413. A ViewPlugin can only see transactions, so state living outside the document has to be made visible to it explicitly.

Two flags whose meaning inverts easily

ignoreEvent() returns true — the CM6 default, and the one we want. The subject of "ignore" is the editor, not the widget: true keeps CM6 out of the way so the badge's own click listener fires. Returning false reads like "let the widget receive clicks" and does the opposite — an earlier revision had a badge that rendered perfectly and was completely inert.

updateDOM() returns true — meaning the element was updated in place and must not be replaced. CM6 rebuilds a widget's DOM whenever eq() is false, which a count change always is; replacing the element would detach the anchor an open popover is positioned against.

Verification

  • pnpm ci:validate: 8/8 check-types, 5/5 test:unit.
  • Driven against a real vault over CDP on this branch alone: badges render with the correct count, the popover opens and lists the same relations, the setting adds and removes badges live, and Reading view correctly shows nothing yet.

Scope check

  • Ran $scope-check against the ENG ticket and final diff.
  • Scope beyond Done When: None. Covers the setting and the overlay for one surface; the second follows.

Local delegated full review

  • Ran a comprehensive review of the entire final diff in a subagent with a fresh context.

Review findings addressed here: badges update in place so an open popover keeps a connected anchor; the outside-click guard no longer treats Obsidian's suggestion overlay as outside; the popover opens toward whichever side has more room; the empty-state message clears when the first relation is added; and a file that stops being a discourse node now triggers the refresh that removes its badges.

Warning

Known limitation: Live Preview skips a link straddling a viewport-range boundary. The badge also does not model buildGroupedRelations dropping relations whose endpoint no longer resolves to a file, so a relation pointing at a deleted note is still counted — that needs a file lookup per relation on a render path, so it is left as a follow-up.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Sep 7, 2026

Copy link
Copy Markdown

ENG-1249

@vercel

vercel Bot commented Sep 7, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
discourse-graph Ready Ready Preview Sep 8, 2026 9:31pm UTC

Request Review

@supabase

supabase Bot commented Sep 7, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project zytfjzqyijgagqxrzbmz because there are no changes detected in packages/database/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread apps/obsidian/src/components/DiscourseContextPopover.tsx
Comment thread apps/obsidian/src/components/DiscourseContextPopover.tsx Outdated
Comment thread apps/obsidian/src/components/DiscourseContextPopover.tsx
Comment thread apps/obsidian/src/utils/discourseContextOverlayRefresh.ts
@graphite-app

graphite-app Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

PR size/scope check

This PR is over our review-size guideline.

  • Recommended: ~200 lines changed
  • Acceptable limit: up to 400 lines when well-scoped/self-contained
  • Preferred file count: fewer than 5 files

Please split this into smaller PRs unless there is a clear reason the changes need to land together.

If keeping it as one PR, please add a brief justification covering:

  • What single problem this PR solves
  • Why the files/changes are coupled

trangdoan982 and others added 4 commits September 8, 2026 17:28
Links to a discourse node carry an inline badge with that node's relation
count; selecting it opens a popover built on RelationshipSection, the
same component the Discourse Context panel renders, so the two cannot
disagree. Toggled by a General setting, applied without a reload.

The count matches what the panel would list rather than what the store
holds: relations awaiting acceptance after an import are excluded, and so
are relations whose relation type has been deleted, which the panel drops
but relations.json keeps.

The badge is plain DOM rather than React so the Reading view counterpart
can share it without mounting a React root per link.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review finding: changing a relation while its popover is open replaced the
badge the popover is anchored to, leaving the positioning callback bound to
a detached element, so the popover jumped on its next resize.

CM6 rebuilds a widget's DOM whenever eq() is false, which a count change
always is, so the badge now updates through updateDOM and the anchor stays
connected. The same helper serves the Reading view path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four review findings:

Picking a node for a new relation dismissed the popover: Obsidian mounts
AbstractInputSuggest's list on document.body, so the outside-click guard
counted it as outside. Clicks inside a suggestion container are now kept.

A popover taller than the space above opened downward and clipped. It now
opens toward whichever side has more room.

The zero-relation message lives outside React, so adding the first relation
from the popover left it beside the new one. It clears itself on the next
index change.

Removing nodeTypeId from a file suppressed the refresh that should have
removed its badges, since the check ran against the new metadata. Files
that were nodes are now remembered until they stop being one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leaves only the constraint each one exists to record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@mdroidian mdroidian left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as #1413: ENG-1249 Add a cached relations index and link resolution

There should be one PR per ticket. If the original ticket needs to be broken up into three, then create a parent ticket and 3 children tickets, please.

Copy link
Copy Markdown
Member Author

ahh gotcha, this answers my question earlier. in case it left you a notification

@trangdoan982
trangdoan982 deleted the eng-1249-live-preview-overlay branch September 11, 2026 03:21
@trangdoan982

Copy link
Copy Markdown
Member Author

Superseded by #1434. Closed automatically when its head branch was renamed to eng-2249-implement-discourse-context-overlay-in-live-preview to match its new ticket, ENG-2249. Same commits, same body (Loom link carried over).

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.

2 participants