Skip to content

Add Evidence Management Dashboard#5057

Draft
rudransh-shrivastava wants to merge 48 commits into
OWASP:feature/bod-candidate-transparencyfrom
rudransh-shrivastava:feature/bod-claim-dash-cherry-claim-2
Draft

Add Evidence Management Dashboard#5057
rudransh-shrivastava wants to merge 48 commits into
OWASP:feature/bod-candidate-transparencyfrom
rudransh-shrivastava:feature/bod-claim-dash-cherry-claim-2

Conversation

@rudransh-shrivastava

Copy link
Copy Markdown
Collaborator

Proposed change

Resolves #5004

PR Depends on #5006 and needs to be rebased.

TODO: update description.

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran all required checks and tests locally; all warnings addressed and failures resolved
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added full claim and evidence management pages, including creating, viewing, editing, submitting, withdrawing, and removing items.
    • Added claim ordering controls for draft and approved claims.
    • Added download support for evidence files and clearer evidence indicators on claim lists.
  • Bug Fixes

    • Improved access restrictions so users only see their own candidate claims and evidence.
    • Added clearer not-found and empty-state messages across claim and evidence screens.
  • Tests

    • Expanded coverage for claim/evidence workflows, form validation, and page navigation.

Walkthrough

Adds backend claim/evidence GraphQL response changes, frontend query and mutation documents, reusable claim/evidence forms and actions, candidate dashboard and claim/evidence pages, and expanded unit and e2e coverage across those routes.

Changes

Candidate Claim Management Dashboard

Layer / File(s) Summary
Backend claim mutation payloads
backend/apps/owasp/api/internal/mutations/board_candidate_claim.py, backend/tests/unit/apps/owasp/api/internal/mutations/board_candidate_claim_test.py
Claim mutation results now include the affected claim object on success, and unit tests assert the returned claim for create, update, discard, submit, and withdraw.
Backend evidence flag annotation
backend/apps/owasp/api/internal/queries/board_candidate_claim.py, backend/apps/owasp/api/internal/nodes/board_candidate_claim.py, backend/tests/unit/apps/owasp/api/internal/queries/board_candidate_claim_test.py, backend/tests/unit/apps/owasp/api/internal/nodes/board_candidate_claim_test.py
The claims query annotates evidence presence, the node exposes has_evidence, and backend tests cover the annotation and resolver fallback.
Frontend GraphQL contracts
frontend/package.json, frontend/src/utils/helpers/apolloClient.ts, frontend/src/types/claim.ts, frontend/src/server/queries/*.ts, frontend/src/server/mutations/*.ts
Upload-capable Apollo transport, claim types, and claim/evidence query and mutation documents are added.
Shared test fixtures
e2e/helpers/*.ts, frontend/__tests__/mockData/*.ts
Shared Playwright auth cookies, route mocking, and claim/evidence test fixtures are added.
Claim action controls
frontend/src/components/DropdownActions.tsx, frontend/src/components/ClaimActions.tsx, frontend/__tests__/unit/components/ActionButton.test.tsx, frontend/__tests__/unit/components/DropdownActions.test.tsx, frontend/__tests__/unit/components/ClaimActions.test.tsx
Claim dropdown actions, confirmation flows, and cache updates are added, with unit tests covering dropdown behavior and status-driven mutations.
Claims dashboard
frontend/src/app/board/[year]/candidates/page.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/page.tsx, frontend/__tests__/unit/pages/BoardCandidatesPage.test.tsx, frontend/__tests__/unit/pages/CandidateClaimsPage.test.tsx, e2e/pages/BoardCandidateClaims.spec.ts
Candidate cards show a manage-claims link for owners, and the claims dashboard adds grouped sections, reordering, and persistence.
Claim details
frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/page.tsx, frontend/__tests__/unit/pages/ClaimDetailsPage.test.tsx, e2e/pages/BoardCandidateClaimDetails.spec.ts
The claim details route renders metadata, evidence lists, and add-evidence navigation, with access, not-found, and breadcrumb coverage.
Claim create and edit
frontend/src/components/ClaimForm.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/edit/page.tsx, frontend/__tests__/unit/pages/CreateClaimPage.test.tsx, frontend/__tests__/unit/pages/EditClaimPage.test.tsx, e2e/pages/BoardCandidateClaimCreate.spec.ts, e2e/pages/BoardCandidateClaimEdit.spec.ts
Claim create and edit pages share form validation, Apollo mutations, cache updates, and route/error handling.
Evidence create and edit
frontend/src/components/forms/shared/FormFileInput.tsx, frontend/src/components/forms/shared/formValidationUtils.ts, frontend/src/components/EvidenceForm.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/create/page.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/edit/page.tsx, frontend/__tests__/unit/pages/CreateEvidencePage.test.tsx, frontend/__tests__/unit/pages/EditEvidencePage.test.tsx, frontend/__tests__/unit/components/EvidenceForm.test.tsx, e2e/pages/BoardCandidateClaimEvidenceCreate.spec.ts, e2e/pages/BoardCandidateClaimEvidenceEdit.spec.ts
Evidence create and edit pages add file validation, form state handling, mutations, cache updates, and route/error handling.
Evidence details
frontend/src/components/EvidenceActions.tsx, frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/page.tsx, frontend/__tests__/unit/pages/EvidenceDetailsPage.test.tsx, frontend/__tests__/unit/components/EvidenceActions.test.tsx, e2e/pages/BoardCandidateClaimEvidenceDetails.spec.ts
The evidence details route renders metadata, download handling, and remove/edit actions, with unit and e2e coverage for access, file, and not-found states.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • arkid15r
  • kasya
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: adding a candidate claims/evidence management dashboard.
Description check ✅ Passed The description is directly related to the PR and references the linked issue and dependency.
Linked Issues check ✅ Passed The changes deliver candidate claim management pages, mutations, queries, and supporting UI/test work requested by #5004.
Out of Scope Changes check ✅ Passed The diff stays focused on claim/evidence management and related test/support code, with no clear unrelated feature work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.15951% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.46%. Comparing base (728d7e0) to head (bebd592).
⚠️ Report is 1 commits behind head on feature/bod-candidate-transparency.

Files with missing lines Patch % Lines
...src/components/forms/shared/formValidationUtils.ts 60.00% 2 Missing and 2 partials ⚠️
...tend/src/components/forms/shared/FormFileInput.tsx 71.42% 0 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                          Coverage Diff                           @@
##           feature/bod-candidate-transparency    #5057      +/-   ##
======================================================================
- Coverage                               98.47%   98.46%   -0.01%     
======================================================================
  Files                                     550      556       +6     
  Lines                                   17675    17995     +320     
  Branches                                 2479     2539      +60     
======================================================================
+ Hits                                    17405    17719     +314     
- Misses                                    166      168       +2     
- Partials                                  104      108       +4     
Flag Coverage Δ
backend 99.06% <100.00%> (+<0.01%) ⬆️
frontend 96.80% <98.07%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...sp/api/internal/mutations/board_candidate_claim.py 82.11% <100.00%> (+0.08%) ⬆️
.../owasp/api/internal/nodes/board_candidate_claim.py 85.71% <100.00%> (+4.46%) ⬆️
...wasp/api/internal/queries/board_candidate_claim.py 100.00% <100.00%> (ø)
frontend/src/components/ClaimActions.tsx 100.00% <100.00%> (ø)
frontend/src/components/ClaimForm.tsx 100.00% <100.00%> (ø)
frontend/src/components/DropdownActions.tsx 100.00% <100.00%> (ø)
frontend/src/components/EvidenceActions.tsx 100.00% <100.00%> (ø)
frontend/src/components/EvidenceForm.tsx 100.00% <100.00%> (ø)
...tend/src/components/forms/shared/FormFileInput.tsx 71.42% <71.42%> (ø)
...src/components/forms/shared/formValidationUtils.ts 90.00% <60.00%> (-10.00%) ⬇️

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b0c8840...bebd592. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

🤖 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
`@backend/tests/unit/apps/owasp/api/internal/queries/board_candidate_claim_test.py`:
- Around line 29-30: The board candidate claim tests currently only stub the
queryset chain and do not verify the optimization contract. In the
`board_candidate_claims` test cases, assert that `annotate()` is called with an
`evidence_exists` keyword argument (along with the existing queryset chaining)
so the tests fail if the annotation is removed and the code falls back to
per-claim `.exists()` queries. Use the existing `mock_qs` setup in
`test_board_candidate_claims` to check the `annotate` call directly.

In `@e2e/helpers/mockClaimAuth.ts`:
- Around line 17-18: `mockClaimAuth` is unconditionally parsing request bodies
as JSON, which crashes on multipart `UploadHttpLink` uploads before the
`operationName` check. Update the `page.route('**/graphql/', ...)` handler to
inspect `request.headers()` first, only call `request.postDataJSON()` when the
content type is JSON, and safely skip or specially handle multipart requests so
evidence upload tests do not fail.

