Chapter 2 Β· the apps

App Screens β€” from Ben's design to the SOT app

The current app is v4 (/v4, apps/sot/) β€” the Source-of-Truth app on Engine V4, documented first below with its critical states. Beneath it, the preserved v1 reference build at /figma-app: the founder's Figma file rebuilt as a navigable offline PWA, battle screens running the real 300-era engine (state in localStorage["rwf.figma.v1"]), gold theme, hash router, installable, fully offline via service worker. v2 (board) and v3 (3D course) have their own chapter on The four versions.

Every screenshot below comes from a headless E2E walk that plays the real product (v4: e2e.mjs 120 checks + e2e-states.mjs 59 checks; v1: e2e.mjs + e2e-daily.mjs). If a screen below exists, a test walked through it.

v4 β€” the Source-of-Truth app

live Β· current

Ben's master spec as a working product: 5-tab nav per the SOT (Battle / Feed / central LOG / Power-Ups / Profile), near-black + gold loot presentation, hero numerals, and the full daily-battle β†’ season β†’ stake loop on the shared Engine V4. Onboarding through create-wizard (11 condensed steps), join-by-code with stake agreement, and the demo jump that seeds a live crew in one tap.

v4 battle home with hero numeral and leaderboard
v4-battle-home.png Β· Battle Home β€” target hero, ring, live board
v4 quick log sheet
v4-quick-log.png Β· Quick Log β€” recents, presets, keypad, timed holds
v4 power-up stack
v4-powerups.png Β· the stack β€” hidden loot cards
v4 season hub
v4-season-hub.png Β· Season Hub β€” standings, calendar, stake
v4 you won the day
v4-you-won.png Β· YOU WON THE DAY + canvas share card
v4 charity donation receipt
v4-stake-donated.png Β· charity resolved β€” receipt + fee

The journey a test walks: welcome + 3 explainers β†’ profile (name / badge / tone) β†’ create wizard (mode, identity, invite, battle days, 200 target, handicap review, exercises, season length, stake, power-up toggles, review) β†’ group created with invite code + QR-style share β†’ season live β†’ Battle Home (adjusted hero, DZ ramp, close-call, live bombs) β†’ Rep Steal (pure gain, asserted in state) β†’ quick log with undo β†’ keypad to 200 β†’ first Daily Win (confetti, canvas PNG share card) β†’ rival banks the day (streak) β†’ deadline β†’ failed days recorded β†’ recap β†’ season hub (1:1 standings, pot maths) β†’ Surprise Bomb HIT and MISS β†’ Lightning Γ—3 on a real log β†’ second Daily Win β†’ season ends β†’ charity chooser ($38.00 after the disclosed $2.00 fee) β†’ receipt β†’ join flow as a second player β†’ language + console gates.

