Skip to content

fix(#1931): show user-friendly warning when answerOption terminology lookup fails - #2000

Open
MaryamMehd wants to merge 16 commits into
mainfrom
issue/1931-answer-option-lookup-failure
Open

fix(#1931): show user-friendly warning when answerOption terminology lookup fails#2000
MaryamMehd wants to merge 16 commits into
mainfrom
issue/1931-answer-option-lookup-failure

Conversation

@MaryamMehd

@MaryamMehd MaryamMehd commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1931

When a questionnaire has answerOption codings without display values, the renderer calls CodeSystem/$lookup to fetch human-readable labels. If the terminology server is unavailable, the lookups silently fail and the UI displays raw SNOMED codes — confusing for end users.

Changes:

  • addDisplayToAnswerOptions now returns a lookupFailedCodingKeys: Set<string> (keyed by system|code) alongside the resolved options, tracking failed lookups at the individual coding level.
  • answerOptionsLookupFailures is threaded through QuestionnaireModel, QuestionnaireStoreType, and useQuestionnaireStore so field components can detect which specific options could not be resolved.
  • ChoiceSelectAnswerOptionFields and OpenChoiceSelectAnswerOptionField now filter out unresolvable options from the dropdown (so no raw codes are shown) and display an amber helper text: "Some items in this list were not able to be displayed".
  • Unit tests in addDisplayToCodings.test.ts updated to assert lookupFailedCodingKeys is correctly populated.

Before / After

Scenario Dropdown behaviour
Working terminology server Human-readable labels as normal ✅
Server unavailable — before fix Raw SNOMED codes (133932002, 394738000) shown in dropdown ❌
Server unavailable — after fix Unresolvable options hidden; amber warning: "Some items in this list were not able to be displayed" ✅

Test plan

  • Run unit tests: cd packages/smart-forms-renderer && npx jest --testPathPattern="addDisplayToCodings" — all tests pass.
  • In the Playground, load a questionnaire with answerOption codings that have no display, set the terminology server to a URL that validates but lacks SNOMED (e.g. https://hapi.fhir.org/baseR4), build the form — affected options are hidden and the amber warning appears.
  • With a working terminology server, the dropdown shows proper labels with no warning.

MARYAMMEHDIZ and others added 5 commits July 10, 2026 14:29
… codes (issue #1931)

Co-authored-by: Cursor <cursoragent@cursor.com>
… error

Co-authored-by: Cursor <cursoragent@cursor.com>
)

Co-authored-by: Cursor <cursoragent@cursor.com>
…sue #1931)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@MaryamMehd
MaryamMehd requested a review from leoniedickson July 10, 2026 07:58
…linkId

Switch answerOptionsLookupFailures from tracking linkIds to individual
coding keys (system|code). The dropdown now stays accessible and shows
a bracketed code fallback (e.g. [133932002]) for each option that could
not be resolved, with a single amber warning below the field for partial
failures — keeping successfully-resolved options fully labelled.

Co-authored-by: Cursor <cursoragent@cursor.com>
@MaryamMehd MaryamMehd changed the title fix: show error alert when answerOption $lookup fails instead of displaying raw codes (issue #1931) fix(#1931): show user-friendly warning when answerOption terminology lookup fails Jul 20, 2026
@MaryamMehd
MaryamMehd marked this pull request as ready for review July 20, 2026 04:06
leoniedickson and others added 10 commits August 25, 2026 15:41
…ion-lookup-failure

# Conflicts:
#	packages/smart-forms-renderer/src/components/FormComponents/ChoiceItems/ChoiceSelectAnswerOptionFields.tsx
#	packages/smart-forms-renderer/src/components/FormComponents/OpenChoiceItems/OpenChoiceSelectAnswerOptionField.tsx
Population-workflow-for-Allergies-1.png is a visual regression baseline for
aboriginalFormPopulation.test.tsx, unrelated to the answerOption lookup
failure fix in this branch — appears to have been committed accidentally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the answerOptionsLookupFailures Set threaded through QuestionnaireModel,
QuestionnaireStoreType and the store. It was redundant: addDisplayToAnswerOptions
attempts a $lookup for every valueCoding missing a display and only writes
display back on success, so "lookup failed" and "valueCoding present but
display still missing" are exactly the same condition already visible on the
option itself. Components now check that directly via a single-argument
isLookupFailedOption(option) in utils/openChoice.ts, with no separate global
state, interface changes, or store plumbing required.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The old name implied a specific, detectable cause ("$lookup failed"), but a
rejected lookup, a not-found response, and a code system the terminology
server simply doesn't recognize are all indistinguishable at this point -
they all just leave valueCoding.display unset. Renaming to reflect what's
actually being checked, and clarifying in the comment that filtering these
out is an intentional clinical-safety choice (raw codes are not acceptable
in this UI), not merely an outage workaround.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…se i18n for warning

- ChoiceCheckboxAnswerOptionFields, ChoiceRadioAnswerOptionFields, and their
  OpenChoice equivalents now filter out answerOption codings whose display
  is unavailable and show the same warning as the Select/Autocomplete
  variants, instead of falling back to the raw code (CheckboxOptionList/
  RadioOptionList's existing `display ?? code` fallback). AnswerValueSet
  variants are untouched since they're a separate mechanism, out of scope
  for this issue.
- Added StyledWarningTypography (Item.styles.ts) alongside the existing
  StyledFeedbackTypography, matching the visual style already used for
  checkbox/radio feedback text.
- The warning copy now goes through rendererStrings.answerOptionDisplayUnavailable
  instead of being hardcoded, consistent with how other terminology-related
  messages (optionsUnavailable, terminologyServerFetchError) are localized.
- Added component tests for the checkbox and radio variants.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…esync, simplify value derivation

- CheckboxOptionList: compare answers by system+code instead of full deepEqual
  on the Coding, matching Select/Radio. Fixes checkboxes silently showing as
  unchecked when a code's display text changes (terminology rename) or is
  temporarily unresolvable, even though the underlying answer hasn't changed.
- Add withFallbackDisplay/includeAnsweredOptions (utils/openChoice.ts) so a
  currently-answered option is never dropped from Select/Radio/Checkbox lists
  just because its display couldn't be freshly resolved this session - it
  falls back to the display captured when it was originally answered, or a
  raw code as a last resort, instead of desyncing (raw code in Select, a
  vanished option in Radio/Checkbox) from what's actually stored.
- hasUnavailableDisplayOptions now checks the raw options list directly
  instead of comparing list lengths post-merge, so the warning still fires
  when the only unresolvable option is the currently-selected one.
- Thread qrAnswer (the actual QuestionnaireResponseItemAnswer) through the
  Select/Radio Item -> View -> Fields chain instead of a lossy, separately-
  computed match-string, removing the redundant valueChoice/valueRadio props
  once qrAnswer covers the same data. Added getAnswerValueString (utils/
  choice.ts) as the shared single-answer-to-match-string helper.
- Tests: answeredOptionDesync.test.tsx covers all three controls surviving a
  failed re-lookup, and the checkbox display-drift fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- OpenChoiceCheckboxAnswerOptionFields: swap plain filter for
  includeAnsweredOptions(options, answers) - answers was already flowing
  through correctly from the Item, so this was a targeted one-line fix.
- OpenChoiceSelectAnswerOptionField: merge valueSelect (when it's a coded
  answer, not free text) back into the visible options list. This variant
  never had the "shows raw code" bug that plain Choice Select had, since
  valueSelect here is already the raw QR answer rather than reconstructed
  from the live options list - the only gap was the option being excluded
  from the pickable dropdown.
- OpenChoiceRadioAnswerOptionFields/Item: thread the already-computed
  `answers` array down (used for includeAnsweredOptions), without touching
  the existing valueRadio/open-label derivation, since that logic has its
  own delicate branching (code vs "Other" free-text vs cleared-but-selected)
  that didn't need to change.
- Tests: extend answeredOptionDesync.test.tsx with the same failed-reload
  coverage for OpenChoice Select/Radio/Checkbox.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sistent with Choice Select

- getAnswerValueString, compareAnswerOptionValue, and findInAnswerOptions now
  check `!== undefined` instead of truthiness, so valueInteger: 0 and
  valueString: '' are treated as real answers instead of "no answer". These
  are the same answer-matching helpers used throughout the answered-option
  desync fix, so a 0/''-valued answer could otherwise silently fail to match
  its option.
- OpenChoiceSelectAnswerOptionItem now prefers a freshly-resolved live option
  (via compareAnswerOptionValue + withFallbackDisplay) over the raw stored
  answer for coded answers, matching Choice Select's behaviour - previously
  it always showed the answer's originally-recorded display verbatim, so a
  terminology rename would never be reflected. Free-text (valueString)
  answers are passed through unchanged, since they have no terminology
  concept to resolve.
- Tests: unit tests for the 0/'' cases in choice.test.ts, and an end-to-end
  test proving OpenChoice Select now shows a renamed display instead of the
  stale recorded one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- withFallbackDisplay now returns null instead of a still-raw option when
  neither the live definition nor the answer's own recorded history has a
  display. includeAnsweredOptions skips the option entirely in that case,
  rather than pushing it into the visible list unconditionally - a
  previously-answered option with no display anywhere now behaves the same
  as a never-answered one (absent from the list) instead of leaking a raw
  code through as a last resort.
- generateOptionKey/generateCodingKey no longer include display as part of
  a coding's identity key when a code is present - display is a label, not
  identity, and can legitimately change (fresh $lookup resolution, a
  terminology rename) without desyncing answerOptionsToggleExpressions'
  enable/disable lookup. The no-code fallback path is now tagged
  ("code:"/"display:") so a coding's code can never collide with a
  different coding's display string.
- Tests: unit tests for the key-collision fix, and end-to-end tests proving
  an answer with no display anywhere is dropped rather than shown as a raw
  code, across Select/Radio/Checkbox.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ning component

Removes duplication across the 6 Select/Radio/Checkbox x Choice/OpenChoice
variants by consolidating the unavailable-display filtering/warning logic
into useAnswerOptionVisibility and AnswerOptionUnavailableWarning, and
unifies coding equality checks on generateCodingKey.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

answerOption list being displayed is a list of codes when $lookup fails

3 participants