In `@e2e/pages/BoardCandidateClaimCreate.spec.ts`:
- Around line 8-22: The mocked createBoardCandidateClaim payload in
BoardCandidateClaimCreate.spec should match the GraphQL result shape used by the
cache update path. Update the CreateBoardCandidateClaimResult mock so it uses
the same claim object contract as production and includes the list fields
expected by GetBoardCandidateClaimsDocument, such as order and hasEvidence,
instead of returning a simplified BoardCandidateClaimNode shape. Keep the
response aligned with the createBoardCandidateClaim and claim symbols so the E2E
test exercises the real cache-append behavior.

In `@e2e/pages/BoardCandidateClaimEdit.spec.ts`:
- Around line 13-24: The edit E2E mock is using the wrong GraphQL typename for
updateBoardCandidateClaim. Update the mock in BoardCandidateClaimEdit.spec.ts to
match the shared ClaimResult contract instead of UpdateBoardCandidateClaimResult
so it stays aligned with the generated mutation and Apollo cache behavior. Keep
the existing update payload shape, but change the __typename on the
updateBoardCandidateClaim result to the real mutation result type used by the
schema.

In `@frontend/__tests__/mockData/mockClaimData.ts`:
- Around line 72-79: Keep mockSingleClaim aligned with the updated claim fixture
shape by adding the missing hasEvidence field so it matches mockClaims and the
current claim model. Update the mock in mockClaimData to include the same fields
used by the claim detail flow, and keep the object structure consistent with the
claim-related test data to avoid fixture drift.

In `@frontend/__tests__/mockData/mockEvidenceData.ts`:
- Around line 1-57: The claim/evidence test fixtures are duplicated in
mockEvidenceData and mockClaimData with slightly different shapes, which will
drift over time. Refactor the shared fixtures by introducing a single base
claim/evidence object or small builder used by both mockEvidence, mockEvidences,
mockClaimForEvidence, and the related mockGet*Data exports, and only override
fields like id and __typename where needed so the two test data modules stay
consistent.

In `@frontend/package.json`:
- Line 22: The new apollo-upload-client dependency in package.json is compatible
with ESM, but the repo’s Node requirement is pinned to ^24.0.0, so make sure the
CI/CD workflow and local dev setup are explicitly using Node 24. Update the
relevant environment/version config used by the frontend build and test jobs,
and verify any Node version manager or setup scripts align with the engines.node
constraint to prevent build or runtime mismatches.

In `@frontend/src/app/board/`[year]/candidates/[login]/claims/create/page.tsx:
- Around line 48-56: Handle candidateQueryError before the access-denied check
in the create claim page so transient fetch failures do not look like
authorization failures. Update the create page component to mirror the edit
page’s error handling by adding an explicit render branch for
candidateQueryError before computing or using isCandidate, and only fall through
to AccessDeniedDisplay when the query succeeded but the user is not allowed.

In `@frontend/src/app/board/`[year]/candidates/[login]/claims/page.tsx:
- Around line 137-157: The Apollo cache update in the reorder mutation is
targeting the wrong query document, so the UI stays stale after a successful
reorder. In the update callback inside the claims page, change both
cache.readQuery and cache.writeQuery to use GetBoardCandidateAndClaimsDocument
instead of GetBoardCandidateClaimsDocument, keeping the same login/year
variables, so the cached result matches the query actually used by the page and
derived state refreshes correctly.

In `@frontend/src/components/ClaimForm.tsx`:
- Around line 87-92: The catch block in ClaimForm’s submit flow is swallowing
mixed GraphQL failures by only checking hasValidationErrors. Update the error
handling around extractGraphQLErrors() to also inspect unmappedErrors, and only
suppress the exception when every GraphQL error was mapped to a field. If any
unmappedErrors remain, still call setBackendErrors(validationErrors) for the
mapped fields but rethrow the original error so the page-level toast path can
handle the unmapped/server error.

