Skip to content

Release 2026.2.4.1 - #714

Open
bencap wants to merge 25 commits into
mainfrom
release-2026.2.4.1
Open

Release 2026.2.4.1#714
bencap wants to merge 25 commits into
mainfrom
release-2026.2.4.1

Conversation

davereinhart and others added 22 commits August 5, 2026 15:00
Add gnomAD IDs (e.g. 17-7676154-G-C) as a search type on the MaveMD
variant search screen and in the homepage hero search.

A gnomAD ID does not name the reference genome its coordinates belong
to, so getAlleleByGnomad translates it into genomic HGVS and resolves
it against GRCh38 first, falling back to GRCh37 when the registry
rejects the position with IncorrectReferenceAllele. Resolving HGVS is
used in preference to the registry's gnomAD cross-reference index,
which only covers variants ClinGen has ingested a gnomAD record for and
so misses registered alleles such as X-41334274-A-C.

- Add lib/gnomad with a GRCh38/GRCh37 RefSeq chromosome accession table
  and VCF-style to HGVS conversion covering substitutions, insertions,
  deletions and delins
- Add getAlleleByGnomad to the ClinGen API module
- Add gnomadIdRegex alongside the other search identifier patterns
- Add the gnomAD search type to SEARCH_TYPE_OPTIONS, and its type,
  colors and placeholder to the homepage hero search
- Add the gnomAD color tokens to the theme
- Correct getAlleleByDbSnp and getAlleleByClinVar return types to
  ClinGenAllele[], which the /alleles endpoint has always returned

Every accession in the table was verified against NCBI (accession to
chromosome and assembly) and against the ClinGen registry (that each
GRCh38/GRCh37 pair describes the same locus).
A gnomAD ID does not name the reference genome its coordinates belong to,
and the same coordinates can be valid under both assemblies while naming a
different variant in each: 1-1000001-G-T resolves under GRCh38 and GRCh37
to loci 64,620 bp apart. Searching one silently took the first assembly
that resolved, so an ID taken from gnomAD v2 could return a confidently
wrong answer with nothing to indicate it.

Results for a gnomAD ID search now report the assembly the coordinates
were read under, alongside the HGVS that was resolved, with a button to
re-read the same ID under the other assembly.

- Return the resolving assembly from getAlleleByGnomad, and accept one to
  read under, which skips the fallback and lets failure propagate
- Thread a forced assembly through defaultSearch so switching reuses the
  existing search and MaveDB lookup path
- Report coordinates that do not match an assembly's reference sequence as
  a warning naming the base actually found there, rather than surfacing
  the registry's IncorrectReferenceAllele as an error
- Show alleles the registry resolved but has not registered, which it
  answers with a blank node id rather than a CA id, instead of discarding
  them as no result; this also affects HGVS searches for unregistered
  variants
- Render one card header for registered and unregistered alleles, so both
  show their GRCh38 and GRCh37 coordinates
Make "Any" the default search type on the MaveMD variant search screen and
the homepage hero. It matches the search string against the identifier
patterns already defined for the specific types, then takes that type's
usual path, so nothing about the individual searches changes.

Detection order matters where those patterns overlap. A VRS digest also
satisfies the deliberately loose HGVS pattern, which asks only for an
identifier, a colon and a description, so it is recognized first. A bare
number is a ClinVar Variation ID only once the more specific forms have
been ruled out, so a dbSNP rsID written without its rs prefix reads as
ClinVar and still needs the dbSNP type chosen explicitly.

Because the selected type stays "Any", the results report which type the
string was taken to be, as a chip in that type's colour, so a reading like
that one is visible rather than silent.

- Add detectSearchType alongside the identifier patterns it tries
- Resolve "Any" to a concrete type at the top of fetchDefaultSearchResults,
  leaving every existing branch untouched
- Add the "Any" type, colours and placeholder to both search screens, and
  point the search type fallbacks at it
- Show the detected type in the results header, inside the existing live
  region so it is announced with the result count
- Size the search type tabs to match the example chips below the search bar
Add "Gene symbol" as a search type on the MaveMD variant search screen and
the homepage hero. Such a search trims and uppercases the string and
resolves to the existing gene page at /genes/:symbol rather than looking up
an allele, leaving GeneView to report a symbol it does not recognize.

The route is replaced rather than pushed. The search screen re-runs its
search from the query params on mount, so a pushed entry would send the
back button here and immediately forward again to the gene page.

Gene symbols are also detected by the "Any" search type. Any bare word
resembles a symbol, so the pattern is tried only once every more specific
identifier has been ruled out. Two consequences follow: a symbol that
collides with a variant identifier loses to it, so CA1-CA14 and RS1 need
the gene symbol type chosen explicitly; and "Any" now rarely reports an
unrecognized identifier, since a string it cannot otherwise place resolves
to a gene page that reports the symbol as not found.

- Add geneSymbolRegex and try it last when detecting a search string
- Navigate to the gene page from the search screen, with the symbol
  standardized to uppercase
- Add the gene symbol type, colours, examples and placeholder to both
  search screens
- Say that "Any" accepts gene symbols in its placeholder, which the search
  screen now takes from the shared placeholders rather than from an example
A gene symbol search replaced the variant search screen's history entry
with the gene page, so the back button skipped past it. Pushing instead is
no better on its own: that screen re-runs its search from the query params
on mount, so returning to a recorded gene search bounced the user straight
forward to the gene page again. Searches started from the homepage had a
further problem, routing through the variant search screen and leaving a
page the user never asked for between the two.

Handle a gene symbol before the search is recorded in the query params,
strip it from the entry being left behind, and push the gene route; and
send the homepage straight to the gene page. The back button now returns
where the search was started from, with nothing left to re-trigger:

  homepage         Home -> gene page,       back -> Home
  search screen    /mavemd -> gene page,    back -> /mavemd, search cleared
  link or bookmark /mavemd?search=BRCA1     back -> /mavemd, no re-run

- Add geneSymbolSearchTarget, which resolves the search type and
  standardizes the symbol, so both screens decide this the same way
- Move gene symbol handling out of fetchDefaultSearchResults, which gene
  searches no longer reach, and into defaultSearch ahead of the query sync
- Await defaultSearch in searchForText and only record the search while
  still on the search screen, so a gene symbol example no longer races the
  navigation and rewrites the gene page's query params
Clicking a gene symbol example did nothing. searchForText selected the
search type, reset the form and set the search text before searching, and
each of those steps queued a query param rewrite of its own — four in all,
plus a watcher clearing the search text mid-flight. Vue Router aborts an
in-flight navigation when a new one starts, so the push to the gene page
lost the race.

Resolve the example up front and navigate before touching any search
state. This covers gene symbol examples and any "Any" example that detects
as one, and leaves every other example on the existing path.

It also makes the previous commit's guard against recording a search after
navigating away unreachable, since defaultSearch can no longer navigate
from here, so that guard is removed again.
Add unit tests for the pure functions behind the "Any", gnomAD ID and gene
symbol search types.

The ambiguities these encode are much of the point: a bare number resolves
to a ClinVar Variation ID rather than an unprefixed dbSNP rsID, CA1-CA14 and
RS1 lose to the ClinGen and dbSNP patterns despite being real gene symbols,
and a hyphenated string cannot be told apart from a symbol like HLA-A. Each
is named in a test so it cannot drift unnoticed.

- detectSearchType: the order it tries the patterns in, and what it returns
  for a string resembling nothing supported
- geneSymbolSearchTarget: standardization, and that it hands a malformed
  symbol back for its caller to reject
- getAlleleByGnomad: the GRCh38 to GRCh37 fallback, reads under a forced
  assembly, and which failures propagate rather than trigger a retry
- Search configuration: that every hero search type has a colour, which the
  homepage dropdown indexes without a fallback, and that each documented
  example resolves to its own type

gnomadIdRegex is already exercised through parseGnomadId in gnomad.test.ts,
so it is tested directly only where that cannot reach: parseGnomadId also
gates on the chromosome table, whereas detectSearchType applies the pattern
on its own.
Give the "Any" search type sage and hand its slate to HGVS, so the search
bar reads as brand-coloured on first load rather than neutral grey. Nothing
tied HGVS to sage.

Rename the token to --color-hgvs, following the name-by-search-type
convention in that block. "Any" now points straight at --color-sage, and
because SEARCH_COLORS.any is also the fallback for an unrecognized search
type, that fallback returns to sage as well.
Address review feedback on #707.

The results described the current form rather than the last search. Typing a
new identifier rewrote the banner beneath the previous results, and a
reading that failed because the position lies past the end of the chromosome
still claimed the reference sequence did not match, because the template had
no way to tell the two apart.

Record the reading rather than the assembly alone: the ID as searched, the
assembly it was read under, and how the registry answered. The error handler
classifies IncorrectReferenceAllele and IncorrectHgvsPosition and threads
that verdict through, so the template can name the real reason instead of
reaching into the search box.

- Hold {id, assembly, mismatch} rather than a bare assembly, and derive the
  banner from the recorded ID
- Tailor the empty state to the kind of mismatch, and show the banner only
  for a reading that resolved, so it no longer contradicts the message below
  it or duplicates its button
- Let the search type watcher run before an example fills the box, so
  choosing an example from another type leaves the text visible
The history API does not scroll and no scrollBehavior was configured, so
every route change left the window where the previous page had it. Arriving
at a gene page from a search made it obvious: the search screen scrolls its
results into view, and the gene page then opened part way down.

Scroll to the top for a new page, restore the saved position on back and
forward, honour hash targets, and leave same-path navigation alone — the
search screens mirror their state into the query string, and those
navigations are not new pages.
…deScoreSetDropDownMenu

Debug the dropdown menu of superseding score set shows private score sets problem.
- Extract `getErrorResponse` out of `api/mavedb/index.ts` and add
  `describeRequestError` alongside it in the new `src/lib/errors.ts`,
  replacing the ad hoc `extractErrorDetail` in ScoreSetCalibrationsView
- Update all call sites to import from `@/lib/errors` instead of
  `@/api/mavedb`
- Add unit tests covering both exported functions
Add use-csv-namespaces.ts and MvCsvColumnDialog.vue: a composable and
dialog that fetch a score set's or variant's available CSV column
namespaces from the new discovery endpoints and let a user pick which
to include, replacing the score-set custom-download dialog's
hand-picked checkbox list (scores/counts/mappedHgvs/etc.) that mapped
to query params the API never actually read.

Sections are grouped and labeled by the server (Measurements,
Annotations, Clinical interpretation, Provenance) rather than
hand-maintained client-side, and split by owning score set when a
variant's calibrations span more than one. Defaults come from the
API's selectedByDefault flag, so a research-use-only or rangeless
calibration is offered but opts in explicitly.

Wire it up in two places:
- MvVariantPreview.vue's "Custom Data" dialog now uses it for score-set
  downloads instead of the old checkbox dialog
- VariantScreen.vue gets a new "Download variant CSV" control backed by
  the variant-level CSV endpoint, split from the existing VA-Spec
  annotation downloads since they're a different kind of artifact (flat
  table vs. nested standard objects); use-variant-lookup.ts gains
  downloadVariantCsvFile and a shared downloadInProgressLabel

Update openapi.d.ts for the new AvailableCsvNamespace /
CsvNamespaceGroup schemas and endpoints, and switch score-sets.ts /
variants.ts's CSV requests from drop_na_columns and
include_post_mapped_hgvs to drop_unused_hgvs_columns and the namespace
list, matching the API's new parameter names.
Fix streamAnnotationsInto (use-score-set-downloads.ts) accumulating
NDJSON chunks as decoded strings: decoding to UTF-16, then joining, then
building a Blob from the result meant a large pathogenicity-statement
download held roughly five copies of the payload in memory at once and
could run the tab out of it. Accumulate raw Uint8Array chunks instead
and build the Blob directly from them, and count newline bytes rather
than decoding to track progress. Also make a truncated stream (fewer
lines than X-Total-Count) throw instead of silently saving a partial
file, since the response has already started by the time that could be
detected.

Replace the per-feature annotatedDownloadInProgress/Progress state with
a single fileDownloadLabel/fileDownloadProgress pair shared by every
download this composable offers (scores, counts, mapped variants,
custom data, and the VA-Spec streams), via a withIndicator() wrapper
that also prevents two downloads running concurrently. Progress is
indeterminate except for the VA-Spec streams, which can count records
against X-Total-Count.

Wire the shared indicator into ScoreSetDownloads.vue as one progress
bar under the button row instead of one hung off the annotated-variants
split button, disable every download button while any download is in
flight, and add reportingFailure() so a rejected download surfaces a
toast instead of an unhandled promise rejection in the console. Switch
the custom-data dialog to MvCsvColumnDialog, matching MvVariantPreview
and VariantScreen.
Fills the annotations card's "Population Frequency" column, until now a
"Data coming soon" placeholder. gnomAD is linked to a single mapped variant
by ClinGen allele ID, so the frequency is a direct assertion about the
measured allele rather than an aggregate over related variants.

- Add MvGnomadSummary: AF with the AC/AN behind it, FAF95 with the genetic
  ancestry group attaining it, and a deep link to the gnomAD variant page,
  bottom-aligned so provenance lines up across the card's columns.
- Read the record from the score set's variant data CSV via
  gnomadFromVariantRow, which normalises the namespace's NA sentinels. A
  variant absent from gnomAD reports as having no record, not zero frequency.
- Derive GnomadFrequency from the generated OpenAPI schema, so renaming or
  retyping a field on the API model breaks compilation here.
- Split HISTOGRAM_VARIANT_DATA_NAMESPACES into SCORE_SET_CHART_NAMESPACES and
  VARIANT_PAGE_NAMESPACES. One list fed two screens under a name describing
  neither, and only the variant page reads gnomAD, so the score set charts no
  longer fetch seven unused columns on every row.

Requires the widened gnomad CSV namespace in the API.
…tate

The API now reports a variant it cannot annotate as a record carrying an error object instead of
letting the exception truncate the body. A download containing those records is complete and worth
saving, but treating it as a clean export would misrepresent it.

Tally error records as the stream arrives and raise a warning toast naming the count. Each chunk is
decoded to scan its lines and then dropped, so only one chunk plus a partial line is ever live and the
body is still retained as bytes -- decoding the whole payload is what previously ran the tab out of
memory. Detection prefilters on a substring and parses only candidates, since these records nest
deeply and parsing every one of a large stream is slow.
feat(ui): report annotation-stream variants the server could not annotate
@coveralls

coveralls commented Aug 12, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 10.274% (+7.7%) from 2.596% — release-2026.2.4.1 into main

@bencap bencap added the core: release A release PR label Aug 12, 2026
@bencap bencap added this to the R1 - Peer Review Revisions milestone Aug 12, 2026
@bencap bencap self-assigned this Aug 12, 2026
@bencap
bencap marked this pull request as ready for review August 12, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment