@wix/agent-skills 1.13.0 → 1.14.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/agent-skills",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Wix Skills for AI coding agents — versioned npm distribution of the wix/skills content.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,13 +51,22 @@ The whole Setup window is a single message of sibling `Bash` calls — **emit th
51
51
  "navLinks": [ { "href": "/", "label": "Home" }, ... ],
52
52
  "loadedPacks": ["stores", "cms", ...],
53
53
  "packsWithComponents": ["stores", "ecom", ...],
54
- "disabledPacks": ["gift-cards", ...]
54
+ "disabledPacks": ["gift-cards", ...],
55
+ "navPath": ".wix/nav.astro",
56
+ "footerPath": ".wix/footer.astro",
57
+ "homePath": ".wix/home.astro"
55
58
  }
56
59
  COMPOSE
57
60
  ```
58
61
 
59
62
  The compose-input shape is documented in `scripts/compose.mjs`'s header.
60
63
 
64
+ **The entire visible page is LLM-generated.** `compose.mjs` still writes the plumbing — `global.css`, `astro.config.mjs`, and `Layout.astro` (the `<html>`/`<head>` shell that imports the fonts, `global.css`, ClientRouter, and the `<Navigation/>`+`<Footer/>` components) — but the **header, footer, and home page are authored by an LLM**, not from templates (there are no `Navigation.astro` / `Footer.astro` / `index.astro` templates anymore). Between `emit-design-tokens.mjs` and `compose.mjs`, dispatch a **Default-tier** generator that reads the token vocabulary at `.wix/design-tokens.css` and authors three files, then pass their paths:
65
+ - `.wix/nav.astro` → `Navigation.astro` (site header). Contract: one `<header class="site-nav" transition:persist="site-nav">` with brand mark + real nav links (`/`, `/about`, `/faq`), a `<!-- nav:links -->` + `<!-- nav:actions -->` marker and an empty `<div class="nav-actions">` for later vertical patches, and a no-JS mobile toggle.
66
+ - `.wix/footer.astro` → `Footer.astro`. Contract: one `<footer class="site-footer">` with brand, a short tagline, and the nav links.
67
+ - `.wix/home.astro` → `index.astro` (full home page). Contract: frontmatter importing `Layout` + a `<Layout>…</Layout>` wrapper; one `<!-- home:<pack> -->` marker per loaded home-contributing pack; and **two decorative-image slots** — `hero` + one section (e.g. `about`). **Each slot is a DEDICATED, EMPTY placeholder element that holds no copy** — e.g. `<div class="hero-image" data-decorative-slot="hero" style="aspect-ratio:4/5"></div>` sitting *beside* the hero text, NOT the `data-decorative-slot` attribute slapped on the content-bearing `<section>`. The image patcher injects an `<img>` only into an empty slot element; if the slot wraps real copy it is skipped (the image never lands). Any element type works (`<div>`/`<figure>`/…) as long as it is its own empty element.
68
+ - **All three:** compose tokens as utility classes so they match the theme; write real copy (no `{{…}}`); keep `transition:persist` + the `site-nav`/`site-footer` classes (the View-Transitions + `global.css` styling target them). `compose.mjs` writes each verbatim and **errors if any path is missing** (`NO_NAV`/`NO_FOOTER`/`NO_HOME`). Manifest records `nav`/`footer`/`home` = `"llm-generated"`.
69
+
61
70
  **In the SAME message — the business Setup Step 4 batch** (frontend-blind; `SETUP.md` owns recipes/package set). These overlap the bridge's `compose.mjs` (~20 s) so it adds no serial wall:
62
71
 
63
72
  3. `Bash` × N — app installs, one curl per `pack.apps[*]` → `SETUP.md` § Step 4a. Packs with no `apps[*]` (`cms`, `ecom`, `gift-cards`) install nothing — skip them.
@@ -46,7 +46,6 @@ These are layout/spacing/typography concerns that should always be utilities, ne
46
46
  > **Never reintroduce a named `--spacing-*` scale.** In Tailwind v4 the `--spacing-*` namespace also generates the width/sizing families (`max-w-*`, `w-*`, `min-w-*`, …), so any `--spacing-<key>` token shadows the matching `max-w-<key>`. The current fix works because the skill's spacing steps happen to equal Tailwind's numeric defaults — but if a brand ever needs genuinely *custom* spacing **values**, put them under a different namespace (e.g. `--rhythm-*`) or use arbitrary values (`py-[5.5rem]`); do not revive `--spacing-*`.
47
47
 
48
48
  **Width / reading columns:** `max-w-md`, `max-w-3xl`, `max-w-6xl`, `max-w-prose` now resolve correctly to the `--container-*` keys (no spacing collision), so use them freely; `container-reading` (the designer `@utility`) or an arbitrary `max-w-[48rem]` also work.
49
-
50
49
  If a designer's `global.css` contains rules like `.featured-section { padding-block: 6rem; }` or `.product-card-body { display: flex; flex-direction: column; gap: 0.5rem; }`, those are misplaced — they belong in markup as utilities (`py-24`, `flex flex-col gap-2`). Inventing such classes ships broken layouts: every consumer needs the designer to have pre-declared the class, and Tailwind v4 silently drops the references when the rule is missing.
51
50
 
52
51
  ## Required tokens — the component-CSS template contract
@@ -212,6 +212,14 @@ const loadedPacks = Array.isArray(input.loadedPacks) ? input.loadedPacks : [];
212
212
  const packsWithComponents = Array.isArray(input.packsWithComponents) ? input.packsWithComponents : [];
213
213
  const disabledPacks = Array.isArray(input.disabledPacks) ? input.disabledPacks : [];
214
214
 
215
+ // Full-page LLM generation (this branch): the LLM authors the entire visible
216
+ // page — header (Navigation.astro), footer (Footer.astro), and home page
217
+ // (index.astro). compose writes each verbatim; there are no templates for them.
218
+ const resolveIn = (p) => { const s = typeof p === "string" ? p.trim() : ""; return s ? (isAbsolute(s) ? s : join(projectDir, s)) : null; };
219
+ const navPath = resolveIn(input.navPath);
220
+ const footerPath = resolveIn(input.footerPath);
221
+ const homePath = resolveIn(input.homePath);
222
+
215
223
  const brandName = brand.name ?? "Brand";
216
224
 
217
225
  // ── small color helper (derivation fail-safe only) ────────────────────────────
@@ -454,27 +462,16 @@ const navItems = navLinks
454
462
  .filter((l) => l && l.href != null && l.label != null)
455
463
  .map((l) => ({ href: String(l.href), label: String(l.label) }));
456
464
 
457
- // ── 4. Navigation.astro ──────────────────────────────────────────────────────────
465
+ // ── 4. Navigation.astro (LLM-generated, verbatim) ─────────────────────────────
458
466
  {
459
- let out = stripAstroHeader(readTemplate("Navigation.astro"));
460
- out = out.replaceAll("{{shell.navBrandMark}}", shell.navBrandMark ?? brandName);
461
- const links = navItems
462
- .map((l) => ` <li class="site-nav-item"><a href="${escAttr(l.href)}">${l.label}</a></li>`)
463
- .join("\n");
464
- out = out.replace(/^\s*\{\{nav\.links\}\}\s*$/m, links);
465
- writeProject("src/components/Navigation.astro", out);
467
+ if (!navPath || !existsSync(navPath)) die("NO_NAV", `LLM-generated header required via navPath (none at ${navPath ?? "<unset>"})`);
468
+ writeProject("src/components/Navigation.astro", readFileSync(navPath, "utf8"));
466
469
  }
467
470
 
468
- // ── 5. Footer.astro ──────────────────────────────────────────────────────────────
471
+ // ── 5. Footer.astro (LLM-generated, verbatim) ─────────────────────────────────
469
472
  {
470
- let out = stripAstroHeader(readTemplate("Footer.astro"));
471
- out = out.replaceAll("{{brand.name}}", brandName);
472
- out = out.replaceAll("{{shell.footerTagline}}", shell.footerTagline ?? "");
473
- const links = navItems
474
- .map((l) => ` <li><a href="${escAttr(l.href)}">${l.label}</a></li>`)
475
- .join("\n");
476
- out = out.replace(/^\s*\{\{nav\.links\}\}\s*$/m, links);
477
- writeProject("src/components/Footer.astro", out);
473
+ if (!footerPath || !existsSync(footerPath)) die("NO_FOOTER", `LLM-generated footer required via footerPath (none at ${footerPath ?? "<unset>"})`);
474
+ writeProject("src/components/Footer.astro", readFileSync(footerPath, "utf8"));
478
475
  }
479
476
 
480
477
  // ── 6. index.astro ────────────────────────────────────────────────────────────────
@@ -485,13 +482,8 @@ const HOME_CONTRIBUTING = ["stores", "bookings", "gift-cards"]; // canonical ord
485
482
  const homePool = new Set([...loadedPacks, ...disabledPacks]);
486
483
  const homeMarkerPacks = HOME_CONTRIBUTING.filter((p) => homePool.has(p));
487
484
  {
488
- let out = stripAstroHeader(readTemplate("index.astro"));
489
- out = out.replaceAll("{{shell.heroHeadline}}", shell.heroHeadline ?? brandName);
490
- out = out.replaceAll("{{shell.heroSub}}", shell.heroSub ?? "");
491
- out = out.replaceAll("{{brand.name}}", brandName);
492
- const markers = homeMarkerPacks.map((p) => ` <!-- home:${p} -->`).join("\n");
493
- out = out.replace(/^\s*\{\{home-markers\}\}\s*$/m, markers);
494
- writeProject("src/pages/index.astro", out);
485
+ if (!homePath || !existsSync(homePath)) die("NO_HOME", `LLM-generated home page required via homePath (none at ${homePath ?? "<unset>"})`);
486
+ writeProject("src/pages/index.astro", readFileSync(homePath, "utf8"));
495
487
  }
496
488
 
497
489
  // ── manifest (the orchestrator parses this off stdout) ────────────────────────
@@ -508,6 +500,9 @@ const manifest = {
508
500
  phase: "compose",
509
501
  data: {
510
502
  filesWritten,
503
+ nav: "llm-generated",
504
+ footer: "llm-generated",
505
+ home: "llm-generated",
511
506
  componentCssImports: packsWithComponents,
512
507
  homeMarkers: homeMarkerPacks.map((p) => `home:${p}`),
513
508
  tokensApplied: {
@@ -2,10 +2,11 @@
2
2
  // Inject Image-Phase-1 decorative URLs into the page shells.
3
3
  //
4
4
  // The design-system Composer (index.astro) and the Phase 4 page designers write
5
- // `<div data-decorative-slot="<key>">…</div>` placeholders in src/pages/*.astro. Image Phase 1 returns a slot→URL map
6
- // in its JSON return; the orchestrator pipes that map (as JSON) into this
5
+ // `<el data-decorative-slot="<key>">…</el>` placeholders in src/pages/*.astro
6
+ // (any element — <div>, <section>, <figure>…). Image Phase 1 returns a slot→URL
7
+ // map in its JSON return; the orchestrator pipes that map (as JSON) into this
7
8
  // script's stdin. This script reads the map + walks src/pages/*.astro,
8
- // then injects an <img> as the first child of each matching slot div —
9
+ // then injects an <img> as the first child of each matching empty slot element —
9
10
  // pure string substitution, no LLM, no file rewrites.
10
11
  //
11
12
  // Usage:
@@ -23,9 +24,9 @@
23
24
  // blocks mode or Image Phase 1 had no URLs to publish).
24
25
  // - src/pages missing at <scaffold-dir> and in any single subdir one
25
26
  // level down → exit 2 status="error".
26
- // - For each `data-decorative-slot="<key>"` in src/pages/*.astro:
27
- // * Key has no URL in the input map → leave the div alone.
28
- // * Div already has a non-comment child → skip with a warning.
27
+ // - For each `data-decorative-slot="<key>"` element in src/pages/*.astro:
28
+ // * Key has no URL in the input map → leave the element alone.
29
+ // * Element already has a non-comment child → skip with a warning.
29
30
  // * Otherwise inject <img …> as the first child.
30
31
  // - Output JSON summary of patches/skips/warnings to stdout.
31
32
 
@@ -99,14 +100,19 @@ const patched = [];
99
100
  const skipped = [];
100
101
  const warnings = [];
101
102
 
102
- const SLOT_DIV_REGEX = /(<div[^>]*\bdata-decorative-slot="([^"]+)"[^>]*>)([\s\S]*?)(<\/div>)/g;
103
+ // Match ANY element carrying data-decorative-slot, not just <div>. Full-page
104
+ // LLM generation routinely puts the slot on a <section>/<figure>/<picture>;
105
+ // the closing tag is matched by backreference (\2) to the opening tag name.
106
+ // Slots are empty placeholders by contract, so the non-greedy body cannot
107
+ // straddle a sibling element of the same tag.
108
+ const SLOT_REGEX = /(<([a-zA-Z][\w-]*)[^>]*\bdata-decorative-slot="([^"]+)"[^>]*>)([\s\S]*?)(<\/\2>)/g;
103
109
 
104
110
  for (const file of candidates) {
105
111
  const original = readFileSync(file, "utf8");
106
112
  let modified = original;
107
113
  let fileChanged = false;
108
114
 
109
- modified = modified.replace(SLOT_DIV_REGEX, (match, openTag, slotKey, inner, closeTag) => {
115
+ modified = modified.replace(SLOT_REGEX, (match, openTag, _tagName, slotKey, inner, closeTag) => {
110
116
  const url = urlMap[slotKey];
111
117
  if (!url) {
112
118
  skipped.push({ file, slot: slotKey, reason: "no URL for this slot in input map" });
@@ -121,7 +127,7 @@ for (const file of candidates) {
121
127
  }
122
128
 
123
129
  if (stripped.length > 0) {
124
- warnings.push({ file, slot: slotKey, reason: "div has existing non-image child content; not patching to avoid clobber" });
130
+ warnings.push({ file, slot: slotKey, reason: "slot element has existing non-image child content; not patching to avoid clobber" });
125
131
  return match;
126
132
  }
127
133
 
@@ -1,27 +0,0 @@
1
- ---
2
- // ── Composer skeleton: src/components/Footer.astro ────────────────────────────
3
- // Substitutions (see compose.mjs):
4
- // {{brand.name}} — brand name for the wordmark + copyright.
5
- // {{shell.footerTagline}} — short brand-voice tagline from data.shell.
6
- // {{nav.links}} — same inlined Navigation links array (mirror/subset);
7
- // one <a href={href}>{label}</a> per entry, labels VERBATIM. No cart,
8
- // login, or account links.
9
- // Fixed bulk (keep literal): transition:persist, the year expression.
10
- const year = new Date().getFullYear();
11
- ---
12
- <footer class="site-footer" transition:persist="site-footer">
13
- <div class="max-w-6xl mx-auto px-6 py-16 flex flex-col md:flex-row md:items-start md:justify-between gap-8">
14
- <div class="flex flex-col gap-3 max-w-prose">
15
- <span class="font-display text-lg text-ink">{{brand.name}}</span>
16
- <p class="text-sm">{{shell.footerTagline}}</p>
17
- </div>
18
- <nav>
19
- <ul class="flex flex-col gap-3 text-sm">
20
- {{nav.links}}
21
- </ul>
22
- </nav>
23
- </div>
24
- <div class="max-w-6xl mx-auto px-6 pb-8 text-xs">
25
- <p>© {year} {{brand.name}}. All rights reserved.</p>
26
- </div>
27
- </footer>
@@ -1,38 +0,0 @@
1
- ---
2
- // ── Composer skeleton: src/components/Navigation.astro ────────────────────────
3
- // Substitutions (see compose.mjs):
4
- // {{shell.navBrandMark}} — brand mark / wordmark text from data.shell.
5
- // {{nav.links}} — one <a href={href}>{label}</a> per entry in the inlined
6
- // Navigation links array, labels VERBATIM (no editorial rebrands). Render
7
- // each inside a <li class="site-nav-item">.
8
- // Fixed bulk (keep literal): transition:persist, the <!-- nav:links --> marker,
9
- // the empty <div class="nav-actions"> mount, the mobile hamburger toggle.
10
- ---
11
- <header class="site-nav" transition:persist="site-nav">
12
- <nav class="max-w-6xl mx-auto px-6 flex items-center justify-between gap-6 py-4">
13
- <a href="/" class="font-display text-lg tracking-tight">{{shell.navBrandMark}}</a>
14
-
15
- <input type="checkbox" id="nav-toggle" class="peer hidden" aria-hidden="true" />
16
- <label
17
- for="nav-toggle"
18
- class="md:hidden inline-flex flex-col gap-[5px] cursor-pointer"
19
- aria-label="Toggle navigation"
20
- >
21
- <span class="block h-[2px] w-6 bg-ink"></span>
22
- <span class="block h-[2px] w-6 bg-ink"></span>
23
- <span class="block h-[2px] w-6 bg-ink"></span>
24
- </label>
25
-
26
- <div
27
- class="hidden peer-checked:flex md:flex absolute md:static top-full left-0 right-0 md:right-auto
28
- flex-col md:flex-row gap-4 md:gap-6 items-start md:items-center
29
- bg-paper md:bg-transparent border-b md:border-0 border-rule px-6 md:px-0 py-4 md:py-0"
30
- >
31
- <ul class="flex flex-col md:flex-row gap-4 md:gap-6 items-start md:items-center">
32
- {{nav.links}}
33
- <!-- nav:links -->
34
- </ul>
35
- <div class="nav-actions"></div>
36
- </div>
37
- </nav>
38
- </header>
@@ -1,56 +0,0 @@
1
- ---
2
- // ── Composer skeleton: src/pages/index.astro ──────────────────────────────────
3
- // Home shell. Substitutions (see compose.mjs):
4
- // {{shell.heroHeadline}}, {{shell.heroSub}} — brand-voice hero copy.
5
- // {{shell.footerTagline}} is NOT used here.
6
- // {{brand.name}} — used in the brand-story copy.
7
- // {{home-markers}} — one `<!-- home:<pack> -->` marker per LOADED pack that
8
- // contributes a home section (e.g. stores, gift-cards). Emit a marker
9
- // ONLY for such packs; never emit a marker no pack will fill (e.g. no
10
- // <!-- home:cms --> — CMS owns brand-story directly). Disabled packs
11
- // (gift-cards) still get their marker; do NOT add hero CTAs / teasers
12
- // pointing at disabled-pack routes.
13
- // Fixed bulk (keep literal): the Layout wrapper, hero/brand-story structure,
14
- // the data-decorative-slot="hero" and "about" placeholders (the orchestrator
15
- // injects <img> into these after Image Phase 1 — keep them empty).
16
- import Layout from '../layouts/Layout.astro';
17
- ---
18
- <Layout>
19
- <section class="relative">
20
- <div class="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
21
- <div class="flex flex-col gap-6">
22
- <h1 class="font-display text-4xl md:text-5xl leading-tight">{{shell.heroHeadline}}</h1>
23
- <p class="text-lg text-mute max-w-prose">{{shell.heroSub}}</p>
24
- </div>
25
- <div
26
- class="hero-image rounded-md"
27
- data-decorative-slot="hero"
28
- style="aspect-ratio: 4/5; background-color: var(--color-paper-warm);"
29
- >
30
- <!-- orchestrator injects <img src={decorativeImages.hero} …> here -->
31
- </div>
32
- </div>
33
- </section>
34
-
35
- {{home-markers}}
36
-
37
- <section class="max-w-6xl mx-auto px-6 py-24 grid grid-cols-1 md:grid-cols-2 gap-16 items-center">
38
- <div
39
- class="about-image rounded-md order-last md:order-first"
40
- data-decorative-slot="about"
41
- style="aspect-ratio: 4/3; background-color: var(--color-paper-warm);"
42
- >
43
- <!-- orchestrator injects <img src={decorativeImages.about} …> here -->
44
- </div>
45
- <div class="flex flex-col gap-4 max-w-prose">
46
- <hr class="editorial-rule w-16 ml-0" />
47
- <h2 class="font-display text-3xl leading-tight">The story behind {{brand.name}}</h2>
48
- <p class="text-ink leading-relaxed">
49
- A short, brand-contextual paragraph about what makes {{brand.name}} distinct.
50
- </p>
51
- <p class="text-mute leading-relaxed">
52
- A second paragraph adding warmth and detail to the brand story.
53
- </p>
54
- </div>
55
- </section>
56
- </Layout>