Docs.

Gear images — sourcing tracker

operations/gear-images.md

Living log of how showcase + review photos get sourced, named, hosted, and wired into the rental catalog. Update as products get covered.

Conventions

  • Hosting: Supabase media bucket, gear/ prefix. Public URL: https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/gear/<file>
  • Showcase (catalog cover + future detail-page gallery): {slug}-{order}.{ext} — order 1 is the cover.
  • Showcase alternates (downloaded but not chosen as the primary): {slug}-{order}-alt{version}.{ext}.
  • Review (detail-page feature article): {slug}-review-{order}.{ext}.
  • Showcase look bar: high-res, high-key lighting, clean/white background, consistent angles within a product type, no watermarks.
  • Edge spacing: every showcase shot is run through scripts/normalize-gear-images.ts, which trims the background and re-pads the product onto a square canvas so its longest side fills ~82% of the frame (consistent margin on all sides; the original tight FX30 vs. roomy FX6 was the motivating contrast). Review images (*-review-*) are skipped — they're editorial 16:9, not cut-outs.
  • DB wiring: items.cover_image = order-1 showcase URL · items.gallery = all showcase URLs · items.review_images = all review URLs. SKUs with multiple unit-rows get the same images on every row.

Pipeline — step-by-step runbook

This is the full, repeatable process for sourcing covers from B&H Photo. Read it cold before starting a batch. Tools used: the Supabase MCP (execute_sql), the Claude-in-Chrome MCP (browser, "studio.chat" profile only — never "Bioscope Foundry"), curl, ImageMagick (magick), and the two scripts in scripts/.

