# Todo application lab

> **Volume:** 2: Application Engineering: Data & Web
> **Source Directory:** [`examples/todo/`](https://github.com/gizai/gizlang/tree/main/examples/todo/)
> **Components:** `model.giz`, `pages/index.ghtml`, `main.giz`
> **Target Port:** Local HTTP (`http://127.0.0.1:8080` or custom port)
> **Verification Status:** Verified via `tests/web/todo.test.giz` and CDP browser journey.

---

## 1. Project Initialization & File Structure

Create a new project using the canonical `todo` template:

```sh
giz init my-todo-app --template todo
cd my-todo-app
```

The template generates three core files:
```text
my-todo-app/
├── model.giz          # Data schema definition & validation rules
├── pages/
│   └── index.ghtml    # Declarative reactive user interface
└── main.giz           # Application entry point & HTTP listener
```

---

## 2. Step 1: Declaring the Model (`model.giz`)

Open `model.giz`. The entire persistence contract is expressed in 11 lines of typed Gizlang code:

```typescript gizlang file="examples/todo/model.giz"
import * as Model from "gizdb:model";

/** A thing to do. The key is made when the record is added and sorts by time. */
@Model.Table("todos")
export type Todo = {
    @Model.Key
    id: string;
    @Model.Min(1) @Model.Max(80)
    text: string;
    done: boolean;
};
```

**Key Points:**
- `@Model.Table("todos")`: Registers the table in the embedded GizDB store.
- `@Model.Key`: Auto-generates a monotonic, time-sorted unique string identifier upon insertion.
- `@Model.Min(1) @Model.Max(80)`: Enforces input length bounds automatically on both form submission and API mutations.

---

## 3. Step 2: Constructing the Reactive View (`pages/index.ghtml`)

In `pages/index.ghtml`, we bind the table to the reactive UI:

```html
<script>
import {Field, Check, Chip, controlId, describedBy} from "gizweb";
import {Todo} from "../model.giz";

let filter = "all" @url;
const todos = table<Todo>("id desc");
</script>

<title>Todos ({todos.count(t => !t.done)} open)</title>
<h1>Todos ({todos.count(t => !t.done)} open)</h1>

<form create={todos} class="ui-form">
  <Field name="text" label="New todo" hint="">
    <input id={controlId("text")} name="text" placeholder="What next?" aria-describedby={describedBy("text", false)}/>
  </Field>
  <button class="ui-button pri">Add</button>
</form>

<Field name="filter" label="Show" hint="">
  <select id={controlId("filter")} value={filter} aria-describedby={describedBy("filter", false)}>
    <option value="all">All</option>
    <option value="open">Open</option>
    <option value="done">Done</option>
  </select>
</Field>

<ul class="todos">
  <li each={t of todos.where(t => filter === "all" || t.done === (filter === "done"))} key={t.id} class:done={t.done}>
    <Check label={t.text}><input type="checkbox" checked={t.done}/></Check>
    <Chip if={t.done} label="Done" tone="ok"/>
    <button class="ui-button ghost" onclick={todos.remove(t)}>Remove</button>
  </li>
</ul>

<button class="ui-button" onclick={todos.removeWhere(t => t.done)}>Clear done</button>
<p if={todos.length === 0} class="ui-muted">Nothing to do.</p>

<style>
  .todos { list-style: none; padding: 0; }
  .todos li { display: flex; align-items: center; gap: 8px; }
  .done .ui-check span { text-decoration: line-through; }
</style>
```

---

## 4. Step 3: Application Entry Point (`main.giz`)

In `main.giz`, configure the HTTP listener:

```typescript gizlang file="examples/todo/main.giz"
// A live todo app over a typed table: the page reads and writes Todo records, the form checks the model's rules, and
// every open tab shows a change by itself. Run: giz run main.giz, then open http://127.0.0.1:8080. With
// TLS_DOMAINS=todo.example.com set, it gets its certificate and serves HTTPS with HTTP/2 and HTTP/3.
// The todos live in the GizDB database that this process serves, in the folder data/db: there is no server to start.
// DATABASE_URL may name a gizdb or PostgreSQL server instead.
import {System} from "gizlang:system";
import * as Model from "gizdb:model";
import * as Pages from "gizweb:pages";
import {Todo} from "./model.giz";
import {site} from "./pages/.routes";

function main(sys: System): i64 {
    return Pages.app(sys, site, [Model.shape<Todo>()]).listen(8080);
}
```

---

## 5. Step 4: Running and Verifying Live Multi-Tab Sync

Run the application:
```sh
giz run main.giz
```

Open two separate browser tabs at `http://127.0.0.1:8080`:

### Live Initial View
When first loaded, the embedded GizDB store provisions the table schema automatically:

![Todo App Initial State](../images/guidebook/todo-app-initial.png)

### Active Multi-Client Collaborative State
When concurrent clients interact with the application—adding tasks, updating filter views, and marking items complete—the embedded change stream keeps all views in synchronization:

![Todo App Collaborative State](../images/guidebook/todo-app-active.png)

1. **Adding an item in Tab A**: Type `"Review Gizlang Learning page"` and submit. Tab B immediately renders the new row and updates the open count without any manual page refresh.
2. **Checking off an item in Tab B**: Click the checkbox next to `"Review Gizlang Learning page"`. Tab A instantly displays the strikethrough styling and `Done` badge.
3. **Zero Configuration**: The database, WebSocket server, static asset bundler, and reactive state synchronization run entirely inside the single native Gizlang binary.
