Opaius / Product Updates

Updates

Public release notes for Loop, written for people first with technical details available when useful.

v1.2.7Current

AI enforcement, model gating, and bonus credit packs

Share

Opie's daily request limit is now actually enforced — no more silently uncapped usage — and you can buy AI credit packs to keep going on the days you blow past the cap. We also added plan-aware model gating so cheaper plans can't accidentally trigger expensive models, and Settings now shows your real daily AI count with a Buy More button right on the AI Usage and Billing tabs.

  • Daily AI request limit is enforced for real now: when you hit your plan's cap, AI calls return a clear 'limit reached' response with the exact reset time instead of silently going through.
  • New AI credit packs in Stripe — Starter (500 requests, $5), Pro (2,000 requests, $15), Studio (10,000 requests, $49). Bonus credits stack on top of your daily plan limit and never expire.
  • Settings → Billing and Settings → AI Usage both show your daily count today (e.g., '18 / 25 requests today') with a + N bonus credits chip and a Buy More button that opens a one-click Stripe checkout.
  • Plan-aware model gating: Free and Garage plans get Gemini 2.5 Flash, Shop adds Gemini 2.5 Pro and GPT-4o Mini, Floor unlocks the full lineup. Disallowed model requests silently downgrade to your plan's best model instead of hard-blocking.
  • After buying credits you'll see a 'Credits added' banner in Settings and the meter refreshes automatically as Stripe confirms the payment.
Details for builders

Replaced the stub in lib/server/billing/enforceLimits.ts with a real org_usage_daily query plus org_billing.ai_bonus_credits lookup; the gateway now throws AI_DAILY_LIMIT_EXCEEDED with current, limit, planLimit, bonusCredits, and resetAt fields, surfaced as HTTP 429 in the loop-assistant and conversations send routes. Added getAllowedModels and resolveModel helpers in lib/ai/aiRouter.ts that downgrade requested models to the plan's default and log the downgrade. Added the org_billing.ai_bonus_credits column (migration 20260504120000), three Stripe one-time prices for credit packs, the new POST /api/billing/buy-ai-credits route, a credit_pack branch in the Stripe webhook with audit-log idempotency, and bonus-credit deduction in trackOrgUsage that fires only when the daily cap is exceeded. UI: components/billing/BuyAICreditsDialog.tsx pack picker, BillingSettings now reads ai_requests_daily and ai_bonus_credits from /api/billing/usage and renders the meter + Buy More CTA, AIUsageSettings adds a top-of-page 'Your Plan's Daily AI Limit' card with the same dialog and renames the existing combined card to 'Provider Rate Limits' to disambiguate.

  • Migration 20260504120000_org_billing_ai_bonus_credits.sql adds ai_bonus_credits INTEGER NOT NULL DEFAULT 0 to org_billing.
  • checkAIRequestLimit returns AIRequestLimitResult with planLimit, bonusCredits, resetAt (next UTC midnight), and code AI_DAILY_LIMIT_EXCEEDED.
  • aiGateway calls checkAIRequestLimit before routing for platform_paid mode and throws a typed error rather than going through enforceOrgLimits('ai_request') for the daily cap.
  • loop-assistant and conversations/[id]/send routes return HTTP 429 with { code, current, limit, planLimit, bonusCredits, resetAt, upgradeUrl } on AI_DAILY_LIMIT_EXCEEDED.
  • aiRouter.runModel accepts an optional planTier (defaults to 'free') and runs the requested model through resolveModel before dispatch.
  • New STRIPE_PRICE_AI_CREDITS_500/2000/10000 env var contract; live-mode prices created via Stripe MCP.
  • POST /api/billing/buy-ai-credits validates org membership, creates a mode='payment' Checkout Session with metadata.credit_pack='true' and returns { url }.
  • Stripe webhook handleCheckoutCompleted now branches on metadata.credit_pack and increments org_billing.ai_bonus_credits via applyCreditPackToOrg, with an audit_events guard against retried webhook deliveries.
  • trackOrgUsage('ai_request') now decrements ai_bonus_credits by the count of requests that overflow max_ai_requests_per_day.
  • /api/billing/usage now selects and returns billing.ai_bonus_credits.
  • BillingSettings replaces 'AI Requests (This Month)' with 'AI Requests Today' against the daily cap, adds the bonus-credits chip + Buy More button, mounts BuyAICreditsDialog, and shows a one-time 'Credits added' banner on ?credits=purchased.
  • AIUsageSettings adds 'Your Plan's Daily AI Limit' as the first card and renames the existing combined card to 'Provider Rate Limits' for clarity.
  • lib/constants/pricingPlans.ts exports AI_CREDIT_PACKS keyed by '500' | '2000' | '10000' with priceEnvVar bindings.
