Skip to content

fix(committees): align onScheduleMeeting with surveys/votes permission-check pattern#1042

Merged
dealako merged 4 commits into
mainfrom
fix/LFXV2-2252-committee-meetings-permission-alignment
Jun 29, 2026
Merged

fix(committees): align onScheduleMeeting with surveys/votes permission-check pattern#1042
dealako merged 4 commits into
mainfrom
fix/LFXV2-2252-committee-meetings-permission-alignment

Conversation

@MRashad26

Copy link
Copy Markdown
Contributor

Summary

Brings CommitteeMeetingsComponent.onScheduleMeeting() to full parity with the pattern established in the parallel PRs for surveys (#1000), votes (#997), and mailing-list (#1016).

Five changes — all identified during review of those sibling PRs:

  • fetchCommittee() over getCommittee() — avoids the tap() side-effect that mutates the shared CommitteeService.committee signal during a permission-only check; matches the surveys/votes approach
  • Lens-aware redirect — injects LensService and uses activeLens() to select /foundation/overview vs /project/overview; meetings was the only committee tab still using a hardcoded /project/overview
  • creating = signal(false) double-click guard — set before fetchCommittee(), cleared in finalize(), bound as [loading]="creating()" on the Schedule Meeting button; prevents rapid double-clicks queuing multiple in-flight permission checks
  • (onClick) over (click)ButtonComponent emits (onClick), not a native click event; the previous (click) binding was silently a no-op on the async check (the routerLink still navigated, bypassing the guard)
  • protected visibility on onScheduleMeeting() — template-only method; protected makes intent clear and matches the surveys/votes convention

Related PRs

Test plan

  • Schedule Meeting button navigates to /meetings/create for a committee writer
  • Access-denied toast appears (with correct overview path) when permission revoked mid-session
  • Button shows spinner and is disabled during the in-flight fetchCommittee() call — rapid double-clicks are blocked
  • Foundation-lens committee redirects to /foundation/overview on denial; project-lens redirects to /project/overview
  • yarn build passes (SSR safety)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

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: 9072c57c-5a83-4e19-baa3-84c17bd308ea

📥 Commits

Reviewing files that changed from the base of the PR and between 6fbd228 and 2086b18.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts

Walkthrough

The committee meetings schedule action now tracks in-flight state, uses the active lens to choose the denial route, and binds the button loading state in the template.

Changes

Schedule Meeting Loading and Routing

Layer / File(s) Summary
Component signal, LensService injection, and onScheduleMeeting rewrite
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts
Adds LensService import and injection, removes take, introduces public creating = signal(false), and rewrites onScheduleMeeting to toggle the signal, compute the deny navigation path from lensService.activeLens(), build query params with _notice=meetings plus optional project, and replace getCommittee(...).pipe(take(1)) with fetchCommittee(...).pipe(finalize(...), takeUntilDestroyed(...)).
Button loading binding and onClick event
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
Adds [loading]="creating()" to the Schedule Meeting button and switches the event binding from (click) to (onClick).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

deploy-preview

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: aligning committee meeting scheduling with the surveys/votes permission-check pattern.
Description check ✅ Passed The description accurately summarizes the changes and is clearly related to the pull request.
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.
✨ 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 fix/LFXV2-2252-committee-meetings-permission-alignment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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

Copilot AI left a comment

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.

Pull request overview

Aligns the committee Meetings “Schedule Meeting” CTA behavior with the established stale-permission mitigation pattern used elsewhere (surveys/votes/mailing-lists), ensuring fresh permission validation at click time and consistent, lens-aware access-denied redirects.

Changes:

  • Switches the schedule flow to use CommitteeService.fetchCommittee() (no shared-signal mutation) for a fresh writer permission check before navigation.
  • Adds lens-aware access-denied redirect (/foundation/overview vs /project/overview) with _notice=meetings.
  • Introduces a creating signal and binds it to the CTA’s [loading] to prevent rapid repeat submissions; updates template to use <lfx-button>’s (onClick) output.

Reviewed changes

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

File Description
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts Adds lens-aware redirect + fresh permission check via fetchCommittee(), plus a creating loading guard for the CTA.
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html Wires the Schedule Meeting CTA to (onClick) and binds [loading]="creating()".

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts (1)

99-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an explicit type to the new signal.

creating is a public component field used by the template; annotate it explicitly, e.g. public creating: WritableSignal<boolean> = signal(false);. As per coding guidelines, **/*.{ts,tsx}: Use type annotations in TypeScript files.

Suggested change
-  public creating = signal(false);
+  public creating: WritableSignal<boolean> = signal(false);
🤖 Prompt for 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.

In
`@apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts`
at line 99, The public component field creating in
committee-meetings.component.ts is missing an explicit type annotation. Update
the class property declaration in CommitteeMeetingsComponent to use a typed
signal, such as WritableSignal<boolean>, while keeping the existing
signal(false) initialization so the template-facing field remains clear and
compliant with TypeScript typing guidelines.

Source: Coding guidelines

🤖 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/committees/components/committee-meetings/committee-meetings.component.ts`:
- Around line 180-193: The onScheduleMeeting handler can still start multiple
fetchCommittee calls if it is clicked again before the UI disables it. Add an
early return at the start of CommitteeMeetingsComponent.onScheduleMeeting that
checks creating() and exits immediately when a scheduling request is already in
progress, so the method stays idempotent even before this.creating is set.

---

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts`:
- Line 99: The public component field creating in
committee-meetings.component.ts is missing an explicit type annotation. Update
the class property declaration in CommitteeMeetingsComponent to use a typed
signal, such as WritableSignal<boolean>, while keeping the existing
signal(false) initialization so the template-facing field remains clear and
compliant with TypeScript typing guidelines.
🪄 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: 8b3efe89-f579-4f9b-95fc-59ab1c598bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 5578277 and f9b4413.

📒 Files selected for processing (2)
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.html
  • apps/lfx-one/src/app/modules/committees/components/committee-meetings/committee-meetings.component.ts

MRashad26 added a commit that referenced this pull request Jun 29, 2026
Add `if (this.creating()) return;` early exit so repeated fast clicks
cannot launch concurrent in-flight permission checks even if the loading
binding on the button were bypassed (per @coderabbitai on PR #1042).

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 6fbd228

Changes Made

  • committee-meetings.component.ts: added if (this.creating()) return; as the first statement in onScheduleMeeting() — component-level idempotency guard so repeated fast clicks cannot start concurrent in-flight permission checks even if the [loading] binding on the button were bypassed (per @coderabbitai)

Threads Resolved

Context for reviewers

This PR aligns onScheduleMeeting() with the permission-check pattern established for surveys (#1000) and votes (#997):

  • Replaced getCommittee().pipe(take(1)) with fetchCommittee().pipe(finalize(), takeUntilDestroyed()) — side-effect-free, proper cleanup
  • Lens-aware deny redirect based on LensService.activeLens()
  • Double-click guard via creating signal + [loading]="creating()" + early-return
  • Fixed (click) to (onClick) so the button's output actually fires
  • Visibility scoped to protected

@dealako ready for review when you're free!

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@MRashad26 Thanks for the update — that addresses my concern. The early if (this.creating()) return; guard in onScheduleMeeting() cleanly prevents concurrent permission checks, and paired with the loading state it makes the action safely idempotent.

🐇✅

dealako
dealako previously approved these changes Jun 29, 2026

@dealako dealako left a comment

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.

@MRashad26 — clean, focused patch. Five well-reasoned fixes that bring committee-meetings into full parity with the surveys/votes/mailing-list permission-check pattern. The (onClick) fix alone is worth the PR — the silent (click) no-op meant the guard was being bypassed entirely on the async path. The fetchCommittee switch, lens-aware redirect, and creating guard all read naturally and follow the established pattern correctly.

Summary

🟡 Minor: 2 issues
⚪ Nit: 1 issue


🟡 Minor — JSDoc removed on non-obvious method

onScheduleMeeting does something a future reader will find surprising: it makes a network call before navigating, purely to check fresh permissions. The original JSDoc explained why (stale writer role, writerGuard parity). That was removed in the refactor, but the WHY is still non-obvious.

Per project conventions (CLAUDE.md): "Only add a comment when the WHY is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise a reader."

A one-liner covers it:

/** Checks writer permission fresh before navigating — prevents a demoted member from reaching /meetings/create. */
protected onScheduleMeeting(): void {

🟡 Minor — Missing ai-assisted label

Per CLAUDE.md policy, PRs where AI helped generate or review code should carry the ai-assisted label. The PR body has the Generated with Claude Code footer, but the label is not applied. Easy fix:

gh pr edit 1042 --add-label ai-assisted

⚪ Nit — JIRA ticket absent from commit messages

.claude/rules/commit-workflow.md says to include the JIRA ticket in commit messages. Both commits omit LFXV2-2252. Branch name has it, commits don't. Worth noting for future hygiene.


AI Bot Reconciliation

CodeRabbit round 1 — actionable: missing if (this.creating()) return; early guard. Resolved by commit 6fbd228. ✅

CodeRabbit round 1 — nit: add WritableSignal<boolean> type annotation to creating. Disagree. component-organization.md explicitly shows public loading = signal(false) (no annotation) as the convention for simple WritableSignals. Other signals in this same component (meetingsLoading, pastMeetingsLoading, calendarLoading) all use the same untyped form. This suggestion contradicts local convention.

CodeRabbit round 2 — no actionable comments after the idempotency fix.

Copilot — no inline comments; clean summary.


Final Decision

Approved with minor comments

Logic is correct. Operator ordering (finalize before takeUntilDestroyed) is safe — finalize fires on destroy-triggered unsubscription, and calling .set(false) on a signal post-destroy is harmless in Angular. No security findings, no SSR concerns. The two minor items are addressable as a follow-up or squash note; they do not block merge.

@dealako dealako added the ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology. label Jun 29, 2026
…s/votes pattern

Five improvements to bring committee-meetings up to parity with the pattern
established in the surveys (#1000), votes (#997), and mailing-list (#1016) PRs:

1. fetchCommittee() over getCommittee() — avoids the tap() side-effect that
   mutates the shared CommitteeService.committee signal during a permission-only check
2. Lens-aware redirect — injects LensService and uses activeLens() to select
   /foundation/overview vs /project/overview, matching writerGuard behaviour
3. creating = signal(false) guard — set before fetchCommittee() and cleared in
   finalize(), bound as [loading]="creating()" on the Schedule Meeting button to
   block rapid double-clicks and show a spinner
4. (onClick) over (click) — ButtonComponent emits (onClick), not a native click event
5. protected visibility on onScheduleMeeting() — template-only method; protected
   makes intent clear and prevents unintended external calls

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Add `if (this.creating()) return;` early exit so repeated fast clicks
cannot launch concurrent in-flight permission checks even if the loading
binding on the button were bypassed (per @coderabbitai on PR #1042).

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26 MRashad26 force-pushed the fix/LFXV2-2252-committee-meetings-permission-alignment branch from 6fbd228 to dd6b9c3 Compare June 29, 2026 21:49
Add back the one-liner JSDoc explaining why this method makes a fresh
network call before navigating — the stale-permission pattern is not
obvious to a reader unfamiliar with it (per @dealako on PR #1042).

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
Copilot AI review requested due to automatic review settings June 29, 2026 21:56
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 3942e92

Changes Made

  • committee-meetings.component.ts: restored the one-liner JSDoc on onScheduleMeeting() explaining the fresh-permission pattern — agreed it's non-obvious without it (per @dealako)

Threads Resolved

1/1 @dealako thread resolved ✅

This completes the iteration. The branch is now aligned with the surveys/votes pattern across all five dimensions: fetchCommittee() (side-effect-free), finalize() + takeUntilDestroyed(), lens-aware deny redirect, creating guard + [loading] binding, and (onClick) event output.

Ready for review, @dealako!

Copilot AI left a comment

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.

Pull request overview

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

@dealako dealako left a comment

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.

Re-review after commit 3942e92d and main merge 2086b18f.

Revision tracking

Round 1 findings (my prior review):

  • 🟡 JSDoc removed on non-obvious method → ✅ resolved by commit 3942e92d — one-liner restored verbatim as suggested
  • 🟡 Missing ai-assisted label → ✅ resolved (applied in round 1)
  • ⚪ JIRA ticket absent from commit messages → still a nit, not a blocker

CodeRabbit actionable (round 1): if (this.creating()) return; → ✅ resolved by commit 6fbd228
Copilot (post-push): no new comments — clean

New commits

  • 3942e92d — single line addition restoring the intent JSDoc; correct and minimal
  • 2086b18f — merge from main; no code changes

Final Decision

Approved — all prior round findings addressed, security audit clean, Copilot clean. Ready to merge.

@dealako dealako merged commit 5818160 into main Jun 29, 2026
11 checks passed
@dealako dealako deleted the fix/LFXV2-2252-committee-meetings-permission-alignment branch June 29, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants