@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.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +14 -112
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +302 -51
- package/skills/wix-headless-fast/entry/skill.md +11 -2
- package/skills/wix-headless-fast/install/AGENTS.md +32 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +360 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/setup.mjs +239 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +226 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/blog/seed/read-site.mjs +22 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +181 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/bookings/seed/read-site.mjs +28 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +238 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/cms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +241 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/seed/read-site.mjs +26 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +216 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +316 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/members/seed/read-site.mjs +15 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +240 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/portfolio/seed/read-site.mjs +20 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +19 -21
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +214 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/read-site.mjs +21 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +242 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/seed/read-site.mjs +25 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +25 -5
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/shared/seed/read-site.mjs +99 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +186 -63
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +314 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +2 -0
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +31 -9
- package/skills/wix-headless-fast/references/storefront/seed/read-site.mjs +38 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +57 -50
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +0 -186
- 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,
|
|
5
|
-
presentation
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
**
|
|
13
|
-
|
|
14
|
-
|
|
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`
|
|
19
|
-
| `wix/
|
|
20
|
-
| `wix/blog/
|
|
21
|
-
| `wix/blog/
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
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,
|
|
39
|
-
|
|
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,
|
|
43
|
-
the shipped `RichContent`, and the tag chips — restyle the `[...slug].astro` template
|
|
44
|
-
|
|
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
|
|
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
|
|
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`**
|
|
100
|
-
HTML and not text: never `set:html`/innerHTML it, never stringify its
|
|
101
|
-
your own node walker. `paragraphs` is the honest
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
`dashboardUrl
|
|
118
|
-
|
|
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
|
-
|
|
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
|
);
|