Kelas Sekejap · Student web app · Critique

Sticky, or just busy?

A walk through the app as a new Form 4 student, with one question: what brings me back tomorrow?

14 Sept 2026Walked: prod shape (v1) and staging shape (v2 + feed + long lesson)Local demo account, mic blocked on purpose
The verdict

Lots of retention furniture. Almost no retention.

The app has a streak, freezes, a weekly quest, a league, a star jar, badges, coins, a feed and a daily word. What it does not have is the two things that actually produce a return visit:

  1. A reason tomorrow is better than now. Nothing in the core loop is time-gated. The done screen offers "Start day 2 now". A keen student can finish a week in one sitting and never feel a pull to come back.
  2. A way to reach a student who closed the tab. No push, no PWA install, no email, no WhatsApp, no share card. Every return depends on memory alone. The mobile number we collect at signup is never used.

And the first session is front-loaded: seven required fields, an email code, then an eight-screen warm-up before the first minute of English. Meanwhile production still runs the 20-minute day the interviews already called heavy. The 5-minute day, the feed and the long-lesson row are all on staging.

What students see today

Two apps: prod and staging

Production · main

Program v1 · 30 days

  • Day 1 hero: "Up next 1 of 8". 8 items: 3 speaking lessons (12 min) + 4 listening lessons (15 min) labelled optional.
  • XP target reads 0 / 175.
  • Quiet Calibration gate on. Feed off. Long lessons off.
  • Copy bug on a fresh day: "This day was already complete. These are here whenever you want them" with 0/3 done.
  • The hero pushes Quick Listen first, from the section the page itself calls optional.
Staging · flags on

Program v2 · 60 days

  • Day = Listen → Build → Speak, 15 + 30 + 15 = 60 XP, "about 5 minutes". Honest and true.
  • Feed page: 31 cards on first open, 8 new a day, ends with "New cards tomorrow".
  • PR #180 adds the 4th row: next speaking practice, greyed but playable.
  • Say More: 2 extra clips, behind a padlock until the 3 steps are done.
  • Everything the research asked for lives here, unseen by students.

The single biggest stickiness action is not a design change. It is promoting staging to main. Until then, every return-rate read comes from the version we already decided against.

The walk

First session, minute by minute

Staging shape, Form 4, day 1. Times are my own on a laptop; a student on a phone with data will be slower.

0:00

Splash → Sign up 7 fields

Full name, username, email, mobile, state, school, form. The school picker is excellent. Mobile is collected and never used anywhere. A consumer signing up alone has no reason to give a school yet.

1:30

Email code

Fine. Students never check email (Yu Juinn), so expect the drop here to be real on prod.

2:00

Quiet Calibration Keep

Feels like content, not a test. Mic offered, never required. BM button and slow play are exactly the embarrassment-blocker answer. Step 2 asks the student to "Help Aina answer" and the answer is a question ("Can I have iced Milo?"), which reads oddly. Ends by dropping the student straight into Day 1, no Home detour. Good.

4:00

Listen: one clip, one question Trust leak

I answered wrong. The card explained why and then offered Collect +15 XP. Grammar Tap and Story Stack already pay nothing for a wrong answer; Listen still does. The explanation also uses an em dash and "signals a one-off change", above the B1 line.

5:00

Back to Home Every step

Collecting XP returns to Home. Home is 3.5 screens long. The student scrolls past the feed teaser, stats, and the weekly quest to find step 2. This happens three times per day.

6:00

Build: Grammar Tap Generic feedback

Four items, one rule card. Item 4 is "I see your point, but I disagree" and the feedback still says "give one reason with because". A student who reads feedback once and finds it generic stops reading feedback. This is the open per-item follow-up from PR #179 and it is visible on day 1.

8:00

Speak: say one sentence No question

Roly says "Give your view and one reason. SPM-style answer." On what? The topic only appears if you tap Hint. The one speaking moment of the day starts in confusion. The blocked-mic path is very good: clear message, try again, or type it.

9:30

Done screen

"That is today done. Five minutes, and you showed up for them." Then Say More, a feed handoff, "Tomorrow: Day 2 · Giving an opinion", and "Start day 2 now". PR #180 tightens this to one Keep going button. Neither version tells me what will be new tomorrow.

10:00

Feed Keep

The voice is right ("handphone is fine la"). Payoffs land. I reached the bottom in one scroll: "That is everything for now. Roly pun rehat dah." The thesis said never show the bottom. Votes live in localStorage only.

What is working

Keep these. Build on them.

The 5-minute day shape

Listen → Build → Speak with a fixed 60-XP day is honest, finishable and legible. "Today · about 5 minutes" is the best line on Home. The 4th-row long lesson (greyed, playable, "open any time") is the right pattern for optional depth.

Staging · lib/program-v2.ts

Mic never blocks progress

Every speaking surface has a tap or type fallback with plain copy. For a product whose root blocker is embarrassment, this is the feature. Keep it sacred.

Calibration, micro-speak, v1 recorder

Quiet Calibration as the front door

Two minutes that feel like play, land the student in content, and quietly record signals. The only onboarding step that earns its place.

lib/quiet-calibration.ts

The streak-break screen

"Your 5-day streak paused. That's all it is, paused." Keeps XP, shows the best run, one CTA. This is how you keep a lapsed student instead of shaming them. The streak is now server-backed on bootstrap, so An Ran's reset bug should be closed.

components/screens/StreakLost.tsx

The feed's voice and payoffs

School-life polls, Manglish quick picks, chat choices with real consequences. Self-refilling units that cost nothing to answer. "New cards tomorrow" is the strongest tomorrow-hook in the app, and it is on the one page that is off in prod.

lib/feed/bank.ts · 51 cards

Analytics can now answer the question

PostHog identifies by backend user id, resets on logout, and fires lesson_started, lesson_abandoned and day_completed. Return rate is readable from day one of the v2 launch. Define D1 and D7 on day_completed and read it weekly.

lib/track.ts
What could be better

Where the loop leaks

Nothing arrives tomorrow

The day is not time-gated, the free head start is 7 days, and the done screen invites "Start day 2 now". Bingeing is fine; the problem is that tomorrow offers nothing you cannot have now. Only the games (daily word, crossword), the feed and the star jar are on a calendar, and they all sit below the fold.

FixDone screen names what is new tomorrow: next topic, new feed cards, new daily word, and the nearest badge ("2 more days to Warming Up"). First session of a new calendar day pays a small "first of the day" bonus. Keep bingeing allowed.

Three round trips through Home per day

Each step ends on Home. The hero updates ("Up next 2 of 3") but the student re-reads greeting, feed teaser, stats and quest card to reach the next step. On a phone that is a lot of thumb for a 5-minute promise.

FixChain the three steps as one session with a three-dot header; Home only at the end. If Home must stay, put today's three steps directly under the hero and move the feed teaser below them.

Feedback that reads generic

Wrong Listen answers pay full XP. Grammar Tap shows one rule for four different items. Speak has no visible question. Each one alone is small; together they teach a student that the app is not really paying attention, and attention is the whole pitch of "Roly listens".

