Pak Jabit: full stack — storefront, Spree customisations, catalogue tooling

A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.

What is here:
  storefront/  the Next.js app
  backend/     ONLY the custom Spree code. Spree itself is a prebuilt image
               that mounts no app code, so every file here needs an explicit
               volume entry in docker-compose.yml or it is silently ignored.
  catalogue/   the scripts that loaded and maintain the products
  docs/        manifest, mock registry, napkins

Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.

The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-15 22:09:55 +08:00
co-authored by Claude Opus 5
commit a58a61b98c
98 changed files with 15524 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
# 🎨 Image Pipeline — Napkin Notes
## What the spec wants
Seller uploads phone photo → system auto-standardizes → consistent product catalogue
- Background removal
- Colour correction
- Lighting normalization
- Resize/crop to exact same dimensions
- Store processed image against product variant
## What we found (FAFO'd 2026-09-13)
### 🥇 Photoroom (recommended for production)
- **URL:** https://app.photoroom.com/create (signed up as `celestjux`)
- **Docs:** https://docs.photoroom.com/
- **Pricing:** Basic $20/mo (BG removal only) · Plus $100/mo (full pipeline) · Enterprise custom
- **Per image:** $0.02 (Basic) / $0.10 (Plus)
- **Apparel-specific features:**
- Ghost Mannequin — makes clothes look worn without a real model
- Virtual Model — AI fashion models wearing the clothes
- Virtual Try-On — customers see clothes on themselves
- AI Ironing — smooth wrinkles on clothes
- AI Relight — fix lighting
- AI Shadows — realistic shadows
- Product Staging, AI Backgrounds, AI Beautifier, AI Expand
- **Batch:** yes, directory/Google Drive/Sheets
- **Sandbox:** 1,000 free watermarked calls + 10 real for testing
- **Verdict:** Does EVERYTHING the spec asks in one API call. $100/mo for 1K images.
Ghost mannequin + AI ironing = killer for men's apparel.
### 🥈 Bria AI (good BG removal, lighter features)
- **API key:** saved at `~/.bria/credentials`
- **Skills installed:** `/root/.claude/skills/bria-skills/`
- **Tested:** ✅ BG removal works (RMBG 2.0), 39% transparent pixels on test image
- **Features:** BG removal, relighting, upscale, product photography, object editing
- **No ghost mannequin, no AI ironing, no virtual try-on**
- **Verdict:** Good for BG removal + basic edits. Fallback to Photoroom for apparel-specific stuff.
### 🥉 rembg (free, self-hosted, offline)
- **Installed:** ✅ via pip, u2net model (170MB) cached at `/root/.rembg/models/`
- **Tested:** ✅ works, decent edges, white-on-light handled ok
- **Limit:** BG removal ONLY. No colour correction, no lighting, no crop.
- **Speed:** ~20s on CPU per image
- **Verdict:** Free forever. Good offline fallback. Need OpenCV/Pillow on top for the rest.
### Quick-RMBG / Claude Background Removal Plugin
- **Repo:** cloned at `/mnt/playground/Playground_V2/Claude-Background-Removal-Plugin/`
- **What it is:** Claude Code skill wrapper around rembg (single/two-pass/batch/Dolphin right-click)
- **pipx install blocked** by ctx git shim — not installed yet
- **Verdict:** Nice wrapper but not needed if we use Photoroom/Bria for the platform.
## Architecture decision
```
Production: Photoroom Plus API ($100/mo)
└─ one call = bg removal + lighting + shadows + crop + resize
└─ ghost mannequin for clothes-on-invisible-model shots
└─ AI ironing for wrinkle-free product photos
Fallback: Bria RMBG 2.0 (free tier / per-image)
└─ BG removal when Photoroom is down or over quota
Offline: rembg + Pillow/OpenCV
└─ dev/testing, batch processing without API costs
```
## ✅ BUILT 2026-09-15 evening — the pipe is real, end to end
```
phone/disk photo -> Image Studio (admin page) -> Photoroom /v2/edit
-> unattached ActiveStorage blob -> review before/after
-> Apply -> Spree product (existing, or created here)
```
- **Admin page:** `/admin/image_studio` (sidebar, between Products and Customers).
Multi-upload from the device, background + size presets, per-file before/after,
then attach to an existing product **or create a new listing** (name, price,
publish toggle). Nothing touches the catalogue until Apply — the result is held
as an unattached blob, so Discard is just navigating away.
- **CLI:** `scraped-catalogue/photoroom_to_spree.py --slug <slug> [--live]`
- **Proven live:** a 6.52 MB phone PNG → 543 KB at 1000×1000; and two uploads →
a new published product visible on the storefront at R259.11.
### The architecture question, settled
Abang's call, and he was right: **the page is needed.** I had argued for a silent
auto-on-upload hook (the spec says *"seller uploads → system standardises"*), but
a seller wants to choose the shot and see the result before it ships. Fully
automatic means a bad cutout goes live silently and the customer finds it first.
**Manual control at the front, programmatic pipe behind it.**
Photoroom's *Shopify Products* connector is a red herring for us — it exists for
merchants with no backend. We have one, so the plain API is the simpler path.
### Money, accurately
| | Reality |
|---|---|
| API free tier | **10 images/month**, not 7 days. 9 left as of 2026-09-15 |
| Sandbox | 1000/month, free, **every output watermarked** |
| 7-day trial | That's the **web app**, a separate meter. Its Batch tool does **250 images at once** through the browser and costs no API credits |
| Plus | $100/mo for the full pipeline |
🔴 **Photoroom adds little to the existing 160 products.** They are scraped
studio shots already on white — background removal removes a background that is
already gone. Across 160 first-images there are only **3 aspect ratios and 4
sizes**; framing is the sole real defect, and Pillow squares them for free,
offline. Photoroom earns its money on *phone photos of new stock*, which is
exactly what Image Studio is for.
### Still open
- [ ] **Batch** — capped at `MAX_PHOTOS = 8`, synchronous, ~3-5s per image. More
needs a background job
- [ ] Currently on the **sandbox** key, so everything is watermarked. Flip
`PHOTOROOM_API_KEY` in the backend `.env` for clean output
- [x] ~~Pillow square-and-pad backfill for the existing 160~~ — **scrapped 2026-09-15,
measured and unnecessary.** See below.
- [ ] Never tested on a real phone photo from Lisa — the actual input this exists
for. Ghost mannequin and AI ironing have nothing to work with on a scraped
studio shot
- [ ] Virtual Try-On — could be a differentiator for the platform
- [ ] Self-hosted option — for v2, when volume justifies building our own pipeline
## 📐 Framing survey — 2026-09-15, the backfill that wasn't needed
I had the Pillow square-and-pad backfill queued as the next job. Measuring the
160 source images first killed it. The real distribution of **first images**:
| Size | Ratio | Count |
|---|---|---|
| 762×1100 | 0.693 | 122 |
| 800×1200 / 801×1200 | 0.667 | 32 |
| 1200×1800 | 0.667 | 4 |
| 1524×2200 | 0.693 | 1 |
| **2400×2400** | **1.000** | **1** |
The grid renders at `aspect-[3/4]` (0.75) with `object-cover`. At 0.693 the crop
takes 7.6% of the height; at 0.667 it takes 11%. The **entire** inconsistency
across 159 products is a 3.4% difference in vertical crop — invisible. Padding
them to square would also be self-defeating: a square displayed in a 3/4 frame
gets re-cropped, so the padding I just added is what gets cut.
🔴 **One image was genuinely broken** — `129-adidas-lifestyle-barreda-shoes-men-white-jp5928`,
the only 1:1 in the catalogue. A square in a 3/4 frame loses **12.5% off each
side**, which cut the heel and the toe off the shoe. Its 22 sibling shoe
listings are all 762×1100 with the subject at 90-95% width and 24-33% height, so
it was reframed to match: crop to the subject bbox, scale to 93% width, centre on
the source's own background (234,238,239). 186KB → 65KB. Applied live via
`reframe_129.py --apply` (upload at position 1, verify by re-read, then delete
the original).
**The lesson, not the fix:** the napkin said "3 aspect ratios and 4 sizes,
framing is the sole real defect" and I turned that into a 160-file job. The
numbers were right and the conclusion was wrong — nobody had checked how far
apart those ratios actually were. Measure the spread, not the count.
### Spree API shape — two gotchas that cost a run
- **Flat objects, not JSON:API.** `/api/v3/admin/products` returns fields on the
object itself (`p["slug"]`), not under `p["attributes"]["slug"]`. The helper in
`photoroom_to_spree.py` only survives because its fallback scan happens to use
the flat form.
- **`per_page` is silently ignored** — it pages on `limit`. Sending `per_page=100`
gets you 25 rows and no error, so a scan that stops at page 1 "finds" nothing.
Meta key is `pages`, not `total_pages`.
**Keys:** `/root/.photoroom/credentials` (chmod 600) on the house box;
`PHOTOROOM_API_KEY` in the backend `.env`.