main afd246b1232f cached
54 files
97.6 KB
27.2k tokens
6 symbols
1 requests
Download .txt
Repository: bencodezen/vue-enterprise-boilerplate
Branch: main
Commit: afd246b1232f
Files: 54
Total size: 97.6 KB

Directory structure:
gitextract_ewk5efrr/

├── .eslintrc.cjs
├── .gitignore
├── .prettierrc.json
├── .vscode/
│   ├── _sfc.code-snippets
│   ├── extensions.json
│   └── settings.json
├── README.md
├── auto-imports.d.ts
├── components.d.ts
├── docs/
│   ├── .vitepress/
│   │   └── config.mts
│   ├── architecture.md
│   ├── development.md
│   ├── editors.md
│   ├── index.md
│   ├── linting.md
│   ├── production.md
│   ├── routing.md
│   ├── state.md
│   ├── tech.md
│   ├── tests.md
│   └── troubleshooting.md
├── e2e/
│   ├── tsconfig.json
│   └── vue.spec.ts
├── env.d.ts
├── index.html
├── package.json
├── playwright.config.ts
├── src/
│   ├── App.vue
│   ├── components/
│   │   ├── BaseButton.spec.ts
│   │   ├── BaseButton.vue
│   │   ├── BaseInputText.spec.ts
│   │   └── BaseInputText.vue
│   ├── composables/
│   │   └── useTheme.ts
│   ├── design/
│   │   ├── _colors.scss
│   │   ├── _durations.scss
│   │   ├── _fonts.scss
│   │   ├── _layers.scss
│   │   ├── _sizes.scss
│   │   ├── _typography.scss
│   │   └── index.scss
│   ├── layouts/
│   │   └── AppLayout.vue
│   ├── main.ts
│   ├── pages/
│   │   ├── about.vue
│   │   └── index.vue
│   ├── router/
│   │   ├── index.ts
│   │   └── routes.ts
│   ├── stores/
│   │   └── counter.ts
│   └── types.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.vitest.json
├── vite.config.ts
└── vitest.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.cjs
================================================
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')

module.exports = {
  root: true,
  'extends': [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier/skip-formatting'
  ],
  parserOptions: {
    ecmaVersion: 'latest'
  }
}


================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Playwright directories
test-results/
playwright-report/

# VitePress
docs/.vitepress/cache
docs/.vitepress/dist


================================================
FILE: .prettierrc.json
================================================
{
  "$schema": "https://json.schemastore.org/prettierrc",
  "semi": false,
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 100,
  "trailingComma": "none"
}

================================================
FILE: .vscode/_sfc.code-snippets
================================================
{
  "veb-sfc": {
    "scope": "vue",
    "prefix": "sfc",
    "body": [
      "<script setup lang='ts'>",
      "${0}",
      "</script>",
      "",
      "<template>",
      "\t",
      "</template>",
      "",
      "<style lang=\"scss\" scoped>",
      "",
      "</style>"
    ],
    "description": "Single File Component (Comp API + TS) from VEB"
  },
  "veb-sfc-options": {
    "scope": "vue",
    "prefix": "sfc-options",
    "body": [
      "<script lang='ts'>",
      "import { defineComponent } from 'vue'",
      "",
      "export default defineComponent({",
      "\t${0}",
      "})",
      "</script>",
      "",
      "<template>",
      "\t",
      "</template>",
      "",
      "<style lang=\"scss\" scoped>",
      "",
      "</style>"
    ],
    "description": "Single File Component (Options API + TS) from VEB"
  },
  "veb-script": {
    "scope": "vue",
    "prefix": "script",
    "body": ["<script setup lang='ts'>", "${0}", "</script>"],
    "description": "Script block (Comp API + TS)"
  },
  "veb-script-options": {
    "scope": "vue",
    "prefix": "script-options",
    "body": [
      "<script lang='ts'>",
      "import { defineComponent } from 'vue'",
      "",
      "export default defineComponent({",
      "\t${0}",
      "})",
      "</script>"
    ],
    "description": "Script block (Options API + TS)"
  },
  "veb-template": {
    "scope": "vue",
    "prefix": "template",
    "body": ["<template>", "\t${0}", "</template>"],
    "description": "Template block"
  },
  "veb-style": {
    "scope": "vue",
    "prefix": "style",
    "body": ["<style lang=\"scss\" scoped>", "${0}", "</style>"],
    "description": "Scoped CSS + Sass styles block from VEB"
  },
  "veb-style-module": {
    "scope": "vue",
    "prefix": "style-module",
    "body": ["<style lang=\"scss\" module>", "${0}", "</style>"],
    "description": "CSS Module + Sass styles block from VEB"
  }
}


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    // Vue - Official Extension
    // https://github.com/vuejs/language-tools
    "vue.volar",

    // Format-on-save with Prettier
    // https://github.com/prettier/prettier-vscode
    "esbenp.prettier-vscode",

    // Playwright Test - Official Extension
    // https://github.com/microsoft/playwright-vscode
    "ms-playwright.playwright",

    // Better Comments
    // https://github.com/aaron-bond/better-comments
    "aaron-bond.better-comments",

    // Path Intellisense
    // https://github.com/ChristianKohler/PathIntellisense
    "christian-kohler.path-intellisense",

    // Peacock - Workspace Color Customizer
    // https://github.com/johnpapa/vscode-peacock
    "johnpapa.vscode-peacock"
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  // ======
  // Spacing
  // ======

  "editor.insertSpaces": true,
  "editor.tabSize": 2,
  "editor.trimAutoWhitespace": true,
  "files.trimTrailingWhitespace": true,
  "files.eol": "\n",
  "files.insertFinalNewline": true,
  "files.trimFinalNewlines": true,

  // ======
  // Files
  // ======

  "files.exclude": {
    "**/*.log": true,
    "**/*.log*": true,
    "**/dist": true,
    "**/coverage": true
  },

  // ======
  // Event Triggers
  // ======

  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.fixAll.stylelint": "explicit",
    "source.fixAll.markdownlint": "explicit"
  },
  "eslint.validate": ["javascript", "javascriptreact", "vue", "vue-html", "html"],

  // ======
  // HTML
  // ======

  "emmet.triggerExpansionOnTab": true,

  // ======
  // CSS
  // ======

  "stylelint.enable": true,
  "css.validate": false,
  "scss.validate": false,

  // ======
  // MARKDOWN
  // ======

  "[markdown]": {
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 80
  }
}


================================================
FILE: README.md
================================================
# Vue Enterprise Boilerplate v3 (alpha)

This repo is currently in active development and considered in alpha release.

