Adding a Platform
This guide describes how to add a new gig platform to Comma. Platforms are data-only: one definition file plus registry wiring. The shift form, terminology merge, and analytics gates read from PlatformRegistry—they should not need new if (platformId === '…') branches for a normal addition.
For broader registry philosophy, see Registry Architecture in this folder.
Checklist
Section titled “Checklist”- Copy
src/registry/platforms/_TEMPLATE.platform.jstosrc/registry/platforms/{id}.platform.js(use a lowercase slug forid, e.g.glovo). - Fill required fields and optional sections (
specificSchema,alertChecks, etc.). - Add or reuse a logo SVG in
src/registry/platforms/_logos.js. - Register the module in
src/registry/platforms/index.js:import+ entry in thePLATFORMSarray (keepotherlast). - If the platform should appear for a province-first market (e.g. Ontario), add its
idto that province’savailablePlatforms(e.g.src/registry/provinces/CA/ON.province.js). - Run
node build.js --prodand fix any validation issues reported at startup (assertPlatformRegistryValidinmain.js).
Settings vs catalog logos
Section titled “Settings vs catalog logos”End users never upload or paste SVG in Settings → Platforms. Activation only picks from the catalog rows already in IndexedDB; Other stays name + color only. Every bundled platform must ship a non-empty logo string in code (see Logos); the app shows that SVG in the header switcher (tabs), onboarding platform grid, and renderPlatformBadge—there is no per-user logo override in the database.
Definition file
Section titled “Definition file”Path: src/registry/platforms/{id}.platform.js
Export: export default { … } (one catalog object per file).
Required fields
Section titled “Required fields”These are enforced by validatePlatformDefinition in src/registry/platforms/index.js (including a non-empty logo string):
| Field | Notes |
|---|---|
id | Unique string slug, lowercase (e.g. skip). Used everywhere: Dexie platforms.id, shift platformId, and other stable string keys. |
name | Human-readable label (e.g. SkipTheDishes). |
color | CSS hex for badges, charts, tabs (e.g. #ED5A1F). |
terminology | Object with at least driver and delivery non-empty strings. Optional: bonus, surge (used in copy and live labels). |
logo | Required non-empty inline SVG string (typically imported from _logos.js). Used in the UI switcher and badges; not editable in Settings. |
relevantFields | Array of string keys for metrics/UI hints; may be []. |
helpUrl | Support URL string; use '' if none. |
Common optional fields
Section titled “Common optional fields”| Field | Notes |
|---|---|
payoutWeekday | 0–6 (Sunday–Saturday) or omit; used where payout hints exist. |
analyticsModules | Feature flags analytics may consult via platformAnalyticsEnabled (see below). Prefer listing every key for clarity, matching existing platforms. |
specificSchema | Per-platform extra shift fields rendered automatically on the shift form for this platformId. Not used on id === 'other' by validation rules for schema rows—keep other minimal. |
alertChecks | Optional rules for notification-style checks (see schema in src/registry/types.js PlatformAlertCheckDef). |
The canonical TypeScript-style shape is documented as PlatformCatalogEntry in src/registry/types.js.
specificSchema (platform-only shift fields)
Section titled “specificSchema (platform-only shift fields)”Each entry drives an extra control on the advanced shift form:
specificSchema: [ { key: 'creditsPromos', kind: 'number', min: 0 }, { key: 'cityScore', kind: 'number', min: 0, max: 100 }, { key: 'notes', kind: 'string' }, { key: 'tags', kind: 'stringArray' }, { key: 'meta', kind: 'object' },],Supported kind values: 'number' | 'string' | 'object' | 'stringArray'.
Optional: min, max, labelKey (i18n key under shifts.ps.* or your own t() key).
Values are stored with the shift under customFields (merged with legacy platformSpecific during save/migration). Do not invent parallel storage keys unless you extend the pipeline.
analyticsModules
Section titled “analyticsModules”Boolean map used by src/modules/analytics/analytics.js via platformAnalyticsEnabled(platformId, module) from src/registry/platforms/terminology.js.
Keys used today (mirror a real platform file, e.g. skip.platform.js):
bonusTrackingsurgeAnalysisblockEarningsbatchTrackingorderTypeTrackingquestTrackingpromotionsTracking
Set a flag to true only if analytics logic for that module is meaningful for the new platform.
- Add something like
export const SVG_XX = \<svg …>`;to [_logos.js`](../src/registry/platforms/_logos.js), or reuse an existing export. - In
{id}.platform.js:import { SVG_XX } from './_logos.js';thenlogo: SVG_XX.
Keep SVG compact; it is bundled as a string.
Registry registration
Section titled “Registry registration”Edit src/registry/platforms/index.js:
import myplatform from './myplatform.platform.js';
const PLATFORMS = [ doordash, ubereats, // … myplatform, other, // must remain last: fallback catalog entry];PlatformRegistry.getById(unknown) resolves unknown ids to other, so other stays the safe tail.
Province / market availability
Section titled “Province / market availability”Ontario (and other provinces) expose availablePlatforms: an array of platform id strings. If your platform operates in that market, append its id there (e.g. CA/ON.province.js availablePlatforms).
If you skip this step, the catalog still loads, but onboarding and province-driven UX may not surface the platform where you expect.
For country-level defaults when no province row exists (e.g. many US states), add defaultAvailablePlatforms on the country definition; resolution order is in Market Resolution.
Exports and stable ids
Section titled “Exports and stable ids”- Onboarding setup JSON uses
exportKind: 'comma_setup', a numericversion, and arrays of platform ids in the same lowercase form as the registry.countryIdandprovinceId(or tax region code) travel with the export for portable imports. - Treat
idas immutable once users have data; renaming requires a Dexie migration mapping old → new ids. - Full rules: Market Resolution (exports and portability).
Terminology overrides (Dexie)
Section titled “Terminology overrides (Dexie)”Base terminology comes from the platform definition. Active rows in IndexedDB platforms can carry a per-user terminology object; at runtime those merge in syncPlatformTerminologyFromRows (see terminology.js). You normally do not change code for that—only the default def in {id}.platform.js.
Validation and QA
Section titled “Validation and QA”- Startup:
assertPlatformRegistryValid()walks every definition; invalidspecificSchema/alertChecksthrows during boot. - Build:
node build.js --prodmust succeed. - Manual: Enable the platform in Settings, open Add shift, pick the platform, and confirm
specificSchemafields render and save; reload and edit to confirm round-trip.
Minimal real-world references
Section titled “Minimal real-world references”| Example | Use when |
|---|---|
other.platform.js | Fallback: no specificSchema. |
skip.platform.js | Small schema + promotionsTracking. |
doordash.platform.js | Schema + alertChecks. |
Related files (quick map)
Section titled “Related files (quick map)”| Area | File |
|---|---|
| Template | src/registry/platforms/_TEMPLATE.platform.js |
| Registry + validation | src/registry/platforms/index.js |
| Logo strings | src/registry/platforms/_logos.js |
| Typedefs | src/registry/types.js (PlatformCatalogEntry) |
| Terminology merge | src/registry/platforms/terminology.js |
| Shift form extras | src/modules/shifts/shift-form.js (reads specificSchema) |
| Ontario market list | src/registry/provinces/CA/ON.province.js |
If you add a new analytics module name, you must implement the corresponding checks in analytics (or leave the flag false until you do).
See also
Section titled “See also”- Adding a Province — province registry,
availablePlatforms, and expense categories. - Adding a Country — country registry and
taxprofile before wiring provinces.