feat(nav): merge foundation and project lenses for hybrid personas#713
feat(nav): merge foundation and project lenses for hybrid personas#713nunoeufrasio wants to merge 8 commits into
Conversation
- Add isHybridPersona signal to LensService; availableLenses suppresses the separate foundation button for hybrid users - Preload both lens item sets in NavigationService for hybrid users so the merged dropdown has both ready immediately - Add isLensActive() helper to LensSwitcherComponent so the merged Projects button appears active for both foundation and project states - Rewrite ProjectSelectorComponent with hybrid mode: All/Foundations/ Projects tabs, role-sorted items (ED→BM→alpha / Maintainer→Contributor →alpha), projects nested under parent foundation in the All tab, and a transparent/borderless search input - Update SidebarComponent to switch the active lens based on item.isFoundation when an item is selected in hybrid mode - Update project lens constant: icon → fa-layer-group, label → Projects Signed-off-by: Nuno Eufrasio <nuno.eufrasio@linuxfoundation.org> Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
The project-lens API can include foundations the user has access to, but those belong in the foundation lens (or the Foundations tab in hybrid mode) — never in the projects list. Filter them out so a project-only Contributor doesn't see foundation entries. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
Polish how persona roles surface across the hybrid lens experience: - selector trigger now shows a compact role-icon badge with tooltip instead of inline text, with left-aligned, truncating description - dropdown rows render the role icon + label inline beneath each project/foundation name - nested project connector lines align with the parent foundation logo - me-lens user card collapses to inline "<icon> Role" for a single persona and to circular icon-only badges with a rich HTML tooltip (role name + bulleted project/foundation list) for multiple personas - tooltip width hugs content so role/project entries never wrap Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughDetect hybrid personas (board+project) and adapt lens UI: LensService exposes isHybridPersona and displayLenses; LensSwitcher uses hybrid-aware active logic; ProjectSelector adds hybridMode with tabs and nested "all" view; Sidebar shows persona tooltips; Navigation preloads sibling lenses for hybrids. ChangesHybrid Persona Mode Support
Sequence Diagram(s)sequenceDiagram
participant PersonaService
participant LensService
participant LensSwitcher
participant ProjectSelector
participant Sidebar
participant NavigationService
PersonaService->>LensService: provide roles
LensService->>LensService: compute isHybridPersona()
LensService->>LensSwitcher: displayLenses + isHybridPersona
LensService->>ProjectSelector: displayLenses + isHybridPersona
LensService->>Sidebar: isHybridPersona
ProjectSelector->>PersonaService: resolve persona per item
NavigationService->>NavigationService: preload sibling lens when hybrid active
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 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 unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Comment |
There was a problem hiding this comment.
Pull request overview
This PR updates navigation/lens behavior and UI to better support “hybrid personas” (users who have both board-scoped roles and project-scoped roles) by presenting a single merged entry in the lens switcher and adapting the project/foundation selector UI (tabs, role labels/icons, nesting).
Changes:
- Update the
projectlens labeling/iconography and introduceisHybridPersonato drive merged-lens behavior. - Preload both foundation + project navigation datasets for hybrid personas and hide the separate foundation lens button in the UI.
- Redesign the sidebar project/foundation selector and Me-lens persona display to show role icons/labels and rich tooltips.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/constants/lens.constants.ts | Renames “Project” lens labels to “Projects” and updates icons. |
| apps/lfx-one/src/app/shared/services/navigation.service.ts | Preloads the sibling lens data for hybrid personas when switching lenses. |
| apps/lfx-one/src/app/shared/services/lens.service.ts | Adds isHybridPersona and hides the foundation lens from availableLenses for hybrid personas. |
| apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts | Adds hybrid flag propagation and builds persona tooltip HTML for Me lens. |
| apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html | Updates Me-lens persona badge display to icon-only with tooltips for multi-role users. |
| apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.scss | Adds tooltip style overrides for persona tooltips. |
| apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts | Adds hybrid mode tabs, role resolution/sorting, nested “All” view, and dual-lens loading/search. |
| apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html | Updates selector trigger and dropdown rendering (tabs, role display, selection state). |
| apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.ts | Treats the merged “project” button as active for both foundation/project states in hybrid personas. |
| apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html | Uses isLensActive() for active styling/icon selection in the lens switcher. |
Comments suppressed due to low confidence (1)
apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html:124
- Same issue here:
[class]overrides the staticmr-1class, so spacing may be lost. Prefer[ngClass]to combine the dynamic icon class with existing utility classes.
@if (getRoleLabel(displayItem.item)) {
<p class="text-xs text-gray-400 leading-none mt-0.5 truncate">
<i [class]="getRoleIcon(displayItem.item)" class="mr-1" aria-hidden="true"></i>{{ getRoleLabel(displayItem.item) }}
</p>
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-713.dev.v2.cluster.linuxfound.info Deployment Details:
The deployment will be automatically removed when this PR is closed. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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.
Inline comments:
In
`@apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html`:
- Around line 111-114: The inline SVG in project-selector.component.html
contains hard-coded fill hex values on the <svg> <path> elements which bypass
the shared theme; replace those literal colors by either referencing the shared
tokenized color classes (e.g., apply the appropriate lfxColors scale classes via
class or [ngClass] on the svg/paths) or move the SVG into a shared asset that
uses the lfxColors tokens, ensuring the fills are bound to the theme tokens
rather than hard-coded hex values; update the fallback logo SVG used in
ProjectSelector (the svg and its path elements) to use those tokenized classes
or a shared asset.
- Around line 63-76: The markup is using ARIA tab roles without implementing the
full tabs pattern; remove role="tablist" and role="tab" from the container and
buttons and instead expose the selection state via an accessible pressed state
(bind aria-pressed to activeTab() === tab) so assistive tech treats these as
toggle/filter buttons; keep the existing (click)="activeTab.set(tab)" handler
and class bindings, add aria-pressed="[activeTab() === tab]" (or
[attr.aria-pressed]="activeTab() === tab") on the button and ensure the label
logic using selectorTabs and activeTab remains unchanged (references:
selectorTabs, activeTab, the button element).
In
`@apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts`:
- Around line 121-126: The hybrid-mode hasMore computed currently ORs both
foundation and project pagination so scrolling on one tab drains the other;
update the computed to be tab-aware: when this.hybridMode() is true, check
this.lens() to decide which pagination to query (call
this.navigationService.hasMore('project')() when lens() === 'project', and call
this.navigationService.hasMore('foundation')() when lens() === 'foundation'),
otherwise keep the existing this.navigationService.hasMore(this.lens())()
behavior; apply the same tab-aware change to the other similar computed/logic
block referenced around lines 181-190.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 48bbf5f3-b3f9-42a2-a6e3-325d3dec09b2
📒 Files selected for processing (10)
apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.htmlapps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.tsapps/lfx-one/src/app/shared/components/project-selector/project-selector.component.htmlapps/lfx-one/src/app/shared/components/project-selector/project-selector.component.tsapps/lfx-one/src/app/shared/components/sidebar/sidebar.component.htmlapps/lfx-one/src/app/shared/components/sidebar/sidebar.component.scssapps/lfx-one/src/app/shared/components/sidebar/sidebar.component.tsapps/lfx-one/src/app/shared/services/lens.service.tsapps/lfx-one/src/app/shared/services/navigation.service.tspackages/shared/src/constants/lens.constants.ts
- Scope hybrid pagination (hasMore/loadMore) to the active tab so the Projects tab can keep advancing instead of exhausting foundations first. - Restore `availableLenses` to the full allowed set and add a separate `displayLenses` for the sidebar switcher, so downstream consumers (NavigationService.applyVisibilityFilters / foundationVisibilityWatcher) keep filtering foundations out of the project lens for hybrid users. - Replace `[class]` with `[ngClass]` on role icons so the static `text-[10px]` styling isn't wiped out. - Make tooltip-only role badges (selector trigger + Me-card multi-role) focusable via `tabindex="0"` + `role="img"` + `aria-label`, with a focus ring so keyboard users can reveal the role info. - Drop `role=tablist`/`role=tab` from the hybrid filter pills (they aren't a true tabs widget); use `role=group` + `aria-pressed` instead. - Swap hard-coded SVG hex fills on the fallback logo for `fill-blue-500` and `fill-blue-800` tokens. - Pre-group nested projects by parent uid so `buildAllTabItems` runs in O(F + P) rather than O(F × P). - Fix the stale comment that described the parent-uid map as the inverse of what the code actually builds. Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
MRashad26
left a comment
There was a problem hiding this comment.
Strict CLAUDE.md / code-enforcer-agent review — 6 findings (4 major + 2 minor). Skipping items already raised by Copilot/CodeRabbit (hybrid pagination scoped to active tab, availableLenses semantic shift, ARIA-tabs vs aria-pressed, hard-coded SVG fallback colors, non-focusable tooltip triggers, [class] binding overriding static class).
- Precompute per-row selector state (isSelected, roleLabel, roleIcon) into DisplayLensItem so the template stops re-invoking methods on every change-detection pass. - Move DisplayLensItem + SelectorTab into @lfx-one/shared/interfaces. - Restore the initializeXxx() pattern for complex computeds in project-selector so the field block stays scannable. - Replace lens-switcher.isLensActive() method with an activeLensId computed and use it directly in templates. - Replace sidebar persona-tooltip HTML-string building (with [escape]=false) with structured data + an inline <ng-template> rendered through pTooltip, so Angular handles escaping. - Collapse the two remaining multi-line inline comments to single lines. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
- Use a type-only import in lens.interface.ts to break a runtime circular dependency with navigation.interface.ts. - Reorder sidebar.onItemSelected so the project/foundation context is set before the lens flips. NavigationService injects selected_uid from ProjectContextService at reload time, and the previous ordering let the lens reload run with the stale selection. - Add a preloadSibling() path in NavigationService that triggers a reload without setting pendingDefaultSelection and bails when the sibling lens is already loaded or in flight, so hybrid preload can't race with the active lens or overwrite the URL/context. - Hybrid-aware emptyMessage on the project selector — show 'No results found' instead of a single-lens-specific copy when the dropdown is rendering mixed All/Foundations/Projects tabs. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
Round 3 — review feedback addressedPushed in c58021d. Latest Copilot pass produced 4 actionable comments; all addressed below. CodeRabbit had no new findings; MRashad26's round-2 items were already resolved in ff3ea63. C1 — Circular import (
|
For non-hybrid users, personaProjects only contains projects with explicit role detections, but the navigation API returns every accessible project. The role badge/description was missing whenever a listed item had no detection entry — even though the user's scope (board-only or project-only) makes the role unambiguous. When the per-item lookup misses for a non-hybrid user, surface the highest-priority persona they hold (executive-director → board-member, maintainer → contributor) so every row in the dropdown and the trigger badge consistently show the role icon and label. Generated with [Claude Code](https://claude.ai/code) Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
- project-selector.component.html: remove nested tabindex="0" from role badge span inside trigger button; the parent button already handles focus/hover for the pTooltip, so the inner span doesn't need its own tab stop (invalid HTML). - navigation.service.ts: add hybridTransitionPreloader so the sibling lens preloads when a persona refresh promotes the user to hybrid mid-session without an activeLens change. Signed-off-by: Nuno Eufrasio <nmeufrasio@gmail.com>
Review feedback summaryThanks for the thorough reviews. All 20 unresolved threads have been addressed and resolved. Addressed in this commit (a9b049d)
Addressed in earlier commits on this branchHybrid mode behaviour
Performance / architecture
Accessibility
Polish
Thanks again to the reviewers — MRashad26, copilot-pull-request-reviewer, and coderabbitai — for the detailed feedback. |
Summary
Users with mixed roles (Executive Director, Board Member, Maintainer, Contributor) previously saw two separate sidebar entries — one for foundations and one for projects — which was confusing for hybrid personas. This PR merges them into a single "Projects" lens whose dropdown adapts to the user's persona mix and clearly surfaces each role.
What changed
Sidebar lens — adapts per persona
Project/Foundation selector trigger
Project/Foundation selector dropdown
Me lens — user card
Test plan
fa-layer-groupicon and Maintainer/Contributor role labels in the dropdown.fa-landmarkicon and ED/Board Member role labels.yarn check-types,yarn lint, andyarn build.Generated with Claude Code