In `@frontend/src/components/EvidenceActions.tsx`:
- Around line 66-82: The cache update in EvidenceActions is targeting the wrong
GraphQL document, so the destination claim page stays stale and refetches
unnecessarily. Update the Apollo update handler in EvidenceActions to write the
removed evidence into the cache entry used by the claim details page, namely
GetClaimAndEvidencesDocument, not GetBoardCandidateClaimEvidencesDocument. Make
sure the variables and response shape match the page query in page.tsx
(including the nested boardCandidateClaim structure) so the evidence list
updates correctly after the redirect.

In `@frontend/src/components/EvidenceForm.tsx`:
- Line 66: Rename the typoed handler in EvidenceForm from handleFilechange to
handleFileChange to match standard camelCase and keep naming consistent; update
the function declaration and any references in the component so the event
handler symbol remains aligned everywhere.
- Around line 179-187: The file picker filtering in the EvidenceForm component
is using extension values without the required leading dots, so the native
accept filter is ineffective. Update the accept value passed to FormFileInput in
EvidenceForm to transform EVIDENCE_ALLOWED_EXTENSIONS into dotted extensions
before joining them, while keeping the existing runtime validation and
handleFilechange behavior unchanged.

In `@frontend/src/components/forms/shared/formValidationUtils.ts`:
- Around line 39-44: The file extension check in formValidationUtils is treating
extension-less filenames as if the whole filename were the extension, which
produces a misleading message. Update the validation logic around the file.name
split/pop handling to explicitly detect when there is no dot in the name and
return a clearer no-extension error before comparing against allowedExtensions.
While touching this block, use a separate allowed string in the same validation
helper to avoid the nested template literal warning in the return message.

In `@frontend/src/types/claim.ts`:
- Around line 3-14: The Claim type is weakening the GraphQL contract by making
backend-provided fields optional; update the frontend type to stay aligned with
the generated operation type instead of hand-typing these fields. In Claim, make
createdAt, hasEvidence, order, and updatedAt required (or replace this alias
with the generated GraphQL type used by the claims dashboard and cache updates)
so Claim, ClaimStatusEnum, and related consumers remain in lockstep.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bce348ca-a335-464e-be8e-96702ea37bc7

📥 Commits

Reviewing files that changed from the base of the PR and between b0c8840 and bebd592.

⛔ Files ignored due to path filters (7)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • frontend/src/types/__generated__/boardQueries.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/claimMutations.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/claimQueries.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/evidenceMutations.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/evidenceQueries.generated.ts is excluded by !**/__generated__/**
  • frontend/src/types/__generated__/graphql.ts is excluded by !**/__generated__/**
📒 Files selected for processing (54)
  • backend/apps/owasp/api/internal/mutations/board_candidate_claim.py
  • backend/apps/owasp/api/internal/nodes/board_candidate_claim.py
  • backend/apps/owasp/api/internal/queries/board_candidate_claim.py
  • backend/tests/unit/apps/owasp/api/internal/mutations/board_candidate_claim_test.py
  • backend/tests/unit/apps/owasp/api/internal/nodes/board_candidate_claim_test.py
  • backend/tests/unit/apps/owasp/api/internal/queries/board_candidate_claim_test.py
  • e2e/helpers/mockAuthCookies.ts
  • e2e/helpers/mockClaimAuth.ts
  • e2e/helpers/mockDashboardCookies.ts
  • e2e/pages/BoardCandidateClaimCreate.spec.ts
  • e2e/pages/BoardCandidateClaimDetails.spec.ts
  • e2e/pages/BoardCandidateClaimEdit.spec.ts
  • e2e/pages/BoardCandidateClaimEvidenceCreate.spec.ts
  • e2e/pages/BoardCandidateClaimEvidenceDetails.spec.ts
  • e2e/pages/BoardCandidateClaimEvidenceEdit.spec.ts
  • e2e/pages/BoardCandidateClaims.spec.ts
  • frontend/__tests__/mockData/mockClaimData.ts
  • frontend/__tests__/mockData/mockEvidenceData.ts
  • frontend/__tests__/unit/components/ActionButton.test.tsx
  • frontend/__tests__/unit/components/ClaimActions.test.tsx
  • frontend/__tests__/unit/components/DropdownActions.test.tsx
  • frontend/__tests__/unit/components/EvidenceActions.test.tsx
  • frontend/__tests__/unit/components/EvidenceForm.test.tsx
  • frontend/__tests__/unit/pages/BoardCandidatesPage.test.tsx
  • frontend/__tests__/unit/pages/CandidateClaimsPage.test.tsx
  • frontend/__tests__/unit/pages/ClaimDetailsPage.test.tsx
  • frontend/__tests__/unit/pages/CreateClaimPage.test.tsx
  • frontend/__tests__/unit/pages/CreateEvidencePage.test.tsx
  • frontend/__tests__/unit/pages/EditClaimPage.test.tsx
  • frontend/__tests__/unit/pages/EditEvidencePage.test.tsx
  • frontend/__tests__/unit/pages/EvidenceDetailsPage.test.tsx
  • frontend/package.json
  • frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/edit/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/edit/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/create/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx
  • frontend/src/app/board/[year]/candidates/[login]/claims/page.tsx
  • frontend/src/app/board/[year]/candidates/page.tsx
  • frontend/src/components/ClaimActions.tsx
  • frontend/src/components/ClaimForm.tsx
  • frontend/src/components/DropdownActions.tsx
  • frontend/src/components/EvidenceActions.tsx
  • frontend/src/components/EvidenceForm.tsx
  • frontend/src/components/forms/shared/FormFileInput.tsx
  • frontend/src/components/forms/shared/formValidationUtils.ts
  • frontend/src/server/mutations/claimMutations.ts
  • frontend/src/server/mutations/evidenceMutations.ts
  • frontend/src/server/queries/boardQueries.ts
  • frontend/src/server/queries/claimQueries.ts
  • frontend/src/server/queries/evidenceQueries.ts
  • frontend/src/types/claim.ts
  • frontend/src/utils/helpers/apolloClient.ts

Comment on lines +29 to 30
mock_qs.annotate.return_value = mock_qs
mock_qs.order_by.return_value = claims

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.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Assert the evidence_exists annotation, not just the chain.

These tests now only stub annotate(), so they will still pass if board_candidate_claims stops annotating and the dashboard falls back to one .exists() query per claim. Please assert that annotate is called with an evidence_exists kwarg to lock in the optimization contract.

Also applies to: 52-53, 73-74

🤖 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
`@backend/tests/unit/apps/owasp/api/internal/queries/board_candidate_claim_test.py`
around lines 29 - 30, The board candidate claim tests currently only stub the
queryset chain and do not verify the optimization contract. In the
`board_candidate_claims` test cases, assert that `annotate()` is called with an
`evidence_exists` keyword argument (along with the existing queryset chaining)
so the tests fail if the annotation is removed and the code falls back to
per-claim `.exists()` queries. Use the existing `mock_qs` setup in
`test_board_candidate_claims` to check the `annotate` call directly.

Comment on lines +17 to +18
await page.route('**/graphql/', async (route, request) => {
const postData = request.postDataJSON()

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.

🎯 Functional Correctness | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\nUsages of mockClaimAuth:\n'
rg -n --type=ts --type=tsx '\bmockClaimAuth\s*\(' e2e frontend

printf '\nPotential multipart/upload GraphQL paths:\n'
rg -n --type=ts --type=tsx 'createUploadLink|multipart/form-data|FormData|Upload|postDataJSON\(' e2e frontend

Repository: OWASP/Nest

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Usages of mockClaimAuth ==="
rg -n '\bmockClaimAuth\s*\(' --type ts --type ts --glob "*test*" --glob "*.spec.ts" --glob "*e2e*" e2e frontend || true

echo "=== Potential multipart/upload GraphQL patterns ==="
rg -n 'createUploadLink|multipart/form-data|new FormData|Upload' --type ts e2e frontend || true

echo "=== Check mockClaimAuth definition ==="
cat -n e2e/helpers/mockClaimAuth.ts | head -40

Repository: OWASP/Nest

Length of output: 4932


Fix postDataJSON() crash on multipart file uploads in mockClaimAuth.

The mockClaimAuth helper unconditionally calls request.postDataJSON() on line 18. Since the frontend uses UploadHttpLink for evidence flows, requests involving files are sent as multipart/form-data. Playwright throws an error when calling postDataJSON() on non-JSON bodies, causing tests like BoardCandidateClaimEvidenceCreate.spec.ts to fail before the operationName filter runs.

Gate the JSON parsing behind a content-type check:

const headers = request.headers()
const contentType = headers['content-type'] || ''
let postData

if (contentType.includes('application/json')) {
  postData = request.postDataJSON()
} else {
  // For multipart uploads, parse body as text and extract operationName manually
  postData = { operationName: null } 
  // Alternative: skip operationName check for non-JSON or parse text body if needed
}

Update the route handler to conditionally access postData only when safe.

🤖 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 `@e2e/helpers/mockClaimAuth.ts` around lines 17 - 18, `mockClaimAuth` is
unconditionally parsing request bodies as JSON, which crashes on multipart
`UploadHttpLink` uploads before the `operationName` check. Update the
`page.route('**/graphql/', ...)` handler to inspect `request.headers()` first,
only call `request.postDataJSON()` when the content type is JSON, and safely
skip or specially handle multipart requests so evidence upload tests do not
fail.

Comment on lines +8 to +22
createBoardCandidateClaim: {
__typename: 'CreateBoardCandidateClaimResult',
ok: true,
message: 'Claim created successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
id: 'new-claim',
key: 'new-claim',
name: 'Test Claim',
description: 'Test description',
status: 'DRAFT',
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
},
},

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the mocked create payload match the GraphQL contract.

The page appends createBoardCandidateClaim.claim straight into GetBoardCandidateClaimsDocument, but this mock returns a different result typename and omits list fields like order/hasEvidence. That weakens the only E2E path covering the cache update because the mocked response shape no longer matches what production returns.

Suggested fix
   createBoardCandidateClaim: {
-    __typename: 'CreateBoardCandidateClaimResult',
+    __typename: 'ClaimResult',
     ok: true,
+    code: null,
     message: 'Claim created successfully!',
     claim: {
       __typename: 'BoardCandidateClaimNode',
       id: 'new-claim',
       key: 'new-claim',
       name: 'Test Claim',
       description: 'Test description',
+      hasEvidence: false,
+      order: 1,
       status: 'DRAFT',
       createdAt: '2025-01-15T10:00:00Z',
       updatedAt: '2025-01-15T10:00:00Z',
     },
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createBoardCandidateClaim: {
__typename: 'CreateBoardCandidateClaimResult',
ok: true,
message: 'Claim created successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
id: 'new-claim',
key: 'new-claim',
name: 'Test Claim',
description: 'Test description',
status: 'DRAFT',
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
},
},
createBoardCandidateClaim: {
__typename: 'ClaimResult',
ok: true,
code: null,
message: 'Claim created successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
id: 'new-claim',
key: 'new-claim',
name: 'Test Claim',
description: 'Test description',
hasEvidence: false,
order: 1,
status: 'DRAFT',
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
},
},
🤖 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 `@e2e/pages/BoardCandidateClaimCreate.spec.ts` around lines 8 - 22, The mocked
createBoardCandidateClaim payload in BoardCandidateClaimCreate.spec should match
the GraphQL result shape used by the cache update path. Update the
CreateBoardCandidateClaimResult mock so it uses the same claim object contract
as production and includes the list fields expected by
GetBoardCandidateClaimsDocument, such as order and hasEvidence, instead of
returning a simplified BoardCandidateClaimNode shape. Keep the response aligned
with the createBoardCandidateClaim and claim symbols so the E2E test exercises
the real cache-append behavior.

Comment on lines +13 to +24
updateBoardCandidateClaim: {
__typename: 'UpdateBoardCandidateClaimResult',
ok: true,
message: 'Claim updated successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
...mockSingleClaim,
name: 'Updated Leadership',
description: 'Updated description',
updatedAt: '2025-01-20T10:00:00Z',
},
},

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the real mutation result typename in the edit mock.

updateBoardCandidateClaim now returns the shared ClaimResult contract, not UpdateBoardCandidateClaimResult. Keeping the E2E mock aligned with the schema matters here because these pages rely on generated GraphQL documents and Apollo cache behavior.

