Skip to content

feat(votes): add permission check on committee Create Vote CTA click#997

Open
MRashad26 wants to merge 13 commits into
mainfrom
feat/LFXV2-2252-committee-vote-permission-check
Open

feat(votes): add permission check on committee Create Vote CTA click#997
MRashad26 wants to merge 13 commits into
mainfrom
feat/LFXV2-2252-committee-vote-permission-check

Conversation

@MRashad26

@MRashad26 MRashad26 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stale permission guard on Create Vote CTA: The committee votes tab shows the "Create Vote" button based on canEdit() — derived from committee.writer at page-load time. If the member's role is downgraded from Manager to Member after the page loads, the stale signal still shows the button. Clicking it without this fix would navigate directly to /votes/create and rely solely on the writerGuard redirect, with no immediate UI feedback.
  • Click handler with fresh permission check: Both Create Vote buttons (table-actions slot and empty-state) now call onCreateVote(), which fetches fresh committee permissions via fetchCommittee() before navigating. On denial, redirects to the lens-appropriate overview (/foundation/overview or /project/overview) with _notice=votes so AppComponent.initAccessDeniedToast() shows the contextual "Access Denied" toast — consistent with the writerGuard denial flow from feat(meetings): add access-denied toast and fix meeting coordinator permissions #992.
  • Edit Vote navigation: The Edit button in votes-table now passes committee_uid and project query params so the edit route has the same context as create — consistent with fix(meetings): pass project+committee_uid queryParams on meeting edit navigation #1025 (meetings) and feat(surveys): enforce fresh committee writer checks for create/edit flows #1000 (surveys).
  • writerGuard extended for votes: writer.guard.ts now accepts committee.writer as a passing condition for writeFeature: 'votes' (and 'surveys'), matching the existing 'meetings' fast-path. A committee Manager who is not a project writer can now reach /votes/create without a double-redirect from the client-side permission check.
  • The writerGuard on /votes/create remains as the final safety net for direct URL access.

Changed files

File Change
committee-votes.component.ts Inject CommitteeService, LensService + Router; add protected onCreateVote() with fresh fetchCommittee() permission check and lens-aware deny redirect; add editVoteQueryParams aliased from createVoteQueryParams
committee-votes.component.html Replace [routerLink] + [queryParams] on both Create Vote buttons with (click)="onCreateVote()"; pass [editQueryParams] to lfx-votes-table
votes-table.component.ts Add editQueryParams input (default {})
votes-table.component.html Wire [routerLink], [queryParams], and (onClick)=stopPropagation directly on the Edit <lfx-button> (no <a> wrapper)
writer.guard.ts Extend committee-writer acceptance to writeFeature: 'votes' and 'surveys' (was 'meetings' only)

References

Test plan

  • Log in as a committee Manager — Create Vote button is visible and clicking it navigates to /votes/create with committee_uid and project query params
  • Downgrade the member to Member role without refreshing the page — Create Vote button remains visible (stale canEdit()) but clicking it redirects to the lens-appropriate overview with an "Access Denied" toast
  • Foundation-lens user denied on Create Vote click redirects to /foundation/overview (not /project/overview)
  • Committee Manager who is not a project writer navigates directly to /votes/create?committee_uid=...writerGuard now accepts committee.writer and allows access
  • As a user with no committee write access navigating directly to /votes/create?committee_uid=...writerGuard blocks and shows the toast
  • Clicking Edit on a draft vote navigates to /votes/<uid>/edit?committee_uid=...&project=<slug> and does not also trigger the row-results drawer

Replace routerLink on both Create Vote buttons (table-actions slot and
empty-state) with an onCreateVote() click handler that fetches fresh
committee permissions before navigating. If the member's role was
downgraded from Manager to Member since the page loaded, the stale
canEdit() signal would still show the button; the click handler catches
this and redirects to /project/overview?_notice=votes so AppComponent
shows the "Access Denied" toast — consistent with the writerGuard
denial flow used for meetings.

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
@MRashad26 MRashad26 requested a review from a team as a code owner June 20, 2026 09:29
Copilot AI review requested due to automatic review settings June 20, 2026 09:29
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Committee vote creation now uses a click handler that rechecks committee write access before routing, and the votes table edit action now forwards query parameters through a new input.

Changes

Vote navigation updates

