diff --git a/app/admin/index.tsx b/app/admin/index.tsx index 41edb16..6de3c76 100644 --- a/app/admin/index.tsx +++ b/app/admin/index.tsx @@ -56,11 +56,7 @@ export default function OverviewDashboard() { /> - + { - // Span callback - operations tracked here - }) + return Sentry.startSpan( + { + name, + op, + }, + () => { + // Span callback - operations tracked here + } + ) } /** diff --git a/specs/002-admin-stats-dashboard/checklists/requirements.md b/specs/002-admin-stats-dashboard/checklists/requirements.md index 5e2f7d2..3f3fc93 100644 --- a/specs/002-admin-stats-dashboard/checklists/requirements.md +++ b/specs/002-admin-stats-dashboard/checklists/requirements.md @@ -36,12 +36,14 @@ All checklist items are complete. The specification is ready to proceed to `/spe ### Validation Details **Content Quality**: ✅ PASS + - Specification focuses on WHAT users need (admin statistics dashboards) and WHY (operational monitoring, user engagement tracking, platform insights) - No mention of specific technologies like React, TypeScript, or Recharts in the specification itself - Written in plain language that non-technical stakeholders can understand - All mandatory sections (User Scenarios, Requirements, Success Criteria) are completed **Requirement Completeness**: ✅ PASS + - No [NEEDS CLARIFICATION] markers present - All 42 functional requirements are specific and testable (e.g., "System MUST display active user counts separated by platform") - Success criteria are quantifiable (e.g., "Dashboard loads within 2 seconds", "95% platform detection accuracy") @@ -51,6 +53,7 @@ All checklist items are complete. The specification is ready to proceed to `/spe - Dependencies on PostHog and Sentry for analytics are clearly stated **Feature Readiness**: ✅ PASS + - Each functional requirement traces back to user stories and acceptance scenarios - User scenarios cover all primary flows: system health monitoring (P1), Golden Signals (P1), engagement tracking (P2), platform comparison (P2), navigation (P1), responsive design (P2) - Success criteria measure actual user outcomes (load time, accuracy, accessibility) rather than implementation details diff --git a/specs/002-admin-stats-dashboard/data-model.md b/specs/002-admin-stats-dashboard/data-model.md index c1c6be2..ae9c633 100644 --- a/specs/002-admin-stats-dashboard/data-model.md +++ b/specs/002-admin-stats-dashboard/data-model.md @@ -8,6 +8,7 @@ This document defines the data structures for admin statistics dashboard metrics ## Overview The admin statistics dashboard works with aggregated analytics data from PostHog and Sentry. All data is: + - **Read-only**: Dashboard displays metrics, does not create/update data - **Ephemeral**: No persistent storage in mobile app (React Query in-memory cache only) - **Time-series**: Most metrics track changes over time (hourly, daily) @@ -21,14 +22,14 @@ Represents tracked user attributes from PostHog for platform/OS analytics. ```typescript interface AnalyticsUser { - userId: string // Unique user identifier - platform: Platform // 'web' | 'mobile' | 'both' - deviceType: DeviceType // 'Desktop' | 'Mobile' | 'Tablet' - os: string // 'iOS' | 'Android' | 'Mac OS X' | 'Windows' | 'Linux' - osVersion: string // e.g., '14.2', '10.15.7' - deviceModel?: string // e.g., 'iPhone 14 Pro', 'SM-G998B' - firstSeen: Date // First event timestamp - lastActive: Date // Most recent activity timestamp + userId: string // Unique user identifier + platform: Platform // 'web' | 'mobile' | 'both' + deviceType: DeviceType // 'Desktop' | 'Mobile' | 'Tablet' + os: string // 'iOS' | 'Android' | 'Mac OS X' | 'Windows' | 'Linux' + osVersion: string // e.g., '14.2', '10.15.7' + deviceModel?: string // e.g., 'iPhone 14 Pro', 'SM-G998B' + firstSeen: Date // First event timestamp + lastActive: Date // Most recent activity timestamp } type Platform = 'web' | 'mobile' | 'both' @@ -36,10 +37,12 @@ type DeviceType = 'Desktop' | 'Mobile' | 'Tablet' ``` **Relationships**: + - One user can have activity on multiple platforms (tracked separately) - Users tracked via PostHog `$os`, `$device_type`, `$lib` properties **Validation Rules**: + - `userId` must be non-empty string - `platform` derived from `$lib` property: 'posthog-react-native' → 'mobile', 'posthog-js' → 'web' - `firstSeen` ≤ `lastActive` @@ -50,30 +53,32 @@ Represents a single measurement at a point in time for time-series charts. ```typescript interface MetricDataPoint { - timestamp: Date // When measurement was taken - metricType: MetricType // Type of metric - value: number // Metric value - platform?: Platform // Platform filter (optional) + timestamp: Date // When measurement was taken + metricType: MetricType // Type of metric + value: number // Metric value + platform?: Platform // Platform filter (optional) metadata?: Record // Additional context } type MetricType = - | 'active_users' // Active user count + | 'active_users' // Active user count | 'requests_per_minute' // Request rate - | 'error_rate' // Error percentage - | 'latency_p50' // 50th percentile latency - | 'latency_p95' // 95th percentile latency - | 'latency_p99' // 99th percentile latency - | 'cpu_usage' // CPU utilization percentage - | 'memory_usage' // Memory utilization percentage - | 'db_pool_usage' // Database connection pool percentage + | 'error_rate' // Error percentage + | 'latency_p50' // 50th percentile latency + | 'latency_p95' // 95th percentile latency + | 'latency_p99' // 99th percentile latency + | 'cpu_usage' // CPU utilization percentage + | 'memory_usage' // Memory utilization percentage + | 'db_pool_usage' // Database connection pool percentage ``` **Relationships**: + - Multiple data points form a time series - Can be filtered by platform for platform-specific metrics **Validation Rules**: + - `timestamp` must be valid date - `value` must be non-negative number - `error_rate`, CPU/memory/DB percentages: 0-100 @@ -85,19 +90,19 @@ Represents aggregated request metrics for traffic/latency analysis. ```typescript interface RequestMetrics { - timeRange: TimeRange // Period these metrics cover - totalRequests: number // Total request count + timeRange: TimeRange // Period these metrics cover + totalRequests: number // Total request count requestsPerMinute: number // Average RPM - platform: Platform // 'web' | 'mobile' | 'combined' + platform: Platform // 'web' | 'mobile' | 'combined' statusCodeBreakdown: { - '2xx': number // Successful requests - '4xx': number // Client errors - '5xx': number // Server errors + '2xx': number // Successful requests + '4xx': number // Client errors + '5xx': number // Server errors } latencyPercentiles: { - p50: number // 50th percentile (ms) - p95: number // 95th percentile (ms) - p99: number // 99th percentile (ms) + p50: number // 50th percentile (ms) + p95: number // 95th percentile (ms) + p99: number // 99th percentile (ms) } } @@ -109,10 +114,12 @@ interface TimeRange { ``` **Relationships**: + - Derived from backend/Sentry request logs - Can be aggregated by platform or combined **Validation Rules**: + - `totalRequests` ≥ sum of status code counts - `requestsPerMinute` = `totalRequests` / minutes in time range - All latency percentiles must be: p50 ≤ p95 ≤ p99 @@ -123,30 +130,32 @@ Represents error statistics for error tracking dashboard. ```typescript interface ErrorEvent { - errorId: string // Unique error identifier (Sentry issue ID) - message: string // Error message - errorType: ErrorType // '4xx' | '5xx' - count: number // Occurrences in time period - lastSeen: Date // Most recent occurrence - platform?: Platform // Where error occurred + errorId: string // Unique error identifier (Sentry issue ID) + message: string // Error message + errorType: ErrorType // '4xx' | '5xx' + count: number // Occurrences in time period + lastSeen: Date // Most recent occurrence + platform?: Platform // Where error occurred affectedUsers?: number // Number of users impacted } type ErrorType = '4xx' | '5xx' interface ErrorBreakdown { - total4xx: number // Total 4xx errors - total5xx: number // Total 5xx errors - percentage4xx: number // 4xx as % of total - percentage5xx: number // 5xx as % of total + total4xx: number // Total 4xx errors + total5xx: number // Total 5xx errors + percentage4xx: number // 4xx as % of total + percentage5xx: number // 5xx as % of total } ``` **Relationships**: + - Multiple occurrences of same error grouped by `errorId` - Can be filtered by platform **Validation Rules**: + - `count` must be positive integer - `percentage4xx` + `percentage5xx` should equal 100 - `message` must be non-empty string @@ -157,18 +166,18 @@ Represents overall platform health at a point in time. ```typescript interface SystemHealthStatus { - status: HealthStatus // 'healthy' | 'degraded' | 'down' - timestamp: Date // When status was determined + status: HealthStatus // 'healthy' | 'degraded' | 'down' + timestamp: Date // When status was determined metrics: { activeUsers: { total: number web: number mobile: number - both: number // Concurrent web + mobile sessions + both: number // Concurrent web + mobile sessions } requestsPerMinute: number - errorRate: number // Percentage (0-100) - latencyP95: number // Milliseconds + errorRate: number // Percentage (0-100) + latencyP95: number // Milliseconds } statusReasons: string[] // Why status is not healthy } @@ -182,10 +191,12 @@ type HealthStatus = 'healthy' | 'degraded' | 'down' ``` **Relationships**: + - Derived from current RequestMetrics and ErrorEvent data - Snapshots taken every 5 minutes (auto-refresh interval) **Validation Rules**: + - `errorRate`: 0-100 (percentage) - `latencyP95` ≥ 0 (milliseconds) - `activeUsers.total` ≥ `activeUsers.web` + `activeUsers.mobile` - `activeUsers.both` @@ -198,30 +209,32 @@ Represents user engagement statistics (DAU/MAU). ```typescript interface EngagementMetrics { timeRange: TimeRange - dau: DailyActiveUsers[] // 24-hour hourly breakdown - mau: number // 30-day unique users + dau: DailyActiveUsers[] // 24-hour hourly breakdown + mau: number // 30-day unique users stickiness: number | null // (DAU/MAU) * 100, or null if MAU = 0 newVsReturning: NewVsReturningData[] } interface DailyActiveUsers { - hour: Date // Hour timestamp - uniqueUsers: number // Unique users in that hour + hour: Date // Hour timestamp + uniqueUsers: number // Unique users in that hour } interface NewVsReturningData { - hour: Date // Hour timestamp - newUsers: number // Users with firstSeen in last 24h - returningUsers: number // Users with firstSeen > 24h ago + hour: Date // Hour timestamp + newUsers: number // Users with firstSeen in last 24h + returningUsers: number // Users with firstSeen > 24h ago } ``` **Relationships**: + - `dau` is array of hourly data points over 24 hours - `mau` is single number for rolling 30-day window - `newVsReturning` uses `AnalyticsUser.firstSeen` to classify users **Validation Rules**: + - `stickiness` = (current DAU / MAU) × 100, or `null` if MAU = 0 - `stickiness` range: 0-100 when not null - `dau` array should have 24 entries (hourly buckets) @@ -237,7 +250,7 @@ interface PlatformDistribution { distribution: { web: PlatformStats mobile: PlatformStats - both: number // Users active on web AND mobile + both: number // Users active on web AND mobile } osVersions: { mobile: OSVersionBreakdown[] @@ -246,26 +259,28 @@ interface PlatformDistribution { } interface PlatformStats { - activeUsers: number // Unique users on platform - percentage: number // % of total users + activeUsers: number // Unique users on platform + percentage: number // % of total users requestsPerMinute: number - errorRate: number // Platform-specific error rate + errorRate: number // Platform-specific error rate } interface OSVersionBreakdown { - os: string // 'iOS', 'Android', 'macOS', 'Windows', 'Linux' - version: string // e.g., '14.2', '10.15.7', 'Unknown' - count: number // Number of users - percentage: number // % of platform users + os: string // 'iOS', 'Android', 'macOS', 'Windows', 'Linux' + version: string // e.g., '14.2', '10.15.7', 'Unknown' + count: number // Number of users + percentage: number // % of platform users } ``` **Relationships**: + - `distribution.both` counts users appearing in both web and mobile - `osVersions.mobile` includes iOS and Android - `osVersions.web` includes macOS, Windows, Linux **Validation Rules**: + - `percentage` values: 0-100 - Sum of `osVersions` percentages per platform should equal 100 - `count` must be non-negative integer @@ -285,32 +300,32 @@ interface GoldenSignalsMetrics { } interface LatencyMetrics { - timeSeries: MetricDataPoint[] // p50, p95, p99 over time - trend: TrendIndicator // 'improving' | 'stable' | 'degrading' - currentP95: number // Current p95 latency (ms) + timeSeries: MetricDataPoint[] // p50, p95, p99 over time + trend: TrendIndicator // 'improving' | 'stable' | 'degrading' + currentP95: number // Current p95 latency (ms) } interface TrafficMetrics { - timeSeries: MetricDataPoint[] // RPM over time - currentRPM: number // Current requests per minute - activeSessions: number // Current active sessions + timeSeries: MetricDataPoint[] // RPM over time + currentRPM: number // Current requests per minute + activeSessions: number // Current active sessions } interface ErrorMetrics { - timeSeries: MetricDataPoint[] // Error rate over time - breakdown: ErrorBreakdown // 4xx vs 5xx split - topErrors: ErrorEvent[] // Top 5 errors by count + timeSeries: MetricDataPoint[] // Error rate over time + breakdown: ErrorBreakdown // 4xx vs 5xx split + topErrors: ErrorEvent[] // Top 5 errors by count } interface SaturationMetrics { - cpu: SaturationDataPoint // Current CPU usage - memory: SaturationDataPoint // Current memory usage - dbPool: SaturationDataPoint // Current DB connection pool + cpu: SaturationDataPoint // Current CPU usage + memory: SaturationDataPoint // Current memory usage + dbPool: SaturationDataPoint // Current DB connection pool } interface SaturationDataPoint { - current: number // Current usage percentage (0-100) - threshold: number // Warning threshold (e.g., 80) + current: number // Current usage percentage (0-100) + threshold: number // Warning threshold (e.g., 80) status: 'normal' | 'warning' | 'critical' } @@ -321,11 +336,13 @@ type TrendIndicator = 'improving' | 'stable' | 'degrading' ``` **Relationships**: + - `latency.timeSeries` contains multiple MetricDataPoint entries - `errors.topErrors` limited to top 5 by count - All metrics cover same `timeRange` **Validation Rules**: + - All percentages: 0-100 - Latency values in milliseconds, must be positive - `topErrors` sorted by count descending @@ -340,35 +357,40 @@ All backend endpoints follow this response structure: ```typescript interface APIResponse { data: T - cached: boolean // True if returned from cache - lastUpdated: Date // When data was fetched/calculated - error: string | null // Error message if API call failed but cached data available + cached: boolean // True if returned from cache + lastUpdated: Date // When data was fetched/calculated + error: string | null // Error message if API call failed but cached data available } ``` ### Endpoint Response Types **GET /api/admin/analytics/system-health** + ```typescript type SystemHealthResponse = APIResponse ``` **GET /api/admin/analytics/golden-signals?period=7d|30d** + ```typescript type GoldenSignalsResponse = APIResponse ``` **GET /api/admin/analytics/engagement?period=24h|7d|30d** + ```typescript type EngagementResponse = APIResponse ``` **GET /api/admin/analytics/platforms?period=30d** + ```typescript type PlatformDistributionResponse = APIResponse ``` **GET /api/admin/errors/summary?period=7d|30d** + ```typescript type ErrorSummaryResponse = APIResponse ``` @@ -379,22 +401,23 @@ Mobile app uses these cache keys for React Query: ```typescript // System health (overview dashboard) -['admin', 'system-health'] - -// Golden Signals -['admin', 'golden-signals', period] // period: '7d' | '30d' - -// Engagement (DAU/MAU) -['admin', 'engagement', period] // period: '24h' | '7d' | '30d' - -// Platform distribution -['admin', 'platforms', period] // period: '30d' - -// Error summary -['admin', 'errors', 'summary', period] // period: '7d' | '30d' +;['admin', 'system-health'][ + // Golden Signals + ('admin', 'golden-signals', period) +][ // period: '7d' | '30d' + // Engagement (DAU/MAU) + ('admin', 'engagement', period) +][ // period: '24h' | '7d' | '30d' + // Platform distribution + ('admin', 'platforms', period) +][ // period: '30d' + // Error summary + ('admin', 'errors', 'summary', period) +] // period: '7d' | '30d' ``` **Cache Configuration**: + - `staleTime`: 4 minutes (data considered fresh) - `cacheTime`: 15 minutes (data kept in cache) - `refetchInterval`: 5 minutes (auto-refresh) @@ -500,32 +523,33 @@ Mobile app uses these cache keys for React Query: ```typescript interface QueryError { status: 'error' - message: string // User-friendly error message - code: ErrorCode // Machine-readable error code - retryable: boolean // Can user retry? + message: string // User-friendly error message + code: ErrorCode // Machine-readable error code + retryable: boolean // Can user retry? } type ErrorCode = - | 'ANALYTICS_API_DOWN' // PostHog/Sentry unavailable - | 'UNAUTHORIZED' // Not admin user - | 'RATE_LIMIT_EXCEEDED' // Too many requests - | 'INVALID_TIME_RANGE' // Bad query parameters - | 'NETWORK_ERROR' // Network failure + | 'ANALYTICS_API_DOWN' // PostHog/Sentry unavailable + | 'UNAUTHORIZED' // Not admin user + | 'RATE_LIMIT_EXCEEDED' // Too many requests + | 'INVALID_TIME_RANGE' // Bad query parameters + | 'NETWORK_ERROR' // Network failure ``` ### Fallback Behaviors -| Error Type | Mobile App Behavior | -|-----------|-------------------| -| Analytics API down | Show cached data with "Last updated: X mins ago" | -| No cached data | Show "Unable to load metrics" with retry button | -| Rate limit exceeded | Show "Too many requests, try again in X minutes" | -| Unauthorized | Redirect to main app with "Admin access required" | -| Network error | Show "Network error" with retry button | +| Error Type | Mobile App Behavior | +| ------------------- | ------------------------------------------------- | +| Analytics API down | Show cached data with "Last updated: X mins ago" | +| No cached data | Show "Unable to load metrics" with retry button | +| Rate limit exceeded | Show "Too many requests, try again in X minutes" | +| Unauthorized | Redirect to main app with "Admin access required" | +| Network error | Show "Network error" with retry button | ## Calculation Formulas ### Stickiness + ``` stickiness = (DAU / MAU) × 100 @@ -537,6 +561,7 @@ Where: ``` ### Error Rate + ``` errorRate = (errorCount / totalRequests) × 100 @@ -547,6 +572,7 @@ Where: ``` ### Requests Per Minute + ``` rpm = totalRequests / (timeRange.minutes) @@ -557,6 +583,7 @@ Where: ``` ### Platform Percentage + ``` platformPercentage = (platformUsers / totalUsers) × 100 diff --git a/specs/002-admin-stats-dashboard/plan.md b/specs/002-admin-stats-dashboard/plan.md index 79ad535..1efbde6 100644 --- a/specs/002-admin-stats-dashboard/plan.md +++ b/specs/002-admin-stats-dashboard/plan.md @@ -22,6 +22,7 @@ Admin-only statistics dashboard for the ACP mobile app to track usage across bot **Scale/Scope**: 4 dashboard screens (Overview, Golden Signals, Engagement, Platforms), 24-hour data retention for charts, supports displaying metrics for up to 100k+ users and 1M+ events/day **Integration Requirements**: + - NEEDS CLARIFICATION: PostHog API endpoint structure, authentication method (API key vs project token), query syntax for custom analytics - NEEDS CLARIFICATION: Sentry API integration approach (REST API vs SDK methods), authentication for fetching error metrics - RESOLVED: Chart library = react-native-gifted-charts (uses existing react-native-svg dependency, simpler API, good performance) @@ -35,10 +36,11 @@ _GATE: Must pass before Phase 0 research. Re-check after Phase 1 design._ **Status**: ✅ PASS (No constitution file exists - using established patterns from 001-acp-mobile) This feature extends the existing acp-mobile app architecture. Following established patterns: -- Expo Router for file-based routing (add app/admin/* routes) + +- Expo Router for file-based routing (add app/admin/\* routes) - React Query for data fetching and caching -- Component-based UI architecture (create components/admin/*) -- Service layer for API integration (add services/analytics/*) +- Component-based UI architecture (create components/admin/\*) +- Service layer for API integration (add services/analytics/\*) - Role-based access control (extend existing auth service) - Offline-capable with cached data (React Query stale-while-revalidate) diff --git a/specs/002-admin-stats-dashboard/quickstart.md b/specs/002-admin-stats-dashboard/quickstart.md index 05d7e66..179037b 100644 --- a/specs/002-admin-stats-dashboard/quickstart.md +++ b/specs/002-admin-stats-dashboard/quickstart.md @@ -27,6 +27,7 @@ This library uses `react-native-svg` which is already installed in the project, ### 2. Verify Existing Dependencies These should already be in `package.json`: + ```json { "react-native-svg": "15.12.1", @@ -64,6 +65,7 @@ services/analytics/ # NEW directory ``` **Estimated LOC**: ~2,500 lines total + - Routes: ~400 lines - Components: ~1,200 lines - Services: ~600 lines @@ -73,6 +75,7 @@ services/analytics/ # NEW directory ### Backend API Changes (Coordinate with Backend Team) Required backend endpoints: + ``` GET /api/admin/analytics/system-health GET /api/admin/analytics/golden-signals?period=7d|30d @@ -82,6 +85,7 @@ GET /api/admin/errors/summary?period=7d|30d ``` Backend responsibilities: + - Query PostHog Personal API - Query Sentry REST API - Implement Redis caching (15-30 min cache duration) @@ -95,6 +99,7 @@ Backend responsibilities: **Time estimate**: 2-3 days 1. **Set up environment variables**: + ```bash # Backend .env POSTHOG_PERSONAL_API_KEY=phx_xxx... @@ -118,13 +123,14 @@ REDIS_URL=redis://... 4. **Create backend API endpoints** matching `contracts/backend-api.yaml` 5. **Add Redis caching layer**: + ```typescript const CACHE_DURATIONS = { 'system-health': 5 * 60, 'golden-signals': 5 * 60, - 'engagement': 15 * 60, - 'platforms': 30 * 60, - 'errors': 5 * 60, + engagement: 15 * 60, + platforms: 30 * 60, + errors: 5 * 60, } ``` @@ -135,6 +141,7 @@ const CACHE_DURATIONS = { **Time estimate**: 1-2 days 1. **Create directory structure**: + ```bash mkdir -p app/admin mkdir -p components/admin/{charts,metrics,layout,guards} @@ -143,6 +150,7 @@ mkdir -p constants ``` 2. **Implement AdminGuard component**: + ```typescript // components/admin/guards/AdminGuard.tsx import { useAuth } from '@/services/auth/authContext' @@ -169,6 +177,7 @@ export function AdminGuard({ children }: { children: React.ReactNode }) { ``` 3. **Set up admin routing**: + ```typescript // app/admin/_layout.tsx import { AdminGuard } from '@/components/admin/guards/AdminGuard' @@ -189,6 +198,7 @@ export default function AdminLayout() { ``` 4. **Create TypeScript types**: + ```typescript // services/analytics/types.ts export interface SystemHealthStatus { @@ -216,6 +226,7 @@ export interface SystemHealthStatus { **Time estimate**: 1-2 days 1. **Create API client wrapper**: + ```typescript // services/analytics/client.ts import { apiClient } from '@/services/api/client' @@ -234,6 +245,7 @@ export const analyticsApi = { ``` 2. **Create React Query hooks**: + ```typescript // services/analytics/hooks/useSystemHealth.ts import { useQuery } from '@tanstack/react-query' @@ -246,8 +258,8 @@ export function useSystemHealth() { const response = await analyticsApi.getSystemHealth() return response.data }, - staleTime: 4 * 60 * 1000, // 4 minutes - cacheTime: 15 * 60 * 1000, // 15 minutes + staleTime: 4 * 60 * 1000, // 4 minutes + cacheTime: 15 * 60 * 1000, // 15 minutes refetchInterval: 5 * 60 * 1000, // 5 minutes auto-refresh }) } @@ -265,6 +277,7 @@ export function useSystemHealth() { **Time estimate**: 2-3 days 1. **Create ChartContainer wrapper**: + ```typescript // components/admin/charts/ChartContainer.tsx import { View, Text, ActivityIndicator } from 'react-native' @@ -289,6 +302,7 @@ export function ChartContainer({ title, loading, error, children }: ChartContain ``` 2. **Implement LineChart component**: + ```typescript // components/admin/charts/LineChart.tsx import { LineChart as GiftedLineChart } from 'react-native-gifted-charts' @@ -338,6 +352,7 @@ export function LineChart({ title, data, loading, error, color = '#007AFF' }: Li - `GaugeChart.tsx` - Saturation metrics (can use progress bars or custom component) 4. **Add responsive sizing hook**: + ```typescript // hooks/useResponsiveChart.ts import { useWindowDimensions } from 'react-native' @@ -359,6 +374,7 @@ export function useResponsiveChart() { **Time estimate**: 1 day 1. **Create MetricCard**: + ```typescript // components/admin/metrics/MetricCard.tsx interface MetricCardProps { @@ -380,6 +396,7 @@ export function MetricCard({ label, value, trend, status }: MetricCardProps) { ``` 2. **Create StatusIndicator**: + ```typescript // components/admin/metrics/StatusIndicator.tsx interface StatusIndicatorProps { @@ -408,6 +425,7 @@ export function StatusIndicator({ status }: StatusIndicatorProps) { **Time estimate**: 3-4 days 1. **Implement Overview Dashboard**: + ```typescript // app/admin/index.tsx import { useSystemHealth } from '@/services/analytics/hooks/useSystemHealth' @@ -460,6 +478,7 @@ export default function OverviewDashboard() { **Time estimate**: 1-2 days 1. **Component tests**: + ```typescript // app/admin/__tests__/index.test.tsx import { render, screen } from '@testing-library/react-native' @@ -498,18 +517,19 @@ test('displays system status', () => { **Time estimate**: 1 day 1. **Add constants file**: + ```typescript // constants/AdminMetrics.ts export const ADMIN_METRICS = { REFRESH_INTERVAL: 5 * 60 * 1000, // 5 minutes - STALE_TIME: 4 * 60 * 1000, // 4 minutes - CACHE_TIME: 15 * 60 * 1000, // 15 minutes + STALE_TIME: 4 * 60 * 1000, // 4 minutes + CACHE_TIME: 15 * 60 * 1000, // 15 minutes HEALTH_THRESHOLDS: { - ERROR_RATE_WARNING: 1, // 1% = degraded - ERROR_RATE_CRITICAL: 5, // 5% = down - LATENCY_WARNING: 100, // 100ms = degraded - LATENCY_CRITICAL: 500, // 500ms = down + ERROR_RATE_WARNING: 1, // 1% = degraded + ERROR_RATE_CRITICAL: 5, // 5% = down + LATENCY_WARNING: 100, // 100ms = degraded + LATENCY_CRITICAL: 500, // 500ms = down }, CHART_COLORS: { @@ -522,6 +542,7 @@ export const ADMIN_METRICS = { ``` 2. **Update agent context**: + ```bash .specify/scripts/bash/update-agent-context.sh claude ``` @@ -539,6 +560,7 @@ export const ADMIN_METRICS = { ### 1. Backend Testing (Backend Team) Test each endpoint with curl/Postman: + ```bash # System health curl -H "Authorization: Bearer $JWT" \ @@ -560,12 +582,14 @@ curl -H "Authorization: Bearer $JWT" \ ### 2. Mobile Testing **Development mode**: + ```bash npm start # Press 'i' for iOS or 'a' for Android ``` **Test scenarios**: + 1. Log in as admin user → navigate to "Admin" tab 2. Verify all 4 dashboards load 3. Pull down to refresh → verify data updates @@ -577,6 +601,7 @@ npm start ### 3. Performance Testing Use React DevTools Profiler to verify: + - Dashboard loads < 2s - Chart render < 1s - 60fps scrolling @@ -587,6 +612,7 @@ Use React DevTools Profiler to verify: ### Issue: "Admin access required" error for admin users **Solution**: Check JWT token contains `role: "admin"`: + ```typescript // Decode JWT in browser console const token = localStorage.getItem('jwt') @@ -597,12 +623,14 @@ console.log(decoded.role) // should be "admin" ### Issue: Charts not rendering **Solution**: Verify react-native-svg is installed: + ```bash npm list react-native-svg # Should show: react-native-svg@15.12.1 (or higher) ``` If charts still don't render, check that you're importing from the correct package: + ```typescript // Correct import { LineChart, PieChart, BarChart } from 'react-native-gifted-charts' @@ -614,6 +642,7 @@ import { LineChart } from 'victory-native-xl' ### Issue: "Unable to load metrics" even when backend is up **Solution**: Check CORS headers on backend: + ```typescript // Backend should include: Access-Control-Allow-Origin: https://your-app.com @@ -623,6 +652,7 @@ Access-Control-Allow-Headers: Authorization, Content-Type ### Issue: Auto-refresh not working **Solution**: React Query refetchInterval requires app to be in foreground: + ```typescript // Add focus refetch refetchOnWindowFocus: true, @@ -632,6 +662,7 @@ refetchOnMount: true, ### Issue: Stale data after backend changes **Solution**: Invalidate React Query cache: + ```typescript import { useQueryClient } from '@tanstack/react-query' @@ -671,6 +702,7 @@ queryClient.invalidateQueries({ queryKey: ['admin'] }) ## Contact For questions about this implementation: + - Mobile team: Slack #acp-mobile - Backend team: Slack #acp-backend - Design questions: Review spec.md and data-model.md first diff --git a/specs/002-admin-stats-dashboard/research.md b/specs/002-admin-stats-dashboard/research.md index 6d1e0cc..494245b 100644 --- a/specs/002-admin-stats-dashboard/research.md +++ b/specs/002-admin-stats-dashboard/research.md @@ -9,16 +9,20 @@ This document resolves all "NEEDS CLARIFICATION" items from the Technical Contex ## 1. PostHog Integration ### Decision + Use **Personal API Keys** for backend proxy endpoints + **Project API Key** for standard event tracking via React Native SDK. ### Rationale + - **Project API Keys** are designed for public POST-only endpoints (event tracking) and are already integrated in the mobile app (posthog-react-native: ^4.14.0) - **Personal API Keys** provide organization-level access to private GET endpoints required for querying analytics (DAU/MAU, platform stats, time-series data) - Personal API Keys have rate limits: Query endpoint 120/hour, analytics endpoints 240/minute and 1200/hour - Personal API Keys must NEVER be exposed in mobile apps due to org-level access concerns ### Implementation Approach + **Backend proxy pattern** (see Section 5): + - Mobile app uses existing Project API Key for event tracking - Admin dashboard queries go through ACP backend endpoints (`/api/admin/analytics/*`) - Backend uses Personal API Key to query PostHog's Query API @@ -26,6 +30,7 @@ Use **Personal API Keys** for backend proxy endpoints + **Project API Key** for ### API Query Patterns **HogQL Query API for time-series data:** + ```typescript // POST /api/projects/:project_id/query/ { @@ -39,11 +44,13 @@ Use **Personal API Keys** for backend proxy endpoints + **Project API Key** for ``` **DAU/MAU Calculation:** + - Create insight with series aggregated by "unique users" (DAU) and "monthly active users" (MAU) - Use formula mode with A/B to calculate stickiness ratio - Filter by `$device_type`, `$os`, `$browser` for platform-specific metrics ### Authentication + ```typescript // Backend to PostHog headers: { @@ -52,22 +59,27 @@ headers: { ``` ### Alternatives Considered + - ❌ **Direct API calls from mobile**: Rejected due to Personal API Key security concerns (org-level access) - ❌ **SDK methods only**: PostHog React Native SDK doesn't expose analytics query methods, only event capture ## 2. Sentry Integration ### Decision + Use **Sentry REST API with Auth Tokens** via backend proxy for fetching error metrics and performance data. ### Rationale + - **DSN (Data Source Name)** is for sending errors TO Sentry from the SDK, not for querying data - **Auth Tokens** (Bearer tokens) are required for reading organization statistics and project metrics via REST API - Sentry React Native SDK (~7.2.0 in package.json) handles error capture, not analytics queries - API provides comprehensive endpoints for all required metrics ### Authentication Approach + Create **Internal Integration for Organization Token**: + ```typescript headers: { 'Authorization': 'Bearer ' @@ -77,6 +89,7 @@ headers: { ### API Endpoints for Admin Dashboard **Error Rate & Breakdown:** + ```bash GET https://sentry.io/api/0/projects/{org_slug}/{project_slug}/stats/ # Query parameter: stat = "received" | "rejected" @@ -84,26 +97,32 @@ GET https://sentry.io/api/0/projects/{org_slug}/{project_slug}/stats/ ``` **Top Errors:** + ```bash GET https://sentry.io/api/0/projects/{org_slug}/{project_slug}/issues/ ``` **Performance/Latency Metrics:** + ```bash GET https://sentry.io/api/0/organizations/{org_slug}/sessions/ ``` ### SDK vs REST API + **Use Both:** + - **SDK (@sentry/react-native)**: Continue using for error capture and performance tracing (already configured) - **REST API**: Backend-only for querying aggregated statistics for admin dashboard ### Implementation Notes + - SDK configuration: `tracesSampleRate` (0-1) for performance monitoring - Performance metrics include: throughput, latency, failed API requests, slow endpoints - Custom measurements via `Sentry.setMeasurement()` for app-specific metrics ### Alternatives Considered + - ❌ **DSN for queries**: DSN is for ingestion only, not data retrieval - ❌ **SDK-based queries**: SDK doesn't expose methods for fetching aggregated statistics - ❌ **Direct API from mobile**: Security risk - auth tokens should never be in client apps @@ -111,28 +130,33 @@ GET https://sentry.io/api/0/organizations/{org_slug}/sessions/ ## 3. Chart Library Selection ### Decision + Use **react-native-gifted-charts** for the admin statistics dashboard. ### Rationale **Zero Additional Dependencies:** + - Uses `react-native-svg` which is already installed in the project (v15.12.1) - No need to install React Native Skia (~2MB bundle size increase) - No bundle size impact - leverages existing dependencies **Simpler API:** + - Declarative, straightforward component API - Less boilerplate than hook-based libraries - Faster development time for admin dashboards - Easier to maintain and understand **Performance:** + - Good performance for admin dashboard use cases (60fps scrolling) - Handles thousands of data points smoothly - Sufficient for data visualization (doesn't need 100+ fps gaming performance) - Smooth animations with optional Reanimated support **Chart Types:** + - Line charts (latency, traffic, DAU trends) - Bar charts (platform comparison, requests/min) - Stacked bar charts (new vs returning users - built-in!) @@ -140,20 +164,24 @@ Use **react-native-gifted-charts** for the admin statistics dashboard. - Population pyramids (could enhance platform comparison) **TypeScript Support:** + - Full TypeScript definitions included - Actively maintained with regular updates through 2024 **Excellent Documentation:** + - Live interactive examples at https://gifted-charts.web.app/ - Clear API documentation with code snippets - Easy to find examples for each chart type **Dependencies Alignment:** Project already has the only required dependency: + - ✅ react-native-svg: 15.12.1 (already installed) - Optional: react-native-reanimated: ~4.1.1 (already installed) for enhanced animations ### Supported Chart Types + - Line charts - `` with curved/straight lines, multiple datasets - Bar charts - `` with grouping, spacing, rounded corners - Stacked bar charts - Built-in stacking support (perfect for new vs returning users) @@ -162,11 +190,13 @@ Project already has the only required dependency: - Population pyramids - For demographic comparisons ### Installation + ```bash npm install react-native-gifted-charts ``` ### Example Usage + ```typescript import { LineChart, BarChart, PieChart } from 'react-native-gifted-charts'; @@ -234,30 +264,36 @@ import { LineChart, BarChart, PieChart } from 'react-native-gifted-charts'; ### Alternatives Considered **❌ Victory Native XL:** + - Requires additional 2MB Skia dependency - More complex hook-based API (slower development) - Overkill for admin dashboards (100+ fps not needed) - Fewer built-in chart types **❌ Recharts:** + - Not compatible with React Native without WebView workaround - Designed for React web, uses DOM APIs **❌ React Native Chart Kit:** + - More limited than Gifted Charts - Less flexible customization - No stacked bar charts built-in **❌ Victory Native (Classic):** + - Superseded by Victory Native XL - Uses react-native-svg but with performance limitations ## 4. Platform Detection ### Decision + Use **PostHog's built-in autocaptured properties** (`$os`, `$browser`, `$device_type`) for platform detection. No custom event properties needed. ### Rationale + - PostHog automatically captures comprehensive platform properties on all events - No additional implementation work required - already available in existing integration - Consistent, standardized property names across all events @@ -285,6 +321,7 @@ Use **PostHog's built-in autocaptured properties** (`$os`, `$browser`, `$device_ ### Platform Detection Patterns **Web vs Mobile Detection:** + ```typescript // Mobile users filter: { "$device_type": "Mobile" } @@ -304,6 +341,7 @@ filter: { "$os": "Android" } ``` **OS Version Breakdown:** + ```typescript // HogQL query for OS version distribution { @@ -315,22 +353,26 @@ filter: { "$os": "Android" } ``` ### Cross-Platform User Tracking + - PostHog automatically associates events when user is authenticated on both platforms - Use `posthog.identify(userId)` with the same userId on both web and mobile - Events from both platforms unified in user's timeline ### Implementation Notes + - All properties starting with `$` are PostHog defaults (autocaptured) - Properties available in insights, cohorts, breakdowns, and HogQL queries - No performance impact - properties captured automatically with every event ### Best Practices + - Use `$device_type` for high-level mobile/desktop/tablet segmentation - Use `$os` + `$os_version` for OS-specific analytics and issue correlation - Use `$lib` as most reliable indicator of SDK source (react-native vs web) - Combine multiple properties for precise segmentation (e.g., "iOS 16+ mobile users") ### Alternatives Considered + - ❌ **Custom event properties**: Unnecessary overhead, duplicates built-in functionality - ❌ **User-agent parsing**: PostHog already does this automatically - ❌ **Manual platform tagging**: Error-prone, inconsistent, maintenance burden @@ -338,16 +380,19 @@ filter: { "$os": "Android" } ## 5. Backend Integration Strategy ### Decision + **Proxy all analytics API calls through ACP backend endpoints.** Never call PostHog/Sentry APIs directly from mobile. ### Rationale **Security:** + - **Critical**: Personal API Keys and auth tokens provide organization-level access - exposing them in mobile apps is a severe security risk - Any secret stored in a client app is extractable by attackers - Backend proxy removes API keys from mobile attack surface entirely **Control & Flexibility:** + - Backend can throttle requests to prevent abuse - Can disable/rate-limit specific user accounts - Can restrict which analytics queries are allowed @@ -355,17 +400,20 @@ filter: { "$os": "Android" } - Can aggregate/transform data before sending to mobile **Performance & Caching:** + - Backend can cache expensive analytics queries - Reduces rate limit pressure on PostHog/Sentry - PostHog Query API: only 120 requests/hour - requires caching for multiple admin users - Can implement smart cache invalidation strategies **Rate Limit Management:** + - PostHog private endpoints: Query 120/hour, Analytics 240/minute & 1200/hour - Multiple admin users won't compete for mobile app's quota - Backend can implement request queuing and retry logic **Maintainability:** + - API keys managed in one place (backend env vars) - Key rotation doesn't require mobile app updates - Can switch analytics providers without mobile changes @@ -434,19 +482,21 @@ filter: { "$os": "Android" } ### Caching Strategy **Redis-based caching on backend:** + ```typescript const CACHE_DURATIONS = { - 'dau-mau': 15 * 60, // 15 minutes + 'dau-mau': 15 * 60, // 15 minutes 'platform-distribution': 30 * 60, // 30 minutes - 'os-versions': 60 * 60, // 1 hour - 'error-summary': 5 * 60, // 5 minutes - 'performance': 5 * 60, // 5 minutes + 'os-versions': 60 * 60, // 1 hour + 'error-summary': 5 * 60, // 5 minutes + performance: 5 * 60, // 5 minutes } const cacheKey = `analytics:${endpoint}:${period}:${date}` ``` **Benefits:** + - Protects against PostHog Query API's 120/hour limit - Multiple admin users don't overwhelm rate limits - Faster response times for mobile app @@ -455,6 +505,7 @@ const cacheKey = `analytics:${endpoint}:${period}:${date}` ### Authentication & Authorization **Mobile to Backend:** + ```typescript // Use existing JWT auth from ACP backend headers: { @@ -468,6 +519,7 @@ headers: { ``` **Backend to Analytics APIs:** + ```typescript // PostHog headers: { @@ -498,7 +550,7 @@ export const useDAUMAU = (period: '7d' | '30d' | '90d') => { return useQuery({ queryKey: ['admin', 'dau-mau', period], queryFn: () => getDAUMAU(period), - staleTime: 5 * 60 * 1000, // 5 minutes + staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 15 * 60 * 1000, // 15 minutes }) } @@ -507,6 +559,7 @@ export const useDAUMAU = (period: '7d' | '30d' | '90d') => { ### Environment Variables **Backend (.env):** + ```bash # PostHog POSTHOG_PERSONAL_API_KEY=phx_xxx... @@ -522,6 +575,7 @@ REDIS_URL=redis://... ``` **Mobile app (.env.local):** + ```bash # Only public tokens for event capture (already configured) EXPO_PUBLIC_POSTHOG_API_KEY=phc_xxx... # Project API Key (public) @@ -532,6 +586,7 @@ EXPO_PUBLIC_POSTHOG_API_KEY=phc_xxx... # Project API Key (public) ### Performance Considerations **Request Batching:** + ```typescript // Backend can batch multiple PostHog queries POST /api/admin/analytics/dashboard @@ -545,6 +600,7 @@ POST /api/admin/analytics/dashboard ``` **Progressive Loading:** + ```typescript // Mobile requests summary first, details on-demand 1. GET /api/admin/analytics/summary (fast, cached) @@ -553,6 +609,7 @@ POST /api/admin/analytics/dashboard ``` ### Error Handling + - Backend should handle PostHog/Sentry API failures gracefully - Return cached data if available when API fails - Include error status in response: `{ data: ..., cached: true, error: null }` @@ -560,27 +617,29 @@ POST /api/admin/analytics/dashboard ### Alternatives Considered **❌ Direct API calls from mobile:** + - Security risk: API keys extractable from mobile app - Rate limit issues: All users share mobile app's quota - Inflexible: Can't add business logic or caching - Key rotation requires app updates **❌ Store API keys in Expo SecureStore:** + - Still extractable by determined attackers - Doesn't solve rate limiting or caching issues - Creates false sense of security ## Summary of Technical Decisions -| Component | Technology/Approach | Key Reason | -|-----------|-------------------|-----------| -| **PostHog Queries** | Personal API Key → Backend Proxy | Security, rate limits, caching | -| **Sentry Queries** | Auth Token → Backend Proxy | Security, organization-level access | -| **Charts** | react-native-gifted-charts | Zero deps (uses existing react-native-svg), simpler API, faster development | -| **Platform Detection** | PostHog built-in properties | No implementation needed, autocaptured | -| **Architecture** | Backend proxy pattern | Security, control, performance, caching | -| **Mobile Caching** | React Query | Client-side cache layer | -| **Backend Caching** | Redis | Rate limit protection, performance | +| Component | Technology/Approach | Key Reason | +| ---------------------- | -------------------------------- | --------------------------------------------------------------------------- | +| **PostHog Queries** | Personal API Key → Backend Proxy | Security, rate limits, caching | +| **Sentry Queries** | Auth Token → Backend Proxy | Security, organization-level access | +| **Charts** | react-native-gifted-charts | Zero deps (uses existing react-native-svg), simpler API, faster development | +| **Platform Detection** | PostHog built-in properties | No implementation needed, autocaptured | +| **Architecture** | Backend proxy pattern | Security, control, performance, caching | +| **Mobile Caching** | React Query | Client-side cache layer | +| **Backend Caching** | Redis | Rate limit protection, performance | ## Integration Testing Strategy diff --git a/specs/002-admin-stats-dashboard/spec.md b/specs/002-admin-stats-dashboard/spec.md index d22365e..5c9fce8 100644 --- a/specs/002-admin-stats-dashboard/spec.md +++ b/specs/002-admin-stats-dashboard/spec.md @@ -161,7 +161,7 @@ As an admin, I need the statistics dashboards to work on both mobile devices (to ### Functional Requirements - **FR-001**: System MUST restrict access to all admin statistics dashboards to users with role="admin" -- **FR-002**: System MUST redirect non-admin users attempting to access /admin/stats/* to the main app with an unauthorized message +- **FR-002**: System MUST redirect non-admin users attempting to access /admin/stats/\* to the main app with an unauthorized message - **FR-003**: System MUST display active user counts separated by platform (web, mobile, total) on the overview dashboard - **FR-004**: System MUST calculate and display requests per minute based on the last hour of activity - **FR-005**: System MUST display error rate as a percentage calculated from the last hour of requests diff --git a/specs/002-admin-stats-dashboard/tasks.md b/specs/002-admin-stats-dashboard/tasks.md index 41c4538..bc256e0 100644 --- a/specs/002-admin-stats-dashboard/tasks.md +++ b/specs/002-admin-stats-dashboard/tasks.md @@ -324,6 +324,7 @@ Phase 10 (Polish) ← All user stories must be complete ### Parallel Execution Examples **Scenario 1: Single Developer** + 1. Complete Phase 1 (Setup) → Phase 2 (Foundation) 2. Implement US1 (Overview) as MVP → Deploy to staging → Gather feedback 3. Implement US2 (Golden Signals) → Deploy @@ -334,15 +335,18 @@ Phase 10 (Polish) ← All user stories must be complete **Scenario 2: Team of 3 Developers** Developer A: + - Phase 1-2 (Setup + Foundation) - US1 (Overview) - US2 (Golden Signals) Developer B (in parallel with A after Phase 2): + - US3 (Engagement) - US4 (Platform Comparison) Developer C (in parallel with A & B after Phase 2): + - Chart components (T035-T039, T049) - Metric components (T023-T024) - Edge cases (Phase 9) @@ -350,10 +354,12 @@ Developer C (in parallel with A & B after Phase 2): **Scenario 3: Frontend + Backend Split** Backend Team (MUST START FIRST): + - T006-T012 (PostHog/Sentry integration + API endpoints) - Deploy to staging before mobile team starts Phase 3 Frontend Team (mobile): + - Phase 1-2 (Setup + Foundation) while backend works - Once backend staging ready: US1-6 in priority order @@ -364,6 +370,7 @@ Frontend Team (mobile): ### Manual Testing Scenarios **Admin User Flow**: + - [ ] Log in as admin user - [ ] Navigate to /admin/stats (Overview dashboard) - [ ] Verify Overview displays: status indicator, active users, error rate, latency, requests/min @@ -379,12 +386,14 @@ Frontend Team (mobile): - [ ] Check "Last updated" timestamp changes **Non-Admin User Flow**: + - [ ] Log in as developer or regular user (not admin) - [ ] Attempt to navigate to /admin/stats - [ ] Verify redirected to main app - [ ] Verify "Admin access required" message displays **Error Scenarios**: + - [ ] Turn off WiFi - [ ] Navigate to admin dashboards - [ ] Verify "Unable to load metrics" error displays @@ -394,6 +403,7 @@ Frontend Team (mobile): - [ ] Verify metrics load successfully **Responsive Design**: + - [ ] Test on iPhone SE (375px width) - verify charts readable - [ ] Test on iPad (768px width) - verify 2-column layout - [ ] Test on desktop (1920px width) - verify 3-column layout @@ -403,6 +413,7 @@ Frontend Team (mobile): - [ ] Verify mouse hover states work on desktop **Edge Cases**: + - [ ] Test with zero active users (verify "No data yet" message) - [ ] Test with MAU = 0 (verify stickiness shows "N/A") - [ ] Test with error rate exactly at thresholds (1%, 5%) @@ -424,6 +435,7 @@ Frontend Team (mobile): **Minimum Viable Product** = Phase 1 + Phase 2 + US1 (Overview Dashboard) This provides: + - ✅ Admin access control - ✅ System health monitoring (most critical operational need) - ✅ Foundation for adding more dashboards incrementally @@ -433,18 +445,22 @@ This provides: ### Incremental Delivery Plan **Week 1**: MVP (US1) + - T001-T029: Setup + Foundation + Overview Dashboard - Deploy to staging, gather admin user feedback **Week 2**: Golden Signals (US2) + - T030-T043: Charts + Golden Signals Dashboard - Deploy to staging, validate metrics accuracy with backend team **Week 3**: Engagement + Platforms (US3, US4) + - T044-T059: Engagement and Platform dashboards in parallel - Deploy to staging, validate DAU/MAU calculations **Week 4**: Polish + Deployment (US5, US6, Phase 9-10) + - T060-T093: Navigation refinements, responsive design, edge cases, polish - Full QA testing cycle - Deploy to production @@ -452,6 +468,7 @@ This provides: ### Parallelization Opportunities **High parallelization** (different files, no dependencies): + - All chart component tasks: T035-T039, T049 - All metric component tasks: T023-T024, T038 - Backend API endpoints: T007-T010 (backend team) @@ -460,6 +477,7 @@ This provides: - Polish tasks: T079-T085 **Must be sequential**: + - Setup → Foundation → User Stories - Hook implementation → Screen implementation (within same story) - Backend endpoints → Mobile integration @@ -469,6 +487,7 @@ This provides: ## Summary **Total Tasks**: 93 tasks + - Phase 1 (Setup): 5 tasks - Phase 2 (Foundation): 12 tasks (7 backend, 5 mobile) - Phase 3 (US1): 12 tasks (3 optional tests + 9 implementation) @@ -481,6 +500,7 @@ This provides: - Phase 10 (Polish): 14 tasks **User Story Task Breakdown**: + - US1 (View System Health): 12 tasks → **MVP Focus** - US2 (Golden Signals): 14 tasks → **High Value** - US3 (Engagement): 9 tasks @@ -493,6 +513,7 @@ This provides: **Independent Test Criteria**: Each user story phase has clear "Independent Test" criteria for verification **Estimated Timeline**: + - Single developer: 3-4 weeks (MVP in 3-5 days) - Team of 3: 1.5-2 weeks (MVP in 2-3 days) - Backend + Frontend teams: 2 weeks total (1 week backend prep + 1 week mobile implementation in parallel)