Skip to content

Foundation: HTMX-first web UI (POC, structure, and rules of the road) #186

Description

@smunini

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

Deliverables

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

  • helios-web crate exists, builds, and is mounted by hfs; UI reachable in a browser.
  • POC demonstrates a real HTMX fragment swap driven by the HX-Request header.
  • HTMX and CSS assets are served locally (no CDN dependency).
  • Discussion document merged on the branch, covering approach, guidelines, and rules of the road with the references cited above.
  • No HTML in Rust source; no browser-facing JSON API introduced; helios-rest FHIR handlers unchanged in intent.

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.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentationenhancementNew feature or request

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions