Skip to content

feat(meetings): add iCal Subscribe + Calendar view to Meetings dashboard (LFXV2-1771)#697

Open
manishdixitlfx wants to merge 2 commits into
mainfrom
feat/LFXV2-1771-ical-subscribe-meetings-dashboard
Open

feat(meetings): add iCal Subscribe + Calendar view to Meetings dashboard (LFXV2-1771)#697
manishdixitlfx wants to merge 2 commits into
mainfrom
feat/LFXV2-1771-ical-subscribe-meetings-dashboard

Conversation

@manishdixitlfx
Copy link
Copy Markdown
Contributor

@manishdixitlfx manishdixitlfx commented May 12, 2026

Summary

Extends the iCal Subscribe modal shipped in LFXV2-1714 / PR #689 and adds a [List] [Calendar] view toggle to the global Meetings dashboard for the foundation and project lenses. Also delivers the BFF backend endpoint so the Subscribe links actually resolve end-to-end.

Related JIRAs: LFXV2-1771 (this), LFXV2-1770 (org lens, deferred), LFXV2-1772 (me lens, parked).

What changed

Frontend

  • Meetings dashboard (meetings-dashboard.component.ts/.html): new viewMode signal + isListView / isCalendarView computeds; new calendarEvents signal that projects the existing filteredMeetings() into FullCalendar EventInput[]; new onSubscribe() opens IcalSubscribeDialogComponent with the lens-appropriate feed URL and a Subscribe — <name> header. The [List] [Calendar] [Subscribe] toggle row sits between the filter bar and the meeting list; Create Meeting stays in the title row.
  • Committee meetings tab: parity fix — cancelled occurrences are now inert on the calendar (no click navigation, no pointer cursor).
  • Dialog data rename: IcalSubscribeDialogData.committeeNamename (the dialog is no longer committee-specific). The existing committee caller is updated.

Shared infrastructure

  • FullCalendar wrapper SCSS (fullcalendar.component.scss): the blanket bg-gray-100 / bg-blue-50 !important overrides on .fc-timegrid-event are now scoped to .fc-timegrid-event.meeting-event only. Without this, every event with an inline backgroundColor rendered with white text on a near-white background in Week view, making the calendar look empty. This also fixes the same latent Week-view bug on the committee tab.
  • FullCalendar wrapper TS: scrollTime fixed at '06:00:00' (was scrolling to the current hour, which overshoots past the morning when checking after lunch). This also affects all other calendars in the app.

Backend (BFF — Express in this repo)

  • controllers/project.controller.ts: new getProjectCalendar mirroring getCommitteeCalendar exactly — public access via M2M token, paginated upcoming + past meetings filtered by tags: project_uid:<id>, reuses meetingsToVEvents + buildVCalendar, returns text/calendar with Cache-Control: public, max-age=900.
  • routes/public-projects.route.ts (new): single public route GET /:id/calendar.ics.
  • ⚠️ server.ts (protected file): wires app.use('/public/api/projects', publicProjectsRouter). Required for the route to work; flagged for code-owner attention.

Shared types

  • packages/shared/src/interfaces/committee.interface.ts: IcalSubscribeDialogData.committeeNamename.

Out of scope (intentional)

  • Me lens — parked in LFXV2-1772. A personal subscription URL requires a token design (calendar clients cannot carry session cookies).
  • Org lens — parked in LFXV2-1770.
  • Foundation feed roll-up: currently includes only meetings directly tagged to the foundation (project_uid:<foundation_uid>). Sub-project meetings are not yet rolled up — would need a new upstream tag or a cross-project fan-out. Suitable for a follow-up.

