Skip to content

feat(nav): merge foundation and project lenses for hybrid personas#713

Open
nunoeufrasio wants to merge 8 commits into
mainfrom
feat/lens-update
Open

feat(nav): merge foundation and project lenses for hybrid personas#713
nunoeufrasio wants to merge 8 commits into
mainfrom
feat/lens-update

Conversation

@nunoeufrasio
Copy link
Copy Markdown
Contributor

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-only users: one "Projects" lens; dropdown lists their projects with role label per row (Maintainer / Contributor).
  • Foundation-only users: one "Foundations" lens; dropdown lists their foundations with role label per row (Executive Director / Board Member).
  • Hybrid users: one merged "Projects" lens; dropdown gets All / Foundations / Projects tabs, with each row showing its role. The "All" tab keeps foundations grouped first (ED → Board Member → alphabetical), with their projects nested beneath, then standalone projects sort Maintainer first.

Project/Foundation selector trigger

  • Replaced inline "Foundation / Project ・ Role" text with a compact role-icon badge + tooltip alongside the type label.
  • Description column is now left-aligned and truncates cleanly when names overflow.

Project/Foundation selector dropdown

  • Each row shows the role icon + role label inline beneath the project/foundation name.
  • Nested project connector lines align with the parent foundation logo.

Me lens — user card

  • Single-role users see an inline " Role" label.
  • Multi-role users get circular icon-only badges; hovering each badge reveals a rich tooltip with the role name (bold) and a bulleted list of the corresponding projects or foundations. Tooltip width hugs the content so list items never wrap.

Test plan

  • Verify project-only persona shows a single "Projects" lens with fa-layer-group icon and Maintainer/Contributor role labels in the dropdown.
  • Verify foundation-only persona shows a single "Foundations" lens with fa-landmark icon and ED/Board Member role labels.
  • Verify hybrid persona shows one merged "Projects" lens with All/Foundations/Projects tabs; selecting a foundation switches to the foundation route, selecting a project switches to the project route.
  • Verify nested projects in the "All" tab line up under their parent foundation logo.
  • Verify trigger truncates long names and stays left-aligned.
  • Verify Me-lens single-role inline display and multi-role icon badges with tooltips (role bold + project bullets, no wrapping).
  • Run yarn check-types, yarn lint, and yarn build.

Generated with Claude Code

- 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>
Copilot AI review requested due to automatic review settings May 15, 2026 08:48
@nunoeufrasio nunoeufrasio requested a review from a team as a code owner May 15, 2026 08:48
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Detect 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.

Changes

Hybrid Persona Mode Support

Layer / File(s) Summary
Lens service hybrid detection & constants
apps/lfx-one/src/app/shared/services/lens.service.ts, packages/shared/src/constants/lens.constants.ts
Adds isHybridPersona signal, simplifies available/display lens init, and updates project lens label/icons to plural "Projects".
Lens switcher hybrid display
apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.ts, apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html
Uses lensService.displayLenses, adds isHybrid flag and activeLensId computed to determine active-state with hybrid-specific project remapping for UI bindings.
Navigation preloading optimization
apps/lfx-one/src/app/shared/services/navigation.service.ts
Preloads sibling foundation/project lens when hybrid personas activate one and the sibling isn't loaded.
ProjectSelector hybrid core
apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts, packages/shared/src/interfaces/lens.interface.ts
Adds DisplayLensItem and hybridMode input; separates foundation/project sources, updates search/pagination to drive both sources, computes displayedItems, and implements buildAllTabItems() to emit nested combined lists.
ProjectSelector template & rendering
apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html
Trigger shows truncated lens label and optional role tooltip, adds hybrid-only tab switcher, restyles search input, iterates displayedItems(), and renders nested/selectable rows with logo fallback and checkmarks.
Sidebar persona badges with tooltips
apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts, apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html, apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.scss
Imports TooltipModule, exposes isHybridPersona, expands personaLabels to include names, renders single vs. multi-label badges with tooltips and ARIA labels, and adds tooltip CSS overrides.

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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective: merging foundation and project lenses for hybrid personas, which is the central feature of this PR.
Description check ✅ Passed The description comprehensively explains the changeset, covering persona-specific behavior, UI updates, and test cases, all directly related to the implemented changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/lens-update

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 project lens labeling/iconography and introduce isHybridPersona to 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 static mr-1 class, 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.

Comment thread apps/lfx-one/src/app/shared/services/lens.service.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 15, 2026

🚀 Deployment Status

Your branch has been deployed to: https://ui-pr-713.dev.v2.cluster.linuxfound.info

Deployment Details:

  • Environment: Development
  • Namespace: ui-pr-713
  • ArgoCD App: ui-pr-713

The deployment will be automatically removed when this PR is closed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between c990ccf and 656de0e.

📒 Files selected for processing (10)
  • apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html
  • apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.ts
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.html
  • apps/lfx-one/src/app/shared/components/project-selector/project-selector.component.ts
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.scss
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts
  • apps/lfx-one/src/app/shared/services/lens.service.ts
  • apps/lfx-one/src/app/shared/services/navigation.service.ts
  • packages/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>
