# Workflow: Adding New Features

## Overview

This workflow demonstrates how to add new features to the mixpanel-react-native library following the established dual-implementation pattern and maintaining compatibility across both native and JavaScript modes.

## 🎯 Feature Addition Workflow

### Step 1: Design the Public API

```javascript
// 1. Update index.d.ts with TypeScript definitions
export class Mixpanel {
  // Add new method signature
  setCustomDimension(dimension: string, value: MixpanelType): void;
}

// 2. Add to main Mixpanel class in index.js
export class Mixpanel {
  setCustomDimension(dimension, value) {
    // Input validation using established pattern
    if (!StringHelper.isValid(dimension)) {
      StringHelper.raiseError("dimension");
    }
    if (!ObjectHelper.isValidOrUndefined(value)) {
      ObjectHelper.raiseError("value");
    }
    
    // Route to implementation
    this.mixpanelImpl.setCustomDimension(this.token, dimension, value);
  }
}
```

**API Design Principles**:
- Follow existing naming conventions (camelCase)
- Include comprehensive input validation
- Use helper classes for consistent error messages
- Route through implementation layer

### Step 2: Implement Native iOS Version

```swift
// ios/MixpanelReactNative.swift
@objc
func setCustomDimension(_ token: String,
                       dimension: String,
                       value: Any,
                       resolver resolve: RCTPromiseResolveBlock,
                       rejecter reject: RCTPromiseRejectBlock) -> Void {
    let instance = MixpanelReactNative.getMixpanelInstance(token)
    guard let instance = instance else {
        reject("Instance Error", "Failed to get Mixpanel instance", nil)
        return
    }
    
    // Convert React Native value to native type
    let processedValue = MixpanelTypeHandler.processProperty(value: value)
    
    // Call native SDK method
    instance.setCustomDimension(dimension: dimension, value: processedValue)
    resolve(nil)
}
```

**Native Implementation Pattern**:
- Use `@objc` decorator for React Native bridge
- Follow Promise-based async pattern
- Include error handling for missing instances
- Use type conversion utilities
- Call through to official native SDK

### Step 3: Implement Native Android Version

```java
// android/.../MixpanelReactNativeModule.java
@ReactMethod
public void setCustomDimension(final String token, 
                              String dimension, 
                              Dynamic value, 
                              Promise promise) throws JSONException {
    MixpanelAPI instance = MixpanelAPI.getInstance(this.mReactContext, token, true);
    if (instance == null) {
        promise.reject("Instance Error", "Failed to get Mixpanel instance");
        return;
    }
    
    synchronized (instance) {
        // Convert React Native Dynamic to appropriate type
        Object processedValue = ReactNativeHelper.dynamicToObject(value);
        
        // Call native SDK method
        instance.setCustomDimension(dimension, processedValue);
        promise.resolve(null);
    }
}
```

**Android Implementation Pattern**:
- Use `@ReactMethod` annotation
- Include thread synchronization for safety
- Use helper utilities for type conversion
- Follow established error handling pattern

### Step 4: Implement JavaScript Fallback

```javascript
// javascript/mixpanel-main.js
export default class MixpanelMain {
  async setCustomDimension(token, dimension, value) {
    if (this.mixpanelPersistent.getOptedOut(token)) {
      MixpanelLogger.log(token, `User has opted out, skipping setCustomDimension`);
      return;
    }

    MixpanelLogger.log(token, `Setting custom dimension '${dimension}' to '${value}'`);
    
    // Store dimension in super properties for future events
    const currentSuperProps = this.mixpanelPersistent.getSuperProperties(token) || {};
    const updatedSuperProps = {
      ...currentSuperProps,
      [`$custom_dimension_${dimension}`]: value
    };
    
    await this.registerSuperProperties(token, updatedSuperProps);
  }
}
```

**JavaScript Implementation Pattern**:
- Check opt-out status first
- Add comprehensive logging
- Use existing infrastructure (super properties)
- Follow async/await pattern

### Step 5: Add Comprehensive Tests

```javascript
// __tests__/custom-dimension.test.js
describe('Custom Dimensions', () => {
  let mixpanel;
  
  beforeEach(() => {
    jest.clearAllMocks();
    mixpanel = new Mixpanel('test-token', true);
  });

  describe('Input Validation', () => {
    it('should throw error for invalid dimension', () => {
      expect(() => {
        mixpanel.setCustomDimension('', 'value');
      }).toThrow('dimension is not a valid string');
    });

    it('should throw error for invalid value', () => {
      expect(() => {
        mixpanel.setCustomDimension('test', Symbol('invalid'));
      }).toThrow('value is not a valid json object');
    });
  });

  describe('Native Mode', () => {
    it('should call native implementation', () => {
      mixpanel.setCustomDimension('user_segment', 'premium');
      
      expect(MixpanelReactNative.setCustomDimension).toHaveBeenCalledWith(
        'test-token',
        'user_segment', 
        'premium'
      );
    });
  });

  describe('JavaScript Mode', () => {
    beforeEach(() => {
      // Force JavaScript mode
      mixpanel = new Mixpanel('test-token', true, false);
    });

    it('should store as super property', async () => {
      await mixpanel.setCustomDimension('user_segment', 'premium');
      
      const superProps = await mixpanel.getSuperProperties();
      expect(superProps).toEqual({
        '$custom_dimension_user_segment': 'premium'
      });
    });

    it('should respect opt-out status', async () => {
      await mixpanel.optOutTracking();
      await mixpanel.setCustomDimension('test', 'value');
      
      // Should not modify super properties when opted out
      const superProps = await mixpanel.getSuperProperties();
      expect(superProps).not.toHaveProperty('$custom_dimension_test');
    });
  });
});
```

