@decocms/start 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,719 @@
1
+ # Migration Gotchas
2
+
3
+ 45 pitfalls discovered during real migrations (espacosmart-storefront, osklen).
4
+
5
+ ## 1. Section Loaders Don't Execute
6
+
7
+ Deco sections have `export const loader = async (props, req, ctx) => { ... }` that runs server-side before the component renders. In TanStack Start, these don't execute automatically. Components typed as `SectionProps<typeof loader>` expect the augmented props, but only receive the raw CMS block props.
8
+
9
+ **Symptom**: Components crash on `.find()`, `.length`, or property access of loader-provided props that are `undefined`.
10
+
11
+ **Fix**: Register them via `registerSectionLoaders()` in `setup.ts`.
12
+
13
+ **Safe-default pattern** (most pragmatic for initial migration):
14
+
15
+ ```typescript
16
+ // Before: component expects loader-augmented props
17
+ function ProductMain({ page, productAdditional, showTogether, priceSimulation, isMobile }: SectionProps<typeof loader>) {
18
+
19
+ // After: destructure with safe defaults for all loader-only props
20
+ function ProductMain(rawProps: any) {
21
+ const {
22
+ page,
23
+ productAdditional = [], // from section loader
24
+ showTogether = [], // from section loader
25
+ showTogetherSimulation = [], // from section loader
26
+ priceSimulation = 0, // from section loader
27
+ noInterestInstallmentValue = null,
28
+ skuProductsKit = [], // from section loader
29
+ isMobile = false, // from section loader (device detection)
30
+ } = rawProps;
31
+ ```
32
+
33
+ This lets the core component render while gracefully degrading features that depend on loader data (cross-selling, price simulation, etc.).
34
+
35
+ ## 2. useEffect Doesn't Run on Server
36
+
37
+ Components relying on `useEffect` to populate state will render empty on SSR.
38
+
39
+ **Fix**: Use TanStack route loaders or section loaders for server-side data.
40
+
41
+ ## 3. Signal .value in Render Doesn't Re-render
42
+
43
+ Reading `signal.value` inside React render doesn't create a subscription.
44
+
45
+ **Fix**: Use `useStore(signal.store)` from `@tanstack/react-store` for reactive reads.
46
+
47
+ ## 4. class vs className
48
+
49
+ Preact accepts both. React only accepts `className`.
50
+
51
+ **Fix**: `grep -rn ' class=' src/ --include='*.tsx'` and replace in JSX contexts.
52
+
53
+ ## 5. dangerouslySetInnerHTML Syntax
54
+
55
+ Some Deco components use `innerHTML` directly.
56
+
57
+ **Fix**: `dangerouslySetInnerHTML={{ __html: content }}`.
58
+
59
+ ## 6. ComponentChildren → ReactNode
60
+
61
+ Not just a type rename. Usually fine in practice.
62
+
63
+ ## 7. VTEX API Auth on Cloudflare Workers
64
+
65
+ Env vars must be set via `wrangler secret put` or `.dev.vars`, not `.env`.
66
+
67
+ ## 8. Cookie Handling
68
+
69
+ In TanStack Start, manage `checkout.vtex.com__orderFormId` cookies manually via `document.cookie`.
70
+
71
+ ## 9. Build Succeeds but Runtime Fails
72
+
73
+ After import rewrites, always test: build → dev → visit pages → test interactive features.
74
+
75
+ ## 10. npm link for Local Dev
76
+
77
+ ```bash
78
+ cd apps-start && npm link
79
+ cd ../deco-start && npm link
80
+ cd ../my-store && npm link @decocms/apps @decocms/start
81
+ ```
82
+
83
+ ## 11. SVG Attributes
84
+
85
+ React uses camelCase: `strokeWidth`, `fillRule`, `clipPath`, etc.
86
+
87
+ ## 12. No Compat Layers
88
+
89
+ After migration: no `src/compat/`, only `~/*` alias, zero compat files in packages.
90
+
91
+ ## 13. AsyncLocalStorage in Client Bundles
92
+
93
+ Use namespace import + runtime conditional (or the `deco-server-only-stubs` Vite plugin).
94
+
95
+ ## 14. TanStack Start Ignores Custom Worker Entry Code
96
+
97
+ **Severity**: CRITICAL -- cache logic, admin routes, and any custom request interception will silently not work in production.
98
+
99
+ TanStack Start's Cloudflare adapter **completely ignores** the `export default` in `server.ts`. It generates its own Worker entry that calls `createStartHandler(defaultStreamHandler)` directly. Custom logic inside `createServerEntry({ async fetch(request) { ... } })` is also stripped by Vite/Rollup in production builds.
100
+
101
+ **Symptom**: Admin routes like `/live/_meta` return HTML instead of JSON. Edge caching (Cache API, X-Cache headers) doesn't work despite being implemented. Every request hits the origin at full SSR cost. The `Cache-Control` headers from route-level `headers()` functions appear correctly (because TanStack applies them), but the custom `X-Cache` header and cache storage never execute.
102
+
103
+ **Diagnosis**: Search the built `dist/server/worker-entry-*.js` bundle for your custom code (e.g., `X-Cache`, `caches.open`, `_cache/purge`). If absent, TanStack stripped it.
104
+
105
+ **Fix**: Create a **separate** `src/worker-entry.ts` file that wraps TanStack Start's built handler. Point `wrangler.jsonc` to this file instead of `@tanstack/react-start/server-entry`.
106
+
107
+ ```typescript
108
+ // src/worker-entry.ts
109
+ import "./setup";
110
+ import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
111
+ import { createDecoWorkerEntry } from "@decocms/start/sdk/workerEntry";
112
+ import { handleMeta, handleDecofileRead, handleDecofileReload, handleRender, corsHeaders } from "@decocms/start/admin";
113
+
114
+ const serverEntry = createServerEntry({
115
+ async fetch(request) {
116
+ return await handler.fetch(request);
117
+ },
118
+ });
119
+
120
+ export default createDecoWorkerEntry(serverEntry, {
121
+ admin: { handleMeta, handleDecofileRead, handleDecofileReload, handleRender, corsHeaders },
122
+ });
123
+ ```
124
+
125
+ ```jsonc
126
+ // wrangler.jsonc -- MUST point to custom entry, NOT the default
127
+ {
128
+ "main": "./src/worker-entry.ts",
129
+ // NOT: "main": "@tanstack/react-start/server-entry"
130
+ }
131
+ ```
132
+
133
+ This ensures admin route interception AND edge caching survive the build because they're in the Worker's own fetch handler, outside of TanStack's build pipeline.
134
+
135
+ ## 15. DaisyUI v4 Theme in Preview Shell
136
+
137
+ DaisyUI v4 with Tailwind v4's `@plugin "daisyui/theme"` scopes all color variables to `[data-theme="light"]`. The admin preview HTML shell (`/live/previews/*`) must include this attribute, or colors will be wrong.
138
+
139
+ **Symptom**: Preview in admin shows default/missing colors while production looks correct.
140
+
141
+ **Fix**: Configure the preview shell in `setup.ts`:
142
+
143
+ ```typescript
144
+ setRenderShell({
145
+ css: appCss,
146
+ fonts: [...],
147
+ theme: "light", // adds data-theme="light" to <html>
148
+ bodyClass: "bg-base-100 text-base-content",
149
+ lang: "pt-BR",
150
+ });
151
+ ```
152
+
153
+ The production HTML has `<html lang="pt-BR" data-theme="light">` set by the TanStack root layout. The preview shell must replicate this.
154
+
155
+ ## 16. Admin Route Cache Bypass
156
+
157
+ `/live/` and `/.decofile` are in `DEFAULT_BYPASS_PATHS`. Admin routes are intercepted before caching.
158
+
159
+ ## 17. SiteTheme is a Stub
160
+
161
+ `Theme.tsx` returns `null`. Colors come from CSS at build time, not CMS at runtime.
162
+
163
+ ## 18. Loader References in JSON Schema
164
+
165
+ `Resolvable` definition with `additionalProperties: true` needed for props that accept loader refs.
166
+
167
+ ---
168
+
169
+ ## 19. `import "./setup"` Ordering (CRITICAL)
170
+
171
+ `import "./setup"` MUST be the first import in both `server.ts` and `worker-entry.ts`. Without it, server functions in Vite split modules execute before `setBlocks()` has been called, causing `resolveDecoPage` to return null → 404 on client-side navigation.
172
+
173
+ **Symptom**: SSR works fine (F5), but clicking links shows "No CMS page block matches this URL".
174
+
175
+ ## 20. loadDeferredSection Must Use POST
176
+
177
+ Without this, the admin shows "Incorrect type. Expected 'array'" for fields that contain loader references in the `.decofile`.
178
+
179
+ ## 19. @tanstack/store subscribe() Returns Object, Not Function
180
+
181
+ **Severity: CRITICAL** -- This causes cascading failures across the entire page.
182
+
183
+ `@tanstack/store@0.9.x`'s `Store.subscribe()` returns `{ unsubscribe: Function }`, NOT a plain function. React's `useSyncExternalStore` (and `useEffect` cleanup) expect the subscribe callback to return a bare unsubscribe function. Passing the object through causes:
184
+
185
+ 1. "TypeError: destroy_ is not a function" (non-minified) / "TypeError: J is not a function" (minified)
186
+ 2. Which cascades into React #419 (hydration failure)
187
+ 3. Which cascades into React #130 (undefined component after hydration bailout)
188
+ 4. Which makes the entire page non-interactive (0 interactive elements)
189
+
190
+ **Symptom**: Page SSR renders fine, but client shows "J is not a function" repeating hundreds of times. All interactive elements stop working.
191
+
192
+ **Fix**: Unwrap the return value in your `Signal.subscribe()` implementation:
193
+
194
+ ```typescript
195
+ subscribe(fn) {
196
+ const sub = store.subscribe(() => fn());
197
+ return typeof sub === "function" ? sub : sub.unsubscribe;
198
+ },
199
+ ```
200
+
201
+ ## 20. createPortal Imported from Wrong Module
202
+
203
+ In Preact, `createPortal` is available from `preact/compat` which maps to `react` in some setups. In React, `createPortal` lives in `react-dom`.
204
+
205
+ **Symptom**: `createPortal is not a function` or components using portals (modals, drawers, toasts) silently fail.
206
+
207
+ **Fix**:
208
+ ```bash
209
+ # Find and replace across all files
210
+ grep -r 'createPortal.*from "react"' src/ --include='*.tsx' -l
211
+ # Change to: import { createPortal } from "react-dom";
212
+ ```
213
+
214
+ ## 21. for Attribute Must Be htmlFor in React JSX
215
+
216
+ Preact accepts both `for` and `htmlFor` on `<label>` elements. React only accepts `htmlFor`. Using `for` causes a hydration mismatch because the server renders `for` but the client expects `htmlFor`.
217
+
218
+ **Symptom**: React #419 hydration errors on pages with labels (search bars, forms, drawers).
219
+
220
+ **Fix**: `grep -r ' for={' src/ --include='*.tsx'` and replace with `htmlFor={`.
221
+
222
+ ## 22. Fresh-Specific Attributes Must Be Removed
223
+
224
+ Fresh/Preact components may use `data-fresh-disable-lock={true}` on elements. This attribute has no meaning in React and can cause hydration mismatches.
225
+
226
+ **Fix**: Remove all `data-fresh-disable-lock` attributes.
227
+
228
+ ## 23. Custom useId with Math.random() Causes Hydration Mismatch
229
+
230
+ Some storefronts have a custom `useId` hook that appends `Math.random()` to generate "unique" IDs. This guarantees different IDs on server vs client, causing React #419.
231
+
232
+ **Fix**: Replace with React's native `useId`:
233
+
234
+ ```typescript
235
+ import { useId as useReactId } from "react";
236
+ export const useId = useReactId;
237
+ ```
238
+
239
+ ## 24. new URL() with Relative Paths Fails in Workers
240
+
241
+ `new URL("/product/p")` works in browsers (uses `window.location` as base) but throws `Invalid URL` in Workers/Node because there's no implicit base.
242
+
243
+ **Fix**: Always provide a base URL:
244
+ ```typescript
245
+ const parsed = new URL(url, "https://localhost");
246
+ return parsed.pathname + parsed.search;
247
+ ```
248
+
249
+ ## 25. Global Variables Throw ReferenceError
250
+
251
+ Code that references undeclared globals (e.g., `userAddressData` injected by VTEX scripts) will throw `ReferenceError: X is not defined` in Workers where those scripts don't run.
252
+
253
+ **Fix**: Access via `globalThis`:
254
+ ```typescript
255
+ const data = (globalThis as any).userAddressData;
256
+ if (data && Array.isArray(data)) { /* use data */ }
257
+ ```
258
+
259
+ ## 26. Section-Type Props Use __resolveType Format
260
+
261
+ In the new `@decocms/start`, section-type props from the CMS arrive as `{ __resolveType: "site/sections/Foo.tsx", ...props }`, NOT the old `{ Component, props }` format. Components that render section props must handle this.
262
+
263
+ **Fix**: Create a `RenderSection` bridge component that:
264
+ 1. Checks for `section.Component` (old format) and renders directly
265
+ 2. Checks for `section.__resolveType` (new format), resolves via `getSection()` from `@decocms/start/cms`, and renders with `React.lazy` + `Suspense`
266
+
267
+ ## 27. jsdom Must Be Replaced in Workers
268
+
269
+ `jsdom` is a heavy Node.js dependency that cannot run in Cloudflare Workers. Components using it for HTML sanitization must use `dompurify` instead.
270
+
271
+ **Fix**: Replace `import { JSDOM } from "jsdom"` with:
272
+ ```typescript
273
+ import DOMPurify from "dompurify";
274
+ const clean = typeof document !== "undefined" ? DOMPurify.sanitize(html) : html;
275
+ ```
276
+
277
+ ## 28. Deno npm: Prefix Must Be Removed
278
+
279
+ Imports like `import Color from "npm:colorjs.io"` use the Deno-specific `npm:` prefix. Vite/Node don't understand it.
280
+
281
+ **Fix**: Remove the `npm:` prefix and install the package: `npm install colorjs.io`.
282
+
283
+ ## 29. Device Context Must Be Server-Driven, Not Hardcoded
284
+
285
+ **Severity**: HIGH — breaks entire page layout (mobile vs desktop)
286
+
287
+ The original Deco/Fresh framework injected `ctx.device` automatically into section contexts. In the new TanStack Start stack, the `Device` context (used by `useDevice()`) must be explicitly provided with the correct value from server-side User-Agent detection.
288
+
289
+ **Symptom**: All visitors see the mobile layout regardless of device. The `useDevice()` hook always returns `{ isMobile: true }` because the `Device.Provider` was hardcoded with `value={{ isMobile: true }}` in `__root.tsx`.
290
+
291
+ **Root cause**: The root route can't use `createServerFn` for device detection (causes Rollup code-split errors with `tss-serverfn-split`). And the Device context default was set to mobile.
292
+
293
+ **Fix**: Detect device inside each page route's existing `createServerFn` loader (which already has access to `getRequestHeader("user-agent")`), return `isMobile` alongside the page data, and wrap the page component with `<Device.Provider>`:
294
+
295
+ ```typescript
296
+ // In routes/index.tsx or routes/$.tsx
297
+ const MOBILE_RE = /mobile|android|iphone|ipad|ipod|webos|blackberry|opera mini|iemobile/i;
298
+
299
+ const loadPage = createServerFn({ method: "GET" }).handler(async () => {
300
+ const ua = getRequestHeader("user-agent") ?? "";
301
+ const matcherCtx = { userAgent: ua, url: getRequestUrl().toString(), path: "/", cookies: getCookies() };
302
+ const page = await resolveDecoPage("/", matcherCtx);
303
+ return { page, isMobile: MOBILE_RE.test(ua) };
304
+ });
305
+
306
+ function HomePage() {
307
+ const { page, isMobile } = Route.useLoaderData();
308
+ return (
309
+ <Device.Provider value={{ isMobile }}>
310
+ <DecoPageRenderer sections={page.resolvedSections} />
311
+ </Device.Provider>
312
+ );
313
+ }
314
+ ```
315
+
316
+ Remove the hardcoded `<Device.Provider value={{ isMobile: true }}>` from `__root.tsx`.
317
+
318
+ **Key constraint**: Do NOT put `createServerFn` in `__root.tsx` — TanStack Start's server function splitter cannot handle it there.
319
+
320
+ ## 30. Stale Edge Cache After Deploy Requires Explicit Purge
321
+
322
+ **Severity**: MEDIUM — causes "Failed to fetch dynamically imported module" errors
323
+
324
+ After deploying a new build to Cloudflare Workers, the edge cache may still serve old HTML that references previous JS bundle hashes. This causes module import failures.
325
+
326
+ **Fix**: After every deploy, purge the cache:
327
+ 1. Set a `PURGE_TOKEN` secret: `npx wrangler secret put PURGE_TOKEN`
328
+ 2. Call the purge endpoint: `POST /_cache/purge` with `Authorization: Bearer <token>` and body `{"paths":["/"]}`
329
+ 3. Automate this in CI/CD (see the deploy.yml workflow)
330
+
331
+ ## 31. CSS Theme Class Prefixes Must Not Be Renamed
332
+
333
+ **Severity**: HIGH — breaks all theme colors
334
+
335
+ The original site uses `seasonal-*` CSS class prefixes for theme variables (e.g., `bg-seasonal-brand-terciary-1`, `text-seasonal-neutral-1`). During migration, do NOT rename these to `header-*`, `footer-*`, or any other prefix. The theme variables are defined centrally and all components reference the same `seasonal-*` namespace.
336
+
337
+ **Fix**: Only change what React strictly requires: `class` → `className`, `for` → `htmlFor`. Preserve all original CSS class names exactly.
338
+
339
+ ## 32. Section Loader Logic Must Not Be Stripped
340
+
341
+ **Severity**: HIGH — sections render empty/broken
342
+
343
+ During migration, section loaders (e.g., `sections/Header/Header.tsx`) may have their async data-fetching logic removed. For example, the `ctx.invoke.vtex.loaders.categories.tree()` call that populates navigation menus. Without it, the header renders with no category links.
344
+
345
+ **Fix**: Keep all section loader logic intact. The loader signature `(props, req, ctx) => {...}` and the `ctx.invoke` calls should be preserved as-is.
346
+
347
+ ## 33. usePartialSection is a No-Op
348
+
349
+ **Severity**: HIGH — tab switching, filter toggling, any partial section re-render breaks silently
350
+
351
+ Deco's `usePartialSection` hook re-renders a section with new props by making a server request for just that section's HTML. In the React port, this mechanism doesn't exist — `usePartialSection` returns empty data attributes.
352
+
353
+ **Symptom**: Tabbed product shelves only show the first tab. Clicking other tabs does nothing. Filter visibility toggles don't work. Any component using `usePartialSection` for dynamic props appears frozen.
354
+
355
+ **Fix**: Replace with React `useState` for client-side state switching. If all tab data is already loaded server-side (common for tabbed shelves where the loader fetches all tabs), just switch between the data client-side:
356
+
357
+ ```typescript
358
+ // Before: relies on usePartialSection to re-render with new tabIndex
359
+ <button {...usePartialSection({ props: { tabIndex: i } })}>
360
+
361
+ // After: React state-driven tab switching
362
+ const [activeTab, setActiveTab] = useState(0);
363
+ <button onClick={() => setActiveTab(i)}>
364
+ // Then render tabs[activeTab].products instead of tabs[tabIndex].products
365
+ ```
366
+
367
+ For filter toggles, replace `usePartialSection({ props: { openFilter: !openFilter } })` with `useState`:
368
+ ```typescript
369
+ const [openFilter, setOpenFilter] = useState(true);
370
+ <button onClick={() => setOpenFilter(!openFilter)}>
371
+ ```
372
+
373
+ ## 34. Commerce Loaders Are Blind to the URL
374
+
375
+ **Severity**: CRITICAL — search and category pages return wrong/no products
376
+
377
+ When `resolve.ts` processes CMS blocks, it passes only the static CMS block props to commerce loaders (PLP, PDP). The current URL, query string (`?q=`), path (`/drywall`), sort, pagination, and filter parameters are never forwarded.
378
+
379
+ **Symptom**: Search pages (`/s?q=parafuso`) return zero products. Category pages (`/drywall`) show random/no products. Sort and pagination controls do nothing.
380
+
381
+ **Root cause**: `resolveValue()` in `resolve.ts` calls commerce loaders with `resolvedProps` (CMS block config only). The `matcherCtx` (containing URL, path, user-agent) is used for matcher evaluation but never passed to commerce loaders.
382
+
383
+ **Fix**: Pass `matcherCtx` as a second argument to commerce loaders in `resolve.ts`. Then the PLP loader can extract `?q=` for search, path for categories, `?sort=` for sorting, `?page=` for pagination, and `?filter.X=Y` for facets.
384
+
385
+ This is a change in `@decocms/start` (resolve.ts). Until upstreamed, use patch-package or vendor the file.
386
+
387
+ ## 35. VTEX Product Loaders Ship with Empty priceSpecification
388
+
389
+ **Severity**: HIGH — no discount badges, no strikethrough prices, no installments
390
+
391
+ All three VTEX product loaders (`vtexProductList`, `productListingPage`, `productDetailsPage`) build offers with `priceSpecification: []`. The `useOffer()` hook depends on this array to extract `ListPrice` (for discount math + strikethrough), `SalePrice`, and `Installment` entries.
392
+
393
+ **Symptom**: Product cards show only one price (no strikethrough). No "X% OFF" discount badge. No "Ou em Nx de R$ X sem juros" installment text.
394
+
395
+ **Fix**: Add a `buildPriceSpecification()` helper to each loader that transforms the VTEX `commertialOffer` data:
396
+
397
+ ```typescript
398
+ function buildPriceSpecification(offer: any): any[] {
399
+ const specs: any[] = [];
400
+ if (offer.ListPrice != null) {
401
+ specs.push({ "@type": "UnitPriceSpecification", priceType: "https://schema.org/ListPrice", price: offer.ListPrice });
402
+ }
403
+ if (offer.Price != null) {
404
+ specs.push({ "@type": "UnitPriceSpecification", priceType: "https://schema.org/SalePrice", price: offer.Price });
405
+ }
406
+ // Find best no-interest installment
407
+ const noInterest = (offer.Installments ?? [])
408
+ .filter((i: any) => i.InterestRate === 0)
409
+ .sort((a: any, b: any) => b.NumberOfInstallments - a.NumberOfInstallments);
410
+ if (noInterest.length > 0) {
411
+ const best = noInterest[0];
412
+ specs.push({
413
+ "@type": "UnitPriceSpecification",
414
+ priceType: "https://schema.org/SalePrice",
415
+ priceComponentType: "https://schema.org/Installment",
416
+ billingDuration: best.NumberOfInstallments,
417
+ billingIncrement: best.Value,
418
+ price: best.TotalValuePlusInterestRate,
419
+ });
420
+ }
421
+ return specs;
422
+ }
423
+ ```
424
+
425
+ This is a change in `@decocms/apps`. Until upstreamed, patch or vendor the loader files.
426
+
427
+ ## 36. VTEX Facets API Response Structure Mismatch
428
+
429
+ The VTEX Intelligent Search facets endpoint returns `{ facets: ISFacetGroup[] }`, NOT a direct `ISFacetGroup[]` array. Accessing `response` directly as an array yields no filter data.
430
+
431
+ Additionally, `PRICERANGE` facets must be converted to `FilterToggle` format (with `value: "min:max"` strings) for the existing `Filters.tsx` component to render them. The component's `isToggle()` filter drops anything that isn't `FilterToggle`.
432
+
433
+ **Fix**: Unwrap with `const facetGroups = response.facets ?? [];` and convert price ranges:
434
+
435
+ ```typescript
436
+ if (group.type === "PRICERANGE") {
437
+ return { "@type": "FilterToggle" as const, key: "price", label: group.name, quantity: 0,
438
+ values: group.values.map((v) => ({
439
+ value: `${v.range.from}:${v.range.to}`, label: `R$ ${v.range.from} - R$ ${v.range.to}`,
440
+ quantity: v.quantity, selected: false, url: `?filter.price=${v.range.from}:${v.range.to}`,
441
+ })),
442
+ };
443
+ }
444
+ ```
445
+
446
+ ## 37. DaisyUI v4 Collapse Broken with Tailwind v4
447
+
448
+ **Severity**: MEDIUM — filter sidebars, FAQ accordions, any collapsible section renders collapsed
449
+
450
+ DaisyUI v4's collapse component uses `grid-template-rows: auto 0fr` with `content-visibility: hidden` and expands via `:has(>input:checked)`. In combination with Tailwind v4, the expand chain breaks — content stays collapsed regardless of checkbox state.
451
+
452
+ **Symptom**: Filter sidebar shows as empty space. Collapse titles may render but content is permanently hidden. Custom CSS overrides on `.collapse` conflict with DaisyUI's generated styles.
453
+
454
+ **Fix**: Replace DaisyUI collapse with native `<details>/<summary>` HTML elements:
455
+
456
+ ```typescript
457
+ // Before: DaisyUI collapse with hidden checkbox
458
+ <div className="collapse">
459
+ <input type="checkbox" defaultChecked />
460
+ <div className="collapse-title">Category</div>
461
+ <div className="collapse-content">...filters...</div>
462
+ </div>
463
+
464
+ // After: Native HTML, works everywhere
465
+ <details open className="group">
466
+ <summary className="cursor-pointer font-semibold">Category</summary>
467
+ <div className="mt-2">...filters...</div>
468
+ </details>
469
+ ```
470
+
471
+ ## 38. Signal Shim Doesn't Auto-Trigger React Re-renders
472
+
473
+ **Severity**: HIGH — drawers don't open, cart badge doesn't update, any signal-driven UI appears frozen
474
+
475
+ The Preact-to-React signal compat shim has a pub/sub pattern (`_listeners`), but reading `signal.value` in a React render function creates NO subscription. React components don't re-render when the signal changes.
476
+
477
+ **Symptom**: Setting `displayCart.value = true` doesn't open the cart drawer. Cart item count badge stays at 0 after adding items. Menu drawer toggle does nothing.
478
+
479
+ **Root cause**: In Preact, `@preact/signals` automatically tracks signal reads in render and re-renders. The shim just has get/set on `.value` with manual `_listeners` — React has no awareness of it.
480
+
481
+ **Fix (recommended)**: Use `useStore` from `@tanstack/react-store` for components that need reactive reads:
482
+
483
+ ```typescript
484
+ import { useStore } from "@tanstack/react-store";
485
+ const { displayCart } = useUI();
486
+ const open = useStore(displayCart.store); // auto re-renders on change
487
+ ```
488
+
489
+ **Fix (interim)**: For components not yet migrated to `useStore`, bridge with `useState` + `useEffect`:
490
+
491
+ ```typescript
492
+ const { displayCart } = useUI();
493
+ const [open, setOpen] = useState(displayCart.value);
494
+ useEffect(() => {
495
+ const unsub = displayCart.subscribe(() => setOpen(displayCart.value));
496
+ return unsub;
497
+ }, []);
498
+ ```
499
+
500
+ **Fix (DaisyUI drawers)**: Since DaisyUI drawers are checkbox-driven, directly toggle the DOM checkbox as a pragmatic workaround:
501
+
502
+ ```typescript
503
+ const toggleDrawer = (id: string, open: boolean) => {
504
+ const checkbox = document.getElementById(id) as HTMLInputElement;
505
+ if (checkbox) checkbox.checked = open;
506
+ };
507
+ ```
508
+
509
+ ## 39. Cart Requires Server-Side Proxy for VTEX API (CORS)
510
+
511
+ **Severity**: HIGH — add-to-cart, minicart, and checkout flow completely broken
512
+
513
+ The storefront domain (e.g., `espacosmart-tanstack.deco.site`) differs from the VTEX checkout domain (`lojaespacosmart.vtexcommercestable.com.br`). Direct browser `fetch()` calls to VTEX are blocked by CORS. Additionally, the `checkout.vtex.com__orderFormId` cookie is scoped to the VTEX domain and inaccessible from the storefront.
514
+
515
+ **Fix**: Use TanStack Start `createServerFn` to create server-side proxy functions:
516
+
517
+ ```typescript
518
+ // src/lib/vtex-cart-server.ts
519
+ import { createServerFn } from "@tanstack/react-start";
520
+
521
+ export const getOrCreateCart = createServerFn({ method: "GET" })
522
+ .validator((orderFormId: string) => orderFormId)
523
+ .handler(async ({ data: orderFormId }) => {
524
+ const url = orderFormId
525
+ ? `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${orderFormId}`
526
+ : `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm`;
527
+ const res = await fetch(url, {
528
+ method: "POST",
529
+ headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
530
+ body: JSON.stringify({ expectedOrderFormSections: ["items", "totalizers", "shippingData", "clientPreferencesData", "storePreferencesData", "marketingData"] }),
531
+ });
532
+ return res.json();
533
+ });
534
+ ```
535
+
536
+ The `useCart` hook manages the `orderFormId` in a client-side cookie and calls these server functions.
537
+
538
+ **Checkout URL**: The minicart's "Finalizar Compra" link must append the `orderFormId` as a query parameter since the VTEX checkout domain can't read the storefront's cookies:
539
+
540
+ ```typescript
541
+ const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
542
+ ```
543
+
544
+ ## 40. Filter Sidebar Invisible Due to Background Color Match
545
+
546
+ **Severity**: LOW — cosmetic, but confusing during development
547
+
548
+ The aside element for search/category filters renders correctly in the DOM (proper width, height, content) but appears invisible because its background matches the page background (e.g., both `#E9E9E9`).
549
+
550
+ **Symptom**: Filters appear "non-existent" even though they're in the DOM. Filter links are accessible but invisible.
551
+
552
+ **Fix**: Add a contrasting background to the filter aside:
553
+
554
+ ```typescript
555
+ <aside className="... bg-white rounded-lg p-4">
556
+ ```
557
+
558
+ ## 41. Component Props `class` vs `className` Causes Silent Failures
559
+
560
+ **Severity**: HIGH — specific component features silently disappear
561
+
562
+ Gotcha #4 covers JSX attributes (`class=` on HTML elements), but this is about **component props** that are destructured as `class`. Preact components often destructure `{ class: _class }` from props because Preact accepts both `class` and `className`. In React, only `className` is passed, so `_class` ends up as `undefined`.
563
+
564
+ **Symptom**: The Drawer component's `className="drawer-end"` never reaches the rendered div. CartDrawer renders without `drawer-end`, making it overlay the wrong side or not render at all.
565
+
566
+ **Fix**: In component interfaces, accept both and merge:
567
+
568
+ ```typescript
569
+ // Before (Preact-style):
570
+ function Drawer({ class: _class, ...rest }) {
571
+ return <div className={`drawer ${_class}`}>
572
+
573
+ // After (React-compatible):
574
+ function Drawer({ className, ...rest }) {
575
+ return <div className={`drawer ${className ?? ""}`}>
576
+ ```
577
+
578
+ Search for `class:` in component destructuring patterns across all files, not just in JSX attributes.
579
+
580
+ ## 42. Tailwind v4 Logical vs Physical Property Cascade Conflict
581
+
582
+ **Severity**: CRITICAL — causes container width mismatches across the entire site
583
+
584
+ Tailwind v4 generates **logical CSS properties** (`padding-inline`, `margin-inline`) while Tailwind v3 generated **physical properties** (`padding-left`, `padding-right`). When an element has BOTH shorthand (`px-*`) and longhand (`pl-*`/`pr-*`) responsive classes, the cascade breaks silently.
585
+
586
+ **Symptom**: Containers are narrower or have asymmetric padding compared to production. The layout "looks off" at certain breakpoints but works at others.
587
+
588
+ **Root cause**: In Tailwind v3, `md:px-6` and `sm:pl-0` both target `padding-left` — same CSS property, media query specificity decides the winner. In Tailwind v4, `md:px-6` targets `padding-inline` (shorthand) while `sm:pl-0` targets `padding-inline-start` (longhand). These are different CSS properties. If `padding-inline-start` appears later in the compiled stylesheet, it overrides the shorthand's start value, creating asymmetric padding.
589
+
590
+ **Example**:
591
+ ```html
592
+ <!-- This pattern exists in many Deco storefronts -->
593
+ <div class="pl-4 sm:pl-0 md:px-6 xl-b:px-0 max-w-[1280px] mx-auto">
594
+ ```
595
+
596
+ In Tailwind v3: at `md` viewport, `px-6` sets `padding-left: 1.5rem` and `padding-right: 1.5rem`, cleanly overriding `sm:pl-0`.
597
+
598
+ In Tailwind v4: at `md` viewport, `px-6` sets `padding-inline: 1.5rem`, but `pl-0` (from `sm:`) may still override `padding-inline-start` depending on stylesheet order.
599
+
600
+ **Fix**: Replace mixed shorthand + longhand patterns with consistent longhand properties:
601
+
602
+ ```
603
+ md:px-6 xl-b:px-0 → md:pl-6 md:pr-6 xl-b:pl-0 xl-b:pr-0
604
+ px-4 lg:px-6 xl-b:px-0 → pl-4 pr-4 lg:pl-6 lg:pr-6 xl-b:pl-0 xl-b:pr-0
605
+ ```
606
+
607
+ **Detection**: Find all elements with mixed patterns:
608
+ ```bash
609
+ grep -rn 'px-[0-9].*pl-\|pl-.*px-[0-9]\|px-[0-9].*pr-\|pr-.*px-[0-9]' src/ --include='*.tsx'
610
+ ```
611
+
612
+ Only convert `px-*` on elements that ALSO have `pl-*` or `pr-*`. Don't blindly replace all `px-*` across the codebase — elements with only `px-*` (no mixed longhand) work fine.
613
+
614
+ Also check for the same issue with `mx-*` mixed with `ml-*`/`mr-*`, and `my-*` mixed with `mt-*`/`mb-*`.
615
+
616
+ ## 43. CSS oklch() Color Variables Must Store Triplets, Not Hex
617
+
618
+ **Severity**: HIGH — all SVG icons render as black, brand colors break
619
+
620
+ Sites that use `oklch(var(--variable))` in SVG fill/stroke attributes (common in Deco storefronts with seasonal/theme color systems) require the CSS variables to store **oklch triplets** (`100% 0.00 0deg`), NOT hex values (`#FFF`). `oklch(#FFF)` is invalid CSS — the browser ignores it and falls back to black.
621
+
622
+ **Symptom**: Slider arrows, footer icons, search icons, filter icons — anything using `oklch(var(--...))` — renders as black circles/shapes instead of the brand colors.
623
+
624
+ **Root cause**: The original site's Theme section (via Deco CMS) outputs oklch triplets into CSS variables. During migration, if the CSS variables are manually set to hex values, every `oklch()` wrapper produces invalid CSS.
625
+
626
+ **Fix**: Convert all theme CSS variables from hex to oklch triplets:
627
+ ```css
628
+ /* WRONG — invalid CSS when used as oklch(var(--bg-seasonal-2)) */
629
+ --bg-seasonal-2: #FFF;
630
+
631
+ /* CORRECT — oklch(100% 0.00 0deg) is valid */
632
+ --bg-seasonal-2: 100% 0.00 0deg;
633
+ ```
634
+
635
+ **Dual-usage caveat**: Variables used BOTH inside `oklch()` wrappers AND directly in CSS properties need different handling:
636
+
637
+ ```css
638
+ /* @theme entries for Tailwind utilities — need oklch() wrapper */
639
+ --color-bg-seasonal-1: oklch(var(--bg-seasonal-1));
640
+
641
+ /* Direct CSS usage — also needs oklch() wrapper */
642
+ background-color: oklch(var(--bg-seasonal-1));
643
+ ```
644
+
645
+ The DaisyUI v4 pattern: `@theme` entries map `--color-X` to `var(--Y)`. Tailwind generates `background-color: var(--color-X)` which resolves to the raw triplet — invalid without the `oklch()` wrapper. Wrap all `@theme` entries that reference oklch-triplet variables.
646
+
647
+ **Python conversion helper**:
648
+ ```python
649
+ from colorjs import Color
650
+ c = Color("#EE4F31")
651
+ l, c_val, h = c.convert("oklch").coords()
652
+ print(f"{l*100:.2f}% {c_val:.2f} {h:.0f}deg") # 64.42% 0.20 33deg
653
+ ```
654
+
655
+ ## 44. Runtime Module Import Kills Lazy-Loaded Sections
656
+
657
+ **Severity**: HIGH — sections silently disappear, data appears in RSC streaming but component renders nothing
658
+
659
+ Vite tree-shakes unused imports in production builds, so a section file that imports a non-existent module may pass `npm run build` without errors. But at runtime, when the section is dynamically imported via `registerSections`'s lazy `() => import("./sections/X")`, ALL imports in the module execute. A missing file kills the entire section module.
660
+
661
+ **Symptom**: Product shelves or other sections disappear. HTML size drops significantly. Product data appears in React streaming data (`$R[...]` notation) but zero product cards render as actual HTML. No error in the build log.
662
+
663
+ **Example**:
664
+ ```typescript
665
+ // sections/Product/ProductShelf.tsx
666
+ import LoadingCard from "~/components/product/loadingCard"; // file doesn't exist!
667
+ export { default, loader } from "~/components/product/ProductShelf";
668
+
669
+ export function LoadingFallback() {
670
+ return <LoadingCard />; // only used here — tree-shaken in build
671
+ }
672
+ ```
673
+
674
+ Build passes because `LoadingFallback` is a named export that nothing imports. But at runtime, the dynamic `import("./sections/Product/ProductShelf")` executes the module, hits the missing `loadingCard` import, and the entire section fails to load.
675
+
676
+ **Fix**: Create the missing file, even if it's a minimal stub:
677
+ ```typescript
678
+ // components/product/loadingCard.tsx
679
+ export default function LoadingCard() {
680
+ return <div className="animate-pulse bg-base-200 h-[400px] w-[200px] rounded" />;
681
+ }
682
+ ```
683
+
684
+ **Prevention**: After copying files from the original repo, verify all imports resolve:
685
+ ```bash
686
+ npx tsc --noEmit # catches missing modules that Vite's tree-shaking hides
687
+ ```
688
+
689
+ ## 45. GitHub Packages npm Requires Auth Even for Public Packages
690
+
691
+ **Severity**: MEDIUM — blocks dependency installation for new contributors and CI
692
+
693
+ GitHub Packages' npm registry (`npm.pkg.github.com`) requires authentication even for public packages. This is a known limitation that GitHub has not resolved. Attempting to `npm install` a public `@decocms/*` package without a token returns `401 Unauthorized`.
694
+
695
+ **Workaround A (recommended for development)**: Use `github:` Git URL syntax instead of npm registry references. This bypasses the npm registry entirely and uses Git HTTPS (no auth needed for public repos):
696
+
697
+ ```json
698
+ {
699
+ "@decocms/apps": "github:decocms/apps-start",
700
+ "@decocms/start": "github:decocms/deco-start#main"
701
+ }
702
+ ```
703
+
704
+ **Important**: The repo name in the `github:` URL must match the actual GitHub repo name, not the npm package name. `@decocms/start` is published from repo `decocms/deco-start`, NOT `decocms/start`.
705
+
706
+ **Workaround B (recommended for production)**: Publish to npmjs.com instead. Only npm's public registry supports truly zero-auth public package installation.
707
+
708
+ **Workaround C (if you must use GitHub Packages)**: Generate a GitHub PAT with `read:packages` scope and configure:
709
+ ```bash
710
+ npm config set //npm.pkg.github.com/:_authToken <YOUR_TOKEN>
711
+ ```
712
+
713
+ Or in project `.npmrc` with an env var (for CI):
714
+ ```
715
+ @decocms:registry=https://npm.pkg.github.com
716
+ //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
717
+ ```
718
+
719
+ **Tradeoff with `github:` syntax**: No semver resolution — `npm update` is meaningless. Pin to a tag for stability: `github:decocms/deco-start#v0.14.2`. Without a tag, you get HEAD of the default branch.