Slappcard — Feature Flags
Purpose: Protocol for gating surfaces that are not part of the Legacy Launch MVP. Default is OFF everywhere. Flags are PostHog-driven (client-side), with a DB-backed admin surface planned for Phase 2. Companions: STACK · ARCHITECTURE · MVP · ENV_CHECKLIST.md
Live status (2026-08-11): enforcement shipped to the stable URL (
https://slappcard-staging.vercel.app) via mergestaging→main(Vercel's production branch)./studio,/demo/*,/label/*render the "not enabled" fallback there.Flags LIVE (2026-08-11):
studio,demo_portals,label_portal,new_checkoutare enabled (active, 100%) in the PostHog project so the staging preview exposes the gated surfaces. Note: PostHog's free plan (one project) + person-aggregation validation blocks URL-condition segmentation via API — per-env targeting needs device-mode conditions set in the PostHog dashboard, or a separate prod PostHog project (see PROD_TWIN.md).PostHog is fully wired:
NEXT_PUBLIC_POSTHOG_KEYon Vercel (all envs) + bws, personal API key in bws (POSTHOG_PERSONAL_API_KEY), session recording enabled (project level,maskAllInputsfor privacy). The 4 flags (studio,demo_portals,label_portal,new_checkout) exist and are OFF (active: false— the release toggle). To enable one: Feature Flags → flag → flip the release toggle (and set rollout 100%). The client gate re-evaluates live viaonFeatureFlags. Ship rule: develop onstaging, merge tomainto publish to the stable URL.
Registry (source of truth)
apps/web/src/lib/features.ts — every flag must be declared here before use:
| Flag | Key | Gates |
|---|---|---|
studio | studio | Print studio surface (/studio, admin-linked) |
demo_portals | demo_portals | /demo/* designer demos |
label_portal | label_portal | /label/* label portal |
new_checkout | new_checkout | Future ordering / checkout flow |
- Defaults are OFF. Do not change the default; enable per environment in the PostHog dashboard instead.
flagKey(flag)maps aFeatureFlagname to its PostHog key string — never hardcode the string in views.
How surfaces are gated
Use the client-side <FeatureGate> wrapper (render-time gate):
// app/studio/page.tsx
import Page from "@/views/StudioPage";
import { FeatureGate } from "@/components/FeatureGate";
export default function RoutePage() {
return (
<FeatureGate flag="studio">
<Page />
</FeatureGate>
);
}
Evaluation rules (all safe-by-default):
NEXT_PUBLIC_POSTHOG_KEY/NEXT_PUBLIC_POSTHOG_HOSTnot set on Vercel → OFF.- Flag not created, or not enabled for the environment → OFF.
- Server render / first paint → OFF (flips client-side after flags load).
- Flag on + loaded → surface renders.
Currently gated surfaces: /studio (studio), /demo/* (demo_portals),
/label/* (label_portal).
Enabling a flag (staging)
- Add
NEXT_PUBLIC_POSTHOG_KEY+NEXT_PUBLIC_POSTHOG_HOSTto Vercel (Preview/Development/Production; they are public by definition — create as non-sensitive sovercel pullcan fetch them for local/CI builds). Staging PostHog project token lives in bws (NEXT_PUBLIC_POSTHOG_KEY) once provisioned. - In the PostHog dashboard, create the flag (e.g.
studio) and enable it for the relevant environment (staging: use theNEXT_PUBLIC_APP_ENV/host filter). - Ship the flag off, verify the fallback panel, then flip in PostHog.
The client gate is enforcement for the Legacy Launch MVP. It is not a security boundary — treat it as UI gating. Do not rely on it for authorization (Convex authz is the real gate for domain ops).
Phase 2 (planned, not built)
featureFlagstable in Convex + admin UI. The AdminPortal Feature flags tab (/admin/feature-flags) is currently aComingSoonPanel— it becomes the management surface (DB-driven with PostHog sync or fallback).- Server-side evaluation via
@posthog/next(needs a server-sidePOSTHOG_API_KEY/POSTHOG_HOSTenv pair) for route middleware, and Convex-side checks for domain-critical actions.
Rules for AI agents
- New non-legacy surface → add a flag to
lib/features.ts, wrap the route in<FeatureGate>, document it in the table above. - Never ship a surface whose flag is absent from the registry.
- Never hardcode flag keys in views; use
flagKey(). - This file is allowlisted for the docs bot (
update_docs.py) — keep it accurate when code changes.