# GHTML

A `.ghtml` file is one page, one component or one library of functions, written as HTML with plain Gizlang in braces.
`tools/cli/ghtml.giz` turns it into a Gizlang module with the same line numbers, so a compile error names the line of the
`.ghtml` file. A program imports a `.ghtml` file as it imports a `.giz` file.

The page says what it shows, what it keeps and what it does. The compiler finds the rest: what is state, what is derived,
what each control changes, and which condition is a database query. There are no `$` functions, no stores and no `bind:`.

## 1. A page

```html
<script>
  import { Todo } from "./model.giz";

  let filter = "all";                       // let is state
  const todos = table<Todo>("id desc");     // a table of the page: its type and its order

  function clear(done: bool) { for (const t of todos.where(t => t.done === done)) { todos.remove(t); } }
</script>

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

<form create={todos}>
  <input name="text" placeholder="What next?"/>
  <button>Add</button>
</form>

<select value={filter}>
  <option value="all">All</option>
  <option value="open">Open</option>
  <option value="done">Done</option>
</select>

<ul>
  <li each={t of todos.where(t => filter === "all" || t.done === (filter === "done"))} key={t.id} class:done={t.done}>
    <input type="checkbox" checked={t.done}/> {t.text}
    <button onclick={todos.remove(t)}>Remove</button>
  </li>
</ul>
<p if={todos.length === 0}>Nothing to do.</p>

<style> .done { text-decoration: line-through; } </style>
```

`create={todos}` adds a row whose fields are the named inputs. `value={filter}` is two-way, and `checked={t.done}` writes the row.
The model is a normal `@Model.Table` type (tests/web/todo/model.giz). Its `@Model.MaxLength` and `@Model.Min` are the rules of the
form: a refusal is answered with status 422 and the reason in `<output role="alert">`.

## 2. Three kinds of file

| Kind | Name | Module |
| --- | --- | --- |
| Component | Capital first: `Card.ghtml` | `export type Props` from its `prop` lines, and `export function Card(_p: Props, children): Html` |
| Library | Only `<template fn>` at the top level, any name | One function for each template; `export` makes it public |
| Route | Lower case, `[` or `_` first: `index.ghtml`, `[slug].ghtml`, `_layout.ghtml` | `export function page(_p, children): Html` and `export const style`; see §7 |

A file has at most one `<script>`, before the markup. Its statements are:

| Statement | Meaning |
| --- | --- |
| `import …`, `type …`, `export type …` | Module code. |
| `prop name: Type;` | A prop of a component. `prop size: string = "m";` has a default (a literal or an expression of the file) that a caller may leave out, in any package: the prop is an optional field of `Props`, and the component fills the default. |
| `let name = value;` | State (a string, `i64`, `f64` or `bool`). Route files only. `@url` after the value keeps it in the address (`?name=value`), `@local` in the browser, `@cookie` in a cookie; see §4. |
| `const t = table<T>("order");` | A table of the page: `T` is a `@Model.Table` type, the string is its `ORDER BY`. Route files only. |
| `const d = expr;` | Derived when `expr` uses a name of the page (it is computed again in each render), else a module constant. |
| `function f(…) { … }` | A function. One that assigns state or writes a table is an *action*: see §4. |

Any other statement is an error that says it belongs in a function. A component and a layout keep no state, table or event:
the page keeps them and passes props. `me`, `params`, `request` and `error` are names that a route file has.

## 3. Markup

- `{expr}` is escaped text; `html={expr}` on `<template>` is raw `Html`.
- Directives are attributes: `if={c}`, `else-if={c}`, `else`, `each={x of xs}` or `each={x, i of xs}` with `key={k}`,
  `class:name={c}`. On one element `each` runs first and `if` runs for each item.
- `<template>` groups without an element: `<template if={c}>`, `<template match={v}><template case={Pattern(x)}>…`,
  `<template const={x = expr}/>`, `<template html={expr}/>`, and `<template fn="name(a: T)" export>…</template>` (a function of the file).
- A capital name is a component: `<Card title="x">…</Card>`. `<slot/>` is the children. A child with `slot="title"` (`<h2 slot="title">…</h2>`) is the content of `<slot name="title"/>` in the component, and `<slot/>` then holds the rest. A component needs no `import`: `Card.ghtml` is found in the folder
  of the file or its `components/` folder, then in each folder above, up to the root of the package. An `import` in the script wins.