v1.2.6

Plans, billing, and trial sharing polish

Share

We tightened up the billing and account experience: real plan tiers now show in your Account page, Plans buttons reflect your current subscription, you can gift your trial to a friend by @username, and a few cosmetic disclaimers and crashes are gone.

  • Account page now shows your real plan (Garage, Shop, Floor, Free, etc.) instead of always saying Free, and the unused Beta pill is gone.
  • Plans tab buttons are now dynamic: Current Plan, Upgrade, Downgrade, or Cancel & Move to Free, based on your active subscription.
  • Billing page splits the Start Free Trial CTA into two: start it yourself, or gift it to a friend, with a live countdown to the token's expiry.
  • Trial Tokens settings can now send your gift to another Loop user by @username, with a confirmation popup showing their avatar, handle, and display name before sending.
  • The Workspace → Integrations tab no longer crashes when you open it (Radix Select empty-value error is fixed).
  • The standalone /pricing public page is gone for now — plans are managed inside Settings → Plans, and the Pricing tab is renamed to Plans across all languages.
Details for builders

AccountSettings now fetches /api/billing/usage and routes the Upgrade button into the Plans tab. PricingCards self-fetches /api/billing/pricing-plan and renders dynamic CTAs (with a Cancel-to-Free flow that hits /api/billing/downgrade). The trial-token gift API accepts recipientUserId and resolves the recipient's email server-side from user_profiles. /pricing was removed from the public middleware allowlist and the /(main)/pricing route was deleted; start-checkout error and cancel URLs now point at /garage/settings?tab=pricing. EffectivePlanTier was widened to include garage|shop|floor (with legacy aliases retained), and the AI registry's PlanTier type was widened to match. The IntegrationsSettings Ragie partition Select uses a __none__ sentinel instead of an illegal empty-string SelectItem value.

  • Removed the public /pricing route and /^\/pricing(.*)/ middleware allowlist entry; routes.public.pricing deleted.
  • Renamed the Pricing settings tab label to Plans in en, de, es, ko, tr, zh-CN locales (URL ?tab=pricing still works).
  • AccountSettings reads org_billing via /api/billing/usage and surfaces planTier + trial state to SettingsAccount.
  • AccountInfo.type widened to free|garage|shop|floor|enterprise|internal (+ legacy aliases) with matching plan badges.
  • BillingSettings adds Gift to a friend CTA next to Start Free Trial plus a TokenClaimCountdown driven by /api/trial-tokens.
  • TrialTokensSettings adds a username field, /api/users/search lookup, and an AlertDialog confirmation showing avatar, @handle, and display name.
  • /api/trial-tokens/gift accepts { recipientUserId } and resolves email server-side (with a self-gift guard).
  • PricingCards renders Current/Upgrade/Downgrade/Cancel CTAs based on /api/billing/pricing-plan; logged-out callers keep the marketing CTA.
  • RedeemTrialTokenClient's expired-token CTA links to /sign-up instead of the removed /pricing page.
  • Removed the Beta pill in SettingsAccount, the (Beta) label in AIAssistant, and the Private Beta line in the auth header.
v1.2.5

Trial tokens for early access sharing

Share

Loop now gives eligible members a trial token they can send to someone else. The flow is built around a simple share link, no-card redemption, and reminders before unused tokens expire.

  • A new Trial Tokens settings section shows your available token, days remaining, and sharing controls.
  • Members can copy a redemption link or email a 30-day Loop trial invite directly from Settings.
  • Recipients can open a public redeem link, sign in or create an account, and activate a 30-day trial without entering a card.
  • Token reminders now run on a schedule so unused gifts get a nudge before they expire.
  • World ID verification has been scaffolded behind a feature flag for future testing, without changing the current signup or billing flow.
