This release includes 2 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+8 more
Affected surfaces
ReleasePort's take
Light signalRelease v0.7.4 introduces a unified PlanFeatures matrix that centralizes subscription overrides and feature gating across server logic, UI components, and pricing pages.
Why it matters: Adopt the new Subscription.overridePlan API for consistent per‑customer grants; update services using ProjectsService checks to align with the shared matrix before your next deployment cycle.
Summary
AI summaryUnified plan-features matrix enables Subscription.overridePlan across server enforcement and pricing UI.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Feature | Medium |
Introduces shared plan-features matrix across server and UI. Introduces shared plan-features matrix across server and UI. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
New PlanFeatures type in @usertour/types with comprehensive per-tier gates. New PlanFeatures type in @usertour/types with comprehensive per-tier gates. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
Shared PLAN_FEATURES matrix in @usertour-packages/constants/billing covers all current product gates. Shared PLAN_FEATURES matrix in @usertour-packages/constants/billing covers all current product gates. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
resolvePlanFeatures, parseOverridePlan, and isWithinLimit helpers now run on server and web. resolvePlanFeatures, parseOverridePlan, and isWithinLimit helpers now run on server and web. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
Subscription.overridePlan becomes canonical layer for per-customer grants. Subscription.overridePlan becomes canonical layer for per-customer grants. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
EnvironmentsService.create now delegates environment limit checks to ProjectsService. EnvironmentsService.create now delegates environment limit checks to ProjectsService. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
New errors TeamMemberAlreadyInvitedError and TeamMemberAlreadyInProjectError added for invite deduplication. New errors TeamMemberAlreadyInvitedError and TeamMemberAlreadyInProjectError added for invite deduplication. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
SubscriptionContext now exposes effective PlanFeatures to consumers. SubscriptionContext now exposes effective PlanFeatures to consumers. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Feature | Medium |
PlanType enum gains ENTERPRISE for self-hosted license path. PlanType enum gains ENTERPRISE for self-hosted license path. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Feature | Medium |
Unlimited sessions render as 123 / Unlimited with hidden caption for unbounded caps. Unlimited sessions render as 123 / Unlimited with hidden caption for unbounded caps. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Feature | Medium |
use-plan-limits web hook provides environment, team member, and sessions limit utilities with override support. use-plan-limits web hook provides environment, team member, and sessions limit utilities with override support. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Feature | Medium |
Pricing page comparison table aligns with usertour.io marketing copy. Pricing page comparison table aligns with usertour.io marketing copy. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Feature | Medium |
Per-customer overrides surface only on user's current plan card. Per-customer overrides surface only on user's current plan card. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Feature | Medium |
Extends PlanType enum with ENTERPRISE value for self‑hosted licensing. Extends PlanType enum with ENTERPRISE value for self‑hosted licensing. Source: granite4.1:30b@2026-05-23-audit Confidence: low |
— |
| Feature | Medium |
Adds useEnvironmentLimit, useTeamMemberLimit, and useSessionsLimit hooks in web with override support. Adds useEnvironmentLimit, useTeamMemberLimit, and useSessionsLimit hooks in web with override support. Source: granite4.1:30b@2026-05-23-audit Confidence: low |
— |
| Feature | Low |
Displays unlimited sessions as "Unlimited" instead of "Infinity" and hides threshold caption when unbounded. Displays unlimited sessions as "Unlimited" instead of "Infinity" and hides threshold caption when unbounded. Source: granite4.1:30b@2026-05-23-audit Confidence: low |
— |
| Bugfix | Medium |
TeamService.inviteTeamMember gate now uses unified ProjectsService check, removing duplicate logic. TeamService.inviteTeamMember gate now uses unified ProjectsService check, removing duplicate logic. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Bugfix | Medium |
Invite mutation now soft-deletes failed invites and surfaces InvitationDeliveryFailedError. Invite mutation now soft-deletes failed invites and surfaces InvitationDeliveryFailedError. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Bugfix | Medium |
Soft‑deletes failed invites and returns InvitationDeliveryFailedError on email delivery failure. Soft‑deletes failed invites and returns InvitationDeliveryFailedError on email delivery failure. Source: granite4.1:30b@2026-05-23-audit Confidence: low |
— |
| Refactor | Medium |
ProjectsService gains resolveProjectFeatures, checkEnvironmentLimit, and checkTeamMemberLimit. ProjectsService gains resolveProjectFeatures, checkEnvironmentLimit, and checkTeamMemberLimit. Source: llm_adapter@2026-05-21 Confidence: high |
— |
| Refactor | Medium |
Drift-prone rows in pricing table now use matrix-driven typed helper instead of hard-coded values. Drift-prone rows in pricing table now use matrix-driven typed helper instead of hard-coded values. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Refactor | Medium |
@usertour-packages/constants promoted from P1 to P2 package type. @usertour-packages/constants promoted from P1 to P2 package type. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Refactor | Medium |
Pricing page icons switched from lucide-react to remix icons. Pricing page icons switched from lucide-react to remix icons. Source: llm_adapter@2026-05-21 Confidence: low |
— |
| Refactor | Low |
Replaces hard‑coded rows in pricing table with matrixRow typed helper. Replaces hard‑coded rows in pricing table with matrixRow typed helper. Source: granite4.1:30b@2026-05-23-audit Confidence: low |
— |
Full changelog
This release lands a shared plan-features matrix that finally unifies how the server enforces plan gates and how the pricing page presents
them — Subscription.overridePlan lights up across the stack so per-customer CS grants take effect everywhere instead of just one layer. The
team-invite path also gains long-missing safety nets, and the pricing settings page aligns with usertour.io's marketing copy.
What's Changed
🧱 Plan features matrix (cross-cutting infrastructure)
- New PlanFeatures type in @usertour/types and a shared PLAN_FEATURES matrix in @usertour-packages/constants/billing that covers every
per-tier value the product gates on today: removeBranding, sessionsLimit, teamMemberLimit, environmentLimit, dataRetentionYears,
apiRateLimit, plus placeholders for upcoming auditLogs / ssoSaml / ssoOidc gates. - resolvePlanFeatures(planType, overridePlan), parseOverridePlan, and isWithinLimit helpers live in @usertour/helpers and run on both server
and web — the same merged feature set drives runtime enforcement and UI rendering. - Subscription.overridePlan (the JSONB column that was wired up but unread) is now the canonical layer for per-customer grants. Override
fields replace base fields via spread, so a CS-granted seat bump or a legacy benefit ({"removeBranding": true} for grandfathered Starter
projects) lands in resolution without any code change. - @usertour-packages/constants was promoted from P1 (source-direct) to P2 (pre-built dist) since the NestJS server now requires it at
runtime. The P1 → P2 migration steps are codified in docs/architecture/packages.md. - PlanType enum gains ENTERPRISE so the self-hosted license path stops using bare strings; the two existing Record<PlanType, …> maps on the
web pick up the new key.
🛡 Server-side quota enforcement
- ProjectsService gains resolveProjectFeatures, checkEnvironmentLimit, and checkTeamMemberLimit. The cloud / self-hosted bypass, subscription
lookup, override merge, count query, and error throw all live in one place and accept a Prisma TransactionClient. - EnvironmentsService.create was previously gated only by the client — useEnvironmentLimit disabled the form button but any consumer that
bypassed the form (stale UI, direct mutation call) could create past the cap. The mutation now delegates to
projectsService.checkEnvironmentLimit inside its own transaction. New EnvironmentLimitError (E0030) with en/zh messages. - TeamService.inviteTeamMember's hand-rolled hobby/starter/growth if-chain is gone; the gate now goes through
projectsService.checkTeamMemberLimit, which honours overridePlan the same way the client does. The roughly 230-line dup between
projects.service and web-socket.service for cloud / self-hosted config resolution also collapses — web-socket.service.getConfig delegates to
projects.
📧 Team invite hardening
- The same email used to accumulate multiple Invite pending rows on the team settings page because there was no dedup before
prisma.invite.create. Each row counted against the seat quota, letting a project artificially fill its allotment by inviting one address
twice. Two new errors register pre-create: TeamMemberAlreadyInvitedError (E0031) and TeamMemberAlreadyInProjectError (E0032). - When the SMTP layer rejected the recipient (e.g. 550 / EENVELOPE for a bad mailbox), the freshly created invite row stayed in the DB while
the client got a generic 500 and the new dedup check blocked the user from retrying. The mutation now wraps sendInviteEmail in a try / catch
— failure soft-deletes the invite (matching the cancel / accept lifecycle) and surfaces InvitationDeliveryFailedError (E0033).
🎨 Pricing page polish
- Comparison table aligns with usertour.io: Support & service section becomes Community / Email / Priority with priority gated to Business
only, the ghost Concierge support row is gone, and the Growth card's redundant Live chat support line drops back to Email support. - Every drift-prone row (Sessions / Data Retention / Environments / API rate / Team members / No-branding) is now matrix-driven via a typed
matrixRow helper instead of a hardcoded values: [false, true, true, true] literal. - Per-customer override surfaces only on the user's current plan card and current plan column. Other cards stay base so a CS-granted sessions
bump on Growth doesn't make the Starter or Business columns claim the same number. The comparison table stays apples-to-apples for upgrade
decisions while honouring the user's actual entitlement on their own row. - Pricing page icons switch from lucide-react + a custom BoxIcon to the project's standard remix icons (@usertour-packages/icons).
- Unlimited sessions render as 123 / Unlimited instead of 123 / Infinity; the percent / threshold caption hides when the cap is unbounded.
🪝 Quota hooks on the web
- SubscriptionContext now exposes effective features: PlanFeatures so consumers stop re-resolving themselves.
- apps/web/src/hooks/use-plan-limits.ts adds useEnvironmentLimit, useTeamMemberLimit, and useSessionsLimit. Each hook composes the
subscription features, the relevant resource list, and the self-hosted bypass into a single { limit, current, canUseMore } shape. Consumers
like EnvironmentCreateForm and MemberInviteDialog shrink to one line and now honour override on the client side too — the server check the
hook mirrors used to be the only place that saw the override.
📘 Architecture doctrine
- docs/architecture/packages.md gets a @usertour/types vs @usertour-packages/constants split (contracts vs values, with the "delete-the-line,
what breaks?" heuristic), a P1 / P2 package-shape section with the 6-step migration checklist that this branch's constants promotion
follows, and a note that runtime values should share as soon as a second real consumer exists — including code that copies the same business
contract values without importing them.
Full Changelog: https://github.com/usertour/usertour/compare/v0.7.3...v0.7.4
Breaking Changes
- @usertour-packages/constants promoted from P1 (source‑direct) to P2 (pre‑built dist); migration steps documented in docs/architecture/packages.md.
- PlanType enum now includes ENTERPRISE; existing code using bare strings for self‑hosted license path must be updated.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About usertour
Usertour is an open-source user onboarding platform. It allows you to create in-app product tours, checklists, and surveys in minutes—effortlessly and with full control.The open-source alternative to Userflow and Appcues
Related context
Related tools
Beta — feedback welcome: [email protected]