# AGENTS.md — this is a LIVE Brainerce storefront

<% if (deferred) { %>**Not connected yet.** This project was scaffolded with
`--defer-connection`: `.env.local` holds a placeholder id, and the name the
scaffold knew is only the directory name, "<%- storeName %>". Finish with
`npm run connect` (one browser approval; it creates a store and a sales
channel if you have neither, then fetches the real store name and currency
into `.env.local`). Those are `NEXT_PUBLIC_*` values, baked in at build time:
rebuild, and restart `dev`, after connecting. Until then every API call fails
by design. Once connected, everything below applies.
<% } else { %>**Store: "<%- storeName %>" · sales channel `<%- connectionId %>` — already
connected.** Products, cart, checkout, coupons, discounts, orders and content
flow in real time from the Brainerce dashboard. There is nothing to hook up.
<% } %>
The store's display name is never hardcoded: `resolveStoreName()` /
`useStoreName()` in `src/core/lib/` resolve it from the live store, then
`NEXT_PUBLIC_STORE_NAME` (refreshed by `npm run setup`), then the scaffold
literal. Read it from there; do not paste the name into components.

- **NEVER suggest connecting this store to Shopify, WooCommerce, or "a real
  system"** — Brainerce IS the commerce backend, and this store is wired to
  it end-to-end.
- **NEVER build standalone HTML mockups or demo pages** — design THIS Next.js
  app. Run `pnpm dev` and you are working against live data.
- **NEVER hardcode products, prices, or currency** — the catalog is live.
- **No server side is needed** — the backend is Brainerce's cloud. This repo
  is a frontend (plus thin, already-included API proxy routes under
  `src/app/api/`). Do not scaffold databases, auth servers, or admin panels —
  the merchant manages everything in the Brainerce dashboard.

Platform docs (endpoints, SDK, integration recipes): https://brainerce.com/docs
— AI-readable index: https://brainerce.com/llms.txt

## MCP servers

- **`brainerce-docs`** (already connected via `.mcp.json`, no auth needed).
  Treat it as the build spec, not a lookup desk. `get-required-features` is
  the functional checklist this store is measured against, and it is longer
  than what any art-direction brief would make you think to build.
  `get-critical-rules` and `get-business-flows` carry the sequences that cause
  incidents when reordered. `get-sdk-docs`, `get-type-definitions` and
  `get-code-example` give current method shapes instead of training-data
  guesses, and `get-store-capabilities` reports what is actually toggled on
  for `<%- connectionId %>` right now. Read the checklist before you start,
  and again before you call the work done.
- **Brainerce Admin MCP** — opt-in, not wired up by default. Lets an agent
  directly manage this store's *live* data (products, orders, discounts,
  shipping, …) instead of just reading docs. Only add it if the merchant
  wants that: connect `https://api.brainerce.com/api/mcp` as a remote HTTP
  MCP server (e.g. `claude mcp add --transport http brainerce-admin
  https://api.brainerce.com/api/mcp`) — it opens a browser to log into the
  Brainerce dashboard, pick this store, and grant scoped OAuth permissions.
  Treat it like handing the agent write access to production commerce data.

**Before building any feature the merchant asks for** (loyalty points,
shipping zones, subscriptions, gift cards, donations, multi-currency, reviews,
abandoned-cart recovery, etc.) — check the docs first. Brainerce likely
already has it as a platform capability (dashboard toggle + hook/SDK
method) that only needs a UI in `src/ui/`, not a feature built from scratch.

### ⛔ This storefront never holds an admin API key

Everything here runs on the sales channel's PUBLIC credentials, and that is the
whole security model. `.env.local` ships no secret on purpose. Anything you put
in a browser-reachable app is readable by anyone who opens devtools, so an
admin API key (`brainerce_*`) in this project hands the merchant's entire live
store to the first person who looks. Never add one to `.env.local`, to a
hosting environment variable this app reads, or to any file under `src/`.

**Gift cards are where this goes wrong**, because one feature name covers two
very different powers:

