helex-tx docs
Documentation for the terminology-server-facing terminology suite — the org.helex.tx.* family: TEDY 🧸
(editor), Teagle 🦅 (aggregator), Flint 🔥🦎 (tx-fhir FHIR engine library), Ucumber 🥒 (UCUM),
Owlexicon 🦉 (wiki), Catticus 🐱 (publisher). See architecture.md.
| Doc | What |
|---|---|
| architecture.md | Module/lib structure, @helex/* reuse, MFE, dev vs deploy |
| dev/flint.md | Flint (tx-fhir) — the FHIR terminology engine library: SPI, providers, embedding, conformance |
| dev/teagle.md | Teagle — the terminology aggregator: server registry, multi-source $expand, editable in-memory server |
| dev/running.md | Running the suite — run_backend.sh/run_frontend.sh for tedy · teagle · flint · ucumber |
| development.md | The two-location dev model, run, sync, verify |
| spec-authoring.md | How we create specifications — the dev-tx.example-scan → reference-editor-reconcile process, aligned to the EMR spec rules |
| dev/publishing.md | Publishing — build the mdBook, deploy to Cloudflare Pages, gate it with Cloudflare Access |
| resource-context-gap.md | ResourceContext gap analysis + plan — the tabbed resource dashboard reference-editor uses, the missing widgets vs @helex/ui/TEDY, and the phased build (R1–R5) |
| accessibility-density-tiny.md | Font/spacing audit + “tiny” verdict — Marina vs @helex/ui density; a “tiny” belongs on the density axis (not FontSize), only for data-grade tables |
Specifications themselves live under specs/ (specs/tedy/…).
Architecture
helex-tx is the standalone home for the terminology-server-facing terminology suite — the React rewrite of reference-editor, built on the Helex platform’s shared libraries.
Modules (target)
The canonical org.helex.tx.* family (names/handles/mascots also in the auto-memory tx-module-family):
| Module | Name | Handle | Package | Mascot | Scope |
|---|---|---|---|---|---|
| Editor | TEDY | tx-edit | org.helex.tx.tedy | 🧸 | terminology editor — code/value/map sets, naming systems, association types, defined properties, observation/specimen definitions, sequences; server-management (spaces, servers, ecosystems, releases); landing/search |
| eXplorer / aggregator | Teagle | tx-vision | org.helex.tx.teagle | 🦅 | multi-server terminology aggregator — server registry, multi-source $expand fan-out, editable in-memory server; built on Flint |
| FHIR API | Flint | tx-fhir | org.helex.tx.fhir | 🔥🦎 | FHIR façade + terminology $operations engine + conformance. A published library (org.helex.tx:tx-fhir) that TEDY and Teagle embed — replaces Kefhir. See dev/flint.md. |
| UCUM | Ucumber | tx-ucum | org.helex.tx.ucum | 🥒 | UCUM utility API (convert / canonicalise / analyse) over Flint’s native UCUM engine |
| Wiki | Owlexicon | tx-wiki | org.helex.tx.wiki | 🦉 | wiki (DB-authored), rendered/published |
| Publisher | Catticus | tx-pub | org.helex.tx.pub | 🐱 | Implementation Guides / publishing |
Built so far: TEDY (authoring engine + /fhir facade over Flint), Flint (tx-fhir library, general-mode
FHIR conformance 1194/1194), Teagle (aggregator on Flint), Ucumber. TEDY currently also carries basic
Structure Definition / Structure Map authoring and import until dedicated modules exist. Reused from EMR (not
rebuilt): uma (privileges), flow (tasks). info/health → shell.
TEDY currently also ships basic Structure Definition and Structure Map authoring (list · create · TxResource detail with Content view) — the modeling resources that ultimately belong to DMO; they live in TEDY until DMO is stood up.
Layers
modules/<ns>/frontend— a self-contained React 19 / Vite SPA that is BOTH a standalone app (main.tsx→App.tsx→AppRoutes.tsx) AND a Module-Federation remote (federated.tsxexposes./App). Mirrors the EMRmodules/tx/frontendblueprint.libs/<name>— domain libraries.@helex/tedy= the terminology-server client (txApi→/txapi), hooks, types, and TEDY-owned components (TedyResourceForm).- Shared
@helex/*foundation (from EMR core/frontend):@helex/ui(App* widgets +ResourceList/ResourceForm),@helex/core(auth, http,useDataController),@helex/state,@helex/i18n,@helex/mfe-setup. Consumed via source aliases today; published as packages in Phase 3 (then this repo builds standalone).
Backend
terminology-server (/ts/*, /structure-definitions, /transformation-definitions, /wiki/*, /space/*, /file-importer/*, /fhir/*). The @helex/tedy client uses a dedicated axios instance on a /txapi prefix — deliberately off @helex/core’s /api (the EMR platform backend).
TEDY’s /fhir façade + terminology $operations are served by the embedded Flint engine
(org.helex.tx:tx-fhir) over a TEDY-DB-backed ResourceStore (org.helex.tx.tedy.fhir.TedyDbResourceStore,
flint.store=tedydb), composed in via org.helex.tx.tedy.config.FhirEngineComposition and gated by
FhirAuthorizationInterceptor. The frontend’s @helex/tx widgets ($expand/$lookup) resolve against this local
/fhir when TEDY_TX_FHIR_TARGET points at the TEDY backend. See dev/flint.md.
No deployment-specific URLs are committed. The dev proxy target is set via VITE_TX_TARGET (required; neutral default), and backend FHIR canonicals (publisher value sets/code systems) via VITE_TX_PUBLISHER_{VS,CS}_URL, read through src/config/tx.ts. See modules/tedy/frontend/.env.example.
Conventions
Follow the EMR frontend contract (AGENTS-frontend.md): lists → ResourceList, detail/edit → ResourceForm (TEDY wraps it as TedyResourceForm), no raw antd / <a> / message.*, everything via t() with locale files, App*/Fhir*/Tx*/Emr* per-lib naming. Tx* widgets bind to the EMR registry backend — not reused for the reference platform /ts/* data.
Deploy (target, Phase 3)
Three modes, all present in EMR today: per-module static-nginx Dockers behind an edge nginx; a monodocker; and MFE via Module Federation (shell host mounts each remote’s remoteEntry.js).
Development
The two-location model (until @helex/* are published)
TEDY consumes the shared @helex/* libs via source aliases (../../core/frontend/libs/*/src), which only resolve when the module sits inside an EMR checkout at <emr>/modules/tedy/frontend. So:
helex-tx(this repo) = canonical source of truth — versioned, reviewed, backed up.- An EMR checkout = the dev/run environment. Use a worktree at
origin/mainfor current libs:.claude/worktrees/tedy-main. - TEDY is git-ignored inside the EMR checkout (via that clone’s
.git/info/exclude), so it never enters EMR history.
Move source between the two — and guard against drift — with scripts/sync-emr.sh:
EMR=/Users/igor/source/emr/repo/.claude/worktrees/tedy-main
./scripts/sync-emr.sh "$EMR" push # helex-tx -> EMR checkout (deploy source)
./scripts/sync-emr.sh "$EMR" pull # EMR checkout -> helex-tx (capture edits)
./scripts/sync-emr.sh "$EMR" check # exit 1 if diverged — run before committing
Phase 3 removes this: publish @helex/* to a registry, and helex-tx builds standalone against the packages.
Run
# one-time: install shared libs' deps + the module's deps in the EMR checkout
cd "$EMR/modules/core/frontend" && npm install
cd "$EMR/modules/tedy/frontend" && npm install
# configure the backend (no deployment URLs are committed) — copy + edit:
cp .env.example .env.local # set VITE_TX_TARGET + VITE_TX_PUBLISHER_{VS,CS}_URL
VITE_TX_TARGET=https://your-terminology-server.example npm run dev # http://localhost:18620/tedy/
- Backend: the dev server proxies
/txapi→${VITE_TX_TARGET}/api.VITE_TX_TARGETis required — there is no committed default (a neutral placeholder resolves nothing). Publisher display names needVITE_TX_PUBLISHER_VS_URL/VITE_TX_PUBLISHER_CS_URL(seesrc/config/tx.ts+.env.example). - Auth: DEV auto-signs-in a guest (
Bearer yupion/txapi; a Vite stub answers/api/uma/*). Disable withVITE_TX_GUEST=0. Production/federation auth is separate (Keycloak OIDC — tracked).
Verify (against consumers, not the lib build)
cd "$EMR/modules/tedy/frontend" && npx tsc -p tsconfig.json --noEmit # Vite skips tsc
Then confirm in the browser. Follow AGENTS-frontend.md; keep EN+ET+RU locale files in src/i18n/ in sync with any new t() key.
Running the terminology suite locally
Two unified scripts run any suite module’s backend and frontend. Each backend is its own standalone Gradle build
under modules/<module>/backend (Flint is modules/tx-fhir); each frontend is a Vite SPA run inside an EMR
checkout where the @helex/* source aliases resolve (the two-location dev model — see
development.md).
Backends — scripts/run_backend.sh <module>
| Module | Command | Port | Storage |
|---|---|---|---|
| TEDY 🧸 | ./scripts/run_backend.sh tedy | 18610 | Postgres — a disposable Docker one is started automatically (or supply TEDY_DB_URL/_USER/_PASSWORD, or TEDY_NO_DB=1). dev profile applies Liquibase. |
| Teagle 🦅 | ./scripts/run_backend.sh teagle | 18650 | stateless (no DB) |
| Flint 🔥🦎 | ./scripts/run_backend.sh flint | 18630 | in-memory (standalone engine; the tx-fhir library’s runnable app) |
| Ucumber 🥒 | ./scripts/run_backend.sh ucumber | 18670 | stateless (no DB) |
Override the port with PORT=…; TEDY’s Docker Postgres port with TEDY_PG_PORT=…. --server.port is applied
universally, so the module’s own port property doesn’t matter.
Frontends — scripts/run_frontend.sh <module> [emr-checkout]
| Module | Command | Proxies /txapi → |
|---|---|---|
| TEDY | ./scripts/run_frontend.sh tedy | TEDY_TARGET (default http://localhost:18610) |
| Teagle | ./scripts/run_frontend.sh teagle | TEAGLE_TARGET (default http://localhost:18650) |
The script writes VITE_TX_TARGET into modules/<module>/frontend/.env and delegates to scripts/dev.sh, which
runs Vite in the EMR checkout ($TEDY_EMR, else the tedy-main worktree) and opens /<module>/. For FHIR
operations, the frontend’s /api/tx/$expand/$lookup proxy target is set via TEDY_TX_FHIR_TARGET — point it at
the same backend (which now serves /fhir itself via Flint) to resolve concept pickers on the local stack.
Typical dev pairs
# TEDY editor, full stack on your own machine
./scripts/run_backend.sh tedy # :18610 (+ throwaway Postgres)
TEDY_TX_FHIR_TARGET=http://localhost:18610 ./scripts/run_frontend.sh tedy # :18620 → /tedy/
# Teagle aggregator
./scripts/run_backend.sh teagle # :18650
./scripts/run_frontend.sh teagle # → /teagle/ (once the Teagle frontend lands)
FHIR conformance harness (Flint)
See the run-fhir-tx-ecosystem-tests auto-memory: run ./scripts/run_backend.sh flint and point the
validator_cli.jar txTests harness at http://localhost:18630/fhir (load each suite’s setup CodeSystems first).
Notes
- Real deployment URLs never live in committed code — only in the gitignored
.envfiles. scripts/check-naming.shgates the tree for forbidden vendor strings; run it before committing.
Flint (tx-fhir) — FHIR terminology engine library 🔥🦎
Flint is the shared FHIR terminology engine of the org.helex.tx.* suite — the FHIR façade + terminology
$operations that replaces Kefhir. It ships as a published library org.helex.tx:tx-fhir (module
modules/tx-fhir, package org.helex.tx.fhir) that consumers — TEDY and Teagle — embed and back with
their own storage. It also runs standalone (in-memory) as the conformance target.
Conformance: general-mode tx-ecosystem = 1194/1194 (see [MEMORY run-fhir-tx-ecosystem-tests] for the harness).
What’s in the library
| Piece | Package | Role |
|---|---|---|
| Engine ops | engine/{ValidateCodeOp,LookupOp,ExpandOp,TranslateOp,EngineContextFactory,RegexFilterRewrite,OpResult} | $validate-code / $lookup / $expand / $translate over the HL7 core R5 terminology kernel (org.hl7.fhir.r5 6.10.1, pinned to the tx-ecosystem validator release). RegexFilterRewrite uses linear-time re2j for compose regex filters. |
| Storage SPI | store/{ResourceStore,InMemoryResourceStore,MutableResourceStore} | The seam a consumer implements. Flint ships only the SPI + the in-memory store (and its mutable variant for editable servers) — no DB dependency. |
| Provider SPI | route/{ExternalTerminologyProvider,TerminologyRouter} + providers/… | Per-system providers consulted before the local kernel: UCUM (native, org.fhir:ucum — validate/lookup/expand + convert/canonicalise/analyse), static CodeSystems (ISO-3166 countries, …), SNOMED (version-URI→Snowstorm-branch), and the upstream-FHIR forward (FhirUpstreamDelegate). |
| HTTP surface | FhirResourceController, FhirOperationController | /fhir/{Type}/{id} + ?url= reads, /fhir/metadata (+?mode=terminology), $versions, and the $operation endpoints (GET + POST). |
| Capability | {CodeSystem,ValueSet,ConceptMap}Capability | commons-fhir FhirResourceCapability beans → aggregated /fhir/metadata. |
| Standalone app | FhirEngineApplication | Runs the engine on the in-memory store (./gradlew runFhirEngine, default :18630). |
Embedding Flint in a consumer
A consumer (TEDY, Teagle) depends on the published jar and supplies three things:
implementation("org.helex.tx:tx-fhir:0.1.0-SNAPSHOT") // + the OOM-safe HAPI/kernel set travels transitively (api)
- A
ResourceStoreimpl selected byflint.store— e.g. TEDY’sorg.helex.tx.tedy.fhir.TedyDbResourceStore(flint.store=tedydb, mapstedy.*→ FHIR on read), Teagle’s registry stores, or the bundledInMemoryResourceStore(flint.store=memory). - Config under
flint.*—flint.store,flint.authoritative-hosts(defaulthelex.org),flint.delegate.snomed-target/.default-target(upstream targets — real URLs in the gitignored.env). - A Spring Security chain (Flint stays auth-free) — e.g. TEDY’s
FhirAuthorizationInterceptorgating/fhir/**on{CodeSystem,ValueSet,ConceptMap}.read. TEDY composes Flint viaorg.helex.tx.tedy.config.FhirEngineComposition(@ComponentScanof the Flint package, excludingFhirEngineApplication).
Dependencies / the OOM caveat
Flint re-exports the runtime kernel as api: commons-fhir + hapi-fhir-structures-r5 + hapi-fhir-validation
(together) + hapi-fhir-caching-caffeine (runtimeOnly) + org.hl7.fhir.r5/utilities 6.10.1 (with the
resolution pin) + re2j + commons-collections4 (the kernel’s TerminologyCache needs it; HAPI marks it
optional). Never add hapi-fhir-validation-resources-r5 — its default-profile value sets inflate ~200 MB and
OOM a small heap. The runFhirEngine task pins maxHeapSize=512m as the regression guard.
Run standalone
./scripts/run_backend.sh flint # in-memory engine on :18630
Or point the tx-ecosystem harness at it (see the run-fhir-tx-ecosystem-tests memory).
Adapters vs the reference server
Providers in depth: see
flint-providers.mdfor the UCUM (native) and LOINC (upstream-delegate) providers — their operations, the local-first routing, and the config keys.
Flint’s SNOMED provider follows the reference server’s Snowstorm-delegation model but adds version-URI→branch
resolution (so versioned editions / the xsct test sub-ontology resolve, where a plain FHIR-forward could not).
UCUM is native in Flint (the reference is too) — it covers the conversion / canonicalise / analyse use-cases
that no generic FHIR delegate can. $subsumes and non-FHIR management endpoints (RF2 import, browse) are out of
Flint’s scope — Flint is the FHIR-serving engine, not a SNOMED editorial tool.
Teagle (tx-vision) — terminology aggregator / eXplorer 🦅
Teagle is the eagle-eyed terminology aggregator — it routes and merges FHIR terminology operations across
multiple registered upstream servers, and can serve editable in-process content. It is the HELEX-platform
successor to the reference Terminology eXplorer (tx-router / tx-viewer), rebuilt on Flint instead of
fhirest/commons-fhir-proxy: every per-source FHIR operation runs through the Flint engine.
Module: modules/teagle/backend (package org.helex.tx.teagle, TeagleApplication, :18650), stateless (no DB),
depends only on org.helex.tx:tx-fhir. Frontend: React + @helex/ui, laid out like the reference eXplorer.
Server registry
An ecosystem.json-style config (tx-ecosystem IG shape; ${VAR:-default} env interpolation, blank-target HTTP
servers dropped as inactive) materialises into TerminologySources of three kinds:
- http — an external FHIR terminology server; operations ride Flint’s
FhirUpstreamDelegate. - local — content served in-process by the Flint kernel.
- in-memory editable (new — the reference had no writable server) — over Flint’s
MutableResourceStore;PUT/DELETE /fhir/{Type}/{id}author content into the running aggregator, immediately expandable. Ideal for tests and demos.
Routing decision (config order; exclusions before authoritative match; exact / glob / regex / type-collection /
version-pinned patterns): no match ⇒ Teagle itself (Flint kernel, fail-closed). Backed by a RoutingCatalog
(explicit-over-wildcard, active-over-retired precedence, id--version resolution).
Operations
- Multi-source
$expand— resolve (inline / store / implicit / remote-owned canonical) → per-system fan-out (local groups on the Flint kernel; remote groups on the owning server) →system|codededup merge → imports (intersection within an include, union across) → post-merge text filter (also forwarded to sub-expansions) → offset/count paging with a true total. A fully-local compose takes a Flint fast-path so aggregation never degrades conformance semantics. ?vs(all-codes implicit VS) is synthesised locally (404 on unknown CS — never an empty 200);?fhir_vs(ECL / isa / refset) routes through Flint’s SNOMED provider (version→branch), with the owning registry server as fallback — an improvement over the reference explorer, which blind-forwarded?fhir_vs.- Registry-aware
$lookup/$validate-codedispatch, and an aggregated/fhir/metadata(+?mode=terminologywith per-CodeSystemtarget-serverannotations)./api/serverslists the registry.
Request model (inherited from the eXplorer)
Content-negotiated reads (same URL, Accept selects UI vs FHIR JSON/XML); versioned resources use | in FHIR
canonicals and -- internally (/fhir/CodeSystem/foo--1.0.6). See the reference
terminology-explorer/docs/routing-specification.md for the full URL/versioning/lifecycle detail Teagle preserves.
Teagle vs FIS
FIS is the platform-wide FHIR gateway (multi-module fan-out via fhir-route-config.yml); Teagle is a
terminology-domain aggregator whose distinct value is external SNOMED/UCUM reach and the editable in-memory
server. They coexist — Teagle can be registered as a FIS target.
Run
./scripts/run_backend.sh teagle # aggregator on :18650
./scripts/run_frontend.sh teagle # React UI (once the frontend lands), proxying to :18650
Spec authoring — how we create TEDY specifications
We reverse-engineer specifications for the reference-editor rewrite from the existing, working reference platform product, then align them to the Helex EMR specification system. The specs are the contract the React modules are (re)built against.
Alignment with the EMR spec system
We follow the EMR spec rules verbatim — do not invent a parallel system:
- Rules:
/Users/igor/source/emr/docs/specifications/rules.md(spec-first, lifecycleProposed→Accepted→Verified→Implemented, mandatory frontmatter + sections, glossary gate, data-model + FHIR-mapping + API-contract rules). - Templates:
/Users/igor/source/emr/docs/specifications/_templates/— pick one per spec:resource-list,resource-create,resource-management,import-tool,tool-feature(+ the two clinical ones we don’t use). - Validation: the EMR
spec-validationskill /validate-speccommand.
TEDY specs live here under specs/tedy/ (see specs/tedy/README.md for IDs). They may be contributed upstream into emr/docs/specifications/ (module prefix TRM, “terminology, via the reference platform”) once accepted — keep them template- and frontmatter-compatible so that move is mechanical.
The two-step derivation
Because the source of truth is a shipped app, every spec is derived in two ordered passes. Pass 1 captures what the product actually does; pass 2 makes it precise and contract-complete.
Step 1 — Scan the live product on dev-tx.example (Claude Chrome)
Drive the real the reference platform UI and record observed behaviour per page. For each screen capture:
- Route (reference-editor path, e.g.
/resources/code-systems) and page title. - Layout: search box, primary actions (e.g. Add code system → Manual / Import FHIR), column-config + filter toggles, side panels.
- Table: column order + labels + which cells are links (e.g.
ID · LAST VERSION · TITLE · PUBLISHER · STATUS · COUNT), row expand, status chips + their values. - Filters: each field + control type (publisher, status, space, …).
- Detail / tabs, empty & error states, and the visible i18n labels (note the active language).
Tools: navigate + screenshot + read_page (accessibility tree) on https://dev-tx.example/... (guest). Output: a raw observed-behaviour note per page (drop screenshots under specs/tedy/_scans/). Do not write contracts yet — only what is observable.
This pass is authoritative for actual UX (what users see and do). It is not authoritative for API shapes, params, permissions, or validation — those aren’t fully observable from the UI.
Step 2 — Reconcile against source, then fix differences
Open the implementation and upgrade the draft into a real spec, fixing any difference from Step 1:
- reference-editor (
<reference-editor-src>) — the component + service for the page: exact columns/params, search params, sort keys, privileges (*.CodeSystem.write), statuses, validation, actions, sub-routes. reference-editor is authoritative for behavioural detail the UI hides. - terminology-server (
<terminology-server-src>) — the API endpoints (/ts/*), request/response envelopes ({data, meta}), error contract, data model (tables, cardinality), and FHIR mapping. Authoritative for contracts + model. - Record every live-vs-source difference in a
## Differencesnote (live wins for UX; source wins for contracts; flag genuine bugs/drift).
Then write the spec with the chosen template: fill the mandatory sections (Description+wireframe, Glossary Terms, Business Feature Sitemap, Data Model, Sitemap/Navigation, API Endpoints, Security/Permissions, NFRs, Business Tests, Source Code References, Traces), set state: Proposed, and populate source-refs with both origins:
- reference-editor/terminology-server paths (behavioural + contract origin), and
- the TEDY React implementation (
helex-txmodule/lib paths) as the rebuild target.
traces-from points to a user story; if none exists yet, note it as a gap (EMR rules require a story before a solution spec — create a thin story or mark Proposed pending one).
Order of work
Each resource is a feature family (MDM pattern): a TEDY.NN overview/common spec (tool-feature) that owns the shared data model / value sets / FHIR mapping / privileges / API surface / navigation, plus child screens TEDY.NN.1 List, TEDY.NN.2 Add, TEDY.NN.3 View (children reference the root, no duplication). Start with the resources already built in TEDY (Code Systems — see the TEDY.01 family) so spec and code cross-check, then move to Value Sets, Map Sets, etc.
Checklist per spec
- Step 1 live scan recorded (screens + observations,
_scans/). - Step 2 source reconcile done;
## Differencescaptured. - Template chosen; all mandatory sections filled; glossary terms mapped.
- Frontmatter complete;
source-refs= reference-editor/-server and TEDY paths. - Data model + FHIR mapping + API/error contract per EMR rules.
- Business tests written (before implementation, for un-built resources).
- Passes the EMR
validate-speccheck.
Publishing the docs
These docs are an mdBook. The book root is
docs/ — book.toml is the config and
SUMMARY.md is the table of contents. Every page listed in
SUMMARY.md becomes a chapter; files outside it (e.g. _scans/) are ignored by
the build.
Build locally
# one-off build → docs/book/ (gitignored)
mdbook build docs
# live-reload preview on http://localhost:3000
mdbook serve docs --open
Install mdBook with brew install mdbook, cargo install mdbook, or a
release binary. CI pins the
version in .github/workflows/docs.yml.
Deploy
Pushes and PRs that touch docs/** build the book and deploy it to
Cloudflare Pages (project helex-tx-docs) via
.github/workflows/docs.yml:
- push to
main→ production deploy athttps://helex-tx-docs.pages.dev - pull request → preview deploy at
https://<branch>.helex-tx-docs.pages.dev(the run summary prints the exact URL), so docs can be reviewed before merge - Run workflow button (Actions tab) → manual deploy of the current branch
Preview URLs are covered by the same Cloudflare Access policy as long as it
includes *.helex-tx-docs.pages.dev (see below).
GitHub Pages is not used: the tx repo is private on a GitHub Team plan,
and access-controlled Pages requires GitHub Enterprise Cloud. Publishing a
private repo’s Pages on Team makes the site world-readable, so we host on
Cloudflare instead, where Cloudflare Access gives real authentication.
One-time Cloudflare setup
Done once by someone with access to the Cloudflare account. No account creation or credential entry is scripted — do these in the dashboard.
-
Pages project. The workflow creates it automatically on first run (
wrangler pages project create helex-tx-docs --production-branch=main), so you can skip this — or pre-create it in the dashboard (Workers & Pages → Create → Pages → Direct Upload, namehelex-tx-docs). The site is served athttps://helex-tx-docs.pages.dev— optionally add a custom domain. -
Create a scoped API token. My Profile → API Tokens → Create Token → template “Edit Cloudflare Workers” (or a custom token with Account › Cloudflare Pages › Edit). Copy the token.
-
Find the Account ID. Any Workers & Pages page shows it in the right rail.
-
Add the two GitHub secrets on
helex-solutions/tx(Settings → Secrets and variables → Actions):CLOUDFLARE_API_TOKEN— the token from step 2CLOUDFLARE_ACCOUNT_ID— the ID from step 3
gh secret set CLOUDFLARE_API_TOKEN --repo helex-solutions/tx gh secret set CLOUDFLARE_ACCOUNT_ID --repo helex-solutions/tx
Gate the site with Cloudflare Access (the “password”)
Cloudflare Access sits in front of the Pages site and requires a login before any page loads — one-time email codes (OTP) by default, or Google/GitHub/SSO.
- Cloudflare dashboard → Zero Trust → Access → Applications → Add an application → Self-hosted.
- Application domain:
helex-tx-docs.pages.dev(and the custom domain, if any). Cover*.helex-tx-docs.pages.devtoo so preview deploys are protected. - Add a policy → Action Allow → include rule Emails (list the people
who may read the docs) or Emails ending in
@helex-solutions.... - Save. Visitors now get a Cloudflare login screen; approved emails receive a one-time code and are let through. Everyone else is blocked.
For a shared static password instead of per-person email, use a Service Token or a “Service Auth” policy — but per-email OTP is stronger and needs no secret sharing, so prefer it.
Zero Trust’s free plan covers up to 50 seats, which is plenty here.
TEDY resource navigation — TxResource architecture
How TEDY presents a terminology resource (Code System / Value Set / Map Set /
Structure Definition / Structure Map): its list → resource → version navigation,
the shared resource bar, and cross-resource work contexts. This follows the
HELEX frontend architecture (emr/docs/architecture/frontend) and advances the
earlier ad-hoc shell.
Principles (from HELEX frontend architecture)
- URL is the source of truth for
{resource, version, tab}. Tabs are links; the active tab is derived from the route, not local state (05-routing-navigation). - Single detail component keyed by
id === 'new'; view ⇄ edit is an in-pageResourceFormmode toggle — no separate/:id/editroute (17-frontend-conventions §11). Create success navigates with{ replace: true }. - Work contexts (parked/pinned resources across types) use the platform’s
generic context-switching framework —
WorkContext+contextTypeRegistry- the connected context bar — with an explicit Pin action (no auto-park) (09-context-handling).
- Naming:
Tx*for the terminology-resource UI (aligns with the@helex/txterminology namespace);Tedy*for TEDY-app-specific pieces.
Components
TxResourceBar (built)
The shared, sticky resource bar shown on every resource/version page:
- Identity — a short resource-type marker (CS / VS / MS / SD / SM) + localized title, with a Pin toggle (see cross-resource work contexts below).
- Submenu tabs — URL-driven; the active tab comes from the route. Tab set per
resource type (below).
routeFor(mode)injects/versions/{version}when a version is active, so the same submenu serves the resource and version levels. - Version area — an All versions dropdown when unversioned; the version
code + a status marker when a version is active. Selecting a version drills into
it (
/{type}/{id}/versions/{version}/{mode},replace). - × unselect — version → resource (same tab,
replace); resource → list.
Version is a path segment, never a query param.
Tab matrix
| Resource | Resource-level tabs | Version-level tabs |
|---|---|---|
| Code System | Summary · Metadata · Concepts · Properties¹ · Provenance · Checklist² | + concepts / properties / provenances / checklists |
| Value Set | Summary · Metadata · Concepts · Provenance | summary · concepts · provenances |
| Map Set | Summary · Metadata · Provenance | summary · provenances |
| Structure Definition | Summary · Metadata · Content³ | summary · content |
| Structure Map | Summary · Metadata · Content³ | — (no versioning⁴) |
¹ CS Properties only when the CS has a Coding-typed property. ² Checklist
privilege-gated. ³ Content = the FSH/JSON (Structure Definition) or composed FML
(Structure Map), read-only for now. ⁴ Structure Maps have no backend versioning,
so the bar shows no version dropdown/drill-in and there are only resource-level tabs.
- Metadata is the single detail component (view + in-page edit toggle) — no
/:id/editroute (HELEX convention). - Value Set rules and Map Set associations are edited inside the respective version summary (they are not top-level tabs).
- Structure Definition / Structure Map live outside
/ts(/structure-definitions,/transformation-definitions) and use numeric ids; both render on the sameTxResourceBar— SD versioned, SM version-less — proving the bar is not tied to the/tsresource shape.
Cross-resource work contexts (pins) — delivered
Pinning uses the platform WorkContext framework, not a bespoke bar:
- The
TxResourceBarPin toggleopenContext()s the resource into the shared ReduxcontextSlice(sessionStorage-persisted); un-pinningcloseContext()s it. - The platform’s connected context bar (already rendered by the app chrome,
ConnectedPatientContextBar) shows the pinned resources — across all five types — as switchable tabs (select → navigate itsactivePath; × → close). registerTedyContextTypes()registers the five context-type codes at bootstrap.- The bar keeps a pinned context’s
activePathcurrent (setContextPath) and re-activates it (switchToContext) as you move between a resource’s tabs, so the platform bar stays on-screen.
No ad-hoc open-resources store — that was removed in P4. (Two known limits: the
platform bar hides on list/non-resource pages, and dev-guest reload clears pins
because re-auth dispatches clearSession; real OIDC keeps them across F5.)
Routes
Layout routes render TxResourceBar + <Outlet>:
/{type} → list (id === 'new' handled by the detail route)
/{type}/:id/(summary|details|concepts|properties|provenances|checklists)
/{type}/:id/versions/:versionCode/(summary|concepts|properties|provenances|checklists)
/{type}/:id/versions/new
No /:id/edit (view⇄edit is in-page). Each leaf is a page body that reads the
version from the path.
Build plan
All three resources are migrated onto TxResourceBar. Code System was the
reference slice; Value Set and Map Set followed the same shape.
- P1 —
TxResourceBar✅ built (sticky bar, URL-driven tabs, version dropdown, ×, back control,routeFor/selectVersion/unselect). - P2 — URL layout routes (list →
:id/{mode}→:id/versions/:versionCode/{mode}) renderingTxResourceBar+<Outlet>. ✅ Code System, Value Set, Map Set ({CodeSystem,ValueSet,MapSet}ResourceLayout), resource + version levels; version is a path segment, no?version. - P3 — tab bodies mapped to modes: Summary, Metadata (in-page edit toggle on
the
detailsroute — no/:id/edit), Concepts, Provenance, and CS Properties (Coding-gated) / Checklist. ✅ Code System (six tabs), Value Set (Summary · Metadata · Concepts(expansion) · Provenance), Map Set (Summary · Metadata · Provenance), with provenance/checklist clients + hooks (/ts/{resource}/{id}/provenances,/ts/checklists). - P3b — move Value Set rule editor + Map Set associations grid into the respective version summary pages. ✅ both (the compose rule-set editor and the associations grid render in the version-level Summary).
- P4 — remove the ad-hoc open-resources store + per-resource shells. ✅
TedyResourceShell,openResourceStore, andshellActionsdeleted. - P5 — verify the route hierarchy end-to-end. ✅ all resources (list → resource → version, every tab, real provenance / associations data, version status marker).
- P6 — cross-resource pins via the platform
WorkContextframework. ✅ Pin toggle + platform context bar (see above). - P7 — Structure Definition + Structure Map on the same bar (outside
/ts, numeric ids; SD versioned, SM version-less). ✅ list / create / detail with Summary · Metadata · Content.
Differences from the earlier TEDY implementation
| Aspect | Earlier (ad-hoc) | Now (HELEX-aligned) |
|---|---|---|
| Resource bar | per-resource shells | one TxResourceBar |
| Active tab | local state / pathname sniff | URL-driven |
| Version | ?version query param | path segment |
| Pins | bespoke store + persistence + pushpin + overflow | platform WorkContext framework (Pin toggle → connected context bar) |
| Metadata | inline toggle (kept) | in-page toggle, no /:id/edit (matches HELEX) |
| Naming | TedyResourceShell/ResourceContext | TxResourceBar |
Resource dashboard (ResourceContext) — gap analysis & implementation plan
Why: the TEDY detail page (TedyResourceForm — a single flat metadata+versions+concepts view) is a fraction of reference-editor’s resource screen. Audited https://dev-tx.example/resources/code-systems/anesteesia-liigid/summary and the reference-editor source. This documents the gap and the plan to close it.
What reference-editor actually renders
The screen is a shared, route-driven tabbed resource shell (resources/resource/components/resource-context.component.ts) wrapping per-tab pages built from widget cards.
tw-resource-context— inputsresourceType(CodeSystem|ValueSet|MapSet|ImplementationGuide|TerminologyServer|StructureDefinition),resource,version,versions,conceptCode,mode(summary|details|concepts|properties|provenance|checklist|…). Renders a top page-bar: resource-type label, localized title, a tab strip (per-type conditional: Summary·Metadata·Concepts·Properties·Provenance·Checklist, +SD elements/fsh/json/uml), and a version selector (“All versions” + per-version routing). Route- and privilege-aware (twPrivileged,twPrivilegeContext).- Summary tab = a 2-column grid of cards:
tw-code-system-info-widget— icon-annotated summary (id, uri, properties, case-sensitivity + content, contact email/url, publisher, linked ValueSet).tw-code-system-versions-widget— version rows (version, date range, concept count, status tag, language, Add to release, linked VS) + Compare + Add version.tw-code-system-unlinked-concepts— backend table (CONCEPT·VERSION·DESIGNATION·AUTHOR·TASK·STATUS, row checkboxes) + search + Link to<version>dropdown + Add concept.tw-resource-tasks-widget(+tw-resource-task-modal) — Opened/All tasks, Add task, task rows.tw-resource-related-artifact-widget— related artifacts.- Value-sets card — referenced VS with rule-source / “version matches latest” indicator.
Shared widget family (resources/resource/components/): resource-context, resource-side-info, resource-form, resource-version-form, resource-identifiers, resource-contacts, resource-configuration-attributes, resource-multi-language-view, resource-readonly-concept, resource-tasks-widget, resource-task-modal, resource-related-artifact-widget, resource-release-modal, resource-fhir-import-modal.
Mapping: reference-editor → @helex/ui → TEDY status
| reference-editor (Marina) | @helex/ui primitive available | TEDY status | Action |
|---|---|---|---|
tw-resource-context (tabbed shell) | EntityHeader + PageTabs + AppSelect (compose) | ❌ missing (only flat TedyResourceForm) | Build TedyResourceShell |
| version selector (“All versions”) | AppSelect/AppDropdown | ❌ | part of shell |
code-system-info-widget (icon summary) | AppCard+FieldItem/AppDescriptions | ⚠️ partial (flat FieldItems) | CodeSystemInfoWidget |
code-system-versions-widget | AppCard+AppTable+AppTag | ⚠️ flat list only | CodeSystemVersionsWidget |
code-system-unlinked-concepts (backend table + checkboxes + link/add) | AppTable/AppDataGrid (selection) | ⚠️ simple read-only concepts table | extend CodeSystemConcepts (columns, selection, actions) |
resource-tasks-widget + resource-task-modal | AppTable+AppModal | ❌ | TedyTasksWidget + task modal (+ tasks client) |
resource-related-artifact-widget | AppCard+AppList | ❌ | RelatedArtifactsWidget |
| value-sets card | AppCard+AppTag+AppLink | ❌ | ReferencedValueSetsWidget |
resource-form (Metadata edit) | ResourceForm | ⚠️ view-only | edit mode (+ sub-widgets below) |
resource-identifiers | TxIdentifier (@helex/tx) — value model IS FHIR Identifier[] | ✅ reuse | free-form (allowCustom/omit concepts) mirrors the reference platform’s plain system+value table; VS-backed via the $expand adapter. No FhirIdentifier exists. |
resource-contacts (ContactDetail[] = name + telecoms) | FhirTelecom (ContactPoint) + FhirAddress exist; no contact widget | build in @helex/fhir | create FhirContact (ContactDetail editor) composing FhirTelecom — standard FHIR datatype, lives with the other Fhir* widgets (shared, sign-off-gated) |
resource-configuration-attributes | AppEditableTable | ❌ | ConfigurationAttributes |
resource-multi-language-view | AppLocalizedName | ⚠️ have input, not view | small helper |
resource-version-form | ResourceForm | ❌ | VersionForm |
resource-release-modal / resource-fhir-import-modal | AppModal | ❌ | later (release → Press; FHIR import → Exo) |
twPrivileged / twPrivilegeContext | useModule().canWrite() (@helex/core) | ⚠️ not wired per-resource | useTedyPrivilege(id,'CodeSystem') wrapper |
Note: @helex/ui has the primitives (EntityHeader, PageTabs, AppSelect, AppTable/AppDataGrid, AppCard, AppTag, FieldItem, AppModal, AppDropdown, AppDivider) — the gap is the composed, terminology-aware shell + the domain widgets, which are TEDY-specific → build in @helex/tedy (do NOT bloat @helex/ui; per the leaned-down convention).
Missing components (to build in @helex/tedy)
TedyResourceShell— theResourceContextequivalent:resourceType+resource+versions+mode; header (icon, localized title, version selector) + route-driven tab strip; wraps routed tab pages. Backed byEntityHeader+PageTabs+AppSelect. (Foundational — everything else mounts inside it.)- Summary widgets:
CodeSystemInfoWidget,CodeSystemVersionsWidget,RelatedArtifactsWidget,ReferencedValueSetsWidget. - Concepts: extend
CodeSystemConcepts→ backend table with the reference platform columns (Concept·Version·Designation·Author·Task·Status), row selection, Link to version + Add concept (and later the concepts tab page + tree). - Tasks:
TedyTasksWidget+ task modal + a tasks client (or reuse EMRflow). - Metadata edit:
ResourceFormedit mode for CodeSystem + sub-widgets: reuse@helex/txTxIdentifier(identifiers), newFhirContactin@helex/fhir(contacts, composingFhirTelecom),ConfigurationAttributes,VersionForm. (NoFhirIdentifier— identifiers =TxIdentifier.) - Privilege gating:
useTedyPrivilege(resourceId, 'CodeSystem')(wraps@helex/coreuseModule) for{id}.CodeSystem.write. - Later/other modules:
resource-release-modal→ Press;resource-fhir-import-modal→ Exo.
Implementation plan (phased)
- Phase R1 — Shell + Summary (read-only). ← DELIVERED (this change).
TedyResourceShell(@helex/tedy) =EntityHeader+PageTabs(merged) + a version-selector slot (tabBarExtraContent) + routed<Outlet>. Code System detail is now a nested routecode-systems/:id→ shell with tabs Summary · Metadata · Concepts (read-only):CodeSystemShellloads once and shares via Outlet context;CodeSystemSummary=CodeSystemInfoWidget+CodeSystemVersionsWidget+ concepts preview;CodeSystemMetadata= full field view;CodeSystemConceptsTab= the concepts table. FlatCodeSystemDetail/TedyResourceForm-for-detail retired (TedyResourceForm kept for create/edit). Properties/Provenance/Checklist tabs + RelatedArtifacts/ValueSets/Tasks widgets + version selector wiring follow in later phases. - Phase R2 — Concepts tab. Full concepts backend table (the reference platform columns + selection) + the concepts tree/search drawer (reuse
@helex/txTxConceptTree/TxConceptSearchDrawerwhere they fit the reference platform/ts/*, else fresh). - Phase R3 — Metadata edit + versions. Metadata edit + write flow DELIVERED (commit
ec538bf); create page DELIVERED (TEDY.01.2, commitf0918f9).@helex/tedysaveCodeSystem→POST /ts/code-systems/transaction; Metadata tab is now view/edit viaResourceForm(hideHeader, inside the shell) +useDataController, editing uri/name/title/description/publisher (TxConceptSelect)/content/caseSensitive/baseCodeSystem/identifiers (TxIdentifier); save invalidates the shell query → back to view. The edit fields were extracted into a sharedCodeSystemFormFieldsand reused byCodeSystemCreate(ResourceFormincreatemode,ideditable only on create, content/caseSensitive seeded, required-field validation), reached from the list’s Add code system action (code-systems/new) and navigating into the new record’s shell on success. Both verified end-to-end (guest write works on dev; create persiststitle={en:...}/content/caseSensitive and lands on the summary tab). Version form + lifecycle DELIVERED (commit493ec33):@helex/tedyversionClient(saveCodeSystemVersionPOST-new/PUT-existing,changeCodeSystemVersionStatusdraft/activate/retire,deleteCodeSystemVersion) +useCodeSystemVersionshooks; sharedVersionFormFields(version key create-only, release/expiration dates bridged toAppDatePicker’s dayjs, supported/preferred languages, algorithm, description, identifiers) reused by standaloneCodeSystemVersionCreate/CodeSystemVersionEditpages (code-systems/:id/versions/new+/:versionCode/edit); the Summary versions widget now lists live versions with Add version, per-row Edit, a single-step status transition (draft→active→retired, retired→draft), and confirmed Delete, invalidating on each change. Verified end-to-end (create+activate via UI; edit/retire/draft/delete via the same endpoints; note: editing an active version’s metadata is frozen server-side, matching reference-editor). Still pending: contacts (FhirContact) + config-attributes sub-widgets, per-resource privilege gating, and the Import FHIR create entry point (owned by Exo). - Phase R4 — Tasks + related artifacts + value sets widgets (Tasks may reuse EMR
flow). - Phase R5 — Generalize. Extract
TedyResourceShell+ shared widgets so ValueSet/MapSet/NamingSystem reuse them (theresourceTypeswitch), mirroring reference-editor’s sharedresource-*family.
Sequencing note: R1 is the unlock — it establishes the shell every terminology resource hangs off, and immediately closes the biggest visual/functional gap. Each phase is spec-first (update the TEDY.01 family: shell/summary → TEDY.01.3; metadata edit → TEDY.01.2 + a versions spec; concepts tab → a new child).
Verify
Per phase: npx tsc -p tsconfig.json --noEmit (module) + browser parity check against dev-tx.example/resources/code-systems/{id}/{tab} for each tab. Keep column/label/tab order matched to reference-editor (record deltas in the specs’ Differences tables).
Plan: align the TEDY Code System Summary with reference-editor
Why. The new TEDY summary (/tedy/code-systems/:id/summary) and the reference-editor
original (dev-tx.example/resources/code-systems/:id/summary) diverge noticeably.
This plan maps the old layout to the new one widget-by-widget and phases the work.
Sources compared:
- Old:
reference-editor/app/src/app/resources/code-system/containers/summary/code-system-summary.component.html(+widgets/*,code-system-info-widget,resource-context.component). - New:
modules/tedy/frontend/src/pages/code-system/{CodeSystemShell,CodeSystemSummary}.tsx+widgets/*.
Side-by-side
| Area | reference-editor (old) | TEDY (new) today | Gap → action |
|---|---|---|---|
| Header | resource-context: icon + localized title + version selector dropdown + status | TedyResourceShell: icon + title + tab strip; version-selector slot exists, unwired | P4 wire a version selector into the shell’s tabBarExtraContent slot |
| Info card | Compact icon-prefixed lines: 🔑 id (copyable) · description · 🔗 uri (copyable) · ▤ properties (names) · ⚙ case-sensitivity + content-mode + experimental · ☎ contacts/telecoms · 📍 publisher (resolved) · 🏷 linked VS link | AppCard + SectionHeading + FieldItem label/value grid: id · uri · name · publisher · content · caseSensitive · description | P1 rebuild as the compact icon-list; add missing: copy-to-clipboard, properties, experimental, contacts, linked-VS link |
| Versions card | Title bar: Compare + Add version; rows: version · date range · count · status · language · add-to-release · linked VS · per-row Compare | Title + Add version; rows: version · status · releaseDate · count · Edit / transition / Delete | P2 add Compare (card + row), date-range, language, linked-VS columns; keep the new lifecycle actions (an improvement) |
| Unlinked concepts (right col) | Backend management table (Concept·Version·Designation·Author·Task·Status, checkboxes) + Link-to-version dropdown + Add concept | (only a read-only concepts preview) | P3a = the R2 concepts work; big build |
| Tasks (right col) | resource-tasks-widget (triage-gated) + opened/all toggle + Add task + task modal | (absent) | P3b reuse EMR flow module widgets; privilege-gated |
| Related artifacts (right col) | resource-related-artifact-widget | (absent) | P3c new domain widget (@helex/tedy) over /ts/.../related-artifacts |
| Value sets (right col) | Value-set impacts list (affected/ok icon, source/current version, reason) | (absent) | P3d new domain widget; needs the impacts endpoint |
| Layout | 2-col m-form-row, several cards per column | 2-col CSS grid, info+versions left / concepts preview right | Converges naturally as P1–P3 land |
Phased plan
P1 — Info-card visual parity (quick, highest visual payoff). ✅ DELIVERED (commit 4f49cd9). New reusable Tedy* UI components live in libs/tedy/src/lib/ui/ — TedyInfoCard (compact card), TedyInfoLine (icon-prefixed compacted line + optional copy-to-clipboard), TedyInlineMeta (divider-separated inline segments). CodeSystemInfoWidget now renders the reference platform-style icon-list (key/link/bars/setting/environment/tag): id + uri copyable, properties, settings line (case-sensitivity + content + experimental), resolved publisher, linked VS. Verified on administrative-gender-cz.
Original P1 scope: Replace the
FieldItem grid in CodeSystemInfoWidget with the reference platform compact icon-list:
@ant-design/icons (Key/Link/Bars/Setting/Contacts/Environment/Tag) prefixing
each line; id + uri get a copy-to-clipboard affordance; render description,
properties (names, when loaded), a settings line (case-sensitivity label +
content-mode + experimental), contacts/telecoms, resolved publisher, and a
linked-VS AppLink. Keep the localized-name + TxConceptDisplay publisher
resolution already in place. No backend change; ~half a day.
P2 — Versions card parity. ✅ DELIVERED (commit 0c05f7c). Versions widget gained Dates (release–expiration), Supported languages, and Value set columns; a card-level Compare button (disabled with <2 versions) wired to a CodeSystemVersionCompare placeholder page (code-systems/:id/compare — diff view deferred). Lifecycle actions retained. Also (per user ask) the Concepts ResourceList gained a filter button (Status: draft/active/retired → codeSystemEntityStatus). Verified on anesteesia-liigid.
Original P2 scope: Extend the versions widget columns to match (date range, language, linked VS) and add a card-level Compare action (+ per-row). Keep the TEDY lifecycle actions (Add/Edit/activate·retire·draft/ Delete) — they exceed the old widget and stay. Compare view itself is a separate screen (defer the compare page to its own task; wire the button to a stub/route first).
P3 — Right-column widgets (the largest gap; each already tracked in
resource-context-gap.md):
- P3a Unlinked/managed concepts — ✅ DELIVERED (commit
45a6ed6).CodeSystemUnlinkedConceptsWidget(backend entity-versions table viaGET .../entity-versions?unlinked=true&status=draft,active) with row selection, search, and a Link-to-<draft-version>action (POST .../versions/{v}/concepts/link); replaces the concepts preview. Verified onanesteesia-liigid(surfaces the unlinked draft concept “4”). Deferred sub-flows: Author/Task columns + create-task, and concept authoring (Add concept) — still R2. - P3b Tasks — ✅ DELIVERED (commit
a0ff69f). the reference platform-native widget overGET /tm/tasks?context=CodeSystem|{id}(NOT the EMRflowmodule — that’s a different backend). Read-only list; create/triage flows later. - P3c Related artifacts — ✅ DELIVERED (
a0ff69f).POST /related-artifacts→useRelatedArtifacts→ card. - P3d Value-set impacts — ✅ DELIVERED (
a0ff69f).GET .../value-set-impacts→useValueSetImpacts→ card (affected/ok flag, source vs current version).
Verified on anesteesia-liigid: Related artifacts = ValueSet anesthesia-types;
Value sets = anesthesia-types 0.0.1 (ok, source 1.0.0 / current 1.0.0); Tasks = none.
P4 — Header version selector. ✅ DELIVERED (commit a0ff69f). CodeSystemShell
populates the shell’s tabBarExtraContent slot with a version AppSelect
(backed by useCodeSystemVersions), tracks the choice in ?version=, and
exposes it via Outlet context (CodeSystemOutletContext.version) for
version-scoped tabs, matching resource-context’s selector.
Recommended order & sizing
- P1 (icon-list info card) — do first; it’s most of the “looks different” and is cheap + backend-free.
- P2 (versions columns + Compare button) — small, rounds out the left column.
- P4 (header version selector) — small, high perceived parity.
- P3a–d — schedule as the R2/R4 phases; each is a real build (backend + widget). P3c/P3d are self-contained; P3a (concepts mgmt) and P3b (tasks) are the heaviest.
Decisions to confirm
- Info card is a shared visual idiom. Building the icon-list inside
@helex/tedy(widget) is fine; if a reusable “icon field list” primitive is wanted in@helex/ui, that’s a separate sign-off-gated change — not assumed here. - Contacts reuse the planned
FhirContact(@helex/fhir, composingFhirTelecom) — seeresource-context-gap.md. - Value-set impacts: verify
impactsendpoint on terminology-server before P3d; otherwise show only the linked value set(s).
Accessibility: is a “tiny” option warranted? (font-size + spacing audit)
Question: TEDY’s concept tables are dense; do we need a new “tiny” accessibility option? Audited Marina/reference-editor vs @helex/ui sizing.
What @helex/ui already has (two orthogonal axes)
- Density (
HelexDensity,libs/ui/src/lib/themes/themes.ts):comfortable(controls 40px, table cell 16/16, gaps 6/12/16/20/32) ·default(32px, 16/16, 4/8/12/16/24) ·compact(24px, 8/8, 2/4/8/8/16 + antdcompactAlgorithm). Driven viaAccessibilityModal→HelexThemeProvider→buildAntTheme/applyThemeCSSVars. - Font size (
HelexFontSize,HelexThemeProvider.tsx):normal14px ·large18px ·extraLarge26px (root<html>font-size +.helex-scaled-*). Both axes only go bigger/looser than baseline exceptcompact.
Marina / reference-editor density (px, base font 14)
Marina + reference-editor both set html{font-size:14px}. the reference platform never drops global font below 14 — it shrinks font only inside nzSize="small" tables (~10.5px).
| Metric | reference-editor | helex comfortable | helex default | helex compact |
|---|---|---|---|---|
| Base font | 14 | 14 | 14 | 14 |
| Control height | 32 (24 small) | 40 | 32 | 24 |
| Table cell padding | 11.2/11.2 (small 5.6/11.2 + font↓~10.5) | 16/16 | 16/16 | 8/8 |
| Card padding | 12 | ~24 | 24 | ~12 |
| Typical row height | small ~27–32 / default ~44 | ~53 | ~53 | ~37 |
Verdict
The opinion is partially right — but as a DENSITY step, not a FontSize option, and only for data-grade tables.
@helex/uicompactis already denser than reference-editor’s default (control 24 vs 32, cell 8 vs 11.2). For ordinary screens, usecompact— no new preset needed.- The only place the reference platform is denser is its small-table mode (~27–32px rows), which shrinks both padding and in-table font — something no
@helex/uidensity does. For big concept tables (the reference platform precedent: a 37,504-row CodeSystem), a denser step buys ~15–30% more visible rows. A narrow, real gap. - Reject a FontSize “tiny” (sub-14 global) — WCAG-adverse; the FontSize axis exists to scale up for low-vision users, and the reference platform never goes sub-14 globally.
If we implement it (recommended shape)
A new HelexDensity 'tiny' (below compact): keep global font 14, compress padding, and shrink font within tables only (mirroring the reference platform’s --*--sm table vars). Proposed tokens (from the audit):
DENSITY_MAPS.tiny = { gapXs:2, gapSm:4, gapMd:6, gapLg:6, gapXl:12, rowPadding:6 }antdComponentSizeFor: tiny → 'small'+compactAlgorithmbuildAntThemetiny overrides:token.controlHeight:24, controlHeightSM:20;components.Table:{cellPaddingBlock:4, cellPaddingBlockSM:4, cellPaddingInline:8, cellPaddingInlineSM:8, cellFontSizeSM:13};components.Card:{bodyPadding:8}. GlobalfontSizestays 14.- Edit sites (
@helex/ui, all additive):themes.ts(HelexDensityunion,DENSITY_MAPS,antdComponentSizeFor,buildAntTheme),settings/AccessibilityModal.tsx(radio +helex.settings.access.tinykey),themes.test.ts. Effort ~½ day.
This is a shared @helex/ui change → needs sign-off (per the leaned-down convention). Cheaper alternative that avoids a global preset: apply antd size="small" + a scoped cellFontSize override on the specific TEDY concept-table components only.
Proposal: add a tiny density step to @helex/ui (sign-off requested)
To: Helex-UI owner
From: TEDY / the reference platform-parity work
Status: Awaiting sign-off — no code has been written. This is a shared @helex/ui change, so per the leaned-down convention it needs your explicit go-ahead before implementation, and it will land on its own worktree/branch (not pushed directly).
Based on: docs/accessibility-density-tiny.md (completed audit of Marina/reference-editor vs @helex/ui sizing)
Effort: ~½ day, fully additive.
1. Verdict (already decided — not re-opening this)
A tiny option is warranted, but with two hard constraints:
- It is a new
HelexDensityvalue, NOT aHelexFontSizeoption. A sub-14px global font is WCAG-adverse and is rejected outright. The FontSize axis exists to scale text up for low-vision users; reference-editor itself never drops global font below 14px. - Its only job is denser data-grade tables.
compactalready covers ordinary screens — it is in fact already denser than reference-editor’s default mode (control 24px vs 32px, table cell padding 8/8 vs 11.2/11.2). The one place the reference platform is denser than anything@helex/uioffers today is its small-table mode (~27–32px rows), which shrinks both cell padding and in-table font — something no current density does. For large concept tables (the reference platform precedent is a 37,504-row CodeSystem) a denser step buys roughly 15–30% more visible rows. That is the narrow, real gaptinyfills.
Everything below is the concrete plan for that decision.
2. WCAG rationale — why density, not font-size
- The
HelexFontSizeaxis (normal14 /large18 /extraLarge26) is an accessibility scale-up control. Adding a sub-14 value to it inverts its purpose and pushes global body text below the readable floor — WCAG-adverse and off-precedent. tinykeeps globalfontSizeat 14 and only compresses layout spacing app-wide. Font shrinks inside tables only (cellFontSizeSM: 13), mirroring the reference platform’s--*--smtable variables — dense grids where the user has opted in, not global body copy.- Because it is opt-in per user (Accessibility modal) and scoped to tables, it does not degrade the default experience or the low-vision scale-up path.
3. Proposed shape (tokens)
New HelexDensity value 'tiny', positioned below compact. Global font stays 14.
DENSITY_MAPS.tiny = { gapXs: 2, gapSm: 4, gapMd: 6, gapLg: 6, gapXl: 12, rowPadding: 6 }
antdComponentSizeFor: tiny → 'small', plus antdcompactAlgorithm(same algorithmcompactalready uses).buildAntThemetinyoverrides:token.controlHeight: 24,token.controlHeightSM: 20components.Table: { cellPaddingBlock: 4, cellPaddingBlockSM: 4, cellPaddingInline: 8, cellPaddingInlineSM: 8, cellFontSizeSM: 13 }components.Card: { bodyPadding: 8 }- Global
token.fontSizestays 14 (unchanged).
4. Edit sites (all additive, all in modules/core/frontend)
| File | Change |
|---|---|
libs/ui/src/lib/themes/themes.ts | Add 'tiny' to the HelexDensity union (line 375); add the DENSITY_MAPS.tiny entry (line ~387); extend antdComponentSizeFor to map tiny → 'small' (line ~402); add the tiny branch of overrides in buildAntTheme (line ~462). |
libs/ui/src/lib/settings/AccessibilityModal.tsx | Add a fourth Compactness radio <Radio value="tiny"> (after line 124) using a new i18n key helex.settings.access.tiny. |
libs/ui/src/lib/themes/themes.test.ts | Extend the density-aware buildAntTheme suite (line 64): assert tiny applies compactAlgorithm, sets the Table cell-padding + cellFontSizeSM overrides, and keeps token.fontSize === 14. |
| i18n (EN + ET + RU) | New key helex.settings.access.tiny in all three locales, with _meta.needsReview on ET/RU per the i18n process. |
Notes for the implementer (post-sign-off):
DENSITY_MAPSalready feeds both the antd token projection (buildAntTheme) and the CSS-var projection (buildHelexCssVars→--helex-gap-*/--helex-row-padding), andthemes.test.tsasserts the two can’t drift — so adding a map entry keeps both surfaces in sync for free.defaultdensity must remain byte-identical to today (the test at line 65 guards this);tinyis a pure addition.
5. Cheaper scoped alternative (no shared change)
If you’d rather not take a shared @helex/ui preset for this: apply antd size="small" plus a scoped cellFontSize/cellFontSizeSM override only on the specific TEDY concept-table components, via a local ConfigProvider theme={{ components: { Table: {...} } }} wrapper around those tables. This delivers the same denser-table win for the one place that needs it, avoids a new global density value and the modal/i18n surface area, and keeps @helex/ui untouched. Trade-off: it’s TEDY-local, so any other module wanting the same density later re-implements it (or we promote it to tiny then anyway).
6. What I’m asking for
Please sign off on one of:
- (A) Shared
tinydensity — the plan in §3–§4. I’ll implement it on a dedicated worktree/branch and open a PR for review; nothing merges without it. - (B) Scoped alternative — §5, no
@helex/uichange. - (C) Neither / discuss — if you see a reason to reshape the tokens or reject the gap.
Until you pick, I will not touch @helex/ui.
TEDY backend — validation/review brief
A self-contained task for an independent review agent. Read this end to end, then execute the
Validation objectives. Work read-only unless asked to fix; report findings ranked by severity with
file:line evidence. The branch is claude/terminology-editor-helex-migration-c79310; all TEDY backend
work lives under modules/tedy/backend/ and migration/tedy-migrate/.
1. Initial task (what was asked)
Migrate the terminology-editor backend from a reference terminology server into helex-tx, on the
Helex platform libraries. Hard requirements from the original brief:
- Create a backend under
modules/tedybased on an emr module template. - Add
run_frontend.sh tedy/run_backend.sh tedyupdate scripts. - Use helex-commons instead of the reference commons; no reference-product or vendor names
(“termx”/“kodality”) may remain in committed code, comments, commits, or PRs (real URLs only in a
gitignored
.env). - Use Forge (FHIR model lib) instead of the reference’s Zmei.
- Analyze the DB, rewrite changesets removing vendor references; every changeset must be
idempotent (
create if not exists, idempotent DML); new schema istedy; ship a separate jar that migrates the legacy DB →tedy, run manually before install; improve the model (a redundant table, an over-long table name). - (Phase 2) FHIR mapping between TEDY resources and FHIR + EMR FIS — out of scope for this review.
- Do not use Kefhir; recreate needed functionality in TEDY.
- Port all tests from the reference server. 9/10. Ensure endpoints work; note optimizations vs the reference.
- Be extensible for OAuth + user permissions.
Full plan: ~/.claude/plans/the-goal-is-migrate-spicy-shore.md.
2. Plan / decisions (locked with the user)
- Template: emr
modules/tx/backend(org.helex.tx) — the terminology-domain module — not genericmdm. - Build model: start standalone — the backend builds independently against published artifacts
org.helex.emr:commons-*(0.1.0-SNAPSHOT) andorg.helex.forge:*(0.20.0), no EMR checkout required; structured to also composite-build inside EMR later. - Package namespace:
org.helex.tx.tedy(emr’stxmodule is uniquelyorg.helex.tx). - Auth: TEDY-owned (a pluggable
SessionProviderchain +@Authorizedinterceptor) becausecore-backendis not published. ABAC ACL deferred untilcommons-tenantis published (a one-line emr change was made: it was added topublishableCoreLibrariesinemr/repo/build.gradle.kts, but the publish itself is a pending user action). Code Systems etc. are ACL-free for now (like emr’s globalmdm.practitioner). - DB idempotency: the
tedymodule changelog is idempotent (CREATE TABLE IF NOT EXISTS, guardedcreate_table_metadata,insert … on conflict) so a fresh install and a migrated legacy DB converge. It first includes the sharedcoreschema from the publishedcommons-db-core. - Data-model renames (agreed):
entity_property→code_system_property,entity_property_value→concept_property,defined_entity_property→defined_property,entity_version_code_system_version_membership→concept_version_membership,map_set*→concept_map*(Java packagemapset→conceptmap, modelsMapSet*→ConceptMap*; REST path stays/ts/map-sets).code_system.permissionsis dropped (ACL replaces it).code_system_entitykept (polymorphic supertype ofconcept+code_system_association). - Tests: two-stage (strengthen the reference suite first, then port to JUnit) — the reference-side strengthening (Stage A) and the full port (Stage B) are not yet done; TEDY ships its own JUnit Testcontainers ITs per slice instead.
- Jackson: commons-db is Jackson 2; Spring MVC (Boot 4) is Jackson 3 — loosely-structured fields are
Object/collections to cross that boundary.
3. Implemented part (what to review)
Commits on the branch (newest first):
| Commit | Content |
|---|---|
104cbf7 | migration extended to Value Sets + Spaces |
6a0c13a | run/contract-verification scripts |
44f51a2 | tedy-migrate standalone migration jar |
66989f5 | Spaces slice |
5f059e1 | Naming Systems + Defined Properties |
a184461 | Map Set slice (concept_map) |
3c9b3e5 | Value Set slice (compose + expansion) |
4e8c41b | concept associations |
3da59df | scaffold + auth core + Code System + concepts |
Backend (modules/tedy/backend, org.helex.tx.tedy)
- Standalone Gradle (JDK 25, Spring Boot 4.0.0);
build.gradle.kts,application.yml,application-dev.yml. - Auth core (
auth/):SessionProviderchain →SessionFilter→SessionStore;@Authorized+AuthorizationInterceptor;SecurityConfig(stateless, authz via the chain);GuestSessionProvidergrants*.*.*in dev (config-gated). Real OAuth/JWT provider is a documented follow-up. - Resources — each
model/ → repository/ (BaseRepository + forge-anvil) → service/ → api/: Code System (codesystem/, incl.concept/), Value Set (valueset/), Map Set (conceptmap/), Naming Systems (namingsystem/), Defined Properties (definedproperty/), Spaces (space/). - Changelog (
src/main/resources/tedy/db/changelog/): master includescore-dbthen the idempotenttedyDDL01…07. - REST:
/ts/code-systems(+/concepts, associations, versions,spaceIdfilter),/ts/value-sets(+/expand),/ts/map-sets(+/associations),/ts/naming-systems,/ts/defined-properties,/spaces(+/packages). - Optimization vs reference: Value Set compose
ruleSetand Map Setscopestored as one JSONB column instead of the reference’s normalized rule tables; expansion computed on demand.
Migration jar (migration/tedy-migrate)
- Standalone runnable jar.
TedyMigratorruns ordered idempotentINSERT … WHERE NOT EXISTSscripts (src/main/resources/migrate/*.sql) in one transaction, disabling the sys-column trigger to preservesys_*verbatim, applying every rename + thedefined_property_idbackfill + acore.seq_idfixup. Covers CS-family, associations, naming systems, concept maps, value sets, and spaces (sys.*→tedy.*).
Scripts (scripts/)
run_backend.sh tedy(Docker Postgres +devprofile Liquibase),run_frontend.sh tedy(VITE_TX_TARGET→ local backend, delegates todev.sh),verify-tedy.sh(curl smoke test),check-naming.sh(prohibited-term gate).
How it was verified (reproduce these)
- Backend ITs (Testcontainers Postgres, run Liquibase, hit HTTP):
CodeSystemControllerIT,ValueSetControllerIT,ConceptMapControllerIT,RegistryControllerIT,SpaceControllerIT—cd modules/tedy/backend && ./gradlew test(7 tests, all green). - Migration IT:
cd migration/tedy-migrate && ./gradlew test(2 tests: renames+backfill, idempotency). - Live smoke:
./scripts/run_backend.sh tedythen./scripts/verify-tedy.sh— all endpoints 200/204. - Naming gate:
./scripts/check-naming.sh— clean.
Known gaps / deferred (do NOT report as defects; confirm they’re correctly scoped out)
ABAC ACL wiring (pending commons-tenant publish); FHIR facade + FIS (Phase 2); the reference-suite test
port (Stage A/B); concept tree-search / cross-CS /ts/concepts / /entity-versions; Spaces GitHub/DevOps
sync + async diff; Map Set property-values / statistics / automap.
4. Validation objectives (the review task)
Assess correctness, faithfulness, and safety. Prioritize:
- Contract fidelity — do the controllers match the endpoint paths, params, and JSON shapes the
frontend clients expect? Cross-check
modules/tedy/backend/.../api/*againstlibs/tedy/src/lib/client/*.tsandlibs/tedy/src/lib/types/*.ts(paths,SearchResult={data,meta}, field names likemapSet/designationType/entityProperty, the/transactionbodies). - Persistence correctness — repositories: JSONB/
text[]/Forge-datatype column mappings (AnvilBeanProcessor/AnvilSqlBuilder), the client-supplied-text-id upsert, optimistic-lock/soft-delete handling, and thespaceIdjoin. Look for SQL injection (all use bound?), N+1 decoration, and Jackson 2/3 boundary bugs. - Renames & data model — confirm every agreed rename is applied consistently across DDL, models, repos,
and the migration; confirm
permissionsis dropped andcode_system_entityis retained as the supertype. - Migration safety — are all scripts truly idempotent (re-run = no change)? Are
sys_*preserved (trigger disabled)? Is thedefined_property_idbackfill correct and collision-free? Does the sequence fixup prevent future id collisions? Any source column referenced that wouldn’t exist in the real legacy schema? (The IT setup approximates it — flag mismatches.) - Auth extensibility — is the
SessionProviderchain genuinely pluggable, and is the guest*.*.*default safe (dev-gated, not on in prod)? Is@Authorizedenforced on every mutating endpoint? - Naming hygiene — independently grep committed sources for the prohibited terms.
- Idempotency of the module changelog — would a fresh install and a migrated DB converge? Any
create_table_metadata/index that isn’t guarded? - Build/verify reproducibility — do the ITs and the live smoke test actually pass on a clean checkout?
Deliver: a ranked findings list (severity, file:line, failure scenario), plus a short verdict on whether
the Phase-1 backend is faithful to the plan and safe to build on.
Heads-up: uncommitted TEDY Value Set “rule” WIP (2026-08-08)
For: whoever is working the TEDY.02 Value Set family.
While making two small TEDY fixes I found an untracked, uncommitted file:
modules/tedy/frontend/src/pages/value-set/rule/ValueSetRuleEditor.tsx
It exists in both places but is committed in neither:
- the emr working copy —
.claude/worktrees/tedy-main(branchfeature/tedy-bootstrap) - canonical helex-tx — it landed here as untracked after a
sync-emr.sh … pull
Why this matters
Because it’s untracked, it’s at risk of being silently lost by:
git clean -fd- a branch reset / checkout
sync-emr.sh … <push|pull>— the sync usesrsync -a --delete, so a sync in the other direction can remove files that aren’t present on the source side
Please do
Commit it to your Value Set branch (or git stash it) so it’s safe, then delete this note. If it’s dead scaffolding, remove the file instead.
Context (not related to this file)
The following were committed today and did not touch ValueSetRuleEditor.tsx:
f717ea1— fix(tedy): unify code-system summary card headingsa909070— docs: record the @helex/ui tiny-density sign-off proposald5719bb— chore(tedy): base html/body font reset
The @helex/ui tiny density + density-coverage sweep merged to emr main (#2343); after you rebase/sync, TEDY picks it up automatically.
TEDY — deferred tasks
Work intentionally stubbed or skipped for now, to be implemented later. Each item notes where it’s currently faked and what “done” looks like.
Auth / UMA (EMR shell integration)
TEDY runs on the EMR shell, whose @helex/state calls the EMR UMA endpoints
(/api/uma/*) on load. TEDY’s backend is terminology-server, which has no UMA
service, so these are faked by the dev-only devGuestAuthStub middleware in
modules/tedy/frontend/vite.config.ts (runs under vite only; never in a build).
- Real authentication — replace the dev guest stub (
GET /api/uma/userinfo,GET /api/uma/auth/mock-users) with real tx Keycloak (OIDC). Until then every privilege gate is open (privileges: ['*']) and the user is a fixedguest/devtenant. This is the gating item for a real deployment. - User preferences —
GET/PUT/DELETE /api/uma/preferencesis currently answered with an empty set ({ data: [] }) by the dev stub so@helex/state’s on-load hydration doesn’t 404; nothing is persisted (menu favourites, pinned items, search prefs, UI prefs all reset per session). Implement a real store.- Contract to match (EMR
UserPreferenceController,modules/uma/backend/.../UserPreferenceController.java):GET /api/uma/preferences→{ data: UserPreferenceEntry[] }, entry ={ tenantAlias: string | null, key: string, value: string }.PUT /api/uma/preferencesbody{ key, value, scope }(scope=global|tenant) →200.DELETE /api/uma/preferences?key=&scope=→200(idempotent soft-delete).
- Contract to match (EMR
Terminology data gaps (dev backend)
- Publisher
$lookup404 — the CS summary resolves the publisher display viaGET /api/tx/$lookup?system=…/CodeSystem/publisher&code=HELEX, which 404s on the dev backend (nopublisherCodeSystem / codeHELEX). Cosmetic (falls back to the raw code); provide the publisher CodeSystem or make the widget tolerate a missing lookup.
Concept presentation
- Server-side paged concepts (+ field selection) — the concept list scales
poorly because every concept is returned fully decorated (all entity versions,
each with its designations + property values) and there is no way to page or trim it
server-side. What’s already mitigated on the client and what’s still missing:
- Done (client): hierarchy roots are paged (
ROOT_PAGE = 100, Load more / Load all, inConceptPresentationTable.tsx) instead of a silentlimit: 200cap; children lazy-expand one level; off-screen property markup is deferred (ConceptCell.tsxuseInView); the cell reads only the current entity version. - Still missing (server): the payload per row stays huge. Measured on
lt-lab-klt-nomenclature(part-of, 7,056 roots) against lmb-api:limit=100= 1.22 s / 2.84 MB (~28 KB/root);limit=200= 1.81 s / 5.71 MB;limit=7056(Load all) = 44 s / 198 MB. rhk10 (is-a, ≈37k) is worse. - “Done” = a server that supports (a) real paging on roots/children/flat lists
with a stable total, and (b) field selection — return only the fields the
presentation needs (e.g. current-version designations of the requested types +
the
showInListproperties), not the full version history. Then drop the client-side Load-all path for large systems. Needs a terminology-server change (the/ts/code-systems/{id}/conceptsendpoint has noproperties=/field-limiting and always decorates).
- Done (client): hierarchy roots are paged (
TEDY specifications
Specifications for the TEDY module (the terminology editor of the reference-editor rewrite). Authored with the process in ../../spec-authoring.md and the EMR spec rules (/Users/igor/source/emr/docs/specifications/rules.md + _templates/).
Conventions
- Prefix:
TEDY(EMR’s terminology prefix isTRM; keep these template/frontmatter-compatible so they can move upstream toemr/docs/specifications/if adopted). - Feature-family IDs (MDM pattern): each resource is a family under
TEDY.NN:TEDY.NN— overview / common spec (tool-feature): owns the shared data model, value sets, FHIR mapping, privileges, common API surface, navigation. Child specs reference it and do NOT duplicate those.TEDY.NN.1— List (resource-list)TEDY.NN.2— Add / Create (resource-create)TEDY.NN.3— View / Management (resource-management)- further children (
.4+) for tools/sub-screens.
- Each child opens with a
## Feature groupingsection linking the root + siblings. - State: start
Proposed; advance only on approval. _scans/holds Step-1 live-scan notes/screenshots from dev-tx.example.
Index
TEDY.01 — Code System family:
| ID | Spec | Template | State |
|---|---|---|---|
| TEDY.01 | Code System — overview / common (data model, FHIR, privileges, API, navigation) | tool-feature | Proposed |
| TEDY.01.1 | Code System — List | resource-list | Proposed (built) |
| TEDY.01.2 | Add Code System | resource-create | Proposed (pending write flow) |
| TEDY.01.3 | Code System — View | resource-management | Proposed (built, read-only) |
| TEDY.01.4 | Code System — Concept Presentation (configurable table + editable detail) | tool-feature | Proposed (built) |
TEDY.02 — Value Set family:
| ID | Spec | Template | State |
|---|---|---|---|
| TEDY.02 | Value Set — overview / common (compose + expansion model, full REST surface, FHIR, privileges, API, navigation) | tool-feature | Proposed (built) |
| TEDY.02.1 | Value Set — List | resource-list | Proposed (built) |
| TEDY.02.2 | Add Value Set | resource-create | Proposed (built) |
| TEDY.02.3 | Value Set — View (Summary · Metadata · Concepts/expansion · Rules/compose + editor) | resource-management | Proposed (built) |
TEDY.03 — Map Set family:
| ID | Spec | Template | State |
|---|---|---|---|
| TEDY.03 | Map Set — overview / common (scope + association model, full REST surface, ConceptMap FHIR) | tool-feature | Proposed (built) |
| TEDY.03.1 | Map Set — List | resource-list | Proposed (built) |
| TEDY.03.2 | Add Map Set | resource-create | Proposed (built) |
| TEDY.03.3 | Map Set — View (Summary · Metadata · Associations grid + editor) | resource-management | Proposed (built) |
New-module families (research + first-slice implementation; open questions in TEDY-new-modules-open-questions.md):
| ID | Spec | State |
|---|---|---|
| TEDY.06 | Implementation Guides | Proposed (list built; deeper screens phased) |
| TEDY.07 | Spaces | Built (list · Metadata Core+GitHub · linked CS/VS/MS add-as-link · Comparison · Repository) |
| TEDY.08 | Servers (terminology/FHIR registry) | Proposed (built: list+filter+create, summary authoritative widgets, full metadata + check-connection, resources browser w/ viewer links, authoritative editor) |
| TEDY.09 | Ecosystems | Proposed (built: list + add/edit) |
| TEDY.10 | Defined Properties | Built (list + create/edit via ResourceList/ResourceForm) |
| TEDY.11 | Naming Systems | Built (list + lifecycle + create/edit with identifiers editor) |
| TEDY.12 | List bulk actions (CS/VS/MS + Space tabs) | Built (select → activate/retire/add-to-release/delete/unlink) |
| TEDY.13 | Releases | Built (list + TxResourceBar detail — Summary w/ Resources+Errors+Checks+Notes+Tasks · Metadata · Provenance · Comparison; add-resource autocomplete+version; server-sync phased) |
| TEDY.14 | Global Search | Built (Concepts · CS · VS · MS · SNOMED groups + space/publisher/CS filters) |
| TEDY.15 | Landing (home dashboard) | Built (Resources/Modeling/Management count cards → lists) |
| TEDY.16 | Unified resource metadata (CS/VS/CM/SD) | Proposed (comparison + unification plan; close CS dataset gap) |
TEDY.17 — File & Source Import family:
| ID | Spec | Template | State |
|---|---|---|---|
| TEDY.17 | File & Source Import — overview / common (analyze→process→job spine, transport, IA/dashboard, privileges, reuse machinery) | tool-feature | Proposed (built) |
| TEDY.17.1 | Code System file import (analyze + column mapping + options + saved/template/portable config) | tool-feature | Proposed (built) |
| TEDY.17.2 | Value Set file import (analyze + code/display mapping into a rule set) | tool-feature | Proposed (built) |
| TEDY.17.3 | Map Set file import (fixed CSV template + scope editor; no analyze) | tool-feature | Proposed (built) |
| TEDY.17.4 | External sources — WHO ICD-10 · Orphanet · LOINC | tool-feature | Proposed (built) |
Cross-cutting:
| ID | Spec | Template | State |
|---|---|---|---|
| TEDY.18 | Configuration & environment variables (VITE_/TX_/TEDY_/OAUTH_ surface, backward-compatible; migration script) | tool-feature | Built |
Next families: TEDY.04 Structure Definitions / TEDY.05 Structure Maps (built, spec pending), … (same overview + list/create/view shape). Build order follows the code — Code Systems first (spec ↔ code cross-check), then Value Sets, etc. Key ValueSet difference: members are derived by expanding the version’s compose ruleSet (or read from its snapshot), not owned like Code System concepts.
id: TEDY.01 template: tool-feature module: tedy state: Proposed traces-from: [] # TODO: link terminology-editor user stories source-refs:
- reference-editor/app/src/app/resources/code-system
- reference-editor/app/src/app/resources/_lib/code-system/model/code-system.ts
- terminology-server (/ts/code-systems)
- helex-tx/libs/tedy/src/lib/client/codeSystemClient.ts
- helex-tx/libs/tedy/src/lib/client/conceptClient.ts
- helex-tx/libs/tedy/src/lib/types/codeSystem.ts author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.01 – Code System (Common Spec)
Description
Common specification for Code System management in TEDY — browse, view, and author FHIR CodeSystems over terminology-server (/ts/code-systems). This root spec owns the shared vocabulary, data model, value sets, FHIR mapping, privileges, common API surface, and navigation so the child (screen) specs don’t duplicate them.
terminology-server owns the persistence + FHIR model; TEDY is the React client. Derived via
spec-authoring.md(Step-1 dev-tx.example scan, Step-2 reference-editor + terminology-server reconcile).
Child specs:
- TEDY.01.1 – Code System List
- TEDY.01.2 – Add Code System
- TEDY.01.3 – Code System View
- TEDY.01.4 – Code System Concept Presentation
Glossary Terms
| Term (canonical) | Meaning |
|---|---|
| Code System | A FHIR CodeSystem — a managed set of concepts (codes) with designations, versions, properties. |
| Concept | A single code within a Code System (its content). |
| Version | A CodeSystemVersion with a publication status (draft/active/retired) + release date. |
| Designation | A concept’s localized display/definition (designationType = display/definition, per language). |
| Publisher | CodeSystem.publisher — resolved via the publisher value set when it is a code, else free text. |
| Space | A reference-platform authoring workspace a resource belongs to. |
| Content mode | CodeSystem.content — complete / supplement / fragment / example / not-present. |
Business Feature Sitemap
flowchart LR
US["(TODO) US: manage code systems"] --> ROOT[TEDY.01]
ROOT --> LST[TEDY.01.1 List]
ROOT --> CRT[TEDY.01.2 Add]
ROOT --> VIEW[TEDY.01.3 View]
LST -->|Add code system| CRT
LST -->|row / ID| VIEW
CRT -->|save| VIEW
Data Model
Shared model for list / create / view. terminology-server owns the tables; TEDY consumes the JSON envelopes.
| Entity / Resource | the reference platform API | FHIR resource | Notes |
|---|---|---|---|
| CodeSystem | /ts/code-systems | CodeSystem | list + detail; lastVersion decorated |
| CodeSystemVersion | /ts/code-systems/{id}/versions | CodeSystem.version | status ∈ draft/active/retired |
| CodeSystemConcept | /ts/code-systems/{id}/concepts | CodeSystem.concept | content; display from designations |
| List envelope | n/a | n/a | { data: [...], meta: { total, offset } } |
| Single-resource envelope | n/a | n/a | resource root fields |
Shared CodeSystem fields
| Field | Type | Cardinality | Rule |
|---|---|---|---|
id | string | 1..1 | immutable identity (the code-system id) |
uri | string | 0..1 | canonical URL; unique |
name | string | 0..1 | machine name |
title | LocalizedName | 0..1 | multilingual display |
description | LocalizedName | 0..1 | |
publisher | string | 0..1 | code from publisher VS or free text |
content | code | 0..1 | codesystem-content-mode |
caseSensitive | code | 0..1 | cs / ci |
baseCodeSystem | string | 0..1 | for supplements |
versions / lastVersion | CodeSystemVersion[] | 0..* | version + status + releaseDate + conceptsTotal |
concepts | CodeSystemConcept[] | 0..* | code + versions[].designations[] |
Used value sets (resolved via @helex/tx)
| Field | ValueSet | Canonical URL | Binding |
|---|---|---|---|
publisher | publisher | https://tx.example/fhir/ValueSet/publisher | extensible |
version status | publication-status | http://hl7.org/fhir/ValueSet/publication-status | required |
content | codesystem-content-mode | http://hl7.org/fhir/ValueSet/codesystem-content-mode | required |
FHIR Mapping (Common)
| Internal field | FHIR R5 path | Mapping | Notes |
|---|---|---|---|
id | CodeSystem.id | direct | |
uri | CodeSystem.url | direct | |
name | CodeSystem.name | direct | |
title | CodeSystem.title | transform | LocalizedName → language-resolved title |
publisher | CodeSystem.publisher | direct | |
content | CodeSystem.content | direct | |
caseSensitive | CodeSystem.caseSensitive | transform | cs/ci → boolean |
lastVersion.version | CodeSystem.version | direct | |
concepts[] | CodeSystem.concept[] | transform | designations → concept.display + designation |
Common Rules
- Phase-0 TEDY is read-only (browse/view). Authoring (create/edit) writes via
POST /ts/code-systems/transaction(child TEDY.01.2 / future edit). - Concept display resolves from
versions[].designations[]wheredesignationType='display'(match language → preferred → first), fallback to the code. - Publisher display resolves via the
publishervalue set (@helex/tx), fallback to the raw value when free text. - Value-set-backed selects use
TxConceptSelect(url=…)(see spec-authoring.md; dev proxies/api/tx/$expand→the reference platform FHIR).
Shared Privileges
| Action | Privilege |
|---|---|
| View / browse | authenticated (dev: guest via the reference platform yupi) |
| Create / edit / delete / versions / concepts | CodeSystem.write (the reference platform {id}.CodeSystem.write) |
Common API Surface
| Method | Path | Privilege | Description |
|---|---|---|---|
| GET | /ts/code-systems | read | list (params: textContains, publisher, versionStatus, spaceId, content, lastVersionDecorated, lang) |
| GET | /ts/code-systems/{id} | read | load one (?decorate=true) |
| GET | /ts/code-systems/{id}/versions | read | versions |
| GET | /ts/code-systems/{id}/concepts | read | concepts (params: textContains, codeContains, displayLanguage) |
| POST | /ts/code-systems/transaction | write | create/update (authoring) |
| GET | /spaces | read | spaces (for the Space filter) |
| GET | /fhir/ValueSet/$expand?url=… | read | value-set expansion (publisher/status/content) |
All TEDY calls go through @helex/tedy’s txApi (/txapi → dev-tx.example /api).
Sitemap / Navigation
/tedy/code-systems— list (TEDY.01.1)/tedy/code-systems/new— create (TEDY.01.2, pending)/tedy/code-systems/{id}— view (TEDY.01.3)
(Standalone Router basename="/tedy"; menu routes are basename-relative — see spec-authoring/dev notes.)
Non-functional Requirements
| Requirement | Target |
|---|---|
| List load | < 2s; server-side pagination (20/page); no eager concept load |
| Detail + concepts load | < 2s; concepts paged (20/page) |
| Search debounce | ~400ms |
Business Tests
- List, view, and (future) create all operate over
/ts/code-systems*with the shared envelope. - Publisher/status/content render via their value sets; concept display via designations.
- Write actions require
CodeSystem.write; read is open on the guest dev server.
Traces
- traces-from: TODO user stories
- children: TEDY.01.1, TEDY.01.2, TEDY.01.3
id: TEDY.01.1 template: resource-list module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/code-system/containers/list/code-system-list.component.html
- reference-editor/app/src/app/resources/_lib/code-system/services/code-system-lib.service.ts
- helex-tx/modules/tedy/frontend/src/pages/CodeSystemList.tsx
- helex-tx/libs/tedy/src/lib/client/codeSystemClient.ts author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.01.1 – Code System List
Feature grouping
Child of TEDY.01 – Code System (Common Spec). Siblings: TEDY.01.2 Add, TEDY.01.3 View. Shared vocabulary, data model, value sets, privileges, and API surface are defined in the root — not repeated here.
Description
List screen for terminology Code Systems (shared ResourceList pattern): header title + search + primary action (Add), filter side panel, column configuration, master-detail preview. Step-1 scan: _scans/tedy.01-code-system-list.md.
Glossary Terms
Per TEDY.01 § Glossary.
Business Feature Sitemap
flowchart LR
ROOT[TEDY.01] --> L[TEDY.01.1 List]
L -->|row / ID| V[TEDY.01.3 View]
L -->|Add code system| C[TEDY.01.2 Add]
Data Model
Reuses TEDY.01 § Data Model. No new entities (read-only over GET /ts/code-systems).
Sitemap / Navigation
/tedy/code-systems — row/ID → /tedy/code-systems/{id} (TEDY.01.3). Federation host mounts under /tedy.
Attributes (columns)
Order mirrors reference-editor (ID · LAST VERSION · TITLE · PUBLISHER · STATUS · COUNT):
| Column | Source | locked | Notes |
|---|---|---|---|
| ID | cs.id → link | yes | to TEDY.01.3 |
| Version | cs.lastVersion.version | no | the reference platform “LAST VERSION” |
| Title | localize(cs.title) | cs.name | no | |
| Publisher | cs.publisher via publisher VS | no | useValueSetExpand/resolveLocalizedDisplay, fallback raw |
| Status | cs.lastVersion.status chip | no | draft/active/retired |
| Concepts | cs.lastVersion.conceptsTotal | no | the reference platform “COUNT” |
Filters
Mirror reference-editor (publisher · status · space) + content:
| Filter | Control | → the reference platform param |
|---|---|---|
| Publisher | TxConceptSelect(url=publisher VS) | publisher |
| Status | TxConceptSelect(url=publication-status VS) | versionStatus |
| Space | AppSelect (from GET /spaces) | spaceId |
| Content | AppSelect (content-mode codes) | content |
Search box → textContains.
Actions
- Add code system (Manual / Import FHIR) — gated on
CodeSystem.write. Pending in TEDY (write flow not built; see TEDY.01.2).
StoryBook Component Contract
ResourceList<CodeSystem> — title tedy.codeSystem.titlePlural; columns per Attributes; dataSource useCodeSystems; rowKey cs.id; search → textContains; filter (AppFilterBar) per Filters; pagination server-side (limit/offset, meta.total); detailView custom Code System preview + Open link, showMetadata:false; columnConfigurable.
API Endpoints
GET /ts/code-systems?limit&offset&textContains&publisher&versionStatus&spaceId&content&lastVersionDecorated&lang · GET /spaces · GET /fhir/ValueSet/$expand (filters). See TEDY.01 § Common API Surface.
Security / Permissions
View: authenticated (dev guest). Add + row actions: CodeSystem.write. See TEDY.01 § Shared Privileges.
Non-functional Requirements
Per TEDY.01 § NFRs.
Business Tests
- Columns render in the reference platform order (ID·Version·Title·Publisher·Status·Concepts).
- Search filters by
textContainsand resets to page 1. - Publisher/Status/Space/Content narrow via the mapped params; Clear resets.
- Row click opens the preview; Open/ID → TEDY.01.3.
- A backend failure surfaces an error (no silent redirect).
Source Code References
CodeSystemList.tsx, codeSystemClient.ts, useCodeSystems.ts, useSpaces.ts. reference-editor: code-system-list.component.*, code-system-lib.service.ts.
Differences (live vs source vs TEDY)
| Aspect | dev-tx.example | reference-editor | TEDY |
|---|---|---|---|
| Column order | ID·LAST VERSION·TITLE·PUBLISHER·STATUS·COUNT | same | matches |
| Publisher cell | publisher VS designation | localizedConceptName | VS-resolved (@helex/tx), fallback raw |
| Publisher/Status filter | value-set concept select | tw-value-set-concept-select | TxConceptSelect(url=…) |
| Version / Count | links | routerLinks | plain text — pending version/concepts sub-routes |
| Row expand | inline row | expanded-row component | not ported (concepts shown on TEDY.01.3) |
| Add actions | Manual / Import FHIR | gated CodeSystem.write | pending (TEDY.01.2) |
Traces
- parent: TEDY.01 · traces-to: TEDY.01.2, TEDY.01.3
id: TEDY.01.2 template: resource-create module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/resources/code-system/containers/edit/code-system-edit.component.ts
- reference-editor/app/src/app/resources/code-system/services/code-system.service.ts # POST /ts/code-systems/transaction
- helex-tx/modules/tedy/frontend/src/pages/code-system/CodeSystemCreate.tsx # create page
- helex-tx/modules/tedy/frontend/src/pages/code-system/CodeSystemFormFields.tsx # shared create/edit fields
- helex-tx/libs/tedy/src/lib/client/codeSystemClient.ts # saveCodeSystem author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.01.2 – Add Code System
Feature grouping
Child of TEDY.01 – Code System (Common Spec). Siblings: TEDY.01.1 List, TEDY.01.3 View.
Status in TEDY: Built (R3 write flow). The create page (
CodeSystemCreate) reuses the R3 metadata form (CodeSystemFormFields) increatemode over the samesaveCodeSystem→POST /ts/code-systems/transactionwrite path. Reached from the list’s Add code system action (code-systems/new); on success it navigates into the new record’s shell (code-systems/:id/summary). Content defaults tocompleteand case-sensitivity toci(both, plus atitle, are NOT NULL in terminology-server); required fields (ID, URI, title, content) are validated client-side. Verified end-to-end against dev-tx.example (guest write). Not yet covered: the Import FHIR entry point (owned by Exo) and version creation (VersionForm, R3-pending).
Description
Create a new Code System. reference-editor exposes two entry points from the list’s Add code system dropdown:
- Manual → the Code System edit form (this spec).
- Import FHIR → a FHIR resource import → owned by Exo (
tx-sync), out of scope here.
Rendered with the shared create form (ResourceForm/TedyResourceForm in create mode).
Glossary Terms
Per TEDY.01 § Glossary.
Business Feature Sitemap
flowchart LR
ROOT[TEDY.01] --> C[TEDY.01.2 Add]
L[TEDY.01.1 List] -->|Add code system → Manual| C
C -->|save| V[TEDY.01.3 View]
Data Model
Reuses TEDY.01 § Data Model.
Sitemap / Navigation
/tedy/code-systems/new → on save, navigate('/tedy/code-systems/{id}', {replace:true}) + land in view mode (TEDY.01.3), per the frontend conventions.
Attributes (create fields)
| Field | Widget | Required | Rule |
|---|---|---|---|
id | AppInput | yes | unique code-system id (immutable after create) |
uri | AppInput | yes | canonical URL; unique |
name | AppInput | no | machine name |
title | AppLocalizedName | no | multilingual |
description | AppLocalizedName | no | |
publisher | TxConceptSelect(url=publisher VS) | no | code or free text |
content | TxConceptSelect(url=content-mode VS) / AppSelect | no | complete/supplement/… |
caseSensitive | AppSelect | no | cs / ci |
baseCodeSystem | AppInput | no | when content=supplement |
Business Logic
- Validate required (
id,uri) + uniqueness before submit; guard double-submit (savingstate). - Save →
POST /ts/code-systems/transactionwith the CodeSystem payload (the reference platform create/update transaction). - On success: notify,
navigateto the new id (replace) and switch toview; on failure:showError. Ctrl+Alt+Enter= save,Ctrl+Alt+Esc= cancel (shortcut providers).
StoryBook Component Contract
TedyResourceForm (mode="create", wrapping ResourceForm) inside PageShortcutProvider; fields per Attributes using App*/Tx* widgets; AppButtonPrimary save + AppButtonCancel.
API Endpoints
POST /ts/code-systems/transaction (CodeSystem.write). See TEDY.01 § Common API Surface.
Security / Permissions
CodeSystem.write. See TEDY.01 § Shared Privileges.
Non-functional Requirements
Create response < 1.5s; field validation feedback < 250ms.
Business Tests
- Missing
id/uriblocks submit with a validation message. - Duplicate
id/uriis rejected (server) and surfaced. - Successful create lands on
/tedy/code-systems/{id}in view mode. - Cancel returns to the list without writing.
Source Code References
reference-editor: code-system-edit.component.ts, code-system.service.ts (transaction). TEDY target: @helex/tedy create client + a create mode on TedyResourceForm — to build.
Out of Scope
- Import FHIR (→ Exo). Concept authoring (→ TEDY.01.3 / concept editor).
Traces
- parent: TEDY.01 · traces-to: TEDY.01.3
id: TEDY.01.3 template: resource-management module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/code-system/containers/summary
- reference-editor/app/src/app/resources/code-system/containers/concepts
- helex-tx/modules/tedy/frontend/src/pages/CodeSystemDetail.tsx
- helex-tx/modules/tedy/frontend/src/components/CodeSystemConcepts.tsx
- helex-tx/libs/tedy/src/lib/client/conceptClient.ts
- helex-tx/libs/tedy/src/lib/utils/conceptDisplay.ts author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.01.3 – Code System View
Feature grouping
Child of TEDY.01 – Code System (Common Spec). Siblings: TEDY.01.1 List, TEDY.01.2 Add.
Description
View a single Code System through the TedyResourceShell (the ResourceContext equivalent — see ../../resource-context-gap.md): a header (icon + localized title + version-selector slot) + a tab strip, with each tab a routed page. R1 tabs (read-only): Summary · Metadata · Concepts. (Properties/Provenance/Checklist follow later phases.)
Shared Page Component
TedyResourceShell (@helex/tedy) = EntityHeader + PageTabs (merged) + routed <Outlet>. CodeSystemShell loads the CodeSystem (?decorate=true) once and shares it via Outlet context; load errors surface via showError + an AppResult error state (no silent redirect). Tabs:
- Summary (
CodeSystemSummary) =CodeSystemInfoWidget+CodeSystemVersionsWidget+ concepts preview. - Metadata (
CodeSystemMetadata) = full field view + edit (R3):ResourceForm(hideHeader) +useDataController; Save →saveCodeSystem(POST /ts/code-systems/transaction, metadata only) → invalidate + back to view. PublisherTxConceptSelect, identifiersTxIdentifier. Create (from the list’s Add) reuses this form + client — TEDY.01.2. - Concepts (
CodeSystemConceptsTab) = theCodeSystemConceptstable.
Glossary Terms
Per TEDY.01 § Glossary.
Business Feature Sitemap
flowchart LR
ROOT[TEDY.01] --> V[TEDY.01.3 View]
L[TEDY.01.1 List] -->|row / ID| V
C[TEDY.01.2 Add] -->|save| V
Data Model
Reuses TEDY.01 § Data Model (CodeSystem + versions + concepts/designations).
Sitemap / Navigation
Nested routes under the shell:
/tedy/code-systems/{id} → (index) → /summary · /tedy/code-systems/{id}/summary · /metadata · /concepts. Tab clicks navigate between them; Back → list. (Properties/Provenance/Checklist tabs added later.)
Sections
| Section | Content | Widget |
|---|---|---|
| General | id, uri, name, publisher (VS-resolved), content, caseSensitive, description | FieldItem grid; publisher via TxConceptDisplay |
| Versions | version + status chip + release date | FieldItem list |
| Concepts | Code · Display · Status, paged + search | CodeSystemConcepts (AppTable); display via conceptDisplay (designations) |
Actions
- Back to list.
- Edit / Delete / new version / duplicate / create-supplement —
CodeSystem.write. Pending in TEDY (read-only Phase-0).
StoryBook Component Contract
TedyResourceForm (mode="view", icon, title, loading, onBack) with SectionHeading + FieldItem sections; CodeSystemConcepts below (AppTable, AppInput search, server pagination). Error → AppResult status="error".
API Endpoints
GET /ts/code-systems/{id}?decorate=true · GET /ts/code-systems/{id}/concepts?limit&offset&textContains&displayLanguage · GET /fhir/CodeSystem/$lookup (publisher display). See TEDY.01 § Common API Surface.
Security / Permissions
View: authenticated (dev guest). Edit actions: CodeSystem.write. See TEDY.01 § Shared Privileges.
Non-functional Requirements
Detail + concepts load < 2s; concepts paged (20/page); large systems (e.g. atc ≈ 6.9k) must not eager-load.
Business Tests
- View shows General, Versions, and a paged Concepts table for the id.
- Concept display resolves from designations in the active language.
- Concept search filters via
textContains. - A failed load shows an error state (not a silent redirect).
Source Code References
TEDY: libs/tedy/.../components/TedyResourceShell.tsx; module pages/code-system/{CodeSystemShell,CodeSystemSummary,CodeSystemMetadata,CodeSystemConceptsTab}.tsx + widgets/{CodeSystemInfoWidget,CodeSystemVersionsWidget}.tsx; components/CodeSystemConcepts.tsx; conceptClient.ts, conceptDisplay.ts. reference-editor: resources/resource/components/resource-context.component.* + code-system summary/concepts containers.
Evolution — ResourceContext shell
This Phase-0 view (flat TedyResourceForm) is a fraction of reference-editor’s screen, which is a tabbed ResourceContext dashboard (Summary·Metadata·Concepts·Properties·Provenance·Checklist) of widget cards. The plan to close that gap — TedyResourceShell + summary/versions/concepts/tasks/related-artifacts/value-sets widgets, phased R1–R5 — is in ../../resource-context-gap.md. When R1 lands, this spec becomes the Summary tab of the shell.
Out of Scope
- Write/edit, version lifecycle, concept authoring, properties/provenances/checklists tabs (future specs; see the gap doc).
Traces
- parent: TEDY.01
id: TEDY.01.4 template: tool-feature module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/code-system/containers/concepts
- terminology-server/ts/code-system (concept + entity-version transaction)
- helex-tx/modules/tedy/frontend/src/pages/code-system/CodeSystemConceptsTab.tsx
- helex-tx/modules/tedy/frontend/src/pages/code-system/concepts/presentation.ts
- helex-tx/modules/tedy/frontend/src/pages/code-system/concepts/ConceptPresentationTable.tsx
- helex-tx/modules/tedy/frontend/src/pages/code-system/concepts/ConceptCell.tsx
- helex-tx/modules/tedy/frontend/src/pages/code-system/concepts/ConceptColumnBuilder.tsx
- helex-tx/modules/tedy/frontend/src/pages/code-system/concepts/ConceptEditForm.tsx
- helex-tx/libs/tedy/src/lib/client/conceptClient.ts
- helex-tx/libs/tedy/src/lib/hooks/useConcepts.ts
- helex-tx/libs/tedy/src/lib/utils/conceptDisplay.ts author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.01.4 – Code System Concept Presentation
Feature grouping
Child of TEDY.01 – Code System (Common Spec). Refines the Concepts tab of TEDY.01.3 – Code System View. Siblings: TEDY.01.1 List, TEDY.01.2 Add.
Description
The Concepts tab renders a Code System’s concepts through a configurable,
definition-driven presentation instead of a fixed code / display / status table.
A Code System carries far more per concept than three columns can show —
designations in many languages and typed property values (some resolved
against other code systems or value sets) — and large systems are hierarchical
(e.g. an ICD-10 edition with tens of thousands of concepts under an is-a tree).
The user composes which fields appear, how they are grouped into columns, and how designations / properties are laid out, using a two-column drag builder. The layout is derived by default from the Code System definition and persisted per Code System. The presentation table itself stays read-only (“light immutable”); editing a concept happens in the selected row’s detail form, which saves the whole concept back through the entity-version transaction.
┌ Concepts ───────────────────────── [ Search ] [ ⚙ Configure ] [ ⛃ ] ┐ ┌ Concept ───────────┐
│ Code │ Display / Designations │ Properties │ │ allergy [active] │
│ allergy │ Allergy │ valueset: …/allergy │ │ Designations +Add│
│ │ en: Allergy │ ui-component: select │ │ en Allergy ★ │
│ │ et: Allergia │ usage: clinical │ │ et Allergia ☆ │
│ │ ru: Аллергия │ careplan-printout: true │ │ Properties +Add ▾ │
│ diagnosis │ Diagnosis │ conceptOrder: 10 │ │ valueset […] │
│ │ en: Diagnosis │ codesystem: …/rhk10 │ │ … │
│ │ … │ … │ │ [ Save ] │
└──────────────────────────────────────────────────────────────────────┘ └────────────────────┘
Glossary Terms
Per TEDY.01 § Glossary, plus:
| Term | Meaning |
|---|---|
| Presentation | The per-Code-System layout config: mode, editable flag, and columns. |
| Column | A first-level group in the presentation; renders one table cell per concept. |
| Element | A second-level item inside a column: code, display, status, designations, properties, or br (line break). |
| Designation block | A single element that renders a concept’s designations, optionally filtered to chosen languages. |
| Property block | A single element that renders a concept’s property values, optionally filtered to chosen properties. |
| Defined property | A CodeSystem.property entry, carrying kind (property | designation), type, showInList, and an optional Coding rule. |
Business Feature Sitemap
flowchart LR
ROOT[TEDY.01] --> VIEW[TEDY.01.3 View]
VIEW --> CT[TEDY.01.4 Concept Presentation]
CT --> TBL[Read-only table\nplain / hierarchical]
CT --> CFG[Configure builder\ndrag columns]
CT --> EDIT[Editable detail\ntransaction save]
The presentation config model
Two levels only: columns (level 1) → elements (level 2). code is pinned as
the first element of the first column and cannot be moved or removed.
type ConceptElement =
| { kind: 'code' } // pinned first; not movable/removable
| { kind: 'display'; bold?: boolean }
| { kind: 'status' }
| { kind: 'designations'; languages?: string[] } // undefined/empty ⇒ all languages
| { kind: 'properties'; names?: string[] } // undefined/empty ⇒ all showInList
| { kind: 'br' }; // explicit line break
interface ConceptColumn {
id: string;
title?: string; // non-localized header override (legacy / fallback)
titles?: Record<string, string>; // localized header per language code, e.g. { en: 'Term', et: 'Termin' }
elements: ConceptElement[];
}
interface ConceptPresentation {
mode: 'plain' | 'hierarchical';
editable: boolean;
columns: ConceptColumn[];
}
designationsandpropertiesare single whole-block elements — the user places the block, then chooses which languages / which properties it includes. Individual designations are never split across columns. A block may share a column with other elements (the default groupsdisplay+designations).- Singleton elements (
code,display,status,designations,properties) may appear at most once across the whole presentation;brmay repeat. - Localized column header — a column may carry a per-language header via
titles(keyed by language code, from the CS’s supported languages). When absent, the header is auto-derived from the column’s elements. Resolution for a display languageL:titles[L]→title(legacy) → any settitlesvalue → the auto-derived label. This is a presentation preference only (client-side; no server field).
Default presentation (derived from the CS definition)
mode=hierarchicalwhenCodeSystem.hierarchyMeaningis set, elseplain.editable=false.columns=[ [code], [display(bold), designations], [properties] ], where thedesignationsblock defaults to all languages and thepropertiesblock defaults to properties withshowInList. A line break is rendered automatically before the designations block (it is not the first text in its column).
Read-only cell rendering rules (per column, per concept)
Each element renders in order into a single cell:
code→concept.code.display→conceptDisplay(concept, language)(bold whenbold).status→ status chip (the concept’s current entity-version status).designations→ the concept’s designations, filtered tolanguagesif set (else all), preferred-language first, each shown aslang + ": " + value, and preceded by a line break unless it is the first text already in the column.properties→ each included property (namesif set, elseshowInList) aslabel: value, one per line;Codingvalues resolve to a display via the propertyrule(value set / code systems); each preceded by a line break unless first in the column.br→ a line break.
Configuration builder
Opened from a Configure button on the concepts toolbar (an AppDrawer, size 880).
Built with @dnd-kit (module-local dependency) as a two-container sortable.
- Left — palette + settings:
modeselect (plain | hierarchical);editablecheckbox; a⋯menu with Clear (empty all but the pinned code column) and Reset to defaults (recompute from the CS definition); and the source elements not yet placed (display,status,designations,properties). - Right — presentation builder: ordered columns, each a droppable group of
sortable elements, with an Add line break affordance and an Add column
button.
displaycarries a Bold toggle;designationsandpropertiescarry an inline picker (languages / property names, “All” when none chosen).codeis rendered pinned in the first column and is not draggable or removable. - Drag palette → column adds; drag element → palette removes; drag within/between columns
reorders (never before the pinned
code). - Rename column — each column header has a rename (✎) control opening a popover with
one input per supported language (seeded from the column’s
titles); the control is highlighted when a custom header is set. Blank inputs fall back to the auto-derived label. - Apply persists the presentation (including per-column
titles) and updates the live table; Cancel discards.
Persistence
Per Code System, in browser localStorage under key
helex.tedy.conceptPresentation.{codeSystemId}. On load the saved config is used;
absent (or on Reset) the default is recomputed from the CS definition. No server-side
presentation store exists.
Editable detail + save
When editable is on, selecting a row opens the editable concept detail form
(otherwise the read-only view is shown):
- Designations — rows of language select · type select · value · preferred star · delete; Add appends a row.
- Property values — one row per value, typed by the defined property:
boolean→ checkbox;integer/decimal→ number input;Coding→TxConceptSelectscoped by the propertyrule.valueSet(falling back to code + code-system inputs); otherwise a text input. Add property appends from the CS’s defined value-properties. - Save builds
{ concept, entityVersion }with the complete designation and property-value lists and POSTs the concept transaction (full replace of the current entity version — terminology-server has no JSON-patch). Version-scoped when a version route is active, else resource-level. The concepts query is invalidated on success.
Data Model
Reuses TEDY.01 § Data Model. Fields consumed here:
CodeSystem.hierarchyMeaning— association type driving the tree (e.g.is-a).CodeSystem.property[](defined properties) —name,type,kind(property|designation),showInList, andrule({ valueSet?, codeSystems? }) for Coding resolution.CodeSystemVersion.supportedLanguages/preferredLanguage— the language set and preferred order.concept → versions[] → { designations[], propertyValues[], associations[] }:Designation:name(value),language,preferred,status,designationType.EntityPropertyValue:value,entityProperty(name),entityPropertyId,entityPropertyType.concept.leaf/concept.childCountgate the tree expander.
API Endpoints
All under /ts/code-systems/{id} (see TEDY.01 § API).
| Purpose | Call |
|---|---|
| List (flat, paged) | GET …/concepts?textContains&codeSystemEntityStatus&codeSystemVersion&displayLanguage&limit&offset&sort |
| Roots (tree) | GET …/concepts?associationRoot={hierarchyMeaning}&sort=code&limit&offset |
| Children (one level, lazy) | `GET …/concepts?associationSource={hierarchyMeaning} |
| Filtered tree | GET …/concepts/tree-search?associationType={hierarchyMeaning}&textContains=… → flat items with parentCode + matched |
| Save (transaction) | POST …/[versions/{version}/]concepts/transaction body { concept, entityVersion } |
| Delete | DELETE …/concepts/{code} |
Envelope: { data, meta }; errors per the TEDY.01 error contract.
Sitemap / Navigation
No new routes. Enhances the existing Concepts tab of TEDY.01.3:
/tedy/code-systems/{id}/concepts (resource-level) and the version-scoped concepts
route when active. The Configure drawer and detail panel are in-page surfaces.
Security / Permissions
- Read: browse the presentation with
CodeSystemread access. - Edit (the
editabletoggle + Save): requires*.CodeSystem.write; the Save action is gated by the same privilege as TEDY.01.2 / the code-system edit flow. - Presentation config is a client-only preference — no permission implications.
Non-Functional Requirements
- Hierarchical mode loads children lazily, one level per expand, so a 30k+ concept system opens without fetching the whole tree.
- The table is never inline-editable; editing is isolated to the detail form to keep large lists responsive.
- i18n: all builder / edit labels in en / et / ru (
tedy.concept.presentation.*,tedy.concept.edit.*); designation language codes render verbatim.
Business Tests
- Default layout — a plain CS opens with columns
code·display(bold) + designations·properties(showInList); designations readlang: value, preferred-language first. - Hierarchical — a CS with
hierarchyMeaningopens as a tree; expanding a node lazily loads its children; leaves show no expander. - Configure round-trip — add / remove / reorder columns and elements, toggle bold, pick designation languages and properties, Apply → the table updates and the config survives reload; Reset restores the derived default; Clear leaves only the pinned code.
- Code pinned —
codecannot be dragged, removed, or displaced from the first cell. - Editable detail — with
editableon, selecting a row shows the edit form; a boolean property renders a checkbox, a Coding property a value-set select; Save posts the transaction and the row reflects the change after refresh. - Localized column header — rename a column with a title per language, Apply → the header shows the title for the active display language and survives reload; switching the display language shows that language’s title (or the auto-derived label if none set).
Source Code References
- Presentation model + persistence:
presentation.ts. - Read-only table (plain + hierarchical):
ConceptPresentationTable.tsx,ConceptCell.tsx. - Builder:
ConceptColumnBuilder.tsx(@dnd-kit). - Editable detail:
ConceptEditForm.tsx. - Tab wiring:
CodeSystemConceptsTab.tsx. - Data layer:
libs/tedyconceptClient.ts(searchConcepts,searchConceptRoots,searchConceptChildren,searchConceptTree,saveConcept,deleteConcept) +useConcepts.ts(useConcepts,useConceptRoots,useSaveConcept).
Differences (live vs source)
- The reference editor’s concept view is a fixed code / display / status table with a
separate edit dialog. TEDY generalises the read layout into a definition-driven,
user-composed presentation while keeping the same terminology-server contracts
(concept list params, hierarchy via
hierarchyMeaning, entity-version transaction). - “Patch” in the original requirement maps to the entity-version transaction (full replace); terminology-server exposes no JSON-patch for concepts.
- Presentation config is a TEDY-only client preference; the reference product has no equivalent per-CS layout store.
Traces
traces-from is empty pending a user story (EMR rules require a story before a solution
spec). Gap: create a thin “configure how a Code System’s concepts are presented” story
and link it here before promotion to Accepted.
id: TEDY.02 template: tool-feature module: tedy state: Proposed traces-from: [] # TODO: link terminology-editor user stories source-refs:
- reference-editor/app/src/app/resources/value-set
- reference-editor/app/src/app/resources/_lib/value-set/model/value-set.ts
- reference-editor/app/src/app/resources/_lib/value-set/model/value-set-version.ts
- reference-editor/app/src/app/resources/_lib/value-set/model/value-set-version-rule.ts
- reference-editor/app/src/app/resources/_lib/value-set/model/value-set-version-rule-set.ts
- reference-editor/app/src/app/resources/_lib/value-set/model/value-set-version-concept.ts
- reference-editor/app/src/app/resources/_lib/value-set/services/value-set-lib.service.ts # GET /ts/value-sets*
- reference-editor/app/src/app/resources/value-set/services/value-set.service.ts # write /ts/value-sets*
- reference-editor/app/src/app/fhir/_lib/value-set/services/fhir-value-set-lib.service.ts # /fhir/ValueSet*
- terminology-server (/ts/value-sets, /ts/value-set-versions, /fhir/ValueSet, /file-importer/value-set) author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.02 – Value Set (Common Spec)
Description
Common specification for Value Set management in TEDY — browse, view, and author FHIR ValueSets over terminology-server (/ts/value-sets). Unlike a Code System (which owns its concepts), a Value Set selects concepts from one or more code systems (or other value sets) through compose rules; those rules are expanded into the concrete member list (and materialized as a snapshot). This root spec owns the shared vocabulary, data model, compose/expansion model, FHIR mapping, privileges, the full API surface, and navigation so the child (screen) specs don’t duplicate them.
terminology-server owns persistence + the FHIR + expansion engine; TEDY is the React client. Derived via
spec-authoring.md(Step-1 dev-tx.example scan, Step-2 reference-editor + terminology-server reconcile). The complete REST surface below was read from reference-editor’s value-set services and confirmed live on dev-tx.example (guestyupi).
Child specs:
Glossary Terms
| Term (canonical) | Meaning |
|---|---|
| Value Set | A FHIR ValueSet — a curated selection of concepts drawn from code systems / other value sets. |
| Version | A ValueSetVersion with a publication status (draft/active/retired) + release date; carries the rule set. |
| Rule set | ValueSetVersion.ruleSet — the container of compose rules for that version (lockedDate, inactive, rules[]). |
| Compose rule | ValueSetVersionRule — one include/exclude entry referencing a code system (or another value set), optionally narrowed by explicit concepts, filters, and properties. |
| Filter | ValueSetRuleFilter — {property, operator, value} on a rule (e.g. is-a, descendent-of); operators per FHIR filter-operator. |
| Expansion | The concrete member list produced by evaluating the rule set (POST /ts/value-sets/expand). |
| Snapshot | ValueSetSnapshot — a stored, materialized expansion for a version (conceptsTotal, expansion[]). |
| Member (concept) | ValueSetVersionConcept — an expanded member: concept (code + system) + display + additionalDesignations + orderNumber + active. |
| Designation | A member’s localized display; display is the chosen one, additionalDesignations the rest. |
| Publisher | ValueSet.publisher — resolved via the publisher value set when a code, else free text. |
Business Feature Sitemap
flowchart LR
US["(TODO) US: manage value sets"] --> ROOT[TEDY.02]
ROOT --> LST[TEDY.02.1 List]
ROOT --> CRT[TEDY.02.2 Add]
ROOT --> VIEW[TEDY.02.3 View]
LST -->|Add value set| CRT
LST -->|row / ID| VIEW
CRT -->|save| VIEW
VIEW --> RULES[Rules / compose]
VIEW --> EXP[Concepts / expansion]
RULES -->|expand| EXP
Data Model
Shared model for list / create / view. terminology-server owns the tables; TEDY consumes the JSON envelopes.
| Entity / Resource | the reference platform API | FHIR | Notes |
|---|---|---|---|
| ValueSet | /ts/value-sets | ValueSet | list + detail; lastVersion / versions decorated |
| ValueSetVersion | /ts/value-sets/{id}/versions | ValueSet.version | status ∈ draft/active/retired; carries ruleSet |
| ValueSetVersionRuleSet | (embedded in version) | ValueSet.compose | {id, lockedDate, inactive, rules[]} |
| ValueSetVersionRule | (embedded in ruleSet) | ValueSet.compose.include/exclude | type = include/exclude |
| ValueSetRuleFilter | (embedded in rule) | ValueSet.compose.*.filter | {property, operator, value} |
| ValueSetVersionConcept | expand result / snapshot | ValueSet.compose.*.concept / expansion.contains | expanded member |
| ValueSetSnapshot | version.snapshot | ValueSet.expansion | materialized {conceptsTotal, expansion[]} |
| List envelope | n/a | n/a | { data: [...], meta: { total, offset } } |
Shared ValueSet fields
| Field | Type | Card. | Rule |
|---|---|---|---|
id | string | 1..1 | identity; renamed only via change-id |
uri | string | 0..1 | canonical URL; unique |
name | string | 0..1 | machine name |
title | LocalizedName | 0..1 | multilingual display |
description / purpose | LocalizedName | 0..1 | |
publisher | string | 0..1 | code from publisher VS or free text |
narrative | string | 0..1 | HTML narrative (FHIR text) |
experimental | boolean | 0..1 | |
sourceReference | string | 0..1 | |
otherTitle | {name, preferred}[] | 0..* | aliases |
topic | {text, tags[]} | 0..1 | |
useContext | {type, value}[] | 0..* | |
identifiers | Identifier[] | 0..* | |
contacts | ContactDetail[] | 0..* | name + telecoms |
configurationAttributes | any[] | 0..* | |
copyright | {holder, jurisdiction, statement} | 0..1 | |
permissions | {admin, editor, viewer, endorser} | 0..1 | |
settings | {reviewRequired, approvalRequired} | 0..1 | |
versions / lastVersion | ValueSetVersion[] | 0..* | version + status + releaseDate + ruleSet + snapshot |
ValueSetVersion fields
| Field | Type | Rule |
|---|---|---|
id | number | version key (with version) for PUT vs POST |
version | string | semver-ish; unique per value set |
status | code | draft / active / retired (lifecycle endpoints) |
releaseDate / expirationDate | date (YYYY-MM-DD) | |
preferredLanguage | string | |
supportedLanguages | string[] | |
description | LocalizedName | |
algorithm | string | version algorithm (e.g. semver) |
ruleSet | ValueSetVersionRuleSet | the compose rules |
snapshot | ValueSetSnapshot | stored expansion (conceptsTotal, expansion[]) |
identifiers | Identifier[] |
Compose model (ValueSet-specific)
ValueSetVersion.ruleSet # ValueSetVersionRuleSet {id, lockedDate, inactive}
└── rules[] # ValueSetVersionRule
├── type # 'include' | 'exclude'
├── codeSystem # source code system id (rule targets a CS…)
├── codeSystemVersion # CodeSystemVersionReference {id, version}
├── valueSet # …or another value set…
├── valueSetVersion # ValueSetVersionReference
├── concepts[] # explicit ValueSetVersionConcept members
├── filters[] # ValueSetRuleFilter {property, operator, value}
└── properties[] # property codes to carry into members
ValueSetVersionConcept (a member): { id, concept: CodeSystemConcept, display: Designation, additionalDesignations: Designation[], orderNumber, active }.
Used value sets (resolved via @helex/tx)
| Field | ValueSet | Canonical URL | Binding |
|---|---|---|---|
publisher | publisher | https://tx.example/fhir/ValueSet/publisher | extensible |
version status | publication-status | http://hl7.org/fhir/ValueSet/publication-status | required |
| supported/preferred language | languages | http://hl7.org/fhir/ValueSet/languages | required (the reference platform has no all-languages) |
rule filter operator | filter-operator | http://hl7.org/fhir/ValueSet/filter-operator | required |
FHIR Mapping (Common)
| Internal field | FHIR R5 path | Mapping |
|---|---|---|
id | ValueSet.id | direct |
uri | ValueSet.url | direct |
name | ValueSet.name | direct |
title | ValueSet.title | transform (LocalizedName → language-resolved) |
publisher | ValueSet.publisher | direct |
purpose | ValueSet.purpose | transform |
narrative | ValueSet.text | direct (HTML) |
version ruleSet.rules[type=include] | ValueSet.compose.include[] | transform |
version ruleSet.rules[type=exclude] | ValueSet.compose.exclude[] | transform |
rule filters[] | compose.*.filter[] | {property, op, value} |
version snapshot.expansion[] | ValueSet.expansion.contains[] | transform |
Common Rules
- A value set’s members are derived by expanding the version’s
ruleSet; never stored on the ValueSet root. Read the members via expansion (POST /ts/value-sets/expand) or the versionsnapshot. - Snapshot vs live: prefer the stored
snapshot.expansionwhen present (fast); fall back to a liveexpandfor drafts / previews. Rule editing previews useexpand-rule(a single unsaved rule). - Version lifecycle mirrors Code System: draft → active → retire, via dedicated endpoints (empty body,
204); editing an active version’s metadata is frozen server-side (the reference platform rule). - Publisher / status / language / filter-operator selects use
TxConceptSelect(url=…)(dev proxies/api/tx/$expand→ the reference platform FHIR). - Concept/member display resolves from
display(a Designation) → language match → the member code.
Shared Privileges
| Action | Privilege |
|---|---|
| View / browse | authenticated (dev: guest via the reference platform yupi) |
| Create / edit / delete / versions / rules | ValueSet.write (the reference platform {id}.ValueSet.write) |
Common API Surface
Base /ts/value-sets unless noted. All TEDY calls go through @helex/tedy’s txApi (/txapi → dev-tx.example /api).
ValueSet CRUD
| Method | Path | Priv | Description |
|---|---|---|---|
| GET | /ts/value-sets | read | list — params below |
| GET | /ts/value-sets/{id}?decorate={bool} | read | load one (decorate embeds versions + ruleSet + snapshot) |
| POST | /ts/value-sets/transaction | write | create/update — body ValueSetTransactionRequest {valueSet, version?, identifiers?} |
| POST | /ts/value-sets/{id}/change-id | write | rename id — body {id: newId} |
| DELETE | /ts/value-sets/{id} | write | delete value set |
| GET | /ts/value-sets/{id}/provenances?version= | read | provenance history |
List params (ValueSetSearchParams): id, ids, idContains, uri, uriContains, name, nameContains, description, descriptionContains, text, textContains, versionId, versionVersion, versionStatus, versionSource, decorated, lastVersionDecorated, lang, codeSystem, conceptCode, spaceId, packageId, packageVersionId, + limit/offset/sort.
Versions + lifecycle
| Method | Path | Priv | Description |
|---|---|---|---|
| GET | /ts/value-sets/{id}/versions | read | version list |
| GET | /ts/value-sets/{id}/versions/{version} | read | load one version (with ruleSet + snapshot) |
| GET | /ts/value-set-versions/{id} | read | load a version by numeric id |
| POST | /ts/value-sets/{id}/versions | write | create version (body ValueSetVersion) |
| PUT | /ts/value-sets/{id}/versions/{version} | write | update version |
| POST | /ts/value-sets/{id}/versions/{version}/draft | write | → draft (empty body, 204) |
| POST | /ts/value-sets/{id}/versions/{version}/activate | write | → active |
| POST | /ts/value-sets/{id}/versions/{version}/retire | write | → retired |
| POST | /ts/value-sets/{id}/versions/{version}/duplicate | write | duplicate — body {valueSet, version} |
| DELETE | /ts/value-sets/{id}/versions/{version} | write | delete version |
Compose rules / rule-sets
| Method | Path | Priv | Description |
|---|---|---|---|
| POST | /ts/value-sets/{id}/versions/{v}/rules | write | add a compose rule (ValueSetVersionRule) |
| PUT | /ts/value-sets/{id}/versions/{v}/rules/{ruleId} | write | update a rule |
| DELETE | /ts/value-sets/{id}/versions/{v}/rules/{ruleId} | write | delete a rule |
| PUT | /ts/value-sets/{id}/versions/{v}/rule-sets/{ruleSetId} | write | update the rule set (e.g. inactive) |
Expansion
| Method | Path | Priv | Description |
|---|---|---|---|
| POST | /ts/value-sets/expand | read | expand a saved version — body {valueSet, valueSetVersion} → ValueSetVersionConcept[] |
| POST | /ts/value-sets/expand-async | read | async expand → JobLogResponse (poll) |
| POST | /ts/value-sets/expand-rule | read | preview one unsaved rule — body {valueSet, valueSetVersion, inactiveConcepts?, rule} → ValueSetVersionConcept[] |
| GET | /ts/value-sets/{id}/versions/{v}/expansion-export?format={csv|xlsx|…} | read | start expansion export (→ processId) |
| GET | /ts/value-sets/expansion-export-{format}/result/{processId} | read | download export result |
FHIR façade (read/validate/sync)
| Method | Path | Description |
|---|---|---|
| GET | /fhir/ValueSet/{id}[|{version}] | read FHIR ValueSet |
| GET | /fhir/ValueSet | FHIR search |
| GET | /fhir/ValueSet/{id}/$expand | expand by id |
| GET | /fhir/ValueSet/$expand?url=… | expand by canonical (used by @helex/tx selects) |
| GET | /fhir/ValueSet/$validate-code | validate a code against the VS |
| POST | /fhir/ValueSet/$sync | sync (body Parameters) |
Import
| Method | Path | Description |
|---|---|---|
| POST | /file-importer/value-set/process | file import (multipart) → JobLogResponse |
Sitemap / Navigation
/tedy/value-sets— list (TEDY.02.1)/tedy/value-sets/new— create (TEDY.02.2)/tedy/value-sets/{id}— view shell (TEDY.02.3): Summary · Metadata · Concepts (expansion) · Rules/tedy/value-sets/{id}/versions/new·/versions/{version}/edit— version form/tedy/value-sets/{id}/compare— version compare (placeholder, shared pattern)
(Standalone Router basename="/tedy"; menu routes are basename-relative.)
Non-functional Requirements
| Requirement | Target |
|---|---|
| List load | < 2s; server-side pagination (20/page); lastVersionDecorated only |
| Expansion (snapshot) | < 2s; prefer stored snapshot; live expand only for drafts/preview |
Rule preview (expand-rule) | responsive; debounce edits |
| Search debounce | ~400ms |
Business Tests
- List / view / create operate over
/ts/value-sets*with the shared envelope. - A version’s members come from expanding its
ruleSet(or itssnapshot), never from the root. - Version lifecycle (draft/activate/retire) + rule add/edit/delete require
ValueSet.write; read is open on the guest dev server. - Publisher/status/language/filter-operator render via their value sets.
Traces
- traces-from: TODO user stories
- children: TEDY.02.1, TEDY.02.2, TEDY.02.3
- sibling family: TEDY.01 Code System (shares the shell, version form, summary widgets)
id: TEDY.02.1 template: resource-list module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/value-set/containers/value-set-list.component.ts
- reference-editor/app/src/app/resources/_lib/value-set/services/value-set-lib.service.ts # GET /ts/value-sets
- helex-tx/modules/tedy/frontend/src/pages/CodeSystemList.tsx # reference pattern author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.02.1 – Value Set List
Feature grouping
Child of TEDY.02 – Value Set (Common Spec). Siblings: TEDY.02.2 Add, TEDY.02.3 View.
Description
Paged, searchable, filterable list of Value Sets over GET /ts/value-sets, rendered with the canonical ResourceList (mirrors the Code System list, TEDY.01.1). Master-detail preview + row → view.
Columns (reference-editor order)
| Column | Source | Notes |
|---|---|---|
| ID | id | AppLink → view |
| Version | lastVersion.version | decorated |
| Title | title (localized) → name | |
| Publisher | publisher | resolved via publisher VS (useValueSetExpand + resolveLocalizedDisplay), fallback raw |
| Status | lastVersion.status | AppTag (draft/active/retired) |
| Concepts | lastVersion.snapshot.conceptsTotal | expansion size (from snapshot); blank when no snapshot |
Search & filters
- Search →
textContains(debounce ~400ms). - Filter panel (
AppFilterBar, mirrors CS list): Publisher (TxConceptSelectpublisher VS) · Status (TxConceptSelectpublication-status →versionStatus) · Space (AppSelectfromGET /spaces→spaceId). - Add value set primary action →
/tedy/value-sets/new(TEDY.02.2).
Query
GET /ts/value-sets?limit=&offset=&textContains=&publisher=&versionStatus=&spaceId=&lastVersionDecorated=true&lang=
→ { data: ValueSet[], meta: { total, offset } }.
API
Per TEDY.02 § Common API Surface: GET /ts/value-sets, GET /spaces, GET /fhir/ValueSet/$expand (publisher/status).
Business Tests
- List renders live
/ts/value-setsrows with the column order above. - Search maps to
textContains; filters map topublisher/versionStatus/spaceId. - Publisher + status resolve via their value sets; concept count from
lastVersion.snapshot.conceptsTotal. - Row / ID opens TEDY.02.3; Add opens TEDY.02.2.
id: TEDY.02.2 template: resource-create module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/value-set/containers/edit/value-set-edit.component.ts
- reference-editor/app/src/app/resources/value-set/services/value-set.service.ts # POST /ts/value-sets/transaction
- helex-tx/modules/tedy/frontend/src/pages/code-system/CodeSystemFormFields.tsx # reference pattern author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.02.2 – Add Value Set
Feature grouping
Child of TEDY.02 – Value Set (Common Spec). Siblings: TEDY.02.1 List, TEDY.02.3 View.
Description
Create a new Value Set. Reached from the list’s Add value set action (value-sets/new). Reuses the shared ResourceForm + field pattern (like TEDY.01.2), writing via POST /ts/value-sets/transaction (body ValueSetTransactionRequest {valueSet, version?, identifiers?}); on success navigate into the new record’s shell.
Import FHIR is a second create entry point (file import →
POST /file-importer/value-set/process), owned by Exo — out of scope here.
Form fields (General)
| Field | Widget | Required | Maps to |
|---|---|---|---|
| ID | AppInput (create-only) | ✓ | valueSet.id |
| URI | AppInput | ✓ | valueSet.uri |
| Name | AppInput | valueSet.name | |
| Publisher | TxConceptSelect (publisher VS) | valueSet.publisher | |
| Experimental | AppSwitch | valueSet.experimental | |
| Title | AppLocalizedName | ✓ | valueSet.title |
| Description | AppLocalizedName | valueSet.description | |
| Purpose | AppLocalizedName | valueSet.purpose | |
| Identifiers | TxIdentifier (allowCustom) | valueSet.identifiers |
No
content/caseSensitive(those are Code System fields). A Value Set has no owned concepts — members come later from the version’s compose rules (TEDY.02.3 Rules tab). Required NOT-NULL fields are enforced server-side ontransaction; validateid,uri,titleclient-side (confirm the exact NOT-NULL set on first save, as done for CodeSystem).
Write
POST /ts/value-sets/transaction → { valueSet: { id, uri, name, title, publisher, description, purpose, experimental, identifiers } } (strip decorated versions/lastVersion/snapshot). On success → /tedy/value-sets/{id}/summary.
Related (post-create, in the view): create a first version (POST .../versions) and add compose rules (POST .../versions/{v}/rules), then expand to populate members.
Business Tests
- Add opens the create form with ID/URI/Title required.
- Save posts
ValueSetTransactionRequestand navigates to the new value set’s summary. - Requires
ValueSet.write(guest dev can write).
id: TEDY.02.3 template: resource-management module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/value-set/containers/summary
- reference-editor/app/src/app/resources/value-set/services/value-set.service.ts
- reference-editor/app/src/app/resources/_lib/value-set/services/value-set-lib.service.ts
- helex-tx/modules/tedy/frontend/src/pages/code-system/CodeSystemShell.tsx # shared shell to reuse author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.02.3 – Value Set View
Feature grouping
Child of TEDY.02 – Value Set (Common Spec). Siblings: TEDY.02.1 List, TEDY.02.2 Add.
Description
Tabbed detail view for one Value Set, reusing the shared TedyResourceShell (header + PageTabs + version selector) built for Code Systems (TEDY.01.3 / R1). Loads GET /ts/value-sets/{id}?decorate=true once and shares it via Outlet context.
Tabs
| Tab | Content | Source |
|---|---|---|
| Summary | Info card (icon-list) · Versions card · right column (expansion preview · Tasks · Related artifacts) | reuse TedyInfoCard/TedyInfoLine; new VS widgets |
| Metadata | Full field view + edit (ResourceForm, hideHeader) reusing the TEDY.02.2 field set | POST /ts/value-sets/transaction |
| Concepts (expansion) | The expanded members via ResourceList (Code · Display · System · Order · Active) | POST /ts/value-sets/expand or version snapshot.expansion[] |
| Rules (compose) | Rule-set editor: include/exclude rules with code-system/value-set refs, explicit concepts, filters | rules + expand-rule preview |
Summary — info card (icon-list)
Mirrors reference-editor’s value-set info widget: 🔑 id (copyable) · description · 🔗 uri (copyable) · ⚙ experimental/purpose · 📍 publisher (resolved). Reuse TedyInfoCard / TedyInfoLine / TedyInlineMeta.
Summary — versions card
Reuse the shared versions widget shape: Version · Status · Dates · Languages · Concepts (snapshot.conceptsTotal) · lifecycle actions (Add / Edit / activate·retire·draft / Delete) + Compare. Each version carries its own ruleSet.
Concepts (expansion)
- Prefer the version’s stored
snapshot.expansion[]; fall back to a livePOST /ts/value-sets/expand {valueSet, valueSetVersion}for drafts. - Member columns from
ValueSetVersionConcept:concept.code·display.name·concept.codeSystem·orderNumber·active. - Export via
expansion-export(later).
Rules (compose)
- Show the version
ruleSet.rules[]: each rule’stype(include/exclude),codeSystem(+version) orvalueSet(+version), explicitconcepts[], andfilters[](property/operator/value; operator from filter-operator VS). - Edit: add (
POST .../rules), update (PUT .../rules/{id}), delete (DELETE .../rules/{id}); toggle rule setinactive(PUT .../rule-sets/{id}). - Live preview a single unsaved rule via
POST /ts/value-sets/expand-rule. - After changes, re-expand to refresh members.
API
Per TEDY.02 § Common API Surface: load (?decorate=true), versions + lifecycle, rules/rule-sets, expand / expand-rule / expand-async, provenances, change-id, delete.
Reuse note
The shell, version form (VersionFormFields + create/edit pages), SemanticVersionSelect, summary info/versions widgets, Tasks + Related-artifacts widgets, and the compact Tedy* UI are all shared with TEDY.01 — this view is largely composition + VS-specific Concepts (expansion) and Rules (compose) tabs. (This is also the R5 “generalize the shell to ValueSet/MapSet” step.)
Business Tests
- View loads
/ts/value-sets/{id}?decorate=trueand renders Summary/Metadata/Concepts/Rules. - Expansion tab shows members from
snapshot(or liveexpand); columns perValueSetVersionConcept. - Rules tab lists include/exclude rules; edits hit rules/rule-sets endpoints; preview via
expand-rule. - Version lifecycle + metadata edit require
ValueSet.write.
id: TEDY.03 template: tool-feature module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/map-set
- reference-editor/app/src/app/resources/_lib/map-set/model/map-set.ts
- reference-editor/app/src/app/resources/_lib/map-set/model/map-set-version.ts # scope + statistics
- reference-editor/app/src/app/resources/_lib/map-set/model/map-set-association.ts
- reference-editor/app/src/app/resources/_lib/map-set/model/map-set-concept.ts
- reference-editor/app/src/app/resources/_lib/map-set/services/map-set-lib.service.ts # GET /ts/map-sets*
- reference-editor/app/src/app/resources/map-set/services/map-set-service.ts # write /ts/map-sets*
- terminology-server (/ts/map-sets, /ts/map-set-versions, /file-importer/map-set) author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.03 – Map Set (Common Spec)
Description
Common specification for Map Set management in TEDY — browse, view, and author FHIR ConceptMaps over terminology-server (/ts/map-sets). A Map Set relates concepts from a source to a target (each a code system or value set, its scope) through associations (individual mappings with a relationship). This root spec owns the shared vocabulary, data + scope/association model, FHIR mapping, privileges, the full REST surface, and navigation so the child (screen) specs don’t duplicate them.
terminology-server owns persistence + the ConceptMap model + automap engine; TEDY is the React client. Derived via
spec-authoring.md; the REST surface below was read from reference-editor’s map-set services and confirmed live on dev-tx.example (guestyupi).
Child specs:
Glossary Terms
| Term (canonical) | Meaning |
|---|---|
| Map Set | A FHIR ConceptMap — a set of mappings from source concepts to target concepts. |
| Version | A MapSetVersion with a publication status + release date; carries the scope + statistics. |
| Scope | MapSetVersion.scope — the source and target: sourceType/targetType (code-system/value-set/external-canonical-uri) + the referenced systems/value sets. |
| Association | MapSetAssociation — one mapping: source {code, codeSystem, display} → target {…}, a relationship, verified, noMap. |
| Relationship | The map relationship (equivalent / source-is-narrower-than-target / source-is-broader-than-target / related-to / not-related-to). |
| No-map | An association marking that a source has no target mapping (noMap: true). |
| Verified | Whether an association has been human-verified (verified). |
| Statistics | MapSetVersion.statistics — counts: sourceConcepts, equivalent, noMap, narrower, broader, unmapped, inactiveSources/Targets. |
| Automap | A background job that proposes associations by matching code / designation (.../associations/automap). |
| Map Set concept | MapSetConcept — a source concept + its associations (the source-centric view). |
| Property | MapSetProperty — a defined property attached to associations. |
Business Feature Sitemap
flowchart LR
US["(TODO) US: manage map sets"] --> ROOT[TEDY.03]
ROOT --> LST[TEDY.03.1 List]
ROOT --> CRT[TEDY.03.2 Add]
ROOT --> VIEW[TEDY.03.3 View]
LST -->|Add map set| CRT
LST -->|row / ID| VIEW
CRT -->|save| VIEW
VIEW --> ASSOC[Associations grid]
ASSOC -->|automap / verify / unmap| ASSOC
Data Model
| Entity / Resource | the reference platform API | FHIR | Notes |
|---|---|---|---|
| MapSet | /ts/map-sets | ConceptMap | list + detail; versions decorated |
| MapSetVersion | /ts/map-sets/{id}/versions | ConceptMap.version | status ∈ draft/active/retired; carries scope + statistics |
| MapSetScope | (embedded in version) | ConceptMap.group.source/target | source/target type + system/value-set refs |
| MapSetAssociation | /ts/map-sets/{id}/associations | ConceptMap.group.element.target | source → target + relationship + verified + noMap |
| MapSetConcept | .../versions/{v}/concepts | ConceptMap.group.element | source concept + its associations |
| MapSetStatistics | version.statistics | n/a | mapping counts |
| MapSetProperty | (embedded in map set) | ConceptMap.property | defined properties on associations |
Shared MapSet fields
| Field | Type | Rule |
|---|---|---|
id | string | identity; renamed via change-id |
uri | string | canonical URL |
name / title | string / LocalizedName | |
description / purpose | LocalizedName | |
publisher | string | publisher VS or free text |
narrative | string | HTML (FHIR text) |
experimental | boolean | |
otherTitle / topic / useContext / sourceReference | … | as ValueSet |
identifiers | Identifier[] | |
contacts | ContactDetail[] | |
copyright / settings | objects | settings.{reviewRequired,approvalRequired} |
versions | MapSetVersion[] | version + status + scope + statistics |
properties | MapSetProperty[] |
MapSetVersion fields
id, version, status, mapSet, releaseDate/expirationDate (YYYY-MM-DD), preferredLanguage, description, algorithm, identifiers, scope (MapSetScope), statistics (MapSetStatistics).
Scope + association model (Map-Set-specific)
MapSetVersion.scope # MapSetScope
├── sourceType # 'code-system' | 'value-set' | 'external-canonical-uri'
├── sourceValueSet | sourceCodeSystems[] # MapSetResourceReference {id, version, uri}
├── targetType
└── targetValueSet | targetCodeSystems[]
MapSetAssociation
├── source: {code, codeSystem, display} # MapSetAssociationEntity
├── target: {code, codeSystem, display}
├── relationship # concept-map-relationship code
├── verified # human-verified
├── noMap # source has no target
└── propertyValues[]
Used value sets (resolved via @helex/tx)
| Field | ValueSet | Canonical URL |
|---|---|---|
publisher | publisher | https://tx.example/fhir/ValueSet/publisher |
version status | publication-status | http://hl7.org/fhir/ValueSet/publication-status |
| supported/preferred language | languages | http://hl7.org/fhir/ValueSet/languages |
association relationship | concept-map-relationship | http://hl7.org/fhir/ValueSet/concept-map-relationship |
FHIR Mapping (Common)
| Internal | FHIR R5 ConceptMap path |
|---|---|
id/uri/name/title/publisher | ConceptMap.id/.url/.name/.title/.publisher |
version scope.sourceCodeSystems/sourceValueSet | ConceptMap.group.source / .sourceScope |
version scope.targetCodeSystems/targetValueSet | ConceptMap.group.target / .targetScope |
association source | group.element (code + display) |
association target + relationship | group.element.target (code + relationship) |
association noMap | group.element.noMap |
Common Rules
- Associations (the mappings) are the content; read them source-centric via
.../versions/{v}/concepts(MapSetConcept+ its associations) or flat via.../associations. - Version lifecycle mirrors the other resources: draft → active → retire (dedicated endpoints, empty body,
204); editing an active version is frozen server-side. - Batch association ops:
associations-batch(bulk upsert),verify(verify/unverify sets),unmap(mark no-map),automap(background job →JobLogResponse). - Statistics are (re)computed server-side;
reload-statistics-asyncrefreshes them. - publisher / status / language / relationship selects use
TxConceptSelect(url=…).
Shared Privileges
| Action | Privilege |
|---|---|
| View / browse | authenticated (dev: guest via the reference platform yupi) |
| Create / edit / delete / versions / associations | MapSet.write (the reference platform {id}.MapSet.write) |
Common API Surface
Base /ts/map-sets unless noted. All TEDY calls go through @helex/tedy’s txApi (/txapi → dev-tx.example /api).
MapSet CRUD
| Method | Path | Priv | Description |
|---|---|---|---|
| GET | /ts/map-sets | read | list — params below |
| GET | /ts/map-sets/{id}?decorate={bool} | read | load one (decorate embeds versions + scope + statistics) |
| POST | /ts/map-sets/transaction | write | create/update — MapSetTransactionRequest {mapSet, version?, properties?, associations?} |
| POST | /ts/map-sets/{id}/change-id | write | rename id — {id: newId} |
| DELETE | /ts/map-sets/{id} | write | delete map set |
| GET | /ts/map-sets/{id}/provenances?version= | read | provenance |
List params (MapSetSearchParams): id, ids, idContains, uri, uriContains, name, nameContains, description, descriptionContains, text, textContains, publisher, versionVersion, versionStatus, versionSource, versionsDecorated, lang, spaceId, packageId, packageVersionId, plus association filters (associationSourceCode, associationSourceSystem, associationTargetCode, associationTargetSystem, … + associationsDecorated), + limit/offset/sort.
Versions + lifecycle
| Method | Path | Description |
|---|---|---|
| GET | /ts/map-sets/{id}/versions | version list |
| GET | /ts/map-sets/{id}/versions/{version} | load one version (scope + statistics) |
| GET | /ts/map-set-versions/{id} | load version by numeric id |
| POST | /ts/map-sets/{id}/versions | create version |
| PUT | /ts/map-sets/{id}/versions/{version} | update version |
| POST | /ts/map-sets/{id}/versions/{version}/{draft|activate|retire} | lifecycle (204) |
| POST | /ts/map-sets/{id}/versions/{version}/duplicate | duplicate |
| DELETE | /ts/map-sets/{id}/versions/{version} | delete version |
| POST | /ts/map-sets/{id}/versions/{version}/reload-statistics-async | recompute statistics → JobLogResponse |
Associations (the mappings)
| Method | Path | Description |
|---|---|---|
| GET | /ts/map-sets/{id}/associations | flat association search (MapSetAssociationSearchParams: mapSetVersion, relationships, verified, noMap) |
| GET | /ts/map-sets/{id}/associations/{associationId} | load one |
| GET | /ts/map-sets/{id}/versions/{v}/concepts | source-centric (MapSetConcept + associations) |
| POST | /ts/map-sets/{id}/versions/{v}/associations | create association |
| PUT | /ts/map-sets/{id}/versions/{v}/associations/{id} | update association |
| POST | /ts/map-sets/{id}/versions/{v}/associations-batch | bulk upsert — {batch: [...]} |
| POST | /ts/map-sets/{id}/versions/{v}/associations/verify | {verifiedIds, unVerifiedIds} |
| POST | /ts/map-sets/{id}/versions/{v}/associations/unmap | {ids} |
| POST | /ts/map-sets/{id}/versions/{v}/associations/automap | MapSetAutomapRequest → JobLogResponse |
| POST | /ts/map-sets/{id}/properties/{propertyId}/delete-usages | clear a property’s usages |
Import
| Method | Path | Description |
|---|---|---|
| GET | /file-importer/map-set/csv-template | download CSV template |
| POST | /file-importer/map-set/process | file import (multipart) → JobLogResponse |
Sitemap / Navigation
/tedy/map-sets— list (TEDY.03.1)/tedy/map-sets/new— create (TEDY.03.2)/tedy/map-sets/{id}— view shell (TEDY.03.3): Summary · Metadata · Associations/tedy/map-sets/{id}/versions/new·/versions/{version}/edit— version form/tedy/map-sets/{id}/compare— version compare (placeholder, shared pattern)
Non-functional Requirements
| Requirement | Target |
|---|---|
| List load | < 2s; server-side pagination (20/page); versionsDecorated only |
| Associations grid | server-paged; filter by relationship / verified / no-map |
| Automap / reload-statistics | async job; non-blocking (poll or fire-and-refresh) |
| Search debounce | ~400ms |
Business Tests
- List / view / create operate over
/ts/map-sets*with the shared envelope. - A version carries a
scope(source→target) andstatistics; associations are read via.../associationsor.../versions/{v}/concepts. - Association authoring (create/edit/verify/unmap/automap) + version lifecycle require
MapSet.write; read is open on the guest dev server. - Publisher/status/language/relationship render via their value sets.
Traces
- traces-from: TODO user stories
- children: TEDY.03.1, TEDY.03.2, TEDY.03.3
- sibling families: TEDY.01 Code System, TEDY.02 Value Set (shared shell, version form, summary widgets,
Tedy*UI)
id: TEDY.03.1 template: resource-list module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/map-set/containers/map-set-list.component.ts
- reference-editor/app/src/app/resources/_lib/map-set/services/map-set-lib.service.ts # GET /ts/map-sets
- helex-tx/modules/tedy/frontend/src/pages/ValueSetList.tsx # reference pattern author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.03.1 – Map Set List
Feature grouping
Child of TEDY.03 – Map Set (Common Spec). Siblings: TEDY.03.2 Add, TEDY.03.3 View.
Description
Paged, searchable, filterable list of Map Sets over GET /ts/map-sets, rendered with the canonical ResourceList (mirrors TEDY.02.1). Master-detail preview + row → view.
Columns
| Column | Source | Notes |
|---|---|---|
| ID | id | AppLink → view |
| Version | versions[0].version (decorated) | latest |
| Title | title (localized) → name | |
| Publisher | publisher | resolved via publisher VS |
| Status | versions[0].status | AppTag |
| Associations | versions[0].statistics.sourceConcepts | mapping count (from statistics); blank when none |
Search & filters
- Search →
textContains(debounce ~400ms). - Filters (
AppFilterBar): Publisher (TxConceptSelectpublisher VS) · Status (TxConceptSelectpublication-status →versionStatus) · Space (AppSelect→spaceId). - Add map set →
/tedy/map-sets/new(TEDY.03.2).
Query
GET /ts/map-sets?limit=&offset=&textContains=&publisher=&versionStatus=&spaceId=&versionsDecorated=true&lang= → { data: MapSet[], meta: { total, offset } }.
Business Tests
- Renders live
/ts/map-setsrows with the column order above. - Search →
textContains; filters →publisher/versionStatus/spaceId. - Row / ID opens TEDY.03.3; Add opens TEDY.03.2.
id: TEDY.03.2 template: resource-create module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/map-set/containers/edit/map-set-edit.component.ts
- reference-editor/app/src/app/resources/map-set/services/map-set-service.ts # POST /ts/map-sets/transaction
- helex-tx/modules/tedy/frontend/src/pages/value-set/ValueSetFormFields.tsx # reference pattern author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.03.2 – Add Map Set
Feature grouping
Child of TEDY.03 – Map Set (Common Spec). Siblings: TEDY.03.1 List, TEDY.03.3 View.
Description
Create a new Map Set. Reached from the list’s Add map set action (map-sets/new). Reuses the shared ResourceForm + field pattern, writing via POST /ts/map-sets/transaction (MapSetTransactionRequest {mapSet, version?, properties?, associations?}); on success navigate into the new record’s shell.
Import is a second create entry point (CSV →
POST /file-importer/map-set/process), owned by Exo — out of scope here.
Form fields (General)
| Field | Widget | Required | Maps to |
|---|---|---|---|
| ID | AppInput (create-only) | ✓ | mapSet.id |
| URI | AppInput | ✓ | mapSet.uri |
| Name | AppInput | mapSet.name | |
| Publisher | TxConceptSelect (publisher VS) | mapSet.publisher | |
| Experimental | AppSwitch | mapSet.experimental | |
| Title | AppLocalizedName | ✓ | mapSet.title |
| Description | AppLocalizedName | mapSet.description | |
| Purpose | AppLocalizedName | mapSet.purpose | |
| Identifiers | TxIdentifier | mapSet.identifiers |
Scope (source/target systems) is set per-version (not on the map set root) — configured in the view once a version exists. Required NOT-NULL fields are enforced server-side on
transaction; validateid,uri,titleclient-side.
Write
POST /ts/map-sets/transaction → { mapSet: { id, uri, name, title, publisher, description, purpose, experimental, identifiers } } (strip decorated versions). On success → /tedy/map-sets/{id}/summary. Then create a first version (with its scope) and add associations (or automap).
Business Tests
- Add opens the create form with ID/URI/Title required.
- Save posts
MapSetTransactionRequestand navigates to the new map set’s summary. - Requires
MapSet.write.
id: TEDY.03.3 template: resource-management module: tedy state: Proposed traces-from: [] source-refs:
- reference-editor/app/src/app/resources/map-set/containers/summary
- reference-editor/app/src/app/resources/map-set/services/map-set-service.ts
- reference-editor/app/src/app/resources/_lib/map-set/services/map-set-lib.service.ts
- helex-tx/modules/tedy/frontend/src/pages/value-set/ValueSetShell.tsx # shared shell to reuse author: TEDY bootstrap created: 2026-08-08 updated: 2026-08-08
TEDY.03.3 – Map Set View
Feature grouping
Child of TEDY.03 – Map Set (Common Spec). Siblings: TEDY.03.1 List, TEDY.03.2 Add.
Description
Tabbed detail view for one Map Set, reusing the shared TedyResourceShell (header + tabs + version selector). Loads GET /ts/map-sets/{id}?decorate=true once and shares it via Outlet context.
Tabs
| Tab | Content | Source |
|---|---|---|
| Summary | Info card (icon-list) · Versions card · Scope (source→target) · Statistics · Tasks · Related artifacts | reuse TedyInfoCard/widgets; new MS scope + stats widgets |
| Metadata | Full field view + edit (ResourceForm) reusing the TEDY.03.2 field set | POST /ts/map-sets/transaction |
| Associations | The mappings grid (source → relationship → target · verified · no-map) with filter + verify/unmap/automap + add/edit | .../associations, .../versions/{v}/concepts, association write ops |
Summary — Scope + Statistics
- Scope (from the selected version): sourceType + source system(s)/value set → targetType + target system(s)/value set.
- Statistics (
version.statistics): sourceConcepts · equivalent · narrower · broader · noMap · unmapped · inactiveSources/Targets — with a Reload statistics action (reload-statistics-async).
Associations tab
- Grid columns: source (code · display · system) → relationship (concept-map-relationship) → target (code · display · system) · verified · no-map.
- Filter (
MapSetAssociationSearchParams): relationship · verified · no-map; server-paged via.../associations. - Row / bulk actions: Verify / Unverify (
.../associations/verify), Unmap (.../associations/unmap), Edit (PUT .../associations/{id}), Add association (POST .../associations), Automap (.../associations/automap→ job). - Association editor: source concept + target concept (
TxConceptSelectover the scope systems) + relationship (TxConceptSelectconcept-map-relationship) + no-map toggle.
API
Per TEDY.03 § Common API Surface: load (?decorate=true), versions + lifecycle, associations (search / concepts / create / update / batch / verify / unmap / automap), reload-statistics, provenances, change-id, delete.
Reuse note
Shell, version form, SemanticVersionSelect, info/versions/Tasks/Related-artifacts widgets, and the compact Tedy* UI are shared with TEDY.01/02 — this view adds MS-specific Scope, Statistics, and Associations surfaces. The associations grid + editor is the MS analogue of the ValueSet rule editor.
Business Tests
- View loads
/ts/map-sets/{id}?decorate=trueand renders Summary/Metadata/Associations. - Summary shows the version scope + statistics; Reload statistics triggers the async job.
- Associations grid lists mappings; filter by relationship/verified/no-map; verify/unmap/automap/add/edit hit the association endpoints.
- Version lifecycle + metadata edit require
MapSet.write.
TEDY.06 — Implementation Guides
State: Proposed (list built; summary/metadata/versions/groups/resources/pages/github phased)
Template: tool-feature. Prefix: TEDY.
Feature grouping
Root spec for the Implementation Guides (IG) family (a versioned resource, like Code System).
Overview
An IG is a FHIR Implementation Guide with versions; each version carries groups, resources (references to CS/VS/MS/StructureDefinition/StructureMap) and pages (wiki content), plus optional GitHub publishing (sushi-config/ig.ini generation + push). Served off /implementation-guides (NOT /ts/*); string id; versions addressed by their string version.
Data model (libs/tedy/src/lib/types/implementationGuide.ts)
ImplementationGuide: id:string, uri, publisher, name, title/description/purpose:LocalizedName, licence, experimental, versions[] (decorated).
ImplementationGuideVersion: id:number, implementationGuide, version, status (draft|active|retired), packageId, fhirVersion, template, algorithm, date (server-derived from last-modified), github{repo,branch,init}, dependsOn[].
Sub-entities (phased): group{name,description}, resource{type,reference,version,name,group}, page{space,page,name,type,group}.
REST API (base /implementation-guides)
GET /implementation-guides?{params}(decoratedinlines versions),GET /{ig},POST /transaction,POST /{ig}/change-id,DELETE /{ig}.- Versions:
GET/POST /{ig}/versions,GET/PUT /{ig}/versions/{version},POST .../{activate|retire|draft}, groups/resources/pages GET/POST (replace-by-retain). GET /{ig}/provenances?version=.- GitHub:
/{ig}/versions/{version}/github/*(authenticate, status, branches, diff, push, ig-initialize, create-branch).
Screens
- List (built) — Id · Last version · FHIR version · Title (+ packageId tag) · Date. Search →
textContains,decorated=true. - Summary (phased) — info widget + versions widget; version-level: version info + editable Groups/Resources/Pages tables (draft-gated).
- Metadata / create + version form (phased) — resource-form (id/uri/name/title/…) + identifiers + contacts + copyright side-info; version form (algorithm/packageId/version/fhirVersion/github/template/dependsOn).
- Provenance (phased) — reuse
ProvenanceList. - GitHub sync (phased) — auth → branch/init → status/diff/push state machine.
Routes & nav
/implementation-guides (list). Nav: Modeling → Implementation Guides (BookOutlined). Phased: :id/summary, :id/edit, :id/provenances, :id/versions/{add,:versionCode/{summary,edit,provenances,github}}. Tabs (via TxResourceBar): Summary + Provenance only.
Privileges
ImplementationGuide.read / .write / .maintain. Templated per-id ({id}.ImplementationGuide.*, *.ImplementationGuide.write for add).
Built artefacts
- Data layer:
types/implementationGuide.ts,client/implementationGuideClient.ts(searchImplementationGuides,loadImplementationGuide),hooks/useImplementationGuides.ts(useImplementationGuides,useImplementationGuide,implementationGuideKeys). - Screens:
pages/ImplementationGuideList.tsx. Route +Modelingnav entry.
See open questions in TEDY-new-modules-open-questions.md.
TEDY.07 — Spaces
State: Built — list + TxResourceBar detail with Summary · Metadata (Core + GitHub edit) · linked Code Systems / Value Sets / Map Sets (add-as-link + unlink) · Comparison (diff) · Repository (git). Create page + list Add. Simplified scope (below) confirmed with the product owner.
Template: tool-feature. Prefix: TEDY.
Feature grouping
Root spec for the Spaces family (a “Management” section resource). References TEDY.08 Servers (bound servers + the cross-server comparison).
Overview
A Space is an authoring workspace that groups terminology resources (CS / VS / MS), optionally bound to terminology servers and a git repository (GitHub, with Azure DevOps as a feature-flagged sibling) for round-tripping FHIR content. Served off /spaces (NOT /ts/*); numeric id.
Design decisions:
- TEDY renders the space detail through the shared
TxResourceBar(same tabbed shell as CS/VS/MS/Servers), NOT the reference editor’s matrix-param “context” shell (;s=;p=;v=). Comparison and Repository are tabs on that bar. - Simplified scope (deliberately narrower than the reference): no package-manager UI, no SSG generator, no ACL editor, no wiki/GitHub import. mdBook (replacing the SSG) is a future concern and is not implemented.
- Resources map directly to the Space. The UI shows the space’s linked resources as one flat set per type; “Add” links an existing resource. Because the backend only persists links through
Package → PackageVersion → PackageResource, this is backed by an auto-managed, hidden default package (one package, one version) — the package/version layer is never surfaced in the UI. - Metadata = Core + GitHub only.
Data model
Space (libs/tedy/src/lib/types/space.ts)
id:number, code, names:LocalizedName, active, shared, globalSearch, terminologyServers:string[], integration:{ github?:{repo,dirs}, msDevops?:{repo,dirs} }.
Package / PackageVersion / PackageResource (libs/tedy/src/lib/types/spacePackage.ts) — internal
The hidden layer backing “map resources directly to the space”:
Package { id?, code?, status?, spaceId? }(statusdraft/active).PackageVersion { id?, version?, description?, packageId?, resources?: PackageResource[] }.PackageResource { id?, resourceType: 'code-system'|'value-set'|'map-set', resourceId, resourceVersion?, terminologyServer?, versionId? }— a reference to an existing CS/VS/MS, not an owned copy.PackageTransactionRequest { pack: Package, version?: PackageVersion }(one transactional save; the backend retains the full resource list — resources not in the payload are soft-cancelled).SpaceDiff { items?: SpaceDiffItem[], error? },SpaceDiffItem { id?, resourceId?, resourceType?, resourceServer?, upToDate? }.- Git:
SpaceGitStatus { sha?, files?: Record<path,'M'|'U'|'D'|'A'|'K'> },SpaceGitDiff { left?, right? }.
REST API (base /spaces)
- Space:
GET/POST /spaces,GET /spaces/{id},GET /spaces?{params}. Save =saveSpace(POSTcreate /PUT-style upsert). - Packages (hidden):
GET/POST /spaces/{spaceId}/packages,GET …/{id}/versions. Save body =PackageTransactionRequest. - Add-as-link resolves the default package + latest version (
loadSpaceDefaultVersion), appends/removes aPackageResource, and re-saves the whole version (linkResourceToSpace/unlinkResourceFromSpace,libs/tedy/src/lib/client/spacePackageLink.ts). Dedup byresourceType + resourceId; creates the default package ({code: space.code||'default', status:'draft'}+ version1.0.0) if absent. - Diff:
GET /spaces/{id}/diff→ Lorque 202{id}; pollGET /lorque-processes/{id}untilfinished/failed;resultis base64 JSON.runSpaceDiff(spaceDiffClient.ts) drives start→poll→decode and rejects with the decoded error onfailed. - Git (
github|msdevops,spaceGitClient.ts):GET /spaces/{p}/providers,POST /spaces/{id}/{p}/authenticate {returnUrl}→{isAuthenticated,redirectUrl},GET /spaces/{id}/{p}/status,GET /spaces/{id}/{p}/diff?file=,POST /spaces/{id}/{p}/push {message,files},POST /spaces/{id}/{p}/pull {files}. - Resource lists are linked-resource reads: the CS/VS/MS search endpoints resolve
spaceIdby JOINing throughpackage_version_resource → package_version → package → space, sosearchCodeSystems({spaceId})returns exactly the linked set.
Metadata — attributes (Core + GitHub)
| Section | Fields | Notes |
|---|---|---|
| Core | code (required), name (AppLocalizedName), active, globalSearch, servers (multiselect of terminology-server codes) | view ⇄ edit via useDataController |
| GitHub | enable + repo (addonBefore=https://github.com/) + per-provider directory inputs (GET /spaces/github/providers) | object-defaulting before binding; save nulls disabled integrations |
| Azure DevOps | same block, feature-flagged (VITE_TX_MSDEVOPS_ENABLED) | hidden unless the flag is on |
ACL, SSG (metadata + generator), packages sub-list and import-from-GitHub are intentionally out of scope.
Screens / tabs (via TxResourceBar)
- List — Code · Name · Active · GitHub; search + Add (gated
*.Space.write→spaces/new). - Create (
SpaceCreate.tsx,spaces/new) — Core-only;saveSpace→ navigate to the new space’s Summary. - Summary — space info card.
- Metadata (
SpaceMetadata.tsx) — read-only view + inline edit (Core + GitHub / MS DevOps). - Code Systems / Value Sets / Map Sets (
SpaceResourceListTab.tsx) — list the space’s linked resources; Add = link an existing resource (searchable picker of existing CS/VS/MS, excluding the space’s full linked set →linkResourceToSpace); per-row Unlink (AppPopconfirm→unlinkResourceFromSpace). - Comparison (
SpaceDiffTab.tsx, modediff) — Run comparison → start the Lorque diff job → poll → table of per-resource/server up-to-date state; idle/loading/error/empty states. - Repository (
SpaceGitTab.tsx, modegithub) — authenticate → Connect (OAuth redirect) when not authenticated, else working-tree status (changed files); Push (commit-message modal, neutral default"update space") + Pull (confirm) + per-file diff modal. Provider-parametrized so MS DevOps reuses the same component under the feature flag.
Routes & nav
/spaces (list), /spaces/new (create). Nav: Management → Spaces. Children under spaces/:id: summary, details, code-systems, value-sets, map-sets, diff, github. TxResourceMode includes diff/github; tabsFor('space') and SpaceResourceLayout.MODES list all seven.
Privileges
*.Space.read (view/list), *.Space.write (add), {id}.Space.write (edit / link / git). Gate with usePrivileged().
Config
VITE_TX_MSDEVOPS_ENABLED (config/tx.ts → MSDEVOPS_ENABLED) toggles the Azure DevOps integration UI. Real repo/server URLs stay in the gitignored .env.
Naming
The reference’s default commit message is a brand string — TEDY uses a neutral default ("update space"). Never commit the reference product names. (Backend runtime errors surfaced in the Comparison tab may echo server-side class names; that text is server data, not TEDY source.)
Reference ↔ TEDY comparison
Re-verified against the reference editor’s space module (sys/space/containers/{space,server,package}) and the terminology-server release/feature docs. The reference drives the space detail through a matrix-param context shell (;s=;p=;v=) with a package/version drawer; TEDY replaces that with the shared TxResourceBar (point 2) — same navigation model as CS/VS/MS/Servers, no matrix params.
View / tab mapping (points 1, 4)
| Reference view | TEDY tab (TxResourceBar) | Status |
|---|---|---|
| Space dashboard (linked resources per type) | Code Systems / Value Sets / Map Sets tabs | Built — linked-resource lists + add-as-link + unlink + bulk actions |
space-edit (metadata + packages + SSG + ACL) | Metadata tab (view ⇄ edit) | Built (Core + GitHub); packages/SSG/ACL descoped |
space-diff (single-resource 3-pane) | Comparison tab | Built — whole-space diff table (Lorque) |
space-diff-matrix (resource × server grid) | Comparison tab | Built — diff items + State filter + row selection + bulk change-server + per-row sync (local/external) |
space-github | Repository tab | Built — auth / status / push / pull / per-file diff |
space-ms-dev-ops | Azure DevOps tab | Built — dedicated msdevops tab/route (flagged), reuses the provider-parametrized SpaceGitTab |
| — | Summary tab | Built (space info card; TEDY addition) |
Metadata attribute comparison (point 3)
| Reference field(s) | Section | TEDY status |
|---|---|---|
code, names, active, globalSearch, terminologyServers | Core | Built (view + edit) |
integration.github (repo + per-provider dirs) | GitHub | Built |
integration.msDevops (repo + dirs) | Azure DevOps | Built, feature-flagged (VITE_TX_MSDEVOPS_ENABLED) |
| packages sub-list (open / add / delete) | Packages | Descoped — replaced by the hidden auto-default package (resources map directly to the space) |
description, languages, defaultLanguage, siteUrl | SSG metadata | Planned (mdBook) — additive/nullable when mdBook lands |
ssgSkin, ssgThemeAccent, ssgThemeSwitcher, ssgFooterMessage, ssgFooterCopyright, ssgTxServer, ssgSearch, ssgLogo | SSG generator | Descoped — mdBook replaces the SSG generator |
acl (owners/editors/viewers), shared | ACL | Descoped — access enforced via *.Space.* privileges |
import-from-GitHub (POST /wiki-import/github) | Import | Descoped — wiki import out of scope |
Remaining gaps & implementation plan (point 5)
Everything the approved simplified scope calls for is built (points 2, 4, 6). Items 1–3 below (the reference deltas) are now also built; item 4 waits on the mdBook decision.
- Comparison matrix (
space-diff-matrix) — Built. The Comparison tab lists diff items with a State filter (up-to-date / differs), row selection, a bulk change comparable server (POST /package-resources/change-server {resourceIds, terminologyServer}, options from the space’sterminologyServers), and a per-row Sync menu — into space (local) / to server (external) — viaPOST /package-resources/{id}/sync(packageResourceClient.ts). - Per-resource Comparison shortcut — Built. Each linked CS/VS/MS row has a Compare action opening the Comparison tab.
- Dedicated MS DevOps tab — Built.
msdevopsadded toTxResourceMode+tabsFor('space')(shown only whenMSDEVOPS_ENABLED) +SpaceResourceLayout.MODES+ aspaces/:id/msdevopsroute renderingSpaceGitTab provider="msdevops"(provider-aware label/icon/empty-state). - mdBook metadata (future) — when the mdBook direction lands, add an mdBook section to Metadata for
description/languages/defaultLanguage/siteUrl(additive, nullable). Not the old SSG generator.
Backend note: the space diff needs a comparable-server-configured space; on the dev backend it returns TC105: Terminology server for current installation is not defined, so the populated matrix (and per-row sync) are verified only against a configured deployment — the error/empty paths are verified in dev.
See open questions in TEDY-new-modules-open-questions.md.
TEDY.08 — Servers (terminology / FHIR server registry)
State: Proposed (built: list [search + filter + Add] · create · TxResourceBar detail [Summary with 5 authoritative widgets · Metadata full form + check-connection · Resources] · authoritative editor). Ecosystem export/import & remote resource listing phased.
Template: tool-feature. Prefix: TEDY.
Feature grouping
Root spec for the Servers family (a “Management” section resource). Sits alongside TEDY.09 Ecosystems (which groups servers) and TEDY.07 Spaces.
Overview
A registry of external terminology / FHIR servers. Each server carries connection + auth config, declared FHIR versions, capability flags, five “authoritative resources” collections (canonical patterns the server owns) and a currentInstallation flag for the local baseline. Served off /servers (NOT /ts/*); numeric id.
Data model (libs/tedy/src/lib/types/server.ts)
Server: id:number, code, names:LocalizedName, rootUrl, kind: ('terminology'|'fhir'|'snomed')[], active, currentInstallation, accessInfo, usage[], supportedOperations[], strategy (inline|cached|local), cachePeriodHours, fhirVersions:{version,url}[], headers: ServerHeader[], authConfig: ServerAuthConfig, capability flags open/token/oauthFlag/smartFlag/certFlag, exclusions[], and the five authoritative collections authoritative / authoritativeValuesets / authoritativeConceptmaps / authoritativeStructuredefinitions / authoritativeStructuremaps (AuthoritativeResource[]).
ServerHeader { key?; value?; _masked? },ServerAuthConfig { authType: none|basic|oauth2|apikey; accessTokenUrl?; clientId?; clientSecret?; scope?; _masked? }.AuthoritativeResource { url?; status?; version?; name?; _inclusive? }.ServerConnectionCheckResult { success; statusCode?; url?; durationMs?; software?; softwareVersion?; fhirVersion?; error? }.SERVER_AUTHORITATIVE_FIELD: Record<ServerResourceType, keyof Server>maps each type → its collection field.
Secrets (authConfig.clientSecret, Authorization header values) are masked server-side; the edit form uses the _masked = “null-means-keep-persisted” convention.
REST API (base /servers)
GET /servers?{params}→SearchResult<Server>; params incl.textContains,kinds,currentInstallation(public view when the caller lacksServer.write).GET /servers/{id}→Server(write-privileged load-for-edit);POST/PUT→ upsert.GET /servers/{id}/check-connection→ServerConnectionCheckResult.GET /servers/{id}/resources/{type}→AuthoritativeResource[](local for current-installation; live-paged remote otherwise).POST /servers/{id}/authoritative/{type}/preview→ matching resources.GET /servers/export/ecosystem?download/POST /servers/import/ecosystem→ FHIR-ecosystem interchange (phased).
Screens (all built unless noted)
- List — Code · Name · Kind · Active · Current installation; search →
textContains; filter panel (kind, current-installation); Add (write-gated) → create. - Create (
ServerCreate) — code, name, kind, FHIR version + URL, active (POST). Full field set edited afterwards on Metadata. - Summary — Server info card + FHIR Ecosystem card, then five authoritative-resource widgets (
ServerAuthoritativeListWidget): each lists the server’sauthoritative*entries (url · version · status), with Add/Change → the authoritative editor and Delete (removes + re-saves the server). - Metadata — read-only view + inline edit of the full field set: code, name, kind, FHIR versions, strategy, cache, usage, supported operations, access info, active/current, custom headers table, auth-config block (authType + accessTokenUrl/clientId/clientSecret/scope, secret-masked), capability flags (open/token/OAuth/SMART/Cert). Card footer has Check connection → colored result tag (tooltip = probed URL) + success/failure toast.
- Resources tab — a classical
ResourceListwith the filter panel on the right (resource-type multiselect, Code Systems preselected) + search overGET /servers/{id}/resources/{type}. Each row has two actions: Open in Terminology eXplorer (web viewer atTX_VIEWER_URL, e.g.https://tx.helex.dev/fhir/{Type}/{name}) and Open external FHIR resource ({server.rootUrl}/{Type}/{name}).TX_VIEWER_URLis configured viaVITE_TX_VIEWER_URL(config/tx.ts; defaulthttps://tx.helex.dev). - Authoritative editor (
/servers/:id/authoritative/:type) — add-by-URL, Include/URL/Version/Status rows, Preview (POST …/preview), Save (writes the mapped collection).
Routes & nav
/servers (list), /servers/new (create), /servers/:id → TxResourceBar tabs (summary/details/resources), /servers/:id/authoritative/:type (editor). Nav: Management → Servers (CloudServerOutlined).
Privileges
Two families coexist: *.Server.read / *.Server.write (backend @Authorized, menu, list Add) and *.Space.read / {id}.Space.write (route guards + in-page edit affordances). Gate with usePrivileged().
Built artefacts
- Data layer:
types/server.ts,client/serverClient.ts(searchServers,loadServer,saveServer,loadServerResources,checkServerConnection,previewAuthoritative),hooks/useServers.ts(useServers,useServer,serverKeys). - Screens:
pages/ServerList.tsx,pages/server/{ServerCreate,ServerResourceLayout,ServerSummary,ServerMetadata,ServerResourcesTab,ServerAuthoritativeEdit}.tsx,pages/server/widgets/ServerAuthoritativeListWidget.tsx.TxResourceBarextended withterminology-server(no version chip) + aresourcesmode.
Phased (documented, not built)
- Ecosystem export/import actions on the list (
/servers/export|import/ecosystem). - Remote resource listing niceties (local-vs-remote link routing,
$summary-aware links) in the Resources tab.
See open questions in TEDY-new-modules-open-questions.md.
TEDY.09 — Ecosystems
State: Proposed (built: list + add/edit)
Template: tool-feature. Prefix: TEDY.
Feature grouping
Root spec for the Ecosystems family (a “Management” section resource). Depends on TEDY.08 Servers for its server pick-list.
Overview
An Ecosystem is a named, coded collection that groups terminology servers and is exposed publicly as an ecosystem.json document (FHIR TX ecosystem format) for an external router. Many-to-many with servers. Served off /ecosystems (NOT /ts/*); numeric id. Not a versioned resource — a plain list + form CRUD.
Data model (libs/tedy/src/lib/types/ecosystem.ts)
Ecosystem: id:number, code, names:LocalizedName, formatVersion (server-defaulted "1", read-only), description, active, serverIds:number[].
EcosystemSearchParams: textContains?, ids?, codes? (+ base paging).
REST API (base /ecosystems)
GET /ecosystems?{params}→SearchResult<Ecosystem>(Space.read).GET /ecosystems/{id}→Ecosystem(Space.read).POST /ecosystems/PUT /ecosystems/{id}→ upsert (Space.write; duplicatecode→ error).- Public, unauthenticated:
GET /public/ecosystems/{code}→ theecosystem.jsondocument (linked/downloaded by the UI, never parsed).
Screens (built)
- List — Code (→ edit) · Name · Servers (tags resolved to server codes) · Active · a
View ecosystem.jsonlink; search →textContains;Add ecosystemgated on*.Space.write. - Add / Edit — one card:
code(immutable on edit), multi-languagename(required), read-onlyformatVersion,description,active,serverIdsmulti-select. Save viaPOST/PUT, then back to the list.
Routes & nav
/ecosystems (list), /ecosystems/new, /ecosystems/:id/edit. Nav: Management → Ecosystems (ClusterOutlined).
Privileges
*.Space.read (view/list), *.Space.write (add), {id}.Space.write (edit).
Built artefacts
- Data layer:
types/ecosystem.ts,client/ecosystemClient.ts(searchEcosystems,loadEcosystem,saveEcosystem,ecosystemJsonUrl),hooks/useEcosystems.ts(useEcosystems,useEcosystem,ecosystemKeys). - Screens:
pages/EcosystemList.tsx,pages/ecosystem/EcosystemForm.tsx. Routes +Managementnav entry.
See open questions in TEDY-new-modules-open-questions.md.
TEDY.10 — Defined Properties
State: Built — list + create/edit form (standard ResourceList + ResourceForm).
Template: tool-feature. Prefix: TEDY.
Overview
A Defined Property is a globally-registered property/designation definition that code systems reference by name — the shared vocabulary behind concept property values and designations. Served off /ts/defined-properties; numeric id. used is server-computed (true when at least one code system references the definition).
Data model (libs/tedy/src/lib/types/definedProperty.ts)
DefinedProperty { id?:number, name?, kind?, type?, uri?, description?:LocalizedName, rule?:PropertyRule, used?:boolean }.
kind—property|designation.type— FHIR concept-property type (code,Coding,string,integer,boolean,dateTime,decimal).rule—PropertyRule { codeSystems?, valueSet?, filters? }; carried through unchanged, not edited in this slice (the reference edit form doesn’t edit it either).used— read-only; flagged with an info marker in the list.
DefinedPropertySearchParams extends QueryParams { textContains? }.
REST API (base /ts/defined-properties)
GET /ts/defined-properties— paged search (textContains).GET /ts/defined-properties/{id}— load one.POST /ts/defined-properties(create) /PUT /ts/defined-properties/{id}(update) —saveDefinedPropertyupserts by presence ofid.POST /ts/defined-properties/{id}/update-related— re-propagate the definition to the code systems that use it (the list’s row action).
Client: libs/tedy/src/lib/client/definedPropertyClient.ts; hook: useDefinedProperties / useDefinedProperty / definedPropertyKeys.
Business rules / constraints
- A
useddefined property locks its identity (referenced by ≥1 code system): in the reference edit form, whenusedthe name / URI / kind / type become read-only while the description stays editable — and there is no delete for defined properties at all. TEDY mirrors this exactly — those four fields getmodeOverride:'view'whenused(Save still applies the description change; subtitle: “In use by a code system — only the description can be changed.”), and the list has no delete action.usedis server-computed, so the constraint is also enforced backend-side. - Editing/adding requires
*.DefinedProperty.write.
Screens
- List (
pages/DefinedPropertyList.tsx,resource-list) — a standardResourceList: Name (link, with ausedinfo marker) · Description · URI · Kind (tag) · Type.textContainssearch; Add (gated*.DefinedProperty.write) → the form; per-row Update related properties action. No delete. - Form (
pages/defined-property/DefinedPropertyForm.tsx,resource-create/resource-management) — a standardResourceFormwith declarativesections: Name (required), URI, Kind (select), Type (select of the concept-property types), Description (localized). Create + edit;POST/PUTthen return to the list.- When the property is
used, name/URI/kind/type are locked (view) and only Description is editable; the whole form isviewwhen the user lacks*.DefinedProperty.write. - The form mount is gated until the record loads into the
dataController—ResourceFormseeds its antd form fromdataController.currentonce at mount, so an asyncdc.loadmust complete before it first renders (areadyflag set in the load effect).
- When the property is
Routes & nav
/defined-properties (list), /defined-properties/new, /defined-properties/:id/edit. Nav: Terminology → Defined Properties (tedyMenu.ts).
Privileges
*.DefinedProperty.write gates Add / edit / update-related. Read is open (list/view).
i18n
tedy.definedProperty.* (fields.*, kind.*, list.*, validation.*, notifications.*, updateRelated, usedHint).
TEDY.11 — Naming Systems
State: Built — list + create/edit form (standard ResourceList + ResourceForm).
Template: tool-feature. Prefix: TEDY.
Overview
A Naming System registers a naming authority for a code system / value set / identifier — the canonical name plus the external identifiers (OID/UUID/URI) that refer to it. Served off /ts/naming-systems; string id. status is a publication lifecycle managed via activate/retire (created as draft); created is server-set.
Data model (libs/tedy/src/lib/types/namingSystem.ts)
NamingSystem { id?:string, names?:LocalizedName, kind?, codeSystem?, source?, description?, status?, created?, identifiers?:NamingSystemIdentifier[] }.
kind—codesystem|valueset|identifier|root(FHIR namingsystem-type).codeSystem— referenced code system id, relevant whenkind === 'codesystem'.description— plain string (not localized).status—draft|active|retired.NamingSystemIdentifier { type?, value?, preferred? }—typefromoid/uuid/uri/other; exactly onepreferred.
NamingSystemSearchParams extends QueryParams { textContains?, lang? }.
REST API (base /ts/naming-systems)
GET /ts/naming-systems— paged search (textContains).GET /ts/naming-systems/{id}— load one.POST /ts/naming-systems— upsert (create and edit both POST the full object; there is no PUT).POST /ts/naming-systems/{id}/activate·POST /ts/naming-systems/{id}/retire— lifecycle.DELETE /ts/naming-systems/{id}.
Client: libs/tedy/src/lib/client/namingSystemClient.ts; hook: useNamingSystems / useNamingSystem / namingSystemKeys.
Business rules / constraints
- Only
draftnaming systems are editable. The reference list opensdraftin the edit form andactive/retiredin a read-only view. TEDY mirrors this — the form drops toviewmode when the record’s status is notdraft(subtitle: “Only draft naming systems can be edited — read-only.”). New records are created asdraft. idis immutable on edit (create-only; disabled in edit).codeSystemis required whenkind === 'codesystem'(validated on save).- Lifecycle is action-driven, not a form field: Activate only from
draft; Retire unless alreadyretired; Delete allowed for any status. All gated on*.NamingSystem.write.
Screens
- List (
pages/NamingSystemList.tsx,resource-list) — a standardResourceList: ID (link → the form) · Name · Source · Kind (tag) · Status (tag).textContainssearch; Add (gated*.NamingSystem.write); per-row lifecycle actions — Activate (draft), Retire (unless retired), Delete (confirm). - Form (
pages/naming-system/NamingSystemForm.tsx) — a standardResourceFormwith declarativesections: ID (required; immutable on edit viamodeOverride:'view'), Name (localized, required), Kind (select), Code system (referenced CS id), Source, Description (textarea), and a repeatable Identifiers editor (type select + value input + preferred star + add/remove). Create + edit;POSTthen return to the list. Same load-gated mount as Defined Properties (ResourceFormseeds once fromdataController.current).- Validation: ID + name + kind required;
codeSystemrequired whenkind === 'codesystem'.
- Validation: ID + name + kind required;
Routes & nav
/naming-systems (list), /naming-systems/new, /naming-systems/:id/edit. Nav: Terminology → Naming Systems (tedyMenu.ts).
Privileges
*.NamingSystem.write gates Add / edit / activate / retire / delete. Read is open.
i18n
tedy.namingSystem.* (fields.*, kind.*, status.*, list.*, validation.*, notifications.*, identifier labels).
TEDY.12 — List bulk actions (CS / VS / MS)
State: Built — multi-select bulk actions on the main Code System / Value Set / Map Set lists and on a Space’s linked CS/VS/MS tabs.
Template: tool-feature. Prefix: TEDY.
Overview
An enhancement over the reference editor (whose lists had only per-row actions): the CS/VS/MS lists gain a Select mode with a Gmail-style floating bulk-action bar, so lifecycle operations run across a selection. Built on ResourceList’s native selection config (a header Select toggle adds the checkbox column; bulkActions renders the bar while ≥1 row is selected).
Actions
Each action applies to the eligible subset of the selection (by each row’s latest-version status) and reports success/failure counts (Promise.allSettled):
- Activate — the selected
draftrows →changeXVersionStatus(id, latestVersion, 'active'). - Retire — the selected
activerows →…'retired'. - Add to release —
draft+activerows → a draft-release picker modal (GET /releases?status=draft) →POST /releases/{id}/resources{resourceType, resourceId, resourceVersion, resourceNames}per row. - Delete — every selected row →
deleteX(id)(danger confirm). - Unlink (Space tabs only) — every selected row →
unlinkResourceFromSpace(spaceId, type, id)(the space-context equivalent of delete; removes the link, not the resource).
Shared component
components/ResourceBulkActions.tsx — the bar content, parametrized so all four (main CS/VS/MS + the Space tab) reuse it:
{ resourceType:'CodeSystem'|'ValueSet'|'MapSet', items:BulkItem[], clear, changeStatus, remove, onUnlink?, onDone, canWrite, canMaintain, canRelease, canUnlink? }.
BulkItem { id, version?, status?, names? } — each list normalizes its own rows (CS/VS use lastVersion, MS uses versions[0]; the Space tab uses latestOf() across both). The release picker uses releaseClient (searchReleases / addResourceToRelease).
Wiring
- Main lists:
CodeSystemList/ValueSetList/MapSetListpassselection={{ selectedKeys, onChange, bulkActions }}. - Space tabs:
SpaceResourceListTabpasses the same plusonUnlink+canUnlink; the space query is decorated (lastVersionDecorated/versionsDecorated) so the Status column and bulk actions have a version/status.
Privileges
Activate/Retire gate on *.{Type}.write; Delete on *.{Type}.maintain; Add to release on *.Release.write; Unlink on the space write privilege (*.Space.write / {id}.Space.write). Each button renders only when its privilege is held.
i18n
tedy.list.bulk.* (activateN, retireN, addToReleaseN, deleteN, unlinkN, deleteConfirm, unlinkConfirm, addToRelease, selectRelease, done, partial, …).
Notes / limits
- The release success path is exercisable only with a draft release present; on a fresh backend create one first.
- Destructive confirms (activate/retire/delete/add) mutate real resources — there is no dry-run.
TEDY.13 — Releases
State: Built — list + TxResourceBar detail (Summary with Resources/Checks/Notes/Tasks · Metadata · Provenance · Comparison) + create. Detail via TxResourceBar.
Template: tool-feature. Prefix: TEDY.
Overview
A Release is a publication bundle that groups pinned resource versions (CodeSystem / ValueSet / MapSet) for coordinated review, validation, server sync, and release-notes generation. Served off /releases; numeric id; a draft → active → retired publication lifecycle. The add-to-release bulk action (TEDY.12) already writes into a release; this family adds the release’s own list + detail.
Data model (libs/tedy/src/lib/client/releaseClient.ts — extend)
Release { id?:number, code?, names?:LocalizedName, planned?, releaseDate?, status?, authors?:string[], resources?:ReleaseResource[], terminologyServer? }.
ReleaseResource { id?:number, resourceType?:'CodeSystem'|'ValueSet'|'MapSet', resourceId?, resourceVersion?, resourceNames?:LocalizedName }.
ReleaseSearchParams extends QueryParams { textContains?, status? } (already present).
REST API (base /releases)
- Release:
GET /releases(search),GET /releases/{id},POST /releases(create) /PUT /releases/{id}(update). - Resources:
GET /releases/{id}/resources,POST /releases/{id}/resources(add — already used by TEDY.12),PUT /releases/{id}/resources/{rid},DELETE /releases/{id}/resources/{rid}. - Lifecycle:
POST /releases/{id}/draft·/activate·/retire. - Sync / notes:
POST /releases/{id}/server-sync {resourceId}(→ job),POST /releases/{id}/validate-sync(→ job),POST /releases/{id}/generate-notes;GET /releases/{id}/notes,GET /releases/{id}/notes/{file}. - Provenance:
GET /releases/{id}/provenances.
Screens
- List (
resource-list) — a standardResourceList: Code (link → summary) · Name · Status (tag) · Planned · Release date · Authors. Search (textContains); Add (gated*.Release.write). - Detail — via
TxResourceBar(unversioned “Management” resource, no version chip), tabs:- Summary — status + lifecycle actions (Make draft / Activate / Retire) only (the release’s own metadata lives on the Metadata tab, not here); a Resources table (Type · Reference → resource summary · Version · Name · Errors = failing checklist count) with Add resource (draft) and a per-row … menu (Edit / Delete, draft-gated); an Unaccomplished checks widget (failing checklist assertions grouped by resource + Run all checks →
POST /checklists/assertions/run-checks); a Notes widget (Generate notes + downloadable attachments via/releases/{id}/notes); and a Tasks widget (ResourceTasksWidgetscoped to the release). Add/Edit resource = a modal with resource-type → resource autocomplete (searchable CS/VS/MS) → version select for the chosen resource. - Metadata (edit) —
ResourceForm: code (immutable on edit), name, planned, releaseDate, authors, terminologyServer. - Provenance — the release’s provenance history (reuse the provenance widget).
- Comparison (diff) — per-resource release diff (current vs comparable server), driven by the sync/diff endpoints.
- Summary — status + lifecycle actions (Make draft / Activate / Retire) only (the release’s own metadata lives on the Metadata tab, not here); a Resources table (Type · Reference → resource summary · Version · Name · Errors = failing checklist count) with Add resource (draft) and a per-row … menu (Edit / Delete, draft-gated); an Unaccomplished checks widget (failing checklist assertions grouped by resource + Run all checks →
Business rules / constraints
- Resources are editable only while
draft(Add / edit / delete resource).active/retiredlock the resource set. - Server-sync requires
status === 'active'+ aterminologyServer+{id}.Release.maintain. - Lifecycle: Make draft (from non-draft), Activate (from draft), Retire (unless retired).
- Add-to-release picks a draft release (see TEDY.12).
Routes & nav
/releases (list), /releases/new (create), /releases/:id/{summary|details|provenances|diff}. Nav: Management → Releases. Add release to TxResourceType / TYPE_SEGMENT / TYPE_LABEL / tabsFor() in TxResourceBar, a TEDY_CONTEXT_META marker (RL), and ReleaseResourceLayout.MODES.
Privileges
*.Release.read (list/view), *.Release.write (add/edit/resources), {id}.Release.write (edit a specific release), {id}.Release.maintain (server-sync).
Phased implementation plan
- Data layer — extend
releaseClient(loadRelease, saveRelease, loadReleaseResources, updateReleaseResource, deleteReleaseResource, changeReleaseStatus, serverSync, validateSync, generateNotes, loadReleaseProvenances) +useReleases/useReleasehooks + exports. - List (
ReleaseList) + route + Management menu entry. - Detail shell —
TxResourceBarreleasetype +ReleaseResourceLayout(Summary/Metadata/Provenance/Comparison) + routes. - Summary — metadata card + resources table (add/edit/delete, draft-gated) + lifecycle actions.
- Metadata create/edit
ResourceForm+/releases/new. - Provenance tab (reuse provenance widget) and Comparison (diff) tab; checks / notes / tasks widgets.
Commit + git push origin master:main per phase.
i18n
tedy.release.* (fields.*, status.*, list.*, resource table labels, lifecycle labels, sync/notes labels, validation, notifications).
TEDY.14 — Global Search
State: Built — cross-resource search dashboard (Concepts · Code Systems · Value Sets · Map Sets · SNOMED CT) with space quick-filters + a publisher/code-system filter panel.
Template: tool-feature. Prefix: TEDY.
Overview
Global Search fans one textContains query out across every terminology resource kind and presents grouped result cards with links. A faithful port of the reference editor’s global-search dashboard. Reached at /global-search.
Result groups (privilege-gated, run in parallel)
- Concepts — cross-code-system concept search,
GET /ts/concepts(searchAllConcepts), scoped by the filter’scodeSystemlist. Columns: Code (→ the code system’s Concepts tab) · Term (the matching designation — first designation whose name contains the query, else preferred, else code) · Code System. Gated*.CodeSystem.read. - Code Systems —
GET /ts/code-systems(useCodeSystems),textContains+spaceId+publisher. Columns: Id · URI · Name. Gated*.CodeSystem.read. - Value Sets —
useValueSets, same filters. Gated*.ValueSet.read. - Map Sets —
useMapSets, same filters. Gated*.MapSet.read. - SNOMED CT —
GET /snomed/concepts?term=(searchSnomedConcepts), only when the query is ≥3 chars. Columns: Concept id · Term (FSN/PT). Gatedsnomed-ct.CodeSystem.read+ a configured SNOMED integration.
Each group renders only when it has rows; a combined empty state shows when all are empty. Result limit 100 per group.
Filters
- Space quick-filter buttons — the
globalSearch-flagged spaces (Space.globalSearch), sorted by code, plus an All button; narrows CS/VS/MS byspaceId. - Filter panel (toggle) — Publisher (
TxConceptSelecton the publisher value set) applied to CS/VS/MS; Code Systems (multiselect) scoping the Concepts group; Clear all.
Data layer
client/conceptClient.ts→searchAllConcepts(params)(GET /ts/concepts).client/snomedClient.ts(new) →searchSnomedConcepts({term, limit})(GET /snomed/concepts) +SnomedConcepttype.- Existing
useCodeSystems/useValueSets/useMapSets/useSpacesfor the resource groups + quick-filters.
Screen
pages/GlobalSearch.tsx — an AppCard with the space quick-filters, a search input + filter toggle, the filter panel, and the five result groups (AppTable per group). Reached from the app search entry / /global-search.
Privileges
Per-group read privileges as listed above; the dashboard itself is open.
i18n
tedy.globalSearch.* (title, placeholder, all, filter, anyCodeSystem, concepts, snomed, col.*).
Notes / limits
- The Concepts group shows the matched designation in a Term column (reference
findDesignationMatch). - SNOMED navigation opens the code system’s concepts context; a dedicated SNOMED concept viewer is a later concern.
TEDY.15 — Landing
State: Built — the app’s default home dashboard.
Template: tool-feature. Prefix: TEDY.
Overview
The Landing page is TEDY’s home dashboard, shown at the app root (/ and /landing). A faithful port of the reference editor’s landing page: per-resource count cards grouped into Resources · Modeling · Management, each card linking to the resource’s list. Every card is gated by the resource’s read privilege, and a group renders only when it has at least one visible card.
Cards
- Resources — Code Systems (
*.CodeSystem.read) · Value Sets (*.ValueSet.read) · Map Sets (*.MapSet.read). - Modeling — Structure Definitions (
*.StructureDefinition.read) · Structure Maps (*.StructureMap.read) · Implementation Guides (*.ImplementationGuide.read). - Management — Servers (
*.Server.read) · Spaces (*.Space.read) · Ecosystems (*.Ecosystem.read) · Releases (*.Release.read).
Each count is meta.total from the resource’s list search issued with limit: 0 (via the existing useCodeSystems / useValueSets / useMapSets / useStructureDefinitions / useStructureMaps / useImplementationGuides / useServers / useSpaceList / useEcosystems / useReleases hooks). Clicking a card navigates to that list.
Screen
pages/Landing.tsx — a title (Terminology editor) + the three grouped card grids. No new data layer (reuses the list hooks).
Routes & nav
/ and /landing both render Landing (root previously redirected to /code-systems). Nav: General → Home (tedyMenu.ts), beside Global search.
i18n
tedy.landing.* (title, home, resources, modeling, management).
Notes / limits
The reference landing also shows Wiki page metrics and created-by-me / assigned-to-me task lists. TEDY has no Wiki module, and the task search (GET /tm/tasks) is modelled only with context/statuses (no per-user params), so those sections are out of scope; a task-summary section can follow once task management lands.
TEDY.16 — Unified resource metadata (CS · VS · CM · SD)
State: Proposed — analysis + comparison + unification plan. Dataset parity required (no field may be dropped except the Permissions and Settings sections).
Template: tool-feature. Prefix: TEDY.
Why
The reference editor builds Code System / Value Set / Map Set (ConceptMap) / Structure Definition edit screens from one shared ResourceFormComponent (resources/resource/components/resource-form.component, i18n entities.resource.*) plus shared sub-components (Identifiers, Configuration attributes, Copyright, Permissions, Settings) — each resource type adds only its type-specific groups. TEDY currently hand-rolls a reduced per-type form (CodeSystemFormFields etc.), so the CS metadata dataset is materially smaller than the reference. This spec unifies TEDY on a shared ResourceMetadataForm and closes the dataset gap. Structure Map (SM) will adopt the same shape later.
Verified live on ehr-feed-category: the reference editor’s /resources/code-systems/ehr-feed-category/{details,edit} vs TEDY’s /tedy/code-systems/ehr-feed-category/details (+ the Add page).
Shared resource fields (the unified core — entities.resource.*)
Common to CS/VS/CM/SD, provided by the shared form:
| Field | Kind | Notes |
|---|---|---|
title | multi-language, required | per-language inputs + “Add translation” |
otherTitle | repeatable {language, name} | “Add other title” |
id | text, required in add, immutable on edit | id-change is a separate confirm modal |
name | text | computable name |
publisher | value-set select (publisher) + Custom free-text toggle | |
uri | text, required | |
description | multi-language | |
purpose | multi-language | |
topic | text + tags | |
useContext | repeatable {type, value} | |
sourceReference | text | |
replaces | resource search (same type) | |
experimental | checkbox | |
externalWebSource | text |
Shared sub-sections (also cross-type):
- Identifiers — repeatable {system, value} with “frequently used” quick-adds (TEDY already has
TxIdentifier). - Configuration attributes — repeatable, “often used” quick-adds.
- Contacts — repeatable.
- Copyright —
copyrightholder /jurisdiction(VS select) / statement. - Permissions (admin/editor/viewer/endorser) — excluded from TEDY (dataset-reduction exception; access is via
*.{Type}.*privileges). - Settings (review required / approval required) — excluded from TEDY (dataset-reduction exception).
Per-type extensions
- Code System —
content(VScodesystem-content-mode, required;supplementrevealsbaseCodeSystemsearch) ·caseSensitive(radio ci/cs, required) ·hierarchyMeaning·sequence(for concept code) · Designations table (code / uri / required / preferred / showInList / description / order) · Properties table (code / uri / type / required / showInList / description / order) · related ValueSet (“all-codes”) · version. Save =CodeSystemTransactionRequest {codeSystem, properties, valueSet, version}. - Value Set — compose (rule sets) + expansion + version (already modelled in TEDY.02).
- Map Set (ConceptMap) — scope (source/target) + associations + version (TEDY.03).
- Structure Definition — content (FSH/JSON) + contentType/contentFormat (TEDY.05, partial).
Difference lists (reference ↔ TEDY-current, CS ehr-feed-category)
View / details
TEDY shows: ID · URI · Name · Title (single) · Publisher · Content · Case sensitive · Base code system · Hierarchy meaning · Replaces · Sequence · Experimental · External web source · Other titles · Description · Purpose · Source reference · Properties (Name+Type only).
Missing in TEDY (present in dev):
- Title as multi-language (dev lists English/Estonian; TEDY shows one).
- Topic.
- Use context.
- Identifiers section.
- Configuration attributes section.
- Designations table (code/uri/required/preferred/showInList/description/order).
- Properties — dev shows uri/type/required/showInList/description/order; TEDY shows only name/type.
- Contacts.
- Copyright block (holder / jurisdiction / statement).
- Human-readable enum labels (dev “Complete” / “Case insensitive” vs TEDY raw
complete/ci). - (excluded) Permissions, Settings.
Edit
TEDY edit (CodeSystemFormFields) = the view set as inputs (ID, URI, Name, Publisher, Content, Case sensitive, Base code system, Hierarchy meaning, Replaces, Title, Other titles, Description, Purpose, Source reference, Experimental, External web source, Identifiers).
Missing vs dev edit: Topic · Use context · Sequence for concept code · Configuration attributes · Designations editor · Properties editor (rich table + “often used”) · Contacts · Copyright/holder/jurisdiction/statement · Publisher Custom toggle · multi-language Description/Purpose · (excluded) Permissions/Settings.
Add (create)
TEDY Add (CodeSystemCreate → same CodeSystemFormFields) has the identical reduced set → same gaps as Edit. Dev’s Add is the same component in add mode (full field set); id required + editable only in add.
Business logic & constraints (from the reference editor’s code-system-edit)
- One component serves view (
:id/details) / edit (:id/edit) / add viamode+viewMode. idrequired in add, immutable on edit (dedicated id-change modal).urirequired;titlerequired;contentrequired (defaultcomplete);caseSensitiverequired (defaultci).content === 'supplement'⇒ baseCodeSystem required/visible.canEdit={id}.CodeSystem.write.- Defaults on load:
copyright/permissions/settings/topic ??= {},identifiers/properties/configurationAttributes ??= []. - Save is a transaction:
{codeSystem, properties, valueSet (related), version}— CS edit can also create/refresh the “all-codes” ValueSet and a version. - Designations/Properties rows carry order + showInList + required/preferred flags; “often used” quick-add seeds common rows.
Implementation plan (phased; no dataset reduction except Permissions/Settings)
Progress — CS-first parity (done):
- Shared reusable editors built in
components/resource-meta/ResourceMetaEditors.tsx:TopicField,UseContextEditor,ConfigAttributesEditor,ContactsEditor,CopyrightFields(the building blocks of the eventualResourceMetadataForm). CodeSystemtype extended:topic,useContext[],configurationAttributes[],copyright{holder,jurisdiction,statement}(added to existingotherTitle,contacts,identifiers,properties).- CS edit/create (
CodeSystemFormFields) gained: Sequence, Topic, Use context, Configuration attributes, Contacts, Copyright (on top of the existing multi-language title/description/purpose, otherTitle, identifiers). - CS view (
CodeSystemMetadata) renders Topic, Use context, Configuration attributes, Contacts, Copyright, with human-readable Content/Case-sensitivity labels. - New fields persist automatically (part of the
CodeSystemobject in the transaction save).
Remaining (CS):
- Designations editor + rich Properties editor (code/uri/type/required/preferred/showInList/description/order) — the largest gap; currently properties are viewed as name/type and edited via the concept-presentation config.
- Publisher Custom free-text toggle (currently a value-set select).
Then (other types):
3. Extract the CS common block + resource-meta editors into one ResourceMetadataForm and adopt in VS / CM / SD (keeping type-specific groups, TEDY.02/.03/.05).
4. SM later (same shape).
UI may be reorganised (grouped sections/accordions) as long as no field is dropped except Permissions/Settings.
Data model additions
CodeSystem (and shared resource fields) need: otherTitle[], topic, useContext[], contacts[], copyright{holder,jurisdiction,statement}, configurationAttributes[], sequence, plus rich properties[] (uri/type/required/preferred/showInList/description/orderNumber) and designations[]. Additive/nullable; align field names with the terminology-server wire model.
See TEDY.01 Code System and open questions in TEDY-new-modules-open-questions.md.
id: TEDY.17 template: tool-feature module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/integration/import (dashboard + all importers)
- reference-editor/app/src/app/integration/import/file-import/file-analysis.service.ts
- reference-editor/app/src/app/resources/_lib/code-system/services/code-system-file-import.service.ts
- reference-editor/app/src/app/resources/_lib/value-set/services/value-set-file-import.service.ts
- reference-editor/app/src/app/resources/_lib/map-set/services/map-set-file-import.service.ts
- reference-editor/app/src/app/integration/_lib/model/integration-import-configuration.ts
- reference-editor branch feat/import-config-portable-file (portable config block)
- terminology-server/fileimporter (analyze + code-system/value-set/map-set process controllers)
- terminology-server job-logs (async job + poll)
- https://tx.example/tutorial/import author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.17 – File & Source Import (Common Spec)
Feature grouping
Overview / common spec for the Import family. Children: TEDY.17.1 Code System file import, TEDY.17.2 Value Set file import, TEDY.17.3 Map Set file import, TEDY.17.4 External source imports (ICD-10 · Orphanet · LOINC). This spec owns the shared import lifecycle, transport contract, job model, navigation (Integration dashboard), privileges, and mapping-reuse machinery; children do not duplicate them.
Description
TEDY imports terminology resources — Code Systems, Value Sets, Map Sets — from
uploaded files or source URLs, and imports a set of well-known external
terminologies (WHO ICD-10, Orphanet rare diseases, LOINC). All importers share
one backend surface under /file-importer/* (generic file import) plus dedicated
per-source endpoints (/icd10/import, /orphanet/import, /loinc/import/*), and
all report progress through the same asynchronous job log.
The importers live in a standalone Integration → Import dashboard (a left system menu with a routed panel per importer), and each resource list’s Add ▾ → Import action deep-links into the matching importer.
Import lifecycle (the shared spine)
Every file importer follows the same three steps; per-source importers use a subset (no analyze/mapping).
- Analyze (synchronous) —
POST {txapi}/file-importer/analyze,multipart/form-datawith partsrequest(JSON{ link, type }) and optionalfile. Returns{ columns: [{ columnName, columnType, columnTypeFormat, hasValues }] }. The server parses the file/URL and detects columns; CSV/TSV/XLSX parsing is server-side — the client never parses the file. - Map (client only) — the user assigns each detected column to a target (per-resource; see children). Reusable via saved mappings, templates, and a portable config file (below). No network calls.
- Process (asynchronous job) —
POST {txapi}/file-importer/{resource}/process,multipart/form-datawith partsrequest(JSON, the resource’s processing DTO) and optionalfile. Returns{ jobId }immediately. - Poll —
GET {txapi}/job-logs/{jobId}every 3 s untilexecution.status !== 'running'(client timeout window ~60 s, cancellable). Result is a JobLog{ errors[], warnings[], successes[] }. On errors/warnings the importer surfaces them inline and offers a downloadable log; on a successful non-dry-run it shows a success toast linking to the created/updated resource.
Transport contract (must match exactly)
- Multipart parts are named
request(a JSON string) andfile(the blob, sent with filenamefiles). Thefilepart is omitted when importing from a URL (link/url/sourceUrlcarries it and the server fetches). releaseDateis serialized to a date string before send.- CS/VS controllers URL-decode the
requestpart before JSON parsing; TEDY sends plain JSON (tolerated). Map Set uses the field nameurl(notlink). - Auth: every endpoint is privilege-guarded (see Privileges). The client attaches the ambient session/bearer to each request (TEDY dev uses the guest superuser).
Endpoints
| Purpose | Endpoint | Method | Body | Async |
|---|---|---|---|---|
| Analyze (column detect) | /file-importer/analyze | POST | multipart request {link,type} + optional file | no |
| CS file import | /file-importer/code-system/process | POST | multipart request (CS DTO) + optional file | job |
| VS file import | /file-importer/value-set/process | POST | multipart request (VS DTO) + optional file | job |
| MS file import | /file-importer/map-set/process | POST | multipart request (MS DTO) + optional file | job |
| MS CSV template | /file-importer/map-set/csv-template | GET | — (application/csv) | no |
| Association import | /file-importer/association/process | POST | multipart request + file | job |
| ICD-10 import | /icd10/import?url= | POST | IntegrationImportConfiguration JSON | job |
| Orphanet import | /orphanet/import | POST | multipart request (config) + optional file | job |
| LOINC list archives | /bob/objects?container=loinc | GET | — | no |
| LOINC archive files | /loinc/archives/{uuid}/files?language= | GET | — | no |
| LOINC upload zip | /bob/objects | POST | multipart container,file,meta | no |
| LOINC import | /loinc/import/from-archive | POST | JSON {archiveUuid, version, language, fileMap?} | job |
| Job log fetch | /job-logs/{jobId} | GET | — | — |
(ATC /atc/import and UCUM /api/v1/ucum/essence/import exist in the reference
editor and are out of scope for this family; see TEDY.17.4 notes.)
Source formats
- Code System:
csv,tsv,xlsx,json. For XLSX the concept worksheet must be namedconcepts; CSV delimiter may be;or,; UTF-8; header on line 1. - Value Set:
csv,tsv(the reference DTO also listsjson,fsh). - Map Set: CSV only, against the downloadable template.
Navigation / Information architecture
- Route family under Integration → Import (e.g.
/tedy/integration/import/...):code-system,value-set,map-set, and (TEDY.17.4)icd-10,orphanet,loinc. A dashboard shell renders a left system menu grouped by section (generic file importers · LOINC · ICD-10 · Orphanet) and a routed panel on the right, mirroring the reference editor’s integration dashboard. - Each importer panel is a card with a breadcrumb header, the importer form, and an inline JobLog result area; global success/error via toast.
- Deep links: the existing per-resource
Add ▾ → Import(CS/VS/MS lists) routes to the matching importer, prefilling the target resource when known.
Mapping-reuse machinery (CS importer; see TEDY.17.1)
All three are client-side and carry over from the reference editor:
- Saved mappings — per-Code-System-id column mappings in
localStorage(keyhelex.tedy.code-system-file-import.mapping:{csId}), with save/load. - Predefined templates — JSON presets under
assets/file-import-templates/{id}.json(+index.json), selectable from a dropdown; applied over the analyzed columns. - Portable config file — a machine-readable JSON block delimited by
===TEDY-IMPORT-CONFIG===/===END-TEDY-IMPORT-CONFIG===,tedyImportConfig: 1, carrying{ codeSystem, codeSystemVersion, sourceFormat, sourceType, rows[] }. It is prepended to the downloaded log so one file doubles as a reusable config; a Load config control reads it client-side (FileReader) and prefills target CS + version + source format + column mapping (mapping applied after the next analyze).
Privileges
- Analyze, CS process, VS process, MS CSV template, ICD-10, Orphanet, LOINC:
CodeSystem.write(CS_WRITE). - Map Set process:
MapSet.read(MS_READ) in the reference backend (noted; TEDY follows the backend’s guard). - Import entry points and the dashboard are privilege-gated; in TEDY dev the guest
profile is a superuser (
['*']), so all are visible.
Data layer (TEDY)
libs/tedy/src/lib/client/fileImportClient.ts—analyzeFile,processCodeSystemImport,processValueSetImport,processMapSetImport,downloadMapSetTemplate.libs/tedy/src/lib/client/integrationImportClient.ts—importIcd10,importOrphanet,listLoincArchives,listLoincArchiveFiles,importLoincFromArchive,uploadBobObject.libs/tedy/src/lib/client/jobClient.ts+hooks/useImportJob.ts— the 3 s poll loop as a hook ({ running, log, error }, timeout + cancel).libs/tedy/src/lib/types/fileImport.ts,types/integrationImport.ts— DTOs modeled on the server request objects (richer than the reference frontend’s: CS carriesspace,spacePackage,autoConceptOrder,valueSetProperties,admin,endorser, etc.) so no capability is dropped.
Shared UI
SourcePicker(link/file toggle + format),JobLogPanel(errors/warnings/ successes + download),ImportOptions(dryRun/clean/replace/generateValueSet),ColumnMappingTable(CS/VS). New pieces vs. existing TEDY: mapping table, JobLog viewer, MS scope editor (TEDY.17.3), LOINC slot mapper (TEDY.17.4). Reused from existing TEDY: resource searches, semantic-version select, publication-status select, multi-language inputs.
Testing
Baseline (what exists upstream)
- Reference editor (frontend): no import tests. The whole Angular app carries
only 3
*.spec.tsfiles, none touching import; there is no test runner wired for the app. So there is no frontend suite to port and none to lose by omission. - terminology-server (backend): well covered for CS. Spock tests under
fileimporter/—CodeSystemImportServiceTest,…FileImportServiceDryRunTest,…FileImportMapperTest,…FileImportProcessorTest,…FileImportLargeFileTest, and theCsv/Tsv/Xlsx FileParserTestparsers. No VS/MS file-import tests on the backend. TEDY relies on this suite for parsing, dry-run diffing, and mapping correctness — TEDY does not re-test server behavior.
TEDY strategy
Add targeted unit tests for the pure-logic seams the port introduces (the parts
that were never covered upstream), plus light component smoke tests. Tooling:
Vitest (jsdom, globals) + @testing-library/react, co-located
*.test.ts(x). The pure-logic pieces live in @helex/tedy, which now carries a test-only
vite.config.mts (jsdom/globals, src/test-setup.ts) mirroring @helex/ui.
@helex/tedy is source-aliased, not a registered Nx project, so the invocation is
npx vitest run from libs/tedy (not nx test). The lib’s test config
deliberately carries no env/proxy config — prefixing + the /txapi proxy stay
in the module’s vite.config.ts (TEDY.18). Each build phase’s acceptance includes
new unit tests green + tsc clean.
Unit tests (high value — pure logic)
useImportJobpolling —start → running → finishedtransition on the 3 s interval (fake timers); resolves the JobLog onceexecution.status !== 'running'; honors the ~60 s timeout window; cancel/unmount stops polling (no furtherjob-logscalls); surfaces a job/HTTP error. MockjobClient.- Column-mapping validation (CS) — at most one
identifier;date/dateTimerows requirepropertyTypeFormat; duplicateconcept-code/hierarchical-conceptwarns; onlyimport-checked rows are considered. - Portable config —
parseConfigextracts the delimited===TEDY-IMPORT-CONFIG===block, falls back to whole-file JSON, returnsundefinedon invalid;buildConfigBlockround-trips;applyMappingRowsmerges saved rows onto analyzed columns bycolumnName(unmatched columns untouched). - Request builders — the multipart shape: parts named exactly
request(JSON) +file(filenamefiles);fileomitted in link mode;releaseDateserialized;properties[]includes onlyimportrows; Map Set usesurl(notlink); DTOs dropundefineds. - Default derivation — property-name list = defaults (
concept-code,hierarchical-concept,is-a) + defined + CS properties, weight-ordered; type defaulted from the chosen property.
Component tests (light — RTL smoke)
- CS importer: analyze mocked → mapping table renders one row per column;
Process disabled until validation passes;
JobLogPanelrenders errors/warnings/ successes and the download affordance. Keep minimal — no real network, no CSV parsing (that’s server-side).
Out of scope for TEDY tests
Actual CSV/TSV/XLSX parsing and end-to-end job execution (backend suite); heavy
widget internals; the external-source server fetch/parse (ICD-10/Orphanet/LOINC) —
covered by manual verification against htx.helex.dev/lmb per child acceptance.
Open questions
- Predefined templates: seed set + where the
assets/file-import-templateslive in the TEDY build (module assets vs@helex/*). - Real privileges once TEDY auth moves off the dev guest superuser (see
docs/TODO.mdauth item).
id: TEDY.17.1 template: tool-feature module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/integration/import/file-import/code-system/csv/code-system-file-import.component.ts
- reference-editor/app/src/app/integration/import/file-import/code-system/code-system-file-import-form.component.ts
- reference-editor/app/src/app/resources/_lib/code-system/services/code-system-file-import.service.ts
- reference-editor/app/src/app/integration/import/file-import/file-analysis.service.ts
- terminology-server/fileimporter/codesystem/CodeSystemFileImportController.java
- terminology-server/fileimporter/codesystem/utils/CodeSystemFileImportRequest.java author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.17.1 – Code System File Import
Feature grouping
Child of TEDY.17 – File & Source Import (Common Spec), which owns the analyze→map→process→poll lifecycle, transport, job model, IA, and reuse machinery. Siblings: TEDY.17.2 VS, TEDY.17.3 MS, TEDY.17.4 External.
Description
Import concepts into a Code System from a csv / tsv / xlsx / json file (or a
source URL). This is the richest importer: after analyze, the user builds a
column → property mapping and sets version + run options, then processes the
import as a background job. The result (a validation diff on dry-run, or the applied
changes) is shown as a JobLog.
Screen flow
Single scrollable form (progressive disclosure); a card with a breadcrumb header
(Import → Code System) and a footer action row.
- Destination Code System — existing (autocomplete search) or new. New reveals: resource id, title (multi-language, required), URI (required), description (multi-language), and a supplement toggle → base Code System + base version.
- Version — existing draft version select, or new: semantic version number, status (publication-status select), release date; for a supplement, the base/supplement version.
- Source — type radio link | file; format select
csv|tsv|xlsx|json; the URL input or the file input accordingly. Analyze button. - Column mapping (after analyze) — one row per detected column (see model). Toolbar: predefined template select, saved mappings save/load, Load config (portable file). A validation summary blocks Process on errors.
- Options —
generateValueSet(Yes/No),dryRun(validate vs import),cleanVersion(delete concepts / hold),replaceConcept(replace / merge). - Process — runs the job; JobLog panel shows errors/warnings/successes; a log download is offered on issues, success toast (with a link to the CS) on a committed run.
Column-mapping model
Each analyzed column becomes an editable row (FileImportPropertyRow):
| Field | Meaning |
|---|---|
columnName | source column (from analyze; read-only) |
propertyName | target: concept-code, hierarchical-concept, is-a, or a defined/CS property; new-property allowed (_newProp) |
propertyType | from the concept-property-type value set, or designation |
propertyTypeFormat | required for date/dateTime (format string) |
propertyCodeSystem | for code/Coding values — the target system |
propertyDelimiter | multi-value split delimiter |
language | for designations (display:en etc.) |
preferred | single preferred designation (mutually exclusive across rows) |
import | include this column (defaults from hasValues) |
Property-name choices are concept-code, hierarchical-concept, is-a merged
with the defined properties and the CS’s own properties, weight-ordered
(concept-code → hierarchical-concept → display → definition → is-a first).
Selecting a name defaults its type from the property definition.
Client validation (before process): at most one identifier property;
date/dateTime rows require a format; warn on duplicate concept-code /
hierarchical-concept.
Request DTO (/file-importer/code-system/process)
multipart request JSON (modeled on the server DTO; TEDY sends the fields it sets):
- source:
type,link codeSystem { id, uri?, title?, description?, supplement? }(uri/title/… only for a new CS)version { number, status, releaseDate, supplementVersion? }properties: [{ columnName, propertyName, propertyType, propertyTypeFormat, propertyCodeSystem, propertyDelimiter, preferred, language }](only rows withimport)- options:
generateValueSet,dryRun,cleanVersion,replaceConcept
Server DTO additionally supports space, spacePackage, autoConceptOrder,
valueSetProperties, importClass, and extra CS metadata (publisher, oid,
contact, admin, endorser, externalWebSource) + version language,
algorithm, oid — TEDY types include these for forward-compat even if the v1 form
does not surface all of them.
Defaults
source.type = link (reference default; TEDY may default to file), format = csv,
dryRun = true, cleanVersion = false, replaceConcept = false,
generateValueSet = false.
Reuse machinery
Saved mappings (localStorage per CS id), predefined templates
(assets/file-import-templates), and the portable ===TEDY-IMPORT-CONFIG===
config block — all as defined in TEDY.17. Decision (this build): all three are
in v1.
Testing
Per the strategy in TEDY.17 → Testing. CS owns the
richest pure-logic surface, so it carries most of the unit tests: mapping
validation (single identifier, date/dateTime format required, duplicate
concept-code/hierarchical-concept), portable config parse/serialize +
applyMappingRows merge-by-columnName, the process request builder (only
import rows in properties[], multipart request+file shape, releaseDate
serialized), and property-name/default derivation ordering. Component smoke:
analyze mocked → mapping rows render, Process gated on validation. No test re-covers
server-side CSV/XLSX parsing.
Acceptance
- Analyze a csv/tsv/xlsx against an lmb CS → columns detected; map columns; dry-run → JobLog diff; real run → concepts imported, success toast links to the CS.
- New CS-import unit tests green (
nx test); component smoke passes. - Save a mapping, reload the page, load it back; export a config, load it into a fresh session and see CS/version/format/mapping prefilled after analyze.
tscclean; en/et/ru i18n in sync; verified againsthtx.helex.dev/lmb.
id: TEDY.17.2 template: tool-feature module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/integration/import/file-import/value-set/value-set-file-import.component.ts
- reference-editor/app/src/app/resources/_lib/value-set/services/value-set-file-import.service.ts
- reference-editor/app/src/app/integration/import/file-import/file-analysis.service.ts
- terminology-server/fileimporter/valueset/ValueSetFileImportController.java author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.17.2 – Value Set File Import
Feature grouping
Child of TEDY.17 – File & Source Import (Common Spec). Siblings: TEDY.17.1 CS, TEDY.17.3 MS, TEDY.17.4 External.
Description
Import Value Set members from a csv / tsv file (or URL). Simpler than the CS
importer: after analyze the user maps only two columns — concept code
(required) and display (optional) — into the version’s rule set, then processes.
Only a dryRun (validate vs. import) toggle; no clean/replace.
Screen flow
Progressive-disclosure form; card + breadcrumb (Import → Value Set).
- Destination Value Set — existing (search) or new (id, title multi-language required, URI required, description).
- Version — existing draft version select, or new: semantic version, status (publication-status), release date.
- Rule set — existing rule select (
codeSystem | version), or new: code-system search + code-system-version select. Concepts radio:allvsexact(default exact). Analyze + mapping appear only forexact. - Source + Analyze (when
exact) — source type radio link | file; format radiocsv | tsv; URL or file input; Analyze. - Column mapping (after analyze) — two selects over detected columns:
code(concept code, required) anddisplay(optional). - Footer —
dryRunradio (import-data vs validate-data), Process.
Request DTO (/file-importer/value-set/process)
multipart request JSON:
link,type(csv|tsv; server also acceptsjson|fsh)valueSet { id, uri?, name?, oid?, title?, description? }(uri/… for a new VS)version { number, status, releaseDate, rule { id?, codeSystem?, codeSystemVersionId? } }mapping { code, display }dryRun,importClass?
(UI-only, not on the wire: concepts all|exact, sourceType, file.)
Defaults
type = csv, sourceType = file, dryRun = true, concepts = exact; new version
status = draft.
Testing
Per TEDY.17 → Testing. VS-specific units: the
process request builder — mapping { code, display } (code required), the
version.rule shape (existing id vs new codeSystem+codeSystemVersionId), and
that the UI-only concepts/sourceType flags gate analyze/mapping but are not
required on the wire; reuse the shared useImportJob tests. Component smoke: the
code/display selects populate from analyzed columns and Process is gated on
code.
Acceptance
- For an existing VS draft version + a rule, analyze a csv, map code (+display), dry-run → JobLog, real run → members added under the version’s rule set.
- New VS-import unit tests green (
nx test). - Create-new VS + version + rule inline, then import.
tscclean; en/et/ru i18n in sync; verified againsthtx.helex.dev/lmb.
id: TEDY.17.3 template: tool-feature module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/integration/import/file-import/concept-map/concept-map-file-import.component.ts
- reference-editor/app/src/app/resources/_lib/map-set/services/map-set-file-import.service.ts
- reference-editor/app/src/app/resources/_lib/map-set/model/map-set-version.ts (MapSetScope)
- reference-editor/app/src/app/resources/_lib/map-set/containers/map-set-scope-form.component
- terminology-server/fileimporter/mapset/MapSetFileImportController.java author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.17.3 – Map Set File Import
Feature grouping
Child of TEDY.17 – File & Source Import (Common Spec). Siblings: TEDY.17.1 CS, TEDY.17.2 VS, TEDY.17.4 External.
Description
Import Map Set associations from a CSV that follows a fixed downloadable template — so there is no analyze step and no column-mapping UI. Instead the importer exposes a scope editor (source/target definitions) and two clean-run options. Source and target Value Sets “should be created beforehand.”
Screen flow
Card + breadcrumb (Import → Concept Map); file-only (no link/file toggle).
- Destination Map Set — existing (search) or new (id, title multi-language, URI, description).
- Version —
- existing draft version select, plus two MS-only clean radios:
cleanRun(delete existing associations / hold) andcleanAssociationRun(overlapping associations: ignore / keep-both); - or new version: semantic version, status, release date, scope editor.
- existing draft version select, plus two MS-only clean radios:
- File — a Download template link (
GET /file-importer/map-set/csv-template→mapset-template.csv) and a file input. - Footer — Process (no dryRun).
Scope editor (MapSetScope)
Per side (source / target): a 3-way sourceType/targetType radio —
code-system | value-set | external-canonical-uri:
code-system→ an editable table of{ codeSystem, codeSystemVersion }rows (sourceCodeSystems/targetCodeSystems, each a{ id, version, uri }reference).value-set→ value-set search + version select (sourceValueSet/targetValueSet).external-canonical-uri→ a URI input.
Equivalence (and source/target codes) are columns in the CSV template, not UI fields.
Request DTO (/file-importer/map-set/process)
multipart request JSON:
type,url(note:url, notlink)mapSet { id, title?, uri?, description? }mapSetVersion { status, version, releaseDate, scope: MapSetScope }cleanRun,cleanAssociationRun
Defaults
fileType = csv, cleanRun = false, cleanAssociationRun = false; new version
status = draft, scope = {}.
Notes / difference from CS·VS
No /analyze round-trip, no dryRun, no column mapping — the CSV template dictates
the columns (source/target code, equivalence, …). The scope editor is the added
complexity (an editable-table + resource searches). (An association importer —
interactive source/target/order mapping with separators, /file-importer/association/*
— exists in the reference editor and is out of scope for v1; note for later.)
Testing
Per TEDY.17 → Testing. MS-specific units: the
process request builder uses url (not link) and carries cleanRun /
cleanAssociationRun; MapSetScope serialization per side
(code-system/value-set/external-canonical-uri → the right
sourceCodeSystems/sourceValueSet/URI shape); reuse the shared useImportJob
tests. No analyze/mapping to test. Component smoke: the scope editor’s 3-way radio
switches the sub-form and the template-download link fires.
Acceptance
- Download the template; upload a filled CSV against an existing MS draft version with a defined scope → associations imported (JobLog).
- New MS-import unit tests green (
nx test). - Create-new MS + version + scope inline, then import.
tscclean; en/et/ru i18n in sync; verified againsthtx.helex.dev/lmb.
id: TEDY.17.4 template: tool-feature module: tedy state: Built traces-from: [] source-refs:
- reference-editor/app/src/app/integration/import/icd-10/integration-icd-import.component.ts
- reference-editor/app/src/app/integration/import/integration-import.component.ts (shared base for server-fetch importers)
- reference-editor/app/src/app/integration/_lib/icd-10/service/integration-icd-lib.service.ts
- reference-editor/app/src/app/integration/import/file-import/code-system/orphanet/orphanet-import.component.ts
- reference-editor/app/src/app/integration/_lib/orphanet/service/integration-orphanet-lib.service.ts
- reference-editor/app/src/app/integration/import/loinc/loinc-import.component.ts
- reference-editor/app/src/app/integration/_lib/bob/service/bob-lib.service.ts
- reference-editor/app/src/app/integration/_lib/model/integration-import-configuration.ts author: TEDY bootstrap created: 2026-08-09 updated: 2026-08-09
TEDY.17.4 – External Source Imports (ICD-10 · Orphanet · LOINC)
Feature grouping
Child of TEDY.17 – File & Source Import (Common Spec). Siblings: TEDY.17.1 CS, TEDY.17.2 VS, TEDY.17.3 MS.
Description
Import three well-known external terminologies. Each is its own pattern, but all report through the shared job log (TEDY.17). Built after CS/VS/MS.
Shared request model — IntegrationImportConfiguration
Used by ICD-10 and Orphanet: { sourceUrl, uri, publisher, version, validFrom, validTo, codeSystem, baseCodeSystem, codeSystemName, codeSystemDescription, codeSystemVersionDescription, status, generateValueSet, cleanRun, cleanConceptRun }.
Static prefill factories exist for ICD-10 (int edition) and Orphanet.
1. WHO ICD-10 — server-side URL fetch
- UI: a single config form (the reference “integration import” base form):
Source URL (required), URI (required), Publisher, Version (required), Valid from
(required) / Valid to, target Code System (required), Base Code System, Code
System name (multi-language, required) + description + version description. A
Set default data button prefills WHO defaults (a hosted zip URL +
uri http://hl7.org/fhir/sid/icd-10, publisher “World Health organization”, version10, codeSystemicd10, name “ICD-10 WHO Edition”). Send request submits. - Endpoint:
POST /icd10/import?url={sourceUrl}with theIntegrationImportConfigurationJSON body. The server downloads and parses the zip atsourceUrl— no browser file upload, no column mapping. - Job: background; reference polls every 5 s (TEDY will use the shared 3 s
useImportJob).editionis a query context (int).
2. Orphanet — extends the CS file importer
- UI: the generic CS file-import form (destination CS + version + options) plus a source card (link | file). No column mapping (server knows the Orphanet XML schema).
- Endpoint:
POST /orphanet/import,multipartrequest(IntegrationImportConfigurationbuilt from the CS form:sourceUrl ← source.file,uri/codeSystem/codeSystemName/codeSystemDescription ← codeSystem.*,version ← codeSystemVersion.version,status,validFrom ← releaseDate, plusgenerateValueSet/cleanRun/cleanConceptRun) + optionalfile. - Job: background (shared poll). Tutorial id example:
orpha-rare-diseases.
3. LOINC — Bob archive + slot mapping (two-phase)
- UI: Version select (distinct versions across stored archives), Language
(
languagesvalue set), LOINC archive select (when >1 match), and — once an archive is chosen — a per-slot CSV select for each of 8 slots:parts(Parts),terminology(LoincPartLink_Primary),supplementary-properties(LoincPartLink_Supplementary),panels(Panels and forms),answer-list,answer-list-link,order-observation(LoincUniversalLabOrdersValueSet),translations(<lang>LinguisticVariant). Each slot lists the zip’s.csventries with a server-suggested one marked and a “(none — use server default)” option. Import opens a confirmation modal. - Upload (phase 1): the zip goes to the object store —
POST /bob/objects(multipartcontainer=loinc,file,meta {version, language}); version auto-detected fromLoinc_<version>.zip. List viaGET /bob/objects?container=loinc; inner entries viaGET /loinc/archives/{uuid}/files?language=. - Import (phase 2):
POST /loinc/import/from-archiveJSON{ archiveUuid, version, language, fileMap? }wherefileMapmaps slot → CSV entry (omit a slot to use the server default). Target Code System isloinc. - Job: background (shared poll).
Navigation
All three are panels in the Integration → Import dashboard’s left menu (grouped: LOINC · ICD-10 · Orphanet), per TEDY.17. ICD-10/Orphanet reuse the config/CS-form UI; LOINC is bespoke (archive picker + slot mapper + confirm modal).
Out of scope (noted for later)
ATC (same server-fetch pattern as ICD-10, int/est editions) and UCUM
(essence XML upload, modal) exist in the reference editor and are not part of this
family’s build.
Testing
Per TEDY.17 → Testing. External-source units:
ICD-10 — Set default data prefill (WHO defaults for the int edition) and the
/icd10/import?url= request builder; Orphanet — the IntegrationImportConfiguration
built from the embedded CS form (sourceUrl ← source.file, validFrom ← releaseDate,
etc.); LOINC — fileMap slot pruning (drop empty slots, undefined when all
default) and version auto-detect from Loinc_<version>.zip. Reuse the shared
useImportJob tests. Server fetch/parse is verified manually against
htx.helex.dev/lmb (no backend VS/MS/external import tests exist to lean on).
Acceptance
- ICD-10: Set default data → Send request → JobLog to completion;
icd10CS populated. - Orphanet: CS form + file/link → import → JobLog; CS created/updated.
- LOINC: upload a
Loinc_<v>.zip, pick slots (or defaults), confirm → JobLog;loincCS populated. tscclean; en/et/ru i18n in sync; verified againsthtx.helex.dev/lmb.
TEDY.18 — Configuration & environment variables
State: Built — env surface implemented. Prefixes VITE_/TX_/TEDY_/OAUTH_ are allow-listed in vite.config.ts; all vars are read + defaulted in src/config/tx.ts. Wired features consume them now; not-yet-ported services read the var but carry a TODO(TEDY.18) until the feature lands.
Template: tool-feature. Prefix: TEDY.
Why
The reference editor’s web app is configured entirely through a runtime web.env (Docker env), and its API through a server.env. TEDY, by contrast, is a federated module hosted inside the EMR shell, so most of the reference’s frontend configuration is either (a) already covered by a small set of VITE_* build/runtime vars, (b) owned by the host shell (auth, UI language, theming, branding), or (c) out of scope because the corresponding feature (FML editor, PlantUML, FSH/SUSHI “chef”, FHIR→UML, Swagger UI) is not ported.
This spec is the single place that:
- lists the full reference frontend config surface and what each var does;
- maps each var to its TEDY equivalent, or records why it is host-owned / out-of-scope;
- defines the recommended TEDY env list and neutral
.env.exampleentries.
Source of truth: the reference project’s public installation guide plus a live reference dev deployment’s web.env / server.env. Per repository policy, no real hostnames, secrets, or brand strings are reproduced here — example values use neutral *.example placeholders, real values live only in the gitignored local .env, and reference env-var names are shown with their brand prefix normalized to TX_ (e.g. the reference’s server-API var appears as TX_API).
Why TEDY vars carry a VITE_ prefix
Vite only exposes env vars prefixed with VITE_ to client code through import.meta.env; every other var in the environment is deliberately withheld from the browser bundle so co-located server secrets (DB passwords, API keys) can’t leak into shipped JS. Any value the frontend must read therefore has to start with VITE_ (or a configured envPrefix). The reference web.env vars need no such prefix because they are not read by Vite — the reference container injects them into a generated runtime config at startup, whereas TEDY reads its config at build/serve time through Vite.
1. Reference frontend config (web.env) — full surface
| Var | Purpose | Example value | TEDY disposition |
|---|---|---|---|
BASE_HREF | App mount path for relative URLs | / or /tedy | Build-time — host mounts TEDY under its route |
TX_API | Terminology-server API base | /api | ✅ Ported → VITE_TX_TARGET |
SWAGGER_URL | Swagger UI path | /swagger/ | ❌ Out of scope (no Swagger UI in TEDY) |
DEFAULT_LANGUAGE | Fallback UI language | en | ⚠️ Host-owned (@helex/i18n) |
UI_LANGUAGES | Selectable UI languages (JSON array) | ["en","et","lt","cs"] | ⚠️ Host-owned |
CONTENT_LANGUAGES | Multilingual content input languages (JSON array) | ["en","et","lt","ru","de","fr"] | 🟡 Candidate — drives TEDY’s localized-name editors |
EXTRA_LANGUAGES | Display-name overrides for extra content langs (JSON object) | {"cs":{"en":"Czech"}} | 🟡 Candidate (only with extra content langs) |
OAUTH_ISSUER | OIDC realm / issuer URL | dummy or realm URL | ⚠️ Host-owned auth |
OAUTH_CLIENT_ID | OAuth2 client id | dummy | ⚠️ Host-owned auth |
OAUTH_SCOPE | OAuth2 scopes at login | openid | ⚠️ Host-owned auth |
GUEST_DISABLED | Disable anonymous access | false | ⚠️ Host-owned auth |
SNOWSTORM_URL | SNOMED (Snowstorm) API base | https://snowstorm.example/ | 🟡 Candidate (TEDY reads SNOMED via backend /snomed; direct base only if deep-calling) |
SNOMED_BROWSER_URL | SNOMED browser deep-link base | https://snomed.example/ | 🟡 Candidate — TEDY has SNOMED search/global-search |
SNOMED_BROWSER_DAILY_BUILD_URL | SNOMED daily-build browser base | https://snomed.example/ | 🟡 Candidate (optional variant) |
FML_EDITOR | FHIR Mapping-Language editor service | /fml-editor | ❌ Out of scope (not ported) |
PLANT_UML_URL | PlantUML diagram service | /plantuml | ❌ Out of scope |
CHEF_URL | FSH/SUSHI/GoFSH compiler service | /chef | ❌ Out of scope (IG authoring) |
CHEF_FHIR_VERSION | FHIR version for the compiler service | 4.3.0 | ❌ Out of scope |
FHIR_UML_CONVERTER_API | FHIR→UML converter service | https://svc.example/fhir2uml/api | ❌ Out of scope |
SKIN / SKIN_URL | Named theme / external stylesheet | (named theme) | ⚠️ Host-owned theming |
BRANDING | Product name in header | (product name) | ⚠️ Host-owned (header is the EMR shell) |
Legend: ✅ ported · 🟡 candidate to add · ⚠️ host-owned (inherited) · ❌ out of scope (feature not ported).
2. TEDY’s implemented env surface
All client-exposed vars are read once in modules/tedy/frontend/src/config/tx.ts. The proxy target and base are read Node-side in vite.config.ts. Prefixes: VITE_ (kept for backward compatibility + language vars), TX_ (terminology-server side integrations), TEDY_ (the app itself), OAUTH_ (auth).
App (TEDY_ prefix)
| Var | Purpose | Default | Consumed | Legacy fallback |
|---|---|---|---|---|
TEDY_TARGET | Terminology-server API base (incl. api segment; differs per deployment, e.g. /api vs /lmb-api). Proxied /txapi → {target}. | https://tx.example/api | ✅ vite proxy | VITE_TX_TARGET |
TEDY_BASE_HREF | App mount path (Vite base). | /tedy/ | ✅ vite base | — |
TEDY_BRANDING | Product name in TEDY-owned chrome. | TEDY | ✅ document title (standalone) | — |
TEDY_MSDEVOPS_ENABLED | Offer the Azure DevOps git integration on a Space’s Metadata + Repository tabs. Unset/0 hides it. | 0 | ✅ MSDEVOPS_ENABLED | VITE_TX_MSDEVOPS_ENABLED |
Terminology-server integrations (TX_ prefix)
| Var | Purpose | Consumed | Legacy fallback |
|---|---|---|---|
TX_PUBLISHER_VS_URL | Publisher ValueSet canonical — resolves publisher display names. | ✅ | VITE_TX_PUBLISHER_VS_URL |
TX_PUBLISHER_CS_URL | Publisher CodeSystem canonical. | ✅ | VITE_TX_PUBLISHER_CS_URL |
TX_VIEWER_TARGET | Terminology eXplorer base — opens a resource’s web page + FHIR API from Servers → Resources. | ✅ | VITE_TX_VIEWER_URL |
TX_SNOWSTORM_TARGET | Snowstorm API base (direct calls; TEDY normally reads SNOMED via backend /snomed). | 🟡 exported; TODO | — |
TX_SNOWSTORM_BROWSER | SNOMED browser deep-link base. | ✅ Global Search SNOMED group | — |
TX_SNOWSTORM_DAILY_BROWSER | SNOMED daily-build browser base. | 🟡 exported; TODO | — |
TX_CHEF_TARGET / TX_CHEF_FHIR_VERSION | FSH/SUSHI compiler (not ported). | ⏸ TODO | — |
TX_PLANTUML_TARGET | PlantUML service (not ported). | ⏸ TODO | — |
TX_FHIR2UML_TARGET | FHIR→UML converter (not ported). | ⏸ TODO | — |
TX_SWAGGER_TARGET | Swagger UI (not ported). | ⏸ TODO | — |
Content / UI languages (VITE_ prefix, JSON-encoded)
| Var | Purpose | Default |
|---|---|---|
VITE_DEFAULT_LANGUAGE | Fallback content language. | en |
VITE_UI_LANGUAGES | Selectable UI languages. | ["en"] |
VITE_CONTENT_LANGUAGES | Languages offered in localized-name editors. | ["en"] |
VITE_EXTRA_LANGUAGES | Display-name overrides for extra content languages. | {} |
✅ VITE_CONTENT_LANGUAGES (+ VITE_EXTRA_LANGUAGES display names) feed every multilingual editor via components/TxLocalizedName (a thin AppLocalizedName wrapper injecting config/tx.ts’s CONTENT_LANGUAGE_OPTIONS). When the var is unset the editors keep their built-in default, so this is opt-in.
Auth (OAUTH_ prefix — host-owned in a deployment)
OAUTH_ISSUER, OAUTH_CLIENT_ID (exported for parity/dev), OAUTH_GUEST_DISABLED (false). ⚠️ Because OAUTH_ is allow-listed for the client bundle, never place OAUTH_CLIENT_SECRET (or any secret) in the frontend build environment — it would be exposed.
Legend: ✅ wired now · 🟡 read+exported, consumer wiring is a TODO(TEDY.18) · ⏸ feature not ported (config reserved).
3. Why the prefixes (and why not drop VITE_)
Vite exposes to the client only env vars matching envPrefix; everything else is withheld so co-located secrets can’t leak. We allow-list ['VITE_','TX_','TEDY_','OAUTH_']:
VITE_is retained so pre-existing local envs and the language vars keep working — the rename is non-breaking.TX_/TEDY_/OAUTH_are TEDY’s own, human-readable prefixes.- An empty prefix (
'') is forbidden — it would expose every var. Vite’s built-ins (import.meta.env.MODE/DEV/PROD/BASE_URL) are always present regardless of prefix.
4. Migration
Legacy names are still honored (via the VITE_ fallbacks in config/tx.ts and the Node-side proxy fallback), so nothing breaks on upgrade. To move an env file to the current names, use the copy-migrator (never mutates the source):
scripts/migrate-tedy-env.sh path/to/old.env # → old.env.migrated
scripts/migrate-tedy-env.sh path/to/old.env new.env # → new.env
scripts/migrate-tedy-env.sh path/to/old.env - # → stdout
Old → new mapping applied by the script:
| Legacy / reference name | Current name |
|---|---|
VITE_TX_TARGET | TEDY_TARGET |
BASE_HREF | TEDY_BASE_HREF |
BRANDING | TEDY_BRANDING |
VITE_TX_MSDEVOPS_ENABLED | TEDY_MSDEVOPS_ENABLED |
DEFAULT_LANGUAGE | VITE_DEFAULT_LANGUAGE |
UI_LANGUAGES | VITE_UI_LANGUAGES |
CONTENT_LANGUAGES | VITE_CONTENT_LANGUAGES |
EXTRA_LANGUAGES | VITE_EXTRA_LANGUAGES |
VITE_TX_PUBLISHER_VS_URL | TX_PUBLISHER_VS_URL |
VITE_TX_PUBLISHER_CS_URL | TX_PUBLISHER_CS_URL |
VITE_TX_VIEWER_URL | TX_VIEWER_TARGET |
SNOWSTORM_URL | TX_SNOWSTORM_TARGET |
SNOMED_BROWSER_URL | TX_SNOWSTORM_BROWSER |
SNOMED_BROWSER_DAILY_BUILD_URL | TX_SNOWSTORM_DAILY_BROWSER |
CHEF_URL | TX_CHEF_TARGET |
CHEF_FHIR_VERSION | TX_CHEF_FHIR_VERSION |
PLANT_UML_URL | TX_PLANTUML_TARGET |
FHIR_UML_CONVERTER_API | TX_FHIR2UML_TARGET |
SWAGGER_URL | TX_SWAGGER_TARGET |
GUEST_DISABLED | OAUTH_GUEST_DISABLED |
The full neutral template lives in modules/tedy/frontend/.env.example; real values live only in the gitignored local .env.
Host-owned / out-of-scope (not TEDY vars)
Theming (SKIN, SKIN_URL) and UI shell are the EMR host’s; OAUTH_SCOPE and the SSO realm/client are host auth. FML_EDITOR has no TEDY consumer and is intentionally omitted.
5. Backend config (context only — not owned by TEDY)
The reference API (server.env) is a separate deployment concern; TEDY only talks to it through VITE_TX_TARGET. For orientation, its surface covers: PostgreSQL connection + pooling (DB_URL, DB_*_PASSWORD, DB_POOL_SIZE), CORS/JVM options, web/API self-URLs (TX_WEB_URL, TX_API_URL), OAuth JWKS validation, Snowstorm base + branch (SNOWSTORM_URL, SNOWSTORM_BRANCH), object storage (MinIO BOB_MINIO_*), the compiler service URL, a GitHub App (id/name/client id/secret) for content publishing, SMTP for notifications, and a conformance-validator target. Secrets (DB/object-store passwords, GitHub client secret, SMTP credentials) and real hostnames are intentionally not reproduced. These are provisioned per deployment by whoever operates the terminology server; nothing here is a TEDY frontend build input.
Open items (TODOs the vars are reserved for)
Wired: SNOMED browser deep-link (Global Search), content languages (all editors via TxLocalizedName), branding (document title). Remaining:
- SNOMED daily-build browser (
TX_SNOWSTORM_DAILY_BROWSER) and direct Snowstorm (TX_SNOWSTORM_TARGET) — no consumer yet (TEDY reads SNOMED via the backend/snomed). - Not-ported services (
TX_CHEF_*,TX_PLANTUML_TARGET,TX_FHIR2UML_TARGET,TX_SWAGGER_TARGET) — consume when/if those features are ported.
TEDY new modules — open questions & risks
Consolidated from the research + first-slice implementation of the four new modules (TEDY.06 Implementation Guides, TEDY.07 Spaces, TEDY.08 Servers, TEDY.09 Ecosystems). Decisions marked DECIDE need product/back-end confirmation before the phased screens land.
Cross-cutting
- Non-
/tsbase paths. Servers (/servers), Ecosystems (/ecosystems), Spaces (/spaces), IGs (/implementation-guides) and the public/public/ecosystemsare served off the API root, not/ts/*. Verified working through the/txapiproxy on the dev backend (list endpoints return 200). Confirm the same holds in staging/prod edge config. (verified in dev) - Numeric vs string ids. Servers/Ecosystems/Spaces use numeric ids; IG uses a string id with versions addressed by string
version. The resource-canonical routing helpers used by CS/VS/MS assume a string id — the new modules use plain numeric-id routes instead. - Auth in dev is the guest superuser. Privilege gating is wired via
usePrivileged(), but everything is visible in dev. Real gating needs the tx OIDC wiring (tracked with the existing tx-Keycloak follow-up). - Naming. No committed code/i18n/comment contains the reference product names; specs use “the reference editor”/“the reference project”. Real backend URLs live only in the gitignored
.env.
Servers (TEDY.08)
- Two privilege families (
Server.*andSpace.*) both gate server actions in the reference (backend usesServer.*; route guards/edit affordances use{id}.Space.*). DECIDE whether the target auth model actually grants{id}.Space.*for server ids. - Load-for-edit needs
Server.write(GET /servers/{id}is write-gated). A read-only details view for aServer.read-only user must source from the list payload (public view, no secrets), notGET /{id}. The built read view usesGET /{id}(fine for the superuser dev); revisit for the phased edit/details split. - Secret masking round-trip. ✅ Implemented —
clientSecret/Authorizationheader values are masked to null on read and the edit form uses the_masked= “null means keep persisted” convention (ServerMetadataseeds_maskedon load and strips it on save). Verify against a server with real credentials that an unchanged edit preserves the secret. editfolded intodetails. TEDY convention has no/:id/editroute (edit is a toggle inside the details/metadata view). This deviates from the reference’s separate/edit— intentional; flag for reviewers.- Remote resource listing (
/servers/{id}/resources/{type}for non-current-installation servers) makes live paged FHIR calls — needs loading UI + tolerance of partial/empty results. exclusionshas no editor in the reference (round-trips via ecosystem import/export only) — treat as pass-through.
Ecosystems (TEDY.09)
- Servers dependency. The picklist + tag resolution need
GET /servers. If/serversis gated by*.Server.read(distinct fromSpace.read), aSpace.read-only user opening Ecosystems could get an empty/403 picklist — handle gracefully. formatVersionis effectively constant"1"(server-defaulted, read-only) — kept in the model/form for fidelity.- Public
ecosystem.jsonlink is/txapi/public/ecosystems/{code}— confirm the proxy forwards/txapi/public/*and that it’s genuinely unauthenticated in the deployed env.
Spaces (TEDY.07)
- Context shell → TxResourceBar (decided, built). The reference uses a matrix-param context shell (
;s=;p=;v=) with a package/version drawer. TEDY instead renders the space throughTxResourceBar; resource tabs read the space’s linked resources, and Comparison/Repository are tabs. - Scope simplification (decided). Package-manager UI, SSG generator, ACL editor and wiki/GitHub import are out of scope; mdBook (SSG replacement) is a future concern. Metadata = Core + GitHub only. This is narrower than the reference on purpose.
- Resources map directly to the Space (built, point 6). A space’s CS/VS/MS are
PackageResources;searchCodeSystems({spaceId})returns exactly the linked set. “Add” links an existing resource (append aPackageResource+PackageTransactionRequest), never creates. The package/version layer is hidden behind an auto-managed default package (linkResourceToSpace/unlinkResourceFromSpace). - Zero-package default (resolved). When a space has no package, add-as-link creates a default package (
{code: space.code||'default', status:'draft'}) with a1.0.0version, then appends. The link picker filters against the space’s full linked set (not just the visible page) so already-linked resources aren’t offered. - Diff is async (Lorque), built.
GET /spaces/{id}/diff→ 202{id}; pollGET /lorque-processes/{id}untilfinished/failed;resultis base64 JSON.runSpaceDiffdrives start→poll→decode and surfaces the decodedfailederror in the tab. Note: on the dev backend the diff fails withTC105: Terminology server for current installation is not defined(no comparable server configured) — the error path is verified; the success path needs a server-configured space to exercise.POST …/sync(per-resource) is not implemented (out of the simplified scope). - GitHub tab = OAuth-redirect state machine (built).
authenticatemay return{isAuthenticated:false, redirectUrl}→ the tab shows Connect (hard redirect). MS DevOps is the identical component parametrized, feature-flagged (VITE_TX_MSDEVOPS_ENABLED). Default commit message is the neutral"update space". Note:GET …/github/statusis slow/blocks without a live OAuth session, so on the dev backend only the not-connected/Connect state is exercised; the status/push/pull/diff paths need a connected repo to verify end-to-end. textContainsspace search — TEDY’s list wires it; DECIDE whether the backend applies it server-side or it should be dropped.
Implementation Guides (TEDY.06)
- Version delete has no backend endpoint — the reference UI exposes it but the controller has none (would 405/404). Omit the delete action (do not port the client method as-is).
- Reference-web IG model has fields the backend ignores (
otherTitle,topic,useContext,sourceReference) — the built type includes only persisted fields. - GitHub push ignores per-file selection (server recomputes all changed files); the per-file checkboxes are cosmetic — DECIDE whether to keep the affordance.
- List delete privilege typo in the reference (
*.Implementation.maintain) — use the correct*.ImplementationGuide.maintain. - “Add FHIR” is a dead menu item in the reference — implement a FHIR-import flow or omit.
MapSetskipped on GitHub export (no PATHS entry) — expected or a gap? DECIDE.- No data in the dev backend — IG list verified empty (“No data”); rich rendering (versions/date/title) is unverified against live data.
Teagle test coverage matrix — reference explorer → helex-tx
Status of every test file of the reference terminology-explorer backend (98 files, 556 @Test
methods) against the migration (plan PHASE 3, P3.6). Dispositions:
- ported — scenario ported (
modules/teagle/backend/src/test, 22 classes / 191 methods, all green). A port covers the surviving semantics; scenarios tied to dropped infrastructure (fhirest plumbing, NPM packages, shared caches) are folded out and noted. - flint — pure operation semantics; ported into the Flint (
modules/tx-fhir) test suite on the in-memory store (see the Flint section at the bottom). - tx-ecosystem — behavior certified by the HL7 tx-ecosystem conformance suite that gates Flint (general mode 1194/1194); no in-repo port needed.
- n/a — infrastructure that is not part of the Teagle architecture (fhirest conformance bootstrap, NPM/IG package registry, shared HTTP caches, background enrichment) or made moot by it (remote body materialization replaced by delegation-to-owner); justification per row.
util
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| GlobMatcherTest (16) | ported | → util/GlobMatcherTest |
| EcosystemPatternMatchingTest (28) | ported | → util/GlobMatcherTest (pattern detection) + util/AuthoritativePatternTest (parser + matcher) |
| AuthoritativeUrlAndQueryParseTest (11) | ported | → util/AuthoritativePatternTest (query split, version pin, type-collection endpoint) |
| FhirResourceIdParserTest (10) | ported (partial) | Teagle’s minimal FhirResourceIdParser covered through RoutingCatalogTest.VersionSuffixedIds + MultiSourceExpandInstanceRoutingTest; the reference’s exotic id forms are out of scope |
| FhirVersionsTest (4) | n/a (deferred) | semver-wildcard version matching (1.x.x) — Teagle’s catalog resolves exact versions; revisit only if an ecosystem config needs wildcard ` |
| AdministrativeGenderRepro_Test (5) | flint | → engine/DisplayLanguagePrecedenceTest (display precedence repro) |
| SystemRequestContextTest (4) | n/a | fhirest request-context plumbing |
ecosystem
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| EcosystemConfigTest (23) | ported (partial) | → ecosystem/EcosystemConfigTest; Teagle adds server type (http/editable/local); npm/cache fields not modeled |
| EnvInterpolatorTest (7) | ported | → ecosystem/EnvInterpolatorTest |
| EcosystemControllerAuthoritativeTest (2) | ported | semantics → registry/ServerRegistryRoutingTest; the REST view is /api/servers |
| EcosystemExclusionsIntegrationTest (16) | ported (partial) | exclusion-before-authoritative → ServerRegistryRoutingTest |
| EcosystemConfigFactoryAutoDeriveTest (4) | n/a (architecture) | strategy auto-derivation (INLINE/CACHED/LOCAL) — Teagle implements INLINE delegation only; CACHED (local expansion of remotely-fetched full CodeSystems) is a possible optimization slice |
| EcosystemConfigFactoryAuthValidationTest (9) | ported | → ecosystem/EcosystemServerAuthTest (missing-field/unsupported-method failures at load time) |
| EcosystemServerAuthDeserializationTest (4) | ported | → ecosystem/EcosystemServerAuthTest (password/oauth credential forms, markdown access_info) |
| UpstreamAuthServiceTest (6, in upstream/) | ported | → upstream/UpstreamAuthServiceTest (Basic, oauth fetch+cache, scope form, invalidate/clearAll, TokenFetchException) |
| EcosystemReloadControllerTest (10) / EcosystemReloadServiceTest (9) | ported (core) | → ecosystem/EcosystemReloadTest (+ POST /api/ecosystem/reload): swap, removed-server catalog cleanup, malformed-config keeps previous registry |
| EcosystemSelfHealServiceTest (5) / EcosystemSelfHealRecoveryTest (1) | ported | → registry/ServerHealthTest (consecutive-failure threshold, immediate recovery) + POST /api/servers/health-check |
| RemoteEcosystemLoaderAuthStrippingTest (2) | ported | → ecosystem/RemoteEcosystemLoaderTest (federation merge, remote auth material stripped, fetch failure drops only the placeholder) |
routing / catalog
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| ResourceRoutingCatalogTest (25) | ported (partial) | → routing/RoutingCatalogTest: explicit/wildcard precedence, duplicates, removal; count/enrichment fields not modeled |
| ResourceRoutingCatalogFindByCanonicalUrlTest (3) | ported | → RoutingCatalogTest.FindByCanonicalUrl |
| ResourceRoutingCatalogActiveVersionSelectionTest (4) | ported | publication precedence (active > retired, then highest version) |
| ResourceRoutingCatalogReverseVersionStripTest (5) | ported | id--version / `id |
| ResourceRoutingCatalogPopulatorTest (37) | ported (core) | → routing/HttpCatalogScanTest: page-walk + register, explicit-vs-wildcard by authoritative pattern, rescan replaces, failed rescan keeps old entries |
| CanonicalResourceCatalogServiceBackoffTest (7) | ported | → HttpCatalogScanTest.retriesWithExponentialBackoffBeforeGivingUp |
| CanonicalResourceCatalogServiceUrlBuildTest (6) | ported (core) | → HttpCatalogScanTest (_count paging url, next-link following incl. foreign-host guard) |
| CanonicalResourceCatalogServiceLenientParseTest (2) | ported | → HttpCatalogScanTest.nonFhirBodyIsSkippedLeniently |
| AuthoritativeCatalogFiltersTest (4) | ported | → HttpCatalogScanTest.statusFilterInThePatternGatesExplicitRegistration |
| RouterRoutingServiceTest (9) | ported (partial) | id-level routing → MultiSourceExpandInstanceRoutingTest + catalog-routed remote reads (TeagleResourceController.remoteRead, verified live against the reference server) |
| CanonicalResolveControllerTest (10) | ported (core) | → api/CatalogControllerTest (/api/resolve: catalog row first, authoritative-pattern fallback, 404) |
| CatalogSnapshotStoreTest (4) | n/a (architecture) | catalog persistence — Teagle’s catalog rebuilds from the HTTP scan at boot; snapshotting is an optimization for very large registries, not scheduled |
operation — multi-source $expand
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| ValueSetMultiSourceExpandFilterTest (19) | ported (core) | → MultiSourceExpandFilterPagingTest |
| ValueSetMultiSourceExpandImplicitVsTest (8) | ported | → MultiSourceExpandImplicitVsTest |
| ValueSetMultiSourceExpandImportIncludeTest (1) | ported | → MultiSourceExpandImportIncludeTest (+ intersection semantics) |
| (merge core, no direct counterpart) | ported | → MultiSourceExpandMergeTest |
| ValueSetMultiSourceExpandUnresolvedUrlTest (4) | ported | → MultiSourceExpandDelegationGuardTest.UnresolvedUrl (tx-ecosystem 4xx/not-found shape; resolved-but-empty stays 200/0) |
| ValueSetMultiSourceExpandEmptyComposeDelegationTest (1) | ported | → MultiSourceExpandDelegationGuardTest.EmptyComposeDelegation |
| ValueSetMultiSourceExpandExternalImplicitDelegationTest (3) | ported | → MultiSourceExpandDelegationGuardTest.ImplicitFhirVs |
| ValueSetMultiSourceExpandImplicitImportDelegationTest (2) | ported | implicit VS as import reference → MultiSourceExpandImportIncludeTest.remoteOwnedCanonicalDelegatesWholeExpandToOwner (the import path re-enters expand, which owns the delegation) |
| ValueSetMultiSourceExpandSearchMissDelegationTest (1) / ValueSetExpandExactRouteMissFallsBackToDiscoveryTest (1) | ported | canonical-delegation on local miss → MultiSourceExpandImportIncludeTest.remoteOwnedCanonicalDelegatesWholeExpandToOwner |
| ValueSetMultiSourceExpandParseGuardTest (3) | ported (adapted) | request-side: unparseable body → 400 invalid (TeagleOperationController.dispatch, exercised via EditableServerCrudTest.unparseableBodyIsRejected + batch reject); upstream-side non-FHIR guard lives in Flint’s delegate (502) |
| ValueSetMultiSourceExpandBaseUrlGuardTest (3) | ported (adapted) | an http server with a blank target is dropped at load (EcosystemConfigTest.dropsHttpServerWhoseTargetInterpolatesToBlank) — the misconfiguration cannot reach request time |
| ValueSetMultiSourceExpandCatalogIdRoutingTest (2) | ported | → MultiSourceExpandInstanceRoutingTest (/ValueSet/{id}/$expand) |
| ValueSetMultiSourceExpandEditionPinTest (2) | ported | → MultiSourceExpandVersionPinTest (catalog pin, authored wins) |
| ValueSetMultiSourceExpandStaleVersionPinRetryTest (1) | ported | → MultiSourceExpandVersionPinTest (self-heal retry, only-our-pin stripped, non-version errors not retried) |
| ValueSetMultiSourceExpandActiveVersionSearchTest (5) | ported (adapted) | the local half → ActiveVersionSelectionTest (active-over-retired, archived resolves, explicit version honored). The remote half is moot: Teagle delegates by canonical url and the owner resolves its own active version |
| ValueSetMultiSourceExpandSummaryReadTest (2) / SummaryFalseTest (1) | n/a (architecture) | summary-body re-reads existed because the explorer materialized remote VS bodies; Teagle delegates the whole $expand to the owner (EmptyComposeDelegation covers the stub-body edge) |
| ValueSetMultiSourceExpandInlineDisplayFloorTest (3) | ported | → operation/InlineDisplayFloorTest — an aggregator-layer rule: the kernel’s language semantics are conformance-exact (an untranslated display legitimately drops), so Teagle backfills authored compose displays on both the fast path and the fan-out merge, never overwriting a resolved display |
| ValueSetMultiSourceExpandPropertyDecorationTest (4) / FallbackPropertyTest (2) / PropertyUriSanitizerTest (7) | flint | → engine/ExpansionPropertyTest (property decoration + declared-uri hygiene); overlaps the tx-ecosystem parameters suite |
| ValueSetMultiSourceExpandSupplementDesignationTest (5) / UpstreamSupplementDesignationTest (8) / SupplementDesignationPackageExpandValidationTest (2) | flint | → engine/SupplementDesignationTest (useSupplement designations on expand + validate); the upstream-supplement merge variant becomes relevant when supplements meet remote sources — noted as a Teagle follow-up |
| ValueSetMultiSourceExpandUpstreamErrorLogLevelTest (2) | n/a | log-level assertion of explorer plumbing |
| ValueSetMultiSourceExpandCompositeSnomedReproIT (1) / ValueSetActiveVersionSelectionLiveIT (4) | n/a (live IT) | live integration repros against specific upstreams; the behavior they pin is covered by the unit ports + the live 2-source verification against the reference server |
operation — router lookup/validate
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| RouterOperationServiceCatalogRoutingTest (1) | ported | → operation/CatalogFirstLookupRoutingTest (patterns first, catalog second, local store outranks, fail-closed) |
| RouterOperationServiceValidateCodeRoutingTest (6) | ported (core) | dispatch shared with lookup (TeagleOperationService.validateCode); VS-url / ?fhir_vs / coding-system routing branches; exercised via TeagleBatchControllerTest + ServerRegistryRoutingTest |
| RouterOperationServiceValidateCodeParameterTest (4) | tx-ecosystem | GET parameter typing + validate-code parameter semantics — conformance parameters/validation suites |
| RouterOperationServiceLookupParameterTypeTest (3) | tx-ecosystem | as above |
| RouterOperationServiceUnresolvedCanonicalTest (6) | tx-ecosystem | fail-closed unknown-system outcomes — kernel behavior under conformance (+ CatalogFirstLookupRoutingTest.unknownSystemStaysLocalAndFailsClosed) |
| RouterOperationServiceStrippedCodeSystemLookupTest (5) / HttpLookupEnrichmentTest (2) | n/a (architecture) | summary-stub re-fetch existed because the explorer materialized remote bodies; Teagle forwards the operation to the owner |
| RouterOperationServiceSupplementLookupTest (4) / UpstreamSupplementLookupTest (4) | flint | → engine/SupplementDesignationTest (supplement-aware $lookup) |
| RouterOperationServiceStaleVersionPinRetryTest (1) | ported | → MultiSourceExpandVersionPinTest.lookupPinsTheCatalogEditionAndSelfHealsAStalePin |
| BatchBundleOperationRoutingTest (5, in bundle/) | ported | → fhir/TeagleBatchControllerTest (POST /fhir batch → batch-response, per-entry status, one failure never fails the batch) |
search / info
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| CatalogSearchControllerTest (3), RoutedCanonicalSearchHandler{CombinedSearch(7),MultiVersion(4),ServerFilter(6)}, ResourceListContractTest (10), SearchTotalCountIndependentTest (0) | ported (core) | → api/CatalogControllerTest (/api/catalog): list-row contract (url + versions[] + status + target), text/type/server filters, multi-version grouping, page-cap with independent total |
| LoadStatisticsServiceTest (6), RouterLoadInfoControllerTest (1) | ported (core) | → api/CatalogControllerTest.statsCountPerServerAndSurfaceDuplicates (/api/catalog/stats: per-server/type counts + rejected duplicates) |
infrastructure not migrated
| Explorer test (methods) | Disposition | Notes |
|---|---|---|
| cache/* — FhirEtagFilterTest (22), TerminologyGenerationRegistryTest (11), TerminologyCacheInvalidatorTest (4), CacheInvalidationEventListenerTest (4), CacheManagementInvalidateEndpointTest (3), InMemorySharedCache (0) | n/a | shared HTTP cache/ETag layer (Caffeine/Redis) — not part of Teagle v1; the catalog version() counter is the revalidation hook if a cache layer is added |
| fhirpackage/* — PackageResourceReadInterceptorTest (15), PackageLoadReadinessTest (4), FhirPackageLoaderScanAllFilesTest (2) | n/a | NPM/IG package registry — replaced by the editable/local store model |
| admin/PackageReloadControllerTest (8) | n/a | package admin endpoint (/api/ecosystem/reload is the surviving reload surface) |
| alias/AliasPreloadBlocksReadinessTest (3) | n/a | fhirest alias preloading |
| enrichment/ResourceEnrichmentServiceTest (2), ValueSetBackgroundExpanderRequestContextTest (2) | n/a | background enrichment of catalog entries |
| conformance/RouterConformanceLoaderMultiVersionTest (1) | n/a | fhirest conformance bootstrap — Flint serves conformance |
| conformance/TerminologyCapabilitiesProviderTest (8) | ported | → MetadataAggregationTest |
New Teagle capabilities without an explorer counterpart
| Teagle test | Covers |
|---|---|
EditableServerCrudTest (7) | the NEW editable in-memory server: FHIR CRUD into Flint’s MutableResourceStore, catalog sync, authored content immediately expandable, delete fails closed, and conformance resources (putsAndReadsAStructureDefinition) served by the same path as terminology |
HttpCatalogScanTest.scansStructureDefinitionAndStructureMapListings | SD/SM are catalogued like any other canonical, and an authoritative pattern claims an SD (they share authoritative with CodeSystem) |
HttpCatalogScanTest.aServerRejectingStructureDefinitionSearchKeepsScanningTheOtherTypes | the live upstream answers an SD search with a not-supported OperationOutcome — one unsupported type must not cost the server’s other types |
HttpCatalogScanTest.theScannedTypeListIsConfigurable | teagle.catalog-scan.types trims the scan so unsupported types cost no retry budget |
MultiSourceExpandMergeTest.fullyLocalComposeStaysOnTheKernelFastPath | conformance-preserving local fast path |
| merge dedup nested-duplicate pruning | MultiSourceExpandMergeTest.duplicateSystemCodePairsAreMergedOnce — fixes a flaw the reference merge had (top-level-only dedup keys) |
Flint op-semantics ports (P3.6b, modules/tx-fhir/src/test — 58 methods green)
| Explorer test (methods) | Flint test | Covers |
|---|---|---|
| SupplementDesignation cluster (5+8+2) + SupplementLookup (4+4) | engine/SupplementDesignationTest | useSupplement designations on $expand / $lookup over the in-memory store (base designations always survive; a supplement of a different base never leaks) |
| PropertyDecoration (4) + FallbackProperty (2) + PropertyUriSanitizer (7) | engine/ExpansionPropertyTest | requested-property projection, declared CodeSystem property uris, missing values never evict members |
| AdministrativeGenderRepro (5) | engine/DisplayLanguagePrecedenceTest | display precedence across displayLanguage / Accept-Language, untranslated fallback |
After the port the full tx-ecosystem general-mode suite was re-run: 1194/1194 — the ported tests document kernel behavior, they do not change it.
Related: Ucumber (tx-ucum)
The reference server’s separate ucum module (14 methods, outside the explorer’s 556) is
migrated as modules/ucumber/backend — UcumApiTest (13 methods green) covers
validate/analyse/convert/canonicalise/commensurability/model listings over Flint’s UcumService;
the essence-import/export administration (DB-backed) is not carried.
Tally
- Explorer total: 98 files / 556 methods.
- ported: 61 files (fully, core, or adapted) → 22 Teagle test classes / 191 methods green
- 3 Flint engine test classes / 58 methods green (P3.6b).
- tx-ecosystem: 5 files subsumed by the conformance gate (1194/1194 general, re-verified after every Flint change).
- n/a: 25 files — 17 dropped infrastructure (caches, packages, aliases, enrichment, fhirest plumbing), 8 made moot by the delegation architecture or explicitly deferred (semver-wildcard routing, catalog snapshots, CACHED strategy, live-IT repros) — each with its justification above.
- Every one of the 98 explorer files is accounted for in the tables above.
Teagle ⇄ reference eXplorer (tx.helex.dev) — parity spec
Comparison of the deployed reference Terminology eXplorer (tx.helex.dev) with the current Teagle web, and
specs for the missing pages to add. Per direction: hold the existing Teagle pages (Servers, Catalog,
Expand playground, Authoring, Operations) and add the ones below.
Reference eXplorer — navigation (observed)
Menu: Dashboard (Landing page · Global Search) · Terminology (Code systems · Value sets · Concept maps ·
Structure definitions · Structure maps) · Summary (Topology). Header: logo → /landing, language, GUEST,
overflow menu, global search box. Resource detail opens at /fhir/{Type}/{id} (content-negotiated: browser → UI,
Accept: application/fhir+json → FHIR).
Page inventory & gap
| Reference page | Route | Teagle today | Action |
|---|---|---|---|
| Landing / Ecosystem dashboard | /landing | Servers (plain table) | ADD dashboard (richer); keep Servers |
| Global Search | (search box) | Catalog search only | ADD cross-resource search page |
| Code systems (list) | /terminology/code-systems | Catalog (unified CS/VS/CM tabs) | ADD per-type pages; keep Catalog |
| Value sets (list) | /terminology/value-sets | Catalog | ADD per-type |
| Concept maps (list) | /terminology/concept-maps | Catalog | ADD per-type |
| Structure definitions (list) | /terminology/structure-definitions | — (none) | DONE |
| Structure maps (list) | /terminology/structure-maps | — (none) | DONE |
| Topology | /terminology/topology | — (none) | ADD graph view |
| Rich resource detail | /fhir/{Type}/{id} | Catalog row → raw JSON | ADD rich detail (tabs + content) |
| FHIR API landing / dev hub | /fhir (text/html) | — (none) | ADD static hub (content-negotiated) |
| Operation Examples | (from /fhir hub) | Expand playground + Operations | REUSE existing (link/seed) |
| Health | (from /fhir hub) | — | ADD trivial status page |
| Expand playground | — | ✔ (Teagle-only, keep) | keep |
| Authoring (editable server) | — | ✔ (Teagle-only, keep) | keep |
| Operations console | — | ✔ (Teagle-only, keep) | keep |
Specs for the missing pages
1. Ecosystem Dashboard (Landing) — /teagle/ landing
Structure (observed): a Resources stat card row — Total Code Systems / Value Sets / Concept Maps / Structure
Definitions / Structure Maps (each a link to its list page); a Server types stat row — FHIR servers / NPM
package targets / IG package targets / Open (public) targets; then one card per server: name + type badge
(FHIR / NPM PACKAGE / IG), FHIR versions, Supported Operations chips ($expand/$lookup/$validate-code), and
per-type counts (CS/VS/CM/SD/SM). Top search box. Data: /api/catalog/stats (per-server + totals) + /api/servers
(kind, ops, fhir versions). Use @helex/ui stat/AppCard + ResourceList where tabular. Keep the current
Servers page as the detailed registry table; Dashboard is the overview landing.
2. Global Search — /teagle/search
A single search box that queries all catalogued resources across servers by url / id / title (and optionally
concept text), returning a merged ResourceList grouped or badged by resource type + owning server, row → detail.
Data: /api/catalog?text=… (extend with a cross-type ?q= if needed). Debounced; type/server facets.
3. Per-type resource lists — /teagle/terminology/{code-systems|value-sets|concept-maps|structure-definitions|structure-maps}
ResourceList per type: columns ID (link → detail) · Last version · Title · Publisher · Status
· Count (concept count for CS; expansion size for VS; mapping count for CM), expandable rows (versions),
title/id filter + funnel (server, status). Data: /api/catalog?type=CodeSystem etc. + /fhir/{Type}?url=.
SD/SM — DONE. The catalog scan indexes both types (HttpCatalogScanner.DEFAULT_TYPES, trimmable via
teagle.catalog-scan.types) and TeagleResourceController serves them, so the same generic list page covers
them. Their column set differs from the reference’s only where the reference itself differs: SD adds Type
(the constrained base type) and Kind, SM leads with Name instead of Title, and neither carries a
Count (a StructureDefinition “count” would mean pulling the whole snapshot per row).
4. Topology — /teagle/topology
A force-directed graph of the ecosystem: nodes = resources (colored by type CS/VS/CM/…), edges = references
(VS→CS via compose, CM→CS via source/target), grouped by owning server. Zoom (+/−), fit, layout toggle, filter
(type/server). Use a graph lib bundled self-contained (no external CDN).
Backend: DONE — GET /api/topology (implemented) returns the Cytoscape shape
{ catalogVersion, truncated, elements: { nodes:[{data:{id,label,resourceType,server}}], edges:[{data:{id,source,target,kind}}] } }. Nodes are one-per-canonical (type,url) from the catalog; edges are
derived server-side (ValueSet compose composes/imports, ConceptMap group source/target, *-scope), only
between catalogued nodes (delegated-but-uncatalogued systems e.g. SNOMED are omitted), cached per catalogVersion.
Node id is "{type}|{bare-url}"; the frontend just renders the elements.
5. Rich resource detail — /teagle/fhir/{Type}/{id} (replaces the raw-JSON view)
Tabs Description | Concepts/Expansion/Mappings/Elements | JSON | XML (content-negotiated fetches). The header shows
the title in the UI language (resolved over the _title translation extensions). Description stacks, in
this order (same for every resource type): the other titles the resource carries, one ET / EN-chipped
line each → a metadata card (Official URL, Version, status/date, Computable Name, Flags,
Server source badge — which registry server served it) → Definition (the localized description) →
Properties (CS property declarations: Code / Description / Type with its ValueSet-or-CodeSystem binding /
URI) → Relations (CS “used in value sets”; VS “includes systems”; CM source/target; SD base definition +
the profiles its differential references; SM its structure[] and import[]) → the sanitized
Narrative (text.div) last, since it only restates the above. The content tab holds — for CS: a concept
table (Code / Display with multi-language designations / Properties) with search + total; for VS:
the merged expansion (reuse the Expand playground’s source-colored ResourceList); for CM: the
mappings table (source → target, relationship); for SD: the element tree
(FhirStructureDefinitionViewer from @helex/fhir — the same viewer TEDY uses: merged
snapshot + differential, slices as children, Σ/?!/C flags, cardinality, types with their target profiles,
binding links, and its own diff/hybrid/snap control). SM has no content tab — its groups and rules are the
body, which the JSON tab already shows, and the reference renders nothing for maps either.
Data: /fhir/{Type}/{id} (+ ?_format), and the ops the resource supports.
6. FHIR API landing / developer hub — /teagle/fhir (static GET /fhir as text/html)
The reference serves a developer-facing docs page at /fhir (content-negotiated: browser → this HTML hub,
Accept: application/fhir+json → the FHIR endpoint itself). It is the API entry point, structured as:
- Intro — “FHIR R5 terminology endpoint with content negotiation”; the Base URL for clients
(
…/fhir) shown as a copyable code span; one-line “use as the base URL for CodeSystem, ValueSet,$lookup,$expand,$validate-code”. - Content Negotiation table —
Acceptheader → result (text/html→Web UI,application/fhir+json/application/json→FHIR JSON,application/fhir+xml/application/xml→FHIR XML, none→FHIR JSON). - URL Patterns table — read (
/fhir/ValueSet/foo), versioned (…/foo|1.0.0), formatted views (…/foo.json,…/foo.xml), concept lookup (/fhir/CodeSystem/foo/active),…/foo/$expand,/fhir/CodeSystem/$lookup(POST),/fhir/metadata. - Browse Resources links → Global Concept Search · CodeSystems · ValueSets · ConceptMaps · StructureDefinitions · StructureMaps (the per-type lists above).
- Examples & Info links → Operation Examples (
$lookup,$validate-code,$expand, batch Bundle) · Ecosystem (dashboard) · Topology · API documentation · Health.
For Teagle: serve a static FHIR API hub page at GET /fhir when Accept: text/html (mirrors the
content-negotiation the reference uses), linking to the Teagle pages above. Two net-new leaf pages it references:
- Operation Examples — a small page of ready-to-run
$lookup/$validate-code/$expand/batch-Bundle request examples (Teagle already has the Expand playground + Operations console — this can link to them / seed them with the examples rather than a new page). - Health — a plain status page (
/actuator/healthor/api/health).
Backend touch-points (flag for the backend, mostly config/read-through)
- SD/SM: catalog indexing + read-through for
StructureDefinition/StructureMap(Flint routes, doesn’t own) — DONE (scan types + the resource/batch controller type sets). Topology emits SD/SM nodes but nobaseDefinition/structureedges yet: deriving those means fetching profile bodies, which is only affordable once the fetch asks for_summary=true. /api/topologygraph endpoint — DONE (TopologyController; see spec #4 for the shape).- Global concept-search / concept-detail cross-references (
/concept-search/{quick,harvest,cross-references}) — still needed. /api/catalog/statsalready exists (dashboard);/api/serversalready exists.
Note
The reference eXplorer’s /fhir/{Type}/{id} content-negotiation (browser UI vs FHIR JSON on the same URL) is a
routing nicety; Teagle can keep UI routes under /teagle/ and fetch FHIR from the backend /fhir/*.
Appendix A — source review of the reference frontend (tx-viewer)
Read of terminology-explorer/frontend/projects/tx-viewer/src/app (routes in app.routes.ts →
terminology/routes.ts + fhir/routes.ts; docs in docs/features/*). Corrections and additions to the above:
- No ETag / conditional-request / IndexedDB anywhere in the reference. Its only caching is RxJS
shareReplay(1), an in-memoryComponentStateStore, andsessionStorage. A stale-while-revalidate / ETag layer would be net-new for Teagle, not a port. - Concept-detail page is a distinct route we missed:
/fhir/{Type}/{id}/{code}→HlxTerminologyConceptDescriptionComponent. Header (code, display+lang, system uri, version), designations table, properties table (with binding-resolved codings), and parents / children / associations (cross-references) tables — each navigable. Add as a 7th missing page (net-new). Data:CodeSystem/$lookup(+ hierarchy properties) and a cross-references lookup (new backend endpoint). - Formatted
.json/.xmlview (/fhir/{Type}/{id}.json|.xml,FhirFormattedViewComponent) — pretty-print viaAcceptnegotiation, copy/download. Folds into the Rich-detail JSON/XML tabs (spec #5). /fhir/examples(FhirExamplesComponent) — server-rendered operation examples; the FHIR-hub (spec #6) Operation-Examples link can point at Teagle’s Expand playground / Operations console instead of a new page.- Resource detail two-step load (
resource.component.ts): render the shell from?_summary=true, then stream the full body — a real perf pattern worth adopting in spec #5.
Appendix B — ideas & optimisations to fold into Teagle (prioritised)
Backend support key: have = Teagle’s /api/{servers,catalog,catalog/stats,resolve} + /fhir/* already
cover it; new = needs a backend addition.
Tier 1 (high value, backend already there):
_summary=trueon all list/detail reads — list views never needconcept[]/expansion; seconds → tens of ms on LOINC/SNOMED-sized resources. (have)- Two-step resource load — summary shell first, full body streamed after (spec #5). (have)
- Ecosystem dashboard stat-cards + server-type breakdown + per-server counts from
catalog/stats+servers(spec #1). (have) - Versioned-id
|→--request rewrite in theteagleApiinterceptor (Tomcat rejects%7C;java.net.URIrejects raw|) — Teagle already parses--server-side. (have) - Structured FHIR-error toasts — parse
OperationOutcomein its several shapes; special-case the multi-server routing-conflict 500 into a sticky, actionable notice naming the conflicting servers (directly relevant to Teagle’s aggregation). (have)
Tier 2 (strong UX):
6. CDK-style virtual scrolling for concept lists/trees (flatten to visible rows) — 5k concepts 3000 ms→80 ms
in the reference; use @tanstack/react-virtual for the CS/VS concept viewers + Expand playground. (n/a)
7. Size-adaptive metadata filter default — preselect all <2000, common-only 2000–5000, none >5000. (n/a)
8. Property-filter table — per-property tags input, AND across / OR within, client-side over the loaded
concept list. (n/a)
9. List state + scroll restoration on back-nav (TanStack Query keepPreviousData + a scroll store). (n/a)
10. Debounced search standardised (reference: 250–500 ms per surface). (have)
11. Accept-Language + displayLanguage threaded into $expand/$lookup, showing the resolved-language
chip. (have)
Tier 3 (larger / net-new):
12. Topology graph — Cytoscape + cose, type-colored nodes, client-side .filtered-out filtering,
fullscreen; 503 + Retry-After retry while the graph builds. Needs a /api/topology endpoint (spec #4).
13. Concept-detail relations page (parents/children/associations) — $lookup (have) + a cross-references
endpoint (new).
14. Global concept text-search + $lookup harvest — /concept-search/{quick,harvest,cross-references} are
new backend endpoints; $lookup/$validate-code exist.
15. 503 “backend warming up” banner + poll until ready — fits Teagle’s reloadable in-memory registry; needs a
ready flag on an ecosystem/health endpoint.
Reference files worth lifting from: resource/resource.component.ts,
resource/concept-description/concept-description.component.ts, resource/concepts-tree-view/*,
resource/concepts-paginated-list/*, terminology/topology/*, terminology/global-search/*,
landing/landing.component.*, core/http/* (interceptors: pipe-encoding, error-handling, lang, readiness).