From d0dc9729fd81ea3b58323e53d1a5b9c1ed2c381a Mon Sep 17 00:00:00 2001 From: slhx agent Date: Fri, 12 Jun 2026 14:40:25 +0200 Subject: [PATCH] docs: explain reusable partials Add the public 'Where are my components?' answer: hemx maps framework component jobs to hemplate partials, generated helpers, app-owned state, IntoEffect composition, and explicit leaf islands. The pre-commit guard now only blocks commits that change REQUIREMENTS.md without an AGENTS.md review, so unrelated follow-up commits are not trapped by old history. req: canonical_authoring/002 req: canonical_authoring/003 --- .githooks/pre-commit | 16 ++++-------- AGENTS.md | 1 + README.md | 4 +++ docs/recipes/reusable-partials.md | 41 +++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 docs/recipes/reusable-partials.md diff --git a/.githooks/pre-commit b/.githooks/pre-commit index a8342ad..b9e4d6f 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,15 +1,9 @@ #!/usr/bin/env bash set -euo pipefail changed=$(git diff --cached --name-only) -# fail if REQs changed but AGENTS.md is older -if echo "$changed" | grep -q '^REQUIREMENTS.md$' && echo "$changed" | grep -q '^AGENTS.md$'; then - # Both changed — OK - : -else - req_time=$(git log -1 --format=%ct -- REQUIREMENTS.md 2>/dev/null || echo 0) - ag_time=$(git log -1 --format=%ct -- AGENTS.md 2>/dev/null || echo 0) - if [ "$req_time" -gt "$ag_time" ]; then - echo "error: REQUIREMENTS.md newer than AGENTS.md — run: redgate agents > AGENTS.md" - exit 1 - fi +# fail only when this commit changes REQs without reviewing AGENTS.md; +# do not block unrelated commits just because an earlier commit changed REQs. +if echo "$changed" | grep -q '^REQUIREMENTS.md$' && ! echo "$changed" | grep -q '^AGENTS.md$'; then + echo "error: REQUIREMENTS.md changed without AGENTS.md — review AGENTS.md or run: redgate agents > AGENTS.md" + exit 1 fi diff --git a/AGENTS.md b/AGENTS.md index be8df98..136d5c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,6 +54,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file - hemx core stays small: effects, typed ids, registries, and wire schema only. - Routing, auth, sessions, transport, transitions, sync, and storage belong in integration/user crates. - Public examples and beginner APIs should use generated resources and `IntoEffect`, not raw ids or runtime opcodes. +- The public component-reuse explanation lives in `docs/recipes/reusable-partials.md`; do not grow a client component framework to explain partial composition. - Hemlate examples must use real hemplate syntax, not Vue/Handlebars sketches: `{+ expr +}` for escaped text, `{+= expr =+}` only for trusted/rendered HTML, `+attr="expr"` for dynamic attributes, and Rust-shaped `h-if`, `h-for`, `h-match`, `h-case` directives (`h-case="_"` is the default arm). - JS runtime changes must preserve root-scoped lookup and avoid selectors, VDOM, expressions, and per-node listeners. - Host capability adapters must stay at the `hemx-host` boundary: they may call host APIs and return host events, but they must not mutate DOM or own app/domain state. req: host/002 diff --git a/README.md b/README.md index 02ce3ab..c7e992d 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ For beginner and production-shaped app code, stay on this path. req: public_api/ - **Effect:** handlers return typed commands that become a checked effect response. Tuple composition is the normal fixed batch syntax; arrays and `Vec` cover fixed or dynamic repeated partial updates. +- **Reuse:** the hemx answer to framework components is reusable hemplate + partials plus generated helpers, app-owned state, `IntoEffect` composition, + and explicit leaf islands when browser-owned behavior is necessary. See + `docs/recipes/reusable-partials.md`. - **Runtime:** the browser checks the build fingerprint, resolves targets within the current `data-hemx-root`, and applies compatible batches. Mismatched server/runtime builds fail closed instead of silently mutating the wrong DOM. diff --git a/docs/recipes/reusable-partials.md b/docs/recipes/reusable-partials.md new file mode 100644 index 0000000..9c5eedc --- /dev/null +++ b/docs/recipes/reusable-partials.md @@ -0,0 +1,41 @@ +# Where are my components? + +In hemx, the reusable UI unit is a **checked hemplate partial plus generated Rust +helpers**, not a client component instance. You still get reuse and composition; +the ownership moves to places Rust apps can inspect and test. req: canonical_authoring/002 req: canonical_authoring/003 + +| Framework component job | hemx home | +| --- | --- | +| Markup and local UI shape | A `.heml` partial rendered from a Rust view struct. | +| Props | The view struct fields passed into the partial/helper. | +| Stable child identity | `h-key` on repeated partials, exposed through generated keyed helpers. | +| Events | Real forms, links, handles, and explicit generated events. | +| State | App-owned Rust state, commands/events/projections, or integration-owned stores. | +| Updating the UI | Generated commands such as `ui::todo_row.replace(row)`. | +| Composition | `impl IntoEffect`: tuples for fixed mixed batches, arrays for fixed repeated batches, and `Vec` for dynamic repeated batches. | +| Client-only widgets | Explicit islands or Web Components at leaf boundaries. | + +A reusable row should be one partial used in both places: initial render and later +updates. The handler builds domain state, converts it to a view value, and returns +generated commands: + +```rust +( + rows + .into_iter() + .map(|row| ui::todo_row.replace(row)) + .collect::>(), + ui::summary.set(summary), + ui::notice.set("Saved"), +) +``` + +That is the component story: the row partial is reusable; the generated helper +knows the target and swap kind; `IntoEffect` composes the update without a client +component runtime, selector lookup, raw ids, raw opcodes, or manual registry +plumbing. req: public_api/005 + +Use an island only when the browser must own high-frequency local behavior, such +as a chart, map, editor, or media widget. The island is an explicit leaf; it can +emit facts back through generated handles/events, but the app still changes +server-owned UI through normal hemx effects. req: interop/003