# hemx — Semantic, Laterally HX hemx makes server-rendered HTML feel like it grew just enough interactivity. App authors change state in Rust, render hemplate partials, and return generated UI intent; the runtime swaps generated slots without selectors, a VDOM, or a client app state framework. > **hemplate owns syntax. hemplate emits surface. hemx consumes surface. hemx owns semantics. JS applies effects.** --- ## laws ### req: law/001 001 A feature belongs in core only if it can be expressed as typed resources plus EffectBatch ops. ### req: law/002 002 A feature belongs in an integration crate if it depends on transport, framework, auth, storage, browser capability, or deployment policy. ### req: law/003 003 A feature belongs in generated API if it improves author ergonomics without adding runtime semantics. ### req: law/004 004 A feature belongs in user code if it is business logic, domain validation, routing policy, authorization policy, persistence, or layout choice. ### req: law/005 005 Add one primitive only if it removes at least five special cases. --- ## pitch ### req: pitch/001 001 hemx is checked hypermedia for Rust: authors write `.heml`, write Rust handlers, and return generated UI commands while the compiler checks every cross-file reference. [north_star] ### req: pitch/005 005 For ordinary server-first apps, no app-owned JavaScript is required: the browser runtime only sees lowered ids and effect bytes. [north_star] ### req: pitch/002 002 No CSS selectors. No hx-* strings. No virtual DOM. No client framework. No hidden global proxy magic. No component hydration. SSR state bootstrap via `data-hemx-st` is allowed, but the browser never reconstructs a component tree. [north_star] ### req: pitch/003 003 hemx replaces React/Vue not with a UI framework, but with a compiler contract: hemplate knows the surface, Rust knows the types, hemx knows the effects, the browser only executes commands. [north_star] ### req: pitch/004 004 The north-star feel: **boring server-rendered HTML with typed, selectorless partial swaps.** Short handler bodies, compile-checked HTML contracts, plain Rust state changes, hemplate-rendered partials, and a tiny runtime that applies effects. [north_star] --- ## canonical_authoring ### req: canonical/001 001 The canonical app shape is templates plus Rust, not a frontend folder: `.heml` files declare `data-hemx-root`, `data-hemx-slot`, `data-hemx-handle`, `data-hemx-form`, `h-key`, optional pending/page/island facts, and handlers return generated UI commands. Ordinary app code avoids selectors, numeric ids, raw effects, wire formats, manual registries/form parsing, raw `SafeHtml`, and raw render calls. Plain CSS owns appearance. [north_star] ### req: canonical/002 002 Typed partial swaps are the primary UX, not an advanced feature: handlers change domain state in Rust, convert domain values into view values, render hemplate partials through generated helpers, and place them into generated targets. The real primitive is generated target + rendered partial + swap kind. [north_star] ### req: canonical/003 003 Canonical keyed-row CRUD reads like ordinary Rust intent: create appends a rendered row partial, update/toggle replaces a keyed row partial, delete removes a keyed row, summary/text/form effects compose in tuples, arrays, or `Vec` for dynamic batches, and no handler chooses a target with a CSS selector. [north_star] ### req: canonical/004 004 Generated helpers may compose only facts uniquely known from templates and checked Rust types: template, slot, optional key, form/control, class token, explicit island/event marker, and effect kind. If a handler parameter, key, form, target, raw route, or legacy target would require guessing, the user must say it explicitly and diagnostics must point to the Rust and hemplate spans. [north_star] ### req: canonical/005 005 Generated helpers name UI intent without mixing domain work: target helpers read as UI effects such as `replace`, `append`, `remove`, `set`, `clear`, `focus`, `set_attr`, or `emit` on generated page, slot, form, class, attribute, or island handles. [north_star] ### req: canonical/009 009 Generated helpers must not combine persistence, routing, rendering, target selection, or domain policy into generic commands such as `refresh`, `save_and_update`, `sync_component`, or `rerender`. [north_star] ### req: canonical/006 006 There is no separate beginner API and expert API. The simple generated shape is canonical and should scale: generated slots, partials, forms, class constants, islands/events, and page helpers are normal. Explicit primitives, raw targets, raw HTML, raw effects, manual registries/form parsing, low-level ids/opcodes, wire formats, and raw routes remain named escape hatches or internals around the same render/target/effect/transport model. [north_star] ### req: canonical/007 007 Opaque islands are explicit leaf adapters: templates declare `data-hemx-island` and optional generated handles/events; server code may emit snapshots/events such as `ui::game.emit(event)`, while island JS owns only high-frequency local behavior. Islands do not introduce a component runtime, client state graph, VDOM, or second UI model. [north_star] ### req: canonical/008 008 Offline/PWA support is opt-in adapter territory. Server-first hemx may fail interactions while offline; cached shells and local-sync queues live in crates such as `hemx-pwa` or `hemx-sync`, reuse generated slots/effects, queue explicit patches, and reconcile with server-canonical effects. Core hemx must not gain a mandatory client state graph, scheduler, CRDT, or local app runtime. [north_star] --- ## modes ### req: mode/001 001 hemx has one core authoring loop: render a partial and place it into a generated target with a swap kind. HTTP handlers, page navigation, push streams, and island events are transport/adapters around that loop. ### req: mode/002 002 Page Enhancer mode is a specialized partial swap for navigation: it updates generated page/content/title/nav targets and adds history, scroll, shell, and fallback behavior. Authors use real anchors with `data-hemx-nav` or `data-hemx-boost`; no user-authored handler is required for ordinary navigation. ### req: mode/003 003 Interaction Handler mode handles forms, buttons, typed params, and generated partial/text/form/island effects through Rust handlers. ### req: mode/004 004 Beginner docs must teach server-first typed partial swaps first, Page Enhancer as navigation around the same slot/effect model, explicit islands for leaf widgets, client-local/WASM only for local high-frequency behavior, and sync/offline last as opt-in adapters. --- ## dx ### req: dx/001 001 The common case must feel like writing server-rendered HTML plus tiny Rust handlers: template, state, hemplate partials, and generated UI swaps. Users should not need to understand Surface IR, ResourceId, EffectWriter, postcard, runtime opcodes, selector targeting, or manual registries for basic apps. [north_star] ### req: dx/002 002 The happy path is: write `.heml`, write a Rust handler, return generated partial/text/form/page/island commands. No manual ids, no manual registry, no manual serialization, no CSS selector targets, no raw render calls, and no manual JavaScript for ordinary app UI. [north_star] ### req: dx/003 003 Public APIs are generated around the user's names. If the template declares `data-hemx-slot="todo_list"`, the user gets `slots::todo_list`, not `SlotId(12)`. ### req: dx/004 004 Common handlers must fit in a small function. Advanced contexts (`EffectWriter`, raw ops, custom encoders) exist but are not part of the beginner path. ### req: dx/005 005 Error messages must explain fixes in author language, not internal language. Say “add `h-key="todo.id"` to this `h-for`”, not “missing ScopeKey for ResourceRef”. ### req: dx/006 006 Generated object-like helpers are the preferred authoring API and are re-exported at the component root, e.g. `todos.append(todo)`, `todo_row.replace(todo)`, `summary.set(text)`, `new_todo.clear()`, and `page.replace(view)`. [north_star] ### req: dx/009 009 Generated object-like helpers hide hemplate rendering and resource lowering in the common path. [north_star] ### req: dx/010 010 Namespaced `targets`, `handles`, `forms`, raw `advanced::slots`, explicit `render(view)`, `target(name)`, `html(value)`, `lower(html)`, and raw effect constructors are compatibility or escape-hatch surfaces, not beginner-prelude exports, canonical handler examples, or ordinary docs. [north_star] ### req: dx/007 007 Tuple composition of `IntoEffect` is the canonical batch syntax: `(a, b, c)` implements `IntoEffect` up to arity 12. `Effect::batch((...))` is available but not required for the happy path. ### req: dx/008 008 User-authored JavaScript is never required for standard forms, lists, navigation, optimistic actions, or server push. Custom JS is only needed at opaque leaf boundaries such as charts, maps, editors, and Web Components. --- ## ceremony ### req: ceremony/001 001 A minimal counter app requires one `.heml` file, one Rust state struct, and one handler function. No manual registry, no manual route table, no manual JS. Under 50 lines of user-authored Rust plus one template. ### req: ceremony/002 002 Generated modules are imported through a prelude or component namespace. Users should not manually include `$OUT_DIR` files in normal apps. ### req: ceremony/003 003 `build.rs` must be a one-liner for the common case: `fn main() { hemx_build::app().run().unwrap(); }` ### req: ceremony/004 004 No API may require users to write numeric ids, raw ResourceIds, raw opcodes, or serialized payloads in normal code. ### req: ceremony/005 005 `cargo run -p hemx-xtask -- app new PATH` creates a generic checked-hypermedia scaffold with a page, form, keyed row partial, notice slot, Rust handlers, and tests using generated helpers instead of raw ids, opcodes, selector UI JavaScript, or manual registry plumbing. [north_star] ### req: ceremony/006 006 `cargo run -p hemx-xtask -- app new --mobile PATH` creates a phone-first starter with a real page/form/keyed partial/notice flow, typed host capability round trip, app-owned command/event/projection recovery truth, and inspectable mobile release/verify metadata without adding a hemx mobile framework, client store, signing-secret owner, or store submission bot. [north_star] --- ## progressive_disclosure ### req: pd/001 001 A beginner can build CRUD with only: `.heml`, Rust handlers, `Form` or typed params, generated object-like UI helpers, and `impl IntoEffect`. ### req: pd/002 002 Atoms are not required for basic server-first apps. They appear only when client-local state, SSR bootstrapped state, or WASM handlers are used. ### req: pd/003 003 Sync, transitions, resources/queries, islands, capabilities, and raw EffectWriter are advanced layers. They must not appear in starter examples. ### req: pd/004 004 Documentation must present levels as adapters around the same core: server-first partial swaps; cached Page/PWA shell; explicit leaf islands or client-local handlers for high-frequency behavior; hybrid sync/offline queues last. Each level introduces only the new primitive it needs. --- ## page_swap ### req: page_swap/001 001 Page swapping is a first-class specialization of partial swapping: render a page partial, place it into generated page targets, then apply history/title/scroll/shell behavior. Authors mark real anchors with `data-hemx-nav`; links keep valid `href` and work without JS. Missing or empty static `href` on a `data-hemx-nav` anchor is a build error. ### req: page_swap/002 002 A `data-hemx-nav` click fetches the target URL as a hemx partial request. The response is conceptually `ui::content.replace(page)` plus optional generated nav/title targets and a `Navigate` effect; it must not introduce selector targeting or a second page-specific UI model. ### req: page_swap/003 003 Page swapping uses generated targets, not CSS selectors. The default content target is the generated slot named `content`, not `#content`; explicit page helpers such as `ui::content.page(req, view)` or `request.page_html(ui::content.render(view), shell)` are adapters around the same partial-swap primitive. ### req: page_swap/004 004 Ordinary page navigation must not require user-authored handlers. Explicit navigation handlers are available only when custom application logic is needed, and they still return generated target/page commands. ### req: page_swap/005 005 Browser back/forward is supported. On `popstate`, hemx fetches the URL as a partial request and applies the same page-swap update without pushing a new history entry. ### req: page_swap/006 006 If a page lacks the expected `content` slot, hemx-axum falls back to normal browser navigation in production and emits a diagnostic in development. ### req: page_swap/007 007 `data-hemx-boost` progressively enhances descendant same-origin anchors and forms. It is a container convention, not a replacement for `data-hemx-nav` on an anchor or `data-hemx-handle` on a form; placing it directly on static anchors or forms is a build error. Links behave like `data-hemx-nav`; forms behave like hemx form submissions. External links, downloads, new-tab links, and modified-clicks preserve native browser behavior. --- ## htmx_equivalents ### req: htmx/001 001 hemx replaces common HTMX use-cases through typed equivalents, not HTMX syntax. ### req: htmx/002 002 Easy equivalents must exist for: generated target replacement, append/prepend/remove, boosted links/forms, page swap, form submit, loading indicators, confirmation, debounce/throttle, polling, history navigation, multi-target updates, response events, SSE/push, validation errors, modals, toasts, table rows, SVG fragments, and form error regions. The copy-paste HTML pattern gallery must cover the core CRUD/form/search/load patterns through boring `.heml`, generated targets/forms/handles, and server-owned Rust state before adding plugin-shaped or browser-policy-heavy patterns. [north_star] ### req: htmx/003 003 hemx core deliberately does not clone selector-based HTMX features: `hx-target` selectors, `hx-select`, `hx-include` selectors, `closest/find/this` target strings, or trigger mini-languages. Equivalent patterns use generated targets, typed params, forms, explicit handlers, and page/push adapters around partial swaps. --- ## component ### req: component/001 001 The primary authoring unit is a hemplate component plus adjacent Rust handlers. A component owns a template root, generated slots, generated handles, generated form checks, and source spans. ### req: component/002 002 hemx supports colocated layout: `todo_list.heml` beside `todo_list.rs`, with generated APIs namespaced by component to avoid global symbol soup. ### req: component/003 003 Generated APIs are component-namespaced by default: `ui::todo_list::todo_row`, `ui::todo_list::create`, `ui::todo_list::new_todo`, and `ui::todo_list::COMPONENT` as a checked `ComponentRef`. Category modules such as `targets`, `handles`, and `forms` remain available for organization/compatibility, while raw slot constants live under `advanced::slots`; global exports (`ui::slots::*`, `ui::handles::*`, `ui::components::*`) are opt-in only. ### req: component/004 004 `#[hemx::surface]` bridges generated code into a user module. Users write `#[hemx::surface] mod ui {}` instead of `include!(concat!(env!("OUT_DIR"), ...))`. hemx-build emits `hemx.generated.rs` which the macro expands in place. No direct `$OUT_DIR` includes in user-authored source. ### req: component/005 005 An optional `#[hemx::component]` macro may validate that every handle declared in the template Surface has a corresponding `#[hemx::handler]` within the annotated module. This is the only macro with cross-handler visibility inside a single module; it remains strictly local. Missing handlers without `#[hemx::component]` are caught at app mount or test time, not `cargo check`. ### req: component/006 006 `#[derive(Hemplate)]` structs are natural component boundaries. hemx_build discovers them automatically; no additional configuration is required for most apps. --- ## surface ### req: surface/001 001 `hemplate_build` may scan `.heml` files and emit `$OUT_DIR/hemplate.surface.postcard` (postcard-encoded, deterministic, versioned). `hemx_build` may also be called in-process with precomputed hemplate Surface facts or, for simple build scripts, ask hemplate to parse/extract the Surface before hemx interprets it. hemx owns no independent `.heml` parser. ### req: surface/002 002 The Surface contains: nodes (NodeId, parent, scope, element, attrs, source span), scopes (ScopeKind: Root | If | Match | Case | For { binding, key_expr }), forms (form controls with raw HTML types), and component uses. ### req: surface/003 003 Node identity is `NodeId` in a parent/scope graph. No `css_path` is used as a primary identifier. An optional `debug_path` string may exist for diagnostics only. ### req: surface/004 004 Form controls in the Surface carry raw HTML facts: `ControlKind::Text`, `ControlKind::Number { min, max, step }`, `ControlKind::Checkbox`, `ControlKind::Select { multiple, options }`, etc. No Rust type mapping lives in hemplate. ### req: surface/005 005 Loop scopes expose the binding name and an optional `key_expr` (e.g. `todo.id`). hemplate does not enforce key usage; it only records it for consumers. hemx_build enforces key presence only when a hemx-addressable node appears inside the loop. ### req: surface/006 006 Surface schema is versioned (`schema_version: u32`). Postcard encoding, no JSON. `no_std`-compatible schema definition so any tool can read it without heavy dependencies. ### req: surface/007 007 `hemplate-derive` does not write Surface files. Surface generation is a `build.rs` / `hemplate_build` concern, proc-macro side-effect free. ### req: surface/008 008 The Surface records hemplate structural directives as first-class facts: `h-for`, `h-key`, `h-if`, `h-else-if`, `h-else`, `h-match`, `h-case`, dynamic `+attr` bindings, and interpolated attr/text expressions. hemx consumes these facts; if a build script points hemx_build at `.heml` files, hemplate still performs parsing and Surface extraction. ### req: surface/009 009 Raw/pre-rendered HTML insertions are opaque Surface holes. The parent element is present; hemx_build emits the appropriate rendering call. ### req: surface/010 010 Attribute values preserve their origin: static literal, dynamic `+attr` binding, or interpolated expression. hemx-build uses this to determine whether a `data-*` handle param is statically known or runtime-extracted. --- ## codegen ### req: codegen/001 001 `hemx_build` generates three artifacts from the generic Surface IR: (a) `hemx.generated.rs` containing ergonomic resource modules (`slots`, `targets`, `handles`, `forms`, `atoms`), (b) `hemx.syms` for proc-macro validation, (c) runtime id-lowering tables. `hemx_build` interprets tool-specific conventions (`data-hemx-*`, `h-for`, `h-key`, form controls) from the Surface. [north_star] ### req: codegen/002 002 Generated view modules expose ergonomic root-level target objects and commands that hide render/lower details: text slots provide `set(text)`, singleton partial slots provide `replace(view)`/`clear()`, keyed collection slots provide `append(view)`, `prepend(view)`, `replace(view)`, `remove(key_or_view)`, and forms provide `clear()`/`clear(field)`/`focus(field)`. String-keyed target objects accept displayable domain ids without caller-side `.to_string()` noise. Commands return `impl IntoEffect`, compose in plain Rust, preserve generated lowering, and fail to generate when the template lacks enough facts to infer the slot, key, form, or renderable view type. ### req: codegen/003 003 Generated module `handles` exports typed constants: `Handle` where `I` is `Form`, a param type, or `()`. Users rarely reference handles directly; they are consumed by `#[hemx::handler]` for validation. ### req: codegen/004 004 Generated module `forms` exports `FormContract` metadata (field names, HTML control kinds, required). `#[hemx::handler]` compares the `Form` type against the contract. Domain types (`Email`, `TodoId`) remain user-authored; no auto-generated structs. ### req: codegen/005 005 Generated module `atoms` exports `Atom` for values that must be addressable, bootstrapped, or synced. Ordinary Rust fields on app/components are not automatically atoms. ### req: codegen/006 006 `hemx-build` discovers `data-hemx-on` event names from hemplate Surface inputs and emits generated `hemx::EventName` constants. Event constants are metadata for checked Rust authoring and diagnostics; they do not create a trigger mini-language or new browser runtime semantics. [north_star] --- ## public_api ### req: public_api/001 001 The generated API is the primary public authoring API. Most user code should return generated partial, text, keyed-row, form, page, nav, or island/event commands, not raw `Effect` constructors or raw render/lower calls. ### req: public_api/002 002 `Effect`, `EffectWriter`, `ResourceId`, `ResourceRef`, and raw opcodes are advanced APIs. They must not appear in beginner docs, generated examples, or common diagnostics. ### req: public_api/003 003 Every generated command returns `impl IntoEffect` and composes through tuple composition. ### req: public_api/004 004 If a common UI operation requires raw `EffectWriter`, the public API is considered incomplete. ### req: public_api/005 005 Beginner-facing page/template composition uses generated render or page helpers. Direct `SafeHtml` construction, raw `html(...)`, raw `target(...)`, raw route fragments, `hemx::advanced::render(...)`, and explicit `ui::render(...)` calls are advanced escape hatches and must not appear in beginner examples or docs. Server-rendered page boundaries may use `hemx::page(...)`; handlers and ordinary partial updates must use generated target/form/page commands. --- ## effect_algebra ### req: effect_algebra/001 001 The canonical op set is minimal and closed: `Put`, `Insert`, `Remove`, `Move`, `Focus`, `Navigate`, `Emit`. ### req: effect_algebra/002 002 `Put` replaces the payload of a resource. For a Slot, this means replacing its rendered contents. For an Atom, this means replacing its stored value. ### req: effect_algebra/003 003 `Insert`, `Remove`, and `Move` operate on keyed collection resources. They require a key type checked by generated `KeyedSlot` wrappers. ### req: effect_algebra/004 004 `Navigate` changes browser history or represents a server redirect. Route matching remains outside hemx core. ### req: effect_algebra/005 005 `Emit` dispatches a native `CustomEvent` and is the only raw JS interop primitive in core. ### req: effect_algebra/006 006 DOM-specific operations such as `innerHTML`, `textContent`, class toggles, or keyed node lookup are runtime lowering details, not separate author-facing concepts. --- ## typed_id ### req: typed_id/001 001 All public cross-page identifiers (`Slot`, `Atom`, `Handle`, `Form`) share a single internal primitive `ResourceId { kind: ResourceKind, id: u32 }`. A concrete runtime target is a `ResourceRef { resource: ResourceId, scope: Option }`. Typed wrappers (`Slot`, `KeyedSlot`, `Atom`, `Handle`, `Form`) enforce kind safety at compile time. No special-case opcodes per resource kind; effects address resources uniformly. [north_star] ### req: typed_id/002 002 `ResourceKind` is an internal closed enum (Slot, Atom, Handle, Form). Navigation is represented by `Navigate` effects, not by route resources. External crates may not add variants. Extensibility comes via `Effect::event` or custom `IntoEffect` implementations, never via new `ResourceKind` variants in core. `Effect::event` lowers to the canonical `Emit` opcode. --- ## scope ### req: scope/001 001 `Scope` is a first-class primitive. Keyed loops (`h-for`) create keyed dynamic scopes and require `h-key` for hemx-addressable nodes. Conditional branches (`h-if`, `h-else-if`, `h-else`, `h-match`, `h-case`) create optional presence scopes. Component instances, modals, tabs, and nested forms are scoped resources. Concrete runtime targets are addressed through `ResourceRef` `{ resource: ResourceId, scope: Option }`. [north_star] --- ## list ### req: list/001 001 Any `data-hemx-slot` or `data-hemx-handle` inside a hemplate `h-for` scope requires a stable key. Preferred syntax: ``. Without a key, hemx-addressable nodes inside the loop are rejected at build time. Keyed identity is `ResourceRef { resource: ResourceId, scope: Some(ScopeKey::KeyValue(...)) }`. ### req: list/002 002 Slots inside a keyed loop receive a composite identity. hemplate records `key_expr` in the Surface; hemx implements keyed slot lookups. ### req: list/003 003 Generated helpers for keyed slots are `append(view)`, `prepend(view)`, `replace(view)`, and `remove(key_or_view)` when the template and view type provide an unambiguous `h-key`. Compatibility functions such as `append(keyed_slot, key, view)` may exist as explicit low-level forms. Mismatch between key type and slot key type is a compile-time error, and missing/ambiguous keys are build errors with template spans. --- ## form ### req: form/001 001 Forms are source of truth in HTML. hemplate Surface exports form shape (controls, names, required, types). hemx checks compatibility with Rust `Form` types through user-authored `#[hemx::form("...")]` domain structs and generated form metadata. No auto-generated structs; domain types (e.g. `Email`) are first-class. The Surface describes; Rust owns; hemx checks. ### req: form/002 002 The handle id is carried as `__h` in POST `application/x-www-form-urlencoded`. A JSON body is allowed at the integration boundary (`application/json`) only if the handler accepts it; core uses form encoding. ### req: form/003 003 Handler receives `form: Form`. Validation errors target `(FormId, field_name)` or generated control ids. The runtime maps them to originating form controls via control ids derived from Surface `NodeId`, not via slot ids. ### req: form/004 004 Form compatibility checks validate field presence, optionality, multiplicity, and parser availability. Parser availability means the submitted value type implements `hemx::FormValue` (blanket-provided for `FromStr`, or explicitly implemented for custom parsers). Domain validation remains Rust logic (`TryFrom`, custom validators, or handler code). ### req: form/005 005 HTML control facts are lower bounds, not complete domain semantics. `type="email"` may require a Rust `Email` parser, but hemplate never defines what a valid business email is. ### req: form/006 006 Generated diagnostics distinguish structure errors from validation errors: missing field / wrong optionality are compile-time issues; invalid submitted values are runtime form errors. --- ## form_effects ### req: form_effects/001 001 Generated form APIs provide common commands: `clear()`, `clear(field)`, `reset()`, `error(field, message)`, `focus(field)`, and `disable_while_pending()`. `form.clear()` clears the canonical generated form without requiring callers to name raw control ids. ### req: form_effects/002 002 Form effects target generated form/control ids, not CSS selectors. ### req: form_effects/003 003 Templates may declare error display targets with `data-hemx-error-for="field"`. Generated form error effects render into those targets when present and fall back to control validity APIs otherwise. --- ## wire ### req: wire/001 001 Authoring HTML uses symbolic `data-hemx-*` attributes. Rendered runtime HTML lowers these to compact numeric metadata: `data-hid`, `data-sid`, optional `data-key`, optional atom ids, optional form/control ids, and `data-hemx-st` for state bootstrap. The browser never sees handler or slot names. `data-hemx-root` marks a scoped root boundary. ### req: wire/002 002 POST bodies carry `application/x-www-form-urlencoded` with distinguished field `__h` (handle id). Server routes by numeric id, not by URL path. ### req: wire/003 003 HTTP interaction responses may be `text/html` fragments containing `