Skip to content

Research & design: schema-driven FHIR Resource Editor prototype (FHIR Schema + extension editing) #264

Description

@smunini

Goal

Research and design (no implementation yet) a FHIR Resource Editor prototype for the HFS web UI: a schema-aware editor that lets a user open a FHIR resource, understand its structure, add/remove nodes of information, and edit extensions — with the schema itself driving what is offerable and what is valid.

The deliverable of this issue is a design document + prototype plan, not production code. A throwaway spike is fine (and encouraged) to de-risk the hard parts, but the output we want is a written design we can review before committing engineering time.

Why now

Two pieces have landed (or are landing) that make this feasible for the first time:

  1. FHIR Schema capability — PR feat(validator): FHIR resource validation — schema engine, $validate, write-path enforcement #232 (feat/fhir-validator, still draft). It introduces helios-fhir-validator, a runtime FHIR Schema engine with an in-memory IR (FhirSchema) built from StructureDefinitions and shipped as embedded per-version schema packs (R4/R4B/R5/R6). This issue should target that engine as its schema source — not a hand-rolled metadata layer, and not the typed helios-fhir structs.
  2. crates/ui — the Askama + htmx, server-rendered UI crate mounted at /ui by the hfs binary, with embedded assets and no runtime CDN dependency.

What the FHIR Schema layer already gives us (from PR #232)

helios_fhir_validator::schema::FhirSchema carries essentially the metadata an editor needs to render a form and offer the right "add node" choices:

Field Editor use
elements (ordered IndexMap) The set of child nodes that can be added, in spec order
type_, kind, base Which widget to render; how to resolve nested complex types
min / max, array, required Cardinality: required markers, "add another" affordance, when to stop offering
choices / choice_of value[x] — offer a type picker, then bind the concrete element
binding (value_set, strength) Coded fields: dropdown/autocomplete backed by $expand against HTS
fixed / pattern Prefill and lock values
constraints (FHIRPath + human) Inline invariant messages
refers Reference pickers scoped to allowed target types
slicing / slices (incl. Match) Profile-driven slices, and the basis for extension slices
extensions Named extension slices declared by a profile
must_support, modifier, summary Emphasis / ordering / progressive disclosure in the form
regex (primitives) Client- and server-side field-level checks

So the research is mostly about how to project this IR into an editable UI, not about acquiring the schema.

Research questions to answer

1. Schema access surface. The validator's IR is internal to a Rust crate. Does the editor consume it in-process (UI crate depends on helios-fhir-validator, renders forms server-side) or over an HTTP surface (a /ui-internal or public endpoint that projects FhirSchema into an editor-friendly JSON)? Consider that PR #232 keeps schemas as compressed packs and resolves cooperatively rather than by snapshot flattening — walking it per-node for a form render needs a look at cost and at the resolver API (SchemaResolver, SchemaRegistry, CompositeResolver).

2. Editing model. Tree editor over raw JSON, or a generated form? Likely both: a structured form view for the common path plus a raw-JSON/source view, kept in sync. Decide what the canonical in-flight representation is (raw serde_json::Value is what the validator walks, which argues for it).

3. Adding nodes. The core requirement. Given a cursor position in the resource, compute the set of addable children from the schema (respecting cardinality already consumed, excluded, choice types, slices), and render an "add element" affordance. Design how this is surfaced (typeahead over element names? a filtered tree of the type?) and how deeply nested complex types are lazily expanded.

4. Extension editing (explicit requirement). Cover both cases:

  • Known/profiled extensions — declared via the extensions sugar / extension slices on a profile; we know the URL, the value type, and the binding, so we can render them like any other field.
  • Ad-hoc extensions — the user supplies an extension URL. Design URL resolution: from stored StructureDefinitions in the tenant, from the schema packs, or unresolvable (fall back to a manual value[x] type picker). Also cover nested/complex extensions (extension.extension) and modifierExtension.
  • Note the limitation flagged in PR feat(validator): FHIR resource validation — schema engine, $validate, write-path enforcement #232's own backlog: core extension definitions aren't in the vendored spec bundles today, so profile extensions sugar referencing core extension URLs currently reports unknown-schema. The design should say how the editor behaves in that gap.

5. Validation feedback loop. How and when the editor validates: live per-field (regex, cardinality, required) versus a full pass through $validate (added in PR #232, returns OperationOutcome). Map OperationOutcome.issue.expression / location back onto editor nodes so errors land on the right field. Note that FHIRPath constraints and terminology bindings are deferred (async) in the engine — the sync structural pass is cheap, the effects pass is not; that shapes the interaction design.

6. Terminology integration. For binding fields, wire code pickers to $expand / $validate-code (HTS). Decide behavior when terminology is unavailable or the value set is huge.

7. Profiles. Editing against meta.profile (and tenant/stored profiles) versus base resource type: does the user pick a profile up front, and does the form then narrow (required slices, must-support emphasis, fixed values)?

8. Technology fit. The UI crate today is server-rendered Askama + htmx with embedded assets and no runtime CDN. A resource editor is the most interaction-heavy thing we'd have built. Assess honestly whether htmx-driven fragments carry it, whether it needs a scoped island of client-side JS, and what that costs us against the crate's existing no-CDN / air-gapped stance. Recommend one, with the tradeoff written down.

9. Prior art. Survey what exists — Health Samurai's FHIR Schema-driven form work, Simplifier/Forge, the HAPI resource editor, LHC-Forms/SDC Questionnaire-based rendering, fhir-schema-based editors — and say what we should and shouldn't copy.

Scope for the prototype (what "enough capability" means)

The prototype should be able to demonstrate, end to end, on at least one FHIR version:

  • Load an existing resource from HFS by type + id (and start a new empty one).
  • Render a schema-driven editable tree/form of it, ordered and typed by the schema.
  • Add a new node at any valid point, with the set of offerable nodes derived from FhirSchema (including a value[x] choice-type pick and a repeating-element "add another").
  • Remove a node.
  • Add, edit, and remove extensions, including at least one profiled extension and one ad-hoc URL extension.
  • Show validation results inline, anchored to the offending node, via the PR feat(validator): FHIR resource validation — schema engine, $validate, write-path enforcement #232 $validate path.
  • Save back to HFS (PUT/POST), surfacing an OperationOutcome on rejection.

Explicitly out of scope for the prototype: full profile authoring, Questionnaire/SDC rendering, bulk/multi-resource editing, offline conflict resolution.

Deliverables

  1. A design doc (in docs/, e.g. docs/resource-editor-design.md) answering the research questions above, with a recommended architecture and the rejected alternatives noted.
  2. A component/data-flow sketch: where schema projection happens, what crosses the wire, what the editor's in-flight state is.
  3. A staged implementation plan with sizing, and a call on the technology question (Epic/pysof #8).
  4. Any spike code kept on a branch, clearly labeled as a spike.

Dependencies / coordination

Not now

Per the request: record and design only — do not build the production editor yet.

Metadata

Metadata

Assignees

Labels

enhancementNew 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