all builds >> N. GUGLIELMI
>> nicola@guglielmi ~ / labs / ilripasso

ilRipasso

Summer revision for Italian primary and middle school, built to behave like the games the children already play: a winding path per subject, streaks, lives that refill, weekly leagues. The engineering decision that shaped everything else was made early and is unusual for a product with accounts, leagues and a database: none of that is required for the app to work.

React 18 + Vite TypeScript Zustand · localStorage Firebase, optional Express + Postgres, optional Cloud Run
>> THE_PROBLEM

An education app for children has a content problem that no amount of good architecture solves. Covering eight school years across six subjects, with enough variety that a child does not see the same question twice in a week, is thousands of items. Writing them all by hand does not finish. Generating them all produces arithmetic drills and calls it a curriculum.

It also has an audience problem: the user is a child, the customer is a parent, and the two want different things from the same screen. And it is used in July, in the car, on a parent's phone, wherever the signal is bad. That last constraint quietly decided the shape of the whole thing.

>> EVERYTHING_CLOUD_IS_ADDITIVE
the app, complete on its own Runs entirely in the browser curriculumper class, per subjecteight school years hand-written pools+ proceduralgenerators progress statexp, streak, livespersisted locally optional, each switched on alone Identityparent account, invite-only Backendusage, feedback, allowlist Textbook scanphotograph a page, practise it Remove all three and a child can still open the app and do a full lesson. That is not a fallback path bolted on afterwards: it is the only path, with three independent layers that attach to it when their configuration is present.

> served as static files by nginx on Cloud Run, with a small API alongside it that the app is written to live without.

>> IN_ACTION
The ilRipasso onboarding inside the app: Ripo the fox explains that getting started needs a parent account so progress is saved, above fields for the parent's email and a password, a checkbox declaring the person is the parent or guardian and accepts the privacy policy and terms, and a disabled create-parent-account button
> the first screen inside the app, and the clearest statement of who the account belongs to: the mascot explains it in the child's language, the form asks an adult for their email, and the button stays disabled until someone declares they are the guardian. the whole product is a phone-width column, even on a desktop.
>> DECISIONS_THAT_MATTERED
[ 01 ]

The cloud is a feature, not a foundation

Identity, the usage backend and the textbook scanner are three separate optional layers, each activated by its own configuration. With none of them present the app is still complete: a child picks a subject, does a lesson, earns experience, keeps a streak, all held in local state. This was not built as an offline fallback and then forgotten, it is the only path there has ever been. Progress lives on one device until an account exists to carry it, which is the honest cost of the arrangement.

[ 02 ]

Half the questions are written, half are generated

Grammar, history and geography need a person to write them, because the interesting part of a question about the Risorgimento is judgement. Arithmetic and geometry do not: they need parameters, a difficulty curve and a seed, and a generator produces endless correct variations that a child cannot memorise their way through. Splitting the content along that line is why the subject coverage is plausible at all. The maintenance burden is two different content systems, and knowing which side a new topic belongs on.

[ 03 ]

The account belongs to the parent

The child uses the app; the adult owns the account and is the one on the allowlist. This is not a feature so much as an admission of who can consent, who can be contacted, and who is responsible for a minor's data. It complicates onboarding, because the person holding the phone is usually not the person who has to create the account, and that friction was accepted rather than designed away.

[ 04 ]

The private gate covered the door as well as the house

A single password protected the whole domain during the closed beta, which also hid the page that explains what the product is. So there was nothing to link to and nothing to index while the app itself stayed correctly shut. The gate moved from the whole server to the application routes only, leaving a public page at the root and everything else refusing entry exactly as before. A five-line change to a server config, worth noticing because a beta with no public face is a beta nobody can be invited to.

[ 05 ]

Configuration that is baked, not read

A browser bundle has no environment to read at runtime: its configuration is compiled into it. That is fine until a deploy builds from source without passing any, at which point a service silently ships pointing nowhere. The values therefore live as build arguments with production defaults, documented in the build file itself with the reason. Unglamorous, and precisely the class of thing that produces a confusing outage weeks later when nobody remembers the build was the configuration step.

[ 06 ]

Nothing is installed on the machine that builds it

Development, type checking and production builds all happen inside containers, with no dependencies on the host at all. It costs a slower inner loop, and it means the thing that builds on a laptop is the thing that builds in the pipeline, with no chance for a locally-installed version to be quietly load-bearing. On a side project touched in bursts across months, that reproducibility is worth more than the seconds it costs.

>> WHY_THIS_MATTERS_TO_YOU

Making every cloud dependency optional sounds like extra work and mostly is not: it forces you to name what the product actually is, separately from the services it happens to use. The result runs on a bad connection, costs nothing to serve, and can be demonstrated on a laptop with the network off. If you are designing something where the network is a hope rather than a guarantee, that constraint is a good one to design under. Let's talk.