Skip to main content

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 OutcomeBundle 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 guideseparate 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 /admin route completely isolated from the main app. Email + password login, NOT Google OAuth. Its own JWT secret (ADMIN_JWT_SECRET). Token stored in sessionStorage so 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:

  1. Image flagging (gallery + lightbox)
  2. Issue flagging (ICard + Detail view, separate column from upvote)
  3. Location browsing (Near Me sort, distance badges, nearby count)
  4. 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_media endpoint. 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

DecisionOption A (chosen)Option B (rejected)Why
Component architectureSingle-file (civic-pulse.jsx ~4000 lines)Multi-file componentsFast iteration — easier to see everything in context
AuthJWT + httpOnly refresh cookieSession-basedStateless backend, scales horizontally
Admin authSeparate JWT secret + sessionStorageReuse user JWTSecurity isolation — admin compromise ≠ user compromise
Feature flags Phase 1localStorageDB-onlyInstant local feedback even if API is down
Flag state Phase 1React App-level stateDynamoDBValidate UX before persisting — parked as Phase 2
DeduplicationParked as future featureBuild nowComplexity too high for current stage; design preserved
Icon libraryLucide React (ISC)Material UI / heroiconsLightweight, tree-shakeable, consistent stroke weight
Location UXExplicit user triggerAuto-request on loadPrivacy-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.

MockWhat it replacesWhere
MOCK_USERS (4 users)Real Google OAuth tokenscivic-pulse.jsx login screen
INITIAL_ISSUES (7 issues)DynamoDB civicpulse-issues tableApp state on boot
MOCK_MESSAGESDynamoDB + Socket.IOChat panel per issue
MOCK_TWEETS (4 tweets)Twitter API v2 Bearer token searchX Feed tab
MOCK_WA_MESSAGES (5 messages)WhatsApp Business Cloud API webhookWA Feed tab
MOCK_IG_MENTIONS (5 posts)Instagram Graph API mentioned_mediaIG Feed tab
Simulated upload progressActual S3 presigned PUTUploadZone component
Picsum.photos image URLsReal uploaded S3 URLsIssue cards + gallery
Hardcoded geo coordinatesReal EXIF GPS from photosINITIAL_ISSUES[*].geo
mediaFlags / issueFlags in statecivicpulse-media-flags DynamoDB tableApp-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

MetricValue
Total lines of code (frontend)~4,200 (single file)
Total lines of code (backend)~2,800 (multi-file)
Claude sessions to v1.015
Features shipped75+ (see project plan)
Tests174 backend (10 suites)
Build time~1.2s (Vite 7)
Bundle size (gzip)148 kB
Docs pages20+
Social integrations3 (X, WhatsApp, Instagram)

What Claude Can't Do (and How to Handle It)

SituationWhat HappenedResolution
Feature is too complex for one sessionDedup detection would require 3+ sessions to implement properlyResearch first → document design → implement incrementally
Context window runs outSession summarized mid-implementationRestore context from summary, continue exactly where it left off
Ambiguous requirement"Add flagging" could mean image or issue or bothClaude asked; user clarified; both were added
Mock vs real ambiguityGPS coordinates — should we use real EXIF or hardcode?Explicitly stated: "hardcode for mock, EXIF for production"

Getting Started

  1. Clone the repo: git clone https://github.com/yashmody/civicpulse
  2. Read the project plan: civicpulse-project-plan.csv — 63 rows, every feature with its mock status
  3. Pick up a Planned item: sort by Status=Planned, find something that interests you
  4. 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.