Files
crewli/dev-docs/MASTER_PROMPT_CURSOR.md
2026-04-14 19:04:19 +02:00

7.4 KiB

Crewli — Cursor Master Prompt

Plak dit BOVEN elke task. Vervang [TASK] onderaan.

MANDATORY PREAMBLE

Read @CLAUDE.md and @dev-docs/SCHEMA.md before starting. Use @workspace for full codebase context. These documents are your source of truth.

If modifying existing code, read the current implementation first. Understand the patterns already in use before writing anything new.

PROJECT CONTEXT

Crewli — multi-tenant SaaS for event/festival management.

  • Frontend: Vue 3 + TypeScript + Vuexy 9.5 (Vuetify 3.10) + Pinia + TanStack Vue Query + VeeValidate + Zod
  • Three SPAs:
    • apps/admin/ — Super Admin (port 5173)
    • apps/app/ — Organizer main app (port 5174)
    • apps/portal/ — Dual-mode portal (port 5175)
  • API base: VITE_API_URL from .env.local (default http://localhost:8000)
  • Axios instance: src/lib/axios.ts — this is the ONLY axios instance. Never create another. Never import axios directly in components.
  • Backend Enums: PHP Enums in api/app/Enums/ define all valid status/type values. TypeScript equivalents must mirror these exactly in src/types/.

Frontend file structure (per app)

src/
├── lib/axios.ts              # Singleton axios instance (DO NOT DUPLICATE)
├── types/                     # TypeScript interfaces per module
│   └── [module].ts
├── composables/api/           # TanStack Query composables per module
│   └── use[Module].ts
├── stores/                    # Pinia stores (cross-component state only)
│   └── use[Module]Store.ts
├── pages/                     # Page components
│   └── [module]/
│       ├── index.vue          # List view
│       └── [id].vue           # Detail view (or side panel)
└── router/                    # Vue Router config

ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS

TypeScript & typing

  1. NO any TYPES. EVER. Every variable, prop, emit, return type, ref, reactive, computed, and API response is fully typed. If you don't know the type, check the backend API Resource in api/app/Http/Resources/ — that defines the contract. Create matching interfaces in src/types/[module].ts.

  2. TYPES FIRST. Before writing any composable or component for a new module, create the TypeScript interfaces in src/types/[module].ts. These mirror the backend API Resource shape. Include:

    • Entity interface (e.g., Shift, Person, Event)
    • Create/Update DTOs (matching backend Form Request fields)
    • Enum-like union types or const objects matching backend PHP Enums
    • Paginated response wrapper if applicable
  3. ENUMS AS CONST OBJECTS. Mirror backend PHP Enums as TypeScript const objects with as const, not as loose string unions scattered across files:

    // src/types/shift.ts
    export const ShiftAssignmentStatus = {
      PENDING_APPROVAL: 'pending_approval',
      APPROVED: 'approved',
      REJECTED: 'rejected',
      CANCELLED: 'cancelled',
      COMPLETED: 'completed',
    } as const
    export type ShiftAssignmentStatus = typeof ShiftAssignmentStatus[keyof typeof ShiftAssignmentStatus]
    

Architecture & patterns

  1. NO DIRECT AXIOS IN COMPONENTS. All API calls go through composables in composables/api/use[Module].ts using TanStack Query (useQuery / useMutation). Components never import axios or src/lib/axios.ts. The composable is the only layer that knows about HTTP.

  2. NO OPTIONS API. Always <script setup lang="ts">. Props via defineProps<{...}>(), emits via defineEmits<{...}>(), expose via defineExpose(). No export default { ... }.

  3. NO PROP DRILLING. If state needs to cross more than one component boundary, use a Pinia store (src/stores/use[Module]Store.ts). Access reactive state via storeToRefs(). Mutations only through store actions.

  4. DELETE OVER ADAPT. If you find duplicate composables, overlapping stores, or conflicting patterns: delete the worse version. Do not build alongside existing code that does the same thing. One implementation per concern.

  5. CONSISTENCY OVER CLEVERNESS. Look at how existing modules handle the same pattern (table views, form dialogs, detail panels, error handling). Use the exact same approach. Never introduce a "better" alternative without refactoring ALL existing modules to match.

  6. SINGLE SOURCE OF TRUTH. Don't duplicate:

    • Validation logic → Zod schema mirrors backend Form Request. One schema per form, referenced by VeeValidate.
    • Enum values → const objects in src/types/, matching backend Enums. Never use raw string literals like 'approved' in templates or logic.
    • API URLs → constructed in composable from module prefix, never hardcoded in components.

UI & UX

  1. NO CUSTOM CSS WHERE VUETIFY HAS A SOLUTION. Before writing any CSS: check if a Vuetify utility class, component prop, or slot achieves the result. Custom CSS is a last resort, must be <style scoped>, and must have a comment explaining why Vuetify couldn't handle it.

  2. EVERY PAGE HAS THREE STATES:

    • Loading: Vuetify skeleton loader or progress indicator
    • Error: User-facing message with retry action (v-alert with retry button). Show what went wrong in user terms, not technical jargon.
    • Empty: Helpful message explaining why the list is empty and what action to take (not a blank white screen). Never show only the happy path.
  3. MOBILE-FIRST. Every component must be usable at 375px width. Use Vuetify's responsive props (cols, sm, md, lg on v-col). Never use fixed pixel widths for layout. Tables on mobile → card views or horizontal scroll with visual indicator.

  4. FORMS USE VEEVALIDATE + ZOD. Define the Zod schema matching the backend Form Request rules. No inline validation logic in templates. Use useForm() and useField() from VeeValidate with the Zod resolver.

Code quality

  1. NO TODO / FIXME / HACK. Complete the implementation or report the blocker. No stubs, no "implement later", no placeholder components.

  2. NO ORPHANED IMPORTS OR UNUSED VARIABLES. Run npx tsc --noEmit after every change. Zero errors, zero warnings. Dead code is deleted immediately, not commented out.

  3. NO GENERIC NAMES. Component and composable names must be specific:

    • DataTable.vue, useApi.ts, helpers.ts, utils.ts
    • ShiftAssignmentTable.vue, useShifts.ts, formatShiftTimeRange.ts

Routing & auth

  1. ROUTER GUARDS. Protected routes check auth state via navigation guard. Portal routes validate token. No unguarded routes to authenticated content. Redirect to login on 401.

Process

  1. COMPONENT CREATION ORDER. For every new page/feature:

    1. TypeScript types in src/types/[module].ts
    2. API composable in src/composables/api/use[Module].ts
    3. Pinia store in src/stores/use[Module]Store.ts (if cross-component)
    4. Vue page component in src/pages/[module]/
    5. Router entry in src/router/
  2. VERIFY BEFORE DONE:

    • npx tsc --noEmit — zero errors
    • No any types anywhere (search: grep -rn ": any\|as any" src/)
    • No TODO/FIXME/HACK
    • All three states (loading, error, empty) implemented
    • Mobile responsive at 375px
    • Consistent with existing module patterns

TASK

[INSERT SPECIFIC TASK HERE]