CASE STUDY 03 · NOMAD COMMUNITY

A week of organising, done by the system.

A beach sports community Rikki started with four people. We built the two sites that run its week.

COMMUNITY
Nomad, Florianópolis, since October 2023
ROLE
Founder and organiser; design, build and running
BUILT ON
Astro, Cloudflare and Firebase, in one repository
900MEMBERS ON WHATSAPP
165PEOPLE HAVE PLAYED A SUNDAY
4COURTS EVERY SUNDAY
The Nomad Volleyball group on the sand at Arena Beach, with the two Nomad flags
Nomad Volleyball at Arena BeachPHOTO · THE NOMADFLORIPA.COM HOME PAGE
01 · BEFORE

Every week, the same three things went wrong.

Sunday ran on a list pasted into the WhatsApp group. Keeping it straight took the organiser’s whole week.

  1. 01

    The list broke

    Names deleted, two copies going round, someone pasting an old one.

    NOW · ONE LIST; YOU ADD OR REMOVE ONLY YOUR OWN NAME
  2. 02

    People played without paying

    A name on the list was no promise of a payment.

    NOW · A PLACE IS YOURS ONCE IT IS PAID; IF NOT, THE NEXT IN LINE GETS IT
  3. 03

    The week went on chasing receipts

    The organiser spent the week asking for comprovantes, the Pix receipts.

    NOW · THE RECEIPT GOES IN WITH THE PAYMENT; THE TICK APPEARS ON ITS OWN
The group post “One list. Every name paid.”: the Sunday list on the site, every place on Court 1 ticked as paid, demo names
The post that moved Sunday to the siteWHATSAPP GROUP · OCTOBER 2026 · DEMO NAMES
02 · HOW IT WORKS

Sign up, pay, play.

Real screens from nomadfloripa.com in its demo mode, so every name is invented. Pick a step.

THE PLAYER · THE ORGANISER

Six screens that replaced a week of messages.

nomadfloripa.com · demo data
The Sunday volleyball screen, demo data: three courts full and paid, seven waiting, training and the BBQFull? The next in line moves up on their own
The Wednesday football screen, demo data: 13 of 15 players, keepers, monthly players, pay-per-play and the waiting listMonthly players first, then the place opens
The football team builder, demo data: three balanced teams from players’ ratings and positionsBalance in one tap
The organiser’s Today screen, demo data: payments to approve, who owes, what needs an answerOnly what needs the organiser
The money screen, demo data: paid in, paid out, what each game made, who still owesWhat each game made
A tournament in the organiser’s console, demo data: 8 teams in 2 pools, 3 of 12 games played on 2 courts, the scores and the pool tableThe bracket builds itself

Sign up for the game, training and the BBQ in one go, pay by Pix and add the receipt. When a place frees up, the next in line is offered it without anyone lifting a finger.

Wednesday at 21:00. Monthly players confirm first; a place they leave opens to pay-per-play and the waiting list, and a kick-off alarm reaches every phone that asked for one.

Teams balanced from players’ ratings of each other and their positions, in one tap instead of twenty minutes of arguing.

One screen each morning: receipts to approve, who still owes, a court to ask the arena for. Everything else is already on track.

One transfer can’t pay twice. Paid in, paid out, what each game made and who still owes, worked out from the lists themselves.

Captains enter a team with an invite link. Pools and the schedule come out of one engine, players report scores from the sand on nomadvolleyball.com, and the bracket builds itself from the last pool score.

03 · THE WEEK

The week, run by the system.

Every five minutes the server reads every open list and does what is due.

  1. SUN
    • Volleyball08:00 an email to every paid place · 10:00 training · 11:00 the game at Arena Beach
    • FootballThe Wednesday list goes up, monthly players first
  2. MON
    • FootballMonthly players confirm, or their place opens to the waiting list
  3. WED
    • Football21:00 kick-off, with an alarm on every phone that asked; teams balanced in one tap
  4. SAT
    • Volleyball12:00 the BBQ is on with ten sign-ups, or called off with nothing to refund
  5. AFTER EACH GAME
    • PlayersShout-outs, the MVP vote, ratings, the trainer’s notes and points
