Making the viewer actually easy to use
The last two passes gave us a working viewer and solid tablet interaction. What they didn't give us was a good first impression. Open the page for the first time and you were staring at a plain dashed box with an unstyled file input, no hint of what the tool can do, and no real feedback when something went wrong. That ends today.
A first screen worth looking at
The biggest change is the empty state. Instead of a blank box, new visitors now see a proper onboarding panel — a DNA glyph, a short sentence about what kind of file to bring, a prominent "Choose file" button, and right next to it a "Load sample" button that fetches a real AB1 trace from the server with a single click. No hunting for a fixture, no reading docs. You can be looking at actual chromatogram data in under two seconds from a cold start.
The sample load path uses import.meta.env.BASE_URL so it resolves to
the right URL whether you're running in dev or served from the GitHub Pages subpath.
That tripped me up on the first try — the file was 200 in dev but 404 on Pages
because I had a bare /sample.ab1 path. Fixed by making it relative to
the base.
Knowing what's happening
Previously, loading a file showed a plain text status string in-place and error messages were equally unstyled. Now there are three distinct banners:
- A spinner + muted text while parsing is in progress.
- A green success banner once the trace is loaded, showing the file name and base count.
- A red error banner with the exact parser message if something goes wrong — invalid format, corrupted data, unsupported file. No silent failures, no crashes, just a clear explanation.
Controls are also disabled while a file is loading, so you can't accidentally trigger an export or a zoom action against a half-loaded trace.
Keyboard and screen reader support
The controls now expose accessible names via their visible button text, and the toolbar
itself carries an aria-label. The status banners use
aria-live so screen readers announce transitions without the user having
to manually navigate there. The hidden #status span is still updated for
automation tests, but it is no longer a live region to avoid duplicate announcements.
Focus rings are now visible on every interactive element, which sounds like a tiny thing but makes a meaningful difference if you actually tab through the UI. The whole site now uses a proper token system for colours, spacing, and radius, so dark mode support came almost for free once the tokens were in place — every surface just maps to a different variable value.
Design system foundation
The CSS got a full rewrite from scattered raw hex values into a token-first system.
Light and dark palettes live in :root and a
prefers-color-scheme: dark block respectively. Type scale, spacing grid,
border radii, and transitions are all named and consistent. The dragging state on the
dropzone now pulses with a proper focus ring rather than just changing border colour.
None of this required a component library. It's plain CSS custom properties, which the browser handles natively and which Vite passes through without transformation.
What's left
Performance is still wide open — the large fixture works but draws every base at every zoom level, which gets choppy as you zoom out. The next pass will add viewport windowing and rAF-throttled pan/zoom, and move heavy parsing off the main thread into a Web Worker. After that, true end-to-end Playwright coverage that exercises real fixtures through the UI, asserts the canvas is non-blank via pixel sampling, and verifies every interaction path we've built so far.