797132647f
Add hemx-lsp for stdio LSP diagnostics, completion, and hover while preserving HTML editor tooling for .heml files. Teach hemx-build to expose generated target and derive-known template context facts, including simple h-for locals, so editor help comes from build-owned facts instead of editor-only parsers. Wire VS Code/Cursor and Neovim documentation and extend the Workout exemplar with a real h-for plan loop for end-to-end proof. req: diag/004 req: diag/005 req: diag/006
187 lines
11 KiB
Markdown
187 lines
11 KiB
Markdown
# hemx
|
|
|
|
hemx is checked hypermedia for Rust: write hemplate templates, write typed Rust
|
|
handlers, and return generated UI commands. The browser receives checked UI
|
|
commands; ordinary server-first apps do not need a frontend framework,
|
|
handwritten UI JavaScript, selector targeting, or raw runtime primitives. req: pitch/001 req: canonical_authoring/001
|
|
|
|
Status: the repository currently has a strong v0 example/API path, a compile-tested
|
|
SaaS tutorial app, an advanced Kanban milestone sketch, and a full techdemo.
|
|
The v1 story now has tutorial, recipe, diagnostics, and stability docs; see
|
|
`docs/v1-readiness.md` for the remaining close-gap audit.
|
|
|
|
Template authoring: `.heml` is HTML plus a small hemplate overlay for escaped
|
|
text, trusted HTML, dynamic attributes, Rust-shaped control directives, generated
|
|
slots/forms/handles, and keyed partial targets. See `docs/hemplate-syntax.md`.
|
|
Editor setup for VS Code, Cursor, and Neovim lives in `docs/editor-support.md`;
|
|
VS Code/Cursor share the repo extension in `editors/vscode-hemx`, while all
|
|
editors keep normal HTML/tree-sitter highlighting and layer `hemx-build`
|
|
diagnostics on top.
|
|
|
|
Local checkout note: until the hemplate crates are published, this repository
|
|
expects `hemplate` checked out next to `hemx` as `../hemplate/hemplate`. The app
|
|
scaffolder fails with that exact path if the prerequisite is missing, instead of
|
|
creating an app that fails later with a vague Cargo path-dependency error.
|
|
|
|
## The normal path
|
|
|
|
For beginner and production-shaped app code, stay on this path. req: public_api/001 req: public_api/005
|
|
|
|
1. **Templates declare the surface.** `.heml` files declare roots, slots,
|
|
forms, handles, keys, page targets, optional pending states, and explicit
|
|
leaf islands with `data-hemx-*` attributes. The stable syntax surface lives in
|
|
`docs/hemplate-syntax.md`.
|
|
2. **Build generates typed helpers.** `hemx_build::app().run()` consumes the
|
|
hemplate Surface and emits generated Rust helpers for slots, forms, handles,
|
|
page targets, classes, and events. req: ceremony/003 req: build/001
|
|
3. **Handlers are plain Rust.** App code uses `#[hemx::handler]` functions with
|
|
ordinary domain types, framework extractors, generated `Form<T>` inputs, and
|
|
`impl IntoEffect` or `Result<impl IntoEffect, E>` returns. req: derive_handler/004
|
|
4. **Handlers return generated commands.** Common handlers return helpers such
|
|
as `todos.append(todo)`, `todo_row.replace(row)`, `new_todo.error("title",
|
|
"Required")`, `new_todo.clear()`, `page.replace(view)`, or tuples of those
|
|
commands. req: dx/006 req: dx/007
|
|
5. **The runtime only applies effects.** The JavaScript runtime is a small,
|
|
root-scoped effect interpreter: no VDOM, no hydration framework, no client
|
|
expression engine, no selector retargeting, and no app state store. req: runtime/003 req: invariant/002
|
|
|
|
## Mental model: render → slot/key → effect → runtime
|
|
|
|
- **Render:** hemplate renders Rust view structs into checked HTML. App code
|
|
normally reaches rendering through generated `ui::page(...)` helpers at a
|
|
server page boundary, not raw HTML construction. req: html_safety/002 req: view/001
|
|
- **Slot/key:** a generated slot names the target, and a generated keyed slot
|
|
also carries the stable row key. A list target inside `h-for` must have a
|
|
stable `h-key`, so row updates are addressable without CSS selectors. req: list/001
|
|
- **Effect:** handlers return typed commands that become a checked effect
|
|
response. Tuple composition is the normal fixed batch syntax; arrays and
|
|
`Vec<T: IntoEffect>` cover fixed or dynamic repeated partial updates.
|
|
- **Reuse:** the hemx answer to framework components is reusable hemplate
|
|
partials plus generated helpers, app-owned state, `IntoEffect` composition,
|
|
and explicit leaf islands when browser-owned behavior is necessary. See
|
|
`docs/recipes/reusable-partials.md`.
|
|
- **Runtime:** the browser checks the build fingerprint, resolves targets within
|
|
the current `data-hemx-root`, and applies compatible batches. Mismatched
|
|
server/runtime builds fail closed instead of silently mutating the wrong DOM.
|
|
req: abi/002 req: check/004 req: failure/005
|
|
|
|
## Forms and errors
|
|
|
|
Forms remain HTML forms. hemx checks the generated form contract against a
|
|
user-authored Rust form type, so domain newtypes such as `Email`, `TodoId`, and
|
|
`Title` parse through ordinary Rust traits rather than generated DTOs. req: form/001
|
|
|
|
Use validation effects for expected user mistakes, and `Result<impl IntoEffect,
|
|
E>` for fallible domain, database, or infrastructure work. Integration crates map
|
|
`E` to generated UI effects, redirects, events, or HTTP responses; the canonical
|
|
app code still uses the same handler shape for plain and fallible handlers. See
|
|
`docs/diagnostics.md` for common compile/build/runtime mistakes and fixes.
|
|
req: failure/004 req: derive_handler/004
|
|
|
|
## Pages, push, CSS, and islands
|
|
|
|
- Page navigation is a specialized generated page/slot effect around real
|
|
anchors and ordinary HTTP routes. hemx does not own routing. req: modes/002 req: axum_integration/002
|
|
- Server push streams send checked effect responses over framework-managed
|
|
transports such as SSE; auth and connection policy stay in the server
|
|
integration. req: wire/004 req: push/002
|
|
- Appearance is plain CSS. Generated class tokens can make dynamic classes
|
|
checked, but hemx does not introduce a styling runtime.
|
|
- Custom JavaScript belongs at explicit opaque leaf boundaries: charts, maps,
|
|
editors, Web Components, or similar widgets. Islands communicate through
|
|
generated handles/events and do not create a second UI model. req: canonical_authoring/007 req: interop/003
|
|
|
|
## Production boundary
|
|
|
|
hemx is not a SaaS platform. Production concerns stay in normal Rust/web crates
|
|
and integrate at explicit boundaries. req: laws/002 req: auth/001
|
|
|
|
- **Persistence:** use SQLx or another storage adapter in your application
|
|
state/handlers. hemx should see ordinary domain values and generated UI
|
|
commands, not own the database layer. See `docs/recipes/sqlx-persistence.md`.
|
|
- **Auth/session:** use Axum/Tower extractors and middleware. Handlers may accept
|
|
typed auth/session context and return ordinary HTTP failures or generated UI
|
|
failures. See `docs/recipes/auth-session-csrf.md`. req: auth/002
|
|
- **CSRF:** keep CSRF policy in middleware/extractors with hidden form fields,
|
|
cookies, and normal SameSite/browser semantics. hemx preserves submitted form
|
|
fields and credentials semantics. See `docs/recipes/auth-session-csrf.md`. req: auth/004 req: auth/005
|
|
- **Observability, feature flags, killswitches, deploy:** use explicit platform
|
|
integrations around handlers, routes, runtime assets, and mobile shells. Core
|
|
hemx must not vendor providers or add framework-specific magic. See
|
|
`docs/recipes/observability-flags.md`, `docs/recipes/deploy-versioning.md`,
|
|
and `docs/recipes/mobile-release.md`. req: examples/006
|
|
- **Mobile starter:** create the phone-first path with `cargo run -p
|
|
hemx-xtask -- app new --mobile PATH`. The starter carries a real app flow,
|
|
typed host capabilities, command/event/projection recovery truth, and
|
|
inspectable mobile release-kit commands without adding a native UI framework.
|
|
Use it for Rust-owned hypermedia apps; use explicit native shells/islands for
|
|
heavy native UI, games, camera-heavy flows, deep OS integration, or complex
|
|
offline sync. req: ceremony/006 req: host/002
|
|
- **PWA/offline/sync:** optional adapters may reuse generated targets/effects,
|
|
but core hemx must not gain a mandatory client state graph or local app
|
|
runtime. Local truth is commands/events/projections, not stored DOM patches or
|
|
stored `EffectBatch` payloads. See `docs/recipes/pwa-offline.md` and
|
|
`docs/recipes/local-command-log.md`. req: canonical_authoring/008 req: local/001 req: local/002
|
|
- **Host capabilities:** browser, PWA, WebView, and native-shell capabilities use
|
|
`hemx-host` manifests/calls/events. Adapters return host facts to app code;
|
|
UI still changes through normal hemx effects. See
|
|
`docs/recipes/host-capabilities.md`. req: host/001 req: host/002 req: host/005
|
|
|
|
## Escape hatches
|
|
|
|
Advanced APIs are named and isolated. Raw effects, low-level ids, manual
|
|
registries, raw HTML/render/target construction, runtime hooks, SSE internals,
|
|
and island internals are for integration crates, tests, migrations, or explicit
|
|
leaf boundaries. They should not appear in beginner examples or ordinary handler
|
|
docs. req: public_api/002 req: public_api/005
|
|
|
|
## Versioning and deploy compatibility
|
|
|
|
The generated API, symbols, effect wire schema, and JavaScript runtime carry
|
|
schema/ABI versions. Deploy a matching server, generated output, and runtime
|
|
asset together. Build fingerprints are derived from the generated surface and ABI
|
|
parts; the runtime refuses incompatible effect responses and integrations should
|
|
fall back to a full page reload when possible. See `docs/recipes/deploy-versioning.md`. req: abi/001 req: abi/002 req: failure/005
|
|
|
|
Before a v1 release, the semver policy and upgrade notes should explicitly state
|
|
which surfaces are stable: beginner generated helpers and handler shapes; the
|
|
wire/runtime ABI; and advanced escape hatches that may remain integration-level.
|
|
See `docs/versioning.md`.
|
|
|
|
## Examples
|
|
|
|
- `examples/v0`: canonical beginner path covering counter, typed todo CRUD,
|
|
form wizard, auth action, page swaps, SSE notifications, and keyed list
|
|
updates. Create the generic starter with `cargo run -p hemx-xtask -- app new
|
|
PATH`; it includes a page, form, keyed row partial, notice slot, handlers,
|
|
tests, and generated append/replace/remove/dynamic-batch updates. Start here.
|
|
req: ceremony/005
|
|
- `examples/saas`: compile-tested v1 tutorial app covering auth/session,
|
|
CSRF-safe mutation, local persistence, generated swaps, page/push shape, plain
|
|
CSS, and one explicit island without provider-heavy platform scope. Read the
|
|
walkthrough in `docs/tutorial-saas.md`; the SQLx persistence recipe in
|
|
`docs/recipes/sqlx-persistence.md` shows the provider boundary without moving
|
|
SQL into core.
|
|
- `examples/workout`: phone-first local-first product exemplar. Create a starter
|
|
with `cargo run -p hemx-xtask -- workout new PATH`; run the exemplar with
|
|
`cargo run -p hemx-xtask -- workout dev` and open `http://127.0.0.1:3028`.
|
|
It keeps workout truth as commands/events/projections and routes export
|
|
through the host capability boundary; `examples/workout/README.md` documents
|
|
the canonical dev, test, production build, mobile release, verification, and
|
|
failure-mode paths. req: examples/001 req: examples/006 req: local/001 req: host/005
|
|
- `examples/kanban`: advanced / north-star milestone boundary sketch. It may
|
|
expose manual registry or render escape hatches while exploring product limits.
|
|
- `examples/techdemo`: advanced integration demo with a leaf island and broader
|
|
product interactions.
|
|
|
|
## Local checks
|
|
|
|
```sh
|
|
cargo run -p hemx-xtask -- test
|
|
cargo check --workspace
|
|
redgate health --strict
|
|
```
|
|
|
|
Use `cargo run -p hemx-xtask -- test` for the full local verification path so
|
|
jobs stay capped for local CPU and memory. req: test/004
|