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
This commit is contained in:
slhx agent
2026-06-12 14:40:25 +02:00
parent 8f96169b03
commit d0dc9729fd
4 changed files with 51 additions and 11 deletions
+4 -10
View File
@@ -1,15 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
changed=$(git diff --cached --name-only) changed=$(git diff --cached --name-only)
# fail if REQs changed but AGENTS.md is older # fail only when this commit changes REQs without reviewing AGENTS.md;
if echo "$changed" | grep -q '^REQUIREMENTS.md$' && echo "$changed" | grep -q '^AGENTS.md$'; then # do not block unrelated commits just because an earlier commit changed REQs.
# Both changed — OK 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"
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 exit 1
fi fi
fi
+1
View File
@@ -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. - 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. - 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. - 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). - 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. - 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 - 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
+4
View File
@@ -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 - **Effect:** handlers return typed commands that become a checked effect
response. Tuple composition is the normal fixed batch syntax; arrays and response. Tuple composition is the normal fixed batch syntax; arrays and
`Vec<T: IntoEffect>` cover fixed or dynamic repeated partial updates. `Vec<T: IntoEffect>` 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 - **Runtime:** the browser checks the build fingerprint, resolves targets within
the current `data-hemx-root`, and applies compatible batches. Mismatched the current `data-hemx-root`, and applies compatible batches. Mismatched
server/runtime builds fail closed instead of silently mutating the wrong DOM. server/runtime builds fail closed instead of silently mutating the wrong DOM.
+41
View File
@@ -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<T: IntoEffect>` 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::<Vec<_>>(),
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