A Vite-powered Vue 3 + TypeScript scaffold configured for building the Tonal Scale progressive web app. The project includes Tailwind CSS, Headless UI, Pinia, Vue Router, Vee-Validate, Vue I18n, VueUse, Lodash, and Luxon out of the box.
- Getting started
- Scripts
- Linting
- Formatting
- Testing
- Continuous integration
- Tech stack
- Progressive web app (PWA) setup
- Form validation
- Localization
- State management
- Environment variables
- Module resolution and aliases
- Documentation
npm install
npm run watchThe legacy scaffolding demo now lives at /scaffolding-demo. The root path currently redirects there until the tonal builder owns the home experience. The page now showcases Tailwind utility surfaces and Headless UI primitives instead of the previous Vuetify sample.
npm run watch– start the local development servernpm run build– produce a production buildnpm run preview– preview the production build locallynpm run lint– run ESLint and Stylelint checks across scripts and stylesnpm run lint:fix– apply autofixes for both ESLint and Stylelintnpm run type-check– runvue-tscwith--noEmitto verify component and script typingsnpm run test– execute Vitest unit tests oncenpm run test:watch– run Vitest in watch mode for rapid feedbacknpm run test:coverage– generate coverage reports using v8 providernpm run test:ci– run Vitest once using a dot reporter for compact CI outputnpm run cypress:install– download/verify the Cypress binary locally if it is missingnpm run cypress:open– install the binary if needed, then launch Cypress in interactive mode after auto-starting the Vite dev servernpm run cypress:run– ensure the Cypress binary exists, then run headlessly after auto-starting the Vite dev servernpm run format– format source, config, and documentation files with Prettiernpm run format:check– verify Prettier formatting without writing changesnpm run check– sequentially run linting, type-checking, and CI-friendly unit testsnpm run ci– run linting, type-checking, CI-friendly unit tests, and build production assets (mirrors the CI workflow)
The project uses Airbnb-flavored ESLint with Vue 3 + TypeScript support and Prettier compatibility, plus Stylelint for CSS and
Vue single-file components. Run npm install first so ESLint can resolve dependencies like @headlessui/vue and @heroicons/vue
referenced in the UI samples.
# Check code quality
npm run lint
# Automatically fix what can be resolved safely
npm run lint:fixPrettier enforces consistent formatting across TypeScript, Vue SFCs, styles, JSON, and documentation. Ignore rules are defined in .prettierignore to skip build artifacts.
# Format all supported files in the repository
npm run format
# Check formatting without making changes
npm run format:checkGit commit hooks use Husky and lint-staged to auto-format staged changes. Hooks install automatically after npm install (via the prepare script). Temporarily bypass a hook run with:
HUSKY=0 git commit -m "your message"Re-enable hooks by unsetting HUSKY or running npm run prepare to reinstall Husky if needed.
Vitest is configured with a jsdom environment and Vue Test Utils for component rendering. Global setup in src/tests/setup.ts registers Vue I18n and validation so components can mount without additional boilerplate.
# Run the full unit test suite once
npm run test
# Start Vitest in watch mode
npm run test:watch
# Produce coverage artifacts in coverage/unit
npm run test:coverageImport application modules using the @/ alias inside tests (e.g., @/views/demo/ScaffoldingDemoView.vue).
Cypress is configured for both E2E and component testing using Vite + Vue bundling. Specs live under cypress/e2e and use
data-cy selectors for stability (see cypress/e2e/smoke.cy.ts for a shell check of the scaffolding demo). Component tests
mount
Vue files with base plugins already registered via cypress/support/component.ts.
# Run headless E2E specs with an auto-started dev server on :5173
npm run cypress:run
# For interactive authoring with an auto-started dev server on :5173
npm run cypress:openIf you see an error about a missing Cypress binary, run npm run cypress:install (already invoked by the scripts above) to
download or repair the local cache before launching the test runner.
The Cypress TypeScript config (tsconfig.cypress.json) mirrors application path aliases so specs can import using @/ when
needed.
GitHub Actions runs the core checks in .github/workflows/ci.yml on pushes to main and all pull requests. The workflow:
- Caches npm dependencies via
actions/setup-nodewithcache: npmfor faster installs. - Installs dependencies with
npm ci. - Executes
npm run lint,npm run type-check,npm run test:ci, andnpm run build.
- Use
npm run checklocally to mirror the CI quality gates without performing a production build. - Use
npm run cilocally if you want to replicate the full CI sequence including the production build. - Cypress remains opt-in for CI to keep the stub lightweight; trigger
npm run cypress:runlocally when working on E2E specs.
- Vite + Vue 3 + TypeScript
- Tailwind CSS + Headless UI
- Pinia, Vue Router, Vue I18n
- Vee-Validate + Yup
- VueUse utilities, Lodash helpers, Luxon date handling
- PWA-ready Vite configuration
- Theme tokens for the tonal builder (colors, fonts, and shadows) live in
tailwind.config.ts. - Global base styles and utility-friendly surfaces are defined in
src/styles/main.css. - Vuetify has been removed in favor of Tailwind + Headless UI primitives to keep the scaffold lightweight.
- The Vite PWA plugin is enabled in
vite.config.tswithautoUpdateservice worker registration. Static assets (favicons, robots.txt, placeholder icons) are bundled frompublic/. - Web app manifest values (name, theme color, start URL, orientation, etc.) and icons live in the same
config under
manifest. Replace the placeholder SVG inpublic/pwa-icon.svgwith production-ready artwork. If marketplaces require PNGs, derive square and maskable variants from the SVG before submission. - Service worker registration happens during app bootstrap via
src/plugins/pwa.ts(invoked insrc/main.ts). Update hooks such asonNeedRefreshcurrently log messages and should be wired to a user-facing toast/dialog when UX requirements arrive. - Caching stays minimal by default (Workbox runtime caching is intentionally empty). Add explicit strategies
in
vite.config.tsonce data-handling policies are defined; avoid caching sensitive endpoints.
-
Global Vee-Validate settings live in
src/plugins/validation.tsand run during app bootstrap (src/main.ts). -
Define schemas with Yup and wrap them with the shared helper:
import { object, string } from 'yup'; import { buildValidationSchema } from '@/utils/validation'; const validationSchema = buildValidationSchema( object({ fullName: string().required('This field is required.'), email: string().required('This field is required.').email('Enter a valid email.'), }), );
-
Convert Vee-Validate error strings into array-friendly output with
toErrorMessages:const errors = toErrorMessages(result.errors?.email);
-
See
src/components/forms/ValidationSampleForm.vuefor a full example that wires the schema intouseForm, uses data-cy selectors for testing, and surfaces translated copy through Vue I18n.
- Vue I18n is initialized in
src/plugins/i18n.tswith the default locale set to English (en) and fallbacks enabled. Supported locales and TypeScript-safe keys live insrc/locales, with sample dictionaries for English and French. - Locale messages are organized by namespace (e.g.,
home.localization,i18n.switcher). Messages for English (en) and French (fr) are bundled eagerly to avoid chunking warnings during the production build. useLocale(src/composables/useLocale.ts) exposescurrentLocale,availableLocales, andswitchLocalehelpers. Switching a locale loads messages on demand and persists the choice withlocalStorageusing thepp_localekey.LocaleSwitcher(src/components/i18n/LocaleSwitcher.vue) demonstrates reading translated strings and updating the active locale in the Home view.- To add a locale:
- Create a new locale file in
src/localesand add it to themessagesexport. - Define translations for all existing keys (
enandfrmust be updated together). - If testing with Vitest, mount components normally; the global test setup already installs the I18n plugin. For locale persistence logic,
pass stubbed storage objects to
switchLocaleorsetLocalein unit tests.
- Create a new locale file in
-
Pinia is registered globally from
src/plugins/pinia.tsinsrc/main.ts, keeping the app entry light while enabling Vue Devtools integration. -
Stores live under
src/storesand should follow theuseXStorenaming convention with descriptive, non-domain-specific namespaces (e.g.,useAppStore). -
Prefer TypeScript-first options stores to make state, getters, and actions explicit. Use discriminated unions or literal types for constrained values when possible.
-
Enable hot module replacement by wrapping stores with
acceptHMRUpdatewhen using Vite:if (import.meta.hot) { import.meta.hot.accept(acceptHMRUpdate(useAppStore, import.meta.hot)); }
-
When testing stores, activate an isolated Pinia instance per spec to avoid shared state:
import { createPinia, setActivePinia } from 'pinia'; beforeEach(() => { setActivePinia(createPinia()); });
-
Import stores via the
@/alias (e.g.,@/stores/app) so refactors remain path-stable.
- Copy
.env.exampleto.env(or.env.local) and set values for your environment. Only variables prefixed withVITE_are exposed to the client bundle. VITE_APP_BASE_PATHcontrols the deployed base path for assets and routing. If unset, the project defaults to/.VITE_API_BASE_URLillustrates how to document app-specific endpoints without committing secrets.
- Path aliases are configured for both TypeScript and Vite. Import application code using the
@/prefix (e.g.,@/routeror@/styles/main.css) instead of long relative paths.
Additional reference documents live under _DOC, organized by focus area:
- Architecture: Routing Foundation – routing structure and navigation guards.
- Guides: AGENTS.md Playbook – instruction design best practices for maintaining repo guardrails.
- Standards: I18n Key Naming Standard – localization key conventions.
Consult these for deeper architectural or product context beyond this README.