← Back to devlog

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:

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.