@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.
Files changed (126) hide show
  1. package/README.md +3 -7
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +2 -0
  4. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
  5. package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
  6. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +14 -0
  7. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +11 -6
  8. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +3 -17
  9. package/skills/wix-app/references/editor-react-component/SSR.md +119 -0
  10. package/skills/wix-design-system/SKILL.md +5 -1
  11. package/skills/wix-headless/SKILL.md +55 -32
  12. package/skills/wix-headless/references/BUILD-astro.md +244 -0
  13. package/skills/wix-headless/references/BUILD-own-build.md +167 -0
  14. package/skills/wix-headless/references/BUILD.md +50 -252
  15. package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
  16. package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
  17. package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
  18. package/skills/wix-headless/references/DISCOVERY.md +41 -422
  19. package/skills/wix-headless/references/PLAN-connect.md +19 -0
  20. package/skills/wix-headless/references/PLAN-create.md +17 -0
  21. package/skills/wix-headless/references/PLAN.md +37 -27
  22. package/skills/wix-headless/references/SEED.md +27 -18
  23. package/skills/wix-headless/references/SETUP.md +32 -213
  24. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
  25. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +45 -0
  26. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +58 -0
  27. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
  28. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
  29. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
  30. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
  31. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
  32. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +47 -27
  33. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
  34. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
  35. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
  36. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
  37. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
  38. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
  39. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
  40. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
  41. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
  42. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
  43. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
  44. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
  45. package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
  46. package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
  47. package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
  48. package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
  49. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +233 -0
  50. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +136 -0
  51. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
  52. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +54 -0
  53. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
  54. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +49 -0
  55. package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
  56. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +356 -0
  57. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +133 -0
  58. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +73 -0
  59. package/skills/wix-headless/references/astro/templates/global.css +2 -2
  60. package/skills/wix-headless/references/astro/templates/index.astro +1 -1
  61. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
  62. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
  63. package/skills/wix-headless/references/bookings/FLOW.md +185 -0
  64. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +90 -0
  65. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
  66. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
  67. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
  68. package/skills/wix-headless/references/commands/known-apps.json +2 -1
  69. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
  70. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
  71. package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
  72. package/skills/wix-headless/references/custom/bookings/WIRING.md +146 -0
  73. package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
  74. package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
  75. package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
  76. package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
  77. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
  78. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
  79. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
  80. package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
  81. package/skills/wix-headless/references/seed-recipes.md +2 -2
  82. package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
  83. package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
  84. package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
  85. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
  86. package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
  87. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
  88. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
  89. package/skills/wix-headless/references/shared/STYLING.md +8 -18
  90. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
  91. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
  92. package/skills/wix-headless/references/verticals/_schema.json +1 -1
  93. package/skills/wix-headless/references/verticals/_schema.md +3 -3
  94. package/skills/wix-headless/references/verticals/blog.md +1 -1
  95. package/skills/wix-headless/references/verticals/bookings.md +39 -0
  96. package/skills/wix-headless/references/verticals/cms.md +1 -1
  97. package/skills/wix-headless/references/verticals/ecom.md +2 -2
  98. package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
  99. package/skills/wix-headless/references/verticals/stores.md +1 -1
  100. package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
  101. package/skills/wix-headless/scripts/compose.mjs +540 -0
  102. package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
  103. package/skills/wix-headless/scripts/flatten.sh +61 -0
  104. package/skills/wix-headless/scripts/generate-images.mjs +242 -0
  105. package/skills/wix-headless/scripts/scaffold.sh +47 -16
  106. package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
  107. package/skills/wix-manage/SKILL.md +13 -1
  108. package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
  109. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
  110. package/skills/wix-manage/references/bookings/create-appointment-service.md +266 -0
  111. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +37 -0
  112. package/skills/wix-manage/references/bookings/create-class-service.md +245 -0
  113. package/skills/wix-manage/references/bookings/create-course-service.md +249 -0
  114. package/skills/wix-manage/references/cms/cms-data-items-crud.md +3 -2
  115. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
  116. package/skills/wix-manage/references/cms/cms-schema-management.md +2 -2
  117. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
  118. package/skills/wix-manage/references/forms/create-form.md +49 -1
  119. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
  120. package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
  121. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
  122. package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
  123. package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
  124. package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
  125. package/skills/wix-headless/scripts/preview.sh +0 -45
  126. package/skills/wix-headless/scripts/release.sh +0 -73
