@wix/agent-skills 1.20.0 → 1.21.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 (183) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +14 -112
  3. package/skills/wix-app/references/APP_TOOLS.md +2 -1
  4. package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
  6. package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
  7. package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
  8. package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
  9. package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
  10. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
  11. package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
  12. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
  13. package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
  14. package/skills/wix-headless-fast/SKILL.md +302 -51
  15. package/skills/wix-headless-fast/entry/skill.md +11 -2
  16. package/skills/wix-headless-fast/install/AGENTS.md +32 -0
  17. package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
  18. package/skills/wix-headless-fast/install/attach.mjs +360 -0
  19. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  20. package/skills/wix-headless-fast/install/setup.mjs +239 -0
  21. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +226 -60
  22. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  23. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  24. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  25. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  26. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  27. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  28. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  29. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  30. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  31. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  32. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  33. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  34. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  35. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  36. package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
  37. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +181 -33
  38. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  39. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  40. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  41. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  42. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  43. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  44. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  45. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  46. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  47. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  48. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  49. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  50. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  51. package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
  52. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +238 -62
  53. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  54. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  55. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  56. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  57. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  58. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  59. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  60. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  61. package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
  62. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +241 -83
  63. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  64. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  65. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  66. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  67. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  68. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  69. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  70. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  71. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  72. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  73. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  74. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  75. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  76. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  77. package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
  78. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +216 -26
  79. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  80. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  81. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  82. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  83. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  84. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  85. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  86. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  87. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  88. package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
  89. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  90. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +316 -63
  91. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  92. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  93. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  94. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  95. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  96. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  97. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  98. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  99. package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
  100. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +240 -59
  101. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  102. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  103. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  104. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  105. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  106. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  107. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  108. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  109. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  110. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  111. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  112. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  113. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  114. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  115. package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
  116. package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
  117. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +214 -51
  118. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  119. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  120. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  121. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  122. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  123. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  124. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  125. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  126. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  127. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  128. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  129. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  130. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  131. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  132. package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
  133. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +242 -47
  134. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  135. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  136. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  137. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  138. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  139. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  140. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  141. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  142. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  143. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  144. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  145. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  146. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  147. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  148. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  149. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  150. package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
  151. package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
  152. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  153. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
  154. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  155. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  156. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  157. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  158. package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
  159. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +186 -63
  160. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  161. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
  162. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
  163. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
  164. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  165. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
  166. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +314 -0
  167. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
  168. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  169. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  170. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
  171. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  172. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  173. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +31 -9
  174. package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
  175. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +57 -50
  176. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  177. package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
  178. package/skills/wix-manage/references/events/create-wix-event.md +5 -1
  179. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
  180. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  181. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  182. package/skills/wix-headless-fast/install/fast-path.mjs +0 -186
  183. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -0,0 +1,239 @@
