@rovela-ai/sdk 0.19.3 → 0.19.5

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 (211) hide show
  1. package/dist/admin/server/admin-session.d.ts.map +1 -1
  2. package/dist/admin/server/admin-session.js +7 -0
  3. package/dist/admin/server/admin-session.js.map +1 -1
  4. package/dist/auth/server/customer-session.d.ts.map +1 -1
  5. package/dist/auth/server/customer-session.js +5 -0
  6. package/dist/auth/server/customer-session.js.map +1 -1
  7. package/dist/checkout/server/checkout-customer.d.ts.map +1 -1
  8. package/dist/checkout/server/checkout-customer.js +5 -0
  9. package/dist/checkout/server/checkout-customer.js.map +1 -1
  10. package/docs/changelog/README.md +53 -0
  11. package/docs/changelog/epoch-01-chrome-consent.md +63 -0
  12. package/docs/changelog/epoch-02-platform-commerce.md +108 -0
  13. package/docs/changelog/epoch-03-i18n-files.md +72 -0
  14. package/docs/changelog/epoch-04-emails.md +65 -0
  15. package/docs/changelog/epoch-05-admin-experience.md +98 -0
  16. package/docs/changelog/epoch-06-mobile-integrity.md +72 -0
  17. package/docs/design/admin-ledger-visual-universe.html +1961 -0
  18. package/docs/design/admin-sidebar-and-band.html +542 -0
  19. package/package.json +3 -1
  20. package/templates/store-template/.claude/skills/add-section/SKILL.md +438 -0
  21. package/templates/store-template/.claude/skills/build-feature/SKILL.md +201 -0
  22. package/templates/store-template/.claude/skills/customize-theme/SKILL.md +167 -0
  23. package/templates/store-template/.claude/skills/debug-errors/SKILL.md +68 -0
  24. package/templates/store-template/.claude/skills/frontend-design/SKILL.md +176 -0
  25. package/templates/store-template/.claude/skills/generate-images/SKILL.md +115 -0
  26. package/templates/store-template/.claude/skills/manage-products/SKILL.md +187 -0
  27. package/templates/store-template/.claude/skills/override-sdk-component/SKILL.md +154 -0
  28. package/templates/store-template/.env.example +50 -0
  29. package/templates/store-template/.mcp.json +8 -0
  30. package/templates/store-template/.rovela/blueprint.example.json +190 -0
  31. package/templates/store-template/CLAUDE.md +14 -0
  32. package/templates/store-template/app/LayoutContent.tsx +106 -0
  33. package/templates/store-template/app/[...path]/page.tsx +79 -0
  34. package/templates/store-template/app/account/addresses/page.tsx +45 -0
  35. package/templates/store-template/app/account/layout.tsx +17 -0
  36. package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +277 -0
  37. package/templates/store-template/app/account/orders/[id]/page.tsx +725 -0
  38. package/templates/store-template/app/account/orders/page.tsx +153 -0
  39. package/templates/store-template/app/account/page.tsx +184 -0
  40. package/templates/store-template/app/account/profile/page.tsx +434 -0
  41. package/templates/store-template/app/admin/(dashboard)/account/page.tsx +16 -0
  42. package/templates/store-template/app/admin/(dashboard)/analytics/events/page.tsx +14 -0
  43. package/templates/store-template/app/admin/(dashboard)/analytics/layout.tsx +48 -0
  44. package/templates/store-template/app/admin/(dashboard)/analytics/page.tsx +15 -0
  45. package/templates/store-template/app/admin/(dashboard)/analytics/visitors/page.tsx +14 -0
  46. package/templates/store-template/app/admin/(dashboard)/categories/[id]/page.tsx +38 -0
  47. package/templates/store-template/app/admin/(dashboard)/categories/new/page.tsx +35 -0
  48. package/templates/store-template/app/admin/(dashboard)/categories/page.tsx +223 -0
  49. package/templates/store-template/app/admin/(dashboard)/customers/[id]/page.tsx +26 -0
  50. package/templates/store-template/app/admin/(dashboard)/customers/page.tsx +33 -0
  51. package/templates/store-template/app/admin/(dashboard)/discounts/page.tsx +16 -0
  52. package/templates/store-template/app/admin/(dashboard)/emails/page.tsx +16 -0
  53. package/templates/store-template/app/admin/(dashboard)/files/page.tsx +16 -0
  54. package/templates/store-template/app/admin/(dashboard)/layout.tsx +44 -0
  55. package/templates/store-template/app/admin/(dashboard)/loading.tsx +88 -0
  56. package/templates/store-template/app/admin/(dashboard)/locations/page.tsx +16 -0
  57. package/templates/store-template/app/admin/(dashboard)/orders/[id]/page.tsx +66 -0
  58. package/templates/store-template/app/admin/(dashboard)/orders/page.tsx +26 -0
  59. package/templates/store-template/app/admin/(dashboard)/page.tsx +146 -0
  60. package/templates/store-template/app/admin/(dashboard)/payments/page.tsx +15 -0
  61. package/templates/store-template/app/admin/(dashboard)/products/[id]/page.tsx +38 -0
  62. package/templates/store-template/app/admin/(dashboard)/products/new/page.tsx +35 -0
  63. package/templates/store-template/app/admin/(dashboard)/products/page.tsx +31 -0
  64. package/templates/store-template/app/admin/(dashboard)/settings/page.tsx +46 -0
  65. package/templates/store-template/app/admin/(dashboard)/shipping/page.tsx +15 -0
  66. package/templates/store-template/app/admin/(dashboard)/tax/page.tsx +15 -0
  67. package/templates/store-template/app/admin/(dashboard)/users/[id]/page.tsx +114 -0
  68. package/templates/store-template/app/admin/(dashboard)/users/page.tsx +16 -0
  69. package/templates/store-template/app/admin/accept-invite/page.tsx +36 -0
  70. package/templates/store-template/app/admin/forgot-password/page.tsx +13 -0
  71. package/templates/store-template/app/admin/layout.tsx +47 -0
  72. package/templates/store-template/app/admin/login/page.tsx +528 -0
  73. package/templates/store-template/app/admin/reset-password/page.tsx +36 -0
  74. package/templates/store-template/app/api/account/addresses/[id]/route.ts +10 -0
  75. package/templates/store-template/app/api/account/addresses/route.ts +10 -0
  76. package/templates/store-template/app/api/account/change-password/route.ts +107 -0
  77. package/templates/store-template/app/api/account/orders/[id]/refund/route.ts +11 -0
  78. package/templates/store-template/app/api/account/orders/[id]/return/route.ts +14 -0
  79. package/templates/store-template/app/api/account/orders/[id]/route.ts +42 -0
  80. package/templates/store-template/app/api/account/orders/route.ts +29 -0
  81. package/templates/store-template/app/api/account/profile/route.ts +165 -0
  82. package/templates/store-template/app/api/admin/accept-invite/route.ts +11 -0
  83. package/templates/store-template/app/api/admin/analytics/events/route.ts +9 -0
  84. package/templates/store-template/app/api/admin/analytics/route.ts +10 -0
  85. package/templates/store-template/app/api/admin/analytics/visitors/route.ts +9 -0
  86. package/templates/store-template/app/api/admin/branding/favicon/route.ts +9 -0
  87. package/templates/store-template/app/api/admin/branding/logo/route.ts +9 -0
  88. package/templates/store-template/app/api/admin/categories/[id]/route.ts +9 -0
  89. package/templates/store-template/app/api/admin/categories/route.ts +9 -0
  90. package/templates/store-template/app/api/admin/check/route.ts +10 -0
  91. package/templates/store-template/app/api/admin/customer-groups/[id]/route.ts +10 -0
  92. package/templates/store-template/app/api/admin/customer-groups/route.ts +10 -0
  93. package/templates/store-template/app/api/admin/customers/[id]/addresses/[addressId]/route.ts +15 -0
  94. package/templates/store-template/app/api/admin/customers/[id]/addresses/route.ts +15 -0
  95. package/templates/store-template/app/api/admin/customers/[id]/route.ts +11 -0
  96. package/templates/store-template/app/api/admin/customers/bulk/route.ts +8 -0
  97. package/templates/store-template/app/api/admin/customers/route.ts +9 -0
  98. package/templates/store-template/app/api/admin/discounts/[id]/route.ts +11 -0
  99. package/templates/store-template/app/api/admin/discounts/route.ts +10 -0
  100. package/templates/store-template/app/api/admin/emails/preview/route.ts +4 -0
  101. package/templates/store-template/app/api/admin/emails/route.ts +5 -0
  102. package/templates/store-template/app/api/admin/emails/test/route.ts +4 -0
  103. package/templates/store-template/app/api/admin/files/route.ts +8 -0
  104. package/templates/store-template/app/api/admin/forgot-password/route.ts +7 -0
  105. package/templates/store-template/app/api/admin/linked-customer/route.ts +11 -0
  106. package/templates/store-template/app/api/admin/locations/[id]/route.ts +10 -0
  107. package/templates/store-template/app/api/admin/locations/route.ts +10 -0
  108. package/templates/store-template/app/api/admin/me/password/route.ts +13 -0
  109. package/templates/store-template/app/api/admin/me/route.ts +11 -0
  110. package/templates/store-template/app/api/admin/orders/[id]/refund/route.ts +7 -0
  111. package/templates/store-template/app/api/admin/orders/[id]/return/approve/route.ts +7 -0
  112. package/templates/store-template/app/api/admin/orders/[id]/return/reject/route.ts +7 -0
  113. package/templates/store-template/app/api/admin/orders/[id]/route.ts +9 -0
  114. package/templates/store-template/app/api/admin/orders/[id]/ship-with-shippo/route.ts +6 -0
  115. package/templates/store-template/app/api/admin/orders/[id]/shippo-rates/route.ts +6 -0
  116. package/templates/store-template/app/api/admin/orders/bulk/route.ts +8 -0
  117. package/templates/store-template/app/api/admin/orders/route.ts +9 -0
  118. package/templates/store-template/app/api/admin/payment-methods/[id]/route.ts +10 -0
  119. package/templates/store-template/app/api/admin/payment-methods/route.ts +10 -0
  120. package/templates/store-template/app/api/admin/products/[id]/route.ts +9 -0
  121. package/templates/store-template/app/api/admin/products/bulk/route.ts +9 -0
  122. package/templates/store-template/app/api/admin/products/route.ts +9 -0
  123. package/templates/store-template/app/api/admin/products/stats/route.ts +10 -0
  124. package/templates/store-template/app/api/admin/reset-password/route.ts +10 -0
  125. package/templates/store-template/app/api/admin/settings/route.ts +11 -0
  126. package/templates/store-template/app/api/admin/setup/route.ts +10 -0
  127. package/templates/store-template/app/api/admin/setup-guide/manual-mark/route.ts +13 -0
  128. package/templates/store-template/app/api/admin/setup-guide/route.ts +10 -0
  129. package/templates/store-template/app/api/admin/shipping/carriers/[id]/route.ts +9 -0
  130. package/templates/store-template/app/api/admin/shipping/carriers/route.ts +9 -0
  131. package/templates/store-template/app/api/admin/shipping/rates/[id]/route.ts +9 -0
  132. package/templates/store-template/app/api/admin/shipping/rates/route.ts +9 -0
  133. package/templates/store-template/app/api/admin/shipping/shippo/api-key/route.ts +1 -0
  134. package/templates/store-template/app/api/admin/shipping/shippo/validate/route.ts +1 -0
  135. package/templates/store-template/app/api/admin/shipping/zones/[id]/route.ts +9 -0
  136. package/templates/store-template/app/api/admin/shipping/zones/route.ts +9 -0
  137. package/templates/store-template/app/api/admin/stats/route.ts +9 -0
  138. package/templates/store-template/app/api/admin/stripe-status/route.ts +1 -0
  139. package/templates/store-template/app/api/admin/tax-zones/[id]/route.ts +7 -0
  140. package/templates/store-template/app/api/admin/tax-zones/route.ts +7 -0
  141. package/templates/store-template/app/api/admin/users/[id]/route.ts +14 -0
  142. package/templates/store-template/app/api/admin/users/route.ts +12 -0
  143. package/templates/store-template/app/api/admin-auth/[...nextauth]/route.ts +17 -0
  144. package/templates/store-template/app/api/analytics/track/route.ts +12 -0
  145. package/templates/store-template/app/api/auth/[...nextauth]/route.ts +19 -0
  146. package/templates/store-template/app/api/auth/forgot-password/route.ts +9 -0
  147. package/templates/store-template/app/api/auth/register/route.ts +9 -0
  148. package/templates/store-template/app/api/auth/resend-verification/route.ts +9 -0
  149. package/templates/store-template/app/api/auth/reset-password/route.ts +10 -0
  150. package/templates/store-template/app/api/auth/verify-email/route.ts +10 -0
  151. package/templates/store-template/app/api/categories/route.ts +10 -0
  152. package/templates/store-template/app/api/checkout/countries/route.ts +7 -0
  153. package/templates/store-template/app/api/checkout/discount/route.ts +9 -0
  154. package/templates/store-template/app/api/checkout/manual/route.ts +10 -0
  155. package/templates/store-template/app/api/checkout/payment-methods/route.ts +8 -0
  156. package/templates/store-template/app/api/checkout/pickup-locations/route.ts +8 -0
  157. package/templates/store-template/app/api/checkout/route.ts +10 -0
  158. package/templates/store-template/app/api/checkout/shipping/route.ts +21 -0
  159. package/templates/store-template/app/api/checkout/status/route.ts +10 -0
  160. package/templates/store-template/app/api/contact/route.ts +164 -0
  161. package/templates/store-template/app/api/media/presign/route.ts +7 -0
  162. package/templates/store-template/app/api/media/route.ts +7 -0
  163. package/templates/store-template/app/api/products/[slug]/route.ts +10 -0
  164. package/templates/store-template/app/api/products/route.ts +10 -0
  165. package/templates/store-template/app/api/store/settings/route.ts +12 -0
  166. package/templates/store-template/app/api/webhooks/stripe/route.ts +63 -0
  167. package/templates/store-template/app/auth/forgot-password/page.tsx +38 -0
  168. package/templates/store-template/app/auth/layout.tsx +17 -0
  169. package/templates/store-template/app/auth/reset-password/page.tsx +81 -0
  170. package/templates/store-template/app/auth/signin/page.tsx +62 -0
  171. package/templates/store-template/app/auth/signup/page.tsx +38 -0
  172. package/templates/store-template/app/auth/verify-email/page.tsx +185 -0
  173. package/templates/store-template/app/cart/page.tsx +137 -0
  174. package/templates/store-template/app/checkout/page.tsx +88 -0
  175. package/templates/store-template/app/checkout/success/page.tsx +50 -0
  176. package/templates/store-template/app/contact/layout.tsx +24 -0
  177. package/templates/store-template/app/contact/page.tsx +321 -0
  178. package/templates/store-template/app/faq/page.tsx +153 -0
  179. package/templates/store-template/app/globals.css +99 -0
  180. package/templates/store-template/app/layout.tsx +269 -0
  181. package/templates/store-template/app/not-found.tsx +35 -0
  182. package/templates/store-template/app/page.tsx +23 -0
  183. package/templates/store-template/app/privacy/page.tsx +281 -0
  184. package/templates/store-template/app/products/[slug]/ProductDetailClient.tsx +179 -0
  185. package/templates/store-template/app/products/[slug]/page.tsx +149 -0
  186. package/templates/store-template/app/products/layout.tsx +26 -0
  187. package/templates/store-template/app/products/page.tsx +102 -0
  188. package/templates/store-template/app/robots.ts +25 -0
  189. package/templates/store-template/app/sitemap.ts +76 -0
  190. package/templates/store-template/app/terms/page.tsx +313 -0
  191. package/templates/store-template/components/PathSyncBridge.tsx +61 -0
  192. package/templates/store-template/components/Providers.tsx +37 -0
  193. package/templates/store-template/components/layout/Footer.tsx +150 -0
  194. package/templates/store-template/components/layout/Header.tsx +162 -0
  195. package/templates/store-template/components/sections/.gitkeep +7 -0
  196. package/templates/store-template/components/sections/CTA.tsx +51 -0
  197. package/templates/store-template/components/sections/Features.tsx +91 -0
  198. package/templates/store-template/components/sections/Hero.tsx +85 -0
  199. package/templates/store-template/eslint.config.mjs +18 -0
  200. package/templates/store-template/lib/store-config.ts +77 -0
  201. package/templates/store-template/lib/store-policy.ts +68 -0
  202. package/templates/store-template/lib/utils.ts +21 -0
  203. package/templates/store-template/next-env.d.ts +6 -0
  204. package/templates/store-template/next.config.js +105 -0
  205. package/templates/store-template/package.json +32 -0
  206. package/templates/store-template/postcss.config.js +6 -0
  207. package/templates/store-template/proxy.ts +150 -0
  208. package/templates/store-template/public/rovela-logo.png +0 -0
  209. package/templates/store-template/tailwind.config.ts +74 -0
  210. package/templates/store-template/tsconfig.json +41 -0
  211. package/templates/store-template/types/next-auth.d.ts +32 -0
