Case Study

TheCoverStory

Brain Time: a daily brain workout that plays like a family English game, built for a dad who would never open an app that looked like a medical test.

Get it on the App Store

Listed in Korea as Daily Brain Challenge and in the US as Brain Time: Family, since both preferred names were taken there. The web build is at dailybrainchallenge.app.

App

Brain Time · 두뇌 시간

Role

Solo Designer & Developer

Platform

iOS · iPadOS · Web

Status

1.2 live · 1.2.1 in review

Home dashboard, English, eleven activity tiles on a dark navy pageThe same dashboard in Korean, following the device languageMy Record, with level, streaks, and the handwriting and signature records

Impact at a glance

I believed Dad played every day. The database said 2 days out of 14, so I rebuilt the app around that.

2 of 14 days

Days my dad completed an activity in the first two weeks on the App Store. In April, on the pre-account version, it was 3 of 23.

0 usable trends

A trend needs 6 play days a month. The most anyone logged in the 30 days to 25 September was 4, and that was me.

5 days straight

A stranger who joined on 26 September has played every day since, about ten activities a day.

In short

A disguised brain workout, and what the data made me change

Brain-training apps in Korea look like a diagnosis, and my dad avoids anything that suggests he is struggling. So I designed and built, alone, an app that trains the domains of the K-MMSE, the standard Korean dementia screen, dressed as a family English game.

I had two things wrong. The ability score was capped and could not see a fall from 95% to 75% accuracy. And my dad played 2 days out of 14, too few for any trend. The most regular player was a stranger.

So I split the app into a short, scored Brain Check and unscored Language Practice, and stopped hiding what the check is.

The Challenge

The apps that could help him are the ones he'd never open

Korean brain-training apps are clinical and aimed at older adults who already suspect something is wrong. My dad won't open anything that implies he's struggling. So the design problem was framing: train the right things without the app ever looking medical, and make him want to open it because it's fun.

The K-MMSE is the standard tool Korean physicians use to screen for early cognitive decline (Kang et al., 1997, adapting Folstein et al., 1975). Two of its domains matter most here: delayed recall, usually the earliest to weaken in Alzheimer's disease, and orientation to time, which tends to follow. Every game maps to a domain it measures.

I did all of it: concept, design, full-stack code and the clinical mapping. My parents had to be able to use it with no onboarding, tutorial or support.

Shipped home dashboard in dark navy: level badge and today's points in the header, the daily word recall banner, eleven lit activity tiles, and a four-tab bottom nav

The cover story

Eleven game tiles. The clinical layer is invisible.

Today's points

Points place the family along a shared trail over a rolling week.

Bottom nav

Four tabs in thumb reach. The family earned a tab of its own.

The games

Each game maps to a K-MMSE domain, and the English had to be convincing

The hardest tradeoff: to keep the cover believable, some games lean more toward vocabulary drill than I wanted. They served the story more than the cognitive goals. A language toggle let Dad play harder Korean activities instead, and the app stayed clinically complete. Later, making every activity bilingual made the toggle unnecessary.

Toggle in Settings

Seven games, split by language

  • 4 always on: 수학 대결, 날짜 퀴즈, 글씨 연습, 다른 하나 찾기
  • English mode: 3 vocabulary games
  • Korean mode: 단어 유창성, 속담 완성, 물건 이름 대기

1.1

Eleven, all bilingual

  • 서명 연습 became the eleventh
  • Toggle removed
  • App follows the device language

1.2.1

Brain Check + Language Practice

  • Scored check, three games a day
  • Vocab Quiz and Sentence Builder move to unscored practice
  • Delayed word recall and Word Meanings join the check
Serial Subtraction active, showing 20 − 3 = ?, number pad, timer, score counter

K-MMSE calculation task

Serial 7s and 3s, each answer held in mind while computing the next.

Cover story

Framed as a math duel against the family.

Timer pressure

Tighter limits at higher levels stress sustained attention.

The original seven

Seven at first, eleven by 1.1

The Korean side added 단어 유창성 (word fluency by category), 속담 완성 (proverb completion) and 물건 이름 대기 (object naming from description). 서명 연습 (signature practice) became the eleventh: the same signature written over and over, kept and dated, so change in the hand is visible. For how they split in 1.2.1, see Brain Check.

Date Quiz in-game screenshot

