create-brainerce-store 1.88.0 → 1.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/index.js +3 -2
  2. package/messages/en.json +21 -0
  3. package/messages/he.json +21 -0
  4. package/package.json +3 -2
  5. package/templates/nextjs/base/.eslintrc.json +2 -0
  6. package/templates/nextjs/base/AI-GUIDE.md +259 -221
  7. package/templates/nextjs/base/TRANSLATIONS.md +6 -0
  8. package/templates/nextjs/base/scripts/agent-docs.mjs +76 -0
  9. package/templates/nextjs/base/scripts/connect.mjs +6 -0
  10. package/templates/nextjs/base/src/app/api/auth/oauth-callback/route.ts +102 -96
  11. package/templates/nextjs/base/src/app/blog/[slug]/{page.tsx.ejs → page.tsx} +41 -144
  12. package/templates/nextjs/base/src/app/blog/page.tsx +187 -0
  13. package/templates/nextjs/base/src/app/blog/rss.xml/route.ts +7 -3
  14. package/templates/nextjs/base/src/app/category/[slug]/page.tsx +26 -11
  15. package/templates/nextjs/base/src/app/faq/page.tsx.ejs +20 -4
  16. package/templates/nextjs/base/src/app/layout.tsx.ejs +18 -50
  17. package/templates/nextjs/base/src/app/page.tsx +109 -75
  18. package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +7 -0
  19. package/templates/nextjs/base/src/app/products/[slug]/page.tsx +267 -220
  20. package/templates/nextjs/base/src/app/products/page.tsx +184 -19
  21. package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +9 -1
  22. package/templates/nextjs/base/src/components/checkout/payment-step.tsx +30 -1
  23. package/templates/nextjs/base/src/components/seo/article-json-ld.tsx +12 -6
  24. package/templates/nextjs/base/src/components/seo/category-json-ld.tsx +9 -10
  25. package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +15 -6
  26. package/templates/nextjs/base/src/components/seo/product-og-meta.tsx +46 -0
  27. package/templates/nextjs/base/src/core/hooks/use-home-data.ts +24 -4
  28. package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +99 -116
  29. package/templates/nextjs/base/src/core/hooks/use-reviews-enabled.ts +17 -0
  30. package/templates/nextjs/base/src/core/lib/listing-query.ts +199 -0
  31. package/templates/nextjs/base/src/core/lib/locale-path.ts +29 -0
  32. package/templates/nextjs/base/src/core/lib/reviews-disabled.ts +19 -0
  33. package/templates/nextjs/base/src/core/lib/server-messages.ts.ejs +34 -0
  34. package/templates/nextjs/base/src/core/lib/store-info.ts +20 -0
  35. package/templates/nextjs/base/src/core/providers/listing-seed.tsx +66 -0
  36. package/templates/nextjs/base/src/ui/product/product-card.tsx +7 -2
  37. package/templates/nextjs/base/src/ui/product/reviews-section.tsx.ejs +6 -2
  38. package/templates/nextjs/designs/atelier/app-overlay/layout.tsx.ejs +18 -50
  39. package/templates/nextjs/designs/atelier/ui/home/testimonials-band.tsx +8 -2
  40. package/templates/nextjs/designs/atelier/ui/product/product-card.tsx +7 -2
  41. package/templates/nextjs/designs/atelier/ui/product/reviews-section.tsx.ejs +6 -2
  42. package/templates/nextjs/ui-canvas/product/reviews-section.tsx.ejs +6 -2
  43. package/templates/nextjs/base/src/app/blog/page.tsx.ejs +0 -277
  44. package/templates/nextjs/base/src/app/products/layout.tsx.ejs +0 -18
