Skip to content

fix(citizen-pwa): resolve QA critical + important issues, fix 4 failing tests#87

Merged
Exc1D merged 9 commits intomainfrom
fix/qa-critical-important-issues
May 1, 2026
Merged

fix(citizen-pwa): resolve QA critical + important issues, fix 4 failing tests#87
Exc1D merged 9 commits intomainfrom
fix/qa-critical-important-issues

Conversation

@Exc1D
Copy link
Copy Markdown
Owner

@Exc1D Exc1D commented May 1, 2026

Summary

Fixes 6 critical and 10 important issues identified by QA assessment, plus 4 pre-existing failing route tests.

Critical fixes (6)

  • callables.tsrequestDataExport and registerCitizen now wrap await callable() in try/catch with descriptive error rethrow (was bare await, no error boundary)
  • RevealSheet.tsx — Clipboard failure now shows "Copy failed — please write it down" to user instead of only console.error; setTimeout timer ref properly cleared on unmount (was void t anti-pattern)
  • useReport.ts + mappers.tsReportTimelineEvent.actor changed from string to string?; mapper individually maps timeline events with safe optional fields instead of unsafe bulk cast

Important fixes (10)

  • incident-meta.tsx — Extracted statusMeta() and severityDotColor() as shared exports (single source of truth, was duplicated in ProfileTab)
  • ProfileTab.tsx — Removed duplicated status/severity logic, imports from incident-meta; useBadges now uses useMemo; handleSignOut shows error feedback instead of silent empty catch
  • RevealSheet.tsx — Extracted REVEAL_VARIANTS to module-level constant (was 42-line object recreated every render); GUARDIAN_BENEFITS extracted to shared constant (was duplicated inline)
  • MapTab/index.tsxseverityLabel('all') now returns "All" instead of "A"
  • App.routes.test.tsx — 4 failing route tests fixed by using findByText for lazy-loaded Suspense routes (was getByText which asserted before React resolved the lazy import)

Test results

  • Before: 209 pass, 4 fail, 2 todo
  • After: 213 pass, 0 fail, 2 todo

Commits (6, one per logical change)

  1. 1ec2fc9 — callables try/catch error boundaries
  2. 13a7ebf — timeline actor optional + safe mapper
  3. 7a1b011 — RevealSheet clipboard feedback + timer cleanup + extract variants
  4. ddc3bc3 — deduplicate status/severity logic + fix useBadges + signOut feedback
  5. 1c64b68 — severityLabel('all') → 'All'
  6. 8a98409 — fix 4 failing lazy-loaded route tests

Summary by Sourcery

Improve reliability and UX of the citizen PWA around reporting, account flows, and routing, while fixing flaky tests and centralizing shared incident metadata.

Bug Fixes:

  • Handle failures from Firebase callable functions for data export and citizen registration with descriptive errors.
  • Make report timeline mapping resilient to missing actor and note fields by safely constructing timeline events.
  • Show user-facing feedback when copying the secret code fails and ensure clipboard timers are cleaned up on unmount.
  • Correct the severity label for the "all" filter to display as "All".
  • Display an error message when sign-out fails instead of silently ignoring the error.
  • Fix lazy-loaded route tests by waiting for content with async queries.

Enhancements:

  • Extract incident status metadata and severity color helpers into shared utilities for consistent presentation across the app.
  • Refactor RevealSheet to reuse shared variants and guardian benefits constants and to compute icons via memoization for clearer, more efficient rendering.
  • Memoize badge derivation in the profile tab to avoid unnecessary recomputation based on reports data.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added shared status and severity display utilities for consistent UI metadata across the app.
  • Bug Fixes

    • Sign-out failures now display an error message to the user.
    • Clipboard copy operations now show error feedback with automatic recovery.
    • Improved error handling for data export and registration operations with clearer failure messages.
    • Fixed severity filter label handling.
  • Tests

    • Updated route tests to ensure proper async behavior validation.

Exc1D and others added 6 commits May 1, 2026 21:25
…appers

requestDataExport and registerCitizen called await callable() with no
error handling — Firebase errors would propagate as opaque internal
errors. Wrap both in try/catch and rethrow descriptive messages with
the original error as cause.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…restore

ReportTimelineEvent.actor was typed as required string but Firestore
data may omit it, violating the TS contract at runtime. Make actor
optional in the interface and map timeline events individually in
mapReportFromFirestore instead of bulk-casting the array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…act variants

