Files
crewli/.cursor/rules/101_vue.mdc
bert.hausmans d82cf42728 chore(rules): rewrite 101_vue.mdc as slim principles file
Drops 17 KB of embedded code templates that had drifted from actual
implementations in apps/app/src/ (auth store template still used
localStorage; portal router guards still showed dual-mode logic that
was consolidated to /portal/* routes within apps/app in PR-B1/B2a).

Slim rewrite: principles + file structure + pointers to actual
reference code in apps/app/src/. Globs narrowed to apps/app/**/*
since apps/portal/ no longer exists. Vuexy component selection
deferred to dev-docs/VUEXY_COMPONENTS.md as canonical registry.

Net: ~17 KB -> ~3 KB, less drift surface, points at living code
instead of duplicating it.
2026-05-06 01:50:39 +02:00

86 lines
3.5 KiB
Plaintext

---
description: Vue 3, TypeScript, and Vuexy patterns for Crewli
globs: ["apps/app/**/*.{vue,ts,tsx}"]
alwaysApply: true
---
# Vue + Vuexy Rules
## Core Principles
1. **Composition API only** — always `<script setup lang="ts">`, never Options API
2. **No `any` types** — use proper typing or `unknown` + narrowing
3. **TanStack Query for API** — never raw axios in components
4. **Pinia for cross-component client state** — server data lives in TanStack Query, never duplicated in stores
5. **Vuetify components first** — custom CSS only when no Vuetify class fits the use case
6. **VeeValidate + Zod** for all form validation
7. **Mobile-first** — minimum 375px width, responsive at every breakpoint
## File structure
```
apps/app/src/
├── lib/axios.ts # Single axios instance (do not duplicate)
├── composables/api/use*.ts # TanStack Query composables (one per resource)
├── stores/use*Store.ts # Pinia stores — client state only
├── types/*.ts # TypeScript interfaces (mirror backend Resources)
├── pages/ # File-based routing via unplugin-vue-router
├── layouts/ # Layout components
├── components/ # Reusable components
└── @core/ # Vuexy core — DO NOT MODIFY
```
## Reference patterns (read these for templates)
For working examples in the actual codebase:
- **Composable pattern:** `apps/app/src/composables/api/useEvents.ts`
- **Pinia store pattern:** `apps/app/src/stores/useAuthStore.ts`
- **Page pattern:** `apps/app/src/pages/events/index.vue`
- **Form pattern:** `apps/app/src/components/events/CreateEventDialog.vue`
- **Layout pattern:** `apps/app/src/layouts/OrganizerLayout.vue`
For Vuexy component selection, consult `dev-docs/VUEXY_COMPONENTS.md` — the registry of @core wrappers and patterns. Always check that registry before writing a custom component.
For auth and routing, see `dev-docs/AUTH_ARCHITECTURE.md` (httpOnly cookies, dual-axios for portal-token routes, route guard logic).
## Strict rules
### TypeScript
- Use `import type { ... }` for type-only imports
- Mirror backend PHP Enums as const objects with `as const` in `apps/app/src/types/`
- Generic API response shape: `{ data: T, meta?: PaginationMeta }`
### Architecture
- Components never import axios directly — always via composables
- Composables call axios via the singleton in `apps/app/src/lib/axios.ts`
- Mutations invalidate query keys after success
- No prop drilling — use Pinia stores when state crosses two component boundaries
### UI
- Three states for every list view: **loading** (VSkeletonLoader), **error** (VAlert with retry button), **empty** (helpful message explaining what action to take)
- Custom CSS forbidden when a Vuetify utility class exists
- Tables on mobile (<768px) collapse to VList or card view — never horizontal scroll without a visual indicator
### Forms
- Zod schema mirrors backend FormRequest validation
- Errors shown inline via VeeValidate's `errors` object
- Submit button disabled while `isPending`
### Routing
- File-based routing via unplugin-vue-router
- Guards in `apps/app/src/plugins/1.router/guards.ts`
- Portal routes are at `/portal/*` (within apps/app), NOT a separate SPA
- Platform admin routes are at `/platform/*`, gated by `super_admin` role
## Avoid
- Options API (`export default { ... }`)
- `any` types
- Raw axios calls in components
- Inline styles
- Direct DOM manipulation
- Mutating props
- Custom CSS when a Vuetify class exists
- Hardcoded URLs or string-literal status values