# Quickstart URL: /docs/get-started/quickstart/ Section: get-started Tags: onboarding, quickstart -------------------------------------------------------------------------------- This guide walks you through building a simple counter app — the "hello world" of Milo. What You'll Learn 0/4 complete Write a pure reducer function Create a Kida terminal template Wire them together with App Run with hot reload via milo dev (function() { const checklist = document.currentScript.closest('.checklist'); if (!checklist) return; const progressBar = checklist.querySelector('.checklist-progress-bar'); const progressText = checklist.querySelector('.checklist-progress-text'); const checkboxes = checklist.querySelectorAll('input[type="checkbox"]'); if (!progressBar || !progressText || !checkboxes.length) return; function updateProgress() { const total = checkboxes.length; const checked = Array.from(checkboxes).filter(cb => cb.checked).length; const percentage = Math.round((checked / total) * 100); progressBar.style.width = percentage + '%'; progressText.textContent = checked + '/' + total; } checkboxes.forEach(function(checkbox) { checkbox.addEventListener('change', updateProgress); }); })(); Build the Counter 1Create a reducerA pure function that manages your state A reducer takes the current state and an action, and returns the next state. It never mutates — always return a new value. # app.py from milo import App, Action def reducer(state, action): if state is None: return {"count": 0} if action.type == "@@KEY" and action.payload.char == " ": return {**state, "count": state["count"] + 1} if action.type == "@@KEY" and action.payload.char == "r": return {**state, "count": 0} return state The reducer handles three cases: Action Condition Result @@INIT state is None Return default state {"count": 0} @@KEY char == " " Increment counter @@KEY char == "r" Reset counter to 0 2Create a templateRender state to the terminal with Kida Milo uses Kida templates for rendering. Create a template file: {# counter.kida #} Count: {{ count }} [SPACE] Increment [R] Reset [Ctrl+C] Quit Your state dict becomes the template context — {{ count }} renders the current value of state["count"]. 3Wire it togetherCreate an App and run the event loop app = App(template="counter.kida", reducer=reducer, initial_state=None) final_state = app.run() print(f"Final count: {final_state['count']}") App connects the pieces: it reads keyboard input, dispatches @@KEY actions to the reducer, re-renders the template on every state change, and returns the final state when the user quits. 4Run itStart your app with hot reload milo dev app:app --watch . Tip Tip The milo dev command uses the module:attribute convention. app:app means "import app from app.py and look up the app attribute." The --watch flag enables hot reload — edit counter.kida and see changes instantly. What just happened? flowchart LR K[Keyboard] -->|"@@KEY"| R[Reducer] R -->|new state| S[Store] S -->|state dict| T[Kida Template] T -->|ANSI output| Term[Terminal] KeyReader captures raw terminal input and produces Key objects Store dispatches @@KEY actions to your reducer Reducer returns new state (immutable — no mutation) Kida template renders state to terminal output LiveRenderer diffs and redraws only changed lines This is the Elm Architecture — a unidirectional data flow where every state transition is explicit and testable. Next steps Database State Management Store, middleware, combined reducers Multi-Screen Flows Chain screens with the >> operator Interactive Forms Collect structured input with validation Sagas Side effects with generators -------------------------------------------------------------------------------- Metadata: - Author: lbliii - Word Count: 477 - Reading Time: 2 minutes