@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,70 @@
1
+ # Preact -> React Import Migration
2
+
3
+ Mechanical find-and-replace. Safe to automate with `sed`.
4
+
5
+ ## Replacements
6
+
7
+ | Find | Replace |
8
+ |------|---------|
9
+ | `from "preact/hooks"` | `from "react"` |
10
+ | `from "preact/compat"` | `from "react"` |
11
+ | `from "preact"` | `from "react"` |
12
+
13
+ ## Special Cases
14
+
15
+ ### ComponentChildren -> ReactNode
16
+
17
+ Preact's `ComponentChildren` maps to React's `ReactNode`:
18
+
19
+ ```typescript
20
+ // OLD
21
+ import type { ComponentChildren } from "preact";
22
+
23
+ // NEW
24
+ import type { ReactNode as ComponentChildren } from "react";
25
+ ```
26
+
27
+ If you want to modernize fully, rename `ComponentChildren` to `ReactNode` across the codebase.
28
+
29
+ ### JSX type
30
+
31
+ ```typescript
32
+ // OLD
33
+ import type { JSX } from "preact";
34
+
35
+ // NEW (works unchanged)
36
+ import type { JSX } from "react";
37
+ ```
38
+
39
+ ### FunctionalComponent -> FC
40
+
41
+ ```typescript
42
+ // OLD
43
+ const Foo: preact.FunctionalComponent<Props> = ...
44
+
45
+ // NEW
46
+ import React from "react";
47
+ const Foo: React.FC<Props> = ...
48
+ ```
49
+
50
+ ## Automation
51
+
52
+ ```bash
53
+ # Bulk replace (macOS sed)
54
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
55
+ -e 's|from "preact/hooks"|from "react"|g' \
56
+ -e 's|from "preact/compat"|from "react"|g'
57
+
58
+ # Bare preact requires care (don't match preact/hooks or preact/compat)
59
+ find src/ -name '*.ts' -o -name '*.tsx' | xargs sed -i '' \
60
+ 's|from "preact"|from "react"|g'
61
+ ```
62
+
63
+ Then handle `ComponentChildren` files individually.
64
+
65
+ ## Verification
66
+
67
+ ```bash
68
+ grep -r 'from "preact' src/ --include='*.ts' --include='*.tsx'
69
+ # Should return ZERO matches
70
+ ```
@@ -0,0 +1,154 @@
1
+ # Platform Hooks Migration
2
+
3
+ Platform hooks (useCart, useUser, useWishlist) are the most complex migration target because they have real business logic.
4
+
5
+ ## Strategy
6
+
7
+ All hooks are **site-local**. No Vite alias tricks. No compat layers.
8
+
9
+ - Active platform hooks (VTEX for this store) -> `~/hooks/useCart.ts` with real implementation
10
+ - Inactive platform hooks (Wake, Shopify, etc.) -> `~/hooks/platform/{name}.ts` with no-op stubs
11
+ - Auth hooks -> `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
12
+
13
+ ## VTEX useCart (Real Implementation)
14
+
15
+ ### Why Server Functions Are Required
16
+
17
+ The storefront domain (e.g., `my-store.deco.site`) differs from the VTEX checkout domain (`account.vtexcommercestable.com.br`). Direct browser `fetch()` calls are blocked by CORS. Additionally, VTEX API credentials (`AppKey`/`AppToken`) must stay server-side.
18
+
19
+ Use TanStack Start `createServerFn` to create server-side proxy functions that the client hook calls transparently.
20
+
21
+ ### Server Functions (~/lib/vtex-cart-server.ts)
22
+
23
+ ```typescript
24
+ import { createServerFn } from "@tanstack/react-start";
25
+
26
+ const ACCOUNT = "myaccount";
27
+ const API_KEY = process.env.VTEX_APP_KEY!;
28
+ const API_TOKEN = process.env.VTEX_APP_TOKEN!;
29
+
30
+ export const getOrCreateCart = createServerFn({ method: "GET" })
31
+ .validator((orderFormId: string) => orderFormId)
32
+ .handler(async ({ data: orderFormId }) => {
33
+ const url = orderFormId
34
+ ? `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${orderFormId}`
35
+ : `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm`;
36
+ const res = await fetch(url, {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ "X-VTEX-API-AppKey": API_KEY,
41
+ "X-VTEX-API-AppToken": API_TOKEN,
42
+ },
43
+ body: JSON.stringify({ expectedOrderFormSections: ["items", "totalizers", "shippingData", "clientPreferencesData", "storePreferencesData", "marketingData"] }),
44
+ });
45
+ return res.json();
46
+ });
47
+
48
+ export const addItemsToCart = createServerFn({ method: "POST" })
49
+ .validator((data: { orderFormId: string; items: any[] }) => data)
50
+ .handler(async ({ data }) => {
51
+ const res = await fetch(
52
+ `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items`,
53
+ {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
56
+ body: JSON.stringify({ orderItems: data.items }),
57
+ },
58
+ );
59
+ return res.json();
60
+ });
61
+
62
+ export const updateCartItems = createServerFn({ method: "POST" })
63
+ .validator((data: { orderFormId: string; items: any[] }) => data)
64
+ .handler(async ({ data }) => {
65
+ const res = await fetch(
66
+ `https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items/update`,
67
+ {
68
+ method: "POST",
69
+ headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
70
+ body: JSON.stringify({ orderItems: data.items }),
71
+ },
72
+ );
73
+ return res.json();
74
+ });
75
+ ```
76
+
77
+ ### Hook (~/hooks/useCart.ts)
78
+
79
+ Key design decisions:
80
+ - **Module-level singleton state** shared across all component instances
81
+ - **Pub/sub pattern** (`_listeners` Set) for notifying React components of changes
82
+ - **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side (not VTEX's domain cookie)
83
+ - Returns `cart` and `loading` with `.value` getter/setter for backward compat with Preact-era components
84
+ - Lazy initialization: cart is fetched on first component mount, not on module load
85
+ - Exports `itemToAnalyticsItem` for cart-specific analytics mapping
86
+
87
+ ### Cross-Domain Checkout
88
+
89
+ The minicart's "Finalizar Compra" button must link to the VTEX checkout domain with the `orderFormId` as a query parameter — the VTEX domain can't read the storefront's cookies:
90
+
91
+ ```typescript
92
+ const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
93
+ ```
94
+
95
+ ### VTEX Types (~/types/vtex.ts)
96
+
97
+ Site-local types for VTEX-specific structures:
98
+ - `OrderFormItem`, `SimulationOrderForm`, `Sla`, `SKU`, `VtexProduct`
99
+
100
+ ## Inactive Platform Stubs
101
+
102
+ For non-VTEX platforms, create minimal no-op files:
103
+
104
+ ```typescript
105
+ // ~/hooks/platform/wake.ts
106
+ export function useCart() {
107
+ return {
108
+ cart: { value: null },
109
+ loading: { value: false },
110
+ addItem: async (_params: any) => {},
111
+ updateItems: async (_params: any) => {},
112
+ removeItem: async (_index: any) => {},
113
+ };
114
+ }
115
+
116
+ export function useUser() {
117
+ return {
118
+ user: { value: null as { email?: string; name?: string } | null },
119
+ loading: { value: false },
120
+ };
121
+ }
122
+
123
+ export function useWishlist() {
124
+ return {
125
+ loading: { value: false },
126
+ addItem: async (_props: any) => {},
127
+ removeItem: async (_props: any) => {},
128
+ getItem: (_props: any) => undefined as any,
129
+ };
130
+ }
131
+ ```
132
+
133
+ Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`.
134
+
135
+ Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
136
+
137
+ ## Import Rewrites
138
+
139
+ ```bash
140
+ sed -i '' 's|from "apps/vtex/hooks/useCart.ts"|from "~/hooks/useCart"|g'
141
+ sed -i '' 's|from "apps/vtex/hooks/useUser.ts"|from "~/hooks/useUser"|g'
142
+ sed -i '' 's|from "apps/vtex/hooks/useWishlist.ts"|from "~/hooks/useWishlist"|g'
143
+ sed -i '' 's|from "apps/vtex/utils/types.ts"|from "~/types/vtex"|g'
144
+ sed -i '' 's|from "apps/shopify/hooks/useCart.ts"|from "~/hooks/platform/shopify"|g'
145
+ sed -i '' 's|from "apps/wake/hooks/useCart.ts"|from "~/hooks/platform/wake"|g'
146
+ # etc. for all platforms
147
+ ```
148
+
149
+ ## Verification
150
+
151
+ ```bash
152
+ grep -r 'from "apps/' src/ --include='*.ts' --include='*.tsx'
153
+ # Should return ZERO matches
154
+ ```
@@ -0,0 +1,220 @@
1
+ # @preact/signals -> TanStack Store Migration
2
+
3
+ Two distinct patterns need different handling.
4
+
5
+ ## Pattern A: Component Hooks (useSignal, useComputed)
6
+
7
+ These are component-local state. Replace with React hooks directly.
8
+
9
+ ### useSignal -> useState
10
+
11
+ ```typescript
12
+ // OLD
13
+ import { useSignal } from "@preact/signals";
14
+ const loading = useSignal(false);
15
+ loading.value = true; // write
16
+ if (loading.value) { ... } // read
17
+
18
+ // NEW
19
+ import { useState } from "react";
20
+ const [loading, setLoading] = useState(false);
21
+ setLoading(true); // write
22
+ if (loading) { ... } // read
23
+ ```
24
+
25
+ Setter naming convention: `set` + capitalized variable name.
26
+
27
+ ### useComputed -> useMemo
28
+
29
+ ```typescript
30
+ // OLD
31
+ import { useComputed } from "@preact/signals";
32
+ const isValid = useComputed(() => name.value.length > 0);
33
+ return <div>{isValid.value}</div>;
34
+
35
+ // NEW
36
+ import { useMemo } from "react";
37
+ const isValid = useMemo(() => name.length > 0, [name]);
38
+ return <div>{isValid}</div>;
39
+ ```
40
+
41
+ ### Automation Tips
42
+
43
+ - `useSignal`/`useComputed` changes are NOT safe for bulk sed (variable names, setter names, `.value` removal all differ per file)
44
+ - Process each file individually: read, identify variable names, transform
45
+ - Watch for: toggle patterns (`x.value = !x.value` -> `setX(prev => !prev)`), object state, conditional assignments
46
+
47
+ ## Pattern B: Module-Level Signals (Global State)
48
+
49
+ These create shared state across components. Use `@tanstack/store`.
50
+
51
+ ### Create ~/sdk/signal.ts
52
+
53
+ ```typescript
54
+ import { Store } from "@tanstack/store";
55
+ import { useSyncExternalStore, useMemo } from "react";
56
+
57
+ export interface Signal<T> {
58
+ readonly store: Store<T>;
59
+ value: T;
60
+ peek(): T;
61
+ subscribe(fn: () => void): () => void;
62
+ }
63
+
64
+ export function signal<T>(initialValue: T): Signal<T> {
65
+ const store = new Store<T>(initialValue);
66
+ return {
67
+ store,
68
+ get value() { return store.state; },
69
+ set value(v: T) { store.setState(() => v); },
70
+ peek() { return store.state; },
71
+ subscribe(fn) {
72
+ // CRITICAL: @tanstack/store@0.9.x returns { unsubscribe: Function },
73
+ // NOT a plain function. React's useSyncExternalStore and useEffect
74
+ // cleanup both expect a bare function. Passing the object causes
75
+ // "TypeError: destroy_ is not a function" at runtime.
76
+ const sub = store.subscribe(() => fn());
77
+ return typeof sub === "function" ? sub : sub.unsubscribe;
78
+ },
79
+ };
80
+ }
81
+
82
+ export function useSignal<T>(initialValue: T): Signal<T> {
83
+ const sig = useMemo(() => signal(initialValue), []);
84
+ useSyncExternalStore(
85
+ (cb) => sig.subscribe(cb),
86
+ () => sig.value,
87
+ () => sig.value,
88
+ );
89
+ return sig;
90
+ }
91
+
92
+ export function useComputed<T>(fn: () => T): Signal<T> {
93
+ const sig = useMemo(() => signal(fn()), []);
94
+ return sig;
95
+ }
96
+
97
+ export function computed<T>(fn: () => T): Signal<T> {
98
+ return signal(fn());
99
+ }
100
+
101
+ export function effect(fn: () => void | (() => void)): () => void {
102
+ const cleanup = fn();
103
+ return typeof cleanup === "function" ? cleanup : () => {};
104
+ }
105
+
106
+ export function batch(fn: () => void): void {
107
+ fn();
108
+ }
109
+
110
+ export function useSignalEffect(fn: () => void | (() => void)): void {
111
+ fn();
112
+ }
113
+
114
+ export type { Signal as ReadonlySignal };
115
+ ```
116
+
117
+ > **WARNING**: The `subscribe()` unwrapping is the single most critical line in
118
+ > this file. Without it, every component using `useSignal` will crash with
119
+ > "TypeError: J is not a function" (minified) or "TypeError: destroy_ is not a
120
+ > function" (non-minified), which then cascades into React #419 hydration
121
+ > failures and #130 undefined component errors across the entire page.
122
+
123
+ ### Replace Imports
124
+
125
+ ```bash
126
+ # Safe for bulk sed
127
+ sed -i '' 's|from "@preact/signals"|from "~/sdk/signal"|g'
128
+ ```
129
+
130
+ ### React Subscriptions
131
+
132
+ The signal shim's `.value` getter/setter does NOT create automatic React subscriptions. Reading `signal.value` during render won't re-render when the signal changes. This is the #1 source of "it works in Preact but not React" bugs.
133
+
134
+ Replace manual useEffect+subscribe boilerplate with `useStore`:
135
+
136
+ ```typescript
137
+ // OLD (manual subscription -- works but verbose)
138
+ const { displayCart } = useUI();
139
+ const [open, setOpen] = useState(false);
140
+ useEffect(() => {
141
+ setOpen(displayCart.value);
142
+ return displayCart.subscribe(() => setOpen(displayCart.value));
143
+ }, []);
144
+
145
+ // NEW (useStore from @tanstack/react-store -- recommended)
146
+ import { useStore } from "@tanstack/react-store";
147
+ const { displayCart } = useUI();
148
+ const open = useStore(displayCart.store);
149
+ ```
150
+
151
+ Write-only consumers (event handlers) don't need `useStore`:
152
+ ```typescript
153
+ // This still works -- .value setter backed by TanStack Store
154
+ onClick={() => { displayCart.value = true; }}
155
+ ```
156
+
157
+ ### DaisyUI Drawer Workaround
158
+
159
+ DaisyUI drawers use a hidden checkbox to toggle visibility. Since signal changes don't trigger React re-renders, the checkbox `checked` attribute never updates. Pragmatic workaround: directly toggle the DOM checkbox alongside the signal:
160
+
161
+ ```typescript
162
+ // After setting the signal, also toggle the drawer checkbox
163
+ displayCart.value = true;
164
+ const checkbox = document.getElementById("cart-drawer") as HTMLInputElement;
165
+ if (checkbox) checkbox.checked = true;
166
+ ```
167
+
168
+ This is an interim pattern until all signal consumers use `useStore`. The `useStore` approach is the proper fix because it makes the Drawer component re-render, which updates the checkbox `checked` prop through React.
169
+
170
+ ### Global State Hook Pattern (useCart, useUI)
171
+
172
+ Hooks that manage global state (cart, UI drawers) need module-level singleton state with a subscription mechanism. The pattern:
173
+
174
+ ```typescript
175
+ let _state: CartData | null = null;
176
+ const _listeners = new Set<() => void>();
177
+
178
+ function notify() { _listeners.forEach((fn) => fn()); }
179
+
180
+ export function useCart() {
181
+ const [, forceUpdate] = useState(0);
182
+
183
+ useEffect(() => {
184
+ const listener = () => forceUpdate((n) => n + 1);
185
+ _listeners.add(listener);
186
+ return () => { _listeners.delete(listener); };
187
+ }, []);
188
+
189
+ return {
190
+ cart: {
191
+ get value() { return _state; },
192
+ set value(v) { _state = v; notify(); },
193
+ },
194
+ // ... methods that update _state and call notify()
195
+ };
196
+ }
197
+ ```
198
+
199
+ Every component calling `useCart()` subscribes to changes, and state updates trigger re-renders across all subscribers. This replaces the Preact signals global reactivity.
200
+
201
+ ## Signal Type References
202
+
203
+ If components use `Signal<T>` as a prop type:
204
+
205
+ ```typescript
206
+ // OLD
207
+ import { Signal } from "@preact/signals";
208
+ interface Props { quantity: Signal<number>; }
209
+
210
+ // NEW
211
+ import type { ReactiveSignal } from "~/sdk/signal";
212
+ interface Props { quantity: ReactiveSignal<number>; }
213
+ ```
214
+
215
+ ## Verification
216
+
217
+ ```bash
218
+ grep -r '@preact/signals' src/ --include='*.ts' --include='*.tsx'
219
+ # Should return ZERO matches
220
+ ```
@@ -0,0 +1,78 @@
1
+ # Vite Configuration
2
+
3
+ ## Final Config (Post-Migration)
4
+
5
+ After all imports are rewritten, the config should be minimal:
6
+
7
+ ```typescript
8
+ import { cloudflare } from "@cloudflare/vite-plugin";
9
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
10
+ import react from "@vitejs/plugin-react";
11
+ import tailwindcss from "@tailwindcss/vite";
12
+ import { defineConfig } from "vite";
13
+ import path from "path";
14
+
15
+ const srcDir = path.resolve(__dirname, "src");
16
+
17
+ export default defineConfig({
18
+ plugins: [
19
+ cloudflare({ viteEnvironment: { name: "ssr" } }),
20
+ tanstackStart({ server: { entry: "server" } }),
21
+ react({
22
+ babel: {
23
+ plugins: [["babel-plugin-react-compiler", { target: "19" }]],
24
+ },
25
+ }),
26
+ tailwindcss(),
27
+ ],
28
+ resolve: {
29
+ alias: {
30
+ "~": srcDir,
31
+ },
32
+ },
33
+ });
34
+ ```
35
+
36
+ **One alias only**: `~` -> `src/`. Nothing else.
37
+
38
+ ## tsconfig.json
39
+
40
+ Must mirror the Vite alias:
41
+
42
+ ```json
43
+ {
44
+ "compilerOptions": {
45
+ "jsx": "react-jsx",
46
+ "moduleResolution": "bundler",
47
+ "module": "ESNext",
48
+ "target": "ES2022",
49
+ "skipLibCheck": true,
50
+ "strictNullChecks": true,
51
+ "baseUrl": ".",
52
+ "paths": {
53
+ "~/*": ["./src/*"]
54
+ }
55
+ },
56
+ "include": ["src/**/*", "vite.config.ts"]
57
+ }
58
+ ```
59
+
60
+ No `$store/*`, `site/*`, `apps/*`, `preact`, `@preact/signals`, `@deco/deco` paths. Those are all dead.
61
+
62
+ ## React Compiler
63
+
64
+ The `babel-plugin-react-compiler` with `target: "19"` enables automatic memoization. Requires `@vitejs/plugin-react` instead of the default SWC plugin.
65
+
66
+ Install: `npm install -D @vitejs/plugin-react babel-plugin-react-compiler`
67
+
68
+ ## Environment Variables
69
+
70
+ For VTEX API keys, use Cloudflare Workers secrets or `.dev.vars`:
71
+
72
+ ```
73
+ VTEX_ACCOUNT=mystore
74
+ VTEX_APP_KEY=...
75
+ VTEX_APP_TOKEN=...
76
+ ```
77
+
78
+ Accessed via `process.env.*` in `createServerFn` handlers.
@@ -0,0 +1,55 @@
1
+ # package.json Template
2
+
3
+ ```json
4
+ {
5
+ "name": "my-tanstack-store",
6
+ "version": "0.1.0",
7
+ "type": "module",
8
+ "scripts": {
9
+ "dev": "npm run generate && vite dev",
10
+ "build": "npm run generate && vite build",
11
+ "generate": "npm run generate:blocks && npm run generate:invoke && npm run generate:schema",
12
+ "generate:blocks": "tsx node_modules/@decocms/start/scripts/generate-blocks.ts",
13
+ "generate:invoke": "tsx node_modules/@decocms/start/scripts/generate-invoke.ts",
14
+ "generate:schema": "tsx node_modules/@decocms/start/scripts/generate-schema.ts --site storefront",
15
+ "deploy": "wrangler deploy"
16
+ },
17
+ "dependencies": {
18
+ "@decocms/apps": "^0.20.1",
19
+ "@decocms/start": "^0.16.4",
20
+ "@tanstack/react-query": "^5.90.21",
21
+ "@tanstack/react-router": "^1.166.2",
22
+ "@tanstack/react-router-devtools": "^1.166.2",
23
+ "@tanstack/react-start": "^1.166.2",
24
+ "@tanstack/react-store": "^0.9.1",
25
+ "react": "^19.2.4",
26
+ "react-dom": "^19.2.4"
27
+ },
28
+ "devDependencies": {
29
+ "@cloudflare/vite-plugin": "^1.26.1",
30
+ "@tailwindcss/vite": "^4.2.1",
31
+ "@tanstack/router-generator": "^1.166.0",
32
+ "@types/react": "^19.0.0",
33
+ "@types/react-dom": "^19.0.0",
34
+ "babel-plugin-react-compiler": "^1.0.0",
35
+ "daisyui": "^5.5.19",
36
+ "tailwindcss": "^4.2.1",
37
+ "tsx": "^4.19.2",
38
+ "typescript": "^5.9.3",
39
+ "vite": "^7.3.1",
40
+ "wrangler": "^4.14.1",
41
+ "@vitejs/plugin-react": "^4.5.2"
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## Notes
47
+
48
+ - `@decocms/start` and `@decocms/apps` come from GitHub Packages — needs `.npmrc`:
49
+ ```
50
+ @decocms:registry=https://npm.pkg.github.com
51
+ //npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
52
+ ```
53
+ - Set `NODE_AUTH_TOKEN` in `.env` (add `.env` to `.gitignore`)
54
+ - `generate` scripts run before dev and build to produce `blocks.gen.ts`, `invoke.gen.ts`, `meta.gen.json`
55
+ - `tsx` is needed for the generate scripts (TypeScript execution)
@@ -0,0 +1,110 @@
1
+ # __root.tsx Template
2
+
3
+ ```typescript
4
+ import { useState } from "react";
5
+ import {
6
+ createRootRoute,
7
+ HeadContent,
8
+ Outlet,
9
+ Scripts,
10
+ useRouterState,
11
+ } from "@tanstack/react-router";
12
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
13
+ import { LiveControls } from "@decocms/start/hooks";
14
+ import { ANALYTICS_SCRIPT } from "@decocms/start/sdk/analytics";
15
+ import appCss from "../styles/app.css?url";
16
+
17
+ // Deco analytics event dispatcher — must be in <head> before any section renders
18
+ const DECO_EVENTS_BOOTSTRAP = `
19
+ window.DECO = window.DECO || {};
20
+ window.DECO.events = window.DECO.events || {
21
+ _q: [],
22
+ dispatch: function(e) {
23
+ if (window.dataLayer) { window.dataLayer.push({ event: e.name, ecommerce: e.params }); }
24
+ this._q.push(e);
25
+ }
26
+ };
27
+ window.dataLayer = window.dataLayer || [];
28
+ `;
29
+
30
+ // Navigation progress bar CSS
31
+ const PROGRESS_CSS = `
32
+ @keyframes decoProgress{0%{width:0}30%{width:50%}60%{width:80%}100%{width:98%}}
33
+ .deco-nav-progress{position:fixed;top:0;left:0;height:3px;background:var(--color-primary,#e53e3e);z-index:9999;animation:decoProgress 4s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;box-shadow:0 0 8px var(--color-primary,#e53e3e)}
34
+ @keyframes decoFadeIn{from{opacity:0}to{opacity:1}}
35
+ .deco-nav-overlay{position:fixed;inset:0;z-index:9998;pointer-events:none;background:rgba(255,255,255,0.15);animation:decoFadeIn .2s ease-out}
36
+ `;
37
+
38
+ function NavigationProgress() {
39
+ const isLoading = useRouterState({ select: (s) => s.isLoading });
40
+ if (!isLoading) return null;
41
+ return (
42
+ <>
43
+ <style dangerouslySetInnerHTML={{ __html: PROGRESS_CSS }} />
44
+ <div className="deco-nav-progress" />
45
+ <div className="deco-nav-overlay" />
46
+ </>
47
+ );
48
+ }
49
+
50
+ export const Route = createRootRoute({
51
+ head: () => ({
52
+ meta: [
53
+ { charSet: "utf-8" },
54
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
55
+ { title: "My Store" },
56
+ ],
57
+ links: [
58
+ { rel: "preconnect", href: "https://fonts.googleapis.com" },
59
+ { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
60
+ // Add your font stylesheet here:
61
+ // { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=..." },
62
+ { rel: "stylesheet", href: appCss },
63
+ { rel: "icon", href: "/favicon.ico" },
64
+ ],
65
+ }),
66
+ component: RootLayout,
67
+ });
68
+
69
+ function RootLayout() {
70
+ const [queryClient] = useState(
71
+ () =>
72
+ new QueryClient({
73
+ defaultOptions: {
74
+ queries: {
75
+ staleTime: import.meta.env.DEV ? 0 : 30_000,
76
+ gcTime: import.meta.env.DEV ? 0 : 5 * 60_000,
77
+ refetchOnWindowFocus: import.meta.env.DEV,
78
+ },
79
+ },
80
+ })
81
+ );
82
+
83
+ return (
84
+ <html lang="pt-BR" data-theme="light" suppressHydrationWarning>
85
+ <head>
86
+ <script dangerouslySetInnerHTML={{ __html: DECO_EVENTS_BOOTSTRAP }} />
87
+ <HeadContent />
88
+ </head>
89
+ <body className="bg-base-100 text-base-content" suppressHydrationWarning>
90
+ <NavigationProgress />
91
+ <QueryClientProvider client={queryClient}>
92
+ <Outlet />
93
+ </QueryClientProvider>
94
+ <LiveControls site={process.env.DECO_SITE_NAME} />
95
+ <script type="module" dangerouslySetInnerHTML={{ __html: ANALYTICS_SCRIPT }} />
96
+ <Scripts />
97
+ </body>
98
+ </html>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Key Points
104
+
105
+ 1. **QueryClientProvider** — Required even if not using React Query directly. @decocms/apps hooks may use it.
106
+ 2. **LiveControls** — Admin iframe bridge. `site` prop must match CMS site name.
107
+ 3. **DECO_EVENTS_BOOTSTRAP** — Must be in `<head>` before sections. Sections dispatch analytics events on render.
108
+ 4. **NavigationProgress** — Visual feedback during client-side navigation.
109
+ 5. **suppressHydrationWarning** — On `<html>` and `<body>` to avoid mismatches from browser extensions.
110
+ 6. **data-theme="light"** — Required for DaisyUI v4/v5 CSS variables to activate.