13 KiB
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 rawname: valuepairs in the Surface.
req:_boundary/003
- 003 slhx never parses
.hemldirectly. It consumeshemplate.surface.postcardemitted byhemplate_build. slhx interprets tool-specific conventions (data-slhx-handle,data-slhx-slot, etc.) from the generic Surface.
req:_build/001
- 001 Build order:
.heml→hemplate_build→hemplate.surface.postcard→slhx_build→slhx.syms+ generated Rust constants.
req:_build/002
- 002
slhx-derive(#[slhx::handler]) readsslhx.symsat expansion time to validate handle names, slot names, and form signatures.
req:_build/003
- 003 A
build.rsfailure (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 checkimmediately with a span pointing to the Rust handler or template source.
req:_check/004
- 004 Page-scoped slot lookup: JS runtime resolves
data-sidonly 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 forslhx_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
batchcomposes effects in declared order. No implicit ordering, no priority weights.
req:_effect/005
- 005
Effect::navigatecarriesNavMode::Push | Replace | Redirect, optionaltitle, andScrollMode. 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-sidordata-hidattributes. Slot ids are allocated deterministically.
req:_form/001
- 001 Forms are first-class. hemplate exports
FormSurfacewith rawControlKindfacts. slhx generates/validates Rust form structs from those facts.
req:_form/002
- 002
Form<T>is generated by slhx-build from the Surface.Tderives from HTML control names and kinds, mapped to Rust types by slhx rules (e.g.type="number"+required→u64; same withoutrequired→Option<u64>).
req:_form/003
- 003 Handler signature mismatch between generated
Form<T>and the handler parameter is acargo checkerror.
req:_form/004
- 004 Progressive enhancement: if JS fails,
<form data-slhx-handle>degrades to normal submission via hidden__hfield. Server reads__hand 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 checkwith 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-hidanddata-sid, delegates events ondocument, 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-slotordata-slhx-handleinside@forrequires an explicitkeyexpression. Syntax:@for item in items key item.id { ... }. Withoutkey, 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 recordskey_exprin 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-corehas zero proc-macro dependencies.
req:_misc/003
- 003 No auth, no routing, no session storage inside slhx core.
slhx-axumprovides 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)returnsOption<&T>. Mutations returnimpl 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_buildscans.hemlfiles 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
NodeIdin a parent/scope graph. Nocss_pathis used as a primary identifier. An optionaldebug_pathstring 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-derivedoes not write Surface files. Surface generation is abuild.rs/hemplate_buildconcern, proc-macro side-effect free.
req:_sync/001
- 001
slhx-syncis 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 anEffectBatchto 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 returnimpl IntoEffectwhen a user joins or leaves a shared session. EmitsEffect::broadcastover a presence channel scoped to the session.
req:_sync/006
- 006
slhx-syncuses 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 futureslhx-crdtcrate.
req:_wire/001
- 001 HTML wire format is standard HTML with
data-hidanddata-sidattributes only. No custom markup, no hx-* attributes.
req:_wire/002
- 002 POST bodies carry
application/x-www-form-urlencodedwith distinguished field__h(handle id). Server routes by numeric id, not by URL path.
req:_wire/003
- 003 Responses are
text/htmlfragments (orapplication/slhxfor push streams). Fragments may contain<template data-slhx>elements whose text content is a base64url-encoded postcardEffectBatch. 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.