Files
hemx/AGENTS.md
T

13 KiB

— AGENTS.md

Auto-generated from REQUIREMENTS.md. Do not edit directly. Edit REQUIREMENTS.md and run: redgate agents > AGENTS.md

Requirements

req:_boundary/001

  • 001 hemplate does not expose a slhx API. It exposes a stable, generic Template Surface IR. slhx is one consumer; a11y tools, test generators, and documentation generators are others.

req:_boundary/002

  • 002 hemplate never interprets data-slhx-*, slhx-*, or any other tool-prefixed attribute. It records them as raw name: value pairs in the Surface.

req:_boundary/003

  • 003 slhx never parses .heml directly. It consumes hemplate.surface.postcard emitted by hemplate_build. slhx interprets tool-specific conventions (data-slhx-handle, data-slhx-slot, etc.) from the generic Surface.

req:_build/001

  • 001 Build order: .hemlhemplate_buildhemplate.surface.postcardslhx_buildslhx.syms + generated Rust constants.

req:_build/002

  • 002 slhx-derive (#[slhx::handler]) reads slhx.syms at expansion time to validate handle names, slot names, and form signatures.

req:_build/003

  • 003 A build.rs failure (missing Surface, version mismatch, stale hash) is a hard error before proc-macro expansion.

req:_build/004

  • 004 Id allocation is deterministic from canonical symbol paths. Stable across builds unless the symbol path changes.

req:_check/001

  • 001 All cross-file references verified at cargo check. Unknown handle → hard error. Unknown slot → hard error. Type mismatch between slot and atom → hard error.

req:_check/002

  • 002 Dead handle warning: #[slhx::handler] never referenced by any template. Dead slot warning: template node never targeted by any handler.

req:_check/003

  • 003 Renaming a slot or handle breaks cargo check immediately with a span pointing to the Rust handler or template source.

req:_check/004

  • 004 Page-scoped slot lookup: JS runtime resolves data-sid only within the current Page root element.

req:_derive_app/001

  • 001 #[slhx::app] marks the root application struct containing all global atoms. It is the registry entry point for slhx_build. Zero- or single-instance per process.

req:_effect/001

  • 001 Effects are a typed command stream describing what, where, and how of a DOM mutation. Declarative: Rust builds the stream; JS applies it.

req:_effect/002

  • 002 The public API is IntoEffect (a trait) for Rust ergonomics and zero-allocation encoding. The wire API is a canonical postcard opcode schema (Op::ReplaceHtml, Op::SetText, Op::PatchAtom, Op::Navigate, Op::Focus, Op::AddClass, Op::RemoveClass, Op::RemoveKeyed, Op::CustomOp). IntoEffect writes opcodes directly; advanced users may implement the trait to stream custom opcodes.

req:_effect/003

  • 003 Core effect helpers: replace(slot, html), patch(slot, atom), text(slot, value), remove_keyed(slot, key), append_keyed(slot, key, html), move_keyed(slot, key, target_slot, target_key, position), class_keyed(slot, key, class, active), navigate(Nav { url, mode, title, scroll }), focus(slot), add_class(slot, class), remove_class(slot, class), batch((...)).

req:_effect/004

  • 004 batch composes effects in declared order. No implicit ordering, no priority weights.

req:_effect/005

  • 005 Effect::navigate carries NavMode::Push | Replace | Redirect, optional title, and ScrollMode. No separate router framework required for basic cases.

req:_effect/006

  • 006 Core effects may carry an opaque transition token, but core never interprets or implements transitions. Transitions live in slhx-transition.

req:_effect/007

  • 007 Zero runtime parsing of selectors. The JS runtime looks up elements by numeric data-sid or data-hid attributes. Slot ids are allocated deterministically.

req:_form/001

  • 001 Forms are first-class. hemplate exports FormSurface with raw ControlKind facts. slhx generates/validates Rust form structs from those facts.

req:_form/002

  • 002 Form<T> is generated by slhx-build from the Surface. T derives from HTML control names and kinds, mapped to Rust types by slhx rules (e.g. type="number" + requiredu64; same without requiredOption<u64>).

req:_form/003

  • 003 Handler signature mismatch between generated Form<T> and the handler parameter is a cargo check error.

req:_form/004

  • 004 Progressive enhancement: if JS fails, <form data-slhx-handle> degrades to normal submission via hidden __h field. Server reads __h and dispatches by numeric handle id.

req:_invariant/001

  • 001 User-authored references are symbolic at author time and numeric at runtime.

req:_invariant/002

  • 002 The JS runtime never parses CSS selectors, expressions, or handler names.

req:_invariant/003

  • 003 Rust handlers return effects; they do not imperatively mutate DOM.

req:_invariant/004

  • 004 Cross-file references fail at cargo check with a precise span.

req:_invariant/005

  • 005 slhx core owns effects, typed ids, and registries only. Routing, auth, sessions, transport, transitions, and sync are integration concerns.

req:_js/001

  • 001 The JS runtime is a single file under 3 kB minified+gzipped. It reads data-hid and data-sid, delegates events on document, and applies effects by direct DOM mutation.

req:_js/002

  • 002 No build step, no virtual DOM, no diffing, no scheduler. Receiving an effect = apply ops immediately in declared order.

req:_js/003

  • 003 The runtime consists of an Op interpreter (ReplaceHtml, SetText, PatchAtom, Navigate, Focus, AddClass, RemoveClass, RemoveKeyed, CustomOp) reading from a postcard byte stream.

req:_list/001

  • 001 Any data-slhx-slot or data-slhx-handle inside @for requires an explicit key expression. Syntax: @for item in items key item.id { ... }. Without key, slhx-addressable nodes inside the loop are rejected at build time. Keyed identity is (SlotId, KeyValue).

req:_list/002

  • 002 Slots inside a keyed loop receive a composite identity: (SlotId, KeyValue), not a flat id. hemplate records key_expr in the Surface; slhx implements keyed slot lookups.

req:_list/003

  • 003 Effects on keyed slots: replace_keyed(slot, key, value), remove_keyed(slot, key), append_keyed(slot, key, value). Mismatch between key type and slot key type is compile-time error.

req:_misc/001

  • 001 Workspace layout: slhx-core (types + postcard schema, no_std), slhx-derive (proc-macros), slhx-build (surface consumer + code generation), slhx-axum (integration), slhx-js (runtime single file), slhx-transition (optional), slhx-sync (optional), slhx-wasm (optional). No kitchen-sink crate.

req:_misc/002

  • 002 All crates compile on stable Rust. MSRV 1.80. slhx-core has zero proc-macro dependencies.

req:_misc/003

  • 003 No auth, no routing, no session storage inside slhx core. slhx-axum provides typed route mounting; actual routing is axum/tower.

req:_misc/004

  • 004 Three execution modes supported: server-first (request/response), client-local WASM (requestAnimationFrame, no round-trip), and hybrid sync (local + remote via slhx-sync). Modes are opt-in per handler, not global.

req:_misc/005

  • 005 The only user-facing proc-macro is #[slhx::handler]. No ! call-syntax macros. Attribute macros only.

req:_ms/001

  • 001 Milestone app: Local-first Multiplayer Kanban. A board with drag-and-drop cards, 60fps pointer-follow, optimistic updates, offline queue, conflict reconciliation, live presence, and SSR-first rendering — all without React/Vue/VDOM, in a single typed Rust codebase. This acts as the north-star integration test for slhx + hemplate + slhx-sync. [north_star]

req:_pitch/001

  • 001 slhx is checked hypermedia for Rust. Authors write HTML templates and Rust handlers. The compiler lowers every cross-file reference to a stable numeric id. The browser runtime only sees 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 hydration. [north_star]

req:_pitch/003

  • 003 slhx replaces React/Vue not with a UI framework, but with a compiler contract: hemplate knows the surface, Rust knows the types, slhx knows the effects, the browser only executes commands. [north_star]

req:_resource/001

  • 001 Async remote data lives in Resource<T> / Query<K, T> / Mutation<I, O>. These are optional, not core primitives. They provide loading/error/refresh semantics without client-side data libraries.

req:_resource/002

  • 002 Effect::resource(res).reload() triggers a re-fetch and re-render. The server sends a new EffectBatch when data is ready.

req:_state/001

  • 001 Atom<T> is a typed, stable-id handle to a piece of state. Atoms are the only state primitive in core.

req:_state/002

  • 002 State shape is flat. Nesting is an anti-pattern; compose via multiple atoms.

req:_state/003

  • 003 No proxy magic. State access is explicit: store.get(atom) returns Option<&T>. Mutations return impl IntoEffect, not side-effects.

req:_state/004

  • 004 Page-local transient state lives in JS as Map<AtomId, unknown>. Not reactive-by-default.

req:_state/005

  • 005 Global long-lived state is stored server-side in a session-compatible way. On re-render the server injects a postcard-encoded blob in <script type="application/slhx-state">; JS hydrates it so client-side handlers (WASM-compiled Rust) can read it without round-trips.

req:_state/006

  • 006 The atom model is isomorphic to ECS. Atoms are components, Pages are worlds, Slots are entities. Scales to game-like WASM applications.

req:_surface/001

  • 001 hemplate_build scans .heml files and emits $OUT_DIR/hemplate.surface.postcard (postcard-encoded, deterministic, versioned).

req:_surface/002

  • 002 The Surface contains: nodes (NodeId, parent, scope, element, attrs, source span), scopes (ScopeKind: Root | If | 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.

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:_sync/001

  • 001 slhx-sync is an optional crate for collaborative / multiplayer state. Provides presence tracking, patch reconciliation, conflict resolution (server-authoritative), and offline queueing. Not part of core.

req:_sync/002

  • 002 SyncEffect::send_patch(atom, patch) queues a state diff for server sync. If offline, the patch is stored in a local queue and sent when connection resumes. If online, it is sent immediately via WebSocket/SSE.

req:_sync/003

  • 003 Effect::ack(atom) acknowledges a successful server-side mutation, allowing the client to clear its local optimistic queue for that atom.

req:_sync/004

  • 004 Effect::broadcast(channel, effect_batch) sends an EffectBatch to all subscribers of a named channel. Used for presence updates and live collaboration. The server framework manages the transport (WS/SSE).

req:_sync/005

  • 005 #[slhx_sync::presence] is an attribute macro on functions that return impl IntoEffect when a user joins or leaves a shared session. Emits Effect::broadcast over a presence channel scoped to the session.

req:_sync/006

  • 006 slhx-sync uses a flat patch model per atom, not CRDT by default. Server is authoritative; clients apply server-canonical state on conflict. Optional CRDT backend may be provided by a future slhx-crdt crate.

req:_wire/001

  • 001 HTML wire format is standard HTML with data-hid and data-sid attributes only. No custom markup, no hx-* attributes.

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 Responses are text/html fragments (or application/slhx for push streams). Fragments may contain <template data-slhx> elements whose text content is a base64url-encoded postcard EffectBatch. JS decodes and applies.

req:_wire/004

  • 004 Server push is supported orthogonally: Effect::push(stream, effect) sends a pre-serialized effect batch over an SSE or WebSocket connection. Connection management is a server-framework concern.

Coverage: 0/68 (100.0% uncited)