Where the copy rules live: tone-aware lines (cheeky / neutral / corporate-safe) on every winner and failure moment; "reps" always, RUF never (enforced by the e2e's language gate).

lives: apps/sot/ (app.js Β· engine.js bridge Β· sot.css) + the shared rules engine apps/sot-engine.js Β· rwf.qalarc.com/v4
proven by: apps/sot/e2e.mjs β€” 120/120, zero console errors, 27+ shots per run

v4 critical states β€” offline Β· sync Β· rest Β· winner-known

live

The SOT's screen inventory flags the states a product needs when things aren't sunny: the realtime link drops (#103), logs race each other (#105, #119), today isn't a battle day (#29, #101), and the day's winner is known while the season runs on (#102). All four exist in v4 as real states with their own e2e walk (e2e-states.mjs, 59 checks).

Offline banner on the v4 app
v4-states-offline-banner.png Β· #104 offline β€” keep logging, queue locally
Queued offline log screen
v4-states-offline-queued.png Β· #120 queued β€” saved on-device, badge on LOG
Reconnecting banner
v4-states-reconnecting.png Β· #103 reconnecting β€” the retry beat
Sync conflict sheet with keep or drop
v4-states-sync-conflict.png Β· #105 sync conflict β€” keep both or drop mine
Duplicate log warning sheet
v4-states-dup-warn.png Β· #119 duplicate warning β€” confirm or undo
Rest day home state
v4-states-rest-day.png Β· #29 rest-day home β€” streak safe, next battle
Winner known transition card
v4-states-win-known.png Β· #102 live half β€” bank your day, tomorrow framed
Battle complete season live card
v4-states-battle-complete.png Β· #102 ended half β€” season live, recap one tap

Offline / reconnecting (#103 + #104 + #120). The app is local-first, so the realtime link is a simulated connection shim β€” demo-toggleable from Settings ("Simulate offline"), which makes the whole surface testable. Offline: an amber banner leads every screen ("you can keep logging"), submitted sets land in a queue (a badge counts them on the central LOG button), and the queue survives reloads. Back online: a visible reconnecting beat, then the queue replays through the real engine and a toast reports the outcome.

Sync conflict (#105) + duplicate warning (#119). Duplicate detection runs on every submit β€” same exercise + same reps (post-conversion) inside 60 seconds by the same player. Live, that's the "did that set count?" sheet: log it anyway (confirm) or undo back to the confirm step. On replay, a queued set that raced an already-landed log becomes a sync conflict: keep both sets or drop mine β€” resolved one by one, with the outcome toast tallying kept vs dropped.

Rest-day home (#29 + #101). When the next battle is more than three hours out, Battle Home becomes the rest-day state: "REST DAY", streak-safe messaging (the returning player's πŸ”₯ streak named and kept safe), the next battle day + live countdown, the season's progress, and a season-hub jump. Rest days never touch streaks β€” stated, and true in the engine.

Battle complete but season live (#102). Two halves of one transition. Winner-known while the battle rolls on: a purple card names the Daily Win taker, pushes bank-your-own-day, and frames tomorrow (next battle, season points on the line). Battle ended while the season runs: the completed battle stays framed on the next battle's home β€” winner, banked/missed counts, tomorrow's angle, and the recap one tap away.

lives: apps/sot/app.js (Conn shim Β· rest-day home Β· #102 cards Β· dupWarn/syncConflict overlays) Β· sot.css
proven by: apps/sot/e2e-states.mjs β€” 59/59, zero console errors; the main 120-check walk stays green alongside it

Index / splash

demo
App index splash screen
app-index.png Β· brand splash, gold theme, theme toggle

What. First paint: wordmark, tagline, the gold/lime theme toggle β€” the entry to onboarding.

How it works. Hash router (#/auth-008 style routes matching Ben's Figma frame names). The theme toggle flips data-theme="gold" β€” see Design System for what that means.

lives: apps/figma-app/index.html Β· app.js router
proven by: e2e.mjs step 1 β€” "index renders", "theme toggle present"

Onboarding β€” name β†’ tier β†’ in

demo
Onboarding name entry with live avatar initials
app-onboard-name.png Β· name entry β€” initials live-update
Tier picker with four fitness tiers
app-onboard-tier.png Β· the tier pick β€” this sets your Γ—multiplier
Onboarding complete screen
app-onboard-done.png Β· "YOU'RE IN" β€” greeted by real name

What. Three screens: pick a display name (avatar initials derive live), pick a fitness tier (couch / casual / fit / athlete β€” the handicap decision), done.

How it works. Draft state accumulates then persists the player object to rwf.figma.v1. The tier chosen here is the multiplier applied to every rep this player ever logs (see Game Rules).

lives: app.js onboard screens (#/auth-008…014)
proven by: e2e.mjs β€” initials live-update, tier selectable, player persisted, greeting by name

Home β€” battles list

demo
Home screen with battle cards
app-home-battles.png Β· battle cards with live status
Leaderboard with six players testing overflow
app-overflow-6.png Β· 6-player overflow test
Home with temporal day state
game-home-temporal.png Β· temporal state chip (day in play)

What. Your battle cards: name, target, days, live standings snapshot, temporal state (which play day, when it closes). Empty state β†’ create your first battle.

How it works. Renders from the matches array in state; each card recomputes its countdown chip via the daily layer, so the home screen is always temporal-state aware. The 6-player shot is the deliberate overflow/edge-case test from the design audit.

lives: app.js home screens (#/home-*)
proven by: e2e.mjs home asserts Β· e2e-daily.mjs temporal chip

Create battle

demo
Create battle screen with day and target chips
app-create-battle.png Β· day chips + target presets (Light = 150)

What. Name the battle, pick play days (week chips), pick a target preset.

How it works. Creates a match through the real engine: createMatch(config, players) with you + 3 demo mates (the demo roster), status open, and generates a CREW-XXXX code β€” the bridge token the bots use (link CREW-…).

lives: app.js create screens (#/create-002…) Β· engine.js createMatch
proven by: e2e.mjs β€” "Light target = 150 reps", "crew code generated", "crew = you + 3 mates"

Waiting room

demo
Waiting room with crew code share card
app-waiting-room.png Β· crew code + `link` grammar share card

What. The pre-match lobby: who's in, the crew code, how mates join from WhatsApp/Slack, and a START EARLY button.

How it works. The share card shows the exact bot grammar (link CREW-XXXX) β€” the app↔bot bridge is a shared code, not a shared login (state unification is tracked in Status β†’ T5). START EARLY flips the match to live immediately.

lives: app.js #/create-014 Β· /connect helper page in the public bundle
proven by: e2e.mjs β€” code shown matches state, START EARLY β†’ live

Battle live

demo
Battle live with progress ring and leaderboard
app-battle-live.png Β· your ring + the live board
Battle screen at final state
app-battle-final.png Β· the board at match end
Battle at danger zone 3
game-dz3.png Β· same screen at DZ3 (screen wash)

What. The main screen: your progress ring (raw / target), the full leaderboard (adjusted score + verified %), the dual-clock countdown, comeback banner, danger-zone chrome, the big LOG button and the power-up button.

How it works. Leaderboard rows come from engine.standings(state) β€” the same function the bots use for their standings card. The ring tracks your raw reps against target; the board sorts by adjusted. Countdown + DZ chrome from the daily layer ticker (see Game Rules β†’ danger zone).

lives: app.js #/battle-001 Β· engine.js standings Β· daily.js applyDzChrome
proven by: e2e.mjs β€” 4 standings rows, ring shows 0β†’20 after log, countdown ticking, no comeback banner until eligible

Log sheet β€” ≀3 taps

demo
Quick log sheet with rep chips
app-log-sheet.png Β· quick chips, live conversion, one CTA
Camera verify note in the log sheet
app-camera-note.png Β· camera-verify route from logging

What. The core loop surface: tap LOG β†’ tap a rep count chip β†’ tap the CTA. Done, mid-workout, with sweaty hands.

How it works. The sheet shows your live conversion ("Γ—1.5" for couch β€” what those reps are worth) and the CTA text mirrors the selection ("LOG 20 PUSH-UPS"). Confirming chains the engine's tag functions β€” applyComeback then applyLightning β€” appends the entry, and if the raw total crosses the target the match closes right there. Camera verify is offered inline for supported exercises.

lives: app.js quick-log sheet Β· state.js logRepsAt
proven by: e2e.mjs β€” sheet opens, conversion shows Γ—1.5, CTA reflects selection, entry lands in state, ring updates

Power-up surfaces

live
Power-up button on battle screen
app-pwr-button.png Β· the PWR button appears once you hold cards
Power-up arsenal sheet
app-pwr-sheet.png Β· arsenal sheet β€” held cards with rarities
Power-up detail sheet
app-pwr-detail.png Β· card detail before activation
Rep steal toast
app-pwr-steal.png Β· steal fires β€” ledger toast
Steal blocked by shield toast
app-pwr-steal-blocked.png Β· shield eats the steal
Lightning activation toast
app-pwr-lightning-toast.png Β· lightning armed
Lightning countdown on battle screen
app-pwr-lightning-live.png Β· Γ—3 window counting down live

What. The full power-up journey: discover the button β†’ open the arsenal β†’ inspect a card β†’ activate β†’ feel it in toasts and on the live board.

How it works. All mechanics are specified in Game Rules β†’ power-ups (Γ—3/10min, 10% steal, shield-block, +30min freeze). The UI layer adds the feedback: steal shows a preview against the current leader before committing; a blocked steal explains that the shield was consumed and the card kept; lightning leaves a persistent countdown on the battle screen while the window is open.

lives: app.js power-up sheets + toasts Β· engine.js activatePowerUp / stealPreview
proven by: e2e.mjs FLOW-05 walk β€” steal, blocked steal, lightning window all asserted

Result + charity pot

demo
Final result standings with medals
app-result.png Β· final order, medals, closer marked
Charity pot on result
app-pot.png Β· the pot rides the result
Winner designating charity
app-pot-designated.png Β· winner directs it
Rematch battle live again
app-rematch.png Β· REMATCH β€” roster carried, fresh match

What. The finale: final standings with medals, the closure bonus applied, the charity pot total, the winner's designation flow, and the one-tap REMATCH.

How it works. Result = finalStandings() (bonus applied, re-ranked) with confetti + count-up finale (motion budget: 1200ms β€” see Design). The pot panel shows the ledger total and, for the winner, the designation picker. Rematch creates a new match with the same roster and rules β€” "run it back" also exists as a bot command (rematch).

lives: app.js result screens Β· engine.js finalStandings / rematchState
proven by: e2e.mjs β€” result renders, rematch goes live with roster carried

Profile + season ladder

demo
Profile screen with stats
app-profile.png Β· profile β€” tier, record, streaks
Season ladder
app-season-ladder.png Β· season ladder β€” points / played / MVP

What. Profile: identity, tier (changeable between matches), match record. Season ladder: the 4-week points table with MVP counts and division movement.

How it works. The ladder is seasonLadder() over recorded match results (3/2/1 + MVP β€” see Game Rules β†’ seasons). The profile doubles as the settings surface for HR straps (resting HR / age β†’ Karvonen) in the first-party app build.

lives: app.js profile + season screens Β· game-core season.ts
proven by: e2e.mjs ladder + profile asserts

The wider design coverage

demo

Beyond the E2E journey, a 67-screenshot walk covers every frame in Ben's file β€” including screens the flow doesn't reach: wagers, corporate, social, edge cases, offline.

Wager screen from the design walk
app-wager.png Β· the wager flow (design)
Corporate screen from the design walk
app-corp.png Β· corporate league (design)
Social screen from the design walk
app-social.png Β· social feed (design)
Edge case screen from the design walk
app-edge.png Β· edge cases (design)
App working offline
app-offline.png Β· the app offline β€” SW serving

How it works. Every Figma frame was extracted with exact fills and bounding boxes from figma/assets/file.json and rebuilt as HTML/CSS (lane F4). The offline shot proves the service worker: with the network cut, the app still boots from cache β€” it's installable as a PWA (manifest.webmanifest + sw.js).

The full walk lives in apps/screenshots/figma-app/ (67 PNGs: auth-*, battle-*, create-*, corp-*, edge-*, home-*, integ-*, join-*, log-*, profile-*, pwr-*, result-*, set-*, social-*, wager-*, _offline-*) β€” the wiki ships the highlights.

lives: apps/figma-app/ Β· extraction notes in figma/notes/analysis.md
proven by: the walk itself + e2e.mjs offline section

The first-party app (/app)

live

A second app exists β€” ours, not Ben's β€” shipped at rwf.qalarc.com/app. It carries the verification lane (camera + HR) and the synced state demo.

First-party app onboarding
webapp-onboard.png Β· onboarding
First-party app match screen
webapp-match.png Β· match live
First-party app result card
webapp-result.png Β· result + share card
First-party app profile
webapp-profile.png Β· profile

How it works. Built from apps/web/src (bundled with Bun, deployed to /app), it shares the same engine port and adds the verifier surfaces: the camera sheet (MoveNet) and BLE HR pairing that feed verified:true + avgHrrPct entries β€” the wiki's Verification chapter documents those mechanics. The figma-app remains the design reference; this is the shipping surface.

lives: apps/web/src/ (main.ts, screens/, verify/) Β· deployed at /app
proven by: 35 browser checks (docs/17) Β· polish screenshots above
← backGame Rules