Loading
Case study
AI SaaS · own product · Nx monorepo · CTO

The traffic that built e-commerce is migrating to AI platforms. GrowGPT helps merchants follow it.

Category
AI commerce SaaS
Client
Own product (Bespokesoft)
Years
2026 — active
Role
Co-founder · CTO · Full-Stack
GrowGPT landing on staging.growgpt.app — ‘The traffic that built your business is disappearing. The traffic that will replace it converts 2× better.’
At a glance

An Nx monorepo with seven apps — React 18 dashboard, Next.js 15 landing, embeddable widget, custom MCP server, MCP discovery layer, enrichment engine and Supabase backend — all shipping under one platform that lets merchants reach customers on ChatGPT and Google AI.

7apps
In one Nx monorepo — web dashboard + landing + widget + MCP server + discovery + enrichment + Supabase
37edge fns
Supabase Deno Edge Functions powering real-time multi-tenant SaaS
5modules
Live product modules — FlowGuide, ShopScout, ProductGenie, DataFlow, ACP/UCP
Conversion rate on ChatGPT-referred traffic vs organic search (Similarweb 2025)
01 — The problem

The channels that built e-commerce for two decades are quietly losing clicks. Where do they go?

Google Search delivers fewer clicks at higher cost — Google AI Overviews intercept the answer before the user ever reaches a product page. Meta Ads ROAS keeps drifting. Meanwhile a generation of shoppers has moved their product discovery into AI platforms: ChatGPT (800M+ weekly active users), Google Gemini, Perplexity. The intent didn’t disappear. It migrated. And it migrated to channels where the merchant has zero control, zero attribution and zero merchandising surface.

The data nobody talks about: ChatGPT-referred traffic converts 2× better than organic search (Similarweb, 2025). High-intent customers who already prequalified the product through an AI conversation land on the merchant’s site ready to buy. But ChatGPT has no commerce surface of its own. Google’s AI Mode shows products without giving the brand a stage. Every retailer with a Shopify or WooCommerce store is invisible at exactly the moment when discovery is happening.

GrowGPT was started in early 2026 to be the missing layer. Five product modules in one platform: FlowGuide (interactive product discovery flows), ShopScout (AI shopping assistant with text + voice), ProductGenie (AI product information assistant), DataFlow (catalog enrichment pipeline) and ACP/UCP (commerce pages + ChatGPT Apps integration). One Nx monorepo, seven deployable apps, multi-tenant SaaS from day one. The CTO job: pick the stack, build the platform, hire the team, hold the architecture together while the product surface explodes from one module to nine in 90 days.

02 — The vision

Five modules, one platform. — merchants ship AI-native commerce without stitching ten vendors together.

Four product principles drove the architecture from the first commit:

  • One monorepo, many surfaces. Nx + pnpm holds web dashboard, marketing landing, embeddable widget, MCP server, MCP discovery layer, enrichment engine and Supabase Edge Functions in one repository. Shared TypeScript types, shared Tailwind preset, shared Supabase client — no copy-paste between apps, no version drift between teams.
  • Multi-tenant at the database, not the application. Supabase Row Level Security enforces tenant isolation at every query. No app code ever filters by tenant_id manually; Postgres rejects the read before it leaves the database. Onboarding a new merchant is one row in merchants + JWT.
  • AI-native, not AI-bolted-on. The custom Model Context Protocol server (apps/mcp-server) lets agentic AI clients query the product catalogue + widget templates as first-class context. ChatGPT can read the merchant’s catalog without a custom plugin per shop. Google Gemini powers enrichment (titles, descriptions, image alts) because it ships structured-output cheap and fast.
  • Five modules, one config. FlowGuide, ShopScout, ProductGenie, DataFlow and ACP/UCP all live behind a single tenant config. Merchants enable a module with one toggle; the dashboard reflects it, the widget reflects it, the Edge Functions provision the data. No second app to install, no second API key.
03 — Who it’s for

Four merchant archetypes, one platform that scales from a one-person Shopify store to a 50K-SKU enterprise catalogue.

GrowGPT ‘Two Directions, One Platform’ section on staging.growgpt.app — LEG A: Be Visible (DataFlow Connect for AI platforms) and LEG B: Own the Narrative (ShopScout + FlowGuide for the merchant’s site)
D2C founder — Shopify, 50–500 SKUs