FixListen: no XP on a wrong answer, same as Grammar Tap. Grammar Tap: per-item rule text (the open #179 follow-up). Speak: show Roly's question above the mic, not behind Hint.

First session costs 4 to 5 minutes before English

Seven fields, a code, eight warm-up screens. The warm-up earns its time. The form does not: a consumer student cannot see why a mobile number or a school is required to try a lesson.

FixSignup = email, username, form. Collect school inside "Join a class" and name, mobile in Profile if ever needed. Or drop mobile until a WhatsApp reminder exists to justify it.

Coins talk a lot and buy nothing

Coins appear on lesson rows, the quest, the jar, the milestones and the freeze sheet. Their only sink is one streak freeze at 120. The weekly quest pays 60, the full star jar 20. Two perfect weeks for one freeze is a bad deal, and a student notices.

FixEither give coins one more visible sink students want (Roly stickers on the profile card, a colourway) or collapse the whole thing: "practise 7 days in a row, get a freeze" and stop printing coin amounts everywhere.

Home order fights the promise

Order today: greeting, hero, feed teaser, stats, weekly quest (with a boba cup), then today's steps, Say More, six game tiles. The thing we want done is the sixth thing on the page.

FixHero → today's steps + long lesson → feed teaser → streak and quest → games. One calendar hook per section, not four.

The feed shows its floor

31 cards, then "That is everything for now". The window is 8 cards a day plus four back-days; a first-day student clears it in two minutes. Votes are per device, so a cleared cache re-asks everything.

FixRecycle answered polls with refreshed splits below the fold instead of an end card, or end on tomorrow's teaser ("Tomorrow: Which cikgu are you?"). Backend votes are already on the option-3 list.

Copy that will be seen on day 1

Prod Home: "This day was already complete" on an unstarted day. Warm-up done: "Start Day 1 · One short lesson" while the v2 day is three steps. Listen explanations with em dashes and idioms. Calibration step 2 says "answer" and expects a question.

FixOne-line PRs each. Worth doing before the promote because day 1 is where trust is set.
The structural gap

No channel out of the app

Searched the whole client: no push notifications, no service worker, no PWA manifest or install prompt, no reminder setting, no email beyond the login code, no share card except the feed's "Who are you" result. If a student closes the tab, the app has no voice.

  • Highest leverage, engineering lane: PWA manifest + install prompt + web push with a student-chosen reminder time. Works on Android Chrome today and on iOS once installed to the home screen.
  • Cheap fallback, ops lane: a WhatsApp Business streak reminder. We already collect the mobile number; right now it is a cost with no return.
  • Our lane now: share cards from the done-screen stickers and streak milestones, using the same navigator.share path the feed already has. Teens share results, not lessons.
Consider removing

Less furniture, more pull

League, until cohorts are class-sized

Weekly XP leaderboard, prizes off in prod, polled every 30 seconds. On a small cohort it is five names. Under v2 the day is capped at 60 XP, so the board becomes "who did the most bonus", which is the opposite of the 5-minute promise. Hide the drawer row until a class of 30+ is live, or scope it to the class.

Two of the three weekly quest tasks

"Practise on 3 days", "Finish 9 activities", "Complete 2 full days" overlap almost entirely. One task, "Finish 3 days this week", says the same thing in a quarter of the space and does not need a boba meter.

The Say More padlock

Two optional clips behind a lock on a day that is already only three steps. A padlock on optional content reads as a wall. The long-lesson row already does greyed-but-playable; do the same here.

Half the games block

Star jar, Daily Word, Spelling Bee, Balloon Rescue, Crossword: six tiles and two calendars ("new jar on Monday", "new puzzle tomorrow"). Keep Daily Word (one a day, a real tomorrow hook). Fold Spelling Bee and Balloon Rescue into Say More. Drop the star jar or the crossword; one calendar reward is enough.

Decided 15 SeptAll four games stay in Kelas Games (a two-tile rail looked odd). The jar drops its Monday reset: ten stars pay 50 XP on the spot, then a new jar starts. Built in PR #183.

Full name and mobile at signup

Neither is used by the product today. Every field before the first lesson is a drop-off point on a phone.

"Rewards" in the drawer

It scrolls Home to the quest card. A menu row that goes to the same page it came from is confusing on a phone.

v1 Home extras

"Learn · phrases", "Listen to a model answer" and the four-lesson listening block go away with the v2 promote. Do not carry any of them into the v2 Home.

If we do five things

Ranked for return rate

Promote staging to main

Minh · staging→main PR

Everything below assumes students are on the 5-minute day. Today they are not.

Give tomorrow a face, then a voice

Ours: done screen · Engineering: PWA + push

Done screen names what arrives tomorrow and the nearest badge. Then a reminder channel so the app can say it out loud.

Cut the front door to three fields

Ours, backend check on profile requirements

Email, username, form. School moves to Join a class. Keep the warm-up exactly as it is.

One session, not three trips

Ours · lib/engine

Chain Listen → Build → Speak. Reorder Home so today's steps sit under the hero. Unlock Say More.

Close the trust leaks in feedback

Ours · three small PRs

No XP for a wrong Listen answer. Per-item Grammar Tap rules. Show the Speak question. Fix the day-1 copy.

How to know it worked: D1 and D7 return on day_completed in PostHog, split by feed door versus Home. Baseline the week before the promote so v1 and v2 can be compared.

Evidence

What the code says

MechanicComes back tomorrow?Survives a new phone?Note
StreakCalendar dayYes (server on bootstrap)Freezes and milestones still local only
Weekly questMon–Mon resetNo (localStorage)60 coins, three overlapping tasks
LeagueWeekly resetYesPrizes flag off in prod
Star jarEmpties MondayNo1 star per game per day, 20 coins per jar
Daily WordOne per dayNoNo XP by design; real tomorrow hook
CrosswordOne per dayNoSecond calendar in the same block
Feed8 new cards a dayNoOff in prod; end card visible
Day N+1Available nowYesNo wait; 7-day free head start
Coinsn/aHybridOnly sink: freeze at 120
Badges3 / 7 / 14 / 30 / 60 daysLocalOnly visible on Profile
Push / email / shareNot built

Not verified in this pass: the live league screen (needs the backend), audio quality on prod, and the LLM coach on a real recording. The v1 speaking lesson with a blocked mic shows "Didn't catch that, tap the mic or type", which is the right fallback.