Files
obsidian-vault/work/wiki/apple-browsers/pixel-definitions.md
T

308 lines
14 KiB
Markdown

---
source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/pixel-definitions.mdc
confidence: 0.9
namespace: work
last_synced: 2026-04-28
description: Rules for creating and maintaining pixel definition JSON5 files that document pixels fired by the iOS and macOS apps
alwaysApply: false
---
# Pixel Registry Definitions
## Overview
Pixel definitions are JSON5 files that document pixels and wide events fired by the iOS and macOS apps. They live in:
- **iOS:** `iOS/PixelDefinitions/pixels/definitions/*.json5`
- **macOS:** `macOS/PixelDefinitions/pixels/definitions/*.json5`
- **iOS wide events:** `iOS/PixelDefinitions/wide_events/definitions/*.json5`
Each platform has its own `params_dictionary.json5` and `suffixes_dictionary.json5` for reusable definitions.
**Note:** Each definitions directory contains a `TEMPLATE.json5` file. These are scaffolds for creating new definition files — they are not real pixel definitions. Ignore them when reviewing or auditing existing definitions (their placeholder `expires` dates are intentional examples).
## Pixel Definition Structure
Each `.json5` file is a JSON5 object where keys are pixel names and values describe the pixel:
```json5
{
"pixel_name_here": {
"description": "When and why this pixel fires",
"owners": ["githubUsername"],
"triggers": ["other"],
"suffixes": ["first_daily_count", "platform", "form_factor"],
"parameters": ["appVersion", "errorCode", "errorDomain"],
// Only for temporary pixels — omit for permanent ones
"expires": "2025-06-30"
}
}
```
### Required Fields
| Field | Type | Description |
|-------|------|-------------|
| `description` | string | When the pixel fires and its purpose |
| `owners` | string[] | GitHub usernames of responsible people |
| `triggers` | string[] | What causes the pixel to fire (see trigger values below) |
### Optional Fields
| Field | Type | Description |
|-------|------|-------------|
| `suffixes` | array | Dynamic parts appended to the pixel name |
| `parameters` | array | Query parameters sent with the pixel |
| `expires` | string | ISO date (`YYYY-MM-DD`) for temporary pixels |
### Trigger Values
Valid trigger values: `"other"`, `"scheduled"`, `"startup"`, `"page_load"`, `"new_tab"`, `"exception"`, `"user_submitted"`, `"search_ddg"`.
Most pixels use `"other"`. Use `"scheduled"` for daily/periodic pixels, `"startup"` for app-launch pixels, and `"page_load"` for navigation-related pixels.
## Determining Parameters from Swift Code
Pixel definitions must document **all** query parameters sent over the wire, including default ones. To determine the correct parameters:
### Always-included Parameters
**`appVersion`** is added by default to every pixel by PixelKit. Include `"appVersion"` in every definition, unless the pixel call disables it.
**`pixelSource`** is automatically added only if the pixel's `standardParameters` property returns `[.pixelSource]`. Check the pixel event's `standardParameters` computed property in Swift — if it returns `[.pixelSource]`, include `"pixelSource"` in the definition.
### Error Parameters
If the pixel event carries an `Error` (via associated value or the `error` property), PixelKit automatically extracts and sends:
- `errorCode` (key: `"e"`) and `errorDomain` (key: `"d"`)
- `underlyingErrorCode` (key: `"ue"`) and `underlyingErrorDomain` (key: `"ud"`) if present
Include these dictionary references in the definition when the pixel carries error information.
### Custom Parameters
Check the pixel event's `parameters` computed property in Swift for any additional parameters. Also inspect the call site where the pixel is fired — look for `withAdditionalParameters:` arguments and trace any helper functions that build those parameters. These are pixel-specific and must be included in the definition (either as dictionary references or inline objects).
### Where to Look in Swift
- **iOS:** `iOS/Core/PixelEvent.swift` defines pixel names. `iOS/Core/Pixel.swift` has `PixelParameters` constants. Check the `parameters` and `standardParameters` properties on the pixel event enum.
- **macOS:** `macOS/DuckDuckGo/Statistics/GeneralPixel.swift` defines many pixel names, parameters, and standard parameters. However, pixel events can also be defined in dedicated files (e.g. `UpdateFlowPixels.swift`, `CrashReportPixels.swift`) — search for types conforming to `PixelKitEvent`.
- **Shared:** `SharedPackages/BrowserServicesKit/Sources/PixelKit/` contains `PixelKit.swift` (firing logic) and `PixelKitEvent.swift` (protocol).
## Reusing Parameters from the Dictionary
`params_dictionary.json5` defines common parameters. Reference them by key name as a string:
```json5
"parameters": [
"appVersion", // Reuses definition from params_dictionary.json5
"errorCode", // key: "e", type: integer
"errorDomain", // key: "d", type: string
"underlyingErrorCode",
"underlyingErrorDomain"
]
```
To define a custom inline parameter, use an object instead:
```json5
"parameters": [
"appVersion",
{
"key": "customParam",
"type": "string",
"description": "What this parameter represents",
"enum": ["value1", "value2"]
}
]
```
### Parameter Object Fields
- `key` — the actual query parameter key sent in the pixel (use this for fixed keys)
- `keyPattern` — regex pattern for dynamic keys (e.g. `"^ue[0-9]?$"` for `ue`, `ue0`, `ue1`, etc.)
- `type``"string"`, `"integer"`, `"number"`, or `"boolean"`
- `description` — what the parameter represents
- `enum` — allowed values (optional)
- `pattern` — regex validation pattern (optional)
- `examples` — example values (optional)
Use `key` or `keyPattern`, not both.
## Suffixes
A suffix is a string appended to the base pixel name to create distinct variants of the same pixel. For example, a pixel with the `"daily"` suffix set will produce a variant with `_daily` appended to the name (e.g. `m_mac_default-browser_daily`). When a pixel has multiple suffix sets, PixelKit generates all combinations — so `["first_daily_count", "platform", "form_factor"]` produces variants like `m_pixel_count_ios_phone`, `m_pixel_daily_ios_tablet`, etc.
In some cases a suffix value is always present (rather than being a set of variants). For example, a pixel fired with the legacy daily frequency always gets `_d` appended to its name. When the suffix is fixed and always present, it may be baked directly into the pixel name in the definition (e.g. `m_mac_daily_active_user_d`) rather than declared in the `suffixes` field. See "Legacy Suffix Patterns" below.
### Reusing Suffixes from the Dictionary
`suffixes_dictionary.json5` defines common suffixes. Reference them by key name:
```json5
// Each string references a suffix set from the dictionary
"suffixes": ["first_daily_count", "platform", "form_factor"]
```
Common suffix keys (both platforms unless noted):
- `first_daily_count``["first", "daily", "count"]`
- `legacy_daily_count``["d", "c"]`
- `daily``["daily"]`
- `daily_standard``["daily", ""]`
- `count``["count"]`
- `daily_count``["daily", "count"]` (macOS only)
- `platform``["ios"]` (iOS only)
- `form_factor``["phone", "tablet"]` (iOS only)
- `time_bucket``["0", "0.1", "0.5", "1", "5", "10", "20", "40", "more"]` (iOS only)
For custom inline suffixes, use an object:
```json5
"suffixes": [
"first_daily_count",
{
"description": "The result of the operation",
"enum": ["success", "failure"]
}
]
```
Inline suffix objects support `description`, `enum`, and optionally `key`, `type`, and `pattern` (same fields as parameter objects).
### Mapping Swift Firing Methods to Suffixes
The suffix you use depends on how the pixel is fired in Swift:
**iOS** (uses `DailyPixel` / `Pixel` / `UniquePixel`):
| Swift Method | Suffix Dictionary Key |
|---|---|
| `Pixel.fire(pixel:)` | No scheduling suffix (use only `platform`/`form_factor`) |
| `DailyPixel.fire(pixel:)` | `"daily"` |
| `DailyPixel.fireDailyAndCount(pixel:)` | `"first_daily_count"` |
| `DailyPixel.fireDailyAndCount(pixel:, pixelNameSuffixes: .legacyDailyPixelSuffixes)` | `"legacy_daily_count"` |
| `UniquePixel.fire(pixel:)` | No suffix (pixel name typically ends in `_u` or `_unique`) |
On iOS, most pixels also get `platform` and `form_factor` suffixes appended automatically. Include `["platform", "form_factor"]` for iOS pixels unless you confirm otherwise.
**macOS** (uses `PixelKit.fire(event, frequency:)`):
| `frequency:` Value | Suffix Dictionary Key |
|---|---|
| `.standard` (or omitted) | No scheduling suffix |
| `.daily` | `"daily"` |
| `.dailyAndCount` | `"daily_count"` (macOS) or `"first_daily_count"` (iOS) |
| `.dailyAndStandard` | `"daily_standard"` |
| `.legacyDaily` | No suffix field — the `_d` is baked into the pixel name (see "Legacy Suffix Patterns") |
| `.legacyDailyAndCount` | `"legacy_daily_count"` |
Note: iOS `DailyPixel.fireDailyAndCount` uses `"first_daily_count"` (includes a `_first` pixel on first-ever fire). macOS `.dailyAndCount` uses `"daily_count"` (no `_first`). Check which platform you are writing for.
### Compound Suffixes
Suffixes can be nested in an inner array to form compound suffixes (combined into a single suffix segment):
```json5
"suffixes": [["platform", "form_factor"]]
```
This produces suffixes like `ios_phone`, `ios_tablet` rather than separate independent suffix positions.
### Legacy Suffix Patterns
Some older pixels use legacy firing frequencies (`.legacyDaily`, `.legacyDailyAndCount`) where the pixel library appends short suffixes like `_d` (daily) or `_c` (count) to the pixel name. In these cases, the suffix is baked directly into the pixel name in the definition rather than declared in the `suffixes` field.
For example, `m_mac_daily_active_user_d` is fired with `frequency: .legacyDaily`. The Swift code defines the base name as `"m_mac_daily_active_user"`, and PixelKit appends `_d` automatically. The definition uses the full name `m_mac_daily_active_user_d` with no `suffixes` field — this is correct.
Compare this to the modern pattern where `m_mac_default-browser` uses `frequency: .daily` and declares `"suffixes": ["daily"]`, producing `m_mac_default-browser_daily`.
When writing definitions for legacy pixels, use the full pixel name (including the baked-in suffix) and use the `"legacy_daily_count"` suffix dictionary entry only if the pixel uses `.legacyDailyAndCount` (which produces both `_d` and `_c` variants).
## Wide Events (iOS)
Wide events use a different, richer schema in `iOS/PixelDefinitions/wide_events/definitions/`. They have a hierarchical structure with `meta`, `feature`, and `feature.data` sections:
```json5
{
"event-name": {
"description": "The purpose of the wide event",
"owners": ["githubUsername"],
"meta": {
"type": "unique-event-name",
"version": "0.0"
},
"feature": {
"name": "feature-name",
"status": ["SUCCESS", "FAILURE", "UNKNOWN"],
"data": {
"ext": {
"custom_field": {
"type": "string",
"description": "A custom extension field",
"enum": ["val1", "val2"]
},
// Can reference props_dictionary.json entries by string
"application_state": "foregroundBackgroundState"
},
"error": {
"domain": { "type": "string", "description": "Error domain" },
"code": { "type": "integer", "description": "Error code" }
}
}
}
}
}
```
Wide events sent as standard pixels (in `pixels/definitions/`) use `wideEvent*` parameters from the params dictionary (e.g. `wideEventAppName`, `wideEventFeatureStatus`, `wideEventErrorDomain`).
## Naming Conventions
- **iOS pixels:** typically prefixed with `m_` (e.g. `m_netp_ev_good_latency`), though some lack this prefix (e.g. `autofill_extension_*`, `attributed_metric_*`)
- **macOS pixels:** typically prefixed with `m_mac_` (e.g. `m_mac_daily_active_user_d`)
- **iOS wide events (as pixels):** prefixed with `m_ios_wide_` (e.g. `m_ios_wide_vpn_connection`)
- Use lowercase with underscores or hyphens
- Always use the exact pixel name string from the Swift code (e.g. from `PixelEvent.swift` or `GeneralPixel.swift`)
## File Organization
Group related pixels into a single definition file named after the feature area, for example:
- `navigation.json5` — Browser navigation pixels
- `onboarding.json5` — Onboarding pixels
- `vpn_latency.json5` — VPN latency measurement pixels
See existing files in the `definitions/` directory for naming patterns.
## Validation and Linting
Run from the `iOS/` or `macOS/` directory:
```bash
# Install dependencies (from repo root)
npm clean-install --include-workspace-root
# Full validation (schema + formatting)
npm run validate-pixel-defs
# Schema validation only
npm run validate-defs-without-formatting
# Check formatting
npm run pixel-lint
# Auto-fix formatting
npm run pixel-lint.fix
```
**Always run `npm run validate-pixel-defs`** from the relevant platform directory after making changes.
## Quick Reference: Adding a New Pixel
1. Determine the pixel name and parameters from the iOS/macOS codebase
2. Find or create the appropriate `.json5` file in `{platform}/PixelDefinitions/pixels/definitions/`
3. Add your pixel entry with `description`, `owners`, `triggers`
4. Add `suffixes` and `parameters` — reuse dictionary entries wherever possible
5. Check whether the pixel should be temporary, and if so then add `"expires": "YYYY-MM-DD"`
6. Run `npm run validate-pixel-defs` from the platform directory
7. Run `npm run pixel-lint.fix` if formatting issues are reported