Solo or two-person operation. Needs an AI shopping assistant live in 24 hours, zero developers required. Embeds the widget snippet on the product pages, points DataFlow at the existing product feed, FlowGuide and ShopScout go live. Pricing tier: Starter.

E-commerce manager — mid-market, 5K–20K SKUs

Owns the AI commerce roadmap for a real retail brand. Cares about ChatGPT Apps integration, custom widget styling per category page, conversation analytics. Hands off DataFlow scheduling to the data team, ShopScout copy to the marketing team. Pricing tier: Growth.

Platform operator — enterprise, 50K+ products

Multi-brand retailer or marketplace. Needs the full module catalogue, custom MCP server extensions, white-label dashboard for client brands, IP whitelisting, SOC2-ready audit logs. Engages directly with the founding team. Pricing tier: Enterprise.

AI commerce agency / integrator

Builds GrowGPT into client stacks. White-label dashboard + ACP (App Configuration Page) per client, programmatic merchant provisioning through the Supabase Edge Functions, can deliver an AI shopping experience without staffing a backend team for every project.

04 — Seven apps, one platform

One Nx monorepo. Seven deployable apps. One config per merchant.

The seven apps below are the deployable units of GrowGPT. Each ships independently, each watches the same Supabase tenant database, each shares TypeScript types + Tailwind config + Supabase client from packages/. No copy-paste between apps, no version drift between teams. The architecture choice that paid for itself the first time we needed to ship the embeddable widget without breaking the dashboard.

APP 01

web — merchant dashboard

React 18 + Vite + Tailwind + shadcn/ui. Merchants configure modules, watch conversations, set widget styles, schedule DataFlow imports. Multi-tenant by JWT.

APP 02

landing — marketing site

Next.js 15 (App Router) + React 19. SSG/ISR for SEO; all articles render dynamically to avoid Supabase build-time fetches. Vercel deploy.

APP 03

widget — embeddable JS

React + esbuild IIFE bundle. Scoped CSS, no Tailwind global scope, runs on any merchant’s site without conflicts. CSP-friendly, XSS-sanitised CSS injection.

APP 04

mcp-server — custom MCP

Custom @modelcontextprotocol/sdk server. Exposes product catalogue + widget templates to ChatGPT / Gemini / Claude agents as first-class context.

APP 05

mcp-discovery — discovery layer

Routes AI-platform discovery queries to the right merchant context. Handles “ChatGPT asks for shoes” → “which merchant’s shoes” resolution.

APP 06

enrichment-service — data engine

Node + Express + Drizzle ORM. Pulls product feeds (CSV / XML / API), enriches with Google Gemini, writes back to Supabase. Cron + dual auth (X-Api-Key + JWT).

APP 07

supabase — backend

PostgreSQL + 37 Deno Edge Functions + PLpgSQL migrations + Row Level Security. Auth, real-time channels, file storage. Zero cold starts.

05 — The architecture

Two stacks at the boundary: frontend ships to Vercel, backend ships to Supabase.

The frontend is three Vercel-deployed apps (landing, dashboard, widget bundle CDN) backed by a shared packages/ directory with TypeScript types, Tailwind preset, Supabase client and shadcn/ui components. The backend lives entirely in Supabase: PostgreSQL with Row Level Security, 37 Deno Edge Functions for the API surface, real-time channels for the dashboard, plus a self-hosted Node enrichment service that pulls product feeds and writes back to Postgres. A custom Model Context Protocol server bridges the platform into ChatGPT, Gemini and Claude as native tooling. One monorepo, two deploy targets, zero shared state outside the database.

Frontend — Vercel-deployed apps + shared packages