Test plan

  • Foundation lens (/foundation/meetings): Subscribe button visible top-right of the list/calendar area; clicking opens the modal with https://app.lfx.dev/public/api/projects/<foundation_uid>/calendar.ics and header Subscribe — <foundation name>.
  • Project lens (/project/meetings): same UX as foundation; URL uses the project's UID.
  • Me lens: Subscribe button is NOT visible (descoped per LFXV2-1772).
  • Org lens: Subscribe button is NOT visible (descoped per LFXV2-1770).
  • List/Calendar toggle: both views render meetings; Calendar view shows the FullCalendar legend; switching back to List shows the meeting cards.
  • Calendar Week view: events are visible (colored backgrounds, white text — not white-on-near-white).
  • Calendar Week view: scrolls to 6 AM on open instead of the current hour.
  • Cancelled meeting occurrence on the calendar: shows a default cursor on hover, click does nothing (verified on both meetings dashboard and committee tab).
  • Backend: curl -i https://<dev or staging>/public/api/projects/<uid>/calendar.ics returns 200, Content-Type: text/calendar, valid BEGIN:VCALENDAR ... END:VCALENDAR body.
  • Paste the iCal URL into Google Calendar / Outlook / Apple Calendar — feed subscribes and renders the meetings.

Notes for reviewers

  • server.ts change is protected — only adds one import and one app.use(...) line; pattern matches the existing publicCommitteesRouter registration.
  • Two shared-component changes affect all calendars (Week view background fix + scrollTime). These are improvements; the committee tab benefits too.
  • No upstream Go service changes — the BFF generates the iCal feed itself from regular meeting queries, mirroring how /public/api/committees/:id/calendar.ics already works.

🤖 Generated with Claude Code