- handleCopySecret: show 'Copy failed' message to user instead of
  only console.error; use ref-based timer with cleanup on unmount
  instead of void t anti-pattern
- Extract REVEAL_VARIANTS to module-level constant (was recreated
  every render as 42-line inline object)
- Extract GUARDIAN_BENEFITS checklist items to shared constant
  (was duplicated between RevealSheet and ProfileTab inline)
- Derive guardianIcon via useMemo keyed on state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d signOut feedback

- Extract statusMeta() and severityDotColor() to incident-meta.tsx as
  single source of truth (was duplicated in ProfileTab)
- ProfileTab: remove local statusMeta/severityDot, import from utils
- useBadges: wrap in useMemo to avoid recalculating every render
- handleSignOut: show 'Sign out failed. Please try again.' on error
  instead of silently swallowing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
charAt(0).toUpperCase() on 'all' produces 'A'. Add early return for
the 'all' filter value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4 route tests asserted synchronously with getByText on lazy-loaded
components wrapped in Suspense. The render completes before React
resolves the lazy import, so only the spinner fallback is in the DOM.
Switch to findByText (async, wraps waitFor) to wait for the component
to mount after Suspense resolves.

All 213 tests now pass (was 209 pass + 4 fail).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 1, 2026

Reviewer's Guide

Implements safer error handling for Firebase callables, improves RevealSheet UX and performance, centralizes incident status/severity metadata, adds memoization and error feedback in ProfileTab, makes timeline mapping tolerant of missing actor fields, fixes severity/all labeling, and updates lazy-loaded route tests to await rendered content, resolving multiple QA-critical and important issues plus four failing tests.

Sequence diagram for RevealSheet copy-to-clipboard feedback and timer cleanup

sequenceDiagram
  actor User
  participant RevealSheet
  participant ClipboardAPI
  participant Timer as CopyTimer

  User->>RevealSheet: clickCopySecretCode()
  RevealSheet->>ClipboardAPI: writeText(secretCode)
  alt copy_success
    ClipboardAPI-->>RevealSheet: resolved
    RevealSheet->>RevealSheet: setCopied(true)
    RevealSheet->>RevealSheet: setCopyError(false)
    RevealSheet->>CopyTimer: clearTimeout(copyTimerRef)
    RevealSheet->>CopyTimer: copyTimerRef = setTimeout(resetCopied,2000)
    CopyTimer-->>RevealSheet: resetCopied()
    RevealSheet->>RevealSheet: setCopied(false)
  else copy_failure
    ClipboardAPI-->>RevealSheet: rejected
    RevealSheet->>RevealSheet: setCopyError(true)
    RevealSheet->>CopyTimer: clearTimeout(copyTimerRef)
    RevealSheet->>CopyTimer: copyTimerRef = setTimeout(resetCopyError,3000)
    CopyTimer-->>RevealSheet: resetCopyError()
    RevealSheet->>RevealSheet: setCopyError(false)
  end

  User-->>RevealSheet: navigates_away_or_sheet_unmounts
  RevealSheet->>CopyTimer: clearTimeout(copyTimerRef) on_unmount
Loading

Sequence diagram for Firebase callables with enhanced error handling

sequenceDiagram
  actor User
  participant UI as CitizenPWAUI
  participant Callables as CallablesService
  participant FNS as FirebaseFunctions
  participant CloudCallable as CloudFunction

  User->>UI: triggerDataExport()
  UI->>Callables: requestDataExport()
  Callables->>FNS: httpsCallable(fns,requestDataExport)
  FNS-->>Callables: DataExportCallable
  Callables->>CloudCallable: call()
  alt export_success
    CloudCallable-->>Callables: void
    Callables-->>UI: resolve()
  else export_failure
    CloudCallable-->>Callables: throws Error
    Callables->>Callables: constructError("Data export request failed",cause)
    Callables-->>UI: throw_wrapped_Error
    UI-->>User: show_export_error_message
  end

  User->>UI: registerCitizen()
  UI->>Callables: registerCitizen()
  Callables->>FNS: httpsCallable(fns,registerCitizen)
  FNS-->>Callables: RegisterCitizenCallable
  Callables->>CloudCallable: call()
  alt registration_success
    CloudCallable-->>Callables: CitizenRegistrationResult
    Callables-->>UI: CitizenRegistrationResult
    UI-->>User: proceed_to_registered_state
  else registration_failure
    CloudCallable-->>Callables: throws Error
    Callables->>Callables: constructError("Citizen registration failed",cause)
    Callables-->>UI: throw_wrapped_Error
    UI-->>User: show_registration_error_message
  end
Loading

Class diagram for updated report timeline mapping and types

classDiagram
  class ReportTimelineEvent {
    +string event
    +number timestamp
    +string actor
    +string note
  }

  class ReportTimelineEventOptionalActor {
    +string event
    +number timestamp
    +string actor
    +string note
  }

  class ReportData {
    +string id
    +ReportStatus status
    +ReportTimelineEventOptionalActor[] timeline
  }

  class mapReportFromFirestore {
    +ReportData mapReportFromFirestore(data)
  }

  class ReportStatus {
  }

  ReportData --> ReportTimelineEventOptionalActor : timeline
  mapReportFromFirestore --> ReportData : returns

  class RawTimelineEvent {
    +string event
    +number timestamp
    +string actor
    +string note
  }

  class FirestoreRecord {
    +string id
    +string status
    +RawTimelineEvent[] timeline
  }

  mapReportFromFirestore --> FirestoreRecord : reads
  FirestoreRecord --> RawTimelineEvent : timeline

  class OptionalActorMappingLogic {
    +ReportTimelineEventOptionalActor map(evt)
  }

  OptionalActorMappingLogic --> RawTimelineEvent : input
  OptionalActorMappingLogic --> ReportTimelineEventOptionalActor : output

  ReportTimelineEventOptionalActor <|-- ReportTimelineEvent : was_strict_required_actor
Loading

Class diagram for incident metadata utilities and ProfileTab integration

classDiagram
  class ProfileTab {
    +boolean signOutError
    +ProfileTab()
    +handleSignOut()
  }

  class ReportCard {
    +ReportCard(report,onTap)
  }

  class MyReport {
    +string id
    +string reportType
    +string status
    +string severity
  }

  class incidentIcon {
    +icon incidentIcon(type)
  }

  class incidentLabel {
    +string incidentLabel(type)
  }

  class statusMeta {
    +StatusMeta statusMeta(status)
  }

  class severityDotColor {
    +string severityDotColor(severity)
  }

  class StatusMeta {
    +string label
    +string bg
    +string color
  }

  class SEVERITY_COLORS {
    +Record~string,string~ SEVERITY_COLORS
  }

  class useBadges {
    +BadgeDef[] useBadges(reports)
  }

  class BadgeDef {
    +string id
    +string title
    +string description
    +boolean earned
  }

  class BADGE_DEFS {
    +BadgeDef[] BADGE_DEFS
  }

  ProfileTab --> useBadges : uses
  useBadges --> BADGE_DEFS : maps_from
  useBadges --> BadgeDef : returns

  ReportCard --> MyReport : report
  ReportCard --> incidentIcon : uses
  ReportCard --> incidentLabel : uses
  ReportCard --> statusMeta : uses
  ReportCard --> severityDotColor : uses

  statusMeta --> StatusMeta : returns
  severityDotColor --> SEVERITY_COLORS : reads

  class FirebaseAuthService {
    +signOut(auth)
  }

  ProfileTab --> FirebaseAuthService : calls_signOut
Loading

Class diagram for Firebase callables with error handling

classDiagram
  class CallablesService {
    +requestDataExport()
    +registerCitizen()
  }

  class FirebaseFunctions {
    +fns()
    +httpsCallable(functions,name)
  }

  class DataExportCallable {
    +call()
  }

  class RegisterCitizenCallable {
    +call()
  }

  class CitizenRegistrationResult {
    +string uid
    +string role
    +string accountStatus
  }

  class Error {
    +string message
    +any cause
  }

  CallablesService --> FirebaseFunctions : uses_httpsCallable
  CallablesService --> DataExportCallable : creates
  CallablesService --> RegisterCitizenCallable : creates

  DataExportCallable --> Error : may_throw
  RegisterCitizenCallable --> Error : may_throw

  CallablesService --> CitizenRegistrationResult : returns_from_registerCitizen

  class requestDataExport {
    +void requestDataExport()
  }

  class registerCitizen {
    +CitizenRegistrationResult registerCitizen()
  }

  requestDataExport --> CallablesService : implemented_by
  registerCitizen --> CallablesService : implemented_by

  requestDataExport --> Error : wraps_and_rethrows
  registerCitizen --> Error : wraps_and_rethrows
Loading

File-Level Changes

