create-magic-storefront 0.1.1 → 0.1.2

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 (107) hide show
  1. package/package.json +1 -1
  2. package/template/.claude/skills/storefront-design/SKILL.md +10 -3
  3. package/template/.claude/skills/storefront-verify/SKILL.md +85 -0
  4. package/template/.claude/skills/vercel-react-best-practices/AGENTS.md +3810 -0
  5. package/template/.claude/skills/vercel-react-best-practices/README.md +123 -0
  6. package/template/.claude/skills/vercel-react-best-practices/SKILL.md +149 -0
  7. package/template/.claude/skills/vercel-react-best-practices/SOURCE.md +7 -0
  8. package/template/.claude/skills/vercel-react-best-practices/metadata.json +15 -0
  9. package/template/.claude/skills/vercel-react-best-practices/rules/_sections.md +46 -0
  10. package/template/.claude/skills/vercel-react-best-practices/rules/_template.md +28 -0
  11. package/template/.claude/skills/vercel-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
  12. package/template/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  13. package/template/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  14. package/template/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  15. package/template/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  16. package/template/.claude/skills/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
  17. package/template/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +82 -0
  18. package/template/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  19. package/template/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  20. package/template/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  21. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
  22. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +60 -0
  23. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  24. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  25. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  26. package/template/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  27. package/template/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  28. package/template/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  29. package/template/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  30. package/template/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  31. package/template/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  32. package/template/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  33. package/template/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  34. package/template/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  35. package/template/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  36. package/template/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  37. package/template/.claude/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
  38. package/template/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  39. package/template/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  40. package/template/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  41. package/template/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  42. package/template/.claude/skills/vercel-react-best-practices/rules/js-request-idle-callback.md +105 -0
  43. package/template/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  44. package/template/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  45. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  46. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  47. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  48. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  49. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  50. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  51. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  52. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
  53. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
  54. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  55. package/template/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  56. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  57. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  58. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  59. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  60. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  61. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  62. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  63. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  64. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  65. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
  66. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  67. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
  68. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  69. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
  70. package/template/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  71. package/template/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  72. package/template/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  73. package/template/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  74. package/template/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  75. package/template/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  76. package/template/.claude/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +149 -0
  77. package/template/.claude/skills/vercel-react-best-practices/rules/server-no-shared-module-state.md +50 -0
  78. package/template/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  79. package/template/.claude/skills/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
  80. package/template/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  81. package/template/.claude/skills/web-interface-guidelines/LICENSE +21 -0
  82. package/template/.claude/skills/web-interface-guidelines/SKILL.md +40 -0
  83. package/template/.claude/skills/web-interface-guidelines/SOURCE.md +9 -0
  84. package/template/.claude/skills/web-interface-guidelines/guidelines.md +155 -0
  85. package/template/AGENTS.md +7 -0
  86. package/template/PAGES.md +7 -2
  87. package/template/_gitignore +1 -0
  88. package/template/app/account/page.tsx +31 -11
  89. package/template/app/cart/page.tsx +50 -54
  90. package/template/app/checkout/page.tsx +86 -38
  91. package/template/app/collections/[handle]/page.tsx +3 -2
  92. package/template/app/error.tsx +6 -4
  93. package/template/app/globals.css +29 -0
  94. package/template/app/layout.tsx +4 -2
  95. package/template/app/not-found.tsx +5 -2
  96. package/template/app/pages/[handle]/page.tsx +3 -2
  97. package/template/app/search/page.tsx +11 -6
  98. package/template/components/buy-box.tsx +7 -4
  99. package/template/components/cart-link.tsx +8 -2
  100. package/template/components/pager.tsx +3 -1
  101. package/template/components/product-grid.tsx +9 -5
  102. package/template/components/sections/banner.tsx +7 -1
  103. package/template/components/sections/deal-of-day.tsx +16 -5
  104. package/template/components/sections/product-shelves.tsx +8 -7
  105. package/template/components/sections/store-reviews.tsx +7 -5
  106. package/template/lib/errors.ts +1 -4
  107. package/template/lib/i18n.ts +315 -0
