2026-07-04 — v22 contig assembly
Work unit 3: load two Sanger reads (typically a forward + reverse pair) and the viewer assembles them into a consensus contig entirely in the browser — no server, no upload, no new npm dependencies. The overlap is found by an ungapped greedy algorithm that tries all four strand orientations, builds a per-position layout, calls IUPAC ambiguity codes at mismatched positions, and exports a clean FASTA. The privacy wedge — data never leaving the machine — is preserved end-to-end.
What changed
-
Ungapped overlap finder (
src/consensus/overlap.ts). Scores every suffix/prefix pairing of length k ≥ minOverlap across four orientations (fwd, fwd-rc, rev, rev-rc) and returns the highest-scoring candidate. Per-position scoring: exact match = +1, IUPAC-compatible partial match = +0.5, mismatch = −1, N or gap = 0 (neutral). For Sanger reads of ~800 bases the inner loop is ~640 k iterations — fast enough to run synchronously. ExportsreverseComplement,scoreOverlap, andfindBestOverlap. -
Contig builder (
src/consensus/contig.ts).buildPairedContigcallsfindBestOverlap, lays the upstream read at offset 0 and the downstream read at offset = upstreamLength − overlapLength, then walks every contig position to build per-positionPositionSupportobjects and a full consensus string. Quality-weighted consensus: when both reads cover a position and their bases differ, the higher-quality read's base wins; when quality is equal or unavailable, an IUPAC ambiguity code is emitted andmismatchCountis incremented.toContigFastaserialises to standard FASTA (80-char line wrap, trailing newline, header format>contig [nameA + nameB] N bp). -
Contig panel UI (
src/components/ContigPanel.ts). A panel below the reference-alignment section showing three labelled sequence rows (forward / reverse / consensus) with the overlap region highlighted, a summary line reporting contig length, overlap length, mismatch count, and single-coverage count, plus "Assemble pair" and "Export contig FASTA" action buttons. The panel is always visible; the Assemble button is only enabled when exactly two traces are loaded. Fully accessible:role="table"row/cell semantics,aria-livestatus region, keyboard-focusable buttons, visible focus rings. -
TraceViewer wiring (
src/components/TraceViewer.ts).syncContigPanel()— mirroring the existingsyncConsensus()pattern — enables/disables the Assemble button based on the slot count and clears any stale contig when the trace set changes.runContigAssembly()extracts base-call sequences and quality arrays from the two active slots, callsbuildPairedContig, renders the result, and enables the export button.syncContigPanel()is called alongside every existingrefreshConsensus()call so the two panels stay in sync. -
Full light + dark theme (
src/style.css).--color-contig-*design tokens in both:root(light) and the dark-mode media query. Overlap cells are highlighted with a distinct tint; mismatch bases are shown in amber/amber-dark; single-coverage regions are subtly dimmed. Three-row sequence display usesoverflow-x: scrollso it stays readable at any viewport width.
Spec grounding
Tests
| File | Count | What is asserted |
|---|---|---|
tests/core/contig.test.ts |
33 |
reverseComplement exact output including IUPAC bases;
scoreOverlap exact numeric values for match / mismatch / N /
IUPAC near-match / gap; findBestOverlap null on no-overlap,
null on all-mismatch, fwd orientation, fwd-rc orientation, rev (B upstream)
orientation, best-score selection across lengths;
buildPairedContig exact contig length, consensus string,
overlapStart/End, coverage array, mismatchCount=0 for identical overlap,
singleCoverageCount, IUPAC ambiguity code at mismatch position,
quality-weighted winner, fwdName/revName assignment, readIds pairing,
support array length and sequential consensusIndex values;
toContigFasta exact header + sequence bytes and 80-char
line wrap.
|
tests/e2e/contig.e2e.test.ts |
6 |
Panel visible after load; Assemble disabled with one trace; Assemble
enabled with two traces; clicking Assemble shows summary with numeric
contig info; FASTA download header starts with >contig [;
Assemble re-disabled after closing second trace.
|
Design decisions
- No new npm dependencies. The overlap finder, contig builder, and FASTA exporter are pure TypeScript with zero runtime imports beyond the existing codebase.
-
Ungapped assembly (v1). The greedy suffix/prefix algorithm
handles the primary Sanger use-case — a forward and reverse read that overlap
cleanly — without the complexity of gapped alignment. A gapped follow-up
(Smith-Waterman or banded DP over the overlap region) is noted as a
// TODOinoverlap.ts. -
Four-orientation search. Rather than requiring the caller to
know which read is forward and which is reverse,
findBestOverlaptries all four orientations (A→B fwd, A→RC(B) fwd-rc, B→A rev, B→RC(A) rev-rc) and returns the best-scoring candidate. This means loading two reads in any order — or in their raw sequencer orientation — always produces a result. - Exactly-two-trace gate. The Assemble button is enabled only when exactly two traces are loaded (not ≥ 2), since pairwise assembly is meaningless for three or more reads. The consensus row uses ≥ 2, so the two panels are complementary rather than redundant.
- Contig not encoded in permalink. A full consensus contig can be hundreds to thousands of bases; encoding it in the URL hash would routinely exceed the 1800-character guardrail. The user can re-run assembly from the original traces, which are themselves re-attachable via the existing privacy-safe reattach flow.