managarten/apps-archived/maerchenzauber/apps/mobile/ANALYTICS.md
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

18 KiB

PostHog Analytics Documentation

This document explains the comprehensive PostHog analytics implementation in the Storyteller mobile app.

Table of Contents

Setup

1. Environment Configuration

Add your PostHog credentials to mobile/.env:

# PostHog Analytics
EXPO_PUBLIC_POSTHOG_API_KEY=your-posthog-api-key
EXPO_PUBLIC_POSTHOG_HOST=https://app.posthog.com

Note: Replace your-posthog-api-key with your actual PostHog project API key from app.posthog.com

2. Installation

The required dependencies are already installed:

  • posthog-react-native - Core PostHog SDK for React Native
  • posthog-js - PostHog JavaScript SDK
  • expo-file-system, expo-application, expo-localization - Required peer dependencies

3. Initialization

Analytics are automatically initialized in the app through the PostHogWebProvider in app/_layout.tsx. No manual initialization is required.

Architecture

Core Files

mobile/
├── src/
│   ├── services/
│   │   └── analytics.ts          # Core analytics service with type-safe events
│   ├── providers/
│   │   └── PostHogWebProvider.tsx # Provider that initializes analytics
│   └── hooks/
│       ├── usePostHogWeb.ts      # Backward-compatible PostHog hook
│       └── useAnalytics.ts       # New React hooks for analytics

Analytics Service

The analytics.ts service provides:

  • Type-safe event tracking - All events are strongly typed using TypeScript
  • Automatic enrichment - Events automatically include device info, session data, etc.
  • Error handling - Graceful degradation if PostHog is not configured
  • User identification - Link events to specific users
  • Screen tracking - Automatic screen view tracking

Event Types

Authentication Events

// Sign in
analytics.track('auth_signin_started', { method: 'email' | 'google' | 'apple' });
analytics.track('auth_signin_completed', { method: 'email', userId: string });
analytics.track('auth_signin_failed', { method: 'email', error: string });

// Sign up
analytics.track('auth_signup_started', { method: 'email' | 'google' | 'apple' });
analytics.track('auth_signup_completed', { method: 'email', userId: string });
analytics.track('auth_signup_failed', { method: 'email', error: string });

// Sign out
analytics.track('auth_signout', { userId: string });

Story Events

// Story creation
analytics.track('story_creation_started', {
  characterId: string,
  characterName: string,
});

analytics.track('story_prompt_entered', {
  promptLength: number,
  language: string,
});

analytics.track('story_generation_completed', {
  storyId: string,
  characterId: string,
  duration: number,
  pageCount: number,
  language: string,
});

analytics.track('story_generation_failed', {
  characterId: string,
  error: string,
  duration: number,
});

// Story interaction
analytics.track('story_viewed', { storyId: string, title: string });
analytics.track('story_page_changed', {
  storyId: string,
  pageNumber: number,
  totalPages: number,
});

analytics.track('story_shared', { storyId: string, method: string });
analytics.track('story_deleted', { storyId: string });

Story Engagement Events

These events track how users interact with and consume stories, providing deep insights into engagement patterns:

// Story session tracking
analytics.track('story_session_started', {
  storyId: string,
  title: string,
  pageCount: number,
});

analytics.track('story_session_ended', {
  storyId: string,
  duration: number, // Total time in milliseconds
  pagesViewed: number, // Number of unique pages viewed
  completed: boolean, // Whether user reached the end
  furthestPage: number, // Highest page number reached
});

// Page-level engagement
analytics.track('story_page_viewed', {
  storyId: string,
  pageNumber: number,
  totalPages: number,
  isStartScreen: boolean, // Start screen (page 0)
  isEndScreen: boolean, // End screen (last page)
});

analytics.track('story_page_duration', {
  storyId: string,
  pageNumber: number,
  duration: number, // Time spent on this page in milliseconds
  isStartScreen: boolean,
  isEndScreen: boolean,
});

// Story completion
analytics.track('story_completed', {
  storyId: string,
  totalDuration: number, // Total time to complete story
  pageCount: number, // Number of story pages (excluding start/end)
  averagePageDuration: number, // Average time per page
});

