CommonSight

A role-based community engagement platform that connects neighborhood reporting, mapping, campaigns, stories, events, and direct messaging.

Unique Value: CommonSight stands out by unifying fragmented community tools into a seamless workflow, enabling neighborhoods to transform real-time observations into coordinated action and impactful storytelling—something no other platform offers.

Problem

Community insight is often fragmented across separate reporting, communication, and planning tools. The goal was to create a single workflow where neighborhood observations can become coordinated action.

Project Snapshot

  • Platform: iOS (SwiftUI + Firebase)
  • Type: Civic/community collaboration app
  • Focus: Role-based workflows, shared data, community storytelling
  • Engagement: Internship project supporting a small business owner's investor demo
  • Team: Small collaboration
  • Timeline: Multi-phase design system initiative

Demo Video

Demo video: CommonSight app walkthrough.

Preview of CommonSight app in action.

Role

Product designer and iOS engineer responsible for information architecture, role-based flow design, SwiftUI implementation, and Firebase integration.

My Contributions

  • Implemented app-level navigation state with gated flows (splash, onboarding, invite/login, profile setup, main experience)
  • Built role-based access patterns across modules for residents, partners, and admins
  • Developed Firestore-backed workflows for observations, campaigns, users, and thread notifications
  • Implemented direct messaging thread UX with unread indicators and attachment support
  • Designed and shipped the narrative pipeline that transforms selected observations into story cards

Constraints

Support multiple user roles and permissions in one product while keeping workflows clear enough for everyday community use and reliable enough for investor-facing demos.

Key Decisions

  • Structured the product into five core tabs: Dashboard, Commons Atlas, Groundtruth, Narrative Alchemy, and Calendar
  • Connected authentication and invite-code logic to role/community-aware app behavior
  • Used Firestore listeners for near real-time updates to campaigns, users, and message notifications
  • Kept map/report/story modules loosely coupled through shared data models and environment-driven view models

Core Features

  • Groundtruth observation capture with category tagging, geolocation context, and optional photo evidence
  • Commons Atlas map overlay for community assets and observations with filter toggles and detail sheets
  • Narrative Alchemy story generation from selected observations and campaign orchestration tools
  • Community-scoped direct messaging with unread thread indicators and attachment support
  • Dashboard summaries for upcoming events, module entry points, and recent activity context

Technical Highlights

  • FirebaseAuth + Firestore integration with normalized community scoping for multi-user collaboration
  • Document listeners for campaigns/users/threads to keep UI in sync without manual refresh loops
  • Stable thread ID strategy and local read-state handling for reliable unread badge behavior
  • Typed model layer (`User`, `Campaign`, `Observation`, `Message`, `StoryCard`) for safer feature composition
  • Code Highlights

    FIRESTORE OBSERVATION MODEL Swift struct for capturing and syncing community observations in Firestore.
    struct Observation: Identifiable, Codable {
        let id: UUID
        var category: ObservationCategory
        var title: String
        var description: String
        var location: Coordinate 
        var locationName: String
        var timestamp: Date
        var userId: String
        var userName: String
        var photoURL: String?
        var status: ObservationStatus
    
        struct Coordinate: Codable {
            let latitude: Double
            let longitude: Double
        }
    
        enum ObservationCategory: String, Codable, CaseIterable {
            case hazard, vacantLot, communitySpace, business, opportunity
        }
    
        enum ObservationStatus: String, Codable {
            case active, reviewed, resolved
        }
    }
    
    FIRESTORE OBSERVATIONS LISTENER Swift function for real-time Firestore observation updates.
    func startObservationsListener(communityCode: String, isDemoMode: Bool = false) {
        observationsListener = db.collection("communities")
            .document(communityCode)
            .collection("observations")
            .addSnapshotListener { [weak self] snapshot, error in
                guard let self else { return }
                if let error {
                    self.errorMessage = "Failed to load observations."
                    self.observations = isDemoMode ? DemoData.allDemoObservations() : []
                    return
                }
                guard let snapshot else { return }
                let fetched = snapshot.documents.compactMap { document in
                    try? document.data(as: Observation.self)
                }
                self.observations = isDemoMode ? fetched + DemoData.allDemoObservations() : fetched
            }
    }
    
    THREAD NOTIFICATION LISTENER Swift function for real-time unread thread tracking in Firestore.
    func startListening(communityCode: String, currentUserId: String) {
        listener = db.collection("communities")
            .document(communityCode)
            .collection("threads")
            .addSnapshotListener { [weak self] snapshot, _ in
                guard let self, let snapshot else { return }
                var unread: Set = []
                for document in snapshot.documents {
                    let data = document.data()
                    let threadId = document.documentID
                    let participantIds = data["participantIds"] as? [String] ?? []
                    guard participantIds.contains(currentUserId) else { continue }
                    let senderId = data["lastMessageSenderId"] as? String
                    if senderId == currentUserId { continue }
                    let lastReadAt = (data["lastReadAt"] as? [String: Timestamp])?[currentUserId]
                    let lastMessageAt = data["lastMessageAt"] as? Timestamp
                    if let lastReadAt, let lastMessageAt, lastReadAt.dateValue() < lastMessageAt.dateValue() {
                        unread.insert(threadId)
                    }
                }
                self.unreadThreadIds = unread
            }
    }
    
    GENERATE STORY CARD FROM OBSERVATION Swift function that transforms an observation into a narrative story card for community action.
    func generateStoryCard(from observation: Observation) -> StoryCard {
        let context = "Observation: \(observation.title)\nCategory: \(observation.category.rawValue)\nLocation: \(observation.locationName)\nDescription: \(observation.description)"
        let callToAction = "Take action: \(observation.status == .active ? "Report or resolve" : "Reviewed")"
        return StoryCard(
            id: UUID(),
            context: context,
            callToAction: callToAction,
            coalition: "Neighborhood Coalition",
            createdAt: Date()
        )
    }
    

    Outcome

    CommonSight evolved into a unified civic workflow rather than separate utilities. The app demonstrates full-stack product thinking on iOS—combining data capture, geospatial context, collaboration, and campaign storytelling into one coherent experience.

    Key Screens

    CommonSight Story Card screen
    Story Card Screen
    The Story Card screen lets users turn field observations into actionable stories, complete with context, call-to-action, and coalition details. Designed for clarity and impact, it helps communities organize and communicate local needs.
    CommonSight Dashboard screen
    Dashboard Screen
    The Dashboard screen provides a summary of coalitions, active campaigns, and recent story cards. It gives users a quick overview of community activity and entry points to deeper engagement.
    CommonSight Create Story screen
    Create Story Screen
    The Create Story screen allows users to select observations and generate new story cards. It supports tagging opportunities, locations, and contributors, streamlining the process of community storytelling.
    CommonSight Messages screen
    Messages Screen
    The Messages screen enables direct communication between coalition partners, with support for real-time updates and attachment sharing. It keeps teams aligned and responsive to community needs.
    CommonSight Profile screen
    Profile Screen
    The Profile screen lets users manage their role, neighborhood, and contact information. It supports role-based access and ensures community members are properly identified and connected.

    Next Iteration

    Planned next steps include richer analytics for campaign impact, stronger moderation workflows, and deeper offline resilience for field reporting scenarios.