THE SERVER · EVERY FIVE MINUTESReads every open list, then does what is due.
  • Offers a freed place to the next in line
  • Counts the BBQ against its minimum
  • Sends the deadline, reminder and morning emails
  • Rings the kick-off alarm
A Nomad week, as the system runs itFROM THE LIVE SITE AND THE SERVER’S SCHEDULED JOB
04 · WHAT WE BUILT

Two sites, one account, one system.

Both sites live in one repository and share one sign-in, one database and one set of server functions.

01
NOMADFLORIPA.COM

The community’s app

  • Volleyball, football and poolLISTS, PAYMENT, WAITING LIST, BBQ
  • Your area: games, points, friends21 POINTS = A FREE SUNDAY
  • The organiser’s consoleTODAY, MONEY, PEOPLE, TOURNAMENTS
02
NOMADVOLLEYBALL.COM

The tournament engine

  • Team entries with an invite linkAPPROVED BY THE ORGANISER
  • Pools, schedule and bracketONE ENGINE FOR APP, CONSOLE, SERVER
  • Scores from the players’ phonesTHE SERVER’S COPY ALWAYS WINS
03
BEHIND BOTH

One system

  • Astro on Cloudflare, Firebase behind28 CLOUD FUNCTIONS
  • English and Brazilian PortugueseBY THE READER’S DEVICE
  • Names never on public pagesLGPD · COUNTS ONLY FOR VISITORS
nomadfloripa.com · demo data
The nomadfloripa.com home page for a signed-in member, demo data: a welcome by name, Sunday’s courts filling, points towards a free game, and football, pool and their own area beside it
The home page, signed inSUNDAY’S COURTS, YOUR POINTS, WHAT IS NEXT
nomad · a tournament · demo data
A tournament running, demo data: eight teams in two pools, three of twelve games played, the scores in order and each pool’s table
A tournament, runningSCORES AND TABLES · MEMBERS WATCH LIVE ON NOMADVOLLEYBALL.COM
05 · THE DECISIONS

Four choices, and what each one cost.

One organiser runs it on a Sunday morning. Each choice keeps that possible.

Four decisions, what was chosen and what it cost later
ChoiceWHAT WE CHOSEWHAT IT COST LATER
01One stack for both sitesSEPTEMBER 2026AstroCloudflareFirebaseTwo sites in one repository, with one sign-in and one database: Astro on Cloudflare, Firebase behind. Next.js meant a full rewrite, and a server of our own meant operations and data-protection duties resting on one person.Sign-in broke on iPhones. The sign-in helper lived on another domain, and Safari walls that storage off. It now passes through each site’s own Cloudflare worker.
02Receipts, not a payment providerSEPTEMBER 2026PixMembers pay by Pix and upload the receipt. Automatic payment waits on a question for the accountant, so for now the system checks receipts.One transfer once arrived as three claims with the same screenshot. Each file’s fingerprint is now stored, so one receipt cannot pay twice. A cropped copy gets a new fingerprint, so the organiser still looks at every receipt.
03One job every five minutesSEPTEMBER 2026One scheduled job reads every open list and does what is due: offers, deadlines, alarms. It began on Wednesday evenings only and grew to every five minutes, every day.Each run reads every open list in full, and nothing happens more exactly than five minutes.
04The server holds the rulesSEPTEMBER 2026FirebaseAnything that touches money or a place on a list runs in a server function, never in the browser. There are 28.On 26 September a release of 34 functions hit the region’s processor limit and 11 failed to deploy. Each function now takes half a processor and one request at a time, so a busy tournament day can queue.
06 · UNDER THE HOOD

One request, one record, every event.

Three real pieces, plain words first and then the code. Demo names; keys and tokens left out.

A · AN API REQUEST, READ END TO END

Every tap goes to one server function.

Both sites send what people do to a single Cloud Function, a few events at a time. The browser posts plain text with sendBeacon, so a batch still leaves when the page closes.

There is no API key in the request: visitors send too, so there is no account to check. Instead the function checks where the request came from, its shape, its size and how often that device sends. A signed-in member’s batch also carries their Firebase ID token, which the server verifies; the user id written in the body is never trusted.

  1. 1The endpoint: one function, POST only, no query string.
  2. 2Who and where: a random session and device id, never a name.
  3. 3What happened: a page, a screen, its timings, a tap.
REQUESTCAPTURED FROM A LOCAL BUILD · DEMO · TRIMMED
POST http://127.0.0.1:5001/demo-nomad/southamerica-east1/log   1
// live: https://southamerica-east1-nomad-community.cloudfunctions.net/log
Content-Type: text/plain;charset=UTF-8

{
  "v": 1,
  "sid": "66f1…8334", "did": "37d8…b3b3",                  2
  "app": "floripa", "n": 1, "demo": true,
  "uid": null, "admin": false,     // a member's batch adds "token"
  "meta": { "ua": "Chrome · Mac · desktop", "tz": "America/Sao_Paulo", … },
  "events": [                                                3
    { "t": "page",   "p": "/volleyball?demo=1", "conn": "4g" },
    { "t": "screen", "s": "volleyball" },
    { "t": "perf",   "ttfb": 7, "lcp": 56, "load": 74 },
    { "t": "click",  "el": { "tag": "a", "text": "Football", "href": "/football" } }
  ]
}
RESPONSESTHE LIVE FUNCTION · 11 NOV 2026
  • 204Preflight from nomadfloripa.com: access-control-allow-origin: https://nomadfloripa.com, POST only, cached for a day
  • 403The same POST from another site: refused, nothing stored
  • 405A GET: only POST is answered
  • 204A good batch: stored, no body sent back · from the code
  • 413 · 429Over 64 KB, or over 40 batches a minute from one device · from the code
B · HOW THE CATALOGUE IS STRUCTURED

A game is a template, its parts and their prices.

Every week’s game is made from a template: Rikki’s real WhatsApp list, turned into data. The sport, the venue and the day are the categories. The court place, the training and the BBQ are the items a player signs up for, and each has its own price for a regular, a newer player or a guest.

A sign-up is one record per part, so a court place, a training and a BBQ are paid, waited for and cancelled separately. Each record is in one of four states: claimed, confirmed, unpaid or cancelled.

  1. 1Categories: the sport, the venue, the day.
  2. 2Items: the court place, the training, the BBQ.
  3. 3Variations: the same item at three prices, by who is playing.
THE SUNDAY TEMPLATETEMPLATES.JS · TRIMMED
export const VOLLEYBALL_SUNDAY = {
  key: 'volleyball-sunday',
  sport: 'volleyball', venue: 'Arena Beach',               1
  day: 'Sunday', time: '11:00 – 13:00',
  courts: 3, courtSize: COURT_SIZE,   // 4 v 4, every court filled to 8
  prices: {                                                 2
    court:  { legend: 27, member: 30, guest: 32 },             3
    clinic: { legend: 15, member: 20, guest: 20 },
    bbq:    { legend: 45, member: 50, guest: 50 },
  },
  clinic: { capacity: 10, courts: 1, level: 'intermediate' },
  bbq: { min: BBQ_MIN },   // on only once ten sign up
};
C · EVENT TRACKING

What is recorded, where it goes, who reads it.

The log records the address, the screen in view and every tap: what a button says, never what anyone types. It also catches the trouble: script errors, a request that failed or took over four seconds, and frustration read from the taps, such as three taps in 600 ms on one spot, or a tap that changed nothing.