@@ -1,221 +1,259 @@
1
- # AI-GUIDE — designing this store safely
2
-
3
- You (an AI coding assistant, or a human designer) have been asked to redesign
4
- this storefront. This guide is the contract that lets you go as bold as you
5
- want without breaking commerce. It was distilled from real agency builds.
6
-
7
- ## The one rule
8
-
9
- **`src/core/` is the platform's. `src/ui/` is yours.**
10
-
11
- Everything under `src/ui/` may be rewritten from scratch — layouts, markup,
12
- classes, animations, entire visual languages. (Canvas-mode stores —
13
- scaffolded with `--canvas` — ship `src/ui/` as bare unstyled skeletons with
14
- `DESIGN ME` markers; same rules apply, you're just starting from zero.) Everything under `src/core/`
15
- (and `src/app/api/`) keeps the store working: cart, checkout, variants,
16
- inventory, auth, SEO, i18n. Never modify it. If a redesign seems to require a
17
- core change, stop and say so instead.
18
-
19
- ## File map
20
-
21
- | Path | Owner | Notes |
22
- |------|-------|-------|
23
- | `src/ui/layout/` | you | header, footer, announcement bar |
24
- | `src/ui/home/` | you | homepage sections |
25
- | `src/ui/product/` | you | card, grid, listing, product page, price, badges, selectors |
26
- | `src/ui/cart/` | you | cart page pieces |
27
- | `src/ui/shared/` | you | loader, image wrapper |
28
- | `src/app/globals.css` | you | tokens + global styles |
29
- | `src/components/ui/` | shared | shadcn/ui primitives — compose them, extend via variants |
30
- | `src/core/**` | platform | hooks, lib, providers — read, never edit |
31
- | `src/components/{checkout,auth,account,seo}` | platform | core-styled in v1; they inherit your tokens |
32
- | `src/app/**` | platform | thin routes; edit only to wire new ui components |
33
-
34
- ## The dialect (shadcn/ui + lucide + cn)
35
-
36
- This store speaks the same component dialect as Lovable / v0 / Bolt output:
37
-
38
- - **`src/components/ui/` holds official shadcn/ui primitives** (button, card,
39
- badge, input, label, textarea, select, checkbox, radio-group, separator,
40
- accordion, dialog, sheet, skeleton, tabs, tooltip). Compose them in `src/ui/`
41
- instead of hand-rolling controls. Need a new look? **Extend via CVA
42
- variants** (add a variant to `buttonVariants`/`badgeVariants`), never by
43
- inline-overriding the same styles in twenty call sites.
44
- - **Icons: lucide-react only.** No inline `<svg>` paths, no emoji-as-icons,
45
- no second icon set. Consistent 16/20/24px sizes.
46
- - **Class merging: `cn()`** from `@/core/lib/utils` (clsx + tailwind-merge) —
47
- it's the same helper shadcn expects, already wired into every primitive.
48
- - **Tokens drive everything**: primitives read `--primary`, `--card`,
49
- `--input`, `--ring`, `--radius`, etc. from `globals.css`. Restyle the store
50
- by changing tokens (and variants), not by scattering literal colors.
51
- - `rounded`, `rounded-md`, `rounded-lg` all derive from `--radius`
52
- (shadcn convention), so corner radius follows the theme automatically.
53
-
54
- ## The hook contract (your data supply)
55
-
56
- UI components get *everything* from `@/core/hooks/*` and
57
- `@/core/providers/store-provider` — never from `@/core/lib` directly:
58
-
59
- - `useHomeData()` → `{ products, banners, loading }`
60
- - `useProductListing()` → products + filters/sort/facets/pagination + handlers
61
- - `useProductPage(product)` → variant/image selection, `priceInfo`, `inventory`,
62
- `quantity`, `handleAddToCart`, `addingToCart`, `addedMessage`, `addToCartError`,
63
- customization + modifier state and errors
64
- - `addToCartError` is `'CART_FULL' | 'FAILED' | null`. **Render it, and render it
65
- outside any `modifierGroups.length > 0` block** — a cart holds at most 50
66
- distinct products and the 51st add is refused, so a design that drops this
67
- leaves the shopper tapping a button that resets and adds nothing. Copy lives
68
- in `productDetail.cartFull` / `productDetail.addToCartFailed`.
69
- - Five more components hold the same message in their OWN local state, because
70
- they add to the cart without this hook: `ui/product/product-card.tsx`,
71
- `ui/product/frequently-bought-together.tsx`, `ui/cart/cart-bundle-offer.tsx`
72
- (all three: `addError`), `app/checkout/page.tsx` (`bumpError`) and
73
- `ui/cart/cart-upgrade-banner.tsx` (`upgradeError`). All five use
74
- `toAddToCartError()` from `@/core/lib/add-to-cart-error`. **Nothing checks that
75
- you kept them** — `check-template-parity.js` compares `@/ui/...` imports and SDK
76
- calls only, so dropping one of these leaves a green build and a silent button.
77
- - `ui/cart/cart-item.tsx` carries the same idea for the two writes it owns,
78
- in its own `lineError` state: a failed quantity change or a failed Remove
79
- used to log to the console and leave the row looking untouched. It uses
80
- `getErrorCode()` from the same file (not `toAddToCartError()` — the 50-line
81
- cap cannot refuse a quantity change, and "we could not add this" would be a
82
- lie about a removal) and four `cart.*` strings, mapped in `LINE_ERROR_KEYS`:
83
- `notEnoughStock`, `itemUnavailable`, `quantityUpdateFailed`, `removeFailed`.
84
- The first two are for refusals a retry can never clear, so do not collapse
85
- them into the generic one. Nothing checks that you kept any of this either.
86
- - `ui/cart/cart-upgrade-banner.tsx` additionally swaps one cart line for
87
- another, and the ORDER of its two SDK calls is a correctness property, not a
88
- style choice: it **adds the upgrade first and removes the original second**,
89
- so a failed add leaves the cart untouched instead of losing the shopper's
90
- line. It also carries `upgradeAdded`, which stops a retry adding the upgrade
91
- twice, and a third message, `cart.upgradeOriginalNotRemoved`, for the case
92
- where the add landed and the removal did not. If you rebuild that component,
93
- keep all three; the reasoning is in the JSDoc on `handleUpgrade`.
94
- - `useCartPage()` / `useCart()` → `{ cart, itemCount, totals, refreshCart }`
95
-
96
- Hooks return data and handlers, never JSX. All catalog content (names, prices,
97
- images, stock) comes from them — **never hardcode products**.
98
-
99
- ## Verification loop (before you declare anything done)
100
-
101
- 1. **Feature coverage.** Call `get-required-features` on the `brainerce-docs`
102
- MCP server (wired by the scaffolder in `.mcp.json`, no setup) and confirm
103
- every mandatory entry is still reachable. First step, not last: you were
104
- invited to rewrite `src/ui/` from scratch, and a feature that lived only in
105
- the shipped reference is gone now with nothing to notice it by. `tsc`
106
- cannot see a missing feature, and these components render nothing while the
107
- merchant has the feature off, so a deleted one and an idle one look the
108
- same. `CLAUDE.md` lists the files people usually lose; the MCP tool is the
109
- actual specification.
110
- 2. `pnpm exec tsc --noEmit` → must be 0.
111
- 3. `pnpm dev` → drive the real flow you changed: home → listing → product →
112
- add to cart → cart → checkout page renders.
113
- 4. Screenshot desktop (1440) AND mobile (390), in the store's language.
114
- 5. If the store is RTL: check nothing is left-anchored that should be
115
- right-anchored, and directional arrows point with the reading direction.
116
-
117
- ## The quality bar (distilled from Lovable / v0 / Bolt system prompts)
118
-
119
- Beautiful is the top priority — the first render must wow. Hard rules:
120
-
121
- 1. **Design system first**: define all HSL semantic tokens in globals.css
122
- BEFORE styling any component. Never direct colors (`text-white`,
123
- `bg-black`) in classNames — tokens only.
124
- 2. **Color budget: exactly 3-5 colors.** 1 brand color, 2-3 neutrals, 1-2
125
- accents. Never purple/violet unless the merchant asked. No gradients by
126
- default — solid colors; gradients only as subtle accents if requested.
127
- 3. **Max 2 font families** (1 display + 1 body, both with the store's
128
- script). Body line-height 1.4-1.6. Nothing below 14px.
129
- 4. **Mobile-first**; flexbox by default, grid for true 2D; no absolute
130
- positioning unless necessary.
131
- 5. **Tailwind discipline**: spacing scale only (`p-4`, never `p-[16px]`),
132
- `gap-*` over margins between siblings, `text-balance` on headlines.
133
- 6. **Real imagery everywhere** — product photos from the catalog do the
134
- heavy lifting; free stock hotlinks (Pexels/Unsplash) for editorial
135
- sections. NEVER abstract filler shapes (gradient circles, blobs).
136
- NEVER emojis as icons — one icon set, consistent 16/20/24px sizes.
137
- 7. **Content-rich**: no section may render empty or thin. Empty states are
138
- designed states with copy and structure, not blank space.
139
- 8. Component variants in the design system, not inline overrides.
140
- 9. Semantic HTML + ARIA + alt text + `sr-only` labels — always.
141
- 10. **Verify with your eyes**: screenshot after each major surface, critique
142
- it ("would a Lovable user accept this?"), fix, and only then move on.
143
-
144
-
145
- ## Motion language (CSS-first)
146
-
147
- The store should feel alive, premium and intentional — every animation guides
148
- attention, confirms an action, or rewards exploration. Rules:
149
-
150
- - **Timing tokens** (use these, never ad-hoc numbers): 80ms instant feedback ·
151
- 180ms hover · 300ms UI state change · 500-600ms section entrance · 900ms
152
- hero only. Stagger siblings 40-80ms.
153
- - **Easing**: entrances `cubic-bezier(0.16, 1, 0.3, 1)` (out-expo); springy
154
- pops `cubic-bezier(0.34, 1.56, 0.64, 1)` (out-back); loops and hovers
155
- ease-in-out. Exits faster than entrances.
156
- - **Reveals**: below-the-fold sections animate on viewport entry
157
- (IntersectionObserver — see `src/ui/shared/reveal.tsx`), never tied to
158
- scroll distance. One orchestrated reveal per section beats scattered
159
- effects; `viewport once` — do not re-animate on every scroll.
160
- - **Micro-interactions**: CSS/WAAPI first (see `src/ui/shared/fly-to-cart.ts`
161
- for the pattern) — ripple, bump, image zoom, quick-add slide-up are all
162
- achievable without a library.
163
- - **Pointer effects** (magnetic buttons, tilt cards, custom cursor, mouse
164
- parallax): desktop only — gate on `(pointer: fine)` and no-op under
165
- `prefers-reduced-motion`. `will-change: transform` ONLY on elements animated
166
- per-frame; never sprinkle it broadly.
167
- - **Never `transition-all`** — always name the property
168
- (`transition-transform`, `transition-opacity`). Animate only compositor
169
- properties (`transform`, `opacity`) — never layout properties
170
- (width/height/top/margin). 60fps is non-negotiable.
171
- - **Ambient effects** (gradient blobs, particles): maximum one per page, hero
172
- only — never on inner pages.
173
- - **framer-motion / GSAP are not installed.** They are allowed, but they are
174
- new runtime dependencies — flag it to the user before adding.
175
-
176
- ## Hard-won gotchas (each one cost a real debugging session)
177
-
178
- - **RTL + cloned images**: `next/image` renders its `<img>` with `inset: 0`.
179
- If you clone one for an animation and set `left`, the leftover `right: 0`
180
- wins in RTL documents (over-constrained boxes resolve to the RIGHT).
181
- Set `inset: 'auto'` before positioning.
182
- - **Never name a custom class `overline`** — it is a Tailwind utility
183
- (`text-decoration-line: overline`) and will draw a line above your text.
184
- - **`rounded` / `rounded-md` / `rounded-lg` all follow `--radius`** (shadcn
185
- convention, wired in tailwind.config.ts) — change the token, not the
186
- classes, when you want sharper or softer corners.
187
- - **Hebrew has no uppercase** — remove `text-transform: uppercase` and wide
188
- `letter-spacing` from heading styles; they are Latin idioms that distort
189
- Hebrew (tracking is acceptable only at caption sizes).
190
- - **Logical properties only**: `ms-/me-`, `ps-/pe-`, `start-/end-`,
191
- `text-start/end`. Never `ml-/mr-`, `left-/right-`, `text-left/right`,
192
- `flex-row-reverse`.
193
- - **Motion**: wrap every animation in a `prefers-reduced-motion: reduce`
194
- guard. One well-orchestrated reveal beats scattered effects.
195
- - **Fonts**: any face used for body or headlines must include the store's
196
- script (e.g. Google Fonts `hebrew` subset). Latin-only display faces are
197
- accent-only.
198
-
199
- ## Never do
200
-
201
- - Modify `src/core/`, `src/app/api/`, or checkout/auth/account internals.
202
- - Remove accessibility attributes (`aria-*`, `role`, `alt`, focus styles).
203
- - Hardcode catalog content, prices, or currency symbols.
204
- - Hardcode the store name. `SiteHeader` / `SiteFooter` receive it as a prop
205
- from the layout; anywhere else use `useStoreName()` (client) or
206
- `resolveStoreName(storeInfo)` (server) from `src/core/lib/`. The name the
207
- scaffold knew may be nothing more than the project directory.
208
- - Swallow the add-to-cart / checkout error states — restyle them, keep them.
209
- - Drop a `src/ui/` component that is the only place a mandatory feature
210
- exists. Rebuilding the look is free; losing the SDK call inside it is not,
211
- and it is silent. See "Rebuilding the look is free" in `CLAUDE.md` for the
212
- files people usually lose, and `get-required-features` for the real list.
213
- - Introduce new runtime dependencies without flagging it.
214
-
215
- ## Adding things
216
-
217
- - New homepage section → new component in `src/ui/home/`, composed in
218
- `home-client.tsx`, data via existing hooks only.
219
- - New UI copy → add keys to **both** `messages/he.json` and
220
- `messages/en.json` (or all supported locales), use `useTranslations()`.
221
- - New microinteraction → CSS-first; JS only for things CSS cannot do.
1
+ # AI-GUIDE — designing this store safely
2
+
3
+ You (an AI coding assistant, or a human designer) have been asked to redesign
4
+ this storefront. This guide is the contract that lets you go as bold as you
5
+ want without breaking commerce. It was distilled from real agency builds.
6
+
7
+ ## The one rule
8
+
9
+ **`src/core/` is the platform's. `src/ui/` is yours.**
10
+
11
+ Everything under `src/ui/` may be rewritten from scratch — layouts, markup,
12
+ classes, animations, entire visual languages. (Canvas-mode stores —
13
+ scaffolded with `--canvas` — ship `src/ui/` as bare unstyled skeletons with
14
+ `DESIGN ME` markers; same rules apply, you're just starting from zero.) Everything under `src/core/`
15
+ (and `src/app/api/`) keeps the store working: cart, checkout, variants,
16
+ inventory, auth, SEO, i18n. Never modify it. If a redesign seems to require a
17
+ core change, stop and say so instead.
18
+
19
+ ## File map
20
+
21
+ | Path | Owner | Notes |
22
+ |------|-------|-------|
23
+ | `src/ui/layout/` | you | header, footer, announcement bar |
24
+ | `src/ui/home/` | you | homepage sections |
25
+ | `src/ui/product/` | you | card, grid, listing, product page, price, badges, selectors |
26
+ | `src/ui/cart/` | you | cart page pieces |
27
+ | `src/ui/shared/` | you | loader, image wrapper |
28
+ | `src/app/globals.css` | you | tokens + global styles |
29
+ | `src/components/ui/` | shared | shadcn/ui primitives — compose them, extend via variants |
30
+ | `src/core/**` | platform | hooks, lib, providers — read, never edit |
31
+ | `src/components/{checkout,auth,account,seo}` | platform | core-styled in v1; they inherit your tokens |
32
+ | `src/app/**` | platform | thin routes; edit only to wire new ui components |
33
+
34
+ ## The dialect (shadcn/ui + lucide + cn)
35
+
36
+ This store speaks the same component dialect as Lovable / v0 / Bolt output:
37
+
38
+ - **`src/components/ui/` holds official shadcn/ui primitives** (button, card,
39
+ badge, input, label, textarea, select, checkbox, radio-group, separator,
40
+ accordion, dialog, sheet, skeleton, tabs, tooltip). Compose them in `src/ui/`
41
+ instead of hand-rolling controls. Need a new look? **Extend via CVA
42
+ variants** (add a variant to `buttonVariants`/`badgeVariants`), never by
43
+ inline-overriding the same styles in twenty call sites.
44
+ - **Icons: lucide-react only.** No inline `<svg>` paths, no emoji-as-icons,
45
+ no second icon set. Consistent 16/20/24px sizes.
46
+ - **Class merging: `cn()`** from `@/core/lib/utils` (clsx + tailwind-merge) —
47
+ it's the same helper shadcn expects, already wired into every primitive.
48
+ - **Tokens drive everything**: primitives read `--primary`, `--card`,
49
+ `--input`, `--ring`, `--radius`, etc. from `globals.css`. Restyle the store
50
+ by changing tokens (and variants), not by scattering literal colors.
51
+ - `rounded`, `rounded-md`, `rounded-lg` all derive from `--radius`
52
+ (shadcn convention), so corner radius follows the theme automatically.
53
+
54
+ ## The hook contract (your data supply)
55
+
56
+ UI components get *everything* from `@/core/hooks/*` and
57
+ `@/core/providers/store-provider` — never from `@/core/lib` directly, except
58
+ the approved presentation helpers listed (as `!@/core/lib/...`) in the `src/ui`
59
+ `no-restricted-imports` rule in `.eslintrc.json`. `next build` runs that lint,
60
+ so importing any other `@/core/lib` module from `src/ui` fails the build:
61
+
62
+ - `useHomeData()` → `{ products, banners, loading }`
63
+ - `useProductListing()` → products + filters/sort/facets/pagination + handlers
64
+ - `useProductPage(product)` → variant/image selection, `priceInfo`, `inventory`,
65
+ `quantity`, `handleAddToCart`, `addingToCart`, `addedMessage`, `addToCartError`,
66
+ customization + modifier state and errors
67
+ - `addToCartError` is `'CART_FULL' | 'FAILED' | null`. **Render it, and render it
68
+ outside any `modifierGroups.length > 0` block** — a cart holds at most 50
69
+ distinct products and the 51st add is refused, so a design that drops this
70
+ leaves the shopper tapping a button that resets and adds nothing. Copy lives
71
+ in `productDetail.cartFull` / `productDetail.addToCartFailed`.
72
+ - Five more components hold the same message in their OWN local state, because
73
+ they add to the cart without this hook: `ui/product/product-card.tsx`,
74
+ `ui/product/frequently-bought-together.tsx`, `ui/cart/cart-bundle-offer.tsx`
75
+ (all three: `addError`), `app/checkout/page.tsx` (`bumpError`) and
76
+ `ui/cart/cart-upgrade-banner.tsx` (`upgradeError`). All five use
77
+ `toAddToCartError()` from `@/core/lib/add-to-cart-error`. **Nothing checks that
78
+ you kept them** — `check-template-parity.js` compares `@/ui/...` imports and SDK
79
+ calls only, so dropping one of these leaves a green build and a silent button.
80
+ - `ui/cart/cart-item.tsx` carries the same idea for the two writes it owns,
81
+ in its own `lineError` state: a failed quantity change or a failed Remove
82
+ used to log to the console and leave the row looking untouched. It uses
83
+ `getErrorCode()` from the same file (not `toAddToCartError()` — the 50-line
84
+ cap cannot refuse a quantity change, and "we could not add this" would be a
85
+ lie about a removal) and four `cart.*` strings, mapped in `LINE_ERROR_KEYS`:
86
+ `notEnoughStock`, `itemUnavailable`, `quantityUpdateFailed`, `removeFailed`.
87
+ The first two are for refusals a retry can never clear, so do not collapse
88
+ them into the generic one. Nothing checks that you kept any of this either.
89
+ - `ui/cart/cart-upgrade-banner.tsx` additionally swaps one cart line for
90
+ another, and the ORDER of its two SDK calls is a correctness property, not a
91
+ style choice: it **adds the upgrade first and removes the original second**,
92
+ so a failed add leaves the cart untouched instead of losing the shopper's
93
+ line. It also carries `upgradeAdded`, which stops a retry adding the upgrade
94
+ twice, and a third message, `cart.upgradeOriginalNotRemoved`, for the case
95
+ where the add landed and the removal did not. If you rebuild that component,
96
+ keep all three; the reasoning is in the JSDoc on `handleUpgrade`.
97
+ - `useCartPage()` / `useCart()` → `{ cart, itemCount, totals, refreshCart }`
98
+
99
+ Hooks return data and handlers, never JSX. All catalog content (names, prices,
100
+ images, stock) comes from them — **never hardcode products**.
101
+
102
+ ## Verification loop (before you declare anything done)
103
+
104
+ 1. **Feature coverage.** Call `get-required-features` on the `brainerce-docs`
105
+ MCP server (wired by the scaffolder in `.mcp.json`, no setup) and confirm
106
+ every mandatory entry is still reachable. First step, not last: you were
107
+ invited to rewrite `src/ui/` from scratch, and a feature that lived only in
108
+ the shipped reference is gone now with nothing to notice it by. `tsc`
109
+ cannot see a missing feature, and these components render nothing while the
110
+ merchant has the feature off, so a deleted one and an idle one look the
111
+ same. `CLAUDE.md` lists the files people usually lose; the MCP tool is the
112
+ actual specification.
113
+ 2. `pnpm exec tsc --noEmit` → must be 0.
114
+ 3. `pnpm dev` → drive the real flow you changed: home → listing → product →
115
+ add to cart → cart → checkout page renders.
116
+ 4. Screenshot desktop (1440) AND mobile (390), in the store's language.
117
+ 5. If the store is RTL: check nothing is left-anchored that should be
118
+ right-anchored, and directional arrows point with the reading direction.
119
+
120
+ ## The quality bar (distilled from Lovable / v0 / Bolt system prompts)
121
+
122
+ Beautiful is the top priority — the first render must wow. Hard rules:
123
+
124
+ 1. **Design system first**: define all HSL semantic tokens in globals.css
125
+ BEFORE styling any component. Never direct colors (`text-white`,
126
+ `bg-black`) in classNames — tokens only.
127
+ 2. **Color budget: exactly 3-5 colors.** 1 brand color, 2-3 neutrals, 1-2
128
+ accents. Never purple/violet unless the merchant asked. No gradients by
129
+ default — solid colors; gradients only as subtle accents if requested.
130
+ 3. **Max 2 font families** (1 display + 1 body, both with the store's
131
+ script). Body line-height 1.4-1.6. Nothing below 14px.
132
+ 4. **Mobile-first**; flexbox by default, grid for true 2D; no absolute
133
+ positioning unless necessary.
134
+ 5. **Tailwind discipline**: spacing scale only (`p-4`, never `p-[16px]`),
135
+ `gap-*` over margins between siblings, `text-balance` on headlines.
136
+ 6. **Real imagery everywhere** — product photos from the catalog do the
137
+ heavy lifting; free stock hotlinks (Pexels/Unsplash) for editorial
138
+ sections. NEVER abstract filler shapes (gradient circles, blobs).
139
+ NEVER emojis as icons — one icon set, consistent 16/20/24px sizes.
140
+ 7. **Content-rich**: no section may render empty or thin. Empty states are
141
+ designed states with copy and structure, not blank space.
142
+ 8. Component variants in the design system, not inline overrides.
143
+ 9. Semantic HTML + ARIA + alt text + `sr-only` labels — always.
144
+ 10. **Verify with your eyes**: screenshot after each major surface, critique
145
+ it ("would a Lovable user accept this?"), fix, and only then move on.
146
+
147
+
148
+ ## Motion language (CSS-first)
149
+
150
+ The store should feel alive, premium and intentional — every animation guides
151
+ attention, confirms an action, or rewards exploration. Rules:
152
+
153
+ - **Timing tokens** (use these, never ad-hoc numbers): 80ms instant feedback ·
154
+ 180ms hover · 300ms UI state change · 500-600ms section entrance · 900ms
155
+ hero only. Stagger siblings 40-80ms.
156
+ - **Easing**: entrances `cubic-bezier(0.16, 1, 0.3, 1)` (out-expo); springy
157
+ pops `cubic-bezier(0.34, 1.56, 0.64, 1)` (out-back); loops and hovers
158
+ ease-in-out. Exits faster than entrances.
159
+ - **Reveals**: below-the-fold sections animate on viewport entry
160
+ (IntersectionObserver — see `src/ui/shared/reveal.tsx`), never tied to
161
+ scroll distance. One orchestrated reveal per section beats scattered
162
+ effects; `viewport once` — do not re-animate on every scroll.
163
+ - **Micro-interactions**: CSS/WAAPI first (see `src/ui/shared/fly-to-cart.ts`
164
+ for the pattern) — ripple, bump, image zoom, quick-add slide-up are all
165
+ achievable without a library.
166
+ - **Pointer effects** (magnetic buttons, tilt cards, custom cursor, mouse
167
+ parallax): desktop only — gate on `(pointer: fine)` and no-op under
168
+ `prefers-reduced-motion`. `will-change: transform` ONLY on elements animated
169
+ per-frame; never sprinkle it broadly.
170
+ - **Never `transition-all`** — always name the property
171
+ (`transition-transform`, `transition-opacity`). Animate only compositor
172
+ properties (`transform`, `opacity`) — never layout properties
173
+ (width/height/top/margin). 60fps is non-negotiable.
174
+ - **Ambient effects** (gradient blobs, particles): maximum one per page, hero
175
+ only — never on inner pages.
176
+ - **framer-motion / GSAP are not installed.** They are allowed, but they are
177
+ new runtime dependencies — flag it to the user before adding.
178
+
179
+ ## Hard-won gotchas (each one cost a real debugging session)
180
+
181
+ - **RTL + cloned images**: `next/image` renders its `<img>` with `inset: 0`.
182
+ If you clone one for an animation and set `left`, the leftover `right: 0`
183
+ wins in RTL documents (over-constrained boxes resolve to the RIGHT).
184
+ Set `inset: 'auto'` before positioning.
185
+ - **Never name a custom class `overline`** — it is a Tailwind utility
186
+ (`text-decoration-line: overline`) and will draw a line above your text.
187
+ - **`rounded` / `rounded-md` / `rounded-lg` all follow `--radius`** (shadcn
188
+ convention, wired in tailwind.config.ts) — change the token, not the
189
+ classes, when you want sharper or softer corners.
190
+ - **Hebrew has no uppercase** — remove `text-transform: uppercase` and wide
191
+ `letter-spacing` from heading styles; they are Latin idioms that distort
192
+ Hebrew (tracking is acceptable only at caption sizes).
193
+ - **Logical properties only**: `ms-/me-`, `ps-/pe-`, `start-/end-`,
194
+ `text-start/end`. Never `ml-/mr-`, `left-/right-`, `text-left/right`,
195
+ `flex-row-reverse`.
196
+ - **Motion**: wrap every animation in a `prefers-reduced-motion: reduce`
197
+ guard. One well-orchestrated reveal beats scattered effects.
198
+ - **Fonts**: any face used for body or headlines must include the store's
199
+ script (e.g. Google Fonts `hebrew` subset). Latin-only display faces are
200
+ accent-only.
201
+ - **Public paths go through `localePath(locale, '/x')`** (`@/core/lib/locale-path`),
202
+ never `` `/${locale}/x` ``. The default locale arrives as a real `params.locale`
203
+ too, and the middleware 308-redirects `/{default}/x` to `/x`, so a canonical
204
+ tag, breadcrumb link or JSON-LD url built the second way points at a redirect.
205
+ - **Every indexable page sets its own `alternates.canonical`; the root layout sets
206
+ none.** A layout-level canonical is inherited by every page without its own and
207
+ tells Google they are duplicates of it. `openGraph` on a page REPLACES the
208
+ layout's object (it does not merge), so repeat `siteName` there.
209
+ - **Product meta description: `deriveSeoDescription(product)`**, never
210
+ `product.seoDescription` alone. The merchant's text can arrive under either
211
+ `seoDescription` or `metaDescription`; reading one silently falls back to the
212
+ start of the description.
213
+ - **`og:type: product` and the `product:*` price/availability tags are real
214
+ `<meta property>` elements** (`components/seo/product-og-meta.tsx`). Next's
215
+ `openGraph.type` has no `product`, and `metadata.other` renders `name=`, which
216
+ the crawlers that read these tags ignore. Do not also set `openGraph.type` on
217
+ the product page, or it carries two `og:type` tags.
218
+ - **`/products` and the home grid are server-seeded.** `app/products/page.tsx`
219
+ reads the URL (search, filters, sort, `?page=`) and passes the first render's
220
+ products through `ListingSeedProvider`; `core/lib/listing-query.ts` is the ONE
221
+ function turning a URL into a query, shared by that page and
222
+ `useProductListing`. Do not fork it, and do not put the listing back behind
223
+ `'use client'` or a `<Suspense>` fallback: the server HTML must contain the
224
+ products, not a spinner. A seeded hook starts with `loading` false.
225
+ - **Metadata, JSON-LD and breadcrumb strings come from the message files**
226
+ (`getServerNamespace('seo', locale)`), not from literals or a
227
+ `locale === 'he'` ternary (English for every third language). The visible
228
+ breadcrumb and the BreadcrumbList JSON-LD are built from the SAME items; the
229
+ JSON-LD components take a required `breadcrumbs` prop.
230
+
231
+ ## Never do
232
+
233
+ - Modify `src/core/`, `src/app/api/`, or checkout/auth/account internals.
234
+ - Remove accessibility attributes (`aria-*`, `role`, `alt`, focus styles).
235
+ - Hardcode catalog content, prices, or currency symbols.
236
+ - Hardcode the store name. `SiteHeader` / `SiteFooter` receive it as a prop
237
+ from the layout; anywhere else use `useStoreName()` (client) or
238
+ `resolveStoreName(storeInfo)` (server) from `src/core/lib/`. The name the
239
+ scaffold knew may be nothing more than the project directory.
240
+ - Swallow the add-to-cart / checkout error states — restyle them, keep them.
241
+ - Drop a `src/ui/` component that is the only place a mandatory feature
242
+ exists. Rebuilding the look is free; losing the SDK call inside it is not,
243
+ and it is silent. See "Rebuilding the look is free" in `CLAUDE.md` for the
244
+ files people usually lose, and `get-required-features` for the real list.
245
+ - Introduce new runtime dependencies without flagging it.
246
+
247
+ ## Adding things
248
+
249
+ - New homepage section → new component in `src/ui/home/`, composed in
250
+ `home-client.tsx`, data via existing hooks only.
251
+ - New place that shows a star rating or a review count (a card, a banner,
252
+ structured data) → gate it with `useReviewsEnabled()` (client) or
253
+ `reviewsAreOn(storeInfo)` from `src/core/lib/store-info` (server). Products
254
+ keep their stored `avgRating` and `reviewCount` while the merchant has reviews
255
+ switched off, so `reviewCount > 0` alone would show a rating for reviews nobody
256
+ can open.
257
+ - New UI copy → add keys to **both** `messages/he.json` and
258
+ `messages/en.json` (or all supported locales), use `useTranslations()`.
259
+ - New microinteraction → CSS-first; JS only for things CSS cannot do.
@@ -79,6 +79,12 @@ The `StoreProvider` (`src/providers/store-provider.tsx`) calls `client.setLocale
79
79
 
80
80
  You never overlay translations yourself — the SDK does it on every request.
81
81
 
82
+ ### Meta descriptions
83
+
84
+ On a product read, `seoDescription` is the **effective** meta description: the active locale's translation when there is one, otherwise the merchant's stored text (which is also served as `metaDescription`). It is `null` only when the merchant wrote none. Never read one of the two names alone: call `deriveSeoDescription(product)`, which takes whichever is present and falls back to a stripped `description`, then the name. The product page (`src/app/products/[slug]/page.tsx`) does exactly that.
85
+
86
+ When you WRITE a product translation, send either name: `metaDescription` is accepted as `seoDescription`.
87
+
82
88
  ## RTL (Hebrew, Arabic, Persian, Urdu, Yiddish)
83
89
 
84
90
  `src/i18n.ts` exports `getDirection(locale)` that delegates to the SDK's `getDirectionForLocale()`. The layout uses it on `<html dir={…}>`:
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Keep CLAUDE.md and AGENTS.md truthful once `npm run connect` has connected
3
+ * the store.
4
+ *
5
+ * WHY THIS EXISTS. A project scaffolded with `--defer-connection` starts both
6
+ * files with a "Not connected yet" paragraph, rendered ONCE at scaffold time.
7
+ * `connect` then wrote the id into `.env.local` and never touched them, so a
8
+ * connected store kept telling every coding agent that read it that it was not
9
+ * connected. The agent believed the file, concluded the store had no channel and
10
+ * asked the user to connect it again.
11
+ *
12
+ * ⛔ THE PATTERN BELOW IS COUPLED TO TWO TEMPLATES. It must keep matching the
13
+ * deferred paragraph in `CLAUDE.md.ejs` and `AGENTS.md.ejs` (first words
14
+ * "**Not connected yet.**", last sentence "Once connected, everything below
15
+ * applies."). `scripts/check-template-parity.js` renders both files deferred and
16
+ * fails `prepublishOnly` if this stops matching, because a pattern that silently
17
+ * matches nothing is exactly the bug it fixes.
18
+ *
19
+ * Only the channel id goes into the prose, and only when it has the shape of one:
20
+ * the store NAME is owner-controlled text, and these files are read by agents as
21
+ * instructions. The name is not needed: the doc already says to read it from
22
+ * `resolveStoreName()`.
23
+ */
24
+
25
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
26
+ import { resolve } from 'node:path';
27
+
28
+ export const AGENT_DOC_FILES = ['CLAUDE.md', 'AGENTS.md'];
29
+
30
+ export const NOT_CONNECTED_PARAGRAPH =
31
+ /\*\*Not connected yet\.\*\*[\s\S]*?Once connected, everything below applies\./;
32
+
33
+ const CHANNEL_ID = /^vc_[A-Za-z0-9_-]+$/;
34
+
35
+ /** The paragraph a connected storefront's docs open with. Mirrors the
36
+ * non-deferred branch of the two templates, minus the store name. */
37
+ export function connectedParagraph(connectionId) {
38
+ return (
39
+ `**Sales channel \`${connectionId}\` — already connected.** Products, cart,\n` +
40
+ 'checkout, coupons, discounts, orders and content flow in real time from the\n' +
41
+ 'Brainerce dashboard. There is nothing to hook up.'
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Rewrite the "Not connected yet" paragraph in each agent doc under `dir`.
47
+ * Returns the names of the files it changed. Never throws: a doc that cannot be
48
+ * read or written must not undo a connection that already succeeded.
49
+ *
50
+ * A doc whose paragraph was edited by hand no longer matches and is left alone.
51
+ * A project that already connected before this script existed is not helped
52
+ * either: it carries the old `connect.mjs`, which never calls this. Its owner
53
+ * deletes the paragraph by hand (see the create-brainerce-store CHANGELOG).
54
+ */
55
+ export function markAgentDocsConnected(dir, connectionId) {
56
+ if (typeof connectionId !== 'string' || !CHANNEL_ID.test(connectionId)) return [];
57
+ const changed = [];
58
+ for (const name of AGENT_DOC_FILES) {
59
+ const path = resolve(dir, name);
60
+ try {
61
+ if (!existsSync(path)) continue;
62
+ const body = readFileSync(path, 'utf8');
63
+ if (!NOT_CONNECTED_PARAGRAPH.test(body)) continue;
64
+ // A function replacement, so `$` sequences in the id can never be read as
65
+ // replacement patterns.
66
+ const next = body.replace(NOT_CONNECTED_PARAGRAPH, () => connectedParagraph(connectionId));
67
+ // Keep a CRLF file CRLF (the paragraph is matched across either ending).
68
+ const out = body.includes('\r\n') ? next.replace(/\r?\n/g, '\r\n') : next;
69
+ writeFileSync(path, out);
70
+ changed.push(name);
71
+ } catch {
72
+ // Best effort by design.
73
+ }
74
+ }
75
+ return changed;
76
+ }