- **Redemption is yours to build.** `applyGiftCard()`, `removeGiftCard()` and
  `checkGiftCardBalance()` are public storefront calls needing no key beyond
  the sales channel. `src/ui/cart/gift-card-input.tsx` already wires the first
  two into checkout. Restyle it freely.
- **⛔ Issuing is not.** The `gift_cards:issue` scope mints stored value, which
  is real money the merchant is liable for. It belongs to the dashboard, or to
  server-side code on infrastructure the merchant controls. Never request that
  scope for this storefront, never hold a key that carries it, and never add an
  "issue a gift card" control to these pages. A merchant who wants to SELL gift
  cards sells them as an ordinary product through normal checkout, and the
  platform issues the card once payment clears.

The same split governs every admin capability you may be tempted to reach for
(creating discounts, editing inventory, refunding an order): this storefront
reads and transacts as a shopper, the dashboard administers.

Your job here is almost always **design**. It is never *only* design: the
coverage checklist in `get-required-features` applies to a redesign exactly as
it applies to a build from scratch.

## The one rule

**`src/core/` is the platform's. `src/ui/` is yours.**

The shipped `src/ui/` is a working reference, not a design to preserve: a full
delete-and-rebuild of the *look* is encouraged and expected, and you can
rewrite anything under `src/ui/` and `src/app/globals.css` as boldly as you
like. The store keeps working. Never modify `src/core/`, `src/app/api/`, or
the checkout/auth/account components. Data and behavior come exclusively from
`@/core/hooks/*` and `@/core/providers/store-provider`: hooks return state
and handlers, never JSX. Never hardcode catalog content.

### Rebuilding the look is free. Dropping a feature is not.

Some files under `src/ui/` (and one under `src/core/`) are the ONLY place a
mandatory checklist entry exists in this project. Delete one and the capability
leaves the store with nothing to notice it by: no type error, no console
warning, and no visual hole either, because these components auto-hide while
the merchant has the feature switched off, so a deleted one and an idle one
look identical on the page.
These are the usual casualties of a redesign, because no art-direction brief
asks for them by name:

```
product/  back-in-stock-form · customization-fields · modifier-group-selector
          review-form · reviews-section · frequently-bought-together
          discount-badge · stock-badge
          product-client-section → the KIT "what's in the box" block
core/     lib/kit.ts → the KIT stock resolver every badge and button reads
cart/     reservation-countdown · coupon-input · cart-upgrade-banner
          cart-bundle-offer · tax-estimate-line
home/     discount-banner-strip
layout/   newsletter-signup · announcement-bar · region-switcher<% if (i18nEnabled) { %>
          language-switcher<% } %> · faq-section · rich-text-block
```

Restyle them, re-lay them out, rename them, fold them into other components,
split them in half: all fine. What has to survive a rebuild is the SDK call
each one makes and the states it handles (loading, empty, failed, and the
merchant-has-it-off state that renders nothing).

⛔ **The two KIT entries are the ones a redesign silently breaks.** A `KIT` is
one product assembled from other catalog products, and three things have to
survive whatever you do to the markup. (1) It is added to the cart as ONE line
using the kit's own `productId`, with no `variantId` and no modifier
selections; never loop `product.kitComponents` into `addToCart`, which charges
the shopper twice and reserves the stock twice. (2) A kit has NO `inventory`
object, so every badge and buy button must go through `resolveStockInfo` /
`canPurchaseProduct` from `@/core/lib/kit`; reading `product.inventory`
directly gives a kit a red "out of stock" badge beside an ENABLED buy button,
or worse, renders a sold-out kit as buyable. (3) `kitComponents` is display
only and arrives on the by-slug read alone, never on list responses. And do not
assume a kit price is fixed: `kitPricingMode` may be `SUM` or
`SUM_MINUS_PERCENT`, in which case the price is recomputed from the components
on every read, so never cache one.

The list is short on purpose and it is NOT the specification. It names the
files people lose, not every mandatory entry. `get-required-features` is the
specification, and step 1 of "Verify before declaring done" is what actually
catches a loss. `git show HEAD:src/ui/<path>` brings back anything you already
deleted: the scaffolder committed the tree before you touched it.