Change Details Files
Harden Firebase callable wrappers with explicit error boundaries.
  • Wrap requestDataExport callable invocation in try/catch and rethrow a descriptive Error including the original cause.
  • Wrap registerCitizen callable in try/catch, returning typed data on success and throwing a descriptive Error with cause on failure.
apps/citizen-pwa/src/services/callables.ts
Refine RevealSheet behavior, feedback, and constants for better UX and lifecycle safety.
  • Extract static reveal variants into a REVEAL_VARIANTS module-level constant and derive the current variant from state.
  • Replace per-variant inline icon with a memoized guardianIcon derived from state for use in StatusBanner.
  • Add copyError state and user-facing error message when clipboard write fails, instead of only logging to console.
  • Use a ref to manage the copy feedback timeout, clearing any existing timeout before setting a new one and on unmount to avoid leaks.
  • Extract GUARDIAN_BENEFITS list to a shared constant and reuse it when rendering the benefits list.
apps/citizen-pwa/src/components/RevealSheet.tsx
Centralize incident status/severity metadata and optimize ProfileTab behavior and sign-out UX.
  • Move statusMeta implementation from ProfileTab into utils/incident-meta and export it for reuse.
  • Introduce SEVERITY_COLORS and severityDotColor in incident-meta to provide a single source of truth for severity color mapping, including a safe default.
  • Update ProfileTab to import statusMeta and severityDotColor and remove the local duplicates.
  • Wrap badge-derivation logic in useBadges with useMemo keyed by report count and verified count.
  • Add signOutError state and surface a visible error message when sign-out fails instead of silently ignoring the error.
apps/citizen-pwa/src/components/ProfileTab.tsx
apps/citizen-pwa/src/utils/incident-meta.tsx
Make report timeline types and mapping robust to optional fields.
  • Change ReportTimelineEvent.actor to be optional to reflect that some events may not have an actor.
  • Replace the previous unsafe cast of timeline data with a mapper that iterates over each event and safely assigns event, timestamp, and optional actor/note fields only when present.
apps/citizen-pwa/src/hooks/useReport.ts
apps/citizen-pwa/src/lib/mappers.ts
Improve incident severity labeling and fix lazy-loaded route tests for Suspense behavior.
  • Update severityLabel to return 'All' when severity is the filter value 'all' instead of capitalizing to 'A'.
  • Change App.routes tests for lazy-loaded routes to use async findByText expectations so assertions wait for Suspense to resolve instead of failing immediately.
apps/citizen-pwa/src/components/MapTab/index.tsx
apps/citizen-pwa/src/App.routes.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

Warning

Rate limit exceeded

@Exc1D has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 22 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2dd9cb0a-d827-49c9-a180-9787b4c4fd08

📥 Commits

Reviewing files that changed from the base of the PR and between 8a98409 and 0ec68b6.

📒 Files selected for processing (6)
  • apps/citizen-pwa/src/components/ProfileTab.tsx
  • apps/citizen-pwa/src/components/RevealSheet.tsx
  • apps/citizen-pwa/src/components/TrackingScreen.tsx
  • apps/citizen-pwa/src/lib/mappers.ts
  • apps/citizen-pwa/src/services/callables.ts
  • infra/firebase/firestore.rules.template
📝 Walkthrough

Walkthrough

This pull request updates tests to use asynchronous assertions, extracts shared incident metadata utilities, refactors UI components to use those utilities with improved memoization and error handling, adjusts type definitions to make timeline actor optional, updates data mapping logic, and enhances error handling in service callables.

Changes

