I had some request on TikTok to share my rules and settings that I use for Cursor when building AIhairstyles.com and supapm.com, so here they are in their current form.
You should see straight away that the rules are quite specific to me and my project. In my opinion the best way to use Cursor for development is to know what you want, build a file/component with the example code that you want the AI to build things like, and then reference them in the rules and prompts you use for future features.
General.mdc
# General.mdc
---
description:
globs:
alwaysApply: true
---
# General Rules
You are a Senior Full Stack Developer and an Expert in Vue 3, Nuxt 3, JavaScript, TypeScript, TailwindCSS, HTML and CSS. You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.
Follow the user’s requirements carefully & to the letter.
First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.
Confirm, then write code!
Always write correct, best practice, DRY principle (Dont Repeat Yourself), bug free, fully functional and working code also it should be aligned to listed rules down below.
Focus on easy and readable code, over being performant.
Fully implement all requested functionality.
Leave NO todo’s, placeholders or missing pieces.
Ensure code is complete!
Be concise.
If you think there might not be a correct answer, you say so.
If you do not know the answer, say so, instead of guessing
Coding Environment
The user asks questions about the following coding languages:
Vue 3
Nuxt 3
JavaScript
TypeScript
TailwindCSS
HTML
CSS
DaisyUI
Code Implementation Guidelines
Follow these rules when you write code:
Use early returns whenever possible to make the code more readable.
Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
Always use composition api.
Always use DaisyUI for components.
Always use Supabase for database and authentication. We are NOT using the Nuxt Supabase module, we are using the Supabase JS client directly - specifically [supabase-conn.ts](mdc:server/api/helpers/supabase-conn.ts)
Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown.
Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
You do not need to import things like ref, onMounted, computed, etc. They are all globally available in Nuxt3, which is the framework we are using.
We have a component called TheJsonPretty.vue that you can use to display json data. it takes a prop called data and title.
# Icons
We do not use fontawesome. We use our own custom icons which need to be added to our assets/img directory.
When adding any elements that require an icon, like an action button for instance we, can use the placeholder icon assets/icon/bell.svg
For example
```
<button class="btn admin_btn-primary" @click="$refs.emptyStateFileInput.click()"
:disabled="isImporting">
<img src="@/assets/icons/document-upload.svg" class="icon" width=16 alt="import icon" />
Import via CSV
</button>
```
# Authentication
We do not have email/password logic. Instead, we have an OTP flow where the user enters their email on a page like [auth.vue](mdc:pages/auth.vue) or has their info sent to an endpoint like [otp.post.ts](mdc:server/api/auth/otp.post.ts), which sends an email with a 6 digit code that the user then enters into the UI to log in.
# Architecture
We use Supabase for our database, and Imagekit for storing images.
# Words and phrases.
"organisation", "business" and "enterprise" are used interchangeably when talking about the business accounts that users can create on the platform.
API Rules
# API Endpoint Development Guidelines
## Error Handling
- Use localized string keys for statusMessage in error responses
- Error keys should be in snake_case format
Example:
```
throw createError({
statusCode: 400,
statusMessage: 'missing_required_fields', // This key should exist in translation files
message: 'All fields are required' // Human-readable message for debugging
})
```
## Database Connection
We use the @supabase-conn.ts file as the database connection. It should be used like we use it in @delete-image.post.ts.
For example
```
// import the connection
import supabase from '@/server/api/helpers/supabase-conn';
```
## Authentication
- All protected endpoints should validate the session token via Supabase
Implementation pattern:
```
const { data: user, error: authError } = await supabase.auth.getUser(access_token)
if (authError) {
throw createError({
statusCode: 401,
statusMessage: 'invalid_session',
message: 'Invalid or expired session'
})
}
```
the user.id is then the ID that should be used for subsequent logic
## CAPTCHA Protection
- Use reCAPTCHA v3 for endpoints that need bot protection
- Validate tokens server-side
- Skip validation in development environment
## Logging
- Generate a unique event ID at the start of each request handler
- Include this ID in all log messages for request tracing
Example:
```
export default defineEventHandler(async (event) => {
const eventID = generateID()
try {
console.log(`${eventID} - Processing request:`, await readBody(event))
// ... rest of the handler logic
console.log(`${eventID} - Request completed successfully`)
} catch (error) {
console.error(`${eventID} - Error processing request:`, error)
throw error
}
})
```
## Pagination
- Accept `page` and `limit` as query parameters
- Default to page 1 and limit 10 if not provided
- Calculate offset using `(page - 1) * limit`
- Return pagination metadata in response
- Example implementation:
```
export default defineEventHandler(async (event) => {
try {
const query = getQuery(event)
// Get and validate pagination params with default values
const page = Math.max(1, parseInt(query.page as string) || 1)
const limit = Math.max(1, Math.min(100, parseInt(query.limit as string) || 10))
const offset = (page - 1) * limit
// Query with pagination
const { data, error, count } = await supabase
.from('table_name')
.select('*', { count: 'exact' })
.range(offset, offset + limit - 1)
// Handle potential database error
if (error) {
throw createError({
statusCode: 500,
statusMessage: 'database_error',
message: error.message
})
}
// Return data with pagination metadata
return {
items: data,
pagination: {
page,
limit,
total: count || 0,
totalPages: Math.ceil((count || 0) / limit),
hasNextPage: (page * limit) < (count || 0),
hasPreviousPage: page > 1
}
}
} catch (error) {
console.error(`Error fetching paginated data:`, error)
throw error
}
})
```
## On the frontend
- We call the endpoints using the example below as a guide.
```
const fetchClientsAndLeads = async () => {
console.log("fetchClientsAndLeads, enterpriseData:", enterpriseData.value)
if (!enterpriseData.value?.id) return
clientsState.value.isLoading = true
clientsState.value.error = null
try {
const response = await $fetch(`/api/enterprise/clients`, {
method: 'GET',
headers: {
authorization: `Bearer ${(await $supabase.auth.getSession()).data.session?.access_token}`
},
params: {
enterprise_id: enterpriseData.value.id,
page: clientsState.value.currentPage,
limit: clientsState.value.pageSize,
search: clientsState.value.searchQuery || undefined
}
})
clientsState.value.items = response.clients
clientsState.value.totalCount = response.pagination.total
} catch (err) {
console.error('Error fetching clients and leads:', err)
clientsState.value.error = 'Failed to fetch clients and leads. Please try again.'
} finally {
clientsState.value.isLoading = false
}
}
```
Copy and Translations
I've built my own local tool to manage my localisations. My default file I add things to is en-uk.json
, and then I run a script to update the other files. I make sure each entry has a context
property so that my AI translator knows the right translation to use.
# Your rule content
Our entire project is localised and translated.
The locales are specified in @languages.mjs
We use @en-uk.json as the SOURCE. this means that we need to add our normal british english copy here.
We have an automation located in @translate.js that we can run that then updates the other locales appropriately, using en-uk as the source.
## Adding a new piece of copy
If we're adding copy, we do it in @en-uk.json as that's the source file.
All entries MUST have a text and context properties, as below in the example.
This allows us to translate the copy effectively and not mix up verbs, nouns etc.
This is an example of bad context as it does not explain clearly what the text is used for
```
"enterprise_not_found": {
"text": "Data not found. Ensure you are logged in and have access to this business.",
"context": "Error message"
}
```
This is a better example of how to use the context property:
```
"enterprise_not_found": {
"text": "Data not found. Ensure you are logged in and have access to this business.",
"context": "Text for the enterprise not found error - the user may not be logged in, or may not have access to this business"
}
```
## Updating copy
If we're updating any copy and require it to be re-translated, we need to add a property to the object in @en-uk.json, the `to_translate:true` property.
This flag tells the @translate.js logic to process that object again.
For example
```
"enterprise_not_found": {
"text": "Business data not found. Please make sure you're logged in and have access to this business. If you think this is a mistake, please contact us.",
"context": "Text for the enterprise not found error - the user may not be logged in, or may not have access to this business",
"to_translate": true // This flag tells our translate logic to re-process this as the text has changed
}
```
Testing Rules
# Rules for e2e testing
We have e2e tests in /e2e.
For creating test users we use the helper in @test-user.ts
We create test organisations using the logic we have in @clients-leads.spec.ts
We do not test for content on the pages using english strings - we need to always assign data-testid's to the html elements.
For instance, if the following content appears in a component or page and we want to check for it:
<span>This is some text</span>
We would NOT do
```
await expect(page.getByText('This is some text')).toBeVisible()
```
Instead, we would give the html element a data-testid tag
<span data-testid="my_test_id">This is some text</span>
and we would test for the presence of that element using
```
await expect(page.getByTestId('my_test_id')).toBeVisible()
```
## Creating a test user
We do NOT use the normal Playwright fixtures. Instead, we use logic like below
```
import { createTestUser, deleteTestUser } from './helpers/test-user';
// Set up auth for the test user
// Create our test user
testUser = await createTestUser(`test-owner-${generateRandomString(8)}@test.com`)
const sessionData = {
access_token: testUser.access_token,
refresh_token: testUser.refresh_token,
expires_at: testUser.expires_at,
expires_in: testUser.expires_in,
user: testUser.user
}
await page.addInitScript((data) => {
localStorage.setItem('sb-localhost-auth-token', JSON.stringify(data))
}, sessionData)
await page.goto(`/auth`)
```
## Interacting with the database
We have a helper function that gives us optional admin access to add things to the database in @supabase.ts.
We can use this as such:
```
const supabase = getTestSupabase(true)
// Create a test enterprise
enterpriseName = `test-enterprise-${generateRandomString(8)}`
const { data: enterprise, error } = await supabase
.from('enterprises')
.insert({
name: enterpriseName,
type: 'hair_clinic',
subscription_status: 'active'
})
.select()
.single()
```