Skip to content

Compartment Editor UI: CRUD for CompartmentDefinitions + a compartment route tester #237

Description

@smunini

Summary

A UI for managing CompartmentDefinition resources — create, update, delete — plus a tester that answers the question you actually have when a compartment route misbehaves: is this resource type in this compartment, via which search parameter, and what search does the server run?

Interactive mockup: docs/mockups/compartment-editor.html — self-contained, open locally in a browser; PNG preview alongside (PR #247)

The mockup is clickable. It is loaded with the real R4 CompartmentDefinition set from crates/fhir-gen/resources/R4/, not placeholder data, so the counts and parameter lists below are the ones you'll actually see. Nothing persists.

Why now

We already ship compartment search end-to-end — crates/rest/src/handlers/compartment.rs serves GET [base]/[compartment]/[id]/[type] and the /* all-types fan-out, backed by helios_fhir::get_compartment_params() (codegen'd from the JSON files above), implemented across SQLite, PostgreSQL, Elasticsearch, and MongoDB.

What's missing is any way to see it. Today, answering "why does GET /Patient/123/Observation return nothing?" means reading a 145-entry JSON file. The helios-ui crate already ships a compartments.svg icon and a nav slot, so this screen is anticipated — it just has nothing behind it.

What the mockup shows

Three panes: a rail listing the definitions, a tabbed editor, and inline results.

Definition tab — metadata CRUD

Canonical URL, code, status, version, publisher, description, plus the two switches that actually change server behavior:

  • search — flip it off and no compartment route resolves for that compartment. The tester says so explicitly rather than silently returning nothing.
  • experimental

Editing anything marks the definition dirty (dot in the rail, Save / Revert enabled). Revert restores from an untouched base copy, so it's honest rather than decorative.

Members tab — membership CRUD

The 145 resource types per compartment, filtered by Members · All types · Excluded, each row showing its parameters as editable chips. Add a param (type + Enter), remove one, or drop a type from the compartment entirely.

Membership is state, so it's encoded as a chip, not just an absence: member (green) / excluded (red). Real R4 member counts —

Compartment Members of 145 types
Patient 66 145
Practitioner 59 145
RelatedPerson 32 145
Device 32 145
Encounter 25 145

Tester tab — the part worth building first

Pick a compartment, an id, a target type, and optional query params. It resolves against the definition as currently edited, not as saved, and warns you when those differ.

  • Patient/example/Observationmember, via subject OR performer, with the equivalent flat search rendered:
    GET /Observation?
      // where
      subject=Patient/example  OR  performer=Patient/example
    
  • Patient/example/Medicationnot a member, with the expected 404 + OperationOutcome, and a nudge to add it from the Members tab.
  • Patient/example/* → expands to all 66 member types, with the note that excluded types are skipped, not failed — which matches the filter_map in compartment.rs:200 that drops non-member types from the fan-out rather than erroring.

Design notes

Nothing new was invented visually. The mockup reuses crates/ui/assets/app.css tokens verbatim (--bg, --surface, --accent: #33b8ff, the [data-theme="dark"] block) and the vendored Figtree face, inlined as a data URI since the Artifact CSP blocks font CDNs — same no-runtime-CDN stance as the crate.

Three semantic tokens are added, kept deliberately distinct from the accent hue so state never reads as branding: --ok, --danger, --warn. Both themes are defined via prefers-color-scheme and the data-theme override, matching assets/theme.js.

Per the crate rules, real markup would live in templates/, not Rust source. The mockup's JS is prototype scaffolding — in the real thing htmx swaps fragments and the tester is a server round-trip.

Open questions

  1. Are these writable at all? The base definitions are codegen'd into the binary via get_compartment_params(). An editor implies a runtime, tenant-scoped override layer that the compartment handler consults before falling back to the compiled set. That's the real work here, and it's a persistence question, not a UI one. The mockup hints at it ("Base definitions ship with the server. Edits create a tenant-scoped override.") but does not answer it.
  2. Should the tester ship first, standalone? It's read-only, needs no override layer, and delivers most of the value. Strong candidate for slicing.
  3. Version scoping. R4/R4B/R5/R6 have different compartment sets — R6 adds Group. The version pill needs to re-key the whole editor.
  4. The Questionnaire oddity. crates/fhir-gen/resources/R4/compartmentdefinition-questionnaire.json (id qs1) has no code and zero resource entries, though R4 marks CompartmentDefinition.code as 1..1. The mockup skips it. Worth confirming it's an upstream example artifact and not something our loader should reject or warn on.
  5. Validation. code must be a resource type; params must exist as SearchParameters on the target type and be of type reference. The editor should refuse to save a param that can't resolve — this is where it would lean on the search parameter registry.

Suggested acceptance criteria

  • Decide Q1 (override layer) — that gates whether this is CRUD or read-only.
  • Ship the tester as a read-only route first, if Q2 lands that way.
  • Members table renders 145 rows per compartment without a scroll jank problem.
  • Editor is version-aware across R4/R4B/R5/R6.
  • Parameter validation against the SearchParameter registry.
  • Works with JavaScript disabled for the read paths (crate baseline; the language switcher precedent).

References

  • crates/rest/src/handlers/compartment.rs — the handler, the /* fan-out
  • crates/fhir-gen/resources/{R4,R4B,R5,R6}/compartmentdefinition-*.json — source data
  • helios_fhir::get_compartment_params() — codegen'd lookup
  • crates/ui/assets/app.css, crates/ui/templates/icons/compartments.svg
  • FHIR CompartmentDefinition · Compartment search

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