Summary
Lay the foundation for a server-rendered, HTMX-first web UI for HFS. Today the project ships only APIs and CLIs (hfs, fhirpath-*, sof-*, hts) built on Axum in helios-rest; there is no browser UI and no templating engine in the workspace. This issue establishes the structure, conventions, and a working proof of concept (POC) so that subsequent feature work (dashboards, terminology browsing, ViewDefinition runners, admin screens, etc.) has a well-lit path.
The goal is not to build a full application. It is to answer, with a running example and a written rationale: "When we build UI in this Rust codebase, where does everything go and why?"
Motivation
- We want interactive, low-JavaScript UI that stays close to the server-side FHIR logic we already have.
- HTMX lets us keep rendering and state on the server (Rust), returning HTML fragments rather than shipping a SPA + JSON API + client state duplication.
- Without an agreed structure, the first UI PR will silently set precedent (template location, asset handling, fragment conventions, crate boundaries). We should decide that deliberately, up front.
Approach (proposed — to be finalized in the discussion doc)
Server-rendered HTML over the existing Axum stack, with HTMX for partial updates:
- New crate:
helios-web (crates/web) — a library crate that owns templates, static assets, and view handlers, mounted by the hfs binary as an Axum sub-router (e.g. under /ui). Keeping it separate from helios-rest preserves the clean FHIR REST surface and lets the UI be feature-gated / disabled in headless deployments.
- Templating: compile-time, type-checked templates (recommend Askama — Jinja2-like, checked at build time; alternatives to weigh in the doc: Maud, Minijinja for runtime flexibility). Rationale + trade-offs belong in the discussion document, not decided unilaterally here.
- HTMX + hypermedia: handlers return full pages on hard navigations and HTML fragments on
HX-Requests. State lives on the server; the client stays thin. No JSON-for-the-browser API and no client-side view models.
- Static assets (
htmx.min.js, minimal CSS, optional Alpine.js for local interactivity) vendored into the repo and served via tower-http ServeDir — or embedded with rust-embed for single-binary deploys. Do not hotlink a CDN in production (supply-chain + offline/air-gapped concerns for a healthcare server).
File placement — rules of the road (to be ratified in the doc)
Where things go
crates/web/src/ — Axum handlers/routers returning impl IntoResponse (HTML). Thin: parse request → call into helios-rest/helios-persistence/helios-hts → render a template.
crates/web/templates/ — .html templates. pages/ for full documents, partials/ (or fragments/) for HTMX-swappable fragments, layouts/ for shared shells.
crates/web/assets/ — vendored htmx.min.js, CSS, images. Versioned/pinned; never fetched at runtime.
- Handlers branch on the
HX-Request header to return a fragment vs. a full page (progressive enhancement — the UI should degrade to working full-page loads without JS where practical).
Where things must NOT go
- No HTML in Rust string literals or
format! — all markup lives in templates.
- No business/FHIR logic in templates — templates render data, they don't compute it. Reuse existing crates for data access; don't re-implement persistence or terminology logic in
helios-web.
- No new browser-facing JSON API to feed the UI — HTMX consumes HTML fragments, not JSON.
- No inline
<script> blobs or scattered JS; prefer HTMX attributes (Locality of Behaviour) and, where truly needed, small pinned assets.
helios-rest's FHIR REST handlers stay UI-agnostic — the UI depends on them, not the reverse.
- Don't couple templates to a single FHIR version; go through the version-agnostic abstractions already in the workspace.
Industry best practices & references
- HTMX documentation — https://htmx.org/docs/ (core
hx-* attributes, request/response headers like HX-Request, HX-Trigger)
- "Hypermedia Systems" (Carson Gross, Adam Stepinski, Deniz Akşimşek) — free book, the canonical treatment of building hypermedia-driven apps — https://hypermedia.systems/
- "Locality of Behaviour" (LoB) — htmx essay motivating co-locating behavior with markup — https://htmx.org/essays/locality-of-behaviour/
- HATEOAS — htmx essay on hypermedia-as-the-engine-of-application-state — https://htmx.org/essays/hateoas/
- "HTMX Patterns" — https://htmx.org/examples/ (active search, click-to-edit, infinite scroll, inline validation — the fragment patterns we'll standardize on)
- Axum — https://docs.rs/axum (routing, extractors,
IntoResponse) and tower-http ServeDir for static files — https://docs.rs/tower-http
- Askama (compile-time templates) — https://docs.rs/askama ; Maud — https://maud.lambda.xyz/ ; Minijinja — https://docs.rs/minijinja (for the templating trade-off analysis)
- rust-embed (embedding assets in the binary) — https://docs.rs/rust-embed
- OWASP — output encoding / XSS prevention as it applies to server-rendered templates (auto-escaping expectations) — https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
Deliverables
- A branch (e.g.
feat/htmx-ui-foundation) containing:
- The
helios-web crate skeleton wired into the hfs binary as an Axum sub-router.
- Vendored HTMX + minimal CSS assets served locally.
- A working POC: at least one full page + one HTMX fragment interaction (e.g. an active-search or click-to-load example backed by a real read path such as CodeSystem/ValueSet lookup or a system/health panel) demonstrating the
HX-Request full-page-vs-fragment pattern.
- Templates organized per the layout above;
cargo build/cargo clippy clean; the page reachable when running hfs.
- A discussion document (
crates/web/README.md or docs/web-ui.md) covering:
- The chosen approach and why (server-rendered HTMX over SPA).
- The finalized templating-engine decision with trade-offs.
- File placement rules ("where things go / must not go") as the durable convention.
- Fragment/partial conventions, header handling, and progressive-enhancement stance.
- Asset/versioning policy (no runtime CDN) and security notes (auto-escaping, XSS).
- References (above).
Acceptance criteria
Out of scope
- A complete or styled application, auth/RBAC-gated screens, or feature-complete FHIR browsing.
- Design system / branding decisions beyond a minimal, functional stylesheet.
- Replacing or restructuring the existing REST/CLI surfaces.
Summary
Lay the foundation for a server-rendered, HTMX-first web UI for HFS. Today the project ships only APIs and CLIs (
hfs,fhirpath-*,sof-*,hts) built on Axum inhelios-rest; there is no browser UI and no templating engine in the workspace. This issue establishes the structure, conventions, and a working proof of concept (POC) so that subsequent feature work (dashboards, terminology browsing, ViewDefinition runners, admin screens, etc.) has a well-lit path.The goal is not to build a full application. It is to answer, with a running example and a written rationale: "When we build UI in this Rust codebase, where does everything go and why?"
Motivation
Approach (proposed — to be finalized in the discussion doc)
Server-rendered HTML over the existing Axum stack, with HTMX for partial updates:
helios-web(crates/web) — a library crate that owns templates, static assets, and view handlers, mounted by thehfsbinary as an Axum sub-router (e.g. under/ui). Keeping it separate fromhelios-restpreserves the clean FHIR REST surface and lets the UI be feature-gated / disabled in headless deployments.HX-Requests. State lives on the server; the client stays thin. No JSON-for-the-browser API and no client-side view models.htmx.min.js, minimal CSS, optional Alpine.js for local interactivity) vendored into the repo and served viatower-httpServeDir— or embedded withrust-embedfor single-binary deploys. Do not hotlink a CDN in production (supply-chain + offline/air-gapped concerns for a healthcare server).File placement — rules of the road (to be ratified in the doc)
Where things go
crates/web/src/— Axum handlers/routers returningimpl IntoResponse(HTML). Thin: parse request → call intohelios-rest/helios-persistence/helios-hts→ render a template.crates/web/templates/—.htmltemplates.pages/for full documents,partials/(orfragments/) for HTMX-swappable fragments,layouts/for shared shells.crates/web/assets/— vendoredhtmx.min.js, CSS, images. Versioned/pinned; never fetched at runtime.HX-Requestheader to return a fragment vs. a full page (progressive enhancement — the UI should degrade to working full-page loads without JS where practical).Where things must NOT go
format!— all markup lives in templates.helios-web.<script>blobs or scattered JS; prefer HTMX attributes (Locality of Behaviour) and, where truly needed, small pinned assets.helios-rest's FHIR REST handlers stay UI-agnostic — the UI depends on them, not the reverse.Industry best practices & references
hx-*attributes, request/response headers likeHX-Request,HX-Trigger)IntoResponse) and tower-httpServeDirfor static files — https://docs.rs/tower-httpDeliverables
feat/htmx-ui-foundation) containing:helios-webcrate skeleton wired into thehfsbinary as an Axum sub-router.HX-Requestfull-page-vs-fragment pattern.cargo build/cargo clippyclean; the page reachable when runninghfs.crates/web/README.mdordocs/web-ui.md) covering:Acceptance criteria
helios-webcrate exists, builds, and is mounted byhfs; UI reachable in a browser.HX-Requestheader.helios-restFHIR handlers unchanged in intent.Out of scope