
Fig. 1LinguaKu's progress view on a fresh install: vocabulary size, a distribution across word-frequency bands, retention, and a fourteen-day review forecast — every figure showing an explicit “not measured yet” state rather than a zero.
A PWA that teaches English and Japanese to Indonesian speakers — no account, no paywall, no ads, no engagement mechanics, and zero recurring cost. A learner on a cheap Android phone taps the icon and is answering a useful question in 108 ms, fully offline, because a memory model predicts they are about to forget it.
IThe problem
Language apps aimed at Indonesian learners are mostly translations of apps designed for English speakers learning European languages. They teach the wrong mistakes: the errors an Indonesian speaker actually makes in English come from specific structural differences — and nobody is teaching those differences directly.
The second problem is access. Streaks, hearts, paywalls and ad breaks are not learning mechanics; they are retention mechanics, and they cost the learner attention and money. The premise here is that a learner in Bogor should tap an icon on a cheap Android phone and be answering a useful question within three seconds, offline, for free, forever.
IIHow it works

The contrastive engine
This is the differentiator. Authored Indonesian notes follow a fixed order — what Indonesian does, what English does instead, one minimal pair — compiled from versioned YAML at build time by a compiler that fails the build on an unanswerable question.
Interference detection tags ordinary wrong answers by category, so the weakness heatmap is built from what a learner does when they are not being tested on it. For Japanese it also names six places where Indonesian gives the learner an advantage — the half of contrastive teaching this audience never hears, because the material is written for English speakers who do not have those advantages.
The review loop, enforced at the data layer
FSRS scheduling over a seven-rung card ladder, from L0 exposure to L6 free production. One active card per item; the rung selects the task and FSRS state carries across promotion.
No card advances without a learner response, and that is enforced structurally rather than by convention: recordReview is the only writer of scheduling state, it cannot be called without a rating, and the review log is append-only at the Dexie hook. The invariant is impossible to violate from feature code.
The graded reader
Running text comes from Simple English Wikipedia, selected by known-token coverage inside a [0.92, 0.98] band — a threshold that is literally unreachable on a single sentence and only becomes meaningful across a paragraph. Below it sits a feed of level-matched sentence pairs, because the Tatoeba corpus is independent pairs rather than documents.
Tap-to-gloss and one-tap mining are entirely local. Mining records an intention, not a card: the word arrives in the next session and becomes a card only when it is actually answered.
The corpus
English ships 23,497 banded sentence pairs, 5,245 lexemes, 1,581 Indonesian glosses, 1,680 graded reading passages, 73 authored collocations, 12 topic clusters, 21 contrastive categories, 126 drills and 75 false friends. Japanese ships 15,324 pairs, 6,904 lexemes, all 1,748 jōyō kanji with component breakdowns, and 14 contrastive categories.
All of it is built by a set of ingest scripts from licensed open corpora, with a licence checker wired into the verify gate so a build cannot ship data whose terms have not been read.
Privacy as a structural property
Optional sync is off by default, and nothing in the feature or data layers even imports it. An end-to-end test drives a full study session and asserts that zero requests leave the origin — which stayed true after an instance was actually deployed.
There is no AI layer and no seam for one. The app is structurally incapable of sending a learner's work to a model, rather than merely configured not to.
IIIDecisions
Honesty encoded in the type system
Every progress figure has an explicit "not measured yet" state and shows it. Charts take number | null and draw a gap rather than a bar of zero, so an unmeasured week cannot be misread as a week of no work. The distinction is enforced by the types, not by discipline.
No CEFR or JLPT level claims
No licence-cleared alignment exists for either, and deriving one from word frequency would be fake precision dressed as a standard. The app bands its own content and says so, rather than borrowing authority it has not earned.
Glosses are reference, never an answer key
Indonesian glosses cover 30% of English words and 4% of Japanese — measured before anything was built on them. Grading a typed meaning against a set that thin would mark good answers wrong, so glosses are shown where they exist and their absence is stated where they do not.
Deployed as an assets-only Worker
There is no Worker script, so no request is ever billed as an invocation and static asset requests stay free and unlimited. The SPA fallback is deliberately disabled: it would answer a missing content shard with index.html and a 200, turning a clean 404 into a JSON parse error somewhere further down.
IVWhat it can’t do
Each of these is stated in the product itself, not only here.
Limitation
Pre-cached audio is empty
The audio set stays empty until a voice model's licence is read and dated. This is named in the README and in the app rather than quietly shipped with an unlicensed voice — but it does mean listening practice is thinner than it should be.
Limitation
The device matrix has one row of five
Real-device coverage is thin and recorded as thin. The app collects a device profile itself in five taps, and the first one already corrected a timeout that was withholding listening practice from a phone perfectly capable of it.
Next
QuantDesk