// Abandonment tracking
analytics.track('story_abandoned', {
  storyId: string,
  lastPage: number, // Last page viewed before leaving
  totalPages: number,
  duration: number, // Time before abandonment
  completionPercentage: number, // How far through the story (0-100)
});

// Story restart
analytics.track('story_restarted', {
  storyId: string,
  fromPage: number, // Page number when restart was triggered
});

// Button interactions
analytics.track('story_end_button_clicked', {
  storyId: string,
  title: string,
});

analytics.track('story_restart_button_clicked', {
  storyId: string,
  fromPage: number, // Page when restart was clicked
});

analytics.track('story_archive_button_clicked', {
  storyId: string,
  title: string,
});

analytics.track('story_archived', {
  storyId: string,
  title: string,
});

Character Events

// Character creation
analytics.track('character_creation_started', {
  method: 'description' | 'photo',
});

analytics.track('character_description_entered', {
  descriptionLength: number,
});

analytics.track('character_photo_selected', {
  source: 'camera' | 'gallery',
});

analytics.track('character_generation_completed', {
  characterId: string,
  name: string,
  method: 'description' | 'photo',
  duration: number,
});

analytics.track('character_generation_failed', {
  method: 'description' | 'photo',
  error: string,
  duration: number,
});

// Character interaction
analytics.track('character_viewed', {
  characterId: string,
  characterName: string,
});

analytics.track('character_shared', { characterId: string, method: string });
analytics.track('character_deleted', { characterId: string });

Credit Events

analytics.track('credits_checked', { balance: number });

analytics.track('credits_insufficient', {
  required: number,
  available: number,
  action: string,
});

analytics.track('credits_consumed', {
  amount: number,
  action: string,
  balance: number,
});

analytics.track('credits_purchase_initiated', { amount: number });
analytics.track('credits_purchase_completed', {
  amount: number,
  paymentMethod: string,
});

// Credits screen tracking
analytics.track('credits_screen_viewed', {
  source: string, // Where user came from (e.g., 'insufficient_modal', 'settings')
  balance: number, // Current credit balance
});

analytics.track('credits_screen_duration', {
  duration: number, // Time spent on credits screen
  purchaseMade: boolean, // Whether a purchase was completed
});

Screen Navigation

analytics.track('screen_viewed', {
  screenName: string,
  params?: Record<string, any>,
  previousScreen?: string
});

Performance & Errors

analytics.track('api_request', {
  endpoint: string,
  method: string,
  duration: number,
  statusCode: number,
  success: boolean
});

analytics.track('error_occurred', {
  error: string,
  errorCode?: string,
  screen: string,
  action?: string,
  metadata?: Record<string, any>
});

Usage Examples

Basic Event Tracking

import { analytics } from '../src/services/analytics';

// Track a simple event
analytics.track('feature_discovered', {
  feature: 'story_sharing',
  source: 'button_click',
});

Using React Hooks

import { useAnalytics, useScreenTracking, useErrorTracking } from '../hooks/useAnalytics';

function MyComponent() {
  // Automatic screen tracking
  useScreenTracking('MyScreen', { userId: '123' });

  // Error tracking with automatic context
  const { trackError } = useErrorTracking('MyScreen');

  const handleAction = async () => {
    try {
      await riskyOperation();
    } catch (error) {
      trackError(error, {
        action: 'riskyOperation',
        metadata: { attemptCount: 3 }
      });
    }
  };

  return <View>...</View>;
}

Performance Tracking

import { usePerformanceTracking } from '../hooks/useAnalytics';

function MyComponent() {
  const { startTimer, endTimer } = usePerformanceTracking();

  const handleLoadData = async () => {
    startTimer('load_data');
    const data = await fetchData();
    endTimer('load_data', { recordCount: data.length });
  };

  return <View>...</View>;
}

Story Engagement Tracking

Track detailed user engagement with stories including time spent, page views, and completion rates:

import { useStoryEngagement } from '../hooks/useStoryEngagement';