Layer / File(s) Summary
Edit query params support
apps/lfx-one/src/app/modules/votes/components/votes-table/votes-table.component.ts, apps/lfx-one/src/app/modules/votes/components/votes-table/votes-table.component.html
Adds an editQueryParams input to VotesTableComponent and passes it to the disabled-vote edit control as query parameters.
Create Vote wiring
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts, apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.html
Injects CommitteeService and Router, wires both Create Vote buttons to onCreateVote(), and passes edit query params into lfx-votes-table.
Create Vote permission check
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts
Implements onCreateVote() to fetch the latest committee by UID, build overview redirect query parameters, and navigate either to create-vote or the overview redirect based on the refreshed writer flag.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: adding a permission check when clicking the committee Create Vote CTA.
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.
Description check ✅ Passed The description clearly matches the changes, covering the fresh create-vote permission check and the edit vote query-param updates.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/LFXV2-2252-committee-vote-permission-check

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

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

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

45-67: 💤 Low value

Consider disabling the button during the permission check to prevent duplicate requests.

While the implementation correctly fetches fresh permissions and handles denial/error cases, rapid clicks could trigger multiple concurrent API calls before navigation completes. Adding a brief loading state would improve UX.

♻️ Optional: Add loading guard
+ public creatingVote = signal<boolean>(false);
+
  public onCreateVote(): void {
+   if (this.creatingVote()) return;
+   this.creatingVote.set(true);
    const committee = this.committee();
    const denyParams: Record<string, string> = { _notice: 'votes' };
    if (committee.project_slug) denyParams['project'] = committee.project_slug;
-   const deny = () => void this.router.navigate(['/project/overview'], { queryParams: denyParams });
+   const deny = () => {
+     this.creatingVote.set(false);
+     void this.router.navigate(['/project/overview'], { queryParams: denyParams });
+   };

    this.committeeService
      .getCommittee(committee.uid)
      .pipe(take(1))
      .subscribe({
        next: (fresh) => {
          if (fresh?.writer !== true) {
            deny();
            return;
          }
          void this.router.navigate(['/votes', 'create'], { queryParams: this.createVoteQueryParams() });
        },
        error: () => deny(),
      });
  }

Then in template: [disabled]="creatingVote()"

