ReleaseMay 29, 2026
Train Hub reshape — one Log, an AI Coach, and "You vs the world" on by default
The mobile training suite got sharper. Rolls and the journal merged into a single Log stream, the Mastery map became a forward-looking Coach that tells you exactly what to drill next, the training journal now works without joining a gym, and "You vs the world" is on by default so you can see how your game stacks up across the sport from day one.
- Rolls + Training journal are now one Log — a single chronological stream of every session and roll, with filter chips (All / Sessions / Rolls) and one "Log" button that asks whether you're logging a class session or a roll. Partner scouting and the leak finder still live here
- Mastery map → Coach: instead of just showing where each technique sits, it now leads with "Drill this next" — a prioritized plan built from your own training (situations you keep getting caught in, techniques going stale, and staples you've never logged). Your state-of-game breakdown moved below the fold
- Training journal no longer requires joining a gym — your focus-area radar is portable and works from your first session, whether or not you're a member anywhere yet
- "You vs the world" is on by default — your training is anonymized and only ever shown as an aggregate across many athletes (never tied to you), and you can opt out anytime from the You-vs-the-world screen
ReleaseMay 28, 2026
Frictionless demo claim + demo-to-real promotion + transparent invoicing
Homepage prospects now go from form submission to a fully-seeded admin dashboard in one click — no Clerk sign-in screen, no re-acceptance of terms, no dead-ends if a stale session is active. Super admins can promote a demo org into a real customer in one move that auto-fires the itemized setup-fee invoice. Member-facing Stripe invoices now carry a per-plan "what's included" line so customers see exactly what they're paying for.
- Magic-link demo claim is now invisible — server mints the Clerk identity + sign-in ticket, client consumes it via signIn.create({ strategy: "ticket" }), no <SignIn /> UI ever shown. If a stale session is active, the page signs out first then claims, so a single click always works regardless of prior auth state
- The homepage form's Terms of Service acceptance now carries forward onto the new user's row at claim time (with the original IP + user-agent preserved in terms_acceptances), so prospects don't get re-prompted for an acceptance they already made
- New superadmin.promoteDemoToReal procedure: wipes seeded @olmdemo.local synthetic members + their check-ins / subscriptions / promotion needs, detaches auto-attached super admins, flips is_demo + demo TTL columns, strips the -demo suffix from the slug (overridable), and audit-logs the transition. Comp orgs keep fees waived; paying customers flip to PENDING
- Setup-fee invoice now auto-fires during promotion (default ON; comp orgs skip). Stripe failures don't roll back the promote — the demo flip still succeeds and the existing Send invoice button can retry
- Setup-fee invoice line items now itemize what the $499 covers: Founder-led onboarding & training call · Stripe Connect setup + payouts verification · Member roster import assistance · Theme + branding customization · Going-live launch support
- Member subscription invoices now carry a per-plan description (Stripe Product.description) so members see what's included on their hosted invoice + emailed receipt. New billing_plans.description column + textarea in the admin plan dialog; updates mirror to Stripe so existing subscriptions get the new copy on their next invoice
- Demo organizations seed with 16 members spanning every belt color (WHITE 0-4 stripes through BLACK), 4 promotion needs covering the full belt arc (WHITE→BLUE through BROWN→BLACK), a published Lachlan Giles guard-retention seminar with 12 confirmed registrations, and 2 sample broadcasts in /messaging
- Demo org banner now reads three tiers of urgency: muted amber >7 days, warning amber 3-7 days with "X days left" pill, urgent red <3 days. Readable in both light + dark (no more cream-on-cream), CTA pre-fills a kevin@trainolm.com mailto with the demo slug + expiry baked in
- Lead notification emails now fan out to multiple inboxes via DEMO_REQUEST_INBOX (comma-separated); transactional emails reference the canonical OLM mark at /api/email-assets/olm-mark instead of an inline-CSS reconstruction
- FIXED: /demo/claim was being blocked by Clerk middleware (sent to accounts.dev/sign-in); /api/email-assets was 404'ing because Turbopack ignored route.tsx + the route wasn't in the public matcher; demo claim was redirecting to /{slug}/dashboard which doesn't exist (admin layout group, no slug prefix)
- FIXED: provisioner crashed with "Invalid time value" because demo-seed appended :00 to Postgres time columns that already had seconds; SES rejected magic-link emails because the helper was passing Reply-To as a custom header instead of using the dedicated replyTo field
- Park City Jiu Jitsu "design partner" framing on /app rewritten — Kevin trains there but PCJJ isn't an OLM customer. New copy: "Built by a practitioner, not a software company — an app for grapplers, built by a grappler." The four phone mockups (Check-in, Journal, Belt, Schedule) are now clickable with hover-lifts and the 8 Edition 06 cards are real Links with hover glows + arrow reveals
ImprovementMay 19, 2026
Per-org auto-grant trial toggle + dismissible dashboard waiver prompt
Admins can flip auto-grant trials on/off per org from settings (defaults to on). The mobile dashboard's waiver prompt is now dismissible with an X — the soft nudge clears, the check-in hard gate stays. The dismissal is per-doc-id, so a NEW required document the gym adds later re-surfaces the card.
- New organizations.auto_grant_trial_on_join column (default true) — admins control whether self-service member.join grants the org's default trial automatically
- Toggle surfaced in Admin → Settings → Membership & Trials next to the trial-value field, with copy explaining the dashboard impact
- Mobile dashboard waiver prompt now has an X dismiss button; dismissed doc ids are persisted in AsyncStorage per-org and auto-pruned when docs get signed
- REGRESSION: a freshly-added required doc re-surfaces the prompt even if previous docs were dismissed (1 vitest contract pinning this)
- Seed file now creates 2 active waivers for Park City BJJ (Liability Waiver + Photo & Media Release) so the multi-doc form of the prompt is visible in dev without manual setup
ReleaseMay 19, 2026
Free trial auto-grants on join + dashboard surfaces trial / waiver state
Members now get their gym's free trial automatically the moment they join — no Skip button required. The mobile dashboard surfaces a free-pass CTA, trial countdown, grace-period nag, and an unsigned-waiver prompt as cards at the top, so members never get surprised at check-in time.
- member.join now routes through computeMembershipFields just like the admin-invite path — trial-abuse suppression, org defaultTrialValue, and the CLASSES-vs-DAYS trial type are all honored
- Members with an existing ACTIVE subscription at the joined org skip the trial (they don't need one); reactivating an INACTIVE membership never re-grants
- member.getMembershipStatus now returns a trialAvailable boolean so the dashboard can show a success-toned "Start free trial" CTA when the member has never had one
- New dashboard access banner: ACTIVE_TRIAL shows days/classes remaining (warning tone at ≤1 left), GRACE_PERIOD shows update-card CTA, TRIAL_EXPIRED shows pick-a-plan, NO_ACCESS with free trial available shows the success-toned trial CTA
- New dashboard waiver prompt: lists unsigned required documents with a one-tap CTA to /sign-waiver/[id]; auto-disappears once all required docs are signed
- Decision logic is pure (apps/expo/src/app/(tabs)/dashboard/_access-banner-logic.ts) and unit-tested — 16 contracts covering every access reason + waiver-count edge case
ReleaseMay 19, 2026
Skip-for-now trial + App Store links in invites + Powered-by-OLM signature
Three onboarding-flow changes ship together: members who hit the billing screen can tap Skip for now to start a free 7-day trial and check in immediately, member invite emails include real App Store + Play Store badges so recipients can download the app from the same email, and a small Powered by OLM signature now sits under the sign-in and pre-org screens so users always see the platform behind their gym.
- New billing.startSelfServiceTrial mutation: idempotent, grants 7 days, only shown when the member is in NO_ACCESS state (won't appear after a trial expires); audited as MEMBER_TRIAL_UPDATE with method=self_service_skip
- Member invite emails now include Apple-provided "Download on the App Store" and Google-provided "Get it on Google Play" badges next to the existing Join button; store URLs default to the official OLM listings and can be overridden via IOS_APP_STORE_URL / ANDROID_PLAY_STORE_URL env vars
- Powered by OLM badge renders on the auth carousel and the pre-org profile screen — single OLM binary, runtime gym rebrand, OLM always visible as the foundation
- Per-org native binary pipeline (apps/expo/scripts/generate-org-build-config.ts + OLM_ORG_SLUG-aware app.config.ts) is wired and tested but treated as an opt-in premium offering, not the default — the standard path is one OLM binary with runtime theming that switches as members join, leave, and re-join gyms
- Check-in gating is unchanged — waiver + active subscription / trial / comp / grandfathered is still required
ImprovementMay 19, 2026
OLM now lives at trainolm.dev
All app and email links — terms, privacy, help, support, contact, billing, deep links — now point at trainolm.dev. Existing mobile installs continue to work; the iOS Universal Link and Android App Link configs were updated to the new domain in the same change.
- User-facing URLs (privacy, terms, help, billing, sign-up, join, check-in) resolve at https://trainolm.dev
- Contact emails moved to @trainolm.dev (hello@, support@, privacy@, noreply@, unsubscribe@)
- JSON-LD organization metadata, OpenGraph cards, llms.txt, and middleware allowed-origins updated to the new domain
- Mobile bundle identifier (com.olm.app) is unchanged, so existing app installs and in-app purchases keep working
ImprovementMay 18, 2026
Mobile dashboard auto-focuses on your home gym
For members who train at multi-location gyms, the dashboard's Today's Classes now defaults to the location you actually train at — derived from your last 3 check-ins — instead of showing every location at once. The full schedule is still one tap away on the Check In tab.
- Majority of last 3 check-ins wins; on a 1/1/1 tie the most-recent location wins (so switching gyms shows up within a few sessions)
- Members with fewer than 3 check-ins still see all locations on the dashboard until a pattern emerges
- A subtle '<Location> · See all' caption above the list links to the full check-in screen so visiting members can see other locations' classes
FixMay 17, 2026
Mobile check-in works again for classes with a capacity cap
Checking in to any class that had a capacity set was failing with a database error. The capacity-locking query has been rewritten so members can check in normally.
- Today's classes also now show on the mobile dashboard reliably — the dashboard and check-in screens share a single timezone-aware "is this session over?" helper so they can no longer disagree
ImprovementMay 15, 2026
Home-page hero: branded iOS home-screen mockup
The first phone in the white-label hero fan now shows an iOS home screen with the gym's branded app icon installed among muted placeholder apps — re-labeled "01 · installed" — to make the real-download / white-label moment unmistakable.
- New `home` PhoneFrame variant: brand-color-glowing app icon with a "↓ just installed" badge sits in a 3×4 grid of muted placeholder apps + dock
- Left and right phones now lock to the same min-height so the side phones in the fan render at equal sizes
- Homepage wrapper now uses `overflow-x-clip` so the hero phone fan + marquee can't introduce horizontal page scroll
ImprovementMay 15, 2026
Private (invitation-only) billing plans
Admins can now mark a billing plan as Private when creating it. Private plans are hidden from the member-facing list and can only be subscribed to by members the admin has explicitly invited via the new "Private plan invite" picker on the member detail page. Useful for friends-and-family rates, scholarship tiers, founding-member deals, or any one-off arrangement.
- New Visibility selector on the create/edit plan dialog (Public — anyone can subscribe / Private — invite-only)
- Private plans show a Private badge in the Plans & Pricing table
- Member-facing plan list filters out private plans the member hasn't been invited to
- Self-serve subscribe endpoints reject private plans unless the member is on the invite list (assigned_plan_id)
ReleaseMay 14, 2026
Public BJJ knowledge surfaces: /techniques, /creators, /athletes
Three new DB-backed public pages — a library of 378 Brazilian Jiu-Jitsu techniques across 38 positions, a directory of 20 instructors and channels, and a directory of 211 notable competitors seeded from Wikidata + Wikipedia. Heavily cross-linked, ISR-revalidated daily, with schema.org HowTo / Person / BreadcrumbList markup for AI search and Google.
- Technique pages grouped by position with type chips (submission / sweep / pass / escape / transition / control / attack)
- Creator profiles with YouTube / Instagram / website links and the techniques they teach
- Athlete profiles with bios sourced from Wikipedia under CC BY-SA attribution + Wikidata structured data
- Sitemap now exposes ~720 URLs total; llms.txt and llms-full.txt advertise the new surfaces to AI crawlers
- Long-form technique content gated behind an editor-approved flag — drafts stay invisible until reviewed
ImprovementMay 9, 2026
/api/health endpoint for uptime monitors
Added `GET /api/health` returning DB + Redis probe results, commit SHA, and env. 200 when all deps respond, 503 if any required dep is degraded. Redis probe gracefully passes when Upstash isn't configured (dev / preview). No auth — pingable from UptimeRobot, Better Stack, Pingdom, etc.
ImprovementMay 9, 2026
E2E test coverage: invites, refunds, CSV exports, Stripe Connect entry
Added Playwright spec files for the invite-member, refund, CSV-export, and Stripe-Connect-onboarding admin flows. Specs are smoke-level (no real Stripe / mailbox dependencies) and skip gracefully when seed data isn't available. Covers OLM-17/18/19/20 manual-test checklist surfaces.
FixMay 8, 2026
Security hardening: drop-in track gate, verified email source, dev bypass double-lock
Three urgent security fixes — drop-in checkout now enforces the KIDS/ADULTS rank-track gate, ensureUser pulls the verified email from Clerk instead of trusting client input, and the dev OTP bypass requires both NODE_ENV !== production AND an explicit ALLOW_DEV_OTP_BYPASS=1 opt-in.
FixMay 8, 2026
Find-a-gym: hide drop-in price when no class is scheduled
Find-a-gym list now only advertises a drop-in price for gyms that actually have an upcoming SCHEDULED class. Gyms with drop-ins enabled but no class on the books no longer show a price for a class that doesn't exist.
FixMay 7, 2026
Drop-in availability: late-night classes + transient errors
Two fixes to the drop-in eligibility logic introduced earlier today, plus removal of the now-unused requiresDropInApproval setting.
- Fix: classes that cross midnight (e.g. 22:00–01:00) no longer drop out of drop-in eligibility the moment they begin. prepareDropIn now treats endTime ≤ startTime as +1 day in the org's local timezone.
- Fix: 'No class today' badge on the gym card / map sheet no longer renders during a transient API error or before the query has run — only when the schedule has been confirmed empty.
- Schema: removed organizations.requiresDropInApproval (column + admin toggle). Drop-ins are pure self-service now; the field had no remaining server-side enforcement after the join-request consolidation.
ImprovementMay 7, 2026
Join requests simplified: drop-ins are now self-service only
Removed the duplicate 'Drop in for a class' option from the Join Request dialog. Drop-ins are paid self-service via the gym card's Drop-in button; the dialog is now exclusively for full-membership requests. Message field is also now optional.
- Mobile JoinRequestDialog: dropped the intent picker entirely. The dialog now always sends a FULL_MEMBER request and explains that visitors should use the 'Drop in' button to walk in for one class.
- Server: joinRequest.request no longer accepts an intent input — always inserts FULL_MEMBER. Existing DROP_IN rows preserved for history.
- Message field is now optional (no min length). Footer reads 'X/1000 · optional', label is 'Message to the gym (optional)', Send button enables regardless of message length.
ImprovementMay 7, 2026
Drop-in: today-only availability + 'No class today' state
The Drop-in CTA now surfaces only when there's actually a SCHEDULED class today in the gym's local timezone. When a gym allows drop-ins but has nothing on the schedule, the gym card and join sheet show a clear 'No class today' state instead of a dead-end Drop-in button.
- API: prepareDropIn filters cs.date == today-in-org-tz and skips sessions whose end time has passed (was returning any future session).
- Mobile join sheet: 3-state copy — 'Not accepting drop-ins' / 'No class scheduled today — try again tomorrow.' / available with fee.
- Find-a-gym list + map: 'No class today' badge replaces the Drop-in $X pill when the org allows drop-ins but no session is on.
ReleaseMay 6, 2026
Drop-ins: ticket model + admin payments page + auto-refunds
Drop-ins now work like event tickets — each paid drop-in is a ticket with three states (Upcoming, Past, Refunded). Admins get a dedicated payments page; members get a 'My visits' screen across both pre-org and in-org contexts.
- New admin page: /billing/drop-ins. KPI cards (revenue, count, refunded, avg fee), Frequent Droppers panel surfacing 3+ paid visits as sales leads, status + date filters, refund action with internal-note dialog, CSV export.
- New mobile screen: 'My visits' on the More tab. Segmented Upcoming / Past / Refunded tabs, ticket cards with org logo + class details, tap-through detail view with Get Directions and Cancel & Refund (>24h policy).
- Server-side webhook finalize: drop-in tickets are now written when Stripe confirms payment, not when the user re-enters the app. Closing the app after checkout no longer loses a paid ticket.
- Auto-refunds when a session is cancelled or capacity-full at finalize time — Stripe refund + ticket flagged with reason, no manual admin work.
- Post-class push notifications: journal prompt at +1h, conversion prompt at +24h (suppressed if user has already joined or was refunded).
- Refund visibility: refunded tickets stay in history with the reason ('Class cancelled', 'Cancelled by you', etc.) instead of vanishing.
ImprovementMay 4, 2026
Curriculum: coverage radar + preset library
Two more layers on /curriculum — a balance-view radar chart (filterable 1/3/6/12 month windows) and a preset library that scaffolds weeks/months from canonical BJJ curricula.
- Coverage radar: complementary view to the heatmap. One axis per category in your discipline's taxonomy; shape shows balance at a glance — a balanced gym fills the whole web, a guard-heavy gym spikes on one side.
- Filter window: Last month / Last 3 months / Last 6 months / Last 12 months, defaulting to last month so it's meaningful from week one.
- Preset picker in the focus dialog: pick from "Fundamentals of Self-Defense (White → Blue)" (81 techniques) or "Blue → Purple (Pedro Sauer)" (88 techniques). Clicking a category fills title + appends technique bullets to the description + auto-tags coverage categories.
- Renders gracefully for brand-new gyms — no "need 4 weeks" gate; shape stabilizes as data accrues.
ReleaseMay 4, 2026
Curriculum: weekly calendar + coverage heatmap
Curriculum got a real spine — a Mon–Sun weekly calendar with drag-to-reschedule, plus a coverage heatmap that tells you what you've been ignoring.
- Weekly calendar on /curriculum showing the past 4 weeks + this week + next 8 weeks. Click any week to set or edit its focus; drag a filled week onto another to move or swap.
- Per-discipline category taxonomy (BJJ, judo, striking, wrestling, MMA, generic) lets you tag months and weeks with what you covered.
- Coverage heatmap: 12-week × N-category grid showing what's been hit and what's been ignored. Pulls from both weekly tags and the parent month's tags.
- Gap report flags the top 3 categories with zero coverage in the last 12 weeks ('No takedowns since Feb 16').
- Mobile members get a new 'This Week' card and a 'Coming Up' list of the next 4 weeks on the curriculum screen.
ReleaseMay 4, 2026
Curriculum: monthly focus for every gym
A simple, opinionated v1: one paragraph per month telling members what your gym is working on. Available to every org out of the box.
- Admin portal — new /curriculum page in the sidebar. Set this month's focus, plan next month, see past months.
- Mobile dashboard — shows a 'Working on…' card with the current month's title + description. Tap through to a dedicated screen with full history.
- Designed around how gyms actually behave: coaches set an intent and partially adhere. The feature embraces drift instead of nagging coaches with a checklist.
- Intentionally minimal — no per-class lessons, no technique-library wiring, no coverage heatmap yet. Those land only if real gyms ask for them after using v1.
ImprovementMay 3, 2026
Compare pages — added 'Demand pipeline' row + sharpened white-label copy
Every /compare page now calls out the consumer-facing OLM app as a discovery channel for gyms, and clarifies what 'true white-label' actually means (members never see OLM branding).
- Added a 'Demand pipeline / member discovery' comparison row to all 5 competitor pages.
- Tightened the white-label rows so it's clear the member-installed app shows your colors and logo only.
ImprovementMay 3, 2026
Added /compare pages for Wodify and BJJLink
Two new competitor breakdowns — Wodify (per-location SaaS with HYROX/Two-Brain co-marketing) and BJJLink (BJJ-only flat-rate, closest philosophical competitor). Honest tradeoffs included.
- /compare/wodify — leads with per-location pricing + add-on creep critique; acknowledges Wodify wins on programming/WOD tools, reporting depth, and HYROX/Two-Brain ecosystem.
- /compare/bjjlink — leads with multi-discipline rank portability and Stripe Connect ownership; acknowledges BJJLink CORAL is genuinely cheaper at scale and has a curriculum builder OLM doesn't yet.
- Both pages auto-flow into the existing /compare index, OG image generator, and 'Other comparisons' carousel — no template changes needed.
- Background research lives in plan/competitors.md.