@decocms/start 2.22.0 → 2.23.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.
@@ -43,7 +43,7 @@ Each phase has entry/exit criteria. Follow in order. Automation % indicates how
43
43
  | [2](#phase-2--signals--state) | Signals & State | ~50% | `references/signals/` |
44
44
  | [3](#phase-3--deco-framework) | Deco Framework Elimination | ~80% | `references/deco-framework/` |
45
45
  | [4](#phase-4--commerce--types) | Commerce Types & UI | ~70% | `references/commerce/` |
46
- | [5](#phase-5--platform-hooks) | Platform Hooks | 0% | `references/platform-hooks/` |
46
+ | [5](#phase-5--platform-hooks) | Platform Hooks (factories, W12+) | template | `references/platform-hooks-factories.md` |
47
47
  | [6](#phase-6--islands-elimination) | Islands Elimination | ~60% | `references/islands.md` |
48
48
  | [7](#phase-7--section-registry) | Section Registry & Setup | 0% | `references/async-rendering.md` |
49
49
  | [8](#phase-8--routes--cms) | Routes & CMS | template | `references/navigation.md` |
@@ -152,14 +152,18 @@ See: `references/commerce/README.md`, `references/vtex-commerce.md`
152
152
 
153
153
  **Entry**: Phase 4 complete
154
154
 
155
- **Actions** (manual implementation):
156
- 1. Create `src/hooks/useCart.ts` — module-level singleton + listener pattern
157
- 2. Create `src/hooks/useUser.ts`, `src/hooks/useWishlist.ts` (stubs or real)
158
- 3. Wire VTEX API calls via `@decocms/apps` invoke functions
155
+ **Actions (Wave 12+ factory-based — current)**:
156
+ 1. `src/hooks/useCart.ts` — 5-line shim around `createUseCart` from `@decocms/apps/vtex/hooks/createUseCart`
157
+ 2. `src/hooks/useUser.ts` 5-line shim around `createUseUser`
158
+ 3. `src/hooks/useWishlist.ts` 5-line shim around `createUseWishlist`
159
+ 4. The migration template (`scripts/migrate/templates/hooks.ts`) emits all three for VTEX sites automatically.
160
+
161
+ For non-VTEX platforms, scaffold no-op stubs using `@decocms/start/sdk/signal` (see factories doc § "Non-VTEX platforms").
159
162
 
160
163
  **Exit**: Cart add/remove works, no `apps/{platform}/hooks` imports
161
164
 
162
- See: `references/platform-hooks/README.md`
165
+ See: `references/platform-hooks-factories.md` (canonical, Wave 12+).
166
+ Pre-W12 manual approach is preserved at `references/platform-hooks/README.md` for sites that haven't migrated to factories yet.
163
167
 
164
168
  ---
165
169
 
@@ -356,7 +360,8 @@ For sites with 100+ sections:
356
360
  | Signals → TanStack Store | `references/signals/` |
357
361
  | Deco framework elimination | `references/deco-framework/` |
358
362
  | Commerce & widget types | `references/commerce/` |
359
- | Platform hooks (VTEX) | `references/platform-hooks/` |
363
+ | Platform hooks (VTEX, factories — Wave 12+) | `references/platform-hooks-factories.md` |
364
+ | Platform hooks (manual, legacy pre-W12) | `references/platform-hooks/README.md` |
360
365
  | Vite configuration | `references/vite-config/` |
361
366
  | Automation commands | `references/codemod-commands.md` |
362
367
  | Islands elimination | `references/islands.md` |
@@ -1,16 +1,29 @@
1
- # Platform Hooks Migration
1
+ # Platform Hooks Migration (legacy reference)
2
2
 
3
- Platform hooks (useCart, useUser, useWishlist) are the most complex migration target because they have real business logic.
3
+ > **This document describes the pre-Wave-12 manual approach.** New
4
+ > migrations should follow
5
+ > [`platform-hooks-factories.md`](../platform-hooks-factories.md), which
6
+ > covers the `createUseCart` / `createUseUser` / `createUseWishlist`
7
+ > factories from `@decocms/apps/vtex/hooks`. The factories collapse
8
+ > everything below into a 5-line site shim per hook.
9
+ >
10
+ > This file is kept for sites that scaffolded before the factories
11
+ > existed — typically sites with `src/lib/vtex-cart-server.ts` or
12
+ > hand-rolled `createServerFn` calls to VTEX endpoints inside
13
+ > `src/hooks/useCart.ts`. See the **"Migrating off the manual approach"**
14
+ > section in the new doc for the cleanup playbook.
4
15
 
5
- ## Strategy
16
+ ---
17
+
18
+ ## Strategy (legacy)
6
19
 
7
20
  All hooks are **site-local**. No Vite alias tricks. No compat layers.
8
21
 
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`
22
+ - Active platform hooks (VTEX for this store) `~/hooks/useCart.ts` with real implementation
23
+ - Inactive platform hooks (Wake, Shopify, etc.) `~/hooks/platform/{name}.ts` with no-op stubs
24
+ - Auth hooks `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
12
25
 
13
- ## VTEX useCart (Real Implementation)
26
+ ## VTEX useCart (Manual Implementation)
14
27
 
15
28
  ### Why Server Functions Are Required
16
29
 
@@ -18,7 +31,7 @@ The storefront domain (e.g., `my-store.deco.site`) differs from the VTEX checkou
18
31
 
19
32
  Use TanStack Start `createServerFn` to create server-side proxy functions that the client hook calls transparently.
20
33
 
21
- ### Server Functions (~/lib/vtex-cart-server.ts)
34
+ ### Server Functions (`~/lib/vtex-cart-server.ts`)
22
35
 
23
36
  ```typescript
24
37
  import { createServerFn } from "@tanstack/react-start";
@@ -40,50 +53,46 @@ export const getOrCreateCart = createServerFn({ method: "GET" })
40
53
  "X-VTEX-API-AppKey": API_KEY,
41
54
  "X-VTEX-API-AppToken": API_TOKEN,
42
55
  },
43
- body: JSON.stringify({ expectedOrderFormSections: ["items", "totalizers", "shippingData", "clientPreferencesData", "storePreferencesData", "marketingData"] }),
56
+ body: JSON.stringify({
57
+ expectedOrderFormSections: [
58
+ "items",
59
+ "totalizers",
60
+ "shippingData",
61
+ "clientPreferencesData",
62
+ "storePreferencesData",
63
+ "marketingData",
64
+ ],
65
+ }),
44
66
  });
45
67
  return res.json();
46
68
  });
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
69
  ```
76
70
 
77
- ### Hook (~/hooks/useCart.ts)
71
+ > **Don't write code like this in new sites.** The factories already wrap
72
+ > all canonical VTEX action endpoints (cart, session, masterdata,
73
+ > newsletter, checkout) in `@decocms/apps/vtex/actions/*`. The migration
74
+ > template scaffolds `src/server/invoke.gen.ts` which exposes them as
75
+ > typed server functions; `~/server/invoke.ts` then re-exports them
76
+ > under `invoke.vtex.actions.*`. The factory consumes that surface and
77
+ > returns the legacy hook shape.
78
78
 
79
- Key design decisions:
79
+ ### Hook (`~/hooks/useCart.ts`)
80
+
81
+ Key design decisions of the legacy manual hook:
80
82
  - **Module-level singleton state** shared across all component instances
81
83
  - **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)
84
+ - **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side
83
85
  - Returns `cart` and `loading` with `.value` getter/setter for backward compat with Preact-era components
84
86
  - Lazy initialization: cart is fetched on first component mount, not on module load
85
87
  - Exports `itemToAnalyticsItem` for cart-specific analytics mapping
86
88
 
89
+ The factory in `@decocms/apps/vtex/hooks/createUseCart` ships *exactly*
90
+ these semantics — that's the implementation behind the new shim. If your
91
+ site needs to extend behaviour (e.g. extra analytics events, custom
92
+ post-add hooks), prefer wrapping the factory's exports rather than
93
+ forking back to a manual hook; the factory leaves space for that
94
+ without giving up the upgrade path.
95
+
87
96
  ### Cross-Domain Checkout
88
97
 
89
98
  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:
@@ -92,10 +101,8 @@ The minicart's "Finalizar Compra" button must link to the VTEX checkout domain w
92
101
  const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
93
102
  ```
94
103
 
95
- ### VTEX Types (~/types/vtex.ts)
96
-
97
- Site-local types for VTEX-specific structures:
98
- - `OrderFormItem`, `SimulationOrderForm`, `Sla`, `SKU`, `VtexProduct`
104
+ This pattern is unchanged by the factory — it's a UI concern, not a hook
105
+ concern. Implement it in your minicart component as before.
99
106
 
100
107
  ## Inactive Platform Stubs
101
108
 
@@ -130,9 +137,13 @@ export function useWishlist() {
130
137
  }
131
138
  ```
132
139
 
133
- Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`.
140
+ Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`. Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
134
141
 
135
- Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
142
+ The factory equivalent for non-VTEX sites is documented in
143
+ [`platform-hooks-factories.md` § "Non-VTEX platforms"](../platform-hooks-factories.md#non-vtex-platforms).
144
+ Until each platform has its own factory in `@decocms/apps`, the stub
145
+ shape above is still correct — but use `@decocms/start/sdk/signal`
146
+ instead of hand-rolled `{ value: ... }` objects.
136
147
 
137
148
  ## Import Rewrites
138
149
 
@@ -143,7 +154,6 @@ sed -i '' 's|from "apps/vtex/hooks/useWishlist.ts"|from "~/hooks/useWishlist"|g'
143
154
  sed -i '' 's|from "apps/vtex/utils/types.ts"|from "~/types/vtex"|g'
144
155
  sed -i '' 's|from "apps/shopify/hooks/useCart.ts"|from "~/hooks/platform/shopify"|g'
145
156
  sed -i '' 's|from "apps/wake/hooks/useCart.ts"|from "~/hooks/platform/wake"|g'
146
- # etc. for all platforms
147
157
  ```
148
158
 
149
159
  ## Verification
@@ -0,0 +1,186 @@
1
+ # Platform Hooks — Factory Pattern
2
+
3
+ > **Canonical reference for `createUseCart` / `createUseUser` /
4
+ > `createUseWishlist` from `@decocms/apps/vtex/hooks`.** These factories
5
+ > ship the legacy invoke-based hook semantics that migrated Fresh sites
6
+ > depend on — module-level singleton state, listener-based re-render,
7
+ > awaitable async actions, signal-shaped accessors. Sites consume them
8
+ > as 5-line shims.
9
+
10
+ This doc replaces the pre-W12 "manual `createServerFn` per VTEX endpoint"
11
+ approach in
12
+ [`platform-hooks/README.md`](./platform-hooks/README.md). If you scaffolded
13
+ a site before Wave 12 (≤ `@decocms/apps@2.x` / `@decocms/start@2.18`), see
14
+ "Migrating off the manual approach" at the bottom.
15
+
16
+ ---
17
+
18
+ ## What the factories own
19
+
20
+ | Concern | Where it lives |
21
+ |---|---|
22
+ | Module-level singleton state (`cart`, `user`, `wishlist`) | Inside the factory closure |
23
+ | `useEffect` + `forceRender(c => c + 1)` re-render pattern | Factory |
24
+ | Signal-shaped accessors (`cart.value`, `user.value`) | Factory |
25
+ | Awaitable mutations (`await addItem(...)`) | Factory |
26
+ | `itemToAnalyticsItem` helper (cart) | Factory |
27
+ | Wishlist arg swap (`productId` ↔ `productGroupId`) | Factory |
28
+ | **VTEX HTTP calls** | NOT in the factory — provided by the `invoke` proxy you pass in |
29
+
30
+ The factory only wires state + listeners. The site provides an `invoke`
31
+ object whose shape is structurally typed against
32
+ `CreateUseCartInvoke` / `CreateUseUserInvoke` / `CreateUseWishlistInvoke`
33
+ (exported next to each factory). The migration template generates an
34
+ `invoke` proxy in `src/server/invoke.ts` that meets all three shapes
35
+ without any extra wiring.
36
+
37
+ ---
38
+
39
+ ## Site-local shim (the entire file)
40
+
41
+ ### `src/hooks/useCart.ts`
42
+
43
+ ```ts
44
+ import { createUseCart } from "@decocms/apps/vtex/hooks/createUseCart";
45
+ import { invoke } from "~/server/invoke";
46
+
47
+ export type { OrderForm, OrderFormItem } from "@decocms/apps/vtex/types";
48
+
49
+ export const { useCart, resetCart, itemToAnalyticsItem } = createUseCart({
50
+ invoke,
51
+ });
52
+ ```
53
+
54
+ ### `src/hooks/useUser.ts`
55
+
56
+ ```ts
57
+ import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser";
58
+ import { invoke } from "~/server/invoke";
59
+
60
+ export type { Person } from "@decocms/apps/vtex/loaders/user";
61
+
62
+ export const { useUser, resetUser } = createUseUser({ invoke });
63
+ ```
64
+
65
+ ### `src/hooks/useWishlist.ts`
66
+
67
+ ```ts
68
+ import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist";
69
+ import { invoke } from "~/server/invoke";
70
+
71
+ export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist";
72
+
73
+ export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
74
+ ```
75
+
76
+ That's the whole hook — no `createServerFn`, no VTEX URLs, no `AppKey`
77
+ plumbing. `npm run migrate` scaffolds these three files automatically
78
+ when `--platform vtex` is set; if you regenerate, the migration template
79
+ in `scripts/migrate/templates/hooks.ts` is the source of truth.
80
+
81
+ ---
82
+
83
+ ## Why a factory and not a single hook?
84
+
85
+ > Two reasons that come up repeatedly when reviewing migration PRs.
86
+
87
+ 1. **`useCart` already exists in apps.** The canonical `vtex/hooks/useCart.ts`
88
+ is built on TanStack Query and exposes the `Minicart` shape — that is
89
+ the shape new code should target. The factory exists strictly so
90
+ already-migrated UIs keep working without a rewrite. Both can coexist
91
+ in one site.
92
+ 2. **Singletons can't live in a shared package without leaking across
93
+ sites.** The factory call instantiates a fresh module-level state per
94
+ site. Importing `useCart` directly from apps would share state across
95
+ any sites that ran in the same Worker (matters less in practice, but
96
+ it's the architectural reason the factory exists).
97
+
98
+ The factory boundary is also the seam where we'd later wire
99
+ `@tanstack/store` if we wanted to — the API shape is signal-compatible
100
+ already.
101
+
102
+ ---
103
+
104
+ ## Non-VTEX platforms
105
+
106
+ Sites that target Wake / Shopify / VNDA / Linx / Nuvemshop still need a
107
+ hook surface that AddToCartButtons can consume. Until each platform has
108
+ its own factory in `@decocms/apps`, scaffold a no-op shim:
109
+
110
+ ```ts
111
+ // src/hooks/useCart.ts (custom platform)
112
+ import { signal } from "@decocms/start/sdk/signal";
113
+
114
+ const cart = signal<unknown>(null);
115
+ const loading = signal(false);
116
+
117
+ export function useCart() {
118
+ return {
119
+ cart,
120
+ loading,
121
+ async getCart() {
122
+ // TODO: call your platform's cart API via ~/server/invoke
123
+ return null;
124
+ },
125
+ async addItems(_items: unknown[]) {
126
+ // TODO
127
+ },
128
+ async updateItems(_items: unknown[]) {
129
+ // TODO
130
+ },
131
+ setCart(next: unknown) {
132
+ cart.value = next;
133
+ },
134
+ };
135
+ }
136
+
137
+ export default useCart;
138
+ ```
139
+
140
+ `@decocms/start/sdk/signal` (re-exported via `~/sdk/signal` after
141
+ migration) gives you the same `.value` getter/setter the factory uses,
142
+ so AddToCart UI components don't need to know which platform is wired.
143
+
144
+ The migration template's `generateGenericUseCart()` emits this stub when
145
+ `--platform` is `custom` (or any non-VTEX value).
146
+
147
+ ---
148
+
149
+ ## Migrating off the manual approach (pre-W12 sites)
150
+
151
+ If your site contains files like `src/lib/vtex-cart-server.ts` or hand-rolled `createServerFn` blocks for VTEX endpoints in `src/hooks/useCart.ts`, the post-cleanup audit will not auto-fix them — the manual code accumulated 6+ months of site-specific edits and the per-site judgment call about "what's still needed?" is real. The mechanical part:
152
+
153
+ 1. Replace the entire body of `src/hooks/useCart.ts` with the 5-line factory shim above.
154
+ 2. Delete `src/lib/vtex-cart-server.ts` (the migration template's `src/server/invoke.gen.ts` provides equivalent server functions wrapping `@decocms/apps/vtex/actions/checkout`).
155
+ 3. Verify `src/server/invoke.ts` exports the proxy shape the factory needs (cart actions under `invoke.vtex.actions`). The migration template scaffolds this; older sites may need to add the missing entries by hand.
156
+ 4. Run `npm run typecheck` — TypeScript will surface any callsites that referenced removed helpers (e.g. local `getOrCreateCart` shims).
157
+ 5. Repeat for `useUser` / `useWishlist`.
158
+
159
+ For `useUser`, the analog of step 2 is removing any local `currentUser` /
160
+ `getUser` server functions in favor of `@decocms/apps/vtex/loaders/user`
161
+ exposed via `invoke.vtex.loaders.user()`. For `useWishlist`, the
162
+ canonical surface is `@decocms/apps/vtex/{actions,loaders}/wishlist`.
163
+
164
+ If you find yourself wanting to add behaviour to a factory (extra cart
165
+ actions, custom analytics events) rather than ripping out the factory and going back to a manual hook:
166
+
167
+ - **Extra read paths** → expose a new loader from
168
+ `@decocms/apps/vtex/loaders/*`, register it in `~/server/invoke.ts`,
169
+ call from your component (the factory doesn't need to know).
170
+ - **Extra write paths** → ditto for `@decocms/apps/vtex/actions/*`.
171
+ - **Cross-cutting business logic** (e.g. PIX-specific offer pricing) →
172
+ this is the kind of seam that justifies a parallel `useOffer` factory.
173
+ Talk to the apps maintainers; opening up a factory's plugin slots is a
174
+ one-PR change in apps, not a per-site rewrite.
175
+
176
+ ---
177
+
178
+ ## Related
179
+
180
+ - `scripts/migrate/templates/hooks.ts` — the template that emits the
181
+ shims above.
182
+ - `apps-start/vtex/hooks/createUseCart.ts` /
183
+ `createUseUser.ts` / `createUseWishlist.ts` — the factories
184
+ themselves; each docstring is the authoritative API reference.
185
+ - `references/platform-hooks/README.md` — historical reference for the
186
+ pre-W12 manual approach (kept for sites that haven't migrated yet).
@@ -239,6 +239,58 @@ Each item carries a status: ⬜ pending, 🟡 in progress, ✅ done, 🚫 blocke
239
239
 
240
240
  > Append-only. Each entry: date, what we found, where it impacts the plan.
241
241
 
242
+ ### 2026-05-01 — Wave 15-A double-check exposed a self-perpetuating template→audit loop
243
+
244
+ - **Q1 (sites→packages promotion completeness):** Two subagents
245
+ swept `casaevideo-storefront` + `baggagio-tanstack`. The big
246
+ A-list icebergs are caught — but **5 cross-site duplications
247
+ satisfying D4** slipped through (`useSuggestions`, `useOffer`
248
+ forks, `useVariantPossibilities` forks, site-local copies of
249
+ framework `clx` / `useSendEvent`, three competing `Picture`
250
+ APIs). Plus 4 migration debts where the framework already has
251
+ the answer (`useCart` factory not adopted in casaevideo,
252
+ `runtime.ts` inline proxy still scaffolded, location matcher
253
+ duplication, inline cookie helpers).
254
+ - **Q2 (script/skill coverage of what we shipped):** Worse. The
255
+ migration script's templates were **scaffolding code that the
256
+ audit's `--fix` then removed** — the textbook
257
+ self-perpetuating loop. `templates/vite-config.ts` was emitting
258
+ `site-manual-chunks` + `deco-stub-meta-gen` (both already
259
+ inside `decoVitePlugin()`). `templates/server-entry.ts` was
260
+ emitting a 47-line `createNestedInvokeProxy` body (already in
261
+ `@decocms/start/sdk`). The factories shipped in W12 (`createUseUser`
262
+ / `createUseWishlist`) had **zero skill mentions** — the
263
+ pre-W12 manual approach was still the canonical doc. Cookie
264
+ passthrough helpers in `cookiePassthrough.ts` were **half-shipped**:
265
+ the deco-start side compiled, the apps-start providers it
266
+ references in its own docstring don't exist, and the migration
267
+ script never wired either.
268
+ - **Decided: ship Wave 15-A as a single PR.** Drop the obsolete
269
+ emissions (G1/G2/G4/G5), expand `dead-runtime-shim` to catch
270
+ both the legacy inline shape (with `Runtime` export) and the
271
+ Wave-15-A canonical re-export shape (skip — desired form),
272
+ publish a `platform-hooks-factories.md` skill that supersedes
273
+ the stale README, and update plan + journal.
274
+ - **Defer to Wave 15-B / 16:** (G3) promotion of the
275
+ `invoke.gen.ts` 170-LOC server-fn block to apps-start needs
276
+ research on TanStack Start's compiler scanning behaviour
277
+ (whether `createServerFn` can be transformed when it lives in
278
+ a node_module). (H1) full cookie-passthrough provider wiring
279
+ (`setRequestCookieProvider` / `setResponseCookieForwarder` in
280
+ apps-start, auto-wire in `setup.ts`) needs design. The 5
281
+ cross-site convergence promotions (`useSuggestions`,
282
+ `useOffer` factory, `Picture` unification, `clx`/`useSendEvent`
283
+ redirects, `relative()` extension) are now in the priority-2
284
+ backlog, sequenced after Wave 15-A merges.
285
+ - **The double-check itself is a useful primitive.** "Did we
286
+ ship script/skill/audit coverage for everything we built?" run
287
+ against the framework + apps inventory consistently surfaces
288
+ these loops. Codify it: when promoting a new factory or
289
+ helper, the PR checklist must include "matching template
290
+ emit", "matching audit-rule expansion", "matching skill doc
291
+ entry". This is the kind of self-check that prevents the next
292
+ 16-month-old stale skill.
293
+
242
294
  ### 2026-05-01 — Wave 14-A rescoped from three codemods to one based on real als data
243
295
 
244
296
  - **Pre-data plan vs post-data plan.** The plan called for three
@@ -984,6 +1036,98 @@ recipes in `references/htmx-rewrite.md`.
984
1036
  not the import), but the import-removal would create dead
985
1037
  references. Order is correct.
986
1038
 
1039
+ ### Wave 15-A (close template→audit loops + factories skill — Priority 2 follow-on) — 🟡 **IN FLIGHT**
1040
+
1041
+ Triggered by the double-check audit on 2026-05-01: subagent sweeps over
1042
+ casaevideo-storefront + baggagio-tanstack revealed (a) the migration
1043
+ template was scaffolding code that the audit's `--fix` then removed,
1044
+ and (b) the W12 factory hooks (`createUseUser`, `createUseWishlist`)
1045
+ had no skill coverage. Wave 15-A closes both loops in one PR.
1046
+
1047
+ **Shipped (one PR against `decocms/deco-start`):**
1048
+
1049
+ 37. `feat(migrate): close template→audit loops for vite plugins, runtime, cookies, branding + factories skill` 🟡 **WAITING ON CI**.
1050
+ - **`templates/vite-config.ts`** — drop `site-manual-chunks` and
1051
+ `deco-stub-meta-gen` plugin emissions. Both already live in
1052
+ `decoVitePlugin()` (`src/vite/plugin.js`). The audit's
1053
+ `obsolete-vite-plugins --fix` was undoing the template's own
1054
+ output; now the template emits clean, the audit catches
1055
+ regressions in legacy sites.
1056
+ - **`templates/server-entry.ts` `generateRuntime()`** — replace
1057
+ the 47-line inline `createNestedInvokeProxy` body with a 6-line
1058
+ re-export from `@decocms/start/sdk`. Sites keep
1059
+ `import { invoke } from "~/runtime"` and `Runtime.invoke`
1060
+ shapes. A2 of the original investigation finally lands at the
1061
+ template layer (was previously only patched post-migration by
1062
+ the audit).
1063
+ - **`templates/server-entry.ts` `generateInvoke()` (VTEX path)**
1064
+ — replace inline `mergeSetCookies` helper with
1065
+ `forwardResponseCookies` from
1066
+ `@decocms/start/sdk/cookiePassthrough`. The framework helper
1067
+ already shipped with try/catch for build-time safety.
1068
+ `getVtexCookies` stays inline (it's auth-specific filtering, not
1069
+ generic passthrough — see Wave 15-B/16 for full provider wiring
1070
+ under H1).
1071
+ - **`templates/routes.ts` + `templates/commerce-loaders.ts`** —
1072
+ replace casaevideo-specific branding leaks ("Tudo para sua
1073
+ casa…" tagline, "O melhor site de compras online…"
1074
+ `productListPageCollection` SEO description) with
1075
+ `${siteTitle}`-derived defaults plus `MIGRATION TODO` markers
1076
+ pointing at the per-site customization spot. CMS `Site.seo`
1077
+ overrides the defaults at runtime so leaving them visible in
1078
+ pre-resolution states is the safe behaviour.
1079
+ - **Audit rule expansion: `dead-runtime-shim`** — previously only
1080
+ flagged when exports were exactly `{ invoke }` or
1081
+ `{ invoke, createNestedInvokeProxy }`. Updated to detect (a)
1082
+ inline `createNestedInvokeProxy` body via regex (catches the
1083
+ legacy 47-line shape **with** `Runtime` export — which the old
1084
+ heuristic missed entirely; this is the shape every existing
1085
+ VTEX site has) and (b) skip the new Wave-15-A canonical
1086
+ re-export shape (where `import invoke from
1087
+ @decocms/start/sdk` is present and no inline proxy body
1088
+ exists). Auto-fix is gated by `safeToAutoFix` metadata: legacy
1089
+ shim shapes get the rewrite + delete; sites that mix the proxy
1090
+ with custom helpers get a warning only. Three new tests.
1091
+ **Verified against casaevideo-storefront**: now flags
1092
+ `[invoke, Runtime] inline createNestedInvokeProxy body` (was
1093
+ missed entirely before).
1094
+ - **Skill: `references/platform-hooks-factories.md`** — new
1095
+ canonical doc covering `createUseCart` / `createUseUser` /
1096
+ `createUseWishlist`. Replaces the pre-W12 manual approach of
1097
+ hand-rolling 200+ LOC of `createServerFn` wrappers per site.
1098
+ Documents the 5-line shim shape, why factories instead of
1099
+ direct hook imports (state isolation per site), non-VTEX
1100
+ stubs using `@decocms/start/sdk/signal`, and the migration
1101
+ path off the manual approach.
1102
+ - **Skill update: `references/platform-hooks/README.md`** —
1103
+ retained as legacy reference but now opens with a
1104
+ "deprecated, see canonical" header pointing at the new doc.
1105
+ The pre-W12 `createServerFn` examples are kept for sites that
1106
+ haven't migrated to factories yet.
1107
+ - **`SKILL.md` index update** — Phase 5 entry now points to the
1108
+ factories doc; reference table lists both new + legacy paths.
1109
+ - 342 → 345 tests pass, typecheck clean, smoke against casaevideo
1110
+ + baggagio confirms expanded rule fires correctly on legacy
1111
+ shapes and stays silent on baggagio (no `runtime.ts` file there).
1112
+
1113
+ **Deferred to Wave 15-B / 16 (intentionally — see discoveries journal):**
1114
+
1115
+ - **G3** — promote the 170-LOC `invoke.gen.ts` VTEX `createServerFn`
1116
+ wrappers into `@decocms/apps/vtex/server-fns`. Needs research on
1117
+ whether TanStack Start's compiler can transform `createServerFn`
1118
+ call sites that live inside a node_module. Not safe to ship blind.
1119
+ - **H1** — full cookie-passthrough provider wiring
1120
+ (`setRequestCookieProvider` / `setResponseCookieForwarder` in
1121
+ apps-start, auto-wire in `templates/setup.ts`). The
1122
+ `cookiePassthrough.ts` docstring already references this design but
1123
+ the apps-start side doesn't exist. Needs a design pass to scope
1124
+ what calls inside `vtex/utils/fetch.ts` need the provider hook
1125
+ (currently each call site forwards cookies manually).
1126
+ - **Cross-site convergence promotions** (5 items) — `useSuggestions`,
1127
+ `useOffer` factory, `Picture` API unification, redirect
1128
+ `useSendEvent`/`clx`/location-matcher imports, `relative()`
1129
+ SKU-stripping extension. Sequenced after 15-A merges.
1130
+
987
1131
  ### Wave 15+ (htmx cleanup PRs on als + propagation to other sites) — Priority 3 / 4
988
1132
 
989
1133
  Each htmx pattern that survives the codemod becomes a per-pattern PR
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "2.22.0",
3
+ "version": "2.23.0",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -462,6 +462,31 @@ function findAttachedLeadingComments(content: string, openIdx: number): number {
462
462
  /* Rule 3 — dead `src/runtime.ts` invoke shim */
463
463
  /* ------------------------------------------------------------------ */
464
464
 
465
+ /**
466
+ * Detection covers two shapes of `src/runtime.ts`:
467
+ *
468
+ * 1. Legacy inline proxy (pre-Wave 15-A migration template) — defines
469
+ * `createNestedInvokeProxy` plus `invoke` and `Runtime` constants.
470
+ * The whole 40-50 LOC body duplicates `@decocms/start/sdk`'s `invoke`.
471
+ *
472
+ * 2. Simple re-export shim — the file only re-exports `invoke` /
473
+ * `createNestedInvokeProxy` (no inline proxy body, but also not yet
474
+ * pointing at `@decocms/start/sdk`).
475
+ *
476
+ * Both should be replaced with `import { invoke } from "@decocms/start/sdk"`
477
+ * at every callsite, and the file deleted. The Wave 15-A migration template
478
+ * scaffolds a thin re-export form that's also acceptable (re-exports
479
+ * `invoke` from `@decocms/start/sdk` and rebuilds `Runtime = { invoke }`);
480
+ * we explicitly skip it via the "imports invoke from @decocms/start/sdk
481
+ * AND no inline proxy" check below.
482
+ */
483
+ const INLINE_PROXY_RE =
484
+ /(?:function|const)\s+createNestedInvokeProxy\b|new\s+Proxy\s*\(\s*Object\.assign\s*\(\s*async\s*\(\s*props/;
485
+ const FRAMEWORK_INVOKE_IMPORT_RE =
486
+ /import\s+\{[^}]*\binvoke\b[^}]*\}\s+from\s+['"]@decocms\/start(?:\/sdk)?['"]/;
487
+
488
+ const ALLOWED_RUNTIME_EXPORTS = new Set(["invoke", "createNestedInvokeProxy", "Runtime"]);
489
+
465
490
  const ruleDeadRuntimeShim: Rule = {
466
491
  id: "dead-runtime-shim",
467
492
  title: "Dead src/runtime.ts invoke shim",
@@ -469,24 +494,54 @@ const ruleDeadRuntimeShim: Rule = {
469
494
  const abs = `${siteDir}/src/runtime.ts`;
470
495
  if (!fs.exists(abs)) return [];
471
496
  const content = fs.readText(abs);
472
- // Heuristic: if the file's only meaningful exports are `invoke` /
473
- // `createNestedInvokeProxy`, it's purely a shim.
497
+
498
+ const hasInlineProxy = INLINE_PROXY_RE.test(content);
499
+ const reExportsFromFramework = FRAMEWORK_INVOKE_IMPORT_RE.test(content);
474
500
  const exports = extractExports(content);
475
- const onlyInvokeShim =
476
- exports.length > 0 && exports.every((e) => ["invoke", "createNestedInvokeProxy"].includes(e));
477
- if (!onlyInvokeShim) return [];
501
+ const onlyKnownInvokeExports =
502
+ exports.length > 0 && exports.every((e) => ALLOWED_RUNTIME_EXPORTS.has(e));
503
+
504
+ // Wave 15-A canonical template: re-exports invoke from @decocms/start/sdk
505
+ // and exposes `Runtime = { invoke }` for legacy callers. No inline proxy
506
+ // body. This is the desired shape — skip.
507
+ if (reExportsFromFramework && !hasInlineProxy) return [];
508
+
509
+ // Site-specific helpers alongside invoke: don't flag — the file has its
510
+ // own purpose beyond shimming. (Old behavior preserved.)
511
+ if (!hasInlineProxy && !onlyKnownInvokeExports) return [];
512
+
513
+ const exportSummary = exports.length > 0 ? exports.join(", ") : "(re-exports only)";
514
+ const flavor = hasInlineProxy ? "inline createNestedInvokeProxy body" : "shim re-exports";
515
+ // Only safe to auto-delete when exports are pure invoke surface; if a
516
+ // legacy file mixes the inline proxy with custom helpers, we still flag
517
+ // it but skip the destructive fix.
518
+ const safeToAutoFix = onlyKnownInvokeExports;
519
+
478
520
  return [
479
521
  {
480
522
  rule: "dead-runtime-shim",
481
523
  severity: "info",
482
524
  file: "src/runtime.ts",
483
- message: `Only re-exports invoke (${exports.join(", ")}) — replace with @decocms/start/sdk`,
484
- fix: 'rg -l "from \\"~/runtime\\"" src/ | xargs sed -i \'\' \'s|from "~/runtime"|from "@decocms/start/sdk"|g\' && rm src/runtime.ts',
525
+ message: safeToAutoFix
526
+ ? `${flavor} [${exportSummary}] replace with @decocms/start/sdk`
527
+ : `${flavor} [${exportSummary}] — manual review: file mixes the runtime proxy with site-specific exports`,
528
+ fix: safeToAutoFix
529
+ ? 'rg -l "from \\"~/runtime\\"" src/ | xargs sed -i \'\' \'s|from "~/runtime"|from "@decocms/start/sdk"|g\' && rm src/runtime.ts'
530
+ : "Move the inline `createNestedInvokeProxy` body to call @decocms/start/sdk's `invoke`; relocate site-specific helpers to a dedicated module before deleting src/runtime.ts",
531
+ meta: {
532
+ hasInlineProxy,
533
+ exports,
534
+ safeToAutoFix,
535
+ },
485
536
  },
486
537
  ];
487
538
  },
488
539
  applyFix(ctx, findings, writer): FixAction[] {
489
540
  if (findings.length === 0) return [];
541
+ // Honor the per-finding safety gate emitted by run() — never auto-delete
542
+ // a runtime.ts that mixes the proxy with site-specific helpers.
543
+ const safe = findings.every((f) => f.meta?.safeToAutoFix !== false);
544
+ if (!safe) return [];
490
545
  const updated = rewriteImportSpec(ctx, writer, "~/runtime", "@decocms/start/sdk");
491
546
  writer.deleteFile(`${ctx.siteDir}/src/runtime.ts`);
492
547
  return [
@@ -227,9 +227,11 @@ describe("rule: dead-runtime-shim", () => {
227
227
  const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
228
228
  expect(r.findings).toHaveLength(1);
229
229
  expect(r.findings[0].file).toBe("src/runtime.ts");
230
+ expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
231
+ expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
230
232
  });
231
233
 
232
- it("does not flag a runtime.ts that exports site-specific helpers", () => {
234
+ it("does not flag a runtime.ts that exports site-specific helpers (no inline proxy)", () => {
233
235
  const fs = makeFs({
234
236
  "/site/src/runtime.ts": "export const invoke = {};\nexport const customHelper = () => 1;\n",
235
237
  });
@@ -237,6 +239,80 @@ describe("rule: dead-runtime-shim", () => {
237
239
  const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
238
240
  expect(r.findings).toEqual([]);
239
241
  });
242
+
243
+ it("does NOT flag the Wave 15-A canonical re-export shape", () => {
244
+ // The migration template now scaffolds a thin re-export from
245
+ // @decocms/start/sdk plus a Runtime alias. No inline proxy body.
246
+ const fs = makeFs({
247
+ "/site/src/runtime.ts":
248
+ 'import { invoke } from "@decocms/start/sdk";\nexport { invoke };\nexport const Runtime = { invoke };\n',
249
+ });
250
+ const report = runAudit(SITE, fs);
251
+ const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
252
+ expect(r.findings).toEqual([]);
253
+ });
254
+
255
+ it("flags the legacy 47-line inline createNestedInvokeProxy body (with Runtime export)", () => {
256
+ // The pre-Wave-15-A migration template emitted a full Proxy body
257
+ // alongside `Runtime = { invoke }`. The earlier rule heuristic
258
+ // missed this shape because `Runtime` was not in its allowlist.
259
+ const fs = makeFs({
260
+ "/site/src/runtime.ts": `
261
+ function createNestedInvokeProxy(path: string[] = []): any {
262
+ return new Proxy(
263
+ Object.assign(async (props: any) => {
264
+ const key = path.join("/");
265
+ const response = await fetch(\`/deco/invoke/\${key}\`, {
266
+ method: "POST",
267
+ headers: { "Content-Type": "application/json" },
268
+ body: JSON.stringify(props ?? {}),
269
+ });
270
+ return response.json();
271
+ }, {}),
272
+ {
273
+ get(_target: any, prop: string) {
274
+ if (prop === "then") return undefined;
275
+ return createNestedInvokeProxy([...path, prop]);
276
+ },
277
+ },
278
+ );
279
+ }
280
+
281
+ export const invoke = createNestedInvokeProxy() as any;
282
+ export const Runtime = { invoke };
283
+ `,
284
+ });
285
+ const report = runAudit(SITE, fs);
286
+ const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
287
+ expect(r.findings).toHaveLength(1);
288
+ expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
289
+ expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
290
+ expect(r.findings[0].message).toContain("inline createNestedInvokeProxy body");
291
+ });
292
+
293
+ it("flags but does NOT auto-fix when inline proxy coexists with site-specific helpers", () => {
294
+ // Defensive: if a site has hand-tuned the runtime file with extra
295
+ // exports beyond invoke/Runtime, deletion would lose data. Surface
296
+ // the issue but skip the destructive fix.
297
+ const fs = makeFs({
298
+ "/site/src/runtime.ts": `
299
+ function createNestedInvokeProxy(path: string[] = []): any {
300
+ return new Proxy(Object.assign(async (props: any) => {}, {}), {});
301
+ }
302
+ export const invoke = createNestedInvokeProxy();
303
+ export const trackPageView = () => console.log("custom tracker");
304
+ `,
305
+ });
306
+ const report = runAudit(SITE, fs);
307
+ const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
308
+ expect(r.findings).toHaveLength(1);
309
+ expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
310
+ expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
311
+ expect(r.findings[0].message).toContain("manual review");
312
+ // applyFix should be a no-op when safeToAutoFix is false — verified
313
+ // implicitly by the runner test for --fix below; here we only
314
+ // assert the metadata gate.
315
+ });
240
316
  });
241
317
 
242
318
  describe("rule: site-local-with-globals", () => {
@@ -204,7 +204,8 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
204
204
  lines.push(` breadcrumb: createBreadcrumbFromPath(url.pathname, url, collection.name) ?? {},`);
205
205
  lines.push(` seo: {`);
206
206
  lines.push(` title: collection.name,`);
207
- lines.push(` description: "O melhor site de compras online para sua casa: compre itens de cozinha, móveis para sala e escritório, acessórios de tecnologia e mais. Clique já!",`);
207
+ lines.push(` // MIGRATION TODO: replace with site-specific category description`);
208
+ lines.push(` description: collection.name,`);
208
209
  lines.push(` noIndexing: false,`);
209
210
  lines.push(` canonical: url.toString(),`);
210
211
  lines.push(` },`);
@@ -67,8 +67,10 @@ import { DecoRootLayout } from "@decocms/start/hooks";
67
67
  // @ts-ignore Vite ?url import
68
68
  import appCss from "../styles/app.css?url";
69
69
 
70
- const DEFAULT_DESCRIPTION =
71
- "${siteTitle} - Tudo para sua casa com os melhores preços.";
70
+ // MIGRATION TODO: customize description, OG image, and locale for ${siteTitle}.
71
+ // The migration scaffold leaves a generic default so it never falls through;
72
+ // CMS \`Site.seo\` overrides this once block resolution kicks in.
73
+ const DEFAULT_DESCRIPTION = "${siteTitle}";
72
74
 
73
75
  export const Route = createRootRoute({
74
76
  head: () => ({
@@ -108,11 +110,14 @@ function generateIndex(ctx: MigrationContext, siteTitle: string): string {
108
110
  import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
109
111
  import { DecoPageRenderer } from "@decocms/start/hooks";
110
112
 
113
+ // MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
114
+ // copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
115
+ // resolution kicks in, so leaving the migration scaffold defaults is safe
116
+ // but visible in pre-block-resolution states.
111
117
  export const Route = createFileRoute("/")({
112
118
  ...cmsHomeRouteConfig({
113
- defaultTitle: "${siteTitle} - Tudo para sua casa",
114
- defaultDescription:
115
- "${siteTitle} - Tudo para sua casa com os melhores preços.",
119
+ defaultTitle: "${siteTitle}",
120
+ defaultDescription: "${siteTitle}",
116
121
  siteName: "${siteTitle}",
117
122
  }),
118
123
  component: HomePage,
@@ -126,8 +131,7 @@ function HomePage() {
126
131
  <div className="min-h-screen flex items-center justify-center">
127
132
  <div className="text-center">
128
133
  <h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
129
- <p className="text-lg text-base-content/60">Tudo para sua casa</p>
130
- <p className="text-sm text-base-content/40 mt-2">Nenhuma página CMS encontrada para /</p>
134
+ <p className="text-sm text-base-content/40 mt-2">No CMS page registered for /</p>
131
135
  </div>
132
136
  </div>
133
137
  );
@@ -152,11 +156,12 @@ function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
152
156
  import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
153
157
  import { DecoPageRenderer } from "@decocms/start/hooks";
154
158
 
159
+ // MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
160
+ // (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
155
161
  const routeConfig = cmsRouteConfig({
156
162
  siteName: "${siteTitle}",
157
- defaultTitle: "${siteTitle} - Tudo para sua casa",
158
- defaultDescription:
159
- "${siteTitle} - Tudo para sua casa com os melhores preços.",
163
+ defaultTitle: "${siteTitle}",
164
+ defaultDescription: "${siteTitle}",
160
165
  ignoreSearchParams: ["skuId"],
161
166
  });
162
167
 
@@ -216,50 +216,20 @@ declare module "@tanstack/react-router" {
216
216
 
217
217
  function generateRuntime(): string {
218
218
  return `/**
219
- * Runtime invoke proxy.
219
+ * Runtime invoke proxy — re-exports the framework canonical from @decocms/start/sdk.
220
220
  *
221
- * Turns nested property access into a typed RPC call to /deco/invoke.
222
- * Converts dot-notation paths to slash-separated keys:
223
- * invoke.vtex.loaders.productList(props)
224
- * POST /deco/invoke/vtex/loaders/productList
221
+ * The implementation (typed RPC over /deco/invoke, dotted-path proxy, .ts
222
+ * suffix fallback) lives in @decocms/start/sdk/invoke. This file exists so
223
+ * existing site code can keep \`import { invoke } from "~/runtime"\` and
224
+ * \`Runtime.invoke\` shapes without churn.
225
225
  *
226
- * The .ts suffix variant is also tried if the primary key isn't found
227
- * (registered loaders may have ".ts" extensions in their keys).
226
+ * Don't reimplement here extend @decocms/start/sdk/invoke instead.
228
227
  */
229
- function createNestedInvokeProxy(path: string[] = []): any {
230
- return new Proxy(
231
- Object.assign(async (props: any) => {
232
- const key = path.join("/");
233
- for (const k of [key, \`\${key}.ts\`]) {
234
- const response = await fetch(\`/deco/invoke/\${k}\`, {
235
- method: "POST",
236
- headers: { "Content-Type": "application/json" },
237
- body: JSON.stringify(props ?? {}),
238
- });
239
- if (response.status === 404) continue;
240
- if (!response.ok) {
241
- throw new Error(\`invoke(\${k}) failed: \${response.status}\`);
242
- }
243
- return response.json();
244
- }
245
- throw new Error(\`invoke(\${key}) failed: handler not found\`);
246
- }, {}),
247
- {
248
- get(_target: any, prop: string) {
249
- if (prop === "then" || prop === "catch" || prop === "finally") {
250
- return undefined;
251
- }
252
- return createNestedInvokeProxy([...path, prop]);
253
- },
254
- },
255
- );
256
- }
228
+ import { invoke } from "@decocms/start/sdk";
257
229
 
258
- export const invoke = createNestedInvokeProxy() as any;
230
+ export { invoke };
259
231
 
260
- export const Runtime = {
261
- invoke,
262
- };
232
+ export const Runtime = { invoke };
263
233
  `;
264
234
  }
265
235
 
@@ -299,11 +269,8 @@ export const invoke = {} as const;
299
269
  * auto-generated in invoke.gen.ts. Run \`npm run generate:invoke\` to update.
300
270
  */
301
271
  import { createServerFn } from "@tanstack/react-start";
302
- import {
303
- getRequestHeader,
304
- getResponseHeaders,
305
- setResponseHeader,
306
- } from "@tanstack/react-start/server";
272
+ import { getRequestHeader } from "@tanstack/react-start/server";
273
+ import { forwardResponseCookies } from "@decocms/start/sdk/cookiePassthrough";
307
274
  import { vtexActions } from "./invoke.gen";
308
275
  ${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";\n` : ""}import {
309
276
  extractVtexCookiesFromHeader,
@@ -314,15 +281,6 @@ ${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";
314
281
 
315
282
  export type { OrderForm } from "./invoke.gen";
316
283
 
317
- function mergeSetCookies(newCookies: string[]): void {
318
- if (newCookies.length === 0) return;
319
- const existing: string[] =
320
- typeof getResponseHeaders().getSetCookie === "function"
321
- ? getResponseHeaders().getSetCookie()
322
- : [];
323
- setResponseHeader("set-cookie", [...existing, ...newCookies]);
324
- }
325
-
326
284
  function getVtexCookies(): string {
327
285
  return extractVtexCookiesFromHeader(getRequestHeader("cookie") ?? "");
328
286
  }
@@ -336,7 +294,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
336
294
  } as any);
337
295
  if (result instanceof Response) {
338
296
  const setCookies = result.headers.getSetCookie?.() ?? [];
339
- mergeSetCookies(stripCookieDomain(setCookies));
297
+ forwardResponseCookies(stripCookieDomain(setCookies));
340
298
  return result.json();
341
299
  }
342
300
  return result;
@@ -344,7 +302,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
344
302
  ` : ""}const _logout = createServerFn({ method: "POST" }).handler(
345
303
  async (): Promise<{ success: boolean }> => {
346
304
  const { setCookies } = await performVtexLogout(getVtexCookies());
347
- mergeSetCookies(setCookies);
305
+ forwardResponseCookies(setCookies);
348
306
  return { success: true };
349
307
  },
350
308
  );
@@ -53,41 +53,6 @@ export default defineConfig({
53
53
  }),
54
54
  tailwindcss(),
55
55
  decoVitePlugin(),
56
- {
57
- name: "site-manual-chunks",
58
- config(_cfg, { command }) {
59
- if (command !== "build") return;
60
- return {
61
- build: {
62
- rollupOptions: {
63
- output: {
64
- manualChunks(id: string) {
65
- if (id.includes("node_modules/react-dom") || id.includes("node_modules/react/"))
66
- return "vendor-react";
67
- if (id.includes("@tanstack/react-router") || id.includes("@tanstack/start"))
68
- return "vendor-router";
69
- if (id.includes("@tanstack/react-query")) return "vendor-query";
70
- },
71
- },
72
- },
73
- },
74
- };
75
- },
76
- },
77
- {
78
- name: "deco-stub-meta-gen",
79
- enforce: "pre" as const,
80
- resolveId(id, importer, options) {
81
- if (!options?.ssr && importer && id.includes("meta.gen")) {
82
- return "\\0stub:meta-gen";
83
- }
84
- },
85
- load(id) {
86
- if (id === "\\0stub:meta-gen") {
87
- return "export default {};";
88
- }
89
- },
90
- },
91
56
  ],
92
57
  build: {
93
58
  sourcemap: "hidden",