refactor!: rename slhx to hemx
Rename the tracked product identity, crate/package names, Rust paths/macros, generated artifacts, runtime files, public attributes, examples, docs, requirements, and tests from slhx to hemx without compatibility shims. Verified with cargo run -p hemx-xtask -- test, cargo test -p hemx-derive --test compile_fail, cargo test -p hemx-js, cargo test -p hemx-axum, cargo test -p hemx-v0-examples, cargo check --workspace, redgate list, redgate refs, redgate health --strict, git diff --check, and git grep/ls-files legacy-name audits. req: misc/001 req: codegen/001 req: component/004 req: runtime/001
This commit is contained in:
+35
-35
@@ -4,18 +4,18 @@ 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 is an explicitly advanced/low-level north-star boundary sketch for slhx + hemplate + slhx-sync, not the beginner-facing authoring path. Raw sync/effect/wire vocabulary below is excluded from beginner-facing examples by design.
|
||||
This is an explicitly advanced/low-level north-star boundary sketch for hemx + hemplate + hemx-sync, not the beginner-facing authoring path. Raw sync/effect/wire vocabulary below is excluded from beginner-facing examples by design.
|
||||
|
||||
---
|
||||
|
||||
## 1. Template: `board.heml`
|
||||
|
||||
```html
|
||||
<section data-slhx-root="board" data-slhx-slot="board" data-slhx-atom="board">
|
||||
<section data-hemx-root="board" data-hemx-slot="board" data-hemx-atom="board">
|
||||
<header>
|
||||
<h1>{+ self.title +}</h1>
|
||||
|
||||
<form data-slhx-handle="create_card" data-slhx-form="create_card">
|
||||
<form data-hemx-handle="create_card" data-hemx-form="create_card">
|
||||
<input name="title" type="text" required>
|
||||
<select name="column">
|
||||
<template h-for="column in &self.columns" h-key="column.id">
|
||||
@@ -26,14 +26,14 @@ This is an explicitly advanced/low-level north-star boundary sketch for slhx + h
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<div class="columns" data-slhx-slot="columns">
|
||||
<div class="columns" data-hemx-slot="columns">
|
||||
<template h-for="column in &self.columns" h-key="column.id">
|
||||
<section class="column" data-slhx-slot="column" +data-column-id="column.id">
|
||||
<section class="column" data-hemx-slot="column" +data-column-id="column.id">
|
||||
<h2>{+ column.title +}</h2>
|
||||
|
||||
<div class="cards" +data-column-id="column.id">
|
||||
<template h-for="card in &column.cards" h-key="card.id">
|
||||
<article class="card" data-slhx-slot="card" data-slhx-handle="drag_card" +data-card-id="card.id" draggable="true">
|
||||
<article class="card" data-hemx-slot="card" data-hemx-handle="drag_card" +data-card-id="card.id" draggable="true">
|
||||
<strong>{+ card.title +}</strong>
|
||||
<small>{+ card.assignee +}</small>
|
||||
</article>
|
||||
@@ -43,7 +43,7 @@ This is an explicitly advanced/low-level north-star boundary sketch for slhx + h
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<aside data-slhx-slot="presence">
|
||||
<aside data-hemx-slot="presence">
|
||||
<template h-for="user in &self.online_users" h-key="user.id">
|
||||
<span>{+ user.name +}</span>
|
||||
</template>
|
||||
@@ -53,16 +53,16 @@ This is an explicitly advanced/low-level north-star boundary sketch for slhx + h
|
||||
|
||||
Notes on keyed scopes:
|
||||
|
||||
- `h-for="column in &self.columns" h-key="column.id"` — **required** for slhx-addressable nodes inside
|
||||
- `h-for="column in &self.columns" h-key="column.id"` — **required** for hemx-addressable nodes inside
|
||||
- `h-for="card in &column.cards" h-key="card.id"` — **required**
|
||||
- A slot inside a keyed loop is addressed as a generated keyed resource, never by selector strings or positional DOM targeting
|
||||
- Without `h-key`, slhx rejects the build — no runtime selector fallback
|
||||
- Without `h-key`, hemx rejects the build — no runtime selector fallback
|
||||
|
||||
---
|
||||
|
||||
## 2. What hemplate exports
|
||||
|
||||
hemplate does **not** interpret `data-slhx-*`. It records raw facts:
|
||||
hemplate does **not** interpret `data-hemx-*`. It records raw facts:
|
||||
|
||||
```rust
|
||||
Node {
|
||||
@@ -70,8 +70,8 @@ Node {
|
||||
element: "article",
|
||||
attrs: [
|
||||
("class", "card"),
|
||||
("data-slhx-slot", "card"),
|
||||
("data-slhx-handle", "drag_card"),
|
||||
("data-hemx-slot", "card"),
|
||||
("data-hemx-handle", "drag_card"),
|
||||
("data-card-id", "{card.id}"),
|
||||
("draggable", "true"),
|
||||
],
|
||||
@@ -87,7 +87,7 @@ FormSurface {
|
||||
}
|
||||
```
|
||||
|
||||
slhx reads this from hemplate Surface facts and generates scoped typed resources:
|
||||
hemx reads this from hemplate Surface facts and generates scoped typed resources:
|
||||
|
||||
```rust
|
||||
use ui::board::{forms, targets};
|
||||
@@ -104,7 +104,7 @@ No string desync. No manual ids. The generated module owns the names.
|
||||
## 3. App State
|
||||
|
||||
```rust
|
||||
#[slhx::app]
|
||||
#[hemx::app]
|
||||
pub struct BoardApp {
|
||||
pub board: Atom<BoardState>,
|
||||
pub drag: Atom<Option<DragState>>,
|
||||
@@ -119,13 +119,13 @@ The same struct runs on server (SSR) and in WASM (client-local effects).
|
||||
## 4. Normal Form: Server-first
|
||||
|
||||
```rust
|
||||
#[derive(SlhxForm)]
|
||||
#[derive(HemxForm)]
|
||||
pub struct CreateCardForm {
|
||||
pub title: String,
|
||||
pub column: ColumnId,
|
||||
}
|
||||
|
||||
#[slhx::handler]
|
||||
#[hemx::handler]
|
||||
pub fn create_card(
|
||||
form: Form<CreateCardForm>,
|
||||
app: &mut BoardApp,
|
||||
@@ -137,7 +137,7 @@ pub fn create_card(
|
||||
(
|
||||
targets::card.append(card.id, &CardView::from(card)),
|
||||
forms::create_card.clear("title"),
|
||||
// slhx-sync: queue atomic board state diff for sync
|
||||
// hemx-sync: queue atomic board state diff for sync
|
||||
SyncEffect::send_patch(atoms::board, Patch::insert_card(form.column, card)),
|
||||
)
|
||||
}
|
||||
@@ -150,7 +150,7 @@ HTML submits as usual. Server returns a typed update batch. Browser applies DOM
|
||||
## 5. Drag: 60fps client-local WASM
|
||||
|
||||
```rust
|
||||
#[slhx::handler(client)]
|
||||
#[hemx::handler(client)]
|
||||
pub fn drag_card(
|
||||
event: DragEvent,
|
||||
app: &mut BoardApp,
|
||||
@@ -163,7 +163,7 @@ pub fn drag_card(
|
||||
}));
|
||||
|
||||
// Client-local extension APIs stay typed by generated resources;
|
||||
// names below are illustrative until slhx-sync lands.
|
||||
// names below are illustrative until hemx-sync lands.
|
||||
Effect::batch((
|
||||
Effect::class_keyed(slots::CARD, event.card_id, "dragging", true),
|
||||
Effect::transform_keyed(
|
||||
@@ -182,7 +182,7 @@ Zero round-trip. Zero custom JS. Pure Rust → typed updates → DOM.
|
||||
## 6. Drop: optimistic update + sync
|
||||
|
||||
```rust
|
||||
#[slhx::handler(client)]
|
||||
#[hemx::handler(client)]
|
||||
pub fn drop_card(
|
||||
event: DropEvent,
|
||||
app: &mut BoardApp,
|
||||
@@ -194,7 +194,7 @@ pub fn drop_card(
|
||||
app.drag.set(None);
|
||||
|
||||
// Client-local extension APIs stay typed by generated resources;
|
||||
// names below are illustrative until slhx-sync lands.
|
||||
// names below are illustrative until hemx-sync lands.
|
||||
Effect::batch((
|
||||
Effect::move_keyed(
|
||||
slots::CARD,
|
||||
@@ -204,7 +204,7 @@ pub fn drop_card(
|
||||
InsertBefore(event.before_card),
|
||||
),
|
||||
Effect::class_keyed(slots::CARD, event.card_id, "dragging", false),
|
||||
// slhx-sync: queue patch, send when online
|
||||
// hemx-sync: queue patch, send when online
|
||||
SyncEffect::send_patch(atoms::BOARD, patch),
|
||||
))
|
||||
}
|
||||
@@ -212,14 +212,14 @@ pub fn drop_card(
|
||||
|
||||
A pure htmx+SSR app cannot model this: 60fps pointer → local transient drag → optimistic update → offline queue → reconciliation. You'd need custom JS or a parallel React/Vue layer.
|
||||
|
||||
slhx models it in one type graph.
|
||||
hemx models it in one type graph.
|
||||
|
||||
---
|
||||
|
||||
## 7. Server reconciliation
|
||||
|
||||
```rust
|
||||
#[slhx_sync::handler]
|
||||
#[hemx_sync::handler]
|
||||
pub fn apply_board_patch(
|
||||
patch: BoardPatch,
|
||||
app: &mut BoardApp,
|
||||
@@ -253,7 +253,7 @@ Server-authoritative on conflict. No Redux sagas. No React Query cache fades.
|
||||
## 8. Presence
|
||||
|
||||
```rust
|
||||
#[slhx_sync::presence]
|
||||
#[hemx_sync::presence]
|
||||
pub fn user_joined(user: UserPresence) -> impl IntoEffect {
|
||||
targets::presence_user.append(user.id, &PresenceBadge::from(user))
|
||||
}
|
||||
@@ -272,22 +272,22 @@ The runtime does not know "presence". It executes generated DOM updates.
|
||||
|
||||
## 9. What app authors write; what the browser receives
|
||||
|
||||
Initial SSR stays an ordinary rendered template with symbolic slhx attributes at
|
||||
Initial SSR stays an ordinary rendered template with symbolic hemx attributes at
|
||||
the authoring boundary:
|
||||
|
||||
```html
|
||||
<section data-slhx-root="board" data-slhx-slot="board" data-slhx-atom="board">
|
||||
<section data-hemx-root="board" data-hemx-slot="board" data-hemx-atom="board">
|
||||
...
|
||||
<article data-slhx-slot="card" data-slhx-handle="select_card" +data-card-id="card.id">
|
||||
<article data-hemx-slot="card" data-hemx-handle="select_card" +data-card-id="card.id">
|
||||
{+ card.title +}
|
||||
</article>
|
||||
...
|
||||
</section>
|
||||
<!-- the app shell loads /slhx.js and any bootstrap state -->
|
||||
<!-- the app shell loads /hemx.js and any bootstrap state -->
|
||||
```
|
||||
|
||||
The compiler lowers those symbols to compact runtime metadata, but that metadata
|
||||
is not an app-authoring contract. Runtime attachment: `/slhx.js` installs
|
||||
is not an app-authoring contract. Runtime attachment: `/hemx.js` installs
|
||||
delegated root listeners for forms, clicks, and pointer/drag events. App authors
|
||||
keep composing generated resources; they do not attach per-node listeners, copy
|
||||
numeric ids, or write selector glue.
|
||||
@@ -298,18 +298,18 @@ No framework download. No VDOM. No hydration. No game loop.
|
||||
|
||||
## 10. Why this is not a React/Vue/htmx app
|
||||
|
||||
| Concern | React/Vue | htmx+SSR | slhx |
|
||||
| Concern | React/Vue | htmx+SSR | hemx |
|
||||
|---|---|---|---|
|
||||
| SSR | RSC/Vue SSR | native | native (hemplate) |
|
||||
| 60fps drag | 100ms re-render + React-DnD | custom JS | WASM handler, typed update |
|
||||
| Optimistic update | useOptimistic | impossible | `board.update` → `SyncEffect::send_patch` |
|
||||
| Offline support | Service Worker + custom | impossible | patch queue in `slhx-sync` |
|
||||
| Offline support | Service Worker + custom | impossible | patch queue in `hemx-sync` |
|
||||
| Conflict resolution | manual / Yjs CRDT | impossible | server-authoritative patch |
|
||||
| Presence | WebSocket + custom state | SSE possible | `Effect::broadcast` over channel |
|
||||
| Keyed DOM | React key | not a concern | `KeyedSlot<K, T>` compile-time |
|
||||
| Forms | React Hook Form | HTML native, but no validation bridge | `Form<T>` derived from `.heml` surface |
|
||||
| Routing | React Router / Vue Router | HTML links, but no state routing | `Effect::navigate` with scroll/title |
|
||||
| Total JS shipped | ~300KB+ | ~20KB htmx + custom | ~3KB slhx.js interpreter |
|
||||
| Total JS shipped | ~300KB+ | ~20KB htmx + custom | ~3KB hemx.js interpreter |
|
||||
|
||||
---
|
||||
|
||||
@@ -317,7 +317,7 @@ No framework download. No VDOM. No hydration. No game loop.
|
||||
|
||||
```text
|
||||
A local-first multiplayer board where all high-frequency UI runs as Rust/WASM effects,
|
||||
all durable state syncs through slhx-sync,
|
||||
all durable state syncs through hemx-sync,
|
||||
all HTML is hemplate-rendered,
|
||||
and the browser runtime only executes typed postcard DOM ops.
|
||||
```
|
||||
@@ -338,6 +338,6 @@ But:
|
||||
```text
|
||||
Rust owns types.
|
||||
hemplate owns structure.
|
||||
slhx owns interaction.
|
||||
hemx owns interaction.
|
||||
browser executes ops.
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user