Suggested fix
   updateBoardCandidateClaim: {
-    __typename: 'UpdateBoardCandidateClaimResult',
+    __typename: 'ClaimResult',
     ok: true,
+    code: null,
     message: 'Claim updated successfully!',
     claim: {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateBoardCandidateClaim: {
__typename: 'UpdateBoardCandidateClaimResult',
ok: true,
message: 'Claim updated successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
...mockSingleClaim,
name: 'Updated Leadership',
description: 'Updated description',
updatedAt: '2025-01-20T10:00:00Z',
},
},
updateBoardCandidateClaim: {
__typename: 'ClaimResult',
ok: true,
code: null,
message: 'Claim updated successfully!',
claim: {
__typename: 'BoardCandidateClaimNode',
...mockSingleClaim,
name: 'Updated Leadership',
description: 'Updated description',
updatedAt: '2025-01-20T10:00:00Z',
},
},
🤖 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 `@e2e/pages/BoardCandidateClaimEdit.spec.ts` around lines 13 - 24, The edit E2E
mock is using the wrong GraphQL typename for updateBoardCandidateClaim. Update
the mock in BoardCandidateClaimEdit.spec.ts to match the shared ClaimResult
contract instead of UpdateBoardCandidateClaimResult so it stays aligned with the
generated mutation and Apollo cache behavior. Keep the existing update payload
shape, but change the __typename on the updateBoardCandidateClaim result to the
real mutation result type used by the schema.

Comment on lines +72 to +79
export const mockSingleClaim = {
key: 'experience-leadership',
name: 'Leadership Experience',
description: 'Experience in leadership.',
status: 'DRAFT',
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
}

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep mockSingleClaim aligned with the new claim shape.

mockClaims includes hasEvidence, but mockSingleClaim drops it. That leaves the detail-page tests blind to the new field added in this PR and makes fixture drift easy to miss.

Suggested change
 export const mockSingleClaim = {
   key: 'experience-leadership',
   name: 'Leadership Experience',
   description: 'Experience in leadership.',
   status: 'DRAFT',
+  hasEvidence: true,
   createdAt: '2025-01-15T10:00:00Z',
   updatedAt: '2025-01-15T10:00:00Z',
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const mockSingleClaim = {
key: 'experience-leadership',
name: 'Leadership Experience',
description: 'Experience in leadership.',
status: 'DRAFT',
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
}
export const mockSingleClaim = {
key: 'experience-leadership',
name: 'Leadership Experience',
description: 'Experience in leadership.',
status: 'DRAFT',
hasEvidence: true,
createdAt: '2025-01-15T10:00:00Z',
updatedAt: '2025-01-15T10:00:00Z',
}
🤖 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 `@frontend/__tests__/mockData/mockClaimData.ts` around lines 72 - 79, Keep
mockSingleClaim aligned with the updated claim fixture shape by adding the
missing hasEvidence field so it matches mockClaims and the current claim model.
Update the mock in mockClaimData to include the same fields used by the claim
detail flow, and keep the object structure consistent with the claim-related
test data to avoid fixture drift.

Comment on lines +66 to +82
update(cache) {
const existing = cache.readQuery<GetBoardCandidateClaimEvidencesQuery>({
query: GetBoardCandidateClaimEvidencesDocument,
variables: { claimKey: claim.key, login, year: Number.parseInt(year) },
})
if (existing) {
cache.writeQuery({
query: GetBoardCandidateClaimEvidencesDocument,
variables: { claimKey: claim.key, login, year: Number.parseInt(year) },
data: {
boardCandidateClaimEvidences: existing.boardCandidateClaimEvidences.filter(
(e) => e.key !== evidence.key
),
},
})
}
},

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.

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find which query document the claim details page (redirect destination) uses to render evidences.
fd -t f 'page.tsx' frontend/src/app/board --exec rg -n -C2 'GetClaimAndEvidencesDocument|GetBoardCandidateClaimEvidencesDocument' {}
# Locate the claim details route specifically
fd -t f 'page.tsx' "frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]" 2>/dev/null

Repository: OWASP/Nest

Length of output: 2607


🏁 Script executed:

cat -n frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/page.tsx | head -50

Repository: OWASP/Nest

Length of output: 2468


Incorrect cache update causes stale UI and unnecessary network requests on the destination page

The update function writes to the cache using GetBoardCandidateClaimEvidencesDocument, but the redirect destination (/board/[year]/candidates/[login]/claims/[claimKey]) reads data using GetClaimAndEvidencesDocument.

Because these are distinct GraphQL documents, the cache.writeQuery call fails to update the evidence list for the destination page. frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/page.tsx will continue to display the removed evidence and trigger an unnecessary network refetch due to its cache-and-network policy.

Update the update function to write to the correct document used by the details page:

Suggested change
// Change the query in cache.writeQuery to match the destination page
cache.writeQuery({
  query: GetClaimAndEvidencesDocument, // <- Use this instead of GetBoardCandidateClaimEvidencesDocument
  variables: { key: claim.key, login, year: Number.parseInt(year) },
  data: {
    boardCandidateClaim: {
      ...existing.boardCandidateClaim,
      boardCandidateClaimEvidences: existing.boardCandidateClaimEvidences.filter(
        (e) => e.key !== evidence.key
      ),
    },
  },
})

Note: Adjust the data shape to match the GetClaimAndEvidencesDocument response structure (likely nested under boardCandidateClaim).

Alternatively, consider invalidating the cache for both documents or using cache.modify with a typed field policy if the data shapes allow a unified update.

🤖 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 `@frontend/src/components/EvidenceActions.tsx` around lines 66 - 82, The cache
update in EvidenceActions is targeting the wrong GraphQL document, so the
destination claim page stays stale and refetches unnecessarily. Update the
Apollo update handler in EvidenceActions to write the removed evidence into the
cache entry used by the claim details page, namely GetClaimAndEvidencesDocument,
not GetBoardCandidateClaimEvidencesDocument. Make sure the variables and
response shape match the page query in page.tsx (including the nested
boardCandidateClaim structure) so the evidence list updates correctly after the
redirect.

}
}