Cohort / File(s) Summary
Test Updates
apps/citizen-pwa/src/App.routes.test.tsx
Route tests updated to use async screen.findByText(...) with await instead of synchronous screen.getByText(...) for assertions on /report, /incidents/:id, /register, and /settings pages.
Shared Utilities
apps/citizen-pwa/src/utils/incident-meta.tsx
Added statusMeta() function to convert status identifiers into display models with labels and Tailwind classes, and severityDotColor() function to return hex colors for severity levels (high, medium, low, fallback #334155).
Data Layer
apps/citizen-pwa/src/hooks/useReport.ts, apps/citizen-pwa/src/lib/mappers.ts
ReportTimelineEvent type updated to make actor field optional. Timeline mapping in mapReportFromFirestore refactored to explicitly transform each entry with conditional actor and note inclusion.
Service Layer
apps/citizen-pwa/src/services/callables.ts
requestDataExport() and registerCitizen() now wrap callable invocations in try-catch blocks, rethrowing with specific error messages and preserving original errors as cause.
Component Refactoring
apps/citizen-pwa/src/components/ProfileTab.tsx, apps/citizen-pwa/src/components/RevealSheet.tsx, apps/citizen-pwa/src/components/MapTab/index.tsx
ProfileTab imports shared metadata utilities and wraps badge computation in useMemo; adds signOutError state to handle and display sign-out failures. RevealSheet extracts inline variants to module-level REVEAL_VARIANTS constant, memoizes guardianIcon computation, and enhances clipboard error handling with state management and 3-second timeout. MapTab explicitly handles 'all' severity value.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Async tests await their verdicts with care,
Utilities shared make metadata fair,
Errors now caught with a graceful embrace,
Memoized badges hop into place! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main purpose of the PR: resolving QA critical and important issues while fixing failing tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/qa-critical-important-issues

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
Review rate limit: 0/1 reviews remaining, refill in 29 minutes and 22 seconds.

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

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In mapReportFromFirestore, data.timeline is assumed to be a non-empty array; consider safely handling undefined/non-array values (e.g., defaulting to an empty array or validating the shape) to avoid runtime errors when the field is missing or malformed.
  • In RevealSheet, signOutError and copyError are set but never explicitly cleared on user re-try actions; you might want to reset these flags when the user re-attempts sign-out or copy to avoid stale error messages.
  • For the clipboard failure path in RevealSheet, you now show a user-facing error but fully swallow the exception; preserving a console.error (or similar logging) in the catch block would help with diagnosing production issues without affecting UX.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `mapReportFromFirestore`, `data.timeline` is assumed to be a non-empty array; consider safely handling `undefined`/non-array values (e.g., defaulting to an empty array or validating the shape) to avoid runtime errors when the field is missing or malformed.
- In `RevealSheet`, `signOutError` and `copyError` are set but never explicitly cleared on user re-try actions; you might want to reset these flags when the user re-attempts sign-out or copy to avoid stale error messages.
- For the clipboard failure path in `RevealSheet`, you now show a user-facing error but fully swallow the exception; preserving a `console.error` (or similar logging) in the `catch` block would help with diagnosing production issues without affecting UX.

## Individual Comments

### Comment 1
<location path="apps/citizen-pwa/src/components/ProfileTab.tsx" line_range="289-296" />
<code_context>
     /* eslint-enable react-hooks/set-state-in-effect */
   }, [reports])

+  const [signOutError, setSignOutError] = useState(false)
+
   const handleSignOut = async () => {
     try {
       await signOut(auth())
       void navigate('/', { replace: true })
     } catch {
-      // sign out failure is non-critical; page will still function
+      setSignOutError(true)
     }
   }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Sign-out error state is never cleared, so the error message can persist unnecessarily.

Once `signOutError` is set to `true`, it never resets, so the error message can persist across subsequent attempts. Consider clearing it at the start of each sign-out attempt:

```ts
const handleSignOut = async () => {
  setSignOutError(false)
  try {
    await signOut(auth())
    void navigate('/', { replace: true })
  } catch {
    setSignOutError(true)
  }
}
```

This keeps the error state in sync with the latest attempt.

Suggested implementation:

```typescript
  const handleSignOut = async () => {
    setSignOutError(false)
    try {
      await signOut(auth())
      void navigate('/', { replace: true })
    } catch {
      setSignOutError(true)
    }
  }

```

