feat(shop): story-37 target templates beside the hand-lowering
- view.html per feature + layout app/header/footer.html: the markup-
first form woc will compile ({{}} auto-escaped, w:if/w:for,
{!! !!} slots, w:component sections); inert today, verified not to
disturb the build
- README: pair is the DX referendum — .html is the target feel,
view.wo is today's cost; doctrine line reworded (templates compile
or don't exist)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
89df517613
commit
5a2e6402c8
7 changed files with 115 additions and 5 deletions
|
|
@ -29,6 +29,7 @@ it and restart: the orders are still there (WAL replay).
|
|||
| `product_page/`, `orders/` | one view module per feature + its root controller file | feature folders |
|
||||
| `static_files/controller.wo` | `/assets/*` from disk, traversal-safe, typed | `angular.json` assets |
|
||||
| `assets/style.css` | ONE real stylesheet, sectioned per feature | the `.scss` files |
|
||||
| `*/view.html`, `layout/*.html` | STORY 37's TARGET — markup-first templates ({{ }} auto-escaped, w:if/w:for, {!! !!} slots) that woc will COMPILE against the view classes; inert today | `.component.html` / Vue `<template>` |
|
||||
| `main.wo` | bootstrap: seed, routes, serve — nothing else | `app-routing.module.ts` + `main.ts` |
|
||||
|
||||
Separation is compiler-enforced where the language allows it today:
|
||||
|
|
@ -38,9 +39,12 @@ sit in the root module beside `types.wo`, because of gap #1 below.
|
|||
|
||||
## What is deliberately different (doctrine)
|
||||
|
||||
- **No `.html`/`.scss` template files.** Views are `.wo` code — the
|
||||
compiler type-checks them, `esc()` is the one escaping rule, and no
|
||||
template engine runs at request time. Styles stay a real CSS file,
|
||||
- **Templates compile or they don't exist (story 37).** The `.html`
|
||||
files here are the TARGET: when 37 lands, `woc` compiles each against
|
||||
its view class (typo'd field = compile error) and the hand-written
|
||||
`render()` methods in `view.wo` disappear. Until then the `.wo` files
|
||||
are the running hand-lowering of exactly that markup, and NO template
|
||||
engine runs at request time — ever. Styles stay a real CSS file,
|
||||
served statically (there is no scss preprocessor).
|
||||
- **No closures, no DI.** A view is a class with fields + `render()`;
|
||||
a controller is a class satisfying `Handler`. Capture = a field.
|
||||
|
|
@ -64,6 +68,8 @@ sit in the root module beside `types.wo`, because of gap #1 below.
|
|||
1. `types.wo` — the entire persistence layer is 20 lines.
|
||||
2. `orders.controller.wo` — the whole buying flow (validate, stock
|
||||
check, decrement, durable insert, render) with no framework magic.
|
||||
3. `product_list/view.wo` — is markup-as-code readable enough without
|
||||
templates? This file is the referendum.
|
||||
3. `product_list/view.html` NEXT TO `product_list/view.wo` — the
|
||||
template 37 will compile vs today's hand-lowering. The pair is the
|
||||
referendum: the `.html` is what writing a view will feel like, the
|
||||
`.wo` is what it costs today.
|
||||
4. `main.wo` — the app at a glance: five routes, one middleware, serve.
|
||||
|
|
|
|||
20
docs/examples/shop/layout/app.html
Normal file
20
docs/examples/shop/layout/app.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!-- layout/app.html — STORY 37's TARGET for the app shell: the document
|
||||
every page shares. {!! !!} slots take prebuilt fragments (header,
|
||||
footer, the page content); app.wo is the hand-lowered equivalent. -->
|
||||
|
||||
<template w:component="AppShell">
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{ self.title }}</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
</head>
|
||||
<body>
|
||||
{!! self.header !!}
|
||||
<main class="site-main">{!! self.content !!}</main>
|
||||
{!! self.footer !!}
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
7
docs/examples/shop/layout/footer.html
Normal file
7
docs/examples/shop/layout/footer.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!-- layout/footer.html — STORY 37's TARGET; footer.wo hand-lowers it. -->
|
||||
|
||||
<template w:component="Footer">
|
||||
<footer class="site-footer">
|
||||
writeonce shop — one binary: server, database, these pages.
|
||||
</footer>
|
||||
</template>
|
||||
11
docs/examples/shop/layout/header.html
Normal file
11
docs/examples/shop/layout/header.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!-- layout/header.html — STORY 37's TARGET; header.wo hand-lowers it. -->
|
||||
|
||||
<template w:component="Header">
|
||||
<header class="site-header">
|
||||
<a href="/" class="brand">writeonce shop</a>
|
||||
<nav class="site-nav">
|
||||
<a href="/">Products</a>
|
||||
<a href="/orders">Orders</a>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
28
docs/examples/shop/orders/view.html
Normal file
28
docs/examples/shop/orders/view.html
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<!-- orders/view.html — STORY 37's TARGET (see product_list/view.html for
|
||||
the convention). view.wo is the hand-lowered equivalent. -->
|
||||
|
||||
<template w:component="OrderOk">
|
||||
<div class="card">
|
||||
<h1>Order placed</h1>
|
||||
<p>{{ self.qty }} × {{ self.name }} — total € {{ self.total }}</p>
|
||||
<p><a href="/orders">See all orders</a> · <a href="/">Keep shopping</a></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template w:component="OrderRow">
|
||||
<tr>
|
||||
<td>{{ self.name }}</td>
|
||||
<td>{{ self.qty }}</td>
|
||||
<td>€ {{ self.total }}</td>
|
||||
<td>{{ self.status }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<template w:component="OrdersPage">
|
||||
<h1>Orders</h1>
|
||||
<p w:if="self.rows == ''">No orders yet — <a href="/">go buy something</a>.</p>
|
||||
<table w:if="self.rows != ''" class="orders">
|
||||
<tr><th>Product</th><th>Qty</th><th>Total</th><th>Status</th></tr>
|
||||
{!! self.rows !!}
|
||||
</table>
|
||||
</template>
|
||||
20
docs/examples/shop/product_list/view.html
Normal file
20
docs/examples/shop/product_list/view.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!-- product_list/view.html — STORY 37's TARGET, not yet compiled.
|
||||
When 37 lands, woc compiles this file against the view classes in
|
||||
view.wo; until then view.wo's render() methods are the HAND-LOWERED
|
||||
equivalent of exactly this markup. Notation ({{ }} auto-escaped,
|
||||
{!! !!} raw slot, w:if / w:for) is illustrative — 37's spec settles
|
||||
the final spelling. -->
|
||||
|
||||
<template w:component="ProductCard">
|
||||
<div class="card">
|
||||
<h3><a href="/p/{{ self.sku }}">{{ self.name }}</a></h3>
|
||||
<p class="price">€ {{ self.price }}</p>
|
||||
<p w:if="self.stock > 0" class="stock">{{ self.stock }} in stock</p>
|
||||
<p w:if="self.stock == 0" class="stock out">sold out</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template w:component="ProductListPage">
|
||||
<h1>Products</h1>
|
||||
<div class="grid">{!! self.cards !!}</div>
|
||||
</template>
|
||||
18
docs/examples/shop/product_page/view.html
Normal file
18
docs/examples/shop/product_page/view.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<!-- product_page/view.html — STORY 37's TARGET (see product_list/view.html
|
||||
for the convention). view.wo is the hand-lowered equivalent. -->
|
||||
|
||||
<template w:component="ProductPage">
|
||||
<div class="card detail">
|
||||
<h1>{{ self.name }}</h1>
|
||||
<p class="price">€ {{ self.price }}</p>
|
||||
<p class="stock">{{ self.stock }} in stock</p>
|
||||
|
||||
<form w:if="self.stock > 0" method="POST" action="/orders/{{ self.sku }}">
|
||||
<label>Quantity</label>
|
||||
<input type="text" name="qty" value="1" class="field">
|
||||
<button type="submit" class="btn">Buy</button>
|
||||
</form>
|
||||
<p w:if="self.stock == 0" class="stock out">sold out</p>
|
||||
</div>
|
||||
<p><a href="/">← all products</a></p>
|
||||
</template>
|
||||
Loading…
Reference in a new issue