- `<meta>` and `<link>` tags in a page or a layout go to the head of the document, once each (`name`, `property` or `http-equiv` is the key; the innermost page wins), and the kernel swaps them when it opens another page.
- `<title>` in a page is the title of the document, once, with the page's values (`<title>Todos ({n} open)</title>`); without it the first `<h1>` is the title.
- Not ghtml, each with a message that names the line: `{#if}` blocks, `bind:`, `use:`, `$props()`, `$state()`, `$effect`.

## 4. Events, state and binding

A page is a function of its state. An event is a button of a form that carries the state; the server runs it and answers the
new page, and the kernel morphs it in. Without JavaScript the same page is a plain form post.

- `onclick` goes on a `<button>`; `onsubmit` and `create={table}` go on a `<form>`. The value is a call (`todos.remove(t)`), a
  function name or statements separated by `;`. A button inside an `each` knows its row.
- A function that assigns state or writes a table (or calls one) gets the names it uses as extra parameters and returns nothing.
  A table write returns a reason when the Gate or the model refuses; the function stops there and the page answers 422.
- `value` or `checked` on an `input`, `select` or `textarea` is two-way when it is a state name (`value={filter}`) or a field of
  the row of an `each` over a table (`checked={t.done}`, needs `key`). Such a control is named by the page; it has no `name`.
  A state is sent on change; a field is written to its row on change, through the same Gate as an action.
- State travels in the hidden form `#_g` (`_s.name`), so a server keeps no session.
- `let q = "" @url;` is state that the address holds: `/?q=x` shows the page with `q` set, and a change of `q` is a step of the browser history.
  A state at its initial value is not in the address. `let tab = "a" @local;` is state the browser keeps for the next visit (`localStorage`, by
  address): the first render has the initial value, then the kernel restores the stored one. `let theme = "light" @cookie;` is state in the cookie `g.theme`
  (one year, `Path=/`, for every page that names it): the server reads it on a visit that the address does not set, so the first render already shows the person's choice.
  Without JavaScript none of the three is kept.
- A bound control of a row sends what it showed (`_o.…`) with its new value. The change is one `UPDATE` of that field; if another person changed the
  field meanwhile the change is refused (a 409 conflict): the page says "That Todo changed meanwhile. Read it again." and shows the new value. A control that shows what it
  posts did not change and is not written.

## 5. Tables and queries

`table<T>(order)` reads a `@Model.Table` type through the actor of the request (the Gate): the person sees and writes only what
the grants allow. `tbl.rows()` is every row (`each={t of todos}`), `tbl.length` and `tbl.count()` the total.

`tbl.where(row => cond)` and `tbl.count(row => cond)` are queries. The condition is read at build time into SQL with typed
parameters; the page values enter as parameters, never as text. A condition has `row.field` compared with a value of the page
(`===`, `!==`, `<`, `<=`, `>`, `>=`), `&&`, `||`, `!`, a boolean field, and `row.field.includes(v)`, `.startsWith(v)`, `.endsWith(v)`.
Anything else is a build error that names the line; a silent scan of rows in memory is a slow path nobody sees.

A table is live: the page carries `data-sub`, the page's live channel (one WebSocket at `/_s`) follows the table, and a change by anyone reaches
the page as the least that changed. The build puts a range (`<!--g:ID-->…<!--/g:ID-->`) around each part whose own code reads a table: a hole, an element with a reading
attribute, an `if`, a `match`, an `each`. Each poll of the channel (with the `_s.*` state of the page and its address) runs the page again for the
changed table and renders nothing but those ranges, so a count, a title and an empty note change with the row and nothing else is rendered or sent. An
`each` over a table whose body is one element that reads no other table is a list: only the rows of the changed keys are read (under the Gate, the
condition of the list and the state of the page), and the answer is those rows with their place and the keys that left. A list with a `limit`, or
whose rows read another table, is one range. The browser keeps the nodes that match (focus, selection, scroll) and changes the rest. A page that
cannot be told (the notice was lost, or a read failed) is fetched again.
GizDB is the default store; `Pages.hosted(sys, db, space, shapes, actor)` takes any `Data` database. Without one (`Pages.standalone`) a
table is empty and a write is refused.

