@decocms/start 0.38.0 → 0.40.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.
- package/.agents/skills/deco-migrate-script/SKILL.md +434 -0
- package/.agents/skills/deco-to-tanstack-migration/SKILL.md +382 -0
- package/.agents/skills/deco-to-tanstack-migration/references/admin-cms.md +154 -0
- package/{.cursor/skills/deco-async-rendering-site-guide/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/async-rendering.md} +296 -31
- package/.agents/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.agents/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.agents/skills/deco-to-tanstack-migration/references/css-styling.md +156 -0
- package/.agents/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.agents/skills/deco-to-tanstack-migration/references/gotchas.md +13 -0
- package/{.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/hydration-fixes.md} +139 -4
- package/.agents/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/{.cursor/skills/deco-islands-migration/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/islands.md} +0 -14
- package/.agents/skills/deco-to-tanstack-migration/references/jsx-migration.md +80 -0
- package/.agents/skills/deco-to-tanstack-migration/references/matchers.md +1064 -0
- package/{.cursor/skills/deco-tanstack-navigation/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/navigation.md} +1 -16
- package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.agents/skills/deco-to-tanstack-migration/references/react-hooks-patterns.md +142 -0
- package/.agents/skills/deco-to-tanstack-migration/references/react-signals-state.md +72 -0
- package/{.cursor/skills/deco-tanstack-search/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/search.md} +1 -13
- package/.agents/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/{.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md → .agents/skills/deco-to-tanstack-migration/references/storefront-patterns.md} +1 -137
- package/.agents/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.agents/skills/deco-to-tanstack-migration/references/vtex-commerce.md +165 -0
- package/.agents/skills/deco-to-tanstack-migration/references/worker-cloudflare.md +209 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.agents/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/README.md +45 -0
- package/package.json +2 -1
- package/src/admin/index.ts +2 -0
- package/src/admin/invoke.ts +53 -5
- package/src/admin/setup.ts +7 -1
- package/src/apps/autoconfig.ts +50 -72
- package/src/sdk/invoke.ts +123 -12
- package/src/sdk/requestContext.ts +42 -0
- package/src/sdk/setupApps.ts +211 -0
- package/src/sdk/workerEntry.ts +6 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +0 -270
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Commerce & Widget Types Migration
|
|
2
|
+
|
|
3
|
+
## Commerce Types
|
|
4
|
+
|
|
5
|
+
Commerce types live in `@decocms/apps/commerce/types`. Import directly:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import type { Product, AnalyticsItem, BreadcrumbList } from "@decocms/apps/commerce/types";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Key types available: `Product`, `ProductGroup`, `ProductListingPage`, `ProductDetailsPage`, `Offer`, `AggregateOffer`, `UnitPriceSpecification`, `ImageObject`, `PropertyValue`, `BreadcrumbList`, `SiteNavigationElement`, `Brand`, `Review`, `AggregateRating`, `Filter`, `FilterToggle`, `FilterRange`, `SortOption`, `PageInfo`, `Suggestion`, `Search`, `AnalyticsItem`, `AddToCartParams`.
|
|
12
|
+
|
|
13
|
+
Replace old imports:
|
|
14
|
+
```bash
|
|
15
|
+
sed -i '' 's|from "apps/commerce/types.ts"|from "@decocms/apps/commerce/types"|g'
|
|
16
|
+
sed -i '' 's|from "~/types/commerce"|from "@decocms/apps/commerce/types"|g'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commerce Utilities
|
|
20
|
+
|
|
21
|
+
Also from `@decocms/apps`:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { mapProductToAnalyticsItem } from "@decocms/apps/commerce/utils/productToAnalyticsItem";
|
|
25
|
+
import { parseRange, formatRange } from "@decocms/apps/commerce/utils/filters";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Replace old imports:
|
|
29
|
+
```bash
|
|
30
|
+
sed -i '' 's|from "apps/commerce/utils/productToAnalyticsItem.ts"|from "@decocms/apps/commerce/utils/productToAnalyticsItem"|g'
|
|
31
|
+
sed -i '' 's|from "apps/commerce/utils/filters.ts"|from "@decocms/apps/commerce/utils/filters"|g'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Widget Types
|
|
35
|
+
|
|
36
|
+
CMS widget types are site-local since they're just string aliases. Create `~/types/widgets.ts`:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
export type ImageWidget = string;
|
|
40
|
+
export type HTMLWidget = string;
|
|
41
|
+
export type VideoWidget = string;
|
|
42
|
+
export type TextWidget = string;
|
|
43
|
+
export type RichText = string;
|
|
44
|
+
export type Secret = string;
|
|
45
|
+
export type Color = string;
|
|
46
|
+
export type ButtonWidget = string;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Replace:
|
|
50
|
+
```bash
|
|
51
|
+
sed -i '' 's|from "apps/admin/widgets.ts"|from "~/types/widgets"|g'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## UI Components (Site-Local)
|
|
55
|
+
|
|
56
|
+
Image, Picture, Seo, Theme etc. are **site-local components** -- they do NOT belong in `@decocms/apps`.
|
|
57
|
+
|
|
58
|
+
Create these in `~/components/ui/`:
|
|
59
|
+
- `Image.tsx` - thin `<img>` wrapper (accepts `preload`, `fit` props for API compat)
|
|
60
|
+
- `Picture.tsx` - `<picture>` + `<Source>` wrapper
|
|
61
|
+
- `Seo.tsx` - head meta tags (stub or real implementation)
|
|
62
|
+
- `Theme.tsx` - CSS variable injection (stub or real)
|
|
63
|
+
- `PoweredByDeco.tsx` - footer badge
|
|
64
|
+
- `Video.tsx` - `<video>` wrapper
|
|
65
|
+
- `SeoPreview.tsx` - admin SEO preview (stub)
|
|
66
|
+
|
|
67
|
+
Replace:
|
|
68
|
+
```bash
|
|
69
|
+
sed -i '' 's|from "apps/website/components/Image.tsx"|from "~/components/ui/Image"|g'
|
|
70
|
+
sed -i '' 's|from "apps/website/components/Picture.tsx"|from "~/components/ui/Picture"|g'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Verification
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
grep -r 'from "apps/' src/ --include='*.ts' --include='*.tsx'
|
|
77
|
+
# Should return ZERO matches
|
|
78
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# CSS / Tailwind v4 / DaisyUI Gotchas
|
|
2
|
+
|
|
3
|
+
> oklch triplets, logical properties, DaisyUI collapse, theme prefixes, sidebar.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
## 15. DaisyUI v4 Theme in Preview Shell
|
|
7
|
+
|
|
8
|
+
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.
|
|
9
|
+
|
|
10
|
+
**Symptom**: Preview in admin shows default/missing colors while production looks correct.
|
|
11
|
+
|
|
12
|
+
**Fix**: Configure the preview shell in `setup.ts`:
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
setRenderShell({
|
|
16
|
+
css: appCss,
|
|
17
|
+
fonts: [...],
|
|
18
|
+
theme: "light", // adds data-theme="light" to <html>
|
|
19
|
+
bodyClass: "bg-base-100 text-base-content",
|
|
20
|
+
lang: "pt-BR",
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The production HTML has `<html lang="pt-BR" data-theme="light">` set by the TanStack root layout. The preview shell must replicate this.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## 17. SiteTheme is a Stub
|
|
28
|
+
|
|
29
|
+
`Theme.tsx` returns `null`. Colors come from CSS at build time, not CMS at runtime.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## 31. CSS Theme Class Prefixes Must Not Be Renamed
|
|
33
|
+
|
|
34
|
+
**Severity**: HIGH — breaks all theme colors
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
**Fix**: Only change what React strictly requires: `class` → `className`, `for` → `htmlFor`. Preserve all original CSS class names exactly.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## 37. DaisyUI v4 Collapse Broken with Tailwind v4
|
|
42
|
+
|
|
43
|
+
**Severity**: MEDIUM — filter sidebars, FAQ accordions, any collapsible section renders collapsed
|
|
44
|
+
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
**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.
|
|
48
|
+
|
|
49
|
+
**Fix**: Replace DaisyUI collapse with native `<details>/<summary>` HTML elements:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Before: DaisyUI collapse with hidden checkbox
|
|
53
|
+
<div className="collapse">
|
|
54
|
+
<input type="checkbox" defaultChecked />
|
|
55
|
+
<div className="collapse-title">Category</div>
|
|
56
|
+
<div className="collapse-content">...filters...</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
// After: Native HTML, works everywhere
|
|
60
|
+
<details open className="group">
|
|
61
|
+
<summary className="cursor-pointer font-semibold">Category</summary>
|
|
62
|
+
<div className="mt-2">...filters...</div>
|
|
63
|
+
</details>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## 40. Filter Sidebar Invisible Due to Background Color Match
|
|
68
|
+
|
|
69
|
+
**Severity**: LOW — cosmetic, but confusing during development
|
|
70
|
+
|
|
71
|
+
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`).
|
|
72
|
+
|
|
73
|
+
**Symptom**: Filters appear "non-existent" even though they're in the DOM. Filter links are accessible but invisible.
|
|
74
|
+
|
|
75
|
+
**Fix**: Add a contrasting background to the filter aside:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
<aside className="... bg-white rounded-lg p-4">
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
## 42. Tailwind v4 Logical vs Physical Property Cascade Conflict
|
|
83
|
+
|
|
84
|
+
**Severity**: CRITICAL — causes container width mismatches across the entire site
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
**Symptom**: Containers are narrower or have asymmetric padding compared to production. The layout "looks off" at certain breakpoints but works at others.
|
|
89
|
+
|
|
90
|
+
**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.
|
|
91
|
+
|
|
92
|
+
**Example**:
|
|
93
|
+
```html
|
|
94
|
+
<!-- This pattern exists in many Deco storefronts -->
|
|
95
|
+
<div class="pl-4 sm:pl-0 md:px-6 xl-b:px-0 max-w-[1280px] mx-auto">
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
In Tailwind v3: at `md` viewport, `px-6` sets `padding-left: 1.5rem` and `padding-right: 1.5rem`, cleanly overriding `sm:pl-0`.
|
|
99
|
+
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
**Fix**: Replace mixed shorthand + longhand patterns with consistent longhand properties:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
md:px-6 xl-b:px-0 → md:pl-6 md:pr-6 xl-b:pl-0 xl-b:pr-0
|
|
106
|
+
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
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Detection**: Find all elements with mixed patterns:
|
|
110
|
+
```bash
|
|
111
|
+
grep -rn 'px-[0-9].*pl-\|pl-.*px-[0-9]\|px-[0-9].*pr-\|pr-.*px-[0-9]' src/ --include='*.tsx'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
Also check for the same issue with `mx-*` mixed with `ml-*`/`mr-*`, and `my-*` mixed with `mt-*`/`mb-*`.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## 43. CSS oklch() Color Variables Must Store Triplets, Not Hex
|
|
120
|
+
|
|
121
|
+
**Severity**: HIGH — all SVG icons render as black, brand colors break
|
|
122
|
+
|
|
123
|
+
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.
|
|
124
|
+
|
|
125
|
+
**Symptom**: Slider arrows, footer icons, search icons, filter icons — anything using `oklch(var(--...))` — renders as black circles/shapes instead of the brand colors.
|
|
126
|
+
|
|
127
|
+
**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.
|
|
128
|
+
|
|
129
|
+
**Fix**: Convert all theme CSS variables from hex to oklch triplets:
|
|
130
|
+
```css
|
|
131
|
+
/* WRONG — invalid CSS when used as oklch(var(--bg-seasonal-2)) */
|
|
132
|
+
--bg-seasonal-2: #FFF;
|
|
133
|
+
|
|
134
|
+
/* CORRECT — oklch(100% 0.00 0deg) is valid */
|
|
135
|
+
--bg-seasonal-2: 100% 0.00 0deg;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Dual-usage caveat**: Variables used BOTH inside `oklch()` wrappers AND directly in CSS properties need different handling:
|
|
139
|
+
|
|
140
|
+
```css
|
|
141
|
+
/* @theme entries for Tailwind utilities — need oklch() wrapper */
|
|
142
|
+
--color-bg-seasonal-1: oklch(var(--bg-seasonal-1));
|
|
143
|
+
|
|
144
|
+
/* Direct CSS usage — also needs oklch() wrapper */
|
|
145
|
+
background-color: oklch(var(--bg-seasonal-1));
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
**Python conversion helper**:
|
|
151
|
+
```python
|
|
152
|
+
from colorjs import Color
|
|
153
|
+
c = Color("#EE4F31")
|
|
154
|
+
l, c_val, h = c.convert("oklch").coords()
|
|
155
|
+
print(f"{l*100:.2f}% {c_val:.2f} {h:.0f}deg") # 64.42% 0.20 33deg
|
|
156
|
+
```
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Deco Framework Import Elimination
|
|
2
|
+
|
|
3
|
+
Replace all `@deco/deco/*` and `$fresh/*` imports with inline equivalents. No shim files needed.
|
|
4
|
+
|
|
5
|
+
## $fresh/runtime.ts
|
|
6
|
+
|
|
7
|
+
### asset(url)
|
|
8
|
+
|
|
9
|
+
The `asset()` function in Fresh prepends the build hash path. In Vite, static assets are handled automatically. Just remove the wrapper:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// OLD
|
|
13
|
+
import { asset } from "$fresh/runtime.ts";
|
|
14
|
+
<img src={asset("/sprites.svg")} />
|
|
15
|
+
|
|
16
|
+
// NEW
|
|
17
|
+
<img src="/sprites.svg" />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### IS_BROWSER
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// OLD
|
|
24
|
+
import { IS_BROWSER } from "$fresh/runtime.ts";
|
|
25
|
+
|
|
26
|
+
// NEW (inline)
|
|
27
|
+
const IS_BROWSER = typeof document !== "undefined";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## @deco/deco (bare import)
|
|
31
|
+
|
|
32
|
+
### SectionProps
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// OLD
|
|
36
|
+
import type { SectionProps } from "@deco/deco";
|
|
37
|
+
type Props = SectionProps<typeof loader>;
|
|
38
|
+
|
|
39
|
+
// NEW (inline type)
|
|
40
|
+
type SectionProps<T extends (...args: any[]) => any> = ReturnType<T>;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Resolved
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// OLD
|
|
47
|
+
import type { Resolved } from "@deco/deco";
|
|
48
|
+
|
|
49
|
+
// NEW
|
|
50
|
+
type Resolved<T = any> = T;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### context
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// OLD
|
|
57
|
+
import { context } from "@deco/deco";
|
|
58
|
+
if (context.isDeploy) { ... }
|
|
59
|
+
|
|
60
|
+
// NEW
|
|
61
|
+
const context = { isDeploy: false, platform: "tanstack-start", site: "my-store", siteId: 0 };
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## @deco/deco/blocks
|
|
65
|
+
|
|
66
|
+
### Section, Block
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// OLD
|
|
70
|
+
import type { Section } from "@deco/deco/blocks";
|
|
71
|
+
|
|
72
|
+
// NEW
|
|
73
|
+
type Section = any;
|
|
74
|
+
type Block = any;
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
These were used for Deco CMS section composition. In TanStack Start, sections are just React components.
|
|
78
|
+
|
|
79
|
+
## @deco/deco/hooks
|
|
80
|
+
|
|
81
|
+
### useScript / useScriptAsDataURI
|
|
82
|
+
|
|
83
|
+
These serialize a function into an inline `<script>` string. Create `~/sdk/useScript.ts`:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
export function useScript(fn: (...args: any[]) => void, ...args: any[]): string {
|
|
87
|
+
const serializedArgs = args.map((a) => JSON.stringify(a)).join(",");
|
|
88
|
+
return `(${fn.toString()})(${serializedArgs})`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function useScriptAsDataURI(fn: (...args: any[]) => void, ...args: any[]): string {
|
|
92
|
+
const code = useScript(fn, ...args);
|
|
93
|
+
return `data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### usePartialSection
|
|
98
|
+
|
|
99
|
+
Deco partial sections don't apply in TanStack Start. Stub:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
export function usePartialSection(props?: Record<string, unknown>) {
|
|
103
|
+
return props || {};
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## @deco/deco/o11y
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// OLD
|
|
111
|
+
import { logger } from "@deco/deco/o11y";
|
|
112
|
+
logger.error("failed", err);
|
|
113
|
+
|
|
114
|
+
// NEW
|
|
115
|
+
console.error("failed", err);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Automation
|
|
119
|
+
|
|
120
|
+
All of these are safe for bulk sed (each import pattern maps to exactly one replacement).
|
|
121
|
+
|
|
122
|
+
## Verification
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
grep -r '@deco/deco' src/ --include='*.ts' --include='*.tsx'
|
|
126
|
+
grep -r '\$fresh/' src/ --include='*.ts' --include='*.tsx'
|
|
127
|
+
# Both should return ZERO matches
|
|
128
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Gotchas Index
|
|
2
|
+
|
|
3
|
+
This file is an index. Each topic has its own focused file.
|
|
4
|
+
|
|
5
|
+
| File | Topic | Key Gotchas |
|
|
6
|
+
|------|-------|-------------|
|
|
7
|
+
| [react-hooks-patterns.md](react-hooks-patterns.md) | useEffect, useQuery, useMemo, lazy init | #2, #33, #46, #47 |
|
|
8
|
+
| [react-signals-state.md](react-signals-state.md) | TanStack Store, signal.value, subscribe() | #3, #19, #38 |
|
|
9
|
+
| [jsx-migration.md](jsx-migration.md) | Preact→React JSX differences | #4–6, #11, #20–22, #41 |
|
|
10
|
+
| [vtex-commerce.md](vtex-commerce.md) | VTEX loaders, cart, facets, price specs | #1, #7–8, #32, #34–36, #39 |
|
|
11
|
+
| [worker-cloudflare.md](worker-cloudflare.md) | Worker entry, build, Cloudflare, npm | #9–10, #12–14, #19, #24–28, #30, #44–45 |
|
|
12
|
+
| [css-styling.md](css-styling.md) | Tailwind v4, oklch, DaisyUI | #15, #17, #31, #37, #40, #42–43 |
|
|
13
|
+
| [admin-cms.md](admin-cms.md) | Admin routes, schema, device context | #16, #18, #23, #26, #29 |
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: deco-tanstack-hydration-fixes
|
|
3
|
-
description: Fix hydration mismatches, flash-of-white, CLS from third-party scripts, scroll-to-top bugs, and React DOM warnings in Deco storefronts on TanStack Start/React/Cloudflare Workers. Use when debugging hydration errors, page flicker during navigation, blank screen on F5, layout shifts from external scripts, or React console warnings about invalid DOM properties.
|
|
4
|
-
---
|
|
5
1
|
|
|
6
2
|
# Hydration & Navigation Fixes for Deco TanStack Storefronts
|
|
7
3
|
|
|
@@ -466,3 +462,142 @@ When investigating hydration/flash issues on a Deco TanStack storefront:
|
|
|
466
462
|
8. **Test with F5 (hard reload)** — SPA navigation may hide issues that appear on cold load
|
|
467
463
|
9. **Check scroll behavior** — navigate from shelf to PDP; verify page scrolls to top
|
|
468
464
|
10. **Compare SSR HTML vs client render** — view source (`Ctrl+U`) and compare with inspected DOM to find hydration diffs
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 13. `useDevice()` Hydration Mismatch in Eager Sections
|
|
469
|
+
|
|
470
|
+
### Root Cause
|
|
471
|
+
|
|
472
|
+
`@decocms/start` shell-renders sections in a **separate React root** that does NOT include providers from `__root.tsx`. This means `useDevice()` — which reads from `Device.Provider` — falls back to the context default (`isMobile: true`) on the server, while the client goes through `__root.tsx` and gets whatever value the Provider has.
|
|
473
|
+
|
|
474
|
+
Result: server renders mobile layout, client renders desktop layout → structural hydration mismatch.
|
|
475
|
+
|
|
476
|
+
This affects ONLY **eager sections** (sections in `alwaysEager` or not wrapped in CMS `Lazy.tsx`). Deferred sections render a skeleton server-side, and the real component loads client-side AFTER hydration — no mismatch.
|
|
477
|
+
|
|
478
|
+
### Which sections are affected
|
|
479
|
+
|
|
480
|
+
Check `alwaysEager` in `setup.ts`:
|
|
481
|
+
```typescript
|
|
482
|
+
setAsyncRenderingConfig({
|
|
483
|
+
alwaysEager: [
|
|
484
|
+
"site/sections/Header/Header.tsx",
|
|
485
|
+
"site/sections/Header/NewHeader.tsx",
|
|
486
|
+
"site/sections/Footer/Footer.tsx",
|
|
487
|
+
"site/sections/Theme/Theme.tsx",
|
|
488
|
+
"site/sections/Images/Carousel.tsx",
|
|
489
|
+
"site/sections/Tipbar.tsx",
|
|
490
|
+
],
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Any of these that call `useDevice()` and render different HTML structure based on `isMobile` will have a hydration error.
|
|
495
|
+
|
|
496
|
+
`LoadingFallback` components are also server-rendered (they're the skeleton while deferred section loads). LoadingFallbacks that use `useDevice()` to change the count or structure of elements will cause hydration mismatches on the skeleton itself.
|
|
497
|
+
|
|
498
|
+
### Fix Pattern A: Use `device` prop from loader (structural branches)
|
|
499
|
+
|
|
500
|
+
For sections that already receive `device` from their loader (`ctx.device`), use the prop instead of the hook:
|
|
501
|
+
|
|
502
|
+
```typescript
|
|
503
|
+
// Before — useDevice() reads from context, missing during shell render
|
|
504
|
+
function Header({ device, ...props }: Props) {
|
|
505
|
+
const { isMobile } = useDevice(); // ← wrong: context not available in shell render
|
|
506
|
+
if (isMobile) return <MobileLayout />;
|
|
507
|
+
return <DesktopLayout />;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// After — device prop comes from loaderData, consistent on server + client
|
|
511
|
+
function Header({ device, ...props }: Props) {
|
|
512
|
+
const isMobile = device === "mobile" || device === "tablet"; // ← correct
|
|
513
|
+
if (isMobile) return <MobileLayout />;
|
|
514
|
+
return <DesktopLayout />;
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The `device` prop comes from the section loader:
|
|
519
|
+
```typescript
|
|
520
|
+
export async function loader(props: Props, req: Request, ctx: AppContext) {
|
|
521
|
+
return {
|
|
522
|
+
...props,
|
|
523
|
+
device: ctx.device as "mobile" | "desktop" | "tablet",
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
`ctx.device` is detected from the request `User-Agent` header server-side. TanStack Start serializes loaderData and sends it to the client, so both server and client always use the same value. No mismatch.
|
|
529
|
+
|
|
530
|
+
Also fix the section's `LoadingFallback` to use `props.device` instead of `useDevice()`:
|
|
531
|
+
```tsx
|
|
532
|
+
// Before
|
|
533
|
+
export const LoadingFallback = (props: LoadingFallbackProps & Props) => {
|
|
534
|
+
const device = useDevice();
|
|
535
|
+
const deviceProp = device.isMobile ? "mobile" : "desktop";
|
|
536
|
+
return <Header {...props} device={deviceProp} />;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// After
|
|
540
|
+
export const LoadingFallback = (props: LoadingFallbackProps & Props) => {
|
|
541
|
+
return <Header {...props} device={props.device ?? "desktop"} />;
|
|
542
|
+
};
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### Fix Pattern B: Remove redundant JS conditionals (show/hide with CSS)
|
|
546
|
+
|
|
547
|
+
When a section renders show/hide elements based on `isMobile` but the containing elements ALREADY have responsive CSS classes (`hidden lg:block`, `hidden lg:flex`, `lg:hidden`), the JS conditional is redundant and causes the mismatch. Simply remove it:
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
// Before — JS conditional + CSS class (redundant, causes mismatch)
|
|
551
|
+
{!isMobile && (
|
|
552
|
+
<div className="hidden lg:flex">...</div>
|
|
553
|
+
)}
|
|
554
|
+
|
|
555
|
+
// After — CSS alone handles responsive behavior
|
|
556
|
+
<div className="hidden lg:flex">...</div>
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
This is the correct fix for Footer and similar layout sections where mobile/desktop differences are already handled by Tailwind responsive prefixes.
|
|
560
|
+
|
|
561
|
+
### Fix Pattern C: CSS-only responsive sizing (LoadingFallback skeletons)
|
|
562
|
+
|
|
563
|
+
For `LoadingFallback` components that use `useDevice()` to vary sizes or counts, replace JS with responsive Tailwind classes:
|
|
564
|
+
|
|
565
|
+
```tsx
|
|
566
|
+
// Before — causes hydration mismatch on skeleton
|
|
567
|
+
function LoadingCard() {
|
|
568
|
+
const device = useDevice();
|
|
569
|
+
return (
|
|
570
|
+
<div className={`max-w-[${device.isMobile ? "160px" : "320px"}]`}>
|
|
571
|
+
<div className={`h-${device.isMobile ? "40" : "60"}`} />
|
|
572
|
+
</div>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// After — CSS handles responsive sizing, no JS needed
|
|
577
|
+
function LoadingCard({ className }: { className?: string }) {
|
|
578
|
+
return (
|
|
579
|
+
<div className={`max-w-[160px] sm:max-w-[320px] ${className ?? ""}`}>
|
|
580
|
+
<div className="h-40 sm:h-60" />
|
|
581
|
+
</div>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// For count variations in LoadingFallback:
|
|
586
|
+
// Before (renders 2 on mobile / 4 on desktop)
|
|
587
|
+
{Array(device.isMobile ? 2 : 4).fill(0).map((_, i) => <LoadingCard key={i} />)}
|
|
588
|
+
|
|
589
|
+
// After (always renders 4, CSS hides extras on mobile)
|
|
590
|
+
<LoadingCard />
|
|
591
|
+
<LoadingCard />
|
|
592
|
+
<LoadingCard className="hidden sm:flex" />
|
|
593
|
+
<LoadingCard className="hidden sm:flex" />
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Quick diagnosis
|
|
597
|
+
|
|
598
|
+
Search for `useDevice` in eager sections to find candidates:
|
|
599
|
+
```bash
|
|
600
|
+
rg "useDevice" src/sections/ src/components/header/ src/components/footer/ --glob "*.tsx"
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
Any result in a component that's always-eager and renders different element types/counts based on `isMobile` needs one of the fix patterns above.
|
|
@@ -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
|
+
```
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: deco-islands-migration
|
|
3
|
-
description: Eliminate the src/islands/ directory when migrating Deco storefronts from Fresh/Preact to TanStack Start/React. Explains why islands don't make sense in the new stack, the problems they cause, how to systematically repoint or move them, and how to fix vanilla-JS DOM patterns that break React hydration. Use when auditing or removing islands from a migrated storefront.
|
|
4
|
-
---
|
|
5
1
|
|
|
6
2
|
# Deco Islands Migration — From Fresh Islands to React Components
|
|
7
3
|
|
|
@@ -239,13 +235,3 @@ function Drawer({ class: classProp = "", className = "" }: Props) {
|
|
|
239
235
|
- [ ] Build succeeds (`npm run build` / `bun run build`)
|
|
240
236
|
- [ ] Dev server starts without errors
|
|
241
237
|
- [ ] Interactive elements work: add to cart, sliders, drawers, modals
|
|
242
|
-
|
|
243
|
-
## Related Skills
|
|
244
|
-
|
|
245
|
-
| Skill | Purpose |
|
|
246
|
-
|-------|---------|
|
|
247
|
-
| `deco-to-tanstack-migration` | Full migration playbook (imports, signals, architecture) |
|
|
248
|
-
| `deco-tanstack-navigation` | SPA navigation patterns (`<a>` → `<Link>`, `useNavigate`, `loaderDeps`, forms) |
|
|
249
|
-
| `deco-tanstack-storefront-patterns` | Runtime fixes post-migration (nested sections, caching, SliderJS, async_hooks, cart, server functions) |
|
|
250
|
-
| `deco-storefront-test-checklist` | Context-aware QA checklist generation |
|
|
251
|
-
| `deco-typescript-fixes` | TypeScript error patterns and fixes |
|