Nine goals mark the moments that count, each with a fixed id in the code, never read off a button’s words. Google Analytics hears them only after a visitor says yes. Batches are stored by the session for 13 months; every night they are summed into counts that are kept. Only the organiser’s console reads them, on its Logs and Growth pages; the public pages show counts, never names.

  1. 1Each goal carries one word that splits it, such as the sport.
  2. 2An unknown id is refused, so a typo fails a test, not the numbers.
THE GOALSSHARED/PUBLIC/JS/GOAL.JS
export const GOALS = {                                    1
  whatsapp_opened:   'group',    // a WhatsApp group link tapped
  signup_started:    'intent',
  account_created:   'method',   // password · google
  profile_completed: 'sports',
  place_taken:       'sport',
  paid:              'sport',    // a comprovante or credit
  share_tapped:      'what',
  invite_copied:     'sport',
  referral:          'sport',
};
export function goal(id, props = {}, win = globalThis.window) {
  if (!Object.hasOwn(GOALS, id)) return null;             2
  …
}
RECORDED

Pages, screens, taps, sheets, errors, slow or failed requests, frustration, goals

STORED

sessions/{sid}, its batches/{n} and issues/{key}; 13 months, then counts only

NEVER

Anything typed, emails, phone numbers, passwords, IP addresses

07 · TWO FIXES

Two bugs, found and fixed.

One found in the browser’s network timings, one by reading what a public record held.

D · DEBUGGING IN THE BROWSER

The page that froze on iPhones.

Rikki, on the site saved to his iPhone’s Home Screen: “when i click on events … it freezes and wont change”. The address changed; the screen didn’t.

Reproduced in Apple’s WebKit engine and each read timed: the database’s streaming connection stalled there, and a read hung 6 to 30 seconds at random, against about 50 ms in Chrome. The cache made no difference, so the fault was the connection. The fix switches Apple’s WebKit to long polling and leaves every other browser alone: every read then answered in 0.1 to 0.9 seconds.

Found on the way: the site’s own log had dropped the opening batches of every page after the first, because each page counted from 1 again and the server skips a number it already has. That is why the freezes left no trace. The count now runs across the whole visit.

  1. 1Apple’s WebKit is every browser on an iPhone, the Home Screen app included.
  2. 2There: long polling, and a cache per tab.
  3. 3Elsewhere: the stream and the shared cache, unchanged.
THE FIXSHARED/PUBLIC/JS/FIREBASE.JS · WRAPPED
export const isAppleWebKit = (nav = globalThis.navigator) =>
  /^Apple/.test(String(nav?.vendor || ''));                 1

export const firestoreSettings = (F, nav) => (isAppleWebKit(nav)
  ? { localCache: F.memoryLocalCache(), experimentalForceLongPolling: true }   2
  : { localCache: F.persistentLocalCache({                  3
      tabManager: F.persistentMultipleTabManager() }) });
BEFORE · WEBKIT

6–30 s a read, at random

AFTER · WEBKIT

0.1–0.9 s every read

E · A SMALL SNIPPET, A JSON MAPPING

Ratings off the public record, in one line.

Making the football teams saved every player’s rating on the game, and anyone can read a game, visitors included. The privacy page promised that only the organiser sees ratings. Nothing live had leaked: the one night on the site held no rating when checked.

The fix is one mapping, run before a game is saved: copy each team without its rating total, and each player without their rating. The ratings now sit in a separate record kept for that night’s players.

  1. 1Take the team’s rating out; keep the rest as t.
  2. 2Do the same for every player.
  3. 3Real output, the demo’s Team B: the same team, minus six numbers.
THE MAPPINGFOOTBALL-TEAMS.JS
export const publicTeams = (teams) => (teams || [])
  .map(({ rating: _sum, ...t }) => ({ ...t,                       1
    players: (t.players || []).map(({ rating: _r, ...p }) => p) }));   2
