# Nuxt Extensions Core Vendoring

> Knowledge base for projects that vendored the @lenne.tech/nuxt-extensions module into their source tree (app/core/, no npm dependency). Covers detecting a vendored frontend, the one-way vendor model, when `app/core/` may be touched, upstream sync and upstream-PR workflows, and cosmetic-vs-substantial classification; no flatten-fix is needed here, unlike the backend core. Activates on "sync frontend core from upstream" or frontend vendor-sync conflicts. NOT for npm-based nuxt-extensions updates or new Nuxt code (use developing-lt-frontend).

- Skill: `lennetech/nuxt-extensions-core-vendoring` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lennetech/nuxt-extensions-core-vendoring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lennetech/nuxt-extensions-core-vendoring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: lenneTech (https://skillmd.com/u/lennetech)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/lennetech/nuxt-extensions-core-vendoring

---


# Vendored nuxt-extensions core Knowledge Base

## Gotchas

- **Upstream tags have NO `v` prefix** — `@lenne.tech/nuxt-extensions` publishes tags like `7.19.0`, not `v7.19.0`. Using `v7.19.0` in git refs or changelog links produces 404s. This differs from `@lenne.tech/nest-server` which also drops the prefix but some tooling adds it back automatically.
- **`nuxt.config.ts` module registration is NOT auto-handled on sync** — When a new composable or module file is added upstream, the sync agent copies the file but does NOT register it in `nuxt.config.ts`. You must manually add the module reference after a sync, otherwise Nuxt does not expose the new functionality.
- **Auto-imports work for composables + components but NOT for types** — Files in `app/core/` are auto-imported by Nuxt for composables and components, but explicitly imported types (e.g. `import type { Foo } from '@lenne.tech/nuxt-extensions'`) need their paths rewritten to relative references after vendoring. This rewrite is not fully automated.
- **Local patches in `app/core/` are invisible to future syncs** — Same gotcha as nest-server-core-vendoring: the updater can't infer your intent. Document intentional local deviations in `app/core/LOCAL-PATCHES.md`.
- **1.7.0 sync brings the AI module** — The 1.7.0 baseline adds `src/runtime/composables/use-lt-ai*.ts`, `src/runtime/lib/ai.ts`, and `src/runtime/types/ai.ts` (~700 LoC). After sync, projects MUST add an `ltExtensions.ai: { enabled, basePath }` section to `nuxt.config.ts` — auto-imports do not work until the option is registered. Default option in upstream is `enabled: true`, so projects that never want AI should set `enabled: false` after the sync.
- **1.7.0 pre-release-only breaking renames** — Only relevant for projects that vendored a 1.7.0 PRE-release into `app/core/`. Rename map (apply via project-wide replace after sync): `useLtAiSnippets` → `useLtAiPrompts`, `LtAiPromptSnippet` → `LtAiPrompt`, `LtAiPromptSnippetInput` → `LtAiPromptInput`, `LtAiPromptTemplate` → `LtAiSlot`, `LtAiPromptTemplateInput` → `LtAiSlotInput`, `UseLtAiSnippetsReturn` → `UseLtAiPromptsReturn`. Additionally: the EXECUTION payload type for `useLtAi.prompt()` was renamed `LtAiPromptInput` → `LtAiPromptRunInput` (CRUD input keeps the conventional `LtAiPromptInput` name). Projects that synced a 1.6.x or earlier baseline are unaffected.

This skill provides **knowledge and resources** for lenne.tech projects that have
vendored the @lenne.tech/nuxt-extensions module into their source tree. For automated
execution, use the matching agents:

- `lt-dev:nuxt-extensions-core-updater` via `/lt-dev:frontend:update-nuxt-extensions-core` --
  pulls upstream changes into the vendored core, with AI-driven curation against
  local patches
- `lt-dev:nuxt-extensions-core-contributor` via `/lt-dev:frontend:contribute-nuxt-extensions-core` --
  identifies substantial local changes to the vendored core and prepares them as
  Upstream-Pull-Requests to the nuxt-extensions repository

## When This Skill Activates

- Discussing the vendored nuxt-extensions pattern
- Asking how to sync frontend core from upstream into a vendored project
- Asking how to port a local frontend fix back to the upstream repository
- Troubleshooting nuxt.config.ts module registration after vendoring
- Planning an upstream-sync with conflict resolution for the frontend core
- Explaining the difference to the classic npm-based update flow for nuxt-extensions

## Skill Boundaries

| User Intent                                                | Correct Skill                         |
| ---------------------------------------------------------- | ------------------------------------- |
| "Sync vendored frontend core from upstream 1.5.3"          | **THIS SKILL**                        |
| "Port this composable fix back to nuxt-extensions"         | **THIS SKILL**                        |
| "Update nuxt-extensions via npm"                           | developing-lt-frontend                |
| "Create a new Nuxt composable"                             | developing-lt-frontend                |
| "Fix a CVE via npm audit"                                  | maintaining-npm-packages              |
| "Sync vendored backend core from upstream"                 | nest-server-core-vendoring            |
| "Modify @lenne.tech/nuxt-extensions itself and test via pnpm link" | contributing-to-lt-framework   |

## Detecting a Vendored Frontend Project

A project is considered **vendored** if **all** of the following are true:

1. `app/core/VENDOR.md` exists (in the frontend subproject root, e.g. `projects/app/app/core/VENDOR.md`)
2. `package.json` does **not** list `@lenne.tech/nuxt-extensions` in
   `dependencies` or `devDependencies`
3. `app/core/` contains the nuxt-extensions source (composables, components, plugins, etc.)

A project is **npm-based** (classic) if:

- `@lenne.tech/nuxt-extensions` is a regular dependency in `package.json`
- There is no `VENDOR.md` under `app/core/`

The `fullstack-updater` (classic agent) detects this automatically and delegates
to `nuxt-extensions-core-updater` for vendored projects.

## Modification Policy (when to touch `app/core/`)

Vendoring copies the nuxt-extensions source into the project tree so Claude
Code can read it directly -- this is a **comprehension aid**, not an
invitation to fork. The policy:

1. **FIRST check whether upstream already has the fix — prefer updating over
   hand-patching.** Before changing anything in `app/core/`, confirm the
   vendored baseline is current: compare the version/commit recorded in
   `app/core/VENDOR.md` against the latest `@lenne.tech/nuxt-extensions`
   release **and** the upstream default branch, and read the upstream version
   of the exact file you intend to change. If a newer release — or upstream
   HEAD — already contains the fix, optimization, or enhancement, adopt it via
   `/lt-dev:frontend:update-nuxt-extensions-core` instead of writing a local
   patch. A hand-patch that duplicates or diverges from an upstream fix
   creates a needless merge conflict on the next sync and often reimplements
   the change worse than the maintainers already did. Only hand-patch when the
   fix genuinely does not exist upstream yet — and then still contribute it
   (step 4). Run `pnpm run check:vendor-freshness` (or read `VENDOR.md`) if
   unsure whether the baseline is stale.
2. **Change `app/core/` ONLY when the change is generally useful to all
   nuxt-extensions consumers.** Valid reasons:
   - Bugfixes that apply to every consumer
   - Framework enhancements with broad applicability (new composables,
     better defaults, SSR fixes)
   - Closing security vulnerabilities
   - Type/config compatibility fixes that every consumer would hit
3. **Every other change belongs in project code** (outside `app/core/`),
   via project-level composables, components, middleware, or plugin
   overrides. Project-specific business rules, customer branding, or
   proprietary integrations must never live in the vendored core.
4. **Generally-useful changes MUST be submitted as an upstream PR** to
   `github.com/lenneTech/nuxt-extensions`. Use
   `/lt-dev:frontend:contribute-nuxt-extensions-core` to prepare the PR.
   Do not let useful fixes rot in a single project's vendor tree -- they
   belong upstream so every consumer benefits and the local patch
   disappears on the next sync.
5. **When in doubt, ask before editing `app/core/`.** The contributor
   agent exists precisely to keep the vendor tree close to upstream.

The `nuxt-extensions-core-contributor` agent enforces this distinction by
categorizing every local commit as **upstream-candidate** (generic) vs.
**project-specific** (stays local) vs. **unclear** (asks the human).

## The Frontend Vendor Model (one-way curation)

The vendored frontend core lives as **first-class project code**. Flow is:

```
Upstream (github.com/lenneTech/nuxt-extensions)
    |
    | /lt-dev:frontend:update-nuxt-extensions-core  (curated, one-way)
    v
Project vendor (app/core/)
    |
    | /lt-dev:frontend:contribute-nuxt-extensions-core  (manual review, cherry-pick)
    v
Upstream PR (via normal GitHub review process)
```

**Local patches are expected.** Projects legitimately modify the vendored core
when business rules, integration adapters, or bugfixes are needed before upstream
ships them. Those patches persist through syncs.

**Changes are never auto-pushed.** The contributor agent prepares PR drafts --
a human reviews and submits them through normal GitHub workflow. No git-subtree
push, no automatic upstream replay.

## No Flatten-Fix Needed

Unlike the backend vendor model (nest-server), the nuxt-extensions source structure
is **already flat**. The upstream repository organizes its code directly in `src/`
without a nested `core/` subdirectory, so when vendored into `app/core/`, no
import-path rewriting is needed for internal files.

This is an explicit simplification compared to the backend vendoring pattern:
- **Backend (nest-server):** Requires flatten-fix on `index.ts`, `core.module.ts`,
  `test/test.helper.ts`, and `core-persistence-model.interface.ts` after every sync
- **Frontend (nuxt-extensions):** No flatten-fix needed -- direct copy works

The only import changes required are in the **consumer project**, not in the
vendored code itself:
1. `nuxt.config.ts` -- remove the `@lenne.tech/nuxt-extensions` module entry,
   replace with local module registration pointing to `app/core/`
2. Up to 4 explicit type/testing imports that reference the npm package name
   need rewriting to relative paths

## Vendor Directory Structure

After vendoring, the frontend subproject looks like:

```
projects/app/
├── app/
│   ├── core/
│   │   ├── VENDOR.md             <- NEW (marker + baseline metadata)
│   │   ├── LICENSE               <- copied for provenance
│   │   ├── composables/          <- copied from upstream src/runtime/composables/
│   │   ├── components/           <- copied from upstream src/runtime/components/
│   │   ├── plugins/              <- copied from upstream src/runtime/plugins/
│   │   ├── middleware/           <- copied from upstream src/runtime/middleware/
│   │   ├── utils/                <- copied from upstream src/runtime/utils/
│   │   ├── types/                <- copied from upstream src/runtime/types/
│   │   └── ...                   <- other runtime directories
│   ├── components/               <- project components
│   ├── composables/              <- project composables
│   └── ...
├── nuxt.config.ts                <- module entry rewritten
├── package.json                  <- @lenne.tech/nuxt-extensions removed
└── ...
```

Nuxt's auto-import mechanism handles composables and components from `app/core/`
automatically -- no consumer-import codemod is needed for auto-imported items.

## nuxt.config.ts Rewrite Pattern

When converting from npm to vendor mode, the `nuxt.config.ts` module registration
changes:

**Before (npm mode):**
```typescript
export default defineNuxtConfig({
  modules: [
    '@lenne.tech/nuxt-extensions',
    // other modules...
  ],
})
```

**After (vendor mode):**
```typescript
export default defineNuxtConfig({
  modules: [
    // '@lenne.tech/nuxt-extensions' removed -- vendored into app/core/
    // other modules...
  ],
})
```

The `lt frontend convert-mode --to vendor` CLI command handles this rewrite
automatically.

## Upstream Sync Workflow (curated)

When a new upstream version is available, the `nuxt-extensions-core-updater` agent:

1. Reads `VENDOR.md` baseline version and baseline commit SHA
2. Clones upstream baseline and upstream target into `/tmp/`
3. Computes three diffs:
   - `upstream-delta.patch`: upstream baseline -> upstream target
   - `local-changes.patch`: upstream baseline -> current project vendor
   - `conflicts.json`: file-level intersection of both diffs
4. Categorizes each upstream hunk:
   - **Clean pick** -- no line overlap with any local change
   - **Conflict** -- touches lines we also modified locally
   - **Not applicable** -- touches code that no consumer file imports
5. Shows the curation proposal to the human for review
6. Applies approved clean-picks
7. Interactive 3-way merge for conflicts
8. Raises the project's npm dependencies to **at least** the upstream target's
   versions (semver-max, never downgrade), adds any new runtime deps, then
   (unless `--no-maintain`) refreshes the rest via `/lt-dev:maintenance:maintain`.
   A vendored project must never ship dependencies older than the upstream it mirrors.
9. Runs `nuxt build` + `pnpm run lint` -- commits if green
10. Syncs the upstream `CLAUDE.md` into the frontend project's `CLAUDE.md` --
   section-level merge preserving project-specific content
11. Updates `VENDOR.md` with new baseline + sync history entry

**IMPORTANT -- Tag format:** nuxt-extensions tags have **no** `v` prefix. Use
`--branch 1.5.3`, not `--branch v1.5.3`.

## Upstream PR Workflow (contribution)

When a local change in the vendored core looks generally useful, the
`nuxt-extensions-core-contributor` agent:

1. Reads `VENDOR.md` baseline version
2. Runs `git log --oneline app/core/` since baseline
3. **Filters out cosmetic commits:**
   - Commit messages matching `chore: format`, `style:`, `oxfmt`, `lint:fix`, `prettier`
   - Commits whose normalized diff (whitespace, quotes, trailing commas removed)
     is empty
4. Categorizes substantial commits:
   - **Upstream-candidate** -- generic bugfix, framework enhancement, framework
     test addition, type correction
   - **Project-specific** -- business rules, customer-specific enums, project-name
     references, proprietary integrations
   - **Unclear** -- asks the human
5. For each candidate:
   - Cherry-picks the commit onto a fresh branch in the local upstream clone
     (1:1 path mapping, no reverse flatten-fix needed)
   - Writes a PR-body draft explaining why the change is generally useful
     and what project motivated it
6. Presents a summary + link list to the human for review
7. Human pushes and opens the PR via normal GitHub flow
8. After merge, the next `update-nuxt-extensions-core` run will pick up the
   change as upstream-delivered and can remove the local patch from
   `VENDOR.md`'s local-changes log

**The ceiling is an open DRAFT PR into `main` — never a merge.** Never merge the
PR, never tag/release/publish. Always `gh pr create --draft`; never `gh pr ready`.
Taking a PR out of draft is how the maintainer says they have decided — that signal
is theirs to give. This holds even when an earlier instruction sounded like blanket
approval ("merge everything").

### Upstream branch model + PR shape (lenneTech/nuxt-extensions)

This repo has **no `develop` branch** — base every contribution on **`main`**.

The contribution shape (derived from merged PR #4 and the 1.8.2/1.8.3/1.8.4 release
commits; it is documented nowhere else):

- **One single commit per PR**, subject = `<target-version>: <what + why>`
  (e.g. `1.8.5: accept the nest-server roles: string[] shape in isAdmin …`).
- The commit **bumps `package.json` itself** (patch for a bugfix).
- The commit carries **src + tests only — NO `CHANGELOG.md` entry.** The maintainer
  adds it separately as `docs: add <version> changelog entry` on `main` after the
  merge. Adding one in the PR duplicates that and causes review churn.
- A pre-commit hook (`.githooks`) auto-runs `oxfmt --write src/` + `oxlint src/`.
- Full gate before committing: `format:check`, `lint`, `dev:prepare`, `test:types`,
  `test`, `build` (`pnpm run check:raw` chains them plus `pnpm audit`).

**Flag the version bump to the user:** if two contribution PRs are open at once,
the second one's bump collides and needs a rebase.

**Upstream vitest needs `pnpm run dev:prepare` first** (it generates
`.nuxt/tsconfig.json`) — without it every test file fails at transform, which looks
like a broken port but is not.

## No Reverse Flatten-Fix Needed

Unlike backend contributions (where `./common/` must become `./core/common/`
before submitting upstream), nuxt-extensions uses a 1:1 path mapping between
the vendored tree and upstream. Files can be cherry-picked directly without
any import-path transformation.

## Cosmetic-vs-Substantial: Why Filter

Running `pnpm run format` and `pnpm run lint` on a freshly vendored upstream
often produces a formatting-only commit if the project's formatter config
differs from upstream's. These commits have real line changes but zero
semantic content. Without filtering, the contributor agent would suggest
them as upstream-PRs -- wasting reviewer time on upstream and creating
unwanted churn.

The filter lives **in the agent**, not in `.oxlintignore` or `.prettierignore`.
The vendored code should always stay format-compliant with project standards.

## Rollback

If the vendor pilot fails or needs to be reverted:

```bash
# In the consumer project frontend:
cd projects/app
git log --oneline | grep -E "vendor|framework" | head -20
# Identify the vendor commit range
git revert <first-vendor-commit>..<last-commit> --no-edit
pnpm install
pnpm run build
```

This restores `@lenne.tech/nuxt-extensions` as an npm dependency and removes the
`app/core/` vendor tree. No data loss, no upstream impact.

## References

- nuxt-extensions upstream: https://github.com/lenneTech/nuxt-extensions
- Changelog: https://github.com/lenneTech/nuxt-extensions/blob/main/CHANGELOG.md
- Releases: https://github.com/lenneTech/nuxt-extensions/releases
- Backend vendor skill (for comparison): `nest-server-core-vendoring`

## Related Skills & Commands

| Element | Relationship |
|---------|--------------|
| **Agent**: `nuxt-extensions-core-updater` | Executes the upstream-to-project sync |
| **Agent**: `nuxt-extensions-core-contributor` | Prepares project-to-upstream PRs |
| `/lt-dev:frontend:update-nuxt-extensions-core` | User-facing entry point for the sync |
| `/lt-dev:frontend:contribute-nuxt-extensions-core` | User-facing entry point for the PR prep |
| `developing-lt-frontend` skill | npm-mode consumption and general Nuxt work |
| `contributing-to-lt-framework` skill | Editing the framework repo itself via `pnpm link` |
| `nest-server-core-vendoring` skill | The backend mirror of this pattern |

