Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

DocWhat
architecture.mdModule/lib structure, @helex/* reuse, MFE, dev vs deploy
dev/flint.mdFlint (tx-fhir) — the FHIR terminology engine library: SPI, providers, embedding, conformance
dev/teagle.mdTeagle — the terminology aggregator: server registry, multi-source $expand, editable in-memory server
dev/running.mdRunning the suiterun_backend.sh/run_frontend.sh for tedy · teagle · flint · ucumber
development.mdThe two-location dev model, run, sync, verify
spec-authoring.mdHow we create specifications — the dev-tx.example-scan → reference-editor-reconcile process, aligned to the EMR spec rules
dev/publishing.mdPublishing — build the mdBook, deploy to Cloudflare Pages, gate it with Cloudflare Access
resource-context-gap.mdResourceContext 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.mdFont/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):

ModuleNameHandlePackageMascotScope
EditorTEDYtx-editorg.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 / aggregatorTeagletx-visionorg.helex.tx.teagle🦅multi-server terminology aggregator — server registry, multi-source $expand fan-out, editable in-memory server; built on Flint
FHIR APIFlinttx-fhirorg.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.
UCUMUcumbertx-ucumorg.helex.tx.ucum🥒UCUM utility API (convert / canonicalise / analyse) over Flint’s native UCUM engine
WikiOwlexicontx-wikiorg.helex.tx.wiki🦉wiki (DB-authored), rendered/published
PublisherCatticustx-puborg.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.tsxApp.tsxAppRoutes.tsx) AND a Module-Federation remote (federated.tsx exposes ./App). Mirrors the EMR modules/tx/frontend blueprint.
  • 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/main for 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_TARGET is required — there is no committed default (a neutral placeholder resolves nothing). Publisher display names need VITE_TX_PUBLISHER_VS_URL / VITE_TX_PUBLISHER_CS_URL (see src/config/tx.ts + .env.example).
  • Auth: DEV auto-signs-in a guest (Bearer yupi on /txapi; a Vite stub answers /api/uma/*). Disable with VITE_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>

ModuleCommandPortStorage
TEDY 🧸./scripts/run_backend.sh tedy18610Postgres — 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 teagle18650stateless (no DB)
Flint 🔥🦎./scripts/run_backend.sh flint18630in-memory (standalone engine; the tx-fhir library’s runnable app)
Ucumber 🥒./scripts/run_backend.sh ucumber18670stateless (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]

ModuleCommandProxies /txapi
TEDY./scripts/run_frontend.sh tedyTEDY_TARGET (default http://localhost:18610)
Teagle./scripts/run_frontend.sh teagleTEAGLE_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 .env files.
  • scripts/check-naming.sh gates 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

PiecePackageRole
Engine opsengine/{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 SPIstore/{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 SPIroute/{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 surfaceFhirResourceController, FhirOperationController/fhir/{Type}/{id} + ?url= reads, /fhir/metadata (+?mode=terminology), $versions, and the $operation endpoints (GET + POST).
Capability{CodeSystem,ValueSet,ConceptMap}Capabilitycommons-fhir FhirResourceCapability beans → aggregated /fhir/metadata.
Standalone appFhirEngineApplicationRuns 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)
  1. A ResourceStore impl selected by flint.store — e.g. TEDY’s org.helex.tx.tedy.fhir.TedyDbResourceStore (flint.store=tedydb, maps tedy.* → FHIR on read), Teagle’s registry stores, or the bundled InMemoryResourceStore (flint.store=memory).
  2. Config under flint.*flint.store, flint.authoritative-hosts (default helex.org), flint.delegate.snomed-target / .default-target (upstream targets — real URLs in the gitignored .env).
  3. A Spring Security chain (Flint stays auth-free) — e.g. TEDY’s FhirAuthorizationInterceptor gating /fhir/** on {CodeSystem,ValueSet,ConceptMap}.read. TEDY composes Flint via org.helex.tx.tedy.config.FhirEngineComposition (@ComponentScan of the Flint package, excluding FhirEngineApplication).

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.md for 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|code dedup 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-code dispatch, and an aggregated /fhir/metadata (+?mode=terminology with per-CodeSystem target-server annotations). /api/servers lists 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, lifecycle Proposed→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-validation skill / validate-spec command.

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 ## Differences note (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-tx module/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

  1. Step 1 live scan recorded (screens + observations, _scans/).
  2. Step 2 source reconcile done; ## Differences captured.
  3. Template chosen; all mandatory sections filled; glossary terms mapped.
  4. Frontmatter complete; source-refs = reference-editor/-server and TEDY paths.
  5. Data model + FHIR mapping + API/error contract per EMR rules.
  6. Business tests written (before implementation, for un-built resources).
  7. Passes the EMR validate-spec check.

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 at https://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.

  1. 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, name helex-tx-docs). The site is served at https://helex-tx-docs.pages.dev — optionally add a custom domain.

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

  3. Find the Account ID. Any Workers & Pages page shows it in the right rail.

  4. Add the two GitHub secrets on helex-solutions/tx (Settings → Secrets and variables → Actions):

    • CLOUDFLARE_API_TOKEN — the token from step 2
    • CLOUDFLARE_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.

  1. Cloudflare dashboard → Zero Trust → Access → Applications → Add an applicationSelf-hosted.
  2. Application domain: helex-tx-docs.pages.dev (and the custom domain, if any). Cover *.helex-tx-docs.pages.dev too so preview deploys are protected.
  3. Add a policy → Action Allow → include rule Emails (list the people who may read the docs) or Emails ending in @helex-solutions....
  4. 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-page ResourceForm mode toggle — no separate /:id/edit route (17-frontend-conventions §11). Create success navigates with { replace: true }.
  • Work contexts (parked/pinned resources across types) use the platform’s generic context-switching frameworkWorkContext + 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/tx terminology 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

ResourceResource-level tabsVersion-level tabs
Code SystemSummary · Metadata · Concepts · Properties¹ · Provenance · Checklist²+ concepts / properties / provenances / checklists
Value SetSummary · Metadata · Concepts · Provenancesummary · concepts · provenances
Map SetSummary · Metadata · Provenancesummary · provenances
Structure DefinitionSummary · Metadata · Content³summary · content
Structure MapSummary · 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/edit route (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 same TxResourceBar — SD versioned, SM version-less — proving the bar is not tied to the /ts resource shape.

Cross-resource work contexts (pins) — delivered

Pinning uses the platform WorkContext framework, not a bespoke bar:

  • The TxResourceBar Pin toggle openContext()s the resource into the shared Redux contextSlice (sessionStorage-persisted); un-pinning closeContext()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 its activePath; × → close).
  • registerTedyContextTypes() registers the five context-type codes at bootstrap.
  • The bar keeps a pinned context’s activePath current (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}) rendering TxResourceBar + <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 details route — 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, and shellActions deleted.
  • 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 WorkContext framework. ✅ 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

AspectEarlier (ad-hoc)Now (HELEX-aligned)
Resource barper-resource shellsone TxResourceBar
Active tablocal state / pathname sniffURL-driven
Version?version query parampath segment
Pinsbespoke store + persistence + pushpin + overflowplatform WorkContext framework (Pin toggle → connected context bar)
Metadatainline toggle (kept)in-page toggle, no /:id/edit (matches HELEX)
NamingTedyResourceShell/ResourceContextTxResourceBar

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 — inputs resourceType (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-conceptsbackend 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 availableTEDY statusAction
tw-resource-context (tabbed shell)EntityHeader + PageTabs + AppSelect (compose)❌ missing (only flat TedyResourceForm)Build TedyResourceShell
version selector (“All versions”)AppSelect/AppDropdownpart of shell
code-system-info-widget (icon summary)AppCard+FieldItem/AppDescriptions⚠️ partial (flat FieldItems)CodeSystemInfoWidget
code-system-versions-widgetAppCard+AppTable+AppTag⚠️ flat list onlyCodeSystemVersionsWidget
code-system-unlinked-concepts (backend table + checkboxes + link/add)AppTable/AppDataGrid (selection)⚠️ simple read-only concepts tableextend CodeSystemConcepts (columns, selection, actions)
resource-tasks-widget + resource-task-modalAppTable+AppModalTedyTasksWidget + task modal (+ tasks client)
resource-related-artifact-widgetAppCard+AppListRelatedArtifactsWidget
value-sets cardAppCard+AppTag+AppLinkReferencedValueSetsWidget
resource-form (Metadata edit)ResourceForm⚠️ view-onlyedit mode (+ sub-widgets below)
resource-identifiersTxIdentifier (@helex/tx) — value model IS FHIR Identifier[]✅ reusefree-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 widgetbuild in @helex/fhircreate FhirContact (ContactDetail editor) composing FhirTelecom — standard FHIR datatype, lives with the other Fhir* widgets (shared, sign-off-gated)
resource-configuration-attributesAppEditableTableConfigurationAttributes
resource-multi-language-viewAppLocalizedName⚠️ have input, not viewsmall helper
resource-version-formResourceFormVersionForm
resource-release-modal / resource-fhir-import-modalAppModallater (release → Press; FHIR import → Exo)
twPrivileged / twPrivilegeContextuseModule().canWrite() (@helex/core)⚠️ not wired per-resourceuseTedyPrivilege(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)

  1. TedyResourceShell — the ResourceContext equivalent: resourceType+resource+versions+mode; header (icon, localized title, version selector) + route-driven tab strip; wraps routed tab pages. Backed by EntityHeader+PageTabs+AppSelect. (Foundational — everything else mounts inside it.)
  2. Summary widgets: CodeSystemInfoWidget, CodeSystemVersionsWidget, RelatedArtifactsWidget, ReferencedValueSetsWidget.
  3. 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).
  4. Tasks: TedyTasksWidget + task modal + a tasks client (or reuse EMR flow).
  5. Metadata edit: ResourceForm edit mode for CodeSystem + sub-widgets: reuse @helex/tx TxIdentifier (identifiers), new FhirContact in @helex/fhir (contacts, composing FhirTelecom), ConfigurationAttributes, VersionForm. (No FhirIdentifier — identifiers = TxIdentifier.)
  6. Privilege gating: useTedyPrivilege(resourceId, 'CodeSystem') (wraps @helex/core useModule) for {id}.CodeSystem.write.
  7. 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 route code-systems/:id → shell with tabs Summary · Metadata · Concepts (read-only): CodeSystemShell loads once and shares via Outlet context; CodeSystemSummary = CodeSystemInfoWidget + CodeSystemVersionsWidget + concepts preview; CodeSystemMetadata = full field view; CodeSystemConceptsTab = the concepts table. Flat CodeSystemDetail/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/tx TxConceptTree/TxConceptSearchDrawer where 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, commit f0918f9). @helex/tedy saveCodeSystemPOST /ts/code-systems/transaction; Metadata tab is now view/edit via ResourceForm (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 shared CodeSystemFormFields and reused by CodeSystemCreate (ResourceForm in create mode, id editable 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 persists title={en:...}/content/caseSensitive and lands on the summary tab). Version form + lifecycle DELIVERED (commit 493ec33): @helex/tedy versionClient (saveCodeSystemVersion POST-new/PUT-existing, changeCodeSystemVersionStatus draft/activate/retire, deleteCodeSystemVersion) + useCodeSystemVersions hooks; shared VersionFormFields (version key create-only, release/expiration dates bridged to AppDatePicker’s dayjs, supported/preferred languages, algorithm, description, identifiers) reused by standalone CodeSystemVersionCreate/CodeSystemVersionEdit pages (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 (the resourceType switch), mirroring reference-editor’s shared resource-* 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

Areareference-editor (old)TEDY (new) todayGap → action
Headerresource-context: icon + localized title + version selector dropdown + statusTedyResourceShell: icon + title + tab strip; version-selector slot exists, unwiredP4 wire a version selector into the shell’s tabBarExtraContent slot
Info cardCompact icon-prefixed lines: 🔑 id (copyable) · description · 🔗 uri (copyable) · ▤ properties (names) · ⚙ case-sensitivity + content-mode + experimental · ☎ contacts/telecoms · 📍 publisher (resolved) · 🏷 linked VS linkAppCard + SectionHeading + FieldItem label/value grid: id · uri · name · publisher · content · caseSensitive · descriptionP1 rebuild as the compact icon-list; add missing: copy-to-clipboard, properties, experimental, contacts, linked-VS link
Versions cardTitle bar: Compare + Add version; rows: version · date range · count · status · language · add-to-release · linked VS · per-row CompareTitle + Add version; rows: version · status · releaseDate · count · Edit / transition / DeleteP2 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
Layout2-col m-form-row, several cards per column2-col CSS grid, info+versions left / concepts preview rightConverges 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 via GET .../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 on anesteesia-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 over GET /tm/tasks?context=CodeSystem|{id} (NOT the EMR flow module — that’s a different backend). Read-only list; create/triage flows later.
  • P3c Related artifacts — ✅ DELIVERED (a0ff69f). POST /related-artifactsuseRelatedArtifacts → card.
  • P3d Value-set impacts — ✅ DELIVERED (a0ff69f). GET .../value-set-impactsuseValueSetImpacts → 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.

  1. P1 (icon-list info card) — do first; it’s most of the “looks different” and is cheap + backend-free.
  2. P2 (versions columns + Compare button) — small, rounds out the left column.
  3. P4 (header version selector) — small, high perceived parity.
  4. 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, composing FhirTelecom) — see resource-context-gap.md.
  • Value-set impacts: verify impacts endpoint 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 + antd compactAlgorithm). Driven via AccessibilityModalHelexThemeProviderbuildAntTheme/applyThemeCSSVars.
  • Font size (HelexFontSize, HelexThemeProvider.tsx): normal 14px · large 18px · extraLarge 26px (root <html> font-size + .helex-scaled-*). Both axes only go bigger/looser than baseline except compact.

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).

Metricreference-editorhelex comfortablehelex defaulthelex compact
Base font14141414
Control height32 (24 small)403224
Table cell padding11.2/11.2 (small 5.6/11.2 + font↓~10.5)16/1616/168/8
Card padding12~2424~12
Typical row heightsmall ~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/ui compact is already denser than reference-editor’s default (control 24 vs 32, cell 8 vs 11.2). For ordinary screens, use compact — 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/ui density 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.

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' + compactAlgorithm
  • buildAntTheme tiny overrides: token.controlHeight:24, controlHeightSM:20; components.Table:{cellPaddingBlock:4, cellPaddingBlockSM:4, cellPaddingInline:8, cellPaddingInlineSM:8, cellFontSizeSM:13}; components.Card:{bodyPadding:8}. Global fontSize stays 14.
  • Edit sites (@helex/ui, all additive): themes.ts (HelexDensity union, DENSITY_MAPS, antdComponentSizeFor, buildAntTheme), settings/AccessibilityModal.tsx (radio + helex.settings.access.tiny key), 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:

  1. It is a new HelexDensity value, NOT a HelexFontSize option. 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.
  2. Its only job is denser data-grade tables. compact already 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/ui offers 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 gap tiny fills.

Everything below is the concrete plan for that decision.

2. WCAG rationale — why density, not font-size

  • The HelexFontSize axis (normal 14 / large 18 / extraLarge 26) 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.
  • tiny keeps global fontSize at 14 and only compresses layout spacing app-wide. Font shrinks inside tables only (cellFontSizeSM: 13), mirroring the reference platform’s --*--sm table 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 antd compactAlgorithm (same algorithm compact already uses).
  • buildAntTheme tiny overrides:
    • token.controlHeight: 24, token.controlHeightSM: 20
    • components.Table: { cellPaddingBlock: 4, cellPaddingBlockSM: 4, cellPaddingInline: 8, cellPaddingInlineSM: 8, cellFontSizeSM: 13 }
    • components.Card: { bodyPadding: 8 }
    • Global token.fontSize stays 14 (unchanged).

4. Edit sites (all additive, all in modules/core/frontend)

FileChange
libs/ui/src/lib/themes/themes.tsAdd '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.tsxAdd 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.tsExtend 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_MAPS already feeds both the antd token projection (buildAntTheme) and the CSS-var projection (buildHelexCssVars--helex-gap-* / --helex-row-padding), and themes.test.ts asserts the two can’t drift — so adding a map entry keeps both surfaces in sync for free.
  • default density must remain byte-identical to today (the test at line 65 guards this); tiny is 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 tiny density — 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/ui change.
  • (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:

  1. Create a backend under modules/tedy based on an emr module template.
  2. Add run_frontend.sh tedy / run_backend.sh tedy update scripts.
  3. 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).
  4. Use Forge (FHIR model lib) instead of the reference’s Zmei.
  5. Analyze the DB, rewrite changesets removing vendor references; every changeset must be idempotent (create if not exists, idempotent DML); new schema is tedy; 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).
  6. (Phase 2) FHIR mapping between TEDY resources and FHIR + EMR FIS — out of scope for this review.
  7. Do not use Kefhir; recreate needed functionality in TEDY.
  8. Port all tests from the reference server. 9/10. Ensure endpoints work; note optimizations vs the reference.
  9. 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 generic mdm.
  • Build model: start standalone — the backend builds independently against published artifacts org.helex.emr:commons-* (0.1.0-SNAPSHOT) and org.helex.forge:* (0.20.0), no EMR checkout required; structured to also composite-build inside EMR later.
  • Package namespace: org.helex.tx.tedy (emr’s tx module is uniquely org.helex.tx).
  • Auth: TEDY-owned (a pluggable SessionProvider chain + @Authorized interceptor) because core-backend is not published. ABAC ACL deferred until commons-tenant is published (a one-line emr change was made: it was added to publishableCoreLibraries in emr/repo/build.gradle.kts, but the publish itself is a pending user action). Code Systems etc. are ACL-free for now (like emr’s global mdm.practitioner).
  • DB idempotency: the tedy module changelog is idempotent (CREATE TABLE IF NOT EXISTS, guarded create_table_metadata, insert … on conflict) so a fresh install and a migrated legacy DB converge. It first includes the shared core schema from the published commons-db-core.
  • Data-model renames (agreed): entity_propertycode_system_property, entity_property_valueconcept_property, defined_entity_propertydefined_property, entity_version_code_system_version_membershipconcept_version_membership, map_set*concept_map* (Java package mapsetconceptmap, models MapSet*ConceptMap*; REST path stays /ts/map-sets). code_system.permissions is dropped (ACL replaces it). code_system_entity kept (polymorphic supertype of concept + 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):

CommitContent
104cbf7migration extended to Value Sets + Spaces
6a0c13arun/contract-verification scripts
44f51a2tedy-migrate standalone migration jar
66989f5Spaces slice
5f059e1Naming Systems + Defined Properties
a184461Map Set slice (concept_map)
3c9b3e5Value Set slice (compose + expansion)
4e8c41bconcept associations
3da59dfscaffold + 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/): SessionProvider chain → SessionFilterSessionStore; @Authorized + AuthorizationInterceptor; SecurityConfig (stateless, authz via the chain); GuestSessionProvider grants *.*.* 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 includes core-db then the idempotent tedy DDL 01…07.
  • REST: /ts/code-systems (+ /concepts, associations, versions, spaceId filter), /ts/value-sets (+ /expand), /ts/map-sets (+ /associations), /ts/naming-systems, /ts/defined-properties, /spaces (+ /packages).
  • Optimization vs reference: Value Set compose ruleSet and Map Set scope stored as one JSONB column instead of the reference’s normalized rule tables; expansion computed on demand.

Migration jar (migration/tedy-migrate)

  • Standalone runnable jar. TedyMigrator runs ordered idempotent INSERT … WHERE NOT EXISTS scripts (src/main/resources/migrate/*.sql) in one transaction, disabling the sys-column trigger to preserve sys_* verbatim, applying every rename + the defined_property_id backfill + a core.seq_id fixup. Covers CS-family, associations, naming systems, concept maps, value sets, and spaces (sys.*tedy.*).

Scripts (scripts/)

  • run_backend.sh tedy (Docker Postgres + dev profile Liquibase), run_frontend.sh tedy (VITE_TX_TARGET → local backend, delegates to dev.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, SpaceControllerITcd 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 tedy then ./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:

  1. Contract fidelity — do the controllers match the endpoint paths, params, and JSON shapes the frontend clients expect? Cross-check modules/tedy/backend/.../api/* against libs/tedy/src/lib/client/*.ts and libs/tedy/src/lib/types/*.ts (paths, SearchResult = {data,meta}, field names like mapSet/designationType/entityProperty, the /transaction bodies).
  2. Persistence correctness — repositories: JSONB/text[]/Forge-datatype column mappings (AnvilBeanProcessor/AnvilSqlBuilder), the client-supplied-text-id upsert, optimistic-lock/soft-delete handling, and the spaceId join. Look for SQL injection (all use bound ?), N+1 decoration, and Jackson 2/3 boundary bugs.
  3. Renames & data model — confirm every agreed rename is applied consistently across DDL, models, repos, and the migration; confirm permissions is dropped and code_system_entity is retained as the supertype.
  4. Migration safety — are all scripts truly idempotent (re-run = no change)? Are sys_* preserved (trigger disabled)? Is the defined_property_id backfill 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.)
  5. Auth extensibility — is the SessionProvider chain genuinely pluggable, and is the guest *.*.* default safe (dev-gated, not on in prod)? Is @Authorized enforced on every mutating endpoint?
  6. Naming hygiene — independently grep committed sources for the prohibited terms.
  7. Idempotency of the module changelog — would a fresh install and a migrated DB converge? Any create_table_metadata/index that isn’t guarded?
  8. 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 (branch feature/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 uses rsync -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.

The following were committed today and did not touch ValueSetRuleEditor.tsx:

  • f717ea1 — fix(tedy): unify code-system summary card headings
  • a909070 — docs: record the @helex/ui tiny-density sign-off proposal
  • d5719bb — 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 fixed guest/dev tenant. This is the gating item for a real deployment.
  • User preferencesGET/PUT/DELETE /api/uma/preferences is 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/preferences body { key, value, scope } (scope = global | tenant) → 200.
      • DELETE /api/uma/preferences?key=&scope=200 (idempotent soft-delete).

Terminology data gaps (dev backend)

  • Publisher $lookup 404 — the CS summary resolves the publisher display via GET /api/tx/$lookup?system=…/CodeSystem/publisher&code=HELEX, which 404s on the dev backend (no publisher CodeSystem / code HELEX). 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, in ConceptPresentationTable.tsx) instead of a silent limit: 200 cap; children lazy-expand one level; off-screen property markup is deferred (ConceptCell.tsx useInView); 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 showInList properties), 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}/concepts endpoint has no properties=/field-limiting and always decorates).

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 is TRM; keep these template/frontmatter-compatible so they can move upstream to emr/docs/specifications/ if adopted).
  • Feature-family IDs (MDM pattern): each resource is a family under TEDY.NN:
    • TEDY.NNoverview / 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.1List (resource-list)
    • TEDY.NN.2Add / Create (resource-create)
    • TEDY.NN.3View / Management (resource-management)
    • further children (.4+) for tools/sub-screens.
  • Each child opens with a ## Feature grouping section 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:

IDSpecTemplateState
TEDY.01Code System — overview / common (data model, FHIR, privileges, API, navigation)tool-featureProposed
TEDY.01.1Code System — Listresource-listProposed (built)
TEDY.01.2Add Code Systemresource-createProposed (pending write flow)
TEDY.01.3Code System — Viewresource-managementProposed (built, read-only)
TEDY.01.4Code System — Concept Presentation (configurable table + editable detail)tool-featureProposed (built)

TEDY.02 — Value Set family:

IDSpecTemplateState
TEDY.02Value Set — overview / common (compose + expansion model, full REST surface, FHIR, privileges, API, navigation)tool-featureProposed (built)
TEDY.02.1Value Set — Listresource-listProposed (built)
TEDY.02.2Add Value Setresource-createProposed (built)
TEDY.02.3Value Set — View (Summary · Metadata · Concepts/expansion · Rules/compose + editor)resource-managementProposed (built)

TEDY.03 — Map Set family:

IDSpecTemplateState
TEDY.03Map Set — overview / common (scope + association model, full REST surface, ConceptMap FHIR)tool-featureProposed (built)
TEDY.03.1Map Set — Listresource-listProposed (built)
TEDY.03.2Add Map Setresource-createProposed (built)
TEDY.03.3Map Set — View (Summary · Metadata · Associations grid + editor)resource-managementProposed (built)

New-module families (research + first-slice implementation; open questions in TEDY-new-modules-open-questions.md):

IDSpecState
TEDY.06Implementation GuidesProposed (list built; deeper screens phased)
TEDY.07SpacesBuilt (list · Metadata Core+GitHub · linked CS/VS/MS add-as-link · Comparison · Repository)
TEDY.08Servers (terminology/FHIR registry)Proposed (built: list+filter+create, summary authoritative widgets, full metadata + check-connection, resources browser w/ viewer links, authoritative editor)
TEDY.09EcosystemsProposed (built: list + add/edit)
TEDY.10Defined PropertiesBuilt (list + create/edit via ResourceList/ResourceForm)
TEDY.11Naming SystemsBuilt (list + lifecycle + create/edit with identifiers editor)
TEDY.12List bulk actions (CS/VS/MS + Space tabs)Built (select → activate/retire/add-to-release/delete/unlink)
TEDY.13ReleasesBuilt (list + TxResourceBar detail — Summary w/ Resources+Errors+Checks+Notes+Tasks · Metadata · Provenance · Comparison; add-resource autocomplete+version; server-sync phased)
TEDY.14Global SearchBuilt (Concepts · CS · VS · MS · SNOMED groups + space/publisher/CS filters)
TEDY.15Landing (home dashboard)Built (Resources/Modeling/Management count cards → lists)
TEDY.16Unified resource metadata (CS/VS/CM/SD)Proposed (comparison + unification plan; close CS dataset gap)

TEDY.17 — File & Source Import family:

IDSpecTemplateState
TEDY.17File & Source Import — overview / common (analyze→process→job spine, transport, IA/dashboard, privileges, reuse machinery)tool-featureProposed (built)
TEDY.17.1Code System file import (analyze + column mapping + options + saved/template/portable config)tool-featureProposed (built)
TEDY.17.2Value Set file import (analyze + code/display mapping into a rule set)tool-featureProposed (built)
TEDY.17.3Map Set file import (fixed CSV template + scope editor; no analyze)tool-featureProposed (built)
TEDY.17.4External sources — WHO ICD-10 · Orphanet · LOINCtool-featureProposed (built)

Cross-cutting:

IDSpecTemplateState
TEDY.18Configuration & environment variables (VITE_/TX_/TEDY_/OAUTH_ surface, backward-compatible; migration script)tool-featureBuilt

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:

Glossary Terms

Term (canonical)Meaning
Code SystemA FHIR CodeSystem — a managed set of concepts (codes) with designations, versions, properties.
ConceptA single code within a Code System (its content).
VersionA CodeSystemVersion with a publication status (draft/active/retired) + release date.
DesignationA concept’s localized display/definition (designationType = display/definition, per language).
PublisherCodeSystem.publisher — resolved via the publisher value set when it is a code, else free text.
SpaceA reference-platform authoring workspace a resource belongs to.
Content modeCodeSystem.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 / Resourcethe reference platform APIFHIR resourceNotes
CodeSystem/ts/code-systemsCodeSystemlist + detail; lastVersion decorated
CodeSystemVersion/ts/code-systems/{id}/versionsCodeSystem.versionstatus ∈ draft/active/retired
CodeSystemConcept/ts/code-systems/{id}/conceptsCodeSystem.conceptcontent; display from designations
List envelopen/an/a{ data: [...], meta: { total, offset } }
Single-resource envelopen/an/aresource root fields

Shared CodeSystem fields

FieldTypeCardinalityRule
idstring1..1immutable identity (the code-system id)
uristring0..1canonical URL; unique
namestring0..1machine name
titleLocalizedName0..1multilingual display
descriptionLocalizedName0..1
publisherstring0..1code from publisher VS or free text
contentcode0..1codesystem-content-mode
caseSensitivecode0..1cs / ci
baseCodeSystemstring0..1for supplements
versions / lastVersionCodeSystemVersion[]0..*version + status + releaseDate + conceptsTotal
conceptsCodeSystemConcept[]0..*code + versions[].designations[]

Used value sets (resolved via @helex/tx)

FieldValueSetCanonical URLBinding
publisherpublisherhttps://tx.example/fhir/ValueSet/publisherextensible
version statuspublication-statushttp://hl7.org/fhir/ValueSet/publication-statusrequired
contentcodesystem-content-modehttp://hl7.org/fhir/ValueSet/codesystem-content-moderequired

FHIR Mapping (Common)

Internal fieldFHIR R5 pathMappingNotes
idCodeSystem.iddirect
uriCodeSystem.urldirect
nameCodeSystem.namedirect
titleCodeSystem.titletransformLocalizedName → language-resolved title
publisherCodeSystem.publisherdirect
contentCodeSystem.contentdirect
caseSensitiveCodeSystem.caseSensitivetransformcs/ci → boolean
lastVersion.versionCodeSystem.versiondirect
concepts[]CodeSystem.concept[]transformdesignations → 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[] where designationType='display' (match language → preferred → first), fallback to the code.
  • Publisher display resolves via the publisher value 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

ActionPrivilege
View / browseauthenticated (dev: guest via the reference platform yupi)
Create / edit / delete / versions / conceptsCodeSystem.write (the reference platform {id}.CodeSystem.write)

Common API Surface

MethodPathPrivilegeDescription
GET/ts/code-systemsreadlist (params: textContains, publisher, versionStatus, spaceId, content, lastVersionDecorated, lang)
GET/ts/code-systems/{id}readload one (?decorate=true)
GET/ts/code-systems/{id}/versionsreadversions
GET/ts/code-systems/{id}/conceptsreadconcepts (params: textContains, codeContains, displayLanguage)
POST/ts/code-systems/transactionwritecreate/update (authoring)
GET/spacesreadspaces (for the Space filter)
GET/fhir/ValueSet/$expand?url=…readvalue-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

RequirementTarget
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

  1. List, view, and (future) create all operate over /ts/code-systems* with the shared envelope.
  2. Publisher/status/content render via their value sets; concept display via designations.
  3. 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):

ColumnSourcelockedNotes
IDcs.id → linkyesto TEDY.01.3
Versioncs.lastVersion.versionnothe reference platform “LAST VERSION”
Titlelocalize(cs.title) | cs.nameno
Publishercs.publisher via publisher VSnouseValueSetExpand/resolveLocalizedDisplay, fallback raw
Statuscs.lastVersion.status chipnodraft/active/retired
Conceptscs.lastVersion.conceptsTotalnothe reference platform “COUNT”

Filters

Mirror reference-editor (publisher · status · space) + content:

FilterControl→ the reference platform param
PublisherTxConceptSelect(url=publisher VS)publisher
StatusTxConceptSelect(url=publication-status VS)versionStatus
SpaceAppSelect (from GET /spaces)spaceId
ContentAppSelect (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; searchtextContains; 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

  1. Columns render in the reference platform order (ID·Version·Title·Publisher·Status·Concepts).
  2. Search filters by textContains and resets to page 1.
  3. Publisher/Status/Space/Content narrow via the mapped params; Clear resets.
  4. Row click opens the preview; Open/ID → TEDY.01.3.
  5. 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)

Aspectdev-tx.examplereference-editorTEDY
Column orderID·LAST VERSION·TITLE·PUBLISHER·STATUS·COUNTsamematches
Publisher cellpublisher VS designationlocalizedConceptNameVS-resolved (@helex/tx), fallback raw
Publisher/Status filtervalue-set concept selecttw-value-set-concept-selectTxConceptSelect(url=…)
Version / CountlinksrouterLinksplain text — pending version/concepts sub-routes
Row expandinline rowexpanded-row componentnot ported (concepts shown on TEDY.01.3)
Add actionsManual / Import FHIRgated CodeSystem.writepending (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) in create mode over the same saveCodeSystemPOST /ts/code-systems/transaction write 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 to complete and case-sensitivity to ci (both, plus a title, 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)

FieldWidgetRequiredRule
idAppInputyesunique code-system id (immutable after create)
uriAppInputyescanonical URL; unique
nameAppInputnomachine name
titleAppLocalizedNamenomultilingual
descriptionAppLocalizedNameno
publisherTxConceptSelect(url=publisher VS)nocode or free text
contentTxConceptSelect(url=content-mode VS) / AppSelectnocomplete/supplement/…
caseSensitiveAppSelectnocs / ci
baseCodeSystemAppInputnowhen content=supplement

Business Logic

  • Validate required (id, uri) + uniqueness before submit; guard double-submit (saving state).
  • Save → POST /ts/code-systems/transaction with the CodeSystem payload (the reference platform create/update transaction).
  • On success: notify, navigate to the new id (replace) and switch to view; 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

  1. Missing id/uri blocks submit with a validation message.
  2. Duplicate id/uri is rejected (server) and surfaced.
  3. Successful create lands on /tedy/code-systems/{id} in view mode.
  4. 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 TedyResourceFormto 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. Publisher TxConceptSelect, identifiers TxIdentifier. Create (from the list’s Add) reuses this form + client — TEDY.01.2.
  • Concepts (CodeSystemConceptsTab) = the CodeSystemConcepts table.

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

SectionContentWidget
Generalid, uri, name, publisher (VS-resolved), content, caseSensitive, descriptionFieldItem grid; publisher via TxConceptDisplay
Versionsversion + status chip + release dateFieldItem list
ConceptsCode · Display · Status, paged + searchCodeSystemConcepts (AppTable); display via conceptDisplay (designations)

Actions

  • Back to list.
  • Edit / Delete / new version / duplicate / create-supplementCodeSystem.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

  1. View shows General, Versions, and a paged Concepts table for the id.
  2. Concept display resolves from designations in the active language.
  3. Concept search filters via textContains.
  4. 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:

TermMeaning
PresentationThe per-Code-System layout config: mode, editable flag, and columns.
ColumnA first-level group in the presentation; renders one table cell per concept.
ElementA second-level item inside a column: code, display, status, designations, properties, or br (line break).
Designation blockA single element that renders a concept’s designations, optionally filtered to chosen languages.
Property blockA single element that renders a concept’s property values, optionally filtered to chosen properties.
Defined propertyA 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[];
}
  • designations and properties are 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 groups display + designations).
  • Singleton elements (code, display, status, designations, properties) may appear at most once across the whole presentation; br may 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 language L: titles[L]title (legacy) → any set titles value → the auto-derived label. This is a presentation preference only (client-side; no server field).

Default presentation (derived from the CS definition)

  • mode = hierarchical when CodeSystem.hierarchyMeaning is set, else plain.
  • editable = false.
  • columns = [ [code], [display(bold), designations], [properties] ], where the designations block defaults to all languages and the properties block defaults to properties with showInList. 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:

  • codeconcept.code.
  • displayconceptDisplay(concept, language) (bold when bold).
  • status → status chip (the concept’s current entity-version status).
  • designations → the concept’s designations, filtered to languages if set (else all), preferred-language first, each shown as lang + ": " + value, and preceded by a line break unless it is the first text already in the column.
  • properties → each included property (names if set, else showInList) as label: value, one per line; Coding values resolve to a display via the property rule (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: mode select (plain | hierarchical); editable checkbox; 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. display carries a Bold toggle; designations and properties carry an inline picker (languages / property names, “All” when none chosen). code is 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; CodingTxConceptSelect scoped by the property rule.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, and rule ({ 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.childCount gate the tree expander.

API Endpoints

All under /ts/code-systems/{id} (see TEDY.01 § API).

PurposeCall
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 treeGET …/concepts/tree-search?associationType={hierarchyMeaning}&textContains=… → flat items with parentCode + matched
Save (transaction)POST …/[versions/{version}/]concepts/transaction body { concept, entityVersion }
DeleteDELETE …/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 CodeSystem read access.
  • Edit (the editable toggle + 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

  1. Default layout — a plain CS opens with columns code · display(bold) + designations · properties(showInList); designations read lang: value, preferred-language first.
  2. Hierarchical — a CS with hierarchyMeaning opens as a tree; expanding a node lazily loads its children; leaves show no expander.
  3. 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.
  4. Code pinnedcode cannot be dragged, removed, or displaced from the first cell.
  5. Editable detail — with editable on, 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.
  6. 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/tedy conceptClient.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 (guest yupi).

Child specs:

Glossary Terms

Term (canonical)Meaning
Value SetA FHIR ValueSet — a curated selection of concepts drawn from code systems / other value sets.
VersionA ValueSetVersion with a publication status (draft/active/retired) + release date; carries the rule set.
Rule setValueSetVersion.ruleSet — the container of compose rules for that version (lockedDate, inactive, rules[]).
Compose ruleValueSetVersionRule — one include/exclude entry referencing a code system (or another value set), optionally narrowed by explicit concepts, filters, and properties.
FilterValueSetRuleFilter{property, operator, value} on a rule (e.g. is-a, descendent-of); operators per FHIR filter-operator.
ExpansionThe concrete member list produced by evaluating the rule set (POST /ts/value-sets/expand).
SnapshotValueSetSnapshot — a stored, materialized expansion for a version (conceptsTotal, expansion[]).
Member (concept)ValueSetVersionConcept — an expanded member: concept (code + system) + display + additionalDesignations + orderNumber + active.
DesignationA member’s localized display; display is the chosen one, additionalDesignations the rest.
PublisherValueSet.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 / Resourcethe reference platform APIFHIRNotes
ValueSet/ts/value-setsValueSetlist + detail; lastVersion / versions decorated
ValueSetVersion/ts/value-sets/{id}/versionsValueSet.versionstatus ∈ draft/active/retired; carries ruleSet
ValueSetVersionRuleSet(embedded in version)ValueSet.compose{id, lockedDate, inactive, rules[]}
ValueSetVersionRule(embedded in ruleSet)ValueSet.compose.include/excludetype = include/exclude
ValueSetRuleFilter(embedded in rule)ValueSet.compose.*.filter{property, operator, value}
ValueSetVersionConceptexpand result / snapshotValueSet.compose.*.concept / expansion.containsexpanded member
ValueSetSnapshotversion.snapshotValueSet.expansionmaterialized {conceptsTotal, expansion[]}
List envelopen/an/a{ data: [...], meta: { total, offset } }

Shared ValueSet fields

FieldTypeCard.Rule
idstring1..1identity; renamed only via change-id
uristring0..1canonical URL; unique
namestring0..1machine name
titleLocalizedName0..1multilingual display
description / purposeLocalizedName0..1
publisherstring0..1code from publisher VS or free text
narrativestring0..1HTML narrative (FHIR text)
experimentalboolean0..1
sourceReferencestring0..1
otherTitle{name, preferred}[]0..*aliases
topic{text, tags[]}0..1
useContext{type, value}[]0..*
identifiersIdentifier[]0..*
contactsContactDetail[]0..*name + telecoms
configurationAttributesany[]0..*
copyright{holder, jurisdiction, statement}0..1
permissions{admin, editor, viewer, endorser}0..1
settings{reviewRequired, approvalRequired}0..1
versions / lastVersionValueSetVersion[]0..*version + status + releaseDate + ruleSet + snapshot

ValueSetVersion fields

FieldTypeRule
idnumberversion key (with version) for PUT vs POST
versionstringsemver-ish; unique per value set
statuscodedraft / active / retired (lifecycle endpoints)
releaseDate / expirationDatedate (YYYY-MM-DD)
preferredLanguagestring
supportedLanguagesstring[]
descriptionLocalizedName
algorithmstringversion algorithm (e.g. semver)
ruleSetValueSetVersionRuleSetthe compose rules
snapshotValueSetSnapshotstored expansion (conceptsTotal, expansion[])
identifiersIdentifier[]

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)

FieldValueSetCanonical URLBinding
publisherpublisherhttps://tx.example/fhir/ValueSet/publisherextensible
version statuspublication-statushttp://hl7.org/fhir/ValueSet/publication-statusrequired
supported/preferred languagelanguageshttp://hl7.org/fhir/ValueSet/languagesrequired (the reference platform has no all-languages)
rule filter operatorfilter-operatorhttp://hl7.org/fhir/ValueSet/filter-operatorrequired

FHIR Mapping (Common)

Internal fieldFHIR R5 pathMapping
idValueSet.iddirect
uriValueSet.urldirect
nameValueSet.namedirect
titleValueSet.titletransform (LocalizedName → language-resolved)
publisherValueSet.publisherdirect
purposeValueSet.purposetransform
narrativeValueSet.textdirect (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 version snapshot.
  • Snapshot vs live: prefer the stored snapshot.expansion when present (fast); fall back to a live expand for drafts / previews. Rule editing previews use expand-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

ActionPrivilege
View / browseauthenticated (dev: guest via the reference platform yupi)
Create / edit / delete / versions / rulesValueSet.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

MethodPathPrivDescription
GET/ts/value-setsreadlist — params below
GET/ts/value-sets/{id}?decorate={bool}readload one (decorate embeds versions + ruleSet + snapshot)
POST/ts/value-sets/transactionwritecreate/update — body ValueSetTransactionRequest {valueSet, version?, identifiers?}
POST/ts/value-sets/{id}/change-idwriterename id — body {id: newId}
DELETE/ts/value-sets/{id}writedelete value set
GET/ts/value-sets/{id}/provenances?version=readprovenance 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

MethodPathPrivDescription
GET/ts/value-sets/{id}/versionsreadversion list
GET/ts/value-sets/{id}/versions/{version}readload one version (with ruleSet + snapshot)
GET/ts/value-set-versions/{id}readload a version by numeric id
POST/ts/value-sets/{id}/versionswritecreate version (body ValueSetVersion)
PUT/ts/value-sets/{id}/versions/{version}writeupdate version
POST/ts/value-sets/{id}/versions/{version}/draftwrite→ draft (empty body, 204)
POST/ts/value-sets/{id}/versions/{version}/activatewrite→ active
POST/ts/value-sets/{id}/versions/{version}/retirewrite→ retired
POST/ts/value-sets/{id}/versions/{version}/duplicatewriteduplicate — body {valueSet, version}
DELETE/ts/value-sets/{id}/versions/{version}writedelete version

Compose rules / rule-sets

MethodPathPrivDescription
POST/ts/value-sets/{id}/versions/{v}/ruleswriteadd a compose rule (ValueSetVersionRule)
PUT/ts/value-sets/{id}/versions/{v}/rules/{ruleId}writeupdate a rule
DELETE/ts/value-sets/{id}/versions/{v}/rules/{ruleId}writedelete a rule
PUT/ts/value-sets/{id}/versions/{v}/rule-sets/{ruleSetId}writeupdate the rule set (e.g. inactive)

Expansion

MethodPathPrivDescription
POST/ts/value-sets/expandreadexpand a saved version — body {valueSet, valueSetVersion}ValueSetVersionConcept[]
POST/ts/value-sets/expand-asyncreadasync expand → JobLogResponse (poll)
POST/ts/value-sets/expand-rulereadpreview one unsaved rule — body {valueSet, valueSetVersion, inactiveConcepts?, rule}ValueSetVersionConcept[]
GET/ts/value-sets/{id}/versions/{v}/expansion-export?format={csv|xlsx|…}readstart expansion export (→ processId)
GET/ts/value-sets/expansion-export-{format}/result/{processId}readdownload export result

FHIR façade (read/validate/sync)

MethodPathDescription
GET/fhir/ValueSet/{id}[|{version}]read FHIR ValueSet
GET/fhir/ValueSetFHIR search
GET/fhir/ValueSet/{id}/$expandexpand by id
GET/fhir/ValueSet/$expand?url=…expand by canonical (used by @helex/tx selects)
GET/fhir/ValueSet/$validate-codevalidate a code against the VS
POST/fhir/ValueSet/$syncsync (body Parameters)

Import

MethodPathDescription
POST/file-importer/value-set/processfile 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

RequirementTarget
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

  1. List / view / create operate over /ts/value-sets* with the shared envelope.
  2. A version’s members come from expanding its ruleSet (or its snapshot), never from the root.
  3. Version lifecycle (draft/activate/retire) + rule add/edit/delete require ValueSet.write; read is open on the guest dev server.
  4. 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)

ColumnSourceNotes
IDidAppLink → view
VersionlastVersion.versiondecorated
Titletitle (localized) → name
Publisherpublisherresolved via publisher VS (useValueSetExpand + resolveLocalizedDisplay), fallback raw
StatuslastVersion.statusAppTag (draft/active/retired)
ConceptslastVersion.snapshot.conceptsTotalexpansion size (from snapshot); blank when no snapshot

Search & filters

  • SearchtextContains (debounce ~400ms).
  • Filter panel (AppFilterBar, mirrors CS list): Publisher (TxConceptSelect publisher VS) · Status (TxConceptSelect publication-status → versionStatus) · Space (AppSelect from GET /spacesspaceId).
  • 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

  1. List renders live /ts/value-sets rows with the column order above.
  2. Search maps to textContains; filters map to publisher / versionStatus / spaceId.
  3. Publisher + status resolve via their value sets; concept count from lastVersion.snapshot.conceptsTotal.
  4. 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)

FieldWidgetRequiredMaps to
IDAppInput (create-only)valueSet.id
URIAppInputvalueSet.uri
NameAppInputvalueSet.name
PublisherTxConceptSelect (publisher VS)valueSet.publisher
ExperimentalAppSwitchvalueSet.experimental
TitleAppLocalizedNamevalueSet.title
DescriptionAppLocalizedNamevalueSet.description
PurposeAppLocalizedNamevalueSet.purpose
IdentifiersTxIdentifier (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 on transaction; validate id, uri, title client-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

  1. Add opens the create form with ID/URI/Title required.
  2. Save posts ValueSetTransactionRequest and navigates to the new value set’s summary.
  3. 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

TabContentSource
SummaryInfo card (icon-list) · Versions card · right column (expansion preview · Tasks · Related artifacts)reuse TedyInfoCard/TedyInfoLine; new VS widgets
MetadataFull field view + edit (ResourceForm, hideHeader) reusing the TEDY.02.2 field setPOST /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, filtersrules + 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 live POST /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’s type (include/exclude), codeSystem(+version) or valueSet(+version), explicit concepts[], and filters[] (property/operator/value; operator from filter-operator VS).
  • Edit: add (POST .../rules), update (PUT .../rules/{id}), delete (DELETE .../rules/{id}); toggle rule set inactive (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

  1. View loads /ts/value-sets/{id}?decorate=true and renders Summary/Metadata/Concepts/Rules.
  2. Expansion tab shows members from snapshot (or live expand); columns per ValueSetVersionConcept.
  3. Rules tab lists include/exclude rules; edits hit rules/rule-sets endpoints; preview via expand-rule.
  4. 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 (guest yupi).

Child specs:

Glossary Terms

Term (canonical)Meaning
Map SetA FHIR ConceptMap — a set of mappings from source concepts to target concepts.
VersionA MapSetVersion with a publication status + release date; carries the scope + statistics.
ScopeMapSetVersion.scope — the source and target: sourceType/targetType (code-system/value-set/external-canonical-uri) + the referenced systems/value sets.
AssociationMapSetAssociation — one mapping: source {code, codeSystem, display} → target {…}, a relationship, verified, noMap.
RelationshipThe map relationship (equivalent / source-is-narrower-than-target / source-is-broader-than-target / related-to / not-related-to).
No-mapAn association marking that a source has no target mapping (noMap: true).
VerifiedWhether an association has been human-verified (verified).
StatisticsMapSetVersion.statistics — counts: sourceConcepts, equivalent, noMap, narrower, broader, unmapped, inactiveSources/Targets.
AutomapA background job that proposes associations by matching code / designation (.../associations/automap).
Map Set conceptMapSetConcept — a source concept + its associations (the source-centric view).
PropertyMapSetProperty — 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 / Resourcethe reference platform APIFHIRNotes
MapSet/ts/map-setsConceptMaplist + detail; versions decorated
MapSetVersion/ts/map-sets/{id}/versionsConceptMap.versionstatus ∈ draft/active/retired; carries scope + statistics
MapSetScope(embedded in version)ConceptMap.group.source/targetsource/target type + system/value-set refs
MapSetAssociation/ts/map-sets/{id}/associationsConceptMap.group.element.targetsource → target + relationship + verified + noMap
MapSetConcept.../versions/{v}/conceptsConceptMap.group.elementsource concept + its associations
MapSetStatisticsversion.statisticsn/amapping counts
MapSetProperty(embedded in map set)ConceptMap.propertydefined properties on associations

Shared MapSet fields

FieldTypeRule
idstringidentity; renamed via change-id
uristringcanonical URL
name / titlestring / LocalizedName
description / purposeLocalizedName
publisherstringpublisher VS or free text
narrativestringHTML (FHIR text)
experimentalboolean
otherTitle / topic / useContext / sourceReferenceas ValueSet
identifiersIdentifier[]
contactsContactDetail[]
copyright / settingsobjectssettings.{reviewRequired,approvalRequired}
versionsMapSetVersion[]version + status + scope + statistics
propertiesMapSetProperty[]

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)

FieldValueSetCanonical URL
publisherpublisherhttps://tx.example/fhir/ValueSet/publisher
version statuspublication-statushttp://hl7.org/fhir/ValueSet/publication-status
supported/preferred languagelanguageshttp://hl7.org/fhir/ValueSet/languages
association relationshipconcept-map-relationshiphttp://hl7.org/fhir/ValueSet/concept-map-relationship

FHIR Mapping (Common)

InternalFHIR R5 ConceptMap path
id/uri/name/title/publisherConceptMap.id/.url/.name/.title/.publisher
version scope.sourceCodeSystems/sourceValueSetConceptMap.group.source / .sourceScope
version scope.targetCodeSystems/targetValueSetConceptMap.group.target / .targetScope
association sourcegroup.element (code + display)
association target + relationshipgroup.element.target (code + relationship)
association noMapgroup.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-async refreshes them.
  • publisher / status / language / relationship selects use TxConceptSelect(url=…).

Shared Privileges

ActionPrivilege
View / browseauthenticated (dev: guest via the reference platform yupi)
Create / edit / delete / versions / associationsMapSet.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

MethodPathPrivDescription
GET/ts/map-setsreadlist — params below
GET/ts/map-sets/{id}?decorate={bool}readload one (decorate embeds versions + scope + statistics)
POST/ts/map-sets/transactionwritecreate/update — MapSetTransactionRequest {mapSet, version?, properties?, associations?}
POST/ts/map-sets/{id}/change-idwriterename id — {id: newId}
DELETE/ts/map-sets/{id}writedelete map set
GET/ts/map-sets/{id}/provenances?version=readprovenance

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

MethodPathDescription
GET/ts/map-sets/{id}/versionsversion 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}/versionscreate 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}/duplicateduplicate
DELETE/ts/map-sets/{id}/versions/{version}delete version
POST/ts/map-sets/{id}/versions/{version}/reload-statistics-asyncrecompute statistics → JobLogResponse

Associations (the mappings)

MethodPathDescription
GET/ts/map-sets/{id}/associationsflat association search (MapSetAssociationSearchParams: mapSetVersion, relationships, verified, noMap)
GET/ts/map-sets/{id}/associations/{associationId}load one
GET/ts/map-sets/{id}/versions/{v}/conceptssource-centric (MapSetConcept + associations)
POST/ts/map-sets/{id}/versions/{v}/associationscreate association
PUT/ts/map-sets/{id}/versions/{v}/associations/{id}update association
POST/ts/map-sets/{id}/versions/{v}/associations-batchbulk 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/automapMapSetAutomapRequestJobLogResponse
POST/ts/map-sets/{id}/properties/{propertyId}/delete-usagesclear a property’s usages

Import

MethodPathDescription
GET/file-importer/map-set/csv-templatedownload CSV template
POST/file-importer/map-set/processfile 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

RequirementTarget
List load< 2s; server-side pagination (20/page); versionsDecorated only
Associations gridserver-paged; filter by relationship / verified / no-map
Automap / reload-statisticsasync job; non-blocking (poll or fire-and-refresh)
Search debounce~400ms

Business Tests

  1. List / view / create operate over /ts/map-sets* with the shared envelope.
  2. A version carries a scope (source→target) and statistics; associations are read via .../associations or .../versions/{v}/concepts.
  3. Association authoring (create/edit/verify/unmap/automap) + version lifecycle require MapSet.write; read is open on the guest dev server.
  4. 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

ColumnSourceNotes
IDidAppLink → view
Versionversions[0].version (decorated)latest
Titletitle (localized) → name
Publisherpublisherresolved via publisher VS
Statusversions[0].statusAppTag
Associationsversions[0].statistics.sourceConceptsmapping count (from statistics); blank when none

Search & filters

  • SearchtextContains (debounce ~400ms).
  • Filters (AppFilterBar): Publisher (TxConceptSelect publisher VS) · Status (TxConceptSelect publication-status → versionStatus) · Space (AppSelectspaceId).
  • 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

  1. Renders live /ts/map-sets rows with the column order above.
  2. Search → textContains; filters → publisher/versionStatus/spaceId.
  3. 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)

FieldWidgetRequiredMaps to
IDAppInput (create-only)mapSet.id
URIAppInputmapSet.uri
NameAppInputmapSet.name
PublisherTxConceptSelect (publisher VS)mapSet.publisher
ExperimentalAppSwitchmapSet.experimental
TitleAppLocalizedNamemapSet.title
DescriptionAppLocalizedNamemapSet.description
PurposeAppLocalizedNamemapSet.purpose
IdentifiersTxIdentifiermapSet.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; validate id, uri, title client-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

  1. Add opens the create form with ID/URI/Title required.
  2. Save posts MapSetTransactionRequest and navigates to the new map set’s summary.
  3. 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

TabContentSource
SummaryInfo card (icon-list) · Versions card · Scope (source→target) · Statistics · Tasks · Related artifactsreuse TedyInfoCard/widgets; new MS scope + stats widgets
MetadataFull field view + edit (ResourceForm) reusing the TEDY.03.2 field setPOST /ts/map-sets/transaction
AssociationsThe 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 (TxConceptSelect over the scope systems) + relationship (TxConceptSelect concept-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

  1. View loads /ts/map-sets/{id}?decorate=true and renders Summary/Metadata/Associations.
  2. Summary shows the version scope + statistics; Reload statistics triggers the async job.
  3. Associations grid lists mappings; filter by relationship/verified/no-map; verify/unmap/automap/add/edit hit the association endpoints.
  4. 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} (decorated inlines 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 + Modeling nav 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? } (status draft/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 (POST create / 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 a PackageResource, and re-saves the whole version (linkResourceToSpace / unlinkResourceFromSpace, libs/tedy/src/lib/client/spacePackageLink.ts). Dedup by resourceType + resourceId; creates the default package ({code: space.code||'default', status:'draft'} + version 1.0.0) if absent.
  • Diff: GET /spaces/{id}/diffLorque 202 {id}; poll GET /lorque-processes/{id} until finished/failed; result is base64 JSON. runSpaceDiff (spaceDiffClient.ts) drives start→poll→decode and rejects with the decoded error on failed.
  • 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 spaceId by JOINing through package_version_resource → package_version → package → space, so searchCodeSystems({spaceId}) returns exactly the linked set.

Metadata — attributes (Core + GitHub)

SectionFieldsNotes
Corecode (required), name (AppLocalizedName), active, globalSearch, servers (multiselect of terminology-server codes)view ⇄ edit via useDataController
GitHubenable + repo (addonBefore=https://github.com/) + per-provider directory inputs (GET /spaces/github/providers)object-defaulting before binding; save nulls disabled integrations
Azure DevOpssame 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.writespaces/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 (AppPopconfirmunlinkResourceFromSpace).
  • Comparison (SpaceDiffTab.tsx, mode diff) — 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, mode github) — 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.tsMSDEVOPS_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 viewTEDY tab (TxResourceBar)Status
Space dashboard (linked resources per type)Code Systems / Value Sets / Map Sets tabsBuilt — 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 tabBuilt — whole-space diff table (Lorque)
space-diff-matrix (resource × server grid)Comparison tabBuilt — diff items + State filter + row selection + bulk change-server + per-row sync (local/external)
space-githubRepository tabBuilt — auth / status / push / pull / per-file diff
space-ms-dev-opsAzure DevOps tabBuilt — dedicated msdevops tab/route (flagged), reuses the provider-parametrized SpaceGitTab
Summary tabBuilt (space info card; TEDY addition)

Metadata attribute comparison (point 3)

Reference field(s)SectionTEDY status
code, names, active, globalSearch, terminologyServersCoreBuilt (view + edit)
integration.github (repo + per-provider dirs)GitHubBuilt
integration.msDevops (repo + dirs)Azure DevOpsBuilt, feature-flagged (VITE_TX_MSDEVOPS_ENABLED)
packages sub-list (open / add / delete)PackagesDescoped — replaced by the hidden auto-default package (resources map directly to the space)
description, languages, defaultLanguage, siteUrlSSG metadataPlanned (mdBook) — additive/nullable when mdBook lands
ssgSkin, ssgThemeAccent, ssgThemeSwitcher, ssgFooterMessage, ssgFooterCopyright, ssgTxServer, ssgSearch, ssgLogoSSG generatorDescoped — mdBook replaces the SSG generator
acl (owners/editors/viewers), sharedACLDescoped — access enforced via *.Space.* privileges
import-from-GitHub (POST /wiki-import/github)ImportDescoped — 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.

  1. 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’s terminologyServers), and a per-row Sync menu — into space (local) / to server (external) — via POST /package-resources/{id}/sync (packageResourceClient.ts).
  2. Per-resource Comparison shortcutBuilt. Each linked CS/VS/MS row has a Compare action opening the Comparison tab.
  3. Dedicated MS DevOps tabBuilt. msdevops added to TxResourceMode + tabsFor('space') (shown only when MSDEVOPS_ENABLED) + SpaceResourceLayout.MODES + a spaces/:id/msdevops route rendering SpaceGitTab provider="msdevops" (provider-aware label/icon/empty-state).
  4. 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 lacks Server.write).
  • GET /servers/{id}Server (write-privileged load-for-edit); POST / PUT → upsert.
  • GET /servers/{id}/check-connectionServerConnectionCheckResult.
  • 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’s authoritative* 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 ResourceList with the filter panel on the right (resource-type multiselect, Code Systems preselected) + search over GET /servers/{id}/resources/{type}. Each row has two actions: Open in Terminology eXplorer (web viewer at TX_VIEWER_URL, e.g. https://tx.helex.dev/fhir/{Type}/{name}) and Open external FHIR resource ({server.rootUrl}/{Type}/{name}). TX_VIEWER_URL is configured via VITE_TX_VIEWER_URL (config/tx.ts; default https://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/:idTxResourceBar 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. TxResourceBar extended with terminology-server (no version chip) + a resources mode.

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; duplicate code → error).
  • Public, unauthenticated: GET /public/ecosystems/{code} → the ecosystem.json document (linked/downloaded by the UI, never parsed).

Screens (built)

  • List — Code (→ edit) · Name · Servers (tags resolved to server codes) · Active · a View ecosystem.json link; search → textContains; Add ecosystem gated on *.Space.write.
  • Add / Edit — one card: code (immutable on edit), multi-language name (required), read-only formatVersion, description, active, serverIds multi-select. Save via POST/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 + Management nav 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 }.

  • kindproperty | designation.
  • type — FHIR concept-property type (code, Coding, string, integer, boolean, dateTime, decimal).
  • rulePropertyRule { 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) — saveDefinedProperty upserts by presence of id.
  • 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 used defined property locks its identity (referenced by ≥1 code system): in the reference edit form, when used the 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 get modeOverride:'view' when used (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. used is server-computed, so the constraint is also enforced backend-side.
  • Editing/adding requires *.DefinedProperty.write.

Screens

  • List (pages/DefinedPropertyList.tsx, resource-list) — a standard ResourceList: Name (link, with a used info marker) · Description · URI · Kind (tag) · Type. textContains search; 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 standard ResourceForm with declarative sections: Name (required), URI, Kind (select), Type (select of the concept-property types), Description (localized). Create + edit; POST/PUT then return to the list.
    • When the property is used, name/URI/kind/type are locked (view) and only Description is editable; the whole form is view when the user lacks *.DefinedProperty.write.
    • The form mount is gated until the record loads into the dataControllerResourceForm seeds its antd form from dataController.current once at mount, so an async dc.load must complete before it first renders (a ready flag set in the load effect).

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[] }.

  • kindcodesystem | valueset | identifier | root (FHIR namingsystem-type).
  • codeSystem — referenced code system id, relevant when kind === 'codesystem'.
  • description — plain string (not localized).
  • statusdraft | active | retired.
  • NamingSystemIdentifier { type?, value?, preferred? }type from oid / uuid / uri / other; exactly one preferred.

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 draft naming systems are editable. The reference list opens draft in the edit form and active/retired in a read-only view. TEDY mirrors this — the form drops to view mode when the record’s status is not draft (subtitle: “Only draft naming systems can be edited — read-only.”). New records are created as draft.
  • id is immutable on edit (create-only; disabled in edit).
  • codeSystem is required when kind === 'codesystem' (validated on save).
  • Lifecycle is action-driven, not a form field: Activate only from draft; Retire unless already retired; Delete allowed for any status. All gated on *.NamingSystem.write.

Screens

  • List (pages/NamingSystemList.tsx, resource-list) — a standard ResourceList: ID (link → the form) · Name · Source · Kind (tag) · Status (tag). textContains search; Add (gated *.NamingSystem.write); per-row lifecycle actions — Activate (draft), Retire (unless retired), Delete (confirm).
  • Form (pages/naming-system/NamingSystemForm.tsx) — a standard ResourceForm with declarative sections: ID (required; immutable on edit via modeOverride:'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; POST then return to the list. Same load-gated mount as Defined Properties (ResourceForm seeds once from dataController.current).
    • Validation: ID + name + kind required; codeSystem required when kind === 'codesystem'.

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 draft rows → changeXVersionStatus(id, latestVersion, 'active').
  • Retire — the selected active rows → …'retired'.
  • Add to releasedraft + active rows → 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 / MapSetList pass selection={{ selectedKeys, onChange, bulkActions }}.
  • Space tabs: SpaceResourceListTab passes the same plus onUnlink + 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 standard ResourceList: 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 checksPOST /checklists/assertions/run-checks); a Notes widget (Generate notes + downloadable attachments via /releases/{id}/notes); and a Tasks widget (ResourceTasksWidget scoped 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.

Business rules / constraints

  • Resources are editable only while draft (Add / edit / delete resource). active/retired lock the resource set.
  • Server-sync requires status === 'active' + a terminologyServer + {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

  1. Data layer — extend releaseClient (loadRelease, saveRelease, loadReleaseResources, updateReleaseResource, deleteReleaseResource, changeReleaseStatus, serverSync, validateSync, generateNotes, loadReleaseProvenances) + useReleases/useRelease hooks + exports.
  2. List (ReleaseList) + route + Management menu entry.
  3. Detail shellTxResourceBar release type + ReleaseResourceLayout (Summary/Metadata/Provenance/Comparison) + routes.
  4. Summary — metadata card + resources table (add/edit/delete, draft-gated) + lifecycle actions.
  5. Metadata create/edit ResourceForm + /releases/new.
  6. 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)

  1. Concepts — cross-code-system concept search, GET /ts/concepts (searchAllConcepts), scoped by the filter’s codeSystem list. 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.
  2. Code SystemsGET /ts/code-systems (useCodeSystems), textContains + spaceId + publisher. Columns: Id · URI · Name. Gated *.CodeSystem.read.
  3. Value SetsuseValueSets, same filters. Gated *.ValueSet.read.
  4. Map SetsuseMapSets, same filters. Gated *.MapSet.read.
  5. SNOMED CTGET /snomed/concepts?term= (searchSnomedConcepts), only when the query is ≥3 chars. Columns: Concept id · Term (FSN/PT). Gated snomed-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 by spaceId.
  • Filter panel (toggle) — Publisher (TxConceptSelect on the publisher value set) applied to CS/VS/MS; Code Systems (multiselect) scoping the Concepts group; Clear all.

Data layer

  • client/conceptClient.tssearchAllConcepts(params) (GET /ts/concepts).
  • client/snomedClient.ts (new) → searchSnomedConcepts({term, limit}) (GET /snomed/concepts) + SnomedConcept type.
  • Existing useCodeSystems / useValueSets / useMapSets / useSpaces for 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:

FieldKindNotes
titlemulti-language, requiredper-language inputs + “Add translation”
otherTitlerepeatable {language, name}“Add other title”
idtext, required in add, immutable on editid-change is a separate confirm modal
nametextcomputable name
publishervalue-set select (publisher) + Custom free-text toggle
uritext, required
descriptionmulti-language
purposemulti-language
topictext + tags
useContextrepeatable {type, value}
sourceReferencetext
replacesresource search (same type)
experimentalcheckbox
externalWebSourcetext

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.
  • Copyrightcopyright holder / 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 Systemcontent (VS codesystem-content-mode, required; supplement reveals baseCodeSystem search) · 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):

  1. Title as multi-language (dev lists English/Estonian; TEDY shows one).
  2. Topic.
  3. Use context.
  4. Identifiers section.
  5. Configuration attributes section.
  6. Designations table (code/uri/required/preferred/showInList/description/order).
  7. Properties — dev shows uri/type/required/showInList/description/order; TEDY shows only name/type.
  8. Contacts.
  9. Copyright block (holder / jurisdiction / statement).
  10. Human-readable enum labels (dev “Complete” / “Case insensitive” vs TEDY raw complete / ci).
  11. (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 via mode + viewMode.
  • id required in add, immutable on edit (dedicated id-change modal).
  • uri required; title required; content required (default complete); caseSensitive required (default ci).
  • 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 eventual ResourceMetadataForm).
  • CodeSystem type extended: topic, useContext[], configurationAttributes[], copyright{holder,jurisdiction,statement} (added to existing otherTitle, 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 CodeSystem object in the transaction save).

Remaining (CS):

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

  1. Analyze (synchronous) — POST {txapi}/file-importer/analyze, multipart/form-data with parts request (JSON { link, type }) and optional file. 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.
  2. 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.
  3. Process (asynchronous job) — POST {txapi}/file-importer/{resource}/process, multipart/form-data with parts request (JSON, the resource’s processing DTO) and optional file. Returns { jobId } immediately.
  4. PollGET {txapi}/job-logs/{jobId} every 3 s until execution.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) and file (the blob, sent with filename files). The file part is omitted when importing from a URL (link/url / sourceUrl carries it and the server fetches).
  • releaseDate is serialized to a date string before send.
  • CS/VS controllers URL-decode the request part before JSON parsing; TEDY sends plain JSON (tolerated). Map Set uses the field name url (not link).
  • 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

PurposeEndpointMethodBodyAsync
Analyze (column detect)/file-importer/analyzePOSTmultipart request {link,type} + optional fileno
CS file import/file-importer/code-system/processPOSTmultipart request (CS DTO) + optional filejob
VS file import/file-importer/value-set/processPOSTmultipart request (VS DTO) + optional filejob
MS file import/file-importer/map-set/processPOSTmultipart request (MS DTO) + optional filejob
MS CSV template/file-importer/map-set/csv-templateGET— (application/csv)no
Association import/file-importer/association/processPOSTmultipart request + filejob
ICD-10 import/icd10/import?url=POSTIntegrationImportConfiguration JSONjob
Orphanet import/orphanet/importPOSTmultipart request (config) + optional filejob
LOINC list archives/bob/objects?container=loincGETno
LOINC archive files/loinc/archives/{uuid}/files?language=GETno
LOINC upload zip/bob/objectsPOSTmultipart container,file,metano
LOINC import/loinc/import/from-archivePOSTJSON {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 named concepts; CSV delimiter may be ; or ,; UTF-8; header on line 1.
  • Value Set: csv, tsv (the reference DTO also lists json, fsh).
  • Map Set: CSV only, against the downloadable template.
  • 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 (key helex.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.tsanalyzeFile, processCodeSystemImport, processValueSetImport, processMapSetImport, downloadMapSetTemplate.
  • libs/tedy/src/lib/client/integrationImportClient.tsimportIcd10, 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 carries space, 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.ts files, 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 the Csv/Tsv/Xlsx FileParserTest parsers. 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)

  1. useImportJob pollingstart → running → finished transition on the 3 s interval (fake timers); resolves the JobLog once execution.status !== 'running'; honors the ~60 s timeout window; cancel/unmount stops polling (no further job-logs calls); surfaces a job/HTTP error. Mock jobClient.
  2. Column-mapping validation (CS) — at most one identifier; date/dateTime rows require propertyTypeFormat; duplicate concept-code / hierarchical-concept warns; only import-checked rows are considered.
  3. Portable configparseConfig extracts the delimited ===TEDY-IMPORT-CONFIG=== block, falls back to whole-file JSON, returns undefined on invalid; buildConfigBlock round-trips; applyMappingRows merges saved rows onto analyzed columns by columnName (unmatched columns untouched).
  4. Request builders — the multipart shape: parts named exactly request (JSON) + file (filename files); file omitted in link mode; releaseDate serialized; properties[] includes only import rows; Map Set uses url (not link); DTOs drop undefineds.
  5. 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; JobLogPanel renders 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-templates live in the TEDY build (module assets vs @helex/*).
  • Real privileges once TEDY auth moves off the dev guest superuser (see docs/TODO.md auth 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.

  1. 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.
  2. Version — existing draft version select, or new: semantic version number, status (publication-status select), release date; for a supplement, the base/supplement version.
  3. Source — type radio link | file; format select csv|tsv|xlsx|json; the URL input or the file input accordingly. Analyze button.
  4. 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.
  5. OptionsgenerateValueSet (Yes/No), dryRun (validate vs import), cleanVersion (delete concepts / hold), replaceConcept (replace / merge).
  6. 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):

FieldMeaning
columnNamesource column (from analyze; read-only)
propertyNametarget: concept-code, hierarchical-concept, is-a, or a defined/CS property; new-property allowed (_newProp)
propertyTypefrom the concept-property-type value set, or designation
propertyTypeFormatrequired for date/dateTime (format string)
propertyCodeSystemfor code/Coding values — the target system
propertyDelimitermulti-value split delimiter
languagefor designations (display:en etc.)
preferredsingle preferred designation (mutually exclusive across rows)
importinclude 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-codehierarchical-conceptdisplaydefinitionis-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 with import)
  • 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.
  • tsc clean; en/et/ru i18n in sync; verified against htx.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).

  1. Destination Value Set — existing (search) or new (id, title multi-language required, URI required, description).
  2. Version — existing draft version select, or new: semantic version, status (publication-status), release date.
  3. Rule set — existing rule select (codeSystem | version), or new: code-system search + code-system-version select. Concepts radio: all vs exact (default exact). Analyze + mapping appear only for exact.
  4. Source + Analyze (when exact) — source type radio link | file; format radio csv | tsv; URL or file input; Analyze.
  5. Column mapping (after analyze) — two selects over detected columns: code (concept code, required) and display (optional).
  6. FooterdryRun radio (import-data vs validate-data), Process.

Request DTO (/file-importer/value-set/process)

multipart request JSON:

  • link, type (csv|tsv; server also accepts json|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 buildermapping { 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.
  • tsc clean; en/et/ru i18n in sync; verified against htx.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).

  1. Destination Map Set — existing (search) or new (id, title multi-language, URI, description).
  2. Version
    • existing draft version select, plus two MS-only clean radios: cleanRun (delete existing associations / hold) and cleanAssociationRun (overlapping associations: ignore / keep-both);
    • or new version: semantic version, status, release date, scope editor.
  3. File — a Download template link (GET /file-importer/map-set/csv-templatemapset-template.csv) and a file input.
  4. FooterProcess (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, not link)
  • 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.
  • tsc clean; en/et/ru i18n in sync; verified against htx.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”, version 10, codeSystem icd10, name “ICD-10 WHO Edition”). Send request submits.
  • Endpoint: POST /icd10/import?url={sourceUrl} with the IntegrationImportConfiguration JSON body. The server downloads and parses the zip at sourceUrl — no browser file upload, no column mapping.
  • Job: background; reference polls every 5 s (TEDY will use the shared 3 s useImportJob). edition is 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, multipart request (IntegrationImportConfiguration built from the CS form: sourceUrl ← source.file, uri/codeSystem/codeSystemName/codeSystemDescription ← codeSystem.*, version ← codeSystemVersion.version, status, validFrom ← releaseDate, plus generateValueSet/cleanRun/cleanConceptRun) + optional file.
  • 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 (languages value 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 .csv entries 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 (multipart container=loinc, file, meta {version, language}); version auto-detected from Loinc_<version>.zip. List via GET /bob/objects?container=loinc; inner entries via GET /loinc/archives/{uuid}/files?language=.
  • Import (phase 2): POST /loinc/import/from-archive JSON { archiveUuid, version, language, fileMap? } where fileMap maps slot → CSV entry (omit a slot to use the server default). Target Code System is loinc.
  • Job: background (shared poll).

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-10Set 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.); LOINCfileMap 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; icd10 CS populated.
  • Orphanet: CS form + file/link → import → JobLog; CS created/updated.
  • LOINC: upload a Loinc_<v>.zip, pick slots (or defaults), confirm → JobLog; loinc CS populated.
  • tsc clean; en/et/ru i18n in sync; verified against htx.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:

  1. lists the full reference frontend config surface and what each var does;
  2. maps each var to its TEDY equivalent, or records why it is host-owned / out-of-scope;
  3. defines the recommended TEDY env list and neutral .env.example entries.

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

VarPurposeExample valueTEDY disposition
BASE_HREFApp mount path for relative URLs/ or /tedyBuild-time — host mounts TEDY under its route
TX_APITerminology-server API base/apiPortedVITE_TX_TARGET
SWAGGER_URLSwagger UI path/swagger/❌ Out of scope (no Swagger UI in TEDY)
DEFAULT_LANGUAGEFallback UI languageen⚠️ Host-owned (@helex/i18n)
UI_LANGUAGESSelectable UI languages (JSON array)["en","et","lt","cs"]⚠️ Host-owned
CONTENT_LANGUAGESMultilingual content input languages (JSON array)["en","et","lt","ru","de","fr"]🟡 Candidate — drives TEDY’s localized-name editors
EXTRA_LANGUAGESDisplay-name overrides for extra content langs (JSON object){"cs":{"en":"Czech"}}🟡 Candidate (only with extra content langs)
OAUTH_ISSUEROIDC realm / issuer URLdummy or realm URL⚠️ Host-owned auth
OAUTH_CLIENT_IDOAuth2 client iddummy⚠️ Host-owned auth
OAUTH_SCOPEOAuth2 scopes at loginopenid⚠️ Host-owned auth
GUEST_DISABLEDDisable anonymous accessfalse⚠️ Host-owned auth
SNOWSTORM_URLSNOMED (Snowstorm) API basehttps://snowstorm.example/🟡 Candidate (TEDY reads SNOMED via backend /snomed; direct base only if deep-calling)
SNOMED_BROWSER_URLSNOMED browser deep-link basehttps://snomed.example/🟡 Candidate — TEDY has SNOMED search/global-search
SNOMED_BROWSER_DAILY_BUILD_URLSNOMED daily-build browser basehttps://snomed.example/🟡 Candidate (optional variant)
FML_EDITORFHIR Mapping-Language editor service/fml-editor❌ Out of scope (not ported)
PLANT_UML_URLPlantUML diagram service/plantuml❌ Out of scope
CHEF_URLFSH/SUSHI/GoFSH compiler service/chef❌ Out of scope (IG authoring)
CHEF_FHIR_VERSIONFHIR version for the compiler service4.3.0❌ Out of scope
FHIR_UML_CONVERTER_APIFHIR→UML converter servicehttps://svc.example/fhir2uml/api❌ Out of scope
SKIN / SKIN_URLNamed theme / external stylesheet(named theme)⚠️ Host-owned theming
BRANDINGProduct 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)

VarPurposeDefaultConsumedLegacy fallback
TEDY_TARGETTerminology-server API base (incl. api segment; differs per deployment, e.g. /api vs /lmb-api). Proxied /txapi → {target}.https://tx.example/api✅ vite proxyVITE_TX_TARGET
TEDY_BASE_HREFApp mount path (Vite base)./tedy/✅ vite base
TEDY_BRANDINGProduct name in TEDY-owned chrome.TEDY✅ document title (standalone)
TEDY_MSDEVOPS_ENABLEDOffer the Azure DevOps git integration on a Space’s Metadata + Repository tabs. Unset/0 hides it.0MSDEVOPS_ENABLEDVITE_TX_MSDEVOPS_ENABLED

Terminology-server integrations (TX_ prefix)

VarPurposeConsumedLegacy fallback
TX_PUBLISHER_VS_URLPublisher ValueSet canonical — resolves publisher display names.VITE_TX_PUBLISHER_VS_URL
TX_PUBLISHER_CS_URLPublisher CodeSystem canonical.VITE_TX_PUBLISHER_CS_URL
TX_VIEWER_TARGETTerminology eXplorer base — opens a resource’s web page + FHIR API from Servers → Resources.VITE_TX_VIEWER_URL
TX_SNOWSTORM_TARGETSnowstorm API base (direct calls; TEDY normally reads SNOMED via backend /snomed).🟡 exported; TODO
TX_SNOWSTORM_BROWSERSNOMED browser deep-link base.✅ Global Search SNOMED group
TX_SNOWSTORM_DAILY_BROWSERSNOMED daily-build browser base.🟡 exported; TODO
TX_CHEF_TARGET / TX_CHEF_FHIR_VERSIONFSH/SUSHI compiler (not ported).TODO
TX_PLANTUML_TARGETPlantUML service (not ported).TODO
TX_FHIR2UML_TARGETFHIR→UML converter (not ported).TODO
TX_SWAGGER_TARGETSwagger UI (not ported).TODO

Content / UI languages (VITE_ prefix, JSON-encoded)

VarPurposeDefault
VITE_DEFAULT_LANGUAGEFallback content language.en
VITE_UI_LANGUAGESSelectable UI languages.["en"]
VITE_CONTENT_LANGUAGESLanguages offered in localized-name editors.["en"]
VITE_EXTRA_LANGUAGESDisplay-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 nameCurrent name
VITE_TX_TARGETTEDY_TARGET
BASE_HREFTEDY_BASE_HREF
BRANDINGTEDY_BRANDING
VITE_TX_MSDEVOPS_ENABLEDTEDY_MSDEVOPS_ENABLED
DEFAULT_LANGUAGEVITE_DEFAULT_LANGUAGE
UI_LANGUAGESVITE_UI_LANGUAGES
CONTENT_LANGUAGESVITE_CONTENT_LANGUAGES
EXTRA_LANGUAGESVITE_EXTRA_LANGUAGES
VITE_TX_PUBLISHER_VS_URLTX_PUBLISHER_VS_URL
VITE_TX_PUBLISHER_CS_URLTX_PUBLISHER_CS_URL
VITE_TX_VIEWER_URLTX_VIEWER_TARGET
SNOWSTORM_URLTX_SNOWSTORM_TARGET
SNOMED_BROWSER_URLTX_SNOWSTORM_BROWSER
SNOMED_BROWSER_DAILY_BUILD_URLTX_SNOWSTORM_DAILY_BROWSER
CHEF_URLTX_CHEF_TARGET
CHEF_FHIR_VERSIONTX_CHEF_FHIR_VERSION
PLANT_UML_URLTX_PLANTUML_TARGET
FHIR_UML_CONVERTER_APITX_FHIR2UML_TARGET
SWAGGER_URLTX_SWAGGER_TARGET
GUEST_DISABLEDOAUTH_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

  1. Non-/ts base paths. Servers (/servers), Ecosystems (/ecosystems), Spaces (/spaces), IGs (/implementation-guides) and the public /public/ecosystems are served off the API root, not /ts/*. Verified working through the /txapi proxy on the dev backend (list endpoints return 200). Confirm the same holds in staging/prod edge config. (verified in dev)
  2. 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.
  3. 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).
  4. 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)

  1. Two privilege families (Server.* and Space.*) both gate server actions in the reference (backend uses Server.*; route guards/edit affordances use {id}.Space.*). DECIDE whether the target auth model actually grants {id}.Space.* for server ids.
  2. Load-for-edit needs Server.write (GET /servers/{id} is write-gated). A read-only details view for a Server.read-only user must source from the list payload (public view, no secrets), not GET /{id}. The built read view uses GET /{id} (fine for the superuser dev); revisit for the phased edit/details split.
  3. Secret masking round-trip. ✅ Implemented — clientSecret / Authorization header values are masked to null on read and the edit form uses the _masked = “null means keep persisted” convention (ServerMetadata seeds _masked on load and strips it on save). Verify against a server with real credentials that an unchanged edit preserves the secret.
  4. edit folded into details. TEDY convention has no /:id/edit route (edit is a toggle inside the details/metadata view). This deviates from the reference’s separate /edit — intentional; flag for reviewers.
  5. 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.
  6. exclusions has no editor in the reference (round-trips via ecosystem import/export only) — treat as pass-through.

Ecosystems (TEDY.09)

  1. Servers dependency. The picklist + tag resolution need GET /servers. If /servers is gated by *.Server.read (distinct from Space.read), a Space.read-only user opening Ecosystems could get an empty/403 picklist — handle gracefully.
  2. formatVersion is effectively constant "1" (server-defaulted, read-only) — kept in the model/form for fidelity.
  3. Public ecosystem.json link is /txapi/public/ecosystems/{code} — confirm the proxy forwards /txapi/public/* and that it’s genuinely unauthenticated in the deployed env.

Spaces (TEDY.07)

  1. 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 through TxResourceBar; resource tabs read the space’s linked resources, and Comparison/Repository are tabs.
  2. 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.
  3. 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 a PackageResource + PackageTransactionRequest), never creates. The package/version layer is hidden behind an auto-managed default package (linkResourceToSpace/unlinkResourceFromSpace).
  4. Zero-package default (resolved). When a space has no package, add-as-link creates a default package ({code: space.code||'default', status:'draft'}) with a 1.0.0 version, 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.
  5. Diff is async (Lorque), built. GET /spaces/{id}/diff → 202 {id}; poll GET /lorque-processes/{id} until finished/failed; result is base64 JSON. runSpaceDiff drives start→poll→decode and surfaces the decoded failed error in the tab. Note: on the dev backend the diff fails with TC105: 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).
  6. GitHub tab = OAuth-redirect state machine (built). authenticate may 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/status is 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.
  7. textContains space search — TEDY’s list wires it; DECIDE whether the backend applies it server-side or it should be dropped.

Implementation Guides (TEDY.06)

  1. 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).
  2. Reference-web IG model has fields the backend ignores (otherTitle, topic, useContext, sourceReference) — the built type includes only persisted fields.
  3. GitHub push ignores per-file selection (server recomputes all changed files); the per-file checkboxes are cosmetic — DECIDE whether to keep the affordance.
  4. List delete privilege typo in the reference (*.Implementation.maintain) — use the correct *.ImplementationGuide.maintain.
  5. “Add FHIR” is a dead menu item in the reference — implement a FHIR-import flow or omit.
  6. MapSet skipped on GitHub export (no PATHS entry) — expected or a gap? DECIDE.
  7. 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)DispositionNotes
GlobMatcherTest (16)portedutil/GlobMatcherTest
EcosystemPatternMatchingTest (28)portedutil/GlobMatcherTest (pattern detection) + util/AuthoritativePatternTest (parser + matcher)
AuthoritativeUrlAndQueryParseTest (11)portedutil/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)flintengine/DisplayLanguagePrecedenceTest (display precedence repro)
SystemRequestContextTest (4)n/afhirest request-context plumbing

ecosystem

Explorer test (methods)DispositionNotes
EcosystemConfigTest (23)ported (partial)ecosystem/EcosystemConfigTest; Teagle adds server type (http/editable/local); npm/cache fields not modeled
EnvInterpolatorTest (7)portedecosystem/EnvInterpolatorTest
EcosystemControllerAuthoritativeTest (2)portedsemantics → 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)portedecosystem/EcosystemServerAuthTest (missing-field/unsupported-method failures at load time)
EcosystemServerAuthDeserializationTest (4)portedecosystem/EcosystemServerAuthTest (password/oauth credential forms, markdown access_info)
UpstreamAuthServiceTest (6, in upstream/)portedupstream/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)portedregistry/ServerHealthTest (consecutive-failure threshold, immediate recovery) + POST /api/servers/health-check
RemoteEcosystemLoaderAuthStrippingTest (2)portedecosystem/RemoteEcosystemLoaderTest (federation merge, remote auth material stripped, fetch failure drops only the placeholder)

routing / catalog

Explorer test (methods)DispositionNotes
ResourceRoutingCatalogTest (25)ported (partial)routing/RoutingCatalogTest: explicit/wildcard precedence, duplicates, removal; count/enrichment fields not modeled
ResourceRoutingCatalogFindByCanonicalUrlTest (3)portedRoutingCatalogTest.FindByCanonicalUrl
ResourceRoutingCatalogActiveVersionSelectionTest (4)portedpublication precedence (active > retired, then highest version)
ResourceRoutingCatalogReverseVersionStripTest (5)portedid--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)portedHttpCatalogScanTest.retriesWithExponentialBackoffBeforeGivingUp
CanonicalResourceCatalogServiceUrlBuildTest (6)ported (core)HttpCatalogScanTest (_count paging url, next-link following incl. foreign-host guard)
CanonicalResourceCatalogServiceLenientParseTest (2)portedHttpCatalogScanTest.nonFhirBodyIsSkippedLeniently
AuthoritativeCatalogFiltersTest (4)portedHttpCatalogScanTest.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)DispositionNotes
ValueSetMultiSourceExpandFilterTest (19)ported (core)MultiSourceExpandFilterPagingTest
ValueSetMultiSourceExpandImplicitVsTest (8)portedMultiSourceExpandImplicitVsTest
ValueSetMultiSourceExpandImportIncludeTest (1)portedMultiSourceExpandImportIncludeTest (+ intersection semantics)
(merge core, no direct counterpart)portedMultiSourceExpandMergeTest
ValueSetMultiSourceExpandUnresolvedUrlTest (4)portedMultiSourceExpandDelegationGuardTest.UnresolvedUrl (tx-ecosystem 4xx/not-found shape; resolved-but-empty stays 200/0)
ValueSetMultiSourceExpandEmptyComposeDelegationTest (1)portedMultiSourceExpandDelegationGuardTest.EmptyComposeDelegation
ValueSetMultiSourceExpandExternalImplicitDelegationTest (3)portedMultiSourceExpandDelegationGuardTest.ImplicitFhirVs
ValueSetMultiSourceExpandImplicitImportDelegationTest (2)portedimplicit VS as import reference → MultiSourceExpandImportIncludeTest.remoteOwnedCanonicalDelegatesWholeExpandToOwner (the import path re-enters expand, which owns the delegation)
ValueSetMultiSourceExpandSearchMissDelegationTest (1) / ValueSetExpandExactRouteMissFallsBackToDiscoveryTest (1)portedcanonical-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)portedMultiSourceExpandInstanceRoutingTest (/ValueSet/{id}/$expand)
ValueSetMultiSourceExpandEditionPinTest (2)portedMultiSourceExpandVersionPinTest (catalog pin, authored wins)
ValueSetMultiSourceExpandStaleVersionPinRetryTest (1)portedMultiSourceExpandVersionPinTest (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)portedoperation/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)flintengine/ExpansionPropertyTest (property decoration + declared-uri hygiene); overlaps the tx-ecosystem parameters suite
ValueSetMultiSourceExpandSupplementDesignationTest (5) / UpstreamSupplementDesignationTest (8) / SupplementDesignationPackageExpandValidationTest (2)flintengine/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/alog-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)DispositionNotes
RouterOperationServiceCatalogRoutingTest (1)portedoperation/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-ecosystemGET parameter typing + validate-code parameter semantics — conformance parameters/validation suites
RouterOperationServiceLookupParameterTypeTest (3)tx-ecosystemas above
RouterOperationServiceUnresolvedCanonicalTest (6)tx-ecosystemfail-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)flintengine/SupplementDesignationTest (supplement-aware $lookup)
RouterOperationServiceStaleVersionPinRetryTest (1)portedMultiSourceExpandVersionPinTest.lookupPinsTheCatalogEditionAndSelfHealsAStalePin
BatchBundleOperationRoutingTest (5, in bundle/)portedfhir/TeagleBatchControllerTest (POST /fhir batch → batch-response, per-entry status, one failure never fails the batch)

search / info

Explorer test (methods)DispositionNotes
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)DispositionNotes
cache/* — FhirEtagFilterTest (22), TerminologyGenerationRegistryTest (11), TerminologyCacheInvalidatorTest (4), CacheInvalidationEventListenerTest (4), CacheManagementInvalidateEndpointTest (3), InMemorySharedCache (0)n/ashared 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/aNPM/IG package registry — replaced by the editable/local store model
admin/PackageReloadControllerTest (8)n/apackage admin endpoint (/api/ecosystem/reload is the surviving reload surface)
alias/AliasPreloadBlocksReadinessTest (3)n/afhirest alias preloading
enrichment/ResourceEnrichmentServiceTest (2), ValueSetBackgroundExpanderRequestContextTest (2)n/abackground enrichment of catalog entries
conformance/RouterConformanceLoaderMultiVersionTest (1)n/afhirest conformance bootstrap — Flint serves conformance
conformance/TerminologyCapabilitiesProviderTest (8)portedMetadataAggregationTest

New Teagle capabilities without an explorer counterpart

Teagle testCovers
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.scansStructureDefinitionAndStructureMapListingsSD/SM are catalogued like any other canonical, and an authoritative pattern claims an SD (they share authoritative with CodeSystem)
HttpCatalogScanTest.aServerRejectingStructureDefinitionSearchKeepsScanningTheOtherTypesthe live upstream answers an SD search with a not-supported OperationOutcome — one unsupported type must not cost the server’s other types
HttpCatalogScanTest.theScannedTypeListIsConfigurableteagle.catalog-scan.types trims the scan so unsupported types cost no retry budget
MultiSourceExpandMergeTest.fullyLocalComposeStaysOnTheKernelFastPathconformance-preserving local fast path
merge dedup nested-duplicate pruningMultiSourceExpandMergeTest.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 testCovers
SupplementDesignation cluster (5+8+2) + SupplementLookup (4+4)engine/SupplementDesignationTestuseSupplement 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/ExpansionPropertyTestrequested-property projection, declared CodeSystem property uris, missing values never evict members
AdministrativeGenderRepro (5)engine/DisplayLanguagePrecedenceTestdisplay 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.

The reference server’s separate ucum module (14 methods, outside the explorer’s 556) is migrated as modules/ucumber/backendUcumApiTest (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 pageRouteTeagle todayAction
Landing / Ecosystem dashboard/landingServers (plain table)ADD dashboard (richer); keep Servers
Global Search(search box)Catalog search onlyADD cross-resource search page
Code systems (list)/terminology/code-systemsCatalog (unified CS/VS/CM tabs)ADD per-type pages; keep Catalog
Value sets (list)/terminology/value-setsCatalogADD per-type
Concept maps (list)/terminology/concept-mapsCatalogADD 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 JSONADD 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 + OperationsREUSE 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: DONEGET /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 — Accept header → 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/health or /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 no baseDefinition/structure edges yet: deriving those means fetching profile bodies, which is only affordable once the fetch asks for _summary=true.
  • /api/topology graph 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/stats already exists (dashboard); /api/servers already 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.tsterminology/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-memory ComponentStateStore, and sessionStorage. 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 / .xml view (/fhir/{Type}/{id}.json|.xml, FhirFormattedViewComponent) — pretty-print via Accept negotiation, 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):

  1. _summary=true on all list/detail reads — list views never need concept[]/expansion; seconds → tens of ms on LOINC/SNOMED-sized resources. (have)
  2. Two-step resource load — summary shell first, full body streamed after (spec #5). (have)
  3. Ecosystem dashboard stat-cards + server-type breakdown + per-server counts from catalog/stats + servers (spec #1). (have)
  4. Versioned-id |-- request rewrite in the teagleApi interceptor (Tomcat rejects %7C; java.net.URI rejects raw |) — Teagle already parses -- server-side. (have)
  5. Structured FHIR-error toasts — parse OperationOutcome in 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).