CASOON Open Source · App
Math practice for primary school, rendered on the server.
Rechenkiste generates maths tasks for grades 1 to 5, checks every answer on the server and swaps tasks and feedback in as HTML fragments. It is the demo app for the AHA stack and @casoon/fragment-renderer.

- task types in the registry
- 105
- grades, from 1 to 5
- 5
- languages: German, English, Ukrainian
- 3
- Astro components behind the htmx fragments
- 12
What it does
Tasks that check themselves
Every task type implements one interface: it generates a question, validates the answer and returns a hint. Equivalent fractions like 6/8 and 3/4 count as correct.
Fragments instead of page loads
htmx posts the answer, the server renders the feedback component with @casoon/fragment-renderer and returns HTML. The next task arrives the same way.
Adaptive and forgiving
Optional adaptive difficulty moves up a grade after three correct answers in a row and down after three incorrect ones, and incorrect tasks can be repeated at the end of a run.
Built for children
Number pad for whole-number answers, SVG drawings for geometry, fractions and clocks, multiple choice and drag and drop, and read-aloud via the browser’s speech synthesis.
From the live app
Try it yourself →


Run it locally
Node and pnpm are all you need. The Cloudflare adapter runs the app in workerd locally, sessions included.
- Clone the repository and install the dependencies with
pnpm install. - Start the dev server with
pnpm devand open the printed URL. - Run
pnpm test,pnpm checkandpnpm lintbefore a change goes in.
git clone https://github.com/casoon/rechenkiste.git
cd rechenkiste
pnpm install
pnpm devconst runtime = createAstroRuntime({
...ahaStackPreset({ locale, htmxHeaders: true }),
components: [
{ id: "feedback-correct", loader: () => import("@fragments/FeedbackCorrect.astro") },
{ id: "feedback-incorrect", loader: () => import("@fragments/FeedbackIncorrect.astro") },
// … task and result fragments
],
});
const html = await runtime.renderToString({ componentId, props });<form
id="answer-form"
hx-post="/api/test/answer"
hx-target="#feedback-container"
hx-swap="innerHTML"
hx-disabled-elt="find button[type='submit']"
>