Skip to main content
GrN.dk

Main navigation

  • Articles
  • Cases
  • Contact
  • Your Digital Project Manager
  • About Greg Nowak
  • Services
  • Portfolio
  • Container
    • Excel Freelancer
    • Kubuntu - tips and tricks
    • Linux Apache MySQL and PHP
    • News
    • Image Gallery
User account menu
  • Log in

Breadcrumb

  1. Home
  2. Cases

Learning to write Thai, one traced letter at a time

Client
GrN.dk — internal build
Sector
Educational apps for children
Period
July 2026

abc.grn.dk ↗

The abc.grn.dk alphabet screen: all 44 Thai consonants as cards colour-coded by class — mid blue, high orange, low green — each with its romanized name, above a practice-range picker and a "Practice 44 letters" button.

At a glance

abc.grn.dk teaches a child to write an alphabet rather than to recognise one: pick a letter, trace it with a finger, and the app scores the shape you actually drew and tells you how close you got. Thai first — all 44 consonants, free — with Japanese hiragana and katakana behind a premium gate. Everything happens in the browser: the handwriting recogniser is plain Dart running on the device, the spoken letter names are files bundled with the app, and there is no account, no runtime backend and no advertising.

44 Thai consonants + 92 Japanese kana, traced rather than tapped
542 bundled voice clips — no dependence on a device Thai voice
58 tests green · 3,176 lines of Dart · 3 packages beyond Flutter

The challenge

Most apps that promise to “teach the alphabet” actually test recognition — hear a letter, tap the letter. Writing is the harder half, and it is the half a five-year-old is really practising. Building that means solving a scoring problem: judge the shape a child drew, without punishing them for drawing the strokes in a different order or a different direction, and without sending a child's drawings to a server.

Thai adds its own demands. There are 44 consonants that come in three tone classes a learner is expected to internalise, and each is named by an acronym — ก is gor gai, กอ ไก่, “gor as in chicken”. An early version spoke those names with the device's own text-to-speech, which failed in the obvious way: a phone with no Thai voice reads the names in English, or says nothing at all.

And then there is the audience. The first difficulty bars — 60%, 75% and 90% similarity to pass — were set by adults and turned out to be far too strict for the small hands the app is for.

The solution

abc.grn.dk is a Flutter web app: 3,176 lines of Dart across 18 files, with three packages beyond Flutter itself. No runtime backend exists — progress lives in the browser's localStorage.

Scoring the handwriting. The recogniser is a 204-line, pure-Dart implementation of the $P point-cloud recognizer (Vatavu, Anthony & Wobbrock, 2012). It flattens whatever the child drew into an unordered cloud of points, resamples it to exactly 32, and matches it against the one target letter — not against all 44, because the app already knows which letter it asked for. Treating strokes as a cloud rather than a sequence is what makes it order- and direction-independent, which is exactly the forgiveness a beginner needs. The honest trade-off is stated in the repo's own README: a recogniser this forgiving cannot teach stroke order. The reference shapes are the letters' centre-lines, generated from the font outlines, so a dead-on trace scores near-zero distance; a forgiveness floor scaled by each letter's stroke width means fatter letters are judged a little more gently than thin ones.

Making it a game rather than a test. A letter is one shot. Trace it, lift your finger, and it checks itself — no button to press. Pass and it moves on by itself; miss and you spend a heart and get more help on the next attempt: first a bare canvas, then the letter's centre-line as guide dots, then the whole letter faint underneath. Run out of hearts and it offers a gentle start-again, never a failure screen. A pass on the first, help-free attempt extends a streak. The praise is a soft ascending chime and the miss is a mellow two-note tone — deliberately never a buzzer.

Re-tuned against real hands. The difficulty presets were redesigned after use: Easy now passes at 40% with 7 hearts and is the default, Normal at 60% and Hard at 85%, both with 5. Rather than let a default apply silently, the very first run asks the learner to choose, and Settings can change it later. The guide dots got the same treatment — placed by walking the letter's skeleton longest-first and keeping a dot only if it is a full step from every dot already placed, because the earlier per-fragment spacing left a visible blob at every junction, spotted on the live site.

Sound without depending on the device. The letter names and effects are 542 audio clips generated ahead of time and bundled with the app: Thai in two voices — female (Premwadee) and male (Niwat) — at three speeds, Japanese in one voice at three speeds, plus the two feedback tones. Pronunciation is therefore identical on every phone, whether or not it has a Thai voice installed, and a single Sound switch turns all of it off.

Two scripts, one shape. Japanese arrived as data plus assets rather than as a second app: 46 hiragana and 46 katakana in gojūon order, their own generated stroke templates and their own voice clips. The one rule that cannot be bent is that the font used to render a letter must also be the font its templates were generated from — otherwise the guide the child traces is not the shape being scored. Thai uses Noto Sans Thai Looped; the kana use a subset of Klee One, a textbook-style face.

What is paid, and how honestly. Japanese and a relaxed, un-gamified practice mode sit behind a per-user premium unlock; Thai stays free and there is no advertising anywhere — “no ads” is a compile-time constant referenced wherever an ad slot could plausibly be added, so the intent lives in the code rather than in a policy page. The checkout itself is not built yet, so the locked state opens a plainly-worded “coming soon” sheet instead of a button that goes nowhere.

