@xenterprises/nuxt-x-marketing 1.4.2 → 1.4.4
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/AGENTS.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# AGENTS.md — @xenterprises/nuxt-x-marketing
|
|
2
|
+
|
|
3
|
+
> Marketing site layer (v1.2.3, 38+ components). For workspace-wide conventions, see the root `AGENTS.md` at `/Users/timmushen/Sites/modules/nuxt-layers/AGENTS.md`.
|
|
4
|
+
|
|
5
|
+
## What this layer is
|
|
6
|
+
Nuxt 4 layer for marketing websites: 38+ pre-built components, consent-aware analytics (GTM/GA4/Clarity via `useConsentTracking`), parallax/scroll-reveal effects, and blog integration. **SSR enabled** (`ssr: true`, unlike the auth layers). Built on `@nuxt/ui` v4 + Tailwind v4 + `@tailwindcss/typography`. `@iconify-json/lucide` is a required peer dep.
|
|
7
|
+
|
|
8
|
+
## Commands (run inside this package)
|
|
9
|
+
| Task | Command |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Dev | `npm run dev` → `nuxi dev .playground` |
|
|
12
|
+
| Build / generate / preview | `npm run build` / `generate` / `preview` (target `.playground`) |
|
|
13
|
+
| Typecheck | `npm run typecheck` (`nuxt typecheck .playground`) |
|
|
14
|
+
| Lint | `npm run lint` (`eslint .`) |
|
|
15
|
+
| Full test suite | `npm test` (= `test:unit && test:e2e`) |
|
|
16
|
+
| Unit (one-shot / watch / coverage) | `npm run test:unit` / `test:unit:watch` / `test:unit:coverage` (spec-name aliases: `test:run` / `test:coverage`) |
|
|
17
|
+
| E2E smoke — plan-17 gate (headless / ui / headed) | `npm run test:e2e` / `test:e2e:ui` / `test:e2e:headed` |
|
|
18
|
+
| E2E full multi-browser + a11y suite | `npm run test:e2e:full` |
|
|
19
|
+
| Visual regression (incl. update snapshots) | `npm run test:visual` / `test:visual:update` |
|
|
20
|
+
|
|
21
|
+
Unit scripts point at `test/vitest.config.js`. There are two Playwright configs:
|
|
22
|
+
|
|
23
|
+
- **`test/playwright.smoke.config.js`** (plan-17 gate, `test:e2e`) — single chromium project, runs only `test/e2e/smoke.spec.js`, `webServer` **builds and previews** `.playground` on port **3114** (`npm run build && PORT=3114 npm run preview`). Smoke covers: homepage hero/navbar/footer render, pricing monthly→yearly toggle (interactive flow), `/blog` list render, and a `/blog/<slug>` post render from Nuxt Content.
|
|
24
|
+
- **`test/playwright.config.js`** (full suite, `test:e2e:full` / `test:visual`) — coverage thresholds (statements/lines 80, branches 75), projects for chromium/firefox/webkit/mobile/tablet, base URL `http://localhost:3000`, `webServer.command: 'npm run dev'`. Runs the accessibility/components/dark-mode/hero/responsive/visual-regression specs.
|
|
25
|
+
|
|
26
|
+
## Layout
|
|
27
|
+
- `app/app.vue` — the **default shell**: config-driven navbar (`XHeaderNav` ← `xMarketing.header`), footer (`XFooter` ← `xMarketing.footer`), and cookie-consent banner (`XMarkPrivacyCookieConsent` ← `xMarketing.tracking`). Opt-outs: `xMarketing.header.active: false`, `footer.active: false`, `consent.active: false`; or ship your own `app/app.vue`.
|
|
28
|
+
- `app/components/X/{Footer,Header,Mark,X,_archive}/` → auto-import prefix **`XMark`** (e.g. `<XMarkLayoutFooter>`, `<XMarkLayoutNavbar>`, `<XMarkSectionNewsletter>`). The `_archive/` folder is superseded — don't extend from it.
|
|
29
|
+
- `app/composables/`: `useConsentTracking.ts` (consent-gated tracking), `useParallax.js`, `useScrollReveal.js`, `useXBlog.js` (Nuxt Content blog helpers — **not** Builder.io). Auto-imported.
|
|
30
|
+
- `content.config.ts` — default `blog` collection for Nuxt Content v3. Consumer markdown lives under `content/blog/`.
|
|
31
|
+
- `app/pages/blog/` — index + `[...slug]` driven by `queryCollection('blog')` + `XMarkBlog*` components.
|
|
32
|
+
- `app/types/*.d.ts`, `app/app.config.ts`, `app/app.vue`, `app/assets/css/x-marketing.css`.
|
|
33
|
+
- `test/` — `unit/`, `e2e/`, `utils/`, `fixtures/` (note: `test/`, not `tests/`); configs and `test/setup.js`.
|
|
34
|
+
- Non-shipped local folders: `prompts/`, `screenshots/`, `plans/`, `componentList.json`.
|
|
35
|
+
|
|
36
|
+
## Conventions
|
|
37
|
+
- **Auto-imports are the source of truth** — no explicit imports for `XMark*` components or `use*` composables (or Nuxt globals like `useAppConfig`/`useRouter`).
|
|
38
|
+
- **Config namespace** `xMarketing` in `app.config.ts`. CSS entry is `app/assets/css/x-marketing.css`.
|
|
39
|
+
- **`.playground/nuxt.config.ts`** adds the `nuxt-shiki` module (bundled themes/langs).
|
|
40
|
+
- **Colors**: `@nuxt/ui` semantic colors only (`primary`/`secondary`/`neutral`/`success`/`warning`/`error`). No custom Tailwind colors.
|
|
41
|
+
- **Forms/modals**: Nuxt UI v4 patterns — `UForm`/`UFormGroup`/`UInput` (don't reformat), `useModal()` composable (not `v-model` refs).
|
|
42
|
+
- **`<script setup>`** here uses `lang="ts"` (this is a layer, not a product app). Composition API only, strict TS, never `as any` / `@ts-ignore`.
|
|
43
|
+
- **Semantic HTML** — avoid div soup; use `<main>`/`<section>`/`<article>`/`<header>`/`<nav>`/`<figure>`.
|
|
44
|
+
|
|
45
|
+
## Testing specifics
|
|
46
|
+
- Unit tests are in `test/unit/` and may be `.js`. `test/setup.js` globally exposes the Vue composition API and provides extensive UI component stubs (`UButton`, `UModal`, `NuxtLink`, `UInput`, …). A `nuxtMetaPlugin` rewrites `import.meta.client` → `true`.
|
|
47
|
+
- E2E focuses on critical paths. `test:e2e` is the plan-17 smoke gate (4 happy-path specs against the built+previewed playground, port 3114); the full multi-browser suite is `test:e2e:full`. Visual regression snapshots live under version control — update deliberately via `test:visual:update`.
|
|
48
|
+
|
|
49
|
+
## X Enterprises standards (cross-cutting)
|
|
50
|
+
Shared stack: Fastify v5 backend, Prisma + NeonDB, BetterAuth (users) / Stack Auth (admins). Full standards (API response shape, multi-tenant `tenant_id` scoping, Fastify plugin order, env vars, Inngest webhook forwarding) live in the root `AGENTS.md`. Rule that touches this layer: webhooks forward to Inngest immediately; `queryCollection(...).find()` (not `.all()`).
|
|
51
|
+
|
|
52
|
+
## Git & release
|
|
53
|
+
Remote is **GitLab**. Branch from `main`; conventional commits; MRs via `glab`. Release policy: ride the 1.x line — components = minor, fixes = patch, breaking changes batch into minors with a Breaking note; the program owner cuts the coordinated 2.0.0 across all layers. Docs live in the sibling `docs` repo — update `docs/content/1.nuxt/3.nuxt-x-marketing/` in lockstep with component/behavior changes.
|
|
54
|
+
|
|
55
|
+
## Status toward stable (v1.2.3 → next stable line)
|
|
56
|
+
See the root `AGENTS.md` **Stable Release Spec** for the full bar. Already past 1.0 with the strongest test infrastructure in the workspace (553 unit tests + Playwright + coverage). It still has gaps vs. the spec.
|
|
57
|
+
|
|
58
|
+
**Gaps vs. the stable bar:**
|
|
59
|
+
1. 🟢 **Quality gates present and green** — `typecheck`, `lint`, `test`/`test:unit`/`test:e2e`, `test:unit:coverage`; spec-name aliases `test:run` / `test:coverage` added (existing names still work). `build` of `.playground` passes.
|
|
60
|
+
2. 🟢 **Peer deps use caret ranges; no `latest` anywhere** — Wave 0 tightened `>=` → `^` for ui/content/lucide/typography/sqlite/nuxt/vue; devDep `vue` pinned `latest` → `^3.5.39`.
|
|
61
|
+
3. 🟢 **Composable coverage ≥80% statements** — v8 coverage now measures `app/composables/**` (was misconfigured to measure `test/` files): **86.81% stmts / 88.27% branch / 93.93% funcs / 86.81% lines** across the 4 composables (`useXBlog.js` 55.9% — Content query helpers aren't logic-testable; aggregate passes). 553 unit tests / 21 files; component test breadth is still uneven across 38+ components.
|
|
62
|
+
4. 🟡 **Lint baseline scopes** — unused-vars/`unified-signatures` softened and `_archive/` ignored; tighten in plan 14. (0 errors, 60 warnings.)
|
|
63
|
+
5. 🟢 **Blog uses Nuxt Content** — Builder.io CDN removed from layer blog pages; peers include `@nuxt/content` + `better-sqlite3`.
|
|
64
|
+
6. 🟢 **Playground e2e smoke** (spec item 8) — `test:e2e` runs the plan-17 smoke gate: `test/playwright.smoke.config.js` + `test/e2e/smoke.spec.js`, single chromium project against a **built+previewed** playground (port 3114). Covers homepage render, pricing toggle interaction, blog list, and blog post render. The full multi-browser/a11y/visual suite is untouched (`test:e2e:full`, `test:visual`). Known `test:e2e:full` debt (pre-existing, 2026-07-24): it runs against `npm run dev` with 30s test timeouts, so full parallelism causes vite-contention timeouts (run with `--workers=2`); and the axe audit specs fail with a large violation backlog (~800 nodes) across the playground's 68 demo components — a real a11y hardening backlog, not a gate. The stale hero title assertion was fixed (`main`-scoped h1, actual playground title).
|
|
65
|
+
7. 🟢 **Batteries-included audit** (spec item 9) — done (plan 18, 2026-07-23). Ideal consumer is `extends` + `app/app.config.ts` + `content.config.ts` blog re-declaration (Nuxt Content source-resolution constraint, documented in docs + README) — proven by a scratch consumer (extends-by-path, config-only) rendering the shell, `/`, `/blog`, `/blog/<slug>` with zero unresolved-component warnings, and `blog.active: false` → 404. Follow-up (b) closed 2026-07-24: `xMarketing.name`/`url` are now live config — the shell's `useSeoMeta` builds the title template (`<page> | <name>` when `name` is set, bare title otherwise) and `og:site_name` from `name`, and the shipped home page reads `url` for `og:url`; layer defaults changed from the placeholder `"X Enterprises"`/`"https://x-enterprises.com"` to `undefined` per the site-overridable-fields convention. Remaining follow-ups (feature backlog, not audit blockers): (a) the footer/newsletter forms submit to `console.log` only — no server route or documented backend contract yet (genuine feature build: needs a server route, a backend contract, and docs); (c) the playground shadows the layer shell with its own docs chrome, so the plan-17 smoke doesn't exercise the shell — the shell proof is the manual scratch consumer, consider a permanent fixture later.
|
|
66
|
+
|
|
67
|
+
**Audit (plan 18, 2026-07-23).** Findings per dimension: (1) Default pages `/`, `/blog`, `/blog/[...slug]` shipped ✓ — but `/` and the shell referenced **phantom components** (`XMarkNavbar`, `XMarkFooter`, `XMarkTestimonials`, `XMarkPricing`, `XMarkBadge`) that don't exist, so a bare consumer got a silently broken shell and a home page missing its testimonials/pricing/badge sections; fixed to the real `XHeaderNav`/`XFooter`/`XMarkSocialProof*`/`XMarkPricingPlans` names. (2) Opt-outs added: `xMarketing.header.active`, `footer.active`, `consent.active` (all default `true`), `blog.active` (existed but was inert — now wired to 404 both blog pages); home page opt-out is standard Nuxt page overriding. (3) No layout ships — the shell lives in `app.vue`; consumer layouts win via `NuxtLayout`. (4) No server routes needed by components; newsletter submit is a stub (follow-up (a)). (5) Config defaults: all site-overridable `""` values converted to `undefined` (`markerProjectId`, `organizationId`, `header.logo.*`, `footer.logo.*`, `footer.bg.*`, `footer.body`); also fixed a mis-nested type declaration that put `tracking` inside `footer` in `AppConfigInput`. (6) SEO: home + blog pages set `useSeoMeta` ✓. (7) Docs quickstart (`docs/content/1.nuxt/3.nuxt-x-marketing/`) already described this exact consumer surface — reality now matches it; README's broken "write your own app.vue with XMarkNavbar" quickstart replaced with the config-only quickstart + opt-out list. (8) Playground demonstrates all 68 components + the content binding recipe, but uses its own docs chrome (see follow-up (c)). Also fixed: `UToggle` → `USwitch` (Nuxt UI v4 rename) in `CookieConsent.vue` + `GDPR.vue` — the consent preferences modal was rendering without its toggles.
|
|
68
|
+
|
|
69
|
+
**Breaking (shell, batch into next minor):** the layer's `app/app.vue` was rewritten from a hardcoded demo shell (which referenced non-existent `XMarkNavbar`/`XMarkFooter` and so rendered no navbar/footer at all) to a config-driven shell (`XHeaderNav` + `XFooter` reading `xMarketing.header`/`footer`, plus the consent banner). Consumers without their own `app/app.vue` will see their rendered shell change: navbar/footer now render from config, the hardcoded newsletter section and dead demo links are gone, and logo fallbacks `/logo-white.svg`/`/logo-dark.svg` were removed.
|
|
70
|
+
|
|
71
|
+
**Breaking (blog):** consumers that relied on `NUXT_PUBLIC_BUILDERIO_KEY` + Builder models must migrate posts to `content/blog/*.md` and install Content peers. Prefer a **2.0** stable marker when shipping this with peer tightening.
|
|
72
|
+
|
|
73
|
+
**Breaking (config, batch into next minor):** the `xMarketing.name`/`url` defaults changed from the placeholders `"X Enterprises"`/`"https://x-enterprises.com"` to `undefined`, and both keys are now live (shell title template / `og:site_name`, home-page `og:url`). Consumers that set `name` will see page titles gain a `| <name>` suffix in the shipped shell; consumers reading the old placeholder defaults from `appConfig` get `undefined`.
|
|
74
|
+
|
|
75
|
+
**Stabilization plan (not "reach 1.0" — already there):** close the remaining gaps — (a) tighten lint rules, (b) commit and refresh the visual-regression baselines, (c) Content blog migration accepted by consumers, ~~(d) align the Playwright suite to the plan-17 smoke scope~~ (done — smoke gate is `test:e2e`), ~~(e) batteries-included audit (plan 18)~~ (done — item 7 🟢). The layer rides the 1.x line; the program owner cuts the coordinated 2.0.0 once all layers are green.
|
package/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
30
30
|
section and its sample product data were dropped with them. Every other section of the
|
|
31
31
|
demo page is unchanged.
|
|
32
32
|
|
|
33
|
+
## [1.4.4] - 2026-09-03
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- `XMarkHero` adds responsive `pb-6 md:pb-8` inside the shared text stack, increasing space below hero copy and actions without moving the background, top edge, or outer frame. The same inset applies to image and plain variants.
|
|
38
|
+
- `XSchema` JSON-LD script uses a text slot instead of `v-text`, clearing `vue/no-v-text-v-html-on-component`. Output is unchanged.
|
|
39
|
+
|
|
40
|
+
## [1.4.3] - 2026-09-03
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `XHeaderNav` and `XXHeaderNav` drop the nested logo link inside `#title`. `UHeader`
|
|
45
|
+
already wraps that slot in a `ULink`, so the extra `NuxtLink` rendered an anchor
|
|
46
|
+
inside an anchor and caused a hydration mismatch. The header link is now the only
|
|
47
|
+
link around the logo.
|
|
48
|
+
- Both Nav variants pass `to="/"` and `:title="header?.logo?.alt"` to `UHeader`, so the
|
|
49
|
+
link `aria-label` is the logo alt text instead of the `"Nuxt UI"` default.
|
|
50
|
+
|
|
33
51
|
## [1.4.2] - 2026-08-23
|
|
34
52
|
|
|
35
53
|
### Fixed
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<UHeader>
|
|
2
|
+
<UHeader to="/" :title="header?.logo?.alt">
|
|
3
3
|
<template #title>
|
|
4
|
-
<
|
|
4
|
+
<template v-if="header?.logo?.src">
|
|
5
5
|
<img
|
|
6
6
|
:src="header?.logo?.src"
|
|
7
7
|
:alt="header?.logo?.alt || ''"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
:alt="header?.logo?.alt || ''"
|
|
13
13
|
class="h-8 hidden dark:block"
|
|
14
14
|
/>
|
|
15
|
-
</
|
|
15
|
+
</template>
|
|
16
16
|
<span v-else>{{ header?.logo?.alt }}</span>
|
|
17
17
|
</template>
|
|
18
18
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
<!-- Content -->
|
|
47
47
|
<div class="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
48
|
-
<div class="max-w-3xl" :class="contentAlignmentClasses">
|
|
48
|
+
<div class="max-w-3xl pb-6 md:pb-8" :class="contentAlignmentClasses">
|
|
49
49
|
<!-- Eyebrow -->
|
|
50
50
|
<p v-if="eyebrow" class="xText-eyebrow text-white/70 mb-4 xFadeUp">
|
|
51
51
|
{{ eyebrow }}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<UHeader>
|
|
2
|
+
<UHeader to="/" :title="header?.logo?.alt">
|
|
3
3
|
<template #title>
|
|
4
|
-
<
|
|
4
|
+
<template v-if="header?.logo?.src">
|
|
5
5
|
<img
|
|
6
6
|
:src="header?.logo?.src"
|
|
7
7
|
:alt="header?.logo?.alt || ''"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
:alt="header?.logo?.alt || ''"
|
|
13
13
|
class="h-8 hidden dark:block"
|
|
14
14
|
/>
|
|
15
|
-
</
|
|
15
|
+
</template>
|
|
16
16
|
<span v-else>{{ header?.logo?.alt }}</span>
|
|
17
17
|
</template>
|
|
18
18
|
|