@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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deco-variant-selection-perf
|
|
3
|
+
description: Optimize product variant selection in Deco TanStack storefronts. Eliminates server re-fetches when switching SKU variants of the same product using replaceState, adds loading states for cross-product variant navigation, and removes preload="intent" from variant links to prevent double-fetch. Use when variant changes are slow, HAR analysis shows duplicate loadCmsPage calls, or when implementing variant selectors in a PDP.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Product Variant Selection Performance
|
|
7
|
+
|
|
8
|
+
Patterns for making variant selection instant in Deco storefronts on TanStack Start. Discovered while optimizing `espacosmart-storefront` where clicking a variant triggered 2 full `loadCmsPage` server calls (1300ms+ each).
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Variant changes on PDP are slow (>500ms)
|
|
13
|
+
- HAR analysis shows duplicate `loadCmsPage` calls with/without `?skuId`
|
|
14
|
+
- `preload="intent"` on variant `<Link>` causes double-fetch
|
|
15
|
+
- Need to add loading feedback for cross-product variant navigation
|
|
16
|
+
- Implementing a new variant selector component
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Key Insight: Two Types of "Variant" Navigation
|
|
21
|
+
|
|
22
|
+
| Type | Example | Data needed | Approach |
|
|
23
|
+
|------|---------|-------------|----------|
|
|
24
|
+
| **Same product, different SKU** | Size 90x0.8 → 90x0.95 | Already loaded in `isVariantOf.hasVariant` | `replaceState` — zero fetch |
|
|
25
|
+
| **Different product** | Product A → Product B (visual variation) | New product data | `navigate()` — single fetch |
|
|
26
|
+
|
|
27
|
+
The CMS block "PDP Loader" does NOT pass `skuId` to the server loader. All SKU data comes in the first load via `isVariantOf.hasVariant`. The `?skuId` in the URL is purely for bookmarking/sharing.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Problem: Double-Fetch on Variant Click
|
|
32
|
+
|
|
33
|
+
### Root Cause
|
|
34
|
+
|
|
35
|
+
When using `<Link to="/slug/p?skuId=160" preload="intent">`:
|
|
36
|
+
|
|
37
|
+
1. **Hover** → TanStack Router fires a prefetch. The `loaderDeps` filters `skuId`, so it sends `loadCmsPage({ data: "/slug/p" })`.
|
|
38
|
+
2. **Click** → Router fires the real navigation. Depending on timing and staleTime, it may fire `loadCmsPage({ data: "/slug/p?skuId=160" })`.
|
|
39
|
+
|
|
40
|
+
Result: **2 server calls** for one variant click, each taking 1-2 seconds (full CMS resolution + section loaders + VTEX API calls).
|
|
41
|
+
|
|
42
|
+
### How to Diagnose
|
|
43
|
+
|
|
44
|
+
Export a HAR from Chrome DevTools (Network tab → Export HAR). Analyze:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Find all loadCmsPage calls
|
|
48
|
+
import json, urllib.parse, base64
|
|
49
|
+
with open('localhost.har') as f:
|
|
50
|
+
har = json.load(f)
|
|
51
|
+
for e in har['log']['entries']:
|
|
52
|
+
url = e['request']['url']
|
|
53
|
+
if '/_serverFn/' not in url:
|
|
54
|
+
continue
|
|
55
|
+
qs = url.split('?')[1] if '?' in url else ''
|
|
56
|
+
params = urllib.parse.parse_qs(qs)
|
|
57
|
+
if 'payload' in params:
|
|
58
|
+
payload = json.loads(urllib.parse.unquote(params['payload'][0]))
|
|
59
|
+
# Extract the "data" (path) from TanStack's serialized payload
|
|
60
|
+
print(f"{e.get('time',0):.0f}ms skuId={'skuId' in str(payload)}")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If you see two calls for the same slug (one with `skuId`, one without), this is the double-fetch.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Fix 1: Same-Product Variants — `replaceState` (Zero Fetch)
|
|
68
|
+
|
|
69
|
+
Replace `<Link>` with `<a>` + `window.history.replaceState`. This changes the URL for bookmarking without triggering any TanStack Router navigation or loader.
|
|
70
|
+
|
|
71
|
+
### Before (slow)
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { Link } from "@tanstack/react-router";
|
|
75
|
+
|
|
76
|
+
function VariantSelector({ product }: { product: Product }) {
|
|
77
|
+
const possibilities = useVariantPossibilities(hasVariant, product);
|
|
78
|
+
return (
|
|
79
|
+
// ...
|
|
80
|
+
<Link to={relativeLink} preload="intent">
|
|
81
|
+
<Avatar variant={relativeLink === relative(url) ? "active" : "default"} />
|
|
82
|
+
</Link>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### After (instant)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { useState, useCallback } from "react";
|
|
91
|
+
|
|
92
|
+
function VariantSelector({ product }: { product: Product }) {
|
|
93
|
+
const possibilities = useVariantPossibilities(hasVariant, product);
|
|
94
|
+
const [currentUrl, setCurrentUrl] = useState(() => relative(product.url));
|
|
95
|
+
|
|
96
|
+
const handleVariantClick = useCallback(
|
|
97
|
+
(e: React.MouseEvent<HTMLAnchorElement>, link: string) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
setCurrentUrl(link);
|
|
100
|
+
window.history.replaceState(null, "", link);
|
|
101
|
+
},
|
|
102
|
+
[],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
// ...
|
|
107
|
+
<a
|
|
108
|
+
href={relativeLink ?? "#"}
|
|
109
|
+
onClick={(e) => relativeLink && handleVariantClick(e, relativeLink)}
|
|
110
|
+
>
|
|
111
|
+
<Avatar
|
|
112
|
+
variant={relativeLink === currentUrl ? "active" : relativeLink ? "default" : "disabled"}
|
|
113
|
+
/>
|
|
114
|
+
</a>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Why This Works
|
|
120
|
+
|
|
121
|
+
1. `replaceState` changes browser URL without notifying TanStack Router → **zero loader execution**
|
|
122
|
+
2. `useState(currentUrl)` tracks the active variant for UI highlighting → **instant re-render**
|
|
123
|
+
3. `<a href>` preserves accessibility (right-click, ctrl+click open in new tab)
|
|
124
|
+
4. The CMS PDP Loader does NOT use `skuId` from the URL — all variant data is in `isVariantOf.hasVariant`
|
|
125
|
+
|
|
126
|
+
### When NOT to Use replaceState
|
|
127
|
+
|
|
128
|
+
- The variant changes the **product** (different `productGroupID`) — use `navigate()` instead
|
|
129
|
+
- The server loader actually reads `skuId` from the request URL to fetch different data
|
|
130
|
+
- SEO requires each variant to be a separate indexable page with unique server-rendered content
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Fix 2: Cross-Product Variants — `navigate()` with Loading
|
|
135
|
+
|
|
136
|
+
For `SkuVariation` (different products shown as visual variations), use `navigate()` but WITHOUT `preload="intent"` and WITH a loading state.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
140
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
141
|
+
|
|
142
|
+
export default function SkuVariation({ products }: { products: Product[] | null }) {
|
|
143
|
+
const [loadingIdx, setLoadingIdx] = useState<number | null>(null);
|
|
144
|
+
const navigate = useNavigate();
|
|
145
|
+
const prevProducts = useRef(products);
|
|
146
|
+
|
|
147
|
+
// Reset loading when products change (navigation completed, component reused)
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (prevProducts.current !== products) {
|
|
150
|
+
setLoadingIdx(null);
|
|
151
|
+
prevProducts.current = products;
|
|
152
|
+
}
|
|
153
|
+
}, [products]);
|
|
154
|
+
|
|
155
|
+
const handleClick = useCallback(
|
|
156
|
+
(e: React.MouseEvent<HTMLAnchorElement>, link: string, idx: number) => {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
setLoadingIdx(idx);
|
|
159
|
+
navigate({ to: link });
|
|
160
|
+
},
|
|
161
|
+
[navigate],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (!products?.length) return null;
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<ul>
|
|
168
|
+
{products.map((product, index) => {
|
|
169
|
+
const link = relative(product.url) ?? "#";
|
|
170
|
+
const isLoading = loadingIdx === index;
|
|
171
|
+
return (
|
|
172
|
+
<li key={index}>
|
|
173
|
+
<a href={link} onClick={(e) => handleClick(e, link, index)} className="relative">
|
|
174
|
+
<div className={`transition-opacity ${isLoading ? "opacity-30" : ""}`}>
|
|
175
|
+
<img src={product.image?.[0]?.url} width={35} height={35} />
|
|
176
|
+
</div>
|
|
177
|
+
{isLoading && (
|
|
178
|
+
<span className="loading loading-spinner loading-xs absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" />
|
|
179
|
+
)}
|
|
180
|
+
</a>
|
|
181
|
+
</li>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
</ul>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Key Details
|
|
190
|
+
|
|
191
|
+
| Aspect | Why |
|
|
192
|
+
|--------|-----|
|
|
193
|
+
| `preload={false}` / no preload | Prevents duplicate fetch (hover + click) |
|
|
194
|
+
| `useRef(prevProducts)` + `useEffect` | Resets loading when component reuses with new props |
|
|
195
|
+
| No `await navigate()` | `navigate` resolves when route renders — component may already be unmounted |
|
|
196
|
+
| `<a href>` not `<Link>` | Avoids TanStack Router's built-in prefetch behavior |
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Fix 3: Remove `preload="intent"` from All Variant Links
|
|
201
|
+
|
|
202
|
+
Any `<Link>` with `preload="intent"` in variant selectors causes prefetch on hover, leading to:
|
|
203
|
+
- Extra server calls
|
|
204
|
+
- Race conditions with the click navigation
|
|
205
|
+
- Wasted bandwidth
|
|
206
|
+
|
|
207
|
+
Replace with `preload={false}` or use `<a>` elements:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Find all variant links with preload="intent"
|
|
211
|
+
rg 'preload="intent"' src/components/product/ --glob '*.tsx' -l
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Files to check:
|
|
215
|
+
- `ProductVariantSelector.tsx`
|
|
216
|
+
- `SkuVariation.tsx`
|
|
217
|
+
- `ProductCardCategory.tsx` (variant selector in PLP cards)
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Verification
|
|
222
|
+
|
|
223
|
+
After applying fixes, export a new HAR and verify:
|
|
224
|
+
|
|
225
|
+
1. **Same-product variant click**: Zero `loadCmsPage` calls (only image requests)
|
|
226
|
+
2. **Cross-product variant click**: Exactly 1 `loadCmsPage` call per product
|
|
227
|
+
3. **No duplicate calls**: Each unique slug appears at most once
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
# Quick HAR verification
|
|
231
|
+
server_calls = [e for e in har['log']['entries'] if '/_serverFn/' in e['request']['url']]
|
|
232
|
+
print(f"Server calls: {len(server_calls)}")
|
|
233
|
+
# Should be 0 for same-product variants, N for N different products
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Related Configuration
|
|
239
|
+
|
|
240
|
+
### `ignoreSearchParams` in Route Config
|
|
241
|
+
|
|
242
|
+
The CMS catch-all route should filter `skuId` from `loaderDeps`:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const config = cmsRouteConfig({
|
|
246
|
+
siteName: "My Store",
|
|
247
|
+
defaultTitle: "My Store",
|
|
248
|
+
ignoreSearchParams: ["skuId"],
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
This prevents `skuId` changes from being treated as dependency changes by TanStack Router.
|
|
253
|
+
|
|
254
|
+
### `staleTime` in Dev Mode
|
|
255
|
+
|
|
256
|
+
With `staleTime: 0` (default in dev), even identical `loaderDeps` trigger re-fetch. Set a minimum staleTime in dev:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// In routeCacheDefaults()
|
|
260
|
+
if (isDev) return { staleTime: 5_000, gcTime: 30_000 };
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Related Skills
|
|
266
|
+
|
|
267
|
+
| Skill | Purpose |
|
|
268
|
+
|-------|---------|
|
|
269
|
+
| `deco-cms-layout-caching` | Cache layout sections (Header/Footer) to avoid redundant API calls |
|
|
270
|
+
| `deco-api-call-dedup` | In-flight deduplication for VTEX API calls |
|
|
271
|
+
| `deco-cms-route-config` | CMS route configuration in `@decocms/start` |
|
|
272
|
+
| `deco-tanstack-storefront-patterns` | General patterns for deco-start storefronts |
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deco-vtex-fetch-cache
|
|
3
|
+
description: SWR in-memory fetch cache for VTEX API responses in @decocms/apps. Ported from deco-cx/deco runtime/fetch/fetchCache.ts. Provides in-flight deduplication + stale-while-revalidate for all VTEX GET requests. Covers fetchWithCache utility, vtexCachedFetch client function, LRU eviction, TTL by HTTP status, integration with intelligentSearch and cross-selling calls. Use when adding caching to VTEX API calls, debugging stale responses, or understanding how the fetch cache layer works.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VTEX Fetch Cache (SWR In-Memory)
|
|
7
|
+
|
|
8
|
+
Server-side SWR cache for all VTEX GET API responses. Ported from `deco-cx/deco` `runtime/fetch/fetchCache.ts` and adapted for `@decocms/apps` on TanStack Start.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Adding SWR caching to new VTEX API calls
|
|
13
|
+
- Understanding why a VTEX response is served stale
|
|
14
|
+
- Debugging cache hit/miss behavior
|
|
15
|
+
- Tuning TTL for specific endpoints
|
|
16
|
+
- Understanding the relationship between `fetchWithCache`, `vtexCachedFetch`, and `createCachedLoader`
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Site Setup
|
|
24
|
+
└→ createCachedLoader (loader-level SWR, 30-120s TTL)
|
|
25
|
+
└→ vtexCachedFetch (HTTP-level SWR, 3min TTL)
|
|
26
|
+
└→ fetchWithCache (core cache engine)
|
|
27
|
+
└→ _fetch (instrumented fetch)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Layer | File | Scope | TTL |
|
|
31
|
+
|-------|------|-------|-----|
|
|
32
|
+
| `createCachedLoader` | `deco-start/src/sdk/cachedLoader.ts` | Loader result (parsed + transformed) | 30-120s per loader |
|
|
33
|
+
| `vtexCachedFetch` | `apps-start/vtex/client.ts` | Raw HTTP JSON response | 3 min (200), 10s (404) |
|
|
34
|
+
| `fetchWithCache` | `apps-start/vtex/utils/fetchCache.ts` | Core SWR + dedup engine | Status-based |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Core: `fetchWithCache` (`vtex/utils/fetchCache.ts`)
|
|
39
|
+
|
|
40
|
+
### Features
|
|
41
|
+
|
|
42
|
+
- **LRU eviction**: Max 500 entries, oldest evicted first
|
|
43
|
+
- **TTL by HTTP status**: 200-299 → 3 min, 404 → 10s, 500+ → never cached
|
|
44
|
+
- **In-flight deduplication**: Concurrent calls for the same URL share one Promise
|
|
45
|
+
- **Stale-while-revalidate**: Stale entries served immediately, refreshed in background
|
|
46
|
+
- **Custom TTL**: Override per-call via `opts.ttl`
|
|
47
|
+
|
|
48
|
+
### API
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { fetchWithCache, FetchCacheOptions } from "@decocms/apps/vtex/utils/fetchCache";
|
|
52
|
+
|
|
53
|
+
// Basic usage
|
|
54
|
+
const data = await fetchWithCache<ProductType>(
|
|
55
|
+
fullUrl, // Cache key (typically the full URL)
|
|
56
|
+
() => fetch(fullUrl, init), // Fetch callback (returns Response)
|
|
57
|
+
{ ttl: 60_000 }, // Optional: override TTL (1 min)
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### How SWR Works
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Call fetchWithCache(key, doFetch)
|
|
65
|
+
├→ Entry exists & fresh? → return cached body
|
|
66
|
+
├→ Entry exists & stale? → return stale, fire background refresh
|
|
67
|
+
├→ Entry missing, inflight exists? → await inflight Promise
|
|
68
|
+
└→ Entry missing, no inflight → execute doFetch(), cache result
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### TTL Configuration
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const TTL_BY_STATUS: Record<string, number> = {
|
|
75
|
+
"2xx": 180_000, // 3 min — success responses
|
|
76
|
+
"404": 10_000, // 10s — not found (may become available)
|
|
77
|
+
"5xx": 0, // never cache server errors
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Error Handling
|
|
82
|
+
|
|
83
|
+
Non-ok responses (status >= 400) throw an error. They are NOT cached. The error propagates to the caller, who should `.catch()` gracefully:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const data = await fetchWithCache<T>(url, doFetch).catch(() => fallback);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Client Integration: `vtexCachedFetch` (`vtex/client.ts`)
|
|
92
|
+
|
|
93
|
+
Convenience wrapper that routes GET requests through `fetchWithCache`:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { vtexCachedFetch } from "@decocms/apps/vtex";
|
|
97
|
+
|
|
98
|
+
// Automatically uses SWR cache for GET
|
|
99
|
+
const products = await vtexCachedFetch<Product[]>(
|
|
100
|
+
`/api/catalog_system/pub/products/search/${slug}/p`,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Non-GET falls through to regular vtexFetch
|
|
104
|
+
const result = await vtexCachedFetch<OrderForm>(
|
|
105
|
+
`/api/checkout/pub/orderForms/simulation`,
|
|
106
|
+
{ method: "POST", body: JSON.stringify(items) },
|
|
107
|
+
);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom TTL per call
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const pageType = await vtexCachedFetch<PageType>(
|
|
114
|
+
`/api/catalog_system/pub/portal/pagetype/${term}`,
|
|
115
|
+
undefined,
|
|
116
|
+
{ cacheTTL: 300_000 }, // 5 min for page types
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## What Uses `vtexCachedFetch` (Current)
|
|
123
|
+
|
|
124
|
+
| Module | Endpoint | Before | After |
|
|
125
|
+
|--------|----------|--------|-------|
|
|
126
|
+
| `slugCache.ts` | `search/{slug}/p` | Manual inflight Map + 5s timeout | `vtexCachedFetch` SWR 3min |
|
|
127
|
+
| `relatedProducts.ts` | `crossselling/{type}/{id}` | Manual `crossSellingInflight` Map | `vtexCachedFetch` SWR 3min |
|
|
128
|
+
| `productDetailsPage.ts` | Kit items search | Plain `vtexFetch` | `vtexCachedFetch` SWR 3min |
|
|
129
|
+
| `client.ts` `cachedPageType` | `pagetype/{term}` | Manual inflight Map | `vtexCachedFetch` SWR 3min |
|
|
130
|
+
|
|
131
|
+
## What Uses `fetchWithCache` Directly
|
|
132
|
+
|
|
133
|
+
| Module | Endpoint | Notes |
|
|
134
|
+
|--------|----------|-------|
|
|
135
|
+
| `client.ts` `intelligentSearch` | IS `product_search`, `facets` | Wrapped inline, uses default TTL |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Comparison with `deco-cx/deco` `fetchCache.ts`
|
|
140
|
+
|
|
141
|
+
| Feature | deco-cx/deco | @decocms/apps |
|
|
142
|
+
|---------|-------------|---------------|
|
|
143
|
+
| Storage | `CacheStorage` (Web Cache API) | In-memory `Map` (LRU) |
|
|
144
|
+
| Persistence | Disk-backed (Deno CacheStorage) | Process-lifetime only |
|
|
145
|
+
| Max entries | Unlimited (disk) | 500 (memory) |
|
|
146
|
+
| TTL source | HTTP `Cache-Control` headers | Status-based defaults |
|
|
147
|
+
| SWR | `stale-while-revalidate` header parsing | Manual background refresh |
|
|
148
|
+
| Dedup | Separate `singleFlight` wrapper | Built into `fetchWithCache` |
|
|
149
|
+
| Redis/FS tiers | Yes (`tiered.ts`: LRU → FS → Redis) | No — single in-memory tier |
|
|
150
|
+
|
|
151
|
+
### Why In-Memory Only?
|
|
152
|
+
|
|
153
|
+
Cloudflare Workers don't have persistent storage APIs accessible during SSR. The Cache API (`caches.default`) is for edge HTTP responses, not arbitrary data. In-memory with LRU is the practical choice for Workers.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Diagnostics
|
|
158
|
+
|
|
159
|
+
### Check Cache Stats
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { getFetchCacheStats, clearFetchCache } from "@decocms/apps/vtex/utils/fetchCache";
|
|
163
|
+
|
|
164
|
+
console.log(getFetchCacheStats());
|
|
165
|
+
// { entries: 42, inflight: 0 }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Clear Cache
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
clearFetchCache(); // Useful after decofile hot-reload
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Verify Cache Hits in Logs
|
|
175
|
+
|
|
176
|
+
With instrumented fetch (`createInstrumentedFetch("vtex")`), look for timing:
|
|
177
|
+
- **Cache HIT**: No `[vtex] GET ...` log appears (response served from cache)
|
|
178
|
+
- **Cache MISS**: `[vtex] GET ...` followed by `[vtex] 200 GET ... Xms`
|
|
179
|
+
- **SWR refresh**: `[vtex] GET ...` appears AFTER the response was already served
|
|
180
|
+
|
|
181
|
+
### Common Issues
|
|
182
|
+
|
|
183
|
+
**Stale data after CMS update**: Wait 3 min for TTL to expire, or restart the dev server to clear in-memory cache.
|
|
184
|
+
|
|
185
|
+
**Cache not working in dev**: `fetchWithCache` works in both dev and prod. Unlike `createCachedLoader` which skips SWR in dev (only dedup), `fetchWithCache` always caches.
|
|
186
|
+
|
|
187
|
+
**POST requests not cached**: By design — `vtexCachedFetch` only caches GET. Use `usePriceSimulationBatch` for simulation POST optimization.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Adding Cache to a New VTEX Endpoint
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Before — no cache
|
|
195
|
+
const data = await vtexFetch<MyType>(`/api/my-endpoint/${id}`);
|
|
196
|
+
|
|
197
|
+
// After — with SWR cache
|
|
198
|
+
const data = await vtexCachedFetch<MyType>(`/api/my-endpoint/${id}`);
|
|
199
|
+
|
|
200
|
+
// With custom TTL
|
|
201
|
+
const data = await vtexCachedFetch<MyType>(
|
|
202
|
+
`/api/my-endpoint/${id}`,
|
|
203
|
+
undefined,
|
|
204
|
+
{ cacheTTL: 60_000 }, // 1 min
|
|
205
|
+
);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
For non-VTEX APIs, use `fetchWithCache` directly:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { fetchWithCache } from "@decocms/apps/vtex/utils/fetchCache";
|
|
212
|
+
|
|
213
|
+
const data = await fetchWithCache<MyType>(url, () => fetch(url, init));
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Related Skills
|
|
219
|
+
|
|
220
|
+
| Skill | Purpose |
|
|
221
|
+
|-------|---------|
|
|
222
|
+
| `deco-api-call-dedup` | Higher-level dedup patterns (slugCache, batching, PLP filtering) |
|
|
223
|
+
| `deco-cms-layout-caching` | Layout section caching (works on top of fetch cache) |
|
|
224
|
+
| `deco-edge-caching` | Cloudflare edge caching (HTTP level, outside the Worker) |
|
|
225
|
+
| `deco-tanstack-storefront-patterns` | General storefront patterns + `createCachedLoader` |
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: find-skills
|
|
3
|
+
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Find Skills
|
|
7
|
+
|
|
8
|
+
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Use this skill when the user:
|
|
13
|
+
|
|
14
|
+
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
15
|
+
- Says "find a skill for X" or "is there a skill for X"
|
|
16
|
+
- Asks "can you do X" where X is a specialized capability
|
|
17
|
+
- Expresses interest in extending agent capabilities
|
|
18
|
+
- Wants to search for tools, templates, or workflows
|
|
19
|
+
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
20
|
+
|
|
21
|
+
## What is the Skills CLI?
|
|
22
|
+
|
|
23
|
+
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
24
|
+
|
|
25
|
+
**Key commands:**
|
|
26
|
+
|
|
27
|
+
- `npx skills find [query]` - Search for skills interactively or by keyword
|
|
28
|
+
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
29
|
+
- `npx skills check` - Check for skill updates
|
|
30
|
+
- `npx skills update` - Update all installed skills
|
|
31
|
+
|
|
32
|
+
**Browse skills at:** https://skills.sh/
|
|
33
|
+
|
|
34
|
+
## How to Help Users Find Skills
|
|
35
|
+
|
|
36
|
+
### Step 1: Understand What They Need
|
|
37
|
+
|
|
38
|
+
When a user asks for help with something, identify:
|
|
39
|
+
|
|
40
|
+
1. The domain (e.g., React, testing, design, deployment)
|
|
41
|
+
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
42
|
+
3. Whether this is a common enough task that a skill likely exists
|
|
43
|
+
|
|
44
|
+
### Step 2: Search for Skills
|
|
45
|
+
|
|
46
|
+
Run the find command with a relevant query:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx skills find [query]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For example:
|
|
53
|
+
|
|
54
|
+
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
55
|
+
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
56
|
+
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
57
|
+
|
|
58
|
+
The command will return results like:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Install with npx skills add <owner/repo@skill>
|
|
62
|
+
|
|
63
|
+
vercel-labs/agent-skills@vercel-react-best-practices
|
|
64
|
+
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: Present Options to the User
|
|
68
|
+
|
|
69
|
+
When you find relevant skills, present them to the user with:
|
|
70
|
+
|
|
71
|
+
1. The skill name and what it does
|
|
72
|
+
2. The install command they can run
|
|
73
|
+
3. A link to learn more at skills.sh
|
|
74
|
+
|
|
75
|
+
Example response:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
|
79
|
+
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
80
|
+
|
|
81
|
+
To install it:
|
|
82
|
+
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
|
83
|
+
|
|
84
|
+
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Step 4: Offer to Install
|
|
88
|
+
|
|
89
|
+
If the user wants to proceed, you can install the skill for them:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx skills add <owner/repo@skill> -g -y
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
96
|
+
|
|
97
|
+
## Common Skill Categories
|
|
98
|
+
|
|
99
|
+
When searching, consider these common categories:
|
|
100
|
+
|
|
101
|
+
| Category | Example Queries |
|
|
102
|
+
| --------------- | ---------------------------------------- |
|
|
103
|
+
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
104
|
+
| Testing | testing, jest, playwright, e2e |
|
|
105
|
+
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
106
|
+
| Documentation | docs, readme, changelog, api-docs |
|
|
107
|
+
| Code Quality | review, lint, refactor, best-practices |
|
|
108
|
+
| Design | ui, ux, design-system, accessibility |
|
|
109
|
+
| Productivity | workflow, automation, git |
|
|
110
|
+
|
|
111
|
+
## Tips for Effective Searches
|
|
112
|
+
|
|
113
|
+
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
114
|
+
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
115
|
+
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
116
|
+
|
|
117
|
+
## When No Skills Are Found
|
|
118
|
+
|
|
119
|
+
If no relevant skills exist:
|
|
120
|
+
|
|
121
|
+
1. Acknowledge that no existing skill was found
|
|
122
|
+
2. Offer to help with the task directly using your general capabilities
|
|
123
|
+
3. Suggest the user could create their own skill with `npx skills init`
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
I searched for skills related to "xyz" but didn't find any matches.
|
|
129
|
+
I can still help you with this task directly! Would you like me to proceed?
|
|
130
|
+
|
|
131
|
+
If this is something you do often, you could create your own skill:
|
|
132
|
+
npx skills init my-xyz-skill
|
|
133
|
+
```
|