Contributing
Thanks for contributing to Comma.
Code style
TypeScript (phone app)
- Strict mode. No
any.tsconfig.jsonsetsstrict: true. If a type is genuinely dynamic, useunknownand narrow it. - No type assertions without justification.
as SomeTypeis acceptable when feeding output from a runtime source (e.g. SQLite results); document why if it is non-obvious. - Prefer
typeoverinterfacefor data shapes; useinterfaceonly when you need declaration merging.
Database
- All queries in
src/database/queries/. No raw SQL in screens or hooks. - No array-level filtering of DB results. If it can be a
WHEREor aJOIN, write it as one. Fetching 10,000 rows to keep 10 is a performance bug. - Mutations via
syncedInsert/syncedUpdate/syncedDelete(src/database/syncedWrites.ts). They stampsyncUpdatedAtautomatically. Directdb.insert()/db.update()are only acceptable on tables that are not synced (locationPoints,tempNativePoints,settings,syncOverwriteLog). - Soft deletes on synced tables. Call
syncedDelete(); never a hardDELETEon a synced table, or the tombstone is lost and the row resurrects on the next sync.
React components
- Function components only. No class components.
- Hooks for logic, components for rendering. Extract non-trivial logic to a custom hook before a component grows long.
- NativeWind for styles. Use Tailwind classes; avoid raw
StyleSheet.createunless you have a reason (e.g. complex animation). - No inline
style={{}}except for dynamic values.
State
- React Query for async data, Zustand for synchronous global state only,
useStatefor local component state. The two stores areuseActiveShiftanduseSettingsStore— add to them sparingly. See State Management.
Naming
- Files:
PascalCase.tsxfor components,camelCase.tsotherwise. - Hooks:
useprefix —useActiveShift,useGPSTracking. - Query keys: string arrays —
["shifts", "recent"],["analytics", "today"].
Comments
Default to no comments. Write self-documenting code and add a comment only when the why is non-obvious:
// GPS jitter: >150 km/h implies a spike, not real movement
if (impliedSpeedKmH > 150) continueDo not add comments that restate what the code does.
Adding a supported country
Comma ships Canada only. Earlier US, UK, and Nepal definitions were removed pending accurate, signed-off tax and mileage numbers — the app must never offer a country whose rules haven't been verified. Nothing in the app branches on the country id; every tax, mileage, currency, and onboarding path reads from the country definition, so adding a market is writing one new definition file per app, not new logic.
A country is one registry file per app, and both must agree.
- Write the phone definition at
src/registry/countries/<CC>/index.ts(a directory:index.ts, plusprovinces/andtax/as needed). It must define:
currencyandsymboldistanceUnit(kmormi)taxrules (default withholding, region preset type, region label, and so on)mileage— a rate table, or explicitlynullfor "no researched rates" (the key must be present, so a new country makes a deliberate choice)defaultAvailablePlatforms
- Write the web definition at
web/src/registry/countries/<CC>.country.jswith the same fields (seeweb/src/registry/countries/_TEMPLATE.country.js). - Register it on both sides. This is the step that actually turns a country on:
- phone: add it to
COUNTRY_MAPinsrc/registry/countries/index.ts, and add its regions toALL_REGIONS - web: add it to the
COUNTRIESarray inweb/src/registry/countries/index.js
- Keep the two in parity. Run
node scripts/check-country-parity.mjs— it verifies the phone and web registries define the same countries with matching shapes. A country registered on one app but not the other, or with mismatched fields, fails the check.
The registries fail loudly on a half-added country: assertCountryRegistryValid() throws if a registered country is missing a required field, and an unregistered country requested at runtime logs an error and falls back to Canada rather than silently serving the wrong tax rate.
Adding a supported platform
Platforms are defined in src/registry/platforms/ (phone) and web/src/registry/ (web), grouped by country.
- Add an entry to the appropriate country's platform list:
{
id: 'new_platform',
label: 'New Platform',
color: '#FF6B00',
textColor: '#FFFFFF',
country: 'CA',
logoEmoji: '', // optional
defaultHourlyRate: '20',
defaultMileageRate: '0.70',
sortPriority: 10,
}- Mirror it on the other app so both offer the same platforms in that country.
- The platform then appears automatically in the activation list for drivers in that country — there is no separate wiring.
Drivers can also add their own custom platforms at runtime; a built-in definition is only needed to offer one out of the box.
Feature flags
Features not ready for everyone should be gated:
- Add the flag name to the type in
src/hooks/useFeatureEnabled.ts. - Set its per-country default in
src/registry/countries/. - Wrap the feature:
const isEnabled = useFeatureEnabled('my_feature')
if (!isEnabled) return null- Expose it in the developer override list so it can be toggled while testing.
Pull requests
- Open an issue first for non-trivial changes.
- One logical change per PR. Don't bundle unrelated fixes.
- TypeScript must pass:
npx tsc --noEmitwith no errors. - Lint must pass:
npm run lintwith no errors. - Test what you build. If you add a query, run it on a device or emulator with real data — type checking verifies shape, not behavior.
- Touching a country? Run
node scripts/check-country-parity.mjs.
PR description checklist
- What changed and why
- How to test it
- Screenshots or recordings for UI changes
- Any database migration, and what it does
Database migrations
If a change needs a schema migration:
- Add a migration entry in
src/database/client.tsand increment the version. - Write idempotent SQL (guard
ADD COLUMNwith apragma_table_infocheck). - Test on a fresh database and on an existing one with real data.
- Note the migration in your PR.
Commit style
Follow Conventional Commits:
feat: add expense receipt photo
fix: GPS jitter threshold too aggressive
refactor: extract mileage calc to a pure function
docs: update GPS engine docTypes: feat, fix, refactor, docs, test, chore.
Reporting bugs
Open an issue at github.com/raiz-toff/Comma with:
- App version (from About)
- Which app (phone or web), device model, and OS version
- Steps to reproduce
- Expected versus actual behavior
- Logs if available
License
Comma is MIT licensed. By contributing, you agree your contributions are under the same license.