Three enforcement patterns emerged. Soft limits query the current count from Convex, compare against a tier-specific cap, and render a counter component ("3 of 15 used") that swaps to an upgrade prompt near the limit. Hard gates check planTier from the workspace record and render a full overlay with upgrade CTA when the feature is above the user's tier. Preview gates are a middle ground: the list/summary view renders normally, but clicking into a detail view hits the gate. Used this for scheduling report drill-downs where showing the summary is a better sales tool than hiding it entirely.
The TierBadge component renders tier-colored pills (Free gets smoke, Pro gets honey, Business gets violet, Enterprise gets honey with border). Placed it in the sidebar nav, workspace switcher, billing page, and settings header. The Convex `getUserWorkspaces` query was extended to return `planTier` per workspace so the badge renders without extra lookups.
Tier assignment was the interesting design problem. Crew ratings and achievements went to Free because they drive engagement. Gating engagement to upsell felt counterproductive. Soft limits were set high enough that Free users hit real value before the cap (client tags at 15, crew certifications at 10, venue photos at 10). Hard gates protect operational leverage features: bulk gig actions, labor budgets, overtime rules.
The first commit touched 55 files. Second pass wired the remaining gates I missed and corrected several tier assignments. Not glamorous but having the enforcement pattern standardized made per-module wiring mechanical.
https://jamcrew.io