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:
slhx agent
2026-06-05 06:52:37 +02:00
parent d4e865ef92
commit c33500440e
69 changed files with 1415 additions and 1415 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
[package]
name = "slhx-techdemo"
name = "hemx-techdemo"
version.workspace = true
edition.workspace = true
publish = false
@@ -11,14 +11,14 @@ path = "src/lib.rs"
axum = "0.7"
futures-util = "0.3"
hemplate = { path = "../../../hemplate/hemplate" }
slhx = { path = "../../slhx" }
slhx-axum = { path = "../../slhx-axum" }
hemx = { path = "../../hemx" }
hemx-axum = { path = "../../hemx-axum" }
tokio = { version = "1", features = ["macros", "net", "rt-multi-thread", "time"] }
[dev-dependencies]
scraper = "0.23"
slhx-test = { path = "../../slhx-test" }
hemx-test = { path = "../../hemx-test" }
thirtyfour = "0.35"
[build-dependencies]
slhx-build = { path = "../../slhx-build" }
hemx-build = { path = "../../hemx-build" }
+9 -9
View File
@@ -1,12 +1,12 @@
# slhx full techdemo
# hemx full techdemo
Run:
cargo run -p slhx-techdemo
cargo run -p hemx-techdemo
Open <http://127.0.0.1:3002>.
This is a polished Linear-style product demo for planning typed work across lanes. It is tailored to showcase slhx strengths:
This is a polished Linear-style product demo for planning typed work across lanes. It is tailored to showcase hemx strengths:
- modern SSR-first UI
- generated target objects from `.heml`
@@ -17,12 +17,12 @@ This is a polished Linear-style product demo for planning typed work across lane
- root-scoped runtime application without selector lookups
- page-enhancer navigation with native link fallback
- SSE server push into a generated slot
- drag-and-drop lane moves persisted by typed server handlers through the slhx runtime
- an explicit advanced opaque canvas island fed by a generated event helper, without teaching slhx core about the widget
- no user-authored browser JavaScript in slhx-managed UI; the island JavaScript is a leaf-widget escape hatch
- drag-and-drop lane moves persisted by typed server handlers through the hemx runtime
- an explicit advanced opaque canvas island fed by a generated event helper, without teaching hemx core about the widget
- no user-authored browser JavaScript in hemx-managed UI; the island JavaScript is a leaf-widget escape hatch
Verification:
cargo test -p slhx-techdemo --test e2e
cargo test -p slhx-techdemo --test browser_e2e
mutest -p slhx-techdemo -f examples/techdemo/src/main.rs -F 'registry' -j 2 --timeout 90 -- --test e2e
cargo test -p hemx-techdemo --test e2e
cargo test -p hemx-techdemo --test browser_e2e
mutest -p hemx-techdemo -f examples/techdemo/src/main.rs -F 'registry' -j 2 --timeout 90 -- --test e2e
+1 -1
View File
@@ -1,3 +1,3 @@
fn main() {
slhx_build::app().run().unwrap();
hemx_build::app().run().unwrap();
}
+6 -6
View File
@@ -1,4 +1,4 @@
#[slhx::surface]
#[hemx::surface]
pub mod ui {}
#[cfg(test)]
@@ -7,8 +7,8 @@ mod tests {
use super::ui::issue_card::advance_work;
use super::ui::issue_lane::events as lane_events;
use hemplate::Hemplate;
use slhx::IntoEffect;
use slhx_test::inspect;
use hemx::IntoEffect;
use hemx_test::inspect;
#[derive(Hemplate)]
#[hemplate = "partials"]
@@ -18,7 +18,7 @@ mod tests {
#[allow(dead_code)]
#[derive(Clone, Debug)]
#[slhx::form("launch_work")]
#[hemx::form("launch_work")]
struct LaunchWork {
title: String,
lane: String,
@@ -43,8 +43,8 @@ mod tests {
// req: examples/001 req: form/001 req: form/004 req: form/006 req: derive_handler/003
#[test]
fn techdemo_form_handler_is_checked_against_hemplate_form() {
#[slhx::handler]
fn launch_work(_form: slhx::Form<LaunchWork>) -> impl IntoEffect {
#[hemx::handler]
fn launch_work(_form: hemx::Form<LaunchWork>) -> impl IntoEffect {
notice.text("queued")
}
+16 -16
View File
@@ -5,14 +5,14 @@ use axum::routing::get;
use axum::Router;
use futures_util::{stream, StreamExt};
use hemplate::Hemplate;
use slhx::{CssClass, CssClasses, EventName, Html, IntoEffect};
use slhx_axum::{
use hemx::{CssClass, CssClasses, EventName, Html, IntoEffect};
use hemx_axum::{
interactions, runtime_js, sse, DispatchRegistry, DispatchRejection, EffectResponse,
InteractionRequest, PageRequest,
};
use slhx_techdemo::ui;
use slhx_techdemo::ui::control_center::{self as control, classes};
use slhx_techdemo::ui::{issue_card as card_control, issue_lane as lane_control};
use hemx_techdemo::ui;
use hemx_techdemo::ui::control_center::{self as control, classes};
use hemx_techdemo::ui::{issue_card as card_control, issue_lane as lane_control};
use std::collections::{BTreeMap, VecDeque};
use std::convert::Infallible;
use std::net::SocketAddr;
@@ -24,7 +24,7 @@ const LANES: [(&str, &str, &str); 3] = [
("runtime", "Runtime", "typed updates → DOM"),
("product", "Product", "Native UX, zero app JS"),
];
const ISLAND_ORBIT: EventName = EventName::new("slhx:island-orbit");
const ISLAND_ORBIT: EventName = EventName::new("hemx:island-orbit");
#[derive(Clone)]
struct WorkItem {
@@ -226,18 +226,18 @@ async fn main() {
.route("/architecture", get(architecture))
.route("/events", get(events))
.route("/favicon.ico", get(favicon))
.route("/slhx.js", get(runtime))
.route("/hemx.js", get(runtime))
.route("/app.css", get(app_css))
.route("/control_center.css", get(control_center_css))
.route("/island.js", get(island_js))
.with_state(state);
let addr = std::env::var("SLHX_TECHDEMO_ADDR")
let addr = std::env::var("HEMX_TECHDEMO_ADDR")
.ok()
.and_then(|addr| addr.parse().ok())
.unwrap_or_else(|| SocketAddr::from(([127, 0, 0, 1], 3002)));
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
println!("slhx techdemo: http://{addr}");
println!("hemx techdemo: http://{addr}");
axum::serve(listener, app).await.unwrap();
}
@@ -246,7 +246,7 @@ async fn home(State(state): State<Arc<Shared>>, request: PageRequest) -> impl In
let demo = state.demo.lock().unwrap().clone();
request
.page_html(page_html(&demo), shell)
.title("slhx Techdemo")
.title("hemx Techdemo")
.fingerprint(ui::BUILD_FINGERPRINT)
}
@@ -261,7 +261,7 @@ async fn architecture(request: PageRequest) -> impl IntoResponse {
});
request
.page_html(body, shell)
.title("slhx Architecture")
.title("hemx Architecture")
.fingerprint(ui::BUILD_FINGERPRINT)
}
@@ -659,11 +659,11 @@ mod tests {
.select(&selector("title"))
.next()
.map(|title| title.text().collect::<String>()),
Some("slhx Techdemo".to_owned())
Some("hemx Techdemo".to_owned())
);
assert_eq!(
document
.select(&selector("script[src=\"/slhx.js\"]"))
.select(&selector("script[src=\"/hemx.js\"]"))
.count(),
1
);
@@ -687,7 +687,7 @@ mod tests {
);
assert_eq!(
document
.select(&selector("main[data-slhx-root=\"techdemo\"]"))
.select(&selector("main[data-hemx-root=\"techdemo\"]"))
.count(),
1
);
@@ -706,7 +706,7 @@ mod tests {
let document = Html::parse_fragment(html.as_str());
assert_eq!(
document
.select(&selector("[data-slhx-root=\"techdemo\"]"))
.select(&selector("[data-hemx-root=\"techdemo\"]"))
.count(),
1
);
@@ -853,7 +853,7 @@ mod tests {
assert_eq!(items.len(), 3);
assert_eq!(
items[1].text().collect::<String>(),
"Fetched HTML with X-SLHX-Partial"
"Fetched HTML with X-HEMX-Partial"
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ main { width:min(1180px, calc(100vw - 32px)); margin:0 auto; padding:38px 0 56px
.hero-shell { display:grid; grid-template-columns:1.35fr .85fr; gap:22px; align-items:stretch; }
.hero-copy, .hero-panel, .command-card, .board-card, .glass-card, .topology { border:1px solid var(--line); background:linear-gradient(145deg, rgba(255,255,255,.14), rgba(255,255,255,.055)); box-shadow:0 24px 90px rgba(0,0,0,.36), inset 0 1px 0 rgba(255,255,255,.12); backdrop-filter: blur(22px) saturate(145%); border-radius:28px; }
.hero-copy { padding:34px; overflow:hidden; position:relative; }
.hero-copy::after { content:"slhx"; position:absolute; right:-18px; bottom:8px; font-size:86px; font-weight:900; color:rgba(255,255,255,.045); }
.hero-copy::after { content:"hemx"; position:absolute; right:-18px; bottom:8px; font-size:86px; font-weight:900; color:rgba(255,255,255,.045); }
.eyebrow { color:var(--cyan); text-transform:uppercase; letter-spacing:.2em; font-weight:800; font-size:12px; }
h1 { font-size:clamp(42px, 7vw, 84px); line-height:.88; letter-spacing:-.075em; margin:12px 0 18px; max-width:900px; text-wrap:balance; overflow-wrap:anywhere; }
h2 { margin:0 0 16px; letter-spacing:-.035em; }
+2 -2
View File
@@ -3,8 +3,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>slhx Techdemo</title>
<script src="/slhx.js" defer></script>
<title>hemx Techdemo</title>
<script src="/hemx.js" defer></script>
<script src="/island.js" defer></script>
<link rel="stylesheet" href="/app.css">
<link rel="stylesheet" href="/control_center.css">
+16 -16
View File
@@ -1,22 +1,22 @@
<main data-slhx-root="techdemo" data-slhx-sse="/events">
<main data-hemx-root="techdemo" data-hemx-sse="/events">
<section class="hero-shell">
<div class="hero-copy">
<p class="eyebrow">slhx Control Plane</p>
<p class="eyebrow">hemx Control Plane</p>
<h1>A Linear-class work system without a frontend framework.</h1>
<p class="lede">Create, inspect, advance, and stream work through native HTML, generated typed resources, and compact update batches. The UI feels app-grade; the model stays server-owned and boring.</p>
</div>
<div class="hero-panel" data-slhx-slot="hero_metrics">{+= self.hero =+}</div>
<div class="hero-panel" data-hemx-slot="hero_metrics">{+= self.hero =+}</div>
</section>
<nav class="topology" data-slhx-slot="nav">
<a href="/" data-slhx-nav="">Live system</a>
<a href="/architecture" data-slhx-nav="">Architecture</a>
<nav class="topology" data-hemx-slot="nav">
<a href="/" data-hemx-nav="">Live system</a>
<a href="/architecture" data-hemx-nav="">Architecture</a>
</nav>
<section class="workspace">
<aside class="command-card">
<h2>Create an issue</h2>
<form id="launch-work" method="post" data-slhx-handle="launch_work" data-slhx-form="launch_work" data-slhx-disable-while-pending>
<form id="launch-work" method="post" data-hemx-handle="launch_work" data-hemx-form="launch_work" data-hemx-disable-while-pending>
<label>Title <input name="title" required="required" value="Ship typed updates"></label>
<label>Lane
<select name="lane" required="required">
@@ -26,13 +26,13 @@
</select>
</label>
<label>Impact <input name="impact" type="number" min="1" max="9" value="7"></label>
<button class="primary-action" type="button" form="launch-work" data-slhx-handle="launch_work">Create issue</button>
<button class="primary-action" type="button" form="launch-work" data-hemx-handle="launch_work">Create issue</button>
</form>
<div class="quick-actions">
<button type="button" data-slhx-handle="simulate_push">Simulate server push</button>
<button type="button" data-slhx-handle="reset_demo">Reset demo</button>
<button type="button" data-hemx-handle="simulate_push">Simulate server push</button>
<button type="button" data-hemx-handle="reset_demo">Reset demo</button>
</div>
<p data-slhx-slot="notice" class="notice">Every control posts through a generated handle and receives typed updates.</p>
<p data-hemx-slot="notice" class="notice">Every control posts through a generated handle and receives typed updates.</p>
</aside>
<section class="board-card">
@@ -40,27 +40,27 @@
<span>Generated slots + keyed cards</span>
<strong>generated resources, no selectors</strong>
</div>
<div data-slhx-slot="board">{+= self.board =+}</div>
<div data-hemx-slot="board">{+= self.board =+}</div>
</section>
</section>
<section class="insight-grid">
<article class="glass-card">
<h2>Update inspector</h2>
<div data-slhx-slot="inspector" class="inspector-slot">{+= self.inspector =+}</div>
<div data-hemx-slot="inspector" class="inspector-slot">{+= self.inspector =+}</div>
</article>
<article class="glass-card island-card" data-slhx-island="orbit" +data-island-snapshot="self.island_snapshot">
<article class="glass-card island-card" data-hemx-island="orbit" +data-island-snapshot="self.island_snapshot">
<h2>Opaque island bridge</h2>
<canvas width="360" height="180" aria-label="Animated island orbit"></canvas>
<p data-island-readout="">Waiting for Rust snapshot…</p>
</article>
<article class="glass-card">
<h2>Activity stream</h2>
<div data-slhx-slot="activity">{+= self.activity =+}</div>
<div data-hemx-slot="activity">{+= self.activity =+}</div>
</article>
<article class="glass-card glow">
<h2>Server push</h2>
<div data-slhx-slot="live_feed" class="live-feed">Waiting for SSE heartbeat…</div>
<div data-hemx-slot="live_feed" class="live-feed">Waiting for SSE heartbeat…</div>
</article>
</section>
+6 -6
View File
@@ -1,4 +1,4 @@
// Opaque leaf-widget island. slhx talks to it only with native CustomEvent payloads.
// Opaque leaf-widget island. hemx talks to it only with native CustomEvent payloads.
// req: interop/001 req: examples/001
(() => {
const roots = new WeakMap();
@@ -20,7 +20,7 @@
function rootOf(node) {
for (let el = node; el; el = el.parentElement) {
if (el.hasAttribute && el.hasAttribute("data-slhx-root")) return el;
if (el.hasAttribute && el.hasAttribute("data-hemx-root")) return el;
}
return document.documentElement;
}
@@ -66,7 +66,7 @@
ctx.fillStyle = "#fff";
ctx.font = "700 16px system-ui, sans-serif";
ctx.fillText("slhx island", 18, 30);
ctx.fillText("hemx island", 18, 30);
ctx.font = "12px system-ui, sans-serif";
ctx.fillStyle = "rgba(255,255,255,.74)";
ctx.fillText(`cards ${state.snapshot.cards} · impact ${state.snapshot.impact} · boost ${state.snapshot.power}`, 18, 50);
@@ -85,11 +85,11 @@
state.snapshot = parseSnapshot(event.detail);
island.setAttribute("data-island-snapshot", event.detail);
};
root.addEventListener("slhx:island-orbit", update);
root.addEventListener("hemx:island-orbit", update);
function tick() {
if (!document.contains(island)) {
root.removeEventListener("slhx:island-orbit", update);
root.removeEventListener("hemx:island-orbit", update);
return;
}
state.frame += 1;
@@ -101,7 +101,7 @@
function scan() {
forEachElement(document, (el) => {
if (el.hasAttribute("data-slhx-island")) boot(el);
if (el.hasAttribute("data-hemx-island")) boot(el);
});
}
@@ -1,5 +1,5 @@
<ol class="activity">
<li>Clicked a real anchor</li>
<li>Fetched HTML with X-SLHX-Partial</li>
<li>Fetched HTML with X-HEMX-Partial</li>
<li>Preserved native fallback semantics</li>
</ol>
@@ -1,5 +1,5 @@
<div class="lanes">
<section class="lane"><h3>hemplate</h3><p>Owns syntax and Surface facts.</p></section>
<section class="lane"><h3>slhx-build</h3><p>Generates resources and lowering tables.</p></section>
<section class="lane"><h3>hemx-build</h3><p>Generates resources and lowering tables.</p></section>
<section class="lane"><h3>runtime</h3><p>Executes compact typed DOM ops.</p></section>
</div>
@@ -2,8 +2,8 @@
<header><strong>{+ self.title +}</strong><span class="pill">{+ self.stage +}</span></header>
<div class="impact"><i +style="self.impact_style"></i></div>
<div class="card-actions">
<button type="button" data-slhx-handle="spotlight_work" +data-work-id="self.id" name="work_id" +value="self.id">Inspect</button>
<button type="button" data-slhx-handle="advance_work" +data-work-id="self.id" name="work_id" +value="self.id">Advance</button>
<button type="button" data-slhx-handle="delete_work" +data-work-id="self.id" name="work_id" +value="self.id">Delete</button>
<button type="button" data-hemx-handle="spotlight_work" +data-work-id="self.id" name="work_id" +value="self.id">Inspect</button>
<button type="button" data-hemx-handle="advance_work" +data-work-id="self.id" name="work_id" +value="self.id">Advance</button>
<button type="button" data-hemx-handle="delete_work" +data-work-id="self.id" name="work_id" +value="self.id">Delete</button>
</div>
</article>
@@ -1,4 +1,4 @@
<section +class="self.class" +data-lane="self.lane_id" data-slhx-handle="move_to_lane" data-slhx-on="drop">
<section +class="self.class" +data-lane="self.lane_id" data-hemx-handle="move_to_lane" data-hemx-on="drop">
<h3>{+ self.title +}</h3>
<p>{+ self.description +}</p>
<template h-for="card in &self.cards">
+6 -6
View File
@@ -1,5 +1,5 @@
use slhx_techdemo::ui::control_center::{self as control, launch_work, simulate_push};
use slhx_test::{
use hemx_techdemo::ui::control_center::{self as control, launch_work, simulate_push};
use hemx_test::{
any_root_selector, document_body_selector, handle_button_selector, handle_selector,
island_probe_script, island_readout_selector, keyed_selector, nav_link_selector,
scoped_island_readout_selector, target_selector,
@@ -36,8 +36,8 @@ impl Drop for ChildProcess {
#[tokio::test]
async fn browser_drives_typed_product_end_to_end() -> WebDriverResult<()> {
// req: examples/001 req: dx/008 req: form/002 req: page_swap/002 req: push/003
let mut app = Command::new(env!("CARGO_BIN_EXE_slhx-techdemo"));
app.env("SLHX_TECHDEMO_ADDR", APP_ADDR);
let mut app = Command::new(env!("CARGO_BIN_EXE_hemx-techdemo"));
app.env("HEMX_TECHDEMO_ADDR", APP_ADDR);
let _app = ChildProcess::spawn(app);
wait_for_tcp(APP_ADDR);
@@ -153,7 +153,7 @@ async fn wait_for_runtime(driver: &WebDriver) -> WebDriverResult<()> {
loop {
let loaded = driver
.execute(
"return !!window.slhx && window.slhx.roots().length > 0",
"return !!window.hemx && window.hemx.roots().length > 0",
Vec::new(),
)
.await?
@@ -164,7 +164,7 @@ async fn wait_for_runtime(driver: &WebDriver) -> WebDriverResult<()> {
return Ok(());
}
if Instant::now() >= deadline {
panic!("timed out waiting for slhx runtime");
panic!("timed out waiting for hemx runtime");
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
+16 -16
View File
@@ -1,9 +1,9 @@
use scraper::{Html, Selector};
use slhx_techdemo::ui::control_center::{self as control, launch_work, reset_demo, simulate_push};
use slhx_techdemo::ui::issue_card::{advance_work, delete_work, spotlight_work};
use slhx_techdemo::ui::issue_lane::move_to_lane as move_to_lane_handle;
use slhx_techdemo::ui::BUILD_FINGERPRINT;
use slhx_test::{
use hemx_techdemo::ui::control_center::{self as control, launch_work, reset_demo, simulate_push};
use hemx_techdemo::ui::issue_card::{advance_work, delete_work, spotlight_work};
use hemx_techdemo::ui::issue_lane::move_to_lane as move_to_lane_handle;
use hemx_techdemo::ui::BUILD_FINGERPRINT;
use hemx_test::{
class_descendant_selector, class_selector, handle_form_body, inspect_wire,
island_attribute_name, island_event_name, island_selector, island_snapshot_marker,
root_selector, sse_endpoint_marker, strong_text_selector, unknown_handle_form_body,
@@ -22,12 +22,12 @@ struct Server {
impl Server {
fn start() -> Self {
let bin = env!("CARGO_BIN_EXE_slhx-techdemo");
let bin = env!("CARGO_BIN_EXE_hemx-techdemo");
let child = Command::new(bin)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("start slhx-techdemo");
.expect("start hemx-techdemo");
let deadline = Instant::now() + Duration::from_secs(5);
while Instant::now() < deadline {
@@ -36,7 +36,7 @@ impl Server {
}
std::thread::sleep(Duration::from_millis(25));
}
panic!("slhx-techdemo did not listen on {ADDR}");
panic!("hemx-techdemo did not listen on {ADDR}");
}
}
@@ -73,7 +73,7 @@ fn product_is_e2e_working_over_http() {
let favicon = get("/favicon.ico");
assert_eq!(favicon.status, 204);
let runtime = get("/slhx.js");
let runtime = get("/hemx.js");
assert_eq!(runtime.status, 200);
assert!(runtime.header("content-type").contains("javascript"));
@@ -85,12 +85,12 @@ fn product_is_e2e_working_over_http() {
assert!(island.text().contains("MutationObserver"));
assert!(island.text().contains("removeEventListener"));
let architecture = request("GET", "/architecture", &[("X-SLHX-Partial", "1")], "");
let architecture = request("GET", "/architecture", &[("X-HEMX-Partial", "1")], "");
assert_eq!(architecture.status, 200);
assert!(architecture.header("x-slhx-partial").contains("true"));
assert!(architecture.header("x-hemx-partial").contains("true"));
let architecture_doc = Html::parse_document(architecture.text());
assert_text(&architecture_doc, "Page swap");
assert_text(&architecture_doc, "slhx-build");
assert_text(&architecture_doc, "hemx-build");
let launch = post(
"/",
@@ -277,19 +277,19 @@ fn product_is_e2e_working_over_http() {
let sse = get("/events?once=1");
assert_eq!(sse.status, 200);
assert!(sse.header("content-type").contains("text/event-stream"));
assert!(sse.text().contains("event: slhx"));
assert!(sse.text().contains("event: hemx"));
assert!(sse.text().contains("data: "));
let unknown = post("/", &unknown_handle_form_body(999999));
assert_eq!(unknown.status, 404);
assert!(unknown.text().contains("unknown slhx handle id 999999"));
assert!(unknown.text().contains("unknown hemx handle id 999999"));
}
fn assert_effect_response(response: &Response) {
assert_eq!(response.status, 200);
assert!(response.header("content-type").contains("application/slhx"));
assert!(response.header("content-type").contains("application/hemx"));
assert_eq!(
response.header("x-slhx-fingerprint"),
response.header("x-hemx-fingerprint"),
BUILD_FINGERPRINT.0.to_string()
);
assert!(!response.effects().is_empty());