Skip to content

Rebuild variant detail, allele grouping, and clinical controls on the allele-centric API - #723

Open
bencap wants to merge 82 commits into
release-2026.3.0from
feature/bencap/api-redesign-for-allele-data-model
Open

bencap wants to merge 82 commits into
release-2026.3.0from
feature/bencap/api-redesign-for-allele-data-model

Conversation

@bencap

@bencap bencap commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

Ports the UI onto the redesigned variant/allele API (the allele-centric data model workstream): a rebuilt variant detail page, cross-level allele grouping, a reworked ClinVar clinical-controls pipeline, a new inline glossary system, and gnomAD population-frequency integration. Protein-viewer download options rode along on this branch as well.

See VariantEffect/mavedb-api#791,

Variant detail page

  • Restructured around related alleles and evidence stats, replacing the old per-variant layout (95af0090d)
  • Promoted the redesigned page to production; renamed "Your variant" → "This variant" and reworked the confidence-badge palette (f32ef0270, fc493bd89)
  • Level-gates ClinVar/gnomAD placement by assay level; principled empty state for variants with no annotations at all (16c1d92c0, 66355474d)
  • Distinguishes a reverse-translation fan-out from a genuine empty state: a protein change with no direct evidence but candidate nucleotide alleles now says so, instead of asserting nothing was found (b1e5c3037)
  • VEP version only shown when a consequence exists; each allele shows its own gnomAD frequency rather than a pooled one (00f222840, 490d9a174)

Allele grouping

  • New projection-pair grouping and a "convergent allele group" case for how related c/g/p alleles display together (969dacab7, a66e07ed1)
  • Confidence-badge axis added to grouping output; projection/relation terminology renamed for clarity (5ec170c6f, b6a1c4f09)

ClinVar clinical controls & calibration

  • New discordance grading, placement reducer, notables, and store for clinical controls; histogram bins controls via resolveControlSeries (c3c4b0c0c, 1c38f90f9, 1b5676e32)
  • Standardized vocabulary on "inferred" for sibling-derived calls; ACMG evidence vocabulary and functional-range colors extracted into a shared source (1bc6f8741, 314809a39, 95dc00da6)
  • Calibration variant requests deduplicated to cut redundant fetches (8bfdb2125)

Glossary / Key drawer

  • New vocabulary "Key drawer" and v-key-term directive for inline term definitions, wired across surfaces and co-located with the modules that own each term (1931d70b1, 3852556ae, 42d7a4f18)
  • Superseded badge and ACMG key sections consolidated into the drawer; duplicate "this variant" glossary entries merged (7f9d7dc7a, f69be88d9, 2c2c8b52f)

gnomAD

  • New frequency-collection library and variant-level surfaces for gnomAD population data (c131d2299)

Score-set data & downloads

  • Variant lookup rebuilt on the new measurements endpoint; lean whole-set variant view channel added (94ea6b0f8, 13d82a88f)
  • Search-and-jump variant picker added to score sets; streaming variant-details export with annotation columns (7a8b4be3d, 1a2c7095a)
  • Per-study aggregation added for RT evidence; assay-level bucketing and RT labels added to measurement types (8906f9aca, 64b4c81fc)

Protein structure viewer

  • Download menu grouped with headers/icons; PDB, PML (ChimeraX), and Mol* MolViewSpec coloring-script downloads added; large-protein support (de4a30b75, d7f56f6d9, b849101c9, cd8d8cf12, 49a42cd64)

Search & API surface

  • Dropped the retired include_nucleotide_siblings flag; retired the ClinGen lookup endpoint in favor of a TTL-cached read wrapper; empty VRS-digest lookups warn instead of 404ing (f3fa94f9e, 6baf82935, c92eeec11)

bencap added 30 commits July 2, 2026 13:01
  Wire the UI up to the new GET /score-sets/{urn}/variants endpoint that
  serves one pre-chewed record per variant (selection key, score,
  consequence, annotation bridge ids, and parsed DNA/protein HGVS blocks).

  - add LeanVariant/HgvsField types mirrored from the OpenAPI schema
  - add getLeanScoreSetVariants and leanScoreSetVariantsUrl to the API
    client
  - fetch the lean view alongside the legacy CSV scoresData channel in
    ScoreSetView, exposing a typed leanVariantRecords handle so consumers
    can migrate off the CSV channel slice by slice
  - regenerate openapi.d.ts for the new path and schemas
Migrate the heatmap, histogram, score-set view, visualizer, and variant
lookup off the legacy CSV-parsed `Variant` model onto the lean
`DisplayVariant`/`LeanVariant` API record. The score-set view now fetches
a single lean whole-set channel instead of the dual histogram-CSV + lean
channels, and variant identity keys move from `accession` to `variantUrn`
throughout.

- Rework use-variant-coordinates around `coordinateFor(variant, level,
  frame)` as the single source of truth for the (level × frame)
  coordinate grid; add `resolveLevel` and `isUnmapped`, and make the
  (mapped, dna) cell null for protein assays (mavedb-api#784)
- Handle unmapped variants: labels fall back to submitted HGVS rather
  than the URN, mapped/clinical mode is disabled and annotated when a set
  has no mapped data, and the search matches both frames
- Extract VEP consequence bucketing into lib/consequences.ts, replacing
  the parsed-HGVS effect classifiers (`variantIsMissense`, etc.) in
  lib/variants.ts; `isStartOrStopLoss` stays as the block-aware heatmap
  filter
- Extract shared d3 chart tooltip builders into lib/tooltips.ts and
  rebuild the heatmap/histogram tooltips on top of them
- Add `isNucleotideHgvs` to lib/mave-hgvs.ts and
  `inferReferenceSequenceFromBlocks` to lib/variants.ts
- Heatmap UX: deferred redraw with a loading indicator, a not-shown
  breakdown (no-coordinate vs complex), a note when the selection is off
  the chart, and viewport-aware hover-tooltip positioning
- Add tests for consequences, mave-hgvs, and use-variant-coordinates
Consume the reworked GET /variants/{urn} envelope: a self-contained
VariantDetailPanel (assay-level facts, primary classification, gnomAD/ClinVar,
supersession badge, link to the variant page) wired into ScoreSetView on the
?variant= selection, plus use-variant-lookup / VariantScreen / the legacy
measurement-URN redirect repointed off the deleted VariantEffectMeasurementWithScoreSet
onto the flat envelope fields (clingenAlleleId bridge, separately fetched score set).

Clinical controls: drop the hard-coded DEFAULT_CLINICAL_CONTROL_VERSION, accept a
nullable version end to end, and render human-readable ClinVar versions
(formatClinvarVersion) in the heatmap/histogram clinical mode. openapi.d.ts
regenerated for the new API shapes.
Match the API field rename: the detail panel reads the superseding *score set*'s URN
(supersession is score-set-versioned), not a variant URN, and the tooltip now reads
"Superseded by score set X". Update the generated VariantDetail type to match. The /alleles/*
schema regen in openapi.d.ts is deliberately left for the alleles-slice work.
…ram url helper

- Support an optional `limit` on getScoreSetScoresPreview and
  getScoreSetCountsPreview so the preview table fetches only the rows it
  renders instead of the full dataset; document that `drop_na_columns`
  is evaluated over the sampled rows
- Remove the now-unused histogramScoreSetVariantDataUrl helper and its
  scoreSetVariantDataParams/namespace constant
Pass the variant's URN as a query param on the ClinGen variant-details
link so the destination page can disambiguate which variant to show
when a single ClinGen allele maps to multiple variants.
- Show the score set URN under its title when showUrn is set, so
  correlated score sets with identical facts can be told apart
- Add an "Assay level" detail row driven by a new assayLevel prop,
  surfacing the score-set-wide protein/cdna/genomic measurement level
- Scope the 2-col top-border reset to the new assay-facts-grid--2col
  class so the 1-col layout keeps a divider between every stacked row
- Pass MAX_ROWS as a limit to the scores/counts preview requests
  instead of fetching the full dataset and slicing client-side
- Derive the "Showing X of Y" total from the score set's numVariants
  rather than the now-truncated fetched rows, since the fetch no
  longer returns the true total
proteinConsequenceBlock referenced variant.proteinLevelHgvs, a field
that no longer exists on DisplayVariant; fall back through
variant.mapped.protein (the mapped protein representation) before
the submitted protein HGVS, matching the documented fallback order.
- Replace MeasurementType (nucleotide/protein/associatedNucleotide)
  with a two-way LevelBucket (nucleotide/protein) derived from the
  mapping record's AssayLevel, since cdna and genomic share one
  visual bucket
- Add assayLevelBucket() to bucket a raw assay level, and
  dominantAssayLevel() to pick the most common non-null level across
  a score set's variants
- Add ASSAY_LEVEL_LABELS for the three per-level labels and
  RELATIONSHIP_LABELS for the RT direct/protein_consequence/
  nucleotide_encoding relationship display strings
- Add unit tests covering bucketing and dominant-level selection,
  including ties and all-null/undefined inputs
Add memoizeRead, a p-memoize/expiry-map wrapper that dedupes
concurrent calls to the same idempotent GET and caches the result
for a short TTL. Read data can be rewritten underneath us at any
time by the mapping/annotation worker, so the cache is tuned to
collapse page-load request bursts rather than to reuse results
long-term; a 30s default TTL bounds staleness while composables
still handle same-page reuse via their own reactive caches.
- Replace lookupVariantsByClingenId (POST clingen-allele-id-lookups)
  with getAlleleMeasurements (GET clingen-alleles/{id}/measurements),
  the new entrypoint for the ClinGen-allele-centric variant page;
  supports includeSuperseded, includeNucleotideSiblings, and asOf
- Wrap getAlleleByCaId, getScoreSet, getLeanScoreSetVariants, and
  getVariantDetail in memoizeRead so repeated reads for the same key
  dedupe and share a short-TTL cache instead of refetching
- Consolidate the duplicated getScoreSet into score-sets.ts and drop
  the copy from calibrations.ts
- Drop getHistogramVariantData now that its only caller is gone
Add groupAlleles, which collapses a variant detail's allele sidecar
into rendered groups: each c<->g projection pair (linked by
projectionOf) folds into one entry with deduplicated annotations,
while the protein apex and projection-failed candidates stay as
one-member groups. Groups carry a measured/pageRoot/derivation
summary and sort measured-and-page-root entries first, then
bottom-up by level (genomic -> cdna -> protein), so the UI can
render one block per real allele instead of one per c/g/p record.

Add unit tests covering the nucleotide-assay and protein-assay
pairing shapes, divergent-annotation flagging, dangling projectionOf,
the measured-vs-null-relation distinction, and page-anchor CAID
exclusion from surfaced links.
Drop a leftover console.log(endpoint) call in useEntityCache's fetch
path.
- Replace the retired lookupVariantsByClingenId flow with
  getAlleleMeasurements, consuming the API's own direct-first order
  instead of re-deriving nucleotide/protein/associatedNucleotide
  buckets from a nested exactMatch/equivalentAa/equivalentNt shape
- Extract per-URN detail/score-set/scores caching into
  useMeasurementCache, and selection + its derived state (score,
  calibration resolution, clingen id) into useMeasurementSelection,
  so useVariantLookup becomes an orchestrating facade over both plus
  useClingenAllele
- Add includeSuperseded and asOf query axes; any change to anchor,
  superseded scope, or as_of bumps a query epoch, clears the caches,
  and refetches, so a slower stale response can't clobber a newer one
- Add a one-shot citation fallback: an initial `?variant=` deep link
  to a superseded measurement enables includeSuperseded once so it
  resolves, without fighting a later manual toggle-off
- Cap background prefetch of non-selected measurement details at 4
  concurrent requests via p-limit, so a large equivalence class
  doesn't fire a request storm on load
- Widen SequenceLevel from 'dna'|'protein' to 'cdna'|'genomic'|
  'protein', mirroring the backend's AnnotationLayer enum, and route
  the mapped frame through the new mapped.{cdna,genomic,protein}
  triple instead of the removed assayLevelHgvs/proteinLevelHgvs
  fields
- In the raw frame, cdna and genomic both alias hgvsNt since the
  submitted string is target-relative and the level split only
  exists post-mapping; sequenceTypeOptions collapses them into one
  "Nucleotide" option keyed by the variant's assayLevel
- Make getHgvsNt in the mapped frame prefer the coding (cdna)
  coordinate over genomic, falling back to genomic only when there
  is no coding projection, so it pairs naturally with the protein
  change in labels and tooltips
- Update tests for the three-level split, the raw-frame NT aliasing,
  the coding-preferred mapped fallback, and the existing protein-only
  no-fabricated-coding guarantee (#784)
Reverse translation expands a change into many redundant equivalence-
class representations (a protein consequence plus its sibling
nucleotide encodings). Add aggregateByStudy to collapse those back
into one entry per study — measurement count, assay level(s)
(protein/nucleotide/mixed), distinct functional classifications, and
functional score range — so search can surface per-study evidence
instead of a row per representation. Studies with more corroborating
measurements sort first, ties broken by title.

Add unit tests covering single- and multi-study collapsing, the
mixed-level flag with internal disagreement, and a null score range
when no measurement carries a score.
…ship

- Rename AlleleResult.variants from nucleotide/protein/
  associatedNucleotide to direct/proteinConsequence/nucleotideEncoding,
  mirroring the API's AlleleMeasurement.relationship, and switch its
  entries from VariantEffectMeasurementWithShortScoreSet to
  AlleleMeasurement
- Add mergeAlleleSpellings to fold one allele's transcript/genomic/MANE
  coordinates into another, deduplicating shared MANE entries, so a
  protein change's several registered transcript alleles collapse into
  one search result instead of several
- Drop the unused `m` regex flag from clinGenAlleleIdRegex/
  clinVarVariationIdRegex/rsIdRegex, switch `||` defaults to `??`, and
  replace a for-in loop with Object.entries in the MANE coordinate walk

Add unit tests for mergeAlleleSpellings covering coordinate folding,
MANE coordinate dedup, and fill-without-overwrite of genome-build HGVS.
Regenerate src/schema/openapi.d.ts against the API's new GET
/clingen-alleles/{id}/measurements endpoint and its supporting
AlleleIdentity/AlleleMeasurement/SequenceLevel/MeasurementRelationship
schemas, and drop the retired clingen-allele-id-lookups endpoint and
its ClingenAlleleIdVariantLookup*/ClingenAlleleVariants types.
- Read `?variant=` off the variant route and pass it through as
  highlightVariantUrn, so VariantScreen can highlight one specific
  measurement without changing the CAID/PAID anchor
- Preserve it through the legacy variant-URN redirect: the resolved
  allele page now carries `?variant=<urn>` so the redirect still
  points at the cited measurement instead of just its allele
- rename getScoreSetByUrn to getScoreSet at its call site
- import getScoreSet from @/api/mavedb/score-sets instead of the
  variants module
- rename getScoreSetClinicalControlOptions to
  getScoreSetClinvarControlOptions to match the ClinVar naming
- add a --nav-height token and have MvNavBar reference it instead of a
  hard-coded 58px, giving full-height panels a single source of truth
- drop the mave-gradient-bar accent from the external identifiers,
  targets, score-set metadata, and experiment keyword/metadata cards
- move the gradient-bar accent from the abstract to the citation card
  on the publication identifier view
Writing `{...cache.value, [urn]: await fetch()}` snapshots the spread
baseline before the await resolves, so two concurrent loaders (the
selected measurement plus a prefetch) both capture the same pre-write
map and the later write clobbers the earlier key. Await into a local
first, then spread, in both loadScoreSet and loadScores.

- note in use-variant-lookup that the citation-honoring path triggers a
  redundant second fetch cycle, to be deduped by a future query cache
Rename the two coordinate frames throughout the resolver and heatmap:
raw -> submitted (target numbering), mapped -> reference. The names now
say what the frames mean rather than how they were produced.

- alias SequenceLevel to the backend enum so genomic is a first-class
  level distinct from cdna, not folded into a single "dna" case
- rework the heatmap tooltip to pair by level: protein <-> coding share
  a transcript, genomic stands alone with no coding pair
- pass the variant URN to the tooltip details link
- drop the now-dead codon-translation helpers and genetic-codes import
- relabel the "Mapped HGVS" download option to "Reference-frame HGVS"
…re set

- reuse SequenceLevel in measurement-types instead of a parallel
  AssayLevel alias; drop the "level" suffix from bucket labels and
  reword relationship labels to read relative to the user's variant
- rename the aggregation's "study" concept to "score set"
  (aggregateByStudy -> aggregateByScoreSet, StudyEvidence ->
  ScoreSetEvidence) and read preferredClassification
- coalesce grouped-allele annotations field-by-field (present-wins) so
  a missing field on one level is carried through rather than read as
  divergence; only a real present-vs-present conflict splits the block
Rename clinical-controls to clinvar-controls and build out the ClinVar
control data layer the variant page and histogram consume.

- add reduceControlPlacement (the D78 divergence fold) producing a
  representative call plus winning-set classifications and directional
  flags, exposed as ClinvarControlPlacement
- add notables helpers: per-class clinical extremes, consequence
  exemplars, score extremes, and median/MAD deviation
- add the useClinvarControls store composable
- alias control types to the generated schema instead of hand-mirroring
  them, and add isClassifiedSignificance to reject ClinVar's "-"
  placeholder significances
- move formatClinvarVersion in from lib/formats and point
  ClinvarControlVariant at ClinvarControlPlacement
- add tooltipFootnote and tooltipEmptyLine helpers
Build the disclosure-ladder infrastructure: a single Key drawer that
any badge can deep-link into, so jargon is explained in one shared
place rather than scattered tooltips.

- add useKeyDrawer, a module-level singleton store driving one drawer
  mounted at the variant-page root, opened via open(term) from any
  subtree
- add the v-key-term directive that turns a badge into a keyboard- and
  screen-reader-accessible deep link into the drawer, centralizing the
  handlers, ARIA, and hover affordance
- add MvKeyDrawer and the variant key-section content definitions
- register the directive and brand the PrimeVue tooltip (sage
  background, body font) as the ladder's first rung
bencap added 26 commits July 18, 2026 13:26
New user-docs section covering variant mapping, reverse translation &
projection, and annotation, plus an "Interpreting Annotated Variants"
page. Wire the pages into the nav and add "annotated variant(s)" glossary
abbreviations.
…s variants

Replace the four-dash facts grid shown for a selected variant with no
resolvable annotations, which added clutter without information.

- Render the facts grid only when a consequence, classification, gnomAD,
  or ClinVar cell resolves; otherwise show a quiet empty-state message
- Keep the identity line in the empty state only when a distinct
  underlying nucleotide coordinate disambiguates the selection
- Move the superseded badge and full-details link into a shared row so
  both survive the empty state without duplicating markup
The VRS digest lookup returns an empty array rather than a 404 when no
MaveDB variants match, so the "not found" warning never fired and the
error was rethrown. Trigger the warning on an empty result and let the
catch handle only genuine request failures.
The related-allele context on a variant's ClinVar and gnomAD cells
could leak the subject's own record back into its "related" list,
and ClinVar precedence would silently borrow a projection sibling's
call even when the measured allele sat at nucleotide level, where
its own absent record is real information rather than a fan-out gap.

- add annotation-subject.ts: a shared SubjectDigest type/helper for
  a measurement's own digest(s), including its c<->g projection twin,
  so gnomAD and ClinVar resolve "is this my own record" the same way
- resolveClinvarRecords/collectGnomadFrequencies now key off the full
  subject digest set instead of a single assayLevelDigest, and drop
  cross-frame duplicates of the subject's own record from the
  related-record enumeration
- reduceControlPlacement only falls through to projection siblings at
  protein assay level; at cdna/genomic level a missing direct record
  returns null (a new 'absent' ClinvarHeadline kind) instead of
  borrowing a sibling's call
- wire assayLevel/assayLevelDigest through VariantDetailPanel and
  VariantScreen to the gnomAD/ClinVar stat components
…ion columns

Replace the retired /mapped-variants JSON download with the
substrate-faithful streaming NDJSON /variant-details export, and
extend the custom CSV download with the new annotation namespaces
(gnomAD, VEP, ClinGen, ClinVar).

- remove downloadMappedVariants; the Variant Details button now
  streams GET /score-sets/{urn}/variant-details via streamNdjson,
  driving a progress bar off the server's X-Total-Count
- fix progress overshooting 100%: count '\n' characters per chunk
  instead of split() segments, which overcounted by one per chunk
  and double-counted lines split across fetch chunk boundaries;
  also clamp the computed percentage to 100
- fix the progress bar rendering under both the Variant Details and
  Annotated Variants buttons at once; track which button's stream
  is active (streamTarget) and gate each bar on its own target
  instead of the shared in-progress flag
- add gnomAD/VEP/ClinGen/ClinVar options to the custom CSV dialog,
  wiring ScoreSetView's resolved clinvarVersion through so the
  versioned ClinVar column can be offered
- update the downloading and API quickstart docs to describe the
  Variant Details export in place of the old mapped-variants JSON
…ence-badge palette

"Your variant" read oddly once the same badge could appear on other pages/contexts,
so the direct-relationship label and its Key-drawer entry are now phrased as "This
variant" instead, with its own dedicated subject color rather than reusing brand
sage. MvMeasurementCard's relationship badge now derives its color from the shared
RELATIONSHIPS dict instead of a hardcoded class, which is what let the two drift
out of sync in the first place.

On the confidence axis (measured/resolved/convergent/candidate), "Resolved" gets
its own dedicated slate rather than reusing the assay-level "Nucleotide" color —
the two answer different questions (was this coordinate measured or derived? vs.
what level was it assayed at?) and coloring them identically read as redundant on
the same card. "Convergent" and "Candidate" are unified onto one shared color: both
represent genuine variant-identity ambiguity, as opposed to "Resolved"'s certain,
deterministic re-expression of the same variant.
Replaces the old VariantScreen with the functional-first redesign: functional
evidence leads, re-anchoring is per-section and always labeled, and clinical/
population reference is shown per allele rather than pooled across a group.

- MvAlleleLedger combines the old separate "related alleles" panel and per-allele
  clinical/population cells into one card per allele group (this-variant/
  this-measurement/collapsed others), each with a role badge and a facts grid.
- VariantConsequenceStat factors the molecular-consequence cell out of
  VariantDetailPanel so the ledger and the score-set panel render it identically.
- VariantClinvarStat/VariantGnomadStat fold their "pooled from N related variants"
  provenance into the stat trigger itself instead of a separate caption.
- use-clingen-allele now surfaces every mapped reference-genome build
  (allGenomicLocationsText) instead of silently keeping only the first, closing a
  gap versus the old VariantInfoSection card.
- clingen.ts extracts the ClinGen registry URL helpers shared by the page and
  search results.
- VariantInfoSection and MvRelatedAlleles are deleted; the ledger fully supersedes
  both.
The `isOpen` watcher only re-fires on open/close transitions, so
clicking a badge while the drawer stays open updated `activeTerm`
without scrolling to it. Watch `activeTerm` directly and scroll
whenever the drawer is open.
…ecision

Cuts redundant restatement (e.g. Convergent said the same thing twice
in different words) and fixes a couple of dangling clauses. Also
corrects the evidence-strength ladder, which only listed 4 of the 5
schema tiers (MODERATE_PLUS was missing despite being a live badge
value).
…rawer

The allele ledger's page-role badge and a measurement's direct-
relationship badge were two names for the same fact (both use the
subject color) — collapse them onto one shared definition instead of
each carrying their own. Moves "This variant" to lead the drawer so
"Relationship to this variant" reads naturally right after it, and
routes MvMeasurementCard's direct-relationship badge to the
"this-variant" deep link (matching MvAlleleLedger's existing pattern)
instead of a "relationship" section that no longer defines it.
Replaces the standalone tooltip (with its own duplicated wording) on
the Superseded badge with a deep link into the shared Key drawer, and
moves the supersession banner after the functional-evidence section
with copy refocused on the score set rather than the individual
measurement. Adds --color-superseded/-light tokens to app.css so the
badge, banner, and glossary chip share one source instead of three
copies of bg-amber-100/text-amber-800.
"Sibling allele" was scattered across the glossary, the histogram
caption/tooltip, and VariantClinvarStat under three different words
("sibling", "related", "pooled") for the same concept. Renames the
glossary section to "Inferred controls" and threads "inferred"
through the histogram tooltip and popover note so the same word
means the same thing everywhere it shows up.

Also fixes the histogram: the projected-control caption was a global
note that couldn't say which series it applied to; replaces it with a
per-series legend flag computed from that series' own classifier, and
teaches the legend to wrap a title across lines (HistogramSerieOptions
now accepts string | string[]) so the added text doesn't widen the
legend enough to cover plotted data. Drops the asterisk convention,
which collided with an unrelated footnote using the same glyph for a
different caveat.
MvAlleleLedger never declared an "identify" prop, so ScoreSetView's
:identify="false" was dead markup. Also reworks the ledger's subtitle
for clarity.
Introduce formatScore() + SCORE_DISPLAY_PRECISION in lib/scores.ts as the single source of truth for rendering a variant's functional score (4 significant figures; null for NA/absent, so callers own the empty case). Route the histogram tooltip, variant-page score, and both measurement components through it, replacing scattered toPrecision(4) literals.
Search indexed only scored variants, so a coordinate query for an in-set variant with an NA score returned "No matching variants" — a false negative on the page's only directory surface. Index the whole set instead; unscored matches select normally.

Since the histogram can't jump to a scoreless variant, the detail panel now carries a Score headline (4 sig figs, or "Not scored") so a selection always reports its measurement.

Also unify the search dropdown's leading dot to one meaning everywhere — a ClinVar control colored by classification — instead of overloading it with consequence-palette and score colors in the empty state.
The API now reaches a CA's protein consequence and sibling nt encodings unconditionally (one behavior on every surface), so the client flag no longer gates anything. Remove includeNucleotideSiblings from the getAlleleMeasurements wrapper (and its memo key) and from the variant search's only call site, which now requests the full equivalence class like the variant page.
MvAlleleLedger never passed assay-gnomad to VariantGnomadStat, so an
allele's own direct frequency was neither promoted to the headline nor
included in the pooled/related list (its digests are in the subject
exclusion set). Only pooled protein-change frequencies from other
alleles survived, leaving the actual allele's cell blank.

Pass the group's coalesced gnomAD annotation, mirroring the existing
vep wiring on the sibling consequence stat, so the allele's own
frequency renders as the direct headline.
…enum values

Renames "twin"/"sibling" to "projection"/"encodings" in the ClinVar/gnomAD
library comments, updates the Cat-VRS `relation` test fixtures from
`is_genomic_of` to `coordinate_representation_of`, and adds the confidence-
badge color tokens (measured/resolved/convergent/candidate) to app.css.
Trims redundant and overly verbose comments across the variant-detail stack
(search screen, variant screen, allele ledger, ClinVar/gnomAD stat cells,
the clinvar-controls libs), fixes a couple of typos, and adds section
dividers to clinvar-control-placement.ts and allele-grouping.ts so each
file's distinct concerns are easier to skim.

Also carries pre-existing, unrelated changes in VariantScreen.vue,
MvAlleleLedger.vue, and allele-grouping.ts (page-group rename and
simplification, "Unclassified" badge label, hasPinnedEntry guard) that
landed on the same lines as the comment edits and couldn't be cleanly
separated from them.
…nnotation state

The compact variant picker showed "No reference annotations were found for this
variant" whenever the protein allele had no direct consequence and no pooled
gnomAD/ClinVar. For a reverse-translated protein change that assertion is false:
the change fans out to candidate nucleotide alleles that carry their own VEP
consequence.

Split the empty state — when candidate-derivation alleles are present, name the
fan-out and scope the absence to population/clinical evidence; keep the original
message only when there is genuinely nothing. The candidate count reuses
groupAlleles so a candidate's c↔g spellings count once.
@bencap bencap linked an issue Sep 19, 2026 that may be closed by this pull request
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 20.469% (+10.0%) from 10.496% — feature/bencap/api-redesign-for-allele-data-model into release-2026.3.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants