← Back to devlog

Inside the file: surfacing trace metadata and peak amplitudes

An .ab1 file is not just a stream of peak heights and base calls. Buried inside every ABIF container is a directory of tagged records — instrument model, sample name, dye set, base-calling mobility file, run timestamp, lane number, operator comments. Professional viewers like Chromas have always surfaced these in a properties panel. Until today, sanger-viewer threw them away.

What shipped in Step 9

The ABIF parser now walks the full tag directory and extracts eight metadata fields wherever they are present in the file:

Every field is guarded: if the tag is absent the field is simply undefined and silently omitted from the panel. The viewer never crashes on missing fields. SCF files, which carry no ABIF tags, still work fine — they get a version field from the SCF header and nothing else.

The old loose Record<string, string | number> metadata type has been replaced with a typed TraceMetadata interface whose fields are all optional. This caught an existing bug: the old parser fell back to reading PBAS data instead of returning undefined when MCHN1 was absent.

Per-base peak amplitudes in the tooltip

The hover tooltip now shows the raw channel values at the selected peak position — all four of them, for every base. Before, you saw something like:

#47 A peak:3221 q:58

Now you see:

#47 A peak:3221 q:58 A:1842 C:104 G:219 T:87

Those four numbers are the signal intensities of the A, C, G, and T channels at the exact sample position of the base call. In a clean trace the called base's channel is the dominant one — exactly as you'd expect. When the numbers look off (say the called base has a much lower amplitude than a neighbour channel) that's a useful diagnostic hint that the base call may be wrong.

Implementing this was a small extension to hitTest: once we have the best matching base index, we know its peak position in sample space, and we can simply index into each of the four Float32Array channels at that position. The values are rounded to integers for display.

The metadata panel

A compact panel appears below the sequence panel whenever a trace is loaded and carries at least one piece of metadata. It renders as a two-column grid: label on the left in muted type, value on the right in monospace. If the file carries nothing useful — an SCF with no ABIF tags, or an AB1 that was written without optional fields — the panel stays hidden rather than showing an empty box.

The CSS uses display: grid; grid-template-columns: max-content 1fr on the row container, with each row using display: contents to participate in the parent grid. This means label and value columns align cleanly without a table, and long values wrap gracefully into the right column only.

Typing and testing

The new TraceMetadata interface propagates through the whole stack — parser return types, TraceData.metadata, the revcomp passthrough, and the structured-clone transfer to the Web Worker. TypeScript catches any site that tries to access the old machine key (now instrument).

Seven new Vitest unit tests assert that the 310.ab1 fixture yields a sample name and an instrument string, that the SCF fixture yields a version and no ABIF fields, and that absent optional fields are undefined rather than throwing. Four new Playwright tests confirm the panel is hidden before load, visible (with at least one row) after loading an AB1, stable after loading an SCF, and that the tooltip text matches the amplitude pattern A:\d+ C:\d+ G:\d+ T:\d+.

What's next

The metadata panel is intentionally minimal right now — plain text, no icons, no copy-to-clipboard. The next passes will add reverse-complement and strand view improvements, PHRED quality display enhancements, and multi-trace workspace support. Each will land as its own small PR, green CI, updated devlog.