Chroma — an open-source WebGL genome browser as an IGV.js alternative (looking for testers + feedback)
Live demo: https://chroma-delta.vercel.app
(no signup, no upload — boots into a chr20:10M window with five demo tracks pre-loaded from public S3 / UCSC / Ensembl)
What it is
Chroma is a browser-based genome viewer aimed at being a faster, more keyboard-friendly alternative to IGV.js. The whole render path is WebGL2 (hand-written, no Three/Pixi); state lives in Solid.js signals; parsing runs in a Comlink-managed worker pool.
I've been driving the whole project through Claude Code — solo dev plus agents — and after ~50 commits, I've hit the wall on knowing what to ask it to build next, hence this post.
What works today
- 5 demo tracks:
- hg19 reference FASTA (IGV/Broad mirror)
- Ensembl gene annotations
- UCSC phyloP100way conservation BigWig
- HG00096 1000G BAM
- HG002 GIAB 300× BAM (hidden by default — too slow to load for the default boot)
- Two-level navigator:
- top bar = whole chromosome with Mb-scale ticks (click to jump, drag to pan, drag empty to drag-create)
- bottom bar = local context with drag-create / move / edge-resize / Esc-cancel
- Reference renderer with two modes:
- colored 1-bp quads at any zoom
- actual A/C/G/T/N letters via a Canvas2D-baked atlas when
basePixelWidth ≥ 12 px
- Gene name labels rendered on a Canvas2D overlay, shrink-wrapped with ellipses at narrow blocks, strand-aware alignment (5' anchors to the leading edge)
- Single-fetch viewport mode at pileup tier (≤50 kb spans): one HTTP Range per nav instead of N tile fetches — 6× speedup on the 1-track B1 cold load (4.7 s → 774 ms)
- Sticky URL → worker dispatch (FNV-1a hash on the file URL) so the per-worker u/gmod parser caches (BAI 8.7 MB) actually get reused instead of scatter-loaded N times
- 64-bit bigint coordinates throughout, with a single sanctioned conversion to Float32 for shader uniforms
- 60 fps pan / zoom on a 1 Mb BAM viewport, p95 fps locked
- 250 unit tests, TypeScript strict +
noUncheckedIndexedAccess, ~88 kB main JS gzipped
Tech stack
- Solid.js for the reactive shell (picked over React for fine-grained signals + smaller bundle — the eventual goal is clinical-report embed)
- WebGL2 hand-written, instanced rectangles for everything geometric; Canvas2D overlay only for text labels
- u/gmod**/{bam, bbi, indexedfasta}** for the format parsers
- u/chenglou**/pretext** for unicode-correct text measurement and shrink-wrap on the label overlay
- Comlink worker pool + Cache API for HTTP range coalescing
- Vite + pnpm + Vitest
Honest gaps (what's still broken/missing)
- B1 cold gate target is 300 ms, currently ~3 s for the default 5-track demo — dominated by one-time BAI parse (~3.7 s for HG00096, ~6.6 s for the 300×). Needs either a streamed BAI parse or a cap-at-N read fetch in the worker.
- No CIGAR support — reads render as plain rectangles, no insertions/deletions/mismatches shown
- No VCF track (parser stubbed)
- No hover/select tooltip yet
- Pileup row collisions across tile boundaries are accepted (cross-tile merge is a carry-forward)
- HG002 300× BAM works but takes ~10 s on first nav — that's why it's
visible: falsein the default seed
What I'm asking the community for
1. Hit it in your browser and try to break it.
Bug reports welcome — please include the locus/zoom level when reporting.
2. Prompt suggestions for what I should build next.
I've been stuck between these and would love opinions:
- VCF track (parser stubbed already)
- CIGAR-aware read rendering with mismatch coloring
- Per-base read sequence letters at deep zoom (would need to extend the SoA
ReadTilewith packedSEQ) - Splitview (two viewports side-by-side, IGV style)
- Click-to-pin tooltip with full feature info
- Cap-at-N read fetch for high-coverage BAM (so the 300× track stops being a footgun)
- Label color is reactive to the dark theme
- BED track for arbitrary user regions
Thanks in advance for any feedback.