From 6e55d9a46f6b245a1ce8a1f1d4728e530f868870 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 1 Jun 2026 23:27:34 +0200 Subject: [PATCH] docs(kanban): show real hemplate composition syntax Refresh the north-star kanban walkthrough to use current hemplate syntax plus lean generated render/slot/form helpers instead of stale sketch syntax and manual ids. req: dx/006 req: component/003 --- examples/kanban.md | 131 ++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 67 deletions(-) diff --git a/examples/kanban.md b/examples/kanban.md index 13d7950..d67f5af 100644 --- a/examples/kanban.md +++ b/examples/kanban.md @@ -11,66 +11,52 @@ This is the north-star integration test for slhx + hemplate + slhx-sync. ## 1. Template: `board.heml` ```html -
-
-

{self.title}

+
+
+

{+ self.title +}

-
- - - -
-
+
+ + + +
+
-
- @for column in self.columns key column.id { -
-

{column.title}

+
+ +
- +
``` Notes on keyed scopes: -- `@for column key column.id` — **required** for slhx-addressable nodes inside -- `@for card key card.id` — **required** -- A slot inside a keyed loop is addressed as `(SlotId, KeyValue)`, never `querySelector(".card:nth-child(3)")` -- Without `key`, slhx rejects the build — no runtime selector fallback +- `h-for="column in &self.columns" h-key="column.id"` — **required** for slhx-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 `querySelector(".card:nth-child(3)")` +- Without `h-key`, slhx rejects the build — no runtime selector fallback --- @@ -101,14 +87,17 @@ FormSurface { } ``` -slhx reads this from `hemplate.surface.postcard` and generates typed constants: +slhx reads this from hemplate Surface facts and generates scoped typed resources: ```rust -pub const CARD: KeyedSlot = KeyedSlot::new(3); -pub const CREATE_CARD: Handle = Handle::new(0); +use ui::board::{forms, slots}; + +slots::card.replace(card.id, CardView::from(card)); +forms::create_card.clear("title"); +ui::render(&BoardView::from(board)); ``` -No string desync. No runtime mapping. +No string desync. No manual ids. The generated module owns the names. --- @@ -145,11 +134,12 @@ pub fn create_card( app.board.update(|board| board.insert_card(form.column, card.clone())); - Effect::batch(( - Effect::append_keyed(slots::CARD, card.id, CardView::from(card)), + ( + slots::card.append(card.id, &CardView::from(card)), + forms::create_card.clear("title"), // slhx-sync: queue atomic board state diff for sync - SyncEffect::send_patch(atoms::BOARD, Patch::insert_card(form.column, card)), - )) + SyncEffect::send_patch(atoms::board, Patch::insert_card(form.column, card)), + ) } ``` @@ -172,10 +162,13 @@ pub fn drag_card( pointer_y: event.y, })); + // Client-local extension APIs stay typed by generated resources; + // names below are illustrative until slhx-sync lands. Effect::batch(( Effect::class_keyed(slots::CARD, event.card_id, "dragging", true), Effect::transform_keyed( - slots::CARD, event.card_id, + slots::CARD, + event.card_id, Transform::translate(event.x, event.y), ), )) @@ -200,10 +193,14 @@ 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. Effect::batch(( Effect::move_keyed( - slots::CARD, event.card_id, - slots::COLUMN, event.to_column, + slots::CARD, + event.card_id, + slots::COLUMN, + event.to_column, InsertBefore(event.before_card), ), Effect::class_keyed(slots::CARD, event.card_id, "dragging", false), @@ -236,14 +233,14 @@ pub fn apply_board_patch( Effect::broadcast( Channel::Board(app.board.id()), Effect::batch(changed_cards.into_iter().map(|c| - Effect::replace_keyed(slots::CARD, c.id, CardView::from(c)) + slots::card.replace(c.id, &CardView::from(c)) )), ), )), PatchResult::Conflict { canonical_board } => Effect::batch(( Effect::set(atoms::BOARD, canonical_board.clone()), - Effect::render(slots::BOARD, BoardView::from(canonical_board)), + slots::board.render(&BoardView::from(canonical_board)), )), } } @@ -258,7 +255,7 @@ Server-authoritative on conflict. No Redux sagas. No React Query cache fades. ```rust #[slhx_sync::presence] pub fn user_joined(user: UserPresence) -> impl IntoEffect { - Effect::append_keyed(slots::PRESENCE_USER, user.id, PresenceBadge::from(user)) + slots::presence_user.append(user.id, &PresenceBadge::from(user)) } ``` @@ -315,8 +312,8 @@ No framework download. No VDOM. No hydration. No game loop. | Offline support | Service Worker + custom | impossible | patch queue in `slhx-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 | `KeydSlot` compile-time | -| Forms | React Hook Form | HTML native, but no validation bridge | `Fork` derived from `.heml` surface | +| Keyed DOM | React key | not a concern | `KeyedSlot` compile-time | +| Forms | React Hook Form | HTML native, but no validation bridge | `Form` 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 |