Jonas ForshellSenior Product Owner
jonas@jforshell.seEmail me
Working buildUser testingCase ยท Own build

Archive Lens, a historical photo catalogue

A friend researches old photographs. The files were safe in Google Drive. The knowledge around them was scattered across names, places, dates, studios and research notes. Archive Lens is the catalogue that connects the two, without moving a single original.

Context
Historical photograph catalogue
Role
Product Owner and builder
Stage
In user testing with its first real user
Stack
React, FastAPI, Supabase, Cloud Run, Google Vision, AI on the user's own key

The short version

The problem
A friend researches old photographs. The files were safe in Google Drive, but the knowledge about them was scattered.
What I did
Designed and built a catalogue that connects the two. OCR reads, AI suggests on her own key, a person approves.
What it was worth
A hosted product in user testing with its first real user, with cost and processing under deliberate control.
The call
Originals never leave Drive, and nothing becomes catalogue data until a person agrees with it.

Easier to watch than to describe

Eight screens, no sound, thirty five seconds. Today, the Drive folders being watched, a run in flight with two failures explained, a photograph reviewed from its back with the front a click away, an album page boxed into four photographs, the catalogue, a search matching a studio mark read off a card, and AI spend against its ceiling.

Built from the product's own screen captures rather than a screen recording, so it re-renders identically every time the interface changes.

From local experiment to hosted product

I started with local OCR extractors and a local worker. Keeping processing next to the files looked efficient. The OCR quality was inconsistent and the setup was far too technical for the person who would use it.

Continuing towards a Windows application would have solved the wrong problem well. I moved to a hosted browser workflow instead. Connect Drive, choose what to process, review the output, search what has been approved.

This is an AI build. Claude and Codex wrote essentially all the code. My part was the product. Scope, decisions, review and testing.

Four decisions define the product

The boundaries
  1. 01

    Originals stay in Drive

    Archive Lens stores Drive references, derived previews, read text, metadata and review state. It never makes a second full resolution copy. The worker downloads an original into temporary storage, derives what it needs and deletes it. Drive remains the source of truth.

  2. 02

    Indexing is not processing

    You can index and browse Drive files before deciding what to process. Nothing quietly consumes OCR quota, processing time or storage just because it exists in a folder. A run admits as many photographs as the month allows and says how many it left out.

  3. 03

    Machine output is a suggestion

    OCR reads the text. An AI pass turns that reading into names, places, dates and studios, and can describe the picture. None of it becomes catalogue data until a person accepts, corrects or ignores it, and nothing a person approved is ever overwritten by a machine.

  4. 04

    AI runs on your own key

    There is no platform key, so AI spend never lands on me. A workspace adds its own provider key and a monthly ceiling that blocks rather than warns. Sending the picture itself is off until someone switches it on, and then only a small derived copy leaves.

The workflow as it stands

  1. Connect Google Drive and save the folders to watch.
  2. Index a folder. That syncs file names and references, nothing more.
  3. Process the folder. The month's allowance decides how many photographs are admitted, and the rest are reported as blocked rather than dropped.
  4. A Cloud Run worker downloads each original temporarily, makes the preview and thumbnail, sends a prepared image to Google Cloud Vision, and deletes the download.
  5. If the workspace has switched AI on, a text pass turns the reading into field suggestions. Describing the picture is a separate opt-in and sends a 384 pixel copy.
  6. Review the photograph, front and back. Send a read line into a field, strike out what is not on the card, turn it, split an album page into its own records, and see when it looks like one already catalogued.
  7. Finalise. The record reaches the catalogue, where search forgives spelling, filters show what is still thin, and shared fields can be set across many records at once.
  8. Export the approved fields as CSV or JSON.
Archive Lens dashboard showing what needs a person today, where the archive stands and the month's Cloud Vision allowance
The dashboard opens on what needs a person today and what is left of the month's allowance.
Archive Lens review queue showing the back of a photograph with its read lines and the suggested fields waiting for approval
Review shows one photograph, front and back. Read lines from either side fill the one record, and a person decides.
Archive Lens processing view showing a run in flight with failures explained
Processing is modelled as explicit stages, including the failures and the retries.
Archive Lens AI and costs view showing the workspace's own provider key and spend against the monthly cap
AI runs on the workspace's own provider key. Spend is shown against its monthly ceiling rather than discovered later.
Archive Lens user flow from Drive Sources through indexing, processing, the AI pass, the review queue, the catalogue and settings
The user flow from Drive connection to reviewed catalogue record.

