Skip to main contentSkip to footer
    Back to Case Studies
    The CornerFighter Cut for gyms and coaches
    Web AppiOS AppIn-House Product

    The Corner

    Every fighter. Every gym. One glance. The coaching side of Fighter Cut: every fighter's last weigh-in, the week's load, what they ate and the work you set them, shown only because the fighter flipped a switch.

    Project Preview

    Live website preview - corner.fightercut.com

    3
    Event types a coach can see: weigh-in, session, assignment done
    2
    Separate consent switches a fighter holds: progress, and nutrition
    0
    Meal photos or fighter notes that ever reach a coach, by schema
    8 days
    From first commit to a live web app and TestFlight

    Roster

    Every fighter at a glance: last weigh-in and the change across the week, sessions, minutes on the mat, roadwork, and assignments done against assigned.

    Assigned work

    Set work for the roster with a due date and watch completion counts climb without chasing anyone. Per-fighter assignments are modelled in the schema.

    Invite codes

    Mint a short team code. A fighter types it into their own app and lands on the roster in seconds.

    Claim, create or join

    Claim a gym a fighter already named, create a new one, or join an existing gym as staff. Claiming comes first, because a duplicate gym orphans everyone who was waiting.

    Staff roles

    Owner and coach are distinct roles in the database. Staff codes bring coaches in; only the owner can remove them or spend the gym's founding spot.

    Two switches, not one

    Progress sharing sends weigh-ins, sessions and completed assignments. A second, separate switch sends a fortnight of nutrition: daily calories and macros, the meals and the recipes behind them. Meal photos and the fighter's own notes never leave their phone. Either switch off deletes what it shared.

    The problem

    A fight coach with a dozen athletes in camp spends Sunday evening texting. How is the weight? Did you run? Did you do the work I set? The answers come back late, partial and in twelve different formats, and by the time a problem shows up on the scale it is a week old. The fighter, meanwhile, is already logging all of it in Fighter Cut, the athlete app we built to plan a safe weight cut. The data existed. It just had no window for the person in the corner.

    The obvious answer is a coach login to the athlete app. We rejected it. What a fighter eats is private in a way a weigh-in is not, and once a coach can see everything a fighter can, the athlete app's whole pitch, that this is your data and nobody else's, is gone. So The Corner is a separate product, in a separate repository, that can only see what a fighter chooses to send it, one switch at a time. The companion-app pattern behind that decision is written up separately.

    The rules we wrote before the first screen

    • Nothing from the athlete app: No cut planning, no food logging, no macro maths, no onboarding questionnaire. A feature request that sounds like one of those is aimed at the wrong app.
    • The fighter holds the switch: Sharing is opt-in from the fighter's side and revocable with one toggle, which deletes the shared stream.
    • Food detail needs its own switch: Progress and nutrition are separate consents. The first version shared no food at all; since 2 September 2026 a fighter can additionally share daily totals, meals and recipes. Photos and their own notes never cross, under any switch.
    • Server-owned, not local-first: The athlete app works offline on IndexedDB. A roster cannot, and pretending otherwise would let a stale cache impersonate a live team.

    What we built

    Five screens, one question

    Every screen answers "how is my fight team doing?" for someone who is not the fighter. Signed out, a coach sees a marketing page with a live count of founding spots and a sign-in. Signed in without a gym, they land in one-time setup. After that, four tabs.

    Roster: last weigh-in and weekly change, sessions, minutes, roadwork and assignment completion for every fighter attached to the club
    Fighter detail: recent weigh-ins, the week's load, what they still owe you and, if they turned it on, a fortnight of nutrition, day by day, with each day's weigh-in beside its totals and the sessions that made them
    Assigned: create work for the whole roster with a due date and see how many have finished it
    Invite: mint a team code fighters type into their own app
    Settings: gym details, coaching staff (invite and remove), the founding-gym code, sign out
    Password reset and signup confirmation pages that live outside the app shell, because both arrive carrying a valid session

    Getting a gym

    1. Claim: When a fighter names a gym in their app and nothing matches, the server creates an unclaimed club and files the request against it. The Corner's setup search finds those clubs, shows how many fighters are waiting, and hands the claimant ownership with every waiting fighter attached.
    2. Create: A new club, with the creator inserted as its owner in the same step. A club whose creator is not a member looks like success and then reads back empty forever, so that insert failing is surfaced, not swallowed.
    3. Join with a staff code: Redeems an owner's invite and joins as a coach, not an owner. Coaches see the roster; they cannot remove staff or spend the founding spot.

    How it works

    Shared backend, split ownership

    The Corner has no SQL of its own. Every table and function it calls, clubs, org members, fighter profiles, progress events, nutrition days, assignments, roster invites, staff invites, join requests and the founding-code pieces, is a migration in the athlete repository. A schema change for a coach feature is written over there and consumed here. Several of those functions have exactly one caller, this app, by design.

    Consent as a data shape

    What reaches a coach is a stream of progress events, three types only: weigh-in, session and assignment done, for fighters whose profile is attached to the club. Nutrition arrives through a second table whose row-level policy demands three things at once: the fighter opted in, the fighter is on this roster, and the reader is staff there. The client checks no consent flag of its own; a stale build can skip a client-side check and cannot skip the database. Three empty states are told apart on screen, because "no food logged" said about a fighter who was never asked to share would be wrong twice.

    Row-level security that does not recurse

    Membership checks run through SECURITY DEFINER helpers rather than inline subqueries against the membership table. The inline form produced a genuine infinite-recursion error in Postgres once; the helpers are now a rule, and a source-grep test keeps the fix from being reverted.

    Founding gyms, enforced in one place

    The first five gyms to redeem a founding code get the Team plan free, permanently. The remaining count is a live database read, the redemption is a single atomic function, and the client cannot fake it. The redeem function checks membership, not ownership, so the UI gates the offer to owner paths.

    Timeline

    1. 17 August 2026: Clubs and fighter profiles land in the athlete app's schema.
    2. 23 August 2026: Org dashboard migration: members, progress events, assignments and roster invites. The Corner repo starts the next day.
    3. 25 August 2026: Emailed links fixed after both were silently landing coaches in the athlete app.
    4. 30 August to 1 September 2026: Founding codes, club claiming, staff management and sub-admin migrations. corner.fightercut.com live, build 3 on TestFlight with the bot check, public join link on the landing page.
    5. 2 to 4 September 2026: Nutrition sharing lands: daily totals first, then meals and recipes behind an expanding day row. Fighters shown by public name.
    6. 7 September 2026: Landing redesign: three photographs, a shorter centred hero, and the descenders the animation had been clipping off the headline.

    Problems worth talking about

    Coaches waking up in the wrong app

    Supabase does not reject an unlisted redirect target; it silently substitutes the project's site URL, which is the athlete app. Both of The Corner's emailed links did exactly that: a coach clicked confirm and landed in a fighter's onboarding questionnaire holding a valid session, with no error anywhere. Both links now build from one origin helper, and a new link type means checking the allow-list first.

    A deploy that says yes and does nothing

    Deploying from a worktree or detached HEAD makes the Pages CLI infer the branch, ship a preview, and print success while production is untouched. The rule now is an explicit branch flag and verifying the live bundle by fetching the chunk and grepping for what was shipped.

    Duplicates that orphan fighters

    Creating a gym under a new slug when a fighter already named it leaves those fighters attached to an unclaimed row nobody will ever own. Setup searches unclaimed clubs first and shows the waiting count, so claiming is the path of least resistance.

    Tests without a DOM

    The test runner deliberately has no DOM. The convention is source-grep: read a file's text and assert the wiring is still there, after stripping comments, because these files explain a fix by naming the construct it replaced and a naive match passes on a reverted file.

    Where it stands

    Beta, reported honestly

    The Corner is live on the web and in TestFlight as Fight Corner, alongside the Fighter Cut athlete app, with public release gated to October 2026. The numbers on this page are counts from the code and schema: event types, consent switches, roles, dates from the commit and migration history. Known gaps are documented too: no in-app billing beyond the founding code, no referral capture, and per-fighter assignments modelled but not yet in the UI. We do not publish gyms, coaches or fighters on a roster for a product that has not launched.

    Meal photos and the fighter's own notes stay on their phone. This app never receives them. Everything here is shared by their choice and disappears the moment they switch it off.

    — The Corner, fighter detail screen

    What This Project Does Well

    Two apps, one Supabase project, one auth table: coaches and fighters sign in against the same store through different screens
    Coaches see fighters by a generated public name, Chris M. rather than a legal name, selected by every roster and detail query and guarded by a test that fails if the full-name column is ever asked for
    A Cloudflare Turnstile check sits in front of every auth call, and when it declines silently, on a VPN or a shared gym network, the sign-in button says why and offers a retry rather than failing like a wrong password
    Not local-first, on purpose: a roster is server-owned, so every screen is a live read and there is no persistence layer to lie with
    Demo data appears only when there are no backend keys at all, never on a failed request, so an outage can never look like a working roster
    Access control lives in Postgres row-level security through SECURITY DEFINER helpers, after an inline membership check caused a real infinite-recursion failure
    The founding-gym programme is atomic in a single database function; the client call is the whole story and the code is never printed on the landing page
    No chart or component libraries: hand-built SVG and a small set of in-house primitives, sharing the athlete app's paper-and-ink design language: Barlow Condensed display type, a cream paper ground, near-black ink and a single blood-red accent
    The September 2026 landing redesign is photo-led: a coach in the corner behind the headline, a phone in a fighter's hand between rounds, a gym-scale weigh-in beside the benefits, with the copy centred in a shorter hero instead of pinned under an empty frame
    Capacitor 8 on Swift Package Manager, its own bundle id under the Fighter Cut root, so both apps sit on one Apple Developer team

    Technologies Used

    React 19, Vite, TypeScript, Tailwind CSS 4, Zustand, Framer Motion springs, hand-built SVG, Supabase (Postgres, Auth, row-level security, SQL functions) shared with the Fighter Cut athlete app, Capacitor 8 for iOS with Swift Package Manager, Vitest, Cloudflare Pages, App Store Connect

    Ready to Build Something Similar?

    Let's discuss how we can help transform your business with a custom digital solution.