fix(nav): stabilize sidebar @for track keys for me-lens menu#1059
Conversation
Track sidebar menu items by routerLink/url (falling back to label) instead of by label alone. Label-based tracking made Angular reuse and mis-map DOM nodes when the menu recomputed after feature flags and persona data resolve post-first-paint, so items rendered under the wrong section headers. Because the Font Awesome kit JS bakes an <svg> into each <i> on first render, reused nodes also kept a stale icon that never refreshed. A stable per-destination key stops the node reuse and fixes both the misplacement and the wrong icons. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
WalkthroughThe sidebar template now tracks repeated items by ChangesSidebar item tracking
Estimated code review effort: 1 (Trivial) | ~5 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR fixes a Me-lens sidebar rendering bug where menu items briefly appeared under the wrong section headers (and some icons stuck permanently wrong) on load. The sidebar menu is built from computed signals that depend on feature flags and persona/lens data resolving after first paint, so the menu recomputes into a different shape. Because the four @for loops tracked by item.label, Angular reused and mis-mapped DOM nodes during the reshape; combined with Font Awesome's kit JS baking an <svg> into each <i> on first render, reused nodes retained stale icons. The fix introduces a stable per-destination track key.
Changes:
- Added
trackNavItem(item)returningitem.routerLink ?? item.url ?? item.labelas a stable identity for@fortracking. - Applied it to all four sidebar menu loops (top-level items, section children, group children, footer items), replacing the previous label-based tracking.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts |
Adds the trackNavItem helper providing a stable per-destination identity, with a comment explaining the reshape/Font-Awesome root cause. |
apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html |
Switches the four sidebar @for loops from track item.label/track childItem.label to track trackNavItem(...). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
MRashad26
left a comment
There was a problem hiding this comment.
Code Review — PR #1059 fix(nav): stabilize sidebar @for track keys for me-lens menu
Overview
Correct root-cause diagnosis: label-based @for track keys were unstable across the two-phase render (LaunchDarkly + persona API resolve after first paint), causing Angular to reuse mis-mapped DOM nodes. Because Font Awesome's SVG+JS kit bakes <svg> into each <i> on first paint, reused nodes kept stale icons. Using routerLink ?? url ?? label as the stable key is the right fix — every leaf item has a unique destination, and sections fall back to their unique label.
Secrets / critical-constants check ✅
No secrets introduced.
Code-standards audit
| # | Severity | Finding |
|---|---|---|
| 1 | 🔴 Critical | track trackNavItem(item) calls a component method in 4 @for loops |
Code quality notes
- The stable-key derivation logic is correct (
routerLink ?? url ?? label). ✅ - All four
@forloops updated — no missed occurrences. ✅ ⚠️ Coordination note: the in-flight PR #1057 (feat/navigation-improvements) introduces the same four label-tracked@forloops independently and also addstrackNavItem. When these two branches merge, the inline-expression approach (see critical finding) removes the conflict.
Verdict: FAIL ❌ (1 critical)
One-line fix per loop — swap to inline expression and drop the method.
Address review comment from @MRashad26: - sidebar.component.html: replace `track trackNavItem(...)` with inline `track (routerLink ?? url ?? label)` in all four @for loops, per the no-functions-in-templates convention; preserve the root-cause rationale as an HTML comment above the first loop (per @MRashad26) - sidebar.component.ts: remove the now-unused trackNavItem method and its comment block Resolves 1 review thread. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
Review Feedback AddressedCommit: f284908 Changes Made
Validation
Coordination noteThe observation about PR #1057 ( Threads Resolved1 of 1 unresolved thread addressed in this iteration. |
Audit: PR #1059 — fix(nav): stabilize sidebar @for track keys for me-lens menununoeufrasio · Scope: Surgical fix for Me-lens sidebar DOM reuse bug 1. WhyLabel-based 2. HowFour 3. Is it correctYes — correct and convention-compliant at latest head. CI Code Quality Checks FAIL — same Prettier issue (remove parens around track expressions). 4. Gaps
5. Q&A
6. Business rules & ADRs
Verdict: 🔴 Required changes (blocked-on-CI-format; then ✅ approved as standalone hotfix) Coordination: If #1057 merges first → close #1059 as superseded. |
luismoriguerra
left a comment
There was a problem hiding this comment.
Audit inline: CI format blocker on track expressions.
Address review comment from @luismoriguerra (CI format:check blocker): - sidebar.component.html: remove parentheses around the `??` track expressions in all four @for loops so Prettier/format:check passes Resolves 1 review thread. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
Review Feedback AddressedCommit: 1cabd12 Changes Made
Validation
Threads Resolved1 of 1 unresolved thread addressed in this iteration. |
Re-audit: PR #1059 — fix(nav): stabilize sidebar @for track keys for me-lens menuHead: What changed since last audit
Prior findings → status
CI & gatesCode Quality Checks and all other checks — pass. Local Merge readiness: safe-to-merge as standalone hotfix. If #1057 lands first, this PR becomes redundant. |
luismoriguerra
left a comment
There was a problem hiding this comment.
Re-audit clean: format:check and inlined @for track keys are fixed. CI and local lint/format checks pass. Approved as safe-to-merge hotfix.
Review Feedback AddressedDocumentation Update (no code change)
No Change Needed
Threads Resolved1 of 1 unresolved thread addressed in this iteration. No code change, so no new commit. |
What
Fixes the Me-lens sidebar rendering in a garbled state on load — menu items appearing under the wrong section headers (e.g. Mentorships / Badges under Security instead of My Growth), then re-shuffling into the correct layout a few seconds later, with some icons (Training & Certifications, Badges) staying wrong even after it settled.
Why
The sidebar menu is built from
computedsignals that depend on data resolving after first paint — feature flags (defaultfalse, real values arrive when LaunchDarkly reaches READY) and persona/lens roles (cookie defaults, then/api/user/personas). So the menu paints once, then recomputes into a different shape.Two things turned that clean recompute into a garbled one:
@fortrack keys. Every loop insidebar.component.htmltracked byitem.label/childItem.label. Labels are not a stable identity across the reshape, so Angular reused and mis-mapped DOM nodes → items rendered under the wrong section header.<i class="fa-…">with a baked-in<svg>on first render. When a node was reused (because of the label-based track) and its[ngClass]="item.icon"changed, FA had already converted the node and didn't re-render it → the wrong icon stuck permanently.How
Track menu items by a stable per-destination key instead of by label, using an inline
trackexpression (the repo convention — no functions in templates):@for (item of itemsWithTestIds(); track item.routerLink ?? item.url ?? item.label) {Applied to all four
@forloops (top-level items, section children, group children, footer items), with thechildItemequivalent in the nested loops. Each leaf item has a uniquerouterLink/url; sections and command-only items fall back to their (unique) label. A condensed HTML comment above the first loop documents the root cause. Because icons are static per destination, retained nodes never need an icon swap and genuinely new items get a fresh<i>that FA renders correctly — fixing both the misplacement and the stale icons.Verification
yarn check-types,yarn lint:check, andyarn buildall pass.redirect_uriis pinned tolocalhost:4200and that port was held by another process, so an authenticated Me-lens render couldn't be captured. The change is a mechanical control-flow track-key fix with no behavioral or styling change;data-testidoutput is unchanged.Notes
feat/navigation-improvementsrefactor moves this menu intoSidebarNavServiceand carries the same four label-tracked loops; the same inlinetrack item.routerLink ?? item.url ?? item.labelkey should be applied there when it rebases/merges.