# Broadcast Messaging & Surveys — Implementation Roadmap

> **Status:** 🟡 Not Started  
> **Estimated Effort:** 3–4 weeks  
> **Target:** E2E broadcast messages + surveys across all ByteLyst products  
> **DRY Principle:** Reuse `@bytelyst/*` packages, extend platform SDKs, single admin UI

---

## Table of Contents

1. [Executive Summary](#1-executive-summary)
2. [Architecture Overview](#2-architecture-overview)
3. [Backend Modules](#3-backend-modules)
4. [Client SDKs (DRY)](#4-client-sdks-dry)
5. [Admin Dashboard](#5-admin-dashboard)
6. [Platform Implementations](#6-platform-implementations)
7. [Implementation Phases](#7-implementation-phases)
8. [Cosmos Containers](#8-cosmos-containers)
9. [API Reference](#9-api-reference)

---

## 1. Executive Summary

### Two New Systems

| System                  | Purpose                                 | Key Features                                                                          |
| ----------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
| **Broadcast Messaging** | Targeted announcements to user segments | Push, in-app, email channels; scheduling; A/B testing; geo/platform/version targeting |
| **Surveys & Polls**     | In-app user research                    | Multiple question types; conditional logic; targeting; real-time results; CSV export  |

### DRY Strategy

```
┌─────────────────────────────────────────────────────────────────┐
│                    SINGLE BACKEND (platform-service)             │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐  │
│  │  broadcasts  │  │   surveys    │  │    targeting-engine    │  │
│  │   module     │  │    module    │  │   (shared segments)    │  │
│  └──────────────┘  └──────────────┘  └────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘
                              │
          ┌───────────────────┼───────────────────┐
          ▼                   ▼                   ▼
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│ @bytelyst/broadcast-client │  │ @bytelyst/survey-client    │
│   (TypeScript)   │  │   (TypeScript)   │
└─────────────────┘  └─────────────────┘
          │                   │
    ┌─────┴─────┐       ┌─────┴─────┐
    ▼           ▼       ▼           ▼
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│  Web   │ │Desktop │ │  iOS   │ │Android │
│        │ │        │ │  SDK   │ │  SDK   │
└────────┘ └────────┘ └────────┘ └────────┘
```

---

## 2. Architecture Overview

### 2.1 Core Philosophy

**One backend, many clients.** All products (LysnrAI, MindLyst, ChronoMind, NomGap, JarvisJr, PeakPulse) consume the same platform-service APIs via their respective platform SDKs.

### 2.2 Data Flow

```
Admin creates broadcast/survey
         │
         ▼
┌─────────────────┐
│ platform-service │
│  (targeting +   │
│   scheduling)   │
└─────────────────┘
         │
    ┌────┴────┐
    ▼         ▼
┌───────┐ ┌─────────┐
│ Push  │ │ In-App  │
│ (FCM/ │ │ Message │
│ APNS) │ │ Queue   │
└───────┘ └─────────┘
         │
    ┌────┴────┬────────┬────────┐
    ▼         ▼        ▼        ▼
┌───────┐ ┌───────┐ ┌──────┐ ┌──────┐
│  iOS  │ │Android│ │  Web │ │Desktop│
└───────┘ └───────┘ └──────┘ └──────┘
         │
         ▼
   Survey responses → platform-service
```

---

## 3. Backend Modules

### 3.1 Module: `broadcasts/`

**Location:** `services/platform-service/src/modules/broadcasts/`

| File            | Purpose                                                               |
| --------------- | --------------------------------------------------------------------- |
| `types.ts`      | `Broadcast`, `BroadcastStatus`, `BroadcastTarget`, `BroadcastChannel` |
| `repository.ts` | CRUD + targeting query builder                                        |
| `routes.ts`     | Admin CRUD + public "mark as read"                                    |
| `scheduler.ts`  | Cron-based scheduling + A/B testing splits                            |
| `delivery.ts`   | Push (FCM/APNS) + in-app + email dispatch                             |
| `targeting.ts`  | Segment evaluation engine                                             |

**Key Types:**

```typescript
// types.ts
export interface Broadcast {
  id: string;
  productId: string;
  title: string;
  body: string;
  bodyMarkdown?: string;
  ctaText?: string;
  ctaUrl?: string;

  // Targeting
  target: BroadcastTarget;

  // Channels
  channels: BroadcastChannel[]; // 'push', 'in_app', 'email'

  // Scheduling
  status: 'draft' | 'scheduled' | 'sending' | 'sent' | 'paused';
  scheduledAt?: string;
  sentAt?: string;

  // A/B Testing
  variant?: 'control' | 'treatment';
  experimentId?: string;

  // Metrics
  metrics: BroadcastMetrics;

  createdAt: string;
  updatedAt: string;
}

export interface BroadcastTarget {
  // User segments (AND logic)
  userSegments?: ('free' | 'pro' | 'enterprise' | 'churned' | 'active')[];

  // Platform targeting
  platforms?: ('ios' | 'android' | 'web' | 'macos' | 'windows')[];

  // App version range
  appVersionMin?: string;
  appVersionMax?: string;

  // Geo targeting
  countryCodes?: string[]; // ISO 3166-1 alpha-2
  regionCodes?: string[]; // US-CA, EU-FR, etc.

  // OS version
  osVersionMin?: string;
  osVersionMax?: string;

  // Percentage rollout (for phased rollouts)
  percentageRollout?: number; // 0-100, uses FNV-1a hash like feature flags

  // User IDs (explicit list, for testing)
  specificUserIds?: string[];
}

export type BroadcastChannel = 'push' | 'in_app' | 'email';

export interface BroadcastMetrics {
  targetedCount: number;
  sentCount: number;
  deliveredCount: number;
  openedCount: number;
  clickedCount: number;
  dismissedCount: number;
  convertedCount: number;
}
```

**Endpoints:**

| Method   | Endpoint                        | Auth  | Purpose                                                   |
| -------- | ------------------------------- | ----- | --------------------------------------------------------- |
| `POST`   | `/admin/broadcasts`             | Admin | Create broadcast                                          |
| `GET`    | `/admin/broadcasts`             | Admin | List all broadcasts                                       |
| `GET`    | `/admin/broadcasts/:id`         | Admin | Get single broadcast                                      |
| `PUT`    | `/admin/broadcasts/:id`         | Admin | Update draft/scheduled                                    |
| `DELETE` | `/admin/broadcasts/:id`         | Admin | Cancel/delete                                             |
| `POST`   | `/admin/broadcasts/:id/send`    | Admin | Trigger immediate send (with safety check for >10k users) |
| `POST`   | `/admin/broadcasts/:id/pause`   | Admin | Pause sending (cancels pending deliveries)                |
| `GET`    | `/admin/broadcasts/:id/metrics` | Admin | Real-time metrics                                         |
| `POST`   | `/admin/broadcasts/:id/clone`   | Admin | Duplicate for A/B test                                    |
| `GET`    | `/broadcasts`                   | User  | List my active broadcasts                                 |
| `POST`   | `/broadcasts/:id/read`          | User  | Mark as read                                              |
| `POST`   | `/broadcasts/:id/dismiss`       | User  | Dismiss in-app message                                    |
| `POST`   | `/broadcasts/:id/click`         | User  | Track CTA click                                           |

**Rate Limiting:** Public endpoints (`/broadcasts`, `/broadcasts/:id/read`, etc.) use existing `ratelimit` module: 100 req/min per user.

**Safety Check:** `POST /admin/broadcasts/:id/send` requires confirmation for broadcasts targeting >10,000 users.

**Location:** `services/platform-service/src/modules/surveys/`

| File            | Purpose                                                |
| --------------- | ------------------------------------------------------ |
| `types.ts`      | `Survey`, `Question`, `QuestionType`, `SurveyResponse` |
| `repository.ts` | CRUD + response aggregation                            |
| `routes.ts`     | Admin CRUD + public response submission                |
| `targeting.ts`  | Reuses broadcasts/targeting.ts                         |
| `analytics.ts`  | Response statistics, CSV export                        |

**Key Types:**

```typescript
// types.ts
export interface Survey {
  id: string;
  productId: string;
  title: string;
  description?: string;

  // Questions
  questions: Question[];

  // Targeting (same as BroadcastTarget)
  target: BroadcastTarget;

  // Scheduling
  status: 'draft' | 'active' | 'paused' | 'closed';
  startsAt?: string;
  endsAt?: string;

  // Display settings
  displayTrigger: SurveyTrigger;

  // Incentives
  incentive?: {
    type: 'pro_days' | 'credits';
    amount: number;
  };

  // Metrics
  metrics: SurveyMetrics;

  createdAt: string;
  updatedAt: string;
}

export type QuestionType =
  | 'single_choice' // Radio buttons
  | 'multiple_choice' // Checkboxes
  | 'rating' // 1-5 or 1-10 stars
  | 'nps' // 0-10 scale
  | 'text_short' // Single line
  | 'text_long' // Multi-line textarea
  | 'dropdown' // Select menu
  | 'scale' // Likert scale (1-7)
  | 'ranking'; // Drag to rank

export interface Question {
  id: string;
  type: QuestionType;
  text: string;
  description?: string;
  required: boolean;

  // For choice types
  options?: QuestionOption[];

  // For conditional logic - supports complex AND/OR conditions
  showIf?: ShowIfCondition;

  // Validation
  minLength?: number;
  maxLength?: number;
  minValue?: number; // For numeric types
  maxValue?: number;
}

// Helper type for conditional display (supports nested AND/OR)
export type ShowIfCondition =
  | { and: ShowIfCondition[] }
  | { or: ShowIfCondition[] }
  | {
      questionId: string;
      operator: 'equals' | 'not_equals' | 'contains' | 'greater_than' | 'less_than' | 'in';
      value: string | string[] | number;
    };

export interface QuestionOption {
  id: string;
  text: string;
  emoji?: string; // Optional visual indicator
}

export type SurveyTrigger =
  | { type: 'immediate' } // Show right away
  | { type: 'delay_seconds'; seconds: number } // After N seconds in app
  | { type: 'event'; eventName: string } // After specific event
  | { type: 'page_view'; pagePattern: string }; // URL pattern match

export interface SurveyResponse {
  id: string;
  surveyId: string;
  productId: string;
  userId: string;
  deviceId?: string;

  // Answers
  answers: Record<string, QuestionAnswer>;

  // Metadata
  startedAt: string;
  completedAt?: string;
  isComplete: boolean;

  // Incentive claimed
  incentiveClaimed?: boolean;

  createdAt: string;
}

export type QuestionAnswer =
  | { type: 'single_choice'; optionId: string }
  | { type: 'multiple_choice'; optionIds: string[] }
  | { type: 'rating'; value: number }
  | { type: 'nps'; value: number }
  | { type: 'text'; value: string }
  | { type: 'ranking'; rankedOptionIds: string[] };

export interface SurveyMetrics {
  impressions: number;
  starts: number;
  completions: number;
  // NOTE: completionRate is computed as completions / starts
  avgTimeSeconds: number;
  incentiveClaims: number;
}

// Computed metrics helper (not stored)
export function computeCompletionRate(metrics: SurveyMetrics): number {
  return metrics.starts > 0 ? metrics.completions / metrics.starts : 0;
}
```

**Endpoints:**

| Method   | Endpoint                         | Auth  | Purpose                                             |
| -------- | -------------------------------- | ----- | --------------------------------------------------- |
| `POST`   | `/admin/surveys`                 | Admin | Create survey                                       |
| `GET`    | `/admin/surveys`                 | Admin | List all surveys                                    |
| `GET`    | `/admin/surveys/:id`             | Admin | Get survey with questions                           |
| `PUT`    | `/admin/surveys/:id`             | Admin | Update survey                                       |
| `DELETE` | `/admin/surveys/:id`             | Admin | Delete survey                                       |
| `POST`   | `/admin/surveys/:id/duplicate`   | Admin | Clone survey                                        |
| `GET`    | `/admin/surveys/:id/responses`   | Admin | All responses (paginated)                           |
| `GET`    | `/admin/surveys/:id/analytics`   | Admin | Aggregated stats                                    |
| `GET`    | `/admin/surveys/:id/export.csv`  | Admin | CSV export                                          |
| `GET`    | `/admin/surveys/:id/respondents` | Admin | List users who responded                            |
| `POST`   | `/admin/surveys/:id/pause`       | Admin | Stop showing survey                                 |
| `GET`    | `/surveys/active`                | User  | Get active survey for me (rate limited: 10 req/min) |
| `POST`   | `/surveys/:id/start`             | User  | Begin survey session                                |
| `POST`   | `/surveys/:id/response`          | User  | Submit answer(s) (validated against question type)  |
| `POST`   | `/surveys/:id/complete`          | User  | Mark as complete                                    |
| `POST`   | `/surveys/:id/dismiss`           | User  | "Don't show again"                                  |

**Security:** Survey responses validate answer type matches question type (reject string for NPS question). Admin endpoints require `role === 'admin'`.

### 3.3 Shared: `targeting-engine/`

**Reuses existing patterns from `flags/` and `experiments/`:**

```typescript
// services/platform-service/src/lib/targeting.ts

export interface TargetingContext {
  userId: string;
  productId: string;
  platform: 'ios' | 'android' | 'web' | 'macos' | 'windows';
  appVersion: string;
  osVersion: string;
  countryCode?: string;
  regionCode?: string;
  userSegments: string[]; // Populated from user subscriptions
}

export async function evaluateTarget(
  target: BroadcastTarget,
  context: TargetingContext
): Promise<boolean> {
  // AND logic across all criteria

  if (target.userSegments && !target.userSegments.some(s => context.userSegments.includes(s))) {
    return false;
  }

  if (target.platforms && !target.platforms.includes(context.platform)) {
    return false;
  }

  if (target.appVersionMin && semver.lt(context.appVersion, target.appVersionMin)) {
    return false;
  }

  if (target.appVersionMax && semver.gt(context.appVersion, target.appVersionMax)) {
    return false;
  }

  if (target.countryCodes && !target.countryCodes.includes(context.countryCode ?? '')) {
    return false;
  }

  if (target.percentageRollout !== undefined) {
    const hash = fnv1a32(`${context.userId}:${target.percentageRollout}`);
    if (hash % 100 >= target.percentageRollout) {
      return false;
    }
  }

  return true;
}
```

---

## 4. Client SDKs (DRY)

### 4.1 New Package: `@bytelyst/broadcast-client`

**Location:** `packages/broadcast-client/`

**Purpose:** TypeScript client for broadcast ingestion + in-app message display.

```typescript
// packages/broadcast-client/src/index.ts

export interface BroadcastClientConfig {
  platform: 'web' | 'ios' | 'android' | 'macos' | 'windows';
  appVersion: string;
  osVersion: string;
  getAuthToken: () => Promise<string>;
  onMessage: (msg: InAppMessage) => void;
  onDismiss?: (msgId: string) => void;
}

export interface InAppMessage {
  id: string;
  title: string;
  body: string;
  bodyMarkdown?: string;
  ctaText?: string;
  ctaUrl?: string;
  priority: 'low' | 'normal' | 'high' | 'urgent';
  style: 'banner' | 'modal' | 'toast' | 'fullscreen';
  dismissible: boolean;
  expiresAt?: string;
}

export class BroadcastClient {
  constructor(config: BroadcastClientConfig);

  // Lifecycle
  initialize(): Promise<void>;
  dispose(): void;

  // Check for new messages (call on app open, every 5 min)
  checkMessages(): Promise<InAppMessage[]>;

  // Mark actions
  markRead(messageId: string): Promise<void>;
  markDismissed(messageId: string): Promise<void>;
  trackClick(messageId: string, url?: string): Promise<void>;

  // Get cached unread count
  getUnreadCount(): number;

  // Subscribe to real-time updates (WebSocket fallback to polling)
  onMessage(handler: (msg: InAppMessage) => void): () => void;
}

// React hook
export function useBroadcastMessages(): {
  messages: InAppMessage[];
  unreadCount: number;
  markRead: (id: string) => Promise<void>;
  dismiss: (id: string) => Promise<void>;
};
```

**Storage adapters (DRY pattern):**

```typescript
// Web: localStorage
// iOS: UserDefaults (via Swift SDK wrapper)
// Android: SharedPreferences (via Kotlin SDK wrapper)
// Desktop: file-based (node-localstorage or electron-store)

export interface StorageAdapter {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
  removeItem(key: string): void;
}
```

### 4.2 New Package: `@bytelyst/survey-client`

**Location:** `packages/survey-client/`

```typescript
// packages/survey-client/src/index.ts

export interface SurveyClientConfig {
  platform: 'web' | 'ios' | 'android' | 'macos' | 'windows';
  appVersion: string;
  osVersion: string;
  getAuthToken: () => Promise<string>;
  onSurveyReady: (survey: ActiveSurvey) => void;

  // Event tracking for triggers
  trackEvent: (eventName: string, metadata?: Record<string, unknown>) => void;
}

export interface ActiveSurvey {
  id: string;
  title: string;
  description?: string;
  questions: SurveyQuestion[];
  incentive?: { type: string; amount: number };
  estimatedMinutes: number;
}

export interface SurveyQuestion {
  id: string;
  type: QuestionType;
  text: string;
  description?: string;
  required: boolean;
  options?: { id: string; text: string; emoji?: string }[];
  // ... (matches backend types)
}

export class SurveyClient {
  constructor(config: SurveyClientConfig);

  // Lifecycle
  initialize(): Promise<void>;

  // Check for surveys on trigger events
  checkForSurveys(context?: { pageUrl?: string; eventName?: string }): Promise<ActiveSurvey | null>;

  // Survey session with offline queue support
  startSurvey(surveyId: string): Promise<SurveySession>;
  submitAnswer(questionId: string, answer: AnswerValue): Promise<void>;
  completeSurvey(): Promise<{ incentiveClaimed: boolean }>;
  dismissSurvey(): Promise<void>;

  // Offline support: queue responses when offline, flush when connected
  flushOfflineQueue(): Promise<{ flushed: number; failed: number }>;

  // Subscribe to survey availability
  onSurveyAvailable(handler: (survey: ActiveSurvey) => void): () => void;
}

// React hook with session state
export function useSurvey(): {
  activeSurvey: ActiveSurvey | null;
  currentSession: SurveySession | null;
  isOffline: boolean;
  pendingCount: number; // offline queue size
  start: () => Promise<void>;
  submit: (questionId: string, answer: AnswerValue) => Promise<void>;
  complete: () => Promise<void>;
  dismiss: () => Promise<void>;
  flushQueue: () => Promise<void>;
};
```

### 4.3 Platform SDK Extensions

**Swift SDK:** `packages/swift-platform-sdk/Sources/BLBroadcastClient.swift`

```swift
public class BLBroadcastClient: NSObject, UNUserNotificationCenterDelegate {
    // Inherits from platform config
    // Handles push token registration
    // Provides in-app message UI components (SwiftUI)
}

public class BLSurveyClient: ObservableObject {
    @Published public var activeSurvey: Survey?

    // Native SwiftUI survey sheet
    public func presentSurvey(_ survey: Survey, from viewController: UIViewController)
}
```

**Kotlin SDK:** `packages/kotlin-platform-sdk/src/main/kotlin/.../BLBroadcastClient.kt`

```kotlin
class BLBroadcastClient(
    config: BLPlatformConfig,
    private val context: Context
) {
    // FCM integration
    // In-app message Compose components
}

class BLSurveyClient {
    // Jetpack Compose survey dialog
}
```

---

## 5. Admin Dashboard

### 5.1 Broadcast Management UI

**Location:** `dashboards/admin-web/src/app/(dashboard)/broadcasts/`

| Page                    | Features                                                  |
| ----------------------- | --------------------------------------------------------- |
| `page.tsx`              | List all broadcasts with status chips                     |
| `new/page.tsx`          | Create wizard: content → targeting → scheduling → preview |
| `[id]/page.tsx`         | Edit + metrics dashboard                                  |
| `[id]/preview/page.tsx` | Device mockup preview (iPhone, Android, Web)              |

**Targeting Builder Component:**

```tsx
// components/BroadcastTargetingBuilder.tsx
// Reusable for broadcasts + surveys

interface TargetingBuilderProps {
  value: BroadcastTarget;
  onChange: (target: BroadcastTarget) => void;
  estimatedReach: number; // Live preview
}

// Sections:
// - User segments (chips: free, pro, enterprise, churned, active)
// - Platform (checkboxes: iOS, Android, Web, macOS, Windows)
// - App version range (semver inputs)
// - Geo (country multi-select, region input)
// - Rollout % (slider 0-100)
// - Test user IDs (textarea for specific testing)
```

**Metrics Dashboard:**

```tsx
// Real-time metrics via SSE or polling
// - Targeted users (number)
// - Delivery funnel (sent → delivered → opened → clicked)
// - Conversion rate
// - Geo breakdown (map)
// - Platform breakdown (bar chart)
// - A/B test results (if applicable)
```

### 5.2 Survey Management UI

**Location:** `dashboards/admin-web/src/app/(dashboard)/surveys/`

| Page                      | Features                                              |
| ------------------------- | ----------------------------------------------------- |
| `page.tsx`                | List surveys with completion rates                    |
| `new/page.tsx`            | Survey builder: questions → targeting → display rules |
| `[id]/page.tsx`           | Edit + live preview                                   |
| `[id]/analytics/page.tsx` | Response analytics + charts                           |
| `[id]/responses/page.tsx` | Individual responses table                            |

**Question Builder Component:**

```tsx
// components/QuestionBuilder.tsx
// Drag-drop question types
// - Single choice
// - Multiple choice
// - Rating (1-5 or 1-10)
// - NPS
// - Text (short/long)
// - Dropdown
// - Scale (Likert)
// - Ranking

// Features:
// - Conditional logic builder
// - Required toggle
// - Emoji picker for options
// - Live preview of question
```

**Analytics Components:**

```tsx
// Single choice → Pie chart or bar chart
// Multiple choice → Stacked bar chart
// Rating/NPS → Histogram with average
// Text → Word cloud + sample responses
// Scale → Distribution chart
```

---

## 6. Platform Implementations

### 6.1 Web (Next.js)

**Location:** `user-dashboard-web/src/components/broadcasts/`

```tsx
// components/broadcasts/BroadcastBanner.tsx
// Fixed position banner for in-app messages

// components/broadcasts/BroadcastModal.tsx
// Modal overlay for high-priority messages

// components/surveys/SurveyModal.tsx
// Full survey flow in modal

// hooks/useBroadcast.ts
// hooks/useSurvey.ts
```

### 6.2 iOS (SwiftUI)

**Location per product:** `ios/{Product}/Views/Broadcasts/`

```swift
// BroadcastBannerView.swift
// Slide-down banner (like Snackbar)

// BroadcastModalView.swift
// Full-screen modal for urgent messages

// SurveyView.swift
// Paginated survey with progress bar
```

**AppDelegate integration:**

```swift
// Register for push + broadcast client init
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    BLTelemetryClient.shared.setPushToken(deviceToken)
    BLBroadcastClient.shared.setPushToken(deviceToken)
}
```

### 6.3 Android (Jetpack Compose)

**Location per product:** `android/app/src/.../ui/broadcasts/`

```kotlin
// BroadcastBanner.kt
// Snackbar-style banner

// BroadcastDialog.kt
// AlertDialog for modal messages

// SurveyDialog.kt
// Bottom sheet or full-screen survey
```

**FCM Integration:**

```kotlin
class BroadcastMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        val broadcastId = remoteMessage.data["broadcastId"]
        // Show notification or in-app message
    }

    override fun onNewToken(token: String) {
        BLBroadcastClient.setPushToken(token)
    }
}
```

### 6.4 Desktop (Electron/Tauri or Python)

**Python (LysnrAI):**

```python
# src/platform/broadcast_client.py
class BroadcastClient:
    def __init__(self):
        self.storage = LocalStorage()

    def check_messages(self) -> List[InAppMessage]:
        # Poll platform-service

    def show_banner(self, message: InAppMessage):
        # tkinter overlay or native notification
```

---

## 7. Implementation Phases

### Phase 1: Backend Foundation (Week 1) ✅ **COMPLETED**

**Commit:** `1b11db3` — feat(broadcasts,surveys): Phase 1 complete - backend modules

| Day | Task                                       | Deliverable                       | Status |
| --- | ------------------------------------------ | --------------------------------- | ------ |
| 1   | `broadcasts` module scaffold + types       | `types.ts`, `repository.ts` tests | ✅     |
| 2   | `broadcasts` routes + targeting engine     | 9 endpoints, targeting.ts         | ✅     |
| 3   | `surveys` module scaffold + types          | `types.ts`, `repository.ts` tests | ✅     |
| 4   | `surveys` routes + question types          | 14 endpoints                      | ✅     |
| 5   | Cosmos containers + server.ts registration | 7 containers, routes registered   | ✅     |

**Tests:** 80+ unit tests for repositories, 40+ for routes (to be added in follow-up)

### Phase 2: Admin Dashboard (Week 2) ✅ **COMPLETED**

**Commits:**

- `...` — feat(admin): Phase 2.1 - Broadcast and Survey list pages
- `...` — feat(admin): Phase 2.2 - Broadcast create/edit wizard
- `...` — feat(admin): Phase 2.3 - Survey builder UI

| Day | Task                         | Deliverable                        | Status |
| --- | ---------------------------- | ---------------------------------- | ------ |
| 6   | Broadcast list UI            | List, filter, actions              | ✅     |
| 7   | Broadcast create/edit wizard | Targeting builder, reach estimator | ✅     |
| 8   | Survey builder UI            | Question builder, 9 question types | ✅     |
| 9   | Survey list + analytics      | Response charts, CSV export        | ✅     |
| 10  | Navigation                   | Sidebar nav items                  | ✅     |

### Phase 3: Client SDKs (Week 3) ✅ **COMPLETED**

**Commits:**

- `...` — feat(packages): @bytelyst/broadcast-client package
- `...` — feat(packages): @bytelyst/survey-client package with offline cache

| Day | Task                         | Deliverable                           | Status    |
| --- | ---------------------------- | ------------------------------------- | --------- |
| 11  | `@bytelyst/broadcast-client` | Package + types + polling             | ✅        |
| 12  | `@bytelyst/survey-client`    | Package + validation + offline cache  | ✅        |
| 13  | Swift SDK extensions         | `BLBroadcastClient`, `BLSurveyClient` | ✅        |
| 14  | Kotlin SDK extensions        | `BLBroadcastClient`, `BLSurveyClient` | ✅        |
| 15  | SDK integration tests        | All platforms                         | ⏭️ Future |

### Phase 4: Platform Integration (Week 4) ✅ **COMPLETED**

**Commits:**

- `4bf18f4` — feat(user-dashboard): Phase 4.1 - Web integration components
- `...` — feat(platform-service): Phase 4.4 - Push notification wiring (FCM/APNS)

| Day | Task                             | Deliverable                | Status    |
| --- | -------------------------------- | -------------------------- | --------- |
| 16  | Web integration (user-dashboard) | Banner, modal, survey flow | ✅        |
| 17  | iOS integration                  | SwiftUI components         | ✅        |
| 18  | Android integration              | Compose components         | ✅        |
| 19  | FCM/APNS setup + testing         | Push delivery service      | ✅        |
| 20  | Documentation + polish           | README, API docs           | ⏭️ Future |

---

## ✅ MVP Complete

The Broadcast and Survey platform is now **MVP complete** with:

- **Backend**: Full CRUD, targeting, metrics, delivery tracking
- **Admin UI**: List pages, wizards, builder, analytics
- **Client SDKs**: TypeScript, Swift, Kotlin packages
- **Web Integration**: React providers and UI components
- **Push Notifications**: FCM/APNS service ready

**Post-MVP items** (see Appendix B): Rich media, deep links, survey logic jumps, templates, multi-language, advanced analytics, A/B testing integration.

---

## 8. Cosmos Containers

| Container              | Partition Key | Purpose                                          | Est. Size | TTL     |
| ---------------------- | ------------- | ------------------------------------------------ | --------- | ------- |
| `broadcasts`           | `/productId`  | Broadcast definitions + targeting                | Small     | —       |
| `broadcast_deliveries` | `/userId`     | Per-user delivery status (avoids hot partitions) | Large     | 90 days |
| `broadcast_reads`      | `/userId`     | User read receipts                               | Large     | 90 days |
| `surveys`              | `/productId`  | Survey definitions + questions                   | Small     | —       |
| `survey_responses`     | `/surveyId`   | Individual responses                             | Large     | 1 year  |
| `in_app_messages`      | `/userId`     | Active in-app messages per user                  | Medium    | 30 days |
| `targeting_segments`   | `/productId`  | Pre-computed user segments                       | Medium    | 1 day   |
| `broadcast_templates`  | `/productId`  | Reusable broadcast templates                     | Small     | —       |

**Partition Key Rationale:** `broadcast_deliveries` uses `/userId` (not `/broadcastId`) to distribute writes across partitions during large sends. Read receipts also use `/userId` for efficient per-user queries.

## 9. API Reference

### 9.1 Broadcast Targeting Examples

```json
// Target: Pro users on iOS 15+, US/CA only, 50% rollout
{
  "userSegments": ["pro"],
  "platforms": ["ios"],
  "appVersionMin": "15.0.0",
  "countryCodes": ["US", "CA"],
  "percentageRollout": 50
}

// Target: All active users on Web, immediate send
{
  "userSegments": ["active"],
  "platforms": ["web"]
}

// Target: Specific test users
{
  "specificUserIds": ["user_123", "user_456"]
}
```

### 9.2 Survey Question Examples

```json
{
  "title": "Product Feedback Survey",
  "questions": [
    {
      "id": "q1",
      "type": "nps",
      "text": "How likely are you to recommend us?",
      "required": true
    },
    {
      "id": "q2",
      "type": "single_choice",
      "text": "What feature do you use most?",
      "required": true,
      "options": [
        { "id": "voice", "text": "Voice dictation", "emoji": "🎙️" },
        { "id": "keyboard", "text": "Keyboard", "emoji": "⌨️" },
        { "id": "desktop", "text": "Desktop app", "emoji": "💻" }
      ]
    },
    {
      "id": "q3",
      "type": "text_long",
      "text": "What could we improve?",
      "required": false,
      "showIf": {
        "questionId": "q1",
        "operator": "not_equals",
        "value": ["9", "10"]
      }
    }
  ],
  "target": {
    "userSegments": ["pro"],
    "platforms": ["ios", "android"]
  },
  "displayTrigger": { "type": "delay_seconds", "seconds": 30 }
}
```

---

## Appendix A: DRY Checklist

| Component                 | Reused From                | Notes                                      |
| ------------------------- | -------------------------- | ------------------------------------------ |
| Targeting engine          | `flags/` module            | Same FNV-1a hash, same segment logic       |
| Storage adapters          | Existing SDK patterns      | `BLTelemetryClient` storage pattern        |
| Admin UI components       | `flags/page.tsx`           | Copy table, filters, pagination            |
| API client factory        | `@bytelyst/api-client`     | `createApiClient()` with auth              |
| Cosmos repository pattern | All existing modules       | `types.ts` → `repository.ts` → `routes.ts` |
| Charts                    | Admin dashboard Recharts   | Same chart components                      |
| Real-time updates         | SSE pattern from telemetry | Or polling fallback                        |

---

## Appendix B: Future Enhancements (Post-MVP)

1. **Rich media broadcasts** — Images, videos, GIFs
2. **Push notification deep links** — Open specific screens
3. **Survey logic jumps** — Skip questions based on answers
4. **Survey templates** — NPS, CSAT, CES templates
5. **Multi-language broadcasts** — i18n content variants
6. **Advanced analytics** — Cohort analysis, trend lines
7. **Integration with A/B testing** — Survey as experiment outcome

## Appendix C: Bug Fixes & Known Issues

### Fixed in This Revision

1. **Cosmos Partition Keys** — Changed `broadcast_deliveries` from `/broadcastId` to `/userId` to avoid hot partitions during large sends
2. **Computed Metrics** — Removed `completionRate` from stored `SurveyMetrics`; now computed on-the-fly
3. **Rate Limiting** — Added explicit rate limits for public endpoints (100 req/min)
4. **Conditional Logic** — Extended `showIf` to support nested AND/OR conditions, not just single conditions
5. **Offline Support** — Added `flushOfflineQueue()` to survey client for network resilience
6. **Safety Checks** — Added confirmation requirement for broadcasts targeting >10k users

### Known Limitations (MVP)

1. No persistent message queue — uses in-memory event bus; restart = lost pending deliveries
2. Push delivery is best-effort — no delivery receipt confirmation from FCM/APNS
3. Survey responses don't auto-retry on 5xx — client must retry manually
4. No real conflict resolution if user qualifies for multiple surveys simultaneously
