# Catalogue


**Site index:** [https://photoslop.2plot.dev/llms.txt](https://photoslop.2plot.dev/llms.txt) — every page on this site, as Markdown.  
**Network index:** [https://2plot.dev/llms.txt](https://2plot.dev/llms.txt) — The 2plot network; start here to discover sibling sites.  
**Sibling sites:** 14 more in The 2plot network — listed in the site index above.  
**Sitemap:** https://photoslop.2plot.dev/sitemap.xml  

The catalogue is where Photo Slop keeps what a signed-in user has made: the images they dropped into
the editor, the renders they saved from it, and whole compositions ("projects") that can be opened
again. It lives at /catalog and needs an account; signed-out visitors are shown the sign-in card.

## What is kept

- **Uploads** — every image dropped on the canvas or added with "Add images" while signed in. The
  original file is stored (converted to PNG or JPEG for the browser when it arrived as TIFF, HEIC or
  another format a browser cannot show), with a thumbnail for the catalogue grid.
- **Projects** — every save from the editor ("Save to catalogue" in the Catalogue tab, or any export
  with "Keep in my catalogue" ticked) stores the PNG render AND the document JSON (the photo-canvas/1
  document: artboard size, every layer with its position, text, effects and crop), the way an
  .excalidraw file travels beside its PNG. Download gives you the image; Open in editor loads the
  document so you pick up exactly where you left off (/editor?project=<id>).
- **Images** — a render without a document, which only the API produces (export.render with
  dest: catalog and no document). It can be placed on the current canvas with /editor?add=<id>, as can
  an upload.

Anonymous visitors can still use the editor; nothing they make is kept between visits, and their
uploads go to a shared, uncatalogued prefix.

## Where it lives, and who can read it

Each user's files are stored under one key prefix in the asset backend, users/<owner-id>/…, together
with a small index file that lists the items. The backend is whatever PHOTO_STORAGE_BACKEND selects —
a local directory or a Cloudflare R2 bucket in production — so the catalogue needs no database.

Access is decided per file. A request for a key under users/<owner>/ is served only when the request's
signed-in user is that owner; anyone else receives 404, so the existence of a file is not disclosed
even to someone who guesses its URL. The identity comes from Clerk (dash-clerk-auth) and is read on the
server for every request; the browser is never asked who it is. When the R2 backend is configured
without a public domain (R2_PUBLIC_BASE unset) the app answers a file request with a short-lived signed
URL after the ownership check; with a public domain set, bucket URLs are reachable by anyone who has
them, so leave it unset for private catalogues.

## The API behind the page

The page is a thin view over a small JSON API, also used by the editor's Catalogue tab:

- GET /photo/api/catalog — the signed-in user's items, profile and a storage summary.
- GET /photo/api/catalog/<id> — one item; GET /photo/api/catalog/<id>/document — a project's document.
- PATCH /photo/api/catalog/<id> with {"name": "…"} renames; DELETE removes the item and its files.
- Saving goes through the export route: POST /photo/api/export with dest=catalog (plus a name, and a
  document field for a project). From the editor that is the export.render command with
  {"dest": "catalog", "name": "…", "project": true}.

Every endpoint answers 401 to a request with no signed-in user.

## Limits

A catalogue holds up to 2000 items. The index is one file per user and is written last-writer-wins, so
saving from two tabs in the same instant can lose one entry. Deleting an item deletes its files.