Details for builders

Adds the WorkOS-backed trial token schema, service helpers, API routes, Settings UI, public redemption page, Stripe trial activation path, middleware exceptions for redeem links, scheduled token notifications, and dormant World ID database/API scaffolding. Stripe subscription webhooks now mint the correct token state for trial vs direct-pay onboarding.

  • New migration: 20260502120000_trial_tokens_and_world_id.sql.
  • New trial token APIs: /api/trial-tokens, /api/trial-tokens/gift, /api/trial-tokens/preview/[token], and /api/trial-tokens/redeem.
  • Compatibility endpoints preserve the spec paths: /api/tokens/gift and /api/tokens/redeem/[link].
  • New public route: /redeem/[token], with middleware allowing preview before authentication.
  • New Vercel cron: /api/cron/trial-token-notifications.
  • World ID routes are present behind NEXT_PUBLIC_WORLD_ID_ENABLED and require the World ID env vars before use.
v1.2.4

Mobile Garage and onboarding gate cleanup

Share

Garage and Settings are steadier on phones, and onboarding now has a clearer source of truth for who is finished. We also cleaned up the legacy username-to-handle gap so completed profiles are easier to audit.

  • The Garage getting-started checklist now appears inline on mobile instead of hiding behind the bottom navigation.
  • Settings now uses stable category buttons and section links on mobile and desktop, avoiding dropdowns that closed before users could tap an item.
  • Onboarding status checks now show the full completion checklist: active account, completed onboarding flag, and a Loop @handle.
  • Legacy profiles that still have an old username but no Loop @handle are now easier to find before they get stuck in onboarding.
Details for builders

GettingStartedWidget no longer switches to a low-z fixed mobile drawer trigger. SettingsPage no longer relies on Radix NavigationMenu dropdowns for the settings section picker. The onboarding gate helper now exposes a checklist object, the debug status endpoint reports the same gate used by protected layouts, and a development-only onboarding audit endpoint surfaces username-to-handle migration gaps. Supabase profile opaiustest was updated to use handle=opaiustest and onboarding_completed=true.

  • Garage checklist renders as a responsive inline card across breakpoints.
  • Settings category navigation uses persistent buttons plus section links, with search results kept in the same stable list surface.
  • Auth gate completion is active account + onboarding_completed + valid handle.
  • Debug onboarding status no longer treats onboarding_completed alone as complete.
  • Added /api/debug/onboarding-audit for development-only counts and legacy candidate inspection.
v1.2.3

More exact garage specs and safer OAuth onboarding

Share

Garage vehicle profiles now understand chassis, submodel, engine, and technical-code identity instead of treating year, make, and model as the only source of truth. This release also keeps Google and Apple sign-ups on the onboarding path until their Loop profile is complete, makes insurance and DMV registration edits stick immediately, and cleans up release-page sharing so /updates loads without hydration noise.

  • Vehicle profiles can now capture exact chassis/platform, technical code, subcode, engine code, engine details, and technical notes.
  • Known catalog matches auto-fill exact identity details where possible, such as BMW 325i E30 generations and Mercedes GL 450 X164/X166 splits.
  • Mercedes GL 450 catalog choices now clearly distinguish 164.871 and 166.872 instead of looking like accidental duplicates.
  • Technical identity appears in garage specs, so profile views can show chassis, engine, and notes alongside the regular year/make/model.
  • Google and Apple sign-ups now fail closed to onboarding until the user has completed the required profile and @ name steps.
  • Insurance and DMV registration edits now show the saved values right away without needing a refresh or a second save.
  • The Updates page share controls now hydrate cleanly on first load.
Details for builders

Adds a normalized chassis-code catalog and resolver, persists resolved/manual identity into garage_vehicles.metadata.vehicle_identity, mirrors important values into spec_sheet, and displays the identity in add/edit vehicle and spec views. OAuth callback, sign-up, and home routing now enforce the onboarding gate before sending users into Garage or Workspace. Garage record edits now apply the PATCH response into client state immediately and reload the garage list with no-store semantics. ReleaseShareButton now renders the relative release path through hydration, then upgrades to window.location.origin after mount so the server and initial client markup match.

  • New normalized data source: apps/frontend/lib/data/vehicle_chassis_codes.json.
  • New resolver: apps/frontend/lib/vehicle-chassis-codes.ts resolves chassis/subcode identity from YMM, explicit technical codes, and user overrides.
  • Garage add/edit forms include slots for technical code, chassis/platform, subcode, engine code, engine details, and technical notes.
  • POST/PATCH garage vehicle APIs save identity metadata and spec-sheet cells without requiring a new database migration.
  • Display specs include technical code, chassis, platform, engine code, engine details, configuration, and notes when present.
  • Static catalog restores GL 450 (164.871) and GL 450 (166.872), with 166.872 covering 2013-2014.
  • OAuth sign-up callback, sign-up route, and home smart landing route users to /onboarding when their profile gate is incomplete.
  • Insurance and DMV registration saves update the selected garage vehicle from the PATCH response before the follow-up reload; /api/garage/vehicles responses now send no-store cache headers.
  • Updates share links avoid SSR/client URL mismatches by initializing from the route path and computing the absolute URL after mount.
v1.2.2

Tidy up Stripe pricing references

Share

Internal cleanup so the pricing page, server checkout, and Stripe stay aligned. No user-visible price changes — Garage is still $12/$99, Shop $29/$278, Floor $49/$470.

  • Pricing page no longer carries a duplicate copy of Stripe price IDs that could drift from the server.
  • Setup docs now describe the current Garage / Shop / Floor tiers and env vars instead of the retired Starter / Team / Pro names.
Details for builders

Removed the dead PRICE_IDS const and per-plan stripePriceId* fields from PricingCards — the CTA already routes through /api/billing/start-checkout?plan=…&billing=… and the server resolves the actual price from STRIPE_PRICE_{GARAGE,SHOP,FLOOR}_{MONTHLY,ANNUAL} via lib/stripe/plans.ts. Refreshed STRIPE_SETUP.md and app/api/billing/README.md to document the current tiers, the new GET /api/billing/start-checkout entry point, and the legacy back-compat env vars. Updated the env-var-not-set error messages in BillingSettings and /api/billing/checkout to name the current vars. The two stale Garage prices in Stripe ($9/mo and $86/yr) were archived via the API earlier this session — no live subscriptions were on either.

v1.2.1

Get Garage now actually opens Stripe Checkout

Share

Tapping Get Garage (or Get Shop / Get Floor) on the pricing page used to silently bounce you back to the home page. Now it opens Stripe Checkout — and after payment Stripe brings you back into the app.

  • Get Garage / Get Shop / Get Floor open Stripe Checkout in one tap, on mobile and desktop.
  • If you're not signed in, you're sent through sign-up and dropped straight into checkout afterward.
  • If checkout can't start for any reason, you'll see a clear toast on the pricing page instead of a silent redirect.
Details for builders

New GET /api/billing/start-checkout endpoint creates a Stripe Checkout Session and 303-redirects to its hosted URL. Bounces logged-out users through /sign-up?next=<self> so the second pass completes checkout. Auto-creates a personal org via createPersonalOrgForUser when the buyer has none. PricingCards swapped from <Link href=/signup?...> (which lost query params via /signup → /sign-up → home) to <a href=/api/billing/start-checkout?plan=...&billing=...>. PricingPageContent surfaces ?checkout_error=<code> as a toast.

v1.2.0

Vehicle catalog: Genesis brand and more MINI and Hyundai years

Share

When you add a car or build a loop, the make and model lists now include the Genesis lineup, third-generation MINI hardtops and Countryman, and longer year coverage for classic Hyundai Genesis and Equus models.

  • Genesis as its own make: G70, G80, Electrified G80, G90, GV60, GV70, Electrified GV70, GV80, and GV80 Coupe with model years through the mid-2020s.
  • MINI: F56 and F55 hardtops (2014–2024), F66 and F65 for newer hardtops, plus second-generation Countryman (F60).
  • Hyundai: Genesis Sedan and Coupe, and Equus, now span the full model years from your reference list (through 2016 where applicable).
Details for builders

Static catalog only: apps/frontend/lib/data/vehicle_makes.json, vehicle_makes_models.json, and vehicle_year_ranges.json. No new Supabase migration; NHTSA-backed vehicle_makes/vehicle_models in Postgres remain filled via vPIC seeding, not these JSON files.

  • Picker and loop draft flows that import vehicle_makes_models.json pick up the new rows at build time.
v1.1.2

Stop the age confirmation loop on mobile Safari

Share

Fix a loop where the age confirmation kept reappearing after you saved it on mobile Safari. Once you answer it, you're done — no more cycle when you navigate to pricing or other pages.

  • After you save your age and safety acknowledgement, the popup will not reappear on this device.
  • If a save fails to persist, you'll see a clear error instead of a misleading success message.
  • Mobile Safari no longer serves stale age-status responses from cache.
Details for builders

AgeSafetyPrompt now records a localStorage 'done' flag once the server confirms disclosure, short-circuiting the GET on subsequent loads. PATCH derives requiresDisclosure from the post-update row and returns 500 if a silent persist failure leaves the columns null. GET/PATCH responses now carry explicit no-store headers and the client cache-busts its fetch — Safari mobile aggressively caches even with cache: 'no-store' otherwise.

v1.1.1

Mobile settings and age prompt fixes

Share

Two quick fixes: account settings are reachable from the mobile menu again, and the age confirmation no longer shows up twice when you've already answered it.

  • Mobile: the Account item in the settings menu now opens reliably instead of closing the dropdown.
  • Age confirmation: the prompt only shows up later in your notifications if you tap "Remind me later".
Details for builders

SettingsPage submenu items render as NavigationMenuLink + next/link so taps don't race Radix's auto-close on touch. Age-safety GET no longer enqueues a notification; a new POST is invoked from the prompt's "Remind me later" path.

v1.1.0

Garage, Opie, and your community—clearer and easier

Share

This release makes Garage upkeep and discovery feel more natural, strengthens Opie and social activity, and smooths notifications and onboarding. We also added clearer safety controls for younger users and a steadier foundation under the hood.

  • Garage Discover stays in your dock—jump in anytime without flipping a switch.
  • Smarter upkeep: log service and fuel faster, see what’s due soon, and explore shops with the Loop Directory.
  • Photos and job media upload more reliably, with a simple gallery when you’re tracking work.
  • Opie and conversations pick up quality-of-life improvements—thread flow, models, and context feel more consistent.
  • Social and dream builds get polish: follows, activity, and suggestions align better with how you actually browse.
  • Younger riders see clearer age- and safety-related prompts where the app needs to be extra careful.
  • Notifications use clearer categories and wording so the important stuff is easier to spot.
Details for builders

Bundles the Apr 21–May 1 train: direct Storage uploads and job media gallery; maintenance intervals, service/fuel logs, shop stubs, Opie maintenance scanner + cron, Loop Directory; broad frontend integration (garage, dream car, social discover/follows/entity events); Discover always-on shell; plus platform work through this commit (mission-control Opie votes, Anthropic routing, age-safety API and profile disclosure, NHTSA/car-care helpers, migration hygiene).

  • Preflight enforces unique migration prefixes; duplicate 20260501120000 resolved (garage spec sheet -> 20260501120100).
  • Remote-history placeholder/sync-only migration files removed from repo in favor of real migrations + linked DB alignment—verify with `pnpm exec supabase migration list --linked` before deploy.
  • Release entry validation: `pnpm run release:check` (see docs/RELEASES.md).
Git compare
v1.0.0

Garage and Opie foundation

Share

Loop now has a public release home for product updates, Garage improvements, and Opie-facing changes. This first entry establishes the release format that future trains will follow.

  • Garage work has a clearer public update trail for owners, shops, and builders.
  • Opie updates can be summarized in plain language before technical details.
  • Release links and images are ready to share when a new train ships.
Details for builders

Initial structured release record for the public updates page.

  • Uses apps/frontend/package.json as the canonical visible version.
  • Adds a release record shape for public copy, highlights, and builder details.
  • Provides a baseline entry that CI and preflight can validate against.