docs(examples): hide batch internals from UI copy

Replace beginner-facing EffectBatch/slot-effect wording with generated update language while keeping wire-batch types in tests and low-level APIs.

req: dx/006

req: examples/003
This commit is contained in:
slhx agent
2026-06-02 03:40:12 +02:00
parent 49743c1a67
commit 998e15758a
10 changed files with 22 additions and 22 deletions
+7 -7
View File
@@ -143,7 +143,7 @@ pub fn create_card(
} }
``` ```
HTML submits as usual. Server returns `EffectBatch`. Browser applies DOM ops. HTML submits as usual. Server returns a typed update batch. Browser applies DOM ops.
--- ---
@@ -175,7 +175,7 @@ pub fn drag_card(
} }
``` ```
Zero round-trip. Zero custom JS. Pure Rust → EffectBatch → DOM. Zero round-trip. Zero custom JS. Pure Rust → typed updates → DOM.
--- ---
@@ -259,14 +259,14 @@ pub fn user_joined(user: UserPresence) -> impl IntoEffect {
} }
``` ```
Browser receives raw `EffectBatch` over WebSocket/SSE: Browser receives typed update bytes over WebSocket/SSE:
```text ```text
Op::AppendKeyed(slot=PRESENCE_USER, key=user_id, html=...) append keyed presence user
Op::RemoveKeyed(slot=PRESENCE_USER, key=user_id) remove keyed presence user
``` ```
The runtime does not know "presence". It executes ops. The runtime does not know "presence". It executes generated DOM updates.
--- ---
@@ -298,7 +298,7 @@ No framework download. No VDOM. No hydration. No game loop.
| Concern | React/Vue | htmx+SSR | slhx | | Concern | React/Vue | htmx+SSR | slhx |
|---|---|---|---| |---|---|---|---|
| SSR | RSC/Vue SSR | native | native (hemplate) | | SSR | RSC/Vue SSR | native | native (hemplate) |
| 60fps drag | 100ms re-render + React-DnD | custom JS | WASM handler, EffectBatch | | 60fps drag | 100ms re-render + React-DnD | custom JS | WASM handler, typed update |
| Optimistic update | useOptimistic | impossible | `board.update``SyncEffect::send_patch` | | 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 `slhx-sync` |
| Conflict resolution | manual / Yjs CRDT | impossible | server-authoritative patch | | Conflict resolution | manual / Yjs CRDT | impossible | server-authoritative patch |
+5 -5
View File
@@ -21,7 +21,7 @@ use std::time::Duration;
const LANES: [(&str, &str, &str); 3] = [ const LANES: [(&str, &str, &str); 3] = [
("compiler", "Compiler", "Surface → generated API"), ("compiler", "Compiler", "Surface → generated API"),
("runtime", "Runtime", "EffectBatch → DOM"), ("runtime", "Runtime", "typed updates → DOM"),
("product", "Product", "Native UX, zero app JS"), ("product", "Product", "Native UX, zero app JS"),
]; ];
@@ -315,7 +315,7 @@ fn registry(shared: Arc<Shared>) -> impl DispatchRegistry {
} }
}); });
if let Some(title) = title { if let Some(title) = title {
demo.spotlight = format!("{title} advanced without a selector: the server returned slot effects."); demo.spotlight = format!("{title} advanced without a selector: the server returned generated slot updates.");
demo.log(format!("Advanced {title}")); demo.log(format!("Advanced {title}"));
} }
demo_effects(&demo, "Pipeline advanced") demo_effects(&demo, "Pipeline advanced")
@@ -382,7 +382,7 @@ fn registry(shared: Arc<Shared>) -> impl DispatchRegistry {
move |_| { move |_| {
// req: push/003 req: examples/001 // req: push/003 req: examples/001
let mut demo = shared.demo.lock().unwrap(); let mut demo = shared.demo.lock().unwrap();
demo.log("Simulated push event produced the same EffectBatch shape"); demo.log("Simulated push event produced the same generated update shape");
( (
ui::put(slots::live_feed, &LiveFeed { ui::put(slots::live_feed, &LiveFeed {
tick: demo.activity.len() as u64, tick: demo.activity.len() as u64,
@@ -691,7 +691,7 @@ mod tests {
assert_eq!(document.select(&selector(".inspector-row")).count(), 3); assert_eq!(document.select(&selector(".inspector-row")).count(), 3);
assert!(document assert!(document
.select(&selector("code")) .select(&selector("code"))
.any(|code| code.text().collect::<String>().contains("EffectBatch"))); .any(|code| code.text().collect::<String>().contains("typed update batch")));
} }
// req: html_safety/002 req: view/001 req: test/005 // req: html_safety/002 req: view/001 req: test/005
@@ -806,7 +806,7 @@ mod tests {
lane.select(&selector("p")) lane.select(&selector("p"))
.next() .next()
.map(|paragraph| paragraph.text().collect::<String>()) .map(|paragraph| paragraph.text().collect::<String>())
.is_some_and(|text| text.contains("EffectBatch ops")) .is_some_and(|text| text.contains("typed DOM ops"))
})); }));
} }
} }
+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-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, .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 { padding:34px; overflow:hidden; position:relative; }
.hero-copy::after { content:"EffectBatch"; position:absolute; right:-18px; bottom:8px; font-size:86px; font-weight:900; color:rgba(255,255,255,.045); } .hero-copy::after { content:"slhx"; 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; } .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; } 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; } h2 { margin:0 0 16px; letter-spacing:-.035em; }
@@ -3,7 +3,7 @@
<div class="hero-copy"> <div class="hero-copy">
<p class="eyebrow">slhx Control Plane</p> <p class="eyebrow">slhx Control Plane</p>
<h1>A Linear-class work system without a frontend framework.</h1> <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 EffectBatches. The UI feels app-grade; the model stays server-owned and boring.</p> <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>
<div class="hero-panel" data-slhx-slot="hero_metrics">{+= self.hero =+}</div> <div class="hero-panel" data-slhx-slot="hero_metrics">{+= self.hero =+}</div>
</section> </section>
@@ -1,5 +1,5 @@
<div class="lanes"> <div class="lanes">
<section class="lane"><h3>hemplate</h3><p>Owns syntax and Surface facts.</p></section> <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>slhx-build</h3><p>Generates resources and lowering tables.</p></section>
<section class="lane"><h3>runtime</h3><p>Executes compact EffectBatch ops.</p></section> <section class="lane"><h3>runtime</h3><p>Executes compact typed DOM ops.</p></section>
</div> </div>
@@ -1,4 +1,4 @@
{+= self.selected =+} {+= self.selected =+}
<div class="inspector-row"><b>What happened</b><br>{+ self.spotlight +}</div> <div class="inspector-row"><b>What happened</b><br>{+ self.spotlight +}</div>
<div class="inspector-row"><b>Wire contract</b><br><code>POST __h → application/slhx → EffectBatch</code></div> <div class="inspector-row"><b>Wire contract</b><br><code>POST __h → application/slhx → typed update batch</code></div>
<div class="inspector-row"><b>Runtime</b><br>Root-scoped delegated listeners; numeric targets only.</div> <div class="inspector-row"><b>Runtime</b><br>Root-scoped delegated listeners; numeric targets only.</div>
@@ -1 +1 @@
<div class="live-row"><strong>SSE tick #{+ self.tick +}</strong><br>Server streamed a typed EffectBatch into <code>slots::live_feed</code>.</div> <div class="live-row"><strong>SSE tick #{+ self.tick +}</strong><br>Server streamed a generated update into <code>slots::live_feed</code>.</div>
+1 -1
View File
@@ -223,7 +223,7 @@ fn product_is_e2e_working_over_http() {
let push_batch = simulated_push.batch(); let push_batch = simulated_push.batch();
assert_payload_contains(&push_batch, "SSE tick"); assert_payload_contains(&push_batch, "SSE tick");
assert_payload_contains(&push_batch, "Push simulated · no client app code"); assert_payload_contains(&push_batch, "Push simulated · no client app code");
assert_payload_contains(&push_batch, "Simulated push event produced the same EffectBatch shape"); assert_payload_contains(&push_batch, "Simulated push event produced the same generated update shape");
assert_emit(&push_batch, "slhx:island-orbit", "activity rows"); assert_emit(&push_batch, "slhx:island-orbit", "activity rows");
let delete_missing = post( let delete_missing = post(
+3 -3
View File
@@ -173,7 +173,7 @@ fn registry(state: Arc<ExampleState>) -> impl DispatchRegistry {
// req: page_swap/002, req: examples/001 // req: page_swap/002, req: examples/001
( (
page_swap::put(page_slots::content, &DocsContent { page_swap::put(page_slots::content, &DocsContent {
message: "This content came from an EffectBatch.", message: "This content came from a generated update response.",
}), }),
page_slots::title.text("Docs"), page_slots::title.text("Docs"),
push("/docs"), push("/docs"),
@@ -257,7 +257,7 @@ mod tests {
// req: html_safety/002 req: view/001 req: test/005 // req: html_safety/002 req: view/001 req: test/005
#[test] #[test]
fn docs_content_payload_is_rendered_by_a_hemplate_view() { fn docs_content_payload_is_rendered_by_a_hemplate_view() {
let html = render_docs_content("This content came from an EffectBatch."); let html = render_docs_content("This content came from a generated update response.");
let document = Html::parse_fragment(html.as_str()); let document = Html::parse_fragment(html.as_str());
assert_eq!( assert_eq!(
document document
@@ -271,7 +271,7 @@ mod tests {
.select(&selector("p")) .select(&selector("p"))
.next() .next()
.map(|paragraph| paragraph.text().collect::<String>()), .map(|paragraph| paragraph.text().collect::<String>()),
Some("This content came from an EffectBatch.".to_owned()) Some("This content came from a generated update response.".to_owned())
); );
} }
+1 -1
View File
@@ -115,7 +115,7 @@ fn canonical_example_docs_do_not_teach_low_level_plumbing() {
"generated handles and slots", "generated handles and slots",
"generated handle ids", "generated handle ids",
"numeric handle ids", "numeric handle ids",
"EffectBatch responses", "EffectBatch",
"manual registry", "manual registry",
"HandlerRegistry", "HandlerRegistry",
"InteractionForm", "InteractionForm",