[2026-04-28] Add apple-browsers .cursor rules (43 files) + executor v2 context

This commit is contained in:
Alexey Martemyanov
2026-04-28 00:30:19 +06:00
parent dac59f4cff
commit 639338eda3
44 changed files with 18230 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
---
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/pixels.mdc
confidence: 0.9
namespace: work
last_synced: 2026-04-28
description: How to define, name, and fire pixels on iOS and macOS.
alwaysApply: false
---
# Pixels
Pixels are one-off telemetry events sent via HTTP GET with a name and optional parameters. They are used for:
- Basic feature usage events (e.g., button clicks, screen impressions)
- Errors (e.g., network failures, parsing errors)
- Conversion and retention (e.g, subscription purchase and activation)
Pixels have the following requirements:
- Use clear & transparent naming, so it's obvious what the pixel and parameters are for. Pixel names should be self-documenting - avoid cryptic abbreviations or shorthand.
- Only include information that is essential for the pixel
- Do not use values that are overly precise, e.g. if using an integer value in a parameter, bucket it into ranges rather than including the value verbatim
- Never include PII, URLs, or other forms of user-identifiable information in pixel names or parameters
## Types of Pixels
### Standard Pixels
Sent every time the event occurs.
```swift
Pixel.fire(pixel: .subscriptionRestoreAfterPurchaseAttempt)
Pixel.fire(pixel: .autofillLoginsSavePromptDisplayed, withAdditionalParameters: [
PixelParameters.autofillPromptTrigger: "manual"
])
```
### Daily Pixels
Sent once per day per event. Used to determine the number of users affected by a particular error
```swift
DailyPixel.fireDailyAndCount(pixel: .subscriptionPurchaseAttempt, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes)
```
### Unique Pixels
Sent once per install for the lifetime of the install.
```swift
UniquePixel.fire(pixel: .subscriptionActivated)
```
## Pixel Definition Patterns
### iOS Pixels
iOS pixels are defined as cases on `Pixel.Event` in `iOS/Core/PixelEvent.swift`. Each enum case maps to an HTTP pixel name string via a computed `name` property.
#### Adding a New iOS Pixel
1. Add a new enum case to `Pixel.Event` in `iOS/Core/PixelEvent.swift`.
2. Add a corresponding case to the `name` computed property (also in `PixelEvent.swift`) that returns the pixel's string name.
3. Fire the pixel using `Pixel.fire`, `DailyPixel.fireDailyAndCount`, or `UniquePixel.fire`.
4. Add the matching pixel definition in `iOS/PixelDefinitions/pixels/definitions/*.json5` - see the `Pixel Validation` section below for more.
#### Enum Case Definition
```swift
extension Pixel {
public enum Event {
case appInstall
case appLaunch
case subscriptionPurchaseAttempt
case subscriptionPurchaseSuccess
case subscriptionActivated
// ...
}
}
```
#### Enum-to-String Mapping
The `Pixel.Event` enum has a computed `name` property that maps each case to its HTTP pixel name string:
```swift
extension Pixel.Event {
public var name: String {
switch self {
case .appInstall: return "m_install"
case .appLaunch: return "ml"
case .subscriptionPurchaseAttempt: return "m_subscribe"
// ...
}
}
}
```
#### Naming Convention
iOS pixel names follow these conventions:
- **Prefix**: Always start with `m_` (for "mobile").
- **Separators**: Use underscores (`_`) or hyphens (`-`) between words.
- **Format**: Use `m_feature_action` or `m_feature-sub-feature_action`.
- **Clarity**: Names should be clear and self-documenting. Anyone reading the pixel name should understand what it represents without needing additional context.
**Avoid legacy shorthand naming.** The codebase contains legacy pixels with cryptic names like `ml`, `mp`, `mf`, `m_r`. These are difficult to understand and should not be used as a template for new pixels. New pixels should use descriptive names.
Examples:
| Style | Enum Case | String Name | Notes |
|-------|-----------|-------------|-------|
| Good | `.pullToRefresh` | `"m_pull-to-reload"` | Clear and descriptive |
| Good | `.autofillLoginsSavePromptDisplayed` | `"m_autofill_logins_save_prompt_displayed"` | Self-documenting |
| Legacy | `.appLaunch` | `"ml"` | Avoid this style for new pixels |
| Legacy | `.privacyDashboardOpened` | `"mp"` | Avoid this style for new pixels |
#### Parameterized Pixel Cases
Enum cases can have associated values that are interpolated into the pixel name:
```swift
// Enum definition with associated value
case networkProtectionLatency(quality: String)
case syncLocalTimestampResolutionTriggered(Feature)
// In the name property
case .networkProtectionLatency(let quality):
return "m_netp_ev_\(quality)_latency"
case .syncLocalTimestampResolutionTriggered(let feature):
return "m_sync_\(feature.name)_local_timestamp_resolution_triggered"
```
### macOS Pixels (PixelKit)
macOS uses `PixelKitEvent` protocol, typically in feature-specific files. The implementation of the protocol looks like this:
```swift
// SharedPackages/BrowserServicesKit/Sources/PixelKit/PixelKitEvent.swift
public protocol PixelKitEvent {
var name: String { get }
var standardParameters: [PixelKitStandardParameter]? { get }
var parameters: [String: String]? { get }
var error: NSError? { get }
}
```
An example implementation of this protocol is:
```swift
// macOS/DuckDuckGo/Statistics/SubscriptionPixel.swift
enum SubscriptionPixel: PixelKitEvent {
case subscriptionActive(AuthVersion)
case subscriptionPurchaseAttempt
case subscriptionPurchaseSuccess
// ...
var name: String {
switch self {
case .subscriptionActive: return "m_mac_privacy-pro_app_subscription_active"
case .subscriptionPurchaseAttempt: return "m_mac_privacy-pro_terms-conditions_subscribe_click"
// ...
}
}
var parameters: [String: String]? {
switch self {
case .subscriptionActive(let authVersion):
return [AuthVersion.key: authVersion.rawValue]
default:
return nil
}
}
}
```
## Pixel Parameters
Use structured parameter keys when reusing them across multiple pixels:
```swift
extension PixelParameters {
static let source = "source"
}
Pixel.fire(pixel: .featureUsed, withAdditionalParameters: [
PixelParameters.source: "keyboard_shortcut"
])
```
## PixelFiring Protocol
For dependency injection and testing, use the `PixelFiring` protocol:
```swift
// iOS/Core/PixelFiring.swift
public protocol PixelFiring {
static func fire(_ pixel: Pixel.Event,
withAdditionalParameters params: [String: String],
includedParameters: [Pixel.QueryParameters],
onComplete: @escaping (Error?) -> Void)
static func fire(_ pixel: Pixel.Event,
withAdditionalParameters params: [String: String])
}
```
## Best Practices
1. **Choose the right pixel type**: When using a standard pixel, consider using a daily pixel as well in order to determine how many users may be impacted by an issue.
2. **Use structured parameters**: Define parameter keys as constants to avoid typos and enable refactoring.
3. **Include error context**: When firing error pixels, include the error object as part of its error parameters.
4. **Consider using EventMapping**: When sending pixels from inside a shared Swift package, define pixels as new enum and emit them using `EventMapping`, then implement the event mapper on the client side.
5. **Consider using instrumentation facades**: For features with multiple related pixels, consider defining an instrumentation protocol to centralize pixel firing logic. See `instrumentation-facade.mdc`.
## Pixel Validation
Pixel definitions are validated from the `PixelDefinitions` folders in each platform:
- `iOS/PixelDefinitions/`
- `macOS/PixelDefinitions/`
Validation runs via `npm run validate-pixel-defs` from the platform folder. JSON5 formatting is enforced with Prettier.
### Directory Layout
- `pixels/definitions/*.json5`: The actual pixel definitions. Each file contains multiple pixel entries.
- `pixels/params_dictionary.json5`: Shared parameter definitions referenced by key.
- `pixels/suffixes_dictionary.json5`: Shared suffix definitions referenced by key.
- `product.json`: Product metadata.
### Defining a Pixel in JSON5
Each JSON5 file defines a map of `pixel_name` to metadata. Use `TEMPLATE.json5` from each platform folder as a starting point. Example:
```json5
{
"m_feature_action": {
"description": "Describe when the pixel fires and its purpose",
"owners": ["github-username"],
"triggers": ["other"],
"suffixes": ["first_daily_count", "platform"],
"parameters": ["appVersion"],
"expires": "2025-01-30"
}
}
```
Key fields:
- `description`: Clear explanation of the pixels purpose and timing.
- `owners`: GitHub usernames responsible for the pixel.
- `triggers`: One or more trigger categories used by validation.
- `suffixes`: Either shared suffix keys from `suffixes_dictionary.json5` or inline suffix definitions.
- `parameters`: Either shared parameter keys from `params_dictionary.json5` or inline parameter definitions.
- `expires` (optional): Date for temporary pixels; omit for permanent pixels.
Prefer referencing shared suffix/parameter keys where possible to keep definitions consistent and validatable.
Pixels have some default values, please check the Pixel implementation in the respective platform to determine what those are.
## Related Files
- `iOS/Core/Pixel.swift` - iOS pixel firing implementation
- `iOS/Core/DailyPixel.swift` - Daily pixel implementation
- `iOS/Core/UniquePixel.swift` - Unique pixel implementation
- `iOS/Core/PixelEvent.swift` - iOS pixel event definitions
- `SharedPackages/BrowserServicesKit/Sources/PixelKit/` - Shared PixelKit implementation