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/Observation → member, via subject OR performer, with the equivalent flat search rendered:
GET /Observation?
// where
subject=Patient/example OR performer=Patient/example
Patient/example/Medication → not 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
- 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.
- Should the tester ship first, standalone? It's read-only, needs no override layer, and delivers most of the value. Strong candidate for slicing.
- Version scoping. R4/R4B/R5/R6 have different compartment sets — R6 adds
Group. The version pill needs to re-key the whole editor.
- 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.
- 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
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
Summary
A UI for managing
CompartmentDefinitionresources — 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.rsservesGET [base]/[compartment]/[id]/[type]and the/*all-types fan-out, backed byhelios_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/Observationreturn nothing?" means reading a 145-entry JSON file. Thehelios-uicrate already ships acompartments.svgicon 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.experimentalEditing 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 —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/Observation→ member, viasubjectORperformer, with the equivalent flat search rendered:Patient/example/Medication→ not a member, with the expected404+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 thefilter_mapincompartment.rs:200that 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.csstokens 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 viaprefers-color-schemeand thedata-themeoverride, matchingassets/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
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.Group. The version pill needs to re-key the whole editor.Questionnaireoddity.crates/fhir-gen/resources/R4/compartmentdefinition-questionnaire.json(idqs1) has nocodeand zeroresourceentries, though R4 marksCompartmentDefinition.codeas 1..1. The mockup skips it. Worth confirming it's an upstream example artifact and not something our loader should reject or warn on.codemust be a resource type; params must exist as SearchParameters on the target type and be of typereference. 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
References
crates/rest/src/handlers/compartment.rs— the handler, the/*fan-outcrates/fhir-gen/resources/{R4,R4B,R5,R6}/compartmentdefinition-*.json— source datahelios_fhir::get_compartment_params()— codegen'd lookupcrates/ui/assets/app.css,crates/ui/templates/icons/compartments.svg