@@ -0,0 +1,65 @@
1
+ ---
2
+ title: Avoid Duplicate Serialization in RSC Props
3
+ impact: LOW
4
+ impactDescription: reduces network payload by avoiding duplicate serialization
5
+ tags: server, rsc, serialization, props, client-components
6
+ ---
7
+
8
+ ## Avoid Duplicate Serialization in RSC Props
9
+
10
+ **Impact: LOW (reduces network payload by avoiding duplicate serialization)**
11
+
12
+ RSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server.
13
+
14
+ **Incorrect (duplicates array):**
15
+
16
+ ```tsx
17
+ // RSC: sends 6 strings (2 arrays × 3 items)
18
+ <ClientList usernames={usernames} usernamesOrdered={usernames.toSorted()} />
19
+ ```
20
+
21
+ **Correct (sends 3 strings):**
22
+
23
+ ```tsx
24
+ // RSC: send once
25
+ <ClientList usernames={usernames} />
26
+
27
+ // Client: transform there
28
+ 'use client'
29
+ const sorted = useMemo(() => [...usernames].sort(), [usernames])
30
+ ```
31
+
32
+ **Nested deduplication behavior:**
33
+
34
+ Deduplication works recursively. Impact varies by data type:
35
+
36
+ - `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated
37
+ - `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference
38
+
39
+ ```tsx
40
+ // string[] - duplicates everything
41
+ usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings
42
+
43
+ // object[] - duplicates array structure only
44
+ users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4)
45
+ ```
46
+
47
+ **Operations breaking deduplication (create new references):**
48
+
49
+ - Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]`
50
+ - Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())`
51
+
52
+ **More examples:**
53
+
54
+ ```tsx
55
+ // ❌ Bad
56
+ <C users={users} active={users.filter(u => u.active)} />
57
+ <C product={product} productName={product.name} />
58
+
59
+ // ✅ Good
60
+ <C users={users} />
61
+ <C product={product} />
62
+ // Do filtering/destructuring in client
63
+ ```
64
+
65
+ **Exception:** Pass derived data when transformation is expensive or client doesn't need original.
@@ -0,0 +1,149 @@
1
+ ---
2
+ title: Hoist Static I/O to Module Level
3
+ impact: HIGH
4
+ impactDescription: avoids repeated file/network I/O per request
5
+ tags: server, io, performance, next.js, route-handlers, og-image
6
+ ---
7
+
8
+ ## Hoist Static I/O to Module Level
9
+
10
+ **Impact: HIGH (avoids repeated file/network I/O per request)**
11
+
12
+ When loading static assets (fonts, logos, images, config files) in route handlers or server functions, hoist the I/O operation to module level. Module-level code runs once when the module is first imported, not on every request. This eliminates redundant file system reads or network fetches that would otherwise run on every invocation.
13
+
14
+ **Incorrect (reads font file on every request):**
15
+
16
+ ```typescript
17
+ // app/api/og/route.tsx
18
+ import { ImageResponse } from 'next/og'
19
+
20
+ export async function GET(request: Request) {
21
+ // Runs on EVERY request - expensive!
22
+ const fontData = await fetch(
23
+ new URL('./fonts/Inter.ttf', import.meta.url)
24
+ ).then(res => res.arrayBuffer())
25
+
26
+ const logoData = await fetch(
27
+ new URL('./images/logo.png', import.meta.url)
28
+ ).then(res => res.arrayBuffer())
29
+
30
+ return new ImageResponse(
31
+ <div style={{ fontFamily: 'Inter' }}>
32
+ <img src={logoData} />
33
+ Hello World
34
+ </div>,
35
+ { fonts: [{ name: 'Inter', data: fontData }] }
36
+ )
37
+ }
38
+ ```
39
+
40
+ **Correct (loads once at module initialization):**
41
+
42
+ ```typescript
43
+ // app/api/og/route.tsx
44
+ import { ImageResponse } from 'next/og'
45
+
46
+ // Module-level: runs ONCE when module is first imported
47
+ const fontData = fetch(
48
+ new URL('./fonts/Inter.ttf', import.meta.url)
49
+ ).then(res => res.arrayBuffer())
50
+
51
+ const logoData = fetch(
52
+ new URL('./images/logo.png', import.meta.url)
53
+ ).then(res => res.arrayBuffer())
54
+
55
+ export async function GET(request: Request) {
56
+ // Await the already-started promises
57
+ const [font, logo] = await Promise.all([fontData, logoData])
58
+
59
+ return new ImageResponse(
60
+ <div style={{ fontFamily: 'Inter' }}>
61
+ <img src={logo} />
62
+ Hello World
63
+ </div>,
64
+ { fonts: [{ name: 'Inter', data: font }] }
65
+ )
66
+ }
67
+ ```
68
+
69
+ **Correct (synchronous fs at module level):**
70
+
71
+ ```typescript
72
+ // app/api/og/route.tsx
73
+ import { ImageResponse } from 'next/og'
74
+ import { readFileSync } from 'fs'
75
+ import { join } from 'path'
76
+
77
+ // Synchronous read at module level - blocks only during module init
78
+ const fontData = readFileSync(
79
+ join(process.cwd(), 'public/fonts/Inter.ttf')
80
+ )
81
+
82
+ const logoData = readFileSync(
83
+ join(process.cwd(), 'public/images/logo.png')
84
+ )
85
+
86
+ export async function GET(request: Request) {
87
+ return new ImageResponse(
88
+ <div style={{ fontFamily: 'Inter' }}>
89
+ <img src={logoData} />
90
+ Hello World
91
+ </div>,
92
+ { fonts: [{ name: 'Inter', data: fontData }] }
93
+ )
94
+ }
95
+ ```
96
+
97
+ **Incorrect (reads config on every call):**
98
+
99
+ ```typescript
100
+ import fs from 'node:fs/promises'
101
+
102
+ export async function processRequest(data: Data) {
103
+ const config = JSON.parse(
104
+ await fs.readFile('./config.json', 'utf-8')
105
+ )
106
+ const template = await fs.readFile('./template.html', 'utf-8')
107
+
108
+ return render(template, data, config)
109
+ }
110
+ ```
111
+
112
+ **Correct (hoists config and template to module level):**
113
+
114
+ ```typescript
115
+ import fs from 'node:fs/promises'
116
+
117
+ const configPromise = fs
118
+ .readFile('./config.json', 'utf-8')
119
+ .then(JSON.parse)
120
+ const templatePromise = fs.readFile('./template.html', 'utf-8')
121
+
122
+ export async function processRequest(data: Data) {
123
+ const [config, template] = await Promise.all([
124
+ configPromise,
125
+ templatePromise,
126
+ ])
127
+
128
+ return render(template, data, config)
129
+ }
130
+ ```
131
+
132
+ When to use this pattern:
133
+
134
+ - Loading fonts for OG image generation
135
+ - Loading static logos, icons, or watermarks
136
+ - Reading configuration files that don't change at runtime
137
+ - Loading email templates or other static templates
138
+ - Any static asset that's the same across all requests
139
+
140
+ When not to use this pattern:
141
+
142
+ - Assets that vary per request or user
143
+ - Files that may change during runtime (use caching with TTL instead)
144
+ - Large files that would consume too much memory if kept loaded
145
+ - Sensitive data that shouldn't persist in memory
146
+
147
+ With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute), module-level caching is especially effective because multiple concurrent requests share the same function instance. The static assets stay loaded in memory across requests without cold start penalties.
148
+
149
+ In traditional serverless, each cold start re-executes module-level code, but subsequent warm invocations reuse the loaded assets until the instance is recycled.
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Avoid Shared Module State for Request Data
3
+ impact: HIGH
4
+ impactDescription: prevents concurrency bugs and request data leaks
5
+ tags: server, rsc, ssr, concurrency, security, state
6
+ ---
7
+
8
+ ## Avoid Shared Module State for Request Data
9
+
10
+ For React Server Components and client components rendered during SSR, avoid using mutable module-level variables to share request-scoped data. Server renders can run concurrently in the same process. If one render writes to shared module state and another render reads it, you can get race conditions, cross-request contamination, and security bugs where one user's data appears in another user's response.
11
+
12
+ Treat module scope on the server as process-wide shared memory, not request-local state.
13
+
14
+ **Incorrect (request data leaks across concurrent renders):**
15
+
16
+ ```tsx
17
+ let currentUser: User | null = null
18
+
19
+ export default async function Page() {
20
+ currentUser = await auth()
21
+ return <Dashboard />
22
+ }
23
+
24
+ async function Dashboard() {
25
+ return <div>{currentUser?.name}</div>
26
+ }
27
+ ```
28
+
29
+ If two requests overlap, request A can set `currentUser`, then request B overwrites it before request A finishes rendering `Dashboard`.
30
+
31
+ **Correct (keep request data local to the render tree):**
32
+
33
+ ```tsx
34
+ export default async function Page() {
35
+ const user = await auth()
36
+ return <Dashboard user={user} />
37
+ }
38
+
39
+ function Dashboard({ user }: { user: User | null }) {
40
+ return <div>{user?.name}</div>
41
+ }
42
+ ```
43
+
44
+ Safe exceptions:
45
+
46
+ - Immutable static assets or config loaded once at module scope
47
+ - Shared caches intentionally designed for cross-request reuse and keyed correctly
48
+ - Process-wide singletons that do not store request- or user-specific mutable data
49
+
50
+ For static assets and config, see [Hoist Static I/O to Module Level](./server-hoist-static-io.md).
@@ -0,0 +1,83 @@
1
+ ---
2
+ title: Parallel Data Fetching with Component Composition
3
+ impact: CRITICAL
4
+ impactDescription: eliminates server-side waterfalls
5
+ tags: server, rsc, parallel-fetching, composition
6
+ ---
7
+
8
+ ## Parallel Data Fetching with Component Composition
9
+
10
+ React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching.
11
+
12
+ **Incorrect (Sidebar waits for Page's fetch to complete):**
13
+
14
+ ```tsx
15
+ export default async function Page() {
16
+ const header = await fetchHeader()
17
+ return (
18
+ <div>
19
+ <div>{header}</div>
20
+ <Sidebar />
21
+ </div>
22
+ )
23
+ }
24
+
25
+ async function Sidebar() {
26
+ const items = await fetchSidebarItems()
27
+ return <nav>{items.map(renderItem)}</nav>
28
+ }
29
+ ```
30
+
31
+ **Correct (both fetch simultaneously):**
32
+
33
+ ```tsx
34
+ async function Header() {
35
+ const data = await fetchHeader()
36
+ return <div>{data}</div>
37
+ }
38
+
39
+ async function Sidebar() {
40
+ const items = await fetchSidebarItems()
41
+ return <nav>{items.map(renderItem)}</nav>
42
+ }
43
+
44
+ export default function Page() {
45
+ return (
46
+ <div>
47
+ <Header />
48
+ <Sidebar />
49
+ </div>
50
+ )
51
+ }
52
+ ```
53
+
54
+ **Alternative with children prop:**
55
+
56
+ ```tsx
57
+ async function Header() {
58
+ const data = await fetchHeader()
59
+ return <div>{data}</div>
60
+ }
61
+
62
+ async function Sidebar() {
63
+ const items = await fetchSidebarItems()
64
+ return <nav>{items.map(renderItem)}</nav>
65
+ }
66
+
67
+ function Layout({ children }: { children: ReactNode }) {
68
+ return (
69
+ <div>
70
+ <Header />
71
+ {children}
72
+ </div>
73
+ )
74
+ }
75
+
76
+ export default function Page() {
77
+ return (
78
+ <Layout>
79
+ <Sidebar />
80
+ </Layout>
81
+ )
82
+ }
83
+ ```
@@ -0,0 +1,34 @@
1
+ ---
2
+ title: Parallel Nested Data Fetching
3
+ impact: CRITICAL
4
+ impactDescription: eliminates server-side waterfalls
5
+ tags: server, rsc, parallel-fetching, promise-chaining
6
+ ---
7
+
8
+ ## Parallel Nested Data Fetching
9
+
10
+ When fetching nested data in parallel, chain dependent fetches within each item's promise so a slow item doesn't block the rest.
11
+
12
+ **Incorrect (a single slow item blocks all nested fetches):**
13
+
14
+ ```tsx
15
+ const chats = await Promise.all(
16
+ chatIds.map(id => getChat(id))
17
+ )
18
+
19
+ const chatAuthors = await Promise.all(
20
+ chats.map(chat => getUser(chat.author))
21
+ )
22
+ ```
23
+
24
+ If one `getChat(id)` out of 100 is extremely slow, the authors of the other 99 chats can't start loading even though their data is ready.
25
+
26
+ **Correct (each item chains its own nested fetch):**
27
+
28
+ ```tsx
29
+ const chatAuthors = await Promise.all(
30
+ chatIds.map(id => getChat(id).then(chat => getUser(chat.author)))
31
+ )
32
+ ```
33
+
34
+ Each item independently chains `getChat` → `getUser`, so a slow chat doesn't block author fetches for the others.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: Minimize Serialization at RSC Boundaries
3
+ impact: HIGH
4
+ impactDescription: reduces data transfer size
5
+ tags: server, rsc, serialization, props
6
+ ---
7
+
8
+ ## Minimize Serialization at RSC Boundaries
9
+
10
+ The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses.
11
+
12
+ **Incorrect (serializes all 50 fields):**
13
+
14
+ ```tsx
15
+ async function Page() {
16
+ const user = await fetchUser() // 50 fields
17
+ return <Profile user={user} />
18
+ }
19
+
20
+ 'use client'
21
+ function Profile({ user }: { user: User }) {
22
+ return <div>{user.name}</div> // uses 1 field
23
+ }
24
+ ```
25
+
26
+ **Correct (serializes only 1 field):**
27
+
28
+ ```tsx
29
+ async function Page() {
30
+ const user = await fetchUser()
31
+ return <Profile name={user.name} />
32
+ }
33
+
34
+ 'use client'
35
+ function Profile({ name }: { name: string }) {
36
+ return <div>{name}</div>
37
+ }
38
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vercel Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: web-interface-guidelines
3
+ description: >-
4
+ Vercel's Web Interface Guidelines: keyboard and focus, forms, touch targets, animation, layout,
5
+ content, performance and contrast rules for accessible, polished UIs. Use when building or
6
+ reviewing any interactive UI in this storefront (header, product options, cart, checkout,
7
+ account forms, dialogs, menus), when asked to "review the UI", "check accessibility" or "audit
8
+ UX", and as the review pass of `storefront-verify`.
9
+ license: MIT
10
+ ---
11
+
12
+ # Web Interface Guidelines
13
+
14
+ The rules are in [`guidelines.md`](guidelines.md) (MUST / SHOULD / NEVER), vendored from Vercel so
15
+ they work offline. Read it before building or reviewing interactive UI.
16
+
17
+ ## Building
18
+
19
+ Apply the MUST rules as you write. The ones a storefront breaks most often:
20
+
21
+ - Forms (checkout, account, OTP): a `<label>` for every field, the right `type`, `inputmode` and
22
+ `autocomplete` (`tel`, `one-time-code`, `street-address`), errors next to the field, focus on the
23
+ first error on submit, the submit button enabled until the request starts.
24
+ - Targets: at least 44px on mobile (the storefront also runs as a Telegram Mini App), mobile
25
+ `<input>` font-size at least 16px.
26
+ - Focus: a visible `:focus-visible` ring on every control; a sticky header never covers it.
27
+ - Feedback: cart and checkout updates announced with `aria-live="polite"`.
28
+ - Images: `alt` from the API or the product title; decorative images `alt=""`.
29
+
30
+ Where `storefront-design` and this file disagree, `storefront-design` wins (it knows the API: for
31
+ example, contrast of a color pair the merchant set is reported, not overridden).
32
+
33
+ ## Reviewing
34
+
35
+ 1. Read `guidelines.md`.
36
+ 2. Read the files under review (or every file changed since the last commit:
37
+ `git diff --name-only HEAD`).
38
+ 3. Report every violation as `path:line — rule — fix`, most severe first (MUST before SHOULD).
39
+ No findings: say so in one line.
40
+ 4. Fix the MUST violations unless asked only to review.
@@ -0,0 +1,9 @@
1
+ # Source
2
+
3
+ `guidelines.md` is vendored unchanged from
4
+ [vercel-labs/web-interface-guidelines](https://github.com/vercel-labs/web-interface-guidelines),
5
+ `AGENTS.md` at commit `e3d624baaf29dc1fc645aff3e38f03e564d2d6b1` (MIT, see `LICENSE`). `SKILL.md`
6
+ is this storefront's own: it tells the agent when to apply the guidelines, and works offline
7
+ instead of fetching them.
8
+
9
+ To update, copy the upstream `AGENTS.md` over `guidelines.md` and record the new commit above.
@@ -0,0 +1,155 @@
1
+ Concise rules for building accessible, fast, delightful UIs. Use MUST/SHOULD/NEVER to guide decisions.
2
+
3
+ ## Interactions
4
+
5
+ ### Keyboard
6
+
7
+ - MUST: Full keyboard support per [WAI-ARIA APG](https://www.w3.org/WAI/ARIA/apg/patterns/)
8
+ - MUST: Visible, unobscured focus rings (`:focus-visible`; group with `:focus-within`); sticky/fixed elements never cover focus
9
+ - MUST: Manage focus (trap, move, return) per APG patterns
10
+ - NEVER: `outline: none` without visible focus replacement
11
+
12
+ ### Targets & Input
13
+
14
+ - MUST: Hit target ≥24px (mobile ≥44px); if visual <24px, expand hit area
15
+ - MUST: Mobile `<input>` font-size ≥16px to prevent iOS zoom
16
+ - NEVER: Disable browser zoom (`user-scalable=no`, `maximum-scale=1`)
17
+ - MUST: `touch-action: manipulation` to prevent double-tap zoom
18
+ - SHOULD: Set `-webkit-tap-highlight-color` to match design
19
+
20
+ ### Forms
21
+
22
+ - MUST: Hydration-safe inputs (no lost focus/value)
23
+ - NEVER: Block paste in `<input>`/`<textarea>`
24
+ - MUST: Loading buttons show spinner and keep original label
25
+ - MUST: Enter submits focused input; in `<textarea>`, ⌘/Ctrl+Enter submits
26
+ - MUST: Keep submit enabled until request starts; then disable with spinner
27
+ - MUST: Accept free text, validate after—don't block typing
28
+ - MUST: Allow incomplete form submission to surface validation
29
+ - MUST: Errors inline next to fields; on submit, focus first error
30
+ - MUST: `autocomplete` + meaningful `name`; correct `type` and `inputmode`
31
+ - SHOULD: Disable spellcheck for emails/codes/usernames
32
+ - SHOULD: Placeholders end with `…` and show example pattern
33
+ - MUST: Warn on unsaved changes before navigation
34
+ - MUST: Compatible with password managers & 2FA; allow pasting codes
35
+ - MUST: Trim values to handle text expansion trailing spaces
36
+ - MUST: No dead zones on checkboxes/radios; label+control share one hit target
37
+
38
+ ### State & Navigation
39
+
40
+ - MUST: URL reflects state (deep-link filters/tabs/pagination/expanded panels)
41
+ - MUST: Back/Forward restores scroll position
42
+ - MUST: Links use `<a>`/`<Link>` for navigation (support Cmd/Ctrl/middle-click)
43
+ - NEVER: Use `<div onClick>` for navigation
44
+
45
+ ### Feedback
46
+
47
+ - SHOULD: Optimistic UI; reconcile on response; on failure rollback or offer Undo
48
+ - MUST: Confirm destructive actions or provide Undo window
49
+ - MUST: Use polite `aria-live` for toasts/inline validation
50
+ - SHOULD: Ellipsis (`…`) for options opening follow-ups ("Rename…") and loading states ("Loading…")
51
+
52
+ ### Touch & Drag
53
+
54
+ - MUST: Generous targets, clear affordances; avoid finicky interactions
55
+ - MUST: Delay first tooltip; subsequent peers instant
56
+ - MUST: `overscroll-behavior: contain` in modals/drawers
57
+ - MUST: During drag, disable text selection and set `inert` on dragged elements
58
+ - MUST: Drag/swipe/pinch/path gestures have a tap/click and keyboard alternative unless essential
59
+ - MUST: If it looks clickable, it must be clickable
60
+
61
+ ### Autofocus
62
+
63
+ - SHOULD: Autofocus on desktop with single primary input; rarely on mobile
64
+
65
+ ## Animation
66
+
67
+ - MUST: Honor `prefers-reduced-motion` (provide reduced variant or disable)
68
+ - SHOULD: Prefer CSS > Web Animations API > JS libraries
69
+ - MUST: Animate compositor-friendly props (`transform`, `opacity`) only
70
+ - NEVER: Animate layout props (`top`, `left`, `width`, `height`)
71
+ - NEVER: `transition: all`—list properties explicitly
72
+ - SHOULD: Animate only to clarify cause/effect or add deliberate delight
73
+ - SHOULD: Choose easing to match the change (size/distance/trigger)
74
+ - MUST: Animations interruptible and input-driven; autoplay only for muted, non-essential loops
75
+ - MUST: Autoplay motion >5s alongside other content has pause, stop, or hide controls
76
+ - MUST: Correct `transform-origin` (motion starts where it "physically" should)
77
+ - MUST: SVG transforms on `<g>` wrapper with `transform-box: fill-box`
78
+
79
+ ## Layout
80
+
81
+ - SHOULD: Optical alignment; adjust ±1px when perception beats geometry
82
+ - MUST: Deliberate alignment to grid/baseline/edges—no accidental placement
83
+ - SHOULD: Balance icon/text lockups (weight/size/spacing/color)
84
+ - MUST: Verify mobile, laptop, ultra-wide (simulate ultra-wide at 50% zoom)
85
+ - MUST: Respect safe areas (`env(safe-area-inset-*)`)
86
+ - MUST: Avoid unwanted scrollbars; fix overflows
87
+ - SHOULD: Flex/grid over JS measurement for layout
88
+
89
+ ## Content & Accessibility
90
+
91
+ - SHOULD: Inline help first; tooltips last resort
92
+ - MUST: Skeletons mirror final content to avoid layout shift
93
+ - MUST: `<title>` matches current context
94
+ - MUST: No dead ends; always offer next step/recovery
95
+ - MUST: Design empty/sparse/dense/error states
96
+ - SHOULD: Curly quotes (“ ”); avoid widows/orphans (`text-wrap: balance`)
97
+ - MUST: `font-variant-numeric: tabular-nums` for number comparisons
98
+ - MUST: Redundant status cues (not color-only); icons have text labels
99
+ - MUST: Accessible names exist even when visuals omit labels
100
+ - MUST: Use `…` character (not `...`)
101
+ - MUST: `scroll-margin-top` on headings; "Skip to content" link; hierarchical `<h1>`–`<h6>`
102
+ - MUST: Resilient to user-generated content (short/avg/very long)
103
+ - MUST: Locale-aware dates/times/numbers (`Intl.DateTimeFormat`, `Intl.NumberFormat`)
104
+ - SHOULD: `translate="no"` on brand names, code tokens, & identifiers to prevent garbled auto-translation
105
+ - MUST: Accurate `aria-label`; decorative elements `aria-hidden`
106
+ - MUST: Icon-only buttons have descriptive `aria-label`
107
+ - MUST: Prefer native semantics (`button`, `a`, `label`, `table`) before ARIA
108
+ - MUST: Media has captions/transcripts/descriptions as applicable; controls are keyboard-operable; decorative media hidden from assistive tech
109
+ - MUST: Non-breaking spaces: `10&nbsp;MB`, `⌘&nbsp;K`, brand names
110
+
111
+ ## Content Handling
112
+
113
+ - MUST: Text containers handle long content (`truncate`, `line-clamp-*`, `break-words`)
114
+ - MUST: Flex children need `min-w-0` to allow truncation
115
+ - MUST: Handle empty states—no broken UI for empty strings/arrays
116
+
117
+ ## Performance
118
+
119
+ - SHOULD: Test iOS Low Power Mode and macOS Safari
120
+ - MUST: Measure reliably (disable extensions that skew runtime)
121
+ - MUST: Track and minimize re-renders (React DevTools/React Scan)
122
+ - MUST: Profile with CPU/network throttling
123
+ - MUST: Batch layout reads/writes; avoid reflows/repaints
124
+ - MUST: Mutations (`POST`/`PATCH`/`DELETE`) target <500ms
125
+ - SHOULD: Prefer uncontrolled inputs; controlled inputs cheap per keystroke
126
+ - MUST: Virtualize large lists (>50 items)
127
+ - MUST: Preload above-fold images; lazy-load the rest
128
+ - MUST: Prevent CLS (explicit image dimensions)
129
+ - SHOULD: `<link rel="preconnect">` for CDN domains
130
+ - SHOULD: Critical fonts: `<link rel="preload" as="font">` with `font-display: swap`
131
+ - SHOULD: `<video autoplay muted loop playsinline>` over animated GIF; provide still/reduced-motion alternative
132
+ - SHOULD: Short non-essential loops include a Safari H.264 MP4 `<picture>` source, `prefers-reduced-motion` media condition, and still fallback
133
+
134
+ ## Dark Mode & Theming
135
+
136
+ - MUST: `color-scheme: dark` on `<html>` for dark themes
137
+ - SHOULD: `<meta name="theme-color">` matches page background
138
+ - MUST: Native `<select>`: explicit `background-color` and `color` (Windows fix)
139
+
140
+ ## Hydration
141
+
142
+ - MUST: Inputs with `value` need `onChange` (or use `defaultValue`)
143
+ - SHOULD: Guard date/time rendering against hydration mismatch
144
+
145
+ ## Design
146
+
147
+ - SHOULD: Layered shadows (ambient + direct)
148
+ - SHOULD: Crisp edges via semi-transparent borders + shadows
149
+ - SHOULD: Nested radii: child ≤ parent; concentric
150
+ - SHOULD: Hue consistency: tint borders/shadows/text toward bg hue
151
+ - MUST: Accessible charts (color-blind-friendly palettes)
152
+ - MUST: Meet contrast—prefer [APCA](https://apcacontrast.com/) over WCAG 2
153
+ - MUST: Increase contrast on `:hover`/`:active`/`:focus`
154
+ - SHOULD: Match browser UI to bg
155
+ - SHOULD: Avoid dark color gradient banding (use background images when needed)
@@ -28,6 +28,8 @@ each error code means — then `PAGES.md` for how each page type is built, and
28
28
  webhook). Anything personal — cart, customer, wishlist — lives in client components under
29
29
  `MagicStoreProvider` (`app/providers.tsx`).
30
30
  - Branch on `MagicStoreError.code`, show `error.detail`.
31
+ - No hard-coded interface text: every word the storefront shows comes from `lib/i18n.ts`, in
32
+ every language it has (`ru`, `uz`, `en`).
31
33
  - Every page handles its empty, error and not-found states (`orNotFound` turns `NOT_FOUND` into
32
34
  the 404 page).
33
35
 
@@ -40,6 +42,11 @@ from a landing page: brand colors come from `shop.branding` at runtime, product
40
42
  come only from the API, nothing is invented (prices, badges, reviews, urgency), and the cart and
41
43
  checkout stay conventional.
42
44
 
45
+ Before calling visual work done, follow `.claude/skills/storefront-verify/SKILL.md`: build, run,
46
+ screenshot every page type at 390 and 1280px, fix what you see. Interactive UI follows
47
+ `.claude/skills/web-interface-guidelines/SKILL.md`; React and Next.js code follows
48
+ `.claude/skills/vercel-react-best-practices/SKILL.md`.
49
+
43
50
  ## Where things go
44
51
 
45
52
  | Path | What |