날짜 퀴즈

Date Quiz

K-MMSE orientation

Year, month, date, day of week, season, time of day: six of the ten K-MMSE orientation items. Now weekly, since the same questions every day stop meaning much.

Serial Subtraction in-game screenshot

수학 대결

Serial Subtraction

K-MMSE calculation

Serial 7s and 3s, the calculation task from the K-MMSE. Each answer is held in mind while computing the next.

Memory Game in-game screenshot

기억력 게임

Memory Game

K-MMSE recall

Five words, tested after two other activities create interference. Distractors come from the same theme, which makes recall harder.

Vocab Quiz in-game screenshot

영어 공부

Vocab Quiz

Now practice

Word-object association across 14 weekly themes. The core of the cover story. Now unscored practice.

Sentence Builder in-game screenshot

문장 맞추기

Sentence Builder

Now practice

Arrange words into a correct sentence. Built in the learning language, so now unscored practice.

Letter Tracing in-game screenshot

글씨 연습

Letter Tracing

Visuospatial

Trace Korean and English characters. Maps to the K-MMSE visuospatial task and exercises fine motor coordination.

Odd One Out in-game screenshot

다른 하나 찾기

Odd One Out

Abstract reasoning

Four words, one from a different category. Tests abstract reasoning and semantic memory.

Scoring

Two number systems with different jobs

Points drive the competition. XP drives difficulty. Before 1.2 the trail reset at midnight, so mid-afternoon it could show one person and an “invite your family” prompt to someone whose family was one tab away. Now it counts seven rolling days, so it is never empty.

Points

Where you are on the family trail

  • Earned by finishing three games in a day (two might be a warmup)
  • Counted over seven rolling days since 1.2, not reset at midnight
  • A missed day costs ground. It does not wipe the trail
  • Shared with the family

XP

How hard the games get

  • Cumulative. Never resets
  • 14 to 71 XP per activity on a quadratic curve. A score of zero still earns 14
  • XP sets level, level sets difficulty
  • Difficulty is never shown as a setting

What a higher level changes, without saying so

Harder vocabulary tiersBigger subtraction numbersMore memory wordsTighter time limits

Measuring

One instrument was doing two jobs with opposite needs

I simulated the update rule and counted the item banks. At one session a day the fixed banks cycled in 2 to 9 days, and faster as players improved: the top band asked Odd One Out for 22 questions from a bank of 20.

The ability score, an item-response estimate (θ), had a bigger problem. It was clamped, so a fall from 95% to 75% accuracy didn't move it at all.

012345θ4.995%3.785%3.175%accuracy on the hardest band (b = 2)Cap at 3.All three read the same.
θ = b + ln(s / (1 − s)), clamped to ±3. Dashed: the part of the estimate the cap threw away. The number only started moving below about 73%.

Training and measurement were sharing one instrument, so I split them.

Training wantsMeasurement wants
RepetitionItems the player has never seen
Difficulty that follows the playerFixed difficulty
Content that never runs outAccuracy left free to move
NowKeeps its adaptive difficultyA probe every 30 days, from reserved items training never shows. Plain accuracy, with its date and denominator.
Six items per probe gives a standard error near 0.12, so the first trend needs two probes, about two months.

A fall from 0.95 to 0.85 now comes back as −0.10. The cost is resolution: one probe can't separate a real change from a bad day. Until the second probe, the trend arrows are hidden instead of showing a number that stopped moving.

Whose Record

The disguise stops at the data

Points, streaks and who played today are shared inside a family automatically. Cognitive results are not. Nobody sees them until the owner grants one named person access, and that access can be taken back. The measurement data stays on the device.

I also built a one-page record for a doctor, exportable only for your own data. It is switched off until a lawyer has read it, because a page that shows a doctor cognitive results is where a game starts to look like a medical device. For now, results show only to their owner, as dated numbers with no interpretation.

Brain Check

The data said nobody was playing enough to measure, so the disguise came off

On 25 September I counted. In the previous 30 days the most play days anyone had logged was 4, and that was me. Most profiles had 1 or 2. A trend needs at least 6 days in 30, so no one's record could support one. And a free choice of any 3 of 11 games let someone skip the one getting hard, which is the one a measurement most needs.

So in 1.2.1 the app opens on a Brain Check, and Language Practice stays locked until it's done.

1 · Brain Check, scored

Three games, picked by a fixed rotation

  • Same set on the phone and the iPad
  • Every domain comes round within two or three days. Date quiz weekly
  • Native language, or no language at all
  • The only thing that counts toward the streak
Unlocks

2 · Language Practice, unscored

Vocabulary and sentence games

A language you are learning keeps improving, so a score on it could rise while memory slips.

Guards on a required check

Crash or denied mic

One skip a day. It records a gap and a reason, never a zero.

Rushing to reach practice

Every game records how long it took.

Reminders

Only on days the check is not done.

Came to learn a language

The lock is a per-person setting.

Dropping the disguise also fixed an ethical problem. “He thinks he is practising English” describes measuring someone's cognition without telling them. The game stays warm, and the check is named.

Ranking

The data could not draw a ranked list honestly

The scoreboard was a numbered list. But the backend knows far less about other people than about you: circle_scores returns a bare completed boolean for everyone else, and the 0 / 3 count exists only for whoever holds the iPad. Points are the only thing it can say about all of us, so position had to be points.

So everyone goes on one trail, placed by score. Players on nil points wait together at the start line. I tried twice to nudge tied pins apart, and both times the labels collided.

I also cut the activity-progress component and five taunt strings that needled you for falling behind.

The week's standings drawn as a winding trail, with family members placed along it by score and the not-yet-started waiting together at the start line

One shared trail

Placed by score, read as distance travelled.

Nobody is last

Players on nil points wait together at the start line.

Close scores cluster

Adjacent players merge into one band with a range.

Offline

A slow connection can't break the streak

Miss a day and the streak resets. If it reset because Supabase was slow over home wifi, Dad would stop trusting it. So every streak update and session save goes to localStorage first.

Game finished

Streak update or session save

Always

localStorage

Written first. If the network fails, this copy is the truth.

Best effort

Supabase

Syncs when the connection returns. The higher streak wins.

Design Iterations

The version that shipped wasn't the first idea

v1

Desktop first, camera-assisted

Desktop and iPad first, so 글씨 연습 had room, with MediaPipe hand tracking for tracing letters in mid-air. It lasted 15 minutes. Tracking ran at ~12fps and was tiring, and my dad doesn't open his laptop or iPad every day.

V1 desktop camera feed
V1 dark canvas iteration
v2

Cream and pastels

On mobile, the first look was dark navy with plain white tiles and emoji. It looked like a productivity tool. My argument was that dark reads as a tool and pastels read as a game, so I rebuilt it in warm off-white with seven pastel gradients and Lucide icons.

Before
Old design in dark navy
After
New design in cream and pastels
v3

The two users overruled me

The cream build shipped on 2 May and was gone on 20 May. Mom and Dad both preferred the dark version and found it easier to read, and their preference outranks my argument. The page went back to deep navy with gold. Each activity kept its own colour as a lit icon and a gradient on its own dark card, so the grid still reads as a set of games.

Light mode stayed as an option. In submission week I found it never overrode an accent, so gold text sat at 1.4:1 against white, where the floor is 4.5:1. After four text-only tokens and 59 repointed rules, the tightest pair in either theme measures 4.82:1. The app now opens dark.

Cream · 2 to 20 May
Cream dashboard with seven pastel tiles
Shipped · live in 1.1
Dark navy dashboard with eleven lit activity tiles

Design Decisions

Five smaller choices

Mobile-first

It gets used on a phone, probably one-handed, at a kitchen table. Every target is at least 44×44px, primary actions sit in a bottom nav within thumb reach, and safe-area insets handle notched phones.

Icons, not emoji

The first version used emoji. They render differently across OS versions and can't be styled, so I replaced every one with Lucide SVG icons, which keep one stroke weight and take a theme.

No tutorial

If a 65-year-old needs a tutorial to start a game, the design failed. Each intro screen shows what the game is and a large start button. The first two questions teach the rules.

Accessibility as display preference

Four switches in Settings: bigger text, bolder text, high contrast, reduced motion. None is labelled "accessibility" or "for older users". If the label implies a need, people won't turn it on.

Level lock

Difficulty rises with XP until a game can feel too hard. One toggle, 난이도 고정, freezes it. Dad doesn't have to explain or admit anything, and the habit stays intact.

