AisleAsk — public storefront SEO deals page
Ask (2026-07-24): take a photo of the front of the store (upload / camera — Yelp/Google auto-fetch later), run it through a painterly artistic filter, and use it as the hero of a public, indexable per-store deals page — an SEO surface for "[store] deals / weekly ad near me".
Why this is a good wedge
- Every crowd-mapped store becomes a landing page ranking for its own name +
"deals". The deals data already exists (
aisleask_deals, crowd-scanned). - The painterly filter makes the hero recognizably their storefront but stylized — distinctive, and it sidesteps re-publishing a third party's exact copyrighted photo verbatim.
- Public pages feed people back into AisleAsk (shop-the-deals + aisle routing).
Photo source (two ToS-clean paths)
- Upload / camera (
generateStorefront) — owner supplies the storefront photo; gpt-image-1 image-EDIT repaints it painterly. Compliant because it's the owner's own image. - ✨ Generate (
generateStorefrontHero) — fully generative painterly exterior from the store's name/type via gpt-image-1, no source photo. The one-tap "auto" path: no upload, no Google imagery.
Why NOT Google Places auto-fetch (decided 2026-07-25): we scoped pulling the
storefront photo from Google Places (QS could serve it — they have the pipeline)
and rejected it on Google's Place Photos ToS: those terms require showing the
photo's attributions and bar caching + derivative works, so painterly-transforming
a Places photo and re-hosting it as our own SEO hero doesn't comply. A generative
hero sidesteps this entirely (no Google input). The Places direction + QS's cost/
ToS analysis is preserved in crosstalk/contracts/aisleask-places-storefront.md
(status: declined-on-ToS). Yelp has the same derivative/attribution constraints.
Data model (migration 571)
Additive on aisleask_stores:
storefront_image_url text— the painterly hero (gpt-image-1 image-EDIT of the uploaded photo).public_slug text(unique, partial index) — the/store/<slug>URL key, derived from the store name, deduped with a numeric suffix.public_deals_enabled boolean NOT NULL DEFAULT false— owner opt-in; the page 404s unless true. Generating a storefront flips it on.
Backend
generateStorefront(userId, storeId, dataUri)— decodes the uploaded photo, runs gpt-image-1 image-edit with a painterly-repaint prompt keyed to the store name (keeps the real building shape/signage recognizable), uploads to theaisleaskStorage bucket (storefronts/<storeId>.png), mints apublic_slug, setsstorefront_image_url, and turns the page on. Mirrorsseason-media.tsimages.edit+toFile.POST /:storeId/storefront { image }.getPublicStore(slug)— no-auth service-role read; returns the store's public fields + active deals, or null unlesspublic_deals_enabled.GET /public/store/:slug(registered before the/:storeId/*routes).public_deals_enabledflows throughupdateStore/sanitizeStorePatch— the owner can toggle the page off (checkbox on the dashboard).
Frontend
- Public page
apps/frontend/src/app/store/[slug]/page.tsx— server component,generateMetadata(OG image = painterly storefront), JSON-LDGroceryStore+Offers for rich results, painterly hero, deals grid, "Open AisleAsk" CTA. Canonical/OG onwww.hivejournal.com. ISRrevalidate: 600. - Dashboard (Map tab): a "🎨 Public deals page (SEO)" block — upload storefront photo → filter → preview + a "View public page ↗" link + a Public on/off toggle.
Version history (migration 572)
Every generated/uploaded hero is kept — a distinct Storage object under
storefronts/<storeId>/<stamp>.png. aisleask_stores.storefront_image_history
(text[], newest-first, capped 12) holds all versions; storefront_image_url stays
the CURRENT hero the public page shows. The dashboard renders a thumbnail row; clicking
an older one repoints the current via POST /:storeId/storefront/select { url } (validates
membership — no regeneration). Apply migration 572.
Later
- QuickSites stands up the Places proxy (contract above) → HJ flips one env var.
- Per-store OG card composited with the deal count.
- City/chain index pages ("QFC deals near Renton") aggregating store pages.