Copy link
Copy Markdown
Contributor

@MRashad26 MRashad26 left a comment

Choose a reason for hiding this comment

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

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).

Comment thread apps/lfx-one/src/app/shared/components/lens-switcher/lens-switcher.component.html Outdated
Comment thread apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts Outdated
- 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>
Copilot AI review requested due to automatic review settings May 15, 2026 09:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comment thread packages/shared/src/interfaces/lens.interface.ts Outdated
Comment thread apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.ts
Comment thread apps/lfx-one/src/app/shared/services/navigation.service.ts
- 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>
@nunoeufrasio
Copy link
Copy Markdown
Contributor Author

Round 3 — review feedback addressed

Pushed 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 (packages/shared/src/interfaces/lens.interface.ts)

lens.interface.ts was importing LensItem as a value while navigation.interface.ts already imports Lens from lens.interface.ts. Switched to import type { LensItem } so the cycle stays type-only and doesn't exist at runtime.

C2 — Selection lost on click (sidebar.component.tsonItemSelected)

setLens() was called before setFoundation/setProject(). NavigationService reloads on activeLens change and injects selected_uid from ProjectContextService at reload time, so the previous ordering let the reload fire against the stale selection — applyDefaultSelection could then overwrite the user's clicked item if it wasn't in the first page. Reordered: context first, lens second.

C3 — Sibling preload races with active lens (navigation.service.ts)

The hybrid preload reused resetAndReload(sibling), which sets pendingDefaultSelection on the sibling. Whichever request landed last could mutate the inactive lens's selection and the ?project= query param non-deterministically. Added a dedicated preloadSibling() path that:

  • Bails out if the sibling state is already loaded() or loading() (no duplicate in-flight requests).
  • Triggers reload$.next() without setting pendingDefaultSelection, so the preload can't overwrite URL/context.

C4 — Wrong empty state in hybrid mode (sidebar.component.html)

emptyMessage was derived from the active lens ("No foundations found" / "No projects found"), but in hybrid mode the dropdown renders mixed All/Foundations/Projects tabs. Now uses 'No results found' when isHybridPersona() is true.

Verification

  • yarn ng build --configuration=development — clean
  • yarn check-types — clean
  • All previously raised review items (rounds 1 + 2) remain addressed

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>
Copilot AI review requested due to automatic review settings May 15, 2026 10:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment thread apps/lfx-one/src/app/shared/services/navigation.service.ts
- 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>
@nunoeufrasio
Copy link
Copy Markdown
Contributor Author

Review feedback summary

Thanks for the thorough reviews. All 20 unresolved threads have been addressed and resolved.

Addressed in this commit (a9b049d)

  • navigation.service.ts — added hybridTransitionPreloader so the sibling lens preloads when a persona refresh promotes the user to hybrid mid-session without an activeLens change.
  • project-selector.component.html — removed tabindex="0" from the role badge <span> nested inside the trigger <button> to fix the invalid nested-focusable-in-button.

Addressed in earlier commits on this branch

Hybrid mode behaviour

  • hasMore / loadMore now branch on activeTab() so foundation pagination doesn't starve the projects tab and the auto-load sentinel stays scoped.
  • displayLenses signal added for UI hiding; availableLenses keeps its authoritative semantics so applyVisibilityFilters still filters foundations out of the project lens for hybrid users.
  • preloadSibling() no longer sets pendingDefaultSelection, so the sibling fetch can't overwrite the active lens's selection or the ?project= query param.
  • Sidebar onItemSelected sets the foundation/project context before changing lens so the subsequent reload sees the just-clicked UID.
  • Sidebar emptyMessage now branches on isHybridPersona() for a hybrid-specific empty state.

Performance / architecture

  • buildAllTabItems() pre-groups projects by parentProjectUid into a Map — O(F + P) instead of O(F × P).
  • displayedItems precomputes per-row state (isSelected, roleLabel, roleIcon); the three method shells were removed. No function calls in row templates.
  • isLensActive() replaced with an activeLensId computed that absorbs the hybrid-merged-lens special case. No method calls in the switcher template.
  • Every complex computed in project-selector now uses a private initXxx() initializer.
  • DisplayLensItem and SelectorTab moved to packages/shared/src/interfaces/lens.interface.ts; LensItem import switched to import type to avoid a runtime cycle.

Accessibility

  • Role badge now uses role="img" + aria-label so screen readers announce the role without needing a tab stop.
  • Sidebar multi-role persona badges use tabindex="0" + aria-label describing role and project names.
  • Sidebar tooltip body rendered via <ng-template> with structured {label, icon, names}[] data so Angular handles escaping ([escape]="false" and the manual escapeHtml helper removed).

Polish

  • [class] swapped for [ngClass] on the role icon so static size class isn't overridden.
  • Map-direction comment corrected (projectUid → parentProjectUid).
  • Multi-line comment blocks collapsed to one line each.

Thanks again to the reviewers — MRashad26, copilot-pull-request-reviewer, and coderabbitai — for the detailed feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants