diff --git a/apps/citizen-pwa/index.html b/apps/citizen-pwa/index.html index 879798f2..9a2d13e2 100644 --- a/apps/citizen-pwa/index.html +++ b/apps/citizen-pwa/index.html @@ -4,6 +4,10 @@ + + + + diff --git a/apps/citizen-pwa/src/components/AlertsTab.tsx b/apps/citizen-pwa/src/components/AlertsTab.tsx index 59f594b3..f204ac09 100644 --- a/apps/citizen-pwa/src/components/AlertsTab.tsx +++ b/apps/citizen-pwa/src/components/AlertsTab.tsx @@ -81,7 +81,7 @@ function AlertCard({ ) } - if (step === 'warn') { - return ( -
-

Delete your account?

-

This will permanently:

- -

This cannot be undone. Your request will be reviewed before deletion is complete.

- - -
- ) - } - return ( -
-

Are you sure?

- - { - setTyped(e.target.value) - }} - autoComplete="off" - /> - {error &&

{error}

} - - +
{ + if (step !== 'submitting' && e.target === e.currentTarget) goIdle() + }} + > + {step === 'warn' ? ( +
+

+ Delete your account? +

+

This will permanently:

+
    +
  • Remove your name, contact info, and account
  • +
  • Anonymize your reports (they remain as public record)
  • +
  • Sign you out immediately
  • +
+

+ This cannot be undone. Your request will be reviewed before deletion is complete. +

+
+ + +
+
+ ) : ( +
+

+ Are you sure? +

+ + { + setTyped(e.target.value) + }} + autoComplete="off" + className="w-full h-12 rounded-xl border-2 border-surface-200 px-4 text-sm font-mono focus:border-danger-500 focus:outline-none mb-4 disabled:opacity-50 disabled:cursor-not-allowed" + /> + {error && ( +

+ {error} +

+ )} +
+ + +
+
+ )}
) } diff --git a/apps/citizen-pwa/src/components/FeedTab.tsx b/apps/citizen-pwa/src/components/FeedTab.tsx index 56ba4430..be25ae41 100644 --- a/apps/citizen-pwa/src/components/FeedTab.tsx +++ b/apps/citizen-pwa/src/components/FeedTab.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { CheckCircle, MapPin } from 'lucide-react' +import { MapPin, Info } from 'lucide-react' import { useNavigate } from 'react-router-dom' import { usePublicIncidents } from '../hooks/usePublicIncidents.js' import { incidentIcon, incidentLabel } from '../utils/incident-meta.js' @@ -16,12 +16,12 @@ function timeAgo(timestamp: number): string { function severityBadgeClass(severity: string): string { if (severity === 'high') - return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-red-100 text-red-800' + return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-red-100 text-red-800' if (severity === 'medium') - return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-orange-50 text-orange-800' + return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-orange-50 text-orange-800' if (severity === 'low') - return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-surface-100 text-surface-700' - return 'px-2 py-0.5 rounded-full text-[10px] font-semibold bg-blue-50 text-blue-900' + return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-surface-100 text-surface-700' + return 'px-2 py-0.5 rounded-full text-xs font-semibold bg-blue-50 text-blue-900' } function FeedCard({ incident, onTap }: { incident: PublicIncident; onTap: () => void }) { @@ -45,7 +45,7 @@ function FeedCard({ incident, onTap }: { incident: PublicIncident; onTap: () =>

{label}

- + {timeAgo(incident.submittedAt)}
@@ -76,11 +76,11 @@ function SkeletonCard() { return (
-
+
-
-
-
+
+
+
@@ -124,7 +124,7 @@ export function FeedTab() { }} className={ filters.severity === value - ? 'bg-[#0f9488] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' + ? 'bg-[#001e40] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' : 'bg-[#f0f4f4] text-[#5e6667] rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' } > @@ -145,7 +145,7 @@ export function FeedTab() { }} className={ filters.window === value - ? 'bg-[#0f9488] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' + ? 'bg-[#001e40] text-white rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' : 'bg-[#f0f4f4] text-[#5e6667] rounded-full px-3 py-1.5 text-xs font-medium flex-shrink-0 border-none cursor-pointer' } > @@ -178,13 +178,13 @@ export function FeedTab() { aria-atomic="true" className="flex flex-col items-center justify-center min-h-[50vh] text-[#768081] px-4" > - - + + -

All clear

+

No incidents

No incidents reported in the selected time window. - + Walang naiulat na insidente sa panahong ito.

diff --git a/apps/citizen-pwa/src/components/LookupScreen.tsx b/apps/citizen-pwa/src/components/LookupScreen.tsx index 8e827e42..36a1946a 100644 --- a/apps/citizen-pwa/src/components/LookupScreen.tsx +++ b/apps/citizen-pwa/src/components/LookupScreen.tsx @@ -1,7 +1,12 @@ import { useState } from 'react' import { httpsCallable } from 'firebase/functions' import { ArrowLeft } from 'lucide-react' -import { fns, hasFirebaseConfig, FIREBASE_ENV_ERROR_MESSAGE } from '../services/firebase.js' +import { + fns, + hasFirebaseConfig, + ensureSignedIn, + FIREBASE_ENV_ERROR_MESSAGE, +} from '../services/firebase.js' interface LookupResult { status: string @@ -50,6 +55,7 @@ export function LookupScreen() { if (!hasFirebaseConfig()) { throw new Error(FIREBASE_ENV_ERROR_MESSAGE) } + await ensureSignedIn() const res = await httpsCallable( fns(), 'requestLookup', @@ -170,6 +176,15 @@ export function LookupScreen() { {`${result.municipalityLabel} MDRRMO`}
+
)}
diff --git a/apps/citizen-pwa/src/components/MapTab/FilterBar.tsx b/apps/citizen-pwa/src/components/MapTab/FilterBar.tsx index 6c192f8b..73324543 100644 --- a/apps/citizen-pwa/src/components/MapTab/FilterBar.tsx +++ b/apps/citizen-pwa/src/components/MapTab/FilterBar.tsx @@ -17,7 +17,7 @@ interface Props { const SEVERITIES: { value: SeverityFilter; label: string; dot?: string }[] = [ { value: 'all', label: 'All' }, { value: 'high', label: 'High', dot: '#dc2626' }, - { value: 'medium', label: 'Medium', dot: '#a73400' }, + { value: 'medium', label: 'Medium', dot: '#7c3500' }, { value: 'low', label: 'Low', dot: '#414849' }, ] diff --git a/apps/citizen-pwa/src/components/MapTab/IncidentLayer.tsx b/apps/citizen-pwa/src/components/MapTab/IncidentLayer.tsx index f47159bd..58d695ad 100644 --- a/apps/citizen-pwa/src/components/MapTab/IncidentLayer.tsx +++ b/apps/citizen-pwa/src/components/MapTab/IncidentLayer.tsx @@ -10,7 +10,7 @@ interface Props { onPinTap: (incident: PublicIncident) => void } -const COLORS = { high: '#dc2626', medium: '#a73400', low: '#414849' } as const +const COLORS = { high: '#dc2626', medium: '#7c3500', low: '#414849' } as const function isValidCoordinate(lat: number, lng: number): boolean { return ( @@ -29,7 +29,7 @@ function makeIcon(color: string, pulse: boolean): L.DivIcon { iconSize: [44, 44], iconAnchor: [22, 22], html: ` -
+
${ pulse ? `
` diff --git a/apps/citizen-pwa/src/components/MapTab/MyReportLayer.tsx b/apps/citizen-pwa/src/components/MapTab/MyReportLayer.tsx index 567690fb..6c08c778 100644 --- a/apps/citizen-pwa/src/components/MapTab/MyReportLayer.tsx +++ b/apps/citizen-pwa/src/components/MapTab/MyReportLayer.tsx @@ -28,7 +28,7 @@ function makeIcon(color: string, queued: boolean): L.DivIcon { iconSize: [44, 44], iconAnchor: [22, 22], html: ` -
+
${ queued diff --git a/apps/citizen-pwa/src/components/MapTab/index.tsx b/apps/citizen-pwa/src/components/MapTab/index.tsx index 02babd1a..8d8d1920 100644 --- a/apps/citizen-pwa/src/components/MapTab/index.tsx +++ b/apps/citizen-pwa/src/components/MapTab/index.tsx @@ -4,6 +4,7 @@ import { Crosshair } from 'lucide-react' import L from 'leaflet' import { PeekSheet } from './PeekSheet.js' import { DetailSheet } from './DetailSheet.js' +import { FilterBar } from './FilterBar.js' import { IncidentLayer } from './IncidentLayer.js' import { MyReportLayer } from './MyReportLayer.js' import { usePublicIncidents } from '../../hooks/usePublicIncidents.js' @@ -27,6 +28,12 @@ const INCIDENT_LABELS: Record = { other: 'Others', } +const WINDOW_LABELS: Record = { + '24h': '24 hours', + '7d': '7 days', + '30d': '30 days', +} + interface SelectedPin { id: string type: 'incident' | 'myReport' @@ -54,7 +61,7 @@ export function MapTab() { const mapRef = useRef(null) const [mapInstance, setMapInstance] = useState(null) const [isOffline, setIsOffline] = useState(() => !navigator.onLine) - const filters = { severity: 'all', window: '24h' } as const + const [filters, setFilters] = useState({ severity: 'all', window: '24h' }) const [selectedPin, setSelectedPin] = useState(null) const [sheetPhase, setSheetPhase] = useState<'hidden' | 'peek' | 'expanded'>('hidden') @@ -191,10 +198,22 @@ export function MapTab() { visibleIncidents.length === 0 && myReports.length === 0 + const showFilterHint = + !incidentsLoading && + !incidentsError && + visibleIncidents.length === 0 && + (myReports.length > 0 || filters.severity !== 'all') + return (
+
+
+ +
+
+ {mapInstance ? ( <> - {showEmpty ? ( + {showEmpty || showFilterHint ? (

- No reported incidents in this area in the last {filters.window}. + {filters.severity !== 'all' + ? `No ${filters.severity} incidents reported in this area in the last ${WINDOW_LABELS[filters.window]}. Try clearing the severity filter.` + : showFilterHint + ? `No reported incidents in this area in the last ${WINDOW_LABELS[filters.window]}.` + : `No reported incidents in this area in the last ${WINDOW_LABELS[filters.window]}.`}

) : null} diff --git a/apps/citizen-pwa/src/components/ProfileTab.tsx b/apps/citizen-pwa/src/components/ProfileTab.tsx index 2fd448ea..e70c21f1 100644 --- a/apps/citizen-pwa/src/components/ProfileTab.tsx +++ b/apps/citizen-pwa/src/components/ProfileTab.tsx @@ -232,7 +232,7 @@ function ReportCard({ report, onTap }: { report: MyReport; onTap: () => void }) /* ── Skeleton card ── */ function SkeletonCard() { return ( -
+
diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx index 305a4e29..442bbafc 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/ContactFields.tsx @@ -47,7 +47,7 @@ export function ContactFields({ htmlFor="reporter-name" className="text-sm font-semibold text-surface-700 block mb-2" > - Your name + Your name / Pangalan - Phone number + Phone number{' '} + / Numero ng telepono
) @@ -310,7 +310,7 @@ export function Step1Evidence({ setReportType(value) setReportTypeError(null) }} - className={`flex flex-col items-center justify-center gap-2 min-h-[80px] rounded-xl border-2 transition-all active:scale-95 ${ + className={`flex flex-col items-center justify-center gap-2 min-h-[80px] px-3 py-3 rounded-xl border-2 transition-all active:scale-95 ${ isSelected ? `${selBorder} ${selBg} shadow-sm` : 'border-surface-200 bg-white hover:border-surface-300' diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx index 61c9c8fd..9c21a6a6 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/Step2WhoWhere.tsx @@ -199,7 +199,7 @@ export function Step2WhoWhere({
) @@ -248,6 +248,25 @@ export function Step2WhoWhere({
) : null} + {locationMethod === 'gps' && !gpsLoading && !location ? ( +
+ {locationError &&

{locationError}

} + +
+ ) : null} + {locationMethod === 'gps' && location ? (
diff --git a/apps/citizen-pwa/src/components/SubmitReportForm/Step3Review.tsx b/apps/citizen-pwa/src/components/SubmitReportForm/Step3Review.tsx index 2f4f3f4d..95e5fb21 100644 --- a/apps/citizen-pwa/src/components/SubmitReportForm/Step3Review.tsx +++ b/apps/citizen-pwa/src/components/SubmitReportForm/Step3Review.tsx @@ -84,7 +84,7 @@ export function Step3Review({
) @@ -125,7 +125,7 @@ export function Step3Review({
{/* Type row */} -
+
@@ -139,7 +139,7 @@ export function Step3Review({ {/* Patient count row */} {reportData.patientCount > 0 && ( -
+
@@ -153,7 +153,7 @@ export function Step3Review({ )} {/* Location row */} -
+
@@ -174,7 +174,7 @@ export function Step3Review({
{/* Contact row */} -
+
@@ -190,7 +190,7 @@ export function Step3Review({
{/* Consent */} -
+
{/* Danger Zone section */} diff --git a/docs/progress.md b/docs/progress.md index 94205a3d..9669676a 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -1,8 +1,55 @@ # Progress +## 2026-05-03 — QA Findings Sweep (branch: `main`) + +Addressed all P0 + P1 + actionable P2/P3 items from `docs/qa-findings-2026-05-03.md` (45 total findings). + +| Finding | Fix | Commit | +| ----------------------------------- | ---------------------------------------------------------------------------- | -------------- | +| P0-1 RevealSheet never shown | Removed nav from `onSuccess`; added `onClose` to RevealSheet success state | `fix/commit-1` | +| P0-2 CORS/auth on requestLookup | Added `ensureSignedIn()` before httpsCallable in LookupScreen | commit 1 | +| P0-3 SW no retry | SW registration with 3-attempt backoff + `sw-registration-failed` event | commit 3 | +| P0-4 iOS PWA meta tags | Added `apple-mobile-web-app-*` meta tags + apple-touch-icon to index.html | commit 3 | +| P0-5 Push toggle state | Toggle disabled when `Notification.permission === 'denied'`; hint text added | commit 1 | +| P0-6 GPS auto-start strands user | "Switch to manual location" button shown when GPS fails | commit 5 | +| P0-7 Sign out button missing | Sign out button added to SettingsPage Account section | commit 1 | +| P1-1 Submit button danger color | Removed `!bg-danger-500` override from Step3Review submit button | commit 1 | +| P1-2 FilterBar not wired | FilterBar imported + `useState` + rendered over map in MapTab | commit 4 | +| P1-3 Marker cursor | `cursor:pointer` added to IncidentLayer + MyReportLayer makeIcon | commit 4 | +| P1-5/P3-16 DeleteAccountFlow | Full modal overlay rewrite: backdrop, Escape key, autoFocus, aria-modal | afb0670 | +| P1-8 Add to Home Screen | Install prompt button in SettingsPage (conditional on deferredInstallPrompt) | commit 1 | +| P2-1 Type button padding | Added `px-3 py-3` to incident type buttons in Step1Evidence | 2f0bf5b | +| P2-4 Review rows bunched | `py-3` → `py-4` on all Step3Review summary rows | 2f0bf5b | +| P2-5 Consent margin | Added `mb-3` to consent div in Step3Review | 2f0bf5b | +| P2-12 Medium severity contrast | `#a73400` → `#7c3500` (meets WCAG AA) in IncidentLayer + FilterBar | 746b4ac | +| P2-14 No reset after lookup | "Check another report" button added below LookupScreen result | 746b4ac | +| P3-3 animate-pulse no motion guard | `animate-pulse` → `motion-safe:animate-pulse` across 6 components | 2f0bf5b | +| P3-6 Teal filter chips | Selected chips changed to authority-navy `#001e40` in FeedTab | afb0670 | +| P3-7 Green empty state | CheckCircle → Info icon, "All clear" → "No incidents", neutral color | afb0670 | +| P3-9 4px border stripes | `border-l-4` → `border-l-2` on AlertsTab cards | 746b4ac | +| P3-12 Badge font size | `text-[10px]` → `text-xs` in FeedTab severity badges | afb0670 | +| P3-13 Filipino caption size | `text-[11px]` → `text-xs` in FeedTab empty-state caption | afb0670 | +| P3-14 Toggle aria-label dup | Removed redundant `role="group"` wrapper from Toggle | 2f0bf5b | +| P3-15 Tagalog absent on core fields | `/ Pangalan` + `/ Numero ng telepono` added to ContactFields labels | 2f0bf5b | + +**Skipped (non-code or out-of-scope):** + +- P1-4, P2-7, P2-8: Firestore staging data population issues +- P2-6: MyReportLayer distinct markers (visual design work, deferred) +- P2-9: Home tagline margin (home page not identified in scope) +- P2-10: DetailSheet remount on pin-type switch (structural refactor) +- P2-11: GPS coordinates human-readable (reverse geocoding feature) +- P2-13: Wizard back nav exits app (browser back behavior, needs nav stack work) +- P3-1/P3-2: GuardianPitchCard not found in src tree +- P3-4/P3-5: Scroll indicator + ProfileTab hierarchy (UX design decisions) +- P3-8: Severity badge icons (design system expansion) +- P3-10/P3-11: Off-palette color tokens (wide-scope token migration) + +**Gate:** `pnpm --filter @bantayog/citizen-pwa lint` — 0 errors (14 pre-existing warnings in test files) + ## 2026-05-03 — PR #91 Review Follow-ups (branch: `fix/citizen-pwa-auth-and-wizard-followups`) -Addressed all Sourcery-ai and CodeRabbit review comments on PR #91. +Addressed all Sourcery AI and CodeRabbit review comments on PR #91. | Source | Issue | Fix | Files | | ---------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | diff --git a/docs/qa-findings-2026-05-03.md b/docs/qa-findings-2026-05-03.md new file mode 100644 index 00000000..75a981d9 --- /dev/null +++ b/docs/qa-findings-2026-05-03.md @@ -0,0 +1,130 @@ +# Bantayog Citizen PWA — QA Findings + +**Staging URL:** https://bantayog-citizen-staging.web.app +**Date:** 2026-05-03 +**Scope:** Citizen PWA — All tabs and flows + +--- + +## 🔴 P0 — PRODUCTION BLOCKERS + +| # | Issue | Root Cause | Fix | Files | +| -------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | +| **P0-1** | Receipt page stuck forever — "being processed" indefinitely; no tracking codes | `SubmitReportForm` routes to `TrackingScreen` (`/reports/{uuid}`) which fetches Firestore (async write may not be complete). `ReceiptScreen` at `/receipt` is **orphaned dead code** — requires `useLocation().state` with `{publicRef, secret}` but navigator passes **no state**. The emotional peak (RevealSheet warm cream gradient + JetBrains Mono reference code) is **trapped in a component that is never reached**. | Route to RevealSheet confirmation flow on submit success, or pass `{publicRef, secret}` via location state | `SubmitReportForm/index.tsx`, `ReceiptScreen.tsx` | +| **P0-2** | CORS error on `requestLookup` — users cannot track reports by code | **Misdiagnosed**: Actually an **auth dependency**. `requestLookup` uses Firebase `onCall` which requires Firebase Auth. `LookupScreen` calls the callable with no sign-in attempt. Browser CORS preflight fails before response is read; the friendly error catch block never fires. Fix is a one-liner. | Add `await ensureSignedIn()` before the callable | `LookupScreen.tsx` | +| **P0-3** | SW registration fails silently — no retry, no user notification | `main.tsx` `catch` block writes only to `console.error`. Users think they're offline-capable but are not. In a crisis this breaks trust completely. | Add retry (3 attempts with backoff) + user-visible banner when SW fails | `main.tsx` | +| **P0-4** | iOS PWA not installable — all 5 meta tags missing | `index.html` missing `apple-mobile-web-app-capable`, `apple-touch-icon`, `apple-mobile-web-app-status-bar-style`, `apple-mobile-web-app-title`, `mask-icon`. iOS users entirely locked out. | Add iOS PWA meta tags to `index.html` | `index.html` | +| **P0-5** | Push notifications toggle lies about its own state | When browser denies permission, toast fires "Failed" but `aria-checked` stays pre-click state. UI doesn't reflect actual permission outcome. Trust-through-calm violation. | Fix toggle state to reflect actual permission outcome | `SettingsPage.tsx`, `Toggle.tsx` | +| **P0-6** | GPS button missing from Step 2 | `useGpsLocation` hook exists in code but the button UI is not rendered in `Step2WhoWhere`. Only manual barangay/municipality entry works. | Render the GPS/Use My Location button | `Step2WhoWhere.tsx` | +| **P0-7** | Sign-out button missing from Settings | Only "Delete my account" in Danger Zone. Canonical settings page has no session end action. `ProfileTab` has `signOut` but Settings (the canonical account controls) doesn't. | Add standalone Sign-out button | `SettingsPage.tsx` | + +--- + +## 🟡 P1 — HIGH PRIORITY + +| # | Issue | Root Cause | Fix | Files | +| -------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------- | +| **P1-1** | Step 3 Submit button uses `danger-500` red | DESIGN.md: "Red...never used as a primary CTA." Submit IS the primary forward action. Red = destructive = wrong at the moment of confirmation. | Change to `bg-authority-navy` / `text-white` | `Step3Review.tsx` | +| **P1-2** | FilterBar.tsx fully implemented but never rendered | `MapTab/index.tsx` never imports or renders it. Filters state is hardcoded as a constant. Users see 36 markers with no way to filter by type. | Import and render `` with live `filters`/`setFilters` state | `MapTab/index.tsx` | +| **P1-3** | Map marker icons have no click affordance | Bare 16×16 circles with `cursor: grab`, no hover scale, no tooltip. Users don't know markers are tappable. Root cause of "36 markers but no click interaction." | Add `cursor: pointer`, scale transform on hover, or tooltip | `IncidentLayer.tsx`, `MyReportLayer.tsx` | +| **P1-4** | Feed shows empty while Map has 36 markers | Different Firestore queries: Feed filters `where('visibilityClass', '==', 'public_alertable')`; Map does NOT use this filter. Different data sources = empty Feed = users lose trust. | Align `usePublicIncidents` query with Map marker query | `usePublicIncidents.ts` | +| **P1-5** | DeleteAccountFlow renders inline, not as a true dialog | `role="dialog"` content inline inside Settings page — no backdrop, no focus trap, no Escape handler, no return focus. Not discoverable as a dialog. | Implement proper dialog with backdrop, focus trap, keyboard a11y | `DeleteAccountFlow.tsx` | +| **P1-6** | Step 2 form data NOT persisted on page refresh | `wizard-snapshot` IndexedDB exists but Step 2 fields are not restored after refresh. | Ensure `Step2WhoWhere` accepts and uses `initialValues` wired to snapshot | `SubmitReportForm/index.tsx`, `Step2WhoWhere.tsx` | +| **P1-7** | Name input clears on Step 2 validation failure | No inline error displayed; form state resets rather than showing error. | Show inline `role="alert"` error below field, preserve form state | `Step2WhoWhere.tsx` | +| **P1-8** | `beforeinstallprompt` captured but never surfaced | `window.deferredInstallPrompt` stored on event but nothing ever reads it. No "Add to Home Screen" UI. | Add install button in Settings wired to `deferredInstallPrompt.prompt()` | `SettingsPage.tsx`, `main.tsx` | + +--- + +## 🟠 P2 — MEDIUM PRIORITY + +| # | Issue | Location | Fix | +| --------- | ------------------------------------------------------------ | ------------- | ---------------------------------------------------------------------------------- | +| **P2-1** | Incident type buttons have no padding (cramped pills) | Wizard Step 1 | Add `px-3 py-2` per DESIGN.md chip spec (8px v × 12px h) | +| **P2-2** | Section labels not uppercase, wrong color/size | All steps | Apply DESIGN.md: `0.75rem/700 uppercase authority-navy` | +| **P2-3** | Select dropdown has no bottom margin (stacks on name input) | Wizard Step 2 | Add `mb-3` to dropdown | +| **P2-4** | Review section rows bunched together (no vertical spacing) | Wizard Step 3 | Add `gap-3` or `mb-3` between summary rows | +| **P2-5** | Consent text no margin from disabled Submit button | Wizard Step 3 | Add `mb-3` below consent text | +| **P2-6** | `MyReportLayer` markers identical to public incident markers | Map tab | Make own-report markers visually distinct | +| **P2-7** | `issuedBy` missing on all alert cards AND detail sheet | Alerts tab | **Firestore data issue** — alerts in staging don't have `issuedBy` field populated | +| **P2-8** | `issuedBy` missing on Feed tab alerts | Feed tab | Same Firestore data population issue | +| **P2-9** | Tagline excessive top margin (32px) | Home page | Reduce to 8–16px | +| **P2-10** | DetailSheet unmounts/remounts on pin-type switch | Map tab | Unify to single render block with mode-aware content | +| **P2-11** | GPS coordinates in raw decimal format, not human-readable | Wizard Step 2 | Show human-readable place name, not `14.10870, 122.94280` | +| **P2-12** | `#a73400` medium severity = 3.5:1 contrast on white | Map markers | Off-palette; adjust to meet 4.5:1 WCAG AA | +| **P2-13** | Back navigation exits wizard entirely | Wizard | Add explicit Back button within wizard (not browser back) | +| **P2-14** | No "Track another report" after successful lookup | LookupScreen | Add secondary action to dismiss or start new lookup | + +--- + +## 🟢 P3 — DESIGN LAW VIOLATIONS (Polish / Brand Consistency) + +| # | Violation | Rule | Fix | +| --------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| **P3-1** | `GuardianPitchCard` uses `bg-gradient from-brand-500 to-brand-600` | DESIGN.md anti-reference: shopping-app gradient forbidden; brand-500/600 not in palette | Replace with solid authority-navy or remove gradient | +| **P3-2** | Primary CTA in GuardianPitchCard uses `text-brand-600` on white | DESIGN.md: primary CTA must be authority-navy (#001e40) with white text | Use `button-primary` component spec | +| **P3-3** | `animate-pulse` without `prefers-reduced-motion` guard | DESIGN.md Do: all animations must have no-motion alternative | Add `useReducedMotion` guard | +| **P3-4** | `overflow-x-auto no-scrollbar` hides badge content | PRODUCT.md inclusivity: hidden content not accessible to first-time smartphone users | Add visible scroll indicator | +| **P3-5** | 7 equal-weight sections on ProfileTab (registered user) | PRODUCT.md: "every screen must work at 4% battery at 2am" — no primary action hierarchy | Establish visual hierarchy with one dominant CTA | +| **P3-6** | Selected filter chips use teal `#0f9488` | DESIGN.md Two-Anchor Rule: only navy + sienna | Change selected chips to `authority-navy` | +| **P3-7** | Empty Feed shows green CheckCircle "All clear" | DESIGN.md: false reassurance during active disaster erodes trust | Neutral "No incidents in this filter" instead | +| **P3-8** | Severity badges lack icons (alerts + feed) | DESIGN.md Status Trio Rule: icon + label + color always | Add Siren/Bell/Info icons to severity pills | +| **P3-9** | 4px left border stripes on alert cards | DESIGN.md 1px border limit rule | Reduce to 1px max or replace with leading icon | +| **P3-10** | Severity badge colors entirely off-palette | DESIGN.md: only 4 status colors defined | Map to spec or formally extend | +| **P3-11** | Fabricated colors not in palette: `#f0f4f4`, `#0f9488`, `#25292a` | DESIGN.md tonal surface system | Replace with palette tokens | +| **P3-12** | Body copy at `text-xs` (12px) in alert cards | DESIGN.md Body spec: `1rem/400` minimum | Bump to `text-sm` minimum | +| **P3-13** | Filipino caption at `0.6875rem` (11px) below WCAG AA | WCAG AA minimum 12px for body text | Bump to `0.75rem` minimum | +| **P3-14** | Toggle `aria-label` collision (button inside `role="group"`) | a11y best practice | Deduplicate or remove `role="group"` wrapper | +| **P3-15** | Tagalog absent on core field labels | PRODUCT.md inclusive-by-default | Add Tagalog equivalents to "Your name", "Phone number" | +| **P3-16** | `DeleteAccountFlow` "Delete" button uses `style={{ color: 'red' }}` without `aria-label` | a11y | Add `aria-label` or use semantic color class | + +--- + +## ✅ CONFIRMED WORKING + +| Feature | Status | +| ---------------------------------------------------------------------- | -------------------------- | +| Wizard E2E flow (Step 1 → 2 → 3 → Submit) | ✅ Functional end-to-end | +| Step 2 validation (disabled Continue until fields complete) | ✅ Correct | +| Error `role="alert"` for missing incident type | ✅ Accessible | +| RevealSheet component (9/10 — Excellent) | ✅ Best-designed component | +| Onboarding flow | ✅ Works | +| Map tab Leaflet + OSM tiles rendering | ✅ | +| Alerts tab severity icons and border colors | ✅ | +| `sw.js` implementation (rejection-gating, SPA fallback, cache cleanup) | ✅ | +| Profile tab for unauthenticated users (8/10 visual) | ✅ | +| LookupScreen error messages (friendly, non-technical) | ✅ | +| `requestPermission` / `disable` Firebase messaging wrappers | ✅ | +| Storage estimate display in Settings | ✅ | +| Step 2 back-navigation state loss (known gap, deferred) | ⚠️ | + +--- + +## ❌ MISSING ALTOGETHER + +| Feature | Status | +| ---------------------------------- | -------------------------------------- | +| Filter bar on Map tab | Code exists, not rendered | +| Data Export button in Settings | Not in UI (callable exists) | +| Sign-out button in Settings | Only Delete account present | +| GPS button in Step 2 | Hook exists, button not rendered | +| Alert filter/sort controls | Not implemented | +| Back button within wizard | Browser back exits entirely | +| "Track another report" post-lookup | Not implemented | +| SW failure user notification | Not implemented | +| iOS PWA install path | Meta tags missing | +| installed prompt UI | Deferred install prompt never surfaced | + +--- + +## Priority Fix Order + +1. **P0-2** — `await ensureSignedIn()` before `requestLookup` (1 line) +2. **P0-6** — GPS button in Step 2 (small UI addition) +3. **P0-1** — Receipt page routing (architectural — biggest UX gap) +4. **P1-2** — FilterBar rendered in MapTab (1 import line + state) +5. **P1-3** — Marker click affordance (CSS change) +6. **P1-1** — Submit button color: danger-500 → authority-navy +7. **P0-3** + **P0-4** — SW retry + iOS meta tags +8. **P0-5** — Push toggle state fix +9. **P1-8** — Install prompt UI in Settings +10. **P1-4** — Feed/Map query alignment (data correctness)