From e4bd6db13a9815c14d92896b7e807f58deb8e075 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 3 Aug 2026 00:09:43 +0200 Subject: [PATCH] docs(agent): track idiomatic Hemx guidance --- .agents/skills/idiomatic-hemx/SKILL.md | 337 +++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 .agents/skills/idiomatic-hemx/SKILL.md diff --git a/.agents/skills/idiomatic-hemx/SKILL.md b/.agents/skills/idiomatic-hemx/SKILL.md new file mode 100644 index 0000000..a7d55ae --- /dev/null +++ b/.agents/skills/idiomatic-hemx/SKILL.md @@ -0,0 +1,337 @@ +--- +name: idiomatic-hemx +description: >- + Use when a user explicitly asks how to design, implement, review, + productionize, or scale an application built with hemx and hemplate, + including deciding whether or where microservices belong. Preserve hemx's + generated-resource and server-owned effect model, then choose the smallest + evidence-backed scale stage. Do not use for generic Rust/Axum architecture, + generic microservice advice, or development of the hemx framework itself. +--- + +# Idiomatic hemx + +## One job + +Choose, explain, or implement the smallest production architecture that keeps a +hemx/hemplate application boring as it grows. The high-leverage move is usually +to preserve one direction of travel: + +```text +semantic .heml surface + -> generated typed resources + -> ordinary Rust domain/application code + -> generated partial/page effects + -> tiny browser runtime +``` + +Scale providers and deployment topology around that loop. Do not replace it +with selectors, a client state graph, raw wire operations, or speculative +services. + +## Trigger boundary + +Load this skill for explicit hemx/hemplate application usage, architecture, +production-readiness, scaling, or a review of those decisions. It also applies +when the user asks whether a hemx application should become microservices. + +Do not load it for: + +- generic Rust, Axum, HTML, CSS, database, or microservice questions with no hemx + application decision; +- visual design alone; +- changing hemx/hemplate internals rather than using their public model; +- a tiny obvious application patch where no authoring or scaling judgment is at + stake. + +## Authority before taste + +Inside the hemx repository, read `AGENTS.md`, then +`docs/v1-product-evidence.md` and `REQUIREMENTS.md`. Use `PLAN.md` only as the +mutable implementation cursor. Inspect only the nearest authoritative material +needed for the decision: + +- `docs/hemplate-syntax.md` for real `.heml` syntax; +- `docs/tutorial-saas.md` for the production-shaped application boundary; +- `docs/recipes/reusable-partials.md` for composition; +- the auth, persistence, observability, deploy/versioning, offline, and local + command-log recipes for their named concerns; +- the nearest maintained example and current public Rust API before naming an + API in code. + +Project requirements and observed code outrank this skill. Outside this +repository, establish the application's versions and elected contracts instead +of assuming current-main APIs. If auth, storage, transport, replay, deployment, +or service contracts are absent, identify the missing decision; do not invent a +provider or abstraction that makes the system look complete. + +When invoked for a Hemx application repository, inspect its elected `AGENTS.md` +or equivalent instruction surface. Ensure it explicitly requires all HTML +surfaces and fragments to be authored as semantic `.heml` templates rendered +through Hemplate's generated typed resources, and explicitly forbids constructing, +concatenating, interpolating, or formatting HTML in Rust. If the current request +authorizes repository edits, patch that instruction surface before application +implementation; otherwise report the missing policy as a blocker. Do not copy the +rest of this skill into project instructions. + +## Decision loop + +### 1. Start with the user job and one load-bearing path + +Name the concrete request, mutation, navigation, push update, or recovery path +that must work. Trace it end to end before discussing topology: + +```text +HTTP/browser input + -> normal auth, CSRF, and typed validation + -> application command/query + -> authoritative state transition + -> rendered generated target/page + -> EffectBatch response or canonical push bytes + -> root-scoped runtime application + -> visible success or explicit recovery +``` + +If this path is unclear, architecture diagrams and service boundaries are +premature. + +### 2. Use the canonical authoring level + +Default to: + +- semantic `.heml` templates plus ordinary Rust; never construct, concatenate, + interpolate, or format HTML strings in Rust, including fragments for source + rendering, Markdown, errors, or test-facing pages; +- `#[hemx::app]`, plain `#[hemx::handler]`, generated components/resources, + typed form models, view wrappers, render/page helpers, and `IntoEffect`; +- generated target, form, control, keyed-slot, and event helpers; +- typed partial swaps expressed as generated target + rendered partial + swap + kind; +- real links and GET forms for navigation and history; +- `data-hemx-revealed-ahead` for viewport-ahead loading while the observed sentinel remains in normal document flow; never move the observed target with CSS to fake prefetch distance; +- plain CSS/SCSS for appearance; +- shared runtime paths and Axum adapters supplied by `hemx-axum`. + +Keep domain types, authorization, persistence, routing, sessions, flags, +observability, and transport policy in the application or integration crate. +Keep `hemx-core` about typed resources and the closed effect/wire contract. +Compose reusable UI from templates, generated components, and ordinary Rust +functions rather than creating a client component framework. + +Use advanced layers only when the job proves the need: + +- opaque island JavaScript is a leaf adapter for high-frequency local behavior; +- client-local/wasm handlers are explicit opt-ins, not the default state model; +- atoms are addressable bootstrap/sync resources, not a general reactive store; +- SSE/WebSocket transport carries canonical versioned `EffectBatch` bytes; it + does not define domain policy. + +### 3. Keep truth on the right side of the boundary + +The browser DOM, stored HTML patches, and stored `EffectBatch` values are not +business truth. Keep authoritative state in ordinary application/domain models +and durable providers. Derive UI effects from that state. + +For local/offline products, use explicit commands, events, and projections. +Replay, deletion, conflict resolution, reconciliation, and export semantics are +product contracts; stop when they have not been chosen. For server products, +normal HTTP security semantics remain authoritative even when interactions are +enhanced. + +Expected failures must become typed, local, useful outcomes: generated field +errors/focus for validation, a root-scoped error outlet for recoverable request +failure, and fail-closed handling for malformed or incompatible responses. +Never turn infrastructure failure into a success-looking empty effect. + +Prefer a **functional core with an imperative shell**. Keep validation, +normalization, authorization decisions, command application, state transitions, +and view-model/projection derivation as deterministic functions over explicit +inputs where that is honest. A useful shape is `state + command -> outcome` or +`facts -> view model`, with typed errors rather than hidden mutation. This makes +the largest behavior space cheap to unit-test and mutation-test. + +Keep HTTP extraction, sessions, database I/O, clocks, randomness/IDs, queues, +push connections, and host capabilities in a thin handler/application shell. +Read their results once, pass ordinary values into the core, persist the returned +outcome, then render generated effects. Pass time, identity, or policy as data +when only the value matters; do not create a trait for every function merely to +mock it. Use a real adapter boundary when ownership or side effects are real. +Purity is a locality tool, not a religion: orchestration and I/O are inherently +effectful, and `EffectBatch` remains UI output rather than domain state. + +### 4. Scale one pressure at a time + +Use this ladder. Enter a stage only when measured load, availability goals, +ownership, compliance, or a distinct failure/resource profile requires it. + +| Stage | Default shape | Required proof before moving on | +| --- | --- | --- | +| One process | One deployable; app-owned adapters; simplest durable store that meets the product contract | The real product path, restart behavior, backup/recovery needs, and current bottleneck are known | +| Production monolith | One release unit for server, generated output, and runtime asset; external durable database/session providers as required | Integration tests cover auth, persistence, failures, migration, fingerprint mismatch, and rollback/reload behavior | +| Horizontal web tier | Stateless request replicas around shared authoritative providers; process memory is cache/ephemeral only unless affinity and loss semantics are explicit | Load evidence shows replica scale helps; migrations, readiness, draining, cache invalidation, and session/CSRF behavior work across replicas | +| Push/fan-out tier | Server-owned ongoing SSE/WebSocket connections; add a broker/backplane only when updates must cross processes | Reconnect, ordering, duplicate, authorization, backpressure, and replay expectations are explicit and tested at the required level | +| Worker or read-model split | Isolate a measured CPU, latency, queue, or failure domain while the application remains one understandable product | Job ownership, idempotency, timeout, retry, deduplication, observability, and recovery contracts exist | +| Microservices | Split an independently owned bounded capability with its own release/scaling/SLO pressure and explicit data/API/event contract | The boundary removes a demonstrated constraint and its distributed failure modes are cheaper than the monolith | + +Prefer vertical resource tuning, query/index fixes, caching with explicit +freshness, bounded concurrency, and horizontal replicas before service +splitting. A large codebase is a module-boundary problem before it is a network +boundary problem. + +### 5. Preserve the hemx boundary across services + +When microservices are justified: + +- keep hemplate rendering, generated resources, and UI `EffectBatch` creation in + the presentation/application edge that owns the page; +- exchange typed domain requests, responses, and events across services—not CSS + selectors, DOM instructions, raw hemx opcodes, or app-authored JSON versions + of the hemx wire format; +- name one authority for each write model and do not let services casually share + mutation ownership; +- version service contracts independently, while deploying each hemx server, + its generated metadata, build fingerprint, and runtime asset as a compatible + release unit; +- preserve end-user credentials, authorization, CSRF, tenancy, deadlines, and + trace context explicitly at each real trust boundary; +- make partial failure visible. Define timeout, idempotency, retry, + deduplication, ordering, compensation, and replay only where the chosen + interaction requires them—never as generic middleware theater. + +Do not put a network hop between a handler and its renderer merely to claim +microservices. Do not use `EffectBatch` as a business event bus or durable event +log. + +### 6. Organize by cohesive product responsibility + +Start small; do not pre-create an architecture directory tree. A production app +usually needs only these durable seams: + +```text +build.rs # hemx-build/global code generation only +src/main.rs # config, concrete providers, process/bootstrap +src/lib.rs # app/router composition and a testable app surface +src// # add only when a feature is already a real seam +templates/app_shell.heml # document shell +templates/.heml # feature surface +templates/partials/ # genuinely reused or independently swapped pieces +tests/.rs # process/integration behavior +tests/browser_.rs # only browser-dependent behavior +``` + +Treat that as a responsibility map, not mandatory scaffolding. A small cohesive +app can remain in `lib.rs`; file count is not architecture. When a feature has +its own state/commands, handlers, rendering, and tests, move that whole seam +together. Keep its domain model, typed input, application operation, handler, +and generated view calls near one another. Do not spread every request across +generic `controllers/`, `services/`, `repositories/`, `dto/`, and `utils/` +directories. + +Keep `main.rs` boring and hard to test because it contains almost no policy. +Expose app construction or mounting from `lib.rs` so integration tests can use +the real router with controlled concrete providers. Put normal routing, +auth/session, persistence, queues, and transport adapters at the app boundary; +do not move them into hemx core or generated template modules. Keep generated +artifacts in the build output rather than copying them into source control. + +Put tiny unit tests beside the responsible module, especially around pure +transitions, validation, authorization decisions, and projections. Put +cross-module HTTP, persistence, and process tests in `tests/`, named for behavior +rather than implementation. Keep browser journeys few and load-bearing. Extract +a shared partial or helper only after actual reuse or independent swap identity +appears. +If a microservice boundary becomes real, that service owns its contract, +provider/migrations, operational entry point, and contract tests; do not mirror +hypothetical services in the source tree first. + +### 7. Prove behavior at the cheapest authoritative boundary + +Choose the tool by what must be observed: + +| Boundary | Default tool | What it proves | +| --- | --- | --- | +| Domain/state | Rust `#[test]` / `#[tokio::test]` | Parsing, invariants, commands, projections, and failure classes | +| Template/build | Compiler plus hemx-build diagnostics | Real `.heml` syntax, generated resources/forms, and cross-file references | +| Handler/effect | `hemx_test` | Generated targets/handles, rendered partials, form bodies, and effect batches without raw ids | +| Router/integration | Real Axum router, usually `tower::ServiceExt`, plus concrete test providers | HTTP status/headers/body, auth, CSRF, sessions, persistence, and malformed requests | +| Static rendered HTML | Rust `scraper` crate (HTML parser + CSS-selector queries) | Escaping, semantic structure, links/forms/attributes, and server-rendered fragments without launching a browser | +| Process lifecycle | `hemx_test::TestProcess` | Readiness, real sockets, child cleanup, restart, and production-binary behavior | +| Browser runtime | Rust `thirtyfour` crate (WebDriver client) with the repository-owned browser smoke | Delegated events, history, focus, polling/revealed bindings, keyed DOM identity, SSE, and recovery | +| Test quality/release | hemx xtask mutation/full-test commands | Mutation resistance and the elected bounded workspace verification path | + +`scraper` is a Rust HTML parsing and CSS-selector library, not a browser or web +framework. Use it when inspecting final server-rendered HTML is enough. It does +not run JavaScript, apply `EffectBatch`, maintain focus/history, or prove +SSE/runtime behavior. `thirtyfour` is a Rust WebDriver client crate; use it only +when an actual browser semantic is the subject; +do not turn every handler assertion into a WebDriver journey. In this repository +`thirtyfour` is the elected Rust browser adapter, so do not add Playwright, +Fantoccini, or another competing browser stack merely from preference. Outside +this repository, preserve the application's existing runner unless a concrete +missing capability justifies migration. + +For WebDriver tests, start the real process through the RAII `TestProcess` +harness, keep selectors in test adapters, and prefer generated-resource or +stable semantic helpers over copied implementation selectors. Browser selector +helpers are not authoring APIs. Avoid sleeps when the runner can wait for the +observable condition. + +Match proof to risk: + +1. Let compilation reject broken templates and generated contracts. +2. Unit-test ordinary domain parsing and state transitions without a browser. +3. Test handlers through `hemx_test` with useful generated-resource assertions. +4. Test route/auth/session/CSRF/persistence and wire failure behavior at the app + integration boundary. +5. Parse static HTML with `scraper` only for facts that do not require runtime + execution. +6. Use focused repository-owned browser smoke for dynamic attributes, + navigation/history, keyed reconciliation, polling/revealed behavior, + no-reload interaction, and runtime recovery. +7. Add compatibility, rolling-deploy, process-restart, multi-replica, load, or + fault tests only when the selected scale stage makes those behaviors part of + the contract. + +In this repository, prefer the stable commands named by `AGENTS.md`, especially +focused crate tests and `cargo run -p hemx-xtask -- test`; use +`cargo run -p hemx-xtask -- html-examples-smoke` for the pattern gallery and +runtime behavior, and the xtask mutation command rather than direct `mutest`. +Do not substitute a passing literal lowering fixture, static HTML parse, or +mocks-only test for proof at the rendered/runtime consumer boundary. + +## Refuse fake sophistication + +Do not introduce: + +- handwritten resource ids, selector retargeting, raw effect constructors, raw + registries, runtime opcodes, manual wire parsing, or hard-coded runtime URLs in + normal app code; +- a VDOM, client router, general client state graph, expression runtime, + per-node listeners, or handwritten JavaScript for ordinary forms/lists/swaps; +- core-owned auth, sessions, persistence, routing, multipart, transport, sync, + analytics, flags, or provider policy; +- a generic repository/service/provider interface before a concrete second use + or required external contract exists; +- microservices by entity name, team aspiration, file count, or hypothetical + scale; +- shared process memory presented as durable or cross-replica state; +- retries, caches, queues, brokers, sagas, event sourcing, CQRS, Kubernetes, or a + service mesh without a named failure/load contract and verification path. + +“Suckless” here means fewer authorities and mechanisms, not fewer safety checks. +The elegant design keeps HTML semantics, typed boundaries, explicit ownership, +and failure truth while deleting accidental layers. + +## Handoff + +For architecture or review requests, report compactly: + +- the user-visible path and current bottleneck/risk; +- the selected scale stage and why the previous stage is insufficient; +- state, rendering, transport, and service ownership; +- the smallest end-to-end change; +- complexity explicitly refused; +- proof run and any unresolved provider/product contract. + +For implementation, make that slice reachable and verify it; do not leave a +“scalable” abstraction that no real path uses.