If `handleSignOut` appears multiple times (e.g., in tests or other components within the same file), apply the same pattern: clear `signOutError` (or the equivalent local error state) at the beginning of each sign-out attempt.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/citizen-pwa/src/components/ProfileTab.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/citizen-pwa/src/components/ProfileTab.tsx`:
- Around line 600-602: The Sign-out error message rendered in ProfileTab.tsx
using the signOutError variable should be made announcement-friendly for screen
readers: update the error <p> element that displays when signOutError is truthy
to include live-region semantics (for example add role="alert" or
aria-live="assertive") so assistive tech will announce the failure; locate the
conditional that renders the <p> with "Sign out failed. Please try again." and
add the attribute to that element.

In `@apps/citizen-pwa/src/components/RevealSheet.tsx`:
- Around line 160-165: The catch block for the clipboard write currently sets
error state but doesn't reset the success flag, which can leave copied=true;
update the catch in RevealSheet.tsx to explicitly clear the success state by
calling setCopied(false) (before or alongside setCopyError(true)), keep the
existing timer logic using copyTimerRef and clearTimeout, and ensure any
existing timers are reset so the error UI is cleared after the same 3000ms
window.
- Line 106: Rename the boolean state variable `copyError` to a predicate-style
name like `hasCopyError` and update its setter from `setCopyError` to
`setHasCopyError`; modify the useState declaration (const [hasCopyError,
setHasCopyError] = useState(false)) and replace all references to `copyError`
and `setCopyError` (including the instances around lines 430-434) across the
component (JSX conditional rendering, event handlers, and any functions that
read or write this state) to the new names to preserve intent and consistency.

In `@apps/citizen-pwa/src/hooks/useReport.ts`:
- Line 12: The hook changed actor to optional (actor?: string) so update
consumers like the TrackingScreen component's timeline renderers to guard
against undefined; in the TrackingScreen.tsx code paths that interpolate e.actor
(the timeline renderer or any place referencing e.actor) wrap it with a
conditional/fallback formatter (e.g., show a default label or omit the actor
when undefined) or use a small helper formatter so the UI never renders
"undefined · …".

In `@apps/citizen-pwa/src/lib/mappers.ts`:
- Around line 12-17: The timeline mapping blindly casts Firestore payloads into
ReportData fields; update the timeline construction to validate and sanitize
each evt before including it in ReportData: ensure the parent timeline is an
array, then filter/map events by checking typeof evt.event === 'string' and
typeof evt.timestamp === 'number' (reject or skip entries that fail), and only
include actor and note if typeof evt.actor === 'string' and typeof evt.note ===
'string'. Locate the timeline mapping in the ReportData construction (the
timeline: (...) .map((evt) => ...) block) and replace the casts with explicit
type checks and safe filtering so invalid or null values are not propagated.

In `@apps/citizen-pwa/src/services/callables.ts`:
- Around line 23-24: The code currently trusts result.data from callable() and
casts it directly; instead add runtime validation before returning: check that
result and result.data exist and that result.data.uid, result.data.role, and
result.data.accountStatus are present and of the expected types (e.g., strings),
or use a small validator function (e.g., validateCallableResponse) or a schema
(zod) to assert the shape; if validation fails, throw a descriptive Error (or
return a safe fallback) rather than blindly casting, and update the return site
(the code around the await callable() call and the result variable) to use the
validated/parsed object.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3e2b2206-f312-46e0-a7c8-804f94a19b0e

📥 Commits

Reviewing files that changed from the base of the PR and between 026279b and 8a98409.

📒 Files selected for processing (8)
  • apps/citizen-pwa/src/App.routes.test.tsx
  • apps/citizen-pwa/src/components/MapTab/index.tsx
  • apps/citizen-pwa/src/components/ProfileTab.tsx
  • apps/citizen-pwa/src/components/RevealSheet.tsx
  • apps/citizen-pwa/src/hooks/useReport.ts
  • apps/citizen-pwa/src/lib/mappers.ts
  • apps/citizen-pwa/src/services/callables.ts
  • apps/citizen-pwa/src/utils/incident-meta.tsx

Comment thread apps/citizen-pwa/src/components/ProfileTab.tsx
Comment thread apps/citizen-pwa/src/components/RevealSheet.tsx Outdated
Comment thread apps/citizen-pwa/src/components/RevealSheet.tsx
Comment thread apps/citizen-pwa/src/hooks/useReport.ts
Comment thread apps/citizen-pwa/src/lib/mappers.ts Outdated
Comment thread apps/citizen-pwa/src/services/callables.ts Outdated
Exc1D and others added 3 commits May 1, 2026 21:47
ReportTimelineEvent.actor became optional in the previous commit.
TrackingScreen passed it into a template literal without null-check,
triggering @typescript-eslint/restrict-template-expressions. Default
to 'system' when actor is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The committed firestore.rules had three manual edits from earlier PRs
that were not reflected in the template:

1. Removed isCitizen() — report_inbox create uses request.auth != null
   to allow anonymous (pseudonymous) citizens to submit
2. Added publicRef, secretHash, correlationId to hasOnly/hasAll
3. Added reporter self-read on reports/{id} via report_private lookup

Update the template to match so build-rules.ts output stays in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ProfileTab: clear signOutError before each attempt; add role="alert"
- RevealSheet: rename copyError -> hasCopyError; reset copied on catch
- mappers: validate timeline event field types at boundary
- callables: validate registerCitizen response shape before returning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Exc1D Exc1D merged commit 942dcac into main May 1, 2026
14 checks passed
@Exc1D Exc1D deleted the fix/qa-critical-important-issues branch May 1, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant