feat(shop): 37 target = in-class raw template literals (developer form)
- separate view.html files dropped; every render() now carries its
'-- 37 target:' literal above the hand-lowered body — the pair is
the DX referendum in one file
- story 37 re-pointed: raw multi-line literal + {{ }} auto-escaped
typed holes + {!! !!} raw slots; structural control stays if/for;
w:if/w:for and .html files demoted to later; forks revised
- rebuild verified on untouched toolchain
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5a2e6402c8
commit
bbf5fb66d3
14 changed files with 130 additions and 147 deletions
|
|
@ -29,7 +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 |
|
| `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 |
|
| `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 |
|
| `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>` |
|
| the `-- 37 target:` block above each `render()` | STORY 37's TARGET — an in-class raw template literal ({{ }} auto-escaped + type-checked, {!! !!} raw slots) replacing the hand-built body below it | Vue `<template>` (in-SFC) |
|
||||||
| `main.wo` | bootstrap: seed, routes, serve — nothing else | `app-routing.module.ts` + `main.ts` |
|
| `main.wo` | bootstrap: seed, routes, serve — nothing else | `app-routing.module.ts` + `main.ts` |
|
||||||
|
|
||||||
Separation is compiler-enforced where the language allows it today:
|
Separation is compiler-enforced where the language allows it today:
|
||||||
|
|
@ -39,13 +39,16 @@ sit in the root module beside `types.wo`, because of gap #1 below.
|
||||||
|
|
||||||
## What is deliberately different (doctrine)
|
## What is deliberately different (doctrine)
|
||||||
|
|
||||||
- **Templates compile or they don't exist (story 37).** The `.html`
|
- **Templates compile or they don't exist (story 37).** The target is
|
||||||
files here are the TARGET: when 37 lands, `woc` compiles each against
|
an IN-CLASS raw template literal: `render()` returns one multi-line
|
||||||
its view class (typo'd field = compile error) and the hand-written
|
literal whose `{{ expr }}` holes are auto-escaped, compile-time-
|
||||||
`render()` methods in `view.wo` disappear. Until then the `.wo` files
|
checked interpolations (typo'd field = compile error); `{!! !!}` is
|
||||||
are the running hand-lowering of exactly that markup, and NO template
|
the raw slot for prebuilt fragments; structural control stays the
|
||||||
engine runs at request time — ever. Styles stay a real CSS file,
|
language's `if`/`for` composing literals. Every `render()` here
|
||||||
served statically (there is no scss preprocessor).
|
carries that target as the `-- 37 target:` comment above it — the
|
||||||
|
body below is today's hand-lowering. 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()`;
|
- **No closures, no DI.** A view is a class with fields + `render()`;
|
||||||
a controller is a class satisfying `Handler`. Capture = a field.
|
a controller is a class satisfying `Handler`. Capture = a field.
|
||||||
- **No sessions/cart yet.** Buying is per-product (qty → order). A cart
|
- **No sessions/cart yet.** Buying is per-product (qty → order). A cart
|
||||||
|
|
@ -68,8 +71,7 @@ sit in the root module beside `types.wo`, because of gap #1 below.
|
||||||
1. `types.wo` — the entire persistence layer is 20 lines.
|
1. `types.wo` — the entire persistence layer is 20 lines.
|
||||||
2. `orders.controller.wo` — the whole buying flow (validate, stock
|
2. `orders.controller.wo` — the whole buying flow (validate, stock
|
||||||
check, decrement, durable insert, render) with no framework magic.
|
check, decrement, durable insert, render) with no framework magic.
|
||||||
3. `product_list/view.html` NEXT TO `product_list/view.wo` — the
|
3. `orders/view.wo` — each `-- 37 target:` literal vs the hand-built
|
||||||
template 37 will compile vs today's hand-lowering. The pair is the
|
body under it. The pair is the referendum: the literal is what
|
||||||
referendum: the `.html` is what writing a view will feel like, the
|
writing a view will feel like, the body is what it costs today.
|
||||||
`.wo` is what it costs today.
|
|
||||||
4. `main.wo` — the app at a glance: five routes, one middleware, serve.
|
4. `main.wo` — the app at a glance: five routes, one middleware, serve.
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -6,6 +6,20 @@
|
||||||
use framework/http
|
use framework/http
|
||||||
use html
|
use html
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `
|
||||||
|
-- <!doctype html><html>
|
||||||
|
-- <head>
|
||||||
|
-- <meta charset="utf-8">
|
||||||
|
-- <meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
-- <title>{{ title }}</title>
|
||||||
|
-- <link rel="stylesheet" href="/assets/style.css">
|
||||||
|
-- </head>
|
||||||
|
-- <body>
|
||||||
|
-- {!! header() !!}
|
||||||
|
-- <main class="site-main">{!! content !!}</main>
|
||||||
|
-- {!! footer() !!}
|
||||||
|
-- </body></html>`;
|
||||||
pub fn app_shell(title: Text, content: Text) -> Text {
|
pub fn app_shell(title: Text, content: Text) -> Text {
|
||||||
let d = "<!doctype html><html><head><meta charset=\"utf-8\">";
|
let d = "<!doctype html><html><head><meta charset=\"utf-8\">";
|
||||||
d = d .. "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">";
|
d = d .. "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
-- layout/footer.wo — the footer fragment (footer.html's analog).
|
-- layout/footer.wo — the footer fragment (footer.html's analog).
|
||||||
use html
|
use html
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `<footer class="site-footer">writeonce shop — one binary: server, database, these pages.</footer>`;
|
||||||
pub fn footer() -> Text {
|
pub fn footer() -> Text {
|
||||||
return el("footer", "site-footer", "writeonce shop — one binary: server, database, these pages.");
|
return el("footer", "site-footer", "writeonce shop — one binary: server, database, these pages.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
-- layout/header.wo — the header fragment (header.html's analog).
|
-- layout/header.wo — the header fragment (header.html's analog).
|
||||||
use html
|
use html
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `
|
||||||
|
-- <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>`;
|
||||||
pub fn header() -> Text {
|
pub fn header() -> Text {
|
||||||
let brand = link("/", "brand", "writeonce shop");
|
let brand = link("/", "brand", "writeonce shop");
|
||||||
let nav = link("/", "", "Products") .. link("/orders", "", "Orders");
|
let nav = link("/", "", "Products") .. link("/orders", "", "Orders");
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
-- orders/view.wo — the confirmation page and the orders table.
|
-- orders/view.wo — the confirmation page and the orders table. The
|
||||||
|
-- comment above each render() is the STORY-37 TARGET (raw template
|
||||||
|
-- literal, {{ }} auto-escaped); the body is today's hand-lowering.
|
||||||
use html
|
use html
|
||||||
|
|
||||||
pub class OrderOk {
|
pub class OrderOk {
|
||||||
|
|
@ -6,6 +8,13 @@ pub class OrderOk {
|
||||||
qty: Int
|
qty: Int
|
||||||
total: Float
|
total: Float
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `
|
||||||
|
-- <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>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let head = el("h1", "", "Order placed");
|
let head = el("h1", "", "Order placed");
|
||||||
let what = el("p", "", "${self.qty} × ${esc(self.name)} — total € ${self.total}");
|
let what = el("p", "", "${self.qty} × ${esc(self.name)} — total € ${self.total}");
|
||||||
|
|
@ -20,6 +29,14 @@ pub class OrderRow {
|
||||||
total: Float
|
total: Float
|
||||||
status: Text
|
status: Text
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `
|
||||||
|
-- <tr>
|
||||||
|
-- <td>{{ self.name }}</td>
|
||||||
|
-- <td>{{ self.qty }}</td>
|
||||||
|
-- <td>€ {{ self.total }}</td>
|
||||||
|
-- <td>{{ self.status }}</td>
|
||||||
|
-- </tr>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let cells = el("td", "", esc(self.name));
|
let cells = el("td", "", esc(self.name));
|
||||||
cells = cells .. el("td", "", "${self.qty}");
|
cells = cells .. el("td", "", "${self.qty}");
|
||||||
|
|
@ -32,6 +49,17 @@ pub class OrderRow {
|
||||||
pub class OrdersPage {
|
pub class OrdersPage {
|
||||||
rows: Text
|
rows: Text
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- if self.rows == "" {
|
||||||
|
-- return `<h1>Orders</h1>
|
||||||
|
-- <p>No orders yet — <a href="/">go buy something</a>.</p>`;
|
||||||
|
-- }
|
||||||
|
-- return `
|
||||||
|
-- <h1>Orders</h1>
|
||||||
|
-- <table class="orders">
|
||||||
|
-- <tr><th>Product</th><th>Qty</th><th>Total</th><th>Status</th></tr>
|
||||||
|
-- {!! self.rows !!}
|
||||||
|
-- </table>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let head = el("h1", "", "Orders");
|
let head = el("h1", "", "Orders");
|
||||||
if self.rows == "" {
|
if self.rows == "" {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
-- product_list/view.wo — the VIEW (view.html's analog). Classes with
|
-- product_list/view.wo — the VIEW. Classes with `fn render() -> Text`:
|
||||||
-- `fn render() -> Text`: fields are exactly the values displayed, the
|
-- fields are exactly the values displayed. No queries here — the
|
||||||
-- markup reads top to bottom. No queries here — the controller fills
|
-- controller fills the fields.
|
||||||
-- the fields. (A future `@view` class would let a projection query fill
|
--
|
||||||
-- them directly; today the controller copies the fields in.)
|
-- Each render() carries its STORY-37 TARGET above it: a raw multi-line
|
||||||
|
-- template literal whose {{ expr }} holes are auto-escaped, compile-
|
||||||
|
-- time-checked interpolations (a typo'd field = compile error). The
|
||||||
|
-- body below is today's hand-lowering of exactly that literal.
|
||||||
use html
|
use html
|
||||||
|
|
||||||
pub class ProductCard {
|
pub class ProductCard {
|
||||||
|
|
@ -11,6 +14,15 @@ pub class ProductCard {
|
||||||
price: Float
|
price: Float
|
||||||
stock: Int
|
stock: Int
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- let tail = `<p class="stock">{{ self.stock }} in stock</p>`;
|
||||||
|
-- if self.stock == 0 { tail = `<p class="stock out">sold out</p>`; }
|
||||||
|
-- return `
|
||||||
|
-- <div class="card">
|
||||||
|
-- <h3><a href="/p/{{ self.sku }}">{{ self.name }}</a></h3>
|
||||||
|
-- <p class="price">€ {{ self.price }}</p>
|
||||||
|
-- {!! tail !!}
|
||||||
|
-- </div>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let h = el("h3", "", link("/p/${esc(self.sku)}", "", esc(self.name)));
|
let h = el("h3", "", link("/p/${esc(self.sku)}", "", esc(self.name)));
|
||||||
let price = el("p", "price", "€ ${self.price}");
|
let price = el("p", "price", "€ ${self.price}");
|
||||||
|
|
@ -25,6 +37,10 @@ pub class ProductCard {
|
||||||
pub class ProductListPage {
|
pub class ProductListPage {
|
||||||
cards: Text
|
cards: Text
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- return `
|
||||||
|
-- <h1>Products</h1>
|
||||||
|
-- <div class="grid">{!! self.cards !!}</div>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let head = el("h1", "", "Products");
|
let head = el("h1", "", "Products");
|
||||||
let grid = el("div", "grid", self.cards);
|
let grid = el("div", "grid", self.cards);
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
<!-- 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>
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
-- product_page/view.wo — the product detail view with the order form.
|
-- product_page/view.wo — the product detail view with the order form.
|
||||||
|
-- The comment above render() is the STORY-37 TARGET (raw template
|
||||||
|
-- literal, {{ }} auto-escaped); the body is today's hand-lowering.
|
||||||
use html
|
use html
|
||||||
|
|
||||||
pub class ProductPage {
|
pub class ProductPage {
|
||||||
|
|
@ -7,6 +9,22 @@ pub class ProductPage {
|
||||||
price: Float
|
price: Float
|
||||||
stock: Int
|
stock: Int
|
||||||
|
|
||||||
|
-- 37 target:
|
||||||
|
-- let action = `
|
||||||
|
-- <form 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>`;
|
||||||
|
-- if self.stock == 0 { action = `<p class="stock out">sold out</p>`; }
|
||||||
|
-- return `
|
||||||
|
-- <div class="card detail">
|
||||||
|
-- <h1>{{ self.name }}</h1>
|
||||||
|
-- <p class="price">€ {{ self.price }}</p>
|
||||||
|
-- <p class="stock">{{ self.stock }} in stock</p>
|
||||||
|
-- {!! action !!}
|
||||||
|
-- </div>
|
||||||
|
-- <p><a href="/">← all products</a></p>`;
|
||||||
fn render() -> Text {
|
fn render() -> Text {
|
||||||
let head = el("h1", "", esc(self.name));
|
let head = el("h1", "", esc(self.name));
|
||||||
let price = el("p", "price", "€ ${self.price}");
|
let price = el("p", "price", "€ ${self.price}");
|
||||||
|
|
|
||||||
|
|
@ -18,24 +18,27 @@ status: refine
|
||||||
>
|
>
|
||||||
> **REDIRECTED 2026-08-23** (developer review of the shop template
|
> **REDIRECTED 2026-08-23** (developer review of the shop template
|
||||||
> against a Vue SFC): render()-as-string-concatenation failed the DX
|
> against a Vue SFC): render()-as-string-concatenation failed the DX
|
||||||
> referendum — markup must be markup-FIRST. New direction:
|
> referendum — markup must be markup-FIRST. **RE-POINTED 2026-08-24**
|
||||||
> **`view.html` template files COMPILED BY `woc` into render code** —
|
> (developer counter-proposal): the leaning is now **in-class raw
|
||||||
> `{{ self.name }}` interpolation (auto-escaped; a raw opt-out spelling
|
> template literals**, not separate `.html` files — `render()` RETURNS
|
||||||
> for prebuilt fragments), `w:if`/`w:for` structural attributes, typed
|
> a raw multi-line string literal whose `{{ expr }}` holes are
|
||||||
> against the view class's fields at compile time (a typo'd field is a
|
> auto-escaped, compile-time-checked interpolations in class scope (a
|
||||||
> compile error), NO template engine at runtime — the doctrine's real
|
> typo'd field is a compile error). Compiler surface shrinks to one
|
||||||
> meaning becomes "no template interpreted at request time", not "no
|
> lexer addition (the raw literal — a general language win: multi-line
|
||||||
> template files". A runtime mustache-lite was considered and REJECTED:
|
> text without `\"` noise) plus one desugar; no file pairing, no
|
||||||
> reflection-free means values degrade to `map<Text, Text>` — typing
|
> `w:component` sections. Structural control stays the language's own
|
||||||
> lost. Client-side reactivity from the Vue sample (`ref`, `@click`,
|
> `if`/`for` composing literals; `w:if`/`w:for` attributes and separate
|
||||||
> `v-model`) stays out under the no-JS posture; qty steppers are form
|
> `.html` files are DEMOTED to a later option that can layer on without
|
||||||
> fields, actions are POSTs. This makes 37 a COMPILER iteration too
|
> breaking this form. Escaping: `{{ }}` always escapes; the raw/slot
|
||||||
> (template-to-code lowering), gated behind the standing
|
> spelling is the one greppable door. A runtime mustache-lite stays
|
||||||
> "no compiler/VM/database changes yet" directive — schedule
|
> REJECTED (reflection-free means untyped `map<Text, Text>`); client-
|
||||||
> accordingly. The shop template
|
> side reactivity (`ref`, `@click`, `v-model`) stays out under the
|
||||||
|
> no-JS posture — forms and POSTs instead. Still a COMPILER iteration,
|
||||||
|
> parked behind the standing "no compiler/VM/database changes yet"
|
||||||
|
> directive. The shop template
|
||||||
> ([`docs/examples/shop`](../../../examples/shop/README.md)) is the
|
> ([`docs/examples/shop`](../../../examples/shop/README.md)) is the
|
||||||
> consumer: its `view.wo` classes become `view.html` + view classes,
|
> consumer: each `render()` body becomes one raw template literal, and
|
||||||
> and its README's recorded gaps ride along (gap #1: `pub` + `@table`
|
> its README's recorded gaps ride along (gap #1: `pub` + `@table`
|
||||||
> cannot combine — blocks a shared model module; gap #2: `@view`
|
> cannot combine — blocks a shared model module; gap #2: `@view`
|
||||||
> projection classes).
|
> projection classes).
|
||||||
|
|
||||||
|
|
@ -115,14 +118,12 @@ detection, event bindings, SPA router) deliberately does not.
|
||||||
Forks the spec must settle (REVISED 2026-08-23 for the compiled-template
|
Forks the spec must settle (REVISED 2026-08-23 for the compiled-template
|
||||||
direction):
|
direction):
|
||||||
|
|
||||||
1. **Template pairing** — Vue-SFC style (one `view.html` whose
|
1. **The raw-literal spelling** — backticks, triple quotes, or another
|
||||||
frontmatter/`<script>` block declares the fields) vs paired files
|
delimiter; and the raw-slot spelling inside it (`{!! !!}` vs `${}`
|
||||||
(`view.html` + a `.wo` view class it compiles against). Leaning:
|
staying unescaped). One rule: `{{ }}` always escapes.
|
||||||
paired — the class stays ordinary `.wo`, the template is pure markup.
|
2. **Directive surface** — v1 ships NONE (structural control is `if`/
|
||||||
2. **Directive surface** — the minimal set: `{{ expr }}` (auto-escaped),
|
`for` composing literals); `w:if`/`w:for` and separate `.html` files
|
||||||
a raw spelling for prebuilt fragments (slots), `w:if`, `w:for`,
|
layer on later only if a sample proves the need.
|
||||||
`w:class`-style conditional classes. Anything beyond is YAGNI until a
|
|
||||||
sample demands it.
|
|
||||||
3. **Escaping default** — `{{ }}` escapes ALWAYS (safer than today's
|
3. **Escaping default** — `{{ }}` escapes ALWAYS (safer than today's
|
||||||
caller-explicit `esc()`); the raw spelling is the only door, and it
|
caller-explicit `esc()`); the raw spelling is the only door, and it
|
||||||
is greppable.
|
is greppable.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue