@thorprovider/storefront 0.3.1 → 0.4.0-mvp.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/CHANGELOG.md +23 -0
- package/README.md +248 -99
- package/assets/templates/app/app/[[...slug]]/page.tsx +13 -2
- package/assets/templates/app/app/layout.tsx +18 -4
- package/assets/templates/app/app/robots.ts +13 -0
- package/assets/templates/app/app/sitemap.ts +12 -0
- package/assets/templates/app/lib/__STOREFRONT__/actions.ts +542 -0
- package/assets/templates/app/lib/__STOREFRONT__/cart-state.ts +66 -0
- package/assets/templates/app/lib/__STOREFRONT__/catalog.ts +40 -57
- package/assets/templates/app/lib/__STOREFRONT__/commerce.ts +608 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.core.ts +165 -0
- package/assets/templates/app/lib/__STOREFRONT__/handlers.ts +5 -36
- package/assets/templates/app/lib/__STOREFRONT__/icon-map.ts +27 -0
- package/assets/templates/app/lib/__STOREFRONT__/mappers.ts +13 -11
- package/assets/templates/app/lib/__STOREFRONT__/registry.tsx +232 -51
- package/assets/templates/app/lib/__STOREFRONT__/runtime.ts +3 -3
- package/assets/templates/app/lib/__STOREFRONT__/schema.ts +121 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.core.ts +470 -0
- package/assets/templates/app/lib/__STOREFRONT__/session.ts +169 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/content.ts +96 -0
- package/assets/templates/app/lib/__STOREFRONT__/shell/props.ts +34 -0
- package/assets/templates/app/lib/__STOREFRONT__/spec/index.ts +100 -11
- package/assets/templates/app/lib/__STOREFRONT__/spec/page-root.ts +5 -1
- package/assets/templates/app/lib/__STOREFRONT__/spec/seo.ts +26 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.core.ts +257 -0
- package/assets/templates/app/lib/__STOREFRONT__/state.ts +7 -31
- package/assets/templates/app/lib/__STOREFRONT__/store-context.ts +142 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.client.tsx +25 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/cta.tsx +16 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.client.tsx +24 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/faq.tsx +5 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/index.ts +17 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/logoCloud.tsx +26 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/pricing.tsx +36 -18
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.cta.ts +19 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/schema.faq.ts +18 -0
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/statistics.tsx +35 -14
- package/assets/templates/app/lib/__STOREFRONT__/views/sections/testimonials.tsx +31 -13
- package/assets/templates/app/lib/__STOREFRONT__/views/types.ts +5 -3
- package/assets/templates/features/checkout/handlers.ts +153 -0
- package/assets/templates/features/checkout/state.ts +61 -0
- package/assets/templates/features/customer-auth/handlers.ts +59 -0
- package/assets/templates/features/customer-auth/state.ts +36 -0
- package/assets/templates/features/recommendations/state.ts +11 -0
- package/assets/templates/views/account/content.ts +35 -0
- package/assets/templates/views/account/index.tsx +114 -0
- package/assets/templates/views/account/registry.tsx +378 -0
- package/assets/templates/views/account/schema.ts +52 -0
- package/assets/templates/views/category/assembly/assembly.json +28 -0
- package/assets/templates/views/category/assembly/index.skeleton.tsx +193 -0
- package/assets/templates/views/category/assembly/registry.skeleton.tsx +33 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/catalog.part +5 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/element.part +34 -0
- package/assets/templates/views/category/assembly/slots/filter/CategoryFilter2/registry.part +71 -0
- package/assets/templates/views/category/content.ts +30 -0
- package/assets/templates/views/category/data.ts +12 -0
- package/assets/templates/views/category/index.tsx +230 -0
- package/assets/templates/views/category/props.ts +16 -0
- package/assets/templates/views/category/registry.tsx +103 -0
- package/assets/templates/views/category/schema.ts +78 -0
- package/assets/templates/views/checkout/content.ts +105 -0
- package/assets/templates/views/checkout/data.ts +23 -0
- package/assets/templates/views/checkout/index.tsx +111 -0
- package/assets/templates/views/checkout/props.ts +49 -0
- package/assets/templates/views/checkout/registry.tsx +217 -0
- package/assets/templates/views/checkout/schema.ts +229 -0
- package/assets/templates/views/home/assembly/assembly.json +24 -0
- package/assets/templates/views/home/assembly/index.skeleton.tsx +72 -0
- package/assets/templates/views/home/assembly/registry.skeleton.tsx +25 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature1/registry.part +11 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/element.part +4 -0
- package/assets/templates/views/home/assembly/slots/benefits/Feature2/registry.part +10 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/categories/ProductCategory2/registry.part +16 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/binding.json +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/catalog.part +4 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/element.part +5 -0
- package/assets/templates/views/home/assembly/slots/hero/StorefrontHero1/registry.part +26 -0
- package/assets/templates/views/home/content.ts +70 -0
- package/assets/templates/views/home/data.ts +15 -0
- package/assets/templates/views/home/index.tsx +38 -84
- package/assets/templates/views/home/props.ts +22 -0
- package/assets/templates/views/home/registry.tsx +38 -22
- package/assets/templates/views/home/schema.ts +93 -0
- package/assets/templates/views/product/content.ts +25 -0
- package/assets/templates/views/product/data.ts +48 -0
- package/assets/templates/views/product/index.tsx +104 -0
- package/assets/templates/views/product/props.ts +20 -0
- package/assets/templates/views/product/registry.tsx +78 -0
- package/assets/templates/views/product/schema.ts +108 -0
- package/assets/templates/views/static/content.ts +99 -0
- package/assets/templates/views/static/index.tsx +60 -0
- package/assets/templates/views/static/registry.tsx +66 -0
- package/assets/templates/views/static/schema.ts +45 -0
- package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/index.tsx +8 -70
- package/{skills/sf-view-gen/assets → assets}/templates/views/user-panel/registry.tsx +23 -18
- package/assets/templates/views/user-panel/schema.ts +79 -0
- package/bin/storefront.js +1278 -79
- package/commands/sf-add-view.md +44 -13
- package/commands/sf-feature.md +28 -0
- package/commands/sf-theme.md +14 -8
- package/commands/sf-view.md +54 -12
- package/package.json +6 -4
- package/recipes/archetypes.json +28 -1
- package/recipes/catalog.json +361 -0
- package/recipes/catalog.mjs +128 -0
- package/recipes/catalog.schema.json +64 -0
- package/recipes/feature.schema.json +89 -0
- package/recipes/features.json +139 -0
- package/recipes/manifest.schema.json +69 -0
- package/recipes/operations.schema.json +43 -0
- package/recipes/recipe.schema.json +11 -2
- package/recipes/recipes.json +60 -5
- package/recipes/sections.json +24 -5
- package/recipes/validate.mjs +784 -32
- package/scripts/candidate.mjs +236 -0
- package/scripts/composers.mjs +140 -0
- package/scripts/file-classes.mjs +116 -0
- package/scripts/logical-controls.mjs +61 -0
- package/scripts/manifest.mjs +298 -0
- package/scripts/operation-registry.mjs +256 -0
- package/scripts/operations.mjs +720 -0
- package/scripts/ownership-conflict.mjs +356 -0
- package/scripts/pinning.mjs +504 -0
- package/scripts/plan-apply.mjs +843 -0
- package/scripts/preflight.mjs +51 -0
- package/scripts/reconcile.mjs +511 -0
- package/scripts/render-templates.mjs +245 -28
- package/scripts/schema-validate.mjs +132 -0
- package/scripts/section-compat.mjs +300 -0
- package/scripts/section-operations.mjs +508 -0
- package/scripts/sync-assets.mjs +22 -15
- package/scripts/view-assembly.mjs +291 -0
- package/scripts/view-selection.mjs +211 -0
- package/skills/sf-feature/SKILL.md +67 -0
- package/skills/sf-theme-gen/SKILL.md +12 -5
- package/skills/sf-view-gen/SKILL.md +123 -93
- package/skills/sf-view-gen/assets/recipes/archetypes.json +28 -1
- package/skills/sf-view-gen/assets/recipes/catalog.json +361 -0
- package/skills/sf-view-gen/assets/recipes/catalog.schema.json +64 -0
- package/skills/sf-view-gen/assets/recipes/features.json +139 -0
- package/skills/sf-view-gen/assets/recipes/recipes.json +60 -5
- package/skills/sf-view-gen/assets/recipes/sections.json +24 -5
- package/skills/sf-view-gen/assets/templates/views/sections/cta.client.tsx +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/cta.tsx +16 -0
- package/skills/sf-view-gen/assets/templates/views/sections/faq.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/faq.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.client.tsx +12 -8
- package/skills/sf-view-gen/assets/templates/views/sections/logoCloud.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.client.tsx +14 -10
- package/skills/sf-view-gen/assets/templates/views/sections/pricing.tsx +2 -9
- package/skills/sf-view-gen/assets/templates/views/sections/schema.cta.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.faq.ts +18 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.logoCloud.ts +19 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.pricing.ts +26 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.statistics.ts +25 -0
- package/skills/sf-view-gen/assets/templates/views/sections/schema.testimonials.ts +23 -0
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.client.tsx +16 -8
- package/skills/sf-view-gen/assets/templates/views/sections/statistics.tsx +2 -7
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.client.tsx +13 -9
- package/skills/sf-view-gen/assets/templates/views/sections/testimonials.tsx +2 -5
- package/skills/sf-view-gen/references/views.md +44 -10
- package/skills/sf-view-gen/scripts/catalog.mjs +128 -0
- package/skills/sf-view-gen/scripts/schema-validate.mjs +132 -0
- package/skills/sf-view-gen/scripts/validate.mjs +784 -32
- package/skills/storefront-workflow/SKILL.md +116 -0
- package/skills/sf-view-gen/assets/templates/views/category/index.tsx +0 -86
- package/skills/sf-view-gen/assets/templates/views/category/registry.tsx +0 -48
- package/skills/sf-view-gen/assets/templates/views/checkout/index.tsx +0 -192
- package/skills/sf-view-gen/assets/templates/views/checkout/registry.tsx +0 -45
- package/skills/sf-view-gen/assets/templates/views/product/index.tsx +0 -122
- package/skills/sf-view-gen/assets/templates/views/product/registry.tsx +0 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- **Candidate pinning contract (local prerelease `0.4.0-mvp.0`, not published)**:
|
|
6
|
+
the install record separates declared compatibility (Node `>=20.9.0`, Yarn 4,
|
|
7
|
+
the `node-modules` linker, direct dependency ranges including `@thorprovider/types`
|
|
8
|
+
and the CLI itself, and `cliVersion`) from the resolved pin (exact `node_modules`
|
|
9
|
+
versions, the real `yarn.lock` content hash, and the package-owned asset BOM).
|
|
10
|
+
`doctor`/`update` validate the record against the live project and refuse to stamp
|
|
11
|
+
an incomplete or mismatched pin set; `doctor` compares the live declarations,
|
|
12
|
+
resolved versions, lockfile, and installed owned BOM against the record instead of
|
|
13
|
+
the record against itself. Only package-owned suite paths are hashed, so a user's
|
|
14
|
+
own skill/command files are neither stamped nor reported as drift.
|
|
15
|
+
- **Composition manifest (in progress)**: a `storefront.config.json` manifest becomes
|
|
16
|
+
the source of truth for composition (theme, views, features, per-slot intents) and
|
|
17
|
+
the structural files are derived from it. This reuses the existing `recipes/` +
|
|
18
|
+
`archetypes.json` registry and the CLI rather than the LLM-driven view-edit path,
|
|
19
|
+
which is superseded: agents will delegate structural changes to the CLI and edit
|
|
20
|
+
only user-owned files. Coordinated with the in-progress `storefront-automation-commands`
|
|
21
|
+
and `deterministic-sf-init-cli` changes (shared registry and CLI).
|
|
22
|
+
- **Project resolution**: only `create` takes a directory. Every other command resolves
|
|
23
|
+
the storefront from the current directory upward (looking for `storefront.config.json`)
|
|
24
|
+
and errors without writing when none is found.
|
|
25
|
+
|
|
3
26
|
## 0.4.0
|
|
4
27
|
|
|
5
28
|
- **Renamed package**: `@thorprovider/create-storefront` → `@thorprovider/storefront`;
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "@thorprovider/storefront"
|
|
3
3
|
purpose: "Deterministic storefront scaffolder and installable command suite for Thor Commerce"
|
|
4
|
-
lastReviewed: "2026-10-
|
|
4
|
+
lastReviewed: "2026-10-08"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# @thorprovider/storefront
|
|
@@ -36,13 +36,18 @@ storefront it generates resolves its own dependencies from the registry.
|
|
|
36
36
|
| Package manager | Yarn with the `node-modules` linker (for generated projects) |
|
|
37
37
|
| OpenCode | a version that supports project skills in `.opencode/skills/` |
|
|
38
38
|
| OS | macOS, Linux, Windows (via WSL) |
|
|
39
|
-
| Network | access to the npm registry
|
|
39
|
+
| Network | access to the npm registry — only for the first scaffold per machine (repeat scaffolds can run offline, see [troubleshooting](#flaky-network-etimedout)) |
|
|
40
40
|
|
|
41
41
|
Generated projects use Yarn with the `node-modules` linker, not Plug'n'Play (PnP).
|
|
42
42
|
Yarn 4 defaults to PnP, which Next.js 16's Turbopack cannot resolve from, so the
|
|
43
43
|
scaffold writes a `.yarnrc.yml` setting `nodeLinker: node-modules` before installing
|
|
44
44
|
dependencies. PnP is not used. You do not need to configure this yourself.
|
|
45
45
|
|
|
46
|
+
The same `.yarnrc.yml` hardens installs for unreliable networks: `httpRetry: 10`
|
|
47
|
+
and `networkConcurrency: 8`. The first successful scaffold warms the global Yarn
|
|
48
|
+
cache (`~/.yarn/berry/cache`); repeat scaffolds then download no packages, and
|
|
49
|
+
`YARN_ENABLE_OFFLINE_MODE=1` drops even the metadata requests.
|
|
50
|
+
|
|
46
51
|
### Install
|
|
47
52
|
|
|
48
53
|
Run the scaffolder directly from npm — no global install required. `npx` and
|
|
@@ -64,10 +69,13 @@ it is where `.opencode/`, the Next.js app, and `lib/<name>/` live. Run the comma
|
|
|
64
69
|
from the parent directory you want the project created in.
|
|
65
70
|
|
|
66
71
|
In a terminal, `create` runs an interactive wizard for any input you omit (name,
|
|
67
|
-
site name, backend URL, preset — Maia is preselected).
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
site name, backend URL, preset — Maia is preselected). Pressing Enter on an empty
|
|
73
|
+
prompt accepts the suggested default: the storefront name is suggested from the
|
|
74
|
+
target directory basename, and the site name from the Title Case of that name
|
|
75
|
+
(`my-shop` → `My Shop`). Without a terminal, every required input must be passed
|
|
76
|
+
as a flag; the command fails fast and writes nothing rather than guessing. Brand
|
|
77
|
+
color is never asked by the wizard — apply it later with `/sf-theme`, or pass
|
|
78
|
+
`--brand` on the command line.
|
|
71
79
|
|
|
72
80
|
Once the package is installed as a dependency, the shorter binary form is available:
|
|
73
81
|
|
|
@@ -96,14 +104,15 @@ storefront create my-shop \
|
|
|
96
104
|
| --- | --- | --- |
|
|
97
105
|
| `<dir>` (positional) | target project directory (created if missing) | required |
|
|
98
106
|
| `--name <name>` | kebab-case storefront name = `lib/<name>/` = `__STOREFRONT__` | prompted in the wizard |
|
|
99
|
-
| `--site-name <name>` | human site name (`__SITE_NAME__`) | `<name>` |
|
|
107
|
+
| `--site-name <name>` | human site name (`__SITE_NAME__`) | Title Case of `<name>` (`my-shop` → `My Shop`) |
|
|
100
108
|
| `--backend-url <url>` | Medusa backend URL | `http://localhost:9000` |
|
|
101
109
|
| `--preset <preset>` | visual preset: `maia`, `nova`, `vega`, `luma`, `lyra`, `mira` | prompted in the wizard (Maia preselected) |
|
|
102
110
|
| `--brand <color>` | named brand color overriding preset primary/nav/ring tokens (never prompted) | none |
|
|
103
111
|
| `--force`, `-f` | overwrite files that already exist | off |
|
|
104
112
|
|
|
105
|
-
The pipeline runs: preconditions → Next.js base
|
|
106
|
-
`.yarnrc.yml` (`nodeLinker: node-modules
|
|
113
|
+
The pipeline runs: preconditions → Next.js base (files only, `--skip-install`) →
|
|
114
|
+
`.yarnrc.yml` (`nodeLinker: node-modules`, retry and concurrency hardening) →
|
|
115
|
+
shadcn base with the preset → dependency install → template copy +
|
|
107
116
|
placeholder substitution → default-page removal → `.env.local` + theme → suite
|
|
108
117
|
install into `.opencode/`.
|
|
109
118
|
|
|
@@ -137,19 +146,57 @@ it incrementally from OpenCode:
|
|
|
137
146
|
|
|
138
147
|
### Backend configuration
|
|
139
148
|
|
|
140
|
-
Generated projects
|
|
141
|
-
|
|
149
|
+
Generated projects resolve an explicit single-store context from the host
|
|
150
|
+
environment before any commerce call. `create` writes a git-ignored `.env.local`
|
|
151
|
+
with the data source and backend URL you passed, a generated cookie secret, and
|
|
152
|
+
blank placeholders for the store identity, plus a committed `.env.example` that
|
|
153
|
+
documents every variable (never a real secret):
|
|
142
154
|
|
|
143
155
|
```bash
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
# .env.local (git-ignored)
|
|
157
|
+
THOR_DATA_SOURCE=medusa
|
|
158
|
+
MEDUSA_BASE_URL=http://localhost:9000
|
|
146
159
|
MEDUSA_PUBLISHABLE_KEY=
|
|
160
|
+
MEDUSA_REGION_ID=
|
|
161
|
+
MEDUSA_SALES_CHANNEL_ID=
|
|
162
|
+
THOR_STORE_ID=my-shop
|
|
163
|
+
THOR_CURRENCY_CODE=
|
|
164
|
+
THOR_SESSION_SECRET=<generated per install>
|
|
147
165
|
```
|
|
148
166
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
The store context is **explicit and validated**:
|
|
168
|
+
|
|
169
|
+
- `THOR_STORE_ID` — stable store identity (seeded from the manifest name).
|
|
170
|
+
- `MEDUSA_SALES_CHANNEL_ID` — the sales channel the store is scoped to.
|
|
171
|
+
- `MEDUSA_REGION_ID` — the backend region that supplies currency, countries, and
|
|
172
|
+
shipping.
|
|
173
|
+
- `THOR_CURRENCY_CODE` — ISO 4217 currency, taken from the environment; it is
|
|
174
|
+
**never hardcoded** in the app.
|
|
175
|
+
- `THOR_SESSION_SECRET` — host-only cookie signing secret (≥16 chars, 32
|
|
176
|
+
recommended). Generate your own; never commit or log it.
|
|
177
|
+
|
|
178
|
+
`THOR_DATA_SOURCE` selects the mode **explicitly** and is never inferred from a
|
|
179
|
+
failure:
|
|
180
|
+
|
|
181
|
+
- `THOR_DATA_SOURCE=mock` renders labelled demo data (`Datos de demostración`).
|
|
182
|
+
The store context must match the mock fixture: channel
|
|
183
|
+
`mock-sales-channel`, region `mock-region-us` (currency `USD`) or
|
|
184
|
+
`mock-region-eu` (currency `EUR`). The committed `.env.example` ships these.
|
|
185
|
+
- `THOR_DATA_SOURCE=medusa` uses the real backend. A missing provider, invalid
|
|
186
|
+
configuration, or a provider failure is surfaced as a **visible error** — the
|
|
187
|
+
store never silently degrades to demo data. Until `MEDUSA_PUBLISHABLE_KEY` is
|
|
188
|
+
set, the home view shows the error notice instead of a fake catalog. Fill in
|
|
189
|
+
the variables, then restart `yarn dev` to serve real catalog data.
|
|
190
|
+
|
|
191
|
+
The guest flow is backend-authoritative end to end: the signed host cookie
|
|
192
|
+
issues a shopper identity and binds a persistent backend cart; the PDP adds the
|
|
193
|
+
explicitly selected variant; the cart and checkout render the backend's totals;
|
|
194
|
+
checkout applies the one approved manual TEST provider after shipping and
|
|
195
|
+
completes the cart with its native discriminator. An ambiguous completion is
|
|
196
|
+
never retried — the shopper reconciles the committed cart, and the active cart
|
|
197
|
+
is cleared only after a confirmed order. No card data is collected at launch.
|
|
198
|
+
|
|
199
|
+
For offline-only work set `THOR_DATA_SOURCE=mock` on purpose.
|
|
153
200
|
|
|
154
201
|
### Troubleshooting / Known Issues
|
|
155
202
|
|
|
@@ -157,54 +204,38 @@ Each entry below follows the same shape: **cause** → **symptom** → **remedy*
|
|
|
157
204
|
|
|
158
205
|
#### Ancestor `.opencode/`
|
|
159
206
|
|
|
160
|
-
- **Cause**:
|
|
161
|
-
|
|
162
|
-
- **
|
|
163
|
-
and `/sf-theme` run commands from the ancestor's `.opencode/` instead of the ones
|
|
164
|
-
this package installed, so you see stale or unrelated behaviour.
|
|
165
|
-
- **Remedy**: scaffold in a clean root with no ancestor `.opencode/` (for example a
|
|
166
|
-
fresh directory outside the monorepo), or remove/relocate the ancestor `.opencode/`
|
|
167
|
-
before opening the project.
|
|
207
|
+
- **Cause**: an ancestor directory already contains a `.opencode/`.
|
|
208
|
+
- **Symptom**: OpenCode resolves the wrong command set (`/sf-add-view`, `/sf-view`, `/sf-theme`), so you see stale or unrelated behaviour.
|
|
209
|
+
- **Remedy**: scaffold in a clean root with no ancestor `.opencode/`, or remove/relocate the ancestor `.opencode/` before opening the project.
|
|
168
210
|
|
|
169
211
|
#### Non-empty target directory
|
|
170
212
|
|
|
171
|
-
- **Cause**: `
|
|
172
|
-
|
|
173
|
-
- **
|
|
174
|
-
`lib/<name>/ already exists — refusing to overwrite`.
|
|
175
|
-
- **Remedy**: choose a different `--name`, or remove the existing `lib/<name>/`
|
|
176
|
-
directory. To extend an existing storefront instead of creating one, use
|
|
177
|
-
`/sf-add-view`. `create-next-app` also refuses to initialize a directory that is
|
|
178
|
-
already a Next.js project unless it can reuse it; run `create` in an empty directory.
|
|
213
|
+
- **Cause**: `lib/<name>/` already exists, or the root is already a Next.js project.
|
|
214
|
+
- **Symptom**: the run stops at the preconditions stage with `lib/<name>/ already exists — refusing to overwrite`.
|
|
215
|
+
- **Remedy**: choose a different `--name`, or remove the existing `lib/<name>/`. To extend an existing storefront, use `/sf-add-view`; run `create` in an empty directory.
|
|
179
216
|
|
|
180
217
|
#### Missing or invalid `--preset`
|
|
181
218
|
|
|
182
|
-
- **Cause**: `--preset` was omitted (
|
|
183
|
-
|
|
184
|
-
- **
|
|
185
|
-
`--preset must be one of: maia, nova, vega, luma, lyra, mira`.
|
|
186
|
-
- **Remedy**: pass `--preset` with one of `maia`, `nova`, `vega`, `luma`, `lyra`,
|
|
187
|
-
`mira`. In a terminal the wizard preselects `maia`, so the preset is only required
|
|
188
|
-
on the command line for non-interactive runs.
|
|
219
|
+
- **Cause**: `--preset` was omitted (non-interactive) or is not a recognized preset.
|
|
220
|
+
- **Symptom**: the run stops at input validation with `--preset must be one of: maia, nova, vega, luma, lyra, mira`.
|
|
221
|
+
- **Remedy**: pass one of those presets. In a terminal the wizard preselects `maia`, so the flag is only required for non-interactive runs.
|
|
189
222
|
|
|
190
223
|
#### Yarn Plug'n'Play / linker
|
|
191
224
|
|
|
192
225
|
- **Cause**: a generated project installed with Yarn's default PnP linker.
|
|
193
|
-
- **Symptom**: Next.js 16's Turbopack fails to resolve the Next.js package
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
226
|
+
- **Symptom**: Next.js 16's Turbopack fails to resolve Next.js (`Could not find the Next.js package`).
|
|
227
|
+
- **Remedy**: `create` writes `.yarnrc.yml` with `nodeLinker: node-modules` before installing; if you set the project up manually, add that file and re-run `yarn install`. Do not use PnP.
|
|
228
|
+
|
|
229
|
+
#### Flaky network (ETIMEDOUT)
|
|
230
|
+
|
|
231
|
+
- **Cause**: an unstable connection stalls registry requests mid-transfer (`RequestError … AggregateError [ETIMEDOUT]`).
|
|
232
|
+
- **Remedy**: the scaffolded `.yarnrc.yml` sets `httpRetry: 10` and `networkConcurrency: 8`; that run warms the global cache (`~/.yarn/berry/cache`), which later scaffolds reuse. For offline re-scaffolding run `YARN_ENABLE_OFFLINE_MODE=1 storefront create <dir> …`; the first scaffold per machine still needs one successful online run.
|
|
199
233
|
|
|
200
234
|
#### Backend not configured
|
|
201
235
|
|
|
202
|
-
- **Cause**: `
|
|
203
|
-
|
|
204
|
-
- **
|
|
205
|
-
- **Remedy**: fill in the variables in [Backend configuration](#backend-configuration)
|
|
206
|
-
and restart `yarn dev`, or use the `mock` adapter data source during local
|
|
207
|
-
development.
|
|
236
|
+
- **Cause**: `THOR_DATA_SOURCE=medusa` but `MEDUSA_BASE_URL`/`MEDUSA_PUBLISHABLE_KEY` are missing, empty, or unreachable.
|
|
237
|
+
- **Symptom**: the storefront renders a visible error notice (`role="alert"`) instead of backend data; real-mode failures never degrade to a fake catalog.
|
|
238
|
+
- **Remedy**: fill in the variables in [Backend configuration](#backend-configuration) and restart `yarn dev`. For offline work set `THOR_DATA_SOURCE=mock` on purpose.
|
|
208
239
|
|
|
209
240
|
---
|
|
210
241
|
|
|
@@ -212,13 +243,18 @@ Each entry below follows the same shape: **cause** → **symptom** → **remedy*
|
|
|
212
243
|
|
|
213
244
|
```
|
|
214
245
|
<project>/.opencode/
|
|
215
|
-
storefront.json # { version, installedAt }
|
|
246
|
+
storefront.json # install record: { version, installedAt, pinning }
|
|
247
|
+
# pinning separates declared ranges from the resolved
|
|
248
|
+
# versions, yarn.lock hash, and owned asset BOM
|
|
216
249
|
commands/
|
|
217
250
|
sf-add-view.md
|
|
218
251
|
sf-view.md
|
|
252
|
+
sf-feature.md
|
|
219
253
|
sf-theme.md
|
|
220
254
|
skills/
|
|
221
|
-
|
|
255
|
+
storefront-workflow/ # the four phases + CLI/manifest authority
|
|
256
|
+
sf-view-gen/ # assets/recipes/, scripts/validate.mjs
|
|
257
|
+
sf-feature/
|
|
222
258
|
sf-theme-gen/ # assets/
|
|
223
259
|
nextjs-shadcn/
|
|
224
260
|
shadcn-theming/
|
|
@@ -229,18 +265,123 @@ Each entry below follows the same shape: **cause** → **symptom** → **remedy*
|
|
|
229
265
|
|
|
230
266
|
Each skill carries the templates, recipes, and scripts it uses inside its own
|
|
231
267
|
directory. Skills address them by relative path, so nothing needs the package to be
|
|
232
|
-
located on disk at generation time.
|
|
268
|
+
located on disk at generation time. The feature command is
|
|
269
|
+
[`commands/sf-feature.md`](./commands/sf-feature.md) and the workflow model is
|
|
270
|
+
[`skills/storefront-workflow/SKILL.md`](./skills/storefront-workflow/SKILL.md).
|
|
271
|
+
|
|
272
|
+
## Workflow: bootstrap → compose → customize → verify
|
|
273
|
+
|
|
274
|
+
The project manifest (`storefront.config.json`) is the structural source of truth;
|
|
275
|
+
the `storefront` CLI is the only writer of derived files; the developer/agent edits
|
|
276
|
+
only user-owned files. Read the installed `storefront-workflow` skill for the full
|
|
277
|
+
model. Every command runs from inside the project (no directory argument).
|
|
278
|
+
|
|
279
|
+
| Phase | Authority | Command |
|
|
280
|
+
| --- | --- | --- |
|
|
281
|
+
| **Bootstrap** | CLI only | `npx @thorprovider/storefront create <dir> --name <name> --preset <preset>` |
|
|
282
|
+
| **Compose** | CLI only (agents choose, CLI writes) | `view add\|remove`, `feature add\|remove`, `theme --mode` |
|
|
283
|
+
| **Customize** | developer/agent | edit user-owned `content.ts`/`props.ts`/`data.ts`/`schema.ts`/`mappers.ts` |
|
|
284
|
+
| **Verify** | CLI + build | `npx @thorprovider/storefront sync`, `doctor`, `yarn build` |
|
|
285
|
+
|
|
286
|
+
**Ownership classes.** *Derived* files (`views/index.ts`, `views/registry.ts`,
|
|
287
|
+
`index.tsx`, `registry.tsx`, `catalog.ts`, `runtime.ts`, `spec/index.ts`,
|
|
288
|
+
`state.ts`, `handlers.ts`, `features/*/adapter.ts`) are regenerated by the CLI and
|
|
289
|
+
never hand-edited. *User-owned* files (content, props, data, local schema, mapper
|
|
290
|
+
bodies) are consumed through thin derived wrappers and survive `sync` and removal.
|
|
291
|
+
*External* files (`app/layout.tsx`, `app/globals.css`) belong to create-next-app +
|
|
292
|
+
shadcn; the CLI only appends idempotent markers.
|
|
293
|
+
|
|
294
|
+
**Curated MVP defaults.** Standalone views `home`, `category`, `product`; the
|
|
295
|
+
`checkout` feature (which owns the `checkout` view — a direct `view add checkout`
|
|
296
|
+
is rejected, use `feature add checkout`). Held post-MVP and rejected before any
|
|
297
|
+
write: `user-panel`, recommendations, and sections. A generated storefront is not a
|
|
298
|
+
complete commerce implementation: the `checkout` feature ships structural wiring
|
|
299
|
+
only, and the purchase flow is owned by the separate `single-tenant-commerce-mvp`
|
|
300
|
+
change.
|
|
301
|
+
|
|
302
|
+
**Prerequisites and pinning.** Node `>=20.9.0`, `node-modules` linker, and the
|
|
303
|
+
canonical Yarn pinned in the generated `package.json` as
|
|
304
|
+
`"packageManager": "yarn@4.5.1"` (Corepack resolves it; the scaffold runs every
|
|
305
|
+
install under that version). The generated project pins an exact compatible
|
|
306
|
+
candidate toolset and installs the local `@thorprovider/*` packages from packed
|
|
307
|
+
tarballs via Yarn `resolutions` (`--packs <dir>`), with no publish and no
|
|
308
|
+
workspace import. No package-manager safety policy is suppressed: the generated
|
|
309
|
+
`.yarnrc.yml` sets no `npmPreapprovedPackages`, and the pinned versions are
|
|
310
|
+
published past Yarn's age gate. `doctor` reports the observed linker/Node/Yarn
|
|
311
|
+
against the declared ranges and exits non-zero on a mismatch instead of a false
|
|
312
|
+
clean stamp.
|
|
313
|
+
|
|
314
|
+
### Configurable composition (E1)
|
|
315
|
+
|
|
316
|
+
The composition engine is a truthful semantic catalog plus a structured
|
|
317
|
+
`inspect → plan → apply` boundary. Run these inside the project:
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
storefront inspect --json
|
|
321
|
+
storefront plan --operations operations.json [--out plan.json] [--json]
|
|
322
|
+
storefront apply --plan plan.json [--json]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
- **`inspect --json`** is read-only and deterministic: `formatVersion: 1` /
|
|
326
|
+
`catalogVersion: 1`, every slot/variant with `executable` or `held` + a reason, and a
|
|
327
|
+
capability catalog with `verified`/`unverified` state. A held or unverified unit is
|
|
328
|
+
never reported as available.
|
|
329
|
+
- **`plan`** validates the whole intended state and emits before/after fingerprints and
|
|
330
|
+
the exact derived-file diff. Without `--out` it writes nothing and prints the plan to
|
|
331
|
+
stdout; with `--out` it writes only a safe plan artifact. `--out` is refused
|
|
332
|
+
(`E_OUT_PATH_REFUSED`) for owned, derived, secret-bearing, or installation paths and
|
|
333
|
+
for the operation input.
|
|
334
|
+
- **`apply`** is the only composition mutation. It applies from the recorded
|
|
335
|
+
before-state, reports a **verified no-op** at the recorded after-state, and rejects
|
|
336
|
+
anything else as stale (`E_STALE_PLAN`); concurrent runs are serialized by a local
|
|
337
|
+
lock. A handled write failure rolls back every touched file. An abrupt process kill is
|
|
338
|
+
**not** crash-atomic — the manifest is written last and `sync` detects and repairs the
|
|
339
|
+
divergence.
|
|
340
|
+
|
|
341
|
+
The operations envelope is `{ "formatVersion": 1, "operations": [ ... ] }` (missing or
|
|
342
|
+
different `formatVersion` → `E_INVALID_ENVELOPE`). Operation names are
|
|
343
|
+
`view.add|remove|configure` and `feature.add|remove|configure`; `view add|remove` and
|
|
344
|
+
`feature add|remove` remain thin aliases over the same engine.
|
|
345
|
+
|
|
346
|
+
**What E1 proves.** The only executable non-default swap is `home.benefits`
|
|
347
|
+
`Feature1 → Feature2` (identical `BenefitsItem[]` data shape, no bridge); every bridged
|
|
348
|
+
variant is `held`. A feature may declare a `config` and `mounts`; the bounded `benefits`
|
|
349
|
+
feature mounts at `home.benefits` with `{ "presentation": "compact" | "detailed" }`,
|
|
350
|
+
claims the slot exactly once, and preserves user-owned content on removal. Checkout
|
|
351
|
+
keeps its existing feature-owned route only — unproven checkout variants or route
|
|
352
|
+
options are rejected (`E_UNPROVEN_CHECKOUT_OPTION`). The category filter's action is
|
|
353
|
+
**held** (`unverified`, owner E2) and its control is omitted rather than exposed as a
|
|
354
|
+
no-op; catalog search/filters (E2) and composable sections (E3) are **not** implemented
|
|
355
|
+
here.
|
|
233
356
|
|
|
234
357
|
## Commands
|
|
235
358
|
|
|
236
359
|
| Command | Purpose |
|
|
237
360
|
| --- | --- |
|
|
238
|
-
| `/sf-add-view <view>` | Add
|
|
239
|
-
| `/sf-view <view> <change>` |
|
|
361
|
+
| `/sf-add-view <view>` | Add a curated view via the CLI (`product`, `category`; `checkout` via `/sf-feature`) |
|
|
362
|
+
| `/sf-view <view> <change>` | Customize a view's user-owned content/props/data, then `sync` |
|
|
363
|
+
| `/sf-feature add\|remove <name>` | Add/remove a feature bundle (e.g. `checkout`) via the CLI |
|
|
240
364
|
| `/sf-theme` | Customize OKLCH color tokens (light/dark), typography, and identity metadata |
|
|
241
365
|
|
|
242
366
|
Scaffolding itself is `npx @thorprovider/storefront create <dir>` — there is no
|
|
243
|
-
`/sf-init` command; the wizard lives in the CLI and is deterministic.
|
|
367
|
+
`/sf-init` command; the wizard lives in the CLI and is deterministic. The color
|
|
368
|
+
mode is likewise a CLI capability: `npx @thorprovider/storefront theme --mode light|dark|system`.
|
|
369
|
+
|
|
370
|
+
## Philosophy: determinism first; the LLM only where there is judgment
|
|
371
|
+
|
|
372
|
+
A storefront task is one of three kinds, and the tool follows the kind:
|
|
373
|
+
|
|
374
|
+
- **Deterministic** (output is a pure function of input) → a CLI subcommand with flags
|
|
375
|
+
(e.g. `storefront theme --mode`). Repeatable, reviewable, no model in the loop.
|
|
376
|
+
- **Mixed** → split it: the script applies the deterministic part, the LLM decides the
|
|
377
|
+
judgment part (`/sf-theme` decides the mode; `storefront theme --mode` applies it).
|
|
378
|
+
- **Pure judgment** → an `sf-*` skill (e.g. `/sf-view`): variant selection, copy, and
|
|
379
|
+
layout choices live here.
|
|
380
|
+
|
|
381
|
+
**Test criterion**: if you can assert the result without a model ("after this, the file
|
|
382
|
+
contains X"), it must be a script; if you cannot, it is skill work. LLM edits are
|
|
383
|
+
non-deterministic and expensive to review, so keep the judgment-heavy work where a
|
|
384
|
+
model adds value and make the rest a reliable script.
|
|
244
385
|
|
|
245
386
|
## Design guarantees
|
|
246
387
|
|
|
@@ -252,40 +393,59 @@ Scaffolding itself is `npx @thorprovider/storefront create <dir>` — there is n
|
|
|
252
393
|
installer copies anything.
|
|
253
394
|
- **Stack-constrained**: UI comes only from `@thorprovider/blocks`; data only from
|
|
254
395
|
`@thorprovider/adapters` and `@thorprovider/runtime/commerce`.
|
|
255
|
-
- **
|
|
256
|
-
|
|
257
|
-
so
|
|
396
|
+
- **Truthful output**: a versioned recipe/catalog exposes every slot and variant with an
|
|
397
|
+
`executable` or `held` status and a reason. Only executable variants render; a held
|
|
398
|
+
variant is rejected before any write, so a site is never presented with a choice that
|
|
399
|
+
silently does nothing. E1 ships a small proven set (`home.benefits` `Feature1`↔
|
|
400
|
+
`Feature2`); bridged variants and optional sections stay held.
|
|
258
401
|
|
|
259
402
|
## Generated project structure
|
|
260
403
|
|
|
261
|
-
Each view is a self-contained module.
|
|
262
|
-
|
|
404
|
+
Each view is a self-contained module. The CLI generates the barrels and wiring
|
|
405
|
+
from the manifest; shared files compose whatever the barrel exports and never name
|
|
406
|
+
a view. Only user-owned files (marked `*`) are hand-edited.
|
|
263
407
|
|
|
264
408
|
```
|
|
265
409
|
lib/<name>/
|
|
266
|
-
catalog.ts, registry.tsx, runtime.ts, handlers.ts, state.ts # shell; compose the barrel
|
|
267
|
-
mappers.ts
|
|
268
|
-
|
|
410
|
+
catalog.ts, registry.tsx, runtime.ts, handlers.ts, state.ts # derived shell; compose the barrel
|
|
411
|
+
mappers.ts * # user-owned: CardProduct -> block props
|
|
412
|
+
shell/
|
|
413
|
+
content.ts * # user-owned: wordmark accents + footer newsletter
|
|
414
|
+
props.ts * # user-owned: shell element props
|
|
415
|
+
spec/index.ts, spec/page-root.ts, spec/types.ts # derived routes built from the barrel
|
|
269
416
|
views/
|
|
270
|
-
index.ts # barrel: export const views: ViewModule[] = [home, ...]
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
home/
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
417
|
+
index.ts # derived server barrel: export const views: ViewModule[] = [home, ...]
|
|
418
|
+
registry.ts # derived client barrel: viewRegistries (no server loaders)
|
|
419
|
+
types.ts # ViewModule / ViewRegistry contract
|
|
420
|
+
home/
|
|
421
|
+
index.tsx # derived thin wrapper (imports ./content, ./props, ./data)
|
|
422
|
+
registry.tsx # derived 'use client' component mappings
|
|
423
|
+
content.ts * # user-owned copy/data
|
|
424
|
+
props.ts * # user-owned element props
|
|
425
|
+
data.ts * # user-owned loader shaping
|
|
426
|
+
schema.ts * # user-owned local Zod mirror
|
|
427
|
+
product/ category/ checkout/ # same shape
|
|
278
428
|
```
|
|
279
429
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
and
|
|
283
|
-
|
|
430
|
+
The shell's derived `spec/index.ts` imports the user-owned `shell/props.ts`
|
|
431
|
+
(`...navbarShellProps`, `...footerShellProps`) and composes it **last**, so the brand
|
|
432
|
+
surface and footer copy are configured without touching derived files. Configure
|
|
433
|
+
`shell/content.ts` (`logoAccent`/`brandAccent`, `navbarClassName`/`footerClassName`,
|
|
434
|
+
`brandDescription`, `linkColumns`); defaults are neutral and link only to served routes
|
|
435
|
+
(`/`, `/categorias`, `/checkout`), never dead `#` links. `newsletter` defaults to `false`
|
|
436
|
+
and the scaffold ships no submit callback, so a content-only newsletter renders no form
|
|
437
|
+
— never a fake form.
|
|
438
|
+
|
|
439
|
+
A view's derived `index.tsx` imports the user-owned `content.ts`/`props.ts`/
|
|
440
|
+
`data.ts`, so a customization changes real spec/loader output and survives `sync`.
|
|
441
|
+
`npx @thorprovider/storefront view add <view>` chooses the intent/variant from the
|
|
442
|
+
registry, writes the derived files, and regenerates the barrels; the agent edits
|
|
443
|
+
only the user-owned files. The scaffold templates live at the package root:
|
|
284
444
|
|
|
285
445
|
```
|
|
286
|
-
assets/templates/app/ # shell (
|
|
287
|
-
assets/templates/views/home/ # home (
|
|
288
|
-
|
|
446
|
+
assets/templates/app/ # shell (generated by `create`)
|
|
447
|
+
assets/templates/views/home/ # home (generated by `create`)
|
|
448
|
+
assets/templates/views/<view>/ # the other views (generated by `storefront view add`)
|
|
289
449
|
```
|
|
290
450
|
|
|
291
451
|
## Development
|
|
@@ -301,24 +461,13 @@ node bin/storefront.js doctor <tmp> # report the recorded version
|
|
|
301
461
|
node scripts/render-templates.mjs <tmp> my-shop # re-render templates only (no install; fast loop)
|
|
302
462
|
```
|
|
303
463
|
|
|
304
|
-
`scripts/render-templates.mjs` is the same renderer
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
the
|
|
311
|
-
meaningful at these moments:
|
|
312
|
-
|
|
313
|
-
| Moment | Action |
|
|
314
|
-
| --- | --- |
|
|
315
|
-
| Day-to-day development | Nothing — use the local binary above |
|
|
316
|
-
| Changing `files`, moving assets, adding or removing directories | `npm pack --dry-run` to list what would ship |
|
|
317
|
-
| CI, before publishing | `yarn check-assets` + `npm pack --dry-run` as a guard |
|
|
318
|
-
| Publishing a release | `npm pack` / `yarn npm publish` — the real tarball |
|
|
319
|
-
|
|
320
|
-
A generated `.tgz` goes stale the moment the tree changes, so it is not a
|
|
321
|
-
development tool. Keep it out of the working tree.
|
|
464
|
+
`scripts/render-templates.mjs` is the same renderer `create` uses for stage 6; it copies
|
|
465
|
+
templates and substitutes placeholders without touching dependencies, so iterating on a
|
|
466
|
+
template costs seconds and no network. `npm pack` verifies a different thing — **what
|
|
467
|
+
actually ships to npm** (the `files` list, and that every asset travels inside its
|
|
468
|
+
skill). Use it only when `files`/assets change or before a release (`npm pack --dry-run`
|
|
469
|
+
to list; `yarn check-assets` + `npm pack --dry-run` in CI). A generated `.tgz` goes
|
|
470
|
+
stale the moment the tree changes, so keep it out of the working tree.
|
|
322
471
|
|
|
323
472
|
## Maintenance
|
|
324
473
|
|
|
@@ -2,16 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
import { notFound } from 'next/navigation'
|
|
4
4
|
import { __STOREFRONT_CAMEL__App } from '@/lib/__STOREFRONT__/runtime'
|
|
5
|
+
import { readCartStateForRender } from '@/lib/__STOREFRONT__/cart-state'
|
|
5
6
|
import { StorefrontRenderer } from './renderer'
|
|
6
7
|
|
|
7
8
|
export const { generateMetadata, generateStaticParams } = __STOREFRONT_CAMEL__App
|
|
8
9
|
|
|
9
10
|
export default async function Page({
|
|
10
11
|
params,
|
|
12
|
+
searchParams,
|
|
11
13
|
}: {
|
|
12
14
|
params: Promise<{ slug?: string[] }>
|
|
15
|
+
searchParams: Promise<Record<string, string | string[] | undefined>>
|
|
13
16
|
}) {
|
|
14
|
-
|
|
17
|
+
// The raw URL search params are forwarded to the loader, which validates them
|
|
18
|
+
// through the canonical boundary before any commerce call (E2 task 5.1).
|
|
19
|
+
const data = await __STOREFRONT_CAMEL__App.getPageData({ params, searchParams })
|
|
15
20
|
if (!data) return notFound()
|
|
16
|
-
|
|
21
|
+
|
|
22
|
+
// Read the signed cookie and load the backend cart for the initial render.
|
|
23
|
+
// This never creates a cart; a guest identity is issued by a Server Action.
|
|
24
|
+
const cartState = await readCartStateForRender()
|
|
25
|
+
const initialState = { ...(data.initialState ?? {}), ...cartState }
|
|
26
|
+
|
|
27
|
+
return <StorefrontRenderer {...data} initialState={initialState} />
|
|
17
28
|
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import type { Metadata } from 'next'
|
|
4
4
|
import { Geist } from 'next/font/google'
|
|
5
|
+
import { ThemeProvider } from 'next-themes'
|
|
5
6
|
import { StorefrontProvider } from '@thorprovider/runtime/host-glue/providers'
|
|
6
7
|
import { themeVarsToCss, themeTokensToVars } from '@thorprovider/runtime/render-core/theme'
|
|
7
8
|
import { DEFAULT_THEME_TOKENS } from '@thorprovider/runtime/contracts'
|
|
8
9
|
import { registry } from '@/lib/__STOREFRONT__/registry'
|
|
10
|
+
import { getSiteUrl } from '@/lib/__STOREFRONT__/store-context'
|
|
9
11
|
import './globals.css'
|
|
10
12
|
|
|
11
13
|
const geist = Geist({
|
|
@@ -15,20 +17,32 @@ const geist = Geist({
|
|
|
15
17
|
|
|
16
18
|
const themeCss = themeVarsToCss(themeTokensToVars(DEFAULT_THEME_TOKENS))
|
|
17
19
|
|
|
20
|
+
// The canonical public origin is optional: when THOR_SITE_URL is unset no base
|
|
21
|
+
// URL is invented. `metadataBase` is left undefined in that case.
|
|
22
|
+
const siteUrl = getSiteUrl()
|
|
23
|
+
|
|
18
24
|
export const metadata: Metadata = {
|
|
25
|
+
metadataBase: siteUrl ? new URL(siteUrl) : undefined,
|
|
19
26
|
title: '__SITE_NAME__',
|
|
20
27
|
description: 'Tienda online de __SITE_NAME__.',
|
|
28
|
+
openGraph: {
|
|
29
|
+
title: '__SITE_NAME__',
|
|
30
|
+
description: 'Tienda online de __SITE_NAME__.',
|
|
31
|
+
url: siteUrl,
|
|
32
|
+
},
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export default function RootLayout({
|
|
24
36
|
children,
|
|
25
37
|
}: Readonly<{ children: React.ReactNode }>) {
|
|
26
38
|
return (
|
|
27
|
-
<html lang="es" className={geist.variable}>
|
|
39
|
+
<html lang="es" className={geist.variable} suppressHydrationWarning>
|
|
28
40
|
<body className="font-sans antialiased">
|
|
29
|
-
<
|
|
30
|
-
{
|
|
31
|
-
|
|
41
|
+
<ThemeProvider attribute="class" defaultTheme="__STOREFRONT_MODE__" enableSystem>
|
|
42
|
+
<StorefrontProvider themeCss={themeCss} registry={registry}>
|
|
43
|
+
{children}
|
|
44
|
+
</StorefrontProvider>
|
|
45
|
+
</ThemeProvider>
|
|
32
46
|
</body>
|
|
33
47
|
</html>
|
|
34
48
|
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/app/robots.ts)
|
|
2
|
+
// Always served. Points at the sitemap only when THOR_SITE_URL is configured.
|
|
3
|
+
|
|
4
|
+
import type { MetadataRoute } from 'next'
|
|
5
|
+
import { robotsSitemapUrl } from '@/lib/__STOREFRONT__/spec/seo'
|
|
6
|
+
import { getSiteUrl } from '@/lib/__STOREFRONT__/store-context'
|
|
7
|
+
|
|
8
|
+
export default function robots(): MetadataRoute.Robots {
|
|
9
|
+
return {
|
|
10
|
+
rules: { userAgent: '*', allow: '/' },
|
|
11
|
+
sitemap: robotsSitemapUrl(getSiteUrl()),
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Generated by Thor Commerce storefront (template: assets/templates/app/app/sitemap.ts)
|
|
2
|
+
// Lists the storefront's fixed routes. Absolute URLs are emitted only when
|
|
3
|
+
// THOR_SITE_URL is configured; otherwise the sitemap is empty (no invented URL).
|
|
4
|
+
|
|
5
|
+
import type { MetadataRoute } from 'next'
|
|
6
|
+
import { __STOREFRONT_CAMEL__Spec } from '@/lib/__STOREFRONT__/spec'
|
|
7
|
+
import { sitemapUrls } from '@/lib/__STOREFRONT__/spec/seo'
|
|
8
|
+
import { getSiteUrl } from '@/lib/__STOREFRONT__/store-context'
|
|
9
|
+
|
|
10
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
11
|
+
return sitemapUrls(__STOREFRONT_CAMEL__Spec.routes ?? {}, getSiteUrl()).map((url) => ({ url }))
|
|
12
|
+
}
|