I Turned Off My Internet and Found a Problem I Would Never Have Designed For
At some point, the app started storing things I actually cared about.
My practice history.
Wrong answers.
Bookmarks.
Vocabulary progress.
XP.
Streaks.
Activity.
Once that happened, losing data stopped being an inconvenience.
It became unacceptable.
The first version could get away with storing everything in the browser because I was the only user and the entire project was still an experiment.
But the app was growing.
And if I ever wanted to use it properly across my phone and computer, local browser storage alone was not going to be enough.
Eventually I needed accounts.
Cloud progress.
Multi-device sync.
And a real backend.
Supabase was the obvious direction.
But there was one thing I did not want to sacrifice just because I was adding the cloud.
The app should still work when the internet doesn't.
I hate apps that become useless offline
This is especially annoying with study tools.
You open an app because you have ten minutes.
Maybe your Wi-Fi is unstable.
Maybe you're travelling.
Maybe mobile data is terrible.
Maybe the connection disappears for thirty seconds.
And suddenly the product acts like you no longer have permission to study.
That makes no sense to me.
If the questions are already available on the device, why should losing internet prevent me from answering them?
And even worse:
Why should a network problem risk losing progress I already made?
So the direction became clear.
The app should be local-first.
Local-first does not mean local-only
This distinction matters.
I did not want to go back to the original system where everything lived permanently inside one browser.
I still wanted the benefits of cloud storage.
An account should eventually let me move between devices.
My phone and computer should understand the same progress.
If I reinstall the app, my history should not disappear forever.
Supabase can provide that shared source of truth.
But the device should not need to ask the server for permission every time I answer a question.
Instead, the app can record important changes locally first.
Then sync them when the network is available.
That way the learning experience remains responsive even when the internet is unreliable.
The happy path is easy
Designing software while assuming everything works is extremely convenient.
The user answers a question.
The app sends the result to the server.
The server saves it.
Done.
But real networks are annoying.
What happens if the request fails?
What if the connection disappears halfway through?
What if the user closes the app before the request completes?
What if they answer more questions while offline?
What if two devices eventually have different versions of the same data?
What if the cloud has older progress than the device?
Once you start asking those questions, "add sync" stops sounding like one feature.
It becomes a system.
I wanted practice to continue normally
The basic idea was simple.
If the internet disappears, the student should still be able to use the app.
Answer questions.
Review words.
Make progress.
Continue the streak.
The app can mark the new changes as waiting to sync.
When connectivity returns, those changes can be pushed to the cloud.
From the student's perspective, studying should continue with as little interruption as possible.
Ideally, losing the internet should feel like a minor status change rather than the app breaking.
That was the goal.
Then I actually tested it.
So I turned the internet off
This is one of those tests that sounds embarrassingly obvious after you do it.
I disconnected the internet.
Used the app.
Created progress while offline.
Then turned the connection back on.
And watched what happened.
The good news was that the underlying idea worked.
The app could recover.
The sync process could begin.
The data wasn't simply disappearing because the connection had been lost.
Technically, that was a success.
Visually, it looked broken.
The sync indicator looked frozen
The app showed that it was syncing.
But the visual state did not communicate activity properly.
It just sat there.
No convincing movement.
No clear indication that work was happening.
Nothing that made me feel confident enough to say:
"Okay, it's processing my offline changes."
Instead, it looked stuck.
And the immediate thought was:
Did I break it?
That was interesting because the underlying code was doing what it was supposed to do.
The problem was not really the sync system.
The problem was trust.
Working and looking like it works are different things
This became one of my favourite lessons from the project.
As the person building the app, I can know that a process is running.
I can inspect logs.
I can understand what the code is doing.
The student cannot.
They only have the interface.
If the interface looks frozen, the product is frozen as far as they are concerned.
It doesn't matter that somewhere underneath, everything is behaving perfectly.
So I changed the syncing state.
It needed visible motion.
Something that clearly communicated:
The app is doing something.
Not a dramatic animation.
Just enough movement that the state feels alive rather than stuck.
It was a tiny visual fix.
But it made the entire sync system feel more trustworthy.
This is why using your own product matters
I probably would not have prioritised that problem while designing the feature on paper.
The checklist would have looked something like:
Offline progress saved.
Connection restored.
Pending changes synced.
Success state shown.
Done.
Technically complete.
But using the product exposed something the checklist did not.
There is a period between "sync started" and "sync finished."
And during that period, the student needs reassurance.
Not documentation.
Not a debug console.
One good animation.
That kind of thing is hard to discover when you only think about features abstractly.
You find it by actually living inside the product.
Offline also changes how streaks should behave
This matters more than it initially seems.
Imagine I study today without internet.
I complete enough practice to maintain my streak.
The app knows I studied today.
The server does not know yet.
Then the connection returns tomorrow.
If the system blindly trusts cloud timestamps without understanding what happened locally, it could incorrectly decide that I missed a day.
That would be awful.
A streak is supposed to reward consistency.
It should not punish someone because their router stopped working.
So local-first architecture is not just about allowing questions to load offline.
Every piece of progression eventually has to understand offline activity correctly.
Attempts.
Vocabulary learning.
XP.
Daily goals.
Streaks.
Everything is connected.
Sync sounds boring until it fails
When I first imagined this app, I thought about questions.
Words.
Practice modes.
Progress.
Maybe animations.
I did not sit around dreaming about synchronization architecture.
Nobody opens an exam-preparation app because it has elegant conflict resolution.
But infrastructure becomes important precisely because the user should not have to think about it.
The best version of sync is almost invisible.
You practise on your phone.
Open the app on your computer later.
Your progress is there.
That's it.
The student should never need to understand why that worked.
But making something feel that simple can require much more thought underneath.
There are still harder sync problems ahead
What I have now is not the final architecture.
Eventually, proper multi-device use introduces harder questions.
What happens when two devices both change something while offline?
Which data wins?
Can some changes simply be merged?
How should the app handle deletions?
How do you avoid duplicate attempts?
What happens if the local database and Supabase disagree?
Those are problems for the more mature version.
The important thing right now is that the direction is correct.
The app should trust the device enough to keep working.
The cloud should make the experience safer and portable.
Neither should make studying feel fragile.
And then I deliberately stopped polishing
After fixing the sync experience, I could see plenty of other things I wanted to change visually.
The streak display could be better.
Some screens could look cleaner.
Animations could be improved.
Spacing could change.
The entire product could become more polished.
And I will do that.
But most of those problems were cosmetic.
The core app was finally becoming reliable enough that polishing every screen immediately would have been a distraction.
I wanted to unlock the important systems first.
Because there were still much bigger questions left.
Why should every practice session contain exactly six questions?
Should students be allowed to choose their own session length?
Should questions have timers?
Should the whole session be timed?
Should vocabulary practice ever be timed at all?
What would an actual exam mode look like?
And eventually:
Can the app stop serving practice generically and start understanding what each student actually needs next?
The project had already gone from 32 questions in a browser to something that could learn, remember and survive losing the internet.
Now I wanted to see how far I could push it.