From photography-workflow
Step 5 of 5 in the photo intake pipeline. Takes a photo that has cleared metadata, quality, property, and model review and pushes it live — writes the catalog entry, creates the sales-platform product and child prices (one per supported print size), regenerates derived lists (for-sale, gallery, sitemap), moves the file from intake to the published location, and archives the original. Mandatory dry-run before any live sales-platform mutation. Mandatory 1024px-long-edge downscale for any pre-flight visual verification. USE WHEN: publish photo, list this print, push to sales, add to catalog, go live, finalize listing, complete the intake pipeline. NOT FOR: metadata tagging (use photo-metadata-helper), quality gating (use quality-review), rights audits (use property-release-review / model-release-review), or unpublishing a live entry (that's the remediation pipeline in property-release-review).
How this skill is triggered — by the user, by Claude, or both
Slash command
/photography-workflow:reviewed-photo-publishThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Step 5 of 5 in the photo intake pipeline. Turns a cleared photo into a live, sellable catalog entry — atomically across catalog source-of-truth, sales platform, derived lists, and file system. Refuses to publish anything that hasn't been stamped by every upstream step.
Step 5 of 5 in the photo intake pipeline. Turns a cleared photo into a live, sellable catalog entry — atomically across catalog source-of-truth, sales platform, derived lists, and file system. Refuses to publish anything that hasn't been stamped by every upstream step.
Publish is the only step in the pipeline that mutates external state (sales platform, live site, archive). It is deliberately segmented so that an upstream FAIL never silently triggers a publish, and a failed publish never silently corrupts an upstream verdict.
Every publish run requires explicit, per-photo user sign-off before any live sales-platform mutation, file move, or queue removal. No exceptions. No shortcuts. No batch-implicit approvals.
The skill ALWAYS runs in this order:
--yes flag, or a "yes do all of them" message earlier in the session is NOT sign-off for the next photo.Acceptable affirmations: "yes publish", "confirmed", "go", "ship it" — paired with enough specificity that there is no doubt which photo is being approved. If the user's reply is ambiguous, ask again. Never infer approval from silence, from "ok", or from a thumbs-up to a different photo.
This rule exists because Stripe mutations are visible to customers within seconds and file moves are not transactionally tied to the catalog write — a partial publish without explicit sign-off is the worst-case outcome of this pipeline.
Before reading any image for pre-flight verification, downscale to 1024px on the long edge. Process only the downscaled copy. If resize fails, HALT and prompt the user.
magick "$FILE" -resize "1024x1024>" "/tmp/pub_$(basename "$FILE")"
Publish does not classify images — it acts on verdicts already in place — but any pre-flight visual check (confirming the file opens, confirming dimensions match the chosen print sizes, confirming the right photo is being published) still goes through the gate. The gate is load-bearing across the whole pipeline; see [[downscale-1024-gate-load-bearing]] for why.
Clean up /tmp/pub_* after publish completes.
For each candidate photo: has every upstream step cleared it, and what does "going live" mean for this photo specifically (which sizes, sale or portfolio-only, which derived lists)?
A photo is publishable only if every upstream verdict on file is one of:
| Step | Acceptable verdict |
|---|---|
| photo-metadata-helper | IPTC ObjectName, Caption-Abstract, Keywords present; PersonInImage present for any identifiable subject |
| quality-review | PASS, or CONDITIONAL PASS with a noted size restriction |
| property-release-review | OK, EXEMPT, or Bucket 2 (portfolio-only — publish to gallery but NOT to sale) |
| model-release-review | OK, EXEMPT, or HARD-FLAG with release confirmed on file |
Anything else is a refuse-to-publish. Bucket 1 (remove entirely) and FAIL never reach this step in a clean run, but the skill double-checks anyway because intake state can drift.
Every publish run requires:
If any of these is missing or ambiguous, the skill asks before doing anything.
Pre-flight verification
PersonInImage is embedded and the model release is on fileResolve print sizes
sizes[] — no sale prices createdDry-run preview + explicit per-photo sign-off
Set price tier (before catalog merge)
Every new photo needs a priceTier before the ladder script runs. Tiers auto-assign for
unpriced entries based on quality signals (printspace-manual → platinum; panoramas/portraits → gold;
full D850/Z9 native ≥8000px → gold; everything else → silver). To override, set priceTier
explicitly in the catalog entry or pass it as metadata before step 5.
Tier multipliers over the 4.8× base:
| Tier | Multiplier | 20×30 | 40×60 | 60×90 |
|---|---|---|---|---|
| silver | 1.0× | €135 | €230 | €365 |
| gold | 1.25× | €170 | €290 | €455 |
| platinum | 1.50× | €205 | €345 | €550 |
Write catalog entry + assign size ladder
Copy photo to /root/uploaded/ then run the pipeline scripts in order:
# 1. Additive merge — NEVER run 01-extract.ts (destroys Stripe IDs)
bun scripts/catalog/01b-merge-new.ts
# 2. Classify into series (up-close / travel / documentary / portraits / panoramas / boudoir)
bun scripts/catalog/02-classify.ts
# 3. Copy to public/images/<series>/
bun scripts/catalog/03-copy-images.ts
# 4. Assign size ladders + apply price tier multipliers
# Auto-tier fires here for new entries without an explicit priceTier
bun scripts/catalog/03b-assign-ladders.ts
For portfolio-only photos: set sizes: [] in catalog.json before running 03b, or add the
entry id to the PORTFOLIO_ONLY set in 03b-assign-ladders.ts.
Create Stripe products and prices (skip for portfolio-only)
# Dry-run first — confirms product/price counts, logs WOULD CREATE lines
bun scripts/catalog/04-stripe-sync.ts --dry-run
# Live run after confirmation
bun scripts/catalog/04-stripe-sync.ts --live
The sync is idempotent (lookup_key–based). Prices are immutable in Stripe — repricing an existing
entry requires creating a new price (with transfer_lookup_key) and archiving the old one; the
sync script does not do this automatically.
Derived lists — no regeneration step needed. lib/products.ts and lib/gallery.ts read
data/catalog.json at request time. The new entry is live as soon as catalog.json is saved.
Verify
data/catalog.jsonpublic/images/<series>/<id>.jpgdata/uploads.jsonRebuild and push GMC feed
After every publish run (regardless of batch size), rebuild the Google Merchant Center feed and push live:
# Dry-run first to confirm product count looks right
bun scripts/gmc-sync.ts --dry-run
# Then push live
bun scripts/gmc-sync.ts
The GMC feed is push-only (gmc-sync.ts is the sole write path — pull feeds are disabled). A publish run that skips the GMC push leaves the feed stale for up to 24 hours. Push immediately after every batch.
Surface result
Order is not negotiable:
A failure at any step HALTs the run and surfaces the partial state. The skill never auto-rolls-back — partial publishes are presented to the user with a recovery plan.
When the user pushes to skip the dry-run, batch-approve, or proceed on a session-level "yes":
This is the one place in the pipeline where calibrated debate does not apply — the cost of a wrong publish is customer-visible within seconds and is not cleanly reversible.
sizes[] and no salesPlatformProductId.npx claudepluginhub derekslinz/photography-workflow --plugin photography-workflowProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.