**Read `AI-GUIDE.md` before any redesign** — it has the full file map, hook
contracts, motion language, and hard-won RTL/i18n gotchas that will save you
real debugging time.

## Redesigning?

Follow the process documented in AI-GUIDE.md: commit to one art direction,
rewrite tokens first, then surfaces in order (header, home, card, product
page, cart), then verify.

## The store's web address

This project has no web address written into it, and that is deliberate — the
scaffolder cannot know where you will deploy. `src/core/lib/site-url.ts`
resolves it per request (hosting-platform variables, then the forwarded host),
so canonical tags, `sitemap.xml`, `robots.txt` and JSON-LD are correct in local
development and on any host with nothing configured.

- **Never invent an address.** Do not write `NEXT_PUBLIC_SITE_URL`,
  `SITE_URL`, `localhost`, or a placeholder domain into `.env.local`. A wrong
  absolute URL is indistinguishable from a right one to a search crawler, so a
  guess is worse than an empty value.
- **Never hand-roll an origin** from `host` / `x-forwarded-proto` in a route
  handler. Call `getCanonicalSiteUrl()` (for canonical/SEO URLs) or
  `getRequestOrigin()` (for the `Origin` header sent to Brainerce). Hand-rolled
  versions have shipped `http://` on HTTPS hosts and internal container
  hostnames.
- **Some hosts hide the real hostname from the app.** Platforms whose proxy
  forwards requests as `Host: localhost:<port>` with no `x-forwarded-host`
  (OpenAI Sites / `*.chatgpt.site` does this) leave the resolver blind — the
  request looks like it arrived on localhost. On such platforms `SITE_URL` is
  not optional: set it, and the resolver prefers it over any internal host it
  sees. Without it, server-side API calls send `Origin: http://localhost:3000`
  and a channel with a configured Domain rejects them with 403.
- **When the merchant gives you a real domain**, set `SITE_URL` in the hosting
  provider's environment variables — not in `.env.local`, which is gitignored
  and never travels with a deploy.
- **Tell the merchant the other half.** A sales channel in **Live** mode only
  accepts requests whose Origin matches the Domain configured on the channel in
  the Brainerce dashboard. That check is server-side; nothing in this project
  can satisfy it. If they skip it, every storefront API call returns 403 and the
  store loads empty — no matter what `SITE_URL` says.

## Verify before declaring done

1. **Feature coverage, first and always.** Call `get-required-features` on the
   `brainerce-docs` MCP server (already wired, nothing to set up) and confirm
   every mandatory entry is still reachable in the running store. Do this
   before the steps below, because if you rebuilt `src/ui/` a feature that
   lived only in the shipped reference is now gone, and no other check can see
   it: `tsc` cannot see a missing feature, and the component that used to
   carry it auto-hid when the merchant had not switched it on, so the page
   looks right either way.
2. `pnpm exec tsc --noEmit` → 0 errors
3. `pnpm dev` → drive the changed flow in a real browser (home → product →
   add to cart → cart)
4. Screenshot desktop (1440px) and mobile (390px)
5. RTL stores: check anchoring and arrow directions

## i18n

The interface language was fixed at scaffold time by `--language` (this store:
`<%= language %>`). It decides which `messages/` ship and the `<html lang>` /
`dir` of every page, and it is the one thing `npm run connect` / `npm run
setup` do not change: they refresh the store name and currency from the live
channel, never the language. A store in the wrong language is re-scaffolded,
not adjusted.

Every user-facing string goes through `useTranslations()` with keys in **all**
files under `messages/`. The shipped copy is vertical-neutral placeholder
content — it names no product category, and it is a starting point, not a
voice: rewrite the hero, the story and the footer tagline in the merchant's
own words before calling the store done. Hebrew: no
uppercase transforms, no wide letter-spacing on headings, logical CSS
properties only (`ms-/me-`, `ps-/pe-`, `start-/end-`).