@@ -0,0 +1,356 @@
1
+ /* src/styles/components-bookings.css
2
+ *
3
+ * Bookings-pack component CSS. Pre-copied by the orchestrator before the build
4
+ * wave (it is brand-agnostic var(--token) CSS). The designer's global.css does
5
+ * NOT publish these classes — they live here. See references/shared/STYLING.md
6
+ * § "Component-specific CSS is owned by the component, not the designer".
7
+ *
8
+ * Class groups:
9
+ * - .service-card / .service-grid / .service-card-* → ServiceCard.astro, services/index.astro
10
+ * - .availability-* / .time-slot* → AvailabilityCalendar.tsx (week calendar + slots)
11
+ * - .booking-* → BookingForm.tsx (schema-driven form)
12
+ *
13
+ * Primary actions (a selected slot, the confirm button) use var(--color-accent)
14
+ * so the flow matches the site's primary-action styling.
15
+ *
16
+ * Direct var(--token) only (no @apply). Fallback chains for sometimes-published
17
+ * tokens, e.g. var(--color-paper-warm, var(--color-paper, var(--color-cream))).
18
+ */
19
+
20
+ /* ── Service grid + card ────────────────────────────────────────────────────── */
21
+ .service-grid {
22
+ display: grid;
23
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
24
+ gap: var(--spacing-xl);
25
+ transition: opacity 200ms ease;
26
+ }
27
+ body[data-navigating="true"] .service-grid {
28
+ opacity: 0.55;
29
+ pointer-events: none;
30
+ }
31
+
32
+ .service-card {
33
+ position: relative;
34
+ display: flex;
35
+ flex-direction: column;
36
+ background: var(--color-paper, var(--color-cream));
37
+ border-radius: var(--radius-xl);
38
+ overflow: hidden;
39
+ text-decoration: none;
40
+ color: inherit;
41
+ transition: transform 280ms ease, box-shadow 280ms ease;
42
+ box-shadow: var(--shadow-card, 0 4px 16px -8px rgba(0, 0, 0, 0.15));
43
+ }
44
+ .service-card:hover {
45
+ transform: translateY(-4px);
46
+ }
47
+ .service-card:hover .service-card-image img {
48
+ transform: scale(1.04);
49
+ }
50
+
51
+ .service-card-image {
52
+ aspect-ratio: 3 / 2;
53
+ overflow: hidden;
54
+ background: var(--color-paper-warm, var(--color-surface-secondary, var(--color-cream)));
55
+ }
56
+ .service-card-image img {
57
+ width: 100%;
58
+ height: 100%;
59
+ object-fit: cover;
60
+ transition: transform 600ms ease;
61
+ }
62
+
63
+ .service-card-meta {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: var(--spacing-xs);
67
+ padding: var(--spacing-md) var(--spacing-md) var(--spacing-lg);
68
+ flex: 1;
69
+ }
70
+ .service-card-name {
71
+ margin: 0;
72
+ font-family: var(--font-display);
73
+ font-size: 1.125rem;
74
+ line-height: 1.25;
75
+ color: var(--color-ink);
76
+ }
77
+ .service-card-tagline {
78
+ margin: 0;
79
+ font-family: var(--font-body);
80
+ font-size: 0.875rem;
81
+ color: var(--color-ink-soft, var(--color-mute));
82
+ line-height: 1.45;
83
+ }
84
+ .service-card-price {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: var(--spacing-sm);
88
+ margin-top: auto;
89
+ padding-top: var(--spacing-sm);
90
+ font-family: var(--font-body);
91
+ font-size: 0.875rem;
92
+ color: var(--color-mute);
93
+ }
94
+ .service-card-price span + span::before {
95
+ content: "·";
96
+ padding-right: var(--spacing-sm);
97
+ color: var(--color-rule);
98
+ }
99
+
100
+ /* ── Availability — week calendar ───────────────────────────────────────────── */
101
+ .availability-calendar {
102
+ display: flex;
103
+ flex-direction: column;
104
+ gap: var(--spacing-lg);
105
+ }
106
+
107
+ .availability-week-nav {
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: space-between;
111
+ gap: var(--spacing-md);
112
+ padding: var(--spacing-sm) 0;
113
+ border-bottom: 1px solid var(--color-rule);
114
+ }
115
+ .availability-week-label {
116
+ font-family: var(--font-display);
117
+ font-size: 1rem;
118
+ color: var(--color-ink);
119
+ flex: 1;
120
+ text-align: center;
121
+ }
122
+
123
+ .availability-nav-btn {
124
+ padding: var(--spacing-sm) var(--spacing-md);
125
+ background: transparent;
126
+ border: 1px solid var(--color-rule);
127
+ border-radius: var(--radius-sm);
128
+ color: var(--color-ink);
129
+ font-family: var(--font-body);
130
+ font-size: 0.875rem;
131
+ cursor: pointer;
132
+ transition: background-color 150ms ease, border-color 150ms ease;
133
+ }
134
+ .availability-nav-btn:hover:not(:disabled) {
135
+ background: var(--color-paper-warm, var(--color-cream));
136
+ border-color: var(--color-ink);
137
+ }
138
+ .availability-nav-btn:disabled {
139
+ opacity: 0.35;
140
+ cursor: not-allowed;
141
+ }
142
+
143
+ /* The 7-day strip. */
144
+ .availability-day-strip {
145
+ display: grid;
146
+ grid-template-columns: repeat(7, 1fr);
147
+ gap: var(--spacing-sm);
148
+ }
149
+ .availability-day {
150
+ display: flex;
151
+ flex-direction: column;
152
+ align-items: center;
153
+ gap: 2px;
154
+ padding: var(--spacing-sm) 0;
155
+ border: 1px solid var(--color-rule);
156
+ border-radius: var(--radius-sm);
157
+ background: var(--color-paper, var(--color-cream));
158
+ color: var(--color-ink);
159
+ font-family: var(--font-body);
160
+ cursor: pointer;
161
+ transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
162
+ }
163
+ .availability-day-name {
164
+ font-size: 0.7rem;
165
+ text-transform: uppercase;
166
+ letter-spacing: 0.04em;
167
+ color: var(--color-mute);
168
+ }
169
+ .availability-day-num {
170
+ font-size: 1rem;
171
+ font-weight: 600;
172
+ }
173
+ .availability-day--has-slots:hover {
174
+ border-color: var(--color-accent);
175
+ }
176
+ .availability-day--empty {
177
+ opacity: 0.4;
178
+ cursor: not-allowed;
179
+ }
180
+ .availability-day--selected {
181
+ background: var(--color-accent);
182
+ border-color: var(--color-accent);
183
+ color: var(--color-paper);
184
+ }
185
+ .availability-day--selected .availability-day-name {
186
+ color: var(--color-paper);
187
+ }
188
+
189
+ .availability-slots {
190
+ display: grid;
191
+ grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
192
+ gap: var(--spacing-sm);
193
+ }
194
+ .availability-loading,
195
+ .availability-empty,
196
+ .availability-error {
197
+ font-family: var(--font-body);
198
+ font-size: 0.9375rem;
199
+ color: var(--color-ink-soft, var(--color-mute));
200
+ padding: var(--spacing-lg) 0;
201
+ text-align: center;
202
+ }
203
+ .availability-empty {
204
+ display: flex;
205
+ flex-direction: column;
206
+ align-items: center;
207
+ gap: var(--spacing-md);
208
+ }
209
+ .availability-error {
210
+ color: var(--color-error, var(--color-accent));
211
+ }
212
+
213
+ /* ── Time-slot buttons ──────────────────────────────────────────────────────── */
214
+ .time-slot {
215
+ padding: var(--spacing-sm) var(--spacing-md);
216
+ border: 1px solid var(--color-rule);
217
+ border-radius: var(--radius-sm);
218
+ background: var(--color-paper, var(--color-cream));
219
+ color: var(--color-ink);
220
+ font-family: var(--font-body);
221
+ font-size: 0.875rem;
222
+ text-align: center;
223
+ cursor: pointer;
224
+ transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
225
+ }
226
+ .time-slot-time {
227
+ font-weight: 500;
228
+ }
229
+ .time-slot--available {
230
+ border-color: var(--color-ink);
231
+ }
232
+ .time-slot--available:hover {
233
+ background: var(--color-accent);
234
+ border-color: var(--color-accent);
235
+ color: var(--color-paper);
236
+ }
237
+ .time-slot--selected {
238
+ background: var(--color-accent);
239
+ border-color: var(--color-accent);
240
+ color: var(--color-paper);
241
+ font-weight: 600;
242
+ }
243
+
244
+ /* ── Booking form (schema-driven) ───────────────────────────────────────────── */
245
+ .booking-form {
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: var(--spacing-lg);
249
+ padding: var(--spacing-xl);
250
+ border: 1px solid var(--color-rule);
251
+ border-radius: var(--radius-md);
252
+ background: var(--color-paper-warm, var(--color-paper, var(--color-cream)));
253
+ }
254
+
255
+ .booking-form-summary {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: var(--spacing-xs);
259
+ padding-bottom: var(--spacing-md);
260
+ border-bottom: 1px solid var(--color-rule);
261
+ }
262
+ .booking-form-summary strong {
263
+ font-family: var(--font-display);
264
+ font-size: 1.125rem;
265
+ color: var(--color-ink);
266
+ }
267
+ .booking-form-summary span {
268
+ font-family: var(--font-body);
269
+ font-size: 0.9375rem;
270
+ color: var(--color-ink-soft, var(--color-mute));
271
+ }
272
+ .booking-form-change {
273
+ align-self: flex-start;
274
+ margin-top: var(--spacing-xs);
275
+ background: none;
276
+ border: none;
277
+ padding: 0;
278
+ font-family: var(--font-body);
279
+ font-size: 0.8125rem;
280
+ color: var(--color-mute);
281
+ text-decoration: underline;
282
+ cursor: pointer;
283
+ }
284
+ .booking-form-change:hover {
285
+ color: var(--color-ink);
286
+ }
287
+
288
+ .booking-field {
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: var(--spacing-xs);
292
+ }
293
+ .booking-label {
294
+ font-family: var(--font-display);
295
+ font-size: 0.75rem;
296
+ text-transform: uppercase;
297
+ letter-spacing: 0.05em;
298
+ color: var(--color-ink-soft, var(--color-mute));
299
+ }
300
+ .booking-required {
301
+ color: var(--color-accent);
302
+ }
303
+
304
+ .booking-input {
305
+ width: 100%;
306
+ padding: var(--spacing-sm) var(--spacing-md);
307
+ font-family: var(--font-body);
308
+ font-size: 0.9375rem;
309
+ border: 1px solid var(--color-rule);
310
+ border-radius: var(--radius-sm);
311
+ background: var(--color-paper, var(--color-cream));
312
+ color: var(--color-ink);
313
+ transition: border-color 150ms ease;
314
+ box-sizing: border-box;
315
+ }
316
+ .booking-input:focus {
317
+ outline: none;
318
+ border-color: var(--color-accent);
319
+ }
320
+ textarea.booking-input {
321
+ resize: vertical;
322
+ min-height: 6rem;
323
+ }
324
+
325
+ .booking-error {
326
+ font-family: var(--font-body);
327
+ font-size: 0.875rem;
328
+ color: var(--color-error, var(--color-accent));
329
+ padding: var(--spacing-sm) var(--spacing-md);
330
+ border: 1px solid var(--color-error, var(--color-accent));
331
+ border-radius: var(--radius-sm);
332
+ }
333
+
334
+ .booking-cta {
335
+ display: inline-flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ width: 100%;
339
+ padding: var(--spacing-md) var(--spacing-xl);
340
+ font-family: var(--font-display);
341
+ font-size: 0.9375rem;
342
+ font-weight: 500;
343
+ background: var(--color-accent);
344
+ color: var(--color-paper);
345
+ border: 1px solid var(--color-accent);
346
+ border-radius: var(--radius-sm);
347
+ cursor: pointer;
348
+ transition: opacity 150ms ease;
349
+ }
350
+ .booking-cta:hover:not(:disabled) {
351
+ opacity: 0.9;
352
+ }
353
+ .booking-cta:disabled {
354
+ opacity: 0.5;
355
+ cursor: not-allowed;
356
+ }
@@ -0,0 +1,133 @@
1
+ ---
2
+ // src/pages/services/[slug].astro — SSR service detail + client-side booking flow.
3
+ // Two dirs deep inside src/ → import with ../../ (not three ../).
4
+ import Layout from "../../layouts/Layout.astro";
5
+ import SeoTags from "../../components/SeoTags.astro";
6
+ import ServiceBookingFlow from "../../components/ServiceBookingFlow";
7
+ import { services } from "@wix/bookings";
8
+ import { forms } from "@wix/forms";
9
+ import { auth } from "@wix/essentials";
10
+
11
+ const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
12
+ const { slug } = Astro.params;
13
+
14
+ let service: any = null;
15
+ try {
16
+ // Ambient SSR via @wix/essentials. Single service by slug: the .eq() builder
17
+ // chain on mainSlug.name + appId (the form the @wix/bookings SDK expects).
18
+ const result = await auth
19
+ .elevate(services.queryServices)({ conditionalFields: ["STAFF_MEMBER_DETAILS"] })
20
+ .eq("mainSlug.name", slug)
21
+ .eq("appId", BOOKING_APP_ID)
22
+ .limit(1)
23
+ .find();
24
+ service = result.items?.[0] ?? null;
25
+ } catch (err) {
26
+ console.error(`[services/${slug}] detail query failed:`, err);
27
+ }
28
+
29
+ if (!service) return Astro.redirect("/services", 302);
30
+
31
+ // Booking-form SCHEMA — the form belongs to the service (service.form._id) in the
32
+ // bookings namespace. Read it server-side and pass the field list to the island.
33
+ // Same renderer the forms vertical uses (references/astro/forms/CONTACT_FORM.md):
34
+ // keep the field's `target` — that is the key createBooking expects in formSubmission.
35
+ let formFields: any[] = [];
36
+ try {
37
+ const formId = service.form?._id;
38
+ if (formId) {
39
+ const res: any = await auth.elevate(forms.getForm)(formId);
40
+ const form = res?.form ?? res;
41
+ // Only render field types we can submit as a simple value. Skip complex,
42
+ // object-valued fields (e.g. a multi-line ADDRESS) — they carry no string
43
+ // `componentType`, and sending a string for them fails createBooking with
44
+ // "must be object". The booking enforces only the contact basics
45
+ // (first_name/last_name/email), so skipping optional complex fields is safe.
46
+ const RENDERABLE = ["TEXT_INPUT", "PHONE_INPUT", "DROPDOWN"];
47
+ formFields = (form?.formFields ?? [])
48
+ .filter(
49
+ (f: any) =>
50
+ f.fieldType === "INPUT" &&
51
+ !f.hidden &&
52
+ RENDERABLE.includes(f.inputOptions?.stringOptions?.componentType),
53
+ )
54
+ .map((f: any) => {
55
+ const opts = f.inputOptions;
56
+ const str = opts?.stringOptions;
57
+ const label =
58
+ str?.textInputOptions?.label ??
59
+ str?.dropdownOptions?.label ??
60
+ str?.phoneInputOptions?.label ??
61
+ "";
62
+ return {
63
+ label,
64
+ target: opts?.target ?? "",
65
+ required: opts?.required ?? false,
66
+ componentType: str?.componentType ?? "TEXT_INPUT",
67
+ identifier: f.identifier ?? "",
68
+ options: str?.dropdownOptions?.options?.map((o: any) => ({
69
+ value: o.value ?? o.label ?? "",
70
+ label: o.label ?? o.value ?? "",
71
+ })),
72
+ };
73
+ });
74
+ }
75
+ } catch (err) {
76
+ console.error(`[services/${slug}] booking form schema failed:`, err);
77
+ }
78
+
79
+ const formatPrice = (p: any): string | undefined => {
80
+ if (!p || p.rateType === "NO_FEE") return undefined;
81
+ if (p.rateType === "FIXED" && p.fixed?.price) {
82
+ const { value, currency } = p.fixed.price;
83
+ if (!value) return undefined;
84
+ return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
85
+ }
86
+ return undefined;
87
+ };
88
+ const price = formatPrice(service.payment);
89
+ const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
90
+ const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
91
+ const serviceName = service.name ?? "Service";
92
+ const seoTags = service.seoData?.tags ?? [];
93
+ ---
94
+
95
+ <!--
96
+ Width: use `container-reading` (max-width: var(--container-prose) + auto margins),
97
+ NOT a bare `max-w-<size>` — in this Tailwind v4 setup an un-emitted max-w-<size>
98
+ key falls back to the spacing scale (e.g. max-w-4xl → ~7rem, one word per line).
99
+ -->
100
+ <Layout title={serviceName} description={service.tagLine ?? service.description}>
101
+ <SeoTags tags={seoTags} slot="head" />
102
+ <section class="container-reading px-lg py-4xl">
103
+ <a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-xl">← All services</a>
104
+
105
+ <!-- Optional banner image — only when the service has media (image-less is the
106
+ common seed case; rendering a fixed placeholder collapses the layout). -->
107
+ {imageUrl && (
108
+ <img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-xl" />
109
+ )}
110
+
111
+ <div class="flex flex-col gap-md mb-3xl">
112
+ <hr class="editorial-rule w-16 ml-0" />
113
+ <h1 class="font-display text-4xl md:text-5xl leading-tight">{serviceName}</h1>
114
+ {service.tagLine && <p class="text-lg text-mute leading-relaxed max-w-prose">{service.tagLine}</p>}
115
+ <div class="flex items-center gap-md text-sm text-mute">
116
+ <span>{duration} min</span>
117
+ {price && <span class="text-ink font-medium">{price}</span>}
118
+ </div>
119
+ {service.description && <p class="text-ink leading-relaxed max-w-prose">{service.description}</p>}
120
+ </div>
121
+
122
+ <section class="border-t border-rule pt-2xl">
123
+ <h2 class="font-display text-3xl leading-tight mb-xl">Book this service</h2>
124
+ <ServiceBookingFlow
125
+ service={service}
126
+ serviceName={serviceName}
127
+ serviceType={service.type ?? "APPOINTMENT"}
128
+ fields={formFields}
129
+ client:only="react"
130
+ />
131
+ </section>
132
+ </section>
133
+ </Layout>
@@ -0,0 +1,73 @@
1
+ ---
2
+ // src/pages/services/index.astro — SSR services listing grid (APPOINTMENT or CLASS).
3
+ // One dir deep inside src/pages → ../../ reaches src/. Adapt copy/headings to the
4
+ // brand; keep the SSR data wiring.
5
+ import Layout from "../../layouts/Layout.astro";
6
+ import ServiceCard from "../../components/ServiceCard.astro";
7
+ import { services } from "@wix/bookings";
8
+ import { auth } from "@wix/essentials";
9
+
10
+ // Wix Bookings app id — the base filter must scope queryServices to it.
11
+ const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
12
+
13
+ let serviceList: any[] = [];
14
+ try {
15
+ // Ambient @wix/astro SSR client via @wix/essentials (same pattern as CMS pages).
16
+ // Do NOT build createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) }) —
17
+ // that env var is client-only and undefined at server render → 500.
18
+ // Pass the request object (filter must include appId) + conditionalFields, then .find().
19
+ const result = await auth
20
+ .elevate(services.queryServices)({
21
+ query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } },
22
+ conditionalFields: ["STAFF_MEMBER_DETAILS"],
23
+ })
24
+ .find();
25
+ serviceList = (result.items ?? []).filter((s: any) => !s.hidden);
26
+ } catch (err) {
27
+ console.error("[services] listing query failed:", err);
28
+ }
29
+
30
+ const getSlug = (s: any): string => s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name ?? s._id;
31
+ const formatPrice = (s: any): string | undefined => {
32
+ const p = s.payment;
33
+ if (!p || p.rateType === "NO_FEE") return undefined;
34
+ if (p.rateType === "FIXED" && p.fixed?.price) {
35
+ const { value, currency } = p.fixed.price;
36
+ if (!value) return undefined;
37
+ return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
38
+ }
39
+ return undefined;
40
+ };
41
+ const getDuration = (s: any): number => s.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
42
+ ---
43
+
44
+ <Layout title="Services">
45
+ <section class="max-w-6xl mx-auto px-lg py-4xl">
46
+ <header class="flex flex-col gap-md max-w-prose mb-3xl">
47
+ <hr class="editorial-rule w-16 ml-0" />
48
+ <h1 class="font-display text-4xl md:text-5xl leading-tight">Our Services</h1>
49
+ <!-- Optional brand tagline -->
50
+ </header>
51
+
52
+ {
53
+ serviceList.length === 0 ? (
54
+ <p class="text-mute">Services coming soon — check back shortly.</p>
55
+ ) : (
56
+ <div class="service-grid">
57
+ {serviceList.map((s) => (
58
+ <ServiceCard
59
+ id={s._id}
60
+ slug={getSlug(s)}
61
+ name={s.name ?? "Untitled"}
62
+ tagLine={s.tagLine ?? s.description}
63
+ durationMinutes={getDuration(s)}
64
+ price={formatPrice(s)}
65
+ imageUrl={s.media?.mainMedia?.image?.url ?? s.media?.items?.[0]?.image?.url}
66
+ type={s.type ?? "APPOINTMENT"}
67
+ />
68
+ ))}
69
+ </div>
70
+ )
71
+ }
72
+ </section>
73
+ </Layout>
@@ -1,7 +1,7 @@
1
1
  /* ── Composer skeleton: src/styles/global.css ──────────────────────────────
2
2
  The fixed bulk below is LITERAL — do not re-author it. Composer's only edit
3
3
  is to replace the {{theme}} block with the @theme palette derived from the
4
- inlined designTokens (see COMPOSE.md § "1. global.css"). Everything outside
4
+ inlined designTokens (see compose.mjs). Everything outside
5
5
  {{theme}} references the stable token vocabulary via var(--token) / built-in
6
6
  utilities and is brand-agnostic; ship it byte-for-byte.
7
7
  ─────────────────────────────────────────────────────────────────────────── */
@@ -11,7 +11,7 @@
11
11
  @theme {
12
12
  /* {{theme}} — Composer substitutes the full palette here from designTokens:
13
13
  every --color-*, --font-*, --spacing-* (2xs..4xl), --container-*, and
14
- --radius-* line. The required-token set (COMPOSE.md § "Token contract")
14
+ --radius-* line. The required-token set (enforced by compose.mjs)
15
15
  MUST all be present, or components render unstyled. Container scale is
16
16
  separate from spacing — never alias --container-3xl to --spacing-3xl. */
17
17
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  // ── Composer skeleton: src/pages/index.astro ──────────────────────────────────
3
- // Home shell. Substitutions (see COMPOSE.md § "6. index.astro"):
3
+ // Home shell. Substitutions (see compose.mjs):
4
4
  // {{shell.heroHeadline}}, {{shell.heroSub}} — brand-voice hero copy.
5
5
  // {{shell.footerTagline}} is NOT used here.
6
6
  // {{brand.name}} — used in the brand-story copy.
@@ -120,9 +120,8 @@ Use these node types to build rich, varied content:
120
120
  - LIST_ITEM nodes contain PARAGRAPH nodes (not TEXT directly)
121
121
  - Mix at least 3 different node types per post for visual variety
122
122
  - Create all 3 posts in sequence (one `curl` call per post)
123
- - **Best practice:** include all fields (title, content, media) in the initial creation call to avoid needing a re-publish
124
123
 
125
- > **Re-publish after PATCH:** If you update a published post (e.g., adding a cover image via PATCH after creation), it becomes `hasUnpublishedChanges: true`. You must call `POST /blog/v3/draft-posts/{draftPostId}/publish` to re-publish. To avoid this, include media in the initial creation call.
124
+ > **Re-publish after PATCH:** If you update a published post (e.g., the image agent adds a cover image via PATCH after creation), it becomes `hasUnpublishedChanges: true`. The agent that PATCHes must call `POST /blog/v3/draft-posts/{draftPostId}/publish` to re-publish. (This seed scope does not attach media — see Step 3.)
126
125
 
127
126
  ## Step 4: Verify
128
127
 
@@ -139,13 +138,24 @@ body: {
139
138
 
140
139
  Confirm 3 posts are returned. Report post titles to the user.
141
140
 
142
- ### Step 5: Log Results
141
+ ## Step 5: Return Results
143
142
 
144
- Write a sidecar file at `.wix/logs/blog-data.md` (see `../../shared/LIFECYCLE_LOG.md` for the sidecar contract). Do **not** append to `.wix/lifecycle.log.md` directly — the orchestrator concatenates all sidecars at the end. Use a `####` heading so the entry nests under `### features-orchestrator` in the assembled log:
143
+ Emit a structured JSON block at the end of your completion message per `../shared/RETURN_CONTRACT.md`. Do **not** write a sidecar file. Include the created posts' `id` and `title` so the image agent can attach cover images by ID:
145
144
 
146
- ```markdown
147
- ## blog
148
- - Status: complete
149
- - Content: {n} posts published ({post titles})
150
- - Images: {generated (n/n attached) | skipped (user declined) | not attempted}
145
+ ```json
146
+ {
147
+ "status": "complete",
148
+ "phase": "blog-seed",
149
+ "scope": "seed",
150
+ "summary": "Created and published {n} on-brand blog posts",
151
+ "data": {
152
+ "blogPosts": [
153
+ { "id": "<uuid>", "title": "<title>" }
154
+ ]
155
+ },
156
+ "files": [],
157
+ "errors": []
158
+ }
151
159
  ```
160
+
161
+ The JSON block MUST be the last content in your message.
@@ -5,7 +5,7 @@ description: "Implements blog vertical via Wix Blog app — @wix/blog SDK, @wix/
5
5
 
6
6
  # Blog Implementer
7
7
 
8
- Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (no site.json reads), return contract, style conventions, and common failure modes.
8
+ Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (read only your `.wix/seeded.json` slice), return contract, style conventions, and common failure modes.
9
9
 
10
10
  ## Scope routing
11
11
 
@@ -13,13 +13,13 @@ Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routi
13
13
  |-------|-------|-----------|
14
14
  | `seed` | Seed (post seeding via REST with Ricos content) | `./BLOG_CONTENT.md` |
15
15
  | `components` | Components (blog service module, RicosViewer, consts, astro.config sitemap) | `../astro/blog/BLOG_SETUP.md` |
16
- | `pages` | Pages (listing, detail, RSS, BlogPost layout, home contribution) | `../astro/blog/BLOG_PAGES.md` |
16
+ | `pages` | Pages (listing, detail, RSS, BlogPost layout — all under `src/pages/blog/*`; no shared-shell patch, so blog merges into one private build agent) | `../astro/blog/BLOG_PAGES.md` |
17
17
 
18
18
  Cover images are generated by the shared image agent (`image-phase-2-entity` scope) AFTER `seed` completes — not by this agent.
19
19
 
20
20
  ## Files this vertical creates / contributes
21
21
 
22
- See `<SKILL_ROOT>/references/verticals/blog.md` frontmatter.
22
+ The `components` and `pages` scopes enumerate their own file lists — see `../astro/blog/BLOG_SETUP.md` (components) and `../astro/blog/BLOG_PAGES.md` (pages).
23
23
 
24
24
  ## Blog-specific failure modes
25
25