@wix/agent-skills 1.7.0 → 1.9.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/README.md +3 -7
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +2 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +14 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +11 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +3 -17
- package/skills/wix-app/references/editor-react-component/SSR.md +119 -0
- package/skills/wix-design-system/SKILL.md +5 -1
- package/skills/wix-headless/SKILL.md +55 -32
- package/skills/wix-headless/references/BUILD-astro.md +244 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +50 -252
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +41 -422
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +37 -27
- package/skills/wix-headless/references/SEED.md +27 -18
- package/skills/wix-headless/references/SETUP.md +32 -213
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +45 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +58 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +47 -27
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
- package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +233 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +136 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +54 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +49 -0
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +356 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +133 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +73 -0
- package/skills/wix-headless/references/astro/templates/global.css +2 -2
- package/skills/wix-headless/references/astro/templates/index.astro +1 -1
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/bookings/FLOW.md +185 -0
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +90 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +146 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
- package/skills/wix-headless/references/seed-recipes.md +2 -2
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
- package/skills/wix-headless/references/shared/STYLING.md +8 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
- package/skills/wix-headless/references/verticals/_schema.json +1 -1
- package/skills/wix-headless/references/verticals/_schema.md +3 -3
- package/skills/wix-headless/references/verticals/blog.md +1 -1
- package/skills/wix-headless/references/verticals/bookings.md +39 -0
- package/skills/wix-headless/references/verticals/cms.md +1 -1
- package/skills/wix-headless/references/verticals/ecom.md +2 -2
- package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
- package/skills/wix-headless/references/verticals/stores.md +1 -1
- package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/scaffold.sh +47 -16
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +13 -1
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
- package/skills/wix-manage/references/bookings/create-appointment-service.md +266 -0
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +37 -0
- package/skills/wix-manage/references/bookings/create-class-service.md +245 -0
- package/skills/wix-manage/references/bookings/create-course-service.md +249 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +3 -2
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +2 -2
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/forms/create-form.md +49 -1
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
- package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
- package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"wix-stores": "215238eb-22a5-4c36-9e7b-e7c08025e04e",
|
|
3
3
|
"wix-forms": "225dd912-7dea-4738-8688-4b8c6955ffc2",
|
|
4
|
-
"wix-blog": "14bcded7-0066-7c35-14d7-466cb3f09103"
|
|
4
|
+
"wix-blog": "14bcded7-0066-7c35-14d7-466cb3f09103",
|
|
5
|
+
"wix-gift-cards": "d80111c5-a0f4-47a8-b63a-65b54d774a27"
|
|
5
6
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: connection-plan
|
|
3
|
+
description: "Integration-mode subagent: read the brought-in site and emit the connection plan — a binding map (existing dynamic regions to wire to Wix) plus an augmentation spec (the connected component to add when the design has none). The orchestrator feeds this to the per-capability wiring subagents. Input-general: infer from the markup; a Claude-Design bundle is optional enrichment."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Connection plan (integration mode)
|
|
7
|
+
|
|
8
|
+
You are a planning subagent for the `wix-headless` skill's **integration mode** (`frontend = "custom"`). The user brought a finished, working site (HTML+CSS/JS) and the skill is connecting it to a live Wix backend. **Your job is to read the site and decide exactly what to connect** — you write **no code**; you return a structured plan the wiring subagents execute.
|
|
9
|
+
|
|
10
|
+
## Inputs (inlined by the orchestrator)
|
|
11
|
+
|
|
12
|
+
- The project's file list + the entry HTML path(s).
|
|
13
|
+
- The **inferred capability set** from Discovery (e.g. `["forms"]`, `["stores"]`, `["blog","forms"]`, `["cms"]` for a client-state app).
|
|
14
|
+
- The resolved **`frontendBuild`** (`none` ⇒ read rendered markup; `own` ⇒ read un-built `src/**` source). The entry-HTML shape is enough on its own (see § "What to read"), but this confirms it.
|
|
15
|
+
- Whether a Claude-Design handoff bundle is present (`README.md`, `chats/`).
|
|
16
|
+
|
|
17
|
+
## What to read — rendered markup OR un-built source
|
|
18
|
+
|
|
19
|
+
A brought-in frontend comes in **two shapes**, and you read whichever you're given (this is input-adaptation, *not* a framework branch of the flow — the structures you emit are the same regardless of framework):
|
|
20
|
+
|
|
21
|
+
1. **Rendered markup (static HTML).** The entry HTML carries real content. Read the entry HTML and any other pages. Extract: `<title>`, headings, body copy, `<form>`s, repeated element structures, and the **CSS custom-property token block** (`:root { --… }` — names + values).
|
|
22
|
+
2. **Un-built SPA source.** The entry HTML is a near-empty shell — `<div id="root"></div>` + `<script type="module" src="/src/main.jsx">` — so it has **no rendered content** to detect regions in. **Read the source instead** (`src/**`: the root component, any data/store modules, `*.css`). Identify how the app **persists state today** — a `localStorage` read/write, an in-memory store, a `fetch` to a stub — and the **in-memory data shape** it keeps. This is the signal for a `persistenceSwap` (structure (c)). Detecting this is what stops an SPA from collapsing to the contact-form floor.
|
|
23
|
+
3. **The handoff bundle (opportunistic, if present).** Read `chats/` and `README.md` to sharpen intent — but the plan must stand on the source/markup alone, since most inputs have no bundle.
|
|
24
|
+
|
|
25
|
+
> **How do you know which shape you're reading?** If the entry HTML has a `<div id="root">`/`<div id="app">` mount + a module script pointing at `src/` and little else, it's un-built SPA source → read `src/**`. Otherwise it's rendered markup. (The orchestrator also passes the resolved `frontendBuild` — `own` ⇒ source, `none` ⇒ markup — but the markup signal alone is enough.)
|
|
26
|
+
|
|
27
|
+
## Produce two structures
|
|
28
|
+
|
|
29
|
+
### (a) Binding map — existing dynamic regions to wire
|
|
30
|
+
|
|
31
|
+
Detect regions the design already shaped for data. **Detection ladder, highest-confidence first:**
|
|
32
|
+
|
|
33
|
+
1. **Explicit annotations** — `data-wix-*`, semantic `id`/`class` (`product-grid`, `post-list`), schema.org microdata / `itemtype`.
|
|
34
|
+
2. **Repetition + shape** — a container with N near-identical children, each holding image + title-like text + price-like text ⇒ a product list; N dated heading+excerpt blocks ⇒ a post list.
|
|
35
|
+
3. **Form semantics** — a `<form>` with recognizable fields ⇒ a submit target.
|
|
36
|
+
4. **Single-detail** — one entity's worth of fields (gallery + title + price + description) ⇒ a detail view.
|
|
37
|
+
|
|
38
|
+
One entry per region:
|
|
39
|
+
|
|
40
|
+
```jsonc
|
|
41
|
+
{
|
|
42
|
+
"file": "index.html",
|
|
43
|
+
"anchor": "section.product-grid", // a stable CSS selector into the existing DOM
|
|
44
|
+
"entity": "stores.products", // stores.products | blog.posts | forms.submit | data.items | bookings.services
|
|
45
|
+
"shape": "list", // list | detail | single | submit
|
|
46
|
+
"template": "article.product-card", // the repeated child to clone per result (list shapes)
|
|
47
|
+
"bindings": { // DOM node (relative to template) → entity field
|
|
48
|
+
"img.thumb@src": "media.mainMedia.image.url",
|
|
49
|
+
"h3.name": "name",
|
|
50
|
+
"span.price": "priceData.formatted.price"
|
|
51
|
+
},
|
|
52
|
+
"sampleCount": 3 // hard-coded samples to remove after wiring
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A purely static editorial design (e.g. a wedding invitation) yields an **empty binding map** — that is expected; the augmentation spec carries it.
|
|
57
|
+
|
|
58
|
+
### (b) Augmentation spec — the connected feature to ADD
|
|
59
|
+
|
|
60
|
+
Per the **always-connect** rule (`INSTRUCTIONS.md` § "Always connect"), every run must end connected. If the inferred capability has no existing region to wire, emit the connected component to inject. Derive the capability from Discovery's inference; the universal floor is a Wix Forms contact/lead form.
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"capability": "rsvp", // rsvp | lead | contact | (or a read capability if augmenting reads)
|
|
65
|
+
"app": "wix-forms",
|
|
66
|
+
"component": "rsvp-form",
|
|
67
|
+
"injectAt": { "file": "index.html", "anchor": "section.closing", "position": "before" },
|
|
68
|
+
"fields": [ // becomes BOTH the Wix Form definition (seed) and the <form>
|
|
69
|
+
{ "name": "fullName", "label": "Your name", "type": "text", "required": true },
|
|
70
|
+
{ "name": "attending", "label": "Will you attend?", "type": "radio", "options": ["Joyfully accepts","Regretfully declines"] },
|
|
71
|
+
{ "name": "guests", "label": "Number of guests", "type": "number" },
|
|
72
|
+
{ "name": "dietary", "label": "Dietary notes", "type": "textarea" }
|
|
73
|
+
],
|
|
74
|
+
"styleFrom": { "selector": ":root", "tokens": ["--terracotta","--sage","--display","--body","--label"] }
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **`injectAt`** — pick a natural seam in the existing layout (before the closing/footer section, after the hero). Use a stable selector that exists in the markup.
|
|
79
|
+
- **`styleFrom`** — list the actual CSS custom-property names you found in the site, so the wiring subagent styles the injected component to match. If the site has no `:root` tokens, list the dominant colors/fonts you observed instead.
|
|
80
|
+
- Multiple capabilities → one augmentation entry each (an array).
|
|
81
|
+
|
|
82
|
+
### (c) Persistence swap — replace a client-state app's data layer with a CMS collection
|
|
83
|
+
|
|
84
|
+
For an **app with client/local state** (todo, notes, tracker, planner, kanban — `INSTRUCTIONS.md` § "Always connect" client-state row), the connection is neither a binding map (no rendered regions in the un-built shell) nor an augmentation (nothing to *add* — the UI exists). It is a **persistence swap**: the app's existing load/save data layer is replaced with `@wix/data`, and the in-memory shape becomes a CMS collection schema. Emit one entry per persisted entity-shape:
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
{
|
|
88
|
+
"sourceFile": "src/App.jsx", // the file holding the data layer (where the swap is applied)
|
|
89
|
+
"dataLayer": { // the functions/effects to rewrite — NAME them, don't rewrite here
|
|
90
|
+
"load": ["load() (reads localStorage 'lists')"],
|
|
91
|
+
"save": ["useEffect persisting 'lists'", "useEffect persisting 'activeId'"],
|
|
92
|
+
"storage": "localStorage" // localStorage | in-memory | fetch-stub
|
|
93
|
+
},
|
|
94
|
+
"inferredShape": { // the in-memory shape → becomes the collection schema (the seeder reads this)
|
|
95
|
+
"collection": "Todos", // PascalCase collection name to seed
|
|
96
|
+
"fields": [ // name + Wix Data field type
|
|
97
|
+
{ "name": "text", "type": "text" },
|
|
98
|
+
{ "name": "done", "type": "boolean" },
|
|
99
|
+
{ "name": "listId", "type": "text" }
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"shared": true // ALWAYS true — visitor token ⇒ one shared/public collection (per-user is deferred)
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- **One entry per distinct entity-shape.** A todo app with `lists` and `todos` yields **two** persistence-swap entries (a `Lists` collection: `name`; a `Todos` collection: `text`, `done`, `listId`).
|
|
107
|
+
- **`dataLayer`** — *name* the functions/effects the wiring agent will rewrite (with a one-line description of what each does today); do not write replacement code (the wiring agent does that, adapting to the framework idiom).
|
|
108
|
+
- **`inferredShape.fields`** — map each in-memory field to a Wix Data type (`text` | `number` | `boolean` | `datetime` | `url` | `image` | `richText` | `reference`). The **seeder** turns this into a public-read collection; the **cms wiring agent** turns the data layer into `@wix/data` `query/insert/update/remove` calls against it.
|
|
109
|
+
- **`shared`** — always `true`. The visitor token gives no per-user identity, so the collection is shared/public across all visitors. The plan should also surface this in `notes[]` so the orchestrator can caveat it to the user (`INSTRUCTIONS.md` § "Scope — deferred").
|
|
110
|
+
- This is **framework-blind**: a React, Vue, or Svelte SPA all produce the same `persistenceSwap` shape; only the wiring agent's idiom differs.
|
|
111
|
+
|
|
112
|
+
## When detection is ambiguous
|
|
113
|
+
|
|
114
|
+
Do **not** guess silently and do **not** call `AskUserQuestion`. Wire the regions you're confident about; for anything unclear, omit it from the binding map and note it in `notes[]` so the wiring subagent can leave a visible `<!-- wix: … -->` comment.
|
|
115
|
+
|
|
116
|
+
**Choose the right output structure before falling through:**
|
|
117
|
+
- **Un-built SPA with a client-state data layer** → emit a **`persistenceSwap`** (structure (c)). **Do NOT fall through to the contact-form floor** — that is the exact nonsense the client-state row was added to fix (a todo app is not a contact form). The persistence swap *is* the connection.
|
|
118
|
+
- **Rendered markup with dynamic regions** → binding map.
|
|
119
|
+
- **Static design with no dynamic region** → augmentation.
|
|
120
|
+
|
|
121
|
+
Fall back to the **Forms contact-form floor** (a contact-form augmentation) only when there is genuinely **no** data layer, **no** rendered region, **and** no capability was inferred — not for a client-state SPA (see the bullet above). Never return an empty plan (no binding map AND no augmentation AND no persistence swap) — that would violate always-connect.
|
|
122
|
+
|
|
123
|
+
## Return
|
|
124
|
+
|
|
125
|
+
Return a single JSON object per `shared/RETURN_CONTRACT.md` conventions:
|
|
126
|
+
|
|
127
|
+
```jsonc
|
|
128
|
+
{
|
|
129
|
+
"status": "ok",
|
|
130
|
+
"data": {
|
|
131
|
+
"bindingMap": [ /* (a) entries, possibly empty */ ],
|
|
132
|
+
"augmentation": [ /* (b) entries — possibly empty when a persistenceSwap carries the connection */ ],
|
|
133
|
+
"persistenceSwap": [ /* (c) entries — present for client-state SPAs; empty otherwise */ ],
|
|
134
|
+
"tokens": { "--terracotta": "#B26049", "--display": "\"Cormorant Garamond\", serif" }, // resolved :root/source tokens for styling
|
|
135
|
+
"notes": [ /* ambiguous regions, omitted-but-noticed structures, the shared-data caveat for any persistenceSwap */ ]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Always-connect invariant:** at least one of `bindingMap`, `augmentation`, or `persistenceSwap` MUST be non-empty. (`augmentation` is no longer required ≥1 — a client-state SPA connects via `persistenceSwap` with no augmentation.)
|
|
141
|
+
|
|
142
|
+
You return data only — no prose to the user, no files written.
|
|
@@ -1,38 +1,88 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: custom-frontend
|
|
3
|
-
description: "
|
|
3
|
+
description: "Frontend-track playbook for integration mode (frontend = custom). The user brings a finished, working frontend (HTML+CSS/JS from Claude Design or any tool) and asks to connect it to Wix. This track connects the site to a live Wix backend — wiring existing dynamic regions to @wix/sdk and, when the design has none, augmenting it with the connected feature its purpose implies — then publishes via a no-build wix release. Input-general: the site itself is the primary signal, never any one tool's bundle format."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Custom
|
|
6
|
+
# Custom frontend — integration mode
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The user already built a working website **outside** this skill (Claude Design, v0, Lovable, a static-site generator, or hand-coding) and wants it connected to Wix. The skill does **not** design or scaffold anything here — it **connects the brought-in site to a live Wix backend** and publishes it.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
This is the frontend-track entry doc for `frontend = "custom"`. The **business track is unchanged** — Setup (`SETUP.md`) and Seed (`SEED.md`) run their normal frontend-blind path; only this frontend track differs from astro.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
## Two locked principles
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
1. **Always connect.** Every integration run must end with the site reading from or writing to Wix. `init` + `release` of a static page with **no** backend connection is **not** an acceptable outcome — that is just hosting static HTML, which is not the point. If the design has no dynamic region to wire, **add** the connected feature its purpose implies (see § "Always connect").
|
|
15
|
+
2. **Input-general.** The skill connects *whatever* the user brings — it is **not** a Claude-Design integration. The **primary signal is always the site itself** (markup, copy, CSS tokens, file/route layout), which every input has. A Claude-Design handoff bundle (`README.md` + `chats/` transcript + `project/*.html`), when present, is **opportunistic enrichment** — read it to sharpen intent inference, but never require it.
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## The technical spine (read before wiring)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
A brought-in frontend connects to Wix with the **browser SDK + anonymous visitor session** — `createClient` + `OAuthStrategy`, no `@wix/astro`, no server runtime, no middleware. This connection model is **framework-blind**: the SDK *calls* are identical whether the brought-in frontend is loose static HTML, a Vite/React/Vue/Svelte SPA, or an already-built astro project. **How `@wix/sdk` physically reaches the page** (a CDN `<script>` vs a bundled `import`) and **how/whether the project builds** before release are *not* this file's concern — they are the **framework-build-class** (`frontendBuild`), resolved in Discovery and owned by the Build layer (`BUILD-own-build.md`). Connect never branches on framework; Build does.
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|---|---|---|
|
|
22
|
-
| `astro` | `<SKILL_ROOT>/references/astro/` (full playbook) | **Supported** |
|
|
23
|
-
| custom (anything else) | this file | **Not available yet** |
|
|
21
|
+
The runtime shape (the static/`none` form shown; an SPA bundles the same calls instead):
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import { createClient, OAuthStrategy } from "https://esm.sh/@wix/sdk";
|
|
26
|
+
import { submissions } from "https://esm.sh/@wix/forms"; // or @wix/stores, @wix/blog, @wix/data
|
|
27
|
+
const wix = createClient({
|
|
28
|
+
modules: { submissions },
|
|
29
|
+
// clientId === appId from wix.config.json (init sets WIX_CLIENT_ID = appId)
|
|
30
|
+
auth: OAuthStrategy({ clientId: "<appId from wix.config.json>" }),
|
|
31
|
+
});
|
|
32
|
+
// OAuthStrategy mints + caches visitor tokens in the browser — no cookie plumbing of our own.
|
|
33
|
+
// …read (queryProducts / queryPosts / query items) or write (createSubmission) against Wix.
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
This covers reads (products, posts, CMS items) **and** writes (form submissions) and cart-via-redirect. The published origin is already allow-listed on the OAuth app by `init`, so the visitor flow works from the deployed URL with no extra call.
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
> **How `@wix/sdk` reaches the page differs per framework-build-class** (CDN `esm.sh` for `none`; bundled `npm import` for `own`) — but that mechanic is the Build layer's concern (§ "The technical spine" above). Either way the `clientId`/`appId` is inlined from `wix.config.json` (no `WIX_CLIENT_ID` env at runtime).
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
2. **Business track (shared, frontend-blind)** — Setup (app installs from a project analysis, `env pull` if needed) and Seed run the **same** frontend-blind path as astro. Per the two-track model, a product/collection/post/form is the same regardless of what renders it, so this track does not change for custom.
|
|
33
|
-
3. **Frontend authoring (the deferred new work)** — per-pack wiring guides under `references/custom/<pack>/…` that inject `@wix/sdk` calls into the user's existing files, **additive-only**, via a heuristic inject-point ladder: explicit `data-wix-*` attribute → semantic class → return a recommendation rather than guessing. This is what the custom track delivers when it is built.
|
|
34
|
-
4. **Release** — `npx @wix/cli@latest release` directly, **no build** (the user's project is published as-is, or the user runs their own build first).
|
|
41
|
+
## The flow
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
The conductor interleaves these; the business track (steps 3–5) runs frontend-blind, concurrent with the connection-plan pass (step 2).
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
1. **Discovery (already done by the conductor).** `DISCOVERY.md` parsed the site, inferred the **domain**, derived the Wix **capability + apps**, and got approval. The resolved capability list + the site's file/token map arrive here.
|
|
46
|
+
2. **Connection plan** — **for `none`, first rename the entry HTML to `index.html`** if it isn't already (Wix serves `index.html` at the site root; any other name publishes but errors at runtime — `BUILD-own-build.md` § "connect × none" owns the rule; do it before this plan so the binding map keys on `index.html`). Then read the site and emit the binding map (existing dynamic regions) + the augmentation spec (the connected component to add). See `references/custom/CONNECTION_PLAN.md`.
|
|
47
|
+
3. **Init** — `npm create @wix/new@latest init` <!-- REVIEW: verify this command/subcommand syntax against the current Wix CLI — `npm create @wix/new@latest` may not take an `init` positional. Left unchanged pending check. --> (non-interactive when logged in) writes `wix.config.json` (`Site`, `appId`, `siteId`, `site.outputDirectory`). **Point `outputDirectory` at the dir that holds the deployable** — for `none` that's where the brought-in `index.html` lives; for `own` it's the project's build output (`dist/`), set after the build. (Init defaults it to `./dist`; the framework layer fixes it — `BUILD-own-build.md`.) Source files untouched at this step.
|
|
48
|
+
4. **Setup (shared business track, `SETUP.md`)** — install the inferred apps (app-install curl, with `x-wix-request-id` capture per the project policy). **Skip** `env pull` (integration inlines `appId` from `wix.config.json` — no `WIX_CLIENT_ID` needed; the `init` project has no `env` command). The per-pack `npm install` decision is framework-keyed (see § "The technical spine"): `none` skips it (CDN imports); `own` instead `npm install`s `@wix/sdk` + the capability modules so the bundler includes them.
|
|
49
|
+
5. **Seed (shared business track, `SEED.md`) — DISPATCH seeders as subagents.** Same per-pack dispatch model as astro: one seeder subagent per capability with a seed recipe, fired as a concurrent background batch. Each creates the backend its capability needs — the **Wix Form definition** (forms), a **CMS collection + items** (cms), sample products/posts (stores/blog) — and returns the IDs / form IDs. **Do not seed inline:** the seeder count scales with the brought-in site's content (unpredictable), so inlining serializes the work and bloats the orchestrator context. Seeded IDs flow into wiring.
|
|
50
|
+
6. **Wiring — writer count is a RUNTIME decision keyed on file topology.** Each capability's `references/custom/<capability>/WIRING.md` is the how-to (wire existing regions to `@wix/sdk` reads, and inject the connected component — e.g. an RSVP `<form>` → `@wix/forms createSubmission` — styled from the design's CSS tokens; additive, never restructure). But **how many writers run depends on whether capabilities share a file**: a single brought-in `index.html` carrying several capabilities (a form *and* a feedback list) must be wired by **one writer** (inline or a single subagent handling all of them) — **never parallel agents on the same file**, or they clobber each other and double the SDK bootstrap. Only capabilities mapping to *distinct* files may be wired by parallel subagents. (Contrast with **seeding**, which always dispatches — backend work, no shared file.)
|
|
51
|
+
7. **Release** — the **shared release tail** (`BUILD.md` § "Shared release tail"). Whether a build runs first is framework-keyed (see § "The technical spine"): `none` has no build (the static `outputDirectory` is already the deployable — run `npx @wix/cli@latest release` directly); `own` runs the project's own `npm run build` first, then points `outputDirectory` at the build output, then releases. **Never `wix build`** — that is astro-only. Extract the published URL from `Site published on <url>`; retry transient release errors serially (`references/shared/PRODUCTION_SHARP_EDGES.md`). Emit the production + dashboard URLs.
|
|
52
|
+
|
|
53
|
+
## Always connect: design intent → Wix capability
|
|
54
|
+
|
|
55
|
+
`DISCOVERY.md` infers the domain and picks the capability; this table is the shared source of truth. **Wix Forms is the universal floor** — when nothing richer fits, a domain-appropriate form is the minimum viable connection.
|
|
56
|
+
|
|
57
|
+
| Inferred domain | Connected feature (wire and/or add) | App(s) | Wiring guide |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| Wedding / event invitation | **RSVP form** (+ optional responses CMS) | Wix Forms (+ CMS) | `custom/forms/WIRING.md` |
|
|
60
|
+
| Store / product mock | product catalog (wire existing grid) | Wix Stores + eCom | `custom/stores/WIRING.md` |
|
|
61
|
+
| Blog / publication | post list + detail (wire existing) | Wix Blog | `custom/blog/WIRING.md` |
|
|
62
|
+
| Salon / spa / studio / coach landing | services list + availability + book (Wix-hosted bookings checkout) | Wix Bookings | `custom/bookings/WIRING.md` |
|
|
63
|
+
| Restaurant / venue landing | reservation / contact form; optional menu CMS | Wix Forms (+ CMS) | `custom/forms/WIRING.md` |
|
|
64
|
+
| SaaS / product landing | lead / waitlist signup form | Wix Forms | `custom/forms/WIRING.md` |
|
|
65
|
+
| Portfolio / agency | contact form; optional projects CMS | Wix Forms (+ CMS) | `custom/forms/WIRING.md` |
|
|
66
|
+
| **App with client/local state** (todo, notes, tracker, planner, kanban — typically a framework SPA persisting to `localStorage`/in-memory) | **persist its state to a Wix CMS collection** (`@wix/data`); the collection schema mirrors the app's in-memory data shape. **The anonymous visitor token gives no per-user identity ⇒ the collection is shared/public** (every visitor reads/writes the same data, not per-user, not cross-device-isolated); tell the user plainly. True per-user storage needs member auth, which is **deferred** (§ "Scope — deferred"). | Wix CMS (Wix Data) | `custom/cms/WIRING.md` § "Framework SPA — persistence swap" |
|
|
67
|
+
| Anything else | contact form (the floor) | Wix Forms | `custom/forms/WIRING.md` |
|
|
68
|
+
|
|
69
|
+
> **Reconciling "connect, don't design."** Augmentation injects the **one connected component** the backend connection requires (an RSVP/lead/contact `<form>`), styled from the design's tokens so it reads as native. It does **not** redesign or re-lay-out the site. "Connecting the dots" includes adding the one dot the design omitted.
|
|
70
|
+
|
|
71
|
+
> **Three connection kinds, not two** (this is a connection-*model* distinction — what to connect — not a framework fork). Beyond **wire an existing region** (swap the data source behind dynamic markup) and **augment with a component** (inject the one connected feature a static design omits), an **app with client/local state** needs a third: **persistence swap.** Such an app already *has* a working data layer (e.g. `localStorage` or in-memory state); the connection **replaces that layer with `@wix/data`** — the UI, component tree, and styling are untouched; only the load/save functions change. A todo app is not "anything else → contact form" (nonsense) — it is a persistence swap onto a CMS collection. The connection planner emits this as a `persistenceSwap` structure (`CONNECTION_PLAN.md` § "(c) Persistence swap"); the cms wiring agent executes it (`custom/cms/WIRING.md` § "Framework SPA — persistence swap"). *How* the swap is mechanically applied (CDN `<script>` vs bundled source) is the Build layer's concern (§ "The technical spine"). The resulting collection is **shared/public** — see the client-state row in the table above for the per-user caveat to surface to the user.
|
|
72
|
+
|
|
73
|
+
## Wiring discipline (applies to every `custom/<cap>/WIRING.md`)
|
|
74
|
+
|
|
75
|
+
- **Additive only.** Never restructure the user's layout or CSS. Swap the *data source* behind a region; inject new components self-contained. For a persistence swap, change only the load/save functions — leave the component tree and styling untouched.
|
|
76
|
+
- **Style from the design's tokens.** Read the site's CSS custom properties (`:root { --… }`) and reuse them verbatim in any injected component so it looks designed-in.
|
|
77
|
+
- **Inline `appId` literally** from `wix.config.json` as the `OAuthStrategy` `clientId`. No env vars at runtime.
|
|
78
|
+
- **The wiring *mechanic* is framework-keyed** (CDN `<script>` for `none`, bundled `import` + source edit for `own`) — the Build layer's concern, not fixed here (see § "The technical spine"). The per-capability guides describe the SDK *call shapes* (framework-blind); the Build cell decides the injection form. (For `none`: one `<script type="module">` per capability, or a shared client bootstrap + per-region render calls.)
|
|
79
|
+
- **Guard every SDK call** in try/catch. On a read error, leave the original sample markup visible; the injected component degrades gracefully if the backend is unreachable.
|
|
80
|
+
|
|
81
|
+
## Scope — deferred (tell the user plainly when relevant)
|
|
82
|
+
|
|
83
|
+
- **Member authentication** (login/logout, member-gated content, **per-user data isolation**) — needs the server OAuth callback routes `@wix/astro` provides; not possible on a pure static/SPA client. Consequence for client-state apps: the persistence-swap collection is shared/public (per-user is the deferred member-auth follow-on) — see the client-state row in the "Always connect" table for the caveat to surface.
|
|
84
|
+
- **SSR cart/session persistence across reload** — client-side cart works via `@wix/ecom` + checkout redirect; cookie-backed persistence needs the server runtime.
|
|
85
|
+
- **`auth.elevate` CMS queries** — elevation needs the app secret (server-only). CMS integration is **public-read + visitor-write collections only**.
|
|
86
|
+
- **Full re-design / re-layout** — out of scope; augmentation adds one connected component, never a redesign.
|
|
87
|
+
- **SSR frameworks the user hand-wrote** (Next.js / Nuxt / Remix and other server-rendered frameworks) — deferred; `wix release` uploads a static `outputDirectory`, and SSR needs a runtime host this track doesn't provide. **Pure client-build SPAs (Vite + React/Vue/Svelte and similar — `frontendBuild: own`) ARE supported** — bundled `@wix/sdk`, source-edit wiring, and the project's own `npm run build` before release, all the Build layer's concern (see § "The technical spine"). Connect stays framework-blind; the framework-build-class is what differs.
|
|
88
|
+
- **Custom domains** — out of scope; the skill releases to the default `*.wix-site-host.com` origin (which `init` already allow-lists). Attaching a custom domain later requires registering the new origin on the OAuth app separately — the skill does not do this.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-blog-wiring
|
|
3
|
+
description: "Integration-mode wiring subagent for the blog capability. Connects a brought-in static site's article markup to Wix Blog — replaces hard-coded post cards/snippets with live @wix/blog queries, rendered into the existing DOM template, client-side via @wix/sdk from CDN. Ricos rich content rendered to HTML on the client."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blog wiring (integration mode)
|
|
7
|
+
|
|
8
|
+
You wire the **blog capability** into a brought-in static site (`frontend = "custom"`). Replace hard-coded post snippets with live `@wix/blog` posts, rendering into the existing markup. Client-side vanilla JS, `@wix/sdk` from CDN, no build. Read `INSTRUCTIONS.md` § "The technical spine" + § "Wiring discipline", and `references/custom/stores/WIRING.md` § "The read-and-render pattern" — blog reuses it; only the entity differs.
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
- **`appId`** — `OAuthStrategy` `clientId`.
|
|
13
|
+
- **Binding-map entries** for `blog.posts` (list and/or `detail`).
|
|
14
|
+
- **Seeded post IDs/slugs** (from Seed).
|
|
15
|
+
|
|
16
|
+
## List
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<script type="module">
|
|
20
|
+
import { createClient, OAuthStrategy } from "https://esm.sh/@wix/sdk@1";
|
|
21
|
+
import { posts } from "https://esm.sh/@wix/blog@1";
|
|
22
|
+
|
|
23
|
+
const wix = createClient({ modules: { posts }, auth: OAuthStrategy({ clientId: "REPLACE_WITH_APP_ID" }) });
|
|
24
|
+
|
|
25
|
+
const list = document.querySelector("[data-blog-list], .post-list"); // binding-map anchor
|
|
26
|
+
const tpl = list?.querySelector(".post-card"); // binding-map template
|
|
27
|
+
if (list && tpl) {
|
|
28
|
+
try {
|
|
29
|
+
const { posts: items } = await wix.posts.queryPosts().limit(10).find();
|
|
30
|
+
const proto = tpl.cloneNode(true);
|
|
31
|
+
list.replaceChildren(...items.map((post) => {
|
|
32
|
+
const card = proto.cloneNode(true);
|
|
33
|
+
const t = card.querySelector(".title"); if (t) t.textContent = post.title ?? "";
|
|
34
|
+
const ex = card.querySelector(".excerpt"); if (ex) ex.textContent = post.excerpt ?? "";
|
|
35
|
+
const link = card.querySelector("a"); if (link && post.slug) link.href = `/post/${post.slug}`;
|
|
36
|
+
const img = card.querySelector("img"); if (img && post.media?.wixMedia?.image) img.src = post.media.wixMedia.image.url ?? img.src;
|
|
37
|
+
return card;
|
|
38
|
+
}));
|
|
39
|
+
} catch (err) { console.error("[wix-blog] post query failed:", err); }
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Apply the binding-map's actual selectors/field paths.
|
|
45
|
+
|
|
46
|
+
## Detail + Ricos
|
|
47
|
+
|
|
48
|
+
For a `detail` region (slug from `location.pathname`): `wix.posts.getPostBySlug(slug)` (or `queryPosts().eq("slug", slug)`), bind title/date, and render the post's **rich content**. Ricos content (`post.richContent`) is a structured document — render it to HTML client-side with `@wix/ricos` (`import { RicosViewer } from "https://esm.sh/@wix/ricos@1"` is React-based; for a no-framework site prefer the post's pre-rendered HTML field when available, else mount a minimal Ricos-to-HTML conversion). If rich rendering is heavy for a static site, bind the post's plain `contentText`/HTML excerpt and link out. **Note this tradeoff in the return.**
|
|
49
|
+
|
|
50
|
+
## Discipline & return
|
|
51
|
+
|
|
52
|
+
Additive; render into the existing template; inline `appId`; guard calls (samples are the fallback). Return per `shared/RETURN_CONTRACT.md`: files edited, anchors wired, whether full Ricos or excerpt rendering was used.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-bookings-wiring
|
|
3
|
+
description: "Integration-mode wiring subagent for the bookings capability. Wires a services list + availability + a schema-driven booking form into a brought-in site, then books via the ecom Cart V2 sequence (createBooking → cart → placeOrder / hosted-checkout redirect). Client-side @wix/sdk; no server runtime, no confirmBooking."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bookings — integration wiring (own / static)
|
|
7
|
+
|
|
8
|
+
You wire the **bookings capability** into a brought-in site (`frontend = "custom"`).
|
|
9
|
+
Client-side `@wix/sdk` — CDN imports for `none`-build, bundled for `own`-build
|
|
10
|
+
(same calls). Read `INSTRUCTIONS.md` § "The technical spine" + § "Wiring discipline".
|
|
11
|
+
|
|
12
|
+
> **The logic is shared.** The step model, the booking SDK sequence, the
|
|
13
|
+
> schema-driven form, and the gotchas are in `../../bookings/FLOW.md` — read it
|
|
14
|
+
> first. The astro vertical's React examples
|
|
15
|
+
> (`../../astro/templates/bookings/*` incl. `bookingDriver.ts`) are the reference
|
|
16
|
+
> implementation; here you run the **same SDK calls** through an `OAuthStrategy`
|
|
17
|
+
> visitor client (adapt the React idiom to whatever framework the site uses).
|
|
18
|
+
|
|
19
|
+
> **Scope (same as the astro vertical — the astro/own distinction collapses).**
|
|
20
|
+
> Services display + a week-calendar availability picker + a schema-driven booking
|
|
21
|
+
> form + the **ecom Cart V2 booking sequence**, all client-side under the visitor
|
|
22
|
+
> identity (no server elevation): `createBooking` (→ `CREATED`) → `createCart` →
|
|
23
|
+
> `calculateCart` → `isCheckoutRequired ? ecomCheckout redirect : placeOrder`.
|
|
24
|
+
> **No `confirmBooking`** — the cart holds the seat, so a client-only site
|
|
25
|
+
> completes the whole flow.
|
|
26
|
+
|
|
27
|
+
## Inputs (inlined in your prompt)
|
|
28
|
+
- **`appId`** — `OAuthStrategy` `clientId`.
|
|
29
|
+
- **Seeded services** — read your `bookings` slice from `.wix/seeded.json`.
|
|
30
|
+
- The site's CSS token names (style additively from them).
|
|
31
|
+
|
|
32
|
+
## The client (acquire once)
|
|
33
|
+
```js
|
|
34
|
+
import { createClient, OAuthStrategy } from "https://esm.sh/@wix/sdk@1"; // bundled for own-build
|
|
35
|
+
import { services, availabilityTimeSlots, eventTimeSlots, bookings } from "https://esm.sh/@wix/bookings@1";
|
|
36
|
+
import { forms } from "https://esm.sh/@wix/forms@1";
|
|
37
|
+
import * as cartV2 from "https://esm.sh/@wix/auto_sdk_ecom_cart-v-2@1";
|
|
38
|
+
import { redirects } from "https://esm.sh/@wix/redirects@1";
|
|
39
|
+
|
|
40
|
+
const wix = createClient({
|
|
41
|
+
modules: { services, availabilityTimeSlots, eventTimeSlots, bookings, forms, redirects, ...cartV2 },
|
|
42
|
+
auth: OAuthStrategy({ clientId: "REPLACE_WITH_APP_ID" }),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Render services
|
|
47
|
+
```js
|
|
48
|
+
// The filter MUST include appId; pass conditionalFields, then .find().
|
|
49
|
+
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
50
|
+
const { items } = await wix.services.queryServices({
|
|
51
|
+
query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } },
|
|
52
|
+
conditionalFields: ["STAFF_MEMBER_DETAILS"],
|
|
53
|
+
}).find();
|
|
54
|
+
const visible = items.filter((s) => !s.hidden);
|
|
55
|
+
// name s.name · tagline s.tagLine · slug s.mainSlug?.name ·
|
|
56
|
+
// duration s.schedule?.availabilityConstraints?.sessionDurations?.[0] ·
|
|
57
|
+
// price s.payment?.fixed?.price ({ value, currency } — value is a string).
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Availability (week calendar)
|
|
61
|
+
Render a **week calendar** (day strip → the day's times), not a flat grid — see
|
|
62
|
+
`../../bookings/FLOW.md` § 5. `fromLocalDate`/`toLocalDate` are **local** strings
|
|
63
|
+
(`YYYY-MM-DDThh:mm:ss`, no `Z`) + a `timeZone`.
|
|
64
|
+
```js
|
|
65
|
+
// APPOINTMENT — serviceId is a single GUID STRING:
|
|
66
|
+
const a = await wix.availabilityTimeSlots.listAvailabilityTimeSlots({ serviceId, fromLocalDate, toLocalDate, timeZone, bookable: true, cursorPaging: { limit: 100 } });
|
|
67
|
+
// slots a.timeSlots[] — localStartDate/localEndDate/scheduleId at the TOP level.
|
|
68
|
+
// CLASS — different namespace, PLURAL serviceIds; slots carry eventInfo.eventId, no scheduleId:
|
|
69
|
+
const c = await wix.eventTimeSlots.listEventTimeSlots({ serviceIds: [serviceId], fromLocalDate, toLocalDate, timeZone, includeNonBookable: false });
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Booking form — schema-driven
|
|
73
|
+
The booking form is a `@wix/forms` form on the service (`service.form._id`). Read
|
|
74
|
+
its schema and render fields by `componentType`, collecting values keyed by
|
|
75
|
+
`target` (`../../bookings/FLOW.md` § 4):
|
|
76
|
+
```js
|
|
77
|
+
const { form } = await wix.forms.getForm(service.form._id);
|
|
78
|
+
const RENDERABLE = ["TEXT_INPUT", "PHONE_INPUT", "DROPDOWN"];
|
|
79
|
+
const fields = (form.formFields ?? []).filter(
|
|
80
|
+
(f) => f.fieldType === "INPUT" && !f.hidden &&
|
|
81
|
+
RENDERABLE.includes(f.inputOptions?.stringOptions?.componentType),
|
|
82
|
+
);
|
|
83
|
+
// render each by f.inputOptions.stringOptions.componentType; collect values by f.inputOptions.target.
|
|
84
|
+
// SKIP complex object-valued fields (e.g. multi-line ADDRESS, no string componentType) —
|
|
85
|
+
// sending a string for them fails createBooking with "must be object". Only
|
|
86
|
+
// first_name/last_name/email are enforced, so skipping optional complex fields is safe.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Book — the ecom Cart V2 sequence
|
|
90
|
+
Mirror `../../astro/templates/bookings/bookingDriver.ts` (the exact payloads).
|
|
91
|
+
Run the same sequence through `wix.*`; the cart holds the seat, so no elevation.
|
|
92
|
+
```js
|
|
93
|
+
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
94
|
+
const STAFF_MEMBER_RESOURCE_TYPE_ID = "1cd44cf8-756f-41c3-bd90-3e2ffcaf1155";
|
|
95
|
+
|
|
96
|
+
// Build the slot. APPOINTMENT carries scheduleId; CLASS carries eventId (Wix
|
|
97
|
+
// derives the rest). No staff chosen → the ANY_RESOURCE fallback.
|
|
98
|
+
const slot =
|
|
99
|
+
slotType === "CLASS"
|
|
100
|
+
? { serviceId, eventId, timezone }
|
|
101
|
+
: {
|
|
102
|
+
serviceId, scheduleId, startDate, endDate, timezone,
|
|
103
|
+
resourceSelections: [{ resourceTypeId: STAFF_MEMBER_RESOURCE_TYPE_ID, selectionMethod: "ANY_RESOURCE" }],
|
|
104
|
+
location: { locationType: "OWNER_BUSINESS" },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// selectedPaymentOption must match the service: online-only → ONLINE,
|
|
108
|
+
// in-person-only → OFFLINE, else ONLINE. DERIVE it — do not hardcode ONLINE: a
|
|
109
|
+
// free / pay-in-person service booked ONLINE is rejected by the cart with
|
|
110
|
+
// INSUFFICIENT_INVENTORY (it's the only service kind that reaches placeOrder).
|
|
111
|
+
const o = service.payment?.options;
|
|
112
|
+
const selectedPaymentOption =
|
|
113
|
+
o?.online && !o?.inPerson ? "ONLINE" : !o?.online && o?.inPerson ? "OFFLINE" : "ONLINE";
|
|
114
|
+
|
|
115
|
+
// 1. createBooking → CREATED. Arg 1 is the BOOKING object (slot nested under
|
|
116
|
+
// bookedEntity); formSubmission/sendSmsReminder go in the options arg.
|
|
117
|
+
const { booking } = await wix.bookings.createBooking(
|
|
118
|
+
{ selectedPaymentOption, totalParticipants: 1, bookedEntity: { slot } },
|
|
119
|
+
{ formSubmission, sendSmsReminder: true }, // formSubmission keyed by each field's `target`
|
|
120
|
+
);
|
|
121
|
+
// 2. createCart — one catalog item per booking._id, catalogReference.appId = BOOKING_APP_ID, channel WEB.
|
|
122
|
+
const cart = await wix.createCart({ catalogItems: [{ quantity: 1, catalogReference: { catalogItemId: booking._id, appId: BOOKING_APP_ID } }], cart: { source: { channelType: "WEB" } } });
|
|
123
|
+
// 3. calculateCart → { cart, summary }, then compute the checkout decision.
|
|
124
|
+
const { cart: calc, summary } = await wix.calculateCart(cart._id);
|
|
125
|
+
const checkoutRequired =
|
|
126
|
+
service.bookingPolicy?.cancellationFeePolicy?.enabled
|
|
127
|
+
? true
|
|
128
|
+
: Number(summary?.priceSummary?.total?.amount ?? 0) === 0
|
|
129
|
+
? false
|
|
130
|
+
: calc?.lineItems?.[0]?.paymentConfig?.paymentOption !== "FULL_PAYMENT_OFFLINE";
|
|
131
|
+
// 4. paid → redirect (ecomCheckout.checkoutId = cartId) ; free/offline → placeOrder.
|
|
132
|
+
if (checkoutRequired) {
|
|
133
|
+
const { redirectSession } = await wix.redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks: { postFlowUrl: window.location.href } });
|
|
134
|
+
window.location.href = redirectSession.fullUrl;
|
|
135
|
+
} else {
|
|
136
|
+
await wix.placeOrder(cart._id); // booked — no redirect
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> Confirm the `@wix/auto_sdk_ecom_cart-v-2` function registration shape on a
|
|
141
|
+
> CDN/own client at wiring time; the payloads above match the astro `bookingDriver.ts`.
|
|
142
|
+
|
|
143
|
+
## Out of v1
|
|
144
|
+
Waitlist and on-site manage/cancel — out of scope (same as the astro vertical).
|
|
145
|
+
Show bookable slots only; post-booking self-service is handled by the Wix-hosted
|
|
146
|
+
flow / member area.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-cms-wiring
|
|
3
|
+
description: "Integration-mode wiring subagent for the cms capability. Connects a brought-in static site's repeated content (portfolio items, team, FAQ, resources) to a Wix CMS (Wix Data) collection — live queries rendered into the existing DOM template, client-side via @wix/sdk from CDN. PUBLIC-READ collections only (no auth.elevate on a static site)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# CMS wiring (integration mode)
|
|
7
|
+
|
|
8
|
+
You wire the **cms capability** into a brought-in (`frontend = "custom"`) frontend. There are **two shapes**, keyed on `frontendBuild` (the *mechanic* is the Build layer's call — `BUILD-own-build.md` § "Wiring cell"; this guide gives the SDK call shapes for both):
|
|
9
|
+
- **Static HTML (`none`)** — replace hard-coded repeated content (portfolio grid, team list, FAQ, resource cards) with live `@wix/data` collection items rendered into the existing markup. Client-side vanilla JS, `@wix/sdk` from **CDN**, no build. (§ "List" below.)
|
|
10
|
+
- **Framework SPA (`own`)** — a **persistence swap**: rewrite the app's source data layer (or write a fresh data module) to call `@wix/data`, `@wix/sdk` **bundled** via `npm`. (§ "Framework SPA — persistence swap" below.)
|
|
11
|
+
|
|
12
|
+
Read `INSTRUCTIONS.md` § "The technical spine" + § "Wiring discipline", and `references/custom/stores/WIRING.md` § "The read-and-render pattern" — the static cms path reuses it.
|
|
13
|
+
|
|
14
|
+
> **Public-read collections ONLY.** A static site has no server, so **`auth.elevate` is not available** — it needs the app secret. The seeded collection MUST have **public read** permission for anonymous visitors, and integration-mode CMS is **read-only on the client** (visitor writes only via Forms). Seed sets the collection's read permission to public; if a collection isn't public-readable, the query returns nothing — surface that, don't try to elevate.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
- **`appId`** — `OAuthStrategy` `clientId`.
|
|
19
|
+
- **Binding-map entries** for `data.items`: `{ anchor, template, bindings }` + the **`collectionId`** (from Seed).
|
|
20
|
+
- **Seeded `collectionId` + `itemIds`**.
|
|
21
|
+
|
|
22
|
+
## List
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script type="module">
|
|
26
|
+
import { createClient, OAuthStrategy } from "https://esm.sh/@wix/sdk@1";
|
|
27
|
+
import { items } from "https://esm.sh/@wix/data@1";
|
|
28
|
+
|
|
29
|
+
const wix = createClient({ modules: { items }, auth: OAuthStrategy({ clientId: "REPLACE_WITH_APP_ID" }) });
|
|
30
|
+
|
|
31
|
+
const COLLECTION = "REPLACE_WITH_COLLECTION_ID";
|
|
32
|
+
const grid = document.querySelector("[data-cms-list], .portfolio-grid"); // binding-map anchor
|
|
33
|
+
const tpl = grid?.querySelector(".item-card"); // binding-map template
|
|
34
|
+
if (grid && tpl) {
|
|
35
|
+
try {
|
|
36
|
+
const { items: rows } = await wix.items.query(COLLECTION).limit(50).find();
|
|
37
|
+
const proto = tpl.cloneNode(true);
|
|
38
|
+
grid.replaceChildren(...rows.map((row) => {
|
|
39
|
+
const card = proto.cloneNode(true);
|
|
40
|
+
// bind by the binding-map field paths (collection field keys)
|
|
41
|
+
const title = card.querySelector(".title"); if (title) title.textContent = row.title ?? "";
|
|
42
|
+
const img = card.querySelector("img"); if (img && row.image) img.src = row.image;
|
|
43
|
+
const body = card.querySelector(".body"); if (body) body.textContent = row.description ?? "";
|
|
44
|
+
return card;
|
|
45
|
+
}));
|
|
46
|
+
} catch (err) { console.error("[wix-data] collection query failed:", err); }
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Apply the binding-map's actual selectors and the seeded collection's actual field keys. The `items` API is the `items` named export of `@wix/data` (CDN-pinned in the snippet above) — `items.query/insert/update/remove` plus the `bulk*` helpers. (See the import callout under § "Framework SPA — persistence swap" for the package rule.)
|
|
52
|
+
|
|
53
|
+
## Framework SPA — persistence swap (`frontendBuild: own`)
|
|
54
|
+
|
|
55
|
+
When the brought-in (or scaffolded) frontend is a **client-build SPA** with a client-state data layer, the connection is a **persistence swap**, not a `<script>` injection: you **rewrite the source data layer** to call the Wix Data `items` API. The SDK is **bundled** (the app `npm install`ed `@wix/sdk @wix/data`), so you `import` it — **never the CDN `esm.sh` form**. The component tree, JSX, and styling are untouched; only the load/save functions change. This dispatch is keyed on the connection plan's `persistenceSwap` entries (`CONNECTION_PLAN.md` § "(c)"), one per `sourceFile`.
|
|
56
|
+
|
|
57
|
+
> **Shared/public collection — say so.** The visitor token gives no per-user identity, so the collection is **shared across all visitors** (not per-user, not cross-device). The collection is seeded **public-read + visitor-write**; surface the shared-data caveat (the orchestrator already told the user). Per-user storage needs member auth — deferred.
|
|
58
|
+
|
|
59
|
+
**Inputs:** `persistenceSwap` entries (`{ sourceFile, dataLayer: {load, save}, inferredShape }`), the seeded `collectionId` per entity-shape, and `appId`.
|
|
60
|
+
|
|
61
|
+
> **Items API: `import { items } from "@wix/data"`.** The Wix Data items API is the `items` named export of **`@wix/data`** (`items.query/insert/update/remove`, `items.bulk*`). Import it, elevate via `createClient({ modules: { items } })`, and call `wix.items.*`. **Do not** import a namespace from `@wix/wix-data-items-sdk` — `@wix/data` is the single documented package the whole skill uses (`references/astro/cms/CMS_FOUNDATIONS.md`, `SETUP.md`).
|
|
62
|
+
|
|
63
|
+
> **🛑 `update()` REPLACES the whole item — it does NOT patch.** Per the [docs](https://dev.wix.com/docs/sdk/business-solutions/data/items/introduction): *"Updating a data item replaces the existing item entirely. If the existing item had fields with values and those fields aren't included in the updated item, the values in those properties are lost."* So `update(COLLECTION, { _id, done })` **wipes `text`, `listId`, and every other field** — the classic "toggling done erases the name" bug. **Always send the FULL item** on `update`. A client-state SPA already holds the whole record in memory — pass all of it. (Same footgun the images phase documents for the REST `PUT` — `images/INSTRUCTIONS.md` § "CMS Items".)
|
|
64
|
+
|
|
65
|
+
**The bundled client + CRUD call shapes** (framework-blind — adapt the *idiom* to React/Vue/Svelte, the calls are identical):
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { createClient, OAuthStrategy } from "@wix/sdk"; // bundled import — NOT esm.sh
|
|
69
|
+
import { items } from "@wix/data"; // Wix Data items API (query/insert/update/remove/bulk*)
|
|
70
|
+
|
|
71
|
+
const wix = createClient({ modules: { items }, auth: OAuthStrategy({ clientId: "REPLACE_WITH_APP_ID" }) });
|
|
72
|
+
const COLLECTION = "REPLACE_WITH_COLLECTION_ID";
|
|
73
|
+
|
|
74
|
+
// load() — was: read localStorage / in-memory
|
|
75
|
+
export const load = async () => {
|
|
76
|
+
const { items: rows } = await wix.items.query(COLLECTION).limit(100).find();
|
|
77
|
+
return rows; // map _id ↔ the app's id field as the app expects
|
|
78
|
+
};
|
|
79
|
+
// create — was: push to array + persist
|
|
80
|
+
export const add = (item) => wix.items.insert(COLLECTION, item); // returns the created item (has _id)
|
|
81
|
+
// update — FULL REPLACE: pass the ENTIRE item, not just the changed field(s)
|
|
82
|
+
export const update = (item) => wix.items.update(COLLECTION, item); // item MUST carry _id + every field to keep
|
|
83
|
+
// delete — was: filter out + persist
|
|
84
|
+
export const remove = (id) => wix.items.remove(COLLECTION, id);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **Partial update?** If you only have the id + the one changed field (not the full record), use **`items.bulkPatch(COLLECTION, [id]).setField("done", value).run()`** — `bulkPatch` modifies *only* the named fields and leaves the rest intact (unlike `update`). For the common SPA case the full record is already in state, so a full `update(item)` is simpler and correct.
|
|
88
|
+
|
|
89
|
+
**Two operations, same call shapes:**
|
|
90
|
+
- **connect × own** — *rewrite* the existing data layer in `persistenceSwap.sourceFile`: replace the named `load`/`save` functions/effects with the calls above. Keep the app's existing call sites and state shape; only the bodies change. Map the app's `id` to Wix Data's `_id`. **On every update/toggle, send the full item** (read it from local state and spread all fields) — never `{ _id, oneField }`.
|
|
91
|
+
- **create × own** — *write a fresh* data module (e.g. `src/wixData.js`) exposing the `load`/`add`/`update`/`remove` surface the generated components import. No archaeology — the skill controls the source.
|
|
92
|
+
|
|
93
|
+
**Local-state vs `@wix/data` sync** (optimistic local update + async write-through, vs read-through on every change) is a **per-app judgment the wiring agent makes at runtime** — not a pattern this guide prescribes. Pick what fits the app; keep edits surgical and diffable.
|
|
94
|
+
|
|
95
|
+
## Discipline & return
|
|
96
|
+
|
|
97
|
+
Additive; for static (`none`) render into the existing template, for SPA (`own`) rewrite only the data-layer functions; inline `appId` + `collectionId`; guard calls; **public-read only** (no elevate). Return per `shared/RETURN_CONTRACT.md`: files edited, the connection kind (`binding` | `persistenceSwap`), `collectionId`(s), and a flag if the collection wasn't public-readable.
|