> This is an ever-evolving, opinionated architecture and dev environment for new Vue 3 + Vite SPA projects using [create-vue](https://github.com/vuejs/create-vue).

🎩 A huge thanks to [Chris Fritz](https://twitter.com/chrisvfritz) for the incredible work that this work builds upon. For those looking for his version, see [this branch for the original Vue 2 enterprise boilerplate](https://github.com/bencodezen/vue-enterprise-boilerplate/tree/vue-2-version).

## Recommended IDE Setup

[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

## Type Support for `.vue` Imports in TS

TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.

If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:

1. Disable the built-in TypeScript Extension
   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.

## Project Setup

```sh
npm install
```

### Compile and Hot-Reload for Development

```sh
npm run dev
```

### Type-Check, Compile and Minify for Production

```sh
npm run build
```

### Run Unit Tests with [Vitest](https://vitest.dev/)

```sh
npm run test:unit
```

### Run End-to-End Tests with [Playwright](https://playwright.dev)

```sh
# Install browsers for the first run
npx playwright install

# When testing on CI, must build the project first
npm run build

# Runs the end-to-end tests
npm run test:e2e
# Runs the tests only on Chromium
npm run test:e2e -- --project=chromium
# Runs the tests of a specific file
npm run test:e2e -- tests/example.spec.ts
# Runs the tests in debug mode
npm run test:e2e -- --debug
```

### Lint with [ESLint](https://eslint.org/)

```sh
npm run lint
```


================================================
FILE: auto-imports.d.ts
================================================
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
  const EffectScope: typeof import('vue')['EffectScope']
  const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
  const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
  const computed: typeof import('vue')['computed']
  const computedAsync: typeof import('@vueuse/core')['computedAsync']
  const computedEager: typeof import('@vueuse/core')['computedEager']
  const computedInject: typeof import('@vueuse/core')['computedInject']
  const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
  const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
  const controlledRef: typeof import('@vueuse/core')['controlledRef']
  const createApp: typeof import('vue')['createApp']
  const createEventHook: typeof import('@vueuse/core')['createEventHook']
  const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
  const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
  const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
  const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
  const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
  const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
  const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
  const customRef: typeof import('vue')['customRef']
  const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
  const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
  const defineComponent: typeof import('vue')['defineComponent']
  const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
  const effectScope: typeof import('vue')['effectScope']
  const extendRef: typeof import('@vueuse/core')['extendRef']
  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
  const getCurrentScope: typeof import('vue')['getCurrentScope']
  const h: typeof import('vue')['h']
  const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
  const inject: typeof import('vue')['inject']
  const injectLocal: typeof import('@vueuse/core')['injectLocal']
  const isDefined: typeof import('@vueuse/core')['isDefined']
  const isProxy: typeof import('vue')['isProxy']
  const isReactive: typeof import('vue')['isReactive']
  const isReadonly: typeof import('vue')['isReadonly']
  const isRef: typeof import('vue')['isRef']
  const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
  const markRaw: typeof import('vue')['markRaw']
  const nextTick: typeof import('vue')['nextTick']
  const onActivated: typeof import('vue')['onActivated']
  const onBeforeMount: typeof import('vue')['onBeforeMount']
  const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
  const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
  const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
  const onDeactivated: typeof import('vue')['onDeactivated']
  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
  const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
  const onLongPress: typeof import('@vueuse/core')['onLongPress']
  const onMounted: typeof import('vue')['onMounted']
  const onRenderTracked: typeof import('vue')['onRenderTracked']
  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
  const onScopeDispose: typeof import('vue')['onScopeDispose']
  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
  const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
  const onUnmounted: typeof import('vue')['onUnmounted']
  const onUpdated: typeof import('vue')['onUpdated']
  const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
  const provide: typeof import('vue')['provide']
  const provideLocal: typeof import('@vueuse/core')['provideLocal']
  const reactify: typeof import('@vueuse/core')['reactify']
  const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
  const reactive: typeof import('vue')['reactive']
  const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
  const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
  const reactivePick: typeof import('@vueuse/core')['reactivePick']
  const readonly: typeof import('vue')['readonly']
  const ref: typeof import('vue')['ref']
  const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
  const refDebounced: typeof import('@vueuse/core')['refDebounced']
  const refDefault: typeof import('@vueuse/core')['refDefault']
  const refThrottled: typeof import('@vueuse/core')['refThrottled']
  const refWithControl: typeof import('@vueuse/core')['refWithControl']
  const resolveComponent: typeof import('vue')['resolveComponent']
  const resolveRef: typeof import('@vueuse/core')['resolveRef']
  const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
  const shallowReactive: typeof import('vue')['shallowReactive']
  const shallowReadonly: typeof import('vue')['shallowReadonly']
  const shallowRef: typeof import('vue')['shallowRef']
  const syncRef: typeof import('@vueuse/core')['syncRef']
  const syncRefs: typeof import('@vueuse/core')['syncRefs']
  const templateRef: typeof import('@vueuse/core')['templateRef']
  const throttledRef: typeof import('@vueuse/core')['throttledRef']
  const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
  const toRaw: typeof import('vue')['toRaw']
  const toReactive: typeof import('@vueuse/core')['toReactive']
  const toRef: typeof import('vue')['toRef']
  const toRefs: typeof import('vue')['toRefs']
  const toValue: typeof import('vue')['toValue']
  const triggerRef: typeof import('vue')['triggerRef']
  const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
  const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
  const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
  const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
  const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
  const unref: typeof import('vue')['unref']
  const unrefElement: typeof import('@vueuse/core')['unrefElement']
  const until: typeof import('@vueuse/core')['until']
  const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
  const useAnimate: typeof import('@vueuse/core')['useAnimate']
  const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
  const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
  const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
  const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
  const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
  const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
  const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
  const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
  const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
  const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
  const useArraySome: typeof import('@vueuse/core')['useArraySome']
  const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
  const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
  const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
  const useAttrs: typeof import('vue')['useAttrs']
  const useBase64: typeof import('@vueuse/core')['useBase64']
  const useBattery: typeof import('@vueuse/core')['useBattery']
  const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
  const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
  const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
  const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
  const useCached: typeof import('@vueuse/core')['useCached']
  const useClipboard: typeof import('@vueuse/core')['useClipboard']
  const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
  const useCloned: typeof import('@vueuse/core')['useCloned']
  const useColorMode: typeof import('@vueuse/core')['useColorMode']
  const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
  const useCounter: typeof import('@vueuse/core')['useCounter']
  const useCssModule: typeof import('vue')['useCssModule']
  const useCssVar: typeof import('@vueuse/core')['useCssVar']
  const useCssVars: typeof import('vue')['useCssVars']
  const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
  const useCycleList: typeof import('@vueuse/core')['useCycleList']
  const useDark: typeof import('@vueuse/core')['useDark']
  const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
  const useDebounce: typeof import('@vueuse/core')['useDebounce']
  const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
  const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
  const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
  const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
  const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
  const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
  const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
  const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
  const useDraggable: typeof import('@vueuse/core')['useDraggable']
  const useDropZone: typeof import('@vueuse/core')['useDropZone']
  const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
  const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
  const useElementHover: typeof import('@vueuse/core')['useElementHover']
  const useElementSize: typeof import('@vueuse/core')['useElementSize']
  const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
  const useEventBus: typeof import('@vueuse/core')['useEventBus']
  const useEventListener: typeof import('@vueuse/core')['useEventListener']
  const useEventSource: typeof import('@vueuse/core')['useEventSource']
  const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
  const useFavicon: typeof import('@vueuse/core')['useFavicon']
  const useFetch: typeof import('@vueuse/core')['useFetch']
  const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
  const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
  const useFocus: typeof import('@vueuse/core')['useFocus']
  const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
  const useFps: typeof import('@vueuse/core')['useFps']
  const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
  const useGamepad: typeof import('@vueuse/core')['useGamepad']
  const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
  const useHead: typeof import('@unhead/vue')['useHead']
  const useIdle: typeof import('@vueuse/core')['useIdle']
  const useImage: typeof import('@vueuse/core')['useImage']
  const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
  const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
  const useInterval: typeof import('@vueuse/core')['useInterval']
  const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
  const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
  const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
  const useLink: typeof import('vue-router')['useLink']
  const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
  const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
  const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
  const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
  const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
  const useMemoize: typeof import('@vueuse/core')['useMemoize']
  const useMemory: typeof import('@vueuse/core')['useMemory']
  const useMounted: typeof import('@vueuse/core')['useMounted']
  const useMouse: typeof import('@vueuse/core')['useMouse']
  const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
  const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
  const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
  const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
  const useNetwork: typeof import('@vueuse/core')['useNetwork']
  const useNow: typeof import('@vueuse/core')['useNow']
  const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
  const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
  const useOnline: typeof import('@vueuse/core')['useOnline']
  const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
  const useParallax: typeof import('@vueuse/core')['useParallax']
  const useParentElement: typeof import('@vueuse/core')['useParentElement']
  const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
  const usePermission: typeof import('@vueuse/core')['usePermission']
  const usePointer: typeof import('@vueuse/core')['usePointer']
  const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
  const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
  const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
  const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
  const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
  const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
  const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
  const usePrevious: typeof import('@vueuse/core')['usePrevious']
  const useRafFn: typeof import('@vueuse/core')['useRafFn']
  const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
  const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
  const useRoute: typeof import('vue-router')['useRoute']
  const useRouter: typeof import('vue-router')['useRouter']
  const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
  const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
  const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
  const useScroll: typeof import('@vueuse/core')['useScroll']
  const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
  const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
  const useShare: typeof import('@vueuse/core')['useShare']
  const useSlots: typeof import('vue')['useSlots']
  const useSorted: typeof import('@vueuse/core')['useSorted']
  const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
  const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
  const useStepper: typeof import('@vueuse/core')['useStepper']
  const useStorage: typeof import('@vueuse/core')['useStorage']
  const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
  const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
  const useSupported: typeof import('@vueuse/core')['useSupported']
  const useSwipe: typeof import('@vueuse/core')['useSwipe']
  const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
  const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
  const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
  const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
  const useThrottle: typeof import('@vueuse/core')['useThrottle']
  const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
  const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
  const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
  const useTimeout: typeof import('@vueuse/core')['useTimeout']
  const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
  const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
  const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
  const useTitle: typeof import('@vueuse/core')['useTitle']
  const useToNumber: typeof import('@vueuse/core')['useToNumber']
  const useToString: typeof import('@vueuse/core')['useToString']
  const useToggle: typeof import('@vueuse/core')['useToggle']
  const useTransition: typeof import('@vueuse/core')['useTransition']
  const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
  const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
  const useVModel: typeof import('@vueuse/core')['useVModel']
  const useVModels: typeof import('@vueuse/core')['useVModels']
  const useVibrate: typeof import('@vueuse/core')['useVibrate']
  const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
  const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
  const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
  const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
  const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
  const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
  const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
  const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
  const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
  const watch: typeof import('vue')['watch']
  const watchArray: typeof import('@vueuse/core')['watchArray']
  const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
  const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
  const watchDeep: typeof import('@vueuse/core')['watchDeep']
  const watchEffect: typeof import('vue')['watchEffect']
  const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
  const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
  const watchOnce: typeof import('@vueuse/core')['watchOnce']
  const watchPausable: typeof import('@vueuse/core')['watchPausable']
  const watchPostEffect: typeof import('vue')['watchPostEffect']
  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
  const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
  const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
  const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
  const whenever: typeof import('@vueuse/core')['whenever']
}
// for type re-export
declare global {
  // @ts-ignore
  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
  import('vue')
}


================================================
FILE: components.d.ts
================================================
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}

/* prettier-ignore */
declare module 'vue' {
  export interface GlobalComponents {
    AppLayout: typeof import('./src/layouts/AppLayout.vue')['default']
    BaseButton: typeof import('./src/components/BaseButton.vue')['default']
    BaseInputText: typeof import('./src/components/BaseInputText.vue')['default']
    BaseLink: typeof import('./src/components/BaseLink.vue')['default']
    RouterLink: typeof import('vue-router')['RouterLink']
    RouterView: typeof import('vue-router')['RouterView']
  }
}


================================================
FILE: docs/.vitepress/config.mts
================================================
import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
  title: "Vue Enterprise Boilerplate",
  description: "Documentation for Vue Enterprise Boilerplate",
  themeConfig: {
    // https://vitepress.dev/reference/default-theme-config
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Examples', link: '/markdown-examples' }
    ],

    sidebar: [
      {
        text: 'Examples',
        items: [
          { text: 'Markdown Examples', link: '/markdown-examples' },
          { text: 'Runtime API Examples', link: '/api-examples' }
        ]
      }
    ],

    socialLinks: [
      { icon: 'github', link: 'https://github.com/vuejs/vitepress' }
    ]
  }
})


================================================
FILE: docs/architecture.md
================================================
# Architecture

- [Architecture](#architecture)
  - [`.vscode`](#vscode)
  - [`docs`](#docs)
  - [`e2e`](#e2e)
  - [`public`](#public)
  - [`src`](#src)
    - [`assets`](#assets)
    - [`components`](#components)
    - [`composables`](#composables)
    - [`design`](#design)
    - [`layouts`](#layouts)
    - [`pages`](#pages)
    - [`router`](#router)
    - [`stores`](#stores)
    - [`App.vue`](#appvue)
    - [`main.ts`](#maints)
    - [`types.ts`](#typests)
  - [Configuration Files](#configuration-files)

## `.vscode`

Settings and extensions specific to this project, for Visual Studio Code. See [the editors doc](editors.md) for more.

## `docs`

You found me! Documentation is powered by [VitePress](https://vitepress.dev/) and can be built as a static site.

```bash
# Start docs dev server
npm run docs:dev

# Build docs for production
npm run docs:build
```

### `.vitepress`

VitePress configuration including sidebar navigation, theme settings, and build options.

## `e2e`

End-to-end tests using [Playwright](https://playwright.dev/). See [the tests doc](tests.md) for more.

## `public`

Static assets that will be served directly without processing. Files here are copied to the build output as-is.

## `src`

Where we keep all our source files.

### `assets`

Static assets like images and fonts that will be processed by Vite. Assets here can be imported in components and will be optimized during build.

### `components`

Reusable Vue components, including [global base components](development.md#base-components). Components are auto-registered via `unplugin-vue-components`.

Unit tests are colocated with components using the `.spec.ts` extension (e.g., `BaseButton.spec.ts`).

### `composables`

Reusable composition functions that encapsulate stateful logic. These follow the `use*` naming convention (e.g., `useTheme.ts`).

Composables are the Vue 3 replacement for mixins, providing better TypeScript support and explicit dependencies.

### `design`

SCSS design system variables and utilities:

- `_colors.scss` - Color palette
- `_typography.scss` - Font scales and text styles
- `_sizes.scss` - Spacing and sizing
- `_fonts.scss` - Font imports
- `_durations.scss` - Animation timing
- `_layers.scss` - Z-index management
- `index.scss` - Central import point

See [the tech doc](tech.md#design-variables) for more.

### `layouts`

Layout components that wrap pages with common structure (navigation, footer, etc.). Layouts receive page content via slots.

### `pages`

Page components that map to routes. Each file in this directory represents a distinct route in the application.

### `router`

Vue Router configuration:

- `index.ts` - Router instance creation and global guards
- `routes.ts` - Route definitions with lazy-loaded pages

See [the routing doc](routing.md) for more.

### `stores`

[Pinia](https://pinia.vuejs.org/) stores for global state management. Each store is a separate file following the composition API pattern.

See [the state doc](state.md) for more.

### `App.vue`

The root Vue component that renders the router view. This is typically the only component to contain global CSS.

### `main.ts`

The entry point to our app, where we create the Vue application instance, register plugins (Pinia, Router, Unhead), and mount to the DOM.

### `types.ts`

Shared TypeScript type definitions and utility types used across the application.

## Configuration Files

| File | Purpose |
|------|---------|
| `vite.config.ts` | Vite build configuration, plugins, and dev server |
| `tsconfig.json` | Root TypeScript configuration |
| `tsconfig.app.json` | TypeScript config for application code |
| `tsconfig.node.json` | TypeScript config for Node.js files |
| `tsconfig.vitest.json` | TypeScript config for Vitest tests |
| `vitest.config.ts` | Unit test configuration |
| `playwright.config.ts` | E2E test configuration |
| `.eslintrc.cjs` | ESLint linting rules |
| `.prettierrc.json` | Prettier formatting rules |
| `env.d.ts` | Environment variable type declarations |
| `components.d.ts` | Auto-generated component type declarations |
| `auto-imports.d.ts` | Auto-generated import type declarations |


================================================
FILE: docs/development.md
================================================
# Setup and Development

- [Setup and Development](#setup-and-development)
  - [First-time setup](#first-time-setup)
  - [Installation](#installation)
  - [Dev server](#dev-server)
    - [Developing with a production API](#developing-with-a-production-api)
  - [Aliases](#aliases)
  - [Auto-imports](#auto-imports)
    - [Base components](#base-components)
    - [Vue APIs](#vue-apis)

## First-time setup

Make sure you have the following installed:

- [Node](https://nodejs.org/en/) (at least the latest LTS)
- [pnpm](https://pnpm.io/) (recommended package manager)

### Why pnpm?

This project recommends pnpm over npm for several reasons:

- **Faster installs** - Uses symlinks and a content-addressable store for efficient caching
- **Disk space efficient** - Packages are stored once globally and linked, not duplicated per project
- **Strict dependencies** - Prevents "phantom dependencies" where code accidentally imports packages not listed in package.json
- **Vue ecosystem standard** - Vue, Vite, and Nuxt all use pnpm for development

To install pnpm:

```bash
# Using npm
npm install -g pnpm

# Or using Corepack (included with Node 16.13+)
corepack enable
corepack prepare pnpm@latest --activate
```

## Installation

```bash
# Install dependencies from package.json
pnpm install
```

> **Note:** npm and yarn will also work if you prefer, but pnpm is recommended for the reasons above.

## Dev server

```bash
# Launch the dev server
pnpm dev

# Launch the dev server and automatically open it in
# your default browser when ready
pnpm dev --open
```

Vite's dev server starts almost instantly and features lightning-fast Hot Module Replacement (HMR).

### Developing with a production API

By default, you may want to use a mock API during development. To develop against a local or production API instead, create a `.env.local` file:

```bash
# .env.local
VITE_API_BASE_URL=http://localhost:3000
```

Or set the environment variable inline:

```bash
# Develop against a local backend server
VITE_API_BASE_URL=http://localhost:3000 pnpm dev

# Develop against a production server
VITE_API_BASE_URL=https://api.example.com pnpm dev
```

Access this in your code via `import.meta.env.VITE_API_BASE_URL`.

## Aliases

Path aliases simplify imports and make refactoring easier. They're configured in both `vite.config.ts` and `tsconfig.app.json` for consistent behavior across dev, build, and IDE intellisense.

| Alias | Path |
|-------|------|
| `@` | `src/` |

Example usage:

```typescript
// Instead of relative paths
import { useTheme } from '../../../composables/useTheme'

// Use aliases
import { useTheme } from '@/composables/useTheme'
```

To add new aliases, update both:
- `vite.config.ts` - for Vite resolution
- `tsconfig.app.json` - for TypeScript and IDE support

## Auto-imports

This project uses [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components) and [unplugin-auto-import](https://github.com/unplugin/unplugin-auto-import) to automatically register components and imports.

### Base components

Components in `src/components/` are automatically registered globally. You don't need to import them to use them in templates:

```vue
<template>
  <!-- BaseButton is auto-imported from src/components/BaseButton.vue -->
  <BaseButton>Click me</BaseButton>
</template>

<script setup lang="ts">
// No import needed!
</script>
```

[Base components](https://vuejs.org/style-guide/rules-strongly-recommended.html#base-component-names) (a.k.a. presentational, dumb, or pure components) that apply app-specific styling should begin with the `Base` prefix (e.g., `BaseButton`, `BaseInput`).

### Vue APIs

Common Vue APIs are auto-imported, so you don't need to manually import them:

```vue
<script setup lang="ts">
// These are auto-imported - no import statement needed
const count = ref(0)
const doubled = computed(() => count.value * 2)

watch(count, (newVal) => {
  console.log('Count changed:', newVal)
})

onMounted(() => {
  console.log('Component mounted')
})
</script>
```

Auto-imported APIs include:
- Vue: `ref`, `computed`, `watch`, `onMounted`, etc.
- Vue Router: `useRouter`, `useRoute`
- Pinia: `defineStore`, `storeToRefs`
- VueUse: Various composables

The generated type declarations are in `auto-imports.d.ts` and `components.d.ts`.


================================================
FILE: docs/editors.md
================================================
# Editor Integration

- [Visual Studio Code](#visual-studio-code)
  - [Recommended Extensions](#recommended-extensions)
  - [Workspace Settings](#workspace-settings)
- [FAQ](#faq)

## Visual Studio Code

This project is optimized for VS Code. With the [recommended extensions](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) and workspace settings in `.vscode/`, you get:

- Vue 3 syntax highlighting and intellisense (via Volar)
- Format-on-save with Prettier
- Lint-on-save with ESLint and Stylelint
- Playwright test integration
- Path autocompletion for imports

### Recommended Extensions

Install recommended extensions when prompted, or run `Extensions: Show Recommended Extensions` from the command palette.

| Extension | Purpose |
|-----------|---------|
| [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) | Vue 3 language support (syntax, intellisense, type checking) |
| [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | Code formatting |
| [Playwright Test](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) | E2E test runner integration |
| [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense) | Autocomplete for file paths |
| [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) | Highlight TODOs, notes, and warnings in comments |
| [Peacock](https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock) | Color-code workspaces (useful for multiple projects) |

> **Note:** If you previously used Vetur for Vue 2, uninstall it. Volar (Vue - Official) is the recommended extension for Vue 3.

### Workspace Settings

The `.vscode/settings.json` file configures:

**Formatting:**
- 2-space indentation
- Trim trailing whitespace
- Insert final newline
- Unix line endings (LF)

**Auto-fix on save:**
- Prettier formats code
- ESLint fixes JavaScript/Vue issues
- Stylelint fixes CSS issues

**File hiding:**
- Hides `dist/`, `coverage/`, and log files from explorer

These settings only apply to this workspace and won't affect your global VS Code configuration.

## FAQ

**What kinds of editor settings and extensions should be added to the project?**

All additions must:

- Be specific to this project's needs
- Not interfere with any team member's workflow

For example, an extension for syntax highlighting or linting is welcome, but personal preferences like color themes or font settings should stay in your user settings, not the workspace.

**Why Volar instead of Vetur?**

Volar (now called "Vue - Official") is the recommended extension for Vue 3. It provides better TypeScript support, improved performance, and is actively maintained by the Vue team. Vetur was designed for Vue 2 and is no longer recommended.


================================================
FILE: docs/index.md
================================================
---
layout: home

hero:
  name: Vue Enterprise Boilerplate
  text: Production-ready Vue 3 architecture
  tagline: An opinionated architecture and dev environment for new Vue 3 SPAs using Vite, TypeScript, and Pinia.
  actions:
    - theme: brand
      text: Get Started
      link: /development
    - theme: alt
      text: View on GitHub
      link: https://github.com/bencodezen/vue-enterprise-boilerplate

features:
  - icon: 📝
    title: Thorough Documentation
    details: Every architectural decision is documented, so even new team members can quickly understand patterns and conventions.
  - icon: 🔒
    title: Type Safety
    details: Full TypeScript support with strict mode, typed props/emits, and comprehensive type inference.
  - icon: ⚡
    title: Lightning Fast
    details: Powered by Vite for instant dev server startup and blazing fast HMR.
  - icon: 🧪
    title: First-Class Testing
    details: Unit testing with Vitest and E2E testing with Playwright, both configured and ready to use.
  - icon: 🎨
    title: Design System
    details: SCSS-based design tokens for colors, typography, spacing, and more.
  - icon: 🛠️
    title: Developer Experience
    details: Auto-imports, component auto-registration, and VS Code integration out of the box.
---

## Why This Boilerplate?

This boilerplate helps teams build large-scale Vue 3 applications by providing:

- **Consistent patterns** - Established conventions for components, state, routing, and testing
- **Scalable architecture** - Folder structure that grows with your application
- **Modern tooling** - Vite, TypeScript, Pinia, Vue Router 4, Vitest, Playwright
- **Documentation culture** - Every pattern documented for team alignment

## Quick Start

```bash
# Clone the repository
git clone https://github.com/bencodezen/vue-enterprise-boilerplate.git
cd vue-enterprise-boilerplate

# Install dependencies
npm install

# Start development server
npm run dev
```

Then open [http://localhost:8080](http://localhost:8080) to see your app.


================================================
FILE: docs/linting.md
================================================
# Linting & Formatting

- [Overview](#overview)
- [Languages](#languages)
- [Scripts](#scripts)
  - [Terminal](#terminal)
  - [Editor](#editor)
- [Configuration](#configuration)
- [FAQ](#faq)

This project uses ESLint and Prettier to catch errors and enforce a consistent code style.

## Overview

| Tool | Purpose |
|------|---------|
| [ESLint](https://eslint.org/) | Catches bugs and enforces code quality rules |
| [Prettier](https://prettier.io/) | Formats code for consistent style |

ESLint handles logic and correctness, Prettier handles formatting. They're configured to work together without conflicts.

## Languages

- **TypeScript/JavaScript** — Linted by ESLint, formatted by Prettier
- **Vue SFCs** — Linted by ESLint (`eslint-plugin-vue`), formatted by Prettier
- **JSON/HTML/CSS/Markdown** — Formatted by Prettier

## Scripts

### Terminal

```bash
# Lint and auto-fix issues
pnpm lint

# Format all files in src/
pnpm format
```

### Editor

With the recommended VS Code extensions, files are automatically:
- Linted on save (ESLint)
- Formatted on save (Prettier)

See [editors.md](editors.md) for setup details.

## Configuration

| Tool | Config File | Docs |
|------|-------------|------|
| ESLint | `.eslintrc.cjs` | [ESLint Configuration](https://eslint.org/docs/user-guide/configuring) |
| Prettier | `.prettierrc.json` | [Prettier Configuration](https://prettier.io/docs/en/configuration.html) |

### ESLint Setup

The ESLint config extends:
- `plugin:vue/vue3-essential` — Vue 3 specific rules
- `eslint:recommended` — Core ESLint rules
- `@vue/eslint-config-typescript` — TypeScript support
- `@vue/eslint-config-prettier/skip-formatting` — Disables formatting rules (Prettier handles those)

### Prettier Setup

```json
{
  "semi": false,
  "tabWidth": 2,
  "singleQuote": true,
  "printWidth": 100,
  "trailingComma": "none"
}
```

## FAQ

**Why separate ESLint and Prettier?**

ESLint is best at catching bugs and enforcing code quality (unused variables, missing returns, etc.). Prettier is best at formatting (indentation, line breaks, quotes). Using both gives you the best of each tool.

**Why no semicolons?**

This is a stylistic choice. JavaScript's ASI (Automatic Semicolon Insertion) handles most cases, and omitting semicolons reduces visual noise. If you prefer semicolons, change `"semi": true` in `.prettierrc.json`.

**How do I add Stylelint for CSS?**

If you need more advanced CSS linting:

```bash
pnpm add -D stylelint stylelint-config-standard-scss
```

Create `stylelint.config.js`:
```js
export default {
  extends: ['stylelint-config-standard-scss']
}
```

Add to `.vscode/settings.json`:
```json
{
  "stylelint.enable": true,
  "css.validate": false,
  "scss.validate": false
}
```

**So many configuration files! Why not move more to `package.json`?**

While some configs can live in `package.json`, separate files offer benefits:
- Easier to find and edit specific tool configs
- Supports dynamic configuration via JavaScript
- Better IDE support and syntax highlighting
- Cleaner `package.json` that focuses on dependencies and scripts


================================================
FILE: docs/production.md
================================================
# Building and Deploying to Production

- [Building for Production](#building-for-production)
- [Previewing the Build](#previewing-the-build)
- [Environment Variables](#environment-variables)
- [Deployment](#deployment)

## Building for Production

```bash
# Build for production
pnpm build
```

This runs TypeScript type checking and then builds optimized assets into the `dist/` directory.

The build process:

1. Type checks with `vue-tsc`
2. Bundles and minifies with Vite
3. Outputs to `dist/` with hashed filenames for cache busting

## Previewing the Build

Before deploying, you can preview the production build locally:

```bash
# Preview the production build
pnpm preview
```

This serves the `dist/` directory on a local server, letting you verify the build works correctly.

## Environment Variables

Vite uses `.env` files for environment-specific configuration:

| File                    | Purpose                              |
| ----------------------- | ------------------------------------ |
| `.env`                  | Default values (committed)           |
| `.env.local`            | Local overrides (not committed)      |
| `.env.production`       | Production values (committed)        |
| `.env.production.local` | Production overrides (not committed) |

Variables must be prefixed with `VITE_` to be exposed to client code:

```bash
# .env.production
VITE_API_BASE_URL=https://api.example.com
```

Access in code:

```typescript
const apiUrl = import.meta.env.VITE_API_BASE_URL
```

> **Security:** Never put secrets in `VITE_` variables — they're embedded in the client bundle and visible to users.

## Deployment

The `dist/` directory contains static files that can be deployed to any static hosting service:

### SPA Routing

If using client-side routing (Vue Router in history mode), configure your server to redirect all requests to `index.html`. See the [Vue Router deployment guide](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) for server-specific examples.


================================================
FILE: docs/routing.md
================================================
# Routing, Layouts, and Pages

- [Overview](#overview)
- [Route Configuration](#route-configuration)
- [Layouts](#layouts)
- [Pages](#pages)
- [Navigation Guards](#navigation-guards)
- [Lazy Loading](#lazy-loading)

## Overview

This project uses [Vue Router 4](https://router.vuejs.org/) for client-side routing:

| File | Purpose |
|------|---------|
| `src/router/index.ts` | Router initialization |
| `src/router/routes.ts` | Route definitions |
| `src/layouts/` | Layout components |
| `src/pages/` | Page components |

## Route Configuration

Routes are defined in `src/router/routes.ts`:

```typescript
export default [
  {
    path: '/',
    name: 'home',
    component: () => import('@/pages/index.vue')
  },
  {
    path: '/about',
    name: 'about',
    component: () => import('@/pages/about.vue')
  }
]
```

The router is created in `src/router/index.ts`:

```typescript
import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router
```

## Layouts

Layout components provide shared structure (header, footer, navigation) around page content. They live in `src/layouts/`.

```vue
<!-- src/layouts/AppLayout.vue -->
<script setup lang="ts"></script>

<template>
  <div class="app-layout">
    <AppHeader />
    <main>
      <slot />
    </main>
    <AppFooter />
  </div>
</template>
```

Use layouts by wrapping page content:

```vue
<!-- src/pages/index.vue -->
<template>
  <AppLayout>
    <h1>Home Page</h1>
    <!-- Page content -->
  </AppLayout>
</template>
```

Or use nested routes for layout-based routing:

```typescript
{
  path: '/',
  component: () => import('@/layouts/AppLayout.vue'),
  children: [
    { path: '', component: () => import('@/pages/index.vue') },
    { path: 'about', component: () => import('@/pages/about.vue') }
  ]
}
```

## Pages

Page components are the top-level components rendered for each route. They live in `src/pages/` and typically:

- Use a layout component for consistent structure
- Fetch data needed for the page
- Compose smaller components together

Keep pages focused on orchestration — extract complex logic into composables and UI into components.

## Navigation Guards

Use navigation guards for authentication, authorization, and data fetching:

```typescript
// src/router/routes.ts
{
  path: '/dashboard',
  component: () => import('@/pages/dashboard.vue'),
  meta: { requiresAuth: true }
}

// src/router/index.ts
router.beforeEach((to, from) => {
  const auth = useAuth()

  if (to.meta.requiresAuth && !auth.isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})
```

Define guards in the router (not components) because:

- Guard logic often applies to multiple routes
- Keeps page components focused on rendering
- Easier to test in isolation

## Lazy Loading

All routes use dynamic imports for automatic code splitting:

```typescript
// Each page becomes a separate chunk
component: () => import('@/pages/about.vue')
```

This means users only download the code for pages they visit, improving initial load time.

For related pages that are often visited together, you can group them into a named chunk:

```typescript
component: () => import(/* webpackChunkName: "settings" */ '@/pages/settings/profile.vue')
```


================================================
FILE: docs/state.md
================================================
# State Management

- [Overview](#overview)
- [Defining Stores](#defining-stores)
- [Using Stores](#using-stores)
- [Store Organization](#store-organization)
- [When to Use Stores](#when-to-use-stores)

## Overview

This project uses [Pinia](https://pinia.vuejs.org/) for state management. Pinia is the official state management library for Vue 3, replacing Vuex.

**Why Pinia over Vuex?**

- Simpler API — no mutations, just state and actions
- Full TypeScript support with type inference
- Composition API style with `ref` and `computed`
- Devtools integration
- Lighter weight

Stores live in `src/stores/`.

## Defining Stores

Use the Composition API style (setup stores) for consistency with components:

```typescript
// src/stores/authStore.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useAuthStore = defineStore('authStore', () => {
  // State
  const user = ref<User | null>(null)
  const token = ref<string | null>(null)

  // Getters (computed)
  const isAuthenticated = computed(() => !!token.value)

  // Actions
  async function login(credentials: Credentials) {
    const response = await api.login(credentials)
    user.value = response.user
    token.value = response.token
  }

  function logout() {
    user.value = null
    token.value = null
  }

  return { user, token, isAuthenticated, login, logout }
})
```

### Naming Conventions

| Element | Convention | Example |
|---------|------------|---------|
| File name | `[domain]Store.ts` | `authStore.ts`, `cartStore.ts` |
| Export name | `use[Domain]Store` | `useAuthStore`, `useCartStore` |
| Store ID | `[domain]Store` | `'authStore'`, `'cartStore'` |

**Avoid single-word names** — Be specific about what the store manages. Use `userProfileStore` instead of `user`, `shoppingCartStore` instead of `cart` if needed for clarity.

## Using Stores

Import and use stores in components:

```vue
<script setup lang="ts">
import { useAuthStore } from '@/stores/authStore'

const authStore = useAuthStore()
</script>

<template>
  <div v-if="authStore.isAuthenticated">
    <p>Welcome, {{ authStore.user?.name }}</p>
    <button @click="authStore.logout">Logout</button>
  </div>
  <div v-else>
    <button @click="showLogin">Login</button>
  </div>
</template>
```

### Destructuring with Reactivity

Use `storeToRefs` to destructure while maintaining reactivity:

```vue
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useAuthStore } from '@/stores/authStore'

const authStore = useAuthStore()
const { user, isAuthenticated } = storeToRefs(authStore)
const { login, logout } = authStore // Actions don't need storeToRefs
</script>
```

## Store Organization

Keep stores focused on a single domain:

```
src/stores/
├── authStore.ts      # Authentication state
├── cartStore.ts      # Shopping cart
├── userStore.ts      # User profile
└── productStore.ts   # Product catalog
```

### Composing Stores

Stores can use other stores:

```typescript
// src/stores/cartStore.ts
import { defineStore } from 'pinia'
import { useAuthStore } from './authStore'

export const useCartStore = defineStore('cartStore', () => {
  const authStore = useAuthStore()

  async function checkout() {
    if (!authStore.isAuthenticated) {
      throw new Error('Must be logged in to checkout')
    }
    // ... checkout logic
  }

  return { checkout }
})
```

## When to Use Stores

**Use Pinia stores for:**

- Authentication state (current user, tokens)
- Data shared across multiple components
- Data that persists across route changes
- Complex state with many actions

**Use local component state for:**

- Form inputs
- UI state (modals, dropdowns)
- Data used by a single component
- Temporary state that resets on navigation

**Rule of thumb:** Start with local state. Extract to a store when you need to share state across components or persist it across routes.


================================================
FILE: docs/tech.md
================================================
# Languages and Technologies

- [TypeScript](#typescript)
  - [Why TypeScript](#why-typescript)
  - [Configuration](#configuration)
  - [TypeScript FAQ](#typescript-faq)
- [Vue](#vue)
  - [Composition API](#composition-api)
  - [Vue Router](#vue-router)
  - [Pinia](#pinia)
- [HTML](#html)
  - [Templates](#templates)
  - [Render Functions](#render-functions)
- [CSS](#css)
  - [SCSS](#scss)
  - [Scoped Styles](#scoped-styles)
  - [Design Variables](#design-variables)
  - [Global CSS](#global-css)
  - [CSS FAQ](#css-faq)

## TypeScript

This project uses TypeScript for type safety and improved developer experience. All `.ts` and `.vue` files are type-checked.

### Why TypeScript

- **Catch errors early** — Type errors are caught at build time, not runtime
- **Better IDE support** — Autocomplete, refactoring, and inline documentation
- **Self-documenting code** — Types serve as documentation for function signatures
- **Vue 3 designed for it** — Vue 3 and its ecosystem have first-class TypeScript support

### Configuration

TypeScript is configured in multiple files:

| File | Purpose |
|------|---------|
| `tsconfig.json` | Base configuration, references other configs |
| `tsconfig.app.json` | Application code configuration |
| `tsconfig.node.json` | Node.js tooling (Vite config, etc.) |
| `tsconfig.vitest.json` | Test configuration |

Key settings:

```json
{
  "compilerOptions": {
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  }
}
```

### Migrating from JavaScript

If migrating an existing JavaScript codebase, enable `allowJs` in `tsconfig.json` for progressive migration:

```json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false
  }
}
```

This allows `.js` and `.ts` files to coexist, so you can migrate file-by-file without being blocked by TypeScript errors across the entire codebase. Once migration is complete, disable `allowJs` to enforce TypeScript everywhere.

**Migration strategy:**
1. Enable `allowJs`, disable `checkJs`
2. Rename files from `.js` to `.ts` one at a time
3. Fix type errors in each file as you convert
4. Once all files are `.ts`, disable `allowJs`

### TypeScript FAQ

**How strict should we be with types?**

Aim for full type coverage, but pragmatism over perfection. Use `any` sparingly and document why when you do. The `unknown` type is often a better choice when the type is truly unknown.

**Should I use `interface` or `type`?**

- Use `interface` for object shapes that might be extended
- Use `type` for unions, primitives, and utility types

```typescript
// Interface for objects
interface User {
  id: string
  name: string
}

// Type for unions
type Status = 'pending' | 'active' | 'completed'
```

## Vue

This project uses [Vue 3](https://vuejs.org/) with the Composition API.

### Composition API

This boilerplate uses `<script setup>` syntax with the Composition API:

```vue
<script setup lang="ts">
import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>
```

### Options API

The Options API is still fully supported in Vue 3 and remains a valid choice:

```vue
<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  data() {
    return { count: 0 }
  },
  computed: {
    doubled(): number {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
})
</script>
```

### Which API to Choose?

Both APIs are production-ready. Choose based on your team's needs:

| Factor | Composition API | Options API |
|--------|-----------------|-------------|
| TypeScript | Better type inference | Requires `defineComponent` wrapper |
| Code organization | Organize by feature/concern | Organize by option type |
| Learning curve | Steeper for Vue beginners | More familiar structure |
| Logic reuse | Composables | Mixins (less flexible) |
| Bundle size | Better tree-shaking | Slightly larger |

**This boilerplate uses Composition API because:**
- Better TypeScript integration
- Composables are more flexible than mixins
- Aligns with Vue ecosystem direction

**Options API is a good choice when:**
- Team is more familiar with it
- Migrating from Vue 2 incrementally
- Simpler components that don't need composables

For a deeper comparison, read the [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html).

### Vue Router

[Vue Router 4](https://router.vuejs.org/) handles client-side routing. See [routing.md](routing.md) for project-specific patterns.

Key resources:
- [Getting Started](https://router.vuejs.org/guide/)
- [Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html)
- [Route Meta Fields](https://router.vuejs.org/guide/advanced/meta.html)

### Pinia

[Pinia](https://pinia.vuejs.org/) handles state management. See [state.md](state.md) for project-specific patterns.

Key resources:
- [Getting Started](https://pinia.vuejs.org/getting-started.html)
- [Defining a Store](https://pinia.vuejs.org/core-concepts/)
- [Using Stores](https://pinia.vuejs.org/core-concepts/#using-the-store)

## HTML

All HTML lives in `.vue` files, either in `<template>` blocks or render functions.

### Templates

Templates are the default and recommended approach for most components:

```vue
<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="user.name" />
    <h2>{{ user.name }}</h2>
    <slot />
  </div>
</template>
```

Vue templates support:
- Self-closing tags: `<MyComponent />`
- Dynamic attributes: `:src="imageUrl"`
- Event handling: `@click="handleClick"`
- Conditional rendering: `v-if`, `v-show`
- List rendering: `v-for`

### Render Functions

Use render functions when you need the full power of JavaScript:

```typescript
import { h } from 'vue'

function render() {
  return h('div', { class: 'container' }, [
    h('h1', 'Hello'),
    h(MyComponent, { prop: value })
  ])
}
```

Render functions are rare — use templates unless you have a specific reason not to.

## CSS

This project uses SCSS with scoped styles.

```vue
<style lang="scss" scoped>
.container {
  padding: 1rem;
}
</style>
```

### SCSS

[SCSS](https://sass-lang.com/) is a superset of CSS that adds:

- [Variables](https://sass-lang.com/guide#variables): `$primary-color: #3498db;`
- [Nesting](https://sass-lang.com/guide#nesting): Nest selectors for cleaner code
- [Mixins](https://sass-lang.com/guide#mixins): Reusable style patterns
- [Functions](https://sass-lang.com/guide#functions): Color manipulation, math, etc.

Any valid CSS is valid SCSS, so you can adopt features gradually.

### Scoped Styles

The `scoped` attribute ensures styles only apply to the current component:

```vue
<style scoped>
/* This .button only affects this component */
.button {
  background: blue;
}
</style>
```

Vue adds a unique attribute (e.g., `data-v-7ba5bd90`) to elements and selectors, preventing style leakage.

**Styling child components:**

Use `:deep()` to style elements inside child components:

```vue
<style scoped>
/* Style an element inside a child component */
:deep(.child-class) {
  color: red;
}
</style>
```

Use sparingly — prefer passing props or classes to child components.

### Design Variables

For shared design tokens (colors, spacing, typography), create a variables file:

```scss
// src/styles/variables.scss
$color-primary: #3498db;
$color-secondary: #2ecc71;
$spacing-unit: 8px;
```

Import in components that need them:

```vue
<style lang="scss" scoped>
@use '@/styles/variables' as *;

.button {
  background: $color-primary;
  padding: $spacing-unit * 2;
}
</style>
```

> **Note:** With Vite, use `@use` instead of `@import` for better performance and to avoid deprecation warnings.

### Global CSS

Global styles should be minimal. Keep them in `src/styles/` and import in `main.ts`:

```typescript
// src/main.ts
import './styles/global.scss'
```

Global CSS should only contain:
- CSS resets or normalization
- Base element styles (typography, links)
- Utility classes (if not using a utility framework)

### CSS FAQ

**Why scoped styles instead of CSS Modules?**

Scoped styles are simpler and sufficient for most use cases. CSS Modules offer slightly better collision protection but add complexity. For this project, scoped styles provide the right balance.

**Why SCSS instead of plain CSS?**

SCSS offers variables, nesting, and mixins that make styles more maintainable. Since SCSS is a superset of CSS, the learning curve is minimal.

**Should I use Tailwind CSS?**

Tailwind is a valid choice for utility-first styling. It's not included by default, but can be added if the team prefers that approach. See the [Tailwind + Vite guide](https://tailwindcss.com/docs/guides/vite#vue).


================================================
FILE: docs/tests.md
================================================
# Testing

- [Overview](#overview)
- [Unit Tests with Vitest](#unit-tests-with-vitest)
  - [Running Unit Tests](#running-unit-tests)
  - [Writing Unit Tests](#writing-unit-tests)
  - [Testing Composables](#testing-composables)
  - [Testing Components](#testing-components)
  - [Mocking](#mocking)
- [End-to-End Tests with Playwright](#end-to-end-tests-with-playwright)
  - [Running E2E Tests](#running-e2e-tests)
  - [Writing E2E Tests](#writing-e2e-tests)
  - [Accessibility-Driven Testing](#accessibility-driven-testing)
- [Test Organization](#test-organization)
- [Mock API](#mock-api)

## Overview

This project uses two testing frameworks:

| Type | Framework | Purpose |
|------|-----------|---------|
| Unit | [Vitest](https://vitest.dev/) | Test functions, composables, and components in isolation |
| E2E | [Playwright](https://playwright.dev/) | Test complete user flows in real browsers |

## Unit Tests with Vitest

### Running Unit Tests

```bash
# Run unit tests once
pnpm test:unit

# Run in watch mode (re-runs on file changes)
pnpm test:unit --watch

# Run with coverage report
pnpm test:unit --coverage
```

### Writing Unit Tests

Vitest uses the same API as Jest (`describe`, `it`, `expect`):

```typescript
// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest'
import { formatDate } from './formatDate'

describe('formatDate', () => {
  it('formats date in default format', () => {
    const date = new Date('2024-01-15')
    expect(formatDate(date)).toBe('January 15, 2024')
  })

  it('handles invalid dates', () => {
    expect(formatDate(null)).toBe('')
  })
})
```

### Testing Composables

Test composables by calling them and asserting on their returned values:

```typescript
// src/composables/useCounter.test.ts
import { describe, it, expect } from 'vitest'
import { useCounter } from './useCounter'

describe('useCounter', () => {
  it('initializes with default value', () => {
    const { count } = useCounter()
    expect(count.value).toBe(0)
  })

  it('increments count', () => {
    const { count, increment } = useCounter()
    increment()
    expect(count.value).toBe(1)
  })

  it('accepts initial value', () => {
    const { count } = useCounter(10)
    expect(count.value).toBe(10)
  })
})
```

### Testing Components

Use `@vue/test-utils` for component testing:

```typescript
// src/components/BaseButton.test.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import BaseButton from './BaseButton.vue'

describe('BaseButton', () => {
  it('renders slot content', () => {
    const wrapper = mount(BaseButton, {
      slots: { default: 'Click me' }
    })
    expect(wrapper.text()).toContain('Click me')
  })

  it('emits click event', async () => {
    const wrapper = mount(BaseButton)
    await wrapper.trigger('click')
    expect(wrapper.emitted('click')).toBeTruthy()
  })

  it('is disabled when prop is set', () => {
    const wrapper = mount(BaseButton, {
      props: { disabled: true }
    })
    expect(wrapper.attributes('disabled')).toBeDefined()
  })
})
```

### Mocking

Vitest provides several mocking utilities:

```typescript
import { vi, describe, it, expect } from 'vitest'

// Mock a function
const mockFn = vi.fn()
mockFn('arg')
expect(mockFn).toHaveBeenCalledWith('arg')

// Mock a module
vi.mock('@/services/api', () => ({
  fetchUser: vi.fn(() => Promise.resolve({ id: '1', name: 'Test' }))
}))

// Mock timers
vi.useFakeTimers()
vi.advanceTimersByTime(1000)
vi.useRealTimers()
```

## End-to-End Tests with Playwright

### Running E2E Tests

```bash
# Run E2E tests headlessly
pnpm test:e2e

# Run with UI mode (interactive)
pnpm test:e2e:ui

# Run specific test file
pnpm test:e2e e2e/login.spec.ts
```

### Writing E2E Tests

E2E tests live in the `e2e/` directory:

```typescript
// e2e/login.spec.ts
import { test, expect } from '@playwright/test'

test('user can log in', async ({ page }) => {
  await page.goto('/login')

  await page.getByLabel('Email').fill('user@example.com')
  await page.getByLabel('Password').fill('password123')
  await page.getByRole('button', { name: 'Log in' }).click()

  await expect(page).toHaveURL('/dashboard')
  await expect(page.getByText('Welcome back')).toBeVisible()
})

test('shows error for invalid credentials', async ({ page }) => {
  await page.goto('/login')

  await page.getByLabel('Email').fill('wrong@example.com')
  await page.getByLabel('Password').fill('wrongpassword')
  await page.getByRole('button', { name: 'Log in' }).click()

  await expect(page.getByText('Invalid credentials')).toBeVisible()
})
```

### Accessibility-Driven Testing

Write selectors from the user's perspective using semantic queries:

```typescript
// Prefer role-based selectors
await page.getByRole('button', { name: 'Submit' })
await page.getByRole('link', { name: 'Log in' })
await page.getByRole('textbox', { name: 'Email' })

// Use labels for form fields
await page.getByLabel('Password')

// Use text for content
await page.getByText('Welcome back')

// Avoid implementation details
// ❌ page.locator('.login-btn')
// ❌ page.locator('#submit-button')
// ❌ page.locator('[data-testid="login"]')
```

**Why accessibility-driven selectors?**

- Tests break when requirements change, not when implementation changes
- Forces you to write accessible HTML (labels, ARIA attributes)
- Mirrors how real users interact with your app

If a selector is hard to write, it's often a sign the HTML needs better accessibility:

```html
<!-- Hard to select (no label) -->
<input type="text" class="email-input" />

<!-- Easy to select -->
<label>
  Email
  <input type="text" />
</label>
```

## Test Organization

```
project/
├── src/
│   ├── composables/
│   │   ├── useAuth.ts
│   │   └── useAuth.test.ts      # Unit test next to source
│   └── components/
│       ├── BaseButton.vue
│       └── BaseButton.test.ts   # Unit test next to source
├── e2e/
│   ├── login.spec.ts            # E2E tests in dedicated folder
│   └── checkout.spec.ts
├── vitest.config.ts
└── playwright.config.ts
```

**Unit tests** live alongside source files (`*.test.ts`). This makes poor coverage obvious and lowers the barrier to adding tests.

**E2E tests** live in `e2e/` directory, organized by user flow or feature.

## Mock API

For development and testing without a backend, you can mock API responses:

**Option 1: MSW (Mock Service Worker)**

```typescript
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/api/user', () => {
    return HttpResponse.json({ id: '1', name: 'Test User' })
  }),
  http.post('/api/login', async ({ request }) => {
    const { email } = await request.json()
    if (email === 'user@example.com') {
      return HttpResponse.json({ token: 'fake-token' })
    }
    return new HttpResponse(null, { status: 401 })
  })
]
```

**Option 2: Environment variable for real API**

```bash
# Test against a staging server
VITE_API_BASE_URL=https://staging.example.com pnpm test:e2e
```

See [development.md](development.md) for more on environment variables.


================================================
FILE: docs/troubleshooting.md
================================================
# Troubleshooting

Common issues and solutions for development.

- [Script Errors](#script-errors)
- [VS Code Issues](#vs-code-issues)
- [TypeScript Errors](#typescript-errors)
- [Vite / Build Issues](#vite--build-issues)
- [Vue-Specific Issues](#vue-specific-issues)

## Script Errors

**Problem:** Errors when running `pnpm dev` or other scripts.

**Solution 1:** Fresh install dependencies

```bash
# Delete node_modules and lockfile
rm -rf node_modules pnpm-lock.yaml

# Reinstall
pnpm install
```

**Solution 2:** Use lockfile from known working state

```bash
rm -rf node_modules

# Restore lockfile from main branch
git checkout origin/main -- pnpm-lock.yaml

# Install with frozen lockfile
pnpm install --frozen-lockfile
```

If this fixes the issue, a dependency update likely caused the problem. Use `pnpm outdated` to identify candidates, then upgrade one at a time to find the culprit.

## VS Code Issues

**Problem:** Files formatted incorrectly on save.

**Cause:** Conflicting formatter extensions or incorrect default formatter.

**Solution:**

1. Ensure Prettier is the default formatter in `.vscode/settings.json`:
   ```json
   {
     "editor.defaultFormatter": "esbenp.prettier-vscode"
   }
   ```

2. Disable or uninstall conflicting formatters (e.g., Beautify, Format)

3. If using Volar, ensure Vetur is uninstalled (they conflict)

**Problem:** Vue files show errors but code works fine.

**Solution:** Restart the Vue language server:
- Open Command Palette (`Cmd+Shift+P`)
- Run "Vue: Restart Vue Server"

## TypeScript Errors

**Problem:** Type errors in IDE but build succeeds (or vice versa).

**Solution:** Restart the TypeScript server:
- Open Command Palette (`Cmd+Shift+P`)
- Run "TypeScript: Restart TS Server"

**Problem:** Types not recognized for auto-imported components/composables.

**Solution:** Ensure generated type files exist:
```bash
# Run dev server to generate types
pnpm dev

# Check these files exist:
# - components.d.ts
# - auto-imports.d.ts
```

Add them to `.gitignore` but not to `tsconfig.json` excludes.

**Problem:** "Cannot find module" for path aliases like `@/`.

**Solution:** Ensure `tsconfig.app.json` has matching paths:
```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

## Vite / Build Issues

**Problem:** Dev server starts but page is blank.

**Solution:** Check browser console for errors. Common causes:
- Missing environment variables (check `.env` files)
- Syntax error in a component
- Failed import (check file paths and extensions)

**Problem:** Build succeeds but production app doesn't work.

**Solution:** Test with preview server:
```bash
pnpm build
pnpm preview
```

Check for:
- Hardcoded `localhost` URLs (use environment variables)
- Missing environment variables in production
- SSR-incompatible code if using SSR

**Problem:** "Failed to resolve import" errors.

**Solution:**
- Check the import path is correct
- Ensure the file extension is included for non-JS/TS files
- For external packages, ensure they're in `dependencies` not just `devDependencies`

## Vue-Specific Issues

**Problem:** Reactivity not working (changes don't update UI).

**Common causes:**
```typescript
// ❌ Destructuring loses reactivity
const { count } = useCounterStore()

// ✅ Use storeToRefs
const { count } = storeToRefs(useCounterStore())

// ❌ Replacing reactive object
const state = reactive({ items: [] })
state = { items: newItems } // Loses reactivity

// ✅ Mutate properties instead
state.items = newItems
```

**Problem:** "Hydration mismatch" warnings (SSR).

**Cause:** Server and client rendered different HTML.

**Solution:**
- Wrap client-only code in `<ClientOnly>` or `onMounted`
- Don't use `Date.now()` or `Math.random()` during render
- Ensure async data is fetched the same way on server and client

**Problem:** Component not updating when prop changes.

**Solution:** Ensure you're not caching the prop value:
```typescript
// ❌ Caches initial value
const localValue = props.value

// ✅ Stays reactive
const localValue = computed(() => props.value)

// ✅ Or use toRef
const localValue = toRef(props, 'value')
```

---

Still stuck? Check the [Vue documentation](https://vuejs.org/) or open an issue with:
- Steps to reproduce
- Expected vs actual behavior
- Error messages (full stack trace)
- Node/pnpm versions (`node -v`, `pnpm -v`)


================================================
FILE: e2e/tsconfig.json
================================================
{
  "extends": "@tsconfig/node20/tsconfig.json",
  "include": ["./**/*"]
}


================================================
FILE: e2e/vue.spec.ts
================================================
import { test, expect } from '@playwright/test'

// See here how to get started:
// https://playwright.dev/docs/intro
test('visits the app root url', async ({ page }) => {
  await page.goto('/')
  await expect(page.locator('h1')).toHaveText('Home Page')
})


================================================
FILE: env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 3 App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "name": "vue-enterprise-boilerplate",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check build-only",
    "preview": "vite preview",
    "test:unit": "vitest",
    "test:e2e": "playwright test",
    "test:e2e:ui": "playwright test --ui",
    "build-only": "vite build",
    "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/",
    "docs": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs"
  },
  "dependencies": {
    "@vueuse/core": "^10.9.0",
    "pinia": "^2.0.36",
    "vue": "^3.4.27",
    "vue-router": "^4.2.0"
  },
  "devDependencies": {
    "@playwright/test": "^1.44.0",
    "@rushstack/eslint-patch": "^1.2.0",
    "@tsconfig/node18": "^2.0.1",
    "@types/jsdom": "^21.1.1",
    "@types/node": "^18.16.8",
    "@unhead/vue": "^1.9.10",
    "@vitejs/plugin-vue": "^4.2.3",
    "@vue/eslint-config-prettier": "^7.1.0",
    "@vue/eslint-config-typescript": "^11.0.3",
    "@vue/test-utils": "^2.4.6",
    "@vue/tsconfig": "^0.4.0",
    "eslint": "^8.39.0",
    "eslint-plugin-vue": "^9.11.0",
    "jsdom": "^22.0.0",
    "npm-run-all": "^4.1.5",
    "prettier": "^2.8.8",
    "sass": "^1.77.1",
    "typescript": "~5.0.4",
    "unplugin-auto-import": "^0.17.6",
    "unplugin-vue-components": "^0.27.0",
    "vite": "^4.3.5",
    "vitepress": "^1.6.4",
    "vitest": "^1.4.0",
    "vue-tsc": "^1.6.4"
  }
}


================================================
FILE: playwright.config.ts
================================================
import process from 'node:process'
import { defineConfig, devices } from '@playwright/test'

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// require('dotenv').config();

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: './e2e',
  /* Maximum time one test can run for. */
  timeout: 30 * 1000,
  expect: {
    /**
     * Maximum time expect() should wait for the condition to be met.
     * For example in `await expect(locator).toHaveText();`
     */
    timeout: 5000
  },
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
    actionTimeout: 0,
    /* Base URL to use in actions like `await page.goto('/')`. */
    baseURL: 'http://localhost:8080',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',

    /* Only on CI systems run the tests headless */
    headless: !!process.env.CI
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome']
      }
    },
    {
      name: 'firefox',
      use: {
        ...devices['Desktop Firefox']
      }
    },
    {
      name: 'webkit',
      use: {
        ...devices['Desktop Safari']
      }
    }

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: {
    //     ...devices['Pixel 5'],
    //   },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: {
    //     ...devices['iPhone 12'],
    //   },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: {
    //     channel: 'msedge',
    //   },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: {
    //     channel: 'chrome',
    //   },
    // },
  ],

  /* Folder for test artifacts such as screenshots, videos, traces, etc. */
  // outputDir: 'test-results/',

  /* Run your local dev server before starting the tests */
  webServer: {
    /**
     * Use the dev server by default for faster feedback loop.
     * Use the preview server on CI for more realistic testing.
     * Playwright will re-use the local server if there is already a dev-server running.
     */
    command: process.env.CI ? 'vite preview --port 8080' : 'vite dev',
    port: 8080,
    reuseExistingServer: !process.env.CI
  }
})


================================================
FILE: src/App.vue
================================================
<script setup lang="ts"></script>

<template>
  <RouterView />
</template>

<style lang="scss"></style>


================================================
FILE: src/components/BaseButton.spec.ts
================================================
import { describe, it, expect } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import BaseButton from './BaseButton.vue'

describe('BaseButton Componenet', () => {
  it('renders its content', () => {
    const slotContent = '<strong>Click me!</strong>'
    const { element } = shallowMount(BaseButton, {
      slots: {
        default: slotContent
      }
    })
    expect(element.innerHTML).toContain(slotContent)
  })

  it('renders default content', () => {
    const slotContent = ''
    const { element } = shallowMount(BaseButton, {
      slots: {
        default: slotContent
      }
    })
    expect(element.innerHTML).toContain('Submit')
  })
})


================================================
FILE: src/components/BaseButton.vue
================================================
<template>
  <button :class="$style.button">
    <slot>Submit</slot>
  </button>
</template>

<style lang="scss" module>
.button {
  @extend %typography-small;

  padding: $size-button-padding;
  border: none;
  background: $color-button-bg;
  color: $color-button-text;
  cursor: pointer;

  &:disabled {
    cursor: not-allowed;
    background: $color-button-disabled-bg;
  }
}
</style>


================================================
FILE: src/components/BaseInputText.spec.ts
================================================
import { describe, it, expect, vi } from 'vitest'
import { shallowMount, mount } from '@vue/test-utils'
import BaseInputText from '@/components/BaseInputText.vue'

describe('@components/BaseInputText', () => {
  it('works with v-model', () => {
    const wrapper = mount(BaseInputText, {
      props: {
        modelValue: 'aaa',
        'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
      }
    })
    const inputWrapper = wrapper.find('input')
    const inputEl = inputWrapper.element

    // Has the correct starting value
    expect(inputEl.value).toEqual('aaa')

    // Sets the input to the correct value when props change
    inputWrapper.setValue('ccc')
    expect(inputEl.value).toEqual('ccc')
  })

  it('allows a type of "password"', () => {
    const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
    shallowMount(BaseInputText, {
      propsData: { value: 'aaa', type: 'password' }
    })
    expect(consoleError).not.toBeCalled()
    consoleError.mockRestore()
  })

  it('does NOT allow a type of "checkbox"', () => {
    const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})
    shallowMount(BaseInputText, {
      propsData: { value: 'aaa', type: 'checkbox' }
    })

    expect(consoleWarn).toBeCalled()
    expect(consoleWarn.mock.calls[0][0]).toContain('custom validator check failed for prop "type"')
    consoleWarn.mockRestore()
  })
})


================================================
FILE: src/components/BaseInputText.vue
================================================
<script setup lang="ts">
defineProps({
  type: {
    type: String,
    default: 'text',
    // Only allow types that essentially just render text boxes
    validator: (value: string) =>
      ['email', 'number', 'password', 'search', 'tel', 'text', 'url'].includes(value)
  }
})

const model = defineModel()
</script>

<template>
  <input v-model="model" :type="type" :class="$style.input" />
</template>

<style lang="scss" module>
.input {
  @extend %typography-small;

  display: block;
  width: 100%;
  padding: $size-input-padding-vertical $size-input-padding-horizontal;
  margin-bottom: $size-grid-padding;
  line-height: 1;
  border: $size-input-border solid $color-input-border;
  border-radius: $size-input-border-radius;
}
</style>


================================================
FILE: src/composables/useTheme.ts
================================================
import { useColorMode } from '@vueuse/core'

type Theme = 'dark' | 'light'

function useTheme() {
  const themePreference = useColorMode()

  function setTheme(theme: Theme) {
    themePreference.value = theme
  }

  return { setTheme, themePreference }
}

export default useTheme


================================================
FILE: src/design/_colors.scss
================================================
// CONTENT
$color-body-bg: #f9f7f5;
$color-text: #444;
$color-heading-text: #35495e;

// LINKS
$color-link-text: #39a275;
$color-link-text-active: $color-text;

// INPUTS
$color-input-border: lighten($color-heading-text, 50%);

// BUTTONS
$color-button-bg: $color-link-text;
$color-button-disabled-bg: darken(desaturate($color-button-bg, 20%), 10%);
$color-button-text: white;


================================================
FILE: src/design/_durations.scss
================================================
$duration-animation-base: 300ms;


================================================
FILE: src/design/_fonts.scss
================================================
$system-default-font-family: -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica', 'Arial',
  sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';

$heading-font-family: $system-default-font-family;
$heading-font-weight: 600;

$content-font-family: $system-default-font-family;
$content-font-weight: 400;

%font-heading {
  font-family: $heading-font-family;
  font-weight: $heading-font-weight;
  color: $color-heading-text;
}

%font-content {
  font-family: $content-font-family;
  font-weight: $content-font-weight;
  color: $color-text;
}


================================================
FILE: src/design/_layers.scss
================================================
$layer-negative-z-index: -1;
$layer-page-z-index: 1;
$layer-dropdown-z-index: 2;
$layer-modal-z-index: 3;
$layer-popover-z-index: 4;
$layer-tooltip-z-index: 5;


================================================
FILE: src/design/_sizes.scss
================================================
// GRID
$size-grid-padding: 1.3rem;

// CONTENT
$size-content-width-max: 50rem;
$size-content-width-min: 25rem;

// INPUTS
$size-input-padding-vertical: 0.75em;
$size-input-padding-horizontal: 1em;
$size-input-padding: $size-input-padding-vertical $size-input-padding-horizontal;
$size-input-border: 1px;
$size-input-border-radius: calc((1em + $size-input-padding-vertical * 2) / 10);

// BUTTONS
$size-button-padding-vertical: calc($size-grid-padding / 2);
$size-button-padding-horizontal: calc($size-grid-padding / 1.5);
$size-button-padding: $size-button-padding-vertical $size-button-padding-horizontal;


================================================
FILE: src/design/_typography.scss
================================================
// Interpolate v1.0

// This mixin generates CSS for interpolation of length properties.
// It has 5 required values, including the target property, initial
// screen size, initial value, final screen size and final value.

// It has two optional values which include an easing property,
// which is a string, representing a CSS animation-timing-function
// and finally a number of bending-points, that determines how many
// interpolations steps are applied along the easing function.

// Author: Mike Riethmuller - @MikeRiethmuller
// More information: http://codepen.io/MadeByMike/pen/a2249946658b139b7625b2a58cf03a65?editors=0100

///
/// @param {String} $property - The CSS property to interpolate
/// @param {Unit} $min-screen - A CSS length unit
/// @param {Unit} $min-value - A CSS length unit
/// @param {Unit} $max-screen - Value to be parsed
/// @param {Unit} $max-value - Value to be parsed
/// @param {String} $easing - Value to be parsed
/// @param {Integer} $bending-points - Value to be parsed
///

// Examples on line 258

// Issues:

// - kubic-bezier requires whitespace
// - kubic-bezier cannot parse negative values

// stylelint-disable scss/dollar-variable-pattern
@mixin typography-interpolate(
  $property,
  $min-screen,
  $min-value,
  $max-screen,
  $max-value,
  $easing: 'linear',
  $bending-points: 2
) {
  // Default Easing 'Linear'
  $p0: 0;
  $p1: 0;
  $p2: 1;
  $p3: 1;

  // Parse Cubic Bezier string
  @if (str-slice($easing, 1, 12) == 'kubic-bezier') {
    // Get the values between the brackets
    // TODO: Deal with different whitespace
    $i: str-index($easing, ')'); // Get index of closing bracket
    $values: str-slice($easing, 14, $i - 1); // Extract values between brackts
    $list: typography-explode($values, ', '); // Split the values into a list

    @debug ($list);

    // Cast values to numebrs
    $p0: typography-number(nth($list, 1));
    $p1: typography-number(nth($list, 2));
    $p2: typography-number(nth($list, 3));
    $p3: typography-number(nth($list, 4));
  }

  @if ($easing == 'ease') {
    $p0: 0.25;
    $p1: 1;
    $p2: 0.25;
    $p3: 1;
  }

  @if ($easing == 'ease-in-out') {
    $p0: 0.42;
    $p1: 0;
    $p2: 0.58;
    $p3: 1;
  }

  @if ($easing == 'ease-in') {
    $p0: 0.42;
    $p1: 0;
    $p2: 1;
    $p3: 1;
  }

  @if ($easing == 'ease-out') {
    $p0: 0;
    $p1: 0;
    $p2: 0.58;
    $p3: 1;
  }

  #{$property}: $min-value;

  @if ($easing == 'linear' or $bending-points < 1) {
    @media screen and (min-width: $min-screen) {
      #{$property}: typography-calc-interpolation($min-screen, $min-value, $max-screen, $max-value);
    }
  } @else {
    // Loop through bending points
    $t: 1 / ($bending-points + 1);
    $i: 1;
    $prev-screen: $min-screen;
    $prev-value: $min-value;

    @while $t * $i <= 1 {
      $bending-point: $t * $i;
      $value: typography-cubic-bezier($p0, $p1, $p2, $p3, $bending-point);
      $screen-int: typography-lerp($min-screen, $max-screen, $bending-point);
      $value-int: typography-lerp($min-value, $max-value, $value);

      @media screen and (min-width: $prev-screen) {
        #{$property}: typography-calc-interpolation(
          $prev-screen,
          $prev-value,
          $screen-int,
          $value-int
        );
      }

      $prev-screen: $screen-int;
      $prev-value: $value-int;
      $i: $i + 1;
    }
  }

  @media screen and (min-width: $max-screen) {
    #{$property}: $max-value;
  }
}

// Requires several helper functions including: pow, calc-interpolation, kubic-bezier, number and explode

// Math functions:

// Linear interpolations in CSS as a Sass function
// Author: Mike Riethmuller | https://madebymike.com.au/writing/precise-control-responsive-typography/ I

@function typography-calc-interpolation($min-screen, $min-value, $max-screen, $max-value) {
  $a: calc(($max-value - $min-value) / ($max-screen - $min-screen));
  $b: $min-value - $a * $min-screen;

  $sign: '+';

  @if ($b < 0) {
    $sign: '-';
    $b: abs($b);
  }

  @return calc(#{$a * 100}vw #{$sign} #{$b});
}

// This is a crude Sass port webkits cubic-bezier function. Looking to simplify this if you can help.
@function typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x) {
  $cx: 3 * $p1x;
  $bx: 3 * ($p2x - $p1x) - $cx;
  $ax: 1 - $cx - $bx;

  $t0: 0;
  $t1: 1;
  $t2: $x;
  $x2: 0;
  $res: 1000;

  @while ($t0 < $t1 or $break) {
    $x2: (($ax * $t2 + $bx) * $t2 + $cx) * $t2;

    @if (abs($x2 - $x) < $res) {
      @return $t2;
    }

    @if ($x > $x2) {
      $t0: $t2;
    } @else {
      $t1: $t2;
    }
    $t2: ($t1 - $t0) * 0.5 + $t0;
  }

  @return $t2;
}

@function typography-cubic-bezier($p1x, $p1y, $p2x, $p2y, $x) {
  $cy: 3 * $p1y;
  $by: 3 * ($p2y - $p1y) - $cy;
  $ay: 1 - $cy - $by;
  $t: typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x);

  @return (($ay * $t + $by) * $t + $cy) * $t;
}

// A stright up lerp
// Credit: Ancient Greeks possibly Hipparchus of Rhodes
@function typography-lerp($a, $b, $t) {
  @return $a + ($b - $a) * $t;
}

// String functions:

// Cast string to number
// Credit: Hugo Giraudel | https://www.sassmeister.com/gist/9fa19d254864f33d4a80
@function typography-number($value) {
  @if type-of($value) == 'number' {
    @return $value;
  } @else if type-of($value) != 'string' {
    $_: log('Value for `to-number` should be a number or a string.');
  }

  $result: 0;
  $digits: 0;
  $minus: str-slice($value, 1, 1) == '-';
  $numbers: (
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9
  );

  @for $i from if($minus, 2, 1) through str-length($value) {
    $character: str-slice($value, $i, $i);

    @if not(index(map-keys($numbers), $character) or $character == '.') {
      @return to-length(if($minus, -$result, $result), str-slice($value, $i));
    }

    @if $character == '.' {
      $digits: 1;
    } @else if $digits == 0 {
      $result: $result * 10 + map-get($numbers, $character);
    } @else {
      $digits: $digits * 10;
      $result: $result + map-get($numbers, $character) / $digits;
    }
  }

  @return if($minus, -$result, $result);
}

// Explode a string by a delimiter
// Credit: https://gist.github.com/danielpchen/3677421ea15dcf2579ff
@function typography-explode($string, $delimiter) {
  $result: ();

  @if $delimiter == '' {
    @for $i from 1 through str-length($string) {
      $result: append($result, str-slice($string, $i, $i));
    }

    @return $result;
  }
  $exploding: true;

  @while $exploding {
    $d-index: str-index($string, $delimiter);

    @if $d-index {
      @if $d-index > 1 {
        $result: append($result, str-slice($string, 1, $d-index - 1));
        $string: str-slice($string, $d-index + str-length($delimiter));
      } @else if $d-index == 1 {
        $string: str-slice($string, 1, $d-index + str-length($delimiter));
      } @else {
        $result: append($result, $string);
        $exploding: false;
      }
    } @else {
      $result: append($result, $string);
      $exploding: false;
    }
  }

  @return $result;
}

// Using vertical rhythm methods from https://scotch.io/tutorials/aesthetic-sass-3-typography-and-vertical-rhythm
// Using perfect 8/9 for low contrast and perfect fifth 2/3 for high
$typography-type-scale: (-1: 0.889rem, 0: 1rem, 1: 1.125rem, 2: 1.266rem, 3: 1.424rem);

@function typography-type-scale($level) {
  @if map-has-key($typography-type-scale, $level) {
    @return map-get($typography-type-scale, $level);
  }

  @warn 'Unknown `#{$level}` in $typography-type-scale.';

  @return null;
}

$typography-type-scale-contrast: (-1: 1rem, 0: 1.3333rem, 1: 1.777rem, 2: 2.369rem, 3: 3.157rem);

@function typography-type-scale-contrast($level) {
  @if map-has-key($typography-type-scale-contrast, $level) {
    @return map-get($typography-type-scale-contrast, $level);
  }

  @warn 'Unknown `#{$level}` in $typography-type-scale-contrast.';

  @return null;
}

$typography-base-font-size: 1rem;
$typography-base-line-height: $typography-base-font-size * 1.25;

$typography-line-heights: (
  -1: $typography-base-line-height,
  0: $typography-base-line-height,
  1: $typography-base-line-height * 1.5,
  2: $typography-base-line-height * 1.5,
  3: $typography-base-line-height * 1.5
);

@function typography-line-height($level) {
  @if map-has-key($typography-line-heights, $level) {
    @return map-get($typography-line-heights, $level);
  }

  @warn 'Unknown `#{$level}` in $line-height.';

  @return null;
}

$typography-base-line-height-contrast: $typography-base-line-height;

$typography-line-heights-contrast: (
  -1: $typography-base-line-height-contrast,
  0: $typography-base-line-height-contrast * 2,
  1: $typography-base-line-height-contrast * 2,
  2: $typography-base-line-height-contrast * 2,
  3: $typography-base-line-height * 3
);

@function typography-line-height-contrast($level) {
  @if map-has-key($typography-line-heights-contrast, $level) {
    @return map-get($typography-line-heights-contrast, $level);
  }

  @warn 'Unknown `#{$level}` in $typography-line-heights-contrast.';

  @return null;
}

// Mixing these two sets of mixins ala Rachel:
@mixin typography-got-rhythm($level: 0) {
  @include typography-interpolate(
    'font-size',
    $size-content-width-min,
    typography-type-scale($level),
    $size-content-width-max,
    typography-type-scale-contrast($level)
  );
  @include typography-interpolate(
    'line-height',
    $size-content-width-min,
    typography-line-height($level),
    $size-content-width-max,
    typography-line-height-contrast($level)
  );
}

%typography-xxlarge {
  @include typography-got-rhythm(3);

  @extend %font-heading;
}

%typography-xlarge {
  @include typography-got-rhythm(2);

  @extend %font-heading;
}

%typography-large {
  @include typography-got-rhythm(1);

  @extend %font-heading;
}

%typography-medium {
  @include typography-got-rhythm(0);

  @extend %font-content;
}

%typography-small {
  @include typography-got-rhythm(-1);

  @extend %font-content;
}


================================================
FILE: src/design/index.scss
================================================
@import 'colors';
@import 'durations';
@import 'fonts';
@import 'layers';
@import 'sizes';
@import 'typography';

:export {
  // Any values that need to be accessible from JavaScript
  // outside of a Vue component can be defined here, prefixed
  // with `global-` to avoid conflicts with classes. For
  // example:
  //
  // global-grid-padding: $size-grid-padding;
  //
  // Then in a JavaScript file, you can import this object
  // as you would normally with:
  //
  // import design from '@design'
  //
  // console.log(design['global-grid-padding'])
}


================================================
FILE: src/layouts/AppLayout.vue
================================================
<script setup lang="ts"></script>

<template>
  <div>
    <slot />
  </div>
</template>

<style lang="scss" scoped></style>


================================================
FILE: src/main.ts
================================================
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createHead } from '@unhead/vue'

import App from '@/App.vue'
import router from '@/router/index'

const app = createApp(App)

/** Pinia **/
/** https://pinia.vuejs.org/ **/
const pinia = createPinia()
app.use(pinia)

/** Vue Router **/
/** https://router.vuejs.org/ **/
app.use(router)

/** Unhead **/
/** https://unhead.unjs.io/ **/
const head = createHead()
app.use(head)

app.mount('#app')


================================================
FILE: src/pages/about.vue
================================================
<script setup lang="ts">
useHead({
  title: 'About - Vue Enterprise Boilerplate',
  meta: [
    {
      name: 'description',
      content: 'The about page for Vue Enterprise Boilerplate!'
    }
  ]
})
</script>

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>

<style>
@media (min-width: 1024px) {
  .about {
    min-height: 100vh;
    display: flex;
    align-items: center;
  }
}
</style>


================================================
FILE: src/pages/index.vue
================================================
<script setup lang="ts">
useHead({
  title: 'Home - Vue Enterprise Boilerplate',
  meta: [
    {
      name: 'description',
      content: 'Welcome to the Vue Enterprise Boilerplate!'
    }
  ]
})
</script>

<template>
  <AppLayout>
    <h1>Home Page</h1>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
  </AppLayout>
</template>

<style lang="scss" scoped></style>


================================================
FILE: src/router/index.ts
================================================
import { createRouter, createWebHistory } from 'vue-router'
import routes from './routes'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router


================================================
FILE: src/router/routes.ts
================================================
export default [
  {
    path: '/',
    name: 'home',
    component: () => import('@/pages/index.vue')
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (About.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import('@/pages/about.vue')
  }
]


================================================
FILE: src/stores/counter.ts
================================================
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})


================================================
FILE: src/types.ts
================================================
import type { Ref } from 'vue'

/**
 * It could be a ref, or a plain value
 */
export type MaybeRef<T> = T | Ref<T>

/**
 * It could be a ref, plain value, or getter function
 */
export type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T)


================================================
FILE: tsconfig.app.json
================================================
{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  "include": ["auto-imports.d.ts", "component.d.ts", "env.d.ts", "src/**/*", "src/**/*.vue"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}


================================================
FILE: tsconfig.json
================================================
{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.node.json"
    },
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.vitest.json"
    }
  ]
}


================================================
FILE: tsconfig.node.json
================================================
{
  "extends": "@tsconfig/node18/tsconfig.json",
  "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
  "compilerOptions": {
    "composite": true,
    "module": "ESNext",
    "types": ["node"]
  }
}


================================================
FILE: tsconfig.vitest.json
================================================
{
  "extends": "./tsconfig.app.json",
  "exclude": [],
  "compilerOptions": {
    "composite": true,
    "lib": [],
    "types": ["node", "jsdom"]
  }
}


================================================
FILE: vite.config.ts
================================================
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'

import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      // global imports to register
      imports: ['vue', 'vue-router', '@vueuse/core', { '@unhead/vue': ['useHead'] }],
      dirs: ['@src/composables']
    }),
    Components({
      dirs: ['src/components', 'src/layouts']
    })
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    port: 8080
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: '@use "@/design/index.scss" as *;'
      }
    }
  }
})


================================================
FILE: vitest.config.ts
================================================
import { fileURLToPath } from 'node:url'
import { mergeConfig } from 'vite'
import { configDefaults, defineConfig } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      environment: 'jsdom',
      exclude: [...configDefaults.exclude, 'e2e/*'],
      root: fileURLToPath(new URL('./', import.meta.url)),
      transformMode: {
        web: [/\.[jt]sx$/],
      },
    }
  })
)
Download .txt
gitextract_ewk5efrr/

├── .eslintrc.cjs
├── .gitignore
├── .prettierrc.json
├── .vscode/
│   ├── _sfc.code-snippets
│   ├── extensions.json
│   └── settings.json
├── README.md
├── auto-imports.d.ts
├── components.d.ts
├── docs/
│   ├── .vitepress/
│   │   └── config.mts
│   ├── architecture.md
│   ├── development.md
│   ├── editors.md
│   ├── index.md
│   ├── linting.md
│   ├── production.md
│   ├── routing.md
│   ├── state.md
│   ├── tech.md
│   ├── tests.md
│   └── troubleshooting.md
├── e2e/
│   ├── tsconfig.json
│   └── vue.spec.ts
├── env.d.ts
├── index.html
├── package.json
├── playwright.config.ts
├── src/
│   ├── App.vue
│   ├── components/
│   │   ├── BaseButton.spec.ts
│   │   ├── BaseButton.vue
│   │   ├── BaseInputText.spec.ts
│   │   └── BaseInputText.vue
│   ├── composables/
│   │   └── useTheme.ts
│   ├── design/
│   │   ├── _colors.scss
│   │   ├── _durations.scss
│   │   ├── _fonts.scss
│   │   ├── _layers.scss
│   │   ├── _sizes.scss
│   │   ├── _typography.scss
│   │   └── index.scss
│   ├── layouts/
│   │   └── AppLayout.vue
│   ├── main.ts
│   ├── pages/
│   │   ├── about.vue
│   │   └── index.vue
│   ├── router/
│   │   ├── index.ts
│   │   └── routes.ts
│   ├── stores/
│   │   └── counter.ts
│   └── types.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
├── tsconfig.vitest.json
├── vite.config.ts
└── vitest.config.ts
Download .txt
SYMBOL INDEX (6 symbols across 4 files)

FILE: components.d.ts
  type GlobalComponents (line 9) | interface GlobalComponents {

FILE: src/composables/useTheme.ts
  type Theme (line 3) | type Theme = 'dark' | 'light'
  function useTheme (line 5) | function useTheme() {

FILE: src/stores/counter.ts
  function increment (line 7) | function increment() {

FILE: src/types.ts
  type MaybeRef (line 6) | type MaybeRef<T> = T | Ref<T>
  type MaybeRefOrGetter (line 11) | type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T)
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (107K chars).
[
  {
    "path": ".eslintrc.cjs",
    "chars": 333,
    "preview": "/* eslint-env node */\nrequire('@rushstack/eslint-patch/modern-module-resolution')\n\nmodule.exports = {\n  root: true,\n  'e"
  },
  {
    "path": ".gitignore",
    "chars": 342,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Stor"
  },
  {
    "path": ".prettierrc.json",
    "chars": 163,
    "preview": "{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"pr"
  },
  {
    "path": ".vscode/_sfc.code-snippets",
    "chars": 1907,
    "preview": "{\n  \"veb-sfc\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"sfc\",\n    \"body\": [\n      \"<script setup lang='ts'>\",\n      \"${0}\",\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 739,
    "preview": "{\n  \"recommendations\": [\n    // Vue - Official Extension\n    // https://github.com/vuejs/language-tools\n    \"vue.volar\","
  },
  {
    "path": ".vscode/settings.json",
    "chars": 1122,
    "preview": "{\n  // ======\n  // Spacing\n  // ======\n\n  \"editor.insertSpaces\": true,\n  \"editor.tabSize\": 2,\n  \"editor.trimAutoWhitespa"
  },
  {
    "path": "README.md",
    "chars": 2624,
    "preview": "# Vue Enterprise Boilerplate v3 (alpha)\n\nThis repo is currently in active development and considered in alpha release.\n\n"
  },
  {
    "path": "auto-imports.d.ts",
    "chars": 19429,
    "preview": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin"
  },
  {
    "path": "components.d.ts",
    "chars": 647,
    "preview": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/"
  },
  {
    "path": "docs/.vitepress/config.mts",
    "chars": 735,
    "preview": "import { defineConfig } from 'vitepress'\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n "
  },
  {
    "path": "docs/architecture.md",
    "chars": 4157,
    "preview": "# Architecture\n\n- [Architecture](#architecture)\n  - [`.vscode`](#vscode)\n  - [`docs`](#docs)\n  - [`e2e`](#e2e)\n  - [`pub"
  },
  {
    "path": "docs/development.md",
    "chars": 4305,
    "preview": "# Setup and Development\n\n- [Setup and Development](#setup-and-development)\n  - [First-time setup](#first-time-setup)\n  -"
  },
  {
    "path": "docs/editors.md",
    "chars": 2899,
    "preview": "# Editor Integration\n\n- [Visual Studio Code](#visual-studio-code)\n  - [Recommended Extensions](#recommended-extensions)\n"
  },
  {
    "path": "docs/index.md",
    "chars": 2013,
    "preview": "---\nlayout: home\n\nhero:\n  name: Vue Enterprise Boilerplate\n  text: Production-ready Vue 3 architecture\n  tagline: An opi"
  },
  {
    "path": "docs/linting.md",
    "chars": 3093,
    "preview": "# Linting & Formatting\n\n- [Overview](#overview)\n- [Languages](#languages)\n- [Scripts](#scripts)\n  - [Terminal](#terminal"
  },
  {
    "path": "docs/production.md",
    "chars": 2038,
    "preview": "# Building and Deploying to Production\n\n- [Building for Production](#building-for-production)\n- [Previewing the Build](#"
  },
  {
    "path": "docs/routing.md",
    "chars": 3376,
    "preview": "# Routing, Layouts, and Pages\n\n- [Overview](#overview)\n- [Route Configuration](#route-configuration)\n- [Layouts](#layout"
  },
  {
    "path": "docs/state.md",
    "chars": 3890,
    "preview": "# State Management\n\n- [Overview](#overview)\n- [Defining Stores](#defining-stores)\n- [Using Stores](#using-stores)\n- [Sto"
  },
  {
    "path": "docs/tech.md",
    "chars": 8780,
    "preview": "# Languages and Technologies\n\n- [TypeScript](#typescript)\n  - [Why TypeScript](#why-typescript)\n  - [Configuration](#con"
  },
  {
    "path": "docs/tests.md",
    "chars": 7085,
    "preview": "# Testing\n\n- [Overview](#overview)\n- [Unit Tests with Vitest](#unit-tests-with-vitest)\n  - [Running Unit Tests](#running"
  },
  {
    "path": "docs/troubleshooting.md",
    "chars": 4364,
    "preview": "# Troubleshooting\n\nCommon issues and solutions for development.\n\n- [Script Errors](#script-errors)\n- [VS Code Issues](#v"
  },
  {
    "path": "e2e/tsconfig.json",
    "chars": 75,
    "preview": "{\n  \"extends\": \"@tsconfig/node20/tsconfig.json\",\n  \"include\": [\"./**/*\"]\n}\n"
  },
  {
    "path": "e2e/vue.spec.ts",
    "chars": 257,
    "preview": "import { test, expect } from '@playwright/test'\n\n// See here how to get started:\n// https://playwright.dev/docs/intro\nte"
  },
  {
    "path": "env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "index.html",
    "chars": 338,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <"
  },
  {
    "path": "package.json",
    "chars": 1600,
    "preview": "{\n  \"name\": \"vue-enterprise-boilerplate\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n   "
  },
  {
    "path": "playwright.config.ts",
    "chars": 2905,
    "preview": "import process from 'node:process'\nimport { defineConfig, devices } from '@playwright/test'\n\n/**\n * Read environment var"
  },
  {
    "path": "src/App.vue",
    "chars": 104,
    "preview": "<script setup lang=\"ts\"></script>\n\n<template>\n  <RouterView />\n</template>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "src/components/BaseButton.spec.ts",
    "chars": 671,
    "preview": "import { describe, it, expect } from 'vitest'\nimport { shallowMount } from '@vue/test-utils'\nimport BaseButton from './B"
  },
  {
    "path": "src/components/BaseButton.vue",
    "chars": 389,
    "preview": "<template>\n  <button :class=\"$style.button\">\n    <slot>Submit</slot>\n  </button>\n</template>\n\n<style lang=\"scss\" module>"
  },
  {
    "path": "src/components/BaseInputText.spec.ts",
    "chars": 1426,
    "preview": "import { describe, it, expect, vi } from 'vitest'\nimport { shallowMount, mount } from '@vue/test-utils'\nimport BaseInput"
  },
  {
    "path": "src/components/BaseInputText.vue",
    "chars": 743,
    "preview": "<script setup lang=\"ts\">\ndefineProps({\n  type: {\n    type: String,\n    default: 'text',\n    // Only allow types that ess"
  },
  {
    "path": "src/composables/useTheme.ts",
    "chars": 281,
    "preview": "import { useColorMode } from '@vueuse/core'\n\ntype Theme = 'dark' | 'light'\n\nfunction useTheme() {\n  const themePreferenc"
  },
  {
    "path": "src/design/_colors.scss",
    "chars": 377,
    "preview": "// CONTENT\n$color-body-bg: #f9f7f5;\n$color-text: #444;\n$color-heading-text: #35495e;\n\n// LINKS\n$color-link-text: #39a275"
  },
  {
    "path": "src/design/_durations.scss",
    "chars": 33,
    "preview": "$duration-animation-base: 300ms;\n"
  },
  {
    "path": "src/design/_fonts.scss",
    "chars": 568,
    "preview": "$system-default-font-family: -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica', 'Arial',\n  sans-serif, 'Apple"
  },
  {
    "path": "src/design/_layers.scss",
    "chars": 160,
    "preview": "$layer-negative-z-index: -1;\n$layer-page-z-index: 1;\n$layer-dropdown-z-index: 2;\n$layer-modal-z-index: 3;\n$layer-popover"
  },
  {
    "path": "src/design/_sizes.scss",
    "chars": 608,
    "preview": "// GRID\n$size-grid-padding: 1.3rem;\n\n// CONTENT\n$size-content-width-max: 50rem;\n$size-content-width-min: 25rem;\n\n// INPU"
  },
  {
    "path": "src/design/_typography.scss",
    "chars": 10002,
    "preview": "// Interpolate v1.0\n\n// This mixin generates CSS for interpolation of length properties.\n// It has 5 required values, in"
  },
  {
    "path": "src/design/index.scss",
    "chars": 558,
    "preview": "@import 'colors';\n@import 'durations';\n@import 'fonts';\n@import 'layers';\n@import 'sizes';\n@import 'typography';\n\n:expor"
  },
  {
    "path": "src/layouts/AppLayout.vue",
    "chars": 124,
    "preview": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <slot />\n  </div>\n</template>\n\n<style lang=\"scss\" scoped></sty"
  },
  {
    "path": "src/main.ts",
    "chars": 470,
    "preview": "import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport { createHead } from '@unhead/vue'\n\nimport App"
  },
  {
    "path": "src/pages/about.vue",
    "chars": 433,
    "preview": "<script setup lang=\"ts\">\nuseHead({\n  title: 'About - Vue Enterprise Boilerplate',\n  meta: [\n    {\n      name: 'descripti"
  },
  {
    "path": "src/pages/index.vue",
    "chars": 408,
    "preview": "<script setup lang=\"ts\">\nuseHead({\n  title: 'Home - Vue Enterprise Boilerplate',\n  meta: [\n    {\n      name: 'descriptio"
  },
  {
    "path": "src/router/index.ts",
    "chars": 211,
    "preview": "import { createRouter, createWebHistory } from 'vue-router'\nimport routes from './routes'\n\nconst router = createRouter({"
  },
  {
    "path": "src/router/routes.ts",
    "chars": 367,
    "preview": "export default [\n  {\n    path: '/',\n    name: 'home',\n    component: () => import('@/pages/index.vue')\n  },\n  {\n    path"
  },
  {
    "path": "src/stores/counter.ts",
    "chars": 306,
    "preview": "import { ref, computed } from 'vue'\nimport { defineStore } from 'pinia'\n\nexport const useCounterStore = defineStore('cou"
  },
  {
    "path": "src/types.ts",
    "chars": 237,
    "preview": "import type { Ref } from 'vue'\n\n/**\n * It could be a ref, or a plain value\n */\nexport type MaybeRef<T> = T | Ref<T>\n\n/**"
  },
  {
    "path": "tsconfig.app.json",
    "chars": 299,
    "preview": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n  \"include\": [\"auto-imports.d.ts\", \"component.d.ts\", \"env.d.ts\", \"src/"
  },
  {
    "path": "tsconfig.json",
    "chars": 191,
    "preview": "{\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    },\n    {\n      \"path\": \"./tsconfig.app"
  },
  {
    "path": "tsconfig.node.json",
    "chars": 241,
    "preview": "{\n  \"extends\": \"@tsconfig/node18/tsconfig.json\",\n  \"include\": [\"vite.config.*\", \"vitest.config.*\", \"cypress.config.*\", \""
  },
  {
    "path": "tsconfig.vitest.json",
    "chars": 153,
    "preview": "{\n  \"extends\": \"./tsconfig.app.json\",\n  \"exclude\": [],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"lib\": [],\n    "
  },
  {
    "path": "vite.config.ts",
    "chars": 825,
    "preview": "import { fileURLToPath, URL } from 'node:url'\n\nimport { defineConfig } from 'vite'\n\nimport vue from '@vitejs/plugin-vue'"
  },
  {
    "path": "vitest.config.ts",
    "chars": 461,
    "preview": "import { fileURLToPath } from 'node:url'\nimport { mergeConfig } from 'vite'\nimport { configDefaults, defineConfig } from"
  }
]

About this extraction

This page contains the full source code of the bencodezen/vue-enterprise-boilerplate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (97.6 KB), approximately 27.2k tokens, and a symbol index with 6 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!