IN, THEN OUTRUN ON THE DEMO DATA · TRIMMED
// balanceTeams(…).teams[1]                                  3
{ "name": "Team B", "rating": 15, "players": [
  { "name": "Yuki R.",  "lane": "midfield", "rating": 3 },
  { "name": "Emma S.",  "lane": "defence",  "rating": 3 }, … ] }

// publicTeams(…)[1], what the game keeps
{ "name": "Team B", "players": [
  { "name": "Yuki R.",  "lane": "midfield" },
  { "name": "Emma S.",  "lane": "defence" }, … ] }
08 · RESULTS

From four people at a game to four courts a week.

Every figure is from the community’s own records; the source sits under each one.

900MEMBERS IN THE WHATSAPP COMMUNITYSOURCE · THE WHATSAPP COMMUNITY
165PEOPLE HAVE PLAYED A SUNDAYSOURCE · THE SUNDAYS-PLAYED LIST, OCTOBER 2026
109ACCOUNTS IN THE SITE’S FIRST FIVE WEEKSSOURCE · THE SITE’S DATABASE, WITH 283 GAME SIGN-UPS
3YEARS OF SUNDAYS, OCTOBER 2026SOURCE · THE ANNIVERSARY ANNOUNCEMENT
  • PROOF · NOMAD · 3,041 COMMITS SINCE 15 APRIL 2026
  • PROOF · NOMAD · 28 SERVER FUNCTIONS, ONE FIVE-MINUTE SCHEDULE
  • PROOF · NOMAD · LIGHTHOUSE 100 IN ALL FOUR SCORES ON HOME, VOLLEYBALL AND FOOTBALL (LOCAL RUN, OCTOBER 2026)
09 · WHO IS BEHIND IT

Run by the people who play.

Rikki founded Nomad Volleyball and still organises it. ArtiFxn Design built both sites, with Rikki directing our AI agents.

The site ships near-daily, and many changes start with the players. After each Sunday they leave notes; a member survey wrote the tournament rules. What members say becomes the next release.

  1. 50% of people showed up late.BUILT · THE SUNDAY MORNING EMAIL, 1 NOV 2026
  2. A ‘Could improve’ section to send to the players privately.BUILT · ONE PRIVATE NOTE, ONLY THE PLAYER SEES IT
  3. A BBQ called off for low turnout after people had paid.

    BUILT · PLEDGES FIRST, MONEY ONLY AT TEN
10 · WHAT IS NEXT

What comes next.

The system grows the way it started: one problem from the group at a time.

BUILT · NOVEMBER 2026

The Christmas tournament

Its own optional BBQ at its own price, run by the same rules as the Sunday one.

PLANNED

Messages from the system

WhatsApp messages sent one to one through the official business API, not pasted by hand.

PLANNED

The console in Portuguese

The public pages already switch to Brazilian Portuguese; the organiser’s console and the emails come next.

NEXT CASE KNG Estate and GOV Roleplay

Read the case
11 · CONTACT

Two doors. Pick yours.

Whether you are hiring or building, the first step is the same: one conversation with Rikki Chandler.

DOOR 1 · FOR HIRING TEAMSHiring for integration, solutions or implementation?

Rikki connects customers to systems, fixes what breaks and explains it so it sticks.

  • Technical integration and onboarding
  • Solutions and implementation
  • Customer engineering and support
Read the CVThe full story
DOOR 2 · FOR CLIENTSA business that should learn from its own data?

Tell us the one thing that should change. We start from that question and a written test of how we will know.

  • AI systems that run your operations
  • Product search and recommendations
  • Design, film and training for your team
Start a projectSee the work
STUDIO
ArtiFxn Design
FOUNDER
Rikki Chandler
BASED
Florianópolis, Brazil
TIME
UTC−3, working with teams across time zones

Every door leads to the same person.