Yuri Korolev
New York
iInfo
Yuri Korolev
New York
06

OpenPics

Product Design2026
OpenPics — Product Designgithub.com/Snoe0/open-pics

Context

Pics.io is a digital asset manager: a library for a team's photographs and video, with folders and tagging and search and permissions over the top, and it runs on storage you already own rather than storage it sells you. OpenPics is an attempt at recreating it, with S3 holding the files and a Supabase Postgres holding everything anybody knows about them, which is where the multi-user half of it is meant to live.

The part that is not a recreation is what you pay. Pics.io is a subscription, charged monthly for software that sits on a bucket you are already paying Amazon for separately. OpenPics is that software, run on your own machine against your own bucket and your own free Supabase project, and the only per-use cost is fractions of a cent each time it looks at a picture. Every credential lives in one file on your computer. The first account that signs up becomes the admin, and everybody after that is a viewer until they are invited as something else.

It is called Vault inside the interface and open-pics on GitHub, which is roughly how far along it is. It builds, it has nineteen routes, and the Supabase project it was pointed at no longer resolves, so running it today means standing a new one up first. All of it was written on one day in August.

Details

Role
Designer and Engineer
Team
Solo
Timeline
Aug 2026
Built with
TypeScript, Next.js, Postgres, AWS S3, OpenAI
Deliverables
Web app, upload pipeline, AI tagging, semantic search
Status
Public on GitHub, unfinished

Tech stack

Storage
AWS S3·Presigned URLs·sharp·dHash
Database
Supabase·Postgres·pgvector·Row level security
Tagging
Vision model·Text embeddings
Interface
Next.js·React·TypeScript·Tailwind
Repair
GitHub Issues·Actions·Claude Code

Final artifacts

OpenPics — The library, and the folder tree down the left
The library, and the folder tree down the left
OpenPics — One asset, and everything the model said about it
One asset, and everything the model said about it
OpenPics — A folder of files, hashed before any of them leave the browser
A folder of files, hashed before any of them leave the browser

Key design moments

01

The bucket was never the argument

Pics.io already runs on storage you own. You point it at a Google Drive or an S3 bucket and the files stay in your account, which is the thing I had assumed I was building an alternative to and is not. What you are paying for is the software sitting on top. So what OpenPics actually replaces is the subscription rather than the storage, and the whole of that argument is in the README's setup section: make a bucket, paste in an IAM policy scoped to it, add a CORS rule so a browser can PUT straight into it, run one SQL file in the Supabase editor, fill in four keys. After that the running cost is whatever S3 was already charging you, a free Supabase project, and fractions of a cent an image. The bucket mine points at is one that already existed from client work, which is exactly the case this was built for and also the reason there is nothing sitting in it that I can photograph.

OpenPics — The bucket was never the argument
The bucket was never the argument
02

The search never looks at the picture

The search box says 'red car at night' in its placeholder, and it does find the red car, and the way it does that is less clever than it sounds. On upload the image goes to a vision model under a strict JSON prompt: eight to fifteen lowercase tags, two to five dominant colours chosen only from a fixed list of twelve, and one sentence covering subject, setting, mood and lighting. Then that sentence is what gets embedded. Not the image. The text the model wrote about the image, with the tags and the colours and the filename glued on the end, into 1536 dimensions in a pgvector column with a cosine index over it, and a query is embedded the same way and matched against those. Which means the search works exactly as well as the sentence does. If the model wrote 'a vintage sedan on a wet street at dusk', then 'red car at night' will not find it, and nothing in the system is capable of noticing. It also reads the 512 pixel thumbnail rather than the original, because that is cheaper, and I decided cheaper mattered before I had any way to measure what it cost me.

OpenPics — The search never looks at the picture
The search never looks at the picture
03

Postgres was the shared thing before the screens were

Postgres is what everybody is looking at, and for a long time nothing on top of it told a screen that anything had changed. Everything the interface knew it learned from a fetch it made itself, and the way it found out about a change was a counter in a React context that other code increments when it has a reason to. Tagging survives that by an accident of the schema rather than by design: tags are unique on name and kind, the join table is keyed on the pair of ids, and the writes are upserts that ignore duplicates, so two people adding 'sunset' to the same photo at the same moment produce one row and no argument. Everything else is last write wins with no version check, and that half does not go away when the screens start updating themselves. If you rename a file while I am renaming it, one of us wins and neither of us is told, and a subscription only means we both watch it happen sooner. The design spec I wrote for the ten second undo on delete says the quiet part in its own trade-offs section: the undo lives in the tab that pressed delete, and every other tab goes on showing the asset until the real delete lands. Then both S3 objects and the row are gone. There is no trash. The subscriptions are what I am adding now, and they close the smaller half of this. The larger half is deciding who wins.

OpenPics — Postgres was the shared thing before the screens were
Postgres was the shared thing before the screens were

Impact

There are no numbers on this one and I would rather leave the row empty than fill it, because everything I could put there would be about me. It has had one user. It has never had two people signed into it at once, and it has never held an asset that was not already mine. What could be measured, once there is anybody to measure against, is the part I would actually want to know: what share of searches turn up the right file on the first try, and how far the vision model's vocabulary drifts from the words a real team uses for its own pictures. Both of those need a library somebody else filled. What I can say without a measurement is the cost, which is a bucket you are already paying for, a free Supabase project, and fractions of a cent an image, against a subscription billed every month for the layer on top.

Reflections

The whole of this was written on 2 August 2026, between about half past one in the afternoon and half past seven in the evening. Close to six thousand lines across sixty-five files, four tracks running as parallel agents against a plan I wrote first, and the pieces arrived working. What that speed does not buy is any of the things that make an asset manager good, all of which are questions about the second person: whether a viewer can find what an editor filed, whether a tag vocabulary stays coherent once more than one person is generating it, whether anybody trusts a delete that gives them ten seconds. Being able to build the shape of a product in an afternoon has quietly stopped being the interesting part, and I keep finding that out on projects like this one.

The oddest thing in the repository is a loop I built and have never watched run. Uncaught errors on either side get fingerprinted by their message and top stack frame, deduplicated against the open issues, and filed on GitHub, which triggers a workflow that puts Claude Code on the codebase to find the root cause and open a pull request, never merged automatically. The issue count on that repository today is zero and so is the pull request count. Nothing has gone wrong because nothing has been used. Meanwhile the Supabase project it pointed at has been deleted, so my own copy does not run either without standing a new one up, which is the same setup the README asks of anybody else. The five most recent commits are still sitting unpushed on my laptop and the last feature I started is half finished in the working tree.