--- 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.