Skip to content

[codex] report visibility wiring#100

Merged
Exc1D merged 5 commits intomainfrom
codex/report-visibility-wireup
May 4, 2026
Merged

[codex] report visibility wiring#100
Exc1D merged 5 commits intomainfrom
codex/report-visibility-wireup

Conversation

@Exc1D
Copy link
Copy Markdown
Owner

@Exc1D Exc1D commented May 4, 2026

What changed

  • Citizen PWA now keeps new reports visible to the creator immediately via local active-report state, while public Map/Feed visibility flips only when verification marks the report public.
  • Verified reports now set visibilityClass: public_alertable in the backend verification callable.
  • The live tracking mapper now normalizes Firestore timestamps and uses lastStatusAt so the timeline/radar page renders from real report docs.
  • Lookup and submission flows were restored so same-device tracking works and the secret code path resolves fresh reports.

Why

The previous wiring left verified reports stuck in the internal bucket, so the public feed never saw them after verification, and the tracking UI could not reliably render live report docs.

Validation

  • npx vitest run src/lib/__tests__/mappers.test.ts src/components/LookupScreen.test.tsx src/components/TrackingScreen.test.tsx src/components/ReportStatusPill.test.tsx src/hooks/useMyActiveReports.test.ts src/components/ProfileTab.test.tsx src/services/submit-report.test.ts
  • pnpm lint and pnpm typecheck in apps/citizen-pwa
  • pnpm typecheck in functions
  • Emulator-backed functions verify-report suite is still blocked by pre-existing rules-unit-testing harness issues (Admin Timestamp seeding / permission-denied path).

Summary by CodeRabbit

  • New Features

    • Reports can now be found offline using locally saved reports when the server is unavailable
  • Bug Fixes

    • Secret code input is now normalized (special characters removed, converted to uppercase) for improved lookup
    • Report timeline events now display correctly with proper timestamp handling
    • Verified reports are now properly marked as visible in alerts
    • Report status transitions are now accurately reflected in tracking

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.

Sorry @Exc1D, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Warning

Rate limit exceeded

@Exc1D has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 7 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: 0240aa88-5afb-4558-a3f6-1b74684e63ee

📥 Commits

Reviewing files that changed from the base of the PR and between e27479a and d4e49bf.

📒 Files selected for processing (8)
  • apps/citizen-pwa/src/components/LookupScreen.test.tsx
  • apps/citizen-pwa/src/hooks/useReport.ts
  • apps/citizen-pwa/src/lib/__tests__/mappers.test.ts
  • apps/citizen-pwa/src/lib/mappers.ts
  • apps/citizen-pwa/src/services/submit-report.test.ts
  • apps/citizen-pwa/src/services/submit-report.ts
  • functions/src/__tests__/callables/verify-report.test.ts
  • functions/src/callables/verify-report.ts
📝 Walkthrough

Walkthrough

This PR enhances citizen report tracking and lookup across multiple layers: normalizing Firestore timestamps and synthesizing missing timeline data in the report mapper, adding local storage fallback for report lookups, canonicalizing legacy report types to modern values, and setting visibility classes and update timestamps during verification transitions. ESLint directives and documentation are also updated.

Changes

Report Data, Timeline, & Visibility Management

Layer / File(s) Summary
Timestamp Normalization & Timeline Synthesis
apps/citizen-pwa/src/lib/mappers.ts
New toMillis() helper normalizes timestamps from raw numbers or Firestore objects; new mapTimelineEvent() validates and maps individual timeline entries. mapReportFromFirestore now synthesizes default timeline from status and timestamps when the live doc omits timeline, uses lastStatusAt as fallback status timestamp, and defaults id to 'unknown' when missing.
Mapper Tests
apps/citizen-pwa/src/lib/__tests__/mappers.test.ts
Tests updated to verify fallback id, optional timeline synthesis from status/timestamps, and lastStatusAt.toMillis() use for status updates.
Verification Visibility & Timestamps
functions/src/callables/verify-report.ts
On every status transition, writes updatedAt: deps.now.toMillis(); when transitioning to verified, also sets visibilityClass: 'public_alertable'.
Verification Tests
functions/src/__tests__/callables/verify-report.test.ts
Firestore emulator port changed to 8081; new assertions verify visibilityClass: 'internal' on new → awaiting_verify and visibilityClass: 'public_alertable' on awaiting_verify → verified.
Learnings & Progress Documentation
docs/learnings.md, docs/progress.md
Added React learning rule on timestamp normalization via toMillis() and lastStatusAt fallback; progress.md documents the work summary and test outcomes.

LookupScreen Secret Normalization & Local Report Fallback

Layer / File(s) Summary
Secret Code Normalization & Local Lookup Logic
apps/citizen-pwa/src/components/LookupScreen.tsx
New normalizeSecretCode() strips non-alphanumerics and uppercases input. handleSubmit now loads local reports via loadReports() and navigates directly if a local report's secret matches the normalized code, falling back to Firebase requestLookup only when no local match exists.
Local Storage Integration & Test Mocks
apps/citizen-pwa/src/components/LookupScreen.test.tsx
Added hoisted mockLoadReports mock and vi.mock for localForageReports; test assertions verify normalized uppercase secret is sent to Firebase, and new test confirms fallback to locally saved report's publicRef when server returns functions/not-found.

Report Type Canonicalization

Layer / File(s) Summary
Type Canonicalization Function
apps/citizen-pwa/src/services/submit-report.ts
New canonicalizeReportType() maps legacy 'public_disturbance' to 'security'; both createDraft() and submitReport() now use canonicalized type instead of raw input.
Canonicalization Tests
apps/citizen-pwa/src/services/submit-report.test.ts
New test verifies reportType: 'public_disturbance' is normalized to 'security' in draft creation; ESLint suppressions removed from upload/contact expectation blocks.

Test & Documentation Refinements

Layer / File(s) Summary
ESLint Directive Cleanup
apps/citizen-pwa/src/components/AlertsTab.test.tsx, apps/citizen-pwa/src/pages/SettingsPage.test.tsx, apps/citizen-pwa/src/services/callables.test.ts, apps/citizen-pwa/src/hooks/useOfflineQueueCount.test.ts, apps/citizen-pwa/src/components/ProfileTab.test.tsx
Removed or narrowed top-level @typescript-eslint/no-unsafe-return directives across multiple test files.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Browser as LookupScreen
    participant LocalStorage as localForageReports
    participant Firebase as Firebase

    User->>Browser: Enter secret code
    Browser->>Browser: normalizeSecretCode(secret)
    Browser->>LocalStorage: loadReports()
    LocalStorage-->>Browser: [local reports]
    
    alt Local match found
        Browser->>Browser: Find report with matching secret
        Browser-->>User: Navigate to /reports/{publicRef}
    else No local match
        Browser->>Firebase: requestLookup(normalized secret)
        Firebase-->>Browser: Server response (report or error)
        Browser-->>User: Navigate to report or display error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Timestamps and secrets now dance in the light,
Local reports checked before we take flight,
Visibility blooms when reports are verified true,
The mapper's now flexible—old shapes welcome too!
From Firebase fallback to security's name,
The citizen's journey just got a new frame. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] report visibility wiring' is directly related to the main change—wiring report visibility across the Citizen PWA and backend. It accurately captures the primary objective from the PR description.
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 codex/report-visibility-wireup

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 15 minutes and 7 seconds.

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

@Exc1D Exc1D marked this pull request as ready for review May 4, 2026 03:20
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/LookupScreen.test.tsx`:
- Around line 127-156: The test claims to verify "server lookup not ready" but
LookupScreen (local check in the component) reads local reports before calling
Firebase, so the vi.mocked(httpsCallable).mockImplementationOnce(...) is never
consumed; remove that dead mock from the test, update the test name to reflect
local-first behavior (e.g., "navigates to locally saved report when a local
match exists"), and add an explicit assertion
expect(vi.mocked(httpsCallable)).not.toHaveBeenCalled() to lock in the
local-first behavior; refer to LookupScreen component (local lookup logic) and
the httpsCallable mock and mockLoadReports in the test to locate the changes.

In `@apps/citizen-pwa/src/lib/mappers.ts`:
- Around line 37-59: mapReportFromFirestore currently accepts any string in
data.status and casts it to ReportStatus; validate data.status against the
allowed ReportStatus values before using or casting it (e.g., check membership
in the ReportStatus enum/union or a Set of valid statuses) and throw an error if
it’s not valid so invalid backend values fail fast; update the validation near
the existing typeof data.status check and ensure downstream uses (timeline
synthesis and the result.status assignment) only use the validated/cast
ReportStatus.
- Around line 56-60: The mapper currently collapses missing inline IDs into the
shared sentinel 'unknown' on the ReportData object; update the mapper that
builds "result: ReportData" (the function that consumes "data", "timeline", and
returns "result") to accept the Firestore document ID as an additional parameter
(e.g., docId) and set id = typeof data.id === 'string' ? data.id : docId; if
passing docId is not possible, replace the shared 'unknown' fallback with a
guaranteed-unique fallback (e.g., a generated UUID) so different Firestore docs
do not collide; ensure you update all call sites to provide the document ID when
available and keep ReportStatus and timeline assignment unchanged.

In `@apps/citizen-pwa/src/services/submit-report.test.ts`:
- Around line 141-158: Add a test that exercises the submitReport normalization
path: call submitReport (or the exported handler that triggers inbox writes)
with a payload where reportType is 'public_disturbance' and assert that
deps.writeInbox (the mocked inbox writer used in tests) was called with a
payload whose reportType === 'security'; reuse the same test setup/mocks used in
submit-report.test.ts (e.g., mockDraftStoreSave / deps.writeInbox) so the test
verifies the actual inbox write path rather than only createDraft's return
value.

In `@apps/citizen-pwa/src/services/submit-report.ts`:
- Around line 55-62: canonicalizeReportType currently maps the legacy
"public_disturbance" but then force-casts any other string to ReportType,
allowing typos/stale values to persist; change it to map "public_disturbance" →
"security" and then validate the incoming string against a whitelist/type-guard
(e.g., an isValidReportType function or REPORT_TYPES array) instead of using "as
ReportType"; if the value is not valid, reject it by throwing an Error (or
returning null/undefined) so callers cannot persist unsupported report types
(update callers to handle the rejection as needed).

In `@functions/src/__tests__/callables/verify-report.test.ts`:
- Around line 61-65: The test currently checks status and visibility but misses
asserting the new updatedAt write; update the assertions after calling
verifyReportCore (the value in result and the DB record fetched via
db.collection('reports').doc(reportId).get()) to confirm updatedAt was set—e.g.,
assert result.updatedAt exists/is a Timestamp (or number) and that
report.updatedAt exists and equals (or is consistent with) result.updatedAt (or
is greater than the previous timestamp), and add the same updatedAt assertion
for the other block around lines 94-99 so the test exercises the updatedAt
update path.
🪄 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: 0180345f-1145-4b5a-8733-95b64e73c478

📥 Commits

Reviewing files that changed from the base of the PR and between f0c26dd and e27479a.

📒 Files selected for processing (15)
  • apps/citizen-pwa/src/components/AlertsTab.test.tsx
  • apps/citizen-pwa/src/components/LookupScreen.test.tsx
  • apps/citizen-pwa/src/components/LookupScreen.tsx
  • apps/citizen-pwa/src/components/ProfileTab.test.tsx
  • apps/citizen-pwa/src/hooks/useOfflineQueueCount.test.ts
  • apps/citizen-pwa/src/lib/__tests__/mappers.test.ts
  • apps/citizen-pwa/src/lib/mappers.ts
  • apps/citizen-pwa/src/pages/SettingsPage.test.tsx
  • apps/citizen-pwa/src/services/callables.test.ts
  • apps/citizen-pwa/src/services/submit-report.test.ts
  • apps/citizen-pwa/src/services/submit-report.ts
  • docs/learnings.md
  • docs/progress.md
  • functions/src/__tests__/callables/verify-report.test.ts
  • functions/src/callables/verify-report.ts
💤 Files with no reviewable changes (4)
  • apps/citizen-pwa/src/pages/SettingsPage.test.tsx
  • apps/citizen-pwa/src/hooks/useOfflineQueueCount.test.ts
  • apps/citizen-pwa/src/services/callables.test.ts
  • apps/citizen-pwa/src/components/AlertsTab.test.tsx

Comment thread apps/citizen-pwa/src/components/LookupScreen.test.tsx Outdated
Comment thread apps/citizen-pwa/src/lib/mappers.ts Outdated
Comment thread apps/citizen-pwa/src/lib/mappers.ts
Comment thread apps/citizen-pwa/src/services/submit-report.test.ts
Comment thread apps/citizen-pwa/src/services/submit-report.ts
Comment thread functions/src/__tests__/callables/verify-report.test.ts
- LookupScreen: remove dead mock, add local-first assertion
- mappers: validate ReportStatus whitelist, accept docId fallback
- useReport: pass snapshot.id to mapReportFromFirestore
- submit-report: whitelist validate canonicalizeReportType, throw on invalid
- verify-report: return updatedAt in result, assert in tests
@Exc1D Exc1D merged commit df4ebc7 into main May 4, 2026
14 checks passed
@Exc1D Exc1D deleted the codex/report-visibility-wireup branch May 4, 2026 04:08
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