LFXV2-1771: Extend the iCal Subscribe modal (LFXV2-1714 / PR #689) and
a List/Calendar view toggle to the global Meetings dashboard for the
foundation and project lenses.

Frontend (apps/lfx-one):
- meetings-dashboard.component.ts: add viewMode signal + isListView /
  isCalendarView computeds; add calendarEvents signal projecting
  filteredMeetings into FullCalendar EventInput[]; add onSubscribe()
  that opens IcalSubscribeDialogComponent with the correct lens-aware
  feed URL; add onCalendarEventClick() (cancelled occurrences are inert).
- meetings-dashboard.component.html: introduce the
  [List] [Calendar] [Subscribe] toggle row between the filter bar and
  the meeting list; render calendar legend + <lfx-fullcalendar> when
  isCalendarView() is active; Create Meeting stays at the top of the
  title row.
- committee-meetings.component.ts: parity fix — cancelled occurrences
  no longer accept clicks and render with cursor-default.
- ical-subscribe-dialog.component.ts + committees caller: rename
  dialog data key committeeName → name (dialog is no longer
  committee-specific).

Shared infrastructure (apps/lfx-one/src/app/shared/components/fullcalendar):
- fullcalendar.component.scss: scope the timegrid bg-gray-100 /
  bg-blue-50 forced backgrounds to .meeting-event-classed entries
  only. Previously the blanket !important override made any
  inline-coloured event invisible in Week view (white text on
  near-white background). This also fixes the latent Week-view
  visibility bug on the committee tab.
- fullcalendar.component.ts: scrollTime fixed at 06:00 instead of
  the current hour, so Week view opens to a sensible starting hour.

Backend (apps/lfx-one/src/server):
- controllers/project.controller.ts: add getProjectCalendar that
  mirrors getCommitteeCalendar exactly — public access via M2M
  token, paginated upcoming + past meetings filtered by tag
  project_uid:<id>, helpers meetingsToVEvents + buildVCalendar,
  text/calendar response with 15-minute Cache-Control.
- routes/public-projects.route.ts (NEW): single public route
  GET /:id/calendar.ics → projectController.getProjectCalendar.
- server.ts (protected): wire app.use('/public/api/projects',
  publicProjectsRouter).

Shared types (packages/shared/src/interfaces):
- IcalSubscribeDialogData.committeeName → name to reflect the
  broader use case.

Scope and follow-ups:
- The "Me" lens is intentionally not implemented (parked in
  LFXV2-1772). A personal subscription URL needs a token design
  because calendar clients cannot carry session cookies.
- The "Org" lens is parked in LFXV2-1770.
- Foundation feed currently includes only meetings directly tagged
  to the foundation (project_uid:<foundation_uid>). Sub-project
  meetings are not yet rolled up; that requires either a new
  upstream tag or a cross-project fan-out and can be a follow-up.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
Copilot AI review requested due to automatic review settings May 12, 2026 22:02
@manishdixitlfx manishdixitlfx requested a review from a team as a code owner May 12, 2026 22:02
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 621a5492-07f7-4b14-84e4-0f592244d6ff

📥 Commits

Reviewing files that changed from the base of the PR and between 6f538df and bfd0993.

📒 Files selected for processing (6)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
  • 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/fullcalendar/fullcalendar.component.scss
  • apps/lfx-one/src/server/controllers/committee.controller.ts
  • apps/lfx-one/src/server/controllers/project.controller.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • 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

Walkthrough

This PR adds calendar view and iCal subscription to the Meetings dashboard. A shared interface is refactored to use a generic name field, the iCal dialog component is updated to consume it, committee meetings integrate cancelled event handling, the Meetings dashboard gains a calendar view toggle with subscription workflow, FullCalendar styling is refined, and a public backend endpoint generates project calendar ICS feeds.

Changes

Calendar iCal Subscription and Meeting Calendar View

Layer / File(s) Summary
Shared Interface Contract
packages/shared/src/interfaces/committee.interface.ts
IcalSubscribeDialogData interface now uses name: string instead of committeeName: string to represent the display name passed to the iCal subscription dialog.
iCal Dialog Component Refactoring
apps/lfx-one/src/app/modules/committees/components/ical-subscribe-dialog/ical-subscribe-dialog.component.ts
Dialog derives name from dialogConfig.data?.name (defaults to "Calendar") and uses it in Outlook Live/365 URL templates, replacing the prior committeeName fallback.
Committee Meetings iCal Integration
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts
Committee meetings now pass committee name via data.name to the iCal dialog, skip navigation on cancelled event clicks, and apply cursor-default styling to cancelled meeting occurrences.
Meetings Dashboard Calendar UI & State
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts, apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html
Introduces viewMode signal and computed view selectors, imports FullCalendar and dialog components, adds List/Calendar toggle buttons and conditional Subscribe button, and includes calendar legend header with colour indicators for default vs cancelled meetings.
Meetings Dashboard Calendar Logic
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts
Defines calendarEvents computed signal projecting filtered meetings into FullCalendar events; implements onCalendarEventClick to navigate to meeting details while ignoring cancelled occurrences; implements onSubscribe to open iCal dialog with feed URL and name for foundation/project lenses; provides meetingToEvents to expand recurring meetings into per-occurrence events with cancelled styling and extendedProps metadata.
FullCalendar Component Styling & Config
apps/lfx-one/src/app/shared/components/fullcalendar/fullcalendar.component.scss, apps/lfx-one/src/app/shared/components/fullcalendar/fullcalendar.component.ts
Narrows timegrid background CSS selector to .fc-timegrid-event.meeting-event to prevent unintended styling of generic events; adds .fc-event.cursor-default inert styling; hardcodes scrollTime to '06:00:00'.
Backend Committee Calendar Filtering
apps/lfx-one/src/server/controllers/committee.controller.ts
getCommitteeCalendar now filters upcoming and past meetings to only MeetingVisibility.PUBLIC and non-restricted entries before building ICS output, and logs a filtered_out metric.
Backend Project Calendar Endpoint
apps/lfx-one/src/server/controllers/project.controller.ts
Adds iCalendar imports, meeting pagination helpers, M2M token handling, and implements getProjectCalendar to validate project ID, ensure M2M token, fetch upcoming/past meetings, filter to public/non-restricted, build .ics payload, and return it with appropriate cache and content headers.
Backend Route & Server Integration
apps/lfx-one/src/server/routes/public-projects.route.ts, apps/lfx-one/src/server/server.ts
Creates new Express router module for public GET /:id/calendar.ics endpoint; mounts router at /public/api/projects in the server application.
Committee Meetings Legend Color
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
Updates the default meeting legend bullet color from bg-blue-600 to bg-blue-500.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding iCal Subscribe functionality and a Calendar view toggle to the Meetings dashboard, with specific scope (LFXV2-1771).
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering frontend changes, shared infrastructure updates, backend implementation, and testing plans with clear detail about what was changed and why.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-1771-ical-subscribe-meetings-dashboard

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

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/modules/meetings/meetings-dashboard/meetings-dashboard.component.html`:
- Around line 225-245: The calendar branch in meetings-dashboard.component.html
(inside the isCalendarView() block that renders <lfx-fullcalendar> and the
loading spinner) doesn't trigger pagination, so calendar view only shows page 1
while list view calls loadMore(); update the calendar branch to invoke the same
pagination fallback used by list view: when (timeFilter() === 'upcoming' &&
meetingsLoading()) || (timeFilter() === 'past' && pastMeetingsLoading()) show
the spinner AND, when calendarEvents() is rendered, ensure you call or wire the
existing loadMore() mechanism (or expose the component method that triggers
loading additional pages) so additional pages are fetched as the calendar needs
them (use the same loadMore() helper used elsewhere to keep behavior consistent
with list mode).

In
`@apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts`:
- Around line 252-253: The feed URL should encode the project ID to avoid
breaking links when it contains reserved characters; update the template that
builds feedUrl (the const feedUrl using projectCtx.uid in
meetings-dashboard.component.ts) to use an encoded ID (e.g., replace
projectCtx.uid with encodeURIComponent(projectCtx.uid)) so the path segment is
safely escaped while leaving the rest of the URL and the name assignment
unchanged.

In `@apps/lfx-one/src/server/controllers/project.controller.ts`:
- Around line 881-888: getProjectCalendar currently aggregates meetings via
fetchAllMeetingPages / meetingService.getMeetings but doesn't filter out
non-public items; after collecting upcoming and past (the variables upcoming and
past) combine them and filter to only include meetings where meeting.visibility
=== MeetingVisibility.PUBLIC and not meeting.restricted before rendering the
ICS. Apply the same change in getCommitteeCalendar: after fetching its
upcoming/past pages, replace the raw concatenation with a filtered list (use
MeetingVisibility.PUBLIC and the restricted flag) so private or restricted
meetings are excluded from the public calendar.
🪄 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: bdf6de84-cdfe-4879-9745-fca34e991903

📥 Commits

Reviewing files that changed from the base of the PR and between 4de59ce and 6f538df.

📒 Files selected for processing (10)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts
  • apps/lfx-one/src/app/modules/committees/components/ical-subscribe-dialog/ical-subscribe-dialog.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/fullcalendar/fullcalendar.component.scss
  • apps/lfx-one/src/app/shared/components/fullcalendar/fullcalendar.component.ts
  • apps/lfx-one/src/server/controllers/project.controller.ts
  • apps/lfx-one/src/server/routes/public-projects.route.ts
  • apps/lfx-one/src/server/server.ts
  • packages/shared/src/interfaces/committee.interface.ts

Comment thread apps/lfx-one/src/server/controllers/project.controller.ts
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

Adds end-to-end iCal subscription for project/foundation meetings and introduces a calendar view on the Meetings dashboard, reusing the existing iCal Subscribe dialog and FullCalendar wrapper.

Changes:

  • Frontend: Adds a List/Calendar toggle and Subscribe action to the Meetings dashboard; maps filtered meetings into FullCalendar events and handles event-click navigation (with cancelled occurrences intended to be inert).
  • Backend: Adds a public /public/api/projects/:id/calendar.ics ICS feed endpoint (route + controller method) mirroring the existing committee calendar feed pattern.
  • Shared/UI: Generalizes the iCal Subscribe dialog data (committeeNamename) and adjusts FullCalendar wrapper styling/behavior (scoped timegrid overrides + fixed scrollTime).

Reviewed changes

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

Show a summary per file
File Description
packages/shared/src/interfaces/committee.interface.ts Renames iCal subscribe dialog data field to be non-committee-specific (name).
apps/lfx-one/src/server/server.ts Registers new public projects router under /public/api/projects.
apps/lfx-one/src/server/routes/public-projects.route.ts Adds public route GET /:id/calendar.ics for project/foundation calendar feeds.
apps/lfx-one/src/server/controllers/project.controller.ts Implements getProjectCalendar() ICS feed generation via MeetingService + ICS helpers.
apps/lfx-one/src/app/shared/components/fullcalendar/fullcalendar.component.ts Sets week view scrollTime to a fixed 06:00:00.
apps/lfx-one/src/app/shared/components/fullcalendar/fullcalendar.component.scss Scopes timegrid event background overrides to .meeting-event only.
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.ts Adds viewMode, calendar event projection, calendar click handling, and Subscribe modal wiring.
apps/lfx-one/src/app/modules/meetings/meetings-dashboard/meetings-dashboard.component.html Adds List/Calendar toggle UI, Subscribe button (foundation/project), and calendar view rendering + legend.
apps/lfx-one/src/app/modules/committees/components/ical-subscribe-dialog/ical-subscribe-dialog.component.ts Updates dialog to use generalized name for Outlook deep-links.
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts Makes cancelled calendar occurrences inert (click short-circuit + attempts cursor change).

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

Comment thread apps/lfx-one/src/server/controllers/project.controller.ts Outdated
Address review comments from @coderabbitai and @copilot-pull-request-reviewer:

- project.controller.ts, committee.controller.ts: filter PRIVATE / restricted
  meetings out of the public iCal feeds. The public endpoints previously
  returned ALL meetings matching the project_uid / committee_uid tag — anyone
  with the UID could subscribe and see private meeting titles + times.
  Now mirrors PublicMeetingController.getMeeting's visibility guard:
  `visibility === MeetingVisibility.PUBLIC && !restricted`
  (per @coderabbitai — security blocker)
- meetings-dashboard.component.ts: switch the calendar event source from the
  list-view's paginated `filteredMeetings()` to the dashboard's non-paginated
  raw signals (rawUserMeetings + rawUserPastMeetings for the Me lens;
  rawFpUpcomingMeetings + rawFpPastMeetings for foundation/project). The
  calendar no longer silently misses meetings beyond the first scroll page
  (per @coderabbitai — major)
- meetings-dashboard.component.ts: wrap projectCtx.uid with encodeURIComponent
  in the feed URL so reserved characters in IDs don't break links
  (per @coderabbitai — minor)
- project.controller.ts: fix JSDoc route comment — was `GET /projects/:id/...`,
  now matches the actual public path `GET /public/api/projects/:id/calendar.ics`
  (per @copilot-pull-request-reviewer)
- fullcalendar.component.scss: add explicit `.fc-event.cursor-default {
  cursor: default !important; }` override so cancelled occurrences (and the
  vote/survey markers on the committee tab) actually drop the pointer
  cursor — the wrapper's `.fc .fc-event { cursor: pointer }` rule has
  higher specificity than Tailwind's `.cursor-default` utility, so
  classNames: ['cursor-default'] alone wouldn't win. Also disable the
  hover-lift on inert events (per @copilot-pull-request-reviewer)
- meetings-dashboard.component.html, committee-meetings.component.html:
  legend dot for "Meeting (default)" changed from bg-blue-600 → bg-blue-500
  so it matches MEETING_TYPE_COLORS.default (#3b82f6 — Tailwind blue-500)
  (per @copilot-pull-request-reviewer)

Resolves 7 review threads.

Signed-off-by: Manish Dixit <mdixit@linuxfoundation.org>
@manishdixitlfx
Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: bfd0993

Security fix (data leakage)

  • project.controller.ts, committee.controller.ts: filter visibility === PUBLIC && !restricted before generating the iCal feed. The public endpoints previously exposed PRIVATE meeting titles + times to anyone holding the project/committee UID. Mirrors PublicMeetingController.getMeeting's guard. (per @coderabbitai)

Calendar correctness

  • meetings-dashboard.component.ts: switched calendar event source from paginated filteredMeetings() to the non-paginated raw signals (rawUserMeetings/rawUserPastMeetings for Me lens; rawFpUpcomingMeetings/rawFpPastMeetings for foundation/project). Calendar now reflects the full set regardless of how far the user scrolled the list. No extra API calls — these signals are already fetched for the stat cards. (per @coderabbitai)
  • meetings-dashboard.component.ts: encodeURIComponent(projectCtx.uid) in the feed URL. (per @coderabbitai)

Polish

  • project.controller.ts: JSDoc route corrected to GET /public/api/projects/:id/calendar.ics. (per @copilot)
  • fullcalendar.component.scss: explicit .fc-event.cursor-default { cursor: default !important; } + disable hover-lift on inert events, so cancelled occurrences (and vote/survey markers on the committee tab) actually drop the pointer cursor. (per @copilot)
  • meetings-dashboard.component.html, committee-meetings.component.html: legend dot for "Meeting (default)" changed from bg-blue-600bg-blue-500 to match MEETING_TYPE_COLORS.default (#3b82f6). (per @copilot)

Threads Resolved

7 of 7 unresolved threads addressed in this iteration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

@manishdixitlfx: Thank you for the thorough summary — all the addressed items look well-considered. A few highlights worth calling out:

  • Security fix — adding the visibility === PUBLIC && !restricted guard to both project.controller.ts and committee.controller.ts is exactly the right approach and correctly mirrors the existing guard in PublicMeetingController.getMeeting. This closes the data-leakage risk on both public calendar endpoints.
  • Calendar source switch — using the non-paginated raw signals (rawFpUpcomingMeetings/rawFpPastMeetings, etc.) ensures the calendar view is always complete, regardless of list scroll position, without incurring additional API calls. Clean solution.
  • encodeURIComponent(projectCtx.uid) — important defensive fix for any UIDs that contain characters unsafe in a URL.
  • Cursor & hover polish — the explicit .fc-event.cursor-default { cursor: default !important; } rule combined with disabling the hover-lift is a nice UX detail for cancelled occurrences.
  • Legend dot color (bg-blue-600bg-blue-500) — good catch keeping it pixel-perfect with MEETING_TYPE_COLORS.default.

I'll kick off a fresh review against the updated commit.

(◕‿◕✿) 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</div>
</div>

<!-- View toggle + Subscribe row — sits between the filter bar and the meeting cards. -->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Section-header comment — remove it.

Problem & Why: Comments that label WHAT a block does are not allowed. The data-testid="meetings-view-actions", role="group", and the button labels make the section self-documenting. Section-header comments add noise and drift out of date as the markup changes.

Suggested fix: Delete the comment line entirely.


// View mode: list (default) or calendar. Calendar renders meetings as
// FullCalendar events; the existing filter pipeline (lens/foundation/project/
// search/meetingType/timeFilter) applies to both views.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: The rule is one short line when the WHY is non-obvious; this 3-line block explains WHAT the signals do, not WHY a non-obvious constraint exists. signal<ViewMode>('list') is self-documenting. Multi-line comment blocks are explicitly forbidden.

Suggested fix: Delete the comment block entirely. If a reminder about filter applicability is needed, a single line on calendarEvents suffices.

// silently miss meetings the user hasn't scrolled to yet.
// Search and meeting-type filters are applied; foundation/project and
// pendingRsvp filters intentionally don't narrow the calendar in this
// iteration (FP raw signals are already scoped to the active context).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: This 8-line block inside the constructor violates the one-line-max rule. The relevant WHY (non-paginated source to avoid calendar gaps) is valuable but must be compressed to a single line.

Suggested fix: Replace the entire block with one line above the assignment:

// Uses non-paginated raw signals to avoid calendar silently missing meetings the user hasn't scrolled to yet.
this.calendarEvents = this.initCalendarEvents();

// Search and meeting-type filters are applied; foundation/project and
// pendingRsvp filters intentionally don't narrow the calendar in this
// iteration (FP raw signals are already scoped to the active context).
this.calendarEvents = computed(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line computed() must use a private init function.

Problem & Why: This component initializes every other complex signal via this.xxx = this.initializeXxx() (see initializeCommittees, initializeFilteredCommittees, initializeVotingStatusOptions, etc.). An inline multi-line computed() assigned directly in the constructor breaks that pattern, makes the constructor harder to read, and puts logic where it can't be easily navigated to. The signal declaration already exists at the class level (public calendarEvents: Signal<EventInput[]>) — it just needs to delegate to a private init function.

Suggested fix:

// In constructor:
this.calendarEvents = this.initCalendarEvents();

// New private method at the bottom of the class:
private initCalendarEvents(): Signal<EventInput[]> {
  return computed(() => {
    const lens = this.activeLens();
    const meetings: (Meeting | PastMeeting)[] =
      lens === 'me' ? [...this.rawUserMeetings(), ...this.rawUserPastMeetings()] : [...this.rawFpUpcomingMeetings(), ...this.rawFpPastMeetings()];
    const filtered = this.filterBySearchAndType(meetings, this.debouncedSearchQuery(), this.meetingTypeFilter());
    return filtered.flatMap((m) => this.meetingToEvents(m));
  });
}

}
}

/**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line JSDoc block — one line max.

Problem & Why: The rule forbids multi-line JSDoc and multi-line comment blocks everywhere in the codebase. This 9-line block far exceeds the one-line maximum. The JIRA ticket references (LFXV2-1772, LFXV2-1770) belong in the PR description, not in source code.

Suggested fix: Replace with a single-line JSDoc:

/** Opens iCal Subscribe modal — foundation/project lenses only; me/org lenses tracked separately. */
public onSubscribe(): void {

}

/**
* Convert one Meeting (or PastMeeting) into FullCalendar EventInput entries.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line JSDoc block — one line max.

Problem & Why: Four-line JSDoc on a private helper method violates the one-line-max rule. The method signature meetingToEvents(meeting: Meeting | PastMeeting): EventInput[] already communicates inputs and outputs.

Suggested fix:

/** Expands one Meeting/PastMeeting into EventInput entries; recurring meetings produce one entry per occurrence. */
private meetingToEvents(meeting: Meeting | PastMeeting): EventInput[] {

const allMeetings = [...upcoming, ...past];
// Public endpoint — filter out PRIVATE / restricted meetings so the feed
// never exposes private metadata to anyone holding the committee UID.
// Mirrors PublicMeetingController.getMeeting's visibility guard.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Three-line inline comment. The rule is one short line when the WHY is non-obvious. The security rationale is worth keeping but must be a single line.

Suggested fix:

// Filter PRIVATE/restricted meetings from the public feed (mirrors PublicMeetingController visibility guard).
const allMeetings = [...upcoming, ...past].filter((m) => m.visibility === MeetingVisibility.PUBLIC && !m.restricted);

*/
export class ProjectController {
private projectService: ProjectService = new ProjectService();
// Injected here (rather than on ProjectService) to keep the calendar endpoint's
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Three-line block explaining the injection decision. Collapse to one line.

Suggested fix:

// Injected here (not on ProjectService) to avoid circular dependency — mirrors CommitteeController.
private meetingService: MeetingService = new MeetingService();

}
}

// ── Calendar ICS Endpoint ────────────────────────────────────────────────
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Section-header comment — remove it.

Problem & Why: // ── Calendar ICS Endpoint ── is a section-header divider comment explaining WHAT. These are not allowed. The method JSDoc immediately below already describes the endpoint.

Suggested fix: Delete this line entirely.


// ── Calendar ICS Endpoint ────────────────────────────────────────────────

/**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line JSDoc block — one line max.

Problem & Why: Seven-line JSDoc on a public method violates the one-line-max rule. The route path is already documented in the route file and public-projects.route.ts. Rationale about the data model and circular dependency belongs in the PR description, not source code.

Suggested fix:

/** GET /public/api/projects/:id/calendar.ics — PUBLIC non-restricted meetings; serves both foundation and project lenses. */
public async getProjectCalendar(...

textColor: '#ffffff',
display: 'block',
// cursor-default on cancelled occurrences removes the pointer affordance;
// onCalendarEventClick also short-circuits when extendedProps.cancelled is true.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Two-line comment explaining the classNames behaviour. One line max.

Suggested fix:

// cursor-default for cancelled occurrences; onCalendarEventClick also guards via extendedProps.cancelled.
classNames: isCancelled ? ['cursor-default'] : [],

// Generic events keep their inline backgroundColor (set via EventInput.backgroundColor),
// so meeting-type colors stay visible in Week view. The original blanket override
// forced bg-gray-100/bg-blue-50 on ALL timegrid events with !important, which combined
// with inline textColor:'#fff' rendered events as invisible white-on-near-white.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Five-line comment explaining why only meeting-event classed entries are dimmed. The WHY is non-obvious (invisible text bug) and worth a comment, but the rule caps it at one line.

Suggested fix:

// Scope dimming to .meeting-event only — generic events use inline backgroundColor; !important override would hide their text.
.fc-timegrid-event.meeting-event {

// the pointer affordance and the hover lift. Higher specificity than
// the .fc-event selector above, so Tailwind's .cursor-default class
// alone wouldn't win — the explicit `cursor: default !important`
// guarantees the override regardless of source order.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Five-line comment explaining the CSS specificity override for cursor. The WHY (Tailwind .cursor-default alone loses to .fc-event specificity) is non-obvious and worth one line.

Suggested fix:

// Explicit !important needed — .fc-event specificity beats Tailwind's .cursor-default alone.
&.cursor-default {

nowIndicator: true,
scrollTime: new Date().getHours() + ':00:00',
// Week view scrolls to 6am on open — earlier hours rarely have meetings
// and scrolling to "now" overshoots past the morning when checking after lunch.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Two-line comment explaining the scrollTime change. Must be one line.

Suggested fix:

// Scroll to 6am — "now" overshoots when checking after lunch; most meetings are in the morning.
scrollTime: '06:00:00',

try {
// When called from the public route there is no OIDC session, so use an
// M2M token. When called from the authenticated route the user's bearer
// token is already on req and no replacement is needed.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Three-line inline comment for the M2M token logic. Must be compressed to one line.

Suggested fix:

// Public route has no session — obtain M2M token so meeting service calls succeed.
if (!req.bearerToken) {


const query = { tags: `project_uid:${id}` };

// Paginate both upcoming and past meetings — first page only would silently
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Two-line comment on pagination rationale. Collapse to one line.

Suggested fix:

// Fetch all pages — first-page-only would silently drop meetings beyond the default page size.
const [upcoming, past] = await Promise.all([

fetchAllMeetingPages((token) => this.meetingService.getMeetings(req, token ? { ...query, page_token: token } : query, 'v1_past_meeting', false)),
]);

// Public endpoint — filter out PRIVATE / restricted meetings so the feed
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Multi-line comment block — one line max.

Problem & Why: Three-line comment mirroring the same block in committee.controller.ts. Both must be one line.

Suggested fix:

// Filter PRIVATE/restricted meetings from the public feed (mirrors PublicMeetingController visibility guard).
const allMeetings = [...upcoming, ...past].filter((m) => m.visibility === MeetingVisibility.PUBLIC && !m.restricted);

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