Status: live, early access, pre-revenue. Shopframe has been live at shopframe.app since 2026-08-11. The configurator, self-serve provisioning, the edge-rendered dealer sites and the Site Manager all work today; the three demo dealers you can click through are fictional. This page is about the architecture and the operating model, not traction — there is none to report yet.
The problem
Independent used-car dealers sit in an awkward gap. Franchise-grade website platforms are priced for franchise stores and come with multi-year terms and change fees; budget builders are cheap and look it. In between is a large population of dealers who need the same seven pages every dealer needs — home, searchable inventory, a detail page per vehicle, financing pre-qual, trade-in estimator, about, contact — plus a leads inbox and a way to keep the site current without a webmaster.
The engineering opportunity was that a dealer site is almost entirely data plus design choices. If the design system is expressive enough and the rendering is cheap enough, one codebase can serve every dealer, and provisioning can be a webhook rather than a project.
What I built
Shopframe is a Cloudflare-native SaaS that lets a dealer design their site in a "free designer," see one all-in price, pay, and have the site exist seconds later. I am the founder, architect and product owner. The build and day-to-day operation are shared with an AI operator under a written contract (more on that below).
- The free designer. A configurator on the homepage where the dealer picks a vibe, a palette, a typeface pairing and a hero layout against a live preview; the price stays behind a veil until they choose to reveal it. The all-in number arrives after the dealer can see what the choices produce, because a price lands better with the value it covers visible than it does before the customer knows what they are getting. Package logic auto-selects the cheapest plan that covers their picks. Four plans, $29 to $499 a month, no setup fee, month to month.
- Self-serve provisioning. Stripe Checkout hands off to a signature-verified webhook that writes the dealer's config, mints a magic-link session and emails it. The dealer's site is live at its own path immediately, with custom-domain routing built into the worker.
- Edge-rendered dealer sites. No build step for paid dealers. Every page — home, SRP, VDP, financing, trade-in, about, contact — renders on request from KV config, KV inventory and R2 photos, with Schema.org
AutoDealerandVehicleJSON-LD, sitemap and robots. - A Site Manager. Session-gated, plain-English control panel: announcement bar, monthly specials, smart badge rules, palette/typeface/layout/vibe, hours, CSV inventory import with header auto-mapping, phone-photo uploads to R2, a leads inbox with pipeline stages and email alerts, a copy-paste Craigslist and Marketplace ad builder, print-ready window stickers and a sales ledger.
- A grounded support chat on the marketing site, running on Workers AI.
Around 9,000 lines of JavaScript across the worker, the renderer, the configurator and the admin, with zero npm dependencies. Three fictional demo dealers — Redline, Lakeside and Crown — are built statically to prove the template swaps cleanly and that no two Shopframe sites read alike.
Architecture and decisions
Multi-tenant rendering at the edge
One worker serves everything. For a request on shopframe.app/<dealer>/… it loads the dealer's config and inventory from KV and renders the page. For a request on a dealer's own hostname it resolves host → dealer with one KV read (falling back to D1 and re-warming the cache), sets the site base to / so links and canonicals are root-relative, and runs the same renderer. Unknown hosts fail closed to a 404. Pages carry a short public cache TTL so a publish shows up within a minute with no rebuild and no deploy.
D1 as system of record, KV as a blind-written cache
The first version stored dealer configs only in KV and the publish route was a read-modify-write. KV reads are cached at the edge for up to a minute, so the merge base could be stale and a publish would silently revert whichever fields the request did not repeat. Rather than mitigate that, the storage model changed. D1 holds one row per dealer: an immutable base JSON written once at provisioning (identity, plan, checkout design choices — never client-writable) and one nullable overlay column per editable presentation field, where NULL means "not overridden" and the base shows through. A publish is a single atomic UPDATE … RETURNING * whose SET list contains only the fields that request sent, so partial writes are safe by construction and no code path merges over a possibly stale read. Keeping identity and plan in the base is also what makes the plan enforceable: the publish statement can only address overlay columns, so a dealer's own edit has no path to the field that decides what their subscription includes — it is a shape in the schema rather than a validation rule a later code path could forget. KV dealer:<id> is only ever written from the row D1 just returned. Every D1 helper degrades to the old path instead of throwing, so an outage slows edits rather than taking sites down.
Zero-dependency billing and auth
Stripe Checkout, the webhook, subscription events, the customer portal and magic-link sign-in are all raw fetch plus WebCrypto — HMAC-SHA256 signature verification with a timing-safe compare for webhooks, HMAC-signed session cookies for dealers, no passwords anywhere. usableStripeKey() routes a missing or malformed key to a graceful "we'll invoice you" 503 so a bad secret can never hard-break the reserve button, and a rejected webhook signature writes a record and emails an alert instead of failing silently. The checkout → webhook → provisioning path has been exercised end to end with a live event.
A design system built from orthogonal axes
Vibe (bold, premium, friendly, modern) × typeface pairing (six) × palette (twelve) × hero layout (split, centered, fullbleed, compact) = 1,152 combinations, all expressed as body classes over unchanged markup, so the configurator preview, the static demo builds, the edge renderer and the dynamic VDP inherit every axis at once. Keys are stable forever because they ride through Stripe metadata into the dealer's config. A per-vibe copy engine picks voice-level strings deterministically from the dealer id, so two dealers with the same vibe still read differently and rebuilds are stable.
Workers AI chat with a D1 rate limit
The support chat runs an 8B model on Workers AI, grounded exclusively in a hand-written facts file with a system prompt that forbids answering from anything else, forbids saying a "coming" feature works and forbids inventing prices or discounts. It is capped at 40 answers per IP per day through an atomic INSERT … ON CONFLICT DO UPDATE … RETURNING counter in D1 — for the same reason configs live there: a KV counter is a read-modify-write over a possibly stale read, and a rate limit that undercounts is not a rate limit. It fails closed if D1 is unavailable, because an unmetered public inference endpoint is not free.
Honest-failure endpoints
Every dependency that can be unconfigured degrades to a truthful message rather than a broken button: missing billing → "reserve and we'll invoice you"; a domain step that needs a token that is not present → an honest 503 with a manual path; /api/health reports booleans for KV, a real D1 query, the AI binding and the shape of the Stripe secrets — never values.
Operating model
This is the part of the project I think is most transferable. Shopframe is run by an AI operator under a written operating contract that lives in the repo alongside the code. The rules are short and non-negotiable: the operator decides and executes free work without asking; anything with a base fee, a trial that converts to paid, a purchase or an account signup goes into a "Needs Grayson" inbox and the operator moves on to the next unblocked item; nothing is claimed done until it is verified against the live URL or API and the evidence is written into a dated run log; production must stay buildable and healthy, with a rollback rule if it does not.
I own spend, accounts, compliance and direction; the operator owns the queue. That split produced the adversarial launch audit — before enabling any paid acquisition, 72 agents were run over the live site through seven lenses and returned a NO-GO, because the marketing pages were promising things the product could not yet keep. Fifteen fixes went out the same night and every ad and pamphlet claim now checks against a "true today" rule in the brand sheet.
Status and boundaries
- Live and early access. The configurator, provisioning, edge rendering, Site Manager and support chat are live. It is pre-revenue; do not read this page as a traction claim.
- Demo dealers are fictional. Redline Auto Group, Lakeside Motors and Crown Motorcars are invented, deliberately noindexed, and exist to prove the design system.
- Not live yet: Craigslist bulk auto-posting runs dry-run only until a dealer's bulk account is connected; DMS live feeds, a two-way texting inbox, Spanish-language pages and Google Vehicle Listings are designed against environment variables but described as "coming," never implied live.
- Custom domains are routed in the worker; self-serve connection was still being switched on at the time of writing.
- What is not on this page: competitor comparisons, resource identifiers, account details, and anything about spend. Pricing is stated because it is public on the product.
Related
Shopframe sits alongside the other ventures on this site — AI Lunchroom, IT Lunchroom, OpsDesk Lite and CipherG — and is the first of them built on the operating-contract model. The platform underneath this portfolio — content model, generated SEO pages, edge worker and lint gates — is documented at graysond.xyz platform. For how I approach the business side of systems like this see Business Systems and Technical Operations; if you want to talk about building or operating something similar, start at Work with me.
Flight log
- 2026-08One vendor, no dependencies. Everything runs on one Cloudflare account — Workers, KV, D1, R2, Workers AI, Email — and the worker imports nothing from npm. Stripe is called over raw REST. Fewer moving parts, fewer things an autonomous run can break.
- 2026-08-15Found that Publish never published. For provisioned dealers the button exported a JSON file — demo-era behavior. Replaced with an allowlisted, typed, length-capped publish API where identity fields are never client-writable.
- 2026-08-16A lost-update bug became a storage decision. KV reads can be a minute stale, so read-modify-write publishes could silently revert edits. Moved dealer configs to D1 with one overlay column per editable field; KV became a blind-written render cache.
- 2026-08-16Audited the launch adversarially before spending a dollar on ads. 72 agents, 7 lenses, 102 findings, 30 confirmed. Verdict NO-GO — the site promised things the product could not yet do. Fifteen fixes shipped the same night.
- 2026-08-16Support chat that is allowed to say no. Grounded only in a hand-written facts file, capped per IP per day by an atomic D1 counter, and told to refuse discounts and unlisted prices rather than improvise.
- nowEarly access, pre-revenue. Demo dealers stay fictional and noindexed; every claim on the marketing site is checked against what is live today.