### Step 6: Update Documentation

```javascript
// Update JSDoc comments
/**
 * Set a custom dimension that will be included with all future events.
 * Custom dimensions allow you to segment your data by user characteristics
 * or behaviors that are important to your analysis.
 *
 * @param {string} dimension The name of the custom dimension
 * @param {object} value The value to associate with this dimension
 * 
 * @example
 * // Set user segment dimension
 * mixpanel.setCustomDimension('user_segment', 'premium');
 * 
 * // Set numeric dimension
 * mixpanel.setCustomDimension('account_age_days', 45);
 */
setCustomDimension(dimension, value) {
  // Implementation...
}
```

### Step 7: Update Native Module Mocks

```javascript
// __tests__/jest_setup.js
jest.doMock("react-native", () => {
  return Object.setPrototypeOf({
    NativeModules: {
      MixpanelReactNative: {
        // ... existing methods
        setCustomDimension: jest.fn(),  // Add new method to mock
      },
    },
  }, ReactNative);
});
```

## 🔍 Integration Testing

### End-to-End Feature Test

```javascript
// __tests__/integration/custom-dimension.integration.test.js
describe('Custom Dimension Integration', () => {
  it('should include custom dimension in tracked events', async () => {
    const mixpanel = new Mixpanel('test-token', true);
    await mixpanel.init();
    
    // Set custom dimension
    await mixpanel.setCustomDimension('user_tier', 'premium');
    
    // Track event
    mixpanel.track('Purchase', { amount: 99.99 });
    
    // Verify dimension included in event
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Purchase',
        properties: expect.objectContaining({
          '$custom_dimension_user_tier': 'premium',
          amount: 99.99
        })
      })
    );
  });
});
```

## 📱 Sample App Updates

### Update Sample Applications

```javascript
// Samples/SimpleMixpanel/App.tsx
const SampleApp = () => {
  const handleSetDimension = () => {
    mixpanel.setCustomDimension('user_segment', 'mobile_user');
  };

  return (
    <SafeAreaView>
      <Button 
        title="Set Custom Dimension" 
        onPress={handleSetDimension} 
      />
    </SafeAreaView>
  );
};
```

## 🚀 Release Considerations

### Version Compatibility

```javascript
// Consider backwards compatibility
// New features should not break existing implementations
// Add feature detection if needed:

if (mixpanel.setCustomDimension) {
  mixpanel.setCustomDimension('feature', 'enabled');
} else {
  // Fallback for older versions
  mixpanel.registerSuperProperties({ 
    '$custom_dimension_feature': 'enabled' 
  });
}
```

#### Example: Gzip Compression Feature [Updated: 2025-05-30]
Added as optional parameter to maintain backward compatibility:
```javascript
// Old API still works
await mixpanel.init(false, {}, "https://api.mixpanel.com");

// New API with compression
await mixpanel.init(false, {}, "https://api.mixpanel.com", true);
```

### Native Module Updates

```bash
# iOS: After adding Swift methods
cd ios && pod install

# Android: After adding Java methods  
cd android && ./gradlew clean && ./gradlew build
```

## ✅ Feature Checklist

### Pre-Implementation
- [ ] Design API following existing patterns
- [ ] Validate feature fits dual-implementation strategy
- [ ] Check compatibility with both native SDKs
- [ ] Plan fallback strategy for JavaScript mode

### Implementation
- [ ] Add TypeScript definitions
- [ ] Implement public API with validation
- [ ] Add iOS native implementation
- [ ] Add Android native implementation  
- [ ] Implement JavaScript fallback
- [ ] Add comprehensive logging

### Testing
- [ ] Unit tests for input validation
- [ ] Unit tests for native mode
- [ ] Unit tests for JavaScript mode
- [ ] Integration tests for complete workflow
- [ ] Update native module mocks
- [ ] Test opt-out behavior

### Documentation & Samples
- [ ] Add JSDoc documentation with examples
- [ ] Update sample applications
- [ ] Test in both development and production
- [ ] Verify autolinking still works

### Release
- [ ] Test iOS pod install process
- [ ] Test Android gradle build process
- [ ] Verify backwards compatibility
- [ ] Update CHANGELOG.md
- [ ] Consider version bump requirements

## 🔧 Common Patterns

### Constants Management
```javascript
// javascript/mixpanel-constants.js
export const CUSTOM_DIMENSION_PREFIX = '$custom_dimension_';
```

### Error Handling
```javascript
// Always include comprehensive error handling
try {
  await this.setCustomDimension(token, dimension, value);
} catch (error) {
  MixpanelLogger.error(token, `Failed to set custom dimension: ${error.message}`);
  // Continue gracefully
}
```

### Configuration Support
```javascript
// javascript/mixpanel-config.js
export class MixpanelConfig {
  setCustomDimensionPrefix(token, prefix) {
    this._config[token] = {
      ...this._config[token],
      customDimensionPrefix: prefix,
    };
  }
}
```

This workflow ensures new features maintain the library's reliability, performance, and compatibility standards while following established architectural patterns.