# shop — the writeonce program template A small store you can buy from, structured the way a real writeonce web app should be. **Copy this directory to start a new app**; every file has one concern, and the module system (one directory = one module, `pub` = the export line) enforces the separation the layout promises. ## Run it ``` cd docs/examples/shop woc . && WO_DATA=./data ./target/shop 8080 # durable store WO_EPHEMERAL=1 ./target/shop 8080 # RAM-only (dev) ``` A program with any durable table (the default) refuses to start without `WO_DATA`; `WO_EPHEMERAL=1` opts into a RAM-only run, `@table(durable: false)` opts a table out. Browse http://127.0.0.1:8080/ — products → product page → buy (stock checked and decremented) → confirmation → /orders. With `WO_DATA`, kill it and restart: the orders are still there (WAL replay). The template builds and runs as written — the two `[deps]` resolve, the seed lands, and every route answers. ## The view form A `render()` body is one backtick raw text literal. Markup is markup: real newlines, real double-quoted attributes, and the method's source indentation removed at compile time, so the served bytes carry the markup's own nesting and not the code's. ``` fn render() -> Text { return `

{{ self.name }}

€ ${self.price}

${stock}
`; } ``` Every `render()` makes its class a **component** — writeonce-view's structural `Component` interface, satisfied by having the method, never declared. Parent components hold children directly (`cards: multi Component`) and render them with `render_all`, so `ProductListPage` knows nothing about `ProductCard` beyond `render()`. The document itself is a component too: `AppShell { title, content }` in `layout/app.wo`, which links a real stylesheet rather than inlining one — that is why it is its own shell and not writeonce-view's `Layout`. Two holes, and the difference is the whole escaping story: - `{{ expr }}` **HTML-escapes** — it compiles to a call to the `esc` in scope (writeonce-view's, unless the app declares its own). Display data goes here; a typo'd field is a compile error, not a broken page. - `${ expr }` is **raw** — for markup you built yourself, like the `${stock}` fragment above or `${content}` in the app shell. Nothing is parsed at request time. The literal is a compile-time form: it produces exactly the string constant and concatenation chain the old hand-written version did, so there is no template engine to ship, warm up, or sandbox. ## The file map (Angular equivalents) | this template | concern | Angular analog | | --- | --- | --- | | `types.wo` | MODEL — `@table` classes ARE the WAL database | `models/*.ts` (+ the entire database) | | `layout/app.wo` | app shell: document, header+footer composition, `ok_html`/`html_error` transport helpers | `app.component.html` | | `layout/header.wo` / `footer.wo` | shared chrome fragments | `header.html` / `footer.html` | | `product_list/view.wo` | VIEW — classes with `fn render() -> Text`, fields = exactly what is displayed | `product-list/view.html` | | `product_list/controller.wo` | CONTROLLER — query the model, fill the view, answer a `Resp`; beside its view in the same module | component `.ts` + service | | `product_page/`, `orders/` | one module per feature: `view.wo` + `controller.wo` | feature folders | | `/assets/*` | served by the framework's `StaticFiles` — the template no longer carries its own copy | `angular.json` assets | | `assets/style.css` | ONE real stylesheet, sectioned per feature | the `.scss` files | | the `render()` bodies | ONE raw text literal each: real newlines, real double-quoted attributes, source indentation removed at compile time, `${}` raw holes and `{{ }}` auto-escaping ones | Vue `