# Coding Conventions — Vue 3 / Nuxt 4 (Nitro) / Node.js
These rules are mandatory for all agents and developers. For high-level agent behavior, see `AGENTS.md`.
## 1. General Principles
- Be conservative, explicit, and boring. Prefer the predictable solution.
- When unsure, ask — don't guess.
- Make minimal, targeted changes. Never refactor unrelated code.
- Preserve existing structure, conventions, and tooling.
- TypeScript strict mode (`strict: true` + `noUncheckedIndexedAccess`).
- Composition API + `
```
## 5. Nitro / Server (Backend)
- API routes: `server/api/**/*.ts` (file-based).
- Validate all incoming data with Zod at the boundary (start of the handler).
- Business logic lives in `server/utils/` or `server/services/`.
- Handlers stay thin: validate → call service → return.
- Errors: use `apiError()` from `~~/server/utils/api/error`, never raw `createError`.
- No side effects at module top level.
- Schemas: import from `~~/shared/schemas`, not from `server/utils/schemas`.
### Error format
All API errors use a standard envelope:
```ts
import { apiError } from '~~/server/utils/api/error'
throw apiError(404, 'IDEA_NOT_FOUND', 'Идея не найдена')
throw apiError(409, 'IDEA_LIMIT_REACHED', 'Достигнут лимит', { activeCount: 10 })
```
Response: `{ error: { code: string, message: string, details?: unknown } }`
### Example API route
```ts
// server/api/users/[id].get.ts
import { parseUuid } from '~~/shared/schemas'
import { getUserById } from '~~/server/utils/users'
export default defineEventHandler(async (event) => {
const id = parseUuid(getRouterParam(event, 'id'))
return getUserById(id)
})
```
## 6. TypeScript
- `interface` for object shapes and public API shapes.
- `type` for unions, utility types, mapped types.
- Explicit return types on all exported functions.
- `import type` for type-only imports.
- Prefer `readonly` / `ReadonlyArray` where practical.
- Narrow with type guards; avoid `as` assertions and `!` except as a last resort.
- Prefer exhaustive handling of unions with `never` checks.
- Treat caught errors as `unknown` and narrow before use.
## 7. Async & Error Handling
- `async/await` only. Floating promises are forbidden.
- Rethrow with context; preserve `cause` when available. Never throw strings.
- Never swallow rejections or errors.
- Client: handle via `useError` / `showError` or the project error boundary.
- Server: `createError({ statusCode, statusMessage, data })`.
## 8. Runtime & Environment (Node.js)
- Target the repo's supported Node LTS (check config/docs; don't assume versions).
- No top-level side effects (I/O, network, env reads, global mutations) unless explicitly intended.
- Env vars: validate centrally once at startup via Zod (`server/utils/env.ts`); read at runtime, not import time; never mutate env in app code (tests only, with scoped setup/teardown).
- Library code must not log. CLIs may log intentionally, with consistent exit codes.
## 9. Logging & Security
- Never log secrets (tokens, keys, passwords, personal data).
- Validate/sanitize all external inputs: paths, URLs, user data (Zod at boundaries).
- No `console.log` in production code.
## 10. Testing (Vitest)
- New business logic requires tests. Exceptions: types-only code, re-exports, comments/formatting.
- Tests must be deterministic and isolated; no shared mutable state.
- Prefer behavioral tests; mock sparingly.
- Cover failure paths, not only happy paths.
- Bug fixes must include a regression test.
- No committed `.only` / `.skip` unless explicitly justified.
- Avoid snapshots unless they add clear value and are stable.
- Unit: Vitest. Components: `@vue/test-utils` + Vitest. API: `nitro-test` or plain HTTP tests.
- Tests are co-located: `*.test.ts` / `*.spec.ts`.
## 11. Accessibility (a11y)
Accessibility is enforced at three levels. All three must pass before merging frontend changes.
### Editor-time: eslint-plugin-vuejs-accessibility
ESLint rules catch common a11y mistakes in Vue templates as you type:
- Missing `alt` on `
`
- Missing labels on form controls
- Click events without keyboard equivalents
- Invalid ARIA attributes
Run with `pnpm lint`. Fix all `vuejs-accessibility/*` errors before committing.
### Runtime: @nuxt/a11y (DevTools)
The `@nuxt/a11y` module runs axe-core in the browser during development:
- Open Nuxt DevTools → "Nuxt a11y" tab
- Click "Scan" to check the current page
- Violations are grouped by severity (critical → minor)
- Click a violation to highlight affected elements with numbered badges
- Enable "Auto-Scan" for continuous monitoring
Use this when building new pages or components — catch issues visually before they reach CI.
### CI: @axe-core/playwright
Automated regression tests in `e2e/accessibility.spec.ts`:
- Run via `pnpm e2e`
- Tests scan key pages (homepage, ideas list, idea detail) against WCAG 2.0/2.1 AA
- Any violation fails the test — blocks merge
When adding a new page, add a corresponding test:
```ts
test('new-page has no accessibility violations', async ({ page }) => {
await page.goto('/new-page')
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice'])
.analyze()
expect(results.violations).toEqual([])
})
```
## 12. Comments & Docs
- Update docs/comments whenever behavior changes.
- Comments explain "why", not "what".
## 12. Dependencies
- Never add dependencies without explicit approval.
- A justification must cover: need, alternatives, maintenance burden, license, security impact.
## 13. Strictly Prohibited
- Options API
- `any`, `@ts-ignore`, unjustified `// @ts-expect-error`
- Default exports in components and utilities (except pages/layouts)
- Editing already-applied migrations
- Adding dependencies without approval
- `console.log` in production code
- Deep relative imports (`../../../`)
- Module-level side effects
- Duplicating types between `app/` and `server/`
- Changing public APIs or introducing breaking changes without explicit instruction
- Stylistic rewrites and micro-optimizations
## 14. Verify Before Committing
- Typecheck + lint + tests pass.
- New behavior has coverage, including failure paths.
- No unintended snapshot changes.
- No unnecessary diff churn.
- No accidental top-level side effects.
- Env usage is validated and intentional.