Building CivicPulse with Claude 🤖
For students: This page documents the exact journey of building CivicPulse — prompt by prompt, session by session — so you can see how a production-grade full-stack app is built collaboratively with an AI model.
CivicPulse went from "I have an idea" → production-ready app across ~11 major Claude sessions. Every architectural decision, every feature, every doc — built through conversation.
The Build Journey
Session-by-Session Breakdown
Each row below is one major Claude session. The Prompt column shows the essence of what was asked — short, direct, and architectural.
| # | Prompt (one line) | Key Outcome | Bundle Size |
|---|---|---|---|
| 1 | "Build a civic issue SPA — React 19 + Vite, Node/Express backend, DynamoDB, Google OAuth, issue CRUD, real-time chat, photo/video upload with EXIF, scoring leaderboard, X/Twitter feed import" | Complete working app: auth, issues, chat, media, scoring, admin panel, feature flags | ~380 kB |
| 2 | "Add feature flags so I can toggle any feature on/off — persist to DB, not just localStorage" | useFlags() hook, DynamoDB config table, public /api/flags with 60s CDN cache | +12 kB |
| 3 | "Build a separate /admin portal with email+password auth, its own JWT secret, dashboard for flags, content, donors, and push notifications" | Full admin sub-app: AdminLogin, AdminDashboard, 4 views, bcrypt, timing-safe login, rate limiting | +38 kB |
| 4 | "Add three landing sections: Supported By (logo grid), Donate (QR + donor wall), Collaborate (GitHub/Claude/beta)" | 3 animated landing sections, donor management, Twitter profile pic fetching | +22 kB |
| 5 | "Add web push notifications (VAPID) and Google Analytics 4 with custom civic events" | Service worker, push subscriptions in DynamoDB, GA4 + 10 custom events, push bell in nav | +18 kB |
| 6 | "Add unit tests for frontend (Vitest) and backend (Jest+Supertest), wire into CI, add OWASP ZAP security scan" | 19 frontend + 30 backend tests, CI gate blocks deploy on failure, ZAP scan on workflow_dispatch | +0 kB |
| 7 | "Set up a Docusaurus docs site with full architecture docs, API reference, scoring explainer, admin guide" | 14+ markdown docs, auto-sidebar, dark mode default, deployment guide | separate site |
| 8 | "Replace all emoji icons in the UI with Lucide React (ISC licensed), add guest browse mode so users can explore without signing in" | Full icon migration (Camera, Shield, Bell, etc.), guest mode, sign-in popup with context | +4 kB |
| 9 | "How can we implement duplicate issue detection — if users report the same problem from different angles?" | Research → multi-signal design (Haversine + dHash + Jaccard). Parked as P2 future feature. Designed, not implemented. | +0 kB |
| 10 | "Add image and issue flagging separate from upvoting, location-based browsing with Near Me sort and distance badges — also mark everything that's mocked in the project plan" | FlagModal, image/issue flags, useUserLocation, haversine distance, Near Me sort, nearby count hint, CSV mock column | +8 kB |
| 11 | "Build a mobile app" | React Native + Expo architecture, 6-phase plan, shared code strategy — added to roadmap | +0 kB |
| 12 | "Add X/Twitter live feed — pull mentions via API v2, show in Social Wall, convert to issues" | xService.js, GET/POST /api/x/mentions, XFeed component, demo/live mode, 21 tests | +8 kB |
| 13 | "Add WhatsApp Business API — webhook for incoming messages, WAFeed component, convert flow" | whatsappService.js, webhook verification + HMAC-SHA256, WAFeed, phone masking, 19 tests | +12 kB |
| 14 | "Build an accountability chain — map every issue to Ward Officer → Corporator → MLA → MP" | accountabilityService.js, 75-record seed data, 5 public + 7 admin CRUD endpoints, AssignmentPanel, Reports tab, 19 tests | +45 kB |
| 15 | "Add Instagram integration — Graph API mentions, IGFeed, convert flow, mirror X pattern" | igService.js, IGFeed.jsx, GET/POST /api/ig/mentions, demo/live mode, 15 tests | +6 kB |
Final bundle:
537 kB(148 kB gzip) — complete production app with 3 social platform integrations.
The Prompts, Annotated
Below are the actual prompts (lightly paraphrased) with annotations on what made them effective. Sessions 1–11 cover the original build; Sessions 12–15 add social integrations and the accountability system.
Session 1 — The Big Bang
"Build me a civic issue reporting app for Indian cities. Single-page React 19 + Vite 7 frontend, Node.js/Express backend, DynamoDB storage. Google OAuth with JWT refresh tokens. Users can file issues (title, description, category, city, priority), attach photos/videos with EXIF GPS extraction, upvote, and chat in real-time via WebSocket. Show a leaderboard. Also pull live complaints from X/Twitter search API. No separate files for components — keep it in one big file for now."
Why it worked:
- ✅ Named the full stack upfront — no back-and-forth on technology choices
- ✅ Described the data model (title, description, category, city, priority)
- ✅ Stated architectural constraints ("no separate files — one big file")
- ✅ Included the non-obvious requirements (EXIF GPS extraction, WebSocket chat, leaderboard)
Session 3 — Admin Portal
"Build a separate
/adminroute completely isolated from the main app. Email + password login, NOT Google OAuth. Its own JWT secret (ADMIN_JWT_SECRET). Token stored insessionStorageso it clears on tab close. Admins can toggle feature flags, edit site content, manage donors, upload QR code for donations, and broadcast push notifications. A superadmin can invite/remove other admins. Non-admin users who try to log in should see a 'no access' screen, not an error."
Why it worked:
- ✅ Security requirements stated explicitly (own JWT, sessionStorage, not Google OAuth)
- ✅ Edge case specified ("no access" screen vs generic error)
- ✅ Role hierarchy described (superadmin vs admin)
- ✅ List of views given upfront — avoided ambiguity
Session 9 — Research First, Build Later
"How can we implement a duplication issue feature? If the user starts uploading an issue, how do I know if the same issue has been reported? Can we do something with geo-location and image comparison?"
What happened: Claude presented 4 options (filter-based, Jaccard fuzzy, image hashing, multi-step form), then designed a full multi-signal scoring system (Haversine × 0.55 + dHash × 0.30 + Jaccard × 0.15).
The follow-up:
"Yes, let's park deduplication on the side. Upload docs and to-do list with this as a future feature request."
Why this pattern is valuable:
- ✅ Research before building — sometimes the right answer is "not now"
- ✅ Design is preserved in the roadmap doc — nothing is lost
- ✅ Pivoting is fast — Claude doesn't argue, it redirects constructively
Session 10 — Multi-requirement in one prompt
"Add image and issue flagging separate from upvoting. Also ability to browse issues in a particular location — issues people see should be based on their location, then they can navigate by category and city. Also mark everything that's mocked clearly in the project plan."
This single prompt packed 3 distinct requests:
- Image flagging (gallery + lightbox)
- Issue flagging (ICard + Detail view, separate column from upvote)
- Location browsing (Near Me sort, distance badges, nearby count)
- Documentation update (mock data column in CSV)
What Claude did: Ran them concurrently, implemented all 4 with proper state architecture (mediaFlags, issueFlags at App level so state persists across navigation).
Mid-session additions (in the same conversation):
- "flagging an issue apart from upvoting it" → added issue-level flag button
- "whatever you are mocking, mark it clearly in the plan document" → triggered CSV mock column
Why this works:
- ✅ Mid-session refinements are fine — Claude tracks context throughout the conversation
- ✅ You don't need to restart — just clarify as you go
Session 12 — X/Twitter Live Integration
"Connect to the X API v2. Pull recent mentions of our handle. Show them in the Social Wall's X tab. Add a 'Convert' button that turns a tweet into a CivicPulse issue. Fall back to demo mode with mock tweets if no API key is set."
Why it worked:
- ✅ Named the exact API (X API v2, not generic "social media")
- ✅ Specified the fallback behaviour (demo mode with mock data)
- ✅ Described the full flow (fetch → display → convert)
- ✅ Incremental extension — built on existing SocialWall scaffold
Session 13 — WhatsApp Business Webhook
"Add WhatsApp Business Cloud API integration. Webhook endpoint for Meta to POST incoming messages. Parse text, images, videos, location from the webhook payload. Show in Social Wall, convert to issues. Mask phone numbers for privacy. Fall back to demo mode without API key."
Why it worked:
- ✅ Push vs pull distinction implied — webhook = push model
- ✅ Privacy requirement stated (phone number masking)
- ✅ Multi-media handling specified (text, images, videos, location)
- ✅ Same pattern as X integration — consistent architecture
Session 14 — Accountability Chain
"Build a hierarchical official assignment system. Given an issue's location (pincode or lat/lng), resolve the chain: Ward Officer → Corporator → MLA → MP. Store jurisdictions, officials, departments, police stations in a DynamoDB-ready single-table schema. Seed data for Mumbai, Thane, Navi Mumbai. Add admin CRUD with CSV import/export. Frontend: AssignmentPanel showing the chain, NewModal with pincode/ward fields, a Reports tab for guided lookup."
Why it worked:
- ✅ Full data model described (4 entity types, hierarchy)
- ✅ DynamoDB constraint stated upfront (single-table schema)
- ✅ Seed data scope specified (3 cities, specific entity counts)
- ✅ Both backend and frontend described in one prompt
Session 15 — Instagram Integration
"Add Instagram integration — mirror the X/Twitter pattern exactly. Instagram Graph API
mentioned_mediaendpoint. igService.js with demo/live modes. IGFeed component mirroring WAFeed. Replace the 'Coming Soon' placeholder in SocialWall. Same ConvertModal flow. Tests mirroring xService.test.js."
Why it worked:
- ✅ "Mirror X pattern" — Claude reused architecture without reinventing
- ✅ Named the specific API endpoint (
mentioned_media) - ✅ Specified what to replace ("Coming Soon" placeholder)
- ✅ Test strategy stated (mirror existing test file)
Prompting Principles
Distilled from building CivicPulse:
1. State the full tech stack in the first message
Don't say "build a web app". Say "React 19 + Vite 7 frontend, Node/Express backend, DynamoDB, AWS S3, JWT auth". Claude will make architectural decisions that are consistent with your constraints rather than picking defaults.
2. Describe constraints, not just features
"Keep everything in one file for now" is a constraint. "No backend persistence in Phase 1 — client state only" is a constraint. Constraints prevent Claude from over-engineering.
3. Give the edge cases
"Users who are not admins should see a 'no access' screen, not an error" — Claude would have built generic 403 handling without this. Edge cases make the difference between a prototype and a product.
4. Research first, build later
When you're not sure how to build something, ask Claude to design it before writing any code. You'll often find the right answer is "not yet" — like deduplication, which became a well-documented future feature instead of half-built complexity.
5. Use mid-session refinements freely
You don't need a perfect prompt. Start with the main ask and add clarifications as the conversation continues. Claude holds full context — "also add X to that" is a completely valid prompting pattern.
6. Name what you're mocking
"This is Phase 1 — client state only, no backend persistence" prevents Claude from building a full DynamoDB table when you're just validating the UX. Explicitly naming the scope prevents over-engineering and keeps the codebase honest.
7. Parallel tasks save context
Group independent tasks in one message: "Add flagging AND location features AND update the project plan". Claude will implement all three concurrently, saving context window and reducing back-and-forth.
8. Ask for the architecture, not just the code
"How can we implement duplicate detection?" produced a better outcome than "Implement duplicate detection" would have. Getting the design first lets you decide if/when to build it.
Architecture Decisions Made With Claude
| Decision | Option A (chosen) | Option B (rejected) | Why |
|---|---|---|---|
| Component architecture | Single-file (civic-pulse.jsx ~4000 lines) | Multi-file components | Fast iteration — easier to see everything in context |
| Auth | JWT + httpOnly refresh cookie | Session-based | Stateless backend, scales horizontally |
| Admin auth | Separate JWT secret + sessionStorage | Reuse user JWT | Security isolation — admin compromise ≠ user compromise |
| Feature flags Phase 1 | localStorage | DB-only | Instant local feedback even if API is down |
| Flag state Phase 1 | React App-level state | DynamoDB | Validate UX before persisting — parked as Phase 2 |
| Deduplication | Parked as future feature | Build now | Complexity too high for current stage; design preserved |
| Icon library | Lucide React (ISC) | Material UI / heroicons | Lightweight, tree-shakeable, consistent stroke weight |
| Location UX | Explicit user trigger | Auto-request on load | Privacy-respecting — browser permission not asked silently |
The Mock Data Map
A key discipline in this project: everything mocked is explicitly labelled. When you hand-off to a backend engineer, they know exactly what to wire up.
| Mock | What it replaces | Where |
|---|---|---|
MOCK_USERS (4 users) | Real Google OAuth tokens | civic-pulse.jsx login screen |
INITIAL_ISSUES (7 issues) | DynamoDB civicpulse-issues table | App state on boot |
MOCK_MESSAGES | DynamoDB + Socket.IO | Chat panel per issue |
MOCK_TWEETS (4 tweets) | Twitter API v2 Bearer token search | X Feed tab |
MOCK_WA_MESSAGES (5 messages) | WhatsApp Business Cloud API webhook | WA Feed tab |
MOCK_IG_MENTIONS (5 posts) | Instagram Graph API mentioned_media | IG Feed tab |
| Simulated upload progress | Actual S3 presigned PUT | UploadZone component |
| Picsum.photos image URLs | Real uploaded S3 URLs | Issue cards + gallery |
| Hardcoded geo coordinates | Real EXIF GPS from photos | INITIAL_ISSUES[*].geo |
mediaFlags / issueFlags in state | civicpulse-media-flags DynamoDB table | App-level flag state |
Start Here — Exercises for Students
Try these prompts in a new Claude conversation to practice the patterns above:
Exercise 1 — The Big Bang
"Build a [your domain] app — [your stack]. Users can [core action 1], [core action 2], [core action 3]. Keep it in one file."
Exercise 2 — Research before building
"I want to add [complex feature] to my app. What are the tradeoffs between [approach A] and [approach B]? Design the architecture before writing any code."
Exercise 3 — Constraint-first
"Add [feature] — Phase 1, client-side only, no backend. Mark it clearly in the code as a mock. Phase 2 will persist to DynamoDB."
Exercise 4 — Mid-session clarification Start with a broad request, then add: "Actually, also make sure [edge case]. And while you're at it, update the docs to reflect this."
Exercise 5 — The audit prompt
"Look at our project plan and mark every row that uses mock or simulated data. Add a 'Mock Data' column with exactly what's mocked and what it replaces."
Key Stats
| Metric | Value |
|---|---|
| Total lines of code (frontend) | ~4,200 (single file) |
| Total lines of code (backend) | ~2,800 (multi-file) |
| Claude sessions to v1.0 | 15 |
| Features shipped | 75+ (see project plan) |
| Tests | 174 backend (10 suites) |
| Build time | ~1.2s (Vite 7) |
| Bundle size (gzip) | 148 kB |
| Docs pages | 20+ |
| Social integrations | 3 (X, WhatsApp, Instagram) |
What Claude Can't Do (and How to Handle It)
| Situation | What Happened | Resolution |
|---|---|---|
| Feature is too complex for one session | Dedup detection would require 3+ sessions to implement properly | Research first → document design → implement incrementally |
| Context window runs out | Session summarized mid-implementation | Restore context from summary, continue exactly where it left off |
| Ambiguous requirement | "Add flagging" could mean image or issue or both | Claude asked; user clarified; both were added |
| Mock vs real ambiguity | GPS coordinates — should we use real EXIF or hardcode? | Explicitly stated: "hardcode for mock, EXIF for production" |
Getting Started
- Clone the repo:
git clone https://github.com/yashmody/civicpulse - Read the project plan:
civicpulse-project-plan.csv— 63 rows, every feature with its mock status - Pick up a Planned item: sort by Status=Planned, find something that interests you
- Start a Claude session with the context from this doc and implement it
💡 The best way to learn is to extend an existing, well-documented project. CivicPulse is intentionally architected to be hackable.