Settings and dark mode, cream build (May 2026)

Settings with language toggle, accessibility switches, level lock, invite codeDark mode, the same seven games with a different visual identity
Shipped My Record screen: level and XP bar, current and longest streak, words learned, and the by-area, handwriting, and signature records

XP never resets

Cumulative XP sets level and difficulty. A zero still earns 14.

Streak counter

Saved locally before Supabase, so a bad connection can't break it.

Private by default

Nobody sees these until the owner grants one named person access.

Localization

Two correct words, and the one I preferred lost

The home-screen label is the only text my primary users read before opening the app, and a localized CFBundleDisplayName costs one strings file. My first pick for Korean was 머리 over 두뇌. Both are correct, but 두뇌 as branding carries a 1990s educational-marketing register (두뇌 개발, 영재 두뇌) and makes a game sound like screening.

I reverted it the same day. 두뇌게임 and 두뇌훈련 are what the category is called in Korean and carry the search volume. 머리운동 does not, and an app nobody finds can't reassure anybody.

The revert missed one file. ko.lproj/InfoPlist.strings was written during the experiment, so through 1.1 a Korean device said 두뇌 시간이에요 in the daily notification and 머리 시간 under the icon. I shipped 1.1 anyway: an icon label isn't indexed, and a review cycle cost more than the inconsistency. 1.2 put 두뇌 시간 under the icon. What I took from it: a revert that touches several files leaves no record of the ones it missed.

How it shippedBackend, packaging, and App ReviewOffline-first writes, the iOS wrapper, and what it took to get through review.

How the Backend Works

localStorage-first writes, optimistic locks on XP

Two Supabase tables: daily_sessions (one row per user per day: scores, completed activities, daily points) and user_progress (cumulative XP, level, streak). The trail polls daily_sessions every 10 seconds, so when Mom finishes a game, Dad sees it within seconds. XP uses an optimistic lock so a network retry can never award the same points twice.

utils/supabase.js: localStorage-first write
export const updateStreak = async (userId) => {
  const today = getTodayDate();

  // Step 1: read best available prior state
  const local = readLocalStreak(userId);
  let curStreak  = local?.currentStreak    ?? 0;
  let longStreak = local?.longestStreak    ?? 0;

  // Step 2: compute new streak
  const { currentStreak, longestStreak } =
    computeStreak(local?.lastActivityDate,
                  curStreak, longStreak, today);

  // Step 3: always write locally, works offline
  writeLocalStreak(userId, {
    currentStreak, longestStreak,
    lastActivityDate: today,
  });

  // Step 4: best-effort Supabase sync
  if (!isSupabaseConfigured()) return;
  try {
    await updateUserProgress(userId, {
      current_streak: currentStreak,
      longest_streak: longestStreak,
      last_activity_date: today,
    });
  } catch (err) {
    // local state preserved, will sync next session
    console.warn('Supabase sync failed', err.message);
  }
};
utils/supabase.js: optimistic lock on XP
export const recordActivityXp = async (
  userId, activityPoints
) => {
  const gain    = computeXpGain(activityPoints);
  const prog    = await ensureUserProgressRow(userId);
  const curXp   = prog.xp ?? 0;
  const newXp   = curXp + gain;
  const newLevel = xpToLevel(newXp);

  // Optimistic lock: only succeeds if XP unchanged.
  // If a retry already ran, 0 rows match → no double-count.
  const { data } = await supabase
    .from('user_progress')
    .update({ xp: newXp, level: newLevel })
    .eq('user_id', userId)
    .eq('xp', curXp)        // ← the lock
    .select('xp, level')
    .maybeSingle();

  if (data) return { gain, xp: data.xp, level: data.level };

  // 0 rows matched: XP already updated. Re-read, award 0.
  const { data: latest } = await supabase
    .from('user_progress')
    .select('xp, level')
    .eq('user_id', userId)
    .single();

  return { gain: 0, xp: latest.xp, level: latest.level };
};

Packaging

What putting it on a phone actually asked for

I wrapped the existing web build in Capacitor instead of rewriting it, so every game and CSS file stayed as it was. The tradeoff: a WKWebView instead of a native framework, with Guideline 4.2 (minimum functionality) as the known risk.

Invitations arrive by KakaoTalk, which opens links in its own web view. Universal Links are least reliable there and Apple's Smart App Banner doesn't render at all, so the browser landing page may be the main path and has to be designed as one.

