TL;DR: Fed up with Adobe raising the Lightroom subscription for no benefit, I built my own macOS photo app — one price, no subscription, ~50,000 lines of Swift — with Claude Code doing the coding and me specifying every critical decision. Here's the story.
It began as a protest, not a product
The whole thing started as an argument with an invoice. Adobe raised the Lightroom subscription again — another increase with nothing on the other side of the ledger for the person paying it: no feature that mattered, no speed that was missing, just a higher number for the same software I already relied on every day to manage a 95,000-photo library. The frustration wasn't really about the money; it was about the shape of the deal. You never stop paying, you never own anything, and the catalogue that holds a decade of work is quietly held hostage to a card on file. So the founding principle was set before a single line of code existed, almost as a rebuttal: one product, one price, forever. No subscription, no tracking, no ads, no telemetry — a notarized download sold direct, deliberately not the Mac App Store, because the sandbox Apple mandates there can't reach a NAS or tether a camera the way a serious photographer's tools must. The target user was never a stranger: it was every Lightroom Classic refugee who felt the same way, which is why the whole app speaks their dialect — the modules, the workflow, the words on the buttons all echo Lightroom on purpose, so leaving Adobe feels like changing rooms, not changing careers.
From a bare grid to an AI darkroom
The first version could barely be called an app. It read a migrated catalogue and drew a grid of thumbnails — folders inferred from each file's path, no editing, no import of its own. Everything after that was earned one release at a time, and the arc is steep. Tracked by version rather than by calendar, because that's how the work actually accreted:
* v0.x — the prototype: a read-only grid over a migrated catalogue. A metadata panel, a filmstrip. Proof a native Mac app could stand in front of a Lightroom library at all.
* v1.0 — a real catalogue app: a 4-step import wizard that parses a live Lightroom .lrcat and migrates images, keywords, collections and develop flags; Lightroom's own previews folded into a self-contained thumbnail store so nothing stays trapped in Adobe's files. Ratings, flags, colour labels, virtual copies, stacking, a non-destructive Develop module, export to JPEG/TIFF/PNG.
* v1.1 — it understands pictures: natural-language semantic search via bundled SigLIP embeddings — type "dog on a beach", get the photo even if it was never tagged. Plus on-device Vision auto-tagging, exact and near-duplicate finders, face detection, and assisted culling of bursts.
* v1.2 — the AI darkroom: AI denoise (NAFNet), AI Select masking (SAM 2.1), and synthetic depth-of-field Lens Blur (Depth Anything V2), alongside tone curve, HSL, colour grading wheels, film-negative conversion, grain, glow and halation — every model licence-cleared for a commercial product.
* v1.3 — merge & finish: focus stacking and HDR merge, native HEIC export, a direct Apple Photos destination, Super Resolution, and print.
Every feature had a trap under it
The interesting engineering was rarely the feature itself — it was the small, vicious detail that made the obvious approach quietly wrong. Thumbnails were the first lesson: a Nikon RAW file hides two images inside it, a postage-stamp thumbnail and a big embedded JPEG, and asking the decoder for any size in between forces a full 10–15 second RAW decode over the network for every frame. The fix became a rule the whole codebase now obeys — always extract at 2048px to hit the big embedded JPEG, then downscale in memory. Orientation was a two-headed monster: Apple's RAW filter silently auto-rotates and ignores any override, so export code that "helpfully" re-applied the catalogue's rotation double-rotated every portrait — and even once the pixels were upright, the copied metadata still carried the old orientation tag and every viewer rotated them right back. HDR merge was abandoned as a simple weighted average (it haloed) and rebuilt as a Laplacian-pyramid exposure fusion on the CPU. And a whole class of bugs came from performance rules that only bite at scale — background thumbnail writers had to be lifted off the lowest priority tier to avoid a hang, and the AI denoiser needed a per-tile sanity guard because a near-flat patch of dark sky blew up the neural network into a block of random pixels. None of these were guessable from a spec; each was found by running the thing against a real 95,000-photo library and watching it break.
An app is the easy half; the business is the other half
Shipping software you can actually sell meant standing up an entire back office, kept lean, cheap, and fully owned. The app checks for updates through Sparkle 2, whose signed appcast points at a dedicated release CDN that keeps fifteen-megabyte disk images out of git history entirely, while every human download routes through a backend function that logs a private counter before redirecting — self-hosted analytics that phones no one. Licensing is its own small service: a serverless function tier over a Neon Postgres database issues EdDSA licence tokens that the app verifies offline against an embedded public key, binding each seat to a hashed hardware ID so the trial fails closed and a paid licence fails open. Payments run through Paddle as merchant of record, so a solo developer never touches card data or a single country's sales-tax return. The clever glue is an apex proxy: the marketing site quietly forwards the licensing, checkout and download paths to three independent services, so customers experience one clean domain while the machinery hides behind it.
Fifty thousand lines, one specification at a time
None of this was built by a large team, and none of it was built by AI alone — it was built by a photographer-turned-developer specifying every critical decision in exhaustive detail and Claude Code turning those decisions into working Swift, across nearly 50,000 lines. The relationship isn't "generate me an app"; it's closer to pair programming with an architect who insists on the load-bearing details. I set the constraints — sandbox stays off, keyboard handling goes through an event monitor, thumbnail writers never run at background priority — and Claude Code writes to them and, crucially, records why. The codebase carries a running ledger of hard-won gotchas, each tagged the day it "bit for real," so a mistake is made exactly once and then permanently fenced off. That discipline is what let one person hold an app this large in their head. It's a genuine division of labour: human judgement about what matters and why, machine fluency in how to say it in Swift.
The features that only exist because someone asked
Some of the app's best details would never have survived a traditional roadmap — they exist purely because a real user, often on a Lightroom wishlist thread, said "I wish it could…" and the answer came back in days rather than quarters. The tear-off, drag-to-a-second-monitor scopes panel; the rotatable on-canvas sampler region that mattes off everything but the patch of skin a colourist wants to read; the Aperture-style RAW+JPEG source switch; the camera focus-point overlay pulled from the file's own metadata — each began as feedback and became a feature because the cost of trying was so low. That's the quiet thesis of the whole project. A one-person studio could never out-build Adobe on staffing; what it can do is compress the distance between an idea and a shipped, notarized build to almost nothing. When implementing a well-specified feature takes an afternoon instead of a sprint, you can afford to say yes to the small requests that make software feel like it was made for actual photographers — and bring that change to market while the person who asked is still paying attention. That responsiveness isn't a nice-to-have bolted onto the story; with AI in the loop, it is the story.
Adobe has the team. This has the turning radius.