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:
+7
-7
@@ -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 |
|
||||||
|
|||||||
@@ -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"))
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user