1
+ // Set up the project in the current folder — one deterministic call from "the folder and the
2
+ // brief" to "brand layer can start", for a folder that is NOT yet a Wix project:
3
+ //
4
+ // node <SKILL_ROOT>/install/setup.mjs --vertical <storefront|bookings|…> \
5
+ // [--plan plan.json] [--business-name "<Brand>"] [--stack astro|react|lib|static] \
6
+ // [--subfolder [--folder-name <npm-safe-name>]]
7
+ //
8
+ // - `wix.config.json` present → refuses. The folder is already a Wix project; deploy.mjs adds
9
+ // this skill's code or a solution to it, then one install, then the seed if there is content.
10
+ // - a project but no `wix.config.json` (a package.json, or an index.html at the root) → CONNECT:
11
+ // `npm create @wix/new@latest init` in place creates the site and the config, then deploy.
12
+ // - otherwise (empty, or loose files such as a CSV or a brief) → CREATE: scaffold the Wix CLI's
13
+ // Astro template and place it in the current directory (`--business-name` required).
14
+ //
15
+ // The script reads file markers only (wix.config.json, a package.json or index.html); everything
16
+ // else is the agent's call: when connecting, `--stack` is required and comes from SKILL.md step 1,
17
+ // and what the project needs for that stack on Wix hosting is prose there, not detection here. The
18
+ // seed runs only when `--plan` is given: a brief that has not supplied any content gets no seed.
19
+ //
20
+ // Composes pieces that also remain individually runnable (deploy.mjs, the vertical's seed module)
21
+ // to recover one failed step. Emits ONE JSON event per line and exits in ~35s with the two long
22
+ // steps — the dependency install and the seed — running detached in the background (logs and
23
+ // completion markers in the final event), so the caller can build the brand layer while they finish.
24
+ import { spawn, spawnSync } from "node:child_process";
25
+ import { existsSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
26
+ import { basename, dirname, join, resolve } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+ import { AGENT_CONFIG_FILES, writeAgentsMd } from "./agents-md.mjs";
29
+
30
+ const SKILL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
31
+
32
+ const emit = (event, extra = {}) => console.log(JSON.stringify({ event, ...extra }));
33
+ const fail = (step, detail) => {
34
+ emit("error", { step, detail: String(detail).slice(0, 600) });
35
+ process.exit(1);
36
+ };
37
+
38
+ // ---- args ---------------------------------------------------------------------------------------
39
+ const argv = process.argv.slice(2);
40
+ const flag = (name) => {
41
+ const i = argv.indexOf(`--${name}`);
42
+ return i !== -1 && argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : null;
43
+ };
44
+ const businessName = flag("business-name");
45
+ const planPath = flag("plan");
46
+ const vertical = flag("vertical");
47
+ const stackFlag = flag("stack");
48
+ // Opt-in: keep a CREATE's project in a subfolder instead of the current directory.
49
+ const subfolder = argv.includes("--subfolder");
50
+ const knownVerticals = readdirSync(join(SKILL_ROOT, "references"), { withFileTypes: true })
51
+ .filter((d) => d.isDirectory() && d.name !== "shared" && existsSync(join(SKILL_ROOT, "references", d.name, "app")))
52
+ .map((d) => d.name);
53
+ const usage = `usage: setup.mjs --vertical <${knownVerticals.join("|")}> [--plan plan.json] [--business-name "<Brand>"] [--stack astro|react|lib|static] [--subfolder]`;
54
+ // --vertical is REQUIRED: a defaulted vertical deploys the wrong code and runs the wrong seed.
55
+ if (!vertical) fail("args", usage);
56
+ if (!knownVerticals.includes(vertical)) {
57
+ fail("args", `unknown vertical "${vertical}" — shipped verticals: ${knownVerticals.join(", ")}`);
58
+ }
59
+ if (stackFlag && !["astro", "react", "lib", "static"].includes(stackFlag)) {
60
+ fail("args", `unknown --stack "${stackFlag}" — astro, react, lib or static`);
61
+ }
62
+ if (planPath && !existsSync(planPath)) fail("args", `plan file not found: ${planPath}`);
63
+
64
+ // ---- 0 · read the folder --------------------------------------------------------------------------
65
+ const cwd = process.cwd();
66
+ const has = (p) => existsSync(join(cwd, p));
67
+ if (has("wix.config.json")) {
68
+ fail("place", "this folder is already a Wix project (wix.config.json): nothing to set up. deploy.mjs <vertical> adds this skill's code or a solution to it; then ONE npm install; then the vertical's seed module if there is content to create");
69
+ }
70
+ const pkg = has("package.json") ? JSON.parse(readFileSync(join(cwd, "package.json"), "utf8")) : null;
71
+ const hasProject = !!pkg || has("index.html");
72
+ const mode = hasProject ? "connect" : "create";
73
+ if (mode === "connect" && !stackFlag) {
74
+ fail("args", "connecting a project on disk needs --stack astro|react|lib|static — the stack you resolved in SKILL.md step 1 for this project");
75
+ }
76
+ const stack = stackFlag ?? "astro";
77
+ emit("folder", { mode, stack, project: hasProject ? (pkg?.name ?? basename(cwd)) : null });
78
+
79
+ let projectDir = cwd;
80
+ let folderName = null;
81
+
82
+ if (mode === "create") {
83
+ // ---- 1 · scaffold -----------------------------------------------------------------------------
84
+ if (!businessName) fail("args", `an empty folder is a CREATE run and needs --business-name. ${usage}`);
85
+ folderName =
86
+ flag("folder-name") ??
87
+ businessName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
88
+ // `let`: the Wix CLI can only scaffold into a subfolder; by default we then move the scaffold up
89
+ // into the current directory and repoint projectDir there, so the detached install/seed and every
90
+ // reported path use the final location.
91
+ projectDir = resolve(cwd, folderName);
92
+ if (existsSync(join(projectDir, "wix.config.json"))) {
93
+ emit("scaffold_skipped", { reason: "project already exists", folder: folderName });
94
+ } else {
95
+ emit("scaffolding", { folder: folderName });
96
+ const scaffold = spawnSync(
97
+ "npm",
98
+ // --skip-git: this wrapper composes its own steps and leaves version control to
99
+ // the caller / the enclosing repo; the scaffold's own `git init` + "Initial
100
+ // commit" is noise here, and becomes a nested-repo (submodule gitlink) hazard
101
+ // if the project is later placed inside an existing repo. --skip-install for the
102
+ // same reason: deps install in a detached step below.
103
+ ["create", "@wix/new@latest", "--", "headless",
104
+ "--folder-name", folderName, "--business-name", businessName,
105
+ "--site-template", "--skip-install", "--skip-git", "--no-publish"],
106
+ { env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 },
107
+ );
108
+ if (scaffold.status !== 0 || !existsSync(join(projectDir, "wix.config.json"))) {
109
+ fail("scaffold", (scaffold.stderr || scaffold.stdout || "scaffold produced no wix.config.json — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-600));
110
+ }
111
+ }
112
+ } else {
113
+ // ---- 1 · init in place --------------------------------------------------------------------------
114
+ // The CLI's `init` links the folder to a NEW site: creates the site and its OAuth app, writes
115
+ // wix.config.json and .env.local, touches nothing else. Non-interactive under CI=1; the site is
116
+ // named after the folder (rename it in the dashboard).
117
+ emit("initializing", { folder: cwd });
118
+ const init = spawnSync("npm", ["create", "@wix/new@latest", "--", "init"],
119
+ { cwd, env: { ...process.env, CI: "1" }, encoding: "utf8", timeout: 300_000 });
120
+ if (init.status !== 0 || !has("wix.config.json")) {
121
+ fail("init", (init.stderr || init.stdout || "init produced no wix.config.json — is the Wix CLI logged in? (npx @wix/cli@latest whoami)").slice(-600));
122
+ }
123
+ }
124
+ const wixConfig = JSON.parse(readFileSync(join(projectDir, "wix.config.json"), "utf8"));
125
+ const siteId = wixConfig.siteId ?? wixConfig.projectId;
126
+ emit(mode === "create" ? "scaffolded" : "connected", { folder: folderName ?? cwd, siteId, stack });
127
+
128
+ // ---- 2 · deploy shipped code + deps + lockfile ---------------------------------------------------
129
+ const deploy = spawnSync(
130
+ "node",
131
+ [join(SKILL_ROOT, "install", "deploy.mjs"), vertical, "--stack", stack, ...(planPath ? ["--plan", resolve(planPath)] : [])],
132
+ { cwd: projectDir, encoding: "utf8", timeout: 60_000 },
133
+ );
134
+ if (deploy.status !== 0) fail("deploy", deploy.stderr || deploy.stdout);
135
+ let deployResult = {};
136
+ try { deployResult = JSON.parse(deploy.stdout); } catch { /* keep going with raw output below */ }
137
+ if (deployResult.error) fail("deploy", deployResult.error);
138
+ emit("deployed", deployResult);
139
+
140
+ // ---- 2c · place a CREATE's project in the current directory -------------------------------------
141
+ // The Wix CLI scaffolds into a subfolder, so the scaffold is moved up into the current directory —
142
+ // the folder that already holds the installed skills — and the subfolder removed. Done HERE, before
143
+ // the detached install below, on purpose: no node_modules exists yet, so the move is instant and
144
+ // cannot collide with a running install. A pure move: the scaffold was created with --skip-git, so
145
+ // there is no nested repo to reconcile — git is whatever the folder already is. Refuses rather than
146
+ // overwrite: an entry that already exists in the current directory stops the move before anything
147
+ // is touched. `--subfolder` skips this step.
148
+ if (mode === "create" && !subfolder && projectDir !== cwd) {
149
+ // The CLI's own agent config files, when its generator managed to write them, are dropped from
150
+ // the scaffold: ours (written below, after the move) carries the same CLI section plus this
151
+ // project's skills. A file the user's folder already has is never overwritten or merged into —
152
+ // it is kept as is, and the agent_configs event says so.
153
+ for (const f of AGENT_CONFIG_FILES) rmSync(join(projectDir, f), { recursive: true, force: true });
154
+ const entries = readdirSync(projectDir);
155
+ const clashes = entries.filter((e) => existsSync(join(cwd, e)));
156
+ if (clashes.length) {
157
+ fail("place", `the current directory already has: ${clashes.join(", ")} — run with --subfolder to keep the project in ${folderName}/, or start in an empty folder`);
158
+ }
159
+ try {
160
+ for (const entry of entries) renameSync(join(projectDir, entry), join(cwd, entry));
161
+ rmSync(projectDir, { recursive: true, force: true });
162
+ projectDir = cwd;
163
+ emit("project_placed", { into: cwd });
164
+ } catch (e) {
165
+ fail("place", e?.stack || e);
166
+ }
167
+ }
168
+
169
+ // ---- 2d · the agent config files `wix create` would have written --------------------------------
170
+ // Skipped by the CLI because of --skip-install. Fill-only: a project that has its own AGENTS.md
171
+ // keeps it (the event says `kept`).
172
+ emit("agent_configs", writeAgentsMd(projectDir, { skill: basename(SKILL_ROOT), stack }));
173
+
174
+ // ---- 3 · start the dependency install, detached --------------------------------------------------
175
+ // ONE install, here, for any project with a package.json (deploy patched it). The static stack has
176
+ // no package.json and nothing to install. `npm ci` only where a lockfile exists: without one it fails
177
+ // with a usage error that sits at the top of the log and reads as a failed install (run 109).
178
+ let install = null;
179
+ if (stack !== "static" && existsSync(join(projectDir, "package.json"))) {
180
+ const installLog = join(projectDir, "npm-install.log");
181
+ const logFd = openSync(installLog, "a");
182
+ const child = spawn("sh", ["-c", existsSync(join(projectDir, "package-lock.json")) ? "npm ci --ignore-scripts || npm install --ignore-scripts" : "npm install --ignore-scripts"], {
183
+ cwd: projectDir,
184
+ detached: true,
185
+ stdio: ["ignore", logFd, logFd],
186
+ });
187
+ child.unref();
188
+ install = { log: installLog, doneMarker: "node_modules/.package-lock.json" };
189
+ emit("install_started", install);
190
+ }
191
+
192
+ // ---- 4 · start the seed, detached (only with a plan) ---------------------------------------------
193
+ // The seed includes a Wix-side provisioning wait of unpredictable length (10-80s); running it in
194
+ // the caller's foreground would idle the agent for exactly that long. Detach it like the install:
195
+ // result JSON + exit-code marker land as files the caller syncs on before release. Seeding is
196
+ // additive: it never deletes or overwrites what the site holds.
197
+ let seed = null;
198
+ if (planPath) {
199
+ const seedDir = join(SKILL_ROOT, "references", vertical, "seed");
200
+ const seedName = existsSync(seedDir)
201
+ ? readdirSync(seedDir).find((f) => f.startsWith("seed-") && f.endsWith(".mjs"))
202
+ : undefined;
203
+ if (!seedName) fail("seed", `no seed module found under ${seedDir}`);
204
+ const seedFile = join(seedDir, seedName);
205
+ const planAbs = resolve(planPath);
206
+ const seedChild = spawn(
207
+ "sh",
208
+ ["-c", `node "${seedFile}" "${planAbs}" > seed-result.json 2> seed.log; echo $? > .seed-exit`],
209
+ { cwd: projectDir, detached: true, stdio: "ignore" },
210
+ );
211
+ seedChild.unref();
212
+ seed = { resultFile: "seed-result.json", log: "seed.log", doneMarker: ".seed-exit", success: "file contains 0" };
213
+ emit("seeding_started", { vertical, ...seed });
214
+ }
215
+
216
+ // ---- done ----------------------------------------------------------------------------------------
217
+ const release = {
218
+ astro: "npx @wix/cli@latest build, then npx @wix/cli@latest release",
219
+ react: "the project's own build, then npx @wix/cli@latest release of the build folder named in wix.config.json (what Wix hosting serves and how routes must be shaped: SKILL.md step 1)",
220
+ lib: "the project's own build, then npx @wix/cli@latest release of the build folder named in wix.config.json (SKILL.md step 1)",
221
+ static: "npx @wix/cli@latest release — no build; wix.config.json site.outputDirectory points at the folder the pages live in",
222
+ }[stack];
223
+ emit("ready_for_brand_layer", {
224
+ mode,
225
+ stack,
226
+ projectDir,
227
+ siteId,
228
+ dashboardUrl: deployResult.dashboardUrl,
229
+ productsUrl: deployResult.productsUrl,
230
+ categoriesUrl: deployResult.categoriesUrl,
231
+ install,
232
+ seed,
233
+ next:
234
+ (planPath
235
+ ? "theme + write the home page; "
236
+ : "the site is new and empty — seed it (a plan per step 2, the vertical's seed module) or say so; theme + write the home page; ") +
237
+ (install || seed ? "then wait for the done markers" + (seed ? ", verify .seed-exit is 0 (else read seed.log and re-run the seed module)" : "") + "; " : "") +
238
+ `then ${release}`,
239
+ });
@@ -1,29 +1,37 @@
1
1
  # Blog — playbook
2
2
 
3
3
  The blog machinery ships as files — post reads (feed paging, slug lookup with the body
4
- fieldsets), category/tag taxonomy, and the Ricos body renderer, typed end-to-end. **The
5
- presentation is yours**: you design and implement the post card, the grid, the blog index
6
- surface, and the post page surface on the shipped hooks/DTOs, plus the home page and the
7
- brand. You never write blog data logic; you never skip designing. The vertical is
8
- **read-only** — visitors read posts; authoring stays in the dashboard.
4
+ fieldsets), category/tag taxonomy, the feed and post state machines, and the Ricos body renderer,
5
+ typed end-to-end. **The presentation doesn't ship — you build it** on the shipped hooks/DTOs: the
6
+ post card, the grid, the blog index surface, the post page surface, the home page, and the brand.
7
+ You never write blog data logic; you never skip designing. The vertical is **read-only** —
8
+ visitors read posts; authoring stays in the dashboard.
9
9
 
10
10
  ## The file map (deployed into `src/`)
11
11
 
12
- **Don't read the shipped files** — this table and the contracts below are everything you
13
- need. Open a shipped file's source only on a real fallback (runtime error / uncovered field),
14
- or to read a reference component's pattern.
12
+ **On Astro and React the shipped files are tested and work as they are** — this table and the
13
+ contracts below are everything you need to use them, so don't spend the run reading their source;
14
+ wire them and build your surfaces. Reading them is the right move when something is off (a runtime
15
+ error, a field this playbook doesn't cover) or when the brief wants a behaviour they don't offer —
16
+ then read the file that owns it and change or extend it. On `lib`, `static`, and a port the
17
+ components don't deploy at all, and each wiring section below opens with the files to read before
18
+ writing their equivalents. Files you edit: `SiteLayout.astro`, `styles/global.css`, and the two
19
+ shipped pages' templates. Files you **create**: your feed island (skeleton below), your post
20
+ surface, and your home page.
15
21
 
16
22
  | file | what it is |
17
23
  |---|---|
18
- | `wix/config.ts` · `wix/sdk.ts` · `wix/media.ts` · `wix/money.ts` | shared auth seam + helpers (deploy configures; nothing to set) |
19
- | `wix/blog/types.ts` | the DTOs (`PostSummary`, `PostDetail`, `BlogCategory`, `BlogTag`, `PostPage`) — contracts below |
20
- | `wix/blog/posts.ts` | `fetchPosts` (paged, filterable), `fetchPostBySlug` (full body) |
21
- | `wix/blog/taxonomy.ts` | `fetchBlogCategories`, `fetchBlogTags`, `fetchCategoryBySlug`, `fetchTagBySlug` |
22
- | `hooks/blog/useBlogFeed.ts` | feed + taxonomy filter + load-more — contract below |
23
- | `hooks/blog/usePost.ts` | post by slug + resolved chips — contract below |
24
- | `components/blog/RichContent.tsx` | the post-body renderer — **wire AS-IS** (machinery, not a reference) |
25
- | `components/blog/BlogFeedView.tsx` (+ `PostCard`) · `PostView.tsx` | **REFERENCE implementations** — correct, plain; build your own instead of shipping them |
26
- | `styles/global.css` | the design system: Tailwind v4 + the `@theme` token block (shared across verticals) |
24
+ | `wix/config.ts` · `wix/sdk.ts` | shared auth seam (deploy configures it — nothing to set by hand) |
25
+ | `wix/media.ts` · `wix/money.ts` | `imgAttrs(url, sizes, ratio)` — every `<img>` attribute for a DTO image (`src`, `srcSet`, `sizes`, lazy): `<img {...imgAttrs(post.coverUrl, "(min-width: 1024px) 33vw, 100vw", 9 / 16)} alt={post.title} />`; `imgSrc()` / `imgSrcSet()` underneath |
26
+ | `wix/blog/types.ts` | the DTOs (`PostSummary`, `PostDetail`, `BlogCategory`, `BlogTag`, `PostPage`) — contracts inlined below |
27
+ | `wix/blog/posts.ts` | `fetchPosts` (cursor-paged, category/tag filtered on Wix), `fetchPostBySlug` (full body), `postMetaLine(post)` ("Aug 27, 2026 · 4 min read") — the transport; the rules and DTO mappers are in `posts-core.ts` beside it (shared with the REST layer) |
28
+ | `wix/blog/taxonomy.ts` | `fetchBlogCategories`, `fetchBlogTags`, `fetchCategoryBySlug`, `fetchTagBySlug` — the transport; mappers in `taxonomy-core.ts` |
29
+ | `wix/blog/blog-feed-store.ts` · `post-store.ts` | the feed and post state machines, framework-free (`createBlogFeedStore()`, `createPostStore()` — `getState`/`subscribe` + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
30
+ | `hooks/blog/useBlogFeed.ts` | React binding of `blog-feed-store.ts`: feed + taxonomy filter + load-more — contract below |
31
+ | `hooks/blog/usePost.ts` | React binding of `post-store.ts`: post by slug + resolved chips — contract below |
32
+ | `components/blog/RichContent.tsx` | the post-body renderer — **wire as-is** (machinery, not a reference) |
33
+ | `components/blog/BlogFeedView.tsx` (+ `PostCard`) · `PostView.tsx` | **reference implementations** — correct, plain; build your own instead of shipping them |
34
+ | `styles/global.css` | **the design system**: Tailwind v4 + the `@theme` token block (shared across verticals). Everything, shipped and yours, styles from these tokens |
27
35
 
28
36
  Astro stack additionally gets:
29
37
 
@@ -31,43 +39,62 @@ Astro stack additionally gets:
31
39
  |---|---|
32
40
  | `layouts/SiteLayout.astro` | site chrome — **yours to brand** (keep the `seo-tags` slot + global.css import). If another vertical is also deployed, its layout won — add a Blog nav link there |
33
41
  | `pages/blog.astro` | SSR feed — **keep the frontmatter**, swap the island import to YOUR component |
34
- | `pages/blog/[...slug].astro` | SSR post page with owner-editable SEO — **keep the frontmatter, the `[...slug]` rest param, and the SEO pieces** (`wixMetadata`, `loadSEOTagsServiceConfig`, `<SEO.Tags>`) exactly; restyle the template. The body island stays `client:only="react"` (the ricos viewer breaks under SSR) |
42
+ | `pages/blog/[...slug].astro` | SSR post page with owner-editable SEO — **keep the frontmatter, the `[...slug]` rest param, and the SEO pieces** (`wixMetadata`, `loadSEOTagsServiceConfig`, `<SEO.Tags>`) exactly; restyle the template. The body island stays `client:only="react"` (the ricos viewer breaks under SSR). A slug that resolves to nothing returns a real 404 |
35
43
 
36
44
  ## What you build — the design job
37
45
 
38
- 1. **The post card + grid** — your tile (cover, title, excerpt, date · min-read presentation)
39
- and rhythm, with skeletons while loading and an honest empty state.
40
- 2. **The blog index surface** — the feed on `useBlogFeed`: category filter (only when >1
46
+ 1. **The post card + grid** — your tile (cover, title, excerpt, `postMetaLine(post)`) and rhythm,
47
+ with skeletons while `posts === null` and an honest empty state for `[]`.
48
+ 2. **The blog index surface** — the feed island on `useBlogFeed`: category filter (only when >1
41
49
  non-empty category), the grid, and a load-more control gated on `hasMore`.
42
- 3. **The post page surface** — header (categories eyebrow, title, date, cover), the body via
43
- the shipped `RichContent`, and the tag chips — restyle the `[...slug].astro` template
44
- around it.
45
- 4. **The home page** — hero, latest posts (fetch in frontmatter → your components), brand
46
- story.
50
+ 3. **The post page surface** — header (categories eyebrow, title, meta line, cover), the body via
51
+ the shipped `RichContent`, and the tag chips — restyle the `[...slug].astro` template around it.
52
+ 4. **The home page** — hero, latest posts (fetch in frontmatter → your components), brand story.
47
53
 
48
54
  Plus the **theme** (`@theme` block, one edit) and the **chrome** (`SiteLayout`, one pass).
49
55
  Style everything with Tailwind utilities on the tokens. Dark theme: the ricos CSS hardcodes
50
56
  near-black text — add a global override scoping `.ricos-content` to the foreground token
51
57
  (in Astro use `<style is:global>`; React islands don't inherit scoped Astro styles).
52
58
 
59
+ ### What a complete blog shows (recommended defaults)
60
+
61
+ Defaults for a blog whose brief says nothing about them; the prompt wins where it differs. Look at
62
+ the seeded content before designing (how many posts, categories, covers, body structure) and design
63
+ for this blog, not for a stereotype of one.
64
+
65
+ - **Home:** what the blog is about and the latest posts (real ones, under a truthful heading) in the
66
+ first screen; a category link goes to the feed filtered to it, never to an anchor.
67
+ - **Index:** a real card — cover, title, meta line — in the first screen at 390px too; the category
68
+ row only when more than one category has posts; loading, empty, and error states that look
69
+ different; load-more, never a raised page size.
70
+ - **Post page:** eyebrow, title, meta line, cover as a bounded band on phones (`max-h-[45vh]`), the
71
+ body, then the tags; a slug that resolves to nothing shows only the not-found state.
72
+ - **Copy:** nothing the author didn't write — no invented bylines, comment counts, likes, or
73
+ "trending" labels; no Wix IDs or technical words in visible text.
74
+
53
75
  ### The contracts your components consume
54
76
 
77
+ Tested and working as they are; read the source when something is off or the brief wants more.
78
+
55
79
  ```ts
56
80
  // PostSummary (tiles) — display-ready:
57
81
  // { id, slug, title, excerpt, dateLabel /* "Aug 26, 2026" | "" */, dateISO,
58
- // minutesToRead /* 0 = unknown */, featured, pinned, coverUrl /* "" = no cover */,
82
+ // minutesToRead /* 0 = unknown */, featured, pinned, coverUrl /* "" = no cover, 16:9 */,
59
83
  // categoryIds, tagIds }
84
+ // postMetaLine(post) → "Aug 26, 2026 · 4 min read" (either half alone; "" when neither is known) —
85
+ // render it inside <time dateTime={post.dateISO}>; don't hand-assemble the separator.
60
86
  // PostDetail adds: richContent (Ricos JSON | null — render ONLY via RichContent),
61
87
  // paragraphs (plain-text fallback body).
62
88
  // BlogCategory: { id, slug, label, description, postCount, coverUrl } // display .label
63
- // BlogTag: { id, slug, label, postCount }
89
+ // BlogTag: { id, slug, label, postCount /* published posts */ }
64
90
 
65
- // useBlogFeed({ initialPage?, initialCategories?, initialTags?, pageSize? }) →
91
+ // useBlogFeed({ initialPage?, initialCategories?, initialTags?, pageSize? /* 20 */ }) →
66
92
  // { posts: PostSummary[]|null /* null = loading → skeletons */,
67
93
  // categories, tags,
68
94
  // activeCategoryId, setActiveCategoryId(id|null), // server-side filter
69
95
  // activeTagId, setActiveTagId(id|null), // mutually exclusive with category
70
96
  // hasMore, loadMore(), loadingMore, error }
97
+ // Filters and paging run on Wix (a changed filter restarts the list; a late response is dropped).
71
98
 
72
99
  // usePost({ slug, initialPost?, initialCategories?, initialTags? }) →
73
100
  // { post: PostDetail|null, notFound /* true = render a 404 state, never invent a post */,
@@ -77,61 +104,200 @@ near-black text — add a global override scoping `.ricos-content` to the foregr
77
104
  // — the ONLY body render path. In Astro: client:only="react".
78
105
  ```
79
106
 
107
+ ### The island you create — skeleton
108
+
109
+ The two Astro pages ship; their frontmatter is machinery (SSR fetch → DTO props → island; the
110
+ post page's SEO block) and stays as shipped. What you create is the feed island `blog.astro` mounts
111
+ (and, in a SPA, the post surface — `PostView.tsx` shows its shape). Hooks first, branches after (an
112
+ early return above a hook changes hook order and React throws). Islands render on the server too,
113
+ after the 200 is sent — nothing in a render path may throw.
114
+
115
+ ```tsx
116
+ // src/components/blog/<YourFeed>.tsx — YOU build it; blog.astro mounts it (swap the import there).
117
+ import { useBlogFeed } from "../../hooks/blog/useBlogFeed";
118
+ import { postMetaLine } from "../../wix/blog/posts";
119
+ import { imgAttrs } from "../../wix/media";
120
+ import type { BlogCategory, BlogTag, PostPage } from "../../wix/blog/types";
121
+
122
+ export default function YourFeed(props: {
123
+ initialPage?: PostPage; // SSR props from blog.astro — pass straight to useBlogFeed;
124
+ initialCategories?: BlogCategory[]; // omitted in a SPA (client fetch)
125
+ initialTags?: BlogTag[];
126
+ }) {
127
+ const feed = useBlogFeed(props);
128
+ const { posts, categories, activeCategoryId, setActiveCategoryId, hasMore, loadMore, loadingMore, error } = feed;
129
+ // …you implement the render:
130
+ // • a category row when more than one category has posts (categories.filter(c => c.postCount > 0)):
131
+ // "All" (null) first, the active one marked by activeCategoryId, each calling setActiveCategoryId
132
+ // • error → a short inline message
133
+ // • posts === null → skeleton tiles; [] → your honest empty state
134
+ // • else YOUR grid of YOUR tiles: cover via
135
+ // <img {...imgAttrs(p.coverUrl, "(min-width: 1024px) 33vw, 100vw", 9 / 16)} alt={p.title} /> — {} when
136
+ // there is no cover, render your placeholder then; title WRAPS (no truncate); excerpt clamped;
137
+ // <time dateTime={p.dateISO}>{postMetaLine(p)}</time>; the tile links to `/blog/${p.slug}`
138
+ // • hasMore → your "load more" control calling loadMore() (disabled while loadingMore)
139
+ }
140
+ ```
141
+
142
+ ### The reference files for stacks where the components don't deploy
143
+
144
+ On `lib`, `static`, and a port, nothing under `components/` or `hooks/` arrives. The state
145
+ machines behind the hooks do arrive — `wix/blog/blog-feed-store.ts`, `post-store.ts` — so you never
146
+ rewrite them: create a store per surface, `subscribe`, render from `getState()`, call its actions.
147
+ Their `*State` interfaces are the render contract; read those. What you write is the rendering —
148
+ card, grid, filter row, post header — and for that read these first; they are tested code for
149
+ exactly that behaviour:
150
+
151
+ 1. `components/blog/BlogFeedView.tsx` — the filter row (only categories with posts, "All" first,
152
+ one active pill), the skeleton grid while `posts === null`, the empty state, load-more disabled
153
+ while loading; `PostCard` inside it is the tile: 16:9 cover, wrapping title, clamped excerpt,
154
+ the meta line in a `<time>`.
155
+ 2. `components/blog/PostView.tsx` — the states in order (not found → error → loading → article),
156
+ the categories eyebrow by `.label`, the tag chips, the body slot.
157
+ 3. `components/blog/RichContent.tsx` — what the body renderer is: the `@wix/ricos` viewer, a React
158
+ component that needs a bundler. On these stacks it doesn't run; the body renders from
159
+ `post.paragraphs` (one `<p>` per entry), and headings, lists, quotes, and inline images flatten
160
+ to text — say so in the closing message. A blog whose posts carry structure stays on Astro.
161
+
162
+ All under `references/blog/app/`.
163
+
80
164
  ### Wiring — Astro (default)
81
165
 
82
166
  1. Set the `@theme` tokens (one edit); brand `SiteLayout.astro` (one pass — merge into the
83
167
  other vertical's layout instead if both are deployed).
84
- 2. Write your components under `src/components/blog/` (new names — don't overwrite the
168
+ 2. Write your feed island under `src/components/blog/` (a new name — don't overwrite the
85
169
  references), swap the island import in `pages/blog.astro`, and restyle the
86
170
  `pages/blog/[...slug].astro` template (keep its frontmatter + SEO pieces + the
87
171
  `client:only="react"` RichContent island). **Author your surfaces in as few messages as
88
172
  possible** — batch multiple Writes per message.
89
173
  3. Write `pages/index.astro` (home) — it exists from the scaffold; Read it before overwriting.
90
174
 
175
+ ### Wiring — another JS framework (`--stack lib`: Vue, Svelte, Solid, plain Vite)
176
+
177
+ Read the reference files listed above before writing any surface.
178
+
179
+ `deploy.mjs blog --stack lib` put the data layer in `src/wix/` and nothing else: `sdk.ts` (the
180
+ visitor client, configured with the public client id), `media.ts`, `money.ts`, and `wix/blog/` —
181
+ `posts.ts`, `taxonomy.ts`, `types.ts`, the `*-core.ts` rules, and the two stores
182
+ `blog-feed-store.ts`, `post-store.ts`. None of it is React. The hooks and components don't ship on
183
+ this stack; the stores replace the hooks, and you write the components in your framework to the
184
+ contracts on this page:
185
+
186
+ - bind the stores with your framework's external-store primitive (Vue: `shallowRef` updated in
187
+ `subscribe`; Svelte: `readable(store.getState(), (set) => store.subscribe(() => set(store.getState())))`;
188
+ Solid: a signal set in `subscribe`). `createBlogFeedStore(options)` per feed (`start()` when
189
+ mounted, `stop()` when unmounted), `createPostStore({ slug, initialPost? })` per post surface.
190
+ State in, actions out — exactly the hooks' contracts above;
191
+ - the body: `@wix/ricos` is a React component — without React it doesn't render; use
192
+ `post.paragraphs` and say so in the closing message.
193
+
194
+ Routes `/blog`, `/blog/:slug` (null → your 404 view); dev server on 4321; a static build goes
195
+ through `npx @wix/cli@latest release` with `site.outputDirectory` pointing at the build folder,
196
+ an SSR build is hosted by you. Post-page tags from `post.title` and `post.excerpt`.
197
+
198
+ ### Wiring — static site (`--stack static`, no bundler)
199
+
200
+ Read the reference files listed above before writing any surface.
201
+
202
+ `deploy.mjs blog --stack static --out site` put the REST layer in `site/js/wix/` (browser ESM,
203
+ the `.ts` beside each `.js` for reading). Everything the visitor loads lives under `site/` —
204
+ pages, styles, `js/` — and `wix.config.json`'s `site.outputDirectory` is `"./site"`; the project
205
+ root (config, plan, seed output) is never the upload. Same function names and DTOs as the table
206
+ above, so the contracts on this page hold unchanged: `fetchPosts`, `fetchPostBySlug`,
207
+ `postMetaLine` from `./js/wix/posts.js`; `fetchBlogCategories`, `fetchBlogTags`,
208
+ `fetchCategoryBySlug`, `fetchTagBySlug` from `./js/wix/taxonomy.js`; `imgAttrs` from
209
+ `./js/wix/media.js`. The state machines ship too: `createBlogFeedStore` from
210
+ `./js/wix/blog-feed-store.js` (the feed — filters, cursor paging; `start()` once the page is up)
211
+ and `createPostStore` from `./js/wix/post-store.js` (the post and its resolved chips). No
212
+ components ship — you write the rendering in plain JS: one render function per surface that reads
213
+ `getState()`, called from `subscribe`, with the surface's controls calling the store's actions.
214
+ Pages are `blog.html` and `post.html?slug=…` (Wix static hosting serves files, not directories —
215
+ name the file and link to it). The body renders from `post.paragraphs` (no Ricos viewer without a
216
+ bundler — see the reference files note). Set `document.title` and the meta description from
217
+ `post.title` and `post.excerpt` once the post loads, as the Astro page does. The visitor token
218
+ persists in `localStorage` on its own; never mint per page. `npx @wix/cli@latest release`
219
+ uploads `site/`.
220
+
221
+ ### Wiring — server-rendered, another language (Flask, Laravel, Rails, …)
222
+
223
+ Read the reference files listed above before writing any surface.
224
+
225
+ Run `deploy.mjs blog --stack static` in the project folder anyway: `js/wix/` is both the
226
+ browser-side code and the readable spec. Reads render on the server: port `js/wix/posts.ts`,
227
+ `taxonomy.ts` and their `*-core.ts` to your language — the same six functions returning the same
228
+ DTO shapes as dicts, one anonymous visitor token per process for these public reads (mint and
229
+ refresh per `client.ts`) — and render the feed and the post page in your templates to the
230
+ contracts above, so titles and excerpts are in the HTML; post-page tags from `post.title` and
231
+ `post.excerpt`. The body is `paragraphs` server-side too (the Ricos viewer is React). Nothing in
232
+ this vertical runs on the visitor's behalf, so the browser side is optional: the feed's category
233
+ filter and load-more can run client-side on `./js/wix/blog-feed-store.js` exactly as the static
234
+ wiring above, or as plain server routes (`/blog?category=<slug>` via `fetchCategoryBySlug`,
235
+ paging by cursor). Routes stay `/blog`, `/blog/<slug>`.
236
+
237
+ **Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted.** Same port for
238
+ the reads, run at build time with one anonymous token; the generator must emit a page for every
239
+ post — walk `fetchPosts` by `nextCursor` until it is null, never only the first page. Run
240
+ `deploy.mjs blog --stack static --out <build dir>` so `js/wix/` is inside the output the pages
241
+ import from, point `site.outputDirectory` at that folder, `wix release`. Pages sit at different
242
+ depths (`/`, `/blog/…`): give the templates one base path to `js/wix/` (a template variable, or
243
+ root-relative `/js/wix/…`), never a relative `./js/wix/`. The frozen grid is the first paint; the
244
+ category filter and load-more still run client-side on it through `createBlogFeedStore()` from
245
+ `./js/wix/blog-feed-store.js`. Close with the live URL, the rebuild + release command, and one
246
+ line for the owner: posts published in the dashboard reach the site when that command runs.
247
+
91
248
  ### Wiring — React SPA (Vite etc.)
92
249
 
93
250
  Import `./styles/global.css` once at the app entry (needs `@tailwindcss/vite` in the vite
94
251
  plugins — deploy added the dep). Routes: `/blog` → your feed on `useBlogFeed`;
95
- `/blog/:slug` → your post surface on `usePost` (the `PostView` reference shows the shape).
252
+ `/blog/:slug` → your post surface on `usePost({ slug })` (the `PostView` reference shows the
253
+ shape; `notFound` → your 404 view). Deploy wrote the public client id into `wix/config.ts`;
254
+ nothing else to configure.
255
+
256
+ Routes on Wix hosting: the host serves files only, so a clean route answers 404 when loaded directly — hash routes, or one HTML file per route, decided before the first route is written; any URL handed to Wix as a return target must be one the host serves (SKILL.md step 1).
96
257
 
97
258
  ## Hard rules
98
259
 
99
- - **The body renders only through `RichContent`** — a post body is a Ricos document, not
100
- HTML and not text: never `set:html`/innerHTML it, never stringify its nodes, never write
101
- your own node walker. `paragraphs` is the honest fallback it already handles.
102
- - **Route by `slug` through the shipped functions** — never hand-build a post/category/tag
103
- URL from ids; display taxonomy by `.label` (the API has no `.name`).
104
- - **`notFound` means not found** — render your 404 state; never invent a post. Only
105
- published posts come back, so a "missing" post is usually an unseeded/unpublished one.
106
- - **Never hand-build a wixstatic image URL** — `coverUrl` is already resolved; anything else
107
- goes through `wix/media.ts`.
108
- - Theme via the `@theme` tokens; no parallel theme files, no hardcoded palettes.
109
- - Live data or an honest empty state — never mock posts, authors, dates, or read times; no
110
- stock placeholder covers. No author bylines or comment/like UI — the DTOs don't carry
111
- them; don't fabricate engagement.
260
+ - **The body renders only through `RichContent`** where it deploys (Astro, React) — a post body
261
+ is a Ricos document, not HTML and not text: never `set:html`/innerHTML it, never stringify its
262
+ nodes, never write your own node walker. Where it doesn't deploy, `paragraphs` is the honest
263
+ body, and the closing message says so.
264
+ - **Data logic only through the shipped exports** — never rewrite their internals or re-derive a
265
+ request shape; extend by adding a function in `wix/blog/` (API contracts: the `wix-docs` skill).
266
+ - **Route by `slug` through the shipped functions** — never hand-build a post/category/tag URL
267
+ from ids; display taxonomy by `.label` (the API has no `.name`).
268
+ - **`notFound` means not found** — render your 404 state; never invent a post. Only published
269
+ posts come back, so a "missing" post is usually an unseeded/unpublished one.
270
+ - **Filter and page at the source** — `useBlogFeed`'s filters and `fetchPosts` run on Wix before
271
+ cursor paging; never filter a loaded page client-side, never raise the page limit instead of
272
+ paging (`hasMore`/`loadMore`).
273
+ - **Never hand-build a wixstatic image URL** — `coverUrl` is already resolved; anything else goes
274
+ through `wix/media.ts`.
275
+ - Where the shipped components deploy: theme via the `@theme` tokens, your markup in Tailwind
276
+ utilities on the same tokens; no parallel theme files, no hardcoded palettes. Where they don't
277
+ (`lib`, `static`, a port): style with what your stack does well, on one token set of your own.
278
+ - Live data or an honest empty state — never mock posts, authors, dates, or read times; no stock
279
+ placeholder covers. No author bylines or comment/like UI — the DTOs don't carry them.
112
280
  - Keep the post page's SEO pieces and `[...slug]` rest param exactly as shipped.
281
+ - **Call every hook before any conditional return.** Hooks first, branches after.
113
282
 
114
283
  ## Point the user to their dashboard
115
284
 
116
- Give the owner the dashboard link plus the Blog pages — the deploy step's JSON printed
117
- `dashboardUrl`; append `/blog/posts` for writing/editing posts and `/blog/categories` for
118
- the category menu. Only published posts appear on the site.
285
+ Hand the owner these links — `{siteId}` is `siteId` in `wix.config.json` (the deploy JSON prints it as
286
+ `dashboardUrl`).
287
+
288
+ | page | `https://manage.wix.com/dashboard/{siteId}/` + |
289
+ |---|---|
290
+ | Posts (published and drafts) | `blog/posts` |
291
+ | Categories | `blog/categories` |
292
+ | Tags | `blog/tags` |
293
+ | Writers | `blog/writers` |
294
+ | Comments | `blog/comments` |
295
+ | Blog settings | `blog/settings` |
296
+
297
+ Only published posts appear on the site.
119
298
 
120
299
  ## Seeding
121
300
 
122
301
  Per `seed/SEED.md` — plain-data `plan.json` into `seed-blog.mjs` from the project root.
123
302
  Seed posts that exercise the UI (~3 posts, 2 categories when the brief has sections, varied
124
303
  content blocks, a cover image per post).
125
-
126
- ## Verify (before declaring done)
127
-
128
- - [ ] `/blog` renders live posts SSR (view-source shows titles) through YOUR components;
129
- empty blog shows your honest empty state.
130
- - [ ] A post page renders the full body via `RichContent` (headings/quotes/lists show, not
131
- raw text), with your header design; a bad slug returns 404/not-found.
132
- - [ ] Category filter narrows the feed (server-side — check the network tab, not a hidden
133
- client filter); load-more appends the next page.
134
- - [ ] Post page view-source carries the SEO tags (Astro).
135
- - [ ] Card/grid/index/post surfaces/home are YOUR designs on the tokens; data-layer/hook
136
- files unedited; `RichContent` wired as-is.
137
- - [ ] Dashboard links handed to the owner.
@@ -2,6 +2,7 @@
2
2
  // Correct and complete; per the skill's model you design and build your own on useBlogFeed.
3
3
  import type { ComponentType, ReactNode } from "react";
4
4
  import { useBlogFeed } from "../../hooks/blog/useBlogFeed";
5
+ import { postMetaLine } from "../../wix/blog/posts";
5
6
  import type { BlogCategory, BlogTag, PostPage, PostSummary } from "../../wix/blog/types";
6
7
 
7
8
  export interface LinkLikeProps {
@@ -38,9 +39,7 @@ export function PostCard({ post, postHref = (slug) => `/blog/${slug}`, LinkCompo
38
39
  <h3 className="mt-3 text-base font-semibold leading-snug text-foreground">{post.title}</h3>
39
40
  {post.excerpt && <p className="mt-1 line-clamp-2 text-sm text-muted-foreground">{post.excerpt}</p>}
40
41
  <p className="mt-2 text-xs text-muted-foreground">
41
- {post.dateLabel && <time dateTime={post.dateISO}>{post.dateLabel}</time>}
42
- {post.dateLabel && post.minutesToRead > 0 ? " · " : ""}
43
- {post.minutesToRead > 0 ? `${post.minutesToRead} min read` : ""}
42
+ <time dateTime={post.dateISO}>{postMetaLine(post)}</time>
44
43
  </p>
45
44
  </LinkComponent>
46
45
  );