fix(#1931): show user-friendly warning when answerOption terminology lookup fails - #2000
Open
MaryamMehd wants to merge 16 commits into
Open
fix(#1931): show user-friendly warning when answerOption terminology lookup fails#2000MaryamMehd wants to merge 16 commits into
MaryamMehd wants to merge 16 commits into
Conversation
…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
marked this pull request as ready for review
July 20, 2026 04:06
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1931
When a questionnaire has
answerOptioncodings withoutdisplayvalues, the renderer callsCodeSystem/$lookupto 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:
addDisplayToAnswerOptionsnow returns alookupFailedCodingKeys: Set<string>(keyed bysystem|code) alongside the resolved options, tracking failed lookups at the individual coding level.answerOptionsLookupFailuresis threaded throughQuestionnaireModel,QuestionnaireStoreType, anduseQuestionnaireStoreso field components can detect which specific options could not be resolved.ChoiceSelectAnswerOptionFieldsandOpenChoiceSelectAnswerOptionFieldnow 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".addDisplayToCodings.test.tsupdated to assertlookupFailedCodingKeysis correctly populated.Before / After
133932002,394738000) shown in dropdown ❌Test plan
cd packages/smart-forms-renderer && npx jest --testPathPattern="addDisplayToCodings"— all tests pass.answerOptioncodings that have nodisplay, 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.