From new features to making things work well
I spent months adding features to Spectral. Now I am spending my time making sure it does not crash when you feed it thirty RAW files.
From new features to making things work well
For the first few months of building Spectral, almost every weekend was about adding something new.
I have a background in web development and photography, working as a frontend developer at creative agency Kaliber by day. Spectral started as a side project born from frustration: I wanted a fast, private photo editor that runs entirely in the browser, without subscriptions or cloud uploads.
Early prototyping was intoxicatingly fast. I started with a crop tool and an exposure slider. Then I wanted RAW support, so I integrated a WebAssembly build of LibRaw. Then came local gradient masks, camera calibration, 3D LUT export, folder sync through the File System Access API, and grading recipes.
It was incredible to see how quickly an idea turned into working software. But adding new features is the easy part. Making everything work reliably when someone drops fifty 26-megapixel RAW files into the window is where the real craftsmanship begins.
When a tool gets in the way
Building quickly creates a subtle trap. When adding a feature feels cheap, you are tempted to say yes to every idea. Whenever I hit a minor inconvenience in my editing, I added a new control.
The problem is that each new button adds visual noise and another place for bugs to hide. At one point, the right-hand panel had so many controls that finding the white balance slider took two seconds of scrolling. Worse, opening thirty large RAW files in a row started eating two gigabytes of browser memory until the tab crashed.
If you are sitting on the couch after a walk and want to edit ten photos from the afternoon, a crash ruins the entire experience. You do not care that the app has twenty different film presets if it freezes while exporting a JPEG.
Software needs strict constraints and taste
One of the biggest lessons from this project is that a clear vision matters more than speed. Without it, you end up with a bloated interface that does everything and nothing well.
I defined a strict style guide (the “Cinematic Aperture” design spec) with dedicated design tokens for colors, spacing, and typography. Every component must adhere to these tokens. Without clear architectural constraints and visual boundaries, a codebase quickly dissolves into a visual and technical mess.
The hidden cost of features
Adding a feature is never a one-time effort. Every feature brings long-term baggage:
- Automated testing. Every new slider, tool, or export format needs comprehensive test coverage. Spectral now runs over 1,900 automated unit and component tests. Without rigorous test suites, a refactor in one area can silently break rendering in another.
- Maintenance overhead. Every additional feature expands the codebase and creates more surface area for edge-case bugs. At some point you spend more time maintaining what you have than building anything new.
Shifting focus to the end product
Over time, my approach has shifted. I spend much less time thinking about what to add next, and far more time focusing on performance and user feel: testing edge cases, measuring frame drops, and evaluating the tangible result.
Building Spectral has also been a continuous learning process. When IndexedDB started choking on dozens of heavy RAW files, I discovered the Origin Private File System (OPFS) — a browser API I had never worked with before. Moving raw image storage to OPFS cut library loading from seconds to under 200 milliseconds.
The same learning curve applied to browser security and hardening. Even without backend servers or user accounts, a local-first app has its own security demands: enforcing strict Content Security Policies, sanitizing imported community recipes and LUT files, and ensuring private photos never leave the local machine. Building Spectral defensively was an eye-opening lesson in making a purely client-side tool robust and safe.
What I am focusing on now
Spectral now has the tools I actually need for color grading: curves, HSL, color wheels, gradients, and LUTs. I do not need more buttons.
Instead, I have spent the last weeks on the unglamorous parts of the codebase:
- Memory and cleanup. Decoding RAW files and rendering WebGL shaders at 60fps generates large arrays in memory. If GPU textures and canvas buffers are not cleaned up explicitly, the browser holds onto them. I overhauled the image loader to release old textures the moment you switch photos in the filmstrip.
- Storage speed. Moving large image files to the Origin Private File System (OPFS) and keeping only metadata in IndexedDB cut library load times from seconds to under 200 milliseconds.
- Worker threads. Calculating a live RGB histogram from a 4K image on the main thread caused a 50 millisecond stutter when dragging the exposure slider. Offloading the histogram calculation to a dedicated Web Worker kept the slider rendering at a steady 60 frames per second.
- Keyboard shortcuts and precision. Sliders now respond with finer precision when holding down the Alt key, and undo history restores the exact state without lag.
Keeping it focused
Spectral is a personal project. I am not trying to build Lightroom, and I do not have an ongoing subscription model to feed with artificial updates or monthly feature checklists.
One person with clear taste and a specific problem can build software that matches their exact workflow. The goal now is to keep it fast, private, and rock-solid so it simply gets out of the way when you want to edit your photos.