A practice ground for the hard conversations charge nurses have to have.
Charge nurses are promoted on clinical skill, then immediately expected to handle high-stakes people conversations they’ve never been trained for — delivering performance feedback, pushing back on unsafe staffing decisions, setting boundaries with physicians, de-escalating mid-shift conflict. These conversations directly affect patient safety, team retention, and unit culture, yet most charge nurses report feeling unprepared and anxious about them. Existing training options are infrequent, generic, and lack the realistic practice that builds genuine confidence.
Free users get 3 sessions. After that: subscribe for unlimited access, or watch a rewarded ad for one bonus session.
| Category | What to look at |
|---|---|
| Next Gen Award | This entire repo — built by a student from a locked spec with an explicit anti-hallucination protocol, plus the six spec documents below |
| HAMM (freemium → subscription) | lib/revenuecat.ts — FREE_SESSION_LIMIT, entitlement checks, Paywalls v2 presentation; app/paywall.tsx |
| Catvertising (rewarded ads) | components/RewardedAdModal.tsx — user-initiated rewarded video; grantBonusSession() for the unlock logic |
| OneSignal | lib/onesignal.ts — SDK init, permission flow, schedule management; app/(tabs)/settings.tsx — notification time picker |
This project was built against a locked specification with an explicit anti-hallucination protocol — a structured set of rules ensuring that every implementation decision traces back to one of six spec documents, and contradictions are flagged as spec bugs rather than resolved by assumption. No package names are guessed, no screen content is invented, no prompt wording is paraphrased.
Two examples from the real build, not hypotheticals:
The persistence bug. Supabase’s JS client defaults to an in-memory auth adapter on React Native — supportsLocalStorage() returns false, but the client silently continues rather than erroring. The result: every app restart created a brand-new anonymous user, orphaning all prior session history behind unreachable user_id-scoped RLS rows. This was traced to the root cause (a missing AsyncStorage adapter in createClient()), reproduced on-device, and fixed with an explicit storage: AsyncStorage configuration — not assumed away as “probably a caching issue.”
The phantom placeholder. During live testing, the AI persona Pat opened with: “Look, [Trainee Name], I’m staring at a total disaster on 4th floor…” — a literal unfilled template slot rendered to the user. No prompt contained [Trainee Name]; the persona prompts refer to “the charge nurse (the trainee)” but never specified a form of address, so the LLM invented a placeholder token. Fixed at the source in 04-SCENARIO-PROMPTS.md with an explicit instruction added to all four persona prompts, then verified against the deployed Edge Function with 13 fresh opening lines across all scenarios.
The spec documents — the single source of truth for every screen, prompt, schema, and design token:
| File | What it covers |
|---|---|
00-START-HERE.md |
Master index, bootstrap commands, source-of-truth rules |
01-PRD.md |
Product requirements, personas, 8 user stories with acceptance criteria |
02-ARCHITECTURE-AND-DATA.md |
Tech stack, folder structure, DB schema with RLS, API contracts |
03-DESIGN-SYSTEM-AND-SCREENS.md |
Design tokens, component library setup, per-screen 3-state specs |
04-SCENARIO-PROMPTS.md |
Verbatim roleplay persona prompts and feedback engine prompt |
AGENTS.md |
Build order, definition of done, anti-hallucination protocol |
| Layer | Technology |
|---|---|
| Client | React Native + Expo SDK 57, TypeScript (strict), Expo Router, gluestack-ui v3 + NativeWind |
| Server state | TanStack Query v5 |
| Chat UI | react-native-gifted-chat |
| Backend | Supabase — PostgreSQL, Auth (anonymous sign-in), RLS on all tables |
| LLM proxy | Supabase Edge Function (Deno) → Google Gemini (structured JSON output) |
| Subscriptions | RevenueCat (react-native-purchases + Paywalls v2) |
| Rewarded ads | Google AdMob (react-native-google-mobile-ads) |
| Push notifications | OneSignal (react-native-onesignal) |
┌─────────────────────────────────────────────────────┐
│ EXPO / REACT NATIVE CLIENT │
│ │
│ Expo Router screens → gluestack-ui components │
│ lib/ wrappers for all external calls │
└──────────┬──────────────────┬───────────┬───────────┘
│ │ │
┌──────▼──────┐ ┌──────▼─────┐ ┌──▼──────────┐
│ Supabase │ │ RevenueCat │ │ OneSignal │
│ Auth + DB │ │ SDK │ │ SDK │
│ + RLS │ │ (subs, │ │ (daily │
│ │ │ paywalls, │ │ push) │
│ Edge Fn │ │ ads) │ │ │
│ (LLM proxy)│ └────────────┘ └──────────────┘
└──────┬──────┘
│
┌──────▼──────┐
│ Google │
│ Gemini │
│ (roleplay │
│ + feedback) │
└─────────────┘
No custom backend server. The Edge Function’s only job is proxying LLM calls so the API key never ships in the client bundle. RevenueCat and OneSignal are called directly from the client via their official SDKs — that’s the intended integration pattern for both, not a shortcut.
Prerequisites: Node.js 18+, Xcode (required by native AdMob and OneSignal SDKs).
cd preprounds
npm install
# Copy env template and fill in your own service keys
cp .env.local.example .env.local
# Set the LLM key as a Supabase Edge Function secret (never client-side)
npx supabase secrets set LLM_API_KEY=<your-gemini-key>
# Generate native project and run
npx expo prebuild
npx expo run:ios
A note for judges: Running a live instance requires your own Supabase project, Gemini API key, RevenueCat app, OneSignal app, and AdMob account. Without those, you can still review all source code and watch the demo video above. The codebase is fully readable without running it — there is no generated or obfuscated code.
PrepRounds is a communication-practice tool, not medical, legal, or HR advice. All scenarios use fictional personas in hypothetical situations. This mirrors the in-app disclaimer presented to every user.