Files
bodyshop/client/src/feature-flags

Feature Flags

The app imports feature-flag hooks from src/feature-flags/splitio-react-replacement.jsx. That module keeps the old Split-shaped component and hook API intact while removing the runtime dependency on Split.

Code should import this local module directly. We no longer rely on a Vite alias for the old Split package.

Current storage contract

The compatibility layer reads the active shop from Redux, then fetches DB-backed assignments from:

GET /feature-flags/bodyshops/:bodyshopId

That endpoint verifies the Firebase user can access the bodyshop through Hasura permissions, then returns cached Redis data when present or refreshes from feature_flags + bodyshop_feature_flags.

On successful backend responses, the client stores the last-known flag payload in browser localStorage for the active bodyshop. If the backend cannot be reached later, the client uses that bodyshop-scoped browser cache for up to 24 hours. If there is no browser cache, unknown flags resolve to "off".

Recommended backend payload shape:

{
  "flags": {
    "Enhanced_Payroll": {
      "treatment": "on",
      "config": null,
      "activeDate": null,
      "deactiveDate": null
    },
    "Demo_Feature": {
      "treatment": "on",
      "config": null,
      "activeDate": "2026-06-01T13:00:00-04:00",
      "deactiveDate": "2026-06-05T17:00:00-04:00"
    }
  }
}

Supported values:

  • true, "true", 1, "on" -> treatment "on"
  • false, "false", 0, "off" -> treatment "off"
  • ISO-ish future date strings -> "on" until the date passes
  • { "treatment": "on" | "off" | "control" | "any-custom-treatment", "config": ... }
  • Scheduled demo windows using activeDate and deactiveDate

Unknown flags default to "off".

Backend registry

Canonical feature flag definitions live in the Hasura-backed feature_flags table and are exposed to the admin panel through GET /adm/feature-flags.

Per-shop assignments live in bodyshop_feature_flags. The admin panel reads them through GET /adm/bodyshops/:bodyshopId/feature-flags and saves them through POST /adm/updateshop.

Hasura invalidates the Redis cache through /feature-flags/cache/invalidate when bodyshop_feature_flags or feature_flags changes. Assignment changes clear the affected shop cache for the current cache version; definition changes increment a global feature flag cache version so old per-shop cache entries become invisible and expire by TTL.

The backend also emits feature-flags-changed over the existing Socket.IO connection. SocketProvider bridges that socket message to a browser event, and SplitFactoryProvider refetches flags when the event is global or matches the active bodyshop. This keeps already-open tabs in sync with admin edits and Hasura-triggered invalidation.

For manual frontend testing, the global footer displays Test Feature Flag Enabled when TEST_FLAG resolves to the on treatment.