Why a browser at all (the B&H host map)

  • www.bhphotovideo.com/* (HTML pages and /images/...) is bot-walled: curl/WebFetch get a 403 HTML page. The Chrome MCP browser loads them fine.
  • static.bhphoto.com/* (the image CDN) is open to curl at full res.

So: discover IDs in the browser (cheap, text-only), download bytes with curl from static.bhphoto.com. Never pull image bytes back through the model as base64 — it's huge; always curl server-side.

B&H file-id anatomy

Every product image filename is {slug}_{ts}_{productId} where:

  • productId + slug come from the product URL /c/product/{pid}-REG/{slug}.html
  • ts is the unix-ish timestamp embedded in the listing thumbnail …/{ts}_{pid}.jpg

Two CDN paths:

  • main hero → https://static.bhphoto.com/images/images{SIZE}/{slug}_{ts}_{pid}.jpg
  • gallery angle → https://static.bhphoto.com/images/multiple_images/images{SIZE}/{ts2}_IMG_{n}.jpg

{SIZE} is e.g. 2500x2500. Per-product max size varies (some only exist at 1500x1500, 1000x1000, or 500x500) — always size-fall-back.

⚠️ Use the slug form ({slug}_{ts}_{pid}). The bare {ts}_{pid} form 404s when "cold" (the resizer only pre-generates the slug path). If even the slug form 404s at every size, the search-link slug ≠ the image-file slug — fall back to Method B (read the canonical base off the product page DOM).

Step 0 — Worklist from the DB (Supabase MCP)

-- counts by category, biggest gaps first
SELECT category, count(DISTINCT sku) AS missing
FROM items
WHERE availability IN ('available','sold') AND (cover_image IS NULL OR cover_image='')
GROUP BY category ORDER BY missing DESC;

-- per-category list to source (sku is the slug; mpn pins exact variant)
SELECT DISTINCT ON (sku) sku, name, manufacturer, mpn
FROM items
WHERE availability IN ('available','sold') AND (cover_image IS NULL OR cover_image='')
  AND category = 'camera lens'
ORDER BY sku;

Step 1 — Discover image IDs in the browser

Connect once: list_connected_browsersselect_browser (the "studio.chat" device) → tabs_context_mcp {createIfEmpty:true} to get a tabId.

Method A — covers in bulk (fast, no product-page visit). Search each SKU by MPN (most precise; falls back to name) and read the top result's link + thumbnail. Batch many SKUs in one browser_batch (a navigate + a javascript_tool extract per SKU). Always capture the product name to eyeball-verify the match. Extractor (clean JS — double every backslash when embedding in browser_batch JSON):

(() => {
  const l = [...document.querySelectorAll('a[href*="/product/"]')]
    .find(a => /\/product\/\d+-REG\//.test(a.href));
  const m = l && l.href.match(/\/product\/(\d+)-REG\/([a-z0-9_]+)\.html/i);
  if (!m) return { base: null, name: 'NO RESULT' };
  const pid = m[1], slug = m[2];
  const img = [...document.querySelectorAll('img')].map(i => i.currentSrc || i.src)
    .find(s => new RegExp('(\\d+)_' + pid + '\\.jpg').test(s));
  const ts = img && img.match(new RegExp('(\\d+)_' + pid + '\\.jpg'))[1];
  const n = document.querySelector('[data-selenium="miniProductPageProductName"]');
  return { name: (n?.innerText || '').trim(), base: ts ? `${slug}_${ts}_${pid}` : null };
})()

Method B — gallery angles + canonical main (one product page). Navigate to /c/product/{pid}-REG/x.html (the slug part is decorative; pid drives it) and extract. Gallery thumbnails are often lazy-loaded — if the first read is empty, re-run, or scan the raw HTML for ids:

(() => {
  const all = [...document.querySelectorAll('img')].map(i => i.currentSrc || i.src);
  const main = [...new Set(all.map(s => s.match(/images\d+x\d+\/([a-z0-9_]+)\.jpg/i)?.[1]).filter(Boolean))];
  // gallery ids live in lazy <img> and inline JSON; scan innerHTML as the fallback
  const ids = [...new Set([...document.documentElement.innerHTML.matchAll(/(\d{8,}_IMG_\d+)/g)].map(x => x[1]))];
  return { main, galleryIds: ids };
})()

Step 2 — Download with curl (size fallback)

Save as {slug}-{order}.jpg into a fresh batch dir under product images/sourced/<batch>/. Use while read to pair slug↔base (do not use set -- $row — it word-split-bit us). Fall back through sizes:

DIR="/Users/brandon/Projects/studio.chat/product images/sourced/<batch>"; mkdir -p "$DIR"; cd "$DIR"
while read -r slug base; do
  [ -z "$base" ] && continue
  for sz in 2500x2500 2000x2000 1500x1500 1000x1000 500x500; do
    code=$(curl -s --retry 3 --retry-delay 1 -o "${slug}-1.jpg" -w "%{http_code}" \
      "https://static.bhphoto.com/images/images${sz}/${base}.jpg")
    [ "$code" = "200" ] && { echo "${slug}: OK ${sz}"; break; }
    sleep 0.6
  done
done <<'EOF'
sku-a  slug_ts_pid
sku-b  slug_ts_pid
EOF
# always confirm: a 12094-byte text/html file is B&H's 404 page, not an image
for f in *.jpg; do echo "$f: $(file -b --mime-type "$f")"; done

Gallery angles use the multiple_images/images{sz}/{id}.jpg path instead.

Step 3 — Normalize (consistent edge spacing)

pnpm tsx scripts/normalize-gear-images.ts "<batch dir>"   # → <dir>/normalized/

Trims the background and re-pads each product to ~82% fill on a square canvas. Skips *-review-* (editorial 16:9). Flags: --fill=0.82, --in-place.

Step 4 — Verify with a contact sheet

ImageMagick montage font is broken in this env (RenderFreetype error) — build grids with +append/-append instead:

cd "<dir>/normalized"; i=0
for f in a-1.jpg b-1.jpg c-1.jpg d-1.jpg; do
  magick "$f" -resize 300x300 -background "#eee" -gravity center -extent 320x320 "/tmp/c_$i.png"; i=$((i+1))
done
magick /tmp/c_0.png /tmp/c_1.png /tmp/c_2.png /tmp/c_3.png +append /tmp/row.png   # rows
magick /tmp/row0.png /tmp/row1.png -append /tmp/sheet.jpg                          # stack rows

Read the sheet and reject anything that isn't: the right product, right mount/color variant, a single product (not a kit/bundle), clean white bg, no watermark, and a consistent angle for its type — front view for camera bodies; for Sony G/GM lenses the family hero is the control side with the red "G" badge front and center, no hood (see the 16-25 re-angle below).

Step 5 — Upload to Supabase

pnpm tsx scripts/upload-gear-images.ts "<dir>/normalized"   # prints public URLs

Uses supabase-js (handles the sb_secret_* key; the raw Storage REST endpoint rejects non-JWT keys). upsert: true overwrites, and Supabase serves with cache-control: no-cache, so re-uploads to the same filename go live immediately (verify with curl -I … && wc -c byte match).

Step 6 — Wire the DB (Supabase MCP)

cover_image = the -1 URL, gallery = all -N URLs, applied to every unit-row of the sku:

WITH base AS (SELECT 'https://pstvpdcqmsodirmrvzam.supabase.co/storage/v1/object/public/media/gear/'::text u)
UPDATE items i SET
  cover_image = (SELECT u FROM base) || i.sku || '-1.jpg',
  gallery = (SELECT array_agg((SELECT u FROM base) || i.sku || '-' || n || '.jpg' ORDER BY n)
             FROM generate_series(1, <count>) n),
  updated_at = now()
WHERE i.sku IN ('sku-a','sku-b')
RETURNING sku, cover_image;

Step 7 — Verify in the catalog

// in the preview page context
fetch('/services/rentals/catalog?category=camera+body&per=48')
  .then(r => r.text())
  .then(h => [...new Set([...h.matchAll(/gear\/([a-z0-9-]+)-1\.jpg/g)].map(m => m[1]))]);

Re-angling / replacing an existing cover (no DB change)

When a wired cover is the wrong angle (e.g. the 16-25 showed the nameplate profile instead of the G-badge-front family hero): open the product page (Method B) → curl the gallery angles → contact-sheet → pick the match → save it under the existing {slug}-1.jpg name → normalize → upload. Because the filename is unchanged and the CDN is no-cache, the catalog updates with no SQL needed.

Gotchas learned (don't relearn these)

  • www.* bot-walled / static.* open; browser for HTML, curl for bytes.
  • Slug form, not bare {ts}_{pid} (cold 404); if slug form 404s at all sizes, the link-slug ≠ file-slug → read the canonical base off the product page.
  • Per-product max size varies (500–2500) → always size-fall-back.
  • The 404 body is a 12094-byte text/html page — assert image/jpeg.
  • Bash: pair fields with while read, never set -- $row.
  • Gallery thumbnails lazy-load → re-extract or scan innerHTML.
  • Recovering the ts when Method A returns slug-only or the product page is Cloudflare-walled ("Just a moment…"). Some search thumbnails load through B&H's image proxy (www.bhphotovideo.com/cdn-cgi/image/fit=…/https://static.bhphoto.com/images/images345x345/{ts}_{pid}.jpg), so the plain _{pid}.jpg matcher misses them. Stay on the (cheaper, less throttled) search page and scan innerHTML for the full base instead: [...html.matchAll(/([a-z0-9_]+_\d{8,}_<pid>)/gi)] → yields {slug}_{ts}_{pid} directly. This avoided product-page hits for LiteFlow K1 (1790969) and the Proaim spike foot (1741120) when product pages were throttled.
  • Variant traps: 7artisans Vision lenses return non-E mounts (barrel is identical, so the cover still looks right); amaran has Gray/White bodies; Godox/2-light "kits" surface a bundle photo instead of the bare unit.
  • Stale large render: images2500x2500 can be a different/older shot than 500–2000 for the same base id. The 16-25 base served the nameplate profile at 2500 but the correct 3/4 G-badge hero at 500–2000 — and the fallback grabs 2500 first, so the cover was wrong. Always eyeball the downloaded large size against the product page's displayed main image; if it differs, step down to 2000x2000.
  • Verify the MODEL, not just the image. The SKU labelled "E PZ 18-105mm F4 G OSS" (SELP18105G) was actually the 18-110mm cine servo zoom (SELP18110G) — a clean SELP18105G image still looked fine but was the wrong product. Cross-check the MPN against the sheet; when a SKU/MPN smells off, re-search by make + model and confirm the returned name.

B&H studio sets are orthographic (front/back/top/sides); the front view is the camera-body cover (matches the FX30).

Status legend

✅ wired (uploaded + DB set) · 🔍 sourcing · ⚠️ have it but want a better shot · ❌ none found yet

Wired

SKUProductShowcaseReviewSourceStatus
ilme-fx6Sony FX62 (cover = 3/4 sensor view)3local folder
ilme-fx30Sony FX303 (cover = front sensor view)local folder
sony-e-pz-10-20mm-f4-gSony E PZ 10–20mm F4 G3 (cover = 3/4)local folder
sony-e-15mm-f1-4-gSony E 15mm F1.4 G3 (cover = 3/4)local folder
ilce-7m5Sony α7 V6 (cover = front)B&H (1935439)
ilce-7rm5Sony α7R V6 (cover = front)B&H (1731389)
ilce-7rm6Sony α7R VI6 (cover = front)B&H (1970580)
ilme-fx2Sony FX26 (cover = front)B&H (1899230)

camera body category: 100% covered (6 SKUs: FX6, FX30, α7 V, α7R V, α7R VI, FX2).

camera lens category: 19/20 covered (B&H, front/cover only so far). Sony FE/E ×12 + 2× Sony PZ/15mm already-local + 5× 7artisans Vision + 2× Fujinon MK. Remaining: DZOFILM Vespid Prime 2 (deferred — see flags).

Corrections: (1) the "18-105" SKU was the wrong model — it's actually the E PZ 18–110mm F4 G OSS (SELP18110G) cine servo zoom; updated DB (sku→sony-e-pz-18-110mm-f4-g-oss, name, mpn, cover) and the master sheet (row 40, Model + Reference). The orphaned sony-e-pz-18-105mm-f4-g-oss-1.jpg can be deleted from storage. (2) the 16-25 G cover now uses the 2000px render of the main-hero base (the correct 3/4 G-badge family angle); its 2500px render is a stale nameplate shot — see gotchas.

light category: 14/15 covered (B&H). The original 10 + amaran 300c White/Gray, amaran 150c Gray, and Godox SK400II-V (all found on B&H by MPN in the part-2 pass). Only smallrig-rc-120d remains — blocked on a name/MPN data conflict, not sourcing.

All local-folder showcase images above were re-run through the normalizer for consistent edge spacing (the FX30 had been touching the frame edge).

Second pass: every product had only its cover (image 1). This pass added showcase images 2 (control / G-badge side profile) and 3 (opposite / nameplate side profile) per the Sony E 15mm F1.4 G convention, sourced from each product's B&H gallery (Method B), then wired into items.gallery as ARRAY[cover, -2, -3] on every unit-row of each sku (cover_image unchanged). Batch working dir: product images/sourced/g2-final/ (+ /normalized). The browser-side www.* host throttles after ~10 rapid product-page hits with a "Just a moment…" Cloudflare interstitial / "you have been blocked" page — pace product-page navigations and let it cool ~1–2 min between bursts (curl from static.* is unaffected).

Angle convention applied: image 2 = control side (AF/MF switch, focus-hold button, G/GM badge, aperture markings); image 3 = nameplate / opposite side (the "FE x/yy G"-style barrel text, or SONY-logo side). Orientation follows what B&H provides per lens (horizontal profile for primes like the 11/12-24/ 70-200; vertical mount-down for the 24-50/28-70/85/100/16-25) — kept consistent within each product. Cover stays the 3/4 front-up hero.

SKUimgsimage 2 / image 3notes
sony-e-11mm-f1-83control side / "E 1.8/11" nameplateB&H 1709231
sony-fe-12-24mm-f2-8-gm3control side (hood) / SONY side (hood)1573884
sony-fe-16-25mm-f2-8-g3control side / "FE 2.8/16-25 G" nameplate1823773 (2000px max)
sony-fe-24-50mm-f2-8-g3control side / "FE 2.8/24-50 G" nameplate1812335
sony-fe-28-70mm-f2-gm3control side / "FE 2/28-70 GM" nameplate1862825
sony-fe-50mm-f1-83"FE 1.8/50" side / SONY side1242613 — budget lens, no control side
sony-fe-70-200mm-f2-8-gm-oss-ii3control side / nameplate side1666363 (white tele, horizontal)
sony-fe-85mm-f1-4-gm-ii3control side / "FE 1.4/85 GM II" nameplate1849952
sony-fe-100mm-f2-8-macro-gm-oss33/4 control side / "FE 2.8/100 MACRO GM" nameplate1924425 (vertical)
sony-e-pz-18-110mm-f4-g-oss3control side / SONY side1280873 (cine servo, horizontal)
sony-2x-teleconverter2"2x TELE CONVERTER" side profile1222778 — only 1 extra clean shot (rest = optical diagram)
fujifilm-fujinon-mk18-55mm-t2-93CINE ZOOM nameplate / FUJIFILM CORP side1321239 (Sony-E)
fujifilm-fujinon-mk50-135mm-t2-93CINE ZOOM nameplate / FUJIFILM CORP sideprofiles from X-mount page 1388315 (Sony-E page 1321240 has only camera-mounted shots; barrel identical across mounts)
7artisans-12mm-t2-9-vision3front 3/4 / front element1732953 (C701B, E) — only front-element shots clean (rest camera-mounted). ✅ Name/sku corrected T1.05 → T2.9 (3 images re-slugged, sheet updated).
7artisans-25mm-t1-05-vision3body 3/4 / body 3/4 (alt rotation)1682778 (C104B, E) — only body rotations (no front/opposite); 4th gallery shot is a package photo
7artisans-35mm-t1-05-vision3nameplate body 3/4 / front 3/41682783 (C204B, E)
7artisans-50mm-t1-05-vision3nameplate body 3/4 / front 3/41682787 (C303B, E)
7artisans-7-5mm-f2-8-mark-ii-fisheye3FISH-EYE side profile / front 3/4 (bulb glass)1628292 (A301B-II, Sony E)
aputure-amaran-150c-white3branded 3/4 reflector / back control panel1799980 (500px max)
aputure-amaran-t2c3straight tube / 3/4 tube + cable1698242 (tube light — no front/back convention)
aputure-storm-1200x3branded 3/4 reflector / side profile1849142
aputure-storm-80c3branded 3/4 reflector / back RGB panel1863228 (500px max)
godox-ad200pro-ii3branded 3/4 / flat side profile1841993
godox-ad400pro-ii3branded 3/4 reflector / back control screen1902867
godox-ad600pro-ii33/4 reflector / back control screen1836274
ilme-fx63(existing control-side) / front sensor straight-onadded image 3 only; 1592066
godox-dp400iii-v3back control panel / front 3/4 (alt)1724906 — sparse gallery (only cover-alt + 1 back shot); img3 is the canonical-main front-alt
smallrig-rc-220b-pro3branded 3/4 reflector + screen / back 3/4 (fan + V-mount)1875531 (the "Combo" listing — used bare-monolight shots only, skipped remote/clamp bundle photos)
smallrig-rm1203back 3/4 (branding + screen) / side edge profile1713648 (500px max)
sony-fe-50mm-f1-2-gm2control-side profile (img 2)✅ Added the one clean lens-only profile B&H has (IMG_1503719). No clean 3rd exists on B&H (rest is the hood, dimension diagrams, spec graphics, lifestyle, lens-mounted). Sony press images (electronics.sony.com) are blocked by the browser's safety restrictions — a 3rd needs a manufacturer shot dropped into product images/.

Showcase-gallery progress: of skus that have a cover — 4 at 6 imgs (bodies), 31 at 3 imgs, 2 at 2 imgs (teleconverter + 50mm F1.2 GM — only one extra clean lens-only shot each on B&H). Every cover-having sku now carries ≥2 angles.

Alternates (downloaded, not chosen as primary)

SKUFileNote
none yet

Overnight session — 2026-05-29 (covers pass, continued)

Resumed the cover-sourcing grind for the rest of the catalog via the B&H Method-A pipeline (search by MPN/name → base id → curl static.bhphoto.com → normalize → upload → wire). 26 covers wired this session across 4 batches (product images/sourced/batch-overnight-{1..4}):

  • batch 1: sony-vertical-grip, sennheiser-mkh-416-p48u3, atomos-shogun-ultra, sound-devices-mixpre-6-ii, dji-rs-4-pro
  • batch 2: dji-rs-4-mini, dji-rs-3-pro, dji-mic-3, dji-mic, core-swx-nano-u98x, beyerdynamic-dt-770-pro-x, sony-cfexpress-type-a-320gb, sandisk-…-v90-256gb
  • batch 3: ifootage-shark-slider-nano-ii-660, deity-microphones-{pr-2,tc-1,tc-sl1,c23}, godox-x3pro-s-ttl-wireless-flash-trigger
  • batch 4: smallrig-np-f970-camera-battery, smallrig-x99-v-mount-battery, ifootage-ma5-6-spider-crab-magic-arm, manfrotto-2-section-single-articulated-arm-w-camera-bracket, nanuk-{918,935,950}

Covers-only so far (gallery 2&3 not yet added for these). Also: FX30 (3rd body) imported + curated onto ilme-fx30; office gear (computers/laptops/phone/NAS + internal NVMe SSDs Samsung 980 PRO / WD SN850X / Crucial P3 / Seagate IronWolf) set availability='private'.

Redo / flag (wrong or weak B&H hit):

  • godox-x3-s-ttl-wireless-flash-triggerRESOLVED. Godox rebranded the X3 to "X Nano"; pid 1803870 (URL slug godox_x_nano_s…) is titled "Godox X3 S Touchscreen TTL Wireless Flash Trigger for Sony" (MPN X3-S) — the bare, non-Pro unit. Cover wired.
  • ⚠️ tilta-floatSTILL FLAGGED. B&H product page 1614551 main hero is the carrying case; its gallery (IMG_1927713–718) is the case again + dark lifestyle/component shots — no clean white-bg shot of the support arm itself. Tilta.com (tilta-float-handheld-gimbal-support-system) only has editorial banners with "FLOAT"/"10KG" text overlays + lifestyle. No clean arm hero found anywhere — needs a manufacturer press shot. Left cover unset.
  • iostream-upstreamRESOLVED. B&H's URL slug has a typo (guv320p) but the product page 1611968 shows MFR # GUV302P (matches our sheet) — "IOGEAR UpStream Mobile Capture Adapter". Cover wired.

Overnight session — 2026-05-29 (continued, part 2)

Second autonomous pass. 44 covers wired (catalog now 117/122 covered; worklist 54 → 5) + 6 Vespid lens items created + 2 accessory rows added. Batches below (product images/sourced/batch-overnight-{5..12,12b} + redos):

  • batch-overnight-5 (6 covers): tilta-battery-plate-for-sony-fx6, tilta-camera-cage-for-sony-fx6-basic-kit, dzofilm-octopus-adapter, kondor-blue-4x5-matte-box-system, smallrig-f60-modular-follow-focus, tilta-nucleus-nano-ii.

  • redos (2 covers): godox-x3-s-ttl-wireless-flash-trigger, iostream-upstream (see resolved flags above).

  • DZOFilm Vespid Prime 2 split — MPN DZO-V26SETIX = B&H pid 1898890 "Vespid2 T1.9 Prime 6-Lens Kit (ARRI PL, Feet)". Confirmed composition from B&H key features: 18, 24, 35, 50, 85, 105 mm, all T1.9, ARRI PL, Full Frame/VV (NB: Vespid2 focal set differs from the original Vespid 16/25/35/50/75/100). Created 6 items rows dzofilm-vespid2-prime-{focal}mm (DZO-V2{focal}IH; pids 1898886/887/888/889/891/885), category 'camera lens', availability 'available', bookable_online true, each with a clean 2000px 3/4 hero cover (consistent DZOFILM-branded barrel shots). Original kit dzofilm-vespid2-prime-set set availability='draft' + split note (NOT deleted); per-lens replacement values left null for the owner (kit was $6,289).

  • Major accessories (DB only, not the Inventory sheet): sony-fx6-handle-block-assembly (Sony "Handle Block Assembly for FX6", MPN A-5025-156-B, pid 1788292 — only 500px on B&H but clean) and sony-xlr-h1-handle-unit (Sony XLR-H1, the FX3/FX30 XLR handle, pid 1729533). Both category 'camera body accessory', manufacturer 'Sony', availability 'draft', bookable_online false, cover each.

  • batch-overnight-6 (6 filter covers): polar-pro-pmvnd-edition-ii-{2-5,6-9}-stop-77mm, zeiss-pol-filter-{72,77}mm, zeiss-uv-filter-{55,72}mm. (NB: B&H serves one generic Zeiss stock photo per filter type — the 72/77 POL pair and the 55/72 UV pair are byte-identical; visually correct since ring size isn't legible.) Zeiss POL searches by name returned a lens — re-searched by MPN (1856-338 / 1934-120).

  • batch-overnight-7 (6 covers): manfrotto-one, sachtler-flowtech-75-aktiv-ms ("aktiv8T flowtech75 MS Tripod System"), sachtler-ace-xl-mk-ii, proaim-swift-camera-track-dolly-system, proaim-12ft-straight-aluminum-camera-dolly-track, proaim-spike-foot-for-swift-camera-dolly.

  • batch-overnight-8 (6 covers): aputure-2-bay-battery-power-station, aputure-cf4-fresnel (re-searched by MPN AP0F056A35 — the name search hit the barndoors), aputure-fresnel-2x, aputure-spotlight-mini-36-lens-kit, smallrig-ra-f150-fresnel-lens, godox-knowled-liteflow-k1-reflector-kit.

Running total this part-2 pass: 26 covers wired (8 + 2 redos + 6 + 6 + the 2 straggler completions counted in 7/8) + 6 Vespid items + 2 handle accessories.

  • batch-overnight-9 (6 digital-storage covers): angelbird-atomx-ssdmini-1tb, lexar-professional-go-portable-ssd-with-hub (B&H lists the 1TB LSL400S001T; our sheet is the 2TB LSL400S002T — identical product photo), nextorage-atomx-ssdmini-500gb, nextorage-nx-a2ae-{1tb,2tb,500gb} (the 1TB & 500GB SKUs are 2-packs, shown as two cards — accurate; MPNs legible on the cards).
  • batch-overnight-10 (7 case covers): nanuk-935-pro-photo-kit, nanuk-970, nanuk-988, orca-or-270 (OR-270 sound bag), shimoda-carry-on-roller-v2, shimoda-dv-roller, shimoda-explore-v2 (re-searched by MPN 520-156 → "Explore v2 30 Photo Starter Kit").
  • batch-overnight-11 (5 covers wired, 1 rejected): tilta-monitor-cage-for-atomos-shogun-ultra-connect-handheld, apple-airpods-max-usb-c (Midnight), other-world-computing-owc-express-1m2, manfrotto-pro-scrim-all-in-one-kit-2-9x2-9m-extra-large (re-searched by MPN MLLC3301K — name search hit a Ubiquiti sensor), manfrotto-black-steel-air-cushioned-stand-w-leveling-leg.

batch-overnight-12 — the "B&H-gap" lights were mostly a search-relevance miss. 4 of the 5 flagged lights ARE on B&H by MPN/pid; wired their covers:

  • aputure-amaran-300c-white (MP00000374, pid 1799981)
  • aputure-amaran-300c-gray (AP30011A99, pid 1762211)
  • aputure-amaran-150c-gray (MP0000006Q, pid 1762210)
  • godox-sk400ii-v (SK400II-V, pid 1724912 — the bare single monolight, NOT the kit listings 1739481/82 that name search surfaced) (amaran monolights are 500px-max on B&H, like 150c-white; clean & color-correct.) Lesson: when a light "isn't on B&H," re-search by exact MPN and by "<brand> <model> monolight" before declaring it a gap — relevance ranking buries the bare SKU under kits/competitors.

Worklist down to 5 uncovered SKUs (genuinely blocked), see flags below.

Remaining-5 flags (covers not wired — need owner / non-B&H asset)

  • smallrig-rc-120d — RESOLVED. Owner confirmed it's the RC 120D (Daylight) = MPN 3470 (not 3471 = the Bi-Color RC 120B). Fixed the MPN in the DB (both unit-rows) and the Inventory sheet (rows 114–115, col C → 3470), and wired the cover (B&H 3470 / pid 1681499, bare monolight 3/4).
  • manfrotto-2-section-wind-up-stand-with-leveling-leg (083NW) — RESOLVED. Clean 1000px shot from the Manfrotto product CDN (cdn.manfrotto.com/media/ catalog/product/m/a/manfrotto-steel-2-section-wind-up-stand-083nw.jpg) — the non-cached path has no watermark, unlike B&H's 500px render.
  • 7artisans-cine-lens-case-for-vision-series — RESOLVED. The Vision cine-lens hard case, sourced from the 7artisans store (Shopify CDN, og:image).
  • tilta-float — RESOLVED (open-case hero). No clean assembled-arm-on-white exists anywhere (B&H gallery = case/parts/dark lifestyle; the one assembled-arm shot is on a dark gradient that won't normalize to white). Used the clean open-case shot (B&H 1614551 main) showing the Float components in foam.
  • apple-airpods-max-lightning — RESOLVED. The DB name read "Sky Blue" but the MPN was already the Space Gray part (MGYH3AM/A, confirmed via Best Buy). Corrected the name → "AirPods Max (Lightning, Space Gray)" and wired the Space Gray cover from Best Buy (bbystatic 6373460, front-on). Sheet Model is the generic "AirPods Max" (drives the sku, left unchanged); sheet Reference was already MGYH3AM/A.

Catalog now 122/122 covered — every public listing has a cover. Still to do (next pass): galleries 2&3 (control-side / nameplate angles) for the SKUs covered in the part-2 + manual passes.

To source (remaining catalog)

SKU counts missing a cover, pulled from the DB (availability IN ('available','sold')), highest-count first. Hero categories (camera bodies, lenses, lights) first. Source: B&H Photo (method above); Full Compass as a fallback.

Superseded by the part-2 pass above — catalog is now 117/122 covered. The list below is the original (pre-part-2) gap snapshot, kept for history. Only 5 SKUs remain uncovered (see "Remaining-5 flags" above): smallrig-rc-120d (data conflict), manfrotto 083NW (watermark), apple-airpods-max-lightning (discontinued), 7artisans cine-lens-case (not carried), tilta-float (no arm hero).

  • camera lens — Vespid Prime 2 split into 6 individual Vespid2 lenses (done)
  • light — all wired (amaran 300c/150c-gray + SK400II-V were findable on B&H after all; only smallrig-rc-120d left, pending the 120B/120D data conflict)
  • digital storage · storage · camera lens filter · camera body stabilizer (tilta-float flagged) · light accessory · camera lens accessory · camera tripod accessory · audio monitor (lightning AirPods flagged) · light modifier · light stand (083NW flagged) · light trigger · video monitor · camera tripod · camera body accessory · digital storage accessory — covered this pass
  • camera body (4) — ✅ α7 V, α7R V, α7R VI, FX2 (whole category done)

Flags to resolve

  • 7artisans-12mm T-stop — RESOLVED. The 12mm Vision is T2.9, not T1.05 (the 25/35/50 are T1.05). Corrected the DB name → "12mm T2.9 Vision" and re-slugged the sku → 7artisans-12mm-t2-9-vision, kept MPN C701B, renamed the 3 storage images, and updated the Inventory sheet.
  • dzofilm-vespid2-prime-set — RESOLVED (part-2). DZO-V26SETIX = the Vespid2 T1.9 6-lens kit (18/24/35/50/85/105mm, ARRI PL). Split into 6 individual lens items; kit row set to draft. See part-2 section.
  • 7artisans Vision mounts: B&H relevance kept returning non-E mounts (X/L/MFT) for the 25/35/50. The lens barrel/housing is identical across mounts (only the hidden rear mount differs), so the covers are visually correct; revisit if exact E-mount shots are wanted.

Lights — alternate-sourcing flags (UPDATED part-2)

  • godox-sk400ii-v / aputure-amaran-300c-white / -300c-gray / -150c-gray — RESOLVED. All four ARE on B&H by MPN/pid (the earlier "gap" was a search-relevance miss — bare SKUs ranked below kits/competitors). Covers wired in batch-overnight-12. See part-2.
  • smallrig-rc-120d — still open, but the blocker is a name/MPN data conflict (sheet MPN 3471 = RC 120B; RC 120D = MPN 3470), not a sourcing gap. Owner to confirm the model; both pids noted above.

Lower-res note: amaran-150c-white/-300c/-150c-gray, storm-80c, rm120 are only on B&H's CDN at 500×500 (larger sizes 403/404); usable but could be upgraded from manufacturer sites later.

Open questions / blockers

  • No gear detail page exists yet. Showcase covers show on catalog cards now; the per-product gallery + review-article only render once a [slug] detail route is built. Review images are uploaded + stored in items.review_images so the data is ready, but nothing displays them yet. Detail page = separate build.
  • Rights: product shots from B&H / Full Compass are manufacturer/retailer assets. Fine to show what a rental house actually rents, but worth confirming before launch; prefer manufacturer-provided press images where available.