🤖 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-votes/committee-votes.component.ts`
around lines 45 - 67, To prevent duplicate API requests from rapid button clicks
during the permission check in the onCreateVote method, add a loading state
signal (named something like creatingVote) initialized to false. Set this signal
to true before the getCommittee API call starts subscribing, then set it back to
false in both the next and error callbacks of the subscribe handler to ensure it
resets regardless of outcome. Finally, update the template to disable the button
by binding [disabled]="creatingVote()" to the create vote button element.
🤖 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.

Nitpick comments:
In
`@apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts`:
- Around line 45-67: To prevent duplicate API requests from rapid button clicks
during the permission check in the onCreateVote method, add a loading state
signal (named something like creatingVote) initialized to false. Set this signal
to true before the getCommittee API call starts subscribing, then set it back to
false in both the next and error callbacks of the subscribe handler to ensure it
resets regardless of outcome. Finally, update the template to disable the button
by binding [disabled]="creatingVote()" to the create vote button element.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 98e32e74-2730-4773-8207-41ce2e0eecd4

📥 Commits

Reviewing files that changed from the base of the PR and between c2fea04 and 4f240f6.

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

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown

🚀 Deployment Status

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

Deployment Details:

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

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

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

Adds a “fresh permission” check when a user clicks the Committee “Create Vote” CTA so that if their committee writer access is revoked after page load, they’re redirected through the same _notice=votes access-denied toast flow rather than relying solely on the route guard.

Changes:

  • Added onCreateVote() to re-fetch committee permissions via CommitteeService.getCommittee() before navigating to the create vote route.
  • Updated both “Create Vote” buttons to call (click)="onCreateVote()" instead of using [routerLink] + [queryParams].

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.ts Adds click handler that re-checks committee writer permission and redirects with _notice=votes on denial.
apps/lfx-one/src/app/modules/committees/components/committee-votes/committee-votes.component.html Routes both CTAs through the new click handler.

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

…rect

Inject LensService and derive the overview path from the active lens so
the access-denied redirect lands on /foundation/overview when the component
is rendered under the foundation lens (/foundation/groups/...) rather than
always redirecting to /project/overview — consistent with writerGuard's
lens-aware denial behavior (per @copilot-pull-request-reviewer).

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

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 1b0e903

Changes Made

  • committee-votes.component.ts: Injected LensService; deny redirect now derives overviewPath from activeLens()/foundation/overview for the foundation lens, /project/overview otherwise. Matches writerGuard's lens-aware behavior exactly (per @copilot-pull-request-reviewer)

No Change Needed

  • CodeRabbit nitpick (loading guard for double-click prevention): Marked "💤 Low value / Optional" by CodeRabbit — navigation completes after the first successful/failed check, making a double-fire harmless in practice. Skipped.

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

Follow-up

committee-meetings.component.ts (onScheduleMeeting) has the same hard-coded /project/overview — shipped in #992. Will fix in a follow-up.

Copilot AI commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

@MRashad26 I've opened a new pull request, #998, to work on those changes. Once the pull request is ready, I'll request review from you.

Add editQueryParams input to lfx-votes-table and wire [queryParams] onto
the edit routerLink so writerGuard receives project and committee_uid
when a committee manager navigates to edit a draft vote — mirrors the
meetings fix (PR #1025).

- votes-table: add editQueryParams input (defaults to {}; non-breaking
  for all existing usages outside committee context)
- votes-table: bind [queryParams]="editQueryParams()" on edit <a>
- committee-votes: add editVoteQueryParams computed signal via
  buildCommitteeCreateQueryParams; pass to [editQueryParams] binding

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

Copy link
Copy Markdown
Contributor Author

Edit permission handling added (commit `e4839f644`)

Follow-up to the create-vote permission check: added the same committee context to the Edit button navigation, consistent with the meetings fix (PR #1025).

Changes

  • votes-table: added editQueryParams input (Record<string, string>, defaults to {}) — non-breaking for all non-committee usages. Edit <a> now binds [queryParams]="editQueryParams()".
  • committee-votes: added editVoteQueryParams computed signal (same buildCommitteeCreateQueryParams as create); passed as [editQueryParams] to lfx-votes-table.

Effect

When a committee manager clicks Edit on a draft vote in the committee tab, the navigation to /votes/:id/edit now carries ?project=<slug>&committee_uid=<uid> — giving writerGuard the project slug it needs to resolve the project check, and committee_uid for any future guard extension.

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 4 out of 4 changed files in this pull request and generated 4 comments.

@luismoriguerra

Copy link
Copy Markdown
Contributor

Audit: PR #997 — feat(votes): add permission check on committee Create Vote CTA click

MRashad26 · feat/LFXV2-2252-committee-vote-permission-checkmain · OPEN · 4 files, +38/−6 · LFXV2-2252

Scope: Fresh committee.writer check on Create Vote click; lens-aware _notice=votes deny redirect; editQueryParams on votes-table Edit links.

Lanes run: yarn lint · yarn check-types · yarn format:check · yarn build · ui-reviewer · bugbot · secrets grep (6a security-review unavailable — manual pass)


1. Why this change

Create Vote stays visible via stale canEdit() (committee.writer at page load). After a mid-session demotion to Member, direct [routerLink] navigation had no immediate feedback — users only hit writerGuard on /votes/create.

2. How it was done

onCreateVote() re-fetches via CommitteeService.getCommittee() before navigating. Denial → lens-aware overview with _notice=votes + project slug → AppComponent.initAccessDeniedToast(). Follow-up commit adds editQueryParams so Edit links carry committee context. Matches the meetings-tab pattern from #992, with lens-aware deny (improvement over meetings).

3. Is it correct

Yes — merge-ready. Gates green in isolated worktree. _notice=votes is registered in ACCESS_DENIED_MESSAGES. writerGuard remains the direct-URL safety net.

Prior review: Lens hard-code → fixed (1b0e903). CodeRabbit loading guard → open (optional UX nit). Copilot duplicate-computed / error-path threads → open (non-blocking).

4. Missing / gaps

  1. Non-blockingeditVoteQueryParams duplicates createVoteQueryParams (committee-votes.component.ts:46,82-84). Bind [editQueryParams]="createVoteQueryParams()" instead.
  2. Non-blocking — No in-flight guard on Create (double-click → duplicate getCommittee calls). Optional creatingVote signal; same gap exists on meetings sibling.
  3. Non-blocking — Edit links still use direct routerLink without fresh committee check. In scope for this PR; writerGuard for votes checks project.writer only (not committee.writer).

5. Q&A this PR resolves

  • Why click handler instead of guard-only? — Immediate deny toast on stale CTA; guard still covers direct URL access.
  • Why lens-aware deny? — Foundation-lens users should land on /foundation/overview.
  • Why editQueryParams? — Edit route + writerGuard need ?project= and ?committee_uid= from the committee tab.

6. Business rules & ADRs

  • Business rule: Create Vote CTA must re-fetch committee.writer on click before navigation; denial uses _notice=votes toast contract shared with writerGuard.
  • ADR status: None added/updated — extends existing client-side _notice + writerGuard pattern.

7. Open questions & confirmations

  • (confirm) Should writerGuard accept committee_uid + committee.writer for writeFeature: 'votes' (today only 'meetings' gets that committee fallback)? Component-level check covers Create; Edit/direct URL still rely on project.writer only.

Verdict: 🟡 Minor comments
Merge readiness: safe-to-merge (optional DRY + loading guard polish)

@ahmedomosanya ahmedomosanya 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.

Audit result: 🟡 Minor comments

The referenced Jira looks too broad for this PR. LFXV2-2252 is the parent epic for Org Lens parity; the specific ticket appears to be LFXV2-2470 (“Allow committee writers to create/manage votes for their committee”). Please update the PR description to reference LFXV2-2470 directly, optionally keeping LFXV2-2252 as the parent epic.

A few non-blocking implementation notes:

  • onCreateVote() calls CommitteeService.getCommittee(), which mutates shared committee service state via tap(). Since this is only a permission probe, fetchCommittee() is the safer no-side-effect API.
  • The error path treats every getCommittee() failure as access denied. A transient 5xx/network failure would redirect with _notice=votes, which can mislead a legitimate writer.
  • The broader permission contract still needs confirmation: writerGuard accepts committee writer access only for meetings today; votes still require project writer access.

Local checks passed: yarn lint:check, yarn check-types, and yarn format:check.

Address Copilot review comments:

- committee-votes.component.ts: switch getCommittee() to fetchCommittee()
  in onCreateVote() to avoid mutating shared committee service state during
  a permission-only check (per T1)
- committee-votes.component.ts: alias editVoteQueryParams to
  createVoteQueryParams instead of duplicating the computed (per T3)
- votes-table.component.html: replace <a> wrapper with direct [routerLink]
  and [queryParams] inputs on <lfx-button> — ButtonComponent already
  supports these inputs; wrapper created invalid nested interactive elements
- votes-table.component.html: add (onClick)=stopPropagation to prevent
  row-select from firing alongside edit route navigation in selectionMode table
- votes-table.component.ts: remove now-unused RouterLink import

Resolves 3 review threads (T1, T3, implicit <a> wrapper issue).

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

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 7268ddc

Changes Made

  • committee-votes.component.ts: switched getCommittee()fetchCommittee() in onCreateVote() to avoid unintended shared service state mutation (T1)
  • committee-votes.component.ts: aliased editVoteQueryParams to createVoteQueryParams instead of duplicating the computed() (T3)
  • votes-table.component.html: replaced <a> wrapper with direct [routerLink], [queryParams], and (onClick)=$event.stopPropagation() on <lfx-button>ButtonComponent already supports these inputs; wrapper created nested interactive elements (invalid HTML)
  • votes-table.component.ts: removed now-unused RouterLink import
  • PR description: updated to document votes-table changes (T4)

Questions Answered

  • T2 (error handler deny-on-any-error): No code change — the deny-on-any-error pattern is intentional and consistent across all LFXV2-2252 components. Any failure means we cannot confirm writer status, so denying is the safe path. The writerGuard re-evaluates on arrival anyway.

Threads Resolved

3 of 4 unresolved threads resolved (T1, T3, T4). T2 left open for discussion — no code change taken.

@MRashad26 MRashad26 requested a review from ahmedomosanya June 26, 2026 16:45
ahmedomosanya
ahmedomosanya previously approved these changes Jun 26, 2026
Copilot AI review requested due to automatic review settings June 27, 2026 00:23
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 3a2f7f9

Changes Made

  • writer.guard.ts: Extended committee-writer acceptance to writeFeature: 'surveys' and 'votes' (was 'meetings' only). A committee Manager who is not a project writer will now be allowed by both the client-side fresh?.writer check and the server-side guard — no double-redirect. This mirrors the same fix in PR feat(surveys): enforce fresh committee writer checks for create/edit flows #1000 (surveys branch).

Threads Resolved

All previously unresolved threads were addressed in prior iterations. This commit resolves the design gap raised by @dealako on PR #1000, which applies equally to this branch.

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 5 out of 5 changed files in this pull request and generated 1 comment.

The three-line JSDoc said "Redirects to project overview" but the
implementation chooses between /foundation/overview and /project/overview
based on the active lens. Remove the comment — the method name is
self-explanatory and the comment was misleading (per @copilot).

LFXV2-2252

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

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commit: 5215a48

Changes Made

  • committee-votes.component.ts: Removed stale three-line JSDoc from onCreateVote() — the comment said "Redirects to project overview" but the implementation routes to either /foundation/overview or /project/overview based on the active lens. Method name is self-explanatory. (per @copilot)

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

LFXV2-2252

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

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 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread apps/lfx-one/src/app/shared/guards/writer.guard.ts
@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Changes Made

  • writer.guard.ts: Extracted supportsCommitteeWriter = writeFeature != null && ['meetings', 'surveys', 'votes'].includes(writeFeature) — both check sites now reference the single flag instead of repeating the triple-OR chain. (per @copilot)

Threads Resolved

1 of 1 unresolved threads addressed in this iteration.

Replace the repeated writeFeature triple-OR chain with a single
supportsCommitteeWriter boolean so the allowlist is defined once and
both check sites stay in sync automatically (per @copilot).

LFXV2-2252

Signed-off-by: Rashad <mrashad@contractor.linuxfoundation.org>
…isfy member-ordering lint

LFXV2-2252

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

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 5 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

apps/lfx-one/src/app/modules/votes/components/votes-table/votes-table.component.html:172

  • The Edit action now stops event propagation to avoid triggering the table row selection handler, but the adjacent Delete action still allows the click to bubble. Since the table uses (onRowSelect) to open vote/results, clicking Delete can also trigger row selection/drawer logic in addition to opening the confirm dialog.
                      </lfx-button>
                      <lfx-button
                        label="Delete"
                        severity="danger"
                        size="small"

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.

Follow-up review — round 3 👋

@MRashad26 thanks for the steady, methodical turnaround on this one. Every item from the previous rounds was addressed with a clear commit reference and rationale, and the small refactors (the supportsCommitteeWriter extraction, the fetchCommittee() swap) genuinely improved the change. Reviewed at HEAD 862b0f2f.

👏 Nice work

  • Lens-aware deny redirectonCreateVote() now mirrors writerGuard's /foundation/overview vs /project/overview selection exactly. Good consistency with the guard's authoritative behavior.
  • fetchCommittee() over getCommittee() — correctly avoids the tap() side-effect that mutated the shared CommitteeService.committee signal for a permission-only check.
  • supportsCommitteeWriter flag — the allowlist (['meetings','surveys','votes']) is now defined once and reused at both check sites; no more drift risk.
  • End-to-end coherence verified — both /votes/create and /votes/:id/edit are wired with writerGuard + data.writeFeature: 'votes', and passing editQueryParams (project + committee_uid) is exactly what lets a committee writer clear the edit guard. The whole flow hangs together.
  • Fail-closed posture — strict !== true checks and error: () => deny() both default to denial. Correct security stance for a stale-CTA guard.

Revision tracking (prior feedback)

  • ✅ Hard-coded /project/overview → lens-aware redirect (1b0e9039)
  • getCommittee() side-effect → fetchCommittee() (7268ddcb)
  • editVoteQueryParams duplication → aliased to createVoteQueryParams (7268ddcb)
  • ✅ Redundant .pipe(take(1)) removed + unused take import dropped (97eb97b6)
  • onCreateVote publicprotected (97eb97b6)
  • ✅ Stale onCreateVote doc comment removed (5215a48a)
  • ✅ Duplicated committee-writer feature check → supportsCommitteeWriter flag (9c721b7f)
  • ✅ PR description updated to mention votes-table changes
  • ⚠️ Deny-on-any-error (Copilot) — author declined with sound rationale: fetchCommittee() is a no-retry GET, and writerGuard re-evaluates on arrival, so a false-deny on a transient error costs only a round-trip, never a lock. Accepted as a deliberate trade-off.
  • ⚪ CodeRabbit nitpick (loading guard to debounce rapid clicks) — still unaddressed; optional, see nit below.

Open items (all non-blocking)

🟡 Minor (2)

  1. votes-table.component.ts:48viewVoteResults() is public but only called from the template ((viewResults)/(viewVote)). Same convention onCreateVote now follows — making it protected resolves the visibility convention and removes the reason the last commit (862b0f2f) had to reorder it for member-ordering lint. (committee-votes.component.ts:48 — note the file is committee-votes, not votes-table.)
  2. Test coverage — no specs exist for committee-votes.component.ts, votes-table.component.ts, or writer.guard.ts. The new branching is worth pinning, especially writerGuard's supportsCommitteeWriter gate and the project === null → checkCommittee() salvage path, plus onCreateVote's allow/deny/error paths. Flagged minor since the repo has no pre-existing specs for these files, but it's the highest-value gap.

Nit (3)
3. votes-table.component.html:174 — the new Edit button correctly adds (onClick)="$event.stopPropagation()" to avoid double-firing onRowSelect, but sibling action buttons (Delete/View/View Results) don't. For Delete this means the results/vote drawer opens behind the confirm dialog. Pre-existing for those buttons and outside this PR's diff — agreeing with Copilot's low-confidence note — worth a follow-up for consistency, not a blocker here.
4. committee-votes.component.ts:57 vs writer.guard.ts:56 — the component derives the lens from lensService.activeLens() while the guard reads route.data['lens']. Equivalent in practice; a one-line comment noting the intentional mirroring would prevent future divergence.
5. committee-votes.component.ts:46editVoteQueryParams aliasing createVoteQueryParams is fine, but the distinct name implies they might differ; a short comment that edit intentionally reuses create params would clarify intent.

AI reconciliation

  • Copilot (06-29 16:46) — clean, "generated no new comments." ✅ Agree.
  • Copilot low-confidence (Delete stopPropagation) — valid; captured as nit #3 with the pre-existing caveat.
  • CodeRabbit (loading guard) — take-it-or-leave-it; concurrent clicks each spawn a self-completing fetchCommittee(), so no correctness issue, only a minor UX debounce. Author's call.

Security

No blocking findings. The client-side check is correctly a UX/stale-CTA guard layered atop the authoritative writerGuard + BFF enforcement; the allowlist extension is properly gated on committee_uid presence; fail-closed throughout. No injection, secrets, or sensitive-PII exposure (project slug + committee_uid in query params are non-PID routing identifiers, consistent with existing conventions).

Decision: ✅ Approved with minor comments

No blocking issues, no merge conflicts, no actionable security findings. The two minor items (protected visibility + test coverage) and three nits are safe to address in a quick follow-up or fast-follow. Note the branch is BEHIND main — a rebase/merge before merging is needed, but that's mechanical.

@MRashad26

Copy link
Copy Markdown
Contributor Author

Thanks @dealako — appreciate the end-to-end coherence check. The supportsCommitteeWriter flag landed on both #997 and #1000 simultaneously so both PRs stay in sync on the guard allowlist. The deny-on-network-error follow-up is tracked alongside the same item on the surveys PR.

…buttons

Add `creating = signal(false)` pending flag to CommitteeVotesComponent — mirrors
the same pattern from CommitteeSurveysComponent. Set before fetchCommittee() and
cleared in finalize() so both next-deny and error paths reset it.

Also fix both Create Vote buttons: (click) → (onClick) (ButtonComponent emits
(onClick), not a native click event) and bind [loading]="creating()" for visual
feedback and re-entry prevention.

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

Copy link
Copy Markdown
Contributor Author

Follow-up PR #1042 opened to bring onScheduleMeeting() (committee-meetings) to full parity with this pattern — same five improvements: fetchCommittee(), lens-aware redirect, creating guard, (onClick), protected visibility.

@MRashad26

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

All threads from previous review iterations are resolved.

Summary of changes on this branch

  • committee-votes.component.ts: added creating = signal(false) pending guard + finalize() to prevent double-click and show spinner during permission check
  • committee-votes.component.html: fixed (click)(onClick) on both Create Vote buttons; added [loading]="creating()" binding

Note: The latest commit (ff50e3990) that added the creating guard landed after your round-3 approval — GitHub dismissed the approval on push. Happy to have you re-approve when you get a chance, but the changes are minor and purely additive.

Thanks again, @dealako!

Copilot AI review requested due to automatic review settings June 29, 2026 21:18

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 5 out of 5 changed files in this pull request and generated no new comments.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants