docs: reorganize roadmaps — broadcast/survey to completed, 10 scaffolded roadmaps to new dir
- Move platform_BROADCAST_SURVEY_ROADMAP.md to completed/ (modules + 43 tests built) - Create docs/roadmaps/scaffolded/ for roadmaps with modules built but full execution pending - Move 10 roadmaps from not-started/ to scaffolded/: ORG_WORKSPACE_RBAC, AGENT_REGISTRY, AGENT_RUNTIME, AI_BUDGET, AI_GOVERNANCE_EVALS, HUMAN_REVIEW, KNOWLEDGE_RAG, SCIM, SUPPORT_CASE, DURABLE_EVENT_BUS - Update WORKSPACE_REVIEW: 17 completed roadmaps, 10 scaffolded, 1 not-started (index) - Only platform_AGENT_PLATFORM_GAP_ROADMAP_INDEX.md remains in not-started/
This commit is contained in:
parent
a8bef2ea08
commit
8c3d54048a
@ -220,32 +220,32 @@
|
|||||||
|
|
||||||
## 3. Roadmaps Inventory
|
## 3. Roadmaps Inventory
|
||||||
|
|
||||||
### Completed (16 Roadmaps)
|
### Completed (17 Roadmaps) — in `docs/roadmaps/completed/`
|
||||||
|
|
||||||
| Roadmap | Date | Key Achievement |
|
| Roadmap | Date | Key Achievement |
|
||||||
| ------------------------------------------ | ---------- | ----------------------------------- |
|
| ------------------------------------------ | ---------- | --------------------------------------- |
|
||||||
| diagnostics_REMOTE_DIAGNOSTICS_ROADMAP.md | 2026-03-03 | Phases 1-3 complete |
|
| diagnostics_REMOTE_DIAGNOSTICS_ROADMAP.md | 2026-03-03 | Phases 1-3 complete |
|
||||||
| platform_COMPONENTS_ROADMAP.md | 2026-02 | 23/25 gap items built |
|
| platform_COMPONENTS_ROADMAP.md | 2026-02 | 23/25 gap items built |
|
||||||
| platform_BACKEND_MIGRATION.md | 2026-02 | All 6 backends migrated |
|
| platform_BACKEND_MIGRATION.md | 2026-02 | All 6 backends migrated |
|
||||||
| platform_SERVICE_CONSOLIDATION_ROADMAP.md | 2026-02 | 3→1 service consolidation |
|
| platform_SERVICE_CONSOLIDATION_ROADMAP.md | 2026-02 | 3→1 service consolidation |
|
||||||
| telemetry_IMPLEMENTATION_ROADMAP.md | 2026-02 | Full telemetry system |
|
| telemetry_IMPLEMENTATION_ROADMAP.md | 2026-02 | Full telemetry system |
|
||||||
| extraction_SERVICE_ROADMAP.md | 2026-02 | Text extraction service |
|
| extraction_SERVICE_ROADMAP.md | 2026-02 | Text extraction service |
|
||||||
| mobile_IOS_PLATFORM_SDK.md | 2026-02 | Swift SDK audit |
|
| mobile_IOS_PLATFORM_SDK.md | 2026-02 | Swift SDK audit |
|
||||||
| mobile_ANDROID_PLATFORM_SDK.md | 2026-02 | Kotlin SDK proposal |
|
| mobile_ANDROID_PLATFORM_SDK.md | 2026-02 | Kotlin SDK proposal |
|
||||||
| mobile_REACT_NATIVE_PLATFORM_SDK.md | 2026-02 | RN SDK gap analysis |
|
| mobile_REACT_NATIVE_PLATFORM_SDK.md | 2026-02 | RN SDK gap analysis |
|
||||||
| cloud_AGNOSTIC_REFACTOR_ROADMAP.md | 2026-02 | Datastore abstraction |
|
| cloud_AGNOSTIC_REFACTOR_ROADMAP.md | 2026-02 | Datastore abstraction |
|
||||||
| cloud_REFERRALS_PARTITION_KEY_MIGRATION.md | 2026-02 | DB migration |
|
| cloud_REFERRALS_PARTITION_KEY_MIGRATION.md | 2026-02 | DB migration |
|
||||||
| platform_COMMON_EXTRACTION_ROADMAP.md | 2026-02 | Shared extraction |
|
| platform_COMMON_EXTRACTION_ROADMAP.md | 2026-02 | Shared extraction |
|
||||||
| product_MARKETPLACE_MODULE_DESIGN.md | 2026-02 | Marketplace design |
|
| product_MARKETPLACE_MODULE_DESIGN.md | 2026-02 | Marketplace design |
|
||||||
| product_PRE_LAUNCH_SIGNUP_SYSTEM.md | 2026-02 | Pre-launch system |
|
| product_PRE_LAUNCH_SIGNUP_SYSTEM.md | 2026-02 | Pre-launch system |
|
||||||
| SHARED_CLIENT_PACKAGES_ROADMAP.md | 2026-03-19 | 9 packages + all product migrations |
|
| SHARED_CLIENT_PACKAGES_ROADMAP.md | 2026-03-19 | 9 packages + all product migrations |
|
||||||
| platform_ACCELERATION_ROADMAP.md | 2026-03-19 | All 4 phases complete |
|
| platform_ACCELERATION_ROADMAP.md | 2026-03-19 | All 4 phases complete |
|
||||||
|
| platform_BROADCAST_SURVEY_ROADMAP.md | 2026-03-20 | Broadcasts + surveys modules + 43 tests |
|
||||||
|
|
||||||
### Scaffolded (11 Roadmaps — modules exist, full roadmap execution pending)
|
### Scaffolded (10 Roadmaps — in `docs/roadmaps/scaffolded/`, modules exist, full roadmap execution pending)
|
||||||
|
|
||||||
| Roadmap | Module(s) | Files | Tests |
|
| Roadmap | Module(s) | Files | Tests |
|
||||||
| --------------------------------------------- | ----------------------- | ----- | ----- |
|
| --------------------------------------------- | ----------------------- | ----- | ----- |
|
||||||
| platform_BROADCAST_SURVEY_ROADMAP.md | broadcasts, surveys | 9 | 43 |
|
|
||||||
| platform_ORG_WORKSPACE_RBAC_ROADMAP.md | orgs | 5 | ✅ |
|
| platform_ORG_WORKSPACE_RBAC_ROADMAP.md | orgs | 5 | ✅ |
|
||||||
| platform_AGENT_REGISTRY_PROMPT_VERSIONING | agents | 5 | ✅ |
|
| platform_AGENT_REGISTRY_PROMPT_VERSIONING | agents | 5 | ✅ |
|
||||||
| platform_AGENT_RUNTIME_ORCHESTRATION | runs | 7 | ✅ |
|
| platform_AGENT_RUNTIME_ORCHESTRATION | runs | 7 | ✅ |
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
> **Status:** 🟡 Not Started
|
> **Status:** 🟡 Not Started
|
||||||
> **Estimated Effort:** 3–4 weeks
|
> **Estimated Effort:** 3–4 weeks
|
||||||
> **Target:** E2E broadcast messages + surveys across all ByteLyst products
|
> **Target:** E2E broadcast messages + surveys across all ByteLyst products
|
||||||
> **DRY Principle:** Reuse `@bytelyst/*` packages, extend platform SDKs, single admin UI
|
> **DRY Principle:** Reuse `@bytelyst/*` packages, extend platform SDKs, single admin UI
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -25,10 +25,10 @@
|
|||||||
|
|
||||||
### Two New Systems
|
### Two New Systems
|
||||||
|
|
||||||
| System | Purpose | Key Features |
|
| System | Purpose | Key Features |
|
||||||
|--------|---------|--------------|
|
| ----------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
| **Broadcast Messaging** | Targeted announcements to user segments | Push, in-app, email channels; scheduling; A/B testing; geo/platform/version targeting |
|
| **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 |
|
| **Surveys & Polls** | In-app user research | Multiple question types; conditional logic; targeting; real-time results; CSV export |
|
||||||
|
|
||||||
### DRY Strategy
|
### DRY Strategy
|
||||||
|
|
||||||
@ -102,14 +102,14 @@ Admin creates broadcast/survey
|
|||||||
|
|
||||||
**Location:** `services/platform-service/src/modules/broadcasts/`
|
**Location:** `services/platform-service/src/modules/broadcasts/`
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
| --------------- | --------------------------------------------------------------------- |
|
||||||
| `types.ts` | `Broadcast`, `BroadcastStatus`, `BroadcastTarget`, `BroadcastChannel` |
|
| `types.ts` | `Broadcast`, `BroadcastStatus`, `BroadcastTarget`, `BroadcastChannel` |
|
||||||
| `repository.ts` | CRUD + targeting query builder |
|
| `repository.ts` | CRUD + targeting query builder |
|
||||||
| `routes.ts` | Admin CRUD + public "mark as read" |
|
| `routes.ts` | Admin CRUD + public "mark as read" |
|
||||||
| `scheduler.ts` | Cron-based scheduling + A/B testing splits |
|
| `scheduler.ts` | Cron-based scheduling + A/B testing splits |
|
||||||
| `delivery.ts` | Push (FCM/APNS) + in-app + email dispatch |
|
| `delivery.ts` | Push (FCM/APNS) + in-app + email dispatch |
|
||||||
| `targeting.ts` | Segment evaluation engine |
|
| `targeting.ts` | Segment evaluation engine |
|
||||||
|
|
||||||
**Key Types:**
|
**Key Types:**
|
||||||
|
|
||||||
@ -123,25 +123,25 @@ export interface Broadcast {
|
|||||||
bodyMarkdown?: string;
|
bodyMarkdown?: string;
|
||||||
ctaText?: string;
|
ctaText?: string;
|
||||||
ctaUrl?: string;
|
ctaUrl?: string;
|
||||||
|
|
||||||
// Targeting
|
// Targeting
|
||||||
target: BroadcastTarget;
|
target: BroadcastTarget;
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
channels: BroadcastChannel[]; // 'push', 'in_app', 'email'
|
channels: BroadcastChannel[]; // 'push', 'in_app', 'email'
|
||||||
|
|
||||||
// Scheduling
|
// Scheduling
|
||||||
status: 'draft' | 'scheduled' | 'sending' | 'sent' | 'paused';
|
status: 'draft' | 'scheduled' | 'sending' | 'sent' | 'paused';
|
||||||
scheduledAt?: string;
|
scheduledAt?: string;
|
||||||
sentAt?: string;
|
sentAt?: string;
|
||||||
|
|
||||||
// A/B Testing
|
// A/B Testing
|
||||||
variant?: 'control' | 'treatment';
|
variant?: 'control' | 'treatment';
|
||||||
experimentId?: string;
|
experimentId?: string;
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
metrics: BroadcastMetrics;
|
metrics: BroadcastMetrics;
|
||||||
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
@ -149,25 +149,25 @@ export interface Broadcast {
|
|||||||
export interface BroadcastTarget {
|
export interface BroadcastTarget {
|
||||||
// User segments (AND logic)
|
// User segments (AND logic)
|
||||||
userSegments?: ('free' | 'pro' | 'enterprise' | 'churned' | 'active')[];
|
userSegments?: ('free' | 'pro' | 'enterprise' | 'churned' | 'active')[];
|
||||||
|
|
||||||
// Platform targeting
|
// Platform targeting
|
||||||
platforms?: ('ios' | 'android' | 'web' | 'macos' | 'windows')[];
|
platforms?: ('ios' | 'android' | 'web' | 'macos' | 'windows')[];
|
||||||
|
|
||||||
// App version range
|
// App version range
|
||||||
appVersionMin?: string;
|
appVersionMin?: string;
|
||||||
appVersionMax?: string;
|
appVersionMax?: string;
|
||||||
|
|
||||||
// Geo targeting
|
// Geo targeting
|
||||||
countryCodes?: string[]; // ISO 3166-1 alpha-2
|
countryCodes?: string[]; // ISO 3166-1 alpha-2
|
||||||
regionCodes?: string[]; // US-CA, EU-FR, etc.
|
regionCodes?: string[]; // US-CA, EU-FR, etc.
|
||||||
|
|
||||||
// OS version
|
// OS version
|
||||||
osVersionMin?: string;
|
osVersionMin?: string;
|
||||||
osVersionMax?: string;
|
osVersionMax?: string;
|
||||||
|
|
||||||
// Percentage rollout (for phased rollouts)
|
// Percentage rollout (for phased rollouts)
|
||||||
percentageRollout?: number; // 0-100, uses FNV-1a hash like feature flags
|
percentageRollout?: number; // 0-100, uses FNV-1a hash like feature flags
|
||||||
|
|
||||||
// User IDs (explicit list, for testing)
|
// User IDs (explicit list, for testing)
|
||||||
specificUserIds?: string[];
|
specificUserIds?: string[];
|
||||||
}
|
}
|
||||||
@ -187,21 +187,21 @@ export interface BroadcastMetrics {
|
|||||||
|
|
||||||
**Endpoints:**
|
**Endpoints:**
|
||||||
|
|
||||||
| Method | Endpoint | Auth | Purpose |
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|--------|----------|------|---------|
|
| -------- | ------------------------------- | ----- | --------------------------------------------------------- |
|
||||||
| `POST` | `/admin/broadcasts` | Admin | Create broadcast |
|
| `POST` | `/admin/broadcasts` | Admin | Create broadcast |
|
||||||
| `GET` | `/admin/broadcasts` | Admin | List all broadcasts |
|
| `GET` | `/admin/broadcasts` | Admin | List all broadcasts |
|
||||||
| `GET` | `/admin/broadcasts/:id` | Admin | Get single broadcast |
|
| `GET` | `/admin/broadcasts/:id` | Admin | Get single broadcast |
|
||||||
| `PUT` | `/admin/broadcasts/:id` | Admin | Update draft/scheduled |
|
| `PUT` | `/admin/broadcasts/:id` | Admin | Update draft/scheduled |
|
||||||
| `DELETE` | `/admin/broadcasts/:id` | Admin | Cancel/delete |
|
| `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/send` | Admin | Trigger immediate send (with safety check for >10k users) |
|
||||||
| `POST` | `/admin/broadcasts/:id/pause` | Admin | Pause sending (cancels pending deliveries) |
|
| `POST` | `/admin/broadcasts/:id/pause` | Admin | Pause sending (cancels pending deliveries) |
|
||||||
| `GET` | `/admin/broadcasts/:id/metrics` | Admin | Real-time metrics |
|
| `GET` | `/admin/broadcasts/:id/metrics` | Admin | Real-time metrics |
|
||||||
| `POST` | `/admin/broadcasts/:id/clone` | Admin | Duplicate for A/B test |
|
| `POST` | `/admin/broadcasts/:id/clone` | Admin | Duplicate for A/B test |
|
||||||
| `GET` | `/broadcasts` | User | List my active broadcasts |
|
| `GET` | `/broadcasts` | User | List my active broadcasts |
|
||||||
| `POST` | `/broadcasts/:id/read` | User | Mark as read |
|
| `POST` | `/broadcasts/:id/read` | User | Mark as read |
|
||||||
| `POST` | `/broadcasts/:id/dismiss` | User | Dismiss in-app message |
|
| `POST` | `/broadcasts/:id/dismiss` | User | Dismiss in-app message |
|
||||||
| `POST` | `/broadcasts/:id/click` | User | Track CTA click |
|
| `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.
|
**Rate Limiting:** Public endpoints (`/broadcasts`, `/broadcasts/:id/read`, etc.) use existing `ratelimit` module: 100 req/min per user.
|
||||||
|
|
||||||
@ -209,13 +209,13 @@ export interface BroadcastMetrics {
|
|||||||
|
|
||||||
**Location:** `services/platform-service/src/modules/surveys/`
|
**Location:** `services/platform-service/src/modules/surveys/`
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|------|---------|
|
| --------------- | ------------------------------------------------------ |
|
||||||
| `types.ts` | `Survey`, `Question`, `QuestionType`, `SurveyResponse` |
|
| `types.ts` | `Survey`, `Question`, `QuestionType`, `SurveyResponse` |
|
||||||
| `repository.ts` | CRUD + response aggregation |
|
| `repository.ts` | CRUD + response aggregation |
|
||||||
| `routes.ts` | Admin CRUD + public response submission |
|
| `routes.ts` | Admin CRUD + public response submission |
|
||||||
| `targeting.ts` | Reuses broadcasts/targeting.ts |
|
| `targeting.ts` | Reuses broadcasts/targeting.ts |
|
||||||
| `analytics.ts` | Response statistics, CSV export |
|
| `analytics.ts` | Response statistics, CSV export |
|
||||||
|
|
||||||
**Key Types:**
|
**Key Types:**
|
||||||
|
|
||||||
@ -226,44 +226,44 @@ export interface Survey {
|
|||||||
productId: string;
|
productId: string;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
// Questions
|
// Questions
|
||||||
questions: Question[];
|
questions: Question[];
|
||||||
|
|
||||||
// Targeting (same as BroadcastTarget)
|
// Targeting (same as BroadcastTarget)
|
||||||
target: BroadcastTarget;
|
target: BroadcastTarget;
|
||||||
|
|
||||||
// Scheduling
|
// Scheduling
|
||||||
status: 'draft' | 'active' | 'paused' | 'closed';
|
status: 'draft' | 'active' | 'paused' | 'closed';
|
||||||
startsAt?: string;
|
startsAt?: string;
|
||||||
endsAt?: string;
|
endsAt?: string;
|
||||||
|
|
||||||
// Display settings
|
// Display settings
|
||||||
displayTrigger: SurveyTrigger;
|
displayTrigger: SurveyTrigger;
|
||||||
|
|
||||||
// Incentives
|
// Incentives
|
||||||
incentive?: {
|
incentive?: {
|
||||||
type: 'pro_days' | 'credits';
|
type: 'pro_days' | 'credits';
|
||||||
amount: number;
|
amount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metrics
|
// Metrics
|
||||||
metrics: SurveyMetrics;
|
metrics: SurveyMetrics;
|
||||||
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QuestionType =
|
export type QuestionType =
|
||||||
| 'single_choice' // Radio buttons
|
| 'single_choice' // Radio buttons
|
||||||
| 'multiple_choice' // Checkboxes
|
| 'multiple_choice' // Checkboxes
|
||||||
| 'rating' // 1-5 or 1-10 stars
|
| 'rating' // 1-5 or 1-10 stars
|
||||||
| 'nps' // 0-10 scale
|
| 'nps' // 0-10 scale
|
||||||
| 'text_short' // Single line
|
| 'text_short' // Single line
|
||||||
| 'text_long' // Multi-line textarea
|
| 'text_long' // Multi-line textarea
|
||||||
| 'dropdown' // Select menu
|
| 'dropdown' // Select menu
|
||||||
| 'scale' // Likert scale (1-7)
|
| 'scale' // Likert scale (1-7)
|
||||||
| 'ranking'; // Drag to rank
|
| 'ranking'; // Drag to rank
|
||||||
|
|
||||||
export interface Question {
|
export interface Question {
|
||||||
id: string;
|
id: string;
|
||||||
@ -271,22 +271,22 @@ export interface Question {
|
|||||||
text: string;
|
text: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
|
|
||||||
// For choice types
|
// For choice types
|
||||||
options?: QuestionOption[];
|
options?: QuestionOption[];
|
||||||
|
|
||||||
// For conditional logic - supports complex AND/OR conditions
|
// For conditional logic - supports complex AND/OR conditions
|
||||||
showIf?: ShowIfCondition;
|
showIf?: ShowIfCondition;
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
minValue?: number; // For numeric types
|
minValue?: number; // For numeric types
|
||||||
maxValue?: number;
|
maxValue?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper type for conditional display (supports nested AND/OR)
|
// Helper type for conditional display (supports nested AND/OR)
|
||||||
export type ShowIfCondition =
|
export type ShowIfCondition =
|
||||||
| { and: ShowIfCondition[] }
|
| { and: ShowIfCondition[] }
|
||||||
| { or: ShowIfCondition[] }
|
| { or: ShowIfCondition[] }
|
||||||
| {
|
| {
|
||||||
@ -301,10 +301,10 @@ export interface QuestionOption {
|
|||||||
emoji?: string; // Optional visual indicator
|
emoji?: string; // Optional visual indicator
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SurveyTrigger =
|
export type SurveyTrigger =
|
||||||
| { type: 'immediate' } // Show right away
|
| { type: 'immediate' } // Show right away
|
||||||
| { type: 'delay_seconds'; seconds: number } // After N seconds in app
|
| { type: 'delay_seconds'; seconds: number } // After N seconds in app
|
||||||
| { type: 'event'; eventName: string } // After specific event
|
| { type: 'event'; eventName: string } // After specific event
|
||||||
| { type: 'page_view'; pagePattern: string }; // URL pattern match
|
| { type: 'page_view'; pagePattern: string }; // URL pattern match
|
||||||
|
|
||||||
export interface SurveyResponse {
|
export interface SurveyResponse {
|
||||||
@ -313,22 +313,22 @@ export interface SurveyResponse {
|
|||||||
productId: string;
|
productId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId?: string;
|
deviceId?: string;
|
||||||
|
|
||||||
// Answers
|
// Answers
|
||||||
answers: Record<string, QuestionAnswer>;
|
answers: Record<string, QuestionAnswer>;
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
startedAt: string;
|
startedAt: string;
|
||||||
completedAt?: string;
|
completedAt?: string;
|
||||||
isComplete: boolean;
|
isComplete: boolean;
|
||||||
|
|
||||||
// Incentive claimed
|
// Incentive claimed
|
||||||
incentiveClaimed?: boolean;
|
incentiveClaimed?: boolean;
|
||||||
|
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QuestionAnswer =
|
export type QuestionAnswer =
|
||||||
| { type: 'single_choice'; optionId: string }
|
| { type: 'single_choice'; optionId: string }
|
||||||
| { type: 'multiple_choice'; optionIds: string[] }
|
| { type: 'multiple_choice'; optionIds: string[] }
|
||||||
| { type: 'rating'; value: number }
|
| { type: 'rating'; value: number }
|
||||||
@ -353,24 +353,24 @@ export function computeCompletionRate(metrics: SurveyMetrics): number {
|
|||||||
|
|
||||||
**Endpoints:**
|
**Endpoints:**
|
||||||
|
|
||||||
| Method | Endpoint | Auth | Purpose |
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|--------|----------|------|---------|
|
| -------- | -------------------------------- | ----- | --------------------------------------------------- |
|
||||||
| `POST` | `/admin/surveys` | Admin | Create survey |
|
| `POST` | `/admin/surveys` | Admin | Create survey |
|
||||||
| `GET` | `/admin/surveys` | Admin | List all surveys |
|
| `GET` | `/admin/surveys` | Admin | List all surveys |
|
||||||
| `GET` | `/admin/surveys/:id` | Admin | Get survey with questions |
|
| `GET` | `/admin/surveys/:id` | Admin | Get survey with questions |
|
||||||
| `PUT` | `/admin/surveys/:id` | Admin | Update survey |
|
| `PUT` | `/admin/surveys/:id` | Admin | Update survey |
|
||||||
| `DELETE` | `/admin/surveys/:id` | Admin | Delete survey |
|
| `DELETE` | `/admin/surveys/:id` | Admin | Delete survey |
|
||||||
| `POST` | `/admin/surveys/:id/duplicate` | Admin | Clone survey |
|
| `POST` | `/admin/surveys/:id/duplicate` | Admin | Clone survey |
|
||||||
| `GET` | `/admin/surveys/:id/responses` | Admin | All responses (paginated) |
|
| `GET` | `/admin/surveys/:id/responses` | Admin | All responses (paginated) |
|
||||||
| `GET` | `/admin/surveys/:id/analytics` | Admin | Aggregated stats |
|
| `GET` | `/admin/surveys/:id/analytics` | Admin | Aggregated stats |
|
||||||
| `GET` | `/admin/surveys/:id/export.csv` | Admin | CSV export |
|
| `GET` | `/admin/surveys/:id/export.csv` | Admin | CSV export |
|
||||||
| `GET` | `/admin/surveys/:id/respondents` | Admin | List users who responded |
|
| `GET` | `/admin/surveys/:id/respondents` | Admin | List users who responded |
|
||||||
| `POST` | `/admin/surveys/:id/pause` | Admin | Stop showing survey |
|
| `POST` | `/admin/surveys/:id/pause` | Admin | Stop showing survey |
|
||||||
| `GET` | `/surveys/active` | User | Get active survey for me (rate limited: 10 req/min) |
|
| `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/start` | User | Begin survey session |
|
||||||
| `POST` | `/surveys/:id/response` | User | Submit answer(s) (validated against question type) |
|
| `POST` | `/surveys/:id/response` | User | Submit answer(s) (validated against question type) |
|
||||||
| `POST` | `/surveys/:id/complete` | User | Mark as complete |
|
| `POST` | `/surveys/:id/complete` | User | Mark as complete |
|
||||||
| `POST` | `/surveys/:id/dismiss` | User | "Don't show again" |
|
| `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'`.
|
**Security:** Survey responses validate answer type matches question type (reject string for NPS question). Admin endpoints require `role === 'admin'`.
|
||||||
|
|
||||||
@ -397,34 +397,34 @@ export async function evaluateTarget(
|
|||||||
context: TargetingContext
|
context: TargetingContext
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// AND logic across all criteria
|
// AND logic across all criteria
|
||||||
|
|
||||||
if (target.userSegments && !target.userSegments.some(s => context.userSegments.includes(s))) {
|
if (target.userSegments && !target.userSegments.some(s => context.userSegments.includes(s))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.platforms && !target.platforms.includes(context.platform)) {
|
if (target.platforms && !target.platforms.includes(context.platform)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.appVersionMin && semver.lt(context.appVersion, target.appVersionMin)) {
|
if (target.appVersionMin && semver.lt(context.appVersion, target.appVersionMin)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.appVersionMax && semver.gt(context.appVersion, target.appVersionMax)) {
|
if (target.appVersionMax && semver.gt(context.appVersion, target.appVersionMax)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.countryCodes && !target.countryCodes.includes(context.countryCode ?? '')) {
|
if (target.countryCodes && !target.countryCodes.includes(context.countryCode ?? '')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.percentageRollout !== undefined) {
|
if (target.percentageRollout !== undefined) {
|
||||||
const hash = fnv1a32(`${context.userId}:${target.percentageRollout}`);
|
const hash = fnv1a32(`${context.userId}:${target.percentageRollout}`);
|
||||||
if ((hash % 100) >= target.percentageRollout) {
|
if (hash % 100 >= target.percentageRollout) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -466,22 +466,22 @@ export interface InAppMessage {
|
|||||||
|
|
||||||
export class BroadcastClient {
|
export class BroadcastClient {
|
||||||
constructor(config: BroadcastClientConfig);
|
constructor(config: BroadcastClientConfig);
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
initialize(): Promise<void>;
|
initialize(): Promise<void>;
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
|
|
||||||
// Check for new messages (call on app open, every 5 min)
|
// Check for new messages (call on app open, every 5 min)
|
||||||
checkMessages(): Promise<InAppMessage[]>;
|
checkMessages(): Promise<InAppMessage[]>;
|
||||||
|
|
||||||
// Mark actions
|
// Mark actions
|
||||||
markRead(messageId: string): Promise<void>;
|
markRead(messageId: string): Promise<void>;
|
||||||
markDismissed(messageId: string): Promise<void>;
|
markDismissed(messageId: string): Promise<void>;
|
||||||
trackClick(messageId: string, url?: string): Promise<void>;
|
trackClick(messageId: string, url?: string): Promise<void>;
|
||||||
|
|
||||||
// Get cached unread count
|
// Get cached unread count
|
||||||
getUnreadCount(): number;
|
getUnreadCount(): number;
|
||||||
|
|
||||||
// Subscribe to real-time updates (WebSocket fallback to polling)
|
// Subscribe to real-time updates (WebSocket fallback to polling)
|
||||||
onMessage(handler: (msg: InAppMessage) => void): () => void;
|
onMessage(handler: (msg: InAppMessage) => void): () => void;
|
||||||
}
|
}
|
||||||
@ -523,7 +523,7 @@ export interface SurveyClientConfig {
|
|||||||
osVersion: string;
|
osVersion: string;
|
||||||
getAuthToken: () => Promise<string>;
|
getAuthToken: () => Promise<string>;
|
||||||
onSurveyReady: (survey: ActiveSurvey) => void;
|
onSurveyReady: (survey: ActiveSurvey) => void;
|
||||||
|
|
||||||
// Event tracking for triggers
|
// Event tracking for triggers
|
||||||
trackEvent: (eventName: string, metadata?: Record<string, unknown>) => void;
|
trackEvent: (eventName: string, metadata?: Record<string, unknown>) => void;
|
||||||
}
|
}
|
||||||
@ -549,22 +549,22 @@ export interface SurveyQuestion {
|
|||||||
|
|
||||||
export class SurveyClient {
|
export class SurveyClient {
|
||||||
constructor(config: SurveyClientConfig);
|
constructor(config: SurveyClientConfig);
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
initialize(): Promise<void>;
|
initialize(): Promise<void>;
|
||||||
|
|
||||||
// Check for surveys on trigger events
|
// Check for surveys on trigger events
|
||||||
checkForSurveys(context?: { pageUrl?: string; eventName?: string }): Promise<ActiveSurvey | null>;
|
checkForSurveys(context?: { pageUrl?: string; eventName?: string }): Promise<ActiveSurvey | null>;
|
||||||
|
|
||||||
// Survey session with offline queue support
|
// Survey session with offline queue support
|
||||||
startSurvey(surveyId: string): Promise<SurveySession>;
|
startSurvey(surveyId: string): Promise<SurveySession>;
|
||||||
submitAnswer(questionId: string, answer: AnswerValue): Promise<void>;
|
submitAnswer(questionId: string, answer: AnswerValue): Promise<void>;
|
||||||
completeSurvey(): Promise<{ incentiveClaimed: boolean }>;
|
completeSurvey(): Promise<{ incentiveClaimed: boolean }>;
|
||||||
dismissSurvey(): Promise<void>;
|
dismissSurvey(): Promise<void>;
|
||||||
|
|
||||||
// Offline support: queue responses when offline, flush when connected
|
// Offline support: queue responses when offline, flush when connected
|
||||||
flushOfflineQueue(): Promise<{ flushed: number; failed: number }>;
|
flushOfflineQueue(): Promise<{ flushed: number; failed: number }>;
|
||||||
|
|
||||||
// Subscribe to survey availability
|
// Subscribe to survey availability
|
||||||
onSurveyAvailable(handler: (survey: ActiveSurvey) => void): () => void;
|
onSurveyAvailable(handler: (survey: ActiveSurvey) => void): () => void;
|
||||||
}
|
}
|
||||||
@ -596,7 +596,7 @@ public class BLBroadcastClient: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
|
|
||||||
public class BLSurveyClient: ObservableObject {
|
public class BLSurveyClient: ObservableObject {
|
||||||
@Published public var activeSurvey: Survey?
|
@Published public var activeSurvey: Survey?
|
||||||
|
|
||||||
// Native SwiftUI survey sheet
|
// Native SwiftUI survey sheet
|
||||||
public func presentSurvey(_ survey: Survey, from viewController: UIViewController)
|
public func presentSurvey(_ survey: Survey, from viewController: UIViewController)
|
||||||
}
|
}
|
||||||
@ -626,12 +626,12 @@ class BLSurveyClient {
|
|||||||
|
|
||||||
**Location:** `dashboards/admin-web/src/app/(dashboard)/broadcasts/`
|
**Location:** `dashboards/admin-web/src/app/(dashboard)/broadcasts/`
|
||||||
|
|
||||||
| Page | Features |
|
| Page | Features |
|
||||||
|------|----------|
|
| ----------------------- | --------------------------------------------------------- |
|
||||||
| `page.tsx` | List all broadcasts with status chips |
|
| `page.tsx` | List all broadcasts with status chips |
|
||||||
| `new/page.tsx` | Create wizard: content → targeting → scheduling → preview |
|
| `new/page.tsx` | Create wizard: content → targeting → scheduling → preview |
|
||||||
| `[id]/page.tsx` | Edit + metrics dashboard |
|
| `[id]/page.tsx` | Edit + metrics dashboard |
|
||||||
| `[id]/preview/page.tsx` | Device mockup preview (iPhone, Android, Web) |
|
| `[id]/preview/page.tsx` | Device mockup preview (iPhone, Android, Web) |
|
||||||
|
|
||||||
**Targeting Builder Component:**
|
**Targeting Builder Component:**
|
||||||
|
|
||||||
@ -670,13 +670,13 @@ interface TargetingBuilderProps {
|
|||||||
|
|
||||||
**Location:** `dashboards/admin-web/src/app/(dashboard)/surveys/`
|
**Location:** `dashboards/admin-web/src/app/(dashboard)/surveys/`
|
||||||
|
|
||||||
| Page | Features |
|
| Page | Features |
|
||||||
|------|----------|
|
| ------------------------- | ----------------------------------------------------- |
|
||||||
| `page.tsx` | List surveys with completion rates |
|
| `page.tsx` | List surveys with completion rates |
|
||||||
| `new/page.tsx` | Survey builder: questions → targeting → display rules |
|
| `new/page.tsx` | Survey builder: questions → targeting → display rules |
|
||||||
| `[id]/page.tsx` | Edit + live preview |
|
| `[id]/page.tsx` | Edit + live preview |
|
||||||
| `[id]/analytics/page.tsx` | Response analytics + charts |
|
| `[id]/analytics/page.tsx` | Response analytics + charts |
|
||||||
| `[id]/responses/page.tsx` | Individual responses table |
|
| `[id]/responses/page.tsx` | Individual responses table |
|
||||||
|
|
||||||
**Question Builder Component:**
|
**Question Builder Component:**
|
||||||
|
|
||||||
@ -721,7 +721,7 @@ interface TargetingBuilderProps {
|
|||||||
// components/broadcasts/BroadcastBanner.tsx
|
// components/broadcasts/BroadcastBanner.tsx
|
||||||
// Fixed position banner for in-app messages
|
// Fixed position banner for in-app messages
|
||||||
|
|
||||||
// components/broadcasts/BroadcastModal.tsx
|
// components/broadcasts/BroadcastModal.tsx
|
||||||
// Modal overlay for high-priority messages
|
// Modal overlay for high-priority messages
|
||||||
|
|
||||||
// components/surveys/SurveyModal.tsx
|
// components/surveys/SurveyModal.tsx
|
||||||
@ -779,7 +779,7 @@ class BroadcastMessagingService : FirebaseMessagingService() {
|
|||||||
val broadcastId = remoteMessage.data["broadcastId"]
|
val broadcastId = remoteMessage.data["broadcastId"]
|
||||||
// Show notification or in-app message
|
// Show notification or in-app message
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewToken(token: String) {
|
override fun onNewToken(token: String) {
|
||||||
BLBroadcastClient.setPushToken(token)
|
BLBroadcastClient.setPushToken(token)
|
||||||
}
|
}
|
||||||
@ -795,10 +795,10 @@ class BroadcastMessagingService : FirebaseMessagingService() {
|
|||||||
class BroadcastClient:
|
class BroadcastClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.storage = LocalStorage()
|
self.storage = LocalStorage()
|
||||||
|
|
||||||
def check_messages(self) -> List[InAppMessage]:
|
def check_messages(self) -> List[InAppMessage]:
|
||||||
# Poll platform-service
|
# Poll platform-service
|
||||||
|
|
||||||
def show_banner(self, message: InAppMessage):
|
def show_banner(self, message: InAppMessage):
|
||||||
# tkinter overlay or native notification
|
# tkinter overlay or native notification
|
||||||
```
|
```
|
||||||
@ -811,58 +811,61 @@ class BroadcastClient:
|
|||||||
|
|
||||||
**Commit:** `1b11db3` — feat(broadcasts,surveys): Phase 1 complete - backend modules
|
**Commit:** `1b11db3` — feat(broadcasts,surveys): Phase 1 complete - backend modules
|
||||||
|
|
||||||
| Day | Task | Deliverable | Status |
|
| Day | Task | Deliverable | Status |
|
||||||
|-----|------|-------------|--------|
|
| --- | ------------------------------------------ | --------------------------------- | ------ |
|
||||||
| 1 | `broadcasts` module scaffold + types | `types.ts`, `repository.ts` tests | ✅ |
|
| 1 | `broadcasts` module scaffold + types | `types.ts`, `repository.ts` tests | ✅ |
|
||||||
| 2 | `broadcasts` routes + targeting engine | 9 endpoints, targeting.ts | ✅ |
|
| 2 | `broadcasts` routes + targeting engine | 9 endpoints, targeting.ts | ✅ |
|
||||||
| 3 | `surveys` module scaffold + types | `types.ts`, `repository.ts` tests | ✅ |
|
| 3 | `surveys` module scaffold + types | `types.ts`, `repository.ts` tests | ✅ |
|
||||||
| 4 | `surveys` routes + question types | 14 endpoints | ✅ |
|
| 4 | `surveys` routes + question types | 14 endpoints | ✅ |
|
||||||
| 5 | Cosmos containers + server.ts registration | 7 containers, routes registered | ✅ |
|
| 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)
|
**Tests:** 80+ unit tests for repositories, 40+ for routes (to be added in follow-up)
|
||||||
|
|
||||||
### Phase 2: Admin Dashboard (Week 2) ✅ **COMPLETED**
|
### Phase 2: Admin Dashboard (Week 2) ✅ **COMPLETED**
|
||||||
|
|
||||||
**Commits:**
|
**Commits:**
|
||||||
|
|
||||||
- `...` — feat(admin): Phase 2.1 - Broadcast and Survey list pages
|
- `...` — feat(admin): Phase 2.1 - Broadcast and Survey list pages
|
||||||
- `...` — feat(admin): Phase 2.2 - Broadcast create/edit wizard
|
- `...` — feat(admin): Phase 2.2 - Broadcast create/edit wizard
|
||||||
- `...` — feat(admin): Phase 2.3 - Survey builder UI
|
- `...` — feat(admin): Phase 2.3 - Survey builder UI
|
||||||
|
|
||||||
| Day | Task | Deliverable | Status |
|
| Day | Task | Deliverable | Status |
|
||||||
|-----|------|-------------|--------|
|
| --- | ---------------------------- | ---------------------------------- | ------ |
|
||||||
| 6 | Broadcast list UI | List, filter, actions | ✅ |
|
| 6 | Broadcast list UI | List, filter, actions | ✅ |
|
||||||
| 7 | Broadcast create/edit wizard | Targeting builder, reach estimator | ✅ |
|
| 7 | Broadcast create/edit wizard | Targeting builder, reach estimator | ✅ |
|
||||||
| 8 | Survey builder UI | Question builder, 9 question types | ✅ |
|
| 8 | Survey builder UI | Question builder, 9 question types | ✅ |
|
||||||
| 9 | Survey list + analytics | Response charts, CSV export | ✅ |
|
| 9 | Survey list + analytics | Response charts, CSV export | ✅ |
|
||||||
| 10 | Navigation | Sidebar nav items | ✅ |
|
| 10 | Navigation | Sidebar nav items | ✅ |
|
||||||
|
|
||||||
### Phase 3: Client SDKs (Week 3) ✅ **COMPLETED**
|
### Phase 3: Client SDKs (Week 3) ✅ **COMPLETED**
|
||||||
|
|
||||||
**Commits:**
|
**Commits:**
|
||||||
|
|
||||||
- `...` — feat(packages): @bytelyst/broadcast-client package
|
- `...` — feat(packages): @bytelyst/broadcast-client package
|
||||||
- `...` — feat(packages): @bytelyst/survey-client package with offline cache
|
- `...` — feat(packages): @bytelyst/survey-client package with offline cache
|
||||||
|
|
||||||
| Day | Task | Deliverable | Status |
|
| Day | Task | Deliverable | Status |
|
||||||
|-----|------|-------------|--------|
|
| --- | ---------------------------- | ------------------------------------- | --------- |
|
||||||
| 11 | `@bytelyst/broadcast-client` | Package + types + polling | ✅ |
|
| 11 | `@bytelyst/broadcast-client` | Package + types + polling | ✅ |
|
||||||
| 12 | `@bytelyst/survey-client` | Package + validation + offline cache | ✅ |
|
| 12 | `@bytelyst/survey-client` | Package + validation + offline cache | ✅ |
|
||||||
| 13 | Swift SDK extensions | `BLBroadcastClient`, `BLSurveyClient` | ✅ |
|
| 13 | Swift SDK extensions | `BLBroadcastClient`, `BLSurveyClient` | ✅ |
|
||||||
| 14 | Kotlin SDK extensions | `BLBroadcastClient`, `BLSurveyClient` | ✅ |
|
| 14 | Kotlin SDK extensions | `BLBroadcastClient`, `BLSurveyClient` | ✅ |
|
||||||
| 15 | SDK integration tests | All platforms | ⏭️ Future |
|
| 15 | SDK integration tests | All platforms | ⏭️ Future |
|
||||||
|
|
||||||
### Phase 4: Platform Integration (Week 4) ✅ **COMPLETED**
|
### Phase 4: Platform Integration (Week 4) ✅ **COMPLETED**
|
||||||
|
|
||||||
**Commits:**
|
**Commits:**
|
||||||
|
|
||||||
- `4bf18f4` — feat(user-dashboard): Phase 4.1 - Web integration components
|
- `4bf18f4` — feat(user-dashboard): Phase 4.1 - Web integration components
|
||||||
- `...` — feat(platform-service): Phase 4.4 - Push notification wiring (FCM/APNS)
|
- `...` — feat(platform-service): Phase 4.4 - Push notification wiring (FCM/APNS)
|
||||||
|
|
||||||
| Day | Task | Deliverable | Status |
|
| Day | Task | Deliverable | Status |
|
||||||
|-----|------|-------------|--------|
|
| --- | -------------------------------- | -------------------------- | --------- |
|
||||||
| 16 | Web integration (user-dashboard) | Banner, modal, survey flow | ✅ |
|
| 16 | Web integration (user-dashboard) | Banner, modal, survey flow | ✅ |
|
||||||
| 17 | iOS integration | SwiftUI components | ✅ |
|
| 17 | iOS integration | SwiftUI components | ✅ |
|
||||||
| 18 | Android integration | Compose components | ✅ |
|
| 18 | Android integration | Compose components | ✅ |
|
||||||
| 19 | FCM/APNS setup + testing | Push delivery service | ✅ |
|
| 19 | FCM/APNS setup + testing | Push delivery service | ✅ |
|
||||||
| 20 | Documentation + polish | README, API docs | ⏭️ Future |
|
| 20 | Documentation + polish | README, API docs | ⏭️ Future |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -882,16 +885,16 @@ The Broadcast and Survey platform is now **MVP complete** with:
|
|||||||
|
|
||||||
## 8. Cosmos Containers
|
## 8. Cosmos Containers
|
||||||
|
|
||||||
| Container | Partition Key | Purpose | Est. Size | TTL |
|
| Container | Partition Key | Purpose | Est. Size | TTL |
|
||||||
|-----------|---------------|---------|-----------|-----|
|
| ---------------------- | ------------- | ------------------------------------------------ | --------- | ------- |
|
||||||
| `broadcasts` | `/productId` | Broadcast definitions + targeting | Small | — |
|
| `broadcasts` | `/productId` | Broadcast definitions + targeting | Small | — |
|
||||||
| `broadcast_deliveries` | `/userId` | Per-user delivery status (avoids hot partitions) | Large | 90 days |
|
| `broadcast_deliveries` | `/userId` | Per-user delivery status (avoids hot partitions) | Large | 90 days |
|
||||||
| `broadcast_reads` | `/userId` | User read receipts | Large | 90 days |
|
| `broadcast_reads` | `/userId` | User read receipts | Large | 90 days |
|
||||||
| `surveys` | `/productId` | Survey definitions + questions | Small | — |
|
| `surveys` | `/productId` | Survey definitions + questions | Small | — |
|
||||||
| `survey_responses` | `/surveyId` | Individual responses | Large | 1 year |
|
| `survey_responses` | `/surveyId` | Individual responses | Large | 1 year |
|
||||||
| `in_app_messages` | `/userId` | Active in-app messages per user | Medium | 30 days |
|
| `in_app_messages` | `/userId` | Active in-app messages per user | Medium | 30 days |
|
||||||
| `targeting_segments` | `/productId` | Pre-computed user segments | Medium | 1 day |
|
| `targeting_segments` | `/productId` | Pre-computed user segments | Medium | 1 day |
|
||||||
| `broadcast_templates` | `/productId` | Reusable broadcast templates | Small | — |
|
| `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.
|
**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.
|
||||||
|
|
||||||
@ -968,15 +971,15 @@ The Broadcast and Survey platform is now **MVP complete** with:
|
|||||||
|
|
||||||
## Appendix A: DRY Checklist
|
## Appendix A: DRY Checklist
|
||||||
|
|
||||||
| Component | Reused From | Notes |
|
| Component | Reused From | Notes |
|
||||||
|-----------|-------------|-------|
|
| ------------------------- | -------------------------- | ------------------------------------------ |
|
||||||
| Targeting engine | `flags/` module | Same FNV-1a hash, same segment logic |
|
| Targeting engine | `flags/` module | Same FNV-1a hash, same segment logic |
|
||||||
| Storage adapters | Existing SDK patterns | `BLTelemetryClient` storage pattern |
|
| Storage adapters | Existing SDK patterns | `BLTelemetryClient` storage pattern |
|
||||||
| Admin UI components | `flags/page.tsx` | Copy table, filters, pagination |
|
| Admin UI components | `flags/page.tsx` | Copy table, filters, pagination |
|
||||||
| API client factory | `@bytelyst/api-client` | `createApiClient()` with auth |
|
| API client factory | `@bytelyst/api-client` | `createApiClient()` with auth |
|
||||||
| Cosmos repository pattern | All existing modules | `types.ts` → `repository.ts` → `routes.ts` |
|
| Cosmos repository pattern | All existing modules | `types.ts` → `repository.ts` → `routes.ts` |
|
||||||
| Charts | Admin dashboard Recharts | Same chart components |
|
| Charts | Admin dashboard Recharts | Same chart components |
|
||||||
| Real-time updates | SSE pattern from telemetry | Or polling fallback |
|
| Real-time updates | SSE pattern from telemetry | Or polling fallback |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user