2026-07-04 — v21 reference alignment + variant/SNP calling
Work unit 2: paste or load a reference sequence; the viewer aligns the Sanger read to it entirely in the browser (no server, no upload), calls substitutions, indels, and ambiguous IUPAC positions with 1-based coordinates, shows them in a filterable review table, and lets you export a CSV or VCF-lite TSV. The privacy wedge — data never leaving the machine — is preserved end-to-end.
What changed
-
Banded semi-global Needleman-Wunsch aligner (
src/alignment/aligner.ts). Semi-global alignment gives free end-gaps on the read so a partial Sanger read can align anywhere within a longer reference. Bandwidth is 20 diagonals on each side, which handles typical sequencing indels while keeping the runtime near-linear. Strand is auto-detected: if the reverse-complement alignment scores higher, the read is flipped. FASTA and raw-sequence input are both accepted. -
IUPAC-aware scoring matrix (
src/alignment/iupac.ts). Ambiguous bases (W, S, M, K, R, Y, B, D, H, V, N) are scored by the fraction of their possible expansions that match the reference base, so a Y read-base against a T reference contributes +0.5 rather than being penalised as a full mismatch. -
CIGAR string builder/parser (
src/alignment/cigar.ts). Coordinate mappersreadPosToRefPosandrefPosToReadPosenable exact 1-based reference positions for every called variant. Insertions returnnullfor the ref coordinate; deletions returnnullfor the read coordinate. -
Variant caller (
src/variants/caller.ts). Walks the CIGAR segment-by-segment. Substitutions that differ from the reference base become SNVs; multi-base runs of I or D are collapsed to a single indel record; read bases that are IUPAC ambiguity codes (but not N) and could differ from the reference emit anambiguouscall. Confidence follows quality score brackets (high ≥ 40, medium ≥ 20, low otherwise). N bases are skipped. -
Variant filter utilities (
src/variants/filter.ts).filterVariantssupports four filter modes — all, high-confidence, ambiguous, and indel — plus suppressed-variant exclusion for export. -
CSV and VCF-lite TSV export (
src/export/variants.ts).toVariantsCsvemits a deterministic UTF-8 CSV with the headerposition,ref,alt,type,confidence,review.toVariantsVcfemits a minimal VCF with the mandatory meta-info lines, header, and one data row per non-suppressed variant. Both are fully deterministic and exact-byte tested. -
Reference panel UI (
src/components/ReferencePanel.ts). A collapsible textarea (FASTA or raw sequence) plus a "Load FASTA" file button and an "Align" button. While alignment runs the button shows "Aligning…" and the textarea is locked. On success, a status line reports strand and 1-based reference span. A "Clear" button resets everything. -
Variant review table (
src/components/VariantTable.ts). A filter tab bar (All / High / Ambig / Indel), row-level review cycling (pending → confirmed → suppressed → pending), and export buttons. The table is hidden while no alignment is active. Suppressed rows show struck-through text and are excluded from all exports. -
Permalink wiring.
variantReviews— the per-variant review state — is encoded into the permalink alongside edits. The reference sequence itself is intentionally not encoded (too large for a URL; the UI says so explicitly). Sharing a view that includes a reference requires the receiver to re-paste the reference and click Align.
Specs grounding this unit
Tests
| File | Count | What is asserted |
|---|---|---|
tests/core/alignment.test.ts |
43 | IUPAC scoring exact values, reverseComplement, CIGAR parse/build, coordinate mapping (readPosToRefPos / refPosToReadPos), CIGAR length helpers, aligner placement + strand detection, known-mismatch variant positions from CIGAR walk. |
tests/core/variants.test.ts |
29 | callVariants exact position + type + confidence + alt sequence; filterVariants all four modes; countByType; toVariantsCsv exact bytes; toVariantsVcf exact bytes; suppressed-variant exclusion. |
tests/e2e/reference-alignment.e2e.test.ts |
8 | Panel visible after load; Align button disabled when empty; Align enabled after paste; status shows strand + ref-pos range; variant table appears; filter tab state transitions; CSV download header exact match; Clear resets panel and hides table. |
Design decisions
- No new npm dependencies. The aligner, scorer, and exporters are pure TypeScript with zero runtime imports beyond the existing codebase.
-
Circular alignment is supported. Setting
circular: truein the aligner rotates the reference to find the best wrap-around placement — useful for plasmid reads. Disabled by default. - Reference not encoded in permalink. A reference sequence can be hundreds to thousands of bases; encoding it in the URL hash would routinely exceed browser limits. The UI documents this: the share link encodes review decisions but not the raw reference. This is consistent with the privacy-first design (the reference is also often the researcher's unpublished data).
-
Variant IDs are deterministic. The format
{alignmentId}:{position}:{ref}:{alt}means review-state can survive a re-alignment to the same reference and be correctly re-applied via permalink.