function StoryViewerScreen({ story }) {
  // Initialize engagement tracking
  const { trackPageView, trackRestart } = useStoryEngagement({
    storyId: story.id,
    title: story.title,
    pageCount: story.pages.length
  });

  // Track when user changes pages
  const handlePageChange = (pageIndex: number) => {
    trackPageView(pageIndex);
    // Automatically tracks:
    // - Page view time
    // - Session duration
    // - Story completion
    // - Abandonment
  };

  // Track when user restarts the story
  const handleRestart = () => {
    trackRestart(currentPage);
  };

  return <StoryViewer onPageChange={handlePageChange} onRestart={handleRestart} />;
}

Credits Screen Tracking

Track when users view the credits/purchase screen and how long they spend there:

import { useCreditsScreenTracking } from '../hooks/useStoryEngagement';

function CreditsScreen({ balance }) {
  // Track screen viewing and duration
  const { markPurchaseMade } = useCreditsScreenTracking('insufficient_modal', balance);

  const handlePurchase = async (amount: number) => {
    const result = await purchaseCredits(amount);

    if (result.success) {
      // Mark that a purchase was made for analytics
      markPurchaseMade();
    }
  };

  return <View>...</View>;
  // Duration is automatically tracked on unmount
}

User Identification

import { analytics } from '../src/services/analytics';

// Identify user after login
await analytics.identify(user.id, {
  email: user.email,
  name: user.name,
  plan: user.plan,
  signupDate: user.createdAt,
});

// Update user properties
await analytics.setUserProperties({
  preferredLanguage: 'de',
  storiesCreated: 5,
});

// Reset on logout
await analytics.reset();

Screen View Tracking

import { analytics } from '../src/services/analytics';

// Track screen view
analytics.trackScreenView('StoryList', {
  storyCount: stories.length,
  filter: 'recent'
});

// Or use the hook for automatic tracking
import { useScreenTracking } from '../hooks/useAnalytics';

function StoryListScreen() {
  useScreenTracking('StoryList', {
    storyCount: stories.length
  });

  return <View>...</View>;
}

Error Tracking

import { analytics } from '../src/services/analytics';

try {
  await createStory(data);
} catch (error) {
  analytics.trackError(error, {
    screen: 'CreateStory',
    action: 'submit_story',
    metadata: {
      characterId: selectedCharacter.id,
      promptLength: prompt.length,
    },
  });
}

Best Practices

1. Use Type-Safe Events

Always use the strongly-typed events defined in the AnalyticsEvent type:

// ✅ Good - Type-safe
analytics.track('story_created', {
  storyId: '123',
  characterId: 'abc',
});

// ❌ Bad - No type safety
posthog?.capture('story_created', { id: '123' });

2. Track User Flows

Track the complete user journey:

// Start of flow
analytics.track('story_creation_started', { characterId: '123' });

// User action
analytics.track('story_prompt_entered', { promptLength: 50 });

// End of flow (success)
analytics.track('story_generation_completed', {
  storyId: '456',
  characterId: '123',
  duration: 5000,
});

// Or end of flow (failure)
analytics.track('story_generation_failed', {
  characterId: '123',
  error: 'API timeout',
  duration: 30000,
});

3. Include Context

Always include relevant context with events:

// ✅ Good - Rich context
analytics.track('story_generation_completed', {
  storyId: story.id,
  characterId: character.id,
  duration: elapsed,
  pageCount: 10,
  language: 'de',
});

// ❌ Bad - Minimal context
analytics.track('story_generation_completed', {
  storyId: story.id,
});

4. Track Performance

Track timing for important operations:

const startTime = Date.now();
try {
  const result = await expensiveOperation();
  const duration = Date.now() - startTime;

  analytics.track('operation_completed', {
    duration,
    success: true,
    resultCount: result.length,
  });
} catch (error) {
  analytics.track('operation_failed', {
    duration: Date.now() - startTime,
    error: error.message,
  });
}

5. Avoid PII

Never track Personally Identifiable Information (PII) in event properties:

// ✅ Good - No PII
analytics.track('profile_updated', {
  fields: ['name', 'avatar'],
  fieldCount: 2,
});

// ❌ Bad - Contains PII
analytics.track('profile_updated', {
  email: 'user@example.com',
  name: 'John Doe',
});

Use identify() for user-level data instead of event properties.

6. Handle Errors Gracefully

Analytics should never break your app:

// The analytics service already handles errors internally
analytics.track('my_event', { data: 'value' });
// If PostHog is not configured or fails, this won't throw

Debugging

Check if Analytics is Enabled

import { analytics } from '../src/services/analytics';

if (analytics.isEnabled()) {
  console.log('Analytics is enabled');
} else {
  console.log('Analytics is disabled or not configured');
}

View Events in Console

All events are logged to the console in development:

[Analytics] Event tracked: story_creation_started {
  characterId: '123',
  characterName: 'Fluffy',
  timestamp: '2025-10-31T...',
  current_screen: 'CreateStory',
  session_duration: 45000
}

Force Flush Events

Events are automatically batched. To force immediate sending:

import { analytics } from '../src/services/analytics';

await analytics.flush();

Common Issues

Analytics not tracking:

  1. Check that EXPO_PUBLIC_POSTHOG_API_KEY is set in .env
  2. Restart Expo with cache clear: npx expo start -c
  3. Check console for initialization errors

Events not appearing in PostHog:

  1. Events are batched - wait a few seconds or call flush()
  2. Check that your PostHog project is active
  3. Verify the API key is correct

TypeScript errors:

  1. Make sure event names match exactly (case-sensitive)
  2. Ensure properties match the type definition
  3. Update the AnalyticsEvent type if adding new events

Adding New Events

To add a new event type:

  1. Add the event definition to AnalyticsEvent type in src/services/analytics.ts:
export type AnalyticsEvent = {
  // ... existing events ...

  // Your new event
  feature_used: {
    featureName: string;
    context: string;
  };
};
  1. Track the event:
analytics.track('feature_used', {
  featureName: 'dark_mode',
  context: 'settings',
});
  1. The event is now type-safe and will autocomplete in your IDE!

PostHog Dashboard

Access your analytics dashboard at app.posthog.com

Useful Views

  • Live Events: See events as they happen in real-time
  • Insights: Create charts and graphs from your events
  • Funnels: Analyze user flows (e.g., sign up → create character → create story)
  • Cohorts: Group users by behavior
  • Session Recordings: Watch user sessions (if enabled)

Creation & Generation

  1. Story Creation Funnel:

    • story_creation_startedstory_prompt_enteredstory_generation_completed
  2. Character Creation Success Rate:

    • character_generation_completed / character_creation_started * 100
  3. Average Story Generation Time:

    • Average of duration property on story_generation_completed events

Story Engagement

  1. Story Completion Rate:

    • story_completed / story_session_started * 100
    • Shows what percentage of users finish stories they start
  2. Average Time Per Story Page:

    • Average of duration property on story_page_duration events (excluding start/end screens)
    • Indicates engagement level - too short might mean skipping, too long might mean confusion
  3. Story Abandonment Points:

    • Chart of lastPage property from story_abandoned events
    • Identify which pages cause users to leave
  4. Story Engagement Funnel:

    • story_session_startedstory_page_viewed (page 1) → story_page_viewed (page 5) → story_completed
    • Shows drop-off at different story stages
  5. Average Pages Viewed Per Session:

    • Average of pagesViewed property on story_session_ended events
    • Measures how much content users consume
  6. Story Restart Rate:

    • Count of story_restarted events / count of story_session_started events
    • Indicates replayability/engagement

User Actions & Behavior

  1. Archive Rate:

    • story_archived / story_archive_button_clicked * 100
    • Shows if users follow through with archiving
  2. Restart vs. End Behavior:

    • Compare story_restart_button_clicked vs story_end_button_clicked
    • Indicates if stories encourage re-reading or are one-time reads
  3. Action Click Analysis:

    • Count of each button click type
    • Helps understand which features are most used

Monetization

  1. Credit Consumption:

    • Sum of amount property on credits_consumed events grouped by action
  2. Credits Screen Conversion:

    • credits_purchase_completed / credits_screen_viewed * 100
    • Shows effectiveness of purchase flow
  3. Time to Purchase Decision:

    • Average of duration property on credits_screen_duration events where purchaseMade = true
    • Helps understand purchase friction

Support

For issues or questions about analytics:

  1. Check the console logs for error messages
  2. Verify environment configuration
  3. Review PostHog documentation: posthog.com/docs
  4. Check this documentation for usage examples