apps/landing
Next.js 15 (App Router) · React 19 · SSG / ISR
apps/web (dashboard)
React 18 · Vite · Tailwind · shadcn/ui · JWT auth
apps/widget — embeddable IIFE bundle
React + esbuild · scoped CSS · XSS-sanitised live preview · CSP-friendly
packages/ — shared workspace code
types · ui (shadcn) · tailwind-config · supabase-client · hooks · shared
Vercel + next.config.js rewrites
/login + /dashboard/* proxy to Vite app — one domain, two apps

Backend — Supabase + Edge Functions + MCP layer

apps/mcp-server
custom @modelcontextprotocol/sdk · product + template context
apps/mcp-discovery
routes AI-platform queries to the right merchant tenant
37 Supabase Edge Functions (Deno)
API surface for dashboard + widget + ChatGPT Apps · zero cold starts · JWT-authenticated
PostgreSQL + Row Level Security (multi-tenant)
tenant isolation enforced at the database · PLpgSQL migrations · real-time channels
apps/enrichment-service
Node + Express + Drizzle · Google Gemini · cron + dual auth
Google Gemini API
structured output for titles / descriptions / alts · rate-limit aware
Supabase managed Postgres + Auth + Storage
no separate Node API server · managed backups · one bill, one dashboard
06 — Technical challenges

Eight engineering problems from the first 90 days of building a multi-tenant AI commerce platform.

01

Extracting the landing into its own Next.js 15 app

Problem. Marketing landing started life inside the React 18 + Vite dashboard app. Once it grew past 90 components, two things broke at the same time: react-router-dom couldn’t do proper metadata for SEO, and the theme toggle component caused hydration mismatches on every page load.

Solution. Commit 513d2da extracted 108 landing files into apps/landing (Next.js 15 App Router + React 19, SSG/ISR). React Router replaced by Next’s Link + metadata API. ThemeToggle hydration fixed with the mounted-state pattern (render nothing until useEffect runs). Server-side Supabase client built with NEXT_PUBLIC_* env vars. next.config.js rewrites /login and /dashboard/* to the Vite dashboard so users see a single domain.

02

Dual auth: API-key for cron, JWT for users

Problem. DataFlow runs scheduled imports of product feeds. A single auth scheme couldn’t do both: cron-triggered imports need a long-lived secret (Supabase scheduled function), user-triggered imports need a short-lived JWT scoped to the tenant. Conflating them either made the cron leak through user sessions or made user actions fail when the cron secret expired.

Solution. Commit 244df94. The Edge Function accepts either auth path: X-Api-Key header for cron-triggered enrichment imports, JWT bearer for dashboard-triggered imports. The enrichment-service holds the API key; the dashboard holds the JWT. Audit log records which path was used per request, so post-mortem on a bad import knows whether to look at the cron config or a user action.

03

XSS-safe widget CSS injection for merchant theming

Problem. Merchants want to apply custom CSS to the embedded product carousel widget — brand fonts, custom colours, hover states. Naive implementation: take CSS string, inject as <style> tag. That’s a script-injection vector at scale: merchant CSS could include } + </style><script>, then execute on every visitor’s browser.

Solution. Commit ac050ed. Custom CSS sanitiser strips </style>, comment escapes, @import URLs and any tag-like sequences; output gets wrapped in a scoped class so it only affects the widget’s shadow tree. Live preview in the dashboard renders the sanitised CSS through the same MCP server endpoint that ships to production, so what the merchant sees is what visitors get. Integration tests verify no <script> can survive the sanitiser even on intentionally hostile input.

04

Scheduled feed imports + Gemini rate limits

Problem. DataFlow enriches product titles, descriptions and image alts through Google Gemini. The naive cron implementation hit Gemini’s quota the first time a merchant scheduled a 10K-product feed. No external queue available (single-VPS deploy for the enrichment-service); needed retry + rate-limit awareness without adding Redis.

Solution. Commit af92197. Cron implemented through Supabase scheduled Edge Functions (no external scheduler). Enrichment-service tracks per-tenant Gemini usage in Postgres; on 429 the worker waits with exponential backoff + full jitter, capped at 60 s. Batch size 10–20 rows per Gemini call — small enough to stay under the per-call quota even with multiple tenants running imports simultaneously. The retry strategy is in code, not in infrastructure.

05

Build-time env vars vs runtime Supabase client

Problem. First Vercel deploy of apps/landing failed at build time: articles pages were doing generateStaticParams + Supabase fetch, and the Supabase client initialised eagerly with env vars that weren’t present during the build container’s prerender step. The build crashed before any pages were emitted.

Solution. Commit 31ef7a3. Articles pages forced to dynamic rendering (no generateStaticParams, no build-time fetch). Supabase client lazy-initialised inside components — env vars checked on render, not at module-import time. TypeScript strict mode disabled inside the Next.js build only (React 18 + Radix UI type conflict that wasn’t worth chasing at deploy o’clock). The fix shipped during a launch window and the landing stayed live.

06

DataFlow UX: three tabs in, one flow out

Problem. First DataFlow UI shipped with three tabs: Upload File, External Feed URL, Local XML. Users picked the wrong tab half the time, didn’t understand which combination of inputs produced which outputs, and routinely failed to schedule a recurring import because the scheduling controls lived on only one tab.

Solution. Commit 6f29844. Three tabs collapsed into a single “Scheduled Feed Imports” flow with a cron-builder UI on top. The merchant picks “import this feed every X hours/days”; the form figures out which transport (file / URL / XML) based on the URL pattern. Less choice, more correct usage. Removed dead code: the upload path had been used by ~3% of test users.

07

Removing the GCS dependency that nobody used

Problem. Early architecture sketch included Google Cloud Storage for asset uploads. The dependency went into package.json, a UI field went into the dashboard, but the actual upload flow ended up using Supabase Storage instead. The GCS import stayed in the bundle, the UI field stayed visible, the SDK stayed in the lockfile — for months.

Solution. Commit ea76397. Removed @google-cloud/storage from package.json, the GCS UI field from the dashboard, and the orphan code paths in the enrichment-service. pnpm-lock.yaml cleaned. The stack now has one storage layer (Supabase), one bill, one set of credentials. Boring cleanup that no user noticed but every future engineer thanks.

08

Visual widget builder with live CSS preview

Problem. Merchants need to see how their widget will look on the actual product page before they paste the embed snippet. Building a separate preview app would double the surface area; running the real widget in a sandbox iframe in the dashboard meant solving the cross-origin CSS injection story all over again.

Solution. Commit 044cc65. Live CSS preview panel inside the dashboard renders the actual widget bundle in an isolated iframe; merchant CSS edits flow through the same sanitiser used in production (challenge 03). The Nx monorepo’s shared packages/ui means the preview imports the same React components shipped in the IIFE bundle. WYSIWYG that actually matches WYG, not a separate “preview” approximation that drifts from the real widget over time.

07 — The workflow

Merchant onboarding in 24 hours: connect catalogue → pick modules → ship widget → AI commerce live.

A new merchant signs up and lands in the dashboard. They paste their product feed URL into DataFlow; the enrichment-service pulls the catalogue, Gemini fills in any missing titles / descriptions / alts, the data lands in Postgres scoped by their tenant ID. They pick which modules to enable (FlowGuide for guided discovery, ShopScout for AI chat, ProductGenie for product-page Q&A); the dashboard generates the widget snippet they paste into Shopify or WooCommerce. In parallel: the MCP server exposes the merchant’s catalogue + widget templates as Model Context Protocol tools, so ChatGPT and Gemini agents can query products on behalf of users without a per-merchant plugin. The first shopper conversation streams within hours of the snippet going live. The dashboard shows conversations + conversion attribution + Gemini cost per merchant in real time through Supabase Realtime channels.

GrowGPT workflow scene — laptop levitating in a dark digital space with neon-green grid mesh, particles and rim-light, premium AI-tech product reveal aesthetic
08 — Feature highlights

Five live modules in the public catalogue, four more shipping or planned.

The full module catalogue on the staging landing shows nine modules grouped into Foundation (DataFlow), AI Platforms (DataFlow Connect, ChatGPT Apps), Your Website (ProductGenie, ShopScout, ShopScout Voice, FlowGuide), and Coming Soon (SmartSearch, SmartLine). Below: the two features that hold the platform together.

GrowGPT module catalogue scene — three monitors on a marble desk with vertical neon-green LED bars between them, displaying the DataFlow / ChatGPT Apps / ShopScout module grid
+ Custom Model Context Protocol server

apps/mcp-server built on @modelcontextprotocol/sdk — exposes each merchant’s product catalogue, widget templates and conversation context as first-class MCP tools. ChatGPT, Gemini and Claude agents can query a merchant’s shop natively, without a per-merchant plugin install. The discovery layer (apps/mcp-discovery) routes incoming AI-platform queries to the right tenant. This is the piece that turns “merchant on a website” into “merchant inside ChatGPT”.

+ DataFlow enrichment pipeline with Gemini

apps/enrichment-service — Node + Express + Drizzle ORM pulls product feeds on a schedule, runs them through Google Gemini for structured output (titles, descriptions, alt text, category-aware enrichment), writes back to Supabase. Dual auth (X-Api-Key for cron, JWT for user-initiated runs). Per-tenant rate-limit tracking in Postgres, exponential-backoff retry on 429, batch size 10–20 per Gemini call. The data engine that makes every other module worth shipping.

09 — Stack

Two stacks, one monorepo — frontend ships to Vercel, backend ships to Supabase.

Every dependency below earns its place by either being managed (Supabase, Vercel — less to operate) or being the smallest correct option (esbuild for the widget bundle, Drizzle for the enrichment service, Nx + pnpm for the workspace). The whole platform shares one tenant database and one set of TypeScript types.

Layer
Tech
Why
Frontend — Vercel-deployed
Landing
Next.js 15 (App Router) + React 19
SSG / ISR for SEO; dynamic article rendering to avoid build-time Supabase fetches. next.config.js rewrites /login + /dashboard/* to the Vite app.
Dashboard
React 18 + Vite + TypeScript
Interactive SPA, Vite’s HMR for fast dev cycles. JWT auth against Supabase; tenant scope from the JWT claims.
UI
Tailwind CSS + shadcn/ui
Same component primitives on landing + dashboard. shadcn is owned code that lives in packages/ui, not a dependency we can’t patch.
Widget
React + esbuild IIFE bundle
Embeds on any merchant site (Shopify, WooCommerce, custom). Scoped CSS, no Tailwind global scope, CSP-friendly. Live CSS preview through the same sanitiser ships to production.
Workspace
Nx + pnpm 10 monorepo
Seven apps, six shared packages. Nx’s dependency graph keeps parallel builds fast; pnpm’s strict mode catches accidental cross-app imports at install time.
Backend — Supabase + AI + enrichment
Database
PostgreSQL via Supabase + Row Level Security
Multi-tenant enforced at the database. No app code filters by tenant_id; Postgres rejects the read.
Edge Functions
37 Supabase Edge Functions (Deno)
API surface for dashboard + widget + ChatGPT Apps. Zero cold starts, JWT-authenticated, deploy via Supabase CLI.
Migrations
PLpgSQL + supabase:gen-types
Schema changes auto-generate TypeScript types into packages/types. Frontend gets compile-time errors the moment Postgres shape drifts.
Enrichment
Node + Express + Drizzle ORM
Self-hosted service for product-feed pulling + Gemini enrichment. Drizzle for typed Postgres access without the ORM overhead.
AI
Google Gemini API (google-generativeai)
~$0.075 per 1M input tokens. Structured-output mode for product fields. The right cost curve for batch enrichment at our scale.
MCP server
Custom @modelcontextprotocol/sdk
Exposes catalogue + templates to ChatGPT / Gemini / Claude agents. The piece that lets a merchant exist inside ChatGPT without a per-shop plugin.
Auth
Supabase Auth (JWT) + X-Api-Key (cron)
Dual auth: users go through JWT, cron jobs go through API keys. Audit log records which path was used per call.
Storage
Supabase Storage
Single storage layer (removed the early GCS dependency that nobody used). One bill, one set of credentials, one access-control story.
Deploy
Vercel (frontend) + Supabase + custom Node service
Vercel handles the React apps + CDN for the widget bundle. Supabase handles Postgres + Edge Functions. Enrichment-service runs on a self-managed Node host.
10 — Results

Live on staging, active build, analysts saying the category is opening right now.

GrowGPT is in active build on staging.growgpt.app with the merchant dashboard at growgpt.app/login. Five product modules live (FlowGuide, ShopScout, ProductGenie, DataFlow, ACP/UCP); two more (SmartSearch, SmartLine) in the queue. Boston Consulting Group, McKinsey and Gartner all published research in 2025 saying the same thing the platform was built around: AI-native shopping is becoming the primary discovery channel for Gen Z and Millennial consumers, and the first retailer in each category to build infrastructure for it captures disproportionate value.

GrowGPT analyst quotes scene — silver laptop magazine-cover composition on dark marble background with neon-green rim-light and green ambient bokeh, showing BCG / McKinsey / Gartner research excerpts
7 apps
In one Nx monorepo, six shared packages — the architecture that lets a small team ship five product modules in 90 days.
37 fns
Supabase Edge Functions powering the multi-tenant API surface for dashboard, widget and ChatGPT Apps.
ChatGPT-referred traffic converts 2× better than organic search (Similarweb 2025) — the data the whole platform bets on.
800M+
ChatGPT weekly active users in 2025 (OpenAI) — the discovery channel the merchants in the dashboard are aiming at.
11 — Engineering decisions

Seven ADRs — the architectural decisions that shaped the first 90 days.

Each decision below ruled out a more popular alternative on purpose. The popular ones came with operational cost, vendor lock or future regret that didn’t fit a two-engineer team shipping five modules in 90 days.

ADR-01

Nx + pnpm monorepo over separate per-app repos

Context. Seven apps (dashboard, landing, widget, MCP server, MCP discovery, enrichment-service, supabase) and six shared packages. The alternative was one repo per app + manual version bumps on every shared change.

Decision. Nx workspace with pnpm lockfile. Shared packages/types auto-generated from Supabase schema; packages/ui holds shadcn components; packages/tailwind-config holds the design tokens.

Consequence. Schema change in Postgres → types regenerate → every app gets a compile-time error in the right spot. No version-skew between widget and dashboard. The trade-off: more upfront setup, more CI configuration — both paid back the first time we needed to ship the widget without breaking the dashboard.

ADR-02

Separate landing (Next.js 15) from dashboard (React 18 + Vite)

Context. Both apps could have lived in one Next.js codebase. The cost: SSG/ISR optimisations on the landing would fight with the SPA-style interactivity of the dashboard, and Vite’s sub-second HMR would be replaced by Next’s slower dev rebuilds.

Decision. Two apps. Landing in Next.js 15 (SSG / ISR / metadata API for SEO). Dashboard in React 18 + Vite (interactive SPA, fast HMR, shadcn/ui). next.config.js rewrites /login + /dashboard/* to the dashboard, so visitors see one domain.

Consequence. Independent build times, independent deploys, optimal tool for each surface. The Vercel rewrites add ~5 ms latency on dashboard routes — acceptable trade for the DX wins.

ADR-03

Supabase (Postgres + Edge Functions) over a custom Node API

Context. The classic answer is an Express or NestJS API server on a VPS, with Postgres alongside, with auth glued in via Passport or Auth.js. Operationally: a server to monitor, an auth flow to maintain, a CORS story to debug.

Decision. Supabase as the entire backend: managed Postgres, 37 Deno Edge Functions for the API surface, Row Level Security for tenant isolation, Supabase Auth for JWT, real-time channels for the dashboard. No separate API server.

Consequence. Zero cold starts on Edge Functions (Deno). Tenant isolation enforced at the database, not the app — app code can’t accidentally leak. The vendor-lock trade-off accepted because Postgres + standard SQL means migrating off is “swap the auth” not “rewrite the model layer”.

ADR-04

Google Gemini over OpenAI for enrichment

Context. Two options for the enrichment pipeline: Gemini (Google) or GPT-4o / GPT-4o-mini (OpenAI). Both ship structured-output modes and similar quality on product-data tasks.

Decision. Gemini API via google-generativeai SDK. Per-1M-input-token cost roughly an order of magnitude cheaper than GPT-4o at the time of decision. Quality on structured product fields (titles, descriptions, alt text) tested side-by-side; Gemini was good enough.

Consequence. Unit economics work at the batch sizes merchants actually run. The MCP server can still integrate Claude or OpenAI for agentic workflows where Gemini falls short; the enrichment cost stays sustainable.

ADR-05

Custom MCP server over off-the-shelf

Context. Off-the-shelf MCP servers exist (GitHub, Notion, file system). For GrowGPT, the “context” the AI agent needs is a merchant’s product catalogue + widget templates + conversation history — none of which fit any off-the-shelf shape.

Decision. Custom apps/mcp-server built on @modelcontextprotocol/sdk. Domain-specific tools (search_products, get_template, list_conversations) typed against the same Postgres schema as the dashboard. Maintenance burden owned in-house.

Consequence. ChatGPT, Gemini and Claude agents can reason over a merchant’s shop natively. The discovery layer (apps/mcp-discovery) handles tenant routing. Unlocks agentic workflows that an off-the-shelf MCP server simply can’t express.

ADR-06

Embeddable widget (React + esbuild) over iframe-only

Context. Two ways to embed an interactive widget on someone else’s site: an <iframe> (safe, isolated, fights CSP, awkward for cross-origin interactivity) or a <script> tag that injects React into the host DOM (faster, smoother, harder to keep clean).

Decision. Script-tag embed. React + esbuild IIFE bundle. Scoped CSS via Shadow DOM (no Tailwind global scope leaks). XSS-sanitised merchant CSS injection. CSP-friendly: no eval, no inline scripts, all assets served with explicit CORS headers.

Consequence. Merchants get a native-like widget experience without iframe sandbox limits. The merchant’s <script> snippet adds ~30 KB gzipped. Live preview in the dashboard uses the same IIFE bundle, so WYSIWYG actually matches.

ADR-07

Dual auth: JWT for users, X-Api-Key for cron

Context. DataFlow runs scheduled imports of product feeds. A single auth scheme can’t serve both well: long-lived secrets for cron jobs vs short-lived JWTs for user-initiated actions.

Decision. Edge Functions accept either: X-Api-Key for cron-triggered enrichment runs, JWT bearer for dashboard-triggered runs. Audit log records which path was used per request.

Consequence. Cron-only secrets never leak through user sessions. User actions never fail because the cron secret rotated. Post-mortem on a bad import knows whether to look at the cron config or a user action.

12 — Build timeline

Six phases in 90 days — from empty repo to five live modules and a custom MCP server.

GrowGPT moved fast because the architecture decisions in ADR-01 through ADR-07 made each phase a small, independent build. The timeline below is real: the repo was created on 2026-02-03, the major refactors and module launches all happened in the first 90 days.

01

Phase 1 — Repo created (early February 2026)

Empty Nx + pnpm workspace. First commits scaffold the seven apps and six shared packages. Supabase project provisioned, Edge Functions skeleton, Postgres tenants table with Row Level Security on day one. Vercel projects connected for landing + dashboard.

02

Phase 2 — First three modules go live

FlowGuide (interactive discovery), ShopScout (AI shopping assistant) and ProductGenie (AI product Q&A) ship with their backend Edge Functions, dashboard configuration screens and widget integration. First merchant onboarded to test end-to-end.

03

Phase 3 — DataFlow + enrichment-service

Self-hosted Node + Express + Drizzle service for pulling product feeds. Google Gemini wired up for structured-output enrichment (titles, descriptions, alt text). Dual auth (X-Api-Key for cron, JWT for users) lands in commit 244df94. DataFlow UX collapses from three confusing tabs into one scheduled-import flow (commit 6f29844).

04

Phase 4 — Landing extracted into Next.js 15 (late February)

Commit 513d2da. 108 landing files moved out of the React/Vite dashboard into apps/landing. React Router replaced by Next’s metadata API. ThemeToggle hydration fix. next.config.js rewrites /login + /dashboard/* so visitors keep seeing one domain. Dynamic article rendering fixes the Vercel build-time env-var crash (commit 31ef7a3).

05

Phase 5 — Widget polish + custom CSS sanitiser

Commit ac050ed — XSS-safe CSS injection so merchants can theme the widget without becoming a script-injection vector. Commit 044cc65 — visual widget template builder with live CSS preview using the same IIFE bundle that ships to production. The piece that turns the widget from “take it or leave it” into “style it like your brand”.

06

Phase 6 — ACP / UCP and the MCP layer (early March)

Commerce Pages (UCP) ship for branded product catalogues. ChatGPT Apps integration (ACP — App Configuration Page) goes live. Custom MCP server + MCP discovery layer let AI platforms query merchant catalogues natively. Removed dead GCS dependency (commit ea76397) and tightened the storage story to Supabase-only. Active build continues — SmartSearch and SmartLine queued next.

Like what you see?
Let’s build the next one.

From a blank page to a working product — AI, automation, full-stack engineering. Get in touch and let’s talk about your idea.