# 08 — MVC structure: model = class, view = htmlx + scss, controller = .wo **Context sources:** [`.dev/reference/writeonce-app/src/app/`](../../../../.dev/reference/writeonce-app/src/app/) (the v1 Angular app whose component anatomy this formalizes), [`./00-overview.md`](./00-overview.md) ("Angular-component-style layout" — `home/{home.wo, home.htmlx, home.css}`), [`./01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) (the view grammar: Mustache + `` + `wo:bind`), [`./02-ui-compiler.md`](./02-ui-compiler.md), [`./03-client-runtime.md`](./03-client-runtime.md), [`../../13-class-model-live-pricing.md`](../../13-class-model-live-pricing.md) (the class methods controllers call), [`../../../examples/pricing/ui/pricing/`](../../../examples/pricing/ui/pricing/) (the reference screen). ## Goal Every writeonce UI screen follows **Model–View–Controller**, with the same file anatomy the v1 Angular app used — but collapsed into the single binary. One screen = one directory with three files: ``` ui/pricing/ ├── pricing.wo # Controller — binds the model into the view, exposes actions ├── pricing.htmlx # View — plain htmlx (Mustache + wo:live/wo:bind), no logic └── pricing.scss # View styles — external, compiled at `wo build` ``` ## The mapping, against the v1 Angular app | MVC role | v1 Angular (`.dev/reference/writeonce-app/src/app/`) | writeonce | | --- | --- | --- | | **Model** | `models/article.ts` (interface) + `services/article.service.ts` (HTTP fetch) | the `class` / `type` declaration itself (`types/product.wo`). No service layer: the database is in-process, and a model binding **is** a query — `LIVE select` for push, `select` for snapshot | | **View** | `article.component.html` + `article.component.css` | `pricing.htmlx` + `pricing.scss`. Plain markup; the only dynamic constructs are Mustache paths and `` / `wo:bind` from [`01-htmlx-format-spec.md`](./01-htmlx-format-spec.md) | | **Controller** | `article.component.ts` (`@Component({templateUrl, styleUrl})`, fields, methods, `service.subscribe(...)`) | `pricing.wo` — declares `view:` / `styles:` (≈ `templateUrl` / `styleUrl`), a `model:` block (≈ component fields), and an `actions:` block whose handlers **call class methods** | What Angular needed four layers for (interface, service, component class, template) writeonce does in three files against one runtime — there is no HTTP client between controller and model because there is no network between them. ## Design decisions 1. **The controller is declarative, like everything else in `.wo`.** It does not contain imperative rendering code; it declares *what* is bound and *which* method each action invokes. Shape: ```wo ##ui #pricing route: /pricing view: pricing.htmlx -- ≈ Angular templateUrl styles: pricing.scss -- ≈ Angular styleUrl -- Model → View binding. Names declared here are the root scope of -- the .htmlx file; LIVE bindings re-patch the view on every commit. model: products: LIVE select Product{ name, sku, prices } watchlist: $session.watchlist -- Controller actions: the only place UI may invoke class methods. actions: set-price(id, amount): Product{ id == id }.set_price(amount) role: Ops | Admin watch(id): session.watchlist += id ``` 2. **The view is plain `.htmlx`, logic-free.** Mustache paths, `{{#each}}`/`{{#if}}`, partials, helpers, `` subtrees, `wo:bind` attributes — nothing else. A `` whose `source` is a bare name resolves against the controller's `model:` block (the M→V binding); an inline query in `source` remains legal for controller-less partials. Views never call methods — they raise actions (`wo:action="set-price"`), the controller dispatches. 3. **Styles are external SCSS, compiled at `wo build`.** No `