# 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 0 001 A feature belongs in core only if it can be expressed as typed resources plus EffectBatch ops. [north_star] ### req: law/002 0 002 A feature belongs in an integration crate if it depends on transport, framework, auth, storage, browser capability, or deployment policy. [north_star] ### req: law/003 0 003 A feature belongs in generated API if it improves author ergonomics without adding runtime semantics. [north_star] ### req: law/004 0 004 A feature belongs in user code if it is business logic, domain validation, routing policy, authorization policy, persistence, or layout choice. [north_star] ### req: law/005 0 005 Add one primitive only if it removes at least five special cases. [north_star] --- ## pitch ### req: pitch/001 0 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 0 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 0 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 0 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 0 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 0 001 The canonical app shape is templates plus Rust, not a frontend folder: `.heml` files declare roots, slots, handles, forms, keys, and optional pending/page/island facts; handlers return generated UI commands. [north_star] ### req: canonical/010 0 010 Ordinary app code avoids selectors, numeric ids, raw effects, wire formats, manual registries/form parsing, raw `SafeHtml`, and raw render calls. [north_star] ### req: canonical/011 0 011 Plain CSS owns appearance. [north_star] ### req: canonical/002 0 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, and place them into generated targets. [north_star] ### req: canonical/014 0 014 The partial-swap primitive is generated target plus rendered partial plus swap kind. [north_star] ### req: canonical/003 0 003 Canonical keyed-row CRUD reads like ordinary Rust intent: create appends a rendered row partial, update/toggle replaces a keyed row partial, and delete removes a keyed row. [north_star] ### req: canonical/015 0 015 Summary, text, and 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 0 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. [north_star] ### req: canonical/016 0 016 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 0 005 Generated helpers name UI intent without mixing domain work: 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 0 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 0 006 There is no separate beginner API and expert API; the simple generated shape is canonical. [north_star] ### req: canonical/012 0 012 Generated slots, partials, forms, class constants, islands/events, and page helpers are normal authoring surfaces. [north_star] ### req: canonical/013 0 013 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 0 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)`. [north_star] ### req: canonical/017 0 017 Island JavaScript owns only high-frequency local behavior and must not introduce a component runtime, client state graph, VDOM, or second UI model. [north_star] ### req: canonical/008 0 008 Offline/PWA support is opt-in adapter territory; server-first hemx may fail interactions while offline. [north_star] ### req: canonical/018 0 018 Cached shells and local-sync queues live in crates such as `hemx-pwa` or `hemx-sync`; they reuse generated slots/effects, queue explicit patches, and reconcile with server-canonical effects. [north_star] ### req: canonical/019 0 019 Core hemx must not gain a mandatory client state graph, scheduler, CRDT, or local app runtime. [north_star] --- ## modes ### req: mode/001 0 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. [north_star] ### req: mode/002 0 002 Page Enhancer is navigation as partial swap: it updates generated page/content/title/nav targets, history, scroll, shell, and fallback behavior. Authors use `data-hemx-nav`/`data-hemx-boost` anchors; ordinary navigation needs no handler. [north_star] ### req: mode/003 0 003 Interaction Handler mode handles forms, buttons, typed params, and generated partial/text/form/island effects through Rust handlers. [north_star] ### req: mode/004 0 004 Beginner docs teach server-first typed partial swaps first, Page Enhancer around the same slot/effect model, explicit leaf-widget islands, client-local/WASM only for high-frequency local behavior, and sync/offline last as opt-in adapters. [north_star] --- ## dx ### req: dx/001 0 001 Common apps feel like HTML plus tiny Rust handlers: templates, state, hemplate partials, and generated UI swaps. Basic apps hide Surface IR, ResourceId, EffectWriter, postcard, runtime opcodes, selectors, and registries. [north_star] ### req: dx/002 0 002 Happy path: write `.heml`, write a Rust handler, return generated partial/text/form/page/island commands. Ordinary app UI uses no manual ids, registry, serialization, selector targets, raw render calls, or JavaScript. [north_star] ### req: dx/003 0 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 0 004 Common handlers fit in a small function. Advanced contexts (`EffectWriter`, raw ops, custom encoders) exist but are not part of the beginner path. ### req: dx/005 0 005 Error messages 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 0 006 Generated object-like helpers are the preferred authoring API and are re-exported at the component root, including `todos.append(todo)`, `todo_row.replace(todo)`, `summary.set(text)`, `new_todo.clear()`, and `page.replace(view)`. [north_star] ### req: dx/009 0 009 Generated object-like helpers hide hemplate rendering and resource lowering in the common path. [north_star] ### req: dx/010 0 010 Namespaced `targets`/`handles`/`forms`, raw `advanced::slots`, explicit render/target/html/lower helpers, and raw effect constructors are compatibility surfaces, not beginner-prelude exports, canonical examples, or ordinary docs. [north_star] ### req: dx/007 0 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 0 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 0 001 A minimal counter app requires one `.heml` file, one Rust state struct, and one handler function. No manual registry, route table, or JS; under 50 lines of Rust plus one template. ### req: ceremony/002 0 002 Generated modules are imported through a prelude or component namespace. Normal apps do not manually include `$OUT_DIR` files. ### req: ceremony/003 0 003 `build.rs` must be a one-liner for the common case: `fn main() { hemx_build::app().run().unwrap(); }` ### req: ceremony/004 0 004 No API may require users to write numeric ids, raw ResourceIds, raw opcodes, or serialized payloads in normal code. ### req: ceremony/005 0 005 `cargo run -p hemx-xtask -- app new PATH` creates a generic scaffold with page, form, keyed row partial, notice slot, handlers, and tests using generated helpers instead of raw ids, opcodes, selector UI JavaScript, or manual registries. [north_star] ### req: ceremony/006 0 006 `cargo run -p hemx-xtask -- app new --mobile PATH` creates a phone-first starter with page/form/keyed partial/notice flow, typed host capability round trip, app-owned recovery truth, and inspectable mobile release/verify metadata. [north_star] ### req: ceremony/007 0 007 The phone-first starter adds no hemx mobile framework, client store, signing-secret owner, or store-submission bot. [north_star] --- ## progressive_disclosure ### req: pd/001 0 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 0 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 0 003 Sync, transitions, resources/queries, islands, capabilities, and raw EffectWriter are advanced layers. They must not appear in starter examples. ### req: pd/004 0 004 Docs present levels as adapters around the same core: server-first partial swaps; cached Page/PWA shell; leaf islands or client-local handlers for high-frequency behavior; hybrid sync/offline queues last. Each level introduces only its new primitive. --- ## page_swap ### req: page_swap/001 0 001 Page swapping specializes partial swapping: render page partials into generated targets, then apply history/title/scroll/shell behavior. `data-hemx-nav` anchors keep valid `href` and work without JS; missing/empty static `href` fails build. [north_star] ### req: page_swap/002 0 002 A `data-hemx-nav` click fetches the target URL as a hemx partial request. The response is `ui::content.replace(page)` plus optional generated nav/title targets and `Navigate`; it must not add selector targeting or a second page UI model. [north_star] ### req: page_swap/003 0 003 Page swapping uses generated targets, not CSS selectors. Default content target is generated slot `content`, not `#content`; page helpers like `ui::content.page(...)` or `request.page_html(...)` adapt the same partial-swap primitive. [north_star] ### req: page_swap/004 0 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. [north_star] ### req: page_swap/005 0 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. [north_star] ### req: page_swap/006 0 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. [north_star] ### req: page_swap/007 0 007 `data-hemx-boost` progressively enhances descendant same-origin anchors and forms as a container convention, not a replacement for anchor `data-hemx-nav` or form `data-hemx-handle`; direct static-anchor/form use is a build error. [north_star] ### req: page_swap/008 0 008 Boosted links behave like `data-hemx-nav`; boosted forms behave like hemx form submissions. External links, downloads, new-tab links, and modified-clicks preserve native browser behavior. [north_star] --- ## htmx_equivalents ### req: htmx/001 0 001 hemx replaces common HTMX use-cases through typed equivalents, not HTMX syntax. [north_star] ### req: htmx/002 0 002 Easy equivalents must exist for generated target replacement, append/prepend/remove, form submit, loading indicators, confirmation, debounce/throttle, drag/drop payloads, validation errors, and form error regions. [north_star] ### req: htmx/006 0 006 Easy component-shape equivalents must exist for modals, toasts, table rows, and SVG fragments. [north_star] ### req: htmx/004 0 004 Navigation and live-update equivalents must exist for boosted links/forms, page swap, polling, history navigation, multi-target updates, response events, and SSE/push. [north_star] ### req: htmx/005 0 005 The copy-paste HTML pattern gallery must cover 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 0 003 hemx core does not clone HTMX selectors (`hx-target`, `hx-select`, `hx-include`, `closest/find/this`) or trigger mini-languages. Equivalents use generated targets, typed params, forms, explicit handlers, and page/push adapters. [north_star] --- ## component ### req: component/001 0 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. [north_star] ### req: component/002 0 002 hemx supports colocated layout: `todo_list.heml` beside `todo_list.rs`, with generated APIs namespaced by component to avoid global symbol soup. [north_star] ### req: component/003 0 003 Generated APIs are component-namespaced by default. [north_star] `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 0 004 `#[hemx::surface]` bridges generated code into a user module. Users write `#[hemx::surface] mod ui {}` instead of direct `$OUT_DIR` includes; hemx-build emits `hemx.generated.rs` for the macro to expand in place. [north_star] ### req: component/005 0 005 Optional `#[hemx::component]` validates that each template Surface handle has a corresponding `#[hemx::handler]` within the annotated module. It is strictly module-local, with cross-handler visibility only inside that module. [north_star] ### req: component/007 0 007 Missing handlers without `#[hemx::component]` are caught at app mount or test time, not `cargo check`. [north_star] ### req: component/006 0 006 `#[derive(Hemplate)]` structs are natural component boundaries. hemx_build discovers them automatically; no additional configuration is required for most apps. [north_star] --- ## surface ### req: surface/001 0 001 `hemplate_build` scans `.heml` and emits versioned postcard Surface facts at `$OUT_DIR/hemplate.surface.postcard`. `hemx_build` may take precomputed facts or ask hemplate to extract them, but hemx owns no independent `.heml` parser. [north_star] ### req: surface/002 0 002 The Surface contains: nodes (NodeId, parent, scope, element, attrs, source span), scopes (ScopeKind: Root | If | Match | Case | For { binding, key_expr }), forms (controls with raw HTML types), and component uses. [north_star] ### req: surface/003 0 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. [north_star] ### req: surface/004 0 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. [north_star] ### req: surface/005 0 005 Loop scopes expose the binding name and optional `key_expr` such as `todo.id`. hemplate only records key usage; hemx_build enforces key presence when a hemx-addressable node appears inside the loop. [north_star] ### req: surface/006 0 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. [north_star] ### req: surface/007 0 007 `hemplate-derive` does not write Surface files. Surface generation is a `build.rs` / `hemplate_build` concern, proc-macro side-effect free. [north_star] ### req: surface/008 0 008 The Surface records hemplate structural directives as first-class facts: [north_star] `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 0 009 Raw/pre-rendered HTML insertions are opaque Surface holes. The parent element is present; hemx_build emits the appropriate rendering call. [north_star] ### req: surface/010 0 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. [north_star] --- ## codegen ### req: codegen/001 0 001 `hemx_build` generates `hemx.generated.rs` resource modules, `hemx.syms` proc-macro facts, and runtime id-lowering tables from Surface IR. It interprets `data-hemx-*`, `h-for`, `h-key`, and form-control conventions from Surface. [north_star] ### req: codegen/002 0 002 Generated view modules expose ergonomic root-level target objects and commands that hide render/lower details for text, partial, and keyed collection slots. [north_star] ### req: codegen/009 0 009 Generated form targets provide `clear()`, `clear(field)`, and `focus(field)` commands. [north_star] ### req: codegen/007 0 007 String-keyed generated target objects accept displayable domain ids without caller-side `.to_string()` noise. [north_star] ### req: codegen/008 0 008 Generated commands return `impl IntoEffect`, compose in plain Rust, preserve generated lowering, and fail to generate when the template lacks facts needed to infer the slot, key, form, or renderable view type. [north_star] ### req: codegen/003 0 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. [north_star] ### req: codegen/004 0 004 Generated module `forms` exports `FormContract` metadata (field names, HTML control kinds, required). `#[hemx::handler]` compares `Form` against the contract. Domain types remain user-authored; no auto-generated structs. [north_star] ### req: codegen/005 0 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. [north_star] ### req: codegen/006 0 006 `hemx-build` discovers `data-hemx-on` event names from Surface inputs and emits `hemx::EventName` constants for checked Rust authoring and diagnostics. Constants do not create a trigger mini-language or new browser semantics. [north_star] --- ## public_api ### req: public_api/001 0 001 The generated API is the primary public authoring API. User code returns generated partial, text, keyed-row, form, page, nav, or island/event commands, not raw `Effect` constructors or raw render/lower calls. [north_star] ### req: public_api/002 0 002 `Effect`, `EffectWriter`, `ResourceId`, `ResourceRef`, and raw opcodes are advanced APIs. They must not appear in beginner docs, generated examples, or common diagnostics. [north_star] ### req: public_api/003 0 003 Every generated command returns `impl IntoEffect` and composes through tuple composition. [north_star] ### req: public_api/004 0 004 If a common UI operation requires raw `EffectWriter`, the public API is incomplete. [north_star] ### req: public_api/005 0 005 Beginner-facing page/template composition uses generated render or page helpers. Direct `SafeHtml`, raw `html(...)`, `target(...)`, route fragments, `hemx::advanced::render(...)`, and explicit `ui::render(...)` are advanced escape hatches. [north_star] ### req: public_api/006 0 006 Server-rendered page boundaries may use `hemx::page(...)`; handlers and ordinary partial updates must use generated target/form/page commands. [north_star] --- ## effect_algebra ### req: effect_algebra/001 0 001 The canonical op set is minimal and closed: `Put`, `Insert`, `Remove`, `Move`, `Focus`, `Navigate`, `Emit`. [north_star] ### req: effect_algebra/002 0 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. [north_star] ### req: effect_algebra/003 0 003 `Insert`, `Remove`, and `Move` operate on keyed collection resources. They require a key type checked by generated `KeyedSlot` wrappers. [north_star] ### req: effect_algebra/004 0 004 `Navigate` changes browser history or represents a server redirect. Route matching remains outside hemx core. [north_star] ### req: effect_algebra/005 0 005 `Emit` dispatches a native `CustomEvent` and is the only raw JS interop primitive in core. [north_star] ### req: effect_algebra/006 0 006 DOM-specific operations such as `innerHTML`, `textContent`, class toggles, or keyed node lookup are runtime lowering details, not separate author-facing concepts. [north_star] --- ## typed_id ### req: typed_id/001 0 001 Public cross-page identifiers (`Slot`, `Atom`, `Handle`, `Form`) share one internal primitive: `ResourceId { kind: ResourceKind, id: u32 }`. Typed wrappers enforce kind safety at compile time. [north_star] ### req: typed_id/002 0 002 `ResourceKind` is an internal closed enum (Slot, Atom, Handle, Form). [north_star] 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. ### req: typed_id/003 0 003 A concrete runtime target is a `ResourceRef { resource: ResourceId, scope: Option }`. Effects address resources uniformly, with no special-case opcodes per resource kind. [north_star] --- ## scope ### req: scope/001 0 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 0 001 Any `data-hemx-slot` or `data-hemx-handle` inside a hemplate `h-for` scope requires a stable key. Use syntax such as ``. [north_star] ### req: list/004 0 004 Without a key, hemx-addressable nodes inside a loop are rejected at build time. Keyed identity is `ResourceRef { resource: ResourceId, scope: Some(ScopeKey::KeyValue(...)) }`. [north_star] ### req: list/002 0 002 Slots inside a keyed loop receive a composite identity. hemplate records `key_expr` in the Surface; hemx implements keyed slot lookups. [north_star] ### req: list/003 0 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`. [north_star] ### req: list/005 0 005 Compatibility functions such as `append(keyed_slot, key, view)` may exist as explicit low-level forms. Key type mismatches are compile-time errors; missing/ambiguous keys are build errors with template spans. [north_star] ### req: list/006 0 006 Filtered keyed collections reconcile by removing filtered-out keys, replacing retained keys, and appending newly visible keys rather than clearing and re-adding every row. [north_star] --- ## form ### req: form/001 0 001 Forms are source of truth in HTML. hemplate Surface exports form shape (controls, names, required, types). hemx checks Rust `Form` compatibility through user-authored `#[hemx::form("...")]` domain structs and generated form metadata. [north_star] ### req: form/007 0 007 Form support generates no domain structs. Domain types such as `Email` are user-authored and first-class; the Surface describes, Rust owns, and hemx checks. [north_star] ### req: form/002 0 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. [north_star] ### req: form/003 0 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. [north_star] ### req: form/004 0 004 Form compatibility checks validate field presence, optionality, multiplicity, and parser availability. [north_star] ### req: form/008 0 008 Parser availability means the submitted value type implements `hemx::FormValue`, via `FromStr` blanket support or explicit custom parsers. Domain validation remains Rust logic (`TryFrom`, custom validators, or handler code). [north_star] ### req: form/005 0 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. [north_star] ### req: form/006 0 006 Generated diagnostics distinguish structure errors from validation errors: missing field / wrong optionality are compile-time issues; invalid submitted values are runtime form errors. [north_star] --- ## form_effects ### req: form_effects/001 0 001 Generated form APIs provide common commands: `clear()`, `clear(field)`, `reset()`, `error(field, message)`, `focus(field)`, and `disable_while_pending()`. `form.clear()` clears the generated form without raw control ids. [north_star] ### req: form_effects/002 0 002 Form effects target generated form/control ids, not CSS selectors. [north_star] ### req: form_effects/003 0 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. [north_star] --- ## wire ### req: wire/001 0 001 Authoring HTML uses symbolic `data-hemx-*` attributes. Runtime HTML lowers them to compact metadata: `data-hid`, `data-sid`, optional `data-key`, atom ids, form/control ids, and `data-hemx-st`. The browser never sees handler or slot names. [north_star] ### req: wire/006 0 006 `data-hemx-root` marks a scoped root boundary. [north_star] ### req: wire/002 0 002 POST bodies carry `application/x-www-form-urlencoded` with distinguished field `__h` (handle id). Server routes by numeric id, not by URL path. [north_star] ### req: wire/003 0 003 HTTP interaction responses may be `text/html` fragments containing `