---
description: Step-by-step workflow for adding new features to the dual-implementation system
globs: []
alwaysApply: false
---

# New Feature Workflow

When adding ANY new feature to the Mixpanel library, follow this EXACT sequence:

## Step 1: Design Public API (REQUIRED)

### Update TypeScript Definitions
```typescript
// index.d.ts - Add method signature
export class Mixpanel {
  setCustomDimension(dimension: string, value: MixpanelType): void;
}
```

### Add to Main Class with Validation
```javascript
// index.js - Add to Mixpanel class
setCustomDimension(dimension, value) {
  // ALWAYS validate inputs using Helper classes
  if (!StringHelper.isValid(dimension)) {
    StringHelper.raiseError("dimension");
  }
  if (!ObjectHelper.isValidOrUndefined(value)) {
    ObjectHelper.raiseError("value");
  }
  
  // Route to implementation (native or JS)
  this.mixpanelImpl.setCustomDimension(this.token, dimension, value);
}
```

**API Design Requirements:**
- Follow camelCase naming convention
- Include comprehensive input validation using Helper classes
- Use token as first parameter for implementation routing
- Return Promise for async operations

## Step 2: Implement iOS Native Version (REQUIRED)

```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)
}
```

**iOS Implementation Requirements:**
- Use `@objc` decorator for React Native bridge
- Follow Promise-based async pattern (resolve/reject)
- Include error handling for missing instances
- Use MixpanelTypeHandler for type conversion
- Call through to official Mixpanel iOS SDK

## Step 3: Implement Android Native Version (REQUIRED)

```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 Requirements:**
- Use `@ReactMethod` annotation
- Include thread synchronization (`synchronized (instance)`)
- Use ReactNativeHelper for type conversion
- Follow established error handling pattern
- Call through to official Mixpanel Android SDK

## Step 4: Implement JavaScript Fallback (REQUIRED)

```javascript
// javascript/mixpanel-main.js
export default class MixpanelMain {
  async setCustomDimension(token, dimension, value) {
    // Check opt-out status FIRST
    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 Requirements:**
- Check opt-out status before ANY operation
- Add comprehensive logging with token context
- Use existing infrastructure (super properties, persistent storage)
- Follow async/await pattern consistently
- Implement equivalent functionality to native versions

## Step 5: Add Comprehensive Tests (REQUIRED)

### Input Validation Tests
```javascript
describe('Custom Dimensions', () => {
  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');
    });
  });
});
```

### Native Mode Tests
```javascript
describe('Native Mode', () => {
  beforeEach(() => {
    mixpanel = new Mixpanel('test-token', true, true); // Force native
  });

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

### JavaScript Mode Tests
```javascript
describe('JavaScript Mode', () => {
  beforeEach(() => {
    mixpanel = new Mixpanel('test-token', true, false); // Force JS
  });

  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');
    
    const superProps = await mixpanel.getSuperProperties();
    expect(superProps).not.toHaveProperty('$custom_dimension_test');
  });
});
```

### Integration Tests
```javascript
describe('Integration', () => {
  it('should include custom dimension in tracked events', async () => {
    await mixpanel.setCustomDimension('user_tier', 'premium');
    mixpanel.track('Purchase', { amount: 99.99 });
    
    expect(MixpanelQueueManager.enqueue).toHaveBeenCalledWith(
      'test-token',
      MixpanelType.EVENTS,
      expect.objectContaining({
        event: 'Purchase',
        properties: expect.objectContaining({
          '$custom_dimension_user_tier': 'premium',
          amount: 99.99
        })
      })
    );
  });
});
```

## Step 6: Update Native Module Mocks (REQUIRED)

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

## Step 7: Add Documentation (REQUIRED)

```javascript
/**
 * 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 8: Test Native Integration (REQUIRED)

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

# Test in sample app
cd Samples/SimpleMixpanel
npm install
npx react-native run-ios
```

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

# Test in sample app
cd Samples/SimpleMixpanel
npx react-native run-android
```

## Step 9: 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>
  );
};
```

## Checklist for New Features

### 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 with @objc
- [ ] Add Android native implementation with @ReactMethod
- [ ] Implement JavaScript fallback
- [ ] Add comprehensive logging with token context

### 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 builds
- [ ] 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 to Follow

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

### Error Handling
```javascript
try {
  await this.setCustomDimension(token, dimension, value);
} catch (error) {
  MixpanelLogger.error(token, `Failed to set custom dimension: ${error.message}`);
  // Continue gracefully - don't let feature failures crash the app
}
```

### Configuration Support
```javascript
// If the feature needs configuration
// javascript/mixpanel-config.js
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.