@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 +1 -1
- package/skills/wix-headless/references/BUILD-astro.md +10 -1
- package/skills/wix-headless/references/shared/STYLING.md +0 -1
- package/skills/wix-headless/scripts/compose.mjs +19 -24
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +15 -9
- package/skills/wix-headless/references/astro/templates/Footer.astro +0 -27
- package/skills/wix-headless/references/astro/templates/Navigation.astro +0 -38
- package/skills/wix-headless/references/astro/templates/index.astro +0 -56
package/package.json
CHANGED
|
@@ -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
|
-
|
|
460
|
-
|
|
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
|
-
|
|
471
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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
|
-
// `<
|
|
6
|
-
//
|
|
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
|
|
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
|
|
28
|
-
// *
|
|
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
|
-
|
|
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(
|
|
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: "
|
|
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>
|