create-brainerce-store 1.50.1 → 1.51.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.
- package/dist/index.js +171 -66
- package/messages/en.json +4 -1
- package/messages/he.json +4 -1
- package/package.json +1 -1
- package/templates/nextjs/base/.claude/commands/design.md +51 -0
- package/templates/nextjs/base/.env.local.ejs +5 -0
- package/templates/nextjs/base/.eslintrc.json +57 -3
- package/templates/nextjs/base/AGENTS.md +41 -0
- package/templates/nextjs/base/AI-GUIDE.md +94 -0
- package/templates/nextjs/base/CLAUDE.md +42 -0
- package/templates/nextjs/base/TRANSLATIONS.md +1 -1
- package/templates/nextjs/base/next.config.ts +18 -10
- package/templates/nextjs/base/package.json.ejs +5 -1
- package/templates/nextjs/base/src/app/account/page.tsx +5 -5
- package/templates/nextjs/base/src/app/api/auth/logout/route.ts +1 -1
- package/templates/nextjs/base/src/app/api/auth/reset-password/route.ts +2 -2
- package/templates/nextjs/base/src/app/api/store/[...path]/route.ts +1 -1
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +4 -4
- package/templates/nextjs/base/src/app/blog/[slug]/page.tsx.ejs +4 -4
- package/templates/nextjs/base/src/app/blog/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/cart/page.tsx +2 -173
- package/templates/nextjs/base/src/app/checkout/page.tsx +12 -11
- package/templates/nextjs/base/src/app/contact/page.tsx +3 -3
- package/templates/nextjs/base/src/app/faq/page.tsx.ejs +2 -2
- package/templates/nextjs/base/src/app/forgot-password/page.tsx +4 -4
- package/templates/nextjs/base/src/app/layout.tsx.ejs +16 -14
- package/templates/nextjs/base/src/app/login/page.tsx +4 -4
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +19 -12
- package/templates/nextjs/base/src/app/page.tsx +4 -4
- package/templates/nextjs/base/src/app/pages/[slug]/page.tsx.ejs +3 -3
- package/templates/nextjs/base/src/app/payment-complete/page.tsx +1 -1
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +6 -6
- package/templates/nextjs/base/src/app/products/page.tsx +4 -623
- package/templates/nextjs/base/src/app/register/page.tsx +4 -4
- package/templates/nextjs/base/src/app/reset-password/page.tsx +5 -5
- package/templates/nextjs/base/src/app/sitemap.ts +1 -1
- package/templates/nextjs/base/src/app/verify-email/page.tsx +5 -5
- package/templates/nextjs/base/src/components/account/address-book.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-customizations.tsx +3 -3
- package/templates/nextjs/base/src/components/account/order-history.tsx +13 -5
- package/templates/nextjs/base/src/components/account/order-payment-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-shipping-block.tsx +2 -2
- package/templates/nextjs/base/src/components/account/order-status-timeline.tsx +2 -2
- package/templates/nextjs/base/src/components/account/profile-section.tsx +3 -3
- package/templates/nextjs/base/src/components/auth/login-form.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +4 -4
- package/templates/nextjs/base/src/components/auth/register-form.tsx +4 -4
- package/templates/nextjs/base/src/components/brainerce-bot.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +24 -3
- package/templates/nextjs/base/src/components/checkout/custom-fields-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/delivery-method-step.tsx +2 -2
- package/templates/nextjs/base/src/components/checkout/order-bump-card.tsx +4 -4
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +6 -6
- package/templates/nextjs/base/src/components/checkout/pickup-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +3 -3
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +3 -3
- package/templates/nextjs/base/src/components/seo/organization-json-ld.tsx +2 -2
- package/templates/nextjs/base/src/components/seo/product-json-ld.tsx +3 -3
- package/templates/nextjs/base/src/core/hooks/use-cart-page.ts +58 -0
- package/templates/nextjs/base/src/core/hooks/use-home-data.ts +51 -0
- package/templates/nextjs/base/src/core/hooks/use-product-listing.ts +295 -0
- package/templates/nextjs/base/src/core/hooks/use-product-page.ts +278 -0
- package/templates/nextjs/base/src/{hooks → core/hooks}/use-search.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/brainerce.ts.ejs +1 -1
- package/templates/nextjs/base/src/core/lib/image-hosts.ts +7 -0
- package/templates/nextjs/base/src/core/lib/product-options.ts +121 -0
- package/templates/nextjs/base/src/{lib → core/lib}/resolve-currency.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/safe-redirect.ts +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/sanitize-html.ts +12 -0
- package/templates/nextjs/base/src/{lib → core/lib}/sanitize.ts.ejs +1 -1
- package/templates/nextjs/base/src/{lib → core/lib}/use-currency.ts +1 -1
- package/templates/nextjs/base/src/{providers → core/providers}/store-provider.tsx.ejs +4 -4
- package/templates/nextjs/base/src/middleware.ts.ejs +2 -2
- package/templates/nextjs/base/src/{components → ui}/cart/cart-bundle-offer.tsx +5 -5
- package/templates/nextjs/base/src/{components → ui}/cart/cart-item.tsx +6 -6
- package/templates/nextjs/base/src/{components → ui}/cart/cart-nudges.tsx +1 -1
- package/templates/nextjs/base/src/{components → ui}/cart/cart-summary.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/cart-upgrade-banner.tsx +5 -5
- package/templates/nextjs/base/src/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/base/src/{components → ui}/cart/coupon-input.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/free-shipping-bar.tsx +4 -4
- package/templates/nextjs/base/src/{components → ui}/cart/reservation-countdown.tsx +2 -2
- package/templates/nextjs/base/src/ui/home/discount-banner-strip.tsx +20 -0
- package/templates/nextjs/base/src/ui/home/featured-products-section.tsx +24 -0
- package/templates/nextjs/base/src/ui/home/hero-section.tsx +29 -0
- package/templates/nextjs/base/src/ui/home/home-client.tsx +29 -0
- package/templates/nextjs/base/src/{components/content → ui/layout}/faq-section.tsx.ejs +2 -2
- package/templates/nextjs/base/src/{components/content → ui/layout}/header-account.tsx +3 -3
- package/templates/nextjs/base/src/{components/content → ui/layout}/rich-text-block.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/content → ui/layout}/site-header.tsx.ejs +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/customization-fields.tsx +18 -51
- package/templates/nextjs/base/src/{components/products → ui/product}/discount-badge.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/frequently-bought-together.tsx +6 -6
- package/templates/nextjs/base/src/{components/products → ui/product}/modifier-group-selector.tsx +10 -77
- package/templates/nextjs/base/src/{components/shared → ui/product}/price-display.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/product-card.tsx +10 -10
- package/templates/nextjs/base/src/{app/products/[slug] → ui/product}/product-client-section.tsx +45 -206
- package/templates/nextjs/base/src/{components/products → ui/product}/product-grid.tsx +3 -3
- package/templates/nextjs/base/src/ui/product/product-listing.tsx +429 -0
- package/templates/nextjs/base/src/{components/products → ui/product}/recommendation-section.tsx +4 -4
- package/templates/nextjs/base/src/{components/reviews → ui/product}/review-form.tsx +2 -2
- package/templates/nextjs/base/src/{components/reviews → ui/product}/reviews-section.tsx +1 -1
- package/templates/nextjs/base/src/{components/products → ui/product}/stock-badge.tsx +2 -2
- package/templates/nextjs/base/src/{components/products → ui/product}/variant-selector.tsx +3 -3
- package/templates/nextjs/base/src/ui/shared/cdn-image.tsx +26 -0
- package/templates/nextjs/base/src/{components → ui}/shared/loading-spinner.tsx +2 -2
- package/templates/nextjs/base/tailwind.config.ts +12 -0
- package/templates/nextjs/designs/passepartout/app-overlay/layout.tsx.ejs +253 -0
- package/templates/nextjs/designs/passepartout/globals.css +412 -0
- package/templates/nextjs/designs/passepartout/messages-patch/en.json +87 -0
- package/templates/nextjs/designs/passepartout/messages-patch/he.json +87 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-bundle-offer.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-nudges.tsx +39 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-upgrade-banner.tsx +141 -0
- package/templates/nextjs/designs/passepartout/ui/cart/cart-view.tsx +149 -0
- package/templates/nextjs/designs/passepartout/ui/cart/free-shipping-bar.tsx +62 -0
- package/templates/nextjs/designs/passepartout/ui/cart/reservation-countdown.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/home/home-client.tsx +401 -0
- package/templates/nextjs/designs/passepartout/ui/layout/announcement-bar.tsx +139 -0
- package/templates/nextjs/designs/passepartout/ui/layout/cart-link.tsx +45 -0
- package/templates/nextjs/designs/passepartout/ui/layout/faq-section.tsx +103 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-footer.tsx +159 -0
- package/templates/nextjs/designs/passepartout/ui/layout/site-header.tsx.ejs +155 -0
- package/templates/nextjs/designs/passepartout/ui/layout/trust-icons.tsx +60 -0
- package/templates/nextjs/designs/passepartout/ui/layout/whatsapp-button.tsx +34 -0
- package/templates/nextjs/designs/passepartout/ui/product/discount-badge.tsx +22 -0
- package/templates/nextjs/designs/passepartout/ui/product/frequently-bought-together.tsx +205 -0
- package/templates/nextjs/designs/passepartout/ui/product/price-display.tsx +111 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-card.tsx +265 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-client-section.tsx +667 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-grid.tsx +35 -0
- package/templates/nextjs/designs/passepartout/ui/product/product-listing.tsx +427 -0
- package/templates/nextjs/designs/passepartout/ui/product/recommendation-section.tsx +113 -0
- package/templates/nextjs/designs/passepartout/ui/product/review-form.tsx +294 -0
- package/templates/nextjs/designs/passepartout/ui/product/reviews-section.tsx.ejs +96 -0
- package/templates/nextjs/designs/passepartout/ui/product/stock-badge.tsx +52 -0
- package/templates/nextjs/designs/passepartout/ui/product/variant-selector.tsx +307 -0
- package/templates/nextjs/designs/passepartout/ui/shared/fly-to-cart.ts +85 -0
- package/templates/nextjs/designs/passepartout/ui/shared/loading-spinner.tsx +33 -0
- package/templates/nextjs/ui-canvas/cart/cart-bundle-offer.tsx +87 -0
- package/templates/nextjs/ui-canvas/cart/cart-item.tsx +121 -0
- package/templates/nextjs/ui-canvas/cart/cart-nudges.tsx +25 -0
- package/templates/nextjs/ui-canvas/cart/cart-summary.tsx +97 -0
- package/templates/nextjs/ui-canvas/cart/cart-upgrade-banner.tsx +103 -0
- package/templates/nextjs/ui-canvas/cart/cart-view.tsx +106 -0
- package/templates/nextjs/ui-canvas/cart/coupon-input.tsx +117 -0
- package/templates/nextjs/ui-canvas/cart/free-shipping-bar.tsx +52 -0
- package/templates/nextjs/ui-canvas/cart/reservation-countdown.tsx +79 -0
- package/templates/nextjs/ui-canvas/home/discount-banner-strip.tsx +19 -0
- package/templates/nextjs/ui-canvas/home/featured-products-section.tsx +23 -0
- package/templates/nextjs/ui-canvas/home/hero-section.tsx +21 -0
- package/templates/nextjs/ui-canvas/home/home-client.tsx +26 -0
- package/templates/nextjs/ui-canvas/layout/announcement-bar.tsx.ejs +101 -0
- package/templates/nextjs/ui-canvas/layout/faq-section.tsx.ejs +71 -0
- package/templates/nextjs/ui-canvas/layout/header-account.tsx +31 -0
- package/templates/nextjs/ui-canvas/layout/language-switcher.tsx.ejs +74 -0
- package/templates/nextjs/ui-canvas/layout/rich-text-block.tsx.ejs +34 -0
- package/templates/nextjs/ui-canvas/layout/site-footer.tsx.ejs +82 -0
- package/templates/nextjs/ui-canvas/layout/site-header.tsx.ejs +119 -0
- package/templates/nextjs/ui-canvas/product/customization-fields.tsx +443 -0
- package/templates/nextjs/ui-canvas/product/discount-badge.tsx +17 -0
- package/templates/nextjs/ui-canvas/product/frequently-bought-together.tsx +166 -0
- package/templates/nextjs/ui-canvas/product/modifier-group-selector.tsx +163 -0
- package/templates/nextjs/ui-canvas/product/price-display.tsx +50 -0
- package/templates/nextjs/ui-canvas/product/product-card.tsx +197 -0
- package/templates/nextjs/ui-canvas/product/product-client-section.tsx +352 -0
- package/templates/nextjs/ui-canvas/product/product-grid.tsx +27 -0
- package/templates/nextjs/ui-canvas/product/product-listing.tsx +224 -0
- package/templates/nextjs/ui-canvas/product/recommendation-section.tsx +95 -0
- package/templates/nextjs/ui-canvas/product/review-form.tsx +269 -0
- package/templates/nextjs/ui-canvas/product/reviews-section.tsx +78 -0
- package/templates/nextjs/ui-canvas/product/stock-badge.tsx +43 -0
- package/templates/nextjs/ui-canvas/product/variant-selector.tsx +210 -0
- package/templates/nextjs/ui-canvas/shared/cdn-image.tsx +29 -0
- package/templates/nextjs/ui-canvas/shared/loading-spinner.tsx +25 -0
- package/templates/nextjs/base/src/app/home-client.tsx +0 -98
- package/templates/nextjs/base/src/components/products/allergen-chips.tsx +0 -78
- /package/templates/nextjs/base/src/{lib → core/lib}/auth.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/csrf.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/navigation.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/nonce.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/seo.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/store-info.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/translations.ts.ejs +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/utils.ts +0 -0
- /package/templates/nextjs/base/src/{lib → core/lib}/validation.ts +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/announcement-bar.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components → ui}/layout/language-switcher.tsx.ejs +0 -0
- /package/templates/nextjs/base/src/{components/content → ui/layout}/site-footer.tsx.ejs +0 -0
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "create-brainerce-store",
|
|
34
|
-
version: "1.
|
|
34
|
+
version: "1.51.0",
|
|
35
35
|
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
36
|
bin: {
|
|
37
37
|
"create-brainerce-store": "dist/index.js"
|
|
@@ -178,7 +178,9 @@ var BRAINERCE_RUNTIME_DEPS = Object.freeze({
|
|
|
178
178
|
// (Product.displayPrice / displayCurrency / displaySalePrice attached
|
|
179
179
|
// additively when getProducts is called with a regionId). Scaffolded stores
|
|
180
180
|
// must pin >=1.30 to get the auto-region + tax-estimate methods.
|
|
181
|
-
|
|
181
|
+
// 1.44 adds the order-level `notes` field to SetShippingAddressDto /
|
|
182
|
+
// SetCheckoutCustomerDto — the scaffolded checkout form types depend on it.
|
|
183
|
+
brainerce: "^1.44.0",
|
|
182
184
|
"isomorphic-dompurify": "^3.8.0"
|
|
183
185
|
});
|
|
184
186
|
|
|
@@ -286,66 +288,61 @@ async function runInteractive(defaults) {
|
|
|
286
288
|
initial: 0
|
|
287
289
|
});
|
|
288
290
|
}
|
|
289
|
-
questions.push(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{
|
|
291
|
+
questions.push({
|
|
292
|
+
type: "select",
|
|
293
|
+
name: "framework",
|
|
294
|
+
message: "Framework:",
|
|
295
|
+
choices: [
|
|
296
|
+
{ title: "Next.js 15 (recommended)", value: "nextjs" },
|
|
297
|
+
{ title: "Vite + React (coming soon)", value: "vite", disabled: true },
|
|
298
|
+
{ title: "Remix (coming soon)", value: "remix", disabled: true }
|
|
299
|
+
],
|
|
300
|
+
initial: 0
|
|
301
|
+
});
|
|
302
|
+
if (!defaults.canvas && !defaults.design) {
|
|
303
|
+
questions.push({
|
|
302
304
|
type: "select",
|
|
303
|
-
name: "
|
|
304
|
-
message: "
|
|
305
|
+
name: "design",
|
|
306
|
+
message: "Design:",
|
|
305
307
|
choices: [
|
|
306
308
|
{
|
|
307
|
-
title: "
|
|
308
|
-
description: "
|
|
309
|
-
value: "
|
|
309
|
+
title: "Passe-Partout \u2014 warm gallery minimal (recommended)",
|
|
310
|
+
description: "Framed product cards, placard PDP, print-serif titles",
|
|
311
|
+
value: "passepartout"
|
|
310
312
|
},
|
|
311
313
|
{
|
|
312
|
-
title: "
|
|
313
|
-
description: "
|
|
314
|
-
value: "
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
title: "Playful",
|
|
318
|
-
description: "Warm, vibrant design with Nunito rounded font",
|
|
319
|
-
value: "playful"
|
|
314
|
+
title: "Minimal \u2014 bare clean base",
|
|
315
|
+
description: "Neutral tokens, unopinionated layout",
|
|
316
|
+
value: "minimal"
|
|
320
317
|
}
|
|
321
318
|
],
|
|
322
319
|
initial: 0
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
questions.push({
|
|
323
|
+
type: "select",
|
|
324
|
+
name: "pkgManager",
|
|
325
|
+
message: "Package manager:",
|
|
326
|
+
choices: [
|
|
327
|
+
{
|
|
328
|
+
title: `pnpm${detectInvokingPackageManager() === "pnpm" ? " (detected)" : ""}`,
|
|
329
|
+
value: "pnpm"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
title: `npm${detectInvokingPackageManager() === "npm" ? " (detected)" : ""}`,
|
|
333
|
+
value: "npm"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
title: `yarn${detectInvokingPackageManager() === "yarn" ? " (detected)" : ""}`,
|
|
337
|
+
value: "yarn"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
title: `bun${detectInvokingPackageManager() === "bun" ? " (detected)" : ""}`,
|
|
341
|
+
value: "bun"
|
|
342
|
+
}
|
|
343
|
+
],
|
|
344
|
+
initial: ["pnpm", "npm", "yarn", "bun"].indexOf(detectInvokingPackageManager())
|
|
345
|
+
});
|
|
349
346
|
const response = await (0, import_prompts.default)(questions, {
|
|
350
347
|
onCancel: () => {
|
|
351
348
|
throw new Error("PROMPT_CANCELLED");
|
|
@@ -357,6 +354,7 @@ async function runInteractive(defaults) {
|
|
|
357
354
|
language: response.language || defaults.language || "en",
|
|
358
355
|
framework: response.framework || defaults.framework || "nextjs",
|
|
359
356
|
theme: response.theme || defaults.theme || "minimal",
|
|
357
|
+
design: response.design || defaults.design || "minimal",
|
|
360
358
|
pkgManager: response.pkgManager || defaults.pkgManager || detectInvokingPackageManager()
|
|
361
359
|
};
|
|
362
360
|
}
|
|
@@ -459,6 +457,37 @@ async function scaffold(options) {
|
|
|
459
457
|
isomorphicDompurifyVersion: BRAINERCE_RUNTIME_DEPS["isomorphic-dompurify"]
|
|
460
458
|
};
|
|
461
459
|
await copyWithEjs(baseDir, targetDir, templateVars);
|
|
460
|
+
if (options.canvas) {
|
|
461
|
+
const canvasDir = import_path.default.join(templatesRoot, framework, "ui-canvas");
|
|
462
|
+
if (!await import_fs_extra.default.pathExists(canvasDir)) {
|
|
463
|
+
throw new Error(`Canvas template not found at ${canvasDir}`);
|
|
464
|
+
}
|
|
465
|
+
const uiTarget = import_path.default.join(targetDir, "src", "ui");
|
|
466
|
+
await import_fs_extra.default.remove(uiTarget);
|
|
467
|
+
await copyWithEjs(canvasDir, uiTarget, templateVars);
|
|
468
|
+
}
|
|
469
|
+
const designName = options.design && options.design !== "minimal" ? options.design : null;
|
|
470
|
+
if (designName && !/^[a-z0-9-]+$/.test(designName)) {
|
|
471
|
+
throw new Error(`Invalid design name "${designName}"`);
|
|
472
|
+
}
|
|
473
|
+
const designDir = designName ? import_path.default.join(templatesRoot, framework, "designs", designName) : null;
|
|
474
|
+
if (designName && !options.canvas) {
|
|
475
|
+
if (!designDir || !await import_fs_extra.default.pathExists(designDir)) {
|
|
476
|
+
throw new Error(`Design "${designName}" not found at ${designDir}`);
|
|
477
|
+
}
|
|
478
|
+
const designUi = import_path.default.join(designDir, "ui");
|
|
479
|
+
if (await import_fs_extra.default.pathExists(designUi)) {
|
|
480
|
+
await copyWithEjs(designUi, import_path.default.join(targetDir, "src", "ui"), templateVars);
|
|
481
|
+
}
|
|
482
|
+
const designCss = import_path.default.join(designDir, "globals.css");
|
|
483
|
+
if (await import_fs_extra.default.pathExists(designCss)) {
|
|
484
|
+
await import_fs_extra.default.copy(designCss, import_path.default.join(targetDir, "src", "app", "globals.css"));
|
|
485
|
+
}
|
|
486
|
+
const designApp = import_path.default.join(designDir, "app-overlay");
|
|
487
|
+
if (await import_fs_extra.default.pathExists(designApp)) {
|
|
488
|
+
await copyWithEjs(designApp, import_path.default.join(targetDir, "src", "app"), templateVars);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
462
491
|
const messagesRoot = findMessagesDir(__dirname);
|
|
463
492
|
const targetMessages = import_path.default.join(targetDir, "messages");
|
|
464
493
|
await import_fs_extra.default.ensureDir(targetMessages);
|
|
@@ -479,6 +508,24 @@ async function scaffold(options) {
|
|
|
479
508
|
import_path.default.join(targetMessages, `${options.language}.json`)
|
|
480
509
|
);
|
|
481
510
|
}
|
|
511
|
+
if (designName && designDir && !options.canvas) {
|
|
512
|
+
const patchDir = import_path.default.join(designDir, "messages-patch");
|
|
513
|
+
if (await import_fs_extra.default.pathExists(patchDir)) {
|
|
514
|
+
const localeFiles = (await import_fs_extra.default.readdir(targetMessages)).filter((f) => f.endsWith(".json"));
|
|
515
|
+
for (const file of localeFiles) {
|
|
516
|
+
const locale = file.replace(/\.json$/, "");
|
|
517
|
+
let patchFile = import_path.default.join(patchDir, `${locale}.json`);
|
|
518
|
+
if (!await import_fs_extra.default.pathExists(patchFile)) {
|
|
519
|
+
patchFile = import_path.default.join(patchDir, "en.json");
|
|
520
|
+
}
|
|
521
|
+
if (!await import_fs_extra.default.pathExists(patchFile)) continue;
|
|
522
|
+
const targetFile = import_path.default.join(targetMessages, file);
|
|
523
|
+
const base = await import_fs_extra.default.readJson(targetFile);
|
|
524
|
+
const patch = await import_fs_extra.default.readJson(patchFile);
|
|
525
|
+
await import_fs_extra.default.writeJson(targetFile, deepMerge(base, patch), { spaces: 2 });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
482
529
|
if (isMultiLocale) {
|
|
483
530
|
const appDir = import_path.default.join(targetDir, "src", "app");
|
|
484
531
|
const localeDir = import_path.default.join(appDir, "[locale]");
|
|
@@ -490,7 +537,7 @@ async function scaffold(options) {
|
|
|
490
537
|
await import_fs_extra.default.move(import_path.default.join(appDir, entry.name), import_path.default.join(localeDir, entry.name));
|
|
491
538
|
}
|
|
492
539
|
}
|
|
493
|
-
if (await import_fs_extra.default.pathExists(themeDir)) {
|
|
540
|
+
if (!options.canvas && !designName && await import_fs_extra.default.pathExists(themeDir)) {
|
|
494
541
|
const themeCss = import_path.default.join(themeDir, "globals.css");
|
|
495
542
|
if (await import_fs_extra.default.pathExists(themeCss)) {
|
|
496
543
|
await import_fs_extra.default.copy(themeCss, import_path.default.join(targetDir, "src", "app", "globals.css"));
|
|
@@ -501,6 +548,18 @@ async function scaffold(options) {
|
|
|
501
548
|
}
|
|
502
549
|
}
|
|
503
550
|
}
|
|
551
|
+
function deepMerge(base, patch) {
|
|
552
|
+
const out = { ...base };
|
|
553
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
554
|
+
const existing = out[key];
|
|
555
|
+
if (value && typeof value === "object" && !Array.isArray(value) && existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
556
|
+
out[key] = deepMerge(existing, value);
|
|
557
|
+
} else {
|
|
558
|
+
out[key] = value;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return out;
|
|
562
|
+
}
|
|
504
563
|
function findTemplatesDir(fromDir) {
|
|
505
564
|
let dir = fromDir;
|
|
506
565
|
for (let i = 0; i < 5; i++) {
|
|
@@ -693,7 +752,11 @@ async function checkForUpdate(name, current) {
|
|
|
693
752
|
res.on("end", () => {
|
|
694
753
|
try {
|
|
695
754
|
const latest = JSON.parse(data).version;
|
|
696
|
-
|
|
755
|
+
const newer = latest && latest.split(".").map(Number).reduce((acc, seg, i) => {
|
|
756
|
+
if (acc !== 0) return acc;
|
|
757
|
+
return seg - (Number(current.split(".")[i]) || 0);
|
|
758
|
+
}, 0) > 0;
|
|
759
|
+
resolve(newer ? latest : null);
|
|
697
760
|
} catch {
|
|
698
761
|
resolve(null);
|
|
699
762
|
}
|
|
@@ -714,7 +777,13 @@ var program = new import_commander.Command();
|
|
|
714
777
|
program.name("create-brainerce-store").description("Scaffold a production-ready e-commerce storefront connected to Brainerce").version(pkg.version).argument("[project-name]", "Name for the project directory").option("--connection-id <id>", "Brainerce vibe-coded connection ID (vc_*)").option(
|
|
715
778
|
"--api-url <url>",
|
|
716
779
|
"Brainerce API base URL (overrides BRAINERCE_API_URL env, defaults to https://api.brainerce.com)"
|
|
717
|
-
).option("--language <lang>", "Store language (en, he)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "
|
|
780
|
+
).option("--language <lang>", "Store language (en, he)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "Token theme to apply (kept for backward compat)", "minimal").option(
|
|
781
|
+
"--design <design>",
|
|
782
|
+
"Storefront design pack: passepartout (warm gallery minimal) or minimal (bare base)"
|
|
783
|
+
).option("--pkg-manager <manager>", "Package manager (npm, pnpm, yarn, bun)").option(
|
|
784
|
+
"--canvas",
|
|
785
|
+
"Scaffold src/ui as bare unstyled skeletons \u2014 a blank canvas for AI-driven design (commerce stays fully wired)"
|
|
786
|
+
).option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (projectNameArg, options) => {
|
|
718
787
|
try {
|
|
719
788
|
logger.banner(pkg.version);
|
|
720
789
|
const nodeError = validateNodeVersion(process.versions.node);
|
|
@@ -746,6 +815,7 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
746
815
|
const VALID_PKG_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
|
|
747
816
|
const VALID_FRAMEWORKS = ["nextjs"];
|
|
748
817
|
const VALID_THEMES = ["minimal", "luxury", "playful"];
|
|
818
|
+
const VALID_DESIGNS = ["minimal", "passepartout"];
|
|
749
819
|
let language = options.language;
|
|
750
820
|
if (language && !VALID_LANGUAGES.includes(language)) {
|
|
751
821
|
logger.error(
|
|
@@ -765,6 +835,11 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
765
835
|
logger.error(`Invalid --theme "${theme}". Expected one of: ${VALID_THEMES.join(", ")}`);
|
|
766
836
|
process.exit(1);
|
|
767
837
|
}
|
|
838
|
+
let design = options.design;
|
|
839
|
+
if (design && !VALID_DESIGNS.includes(design)) {
|
|
840
|
+
logger.error(`Invalid --design "${design}". Expected one of: ${VALID_DESIGNS.join(", ")}`);
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
768
843
|
let pkgManager = options.pkgManager;
|
|
769
844
|
if (pkgManager && !VALID_PKG_MANAGERS.includes(pkgManager)) {
|
|
770
845
|
logger.error(
|
|
@@ -774,6 +849,7 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
774
849
|
}
|
|
775
850
|
const skipGit = options.git === false;
|
|
776
851
|
const skipInstall = options.install === false;
|
|
852
|
+
const canvas = options.canvas === true;
|
|
777
853
|
let storeInfo = null;
|
|
778
854
|
let resolvedApiUrl = candidateApiUrls[0];
|
|
779
855
|
if (connectionId) {
|
|
@@ -813,13 +889,16 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
813
889
|
language: languageDefault,
|
|
814
890
|
framework,
|
|
815
891
|
theme,
|
|
816
|
-
|
|
892
|
+
design,
|
|
893
|
+
pkgManager,
|
|
894
|
+
canvas
|
|
817
895
|
});
|
|
818
896
|
projectName = answers.projectName;
|
|
819
897
|
connectionId = answers.connectionId;
|
|
820
898
|
language = answers.language;
|
|
821
899
|
framework = answers.framework;
|
|
822
900
|
theme = answers.theme;
|
|
901
|
+
design = answers.design;
|
|
823
902
|
pkgManager = answers.pkgManager;
|
|
824
903
|
}
|
|
825
904
|
const nameError = validateProjectName(projectName);
|
|
@@ -871,7 +950,9 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
871
950
|
currency: storeInfo.currency,
|
|
872
951
|
language: language || storeInfo.language,
|
|
873
952
|
i18n: storeInfo.i18n,
|
|
874
|
-
scaffoldInPlace
|
|
953
|
+
scaffoldInPlace,
|
|
954
|
+
canvas,
|
|
955
|
+
design: canvas ? void 0 : design || "minimal"
|
|
875
956
|
});
|
|
876
957
|
scaffoldSpinner.succeed(
|
|
877
958
|
scaffoldInPlace ? "Scaffolded into current directory" : `Scaffolded into ./${projectName}`
|
|
@@ -911,14 +992,38 @@ program.name("create-brainerce-store").description("Scaffold a production-ready
|
|
|
911
992
|
);
|
|
912
993
|
}
|
|
913
994
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
995
|
+
if (canvas) {
|
|
996
|
+
logger.success("\nYour Brainerce store is ready \u2014 as a blank canvas!\n");
|
|
997
|
+
logger.info(
|
|
998
|
+
"Commerce is fully wired (cart, checkout, variants, coupons), but src/ui\nis bare, unstyled skeletons with zero aesthetic opinion. THE next step\nis to design it \u2014 open this folder in Claude Code and run:"
|
|
999
|
+
);
|
|
1000
|
+
logger.step(`/design <your art-direction brief>`);
|
|
1001
|
+
logger.info("\nThen:");
|
|
1002
|
+
if (!scaffoldInPlace) {
|
|
1003
|
+
logger.step(`cd ${projectName}`);
|
|
1004
|
+
}
|
|
1005
|
+
logger.step(`${pkgManager}${pkgManager === "npm" ? " run" : ""} dev`);
|
|
1006
|
+
logger.info(
|
|
1007
|
+
`
|
|
1008
|
+
Your store will be running at http://localhost:3000
|
|
1009
|
+
(hook contracts + rules: AI-GUIDE.md)`
|
|
1010
|
+
);
|
|
1011
|
+
} else {
|
|
1012
|
+
logger.success("\nYour Brainerce store is ready!\n");
|
|
1013
|
+
logger.info("Next steps:");
|
|
1014
|
+
if (!scaffoldInPlace) {
|
|
1015
|
+
logger.step(`cd ${projectName}`);
|
|
1016
|
+
}
|
|
1017
|
+
logger.step(`${pkgManager}${pkgManager === "npm" ? " run" : ""} dev`);
|
|
1018
|
+
logger.info(`
|
|
921
1019
|
Your store will be running at http://localhost:3000`);
|
|
1020
|
+
logger.info(
|
|
1021
|
+
`
|
|
1022
|
+
Want a unique design? Open this folder in Claude Code and run:
|
|
1023
|
+
/design <your art-direction brief>
|
|
1024
|
+
(commerce stays intact \u2014 see AI-GUIDE.md)`
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
922
1027
|
const latestVersion = await updateCheck;
|
|
923
1028
|
if (latestVersion) logger.updateAvailable(latestVersion, pkg.version);
|
|
924
1029
|
} catch (err) {
|
package/messages/en.json
CHANGED
|
@@ -247,7 +247,9 @@
|
|
|
247
247
|
"privacyPolicyLink": "Privacy Policy",
|
|
248
248
|
"privacyRequired": "You must accept the privacy policy to continue",
|
|
249
249
|
"acceptsMarketing": "Send me news, promotions, and updates by email",
|
|
250
|
-
"saveDetailsForNextTime": "Save my details for faster checkout next time"
|
|
250
|
+
"saveDetailsForNextTime": "Save my details for faster checkout next time",
|
|
251
|
+
"orderNotes": "Order notes (optional)",
|
|
252
|
+
"orderNotesPlaceholder": "Anything we should know about your order? e.g. delivery instructions"
|
|
251
253
|
},
|
|
252
254
|
"auth": {
|
|
253
255
|
"loginPageTitle": "Sign In",
|
|
@@ -331,6 +333,7 @@
|
|
|
331
333
|
"orderHistory": "Order History",
|
|
332
334
|
"noOrders": "No orders yet",
|
|
333
335
|
"noOrdersDesc": "Your order history will appear here after your first purchase.",
|
|
336
|
+
"orderNotes": "Your order note",
|
|
334
337
|
"verified": "Verified",
|
|
335
338
|
"unverified": "Unverified",
|
|
336
339
|
"memberSince": "Member since",
|
package/messages/he.json
CHANGED
|
@@ -247,7 +247,9 @@
|
|
|
247
247
|
"privacyPolicyLink": "מדיניות הפרטיות",
|
|
248
248
|
"privacyRequired": "יש לאשר את מדיניות הפרטיות כדי להמשיך",
|
|
249
249
|
"acceptsMarketing": "שלחו לי חדשות, מבצעים ועדכונים במייל",
|
|
250
|
-
"saveDetailsForNextTime": "שמרו את הפרטים שלי לתשלום מהיר בפעם הבאה"
|
|
250
|
+
"saveDetailsForNextTime": "שמרו את הפרטים שלי לתשלום מהיר בפעם הבאה",
|
|
251
|
+
"orderNotes": "הערות להזמנה (אופציונלי)",
|
|
252
|
+
"orderNotesPlaceholder": "משהו שכדאי שנדע על ההזמנה? למשל הנחיות למשלוח"
|
|
251
253
|
},
|
|
252
254
|
"auth": {
|
|
253
255
|
"loginPageTitle": "התחברות",
|
|
@@ -331,6 +333,7 @@
|
|
|
331
333
|
"orderHistory": "היסטוריית הזמנות",
|
|
332
334
|
"noOrders": "אין הזמנות עדיין",
|
|
333
335
|
"noOrdersDesc": "היסטוריית ההזמנות שלך תופיע כאן לאחר הרכישה הראשונה.",
|
|
336
|
+
"orderNotes": "ההערה שלך להזמנה",
|
|
334
337
|
"verified": "מאומת",
|
|
335
338
|
"unverified": "לא מאומת",
|
|
336
339
|
"memberSince": "חבר מאז",
|
package/package.json
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Redesign this storefront's presentation layer to a distinctive, production-grade look
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are the design lead for this Brainerce storefront. Design brief from the
|
|
6
|
+
merchant: **$ARGUMENTS** (if empty, ask for a one-line direction first — e.g.
|
|
7
|
+
"dark cinematic jewelry gallery", "brutalist streetwear", "warm Mediterranean
|
|
8
|
+
boutique").
|
|
9
|
+
|
|
10
|
+
## Rebuild, don't restyle
|
|
11
|
+
|
|
12
|
+
The shipped `src/ui/` is a WORKING REFERENCE, not an aesthetic to preserve.
|
|
13
|
+
(In a `--canvas` scaffold it's already a bare unstyled skeleton — each
|
|
14
|
+
component carries a `DESIGN ME` marker telling you what the surface is and
|
|
15
|
+
which hook feeds it; skip straight to designing.)
|
|
16
|
+
You are expected to design from scratch: deleting entire files under
|
|
17
|
+
`src/ui/` and rebuilding them around the same hooks is encouraged — that is
|
|
18
|
+
what the folder exists for. Do not inherit the current look's layout,
|
|
19
|
+
spacing, or component structure unless it genuinely serves your direction.
|
|
20
|
+
Build the store YOU would build; the reference only shows which hooks feed
|
|
21
|
+
which surface.
|
|
22
|
+
|
|
23
|
+
## Ground rules (non-negotiable)
|
|
24
|
+
|
|
25
|
+
1. Read `AI-GUIDE.md` and `src/ui/`'s current structure BEFORE writing code.
|
|
26
|
+
2. You may rewrite anything in `src/ui/**` and `src/app/globals.css`, and
|
|
27
|
+
wire new ui components in the thin `src/app/**` routes. You may NOT touch
|
|
28
|
+
`src/core/**`, `src/app/api/**`, or checkout/auth/account components.
|
|
29
|
+
3. All data via `@/core/hooks/*` and the store provider — no hardcoded
|
|
30
|
+
products, prices, or currency symbols.
|
|
31
|
+
4. All copy via `useTranslations()`, keys added to every file in `messages/`.
|
|
32
|
+
|
|
33
|
+
## The process
|
|
34
|
+
|
|
35
|
+
1. **Commit to ONE art direction** — write it down in 4 lines (typography
|
|
36
|
+
pairing incl. a script-compatible webfont, 6-10 color tokens, layout
|
|
37
|
+
concept, motion language) at the top of `DESIGN.md` in the project root.
|
|
38
|
+
Bold and specific beats safe and generic; do not hedge toward the middle.
|
|
39
|
+
2. **Tokens first**: rewrite `globals.css` custom properties + font loading
|
|
40
|
+
in the locale layout.
|
|
41
|
+
3. **Surfaces in order**: header/footer → homepage sections → product card +
|
|
42
|
+
grid → product page → cart. Keep each step compiling.
|
|
43
|
+
4. **Micro-details**: hover states, loading states, one signature
|
|
44
|
+
interaction. Wrap all motion in `prefers-reduced-motion` guards.
|
|
45
|
+
5. **Verify** (mandatory): `pnpm exec tsc --noEmit` = 0, then `pnpm dev` and
|
|
46
|
+
drive home → product → add-to-cart → cart in a real browser; screenshot
|
|
47
|
+
desktop (1440) AND mobile (390). For RTL locales check anchoring and
|
|
48
|
+
arrow directions. Fix what looks off before reporting.
|
|
49
|
+
|
|
50
|
+
Report with: the direction chosen, what changed, screenshots, and anything
|
|
51
|
+
that needs merchant input (e.g. brand name, logo, photography).
|
|
@@ -17,3 +17,8 @@ NEXT_PUBLIC_BRAINERCE_API_URL=<%- apiBaseUrlEnv %>
|
|
|
17
17
|
|
|
18
18
|
# Public site URL — used for sitemap, robots.txt, and SEO metadata
|
|
19
19
|
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
20
|
+
|
|
21
|
+
# Optional: floating WhatsApp contact button (used by some designs, e.g.
|
|
22
|
+
# passepartout). Digits only, international format without "+"
|
|
23
|
+
# (e.g. 14155550123). Leave unset to hide the button.
|
|
24
|
+
# NEXT_PUBLIC_WHATSAPP_PHONE=
|
|
@@ -1,3 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": [
|
|
3
|
-
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"next/core-web-vitals"
|
|
4
|
+
],
|
|
5
|
+
"overrides": [
|
|
6
|
+
{
|
|
7
|
+
"files": [
|
|
8
|
+
"src/ui/**/*.{ts,tsx}"
|
|
9
|
+
],
|
|
10
|
+
"rules": {
|
|
11
|
+
"no-restricted-imports": [
|
|
12
|
+
"error",
|
|
13
|
+
{
|
|
14
|
+
"patterns": [
|
|
15
|
+
{
|
|
16
|
+
"group": [
|
|
17
|
+
"@/core/lib/*",
|
|
18
|
+
"!@/core/lib/auth",
|
|
19
|
+
"!@/core/lib/brainerce",
|
|
20
|
+
"!@/core/lib/image-hosts",
|
|
21
|
+
"!@/core/lib/navigation",
|
|
22
|
+
"!@/core/lib/product-options",
|
|
23
|
+
"!@/core/lib/sanitize",
|
|
24
|
+
"!@/core/lib/sanitize-html",
|
|
25
|
+
"!@/core/lib/translations",
|
|
26
|
+
"!@/core/lib/use-currency",
|
|
27
|
+
"!@/core/lib/utils"
|
|
28
|
+
],
|
|
29
|
+
"message": "UI consumes data via @/core/hooks/* and @/core/providers/* — not core internals. (Approved presentation helpers are exempt.)"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"files": [
|
|
38
|
+
"src/core/**/*.{ts,tsx}"
|
|
39
|
+
],
|
|
40
|
+
"rules": {
|
|
41
|
+
"no-restricted-imports": [
|
|
42
|
+
"error",
|
|
43
|
+
{
|
|
44
|
+
"patterns": [
|
|
45
|
+
{
|
|
46
|
+
"group": [
|
|
47
|
+
"@/ui/*"
|
|
48
|
+
],
|
|
49
|
+
"message": "core/ must never depend on presentation (ui/)."
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# AGENTS.md — this is a Brainerce storefront
|
|
2
|
+
|
|
3
|
+
This store's commerce engine (cart, checkout, variants, auth, i18n) is
|
|
4
|
+
already wired and working. Your job here is almost always **design**.
|
|
5
|
+
|
|
6
|
+
## The one rule
|
|
7
|
+
|
|
8
|
+
**`src/core/` is the platform's. `src/ui/` is yours.**
|
|
9
|
+
|
|
10
|
+
The shipped `src/ui/` is a working reference, not a design to preserve —
|
|
11
|
+
full delete-and-rebuild of ui files is encouraged and expected.
|
|
12
|
+
Rewrite anything under `src/ui/` and `src/app/globals.css` as boldly as you
|
|
13
|
+
like — the store keeps working. Never modify `src/core/`, `src/app/api/`, or
|
|
14
|
+
the checkout/auth/account components. Data and behavior come exclusively from
|
|
15
|
+
`@/core/hooks/*` and `@/core/providers/store-provider` — hooks return state
|
|
16
|
+
and handlers, never JSX. Never hardcode catalog content.
|
|
17
|
+
|
|
18
|
+
**Read `AI-GUIDE.md` before any redesign** — it has the full file map, hook
|
|
19
|
+
contracts, and hard-won RTL/i18n gotchas that will save you real debugging
|
|
20
|
+
time.
|
|
21
|
+
|
|
22
|
+
## Redesigning?
|
|
23
|
+
|
|
24
|
+
Follow the process documented in AI-GUIDE.md: commit to one art direction,
|
|
25
|
+
rewrite tokens first, then surfaces in order (header, home, card, product
|
|
26
|
+
page, cart), then verify.
|
|
27
|
+
|
|
28
|
+
## Verify before declaring done
|
|
29
|
+
|
|
30
|
+
1. `pnpm exec tsc --noEmit` → 0 errors
|
|
31
|
+
2. `pnpm dev` → drive the changed flow in a real browser (home → product →
|
|
32
|
+
add to cart → cart)
|
|
33
|
+
3. Screenshot desktop (1440px) and mobile (390px)
|
|
34
|
+
4. RTL stores: check anchoring and arrow directions
|
|
35
|
+
|
|
36
|
+
## i18n
|
|
37
|
+
|
|
38
|
+
Every user-facing string goes through `useTranslations()` with keys in **all**
|
|
39
|
+
files under `messages/`. Hebrew: no uppercase transforms, no wide
|
|
40
|
+
letter-spacing on headings, logical CSS properties only (`ms-/me-`, `ps-/pe-`,
|
|
41
|
+
`start-/end-`).
|