@@ -0,0 +1,438 @@
1
+ ---
2
+ name: add-section
3
+ description: Use when the user wants to add a new section to the homepage or any page. Covers component creation, page.tsx integration, responsive layout, and content from blueprint.
4
+ ---
5
+
6
+ # Add Homepage Section
7
+
8
+ > **Languages**: before writing any user-visible copy, check the store's
9
+ > languages (`SELECT default_locale, enabled_locales FROM store_settings`).
10
+ > Multilingual stores need every new string in ALL enabled languages via the
11
+ > store dictionary — see CLAUDE.md §5.5 "Languages & translations". Internal
12
+ > links always use `LocaleLink` from `@rovela-ai/sdk/core`, never raw
13
+ > `next/link`.
14
+
15
+ ## Step-by-Step Process
16
+
17
+ ### 1. Create the Component
18
+
19
+ Create a new file at `/app/components/sections/SectionName.tsx`:
20
+
21
+ ```tsx
22
+ 'use client';
23
+
24
+ export default function SectionName() {
25
+ return (
26
+ <section className="py-16 md:py-24 px-4">
27
+ <div className="max-w-7xl mx-auto">
28
+ {/* Section content */}
29
+ </div>
30
+ </section>
31
+ );
32
+ }
33
+ ```
34
+
35
+ Add `'use client'` only if the component uses hooks (`useState`, `useEffect`) or event handlers (`onClick`, `onSubmit`). Pure display sections don't need it.
36
+
37
+ ### 2. Import in page.tsx
38
+
39
+ ```tsx
40
+ // In /app/app/page.tsx
41
+ import SectionName from '@/components/sections/SectionName';
42
+
43
+ // Add below existing sections
44
+ <SectionName />
45
+ ```
46
+
47
+ ### 3. Verify
48
+
49
+ Run error check (get_errors) to confirm no TypeScript issues.
50
+
51
+ ## Content Sources
52
+
53
+ - Use exact content from `/app/.rovela/blueprint.json` for headlines, descriptions, testimonials
54
+ - Hardcode content directly in the component (not runtime reads)
55
+ - For images: call `mcp__r2__generate_image` directly in this session (never via Task/Agent — subagents don't inherit MCP). Write descriptive scene prompts (40-75 words) using photography terminology. Use `imageUrl` from blueprint if already populated. A CSS gradient is acceptable per-image ONLY when the tool returns `{ success: false }` (Gemini API error/safety block) for that call — never as a substitute for skipping
56
+ - Never hardcode external image URLs — always generate or use blueprint data
57
+
58
+ ## Header Customization
59
+
60
+ The header is the FIRST thing visitors see. It MUST reflect the store's brand personality — never leave it as a generic white bar with gray text.
61
+
62
+ **Read the Visual Creative Brief in CLAUDE.md** before customizing the header.
63
+
64
+ ### Solid Header (default)
65
+
66
+ Use when the hero does NOT have a full-bleed background image:
67
+
68
+ ```tsx
69
+ <header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
70
+ <nav className="container mx-auto px-4 sm:px-6 lg:px-8">
71
+ <div className="flex h-16 items-center justify-between">
72
+ {/* Logo: use font-heading, brand-appropriate weight */}
73
+ <Link href="/" className="text-xl font-bold font-heading tracking-tight">
74
+ {storeName}
75
+ </Link>
76
+
77
+ {/* Nav links: use brand-appropriate hover color */}
78
+ <div className="hidden md:flex items-center gap-8">
79
+ {navigation.map((item) => (
80
+ <Link key={item.name} href={item.href}
81
+ className="text-sm font-medium text-muted-foreground transition-colors hover:text-primary">
82
+ {item.name}
83
+ </Link>
84
+ ))}
85
+ </div>
86
+
87
+ {/* Actions */}
88
+ <div className="flex items-center gap-3">
89
+ <CartIcon showCount />
90
+ {/* Mobile menu toggle */}
91
+ </div>
92
+ </div>
93
+ </nav>
94
+ </header>
95
+ ```
96
+
97
+ ### Transparent Header (for hero with background image)
98
+
99
+ Use when hero has `imageUrl` and layout is `fullscreen` or `center`:
100
+
101
+ ```tsx
102
+ 'use client';
103
+ import { useState, useEffect } from 'react';
104
+
105
+ export function Header() {
106
+ const [scrolled, setScrolled] = useState(false);
107
+
108
+ useEffect(() => {
109
+ const onScroll = () => setScrolled(window.scrollY > 50);
110
+ window.addEventListener('scroll', onScroll);
111
+ return () => window.removeEventListener('scroll', onScroll);
112
+ }, []);
113
+
114
+ return (
115
+ <header className={cn(
116
+ 'fixed top-0 z-50 w-full transition-all duration-300',
117
+ scrolled
118
+ ? 'bg-background/95 backdrop-blur border-b border-border/40 shadow-sm'
119
+ : 'bg-transparent'
120
+ )}>
121
+ <nav className="container mx-auto px-4 sm:px-6 lg:px-8">
122
+ <div className="flex h-16 items-center justify-between">
123
+ <Link href="/" className={cn(
124
+ 'text-xl font-bold font-heading tracking-tight transition-colors',
125
+ scrolled ? 'text-foreground' : 'text-white'
126
+ )}>
127
+ {storeName}
128
+ </Link>
129
+ {/* Nav links: same transition pattern for text color */}
130
+ </div>
131
+ </nav>
132
+ </header>
133
+ );
134
+ }
135
+ ```
136
+
137
+ When using transparent header, add `pt-0` to the hero section (header overlaps it), NOT `mt-16`.
138
+
139
+ ---
140
+
141
+ ## Hero Section Patterns
142
+
143
+ The hero is the most visually impactful section. Blueprint data is at `blueprint.homepage.hero`.
144
+
145
+ **Required data points to implement:**
146
+ - `headline.text` + `headline.emphasis[]` (words to style differently)
147
+ - `subheadline.text`
148
+ - `cta.primary` + `cta.secondary` (button text, variant, link)
149
+ - `imageUrl` (from blueprint, or generated via `mcp__r2__generate_image` called directly in this session — per-image gradient fallback ONLY if that specific call returns `{ success: false }`)
150
+ - `socialProof` (optional stats bar)
151
+ - `layout` (split, center, fullscreen)
152
+
153
+ ### Split Layout (most common)
154
+
155
+ Two columns: text left, image right. Stacks on mobile.
156
+
157
+ ```tsx
158
+ <section className="relative py-20 lg:py-28">
159
+ <div className="container mx-auto px-4 sm:px-6 lg:px-8">
160
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
161
+ {/* Text column */}
162
+ <div className="max-w-xl">
163
+ <h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold font-heading tracking-tight leading-[1.1]">
164
+ {/* Render emphasis words with accent color */}
165
+ Barefoot Shoes for{' '}
166
+ <span className="text-primary">Natural Movement</span>
167
+ </h1>
168
+ <p className="mt-6 text-lg text-muted-foreground leading-relaxed">
169
+ {subheadline}
170
+ </p>
171
+ <div className="mt-8 flex flex-wrap items-center gap-4">
172
+ <Link href={cta.primary.link} className="inline-flex items-center px-7 py-3.5 rounded-lg bg-primary text-primary-foreground font-semibold text-sm shadow-sm hover:bg-primary/90 transition-colors">
173
+ {cta.primary.text}
174
+ </Link>
175
+ {cta.secondary && (
176
+ <Link href={cta.secondary.link} className="inline-flex items-center px-7 py-3.5 rounded-lg border border-border font-semibold text-sm hover:bg-accent transition-colors">
177
+ {cta.secondary.text}
178
+ </Link>
179
+ )}
180
+ </div>
181
+
182
+ {/* Social proof (if exists in blueprint) */}
183
+ {socialProof && (
184
+ <div className="mt-10 flex items-center gap-8 border-t border-border/50 pt-8">
185
+ {socialProof.content.map((stat) => (
186
+ <div key={stat.label}>
187
+ <div className="text-2xl font-bold font-heading">{stat.value}</div>
188
+ <div className="text-xs text-muted-foreground">{stat.label}</div>
189
+ </div>
190
+ ))}
191
+ </div>
192
+ )}
193
+ </div>
194
+
195
+ {/* Image column */}
196
+ <div className="relative aspect-[4/5] lg:aspect-[3/4] rounded-2xl overflow-hidden">
197
+ {imageUrl ? (
198
+ <img src={imageUrl} alt={headline} className="absolute inset-0 w-full h-full object-cover" />
199
+ ) : (
200
+ <div className="absolute inset-0 bg-gradient-to-br from-primary/20 via-accent/10 to-secondary/20" />
201
+ )}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </section>
206
+ ```
207
+
208
+ ### Center Layout
209
+
210
+ Full-width centered text. Image as subtle background or below text.
211
+
212
+ ```tsx
213
+ <section className="relative py-24 lg:py-36">
214
+ {/* Optional background image with overlay */}
215
+ {imageUrl && (
216
+ <>
217
+ <img src={imageUrl} alt="" className="absolute inset-0 w-full h-full object-cover" />
218
+ <div className="absolute inset-0 bg-background/80 backdrop-blur-sm" />
219
+ </>
220
+ )}
221
+
222
+ <div className="relative container mx-auto px-4 sm:px-6 lg:px-8 text-center">
223
+ <h1 className="text-4xl sm:text-5xl lg:text-7xl font-bold font-heading tracking-tight leading-[1.1] max-w-4xl mx-auto">
224
+ {/* Emphasis words */}
225
+ Crafted for <span className="text-primary">Comfort</span>
226
+ </h1>
227
+ <p className="mt-6 text-lg lg:text-xl text-muted-foreground max-w-2xl mx-auto leading-relaxed">
228
+ {subheadline}
229
+ </p>
230
+ <div className="mt-10 flex items-center justify-center gap-4">
231
+ <Link href={cta.primary.link} className="inline-flex items-center px-8 py-4 rounded-lg bg-primary text-primary-foreground font-semibold shadow-sm hover:bg-primary/90 transition-colors">
232
+ {cta.primary.text}
233
+ </Link>
234
+ {cta.secondary && (
235
+ <Link href={cta.secondary.link} className="inline-flex items-center px-8 py-4 rounded-lg border border-border font-semibold hover:bg-accent transition-colors">
236
+ {cta.secondary.text}
237
+ </Link>
238
+ )}
239
+ </div>
240
+
241
+ {/* Social proof centered */}
242
+ {socialProof && (
243
+ <div className="mt-14 flex items-center justify-center gap-10 text-center">
244
+ {socialProof.content.map((stat) => (
245
+ <div key={stat.label}>
246
+ <div className="text-3xl font-bold font-heading">{stat.value}</div>
247
+ <div className="text-sm text-muted-foreground">{stat.label}</div>
248
+ </div>
249
+ ))}
250
+ </div>
251
+ )}
252
+ </div>
253
+ </section>
254
+ ```
255
+
256
+ ### Fullscreen Layout
257
+
258
+ Viewport-height hero with background image. Use transparent header (see above).
259
+
260
+ ```tsx
261
+ <section className="relative min-h-[90vh] flex items-end pb-20 lg:pb-28">
262
+ {/* Background image */}
263
+ {imageUrl ? (
264
+ <>
265
+ <img src={imageUrl} alt="" className="absolute inset-0 w-full h-full object-cover" />
266
+ <div className="absolute inset-0 bg-gradient-to-t from-background via-background/50 to-transparent" />
267
+ </>
268
+ ) : (
269
+ <div className="absolute inset-0 bg-gradient-to-br from-primary/30 via-background to-accent/20" />
270
+ )}
271
+
272
+ <div className="relative container mx-auto px-4 sm:px-6 lg:px-8">
273
+ <div className="max-w-2xl">
274
+ <h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold font-heading tracking-tight leading-[1.1] text-foreground">
275
+ {headline}
276
+ </h1>
277
+ <p className="mt-6 text-lg text-muted-foreground leading-relaxed">
278
+ {subheadline}
279
+ </p>
280
+ <div className="mt-8 flex flex-wrap items-center gap-4">
281
+ <Link href={cta.primary.link} className="inline-flex items-center px-7 py-3.5 rounded-lg bg-primary text-primary-foreground font-semibold shadow-sm hover:bg-primary/90 transition-colors">
282
+ {cta.primary.text}
283
+ </Link>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </section>
288
+ ```
289
+
290
+ ### Emphasis Words
291
+
292
+ Style words from `headline.emphasis[]` to stand out. Pick ONE approach per store:
293
+
294
+ ```tsx
295
+ {/* Option 1: Brand color */}
296
+ <span className="text-primary">Natural</span>
297
+
298
+ {/* Option 2: Gradient text */}
299
+ <span className="bg-gradient-to-r from-primary to-accent bg-clip-text text-transparent">Natural</span>
300
+
301
+ {/* Option 3: Underline accent */}
302
+ <span className="underline decoration-primary decoration-4 underline-offset-4">Natural</span>
303
+ ```
304
+
305
+ ### Hero Image Fallback
306
+
307
+ ALWAYS handle missing `imageUrl`:
308
+
309
+ ```tsx
310
+ {imageUrl ? (
311
+ <img src={imageUrl} alt={headline} className="w-full h-full object-cover" />
312
+ ) : (
313
+ <div className="w-full h-full bg-gradient-to-br from-primary/20 via-accent/10 to-secondary/20" />
314
+ )}
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Video in Sections
320
+
321
+ Any section that takes an `imageUrl` can take a video URL instead — the merchant can paste a `.mp4` / `.webm` / `.mov` URL into chat ("use this for the hero") or store it via the admin product gallery. When you receive a media URL, decide between `<img>` and `<video>` by file extension.
322
+
323
+ **Hero / background loop** — autoplay required, `muted` and `playsInline` are non-negotiable, `prefers-reduced-motion` falls back to a still:
324
+
325
+ ```tsx
326
+ {isVideoUrl(mediaUrl) ? (
327
+ <video
328
+ src={mediaUrl}
329
+ autoPlay
330
+ muted
331
+ loop
332
+ playsInline
333
+ preload="metadata"
334
+ className="absolute inset-0 w-full h-full object-cover motion-reduce:hidden"
335
+ />
336
+ ) : (
337
+ <img src={mediaUrl} alt={headline} className="absolute inset-0 w-full h-full object-cover" />
338
+ )}
339
+ ```
340
+
341
+ **Foreground / testimonial / product demo** — explicit user control:
342
+
343
+ ```tsx
344
+ <video
345
+ src={mediaUrl}
346
+ controls
347
+ preload="metadata"
348
+ className="w-full rounded-lg"
349
+ />
350
+ ```
351
+
352
+ The `isVideoUrl` helper is exported from `@rovela-ai/sdk/media`. Or inline it: `url.match(/\.(mp4|webm|mov)(\?|#|$)/i)`.
353
+
354
+ Never embed YouTube / Vimeo iframes — out of scope.
355
+
356
+ ---
357
+
358
+ ## Other Section Layout Patterns
359
+
360
+ ### Feature Grid (3-4 items)
361
+
362
+ ```tsx
363
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
364
+ {features.map((f) => (
365
+ <div key={f.title} className="p-6 rounded-lg bg-card border border-border">
366
+ <h3 className="text-lg font-semibold">{f.title}</h3>
367
+ <p className="mt-2 text-muted-foreground">{f.description}</p>
368
+ </div>
369
+ ))}
370
+ </div>
371
+ ```
372
+
373
+ ### Testimonial Cards
374
+
375
+ ```tsx
376
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
377
+ {testimonials.map((t) => (
378
+ <blockquote key={t.author} className="p-6 bg-card rounded-lg border border-border">
379
+ <p className="text-foreground italic">"{t.quote}"</p>
380
+ <footer className="mt-4 text-sm text-muted-foreground">— {t.author}</footer>
381
+ </blockquote>
382
+ ))}
383
+ </div>
384
+ ```
385
+
386
+ ### CTA Banner
387
+
388
+ ```tsx
389
+ <div className="text-center py-16 bg-primary text-primary-foreground rounded-2xl">
390
+ <h2 className="text-3xl font-bold">{headline}</h2>
391
+ <p className="mt-4 text-primary-foreground/80 max-w-xl mx-auto">{subheadline}</p>
392
+ <a href="/products" className="mt-8 inline-block px-8 py-3 bg-background text-foreground rounded-lg font-medium">
393
+ {buttonText}
394
+ </a>
395
+ </div>
396
+ ```
397
+
398
+ ## Valid Routes for Links
399
+
400
+ Link only to pages that exist:
401
+ - Storefront: `/`, `/products`, `/cart`, `/checkout`
402
+ - Auth: `/auth/signin`, `/auth/signup`, `/account`
403
+ - Legal: `/contact`, `/privacy`, `/terms`
404
+
405
+ Pages that DO NOT exist: `/about`, `/gallery`, `/pricing`, `/faq`, `/blog`, `/services`, `/team`
406
+
407
+ Use anchor links (`#features`, `#testimonials`) for same-page navigation.
408
+
409
+ ## Section Type Catalog (16 types — picked from the brief's `homepageStructure`)
410
+
411
+ When the merchant asks to "add a [section type]," look up the type in this catalog and use the matching skeleton. When they don't name a type, infer from intent ("add a process explanation" → `process-reveal`; "add an about-the-founders block" → `founders-note`; "add an editorial product spread" → `lookbook-spread`).
412
+
413
+ - **`hero`** — first-impression headline + subhead + CTA. Pattern per the brief's `Hero pattern:` callout (9 patterns).
414
+ - **`features`** — 3-4 card grid of brand benefits. `<section className="py-16 lg:py-24">` with `grid grid-cols-1 md:grid-cols-3 gap-8`. Each card: icon + h3 (≤4 words) + p (≤16 words).
415
+ - **`process-reveal`** — How it's made / roasted / cured. 3-5 process steps with large step numbers + h3 + 30-50 word description. Use real verbs.
416
+ - **`lookbook-spread`** — Editorial product spread (uses `editorial-2col` or `bento-grid` pattern). 3-6 products with prose copy.
417
+ - **`manifesto-block`** — Single-column long-form brand prose. `<section className="py-32 lg:py-48 bg-muted/30">` + h2 + 60-120 word paragraph in display voice.
418
+ - **`founders-note`** — Founder photo + signature quote. 5-col grid: photo (col-span-2) + quote (col-span-3, font-serif italic).
419
+ - **`values-strip`** — Compact icon + label row. 3-5 brand values, `min-h-[8vh]` short rhythm.
420
+ - **`how-it-works`** — Numbered steps for services/SaaS. 3 steps with big numbers + h3 + ≤20 word descriptions.
421
+ - **`testimonials`** — Customer quotes. 2-4 quotes max, ≤30 words each, name specific product attributes.
422
+ - **`social-proof`** — Press logos OR metric row ("4.9★ from 10K reviews"). Centered grid.
423
+ - **`guide-spread`** — Educational content. 80-120 word intro + 2-4 guide entries with inline images.
424
+ - **`faq`** — Accordion. 4-6 questions, ≤12 word question + ≤30 word answer.
425
+ - **`newsletter-cta`** — Newsletter signup. `<section className="py-20 lg:py-24 bg-muted/30 text-center">` + h2 + 30-word body + email input + button.
426
+ - **`trust`** — Trust badges row. 3-4 specific commitments with lucide icons + ≤8 word body.
427
+ - **`cta`** — Final call-to-action. Big headline + single primary button.
428
+ - **`product-showcase`** — Real products from DB. Pattern per the brief's `Product showcase:` callout (6 patterns).
429
+
430
+ ## Rhythm Sizes (5 sizes — variable cadence per section)
431
+
432
+ - **`xs`** — `min-h-[8vh] py-4 lg:py-6` — strip, ribbon, pull-quote
433
+ - **`sm`** — `py-12 lg:py-16` — trust strips, newsletter, social proof
434
+ - **`md`** — `py-20 lg:py-24` — default for commerce sections
435
+ - **`lg`** — `min-h-screen` or `py-32 lg:py-40` — hero, CTA, manifesto
436
+ - **`xl`** — `min-h-[150vh]` or `py-40 lg:py-64` — editorial deep dives, lookbook spreads
437
+
438
+ When adding a section, pick a rhythm that VARIES the page cadence — if surrounding sections are `md-md-md`, an inserted `xs` (strip) or `lg` (deep) breaks the rhythm and reads as designed.
@@ -0,0 +1,201 @@
1
+ ---
2
+ name: build-feature
3
+ description: Use when the user asks for a new custom feature or module (booking, reviews, loyalty, wishlist, blog, etc.) that requires new database tables, API routes, and pages.
4
+ ---
5
+
6
+ # Build Custom Feature
7
+
8
+ ## Workflow
9
+
10
+ 1. **Create database tables** via `getSql()` and `execute_sql` MCP tool
11
+ 2. **Build API routes** in `/app/api/` using `getSql()` for queries
12
+ 3. **Build frontend pages** (customer-facing and/or admin)
13
+ 4. **Update navigation** (Header, Footer, admin sidebar)
14
+ 5. **Verify** with `get_errors` after each file
15
+
16
+ ## Database Access
17
+
18
+ **CRITICAL**: The SDK's `db` export is a Drizzle ORM instance. It does NOT have `.query()`, `.execute()`, or `.run()` methods.
19
+
20
+ ### For custom tables, use `getSql()`:
21
+
22
+ ```typescript
23
+ import { getSql } from '@rovela-ai/sdk/core/server'
24
+
25
+ const sql = getSql()
26
+
27
+ // Results are arrays directly — NO .rows wrapper
28
+ const rows = await sql`SELECT * FROM my_table WHERE status = ${status}`
29
+ // rows = [{id: '...', name: '...'}, ...]
30
+
31
+ // Parameterized (safe from SQL injection)
32
+ await sql`INSERT INTO my_table (name, value) VALUES (${name}, ${value}) RETURNING *`
33
+ ```
34
+
35
+ ### For existing tables, use SDK query helpers:
36
+
37
+ ```typescript
38
+ import { findProducts, findOrders, findCustomerByEmail } from '@rovela-ai/sdk/core/server'
39
+
40
+ const products = await findProducts({ status: 'active' })
41
+ const order = await findOrderById(orderId)
42
+ ```
43
+
44
+ ## Create Tables
45
+
46
+ Use `execute_sql` MCP tool first, then verify:
47
+
48
+ ```sql
49
+ CREATE TABLE IF NOT EXISTS my_feature (
50
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
51
+ name TEXT NOT NULL,
52
+ status TEXT DEFAULT 'active',
53
+ data JSONB DEFAULT '{}',
54
+ created_at TIMESTAMPTZ DEFAULT NOW(),
55
+ updated_at TIMESTAMPTZ DEFAULT NOW()
56
+ );
57
+ ```
58
+
59
+ Then confirm with `list_objects` and `get_object_details`.
60
+
61
+ ## Custom API Route Template
62
+
63
+ ```typescript
64
+ // app/api/my-feature/route.ts
65
+ import { NextRequest, NextResponse } from 'next/server'
66
+ import { getSql } from '@rovela-ai/sdk/core/server'
67
+
68
+ export async function GET(request: NextRequest) {
69
+ try {
70
+ const sql = getSql()
71
+ const { searchParams } = new URL(request.url)
72
+ const status = searchParams.get('status') || 'active'
73
+
74
+ const rows = await sql`
75
+ SELECT * FROM my_feature WHERE status = ${status}
76
+ ORDER BY created_at DESC
77
+ `
78
+ return NextResponse.json({ data: rows })
79
+ } catch (error) {
80
+ console.error('[MyFeature] GET error:', error)
81
+ return NextResponse.json({ error: 'Failed to load' }, { status: 500 })
82
+ }
83
+ }
84
+
85
+ export async function POST(request: NextRequest) {
86
+ try {
87
+ const body = await request.json()
88
+ if (!body.name) {
89
+ return NextResponse.json({ error: 'Name is required' }, { status: 400 })
90
+ }
91
+
92
+ const sql = getSql()
93
+ const result = await sql`
94
+ INSERT INTO my_feature (name, data)
95
+ VALUES (${body.name}, ${JSON.stringify(body.data || {})}::jsonb)
96
+ RETURNING *
97
+ `
98
+ return NextResponse.json({ data: result[0] }, { status: 201 })
99
+ } catch (error) {
100
+ console.error('[MyFeature] POST error:', error)
101
+ return NextResponse.json({ error: 'Failed to create' }, { status: 500 })
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Admin Page Pattern
107
+
108
+ Admin pages go in `app/admin/(dashboard)/`. **The Admin Design Contract (CLAUDE.md §7.6) is BINDING here** — use the SDK admin kit, never rebuild it. Full visual canon (only if §7.6 leaves a question open): Grep sections of `node_modules/@rovela-ai/sdk/docs/design/admin-ledger-visual-universe.html` — never read it whole.
109
+
110
+ ```typescript
111
+ // app/admin/(dashboard)/my-feature/page.tsx
112
+ 'use client'
113
+
114
+ import { useState, useEffect } from 'react'
115
+ import { AdminPageHeader, AdminEmptyState, adminToast } from '@rovela-ai/sdk/admin'
116
+ import { Calendar } from 'lucide-react'
117
+
118
+ export default function AdminMyFeaturePage() {
119
+ const [data, setData] = useState<MyRow[]>([])
120
+ const [loading, setLoading] = useState(true)
121
+ const [loadError, setLoadError] = useState<string | null>(null)
122
+
123
+ useEffect(() => {
124
+ fetch('/api/admin/my-feature')
125
+ .then(res => res.ok ? res.json() : Promise.reject(res))
126
+ .then(json => setData(json.data || []))
127
+ .catch(() => setLoadError('Could not load. Refresh to retry.'))
128
+ .finally(() => setLoading(false))
129
+ }, [])
130
+
131
+ return (
132
+ <div className="space-y-6">
133
+ {/* AdminPageHeader owns the title + primary action — never a raw <h1> */}
134
+ <AdminPageHeader title="My feature" subtitle="One line on what this manages." />
135
+ <div className="admin-card admin-card-body">
136
+ {loadError && <div className="admin-alert-inline admin-alert-inline-error mb-4">{loadError}</div>}
137
+ {loading ? (
138
+ <div className="flex justify-center p-12"><span className="admin-spinner admin-spinner-lg" /></div>
139
+ ) : data.length === 0 ? (
140
+ <AdminEmptyState icon={<Calendar />} title="Nothing here yet" description="Create your first item to get started." />
141
+ ) : (
142
+ /* Tables: wrap in <div className="overflow-x-auto">, mark secondary
143
+ columns admin-col-optional. 2-4 column card lists: stacked rows,
144
+ not a table. Admin tokens only (hsl(var(--admin-*)) / admin-*
145
+ classes) — NEVER storefront tokens like text-muted-foreground. */
146
+ <div className="overflow-x-auto">{/* rows */}</div>
147
+ )}
148
+ </div>
149
+ </div>
150
+ )
151
+ }
152
+ ```
153
+
154
+ Non-negotiables on every admin page you build:
155
+ - **Spinner**: the `admin-spinner` class — never `Loader2` or any animated lucide icon.
156
+ - **Events → `adminToast.success/error`** (deleted, sent, saved-transactionally, failed); **states → inline `admin-alert`**. Never both for one outcome.
157
+ - **Dialogs → `AdminModal`; deletes → `DeleteConfirmDialog`** — never native `confirm()`/`alert()`, never a hand-rolled fixed overlay, never inline "Delete? Yes/No".
158
+ - **Document-shaped forms** (settings-like editing of an existing entity) save through `AdminSaveBar` + `useDirtyState`; transactional create/edit keeps inline buttons.
159
+ - **Money**: decimal currency units (`29.99`), never cents. **Dates**: `useAdminDates` (store timezone). **Orders**: display in `order.currency`.
160
+ - **375px**: responsive grid prefixes, no fixed widths over ~340px.
161
+
162
+ ## Admin Sidebar Navigation
163
+
164
+ When your feature has an admin page, you MUST add it to the admin sidebar.
165
+
166
+ Edit `app/admin/(dashboard)/layout.tsx` to pass `navItems` to `AdminLayout`:
167
+
168
+ ```typescript
169
+ import { Calendar } from 'lucide-react'
170
+
171
+ <AdminLayout
172
+ navItems={[
173
+ { id: 'my-feature', label: 'My Feature', href: '/admin/my-feature', icon: <Calendar className="h-4 w-4" /> },
174
+ ]}
175
+ >
176
+ ```
177
+
178
+ If `navItems` already exists, add your item to the existing array.
179
+ Active state is auto-detected from the URL — no extra config needed.
180
+
181
+ ## Common Mistakes
182
+
183
+ | Mistake | Fix |
184
+ |---------|-----|
185
+ | `db.query(sql)` | Use `getSql()` tagged templates |
186
+ | `db.execute(sql)` | Use `getSql()` tagged templates |
187
+ | `result.rows[0]` | `result[0]` — no `.rows` wrapper |
188
+ | Missing `?.` on API responses | Always check `res.ok` before accessing data |
189
+ | Admin API auth check with `getServerSession` | Admin pages are already protected by `AdminGuard` layout |
190
+ | Forgot admin sidebar item | If feature has admin page, MUST add `navItems` to AdminLayout in layout.tsx |
191
+
192
+ ## Checklist
193
+
194
+ - [ ] Tables created and verified with `list_objects`
195
+ - [ ] API routes return correct data (test with `execute_sql` for comparison)
196
+ - [ ] Frontend fetches from real API (not hardcoded data)
197
+ - [ ] Error states handled (loading, empty, error)
198
+ - [ ] `get_errors` returns zero errors
199
+ - [ ] Customer navigation updated (Header/Footer links)
200
+ - [ ] Admin sidebar updated if feature has admin page (`navItems` in layout.tsx)
201
+ - [ ] Mobile responsive (no fixed widths)