You choose, then defend it
We won't tell you which libraries or patterns to use. Working that out, defending it in code review and rewriting it when you're wrong is the job.
A structured onboarding programme for junior engineers. You'll build a todo application from a blank repository to a live, cloud-hosted service — learning the Koala way as you go.
How this works
A todo list, from a blank repository to production. The product is boring on purpose, so you can focus on the how, not the what.
We won't tell you which libraries or patterns to use. Working that out, defending it in code review and rewriting it when you're wrong is the job.
Use them heavily, as we do. Scope tightly, read every diff, push back. If you can't explain your own PR, the tool is using you.
The same pull request review as production code. Expect to be challenged, and to rewrite things you were proud of.
The conventions in AGENTS.md in the main Koala repository are binding from day one.
You're embedded in a delivery team from day one: ceremonies, pairing on real work, and shipping small changes as you go.
What you'll learn
The modules
Each module is a deliberate step forward. Don't skip ahead. You are expected to pause at the end of each module for a review with your mentor before moving on.
Phase 1
A repository, a first program, and a pipeline that proves it works.
Get a repository set up the way we work. Everything that follows lives in this repo and moves through pull requests. There are no exceptions, even when you're the only contributor.
<yourname>-todo in the Koala GitHub organisation.main branch that can only be updated via reviewed pull requests.A small CLI that lets someone list, add, complete, and remove todos. The product is trivial on purpose — the point is to land your first .NET project, your first tests, and your first PR against yourself.
Automate the checks that you'd otherwise forget to run. A pull request that can't be proven safe isn't a pull request — it's a guess.
Add a web application alongside the CLI. The CLI keeps working — both applications talk to the same domain. For now, every interaction on the web is a full page load: no JavaScript, no AJAX. If the page works with JavaScript disabled, you've done it right.
Phase 2
See what it's doing, model it properly, and make it outlive the process.
You cannot fix what you cannot see. Your application must emit structured logs that a human can query when something goes wrong. A log like "Something broke" is worse than no log at all.
The tool is Seq — running locally through task services and in every deployed environment, so you query production with the exact same tool you used on your laptop. Logs and distributed traces land in the same place, and a single trace ID stitches every log for one request into one timeline — click a slow request and read the whole story end to end.
Your todo type should stop being a property bag with public setters. Model every state transition as a method on the entity itself. Errors should be values your caller can handle, not exceptions it has to catch.
Todos now outlive the process. Store them in PostgreSQL — it's what we run in production, so you might as well learn its quirks now. Write migrations as if someone else is running them against a production table tomorrow, because one day, someone will be.
Local dependencies run in Docker. Common workflows are scripted with Taskfile. A new joiner should be able to clone, run one command, and have everything running locally — application, database, and anything else it talks to.
Phase 3
Scoped access, shared components, and HTML that JavaScript only enhances.
Users sign in through Kinde — our hosted auth provider. You don't roll your own login screen, you don't store passwords, you don't reinvent OIDC. What you do own is authorization: making sure every user only ever sees their own todos. This is the single most common place juniors ship a serious bug. Assume a malicious user is hand-crafting URLs.
Koala has a shared component library expressed as tag helpers. Apply them. Consistency matters more than your personal taste in markup. Validation is server-side — client-side validation is not a thing we do.
Now you can sprinkle in client-side interactivity — dark mode, dropdowns, confirmation dialogs. The rule: if JavaScript fails to load, every feature must still work. No exceptions.
Swap fragments of the page instead of reloading the whole thing. Validate fields as the user tabs through the form. Open side panels that load their content on demand. Every one of these features must still have a full-page fallback route.
Phase 4
Fast, hosted and deployed on every merge, with nobody watching.
Make hot reads fast without making them wrong. A cache that returns stale data after a write is a bug factory.
Ship the application to Azure, on a subdomain somebody external could actually hit. This is the first time the thing you've built exists outside your laptop. It's a bigger deal than it sounds.
A merge to main should deploy automatically, safely, and without anyone watching. Infrastructure is described in code — you should be able to reproduce the entire environment from the repository.
House rules
They exist because we've seen the alternative.