A full-screen game hides the dashboard header, so the top of the game becomes the top of the screen. Every other rule took its safe-area inset, but nobody owned that edge, and the back button sat under the Dynamic Island.

The privacy questionnaire found something worse. Both policy pages said the app doesn't touch the microphone, but the word fluency game has used Apple's speech recognizer since the day it was built. The policy denied it. It is now bilingual with the Korean governing, names Apple as an overseas transfer recipient as PIPA requires, and declining the permission leaves the activity playable by typing.

The build that shipped

The same eleven activities, in whichever language the phone is already in

App Store screenshots from the real build: the same four screens on an English device and a Korean one. Only the chrome changes. Handwriting still traces Hangul, because the characters are the material being studied.

English device

Eleven activities, no toggle, English device
Eleven activities, no toggle
Hangul tracing, English chrome, English device
Hangul tracing, English chrome
My Record, not My Score, English device
My Record, not My Score
Family, joined by link, English device
Family, joined by link

한국어 device

Eleven activities, no toggle, 한국어 device
Eleven activities, no toggle
Hangul tracing, English chrome, 한국어 device
Hangul tracing, English chrome
My Record, not My Score, 한국어 device
My Record, not My Score
Family, joined by link, 한국어 device
Family, joined by link
The same dashboard on a 13-inch iPad, activity grid reflowed
iPad, where the grid reflows. Both orientations were checked in a browser at the device's dimensions.

Submitted

What went into App Review

The first submission was approved and released the next day, with 1.1 two days after that. 1.2 is live now. 1.2.1, which carries the Brain Check, went into review on 30 September as build 33.

A reviewer works alone, on one device, with no second account, and three true things about this app look broken from there: there is no sign-in, the family invite is a single-use link with nobody to send it to, and some content stays Korean on purpose. The review notes cover each one.

The last blocker: App Store Connect wanted a demo username and password because “Sign-in required” was ticked on an app with no sign-in. Unticking it was the fix.

Version

1.0 (3)

Submitted

16 Sep 2026

Released

17 Sep 2026

Localized

English · 한국어

The week before that

ARCHIVE SUCCEEDED, signed for the wrong destination

TestFlight was empty, months after I believed builds were going up. Archiving locally showed why: xcodebuild printed ARCHIVE SUCCEEDED while signing with an Apple Development certificate and a provisioning profile that names specific devices, which the store refuses.

Only the exported .ipa showed Apple Distribution, get-task-allow off and no device list, so that is the file to inspect. Build 2 went up that way and was replaced anyway, because it predated the in-app privacy link and the dark default.

Outcome

Did he play today? Measured this time

I set one metric from the start: did he play today? For months I answered it from what I believed, which was yes. When I read it out of the database, it was mostly no.

1718192021222324252627282930
Dad
Me
Stranger A
Stranger B
Days with at least one completed activity, 17–30 September 2026, the first two weeks on the App Store. Strangers A and B joined on 26 September as their own two-person family. Read from the production database.

Dad played on 2 of the first 14 days on the App Store. In April, before accounts existed, it was 3 of 23. My parents like the app. Liking it and opening it every morning turned out to be different things.

The most regular player is a stranger who joined on 26 September, set up a two-person family, and has finished about ten activities a day since. One person is not a finding, but it is the first sign the app holds someone with no reason to be polite to me.

1.2.1 went into review on 30 September. The test is whether Dad's row fills in over October. I'll know from the data, not from asking.

Limits and open questions

What this project cannot claim yet

One family and two strangers. That is enough to find out a design is wrong, and too little to say one works.

Nothing is validated. The probes have never been compared with a clinician giving the real K-MMSE, so a fall in probe accuracy is a reason to ask.

Practising the test can change the test. Several games use actual K-MMSE tasks, like serial subtraction and date orientation. Daily practice could raise a score at a real screening and hide the decline it exists to catch. Moving the date quiz to weekly reduces this but doesn't solve it.

What I'd study next: how often someone has to play before a home measure means anything, whether timing alone can tell a rushed session from real decline, and whether an openly named brain check keeps older adults playing as well as a disguised one.

See the live appOpens in a new tab

Eun Ji and her dad in the mountains

with love, THE favorite daughter