fix(settings): update "on this page" highlight as user scrolls#730
Conversation
Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Switch the IntersectionObserver to watch each section's <h2> heading sentinel instead of the full section <div>, so a long section no longer keeps the highlight active while the next section's heading is already crossing the activation band. Addresses the Copilot review on PR #729. Also add a passive scroll listener that snaps the highlight to the last section when the user is within 4px of the page bottom, since the last section is short enough that its heading may never enter the activation band. The override only fires on scrollable pages and is registered inside the existing isPlatformBrowser guard, so SSR stays unaffected. Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
Walkthrough
ChangesScroll-spy Feature
🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds scroll-spy behavior to the "On this page" TOC in the account settings page so the highlighted item updates as the user scrolls, instead of only on click.
Changes:
- Added an
IntersectionObserverover the three section heading sentinels (email-settings,password,developer-settings) to driveactiveSection. - Initialized the observer via
afterNextRenderwith SSR guard, and cleaned up viadestroyRef.onDestroy. - Added a scroll listener that snaps the highlight to the last section when the page is scrolled to the bottom, working around the short final section never entering the activation band.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/settings/account-settings/account-settings.component.ts (1)
506-506: 💤 Low valueConsider documenting the magic numbers.
The
-70%bottom margin and4pixel bottom threshold are effective but could benefit from inline comments explaining their purpose:
-70%: Creates activation band in upper ~30% of viewport (below header)4: Small buffer to account for sub-pixel rendering/rounding📝 Optional: Add inline clarifications
- { rootMargin: '-80px 0px -70% 0px', threshold: 0 } + { + rootMargin: '-80px 0px -70% 0px', // Activation band: below header, upper ~30% of viewport + threshold: 0 + }- window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4; + window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 4; // 4px buffer for sub-pixel roundingAlso applies to: 515-515
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/lfx-one/src/app/modules/settings/account-settings/account-settings.component.ts` at line 506, Document the magic numbers used in the IntersectionObserver options in account-settings.component.ts: add short inline comments next to the rootMargin string '-80px 0px -70% 0px' explaining that '-80px' accounts for the fixed header offset and '-70%' creates an activation band in the upper ~30% of the viewport, and add a comment next to the threshold value (the 0 / or the 4px buffer usage nearby) describing that the small pixel buffer (4) compensates for sub-pixel rendering/rounding; locate these comments where the IntersectionObserver is created (the options object containing rootMargin and threshold) so future readers understand the rationale for those numbers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In
`@apps/lfx-one/src/app/modules/settings/account-settings/account-settings.component.ts`:
- Line 506: Document the magic numbers used in the IntersectionObserver options
in account-settings.component.ts: add short inline comments next to the
rootMargin string '-80px 0px -70% 0px' explaining that '-80px' accounts for the
fixed header offset and '-70%' creates an activation band in the upper ~30% of
the viewport, and add a comment next to the threshold value (the 0 / or the 4px
buffer usage nearby) describing that the small pixel buffer (4) compensates for
sub-pixel rendering/rounding; locate these comments where the
IntersectionObserver is created (the options object containing rootMargin and
threshold) so future readers understand the rationale for those numbers.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dd705c30-7f8f-4209-a3b6-c9d010eeeb79
📒 Files selected for processing (1)
apps/lfx-one/src/app/modules/settings/account-settings/account-settings.component.ts
Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
|
Commit Small code-quality polish addressing the self-review NIT from the PR review:
|
MRashad26
left a comment
There was a problem hiding this comment.
Strict pass against ~/LFX/code-enforcer-agent.md. The IntersectionObserver approach is the right primitive, SSR is correctly guarded with isPlatformBrowser, and destroyRef.onDestroy cleanup is in place. Two findings worth a look — one about coupling production behavior to test selectors, one about the end-of-scroll override.
Address review comments from @MRashad26: - account-settings.component.html: add id="<section>-heading" to each of the three <h2> elements so production code can query them by id - account-settings.component.ts: replace document.querySelector with document.getElementById for heading lookups; remove scroll listener and magic-pixel bottom-detection in favour of a second IntersectionObserver over a 1px sentinel div appended at the end of the content column Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Review Feedback AddressedCommit: aba0d80 Changes Made
No Change Neededcoderabbitai's top-level nitpick about documenting the magic numbers ( Threads Resolved2 of 2 unresolved threads addressed in this iteration. |
Address review comments from copilot-pull-request-reviewer: - account-settings.component.html: add id="scroll-end-sentinel" to the sentinel div so production code queries it by id, not data-testid - account-settings.component.ts: replace querySelector with getElementById for the sentinel lookup (same fix pattern as round 1 heading lookups) - account-settings.component.ts: guard the end-observer callback so it only snaps to the last section when the last heading's bottom has cleared the 80px header offset; prevents the TOC from pinning to "Developer Settings" on tall viewports where the sentinel is visible from initial paint without any scrolling Signed-off-by: Fayaz G <5818912+fayazg@users.noreply.github.com>
Review Feedback Addressed (Round 2)Commit: 990cbab Changes Made
No Change Needed
Threads Resolved2 of 3 new threads resolved. Thread 1 (tie-breaking concern) left open pending reviewer acknowledgment. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
apps/lfx-one/src/app/modules/settings/account-settings/account-settings.component.ts:525
- The end-of-page sentinel guard duplicates the same 80px header offset used in the main observer’s
rootMargin. Reuse a sharedheaderOffsetPxconstant here to keep both observers in sync if the layout/header changes.
// to show the whole page without scrolling the sentinel is already intersecting
// from initial paint — this guard prevents pinning the TOC to the last section
// before the user has reached it.
if (entry.isIntersecting && lastHeading && lastHeading.getBoundingClientRect().bottom <= 80) {
this.activeSection.set(lastSectionId);
| const activeId = sectionIds.find((id) => intersecting.has(id)); | ||
| if (activeId) this.activeSection.set(activeId); | ||
| }, | ||
| { rootMargin: '-80px 0px -70% 0px', threshold: 0 } | ||
| ); |
Fixes LFXV2-1877
Summary
IntersectionObserveron the three section anchors (email-settings,password,developer-settings) instead of relying on click-only state.afterNextRenderand disconnected throughdestroyRef.onDestroy. Guarded withisPlatformBrowserper.claude/rules/ssr-safety.md, so SSR is unaffected.rootMargin: '-80px 0px -70% 0px'puts the highlight on the section whose top crosses roughly the upper third of the viewport, below the sticky page header.scrollToSection()is unchanged — the click still setsactiveSectionimmediately for instant feedback, and the observer takes over once the smooth scroll settles.Test plan
/settings/accountyarn buildpasses (browser + SSR bundles)yarn lint:checkpasses