Delivery included the pipeline. Its own virtual host and git repository, TLS, and a poller that watches the repository every ten minutes: a new commit on master builds the web release, stamps the git SHA into the binary, deploys it and purges Cloudflare. Because Flutter's main.dart.js keeps the same filename between builds, the domain also carries a bypass-cache rule — purge-by-URL propagation was losing races with the post-deploy check. The build stamp is printed on the home screen (v10.0.0 · 4533787), so anyone testing can say exactly which build they are looking at.

The results

The app has been live at abc.grn.dk since 19 July 2026 and is currently v10.0.0, with 58 tests green — the recogniser calibration, the game's rules and the screen navigation all covered without a browser or a device in the loop.

It is also a worked example of the delivery model the rest of this portfolio describes: every feature arrived as a Trello card picked up by an AI agent, from the first tracing loop through the Japanese set to the difficulty redesign — 15 commits between 19 and 24 July 2026, each one deployed by the poller within ten minutes of being pushed.

What it is not, it says out loud. English is listed as coming soon, the paid checkout is not built, and the recogniser is forgiving by design and so will not teach stroke order — a feature on top of the recogniser, not a data change. Both of the changes that mattered most, the dot spacing and the difficulty bars, came from watching the live app rather than from the plan, which is the argument for shipping small and often on a domain of its own.

Draw the letter — it checks itself.

The instruction line at the bottom of every practice screen
  • Flutter
  • language learning
  • text recognition
  • Custom Development
  • AI agents

Got a project that needs the same kind of hands-on delivery?

Your digital project manager

Review Greg on Google

Greg Nowak Google Reviews

 

Illustrated infographic summarizing: One Timeout, Two Orders: Make AI Actions Safe to Retry
One Timeout, Two Orders: Make AI Actions Safe to Retry
2026-07-25

A timed-out AI action may already have succeeded. Stable keys, durable ledgers, queues and stored results prevent a routine retry from duplicating real work.

Illustrated infographic summarizing: Your AI Visibility Dashboard Needs a Methodology, Not More Charts
Your AI Visibility Dashboard Needs a Methodology, Not More Charts
2026-07-24

A practical framework for measuring AI-search visibility with fixed prompts, repeated tests, separate metrics, retained evidence, and honest reporting.

Illustrated infographic summarizing: AI Admin APIs Are Here—But Your Directory Is Still the Source of Truth
AI Admin APIs Are Here—But Your Directory Is Still the Source of Truth
2026-07-23

New AI admin APIs can automate access and spend controls, but reliable governance still starts with authoritative directory data and clear ownership.

Illustrated infographic summarizing: OpenAI Presence Arrived—But Is Your Workflow Ready for an Agent?
OpenAI Presence Arrived—But Is Your Workflow Ready for an Agent?
2026-07-22

Before an AI agent can take on real work, its workflow needs clear scope, permissions, handoffs, evaluation cases, and production monitoring.

Illustrated infographic summarizing: Chatbot Transcripts Quietly Became a Retention and Redaction Problem
Chatbot Transcripts Quietly Became a Retention and Redaction Problem
2026-07-21

Chatbot transcripts spread across providers, logs and support tools. Here is how to map each copy, redact sensitive data and test deletion properly.

Illustrated infographic summarizing: Cloudflare Service Keys Stop in September: Find Every Caller
Cloudflare Service Keys Stop in September: Find Every Caller
2026-07-20

Cloudflare Service Keys stop working on September 30, 2026. Here is how to find every caller, move to scoped API tokens and avoid a late outage.

Illustrated infographic summarizing: Your AI Workflow Needs an Acceptance Test Before It Meets Customers
Your AI Workflow Needs an Acceptance Test Before It Meets Customers
2026-07-19

A practical way to test AI workflows using realistic scenarios, tool checks, human rubrics, regression suites, and clear release gates.

Three cover candidates for The Goats Were Load-Bearing fanned on a dark background: an ember-lit door, three slow knocks, and a founders' ledger
The Goats Were Load-Bearing: a fantasy where the bill always comes due
2026-07-19

A teaser for the upcoming darkly comic fantasy novel The Goats Were Load-Bearing — a village, a door that must stay poor, and the worst possible time to sell the herd. Readers pick the cover.

Vegan Power game: the yellow player catches falling fruit while a chicken and a cow look on
Vegan Power: The Little Game About Eating Fruit, Not Friends
2026-07-19

Vegan Power is a free browser game where you catch fruit, dodge the animals, protect seven hearts, and chase a better high score.

KotobaMon title screen: the Japanese logo コトバモン over a low-poly 3D island with monsters, cherry-blossom trees and a trainer.
KotobaMon: Shipping a 3D Browser Game With No Build Step and Self-Hosted Voice
2026-07-19

A look at fantasy.grn.dk, a browser-based 3D game that teaches Japanese with no build step, procedural art and self-hosted AI voice, and what its constraints show about shipping interactive products fast and cheap.

More articles
RSS feed

GrN.dk web platforms, web optimization, data analysis, data handling and logistics.