`tbl.grid()` is the table as HTML: a `<giz-grid>` around a `<table>` with a head row of the field labels (`data-field` the name)
and a row for each row the person may read (`data-k` its key), with the text of each cell escaped. `tbl.editableGrid()` is the same
table whose cells of the fields that are not the key are bound controls, as `value={t.field}` in an `each`: a change updates that
field through the Gate, and a field that another person changed meanwhile is a conflict. Without script the editable grid is a form;
with the `giz-grid` enhancement ([components](../../web/components/README.md#library-elements)) RevoGrid Pro shows the table (sort,
filter, range selection, copy, paste, fill, export), and an edit changes the cell's control. A page with a design writes its own markup.

A page follows its tables only for a client that runs the kernel (a browser: `Sec-Fetch-Mode` or `x-giz` on the request). A crawler, a
`curl` or a link preview gets the page without a watch, so it holds nothing in the database. An engine watch is a cursor of one reader, a
connection holds 128 of them and a view that is not polled keeps its own for the 60 s lease; a page that finds no free watch is served
static until it is loaded again. A watch shared by pages with one query needs a feed in Data and is not built.

`tbl.where(row => cond, { order: "text desc", limit: 5 })` reads the first rows of that order (`order` is a literal of field names, `limit` a
value of the page). `tbl.removeWhere(row => cond)` deletes every row that the condition selects, as the person may, and stops at the first refusal.

`<form create={tbl}>` posts the fields named by its inputs (`name="text"` is the field `text` of `T`); the key is made by the page
and sorts by time.

A refused write keeps its own status: 422 for what the model refuses, 409 for a change that lost to another, 403 and 404 for the Gate. A read
that the database failed is not an empty list: the request answers 500 with the table and the reason. `server-timing` on each page answer
gives the time to render and the reads and writes it made, for the network panel of the browser. The live channel of a page tells a person of the
changes of rows that the person may read and of the removal of rows, and of nothing else (a row that the person may not read is never sent); it needs the tables of the host
(`Pages.hosted(…, shapes, …)`).

**Tools for an agent.** The tables of the host are also tools: `GET /_tools` lists `list_T`, `get_T`, `add_T`, `update_T` and `remove_T` for each
table `T`, each with a JSON Schema of its input from the same Shape (so a limit of a field is the same limit), and `POST /_tools/NAME` with a JSON
body runs one. A tool runs as the person of the request through the Gate, reads at most the rows a page reads, and refuses with the same
statuses and a JSON reason. A call must be `application/json`, so a form on another site cannot make it.

## 6. Style

`<style>` is scoped: the elements of the file get a class `g-HASH`, and the module exports the scoped CSS as `style`.

**Tailwind v4.** `web/tailwind.giz` runs the official standalone compiler (no Node) over the files of the site and writes the style sheet of only the
classes they use. The version is pinned in the module with the SHA-256 of each platform's executable: it is fetched once into
`$GIZLANG_CACHE/tailwind/v4.3.3/`, checked, and run from there (`GIZLANG_TAILWIND` names an executable of your own). The sheet is kept by the content of
the files, the tokens and the version, so an unchanged site never starts the compiler.

```gizlang
import {System} from "gizlang:system";
import * as Web from "gizweb";
import * as Pages from "gizweb:pages";
import * as Tailwind from "gizweb:tailwind";
import * as Tokens from "gizweb:tokens";

/** The pages of the site. Each server thread makes its own. */
function site(): Pages.Site { return Pages.site([Pages.markdown("/", "# Home")], [], []); }

/** Serves the site on port 8080, with the style sheet of the Tailwind classes that its files use. */
export function serve(sys: System, brand: string): i64 {
    const tokens = Tokens.css(Web.uiTheme().concat([Tokens.Theme("brand", brand)]), true)!;   // DTCG JSON -> @theme
    const css = Tailwind.stylesheet(sys, ["pages"], tokens, "")!;
    return Web.listen(sys, 8080, Pages.shell(css), Web.serving(), (own: System) => Pages.handler(site(), Pages.shell(css), Pages.standalone(own)));
}
```

- `class="p-4 text-brand-500"` and `class:font-bold={bold}` both work: the compiler cannot see a `class:` directive, so the module lists the names after
  `class:` in the `.ghtml` files as `@source inline(…)`.
- `Pages.shell(css)` puts the built-in design system in the `components` layer, so a utility on an element beats a component's default, and the scoped
  `<style>` of a component beats a utility. `Pages.shell("")` is the design system alone.
- `dark:` follows the colour scheme of the browser, and `data-theme="dark"` or `"light"` on the root forces it.

**Design tokens.** `web/tokens.giz` reads tokens in the DTCG format and writes CSS custom properties: `color.brand.500` is `--color-brand-500`, so under
Tailwind it gives `bg-brand-500`; an alias `{color.brand.500}` is `var(--color-brand-500)`. With Tailwind the base theme is its `@theme`, else `:root`.
A second file replaces tokens: `dark` applies by the colour scheme and by `data-theme="dark"`, any other name (a brand, a tenant) by
`data-theme="NAME"`. A token that an overlay names and the base does not, a value of the wrong shape and JSON that does not parse are errors that name the
file and the token.

**The default theme.** `web/theme/base.json` and its `dark.json` overlay (`Web.uiTheme()`) are the tokens of the built-in components: colors
(`bg`, `surface`, `hover`, `fg`, `muted`, `line`, `line-strong`, `accent`, `on-accent`, `link` and the four states), `font`, `text` (12 to 28 px),
`radius`, `shadow`, `spacing` (4 px) and `control.h` (32 px). `ui.css` uses only these, so a site restyles every component with an overlay of a few
tokens and never copies a rule. `Web.uiStyle()` is the theme as `:root` and the components; with Tailwind, the theme goes into `@theme` and
`Pages.shell(css)` adds the components alone (`Web.uiComponents()`).

## 7. Pages: a folder of files is a site

A program imports `site` from the module `.routes` of a folder; the CLI writes that module from the files below the folder:

```
pages/
  _layout.ghtml        wraps every page below it
  index.ghtml          /
  about.md             /about        (Markdown, in the layout)
  raw.html             /raw          (sent as it is)
  404.ghtml            the page of an unknown address, status 404
  blog/
    _layout.ghtml      wraps the pages of /blog, inside the root layout
    index.ghtml        /blog
    [slug].ghtml       /blog/{slug}  (params.get("slug"))
  Card.ghtml           a component: no page
```

```
import {System} from "gizlang:system";
import * as Web from "gizweb";
import * as Pages from "gizweb:pages";
import {site} from "./pages/.routes";

function main(sys: System): i64 {
    return Web.listen(sys, 8080, Pages.shell(""), Web.serving(), (own: System) => Pages.handler(site(), Pages.shell(""), Pages.standalone(own)));
}
```

A route file has `params` (the `[name]` values), `request` (the `Http.Request`) and `me` (the actor) as names; `<slot/>` in a layout is the page inside it. A page is the body of the page shell (`Web.listen` gives the design tokens, the kernel, ETags and HTTPS); the title is its first
`<h1>`, and the scoped styles of the page and its layouts come first. A page that starts with `<!doctype` is sent as it is. Two files with one address, or a folder
without a page, stop the build with a message that names the files. A name that starts with `.` or `_` (except
`_layout.ghtml`), a capital letter first, and other file types are no page.

## 8. Compiled by the CLI

Every command that builds a program (`run`, `live`, `check`, `build`, `test`) reads `.ghtml` imports. A library (`web/`) may
hold `.ghtml` files and import them from each other.

## 9. Languages

A site in several languages is written in English, with a catalog `locales/TAG.json` beside its pages for each other language
([MULTILINGUAL-ARCHITECTURE](../internals/localization.md)). The text a person reads (a run of text, holes and inline elements, and
attributes such as `title`, `alt`, `placeholder` and `aria-label`) is a message keyed by itself in MessageFormat 2:

```
<p>Read the <a href="/terms">terms</a>, {user.name}.</p>   →   "Read the {#a}terms{/a}, {$name}."
```

- A page has the ambient `locale` (the request's language, a `Locale` of `gizlang:locale`) and `languages` (the site's). A component
  translates its text when it declares `prop locale: Locale;`, and a caller passes it: `<Card {locale}/>`.
- `<template plural={n}>` with `<template case="one">`, `case="other"` and exact counts (`case="=0"`) is one message whose translation
  names the plural categories of its own language.
- `translate="no"` and `pre`, `code`, `kbd`, `samp`, `var` and `textarea` keep their text.
- `giz i18n pages --add ko` starts a catalog, `giz i18n pages` adds the new messages to each catalog, and `--check` fails CI on
  a catalog that is behind. A translation that drops an element or names a missing placeholder stops the build at its line.
- `/ko/…` is the Korean address of a page, `?hl=ko` the person's choice, and a browser opens its own language;
  `<LanguagePicker {locale} {languages} path={request.path}/>` links the languages. A file with no catalog compiles as it did.