What the AI pass does

Built, not yet proven

Cloud Vision reads the text on a card. It does not know that "S. Richards, Photographer, Wellington" is a studio, a photographer and a place, or that "1869" is a date. The AI pass does that. It takes the reading, the filename, the folder and the workspace's own vocabulary, and returns a set of fields with a confidence on each. Title, subject, photographer, studio, place, date. Every one arrives as a suggestion beside the field it belongs to, and review treats it exactly like a line the OCR read. Accept, correct or ignore.

Two passes, priced differently. The text pass sends no image and is the cheapest call in the system. It runs after every read, and it can be run over an archive that was read before AI was switched on. The description pass sends a 384 pixel colour copy of the picture and writes one or two sentences about what is visible. It is off until a workspace turns it on, because a picture leaving for a third party is a new outbound flow and deserves a deliberate choice. Descriptions say what is in the picture, never who a person is.

The archive can be told about itself. A short instruction travels with every request, something like "studio marks are printed on the reverse and inherited notes are in ink over them, never guess a sitter's name from a note that only states a relationship". Each version of that instruction is kept, so two prompts can be compared on what they got right.

Money stays visible. The workspace picks a provider, Anthropic, Google, OpenAI or OpenRouter, and adds its own key. A cheap model answers first and a stronger one is only asked when the first is unsure. An identical request on an unchanged photograph is served from cache and costs nothing. A run past a threshold shows its estimated total and asks twice. The monthly ceiling blocks rather than warns. Afterwards an accuracy report says how many suggested fields were accepted, edited or rejected, and what each accepted field cost. A thousand photographs on the cheapest models cost a few dollars at most. The real risk is not the per photo price but re-running the whole archive on a prompt that turns out to be wrong, which is what the estimate and the report are for.

All of that is built and none of it is proven. The pass has not yet been run over the real archive, so nobody knows how often it names the right studio or invents a date. The golden set exists for exactly that measurement, a handful of photographs someone vouched for, and its screen says plainly that the comparison run is still missing rather than showing a dead button. Running that evaluation is the next piece of AI work, and it comes before any more AI features.

What the research added

August 2026

After three months with one real user I could not tell which of her habits were the hobby's and which were hers. So I read the guides people follow to identify old photographs, the forums where they get stuck, and the complaint threads of the tools they leave. Six things came out of that, and all six are built.

Search forgives spelling, because names on the back of a card are rarely spelt the way the family spells them now. Filters show what is still thin, no place, no date, no subject, because the backlog matters more than the done pile. Review can be narrowed to one folder, since every guide says one box at a time. Shared fields can be set across many records at once. Every photograph gets a perceptual fingerprint, so review says when one looks like a print already catalogued.

The largest was the back of the photograph. Every guide treats the front and the back as two separate sets of evidence, the picture on one side and the studio stamp, tax stamp and inherited handwriting on the other. The app had no idea a back existed. A back is now its own file with its own reading, paired to its front, and review shows both sides of one record.

Cost is a product decision, not a footnote

A historical archive gets large quickly, so processing is deliberate rather than automatic. Indexing is separate from OCR, there are monthly caps, originals are processed temporarily, and only derived assets are stored.

AI enrichment follows the same shape. The key is the workspace's own, so the spend lands on the person using it and never on me. The text pass runs first, because that is where the fields come from and it is the cheapest call in the system. The picture is only sent when a description is wanted, and never at full size.

Queue state, storage use and both allowances stay visible. Partly that is about cost. Mostly it is because the person using it should decide when processing happens and be able to see what it consumes.

What still has to be proved

The project is in active discovery, and the uncertainty in archive work cannot be resolved by a machine reading. Dates are partial. A name might belong to the subject, the photographer or the studio. Handwriting is unclear. Research improves over time and the record has to be able to change with it.

The largest open question is the AI pass. It is built end to end and has not been measured on real cards, so its value is a claim until the golden set has been run against it. Some smaller edges are known and left open on purpose. A word read off the back does not yet find the photograph in search. The providers' half price batch pricing is built but not wired, because halving the cost of a few hundred photographs is worth less than the moving parts it needs, and that changes only if the archive is opened to more people.

The next step is watching real review sessions and shaping the review workspace, grouping, search and export around what actually happens, instead of what I imagine happens.

The value of this product is the review workflow, not the volume of text it can read. Source files stay in Drive, processing stays deliberate, and catalogue knowledge only becomes authoritative after a person has agreed with it.

Decision log

Newest first

When the code costs almost nothing, the mistakes move. Nobody in this log failed to build something. What went wrong, every time, was building the wrong thing, or the right thing at the wrong time. A Windows app the user could never have run. A golden-set scorer nobody opened. Batch pricing built a month before there was volume to discount. Research done in August that should have been done in May. This build is an extreme case, since the machine wrote all of it, but every team with AI in the loop is moving the same way. The scarce skill is no longer making it work. It is deciding what to make, and when.

One entry per release that carried a call worth defending. The tag says which kind it turned out to be, and the "got wrong" lines are the part I would keep if I had to cut everything else.

  1. Wrong time

    The database boundary stopped being optional

    Since August the database itself decides which archive a request may touch. That was rolled out behind a switch, off by default, on purpose. Staging first, watch for a wrong policy, then production. The staged part was right. Leaving the switch optional afterwards was not. An audit of the code found that nothing stopped a hosted deploy from quietly running with the boundary off, and nothing recorded which environments had it on.

    So it is no longer a choice anywhere people can reach. A hosted copy of the app on Postgres refuses to start unless the database enforces the boundary. Both environments already had it on, which took a manual look to find out. The same audit fixed a worker that had been failing every fingerprint job for want of one parameter, and the browser tests now run the real worker against the real API so a miss like that cannot pass twice.

    Got wrong

    The rollout switch was left as a permanent option. A default that says off proves nothing about what is deployed, and I had no way of knowing without going to look.

  2. Wrong thing

    The AI had four screens and no front door

    Turning it on, paying for it, judging it and tuning it were spread over four screens, with two buttons that started a run and two tables scoring the same thing. Nothing anywhere said where the results appear. Two different people were sharing those screens. Someone enriching their own archive needs four steps in a line. Whoever decides what the archive sounds like needs a workbench, and that is a different job.

    So they were split. Enriching became a numbered step between processing and review, ending with a sentence saying the suggestions wait in review. The rules the model is given stopped being source code and became something a person can edit, try against prepared cases, and publish when it is better. Only a named account sees that, and the rest of the archive never learns it is there.

    Got wrong

    The second run button had never worked. It sent a request the server refused, and because the first button worked nobody noticed, including the test written to cover it. Removing the duplicate was the fix. The same week showed the repository wide check had quietly stopped running on the machine it was meant to protect, which hid a real fault underneath it.

  3. Right thing

    The demo learned the back of the card

    The rig now pairs each back to its front the way review does, the case study says what the AI pass does, and the flow diagrams show the passes after OCR rather than in place of it. This log started the next day.

  4. Right thing

    Five rounds on one sentence

    The duplicate scan said every photograph had already been fingerprinted while thousands had none, three times, each time for a different reason. Each fix had added a skip without adding the number that made it visible. The answer was six counts that partition every un-fingerprinted photograph, so the all-clear can only appear when it is true.

    Got wrong

    Twice more on the way. A worker timing out looked like an unreadable photograph and would have retired it for good, and "retry the failed ones" was retrying everything.

  5. Right thing

    Six features, one wave each

    Search that forgives spelling. Thin-field filters, because the backlog matters more than the done pile. One folder at a time in review. Bulk edit through the same per-field chokepoint as hand editing. A perceptual fingerprint for every photograph. And the back of the photograph as its own file, paired to the front, with review showing both sides of one record.

    Got wrong

    Widening the search projection changed what the archive displayed, and on Postgres seven fields silently stopped being searchable while the SQLite test database kept finding them. Fixed by splitting display from search into two columns and covering each field on Postgres.

  6. Wrong time

    Found out who the user actually is

    Three months in, the feedback from one person was thin. Not because the product was fine, but because she is one person with a life. I could not tell her habits from the hobby's. So I read the guides, the forums and the complaint threads of the tools people leave, and treated that as the second user. Six features fell out, and the plan started with refactors, because the review-queue rule existed in five copies and the filter block in three.

    Got wrong

    I waited for feedback to arrive instead of going to get it. The research should have been done in May.

  7. Right thing

    Postgres enforces the boundary, and one visual system

    Workspace isolation moved into the database itself, with proofs in CI and the real sign-in paths run against the real policies. Every commit is now scanned for leaked secrets. The legacy stylesheet was deleted, and a third audit closed the pieces the redesign left behind.

  8. Right thing

    The product photographs itself

    A rig seeds a throwaway archive from public-domain cartes de visite, walks eight screens and saves them. A silent video is built from those stills. A screen recording drifts and goes stale. This re-renders identically whenever the interface changes, and it lets people see the product without needing the first user's time.

  9. Right thing

    Nine things raised from real photographs

    The first user's cards were sideways, held scanner marks read as letters, and one album page carried four people's captions in one record. So, turn a photograph, mark a read line as not on it, re-read it for real, fix a sideways preview without paying for a read, and split a page into records of its own.

    Got wrong

    The re-read matched the cache on the Drive checksum, which a turn never changes, so it would have returned the same sideways text and spent nothing. And the worker never asked for those two job types, so they were created and never claimed. Both found by trying it.

  10. Right thing

    Redesigned from a written brief

    A brief for AI design tools set the hard rules first. Originals never leave Drive, machines suggest and a person approves, and no layout shift for transient controls. The interface was rebuilt to the result in one commit. Notes became rows the same week, each remembering which reading it came from.

  11. Right thing

    A user is a user

    The admin role was removed rather than secured. Each person owns a private workspace, so there was nothing for an admin to be admin of. The same day, a cap on how many photographs one press can queue, and security headers from the web app.

  12. Right thing

    A fourth provider, scoped narrower on purpose

    OpenRouter for free-tier trials. Text only, with no code path that can send an image, no batch, and a live model catalog because the free tier rotates too fast to seed. Its routing cannot promise zero data retention, so the credential card says so in plain words instead of forcing every request through the few hosts that can.

  13. Wrong thing

    Deleted what nobody could reach

    An audit removed the golden-set scorer, the single-image extract endpoint, two superseded endpoints and an unused shell. Each removal is recorded with why, and the batch code that stays is recorded as kept on purpose, so the next audit does not flag it as forgotten.

    Got wrong

    The golden-set scorer was a whole feature built to answer a question the accuracy report already answered. Nobody opened it. The set itself came back with the August redesign, as a list of photographs someone vouched for, and its screen says plainly that the comparison run does not exist rather than showing a dead button.

  14. Right thing

    Every failure gets a reference

    Deleting a record now clears every child row and every thumbnail. Every API error shows an id, browser crashes report themselves, and Settings lists what was recorded. Built because "it did not work" from a non-developer is a dead end without something to quote.

  15. Wrong time

    AI on the user's own key, built in a day, parked in two

    An AI pass turns the reading into fields. No platform key exists, so spend can only ever land on the workspace. Text first, because that is where the fields come from. The description pass sends a 384 pixel copy and is off until switched on. An estimate before a run, a ceiling that blocks, and an accuracy report afterwards.

    Batch APIs for all three providers were built the same day and deliberately not wired two days later, with the trigger for revisiting written down. Half price is real money at volume, and this archive is not volume.

    None of it has been run over the real archive yet. The accuracy report and the golden set exist to measure it, and that measurement is still ahead.

    Got wrong

    Building the batch path before there was anything to discount. Two days of work that the plan for one user never needed. And a month later the pass is still unproven on real cards, which is the only proof that counts.

  16. Right thing

    Made it safe before it could be shared

    A security review before a second person touched it. Private workspaces with assets repaired to their real owners, Drive sign-in bound to the browser and single use, deny-all row security on operational tables, and the local worker prototype deleted rather than kept as a second boundary. Production stayed untouched until staging proved it.

  17. Wrong time

    Put it down and built something else

    The MVP was in the first user's hands. It was her idea, and she is hard to get time from, so waiting for feedback would have meant weeks of nothing. I went and built Resume Builder instead and let Archive Lens sit until there was something to react to. Eight weeks without a commit, on purpose.

  18. Right thing

    A dense workbench, not a brochure

    A design contract from Stitch, three panes, full-text search, and browser history that follows filters and the selected record. The rule written down then still governs the interface. Operational, restrained, and it always says what happened and what to do next.

  19. Right thing

    Processing became a cost decision

    Cloud Run workers download an original into temporary storage, derive a preview and an OCR image, call Cloud Vision, and delete the download. The last Tesseract path was removed on the 10th.

    A monthly cap of a thousand reads, configurable, and the option to bill OCR to the user's own Google key. Indexing a folder was separated from reading it, so nothing spends quota just because it exists.

  20. Wrong thing

    Started local, left the same day

    Three OCR engines compared on real cards, a local worker, a local review screen. The readings were inconsistent and the setup needed a developer. A hosted deploy was configured before the day was out.

    The call that outlived everything else was made here too. Originals stay in Google Drive, and the app only ever holds references and derived copies.