[
  {
    "path": ".eslintrc.cjs",
    "content": "/* eslint-env node */\nrequire('@rushstack/eslint-patch/modern-module-resolution')\n\nmodule.exports = {\n  root: true,\n  'extends': [\n    'plugin:vue/vue3-essential',\n    'eslint:recommended',\n    '@vue/eslint-config-typescript',\n    '@vue/eslint-config-prettier/skip-formatting'\n  ],\n  parserOptions: {\n    ecmaVersion: 'latest'\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n# Editor directories and files\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Playwright directories\ntest-results/\nplaywright-report/\n\n# VitePress\ndocs/.vitepress/cache\ndocs/.vitepress/dist\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"trailingComma\": \"none\"\n}"
  },
  {
    "path": ".vscode/_sfc.code-snippets",
    "content": "{\n  \"veb-sfc\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"sfc\",\n    \"body\": [\n      \"<script setup lang='ts'>\",\n      \"${0}\",\n      \"</script>\",\n      \"\",\n      \"<template>\",\n      \"\\t\",\n      \"</template>\",\n      \"\",\n      \"<style lang=\\\"scss\\\" scoped>\",\n      \"\",\n      \"</style>\"\n    ],\n    \"description\": \"Single File Component (Comp API + TS) from VEB\"\n  },\n  \"veb-sfc-options\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"sfc-options\",\n    \"body\": [\n      \"<script lang='ts'>\",\n      \"import { defineComponent } from 'vue'\",\n      \"\",\n      \"export default defineComponent({\",\n      \"\\t${0}\",\n      \"})\",\n      \"</script>\",\n      \"\",\n      \"<template>\",\n      \"\\t\",\n      \"</template>\",\n      \"\",\n      \"<style lang=\\\"scss\\\" scoped>\",\n      \"\",\n      \"</style>\"\n    ],\n    \"description\": \"Single File Component (Options API + TS) from VEB\"\n  },\n  \"veb-script\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"script\",\n    \"body\": [\"<script setup lang='ts'>\", \"${0}\", \"</script>\"],\n    \"description\": \"Script block (Comp API + TS)\"\n  },\n  \"veb-script-options\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"script-options\",\n    \"body\": [\n      \"<script lang='ts'>\",\n      \"import { defineComponent } from 'vue'\",\n      \"\",\n      \"export default defineComponent({\",\n      \"\\t${0}\",\n      \"})\",\n      \"</script>\"\n    ],\n    \"description\": \"Script block (Options API + TS)\"\n  },\n  \"veb-template\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"template\",\n    \"body\": [\"<template>\", \"\\t${0}\", \"</template>\"],\n    \"description\": \"Template block\"\n  },\n  \"veb-style\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"style\",\n    \"body\": [\"<style lang=\\\"scss\\\" scoped>\", \"${0}\", \"</style>\"],\n    \"description\": \"Scoped CSS + Sass styles block from VEB\"\n  },\n  \"veb-style-module\": {\n    \"scope\": \"vue\",\n    \"prefix\": \"style-module\",\n    \"body\": [\"<style lang=\\\"scss\\\" module>\", \"${0}\", \"</style>\"],\n    \"description\": \"CSS Module + Sass styles block from VEB\"\n  }\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    // Vue - Official Extension\n    // https://github.com/vuejs/language-tools\n    \"vue.volar\",\n\n    // Format-on-save with Prettier\n    // https://github.com/prettier/prettier-vscode\n    \"esbenp.prettier-vscode\",\n\n    // Playwright Test - Official Extension\n    // https://github.com/microsoft/playwright-vscode\n    \"ms-playwright.playwright\",\n\n    // Better Comments\n    // https://github.com/aaron-bond/better-comments\n    \"aaron-bond.better-comments\",\n\n    // Path Intellisense\n    // https://github.com/ChristianKohler/PathIntellisense\n    \"christian-kohler.path-intellisense\",\n\n    // Peacock - Workspace Color Customizer\n    // https://github.com/johnpapa/vscode-peacock\n    \"johnpapa.vscode-peacock\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  // ======\n  // Spacing\n  // ======\n\n  \"editor.insertSpaces\": true,\n  \"editor.tabSize\": 2,\n  \"editor.trimAutoWhitespace\": true,\n  \"files.trimTrailingWhitespace\": true,\n  \"files.eol\": \"\\n\",\n  \"files.insertFinalNewline\": true,\n  \"files.trimFinalNewlines\": true,\n\n  // ======\n  // Files\n  // ======\n\n  \"files.exclude\": {\n    \"**/*.log\": true,\n    \"**/*.log*\": true,\n    \"**/dist\": true,\n    \"**/coverage\": true\n  },\n\n  // ======\n  // Event Triggers\n  // ======\n\n  \"editor.formatOnSave\": true,\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\",\n    \"source.fixAll.stylelint\": \"explicit\",\n    \"source.fixAll.markdownlint\": \"explicit\"\n  },\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"vue\", \"vue-html\", \"html\"],\n\n  // ======\n  // HTML\n  // ======\n\n  \"emmet.triggerExpansionOnTab\": true,\n\n  // ======\n  // CSS\n  // ======\n\n  \"stylelint.enable\": true,\n  \"css.validate\": false,\n  \"scss.validate\": false,\n\n  // ======\n  // MARKDOWN\n  // ======\n\n  \"[markdown]\": {\n    \"editor.wordWrap\": \"wordWrapColumn\",\n    \"editor.wordWrapColumn\": 80\n  }\n}\n"
  },
  {
    "path": "README.md",
    "content": "# Vue Enterprise Boilerplate v3 (alpha)\n\nThis repo is currently in active development and considered in alpha release.\n\n> 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).\n\n🎩 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).\n\n## Recommended IDE Setup\n\n[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).\n\n## Type Support for `.vue` Imports in TS\n\nTypeScript 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.\n\nIf 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:\n\n1. Disable the built-in TypeScript Extension\n   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette\n   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`\n2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.\n\n## Project Setup\n\n```sh\nnpm install\n```\n\n### Compile and Hot-Reload for Development\n\n```sh\nnpm run dev\n```\n\n### Type-Check, Compile and Minify for Production\n\n```sh\nnpm run build\n```\n\n### Run Unit Tests with [Vitest](https://vitest.dev/)\n\n```sh\nnpm run test:unit\n```\n\n### Run End-to-End Tests with [Playwright](https://playwright.dev)\n\n```sh\n# Install browsers for the first run\nnpx playwright install\n\n# When testing on CI, must build the project first\nnpm run build\n\n# Runs the end-to-end tests\nnpm run test:e2e\n# Runs the tests only on Chromium\nnpm run test:e2e -- --project=chromium\n# Runs the tests of a specific file\nnpm run test:e2e -- tests/example.spec.ts\n# Runs the tests in debug mode\nnpm run test:e2e -- --debug\n```\n\n### Lint with [ESLint](https://eslint.org/)\n\n```sh\nnpm run lint\n```\n"
  },
  {
    "path": "auto-imports.d.ts",
    "content": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin-auto-import\nexport {}\ndeclare global {\n  const EffectScope: typeof import('vue')['EffectScope']\n  const asyncComputed: typeof import('@vueuse/core')['asyncComputed']\n  const autoResetRef: typeof import('@vueuse/core')['autoResetRef']\n  const computed: typeof import('vue')['computed']\n  const computedAsync: typeof import('@vueuse/core')['computedAsync']\n  const computedEager: typeof import('@vueuse/core')['computedEager']\n  const computedInject: typeof import('@vueuse/core')['computedInject']\n  const computedWithControl: typeof import('@vueuse/core')['computedWithControl']\n  const controlledComputed: typeof import('@vueuse/core')['controlledComputed']\n  const controlledRef: typeof import('@vueuse/core')['controlledRef']\n  const createApp: typeof import('vue')['createApp']\n  const createEventHook: typeof import('@vueuse/core')['createEventHook']\n  const createGlobalState: typeof import('@vueuse/core')['createGlobalState']\n  const createInjectionState: typeof import('@vueuse/core')['createInjectionState']\n  const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']\n  const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']\n  const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']\n  const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']\n  const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']\n  const customRef: typeof import('vue')['customRef']\n  const debouncedRef: typeof import('@vueuse/core')['debouncedRef']\n  const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']\n  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']\n  const defineComponent: typeof import('vue')['defineComponent']\n  const eagerComputed: typeof import('@vueuse/core')['eagerComputed']\n  const effectScope: typeof import('vue')['effectScope']\n  const extendRef: typeof import('@vueuse/core')['extendRef']\n  const getCurrentInstance: typeof import('vue')['getCurrentInstance']\n  const getCurrentScope: typeof import('vue')['getCurrentScope']\n  const h: typeof import('vue')['h']\n  const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']\n  const inject: typeof import('vue')['inject']\n  const injectLocal: typeof import('@vueuse/core')['injectLocal']\n  const isDefined: typeof import('@vueuse/core')['isDefined']\n  const isProxy: typeof import('vue')['isProxy']\n  const isReactive: typeof import('vue')['isReactive']\n  const isReadonly: typeof import('vue')['isReadonly']\n  const isRef: typeof import('vue')['isRef']\n  const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']\n  const markRaw: typeof import('vue')['markRaw']\n  const nextTick: typeof import('vue')['nextTick']\n  const onActivated: typeof import('vue')['onActivated']\n  const onBeforeMount: typeof import('vue')['onBeforeMount']\n  const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']\n  const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']\n  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']\n  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']\n  const onClickOutside: typeof import('@vueuse/core')['onClickOutside']\n  const onDeactivated: typeof import('vue')['onDeactivated']\n  const onErrorCaptured: typeof import('vue')['onErrorCaptured']\n  const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']\n  const onLongPress: typeof import('@vueuse/core')['onLongPress']\n  const onMounted: typeof import('vue')['onMounted']\n  const onRenderTracked: typeof import('vue')['onRenderTracked']\n  const onRenderTriggered: typeof import('vue')['onRenderTriggered']\n  const onScopeDispose: typeof import('vue')['onScopeDispose']\n  const onServerPrefetch: typeof import('vue')['onServerPrefetch']\n  const onStartTyping: typeof import('@vueuse/core')['onStartTyping']\n  const onUnmounted: typeof import('vue')['onUnmounted']\n  const onUpdated: typeof import('vue')['onUpdated']\n  const pausableWatch: typeof import('@vueuse/core')['pausableWatch']\n  const provide: typeof import('vue')['provide']\n  const provideLocal: typeof import('@vueuse/core')['provideLocal']\n  const reactify: typeof import('@vueuse/core')['reactify']\n  const reactifyObject: typeof import('@vueuse/core')['reactifyObject']\n  const reactive: typeof import('vue')['reactive']\n  const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']\n  const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']\n  const reactivePick: typeof import('@vueuse/core')['reactivePick']\n  const readonly: typeof import('vue')['readonly']\n  const ref: typeof import('vue')['ref']\n  const refAutoReset: typeof import('@vueuse/core')['refAutoReset']\n  const refDebounced: typeof import('@vueuse/core')['refDebounced']\n  const refDefault: typeof import('@vueuse/core')['refDefault']\n  const refThrottled: typeof import('@vueuse/core')['refThrottled']\n  const refWithControl: typeof import('@vueuse/core')['refWithControl']\n  const resolveComponent: typeof import('vue')['resolveComponent']\n  const resolveRef: typeof import('@vueuse/core')['resolveRef']\n  const resolveUnref: typeof import('@vueuse/core')['resolveUnref']\n  const shallowReactive: typeof import('vue')['shallowReactive']\n  const shallowReadonly: typeof import('vue')['shallowReadonly']\n  const shallowRef: typeof import('vue')['shallowRef']\n  const syncRef: typeof import('@vueuse/core')['syncRef']\n  const syncRefs: typeof import('@vueuse/core')['syncRefs']\n  const templateRef: typeof import('@vueuse/core')['templateRef']\n  const throttledRef: typeof import('@vueuse/core')['throttledRef']\n  const throttledWatch: typeof import('@vueuse/core')['throttledWatch']\n  const toRaw: typeof import('vue')['toRaw']\n  const toReactive: typeof import('@vueuse/core')['toReactive']\n  const toRef: typeof import('vue')['toRef']\n  const toRefs: typeof import('vue')['toRefs']\n  const toValue: typeof import('vue')['toValue']\n  const triggerRef: typeof import('vue')['triggerRef']\n  const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']\n  const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']\n  const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']\n  const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']\n  const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']\n  const unref: typeof import('vue')['unref']\n  const unrefElement: typeof import('@vueuse/core')['unrefElement']\n  const until: typeof import('@vueuse/core')['until']\n  const useActiveElement: typeof import('@vueuse/core')['useActiveElement']\n  const useAnimate: typeof import('@vueuse/core')['useAnimate']\n  const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']\n  const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']\n  const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']\n  const useArrayFind: typeof import('@vueuse/core')['useArrayFind']\n  const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']\n  const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']\n  const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']\n  const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']\n  const useArrayMap: typeof import('@vueuse/core')['useArrayMap']\n  const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']\n  const useArraySome: typeof import('@vueuse/core')['useArraySome']\n  const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']\n  const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']\n  const useAsyncState: typeof import('@vueuse/core')['useAsyncState']\n  const useAttrs: typeof import('vue')['useAttrs']\n  const useBase64: typeof import('@vueuse/core')['useBase64']\n  const useBattery: typeof import('@vueuse/core')['useBattery']\n  const useBluetooth: typeof import('@vueuse/core')['useBluetooth']\n  const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']\n  const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']\n  const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']\n  const useCached: typeof import('@vueuse/core')['useCached']\n  const useClipboard: typeof import('@vueuse/core')['useClipboard']\n  const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']\n  const useCloned: typeof import('@vueuse/core')['useCloned']\n  const useColorMode: typeof import('@vueuse/core')['useColorMode']\n  const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']\n  const useCounter: typeof import('@vueuse/core')['useCounter']\n  const useCssModule: typeof import('vue')['useCssModule']\n  const useCssVar: typeof import('@vueuse/core')['useCssVar']\n  const useCssVars: typeof import('vue')['useCssVars']\n  const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']\n  const useCycleList: typeof import('@vueuse/core')['useCycleList']\n  const useDark: typeof import('@vueuse/core')['useDark']\n  const useDateFormat: typeof import('@vueuse/core')['useDateFormat']\n  const useDebounce: typeof import('@vueuse/core')['useDebounce']\n  const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']\n  const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']\n  const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']\n  const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']\n  const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']\n  const useDevicesList: typeof import('@vueuse/core')['useDevicesList']\n  const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']\n  const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']\n  const useDraggable: typeof import('@vueuse/core')['useDraggable']\n  const useDropZone: typeof import('@vueuse/core')['useDropZone']\n  const useElementBounding: typeof import('@vueuse/core')['useElementBounding']\n  const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']\n  const useElementHover: typeof import('@vueuse/core')['useElementHover']\n  const useElementSize: typeof import('@vueuse/core')['useElementSize']\n  const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']\n  const useEventBus: typeof import('@vueuse/core')['useEventBus']\n  const useEventListener: typeof import('@vueuse/core')['useEventListener']\n  const useEventSource: typeof import('@vueuse/core')['useEventSource']\n  const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']\n  const useFavicon: typeof import('@vueuse/core')['useFavicon']\n  const useFetch: typeof import('@vueuse/core')['useFetch']\n  const useFileDialog: typeof import('@vueuse/core')['useFileDialog']\n  const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']\n  const useFocus: typeof import('@vueuse/core')['useFocus']\n  const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']\n  const useFps: typeof import('@vueuse/core')['useFps']\n  const useFullscreen: typeof import('@vueuse/core')['useFullscreen']\n  const useGamepad: typeof import('@vueuse/core')['useGamepad']\n  const useGeolocation: typeof import('@vueuse/core')['useGeolocation']\n  const useHead: typeof import('@unhead/vue')['useHead']\n  const useIdle: typeof import('@vueuse/core')['useIdle']\n  const useImage: typeof import('@vueuse/core')['useImage']\n  const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']\n  const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']\n  const useInterval: typeof import('@vueuse/core')['useInterval']\n  const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']\n  const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']\n  const useLastChanged: typeof import('@vueuse/core')['useLastChanged']\n  const useLink: typeof import('vue-router')['useLink']\n  const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']\n  const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']\n  const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']\n  const useMediaControls: typeof import('@vueuse/core')['useMediaControls']\n  const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']\n  const useMemoize: typeof import('@vueuse/core')['useMemoize']\n  const useMemory: typeof import('@vueuse/core')['useMemory']\n  const useMounted: typeof import('@vueuse/core')['useMounted']\n  const useMouse: typeof import('@vueuse/core')['useMouse']\n  const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']\n  const useMousePressed: typeof import('@vueuse/core')['useMousePressed']\n  const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']\n  const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']\n  const useNetwork: typeof import('@vueuse/core')['useNetwork']\n  const useNow: typeof import('@vueuse/core')['useNow']\n  const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']\n  const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']\n  const useOnline: typeof import('@vueuse/core')['useOnline']\n  const usePageLeave: typeof import('@vueuse/core')['usePageLeave']\n  const useParallax: typeof import('@vueuse/core')['useParallax']\n  const useParentElement: typeof import('@vueuse/core')['useParentElement']\n  const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']\n  const usePermission: typeof import('@vueuse/core')['usePermission']\n  const usePointer: typeof import('@vueuse/core')['usePointer']\n  const usePointerLock: typeof import('@vueuse/core')['usePointerLock']\n  const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']\n  const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']\n  const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']\n  const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']\n  const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']\n  const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']\n  const usePrevious: typeof import('@vueuse/core')['usePrevious']\n  const useRafFn: typeof import('@vueuse/core')['useRafFn']\n  const useRefHistory: typeof import('@vueuse/core')['useRefHistory']\n  const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']\n  const useRoute: typeof import('vue-router')['useRoute']\n  const useRouter: typeof import('vue-router')['useRouter']\n  const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']\n  const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']\n  const useScriptTag: typeof import('@vueuse/core')['useScriptTag']\n  const useScroll: typeof import('@vueuse/core')['useScroll']\n  const useScrollLock: typeof import('@vueuse/core')['useScrollLock']\n  const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']\n  const useShare: typeof import('@vueuse/core')['useShare']\n  const useSlots: typeof import('vue')['useSlots']\n  const useSorted: typeof import('@vueuse/core')['useSorted']\n  const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']\n  const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']\n  const useStepper: typeof import('@vueuse/core')['useStepper']\n  const useStorage: typeof import('@vueuse/core')['useStorage']\n  const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']\n  const useStyleTag: typeof import('@vueuse/core')['useStyleTag']\n  const useSupported: typeof import('@vueuse/core')['useSupported']\n  const useSwipe: typeof import('@vueuse/core')['useSwipe']\n  const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']\n  const useTextDirection: typeof import('@vueuse/core')['useTextDirection']\n  const useTextSelection: typeof import('@vueuse/core')['useTextSelection']\n  const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']\n  const useThrottle: typeof import('@vueuse/core')['useThrottle']\n  const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']\n  const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']\n  const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']\n  const useTimeout: typeof import('@vueuse/core')['useTimeout']\n  const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']\n  const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']\n  const useTimestamp: typeof import('@vueuse/core')['useTimestamp']\n  const useTitle: typeof import('@vueuse/core')['useTitle']\n  const useToNumber: typeof import('@vueuse/core')['useToNumber']\n  const useToString: typeof import('@vueuse/core')['useToString']\n  const useToggle: typeof import('@vueuse/core')['useToggle']\n  const useTransition: typeof import('@vueuse/core')['useTransition']\n  const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']\n  const useUserMedia: typeof import('@vueuse/core')['useUserMedia']\n  const useVModel: typeof import('@vueuse/core')['useVModel']\n  const useVModels: typeof import('@vueuse/core')['useVModels']\n  const useVibrate: typeof import('@vueuse/core')['useVibrate']\n  const useVirtualList: typeof import('@vueuse/core')['useVirtualList']\n  const useWakeLock: typeof import('@vueuse/core')['useWakeLock']\n  const useWebNotification: typeof import('@vueuse/core')['useWebNotification']\n  const useWebSocket: typeof import('@vueuse/core')['useWebSocket']\n  const useWebWorker: typeof import('@vueuse/core')['useWebWorker']\n  const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']\n  const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']\n  const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']\n  const useWindowSize: typeof import('@vueuse/core')['useWindowSize']\n  const watch: typeof import('vue')['watch']\n  const watchArray: typeof import('@vueuse/core')['watchArray']\n  const watchAtMost: typeof import('@vueuse/core')['watchAtMost']\n  const watchDebounced: typeof import('@vueuse/core')['watchDebounced']\n  const watchDeep: typeof import('@vueuse/core')['watchDeep']\n  const watchEffect: typeof import('vue')['watchEffect']\n  const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']\n  const watchImmediate: typeof import('@vueuse/core')['watchImmediate']\n  const watchOnce: typeof import('@vueuse/core')['watchOnce']\n  const watchPausable: typeof import('@vueuse/core')['watchPausable']\n  const watchPostEffect: typeof import('vue')['watchPostEffect']\n  const watchSyncEffect: typeof import('vue')['watchSyncEffect']\n  const watchThrottled: typeof import('@vueuse/core')['watchThrottled']\n  const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']\n  const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']\n  const whenever: typeof import('@vueuse/core')['whenever']\n}\n// for type re-export\ndeclare global {\n  // @ts-ignore\n  export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'\n  import('vue')\n}\n"
  },
  {
    "path": "components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    AppLayout: typeof import('./src/layouts/AppLayout.vue')['default']\n    BaseButton: typeof import('./src/components/BaseButton.vue')['default']\n    BaseInputText: typeof import('./src/components/BaseInputText.vue')['default']\n    BaseLink: typeof import('./src/components/BaseLink.vue')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n  }\n}\n"
  },
  {
    "path": "docs/.vitepress/config.mts",
    "content": "import { defineConfig } from 'vitepress'\n\n// https://vitepress.dev/reference/site-config\nexport default defineConfig({\n  title: \"Vue Enterprise Boilerplate\",\n  description: \"Documentation for Vue Enterprise Boilerplate\",\n  themeConfig: {\n    // https://vitepress.dev/reference/default-theme-config\n    nav: [\n      { text: 'Home', link: '/' },\n      { text: 'Examples', link: '/markdown-examples' }\n    ],\n\n    sidebar: [\n      {\n        text: 'Examples',\n        items: [\n          { text: 'Markdown Examples', link: '/markdown-examples' },\n          { text: 'Runtime API Examples', link: '/api-examples' }\n        ]\n      }\n    ],\n\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/vuejs/vitepress' }\n    ]\n  }\n})\n"
  },
  {
    "path": "docs/architecture.md",
    "content": "# Architecture\n\n- [Architecture](#architecture)\n  - [`.vscode`](#vscode)\n  - [`docs`](#docs)\n  - [`e2e`](#e2e)\n  - [`public`](#public)\n  - [`src`](#src)\n    - [`assets`](#assets)\n    - [`components`](#components)\n    - [`composables`](#composables)\n    - [`design`](#design)\n    - [`layouts`](#layouts)\n    - [`pages`](#pages)\n    - [`router`](#router)\n    - [`stores`](#stores)\n    - [`App.vue`](#appvue)\n    - [`main.ts`](#maints)\n    - [`types.ts`](#typests)\n  - [Configuration Files](#configuration-files)\n\n## `.vscode`\n\nSettings and extensions specific to this project, for Visual Studio Code. See [the editors doc](editors.md) for more.\n\n## `docs`\n\nYou found me! Documentation is powered by [VitePress](https://vitepress.dev/) and can be built as a static site.\n\n```bash\n# Start docs dev server\nnpm run docs:dev\n\n# Build docs for production\nnpm run docs:build\n```\n\n### `.vitepress`\n\nVitePress configuration including sidebar navigation, theme settings, and build options.\n\n## `e2e`\n\nEnd-to-end tests using [Playwright](https://playwright.dev/). See [the tests doc](tests.md) for more.\n\n## `public`\n\nStatic assets that will be served directly without processing. Files here are copied to the build output as-is.\n\n## `src`\n\nWhere we keep all our source files.\n\n### `assets`\n\nStatic assets like images and fonts that will be processed by Vite. Assets here can be imported in components and will be optimized during build.\n\n### `components`\n\nReusable Vue components, including [global base components](development.md#base-components). Components are auto-registered via `unplugin-vue-components`.\n\nUnit tests are colocated with components using the `.spec.ts` extension (e.g., `BaseButton.spec.ts`).\n\n### `composables`\n\nReusable composition functions that encapsulate stateful logic. These follow the `use*` naming convention (e.g., `useTheme.ts`).\n\nComposables are the Vue 3 replacement for mixins, providing better TypeScript support and explicit dependencies.\n\n### `design`\n\nSCSS design system variables and utilities:\n\n- `_colors.scss` - Color palette\n- `_typography.scss` - Font scales and text styles\n- `_sizes.scss` - Spacing and sizing\n- `_fonts.scss` - Font imports\n- `_durations.scss` - Animation timing\n- `_layers.scss` - Z-index management\n- `index.scss` - Central import point\n\nSee [the tech doc](tech.md#design-variables) for more.\n\n### `layouts`\n\nLayout components that wrap pages with common structure (navigation, footer, etc.). Layouts receive page content via slots.\n\n### `pages`\n\nPage components that map to routes. Each file in this directory represents a distinct route in the application.\n\n### `router`\n\nVue Router configuration:\n\n- `index.ts` - Router instance creation and global guards\n- `routes.ts` - Route definitions with lazy-loaded pages\n\nSee [the routing doc](routing.md) for more.\n\n### `stores`\n\n[Pinia](https://pinia.vuejs.org/) stores for global state management. Each store is a separate file following the composition API pattern.\n\nSee [the state doc](state.md) for more.\n\n### `App.vue`\n\nThe root Vue component that renders the router view. This is typically the only component to contain global CSS.\n\n### `main.ts`\n\nThe entry point to our app, where we create the Vue application instance, register plugins (Pinia, Router, Unhead), and mount to the DOM.\n\n### `types.ts`\n\nShared TypeScript type definitions and utility types used across the application.\n\n## Configuration Files\n\n| File | Purpose |\n|------|---------|\n| `vite.config.ts` | Vite build configuration, plugins, and dev server |\n| `tsconfig.json` | Root TypeScript configuration |\n| `tsconfig.app.json` | TypeScript config for application code |\n| `tsconfig.node.json` | TypeScript config for Node.js files |\n| `tsconfig.vitest.json` | TypeScript config for Vitest tests |\n| `vitest.config.ts` | Unit test configuration |\n| `playwright.config.ts` | E2E test configuration |\n| `.eslintrc.cjs` | ESLint linting rules |\n| `.prettierrc.json` | Prettier formatting rules |\n| `env.d.ts` | Environment variable type declarations |\n| `components.d.ts` | Auto-generated component type declarations |\n| `auto-imports.d.ts` | Auto-generated import type declarations |\n"
  },
  {
    "path": "docs/development.md",
    "content": "# Setup and Development\n\n- [Setup and Development](#setup-and-development)\n  - [First-time setup](#first-time-setup)\n  - [Installation](#installation)\n  - [Dev server](#dev-server)\n    - [Developing with a production API](#developing-with-a-production-api)\n  - [Aliases](#aliases)\n  - [Auto-imports](#auto-imports)\n    - [Base components](#base-components)\n    - [Vue APIs](#vue-apis)\n\n## First-time setup\n\nMake sure you have the following installed:\n\n- [Node](https://nodejs.org/en/) (at least the latest LTS)\n- [pnpm](https://pnpm.io/) (recommended package manager)\n\n### Why pnpm?\n\nThis project recommends pnpm over npm for several reasons:\n\n- **Faster installs** - Uses symlinks and a content-addressable store for efficient caching\n- **Disk space efficient** - Packages are stored once globally and linked, not duplicated per project\n- **Strict dependencies** - Prevents \"phantom dependencies\" where code accidentally imports packages not listed in package.json\n- **Vue ecosystem standard** - Vue, Vite, and Nuxt all use pnpm for development\n\nTo install pnpm:\n\n```bash\n# Using npm\nnpm install -g pnpm\n\n# Or using Corepack (included with Node 16.13+)\ncorepack enable\ncorepack prepare pnpm@latest --activate\n```\n\n## Installation\n\n```bash\n# Install dependencies from package.json\npnpm install\n```\n\n> **Note:** npm and yarn will also work if you prefer, but pnpm is recommended for the reasons above.\n\n## Dev server\n\n```bash\n# Launch the dev server\npnpm dev\n\n# Launch the dev server and automatically open it in\n# your default browser when ready\npnpm dev --open\n```\n\nVite's dev server starts almost instantly and features lightning-fast Hot Module Replacement (HMR).\n\n### Developing with a production API\n\nBy 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:\n\n```bash\n# .env.local\nVITE_API_BASE_URL=http://localhost:3000\n```\n\nOr set the environment variable inline:\n\n```bash\n# Develop against a local backend server\nVITE_API_BASE_URL=http://localhost:3000 pnpm dev\n\n# Develop against a production server\nVITE_API_BASE_URL=https://api.example.com pnpm dev\n```\n\nAccess this in your code via `import.meta.env.VITE_API_BASE_URL`.\n\n## Aliases\n\nPath 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.\n\n| Alias | Path |\n|-------|------|\n| `@` | `src/` |\n\nExample usage:\n\n```typescript\n// Instead of relative paths\nimport { useTheme } from '../../../composables/useTheme'\n\n// Use aliases\nimport { useTheme } from '@/composables/useTheme'\n```\n\nTo add new aliases, update both:\n- `vite.config.ts` - for Vite resolution\n- `tsconfig.app.json` - for TypeScript and IDE support\n\n## Auto-imports\n\nThis 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.\n\n### Base components\n\nComponents in `src/components/` are automatically registered globally. You don't need to import them to use them in templates:\n\n```vue\n<template>\n  <!-- BaseButton is auto-imported from src/components/BaseButton.vue -->\n  <BaseButton>Click me</BaseButton>\n</template>\n\n<script setup lang=\"ts\">\n// No import needed!\n</script>\n```\n\n[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`).\n\n### Vue APIs\n\nCommon Vue APIs are auto-imported, so you don't need to manually import them:\n\n```vue\n<script setup lang=\"ts\">\n// These are auto-imported - no import statement needed\nconst count = ref(0)\nconst doubled = computed(() => count.value * 2)\n\nwatch(count, (newVal) => {\n  console.log('Count changed:', newVal)\n})\n\nonMounted(() => {\n  console.log('Component mounted')\n})\n</script>\n```\n\nAuto-imported APIs include:\n- Vue: `ref`, `computed`, `watch`, `onMounted`, etc.\n- Vue Router: `useRouter`, `useRoute`\n- Pinia: `defineStore`, `storeToRefs`\n- VueUse: Various composables\n\nThe generated type declarations are in `auto-imports.d.ts` and `components.d.ts`.\n"
  },
  {
    "path": "docs/editors.md",
    "content": "# Editor Integration\n\n- [Visual Studio Code](#visual-studio-code)\n  - [Recommended Extensions](#recommended-extensions)\n  - [Workspace Settings](#workspace-settings)\n- [FAQ](#faq)\n\n## Visual Studio Code\n\nThis 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:\n\n- Vue 3 syntax highlighting and intellisense (via Volar)\n- Format-on-save with Prettier\n- Lint-on-save with ESLint and Stylelint\n- Playwright test integration\n- Path autocompletion for imports\n\n### Recommended Extensions\n\nInstall recommended extensions when prompted, or run `Extensions: Show Recommended Extensions` from the command palette.\n\n| Extension | Purpose |\n|-----------|---------|\n| [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) | Vue 3 language support (syntax, intellisense, type checking) |\n| [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | Code formatting |\n| [Playwright Test](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) | E2E test runner integration |\n| [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense) | Autocomplete for file paths |\n| [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) | Highlight TODOs, notes, and warnings in comments |\n| [Peacock](https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock) | Color-code workspaces (useful for multiple projects) |\n\n> **Note:** If you previously used Vetur for Vue 2, uninstall it. Volar (Vue - Official) is the recommended extension for Vue 3.\n\n### Workspace Settings\n\nThe `.vscode/settings.json` file configures:\n\n**Formatting:**\n- 2-space indentation\n- Trim trailing whitespace\n- Insert final newline\n- Unix line endings (LF)\n\n**Auto-fix on save:**\n- Prettier formats code\n- ESLint fixes JavaScript/Vue issues\n- Stylelint fixes CSS issues\n\n**File hiding:**\n- Hides `dist/`, `coverage/`, and log files from explorer\n\nThese settings only apply to this workspace and won't affect your global VS Code configuration.\n\n## FAQ\n\n**What kinds of editor settings and extensions should be added to the project?**\n\nAll additions must:\n\n- Be specific to this project's needs\n- Not interfere with any team member's workflow\n\nFor 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.\n\n**Why Volar instead of Vetur?**\n\nVolar (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.\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\n\nhero:\n  name: Vue Enterprise Boilerplate\n  text: Production-ready Vue 3 architecture\n  tagline: An opinionated architecture and dev environment for new Vue 3 SPAs using Vite, TypeScript, and Pinia.\n  actions:\n    - theme: brand\n      text: Get Started\n      link: /development\n    - theme: alt\n      text: View on GitHub\n      link: https://github.com/bencodezen/vue-enterprise-boilerplate\n\nfeatures:\n  - icon: 📝\n    title: Thorough Documentation\n    details: Every architectural decision is documented, so even new team members can quickly understand patterns and conventions.\n  - icon: 🔒\n    title: Type Safety\n    details: Full TypeScript support with strict mode, typed props/emits, and comprehensive type inference.\n  - icon: ⚡\n    title: Lightning Fast\n    details: Powered by Vite for instant dev server startup and blazing fast HMR.\n  - icon: 🧪\n    title: First-Class Testing\n    details: Unit testing with Vitest and E2E testing with Playwright, both configured and ready to use.\n  - icon: 🎨\n    title: Design System\n    details: SCSS-based design tokens for colors, typography, spacing, and more.\n  - icon: 🛠️\n    title: Developer Experience\n    details: Auto-imports, component auto-registration, and VS Code integration out of the box.\n---\n\n## Why This Boilerplate?\n\nThis boilerplate helps teams build large-scale Vue 3 applications by providing:\n\n- **Consistent patterns** - Established conventions for components, state, routing, and testing\n- **Scalable architecture** - Folder structure that grows with your application\n- **Modern tooling** - Vite, TypeScript, Pinia, Vue Router 4, Vitest, Playwright\n- **Documentation culture** - Every pattern documented for team alignment\n\n## Quick Start\n\n```bash\n# Clone the repository\ngit clone https://github.com/bencodezen/vue-enterprise-boilerplate.git\ncd vue-enterprise-boilerplate\n\n# Install dependencies\nnpm install\n\n# Start development server\nnpm run dev\n```\n\nThen open [http://localhost:8080](http://localhost:8080) to see your app.\n"
  },
  {
    "path": "docs/linting.md",
    "content": "# Linting & Formatting\n\n- [Overview](#overview)\n- [Languages](#languages)\n- [Scripts](#scripts)\n  - [Terminal](#terminal)\n  - [Editor](#editor)\n- [Configuration](#configuration)\n- [FAQ](#faq)\n\nThis project uses ESLint and Prettier to catch errors and enforce a consistent code style.\n\n## Overview\n\n| Tool | Purpose |\n|------|---------|\n| [ESLint](https://eslint.org/) | Catches bugs and enforces code quality rules |\n| [Prettier](https://prettier.io/) | Formats code for consistent style |\n\nESLint handles logic and correctness, Prettier handles formatting. They're configured to work together without conflicts.\n\n## Languages\n\n- **TypeScript/JavaScript** — Linted by ESLint, formatted by Prettier\n- **Vue SFCs** — Linted by ESLint (`eslint-plugin-vue`), formatted by Prettier\n- **JSON/HTML/CSS/Markdown** — Formatted by Prettier\n\n## Scripts\n\n### Terminal\n\n```bash\n# Lint and auto-fix issues\npnpm lint\n\n# Format all files in src/\npnpm format\n```\n\n### Editor\n\nWith the recommended VS Code extensions, files are automatically:\n- Linted on save (ESLint)\n- Formatted on save (Prettier)\n\nSee [editors.md](editors.md) for setup details.\n\n## Configuration\n\n| Tool | Config File | Docs |\n|------|-------------|------|\n| ESLint | `.eslintrc.cjs` | [ESLint Configuration](https://eslint.org/docs/user-guide/configuring) |\n| Prettier | `.prettierrc.json` | [Prettier Configuration](https://prettier.io/docs/en/configuration.html) |\n\n### ESLint Setup\n\nThe ESLint config extends:\n- `plugin:vue/vue3-essential` — Vue 3 specific rules\n- `eslint:recommended` — Core ESLint rules\n- `@vue/eslint-config-typescript` — TypeScript support\n- `@vue/eslint-config-prettier/skip-formatting` — Disables formatting rules (Prettier handles those)\n\n### Prettier Setup\n\n```json\n{\n  \"semi\": false,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"printWidth\": 100,\n  \"trailingComma\": \"none\"\n}\n```\n\n## FAQ\n\n**Why separate ESLint and Prettier?**\n\nESLint 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.\n\n**Why no semicolons?**\n\nThis 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`.\n\n**How do I add Stylelint for CSS?**\n\nIf you need more advanced CSS linting:\n\n```bash\npnpm add -D stylelint stylelint-config-standard-scss\n```\n\nCreate `stylelint.config.js`:\n```js\nexport default {\n  extends: ['stylelint-config-standard-scss']\n}\n```\n\nAdd to `.vscode/settings.json`:\n```json\n{\n  \"stylelint.enable\": true,\n  \"css.validate\": false,\n  \"scss.validate\": false\n}\n```\n\n**So many configuration files! Why not move more to `package.json`?**\n\nWhile some configs can live in `package.json`, separate files offer benefits:\n- Easier to find and edit specific tool configs\n- Supports dynamic configuration via JavaScript\n- Better IDE support and syntax highlighting\n- Cleaner `package.json` that focuses on dependencies and scripts\n"
  },
  {
    "path": "docs/production.md",
    "content": "# Building and Deploying to Production\n\n- [Building for Production](#building-for-production)\n- [Previewing the Build](#previewing-the-build)\n- [Environment Variables](#environment-variables)\n- [Deployment](#deployment)\n\n## Building for Production\n\n```bash\n# Build for production\npnpm build\n```\n\nThis runs TypeScript type checking and then builds optimized assets into the `dist/` directory.\n\nThe build process:\n\n1. Type checks with `vue-tsc`\n2. Bundles and minifies with Vite\n3. Outputs to `dist/` with hashed filenames for cache busting\n\n## Previewing the Build\n\nBefore deploying, you can preview the production build locally:\n\n```bash\n# Preview the production build\npnpm preview\n```\n\nThis serves the `dist/` directory on a local server, letting you verify the build works correctly.\n\n## Environment Variables\n\nVite uses `.env` files for environment-specific configuration:\n\n| File                    | Purpose                              |\n| ----------------------- | ------------------------------------ |\n| `.env`                  | Default values (committed)           |\n| `.env.local`            | Local overrides (not committed)      |\n| `.env.production`       | Production values (committed)        |\n| `.env.production.local` | Production overrides (not committed) |\n\nVariables must be prefixed with `VITE_` to be exposed to client code:\n\n```bash\n# .env.production\nVITE_API_BASE_URL=https://api.example.com\n```\n\nAccess in code:\n\n```typescript\nconst apiUrl = import.meta.env.VITE_API_BASE_URL\n```\n\n> **Security:** Never put secrets in `VITE_` variables — they're embedded in the client bundle and visible to users.\n\n## Deployment\n\nThe `dist/` directory contains static files that can be deployed to any static hosting service:\n\n### SPA Routing\n\nIf 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.\n"
  },
  {
    "path": "docs/routing.md",
    "content": "# Routing, Layouts, and Pages\n\n- [Overview](#overview)\n- [Route Configuration](#route-configuration)\n- [Layouts](#layouts)\n- [Pages](#pages)\n- [Navigation Guards](#navigation-guards)\n- [Lazy Loading](#lazy-loading)\n\n## Overview\n\nThis project uses [Vue Router 4](https://router.vuejs.org/) for client-side routing:\n\n| File | Purpose |\n|------|---------|\n| `src/router/index.ts` | Router initialization |\n| `src/router/routes.ts` | Route definitions |\n| `src/layouts/` | Layout components |\n| `src/pages/` | Page components |\n\n## Route Configuration\n\nRoutes are defined in `src/router/routes.ts`:\n\n```typescript\nexport default [\n  {\n    path: '/',\n    name: 'home',\n    component: () => import('@/pages/index.vue')\n  },\n  {\n    path: '/about',\n    name: 'about',\n    component: () => import('@/pages/about.vue')\n  }\n]\n```\n\nThe router is created in `src/router/index.ts`:\n\n```typescript\nimport { createRouter, createWebHistory } from 'vue-router'\nimport routes from './routes'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes\n})\n\nexport default router\n```\n\n## Layouts\n\nLayout components provide shared structure (header, footer, navigation) around page content. They live in `src/layouts/`.\n\n```vue\n<!-- src/layouts/AppLayout.vue -->\n<script setup lang=\"ts\"></script>\n\n<template>\n  <div class=\"app-layout\">\n    <AppHeader />\n    <main>\n      <slot />\n    </main>\n    <AppFooter />\n  </div>\n</template>\n```\n\nUse layouts by wrapping page content:\n\n```vue\n<!-- src/pages/index.vue -->\n<template>\n  <AppLayout>\n    <h1>Home Page</h1>\n    <!-- Page content -->\n  </AppLayout>\n</template>\n```\n\nOr use nested routes for layout-based routing:\n\n```typescript\n{\n  path: '/',\n  component: () => import('@/layouts/AppLayout.vue'),\n  children: [\n    { path: '', component: () => import('@/pages/index.vue') },\n    { path: 'about', component: () => import('@/pages/about.vue') }\n  ]\n}\n```\n\n## Pages\n\nPage components are the top-level components rendered for each route. They live in `src/pages/` and typically:\n\n- Use a layout component for consistent structure\n- Fetch data needed for the page\n- Compose smaller components together\n\nKeep pages focused on orchestration — extract complex logic into composables and UI into components.\n\n## Navigation Guards\n\nUse navigation guards for authentication, authorization, and data fetching:\n\n```typescript\n// src/router/routes.ts\n{\n  path: '/dashboard',\n  component: () => import('@/pages/dashboard.vue'),\n  meta: { requiresAuth: true }\n}\n\n// src/router/index.ts\nrouter.beforeEach((to, from) => {\n  const auth = useAuth()\n\n  if (to.meta.requiresAuth && !auth.isAuthenticated) {\n    return { name: 'login', query: { redirect: to.fullPath } }\n  }\n})\n```\n\nDefine guards in the router (not components) because:\n\n- Guard logic often applies to multiple routes\n- Keeps page components focused on rendering\n- Easier to test in isolation\n\n## Lazy Loading\n\nAll routes use dynamic imports for automatic code splitting:\n\n```typescript\n// Each page becomes a separate chunk\ncomponent: () => import('@/pages/about.vue')\n```\n\nThis means users only download the code for pages they visit, improving initial load time.\n\nFor related pages that are often visited together, you can group them into a named chunk:\n\n```typescript\ncomponent: () => import(/* webpackChunkName: \"settings\" */ '@/pages/settings/profile.vue')\n```\n"
  },
  {
    "path": "docs/state.md",
    "content": "# State Management\n\n- [Overview](#overview)\n- [Defining Stores](#defining-stores)\n- [Using Stores](#using-stores)\n- [Store Organization](#store-organization)\n- [When to Use Stores](#when-to-use-stores)\n\n## Overview\n\nThis project uses [Pinia](https://pinia.vuejs.org/) for state management. Pinia is the official state management library for Vue 3, replacing Vuex.\n\n**Why Pinia over Vuex?**\n\n- Simpler API — no mutations, just state and actions\n- Full TypeScript support with type inference\n- Composition API style with `ref` and `computed`\n- Devtools integration\n- Lighter weight\n\nStores live in `src/stores/`.\n\n## Defining Stores\n\nUse the Composition API style (setup stores) for consistency with components:\n\n```typescript\n// src/stores/authStore.ts\nimport { ref, computed } from 'vue'\nimport { defineStore } from 'pinia'\n\nexport const useAuthStore = defineStore('authStore', () => {\n  // State\n  const user = ref<User | null>(null)\n  const token = ref<string | null>(null)\n\n  // Getters (computed)\n  const isAuthenticated = computed(() => !!token.value)\n\n  // Actions\n  async function login(credentials: Credentials) {\n    const response = await api.login(credentials)\n    user.value = response.user\n    token.value = response.token\n  }\n\n  function logout() {\n    user.value = null\n    token.value = null\n  }\n\n  return { user, token, isAuthenticated, login, logout }\n})\n```\n\n### Naming Conventions\n\n| Element | Convention | Example |\n|---------|------------|---------|\n| File name | `[domain]Store.ts` | `authStore.ts`, `cartStore.ts` |\n| Export name | `use[Domain]Store` | `useAuthStore`, `useCartStore` |\n| Store ID | `[domain]Store` | `'authStore'`, `'cartStore'` |\n\n**Avoid single-word names** — Be specific about what the store manages. Use `userProfileStore` instead of `user`, `shoppingCartStore` instead of `cart` if needed for clarity.\n\n## Using Stores\n\nImport and use stores in components:\n\n```vue\n<script setup lang=\"ts\">\nimport { useAuthStore } from '@/stores/authStore'\n\nconst authStore = useAuthStore()\n</script>\n\n<template>\n  <div v-if=\"authStore.isAuthenticated\">\n    <p>Welcome, {{ authStore.user?.name }}</p>\n    <button @click=\"authStore.logout\">Logout</button>\n  </div>\n  <div v-else>\n    <button @click=\"showLogin\">Login</button>\n  </div>\n</template>\n```\n\n### Destructuring with Reactivity\n\nUse `storeToRefs` to destructure while maintaining reactivity:\n\n```vue\n<script setup lang=\"ts\">\nimport { storeToRefs } from 'pinia'\nimport { useAuthStore } from '@/stores/authStore'\n\nconst authStore = useAuthStore()\nconst { user, isAuthenticated } = storeToRefs(authStore)\nconst { login, logout } = authStore // Actions don't need storeToRefs\n</script>\n```\n\n## Store Organization\n\nKeep stores focused on a single domain:\n\n```\nsrc/stores/\n├── authStore.ts      # Authentication state\n├── cartStore.ts      # Shopping cart\n├── userStore.ts      # User profile\n└── productStore.ts   # Product catalog\n```\n\n### Composing Stores\n\nStores can use other stores:\n\n```typescript\n// src/stores/cartStore.ts\nimport { defineStore } from 'pinia'\nimport { useAuthStore } from './authStore'\n\nexport const useCartStore = defineStore('cartStore', () => {\n  const authStore = useAuthStore()\n\n  async function checkout() {\n    if (!authStore.isAuthenticated) {\n      throw new Error('Must be logged in to checkout')\n    }\n    // ... checkout logic\n  }\n\n  return { checkout }\n})\n```\n\n## When to Use Stores\n\n**Use Pinia stores for:**\n\n- Authentication state (current user, tokens)\n- Data shared across multiple components\n- Data that persists across route changes\n- Complex state with many actions\n\n**Use local component state for:**\n\n- Form inputs\n- UI state (modals, dropdowns)\n- Data used by a single component\n- Temporary state that resets on navigation\n\n**Rule of thumb:** Start with local state. Extract to a store when you need to share state across components or persist it across routes.\n"
  },
  {
    "path": "docs/tech.md",
    "content": "# Languages and Technologies\n\n- [TypeScript](#typescript)\n  - [Why TypeScript](#why-typescript)\n  - [Configuration](#configuration)\n  - [TypeScript FAQ](#typescript-faq)\n- [Vue](#vue)\n  - [Composition API](#composition-api)\n  - [Vue Router](#vue-router)\n  - [Pinia](#pinia)\n- [HTML](#html)\n  - [Templates](#templates)\n  - [Render Functions](#render-functions)\n- [CSS](#css)\n  - [SCSS](#scss)\n  - [Scoped Styles](#scoped-styles)\n  - [Design Variables](#design-variables)\n  - [Global CSS](#global-css)\n  - [CSS FAQ](#css-faq)\n\n## TypeScript\n\nThis project uses TypeScript for type safety and improved developer experience. All `.ts` and `.vue` files are type-checked.\n\n### Why TypeScript\n\n- **Catch errors early** — Type errors are caught at build time, not runtime\n- **Better IDE support** — Autocomplete, refactoring, and inline documentation\n- **Self-documenting code** — Types serve as documentation for function signatures\n- **Vue 3 designed for it** — Vue 3 and its ecosystem have first-class TypeScript support\n\n### Configuration\n\nTypeScript is configured in multiple files:\n\n| File | Purpose |\n|------|---------|\n| `tsconfig.json` | Base configuration, references other configs |\n| `tsconfig.app.json` | Application code configuration |\n| `tsconfig.node.json` | Node.js tooling (Vite config, etc.) |\n| `tsconfig.vitest.json` | Test configuration |\n\nKey settings:\n\n```json\n{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true\n  }\n}\n```\n\n### Migrating from JavaScript\n\nIf migrating an existing JavaScript codebase, enable `allowJs` in `tsconfig.json` for progressive migration:\n\n```json\n{\n  \"compilerOptions\": {\n    \"allowJs\": true,\n    \"checkJs\": false\n  }\n}\n```\n\nThis 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.\n\n**Migration strategy:**\n1. Enable `allowJs`, disable `checkJs`\n2. Rename files from `.js` to `.ts` one at a time\n3. Fix type errors in each file as you convert\n4. Once all files are `.ts`, disable `allowJs`\n\n### TypeScript FAQ\n\n**How strict should we be with types?**\n\nAim 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.\n\n**Should I use `interface` or `type`?**\n\n- Use `interface` for object shapes that might be extended\n- Use `type` for unions, primitives, and utility types\n\n```typescript\n// Interface for objects\ninterface User {\n  id: string\n  name: string\n}\n\n// Type for unions\ntype Status = 'pending' | 'active' | 'completed'\n```\n\n## Vue\n\nThis project uses [Vue 3](https://vuejs.org/) with the Composition API.\n\n### Composition API\n\nThis boilerplate uses `<script setup>` syntax with the Composition API:\n\n```vue\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\n\nconst count = ref(0)\nconst doubled = computed(() => count.value * 2)\n\nfunction increment() {\n  count.value++\n}\n</script>\n```\n\n### Options API\n\nThe Options API is still fully supported in Vue 3 and remains a valid choice:\n\n```vue\n<script lang=\"ts\">\nimport { defineComponent } from 'vue'\n\nexport default defineComponent({\n  data() {\n    return { count: 0 }\n  },\n  computed: {\n    doubled(): number {\n      return this.count * 2\n    }\n  },\n  methods: {\n    increment() {\n      this.count++\n    }\n  }\n})\n</script>\n```\n\n### Which API to Choose?\n\nBoth APIs are production-ready. Choose based on your team's needs:\n\n| Factor | Composition API | Options API |\n|--------|-----------------|-------------|\n| TypeScript | Better type inference | Requires `defineComponent` wrapper |\n| Code organization | Organize by feature/concern | Organize by option type |\n| Learning curve | Steeper for Vue beginners | More familiar structure |\n| Logic reuse | Composables | Mixins (less flexible) |\n| Bundle size | Better tree-shaking | Slightly larger |\n\n**This boilerplate uses Composition API because:**\n- Better TypeScript integration\n- Composables are more flexible than mixins\n- Aligns with Vue ecosystem direction\n\n**Options API is a good choice when:**\n- Team is more familiar with it\n- Migrating from Vue 2 incrementally\n- Simpler components that don't need composables\n\nFor a deeper comparison, read the [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html).\n\n### Vue Router\n\n[Vue Router 4](https://router.vuejs.org/) handles client-side routing. See [routing.md](routing.md) for project-specific patterns.\n\nKey resources:\n- [Getting Started](https://router.vuejs.org/guide/)\n- [Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html)\n- [Route Meta Fields](https://router.vuejs.org/guide/advanced/meta.html)\n\n### Pinia\n\n[Pinia](https://pinia.vuejs.org/) handles state management. See [state.md](state.md) for project-specific patterns.\n\nKey resources:\n- [Getting Started](https://pinia.vuejs.org/getting-started.html)\n- [Defining a Store](https://pinia.vuejs.org/core-concepts/)\n- [Using Stores](https://pinia.vuejs.org/core-concepts/#using-the-store)\n\n## HTML\n\nAll HTML lives in `.vue` files, either in `<template>` blocks or render functions.\n\n### Templates\n\nTemplates are the default and recommended approach for most components:\n\n```vue\n<template>\n  <div class=\"user-card\">\n    <img :src=\"user.avatar\" :alt=\"user.name\" />\n    <h2>{{ user.name }}</h2>\n    <slot />\n  </div>\n</template>\n```\n\nVue templates support:\n- Self-closing tags: `<MyComponent />`\n- Dynamic attributes: `:src=\"imageUrl\"`\n- Event handling: `@click=\"handleClick\"`\n- Conditional rendering: `v-if`, `v-show`\n- List rendering: `v-for`\n\n### Render Functions\n\nUse render functions when you need the full power of JavaScript:\n\n```typescript\nimport { h } from 'vue'\n\nfunction render() {\n  return h('div', { class: 'container' }, [\n    h('h1', 'Hello'),\n    h(MyComponent, { prop: value })\n  ])\n}\n```\n\nRender functions are rare — use templates unless you have a specific reason not to.\n\n## CSS\n\nThis project uses SCSS with scoped styles.\n\n```vue\n<style lang=\"scss\" scoped>\n.container {\n  padding: 1rem;\n}\n</style>\n```\n\n### SCSS\n\n[SCSS](https://sass-lang.com/) is a superset of CSS that adds:\n\n- [Variables](https://sass-lang.com/guide#variables): `$primary-color: #3498db;`\n- [Nesting](https://sass-lang.com/guide#nesting): Nest selectors for cleaner code\n- [Mixins](https://sass-lang.com/guide#mixins): Reusable style patterns\n- [Functions](https://sass-lang.com/guide#functions): Color manipulation, math, etc.\n\nAny valid CSS is valid SCSS, so you can adopt features gradually.\n\n### Scoped Styles\n\nThe `scoped` attribute ensures styles only apply to the current component:\n\n```vue\n<style scoped>\n/* This .button only affects this component */\n.button {\n  background: blue;\n}\n</style>\n```\n\nVue adds a unique attribute (e.g., `data-v-7ba5bd90`) to elements and selectors, preventing style leakage.\n\n**Styling child components:**\n\nUse `:deep()` to style elements inside child components:\n\n```vue\n<style scoped>\n/* Style an element inside a child component */\n:deep(.child-class) {\n  color: red;\n}\n</style>\n```\n\nUse sparingly — prefer passing props or classes to child components.\n\n### Design Variables\n\nFor shared design tokens (colors, spacing, typography), create a variables file:\n\n```scss\n// src/styles/variables.scss\n$color-primary: #3498db;\n$color-secondary: #2ecc71;\n$spacing-unit: 8px;\n```\n\nImport in components that need them:\n\n```vue\n<style lang=\"scss\" scoped>\n@use '@/styles/variables' as *;\n\n.button {\n  background: $color-primary;\n  padding: $spacing-unit * 2;\n}\n</style>\n```\n\n> **Note:** With Vite, use `@use` instead of `@import` for better performance and to avoid deprecation warnings.\n\n### Global CSS\n\nGlobal styles should be minimal. Keep them in `src/styles/` and import in `main.ts`:\n\n```typescript\n// src/main.ts\nimport './styles/global.scss'\n```\n\nGlobal CSS should only contain:\n- CSS resets or normalization\n- Base element styles (typography, links)\n- Utility classes (if not using a utility framework)\n\n### CSS FAQ\n\n**Why scoped styles instead of CSS Modules?**\n\nScoped 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.\n\n**Why SCSS instead of plain CSS?**\n\nSCSS offers variables, nesting, and mixins that make styles more maintainable. Since SCSS is a superset of CSS, the learning curve is minimal.\n\n**Should I use Tailwind CSS?**\n\nTailwind 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).\n"
  },
  {
    "path": "docs/tests.md",
    "content": "# Testing\n\n- [Overview](#overview)\n- [Unit Tests with Vitest](#unit-tests-with-vitest)\n  - [Running Unit Tests](#running-unit-tests)\n  - [Writing Unit Tests](#writing-unit-tests)\n  - [Testing Composables](#testing-composables)\n  - [Testing Components](#testing-components)\n  - [Mocking](#mocking)\n- [End-to-End Tests with Playwright](#end-to-end-tests-with-playwright)\n  - [Running E2E Tests](#running-e2e-tests)\n  - [Writing E2E Tests](#writing-e2e-tests)\n  - [Accessibility-Driven Testing](#accessibility-driven-testing)\n- [Test Organization](#test-organization)\n- [Mock API](#mock-api)\n\n## Overview\n\nThis project uses two testing frameworks:\n\n| Type | Framework | Purpose |\n|------|-----------|---------|\n| Unit | [Vitest](https://vitest.dev/) | Test functions, composables, and components in isolation |\n| E2E | [Playwright](https://playwright.dev/) | Test complete user flows in real browsers |\n\n## Unit Tests with Vitest\n\n### Running Unit Tests\n\n```bash\n# Run unit tests once\npnpm test:unit\n\n# Run in watch mode (re-runs on file changes)\npnpm test:unit --watch\n\n# Run with coverage report\npnpm test:unit --coverage\n```\n\n### Writing Unit Tests\n\nVitest uses the same API as Jest (`describe`, `it`, `expect`):\n\n```typescript\n// src/utils/formatDate.test.ts\nimport { describe, it, expect } from 'vitest'\nimport { formatDate } from './formatDate'\n\ndescribe('formatDate', () => {\n  it('formats date in default format', () => {\n    const date = new Date('2024-01-15')\n    expect(formatDate(date)).toBe('January 15, 2024')\n  })\n\n  it('handles invalid dates', () => {\n    expect(formatDate(null)).toBe('')\n  })\n})\n```\n\n### Testing Composables\n\nTest composables by calling them and asserting on their returned values:\n\n```typescript\n// src/composables/useCounter.test.ts\nimport { describe, it, expect } from 'vitest'\nimport { useCounter } from './useCounter'\n\ndescribe('useCounter', () => {\n  it('initializes with default value', () => {\n    const { count } = useCounter()\n    expect(count.value).toBe(0)\n  })\n\n  it('increments count', () => {\n    const { count, increment } = useCounter()\n    increment()\n    expect(count.value).toBe(1)\n  })\n\n  it('accepts initial value', () => {\n    const { count } = useCounter(10)\n    expect(count.value).toBe(10)\n  })\n})\n```\n\n### Testing Components\n\nUse `@vue/test-utils` for component testing:\n\n```typescript\n// src/components/BaseButton.test.ts\nimport { describe, it, expect } from 'vitest'\nimport { mount } from '@vue/test-utils'\nimport BaseButton from './BaseButton.vue'\n\ndescribe('BaseButton', () => {\n  it('renders slot content', () => {\n    const wrapper = mount(BaseButton, {\n      slots: { default: 'Click me' }\n    })\n    expect(wrapper.text()).toContain('Click me')\n  })\n\n  it('emits click event', async () => {\n    const wrapper = mount(BaseButton)\n    await wrapper.trigger('click')\n    expect(wrapper.emitted('click')).toBeTruthy()\n  })\n\n  it('is disabled when prop is set', () => {\n    const wrapper = mount(BaseButton, {\n      props: { disabled: true }\n    })\n    expect(wrapper.attributes('disabled')).toBeDefined()\n  })\n})\n```\n\n### Mocking\n\nVitest provides several mocking utilities:\n\n```typescript\nimport { vi, describe, it, expect } from 'vitest'\n\n// Mock a function\nconst mockFn = vi.fn()\nmockFn('arg')\nexpect(mockFn).toHaveBeenCalledWith('arg')\n\n// Mock a module\nvi.mock('@/services/api', () => ({\n  fetchUser: vi.fn(() => Promise.resolve({ id: '1', name: 'Test' }))\n}))\n\n// Mock timers\nvi.useFakeTimers()\nvi.advanceTimersByTime(1000)\nvi.useRealTimers()\n```\n\n## End-to-End Tests with Playwright\n\n### Running E2E Tests\n\n```bash\n# Run E2E tests headlessly\npnpm test:e2e\n\n# Run with UI mode (interactive)\npnpm test:e2e:ui\n\n# Run specific test file\npnpm test:e2e e2e/login.spec.ts\n```\n\n### Writing E2E Tests\n\nE2E tests live in the `e2e/` directory:\n\n```typescript\n// e2e/login.spec.ts\nimport { test, expect } from '@playwright/test'\n\ntest('user can log in', async ({ page }) => {\n  await page.goto('/login')\n\n  await page.getByLabel('Email').fill('user@example.com')\n  await page.getByLabel('Password').fill('password123')\n  await page.getByRole('button', { name: 'Log in' }).click()\n\n  await expect(page).toHaveURL('/dashboard')\n  await expect(page.getByText('Welcome back')).toBeVisible()\n})\n\ntest('shows error for invalid credentials', async ({ page }) => {\n  await page.goto('/login')\n\n  await page.getByLabel('Email').fill('wrong@example.com')\n  await page.getByLabel('Password').fill('wrongpassword')\n  await page.getByRole('button', { name: 'Log in' }).click()\n\n  await expect(page.getByText('Invalid credentials')).toBeVisible()\n})\n```\n\n### Accessibility-Driven Testing\n\nWrite selectors from the user's perspective using semantic queries:\n\n```typescript\n// Prefer role-based selectors\nawait page.getByRole('button', { name: 'Submit' })\nawait page.getByRole('link', { name: 'Log in' })\nawait page.getByRole('textbox', { name: 'Email' })\n\n// Use labels for form fields\nawait page.getByLabel('Password')\n\n// Use text for content\nawait page.getByText('Welcome back')\n\n// Avoid implementation details\n// ❌ page.locator('.login-btn')\n// ❌ page.locator('#submit-button')\n// ❌ page.locator('[data-testid=\"login\"]')\n```\n\n**Why accessibility-driven selectors?**\n\n- Tests break when requirements change, not when implementation changes\n- Forces you to write accessible HTML (labels, ARIA attributes)\n- Mirrors how real users interact with your app\n\nIf a selector is hard to write, it's often a sign the HTML needs better accessibility:\n\n```html\n<!-- Hard to select (no label) -->\n<input type=\"text\" class=\"email-input\" />\n\n<!-- Easy to select -->\n<label>\n  Email\n  <input type=\"text\" />\n</label>\n```\n\n## Test Organization\n\n```\nproject/\n├── src/\n│   ├── composables/\n│   │   ├── useAuth.ts\n│   │   └── useAuth.test.ts      # Unit test next to source\n│   └── components/\n│       ├── BaseButton.vue\n│       └── BaseButton.test.ts   # Unit test next to source\n├── e2e/\n│   ├── login.spec.ts            # E2E tests in dedicated folder\n│   └── checkout.spec.ts\n├── vitest.config.ts\n└── playwright.config.ts\n```\n\n**Unit tests** live alongside source files (`*.test.ts`). This makes poor coverage obvious and lowers the barrier to adding tests.\n\n**E2E tests** live in `e2e/` directory, organized by user flow or feature.\n\n## Mock API\n\nFor development and testing without a backend, you can mock API responses:\n\n**Option 1: MSW (Mock Service Worker)**\n\n```typescript\n// src/mocks/handlers.ts\nimport { http, HttpResponse } from 'msw'\n\nexport const handlers = [\n  http.get('/api/user', () => {\n    return HttpResponse.json({ id: '1', name: 'Test User' })\n  }),\n  http.post('/api/login', async ({ request }) => {\n    const { email } = await request.json()\n    if (email === 'user@example.com') {\n      return HttpResponse.json({ token: 'fake-token' })\n    }\n    return new HttpResponse(null, { status: 401 })\n  })\n]\n```\n\n**Option 2: Environment variable for real API**\n\n```bash\n# Test against a staging server\nVITE_API_BASE_URL=https://staging.example.com pnpm test:e2e\n```\n\nSee [development.md](development.md) for more on environment variables.\n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "# Troubleshooting\n\nCommon issues and solutions for development.\n\n- [Script Errors](#script-errors)\n- [VS Code Issues](#vs-code-issues)\n- [TypeScript Errors](#typescript-errors)\n- [Vite / Build Issues](#vite--build-issues)\n- [Vue-Specific Issues](#vue-specific-issues)\n\n## Script Errors\n\n**Problem:** Errors when running `pnpm dev` or other scripts.\n\n**Solution 1:** Fresh install dependencies\n\n```bash\n# Delete node_modules and lockfile\nrm -rf node_modules pnpm-lock.yaml\n\n# Reinstall\npnpm install\n```\n\n**Solution 2:** Use lockfile from known working state\n\n```bash\nrm -rf node_modules\n\n# Restore lockfile from main branch\ngit checkout origin/main -- pnpm-lock.yaml\n\n# Install with frozen lockfile\npnpm install --frozen-lockfile\n```\n\nIf 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.\n\n## VS Code Issues\n\n**Problem:** Files formatted incorrectly on save.\n\n**Cause:** Conflicting formatter extensions or incorrect default formatter.\n\n**Solution:**\n\n1. Ensure Prettier is the default formatter in `.vscode/settings.json`:\n   ```json\n   {\n     \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n   }\n   ```\n\n2. Disable or uninstall conflicting formatters (e.g., Beautify, Format)\n\n3. If using Volar, ensure Vetur is uninstalled (they conflict)\n\n**Problem:** Vue files show errors but code works fine.\n\n**Solution:** Restart the Vue language server:\n- Open Command Palette (`Cmd+Shift+P`)\n- Run \"Vue: Restart Vue Server\"\n\n## TypeScript Errors\n\n**Problem:** Type errors in IDE but build succeeds (or vice versa).\n\n**Solution:** Restart the TypeScript server:\n- Open Command Palette (`Cmd+Shift+P`)\n- Run \"TypeScript: Restart TS Server\"\n\n**Problem:** Types not recognized for auto-imported components/composables.\n\n**Solution:** Ensure generated type files exist:\n```bash\n# Run dev server to generate types\npnpm dev\n\n# Check these files exist:\n# - components.d.ts\n# - auto-imports.d.ts\n```\n\nAdd them to `.gitignore` but not to `tsconfig.json` excludes.\n\n**Problem:** \"Cannot find module\" for path aliases like `@/`.\n\n**Solution:** Ensure `tsconfig.app.json` has matching paths:\n```json\n{\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n```\n\n## Vite / Build Issues\n\n**Problem:** Dev server starts but page is blank.\n\n**Solution:** Check browser console for errors. Common causes:\n- Missing environment variables (check `.env` files)\n- Syntax error in a component\n- Failed import (check file paths and extensions)\n\n**Problem:** Build succeeds but production app doesn't work.\n\n**Solution:** Test with preview server:\n```bash\npnpm build\npnpm preview\n```\n\nCheck for:\n- Hardcoded `localhost` URLs (use environment variables)\n- Missing environment variables in production\n- SSR-incompatible code if using SSR\n\n**Problem:** \"Failed to resolve import\" errors.\n\n**Solution:**\n- Check the import path is correct\n- Ensure the file extension is included for non-JS/TS files\n- For external packages, ensure they're in `dependencies` not just `devDependencies`\n\n## Vue-Specific Issues\n\n**Problem:** Reactivity not working (changes don't update UI).\n\n**Common causes:**\n```typescript\n// ❌ Destructuring loses reactivity\nconst { count } = useCounterStore()\n\n// ✅ Use storeToRefs\nconst { count } = storeToRefs(useCounterStore())\n\n// ❌ Replacing reactive object\nconst state = reactive({ items: [] })\nstate = { items: newItems } // Loses reactivity\n\n// ✅ Mutate properties instead\nstate.items = newItems\n```\n\n**Problem:** \"Hydration mismatch\" warnings (SSR).\n\n**Cause:** Server and client rendered different HTML.\n\n**Solution:**\n- Wrap client-only code in `<ClientOnly>` or `onMounted`\n- Don't use `Date.now()` or `Math.random()` during render\n- Ensure async data is fetched the same way on server and client\n\n**Problem:** Component not updating when prop changes.\n\n**Solution:** Ensure you're not caching the prop value:\n```typescript\n// ❌ Caches initial value\nconst localValue = props.value\n\n// ✅ Stays reactive\nconst localValue = computed(() => props.value)\n\n// ✅ Or use toRef\nconst localValue = toRef(props, 'value')\n```\n\n---\n\nStill stuck? Check the [Vue documentation](https://vuejs.org/) or open an issue with:\n- Steps to reproduce\n- Expected vs actual behavior\n- Error messages (full stack trace)\n- Node/pnpm versions (`node -v`, `pnpm -v`)\n"
  },
  {
    "path": "e2e/tsconfig.json",
    "content": "{\n  \"extends\": \"@tsconfig/node20/tsconfig.json\",\n  \"include\": [\"./**/*\"]\n}\n"
  },
  {
    "path": "e2e/vue.spec.ts",
    "content": "import { test, expect } from '@playwright/test'\n\n// See here how to get started:\n// https://playwright.dev/docs/intro\ntest('visits the app root url', async ({ page }) => {\n  await page.goto('/')\n  await expect(page.locator('h1')).toHaveText('Home Page')\n})\n"
  },
  {
    "path": "env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vue 3 App</title>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vue-enterprise-boilerplate\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"run-p type-check build-only\",\n    \"preview\": \"vite preview\",\n    \"test:unit\": \"vitest\",\n    \"test:e2e\": \"playwright test\",\n    \"test:e2e:ui\": \"playwright test --ui\",\n    \"build-only\": \"vite build\",\n    \"type-check\": \"vue-tsc --noEmit -p tsconfig.vitest.json --composite false\",\n    \"lint\": \"eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore\",\n    \"format\": \"prettier --write src/\",\n    \"docs\": \"vitepress dev docs\",\n    \"docs:build\": \"vitepress build docs\",\n    \"docs:preview\": \"vitepress preview docs\"\n  },\n  \"dependencies\": {\n    \"@vueuse/core\": \"^10.9.0\",\n    \"pinia\": \"^2.0.36\",\n    \"vue\": \"^3.4.27\",\n    \"vue-router\": \"^4.2.0\"\n  },\n  \"devDependencies\": {\n    \"@playwright/test\": \"^1.44.0\",\n    \"@rushstack/eslint-patch\": \"^1.2.0\",\n    \"@tsconfig/node18\": \"^2.0.1\",\n    \"@types/jsdom\": \"^21.1.1\",\n    \"@types/node\": \"^18.16.8\",\n    \"@unhead/vue\": \"^1.9.10\",\n    \"@vitejs/plugin-vue\": \"^4.2.3\",\n    \"@vue/eslint-config-prettier\": \"^7.1.0\",\n    \"@vue/eslint-config-typescript\": \"^11.0.3\",\n    \"@vue/test-utils\": \"^2.4.6\",\n    \"@vue/tsconfig\": \"^0.4.0\",\n    \"eslint\": \"^8.39.0\",\n    \"eslint-plugin-vue\": \"^9.11.0\",\n    \"jsdom\": \"^22.0.0\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.8.8\",\n    \"sass\": \"^1.77.1\",\n    \"typescript\": \"~5.0.4\",\n    \"unplugin-auto-import\": \"^0.17.6\",\n    \"unplugin-vue-components\": \"^0.27.0\",\n    \"vite\": \"^4.3.5\",\n    \"vitepress\": \"^1.6.4\",\n    \"vitest\": \"^1.4.0\",\n    \"vue-tsc\": \"^1.6.4\"\n  }\n}\n"
  },
  {
    "path": "playwright.config.ts",
    "content": "import process from 'node:process'\nimport { defineConfig, devices } from '@playwright/test'\n\n/**\n * Read environment variables from file.\n * https://github.com/motdotla/dotenv\n */\n// require('dotenv').config();\n\n/**\n * See https://playwright.dev/docs/test-configuration.\n */\nexport default defineConfig({\n  testDir: './e2e',\n  /* Maximum time one test can run for. */\n  timeout: 30 * 1000,\n  expect: {\n    /**\n     * Maximum time expect() should wait for the condition to be met.\n     * For example in `await expect(locator).toHaveText();`\n     */\n    timeout: 5000\n  },\n  /* Fail the build on CI if you accidentally left test.only in the source code. */\n  forbidOnly: !!process.env.CI,\n  /* Retry on CI only */\n  retries: process.env.CI ? 2 : 0,\n  /* Opt out of parallel tests on CI. */\n  workers: process.env.CI ? 1 : undefined,\n  /* Reporter to use. See https://playwright.dev/docs/test-reporters */\n  reporter: 'html',\n  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */\n  use: {\n    /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */\n    actionTimeout: 0,\n    /* Base URL to use in actions like `await page.goto('/')`. */\n    baseURL: 'http://localhost:8080',\n\n    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */\n    trace: 'on-first-retry',\n\n    /* Only on CI systems run the tests headless */\n    headless: !!process.env.CI\n  },\n\n  /* Configure projects for major browsers */\n  projects: [\n    {\n      name: 'chromium',\n      use: {\n        ...devices['Desktop Chrome']\n      }\n    },\n    {\n      name: 'firefox',\n      use: {\n        ...devices['Desktop Firefox']\n      }\n    },\n    {\n      name: 'webkit',\n      use: {\n        ...devices['Desktop Safari']\n      }\n    }\n\n    /* Test against mobile viewports. */\n    // {\n    //   name: 'Mobile Chrome',\n    //   use: {\n    //     ...devices['Pixel 5'],\n    //   },\n    // },\n    // {\n    //   name: 'Mobile Safari',\n    //   use: {\n    //     ...devices['iPhone 12'],\n    //   },\n    // },\n\n    /* Test against branded browsers. */\n    // {\n    //   name: 'Microsoft Edge',\n    //   use: {\n    //     channel: 'msedge',\n    //   },\n    // },\n    // {\n    //   name: 'Google Chrome',\n    //   use: {\n    //     channel: 'chrome',\n    //   },\n    // },\n  ],\n\n  /* Folder for test artifacts such as screenshots, videos, traces, etc. */\n  // outputDir: 'test-results/',\n\n  /* Run your local dev server before starting the tests */\n  webServer: {\n    /**\n     * Use the dev server by default for faster feedback loop.\n     * Use the preview server on CI for more realistic testing.\n     * Playwright will re-use the local server if there is already a dev-server running.\n     */\n    command: process.env.CI ? 'vite preview --port 8080' : 'vite dev',\n    port: 8080,\n    reuseExistingServer: !process.env.CI\n  }\n})\n"
  },
  {
    "path": "src/App.vue",
    "content": "<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",
    "content": "import { describe, it, expect } from 'vitest'\nimport { shallowMount } from '@vue/test-utils'\nimport BaseButton from './BaseButton.vue'\n\ndescribe('BaseButton Componenet', () => {\n  it('renders its content', () => {\n    const slotContent = '<strong>Click me!</strong>'\n    const { element } = shallowMount(BaseButton, {\n      slots: {\n        default: slotContent\n      }\n    })\n    expect(element.innerHTML).toContain(slotContent)\n  })\n\n  it('renders default content', () => {\n    const slotContent = ''\n    const { element } = shallowMount(BaseButton, {\n      slots: {\n        default: slotContent\n      }\n    })\n    expect(element.innerHTML).toContain('Submit')\n  })\n})\n"
  },
  {
    "path": "src/components/BaseButton.vue",
    "content": "<template>\n  <button :class=\"$style.button\">\n    <slot>Submit</slot>\n  </button>\n</template>\n\n<style lang=\"scss\" module>\n.button {\n  @extend %typography-small;\n\n  padding: $size-button-padding;\n  border: none;\n  background: $color-button-bg;\n  color: $color-button-text;\n  cursor: pointer;\n\n  &:disabled {\n    cursor: not-allowed;\n    background: $color-button-disabled-bg;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/BaseInputText.spec.ts",
    "content": "import { describe, it, expect, vi } from 'vitest'\nimport { shallowMount, mount } from '@vue/test-utils'\nimport BaseInputText from '@/components/BaseInputText.vue'\n\ndescribe('@components/BaseInputText', () => {\n  it('works with v-model', () => {\n    const wrapper = mount(BaseInputText, {\n      props: {\n        modelValue: 'aaa',\n        'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })\n      }\n    })\n    const inputWrapper = wrapper.find('input')\n    const inputEl = inputWrapper.element\n\n    // Has the correct starting value\n    expect(inputEl.value).toEqual('aaa')\n\n    // Sets the input to the correct value when props change\n    inputWrapper.setValue('ccc')\n    expect(inputEl.value).toEqual('ccc')\n  })\n\n  it('allows a type of \"password\"', () => {\n    const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})\n    shallowMount(BaseInputText, {\n      propsData: { value: 'aaa', type: 'password' }\n    })\n    expect(consoleError).not.toBeCalled()\n    consoleError.mockRestore()\n  })\n\n  it('does NOT allow a type of \"checkbox\"', () => {\n    const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})\n    shallowMount(BaseInputText, {\n      propsData: { value: 'aaa', type: 'checkbox' }\n    })\n\n    expect(consoleWarn).toBeCalled()\n    expect(consoleWarn.mock.calls[0][0]).toContain('custom validator check failed for prop \"type\"')\n    consoleWarn.mockRestore()\n  })\n})\n"
  },
  {
    "path": "src/components/BaseInputText.vue",
    "content": "<script setup lang=\"ts\">\ndefineProps({\n  type: {\n    type: String,\n    default: 'text',\n    // Only allow types that essentially just render text boxes\n    validator: (value: string) =>\n      ['email', 'number', 'password', 'search', 'tel', 'text', 'url'].includes(value)\n  }\n})\n\nconst model = defineModel()\n</script>\n\n<template>\n  <input v-model=\"model\" :type=\"type\" :class=\"$style.input\" />\n</template>\n\n<style lang=\"scss\" module>\n.input {\n  @extend %typography-small;\n\n  display: block;\n  width: 100%;\n  padding: $size-input-padding-vertical $size-input-padding-horizontal;\n  margin-bottom: $size-grid-padding;\n  line-height: 1;\n  border: $size-input-border solid $color-input-border;\n  border-radius: $size-input-border-radius;\n}\n</style>\n"
  },
  {
    "path": "src/composables/useTheme.ts",
    "content": "import { useColorMode } from '@vueuse/core'\n\ntype Theme = 'dark' | 'light'\n\nfunction useTheme() {\n  const themePreference = useColorMode()\n\n  function setTheme(theme: Theme) {\n    themePreference.value = theme\n  }\n\n  return { setTheme, themePreference }\n}\n\nexport default useTheme\n"
  },
  {
    "path": "src/design/_colors.scss",
    "content": "// CONTENT\n$color-body-bg: #f9f7f5;\n$color-text: #444;\n$color-heading-text: #35495e;\n\n// LINKS\n$color-link-text: #39a275;\n$color-link-text-active: $color-text;\n\n// INPUTS\n$color-input-border: lighten($color-heading-text, 50%);\n\n// BUTTONS\n$color-button-bg: $color-link-text;\n$color-button-disabled-bg: darken(desaturate($color-button-bg, 20%), 10%);\n$color-button-text: white;\n"
  },
  {
    "path": "src/design/_durations.scss",
    "content": "$duration-animation-base: 300ms;\n"
  },
  {
    "path": "src/design/_fonts.scss",
    "content": "$system-default-font-family: -apple-system, 'BlinkMacSystemFont', 'Segoe UI', 'Helvetica', 'Arial',\n  sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';\n\n$heading-font-family: $system-default-font-family;\n$heading-font-weight: 600;\n\n$content-font-family: $system-default-font-family;\n$content-font-weight: 400;\n\n%font-heading {\n  font-family: $heading-font-family;\n  font-weight: $heading-font-weight;\n  color: $color-heading-text;\n}\n\n%font-content {\n  font-family: $content-font-family;\n  font-weight: $content-font-weight;\n  color: $color-text;\n}\n"
  },
  {
    "path": "src/design/_layers.scss",
    "content": "$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-z-index: 4;\n$layer-tooltip-z-index: 5;\n"
  },
  {
    "path": "src/design/_sizes.scss",
    "content": "// GRID\n$size-grid-padding: 1.3rem;\n\n// CONTENT\n$size-content-width-max: 50rem;\n$size-content-width-min: 25rem;\n\n// INPUTS\n$size-input-padding-vertical: 0.75em;\n$size-input-padding-horizontal: 1em;\n$size-input-padding: $size-input-padding-vertical $size-input-padding-horizontal;\n$size-input-border: 1px;\n$size-input-border-radius: calc((1em + $size-input-padding-vertical * 2) / 10);\n\n// BUTTONS\n$size-button-padding-vertical: calc($size-grid-padding / 2);\n$size-button-padding-horizontal: calc($size-grid-padding / 1.5);\n$size-button-padding: $size-button-padding-vertical $size-button-padding-horizontal;\n"
  },
  {
    "path": "src/design/_typography.scss",
    "content": "// Interpolate v1.0\n\n// This mixin generates CSS for interpolation of length properties.\n// It has 5 required values, including the target property, initial\n// screen size, initial value, final screen size and final value.\n\n// It has two optional values which include an easing property,\n// which is a string, representing a CSS animation-timing-function\n// and finally a number of bending-points, that determines how many\n// interpolations steps are applied along the easing function.\n\n// Author: Mike Riethmuller - @MikeRiethmuller\n// More information: http://codepen.io/MadeByMike/pen/a2249946658b139b7625b2a58cf03a65?editors=0100\n\n///\n/// @param {String} $property - The CSS property to interpolate\n/// @param {Unit} $min-screen - A CSS length unit\n/// @param {Unit} $min-value - A CSS length unit\n/// @param {Unit} $max-screen - Value to be parsed\n/// @param {Unit} $max-value - Value to be parsed\n/// @param {String} $easing - Value to be parsed\n/// @param {Integer} $bending-points - Value to be parsed\n///\n\n// Examples on line 258\n\n// Issues:\n\n// - kubic-bezier requires whitespace\n// - kubic-bezier cannot parse negative values\n\n// stylelint-disable scss/dollar-variable-pattern\n@mixin typography-interpolate(\n  $property,\n  $min-screen,\n  $min-value,\n  $max-screen,\n  $max-value,\n  $easing: 'linear',\n  $bending-points: 2\n) {\n  // Default Easing 'Linear'\n  $p0: 0;\n  $p1: 0;\n  $p2: 1;\n  $p3: 1;\n\n  // Parse Cubic Bezier string\n  @if (str-slice($easing, 1, 12) == 'kubic-bezier') {\n    // Get the values between the brackets\n    // TODO: Deal with different whitespace\n    $i: str-index($easing, ')'); // Get index of closing bracket\n    $values: str-slice($easing, 14, $i - 1); // Extract values between brackts\n    $list: typography-explode($values, ', '); // Split the values into a list\n\n    @debug ($list);\n\n    // Cast values to numebrs\n    $p0: typography-number(nth($list, 1));\n    $p1: typography-number(nth($list, 2));\n    $p2: typography-number(nth($list, 3));\n    $p3: typography-number(nth($list, 4));\n  }\n\n  @if ($easing == 'ease') {\n    $p0: 0.25;\n    $p1: 1;\n    $p2: 0.25;\n    $p3: 1;\n  }\n\n  @if ($easing == 'ease-in-out') {\n    $p0: 0.42;\n    $p1: 0;\n    $p2: 0.58;\n    $p3: 1;\n  }\n\n  @if ($easing == 'ease-in') {\n    $p0: 0.42;\n    $p1: 0;\n    $p2: 1;\n    $p3: 1;\n  }\n\n  @if ($easing == 'ease-out') {\n    $p0: 0;\n    $p1: 0;\n    $p2: 0.58;\n    $p3: 1;\n  }\n\n  #{$property}: $min-value;\n\n  @if ($easing == 'linear' or $bending-points < 1) {\n    @media screen and (min-width: $min-screen) {\n      #{$property}: typography-calc-interpolation($min-screen, $min-value, $max-screen, $max-value);\n    }\n  } @else {\n    // Loop through bending points\n    $t: 1 / ($bending-points + 1);\n    $i: 1;\n    $prev-screen: $min-screen;\n    $prev-value: $min-value;\n\n    @while $t * $i <= 1 {\n      $bending-point: $t * $i;\n      $value: typography-cubic-bezier($p0, $p1, $p2, $p3, $bending-point);\n      $screen-int: typography-lerp($min-screen, $max-screen, $bending-point);\n      $value-int: typography-lerp($min-value, $max-value, $value);\n\n      @media screen and (min-width: $prev-screen) {\n        #{$property}: typography-calc-interpolation(\n          $prev-screen,\n          $prev-value,\n          $screen-int,\n          $value-int\n        );\n      }\n\n      $prev-screen: $screen-int;\n      $prev-value: $value-int;\n      $i: $i + 1;\n    }\n  }\n\n  @media screen and (min-width: $max-screen) {\n    #{$property}: $max-value;\n  }\n}\n\n// Requires several helper functions including: pow, calc-interpolation, kubic-bezier, number and explode\n\n// Math functions:\n\n// Linear interpolations in CSS as a Sass function\n// Author: Mike Riethmuller | https://madebymike.com.au/writing/precise-control-responsive-typography/ I\n\n@function typography-calc-interpolation($min-screen, $min-value, $max-screen, $max-value) {\n  $a: calc(($max-value - $min-value) / ($max-screen - $min-screen));\n  $b: $min-value - $a * $min-screen;\n\n  $sign: '+';\n\n  @if ($b < 0) {\n    $sign: '-';\n    $b: abs($b);\n  }\n\n  @return calc(#{$a * 100}vw #{$sign} #{$b});\n}\n\n// This is a crude Sass port webkits cubic-bezier function. Looking to simplify this if you can help.\n@function typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x) {\n  $cx: 3 * $p1x;\n  $bx: 3 * ($p2x - $p1x) - $cx;\n  $ax: 1 - $cx - $bx;\n\n  $t0: 0;\n  $t1: 1;\n  $t2: $x;\n  $x2: 0;\n  $res: 1000;\n\n  @while ($t0 < $t1 or $break) {\n    $x2: (($ax * $t2 + $bx) * $t2 + $cx) * $t2;\n\n    @if (abs($x2 - $x) < $res) {\n      @return $t2;\n    }\n\n    @if ($x > $x2) {\n      $t0: $t2;\n    } @else {\n      $t1: $t2;\n    }\n    $t2: ($t1 - $t0) * 0.5 + $t0;\n  }\n\n  @return $t2;\n}\n\n@function typography-cubic-bezier($p1x, $p1y, $p2x, $p2y, $x) {\n  $cy: 3 * $p1y;\n  $by: 3 * ($p2y - $p1y) - $cy;\n  $ay: 1 - $cy - $by;\n  $t: typography-solve-bexier-x($p1x, $p1y, $p2x, $p2y, $x);\n\n  @return (($ay * $t + $by) * $t + $cy) * $t;\n}\n\n// A stright up lerp\n// Credit: Ancient Greeks possibly Hipparchus of Rhodes\n@function typography-lerp($a, $b, $t) {\n  @return $a + ($b - $a) * $t;\n}\n\n// String functions:\n\n// Cast string to number\n// Credit: Hugo Giraudel | https://www.sassmeister.com/gist/9fa19d254864f33d4a80\n@function typography-number($value) {\n  @if type-of($value) == 'number' {\n    @return $value;\n  } @else if type-of($value) != 'string' {\n    $_: log('Value for `to-number` should be a number or a string.');\n  }\n\n  $result: 0;\n  $digits: 0;\n  $minus: str-slice($value, 1, 1) == '-';\n  $numbers: (\n    '0': 0,\n    '1': 1,\n    '2': 2,\n    '3': 3,\n    '4': 4,\n    '5': 5,\n    '6': 6,\n    '7': 7,\n    '8': 8,\n    '9': 9\n  );\n\n  @for $i from if($minus, 2, 1) through str-length($value) {\n    $character: str-slice($value, $i, $i);\n\n    @if not(index(map-keys($numbers), $character) or $character == '.') {\n      @return to-length(if($minus, -$result, $result), str-slice($value, $i));\n    }\n\n    @if $character == '.' {\n      $digits: 1;\n    } @else if $digits == 0 {\n      $result: $result * 10 + map-get($numbers, $character);\n    } @else {\n      $digits: $digits * 10;\n      $result: $result + map-get($numbers, $character) / $digits;\n    }\n  }\n\n  @return if($minus, -$result, $result);\n}\n\n// Explode a string by a delimiter\n// Credit: https://gist.github.com/danielpchen/3677421ea15dcf2579ff\n@function typography-explode($string, $delimiter) {\n  $result: ();\n\n  @if $delimiter == '' {\n    @for $i from 1 through str-length($string) {\n      $result: append($result, str-slice($string, $i, $i));\n    }\n\n    @return $result;\n  }\n  $exploding: true;\n\n  @while $exploding {\n    $d-index: str-index($string, $delimiter);\n\n    @if $d-index {\n      @if $d-index > 1 {\n        $result: append($result, str-slice($string, 1, $d-index - 1));\n        $string: str-slice($string, $d-index + str-length($delimiter));\n      } @else if $d-index == 1 {\n        $string: str-slice($string, 1, $d-index + str-length($delimiter));\n      } @else {\n        $result: append($result, $string);\n        $exploding: false;\n      }\n    } @else {\n      $result: append($result, $string);\n      $exploding: false;\n    }\n  }\n\n  @return $result;\n}\n\n// Using vertical rhythm methods from https://scotch.io/tutorials/aesthetic-sass-3-typography-and-vertical-rhythm\n// Using perfect 8/9 for low contrast and perfect fifth 2/3 for high\n$typography-type-scale: (-1: 0.889rem, 0: 1rem, 1: 1.125rem, 2: 1.266rem, 3: 1.424rem);\n\n@function typography-type-scale($level) {\n  @if map-has-key($typography-type-scale, $level) {\n    @return map-get($typography-type-scale, $level);\n  }\n\n  @warn 'Unknown `#{$level}` in $typography-type-scale.';\n\n  @return null;\n}\n\n$typography-type-scale-contrast: (-1: 1rem, 0: 1.3333rem, 1: 1.777rem, 2: 2.369rem, 3: 3.157rem);\n\n@function typography-type-scale-contrast($level) {\n  @if map-has-key($typography-type-scale-contrast, $level) {\n    @return map-get($typography-type-scale-contrast, $level);\n  }\n\n  @warn 'Unknown `#{$level}` in $typography-type-scale-contrast.';\n\n  @return null;\n}\n\n$typography-base-font-size: 1rem;\n$typography-base-line-height: $typography-base-font-size * 1.25;\n\n$typography-line-heights: (\n  -1: $typography-base-line-height,\n  0: $typography-base-line-height,\n  1: $typography-base-line-height * 1.5,\n  2: $typography-base-line-height * 1.5,\n  3: $typography-base-line-height * 1.5\n);\n\n@function typography-line-height($level) {\n  @if map-has-key($typography-line-heights, $level) {\n    @return map-get($typography-line-heights, $level);\n  }\n\n  @warn 'Unknown `#{$level}` in $line-height.';\n\n  @return null;\n}\n\n$typography-base-line-height-contrast: $typography-base-line-height;\n\n$typography-line-heights-contrast: (\n  -1: $typography-base-line-height-contrast,\n  0: $typography-base-line-height-contrast * 2,\n  1: $typography-base-line-height-contrast * 2,\n  2: $typography-base-line-height-contrast * 2,\n  3: $typography-base-line-height * 3\n);\n\n@function typography-line-height-contrast($level) {\n  @if map-has-key($typography-line-heights-contrast, $level) {\n    @return map-get($typography-line-heights-contrast, $level);\n  }\n\n  @warn 'Unknown `#{$level}` in $typography-line-heights-contrast.';\n\n  @return null;\n}\n\n// Mixing these two sets of mixins ala Rachel:\n@mixin typography-got-rhythm($level: 0) {\n  @include typography-interpolate(\n    'font-size',\n    $size-content-width-min,\n    typography-type-scale($level),\n    $size-content-width-max,\n    typography-type-scale-contrast($level)\n  );\n  @include typography-interpolate(\n    'line-height',\n    $size-content-width-min,\n    typography-line-height($level),\n    $size-content-width-max,\n    typography-line-height-contrast($level)\n  );\n}\n\n%typography-xxlarge {\n  @include typography-got-rhythm(3);\n\n  @extend %font-heading;\n}\n\n%typography-xlarge {\n  @include typography-got-rhythm(2);\n\n  @extend %font-heading;\n}\n\n%typography-large {\n  @include typography-got-rhythm(1);\n\n  @extend %font-heading;\n}\n\n%typography-medium {\n  @include typography-got-rhythm(0);\n\n  @extend %font-content;\n}\n\n%typography-small {\n  @include typography-got-rhythm(-1);\n\n  @extend %font-content;\n}\n"
  },
  {
    "path": "src/design/index.scss",
    "content": "@import 'colors';\n@import 'durations';\n@import 'fonts';\n@import 'layers';\n@import 'sizes';\n@import 'typography';\n\n:export {\n  // Any values that need to be accessible from JavaScript\n  // outside of a Vue component can be defined here, prefixed\n  // with `global-` to avoid conflicts with classes. For\n  // example:\n  //\n  // global-grid-padding: $size-grid-padding;\n  //\n  // Then in a JavaScript file, you can import this object\n  // as you would normally with:\n  //\n  // import design from '@design'\n  //\n  // console.log(design['global-grid-padding'])\n}\n"
  },
  {
    "path": "src/layouts/AppLayout.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <div>\n    <slot />\n  </div>\n</template>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/main.ts",
    "content": "import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport { createHead } from '@unhead/vue'\n\nimport App from '@/App.vue'\nimport router from '@/router/index'\n\nconst app = createApp(App)\n\n/** Pinia **/\n/** https://pinia.vuejs.org/ **/\nconst pinia = createPinia()\napp.use(pinia)\n\n/** Vue Router **/\n/** https://router.vuejs.org/ **/\napp.use(router)\n\n/** Unhead **/\n/** https://unhead.unjs.io/ **/\nconst head = createHead()\napp.use(head)\n\napp.mount('#app')\n"
  },
  {
    "path": "src/pages/about.vue",
    "content": "<script setup lang=\"ts\">\nuseHead({\n  title: 'About - Vue Enterprise Boilerplate',\n  meta: [\n    {\n      name: 'description',\n      content: 'The about page for Vue Enterprise Boilerplate!'\n    }\n  ]\n})\n</script>\n\n<template>\n  <div class=\"about\">\n    <h1>This is an about page</h1>\n  </div>\n</template>\n\n<style>\n@media (min-width: 1024px) {\n  .about {\n    min-height: 100vh;\n    display: flex;\n    align-items: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/pages/index.vue",
    "content": "<script setup lang=\"ts\">\nuseHead({\n  title: 'Home - Vue Enterprise Boilerplate',\n  meta: [\n    {\n      name: 'description',\n      content: 'Welcome to the Vue Enterprise Boilerplate!'\n    }\n  ]\n})\n</script>\n\n<template>\n  <AppLayout>\n    <h1>Home Page</h1>\n    <img alt=\"Vue logo\" class=\"logo\" src=\"@/assets/logo.svg\" width=\"125\" height=\"125\" />\n  </AppLayout>\n</template>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "src/router/index.ts",
    "content": "import { createRouter, createWebHistory } from 'vue-router'\nimport routes from './routes'\n\nconst router = createRouter({\n  history: createWebHistory(import.meta.env.BASE_URL),\n  routes\n})\n\nexport default router\n"
  },
  {
    "path": "src/router/routes.ts",
    "content": "export default [\n  {\n    path: '/',\n    name: 'home',\n    component: () => import('@/pages/index.vue')\n  },\n  {\n    path: '/about',\n    name: 'about',\n    // route level code-splitting\n    // this generates a separate chunk (About.[hash].js) for this route\n    // which is lazy-loaded when the route is visited.\n    component: () => import('@/pages/about.vue')\n  }\n]\n"
  },
  {
    "path": "src/stores/counter.ts",
    "content": "import { ref, computed } from 'vue'\nimport { defineStore } from 'pinia'\n\nexport const useCounterStore = defineStore('counter', () => {\n  const count = ref(0)\n  const doubleCount = computed(() => count.value * 2)\n  function increment() {\n    count.value++\n  }\n\n  return { count, doubleCount, increment }\n})\n"
  },
  {
    "path": "src/types.ts",
    "content": "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/**\n * It could be a ref, plain value, or getter function\n */\nexport type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T)\n"
  },
  {
    "path": "tsconfig.app.json",
    "content": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n  \"include\": [\"auto-imports.d.ts\", \"component.d.ts\", \"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n  \"exclude\": [\"src/**/__tests__/*\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    },\n    {\n      \"path\": \"./tsconfig.app.json\"\n    },\n    {\n      \"path\": \"./tsconfig.vitest.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "tsconfig.node.json",
    "content": "{\n  \"extends\": \"@tsconfig/node18/tsconfig.json\",\n  \"include\": [\"vite.config.*\", \"vitest.config.*\", \"cypress.config.*\", \"playwright.config.*\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"module\": \"ESNext\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "tsconfig.vitest.json",
    "content": "{\n  \"extends\": \"./tsconfig.app.json\",\n  \"exclude\": [],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"lib\": [],\n    \"types\": [\"node\", \"jsdom\"]\n  }\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { fileURLToPath, URL } from 'node:url'\n\nimport { defineConfig } from 'vite'\n\nimport vue from '@vitejs/plugin-vue'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport Components from 'unplugin-vue-components/vite'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [\n    vue(),\n    AutoImport({\n      // global imports to register\n      imports: ['vue', 'vue-router', '@vueuse/core', { '@unhead/vue': ['useHead'] }],\n      dirs: ['@src/composables']\n    }),\n    Components({\n      dirs: ['src/components', 'src/layouts']\n    })\n  ],\n  resolve: {\n    alias: {\n      '@': fileURLToPath(new URL('./src', import.meta.url))\n    }\n  },\n  server: {\n    port: 8080\n  },\n  css: {\n    preprocessorOptions: {\n      scss: {\n        additionalData: '@use \"@/design/index.scss\" as *;'\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { fileURLToPath } from 'node:url'\nimport { mergeConfig } from 'vite'\nimport { configDefaults, defineConfig } from 'vitest/config'\nimport viteConfig from './vite.config'\n\nexport default mergeConfig(\n  viteConfig,\n  defineConfig({\n    test: {\n      environment: 'jsdom',\n      exclude: [...configDefaults.exclude, 'e2e/*'],\n      root: fileURLToPath(new URL('./', import.meta.url)),\n      transformMode: {\n        web: [/\\.[jt]sx$/],\n      },\n    }\n  })\n)\n"
  }
]