Skip to content

Conversation

@asithade
Copy link
Contributor

@asithade asithade commented Jan 6, 2026

Summary

Implements a reusable ScrollShadowDirective and applies it across multiple dashboard and module components for consistent scroll fade effects. This eliminates code duplication and provides a unified pattern for scroll detection and visual feedback.

Changes

New Features

  • ScrollShadowDirective: Reusable Angular directive for scroll shadow detection and visibility management
    • Horizontal scroll detection (showLeftShadow, showRightShadow)
    • Vertical scroll detection (showTopShadow, showBottomShadow) for future iterations
    • Configurable scroll distance via input property
    • SSR-safe implementation

Components Updated

  • Dashboard Components:

    • Recent Progress: Integrated scroll shadow directive with carousel controls
    • Foundation Health: Added shadow overlays with Tailwind gradients
    • Organization Involvement: Replaced custom scroll logic with directive
  • Module Components:

    • Committee Dashboard and Table
    • Mailing List Dashboard, Table, and Manage
    • Meetings Dashboard and Top Bar

Technical Improvements

  • Fixed accessibility modifiers (added public keywords)
  • Resolved type safety issues in ViewChild declarations
  • Removed unnecessary optional chaining in templates
  • Applied consistent Tailwind CSS gradient styling (#f9fafb background)
  • Enhanced code reusability across components

Related Tickets

  • LFXV2-956: Implement scroll shadow directive
  • LFXV2-957: Apply scroll shadow directive to carousel components
  • LFXV2-958: Enhance committee dashboard and table components
  • LFXV2-959: Enhance mailing-list components
  • LFXV2-960: Enhance meetings dashboard and navigation

Testing

  • Build validation: ✅ Passed
  • Linting: ✅ Passed
  • Type checking: ✅ Passed
  • Formatting: ✅ Prettier compliant

This commit implements a reusable ScrollShadowDirective and applies it across
multiple dashboard and module components for consistent scroll fade effects.

Key Changes:
- Create ScrollShadowDirective with horizontal and vertical scroll detection
- Apply directive to dashboard components (recent-progress, foundation-health, organization-involvement)
- Add scroll shadow overlays with Tailwind CSS gradients
- Enhance committee, mailing-list, and meetings components
- Remove custom scroll logic in favor of centralized directive
- Fix accessibility modifiers and type safety issues

Related tickets:
- LFXV2-956: Implement scroll shadow directive
- LFXV2-957: Apply scroll shadow directive to carousel components
- LFXV2-958: Enhance committee dashboard and table components
- LFXV2-959: Enhance mailing-list components
- LFXV2-960: Enhance meetings dashboard and navigation

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
Signed-off-by: Asitha de Silva <[email protected]>
@asithade asithade requested a review from jordane as a code owner January 6, 2026 19:58
Copilot AI review requested due to automatic review settings January 6, 2026 19:58
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

Walkthrough

Adds a standalone ScrollShadowDirective for scroll-aware shadow overlays and smooth scrolling; moves search/filter UI from several dashboards into their child table components via new input bindings; integrates meetings time filter into the top-bar; converts mailing-list creation to a stepper flow; removes a sidebar shadow utility.

Changes

Cohort / File(s) Summary
Committee Dashboard & Table Refactor
apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html, apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts, apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html, apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
Removed sticky top bar (search/filters) from dashboard template; added public inputs on lfx-committee-table: searchForm, categoryOptions, votingStatusOptions. Table component now declares form inputs and imports for input/select UI.
Mailing Lists: Dashboard, Table, Manage
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html, .../mailing-list-dashboard.component.ts, apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html, .../mailing-list-table.component.ts, apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html, .../mailing-list-manage.component.ts
Dashboard now defers render until servicesLoaded and exposes searchForm, committeeFilterOptions, statusFilterOptions to table; added service discovery signals (servicesLoaded, availableServices, hasNoServices) with parent-project fallback. Table receives search/filter inputs and adds form/select imports. Manage view converted to a 3-step stepper UI; removed previous no-services branch, hasNoServices property, onSkip(), and Card import.
Scroll Shadow Directive + Carousel updates
apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts, apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html, .../foundation-health.component.ts, apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html, .../organization-involvement.component.ts, apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html, .../recent-progress.component.ts
Added ScrollShadowDirective (public signals: showLeftShadow, showRightShadow, showTopShadow, showBottomShadow; input scrollDistance; methods scrollLeft/scrollRight) and replaced ElementRef-based scrolling in carousel components with directive-based @ViewChild(ScrollShadowDirective) usage. Templates now call scrollShadowDirective.scrollLeft/scrollRight() and conditionally render shadow overlays from directive signals.
Meetings: Time Filter Integration
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html, .../meetings-top-bar.component.ts, apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html, .../meetings-dashboard.component.ts
Moved time filter into the top-bar component: top-bar exposes timeFilterOptions and timeFilterChange output; dashboard removed the separate time filter bar and now handles timeFilterChange via onTimeFilterChange(), switching to a signal-driven filter.
Sidebar Styling
apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
Removed complex non-mobile shadow utility from ngClass, retaining border styling only.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User
participant CarouselComponent as Component
participant ScrollDirective as ScrollShadowDirective
participant Scroller as ScrollContainer
Note over ScrollDirective: New directive attached to scroller (host)
User->>Component: click left/right control
Component->>ScrollDirective: call scrollLeft()/scrollRight()
ScrollDirective->>Scroller: perform smooth scroll by scrollDistance
ScrollDirective->>ScrollDirective: updateScrollShadows() (calculate offsets)
ScrollDirective-->>Component: expose signals showLeftShadow/showRightShadow
Component->>Component: template reacts to signals and toggles shadow overlays

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: implementing a reusable scroll shadow directive across dashboards, which is the primary focus of this PR.
Description check ✅ Passed The description provides comprehensive context about the ScrollShadowDirective implementation, affected components, and technical improvements, all directly related to the changeset.
Linked Issues check ✅ Passed The PR successfully implements all linked issue objectives: ScrollShadowDirective creation (LFXV2-956), directive application to carousels (LFXV2-957), committee module enhancements (LFXV2-958), mailing-list improvements (LFXV2-959), and meetings module updates (LFXV2-960).
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue objectives. The sidebar shadow removal and committee/mailing-list/meetings form control updates all serve the stated goals of enhancing UI/UX and refactoring components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/LFXV2-956

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

Copy link

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts (2)

86-95: Critical: Category and voting status filters are broken.

The categoryFilter and votingStatusFilter signals are never updated when users change the corresponding dropdown values in the child component. The filtering logic at lines 230-243 relies on these signals, but there's no subscription to the form control valueChanges for category and votingStatus.

Currently, only the search filter works because it has a valueChanges subscription (line 144). The category and voting status filters will not function.

🔎 Proposed fix: Subscribe to form control changes

Add subscriptions similar to initializeSearchTerm:

     // Initialize search form
     this.searchForm = this.initializeSearchForm();
-    this.categoryFilter = signal<string | null>(null);
-    this.votingStatusFilter = signal<string | null>(null);
     this.searchTerm = this.initializeSearchTerm();
+    this.categoryFilter = this.initializeCategoryFilter();
+    this.votingStatusFilter = this.initializeVotingStatusFilter();

Then add these initialization methods after initializeSearchTerm:

  private initializeCategoryFilter(): Signal<string | null> {
    return toSignal(
      this.searchForm.get('category')!.valueChanges.pipe(
        startWith(null),
        distinctUntilChanged()
      ),
      { initialValue: null }
    );
  }

  private initializeVotingStatusFilter(): Signal<string | null> {
    return toSignal(
      this.searchForm.get('votingStatus')!.valueChanges.pipe(
        startWith(null),
        distinctUntilChanged()
      ),
      { initialValue: null }
    );
  }

104-110: Remove orphaned event handler methods.

The onCategoryChange and onVotingStatusChange methods are no longer called since the filter UI moved to the child component. These methods should be removed to avoid confusion.

🔎 Proposed cleanup
-  public onCategoryChange(value: string | null): void {
-    this.categoryFilter.set(value);
-  }
-
-  public onVotingStatusChange(value: string | null): void {
-    this.votingStatusFilter.set(value);
-  }
-
   public onSearch(): void {
     // Trigger search through form control value changes
   }
🤖 Fix all issues with AI Agents
In
@apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts:
- Line 20: The timeFilterValue input is not propagated into the form control
(timeFilter), leaving the form hardcoded to 'upcoming'; add an effect (or
ngOnChanges) in the MeetingsTopBarComponent that watches timeFilterValue and
updates the FormControl (timeFilter.setValue or patchValue) after the form is
initialized in the constructor so the form reflects the parent-provided value,
guard against redundant updates by comparing current value before setting, and
apply the same sync pattern for the other inputs referenced around lines 36-40.

In @apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts:
- Around line 23-27: The directive attaches a scroll listener in ngAfterViewInit
but never removes it, causing a memory leak; implement OnDestroy, store the
listener callback (or use a bound method reference) when adding it to
this.el.nativeElement in ngAfterViewInit, and remove it in ngOnDestroy via
removeEventListener on the same element and same callback (or use
Renderer2.listen and call the returned cleanup function from ngOnDestroy);
ensure references to ngAfterViewInit, ngOnDestroy, updateScrollShadows, and
this.el.nativeElement (or container) are used to locate and fix the code.
🧹 Nitpick comments (11)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html (2)

80-91: Unused template variable and inconsistent signal invocation.

  1. let-activateCallback="activateCallback" is declared but never used in the template. Remove it to reduce noise.

  2. Line 84 passes formValue (the signal reference) while line 83 passes form() (the signal's value). If lfx-mailing-list-basic-info expects a value rather than a signal for formValue, this should be [formValue]="formValue()".

🔎 Proposed fix
 <p-step-panel [value]="1" data-testid="mailing-list-manage-panel-1">
-  <ng-template #content let-activateCallback="activateCallback">
+  <ng-template #content>
     <lfx-mailing-list-basic-info
       [form]="form()"
-      [formValue]="formValue"
+      [formValue]="formValue()"
       [service]="selectedService()"
       [prefix]="servicePrefix()"
       [maxGroupNameLength]="maxGroupNameLength()"
       [isEditMode]="isEditMode()">
     </lfx-mailing-list-basic-info>
   </ng-template>
 </p-step-panel>

Please verify whether lfx-mailing-list-basic-info expects the formValue input as a signal or as a plain value. If it expects a signal, the current code is correct; otherwise, invoke the signal with formValue().


93-107: Remove unused template variables for consistency.

The let-activateCallback="activateCallback" declarations in the remaining step panels (lines 94, 100) are also unused. Clean these up for consistency.

🔎 Proposed fix
 <p-step-panel [value]="2" data-testid="mailing-list-manage-panel-2">
-  <ng-template #content let-activateCallback="activateCallback">
+  <ng-template #content>
     <lfx-mailing-list-settings [form]="form()"></lfx-mailing-list-settings>
   </ng-template>
 </p-step-panel>

 <p-step-panel [value]="3" data-testid="mailing-list-manage-panel-3">
-  <ng-template #content let-activateCallback="activateCallback">
+  <ng-template #content>
     <!-- Step 3: People & Groups - Placeholder -->
     <div class="text-center text-gray-500 py-8">
       <p>Step 3: People & Groups Component</p>
       <p class="text-sm mt-2">Coming next</p>
     </div>
   </ng-template>
 </p-step-panel>
apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts (1)

50-52: Consider extracting filter option type to a shared interface.

The inline type { label: string; value: string | null }[] is used for both committeeFilterOptions and statusFilterOptions. Per coding guidelines, prefer TypeScript interfaces for better maintainability and reusability.

🔎 Suggested interface

Create a shared interface (e.g., in @lfx-one/shared/interfaces):

export interface FilterOption {
  label: string;
  value: string | null;
}

Then update the inputs:

-  public committeeFilterOptions = input.required<{ label: string; value: string | null }[]>();
-  public statusFilterOptions = input.required<{ label: string; value: string | null }[]>();
+  public committeeFilterOptions = input.required<FilterOption[]>();
+  public statusFilterOptions = input.required<FilterOption[]>();
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html (1)

31-42: Update data-testid to follow naming convention.

The data-testid should follow the [section]-[component]-[element] pattern. Consider renaming time-filter-dropdown to meetings-topbar-timefilter-dropdown for consistency with the hierarchical structure.

As per coding guidelines for HTML templates.

🔎 Proposed fix
     <lfx-select
       [form]="searchForm"
       control="timeFilter"
       size="small"
       [options]="timeFilterOptions()"
       placeholder="Select Time"
       styleClass="w-full"
       (onChange)="onTimeFilterChange($event.value)"
-      data-testid="time-filter-dropdown"></lfx-select>
+      data-testid="meetings-topbar-timefilter-dropdown"></lfx-select>
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts (2)

29-33: Replace computed signal with readonly property for static data.

The timeFilterOptions uses computed() but returns static data that never changes. Using computed() for static data creates unnecessary reactivity overhead. Consider using a readonly property instead.

🔎 Proposed fix
-    // Initialize time filter options
-    this.timeFilterOptions = computed(() => [
-      { label: 'Upcoming', value: 'upcoming' },
-      { label: 'Past', value: 'past' },
-    ]);
+    // Initialize time filter options
+    this.timeFilterOptions = [
+      { label: 'Upcoming', value: 'upcoming' },
+      { label: 'Past', value: 'past' },
+    ];

Then update the property declaration at line 26:

-  public timeFilterOptions: Signal<{ label: string; value: 'upcoming' | 'past' }[]>;
+  public readonly timeFilterOptions: { label: string; value: 'upcoming' | 'past' }[];

50-58: Remove unnecessary truthy check.

The if (value) guard is unnecessary since value is typed as 'upcoming' | 'past', and both are truthy strings. This check adds no value and can be simplified.

🔎 Proposed fix
     // Subscribe to time filter changes
     this.searchForm
       .get('timeFilter')
       ?.valueChanges.pipe(takeUntilDestroyed())
       .subscribe((value) => {
-        if (value) {
-          this.timeFilterChange.emit(value);
-        }
+        this.timeFilterChange.emit(value);
       });
apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts (1)

42-42: Consider using a signal for searchForm to align with Angular 19 best practices.

Per the coding guideline to "use zoneless change detection with signals in Angular 19," the searchForm property could be converted to a signal for consistency. However, this is a minor improvement since the current implementation works correctly with Angular's input signal conversion.

apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html (1)

98-102: Unnecessary null-check for ViewChild in template.

The template checks scrollShadowDirective && before accessing its properties, but the ViewChild is declared with the definite assignment assertion (!), indicating it will always be initialized. Since the directive is applied to line 51 and ViewChild queries are resolved after view initialization, the null-check is redundant in the conditional blocks.

🔎 Simplify the conditional checks
 <!-- Right shadow fade -->
-@if (scrollShadowDirective && scrollShadowDirective.showRightShadow()) {
+@if (scrollShadowDirective.showRightShadow()) {
   <div
     class="absolute top-0 bottom-0 right-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_right,transparent,#f9fafb)]"
     data-testid="dashboard-involvement-shadow-right"></div>
 }

 <!-- Left shadow fade -->
-@if (scrollShadowDirective && scrollShadowDirective.showLeftShadow()) {
+@if (scrollShadowDirective.showLeftShadow()) {
   <div
     class="absolute top-0 bottom-0 left-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_left,transparent,#f9fafb)]"
     data-testid="dashboard-involvement-shadow-left"></div>
 }

Also applies to: 105-109

apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html (1)

59-63: Unnecessary null-check for ViewChild in template.

Similar to the organization-involvement component, the template includes redundant null-checks on scrollShadowDirective before accessing its properties. Since the ViewChild is declared with a definite assignment assertion and the directive is applied at line 42, the null-checks are unnecessary.

🔎 Simplify the conditional checks
 <!-- Right shadow fade -->
-@if (scrollShadowDirective && scrollShadowDirective.showRightShadow()) {
+@if (scrollShadowDirective.showRightShadow()) {
   <div
     class="absolute top-0 bottom-0 right-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_right,transparent,#f9fafb)]"
     data-testid="dashboard-recent-progress-shadow-right"></div>
 }

 <!-- Left shadow fade -->
-@if (scrollShadowDirective && scrollShadowDirective.showLeftShadow()) {
+@if (scrollShadowDirective.showLeftShadow()) {
   <div
     class="absolute top-0 bottom-0 left-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_left,transparent,#f9fafb)]"
     data-testid="dashboard-recent-progress-shadow-left"></div>
 }

Also applies to: 66-70

apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html (2)

181-181: Remove unnecessary null checks on scrollShadowDirective.

The null checks scrollShadowDirective && are unnecessary because the component declares the ViewChild with a non-null assertion operator (!), guaranteeing the directive instance exists when these conditions are evaluated.

🔎 Proposed simplification
       <!-- Right shadow fade -->
-      @if (scrollShadowDirective && scrollShadowDirective.showRightShadow()) {
+      @if (scrollShadowDirective.showRightShadow()) {
         <div
           class="absolute top-0 bottom-0 right-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_right,transparent,#f9fafb)]"
           data-testid="foundation-health-shadow-right"></div>
       }

       <!-- Left shadow fade -->
-      @if (scrollShadowDirective && scrollShadowDirective.showLeftShadow()) {
+      @if (scrollShadowDirective.showLeftShadow()) {
         <div
           class="absolute top-0 bottom-0 left-0 w-32 z-10 pointer-events-none bg-[linear-gradient(to_left,transparent,#f9fafb)]"
           data-testid="foundation-health-shadow-left"></div>
       }

Also applies to: 188-188


183-183: Consider using Tailwind color variables instead of hardcoded hex.

The hardcoded color #f9fafb (appears to be gray-50) reduces maintainability and consistency with the design system. Consider using CSS custom properties or Tailwind's color system for better theme consistency.

As per coding guidelines, "Use Tailwind CSS with PrimeUI plugin and LFX colors for styling."

You could either:

  1. Define a CSS custom property in your theme (e.g., var(--surface-ground))
  2. Use Tailwind's color in a CSS class if gradients with Tailwind colors are supported in your setup

Also applies to: 190-190

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b44456 and 1a3abd0.

📒 Files selected for processing (22)
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
🧰 Additional context used
📓 Path-based instructions (8)
apps/lfx-one/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.{ts,tsx}: Use zoneless change detection with signals in Angular 19
Always use direct imports for standalone components - no barrel exports
Use ESLint and Prettier for code quality, following Angular-specific ESLint rules

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
apps/lfx-one/src/**/*.component.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.ts: All PrimeNG components must be wrapped in LFX components for UI library independence
Add data-testid attributes when creating new components for reliable test targeting

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript interfaces instead of union types for better maintainability

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
{apps,packages}/**/*.{ts,tsx,js,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx,js,html,scss,css}: License headers are required on all source files
Always prepend 'Generated with Claude Code (https://claude.ai/code)' if Claude Code assisted with the code

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Do not nest ternary expressions
Always use async/await for promises in TypeScript/JavaScript
Use camelCase for variable and function names

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
apps/lfx-one/**/*.{ts,tsx,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier configuration with Tailwind integration in lfx-one app

Files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
apps/lfx-one/src/**/*.component.html

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.html: Use data-testid naming convention: [section]-[component]-[element] for hierarchical structure
Use flex + flex-col + gap-* instead of space-y-* for spacing

Files:

  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
apps/lfx-one/src/**/*.{html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS with PrimeUI plugin and LFX colors for styling

Files:

  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html
🧠 Learnings (1)
📚 Learning: 2025-12-10T23:20:29.281Z
Learnt from: asithade
Repo: linuxfoundation/lfx-v2-ui PR: 206
File: apps/lfx-one/src/app/modules/meetings/components/agenda-template-selector/agenda-template-selector.component.ts:11-15
Timestamp: 2025-12-10T23:20:29.281Z
Learning: For Angular 19+ projects, standalone defaults to true for components, directives, and pipes when not explicitly declared. Therefore, components with an imports array in the Component decorator do not require an explicit standalone: true declaration to be standalone. In reviews, verify that a component without an explicit standalone flag and with an imports array will behave as a standalone component according to Angular 19+ defaults. If you need to enforce explicit standalone behavior for clarity or consistency, you can still add standalone: true, but it is not required by default in Angular 19+. Ensure this guidance is considered when reviewing any component.ts files across the codebase.

Applied to files:

  • apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts
  • apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
🧬 Code graph analysis (1)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts (1)
packages/shared/src/interfaces/mailing-list.interface.ts (1)
  • GroupsIOService (37-68)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🔇 Additional comments (24)
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.ts (2)

24-29: LGTM!

The component decorator is correctly structured. The imports array includes all necessary modules for the stepper-based flow. Based on learnings, Angular 19+ defaults standalone: true when an imports array is present, so explicit declaration is not required.


296-343: LGTM!

The reactive service initialization chain is well-structured with proper fallback logic to the parent project and comprehensive error handling via catchError.

apps/lfx-one/src/app/modules/mailing-lists/mailing-list-manage/mailing-list-manage.component.html (2)

14-16: LGTM!

The page title correctly displays "Add Mailing List" in create mode and "Edit Mailing List" in edit mode. The data-testid attribute follows the naming convention.


18-76: LGTM!

The stepper configuration and navigation controls are well-implemented. The linear binding correctly allows free navigation only in edit mode, and the conditional button rendering logic is appropriate for the multi-step flow.

apps/lfx-one/src/app/modules/mailing-lists/components/mailing-list-table/mailing-list-table.component.html (1)

5-46: Well-structured filter bar implementation.

The filter controls are properly implemented with:

  • Responsive layout using flex-col to md:flex-row
  • Proper gap-* spacing instead of space-y-* (per coding guidelines)
  • Correct data-testid naming convention following [section]-[component]-[element] pattern
apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.html (2)

12-39: Well-implemented empty state for missing services.

The "No Services" empty state follows the established patterns with proper layout using flex + flex-col + gap-*. The contact support button provides a clear call-to-action.


93-96: No issues found. The HTML at line 95 correctly binds [statusFilterOptions]="statusOptions()" to the signal defined in the component's TypeScript file (line 57), and the signal is properly initialized at line 101.

Likely an incorrect or invalid review comment.

apps/lfx-one/src/app/modules/mailing-lists/mailing-list-dashboard/mailing-list-dashboard.component.ts (2)

249-285: Service initialization logic is well-structured with proper fallback.

The implementation correctly:

  1. Resets servicesLoaded when project changes
  2. Falls back to parent project services when none are found
  3. Sets servicesLoaded to true on both success and error paths

However, the deeply nested switchMap operators increase cognitive complexity.


68-72: Clear signal naming for service availability state.

The three signals (servicesLoaded, availableServices, hasNoServices) provide a clean API for the template to handle different loading and empty states.

apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts (1)

65-67: LGTM!

The handler correctly emits the time filter change event.

apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html (1)

23-34: LGTM!

The time filter integration is implemented correctly. The bindings properly connect the parent's timeFilter signal with the child component's input/output, and the conditional meeting selection is accurate.

apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts (2)

5-5: LGTM!

The import changes correctly remove form-related dependencies that are no longer needed after moving time filter logic to the child component.

Also applies to: 22-22


91-93: LGTM!

The onTimeFilterChange handler correctly updates the time filter signal, which triggers reactive data loading through the observable conversions at lines 98 and 153.

apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.html (1)

5-46: Filter bar implementation looks good.

The new filter bar correctly uses:

  • flex + flex-col + gap-* spacing utilities (per coding guidelines)
  • Proper data-testid attributes for test targeting
  • Correct form binding syntax with searchForm() calling the input signal
  • Responsive layout with md:flex-row breakpoint

The control names (search, category, votingStatus) match the parent component's FormGroup definition.

apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.ts (1)

23-23: Imports cleanup looks correct.

The removal of ReactiveFormsModule, InputTextComponent, and SelectComponent is appropriate since these are now only used in the child CommitteeTableComponent. The parent component still correctly imports FormControl and FormGroup types needed for its form logic.

apps/lfx-one/src/app/modules/committees/components/committee-table/committee-table.component.ts (1)

6-6: Form-related inputs and imports are correctly implemented.

The component properly:

  • Imports ReactiveFormsModule and FormGroup for form binding
  • Adds InputTextComponent and SelectComponent for the filter controls
  • Declares three required inputs with appropriate types (FormGroup and option arrays)
  • Ensures parent components must provide these inputs via input.required()

The input signal pattern aligns with Angular 19 best practices, and the component is correctly standalone by default (per project learnings).

Also applies to: 10-11, 29-29, 35-36, 57-59

apps/lfx-one/src/app/modules/committees/committee-dashboard/committee-dashboard.component.html (1)

65-67: Input bindings to child component are syntactically correct.

The template correctly passes:

  • searchForm as a FormGroup instance (Angular handles input signal conversion)
  • categories() and votingStatusOptions() by calling the signal functions

The bindings satisfy the child component's required inputs.

Note: The functionality of the category and voting status filters depends on the parent component subscribing to form control value changes, which is flagged as a critical issue in the TypeScript file review.

apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html (1)

23-36: LGTM! Clean directive integration.

The template correctly integrates the ScrollShadowDirective with proper accessibility labels on carousel control buttons, consistent data-testid naming, and correct application of the directive with Tailwind flex/gap spacing patterns.

Also applies to: 50-51

apps/lfx-one/src/app/shared/components/sidebar/sidebar.component.html (1)

4-9: LGTM! Simplified sidebar styling.

Removing the custom shadow utility while retaining the border creates a cleaner visual separation. This aligns with the PR's focus on directive-based scroll shadows for specific carousel components.

apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.ts (2)

36-36: LGTM! Type-safe ViewChild declaration.

The ViewChild correctly uses the directive type as the selector, providing better type safety compared to string-based selectors. The public modifier appropriately exposes the directive for template access, and the definite assignment assertion is valid since the directive is applied in the template.


4-4: Clean directive integration with proper imports.

The imports are correctly structured with the ScrollShadowDirective added to both the component's imports array and ES6 imports. This follows Angular 19+ standalone component patterns appropriately.

Based on learnings, the component defaults to standalone in Angular 19+, so the explicit standalone declaration is not required.

Also applies to: 10-10, 31-31

apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html (1)

23-36: Consistent directive implementation across carousel components.

The recent-progress component follows the same directive-based scroll pattern as organization-involvement, ensuring consistency across dashboard carousels. The template correctly applies the directive and wires up carousel controls.

Also applies to: 41-42

apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.ts (1)

4-4: Consistent directive integration pattern.

The foundation-health component follows the same clean refactoring pattern as the other dashboard components, replacing ElementRef-based scrolling with the type-safe ScrollShadowDirective. The imports, component metadata, and ViewChild declaration are all properly structured.

Also applies to: 9-9, 27-27, 32-32

apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.ts (1)

4-4: LGTM: Clean integration with ScrollShadowDirective.

The component correctly:

  • Imports the necessary types (ViewChild and ScrollShadowDirective)
  • Adds the directive to the component's imports array
  • Updates the ViewChild binding from ElementRef to ScrollShadowDirective
  • Uses the protected access modifier for template access
  • Applies non-null assertion appropriately

This follows the established pattern across the PR for directive-based scroll shadow management.

Also applies to: 21-21, 39-39, 44-44

Copy link
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 implements a reusable ScrollShadowDirective to provide consistent scroll shadow effects across dashboard and module components, eliminating code duplication. The directive detects horizontal and vertical scroll positions and exposes signals to control shadow visibility.

Key Changes:

  • Introduced ScrollShadowDirective with horizontal/vertical scroll detection and configurable scroll distance
  • Refactored Recent Progress, Foundation Health, and Organization Involvement dashboard components to use the directive
  • Consolidated filter bars into table components for Committee and Mailing List modules
  • Moved time filter from separate section into top bar for Meetings dashboard

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
scroll-shadow.directive.ts New directive providing scroll shadow detection and visibility management
sidebar.component.html Removed shadow styling from sidebar border
recent-progress.component.* Integrated scroll shadow directive, removed custom scroll methods
organization-involvement.component.* Applied scroll shadow directive to carousel
foundation-health.component.* Applied scroll shadow directive with custom scroll distance
meetings-dashboard.component.* Consolidated time filter into top bar, removed form logic
meetings-top-bar.component.* Added time filter control and event handling
mailing-list-manage.component.* Removed unused service check logic and skip functionality, updated button label
mailing-list-dashboard.component.* Added service availability check with empty state
mailing-list-table.component.* Moved filter bar into table component
committee-dashboard.component.* Moved filter bar from dashboard to table component
committee-table.component.* Added filter bar inside table card

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix memory leak by properly cleaning up scroll event listener
- Make directive SSR-safe using afterNextRender
- Fix ExpressionChangedAfterItHasBeenCheckedError with setTimeout
- Add aria-hidden to decorative shadow overlays for accessibility
- Remove unused timeFilterValue input from meetings-top-bar component

🤖 Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Asitha de Silva <[email protected]>
Copy link

@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

🤖 Fix all issues with AI Agents
In
@apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts:
- Line 22: Add an optional input to allow parent initialization: declare
timeFilterValue = input<'upcoming' | 'past'>() and then initialize the form
control using that input instead of hardcoding 'upcoming' (i.e., construct the
control with new FormControl<'upcoming' | 'past'>(this.timeFilterValue() ??
'upcoming')). Keep the existing public readonly timeFilterChange =
output<'upcoming' | 'past'>() and ensure the component emits via
onTimeFilterChange as before so parent and child stay in sync.
- Around line 64-66: The onTimeFilterChange handler currently emits the new
value without updating the reactive form control, causing potential
desynchronization; update the searchForm's timeFilter FormControl (e.g., call
setValue or patchValue on searchForm.get('timeFilter')) before calling
this.timeFilterChange.emit(value), and apply the same change to
onMeetingTypeChange by updating the meetingType FormControl on the searchForm
before emitting via this.meetingTypeChange.emit(value).

In @apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts:
- Around line 24-34: The constructor still uses setTimeout before calling
updateScrollShadows which reintroduces the
ExpressionChangedAfterItHasBeenCheckedError; remove the setTimeout and invoke
this.updateScrollShadows() directly inside the afterNextRender callback after
adding the scroll listener (in the public constructor where afterNextRender,
this.el, container, this.scrollHandler, and this.destroyRef are used) so the
initial shadow state is updated after the first render without extra microtask
scheduling.
🧹 Nitpick comments (2)
apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts (1)

46-46: Consider throttling scroll updates for performance.

Scroll events can fire at 60+ FPS. While likely acceptable for this use case, wrapping the handler in requestAnimationFrame would batch updates to the render cycle and reduce redundant signal updates.

🔎 Optional optimization using requestAnimationFrame
+  private rafId: number | null = null;
+
-  private scrollHandler = (): void => this.updateScrollShadows();
+  private scrollHandler = (): void => {
+    if (this.rafId !== null) return;
+    this.rafId = requestAnimationFrame(() => {
+      this.updateScrollShadows();
+      this.rafId = null;
+    });
+  };

If using this, also cancel any pending frame in the destroy callback:

this.destroyRef.onDestroy(() => {
  container.removeEventListener('scroll', this.scrollHandler);
  if (this.rafId !== null) cancelAnimationFrame(this.rafId);
});
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts (1)

28-32: Consider making timeFilterOptions a readonly property initialized at declaration.

Since timeFilterOptions contains static data that never changes, initializing it in the constructor adds unnecessary overhead. Moving it to a readonly property initialized at declaration would be cleaner and more efficient.

🔎 Proposed refactor
- public timeFilterOptions: { label: string; value: 'upcoming' | 'past' }[];
+ public readonly timeFilterOptions = [
+   { label: 'Upcoming', value: 'upcoming' as const },
+   { label: 'Past', value: 'past' as const },
+ ];

  public constructor() {
-   // Initialize time filter options
-   this.timeFilterOptions = [
-     { label: 'Upcoming', value: 'upcoming' },
-     { label: 'Past', value: 'past' },
-   ];
-
    // Initialize form
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1a3abd0 and 800815c.

📒 Files selected for processing (7)
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
🧰 Additional context used
📓 Path-based instructions (8)
apps/lfx-one/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.{ts,tsx}: Use zoneless change detection with signals in Angular 19
Always use direct imports for standalone components - no barrel exports
Use ESLint and Prettier for code quality, following Angular-specific ESLint rules

Files:

  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript interfaces instead of union types for better maintainability

Files:

  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
{apps,packages}/**/*.{ts,tsx,js,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

{apps,packages}/**/*.{ts,tsx,js,html,scss,css}: License headers are required on all source files
Always prepend 'Generated with Claude Code (https://claude.ai/code)' if Claude Code assisted with the code

Files:

  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: Do not nest ternary expressions
Always use async/await for promises in TypeScript/JavaScript
Use camelCase for variable and function names

Files:

  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
apps/lfx-one/**/*.{ts,tsx,html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Prettier configuration with Tailwind integration in lfx-one app

Files:

  • apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts
  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
apps/lfx-one/src/**/*.component.html

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.html: Use data-testid naming convention: [section]-[component]-[element] for hierarchical structure
Use flex + flex-col + gap-* instead of space-y-* for spacing

Files:

  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
apps/lfx-one/src/**/*.{html,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS with PrimeUI plugin and LFX colors for styling

Files:

  • apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html
  • apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html
apps/lfx-one/src/**/*.component.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/lfx-one/src/**/*.component.ts: All PrimeNG components must be wrapped in LFX components for UI library independence
Add data-testid attributes when creating new components for reliable test targeting

Files:

  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
🧠 Learnings (1)
📚 Learning: 2025-12-10T23:20:29.281Z
Learnt from: asithade
Repo: linuxfoundation/lfx-v2-ui PR: 206
File: apps/lfx-one/src/app/modules/meetings/components/agenda-template-selector/agenda-template-selector.component.ts:11-15
Timestamp: 2025-12-10T23:20:29.281Z
Learning: For Angular 19+ projects, standalone defaults to true for components, directives, and pipes when not explicitly declared. Therefore, components with an imports array in the Component decorator do not require an explicit standalone: true declaration to be standalone. In reviews, verify that a component without an explicit standalone flag and with an imports array will behave as a standalone component according to Angular 19+ defaults. If you need to enforce explicit standalone behavior for clarity or consistency, you can still add standalone: true, but it is not required by default in Angular 19+. Ensure this guidance is considered when reviewing any component.ts files across the codebase.

Applied to files:

  • apps/lfx-one/src/app/modules/meetings/meetings-dashboard/components/meetings-top-bar/meetings-top-bar.component.ts
🔇 Additional comments (9)
apps/lfx-one/src/app/shared/directives/scroll-shadow.directive.ts (3)

1-9: LGTM: Proper license header and directive setup.

The standalone directive with the [lfxScrollShadow] selector follows Angular conventions.


36-44: LGTM: Clean scroll navigation implementation.

The scrollLeft() and scrollRight() methods correctly use scrollBy with smooth behavior and respect the configurable scrollDistance input.


48-80: LGTM: Robust scroll position detection.

The shadow calculation logic correctly handles:

  • Sub-pixel rounding with -1 tolerance
  • Non-scrollable containers (signals set to false)
  • Both horizontal and vertical scroll axes
apps/lfx-one/src/app/modules/dashboards/components/organization-involvement/organization-involvement.component.html (2)

23-31: LGTM: Carousel controls correctly delegate to directive.

The scroll buttons properly invoke the directive's scrollLeft() and scrollRight() methods.


96-111: LGTM: Shadow overlays are accessible and well-styled.

Good implementation:

  • aria-hidden="true" correctly marks decorative elements for screen readers
  • Tailwind gradient with #f9fafb matches the specified background color
  • pointer-events-none ensures shadows don't block interactions
apps/lfx-one/src/app/modules/dashboards/components/recent-progress/recent-progress.component.html (2)

41-56: LGTM: Directive integration follows established pattern.

The carousel container correctly applies the lfxScrollShadow directive with appropriate Tailwind classes for horizontal scrolling (overflow-x-auto, scroll-smooth, hide-scrollbar).


57-72: LGTM: Shadow overlays are properly implemented.

Consistent with the organization-involvement component:

  • aria-hidden="true" for accessibility
  • Correct gradient directions for left/right fades
  • Proper data-testid naming convention
apps/lfx-one/src/app/modules/dashboards/components/foundation-health/foundation-health.component.html (2)

55-56: LGTM: Good use of configurable scroll distance.

The [scrollDistance]="320" input appropriately customizes the scroll step for the foundation health cards, which appear wider than the default 300px.


179-194: LGTM: Shadow overlays follow the consistent pattern.

Implementation matches other dashboard components with:

  • aria-hidden="true" for accessibility
  • Correct gradient colors (#f9fafb) per coding guidelines
  • Proper data-testid naming (foundation-health-shadow-*)

@asithade asithade merged commit 1ba2260 into main Jan 6, 2026
6 checks passed
@asithade asithade deleted the feat/LFXV2-956 branch January 6, 2026 20:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants