Repository navigation
ENG-2367 Scroll to a candidate result in context of its page in the Roam search preview - #1537
Conversation
…oam search preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4H5VZE41NEK55DN1838KCQ5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4H5X6KPB0FEMKQJG412E3E5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4H6EGPGW6M0R764KZ9MC20S
…result Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Entire-Checkpoint: 01M4H6RZBX9NGXPWPTYMF6PS91
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
| const showPage = useMemo( | ||
| () => !!pageUid && !hasCollapsedAncestor(uid), | ||
| [pageUid, uid], | ||
| ); | ||
| const renderUid = showPage ? pageUid : uid; |
There was a problem hiding this comment.
🟡 Moved candidates preview the wrong page
When a candidate moves between indexing and selection, pageUid still identifies its former page. The preview shows that page and cannot reveal the candidate.
Learn more
Candidate results store the page UID when queryCandidatesForType builds the search index. The index persists while the dialog is open. Moving a candidate to another page during that time leaves the candidate UID valid but the stored page UID stale. The preview renders the former page, and revealBlockInPreview cannot find the candidate there.
Example: Search indexes block b1 on page p1. Move b1 to page p2, then select its search row. The preview renders p1 and scrolls to its top instead of displaying b1 on p2.
Recommended fix: Resolve the candidate's current page UID when rendering its preview, or verify the indexed page UID still owns the block and fall back to rendering the block if it changed. Refresh the preview when that relationship changes.
Was this helpful? React with 👍 or 👎 to provide feedback.
| new Promise<void>((resolve) => | ||
| window.setTimeout(resolve, IMAGE_LOAD_TIMEOUT_MS), | ||
| ), |
There was a problem hiding this comment.
🟡 Slow images displace the candidate
When an image above the candidate loads after one second, waitForImages releases the reveal early. The image shifts the page afterward, leaving the candidate off-center or outside the preview.
Learn more
The candidate preview waits for images before measuring the target row and setting the scroll position. The one-second timer can resolve before an image above that row finishes loading. The later image height change shifts the row, but the reveal effect only runs once per selected UID.
Example: An image above block b1 takes two seconds to load. At one second the preview centers b1 using the image's empty height; one second later the image expands by 1200 pixels and pushes b1 below the visible area.
Recommended fix: Keep the initial timeout for responsiveness, but remeasure and adjust the scroll when pending images finish loading, provided the same candidate remains active. Clean up image listeners and timers when switching results.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Leaving this as is. The 1000 ms cap is deliberate and matches the Obsidian preview (ENG-2273); without it, a slow or broken image would hold the reveal indefinitely. An image that finishes after the cap can still shift the row, which is an accepted limit shared with Obsidian. Live check: an 800×1200 image above the candidate loaded inside the cap and the row stayed centred.
🤖 Addressed by Claude Code
Reviewer brief
CandidatePreview(apps/roam/src/components/AdvancedNodeSearchDialog/AdvancedSearchDialog.tsx). The render effect owns the mount node and unmounts only after its render settles. The reveal effect runs only when the rendered uid matches the one this result needs, which stops a fast switch from scrolling or flashing the wrong block.hasCollapsedAncestorhas no unit test. Its query was checked live (collapsed parent, collapsed grandparent, all ancestors open).The diagram shows what happens when a result is selected:
flowchart TD A[Select result in AdvancedSearchDialog] --> B{result.candidate?} B -- no --> N[RenderRoamPage / RenderRoamBlock zoomPath<br/>unchanged; scrollTop = 0 if previous was a candidate] B -- yes --> C{hasCollapsedAncestor uid<br/>:block/parents with :block/open false} C -- no --> P[renderPage candidate.pageUid, hide-mentions] C -- yes --> Z[renderBlock uid, zoom-path] P --> W[await render, then waitForImages ≤1000ms] Z --> W W --> G{renderedUid === renderUid?<br/>not cancelled} G -- yes --> R[revealBlockInPreview<br/>.rm-block data-block-uid, skip embeds<br/>set container scrollTop to centre, add flash class] R -- not found --> T[scrollTop = 0, no flash]Verification
Ran live in Roam on the test graph with
dg-roam-load-extensionata8585bb8.pnpm ci:validatepasses.:block/open falseancestors:block/openfalse/false/true before and afterTests added:
apps/roam/src/utils/__tests__/revealBlockInPreview.test.ts(jsdom): flashes only the tagged block's own row, skips embedded copies, ignores copies outside the preview, centres the row, scrolls to the top when the block isn't rendered, and clears the flash on cleanup and onanimationend.apps/roam/src/utils/__tests__/candidateNodeSearch.test.ts: candidates carrypageUid.Rerun with
pnpm -C apps/roam test.Not verified:
Loom video
pending
Scope check
$scope-checkagainst ENG-2367 and the final diff.Done When: When the tagged block isn't found in the rendered page for a reason other than a collapsed parent, the preview scrolls back to the top and doesn't flash. The zoomed fallback for a collapsed parent also flashes the block.Standards check
$dg-pr-adherence-checkagainst the final diff and PR metadata.Local delegated full review
$dg-delegated-full-reviewwhen no other full-review workflow is available.No findings on
a8585bb8. Earlier rounds found three Low issues, all fixed in this branch:🤖 Generated with Claude Code