const handleFilechange = (e: React.ChangeEvent<HTMLInputElement>) => {

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.

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Typo in handler name: handleFilechangehandleFileChange.

Minor naming consistency for camelCase.

🤖 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 `@frontend/src/components/EvidenceForm.tsx` at line 66, Rename the typoed
handler in EvidenceForm from handleFilechange to handleFileChange to match
standard camelCase and keep naming consistent; update the function declaration
and any references in the component so the event handler symbol remains aligned
everywhere.

Comment on lines +179 to +187
<FormFileInput
id="evidence-file"
label="File (optional)"
onChange={handleFilechange}
accept={EVIDENCE_ALLOWED_EXTENSIONS.join(',')}
selectedFile={formData.file}
error={fileError}
touched={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.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

HTML input accept attribute valid values file extension with or without leading dot

💡 Result:

When using the accept attribute for an HTML file input, valid file extension specifiers must begin with a period (.) [1][2]. According to web standards and documentation, a valid filename extension specifier is defined as a case-insensitive extension starting with a period character, such as.jpg,.pdf, or.doc [1][3]. Using an extension without the leading dot (e.g., jpg instead of.jpg) is not considered a valid extension specifier for the accept attribute [1][2]. If you provide an invalid format, the browser may ignore that specific part of the attribute value or fail to correctly trigger the file type filter in the system file picker. To ensure cross-browser compatibility and correct behavior, always include the leading period for file extensions [1][2]. In addition to file extensions, the accept attribute also accepts valid MIME type strings (e.g., image/png) and shorthand wildcards (e.g., image/, audio/, or video/*) [1][3]. These can be mixed with file extensions in a comma-separated list [1][4].

Citations:


Fix accept attribute to include leading dots for proper file filtering

The current implementation generates a comma-separated list of extensions without leading dots (e.g., pdf,docx), which browsers ignore because the HTML accept attribute strictly requires extensions to start with a period (e.g., .pdf, .docx). Update the value generation to prepend a dot to each extension:

Proposed fix
-            accept={EVIDENCE_ALLOWED_EXTENSIONS.join(',')}
+            accept={EVIDENCE_ALLOWED_EXTENSIONS.map((e) => `.${e}`).join(',')}

The runtime validation remains necessary, but this change ensures the native file picker correctly filters supported file types.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FormFileInput
id="evidence-file"
label="File (optional)"
onChange={handleFilechange}
accept={EVIDENCE_ALLOWED_EXTENSIONS.join(',')}
selectedFile={formData.file}
error={fileError}
touched={true}
/>
<FormFileInput
id="evidence-file"
label="File (optional)"
onChange={handleFilechange}
accept={EVIDENCE_ALLOWED_EXTENSIONS.map((e) => `.${e}`).join(',')}
selectedFile={formData.file}
error={fileError}
touched={true}
/>
🤖 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 `@frontend/src/components/EvidenceForm.tsx` around lines 179 - 187, The file
picker filtering in the EvidenceForm component is using extension values without
the required leading dots, so the native accept filter is ineffective. Update
the accept value passed to FormFileInput in EvidenceForm to transform
EVIDENCE_ALLOWED_EXTENSIONS into dotted extensions before joining them, while
keeping the existing runtime validation and handleFilechange behavior unchanged.

Comment on lines +39 to +44
const ext = file.name.split('.').pop()?.toLowerCase()
if (!allowedExtensions.some((e) => e.toLowerCase() === ext)) {
return `File extension .${ext} is not supported. Allowed: ${allowedExtensions.map((e) => `.${e}`).join(', ')}`
}
return undefined
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Extension-less filenames yield a misleading error.

For a file named report (no dot), split('.').pop() returns the whole name report, so ext becomes "report" and the message reads File extension .report is not supported. Guard for the no-extension case to produce a clearer message.

🐛 Proposed guard
 export const validateFileExtension = (
   file: File,
   allowedExtensions: string[]
 ): string | undefined => {
-  const ext = file.name.split('.').pop()?.toLowerCase()
+  const parts = file.name.split('.')
+  const ext = parts.length > 1 ? parts.pop()?.toLowerCase() : undefined
   if (!allowedExtensions.some((e) => e.toLowerCase() === ext)) {
-    return `File extension .${ext} is not supported. Allowed: ${allowedExtensions.map((e) => `.${e}`).join(', ')}`
+    const allowed = allowedExtensions.map((e) => `.${e}`).join(', ')
+    return `File extension ${ext ? `.${ext}` : '(none)'} is not supported. Allowed: ${allowed}`
   }
   return undefined
 }

The same diff also resolves the SonarCloud nested-template-literal warning on Line 41 by extracting the allowed string.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ext = file.name.split('.').pop()?.toLowerCase()
if (!allowedExtensions.some((e) => e.toLowerCase() === ext)) {
return `File extension .${ext} is not supported. Allowed: ${allowedExtensions.map((e) => `.${e}`).join(', ')}`
}
return undefined
}
const parts = file.name.split('.')
const ext = parts.length > 1 ? parts.pop()?.toLowerCase() : undefined
if (!allowedExtensions.some((e) => e.toLowerCase() === ext)) {
const allowed = allowedExtensions.map((e) => `.${e}`).join(', ')
return `File extension ${ext ? `.${ext}` : '(none)'} is not supported. Allowed: ${allowed}`
}
return undefined
}
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 41-41: Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=OWASP_Nest&issues=AZ8E3ywlJuwevvl1djlS&open=AZ8E3ywlJuwevvl1djlS&pullRequest=5057

🤖 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 `@frontend/src/components/forms/shared/formValidationUtils.ts` around lines 39
- 44, The file extension check in formValidationUtils is treating extension-less
filenames as if the whole filename were the extension, which produces a
misleading message. Update the validation logic around the file.name split/pop
handling to explicitly detect when there is no dot in the name and return a
clearer no-extension error before comparing against allowedExtensions. While
touching this block, use a separate allowed string in the same validation helper
to avoid the nested template literal warning in the return message.

Source: Linters/SAST tools

Comment on lines +3 to +14
export type Claim = {
__typename?: string
id: string
createdAt?: string
description: string
hasEvidence?: boolean
key: string
name: string
order?: number
status: ClaimStatusEnum
updatedAt?: string
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Avoid loosening the generated GraphQL contract here.

This hand-written type marks createdAt, hasEvidence, order, and updatedAt as optional even though the backend node exposes them as concrete fields and the claims dashboard already consumes createdAt as present. That weakens type-checking right where this PR is adding cache updates and new routes. Prefer reusing the generated operation type (or make these fields required) so frontend and GraphQL stay in lockstep.

🤖 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 `@frontend/src/types/claim.ts` around lines 3 - 14, The Claim type is weakening
the GraphQL contract by making backend-provided fields optional; update the
frontend type to stay aligned with the generated operation type instead of
hand-typing these fields. In Claim, make createdAt, hasEvidence, order, and
updatedAt required (or replace this alias with the generated GraphQL type used
by the claims dashboard and cache updates) so Claim, ClaimStatusEnum, and
related consumers remain in lockstep.

@cubic-dev-ai cubic-dev-ai 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.

14 issues found across 61 files

Confidence score: 2/5

  • frontend/package.json downgrades dompurify from 3.4.11 to 3.4.9, which reintroduces a known sanitizer vulnerability; merging as-is reopens a concrete security risk in HTML sanitization paths—restore 3.4.11+ before merging.
  • frontend/src/components/forms/shared/FormFileInput.tsx has a behavior/accessibility mismatch (required is visual-only, and errors are not connected via aria-describedby/aria-invalid), so users can submit without a file and assistive tech may miss validation feedback—wire the required/error props through to the native input before merge.
  • File/URL validation flow is inconsistent across frontend/src/components/forms/shared/formValidationUtils.ts and frontend/src/components/EvidenceForm.tsx (dotless filenames treated as extensions, and sourceUrl backend errors can persist after file selection), which can leave users stuck in confusing or stale error states—fix extension parsing and clear the URL error when file selection satisfies the requirement.
  • A few interaction paths can fail confusingly in production: empty-action menus in frontend/src/components/DropdownActions.tsx, NaN year variables in frontend/src/components/EvidenceActions.tsx, and query failures shown as “Access Denied” in frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx—add guards and explicit error handling so failures are recoverable and correctly messaged before merging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/components/DropdownActions.tsx">

<violation number="1" location="frontend/src/components/DropdownActions.tsx:20">
P3: New component duplicates existing dropdown behavior already implemented in `EntityActions`. Keeping both copies increases drift risk for future accessibility/keyboard fixes.</violation>

<violation number="2" location="frontend/src/components/DropdownActions.tsx:86">
P2: Component opens an empty dropdown when `options.length === 0`. Guarding empty options avoids a non-functional actions button and confusing keyboard/focus behavior.</violation>
</file>

<file name="frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx">

<violation number="1" location="frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx:50">
P2: Query failures are rendered as “Access Denied” because the access check runs even when `candidateQueryError` is set. This misclassifies backend/network errors and hides the real failure state.</violation>
</file>

<file name="frontend/src/components/ClaimForm.tsx">

<violation number="1" location="frontend/src/components/ClaimForm.tsx:41">
P3: This file duplicates shared form-state and submit-error logic already present in other forms. Extracting a shared hook/helper would reduce maintenance drift across forms.</violation>
</file>

<file name="frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/page.tsx">

<violation number="1" location="frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/page.tsx:102">
P2: Null file URL path is silent; download click can appear broken. Show an error toast when URL is missing.</violation>
</file>

<file name="frontend/src/server/mutations/claimMutations.ts">

<violation number="1" location="frontend/src/server/mutations/claimMutations.ts:10">
P2: Repeated field selections across all six mutations should use a shared GraphQL fragment to keep fields in sync and reduce maintenance burden.</violation>
</file>

<file name="frontend/src/components/EvidenceActions.tsx">

<violation number="1" location="frontend/src/components/EvidenceActions.tsx:63">
P2: Validate `year` before using it in mutation/query variables. Passing `NaN` into required `Int` variables can break evidence removal and cache updates.</violation>

<violation number="2" location="frontend/src/components/EvidenceActions.tsx:111">
P3: Use strict equality for status checks. Loose equality here is unnecessary and can cause coercion-based false positives.</violation>
</file>

<file name="frontend/src/components/forms/shared/FormFileInput.tsx">

<violation number="1" location="frontend/src/components/forms/shared/FormFileInput.tsx:12">
P2: `required` prop shows a visual asterisk but is not passed to `<input type="file">`. Browser won't enforce file selection, creating a mismatch between visual indication and actual behavior.</violation>

<violation number="2" location="frontend/src/components/forms/shared/FormFileInput.tsx:42">
P2: Error message not linked to input via `aria-describedby` or `aria-invalid`. Screen readers won't announce the error when focused on the file input.</violation>
</file>

<file name="frontend/src/components/forms/shared/formValidationUtils.ts">

<violation number="1" location="frontend/src/components/forms/shared/formValidationUtils.ts:39">
P2: `validateFileExtension` misidentifies files without a dot as having an extension. `split('.').pop()` returns the whole filename when no dot exists (e.g., 'Makefile'), producing a confusing error.</violation>
</file>

<file name="frontend/src/components/EvidenceForm.tsx">

<violation number="1" location="frontend/src/components/EvidenceForm.tsx:76">
P2: `sourceUrl` backend error is not cleared when a file is selected. Users can still see an invalid error state after satisfying the "file or URL" requirement.</violation>

<violation number="2" location="frontend/src/components/EvidenceForm.tsx:183">
P2: File picker `accept` string uses bare extensions instead of valid file type specifiers. This weakens client-side file type filtering in the chooser.</violation>
</file>

<file name="frontend/package.json">

<violation number="1" location="frontend/package.json:28">
P1: Downgrading dompurify from 3.4.11 to 3.4.9 reintroduces a security vulnerability. Version 3.4.11 fixes a leaky-config issue where `setConfig()` bypasses the `_parseConfig` clone-guard, allowing `uponSanitizeAttribute` hooks to permanently mutate the shared `ALLOWED_ATTR` object — an XSS bypass.</violation>
</file>

Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.

Re-trigger cubic

Comment thread frontend/package.json
"date-fns": "4.3.0",
"dayjs": "1.11.21",
"dompurify": "3.4.11",
"dompurify": "3.4.9",

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.

P1: Downgrading dompurify from 3.4.11 to 3.4.9 reintroduces a security vulnerability. Version 3.4.11 fixes a leaky-config issue where setConfig() bypasses the _parseConfig clone-guard, allowing uponSanitizeAttribute hooks to permanently mutate the shared ALLOWED_ATTR object — an XSS bypass.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/package.json, line 28:

<comment>Downgrading dompurify from 3.4.11 to 3.4.9 reintroduces a security vulnerability. Version 3.4.11 fixes a leaky-config issue where `setConfig()` bypasses the `_parseConfig` clone-guard, allowing `uponSanitizeAttribute` hooks to permanently mutate the shared `ALLOWED_ATTR` object — an XSS bypass.</comment>

<file context>
@@ -17,18 +17,19 @@
     "date-fns": "4.3.0",
     "dayjs": "1.11.21",
-    "dompurify": "3.4.11",
+    "dompurify": "3.4.9",
     "framer-motion": "12.40.0",
     "graphql": "16.14.0",
</file context>
Suggested change
"dompurify": "3.4.9",
"dompurify": "3.4.11",

}
}

return (

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.

P2: Component opens an empty dropdown when options.length === 0. Guarding empty options avoids a non-functional actions button and confusing keyboard/focus behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/DropdownActions.tsx, line 86:

<comment>Component opens an empty dropdown when `options.length === 0`. Guarding empty options avoids a non-functional actions button and confusing keyboard/focus behavior.</comment>

<file context>
@@ -0,0 +1,139 @@
+    }
+  }
+
+  return (
+    <div className="relative" ref={dropdownRef}>
+      <button
</file context>


const isCandidate = candidateGraphQLData?.boardOfDirectors?.candidate != null

if (!isCandidate || session?.user?.login !== login) {

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.

P2: Query failures are rendered as “Access Denied” because the access check runs even when candidateQueryError is set. This misclassifies backend/network errors and hides the real failure state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/app/board/[year]/candidates/[login]/claims/create/page.tsx, line 50:

<comment>Query failures are rendered as “Access Denied” because the access check runs even when `candidateQueryError` is set. This misclassifies backend/network errors and hides the real failure state.</comment>

<file context>
@@ -0,0 +1,127 @@
+
+  const isCandidate = candidateGraphQLData?.boardOfDirectors?.candidate != null
+
+  if (!isCandidate || session?.user?.login !== login) {
+    return (
+      <AccessDeniedDisplay
</file context>

})
return
}
if (url) {

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.

P2: Null file URL path is silent; download click can appear broken. Show an error toast when URL is missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/app/board/[year]/candidates/[login]/claims/[claimKey]/evidences/[evidenceKey]/page.tsx, line 102:

<comment>Null file URL path is silent; download click can appear broken. Show an error toast when URL is missing.</comment>

<file context>
@@ -0,0 +1,134 @@
+      })
+      return
+    }
+    if (url) {
+      const a = document.createElement('a')
+      a.href = url
</file context>

@@ -0,0 +1,127 @@
import { gql } from '@apollo/client'

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.

P2: Repeated field selections across all six mutations should use a shared GraphQL fragment to keep fields in sync and reduce maintenance burden.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/server/mutations/claimMutations.ts, line 10:

<comment>Repeated field selections across all six mutations should use a shared GraphQL fragment to keep fields in sync and reduce maintenance burden.</comment>

<file context>
@@ -0,0 +1,127 @@
+      code
+      message
+      claim {
+        createdAt
+        description
+        hasEvidence
</file context>

id="evidence-file"
label="File (optional)"
onChange={handleFilechange}
accept={EVIDENCE_ALLOWED_EXTENSIONS.join(',')}

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.

P2: File picker accept string uses bare extensions instead of valid file type specifiers. This weakens client-side file type filtering in the chooser.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/EvidenceForm.tsx, line 183:

<comment>File picker `accept` string uses bare extensions instead of valid file type specifiers. This weakens client-side file type filtering in the chooser.</comment>

<file context>
@@ -0,0 +1,195 @@
+            id="evidence-file"
+            label="File (optional)"
+            onChange={handleFilechange}
+            accept={EVIDENCE_ALLOWED_EXTENSIONS.join(',')}
+            selectedFile={formData.file}
+            error={fileError}
</file context>

} else {
setFileError(undefined)
}
if (backendErrors['file']) {

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.

P2: sourceUrl backend error is not cleared when a file is selected. Users can still see an invalid error state after satisfying the "file or URL" requirement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/EvidenceForm.tsx, line 76:

<comment>`sourceUrl` backend error is not cleared when a file is selected. Users can still see an invalid error state after satisfying the "file or URL" requirement.</comment>

<file context>
@@ -0,0 +1,195 @@
+    } else {
+      setFileError(undefined)
+    }
+    if (backendErrors['file']) {
+      setBackendErrors((prev) => {
+        const next = { ...prev }
</file context>

}

const DropdownActions: React.FC<DropdownActionsProps> = ({ options, label }) => {
const [dropdownOpen, setDropdownOpen] = useState(false)

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.

P3: New component duplicates existing dropdown behavior already implemented in EntityActions. Keeping both copies increases drift risk for future accessibility/keyboard fixes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/DropdownActions.tsx, line 20:

<comment>New component duplicates existing dropdown behavior already implemented in `EntityActions`. Keeping both copies increases drift risk for future accessibility/keyboard fixes.</comment>

<file context>
@@ -0,0 +1,139 @@
+}
+
+const DropdownActions: React.FC<DropdownActionsProps> = ({ options, label }) => {
+  const [dropdownOpen, setDropdownOpen] = useState(false)
+  const [focusIndex, setFocusIndex] = useState(-1)
+  const dropdownRef = useRef<HTMLDivElement>(null)
</file context>

const [touched, setTouched] = useState<Record<string, boolean>>({})
const [backendErrors, setBackendErrors] = useState<Record<string, string>>({})

const handleInputChange = (name: string, value: string | number) => {

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.

P3: This file duplicates shared form-state and submit-error logic already present in other forms. Extracting a shared hook/helper would reduce maintenance drift across forms.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/ClaimForm.tsx, line 41:

<comment>This file duplicates shared form-state and submit-error logic already present in other forms. Extracting a shared hook/helper would reduce maintenance drift across forms.</comment>

<file context>
@@ -0,0 +1,134 @@
+  const [touched, setTouched] = useState<Record<string, boolean>>({})
+  const [backendErrors, setBackendErrors] = useState<Record<string, string>>({})
+
+  const handleInputChange = (name: string, value: string | number) => {
+    setFormData((prev) => ({ ...prev, [name]: value }))
+    if (backendErrors[name]) {
</file context>

}

const options = [
...(claim.status == 'DRAFT'

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.

P3: Use strict equality for status checks. Loose equality here is unnecessary and can cause coercion-based false positives.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/EvidenceActions.tsx, line 111:

<comment>Use strict equality for status checks. Loose equality here is unnecessary and can cause coercion-based false positives.</comment>

<file context>
@@ -0,0 +1,183 @@
+  }
+
+  const options = [
+    ...(claim.status == 'DRAFT'
+      ? [
+          {
</file context>

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.

1 participant