From bb02df5f69bc5c359e0bcd1137cf532bdcc57613 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 20 Jan 2026 09:25:29 +1000 Subject: [PATCH] Update Cursor rules structure (#41338) * update and expand cursor rules structure * rule copy * rule updates --- .cursor/rules/docs-graphql.mdc | 267 ------------ .cursor/rules/docs-test-requirements.mdc | 71 --- .../docs-embeddings-generation/RULE.md} | 21 +- .cursor/rules/docs/docs-graphql/RULE.md | 132 ++++++ .../rules/docs/docs-test-requirements/RULE.md | 25 ++ .cursor/rules/studio-ui.mdc | 409 ------------------ .cursor/rules/studio/RULE.md | 33 ++ .cursor/rules/studio/alerts/RULE.md | 13 + .../best-practices/RULE.md} | 21 +- .cursor/rules/studio/cards/RULE.md | 14 + .cursor/rules/studio/charts/RULE.md | 26 ++ .cursor/rules/studio/component-system/RULE.md | 16 + .cursor/rules/studio/empty-states/RULE.md | 25 ++ .cursor/rules/studio/forms/RULE.md | 35 ++ .cursor/rules/studio/layout/RULE.md | 28 ++ .cursor/rules/studio/navigation/RULE.md | 19 + .../rules/studio/project-structure/RULE.md | 19 + .cursor/rules/studio/queries/RULE.md | 113 +++++ .cursor/rules/studio/sheets/RULE.md | 23 + .cursor/rules/studio/styling/RULE.md | 16 + .cursor/rules/studio/tables/RULE.md | 29 ++ .../e2e-studio/RULE.md} | 4 +- .../rules/testing/unit-integration/RULE.md | 10 + .cursor/rules/unit-integration-testing.mdc | 6 - 24 files changed, 603 insertions(+), 772 deletions(-) delete mode 100644 .cursor/rules/docs-graphql.mdc delete mode 100644 .cursor/rules/docs-test-requirements.mdc rename .cursor/rules/{docs-embeddings-generation.md => docs/docs-embeddings-generation/RULE.md} (79%) create mode 100644 .cursor/rules/docs/docs-graphql/RULE.md create mode 100644 .cursor/rules/docs/docs-test-requirements/RULE.md delete mode 100644 .cursor/rules/studio-ui.mdc create mode 100644 .cursor/rules/studio/RULE.md create mode 100644 .cursor/rules/studio/alerts/RULE.md rename .cursor/rules/{studio-best-practices.mdc => studio/best-practices/RULE.md} (97%) create mode 100644 .cursor/rules/studio/cards/RULE.md create mode 100644 .cursor/rules/studio/charts/RULE.md create mode 100644 .cursor/rules/studio/component-system/RULE.md create mode 100644 .cursor/rules/studio/empty-states/RULE.md create mode 100644 .cursor/rules/studio/forms/RULE.md create mode 100644 .cursor/rules/studio/layout/RULE.md create mode 100644 .cursor/rules/studio/navigation/RULE.md create mode 100644 .cursor/rules/studio/project-structure/RULE.md create mode 100644 .cursor/rules/studio/queries/RULE.md create mode 100644 .cursor/rules/studio/sheets/RULE.md create mode 100644 .cursor/rules/studio/styling/RULE.md create mode 100644 .cursor/rules/studio/tables/RULE.md rename .cursor/rules/{e2e-testing.mdc => testing/e2e-studio/RULE.md} (98%) create mode 100644 .cursor/rules/testing/unit-integration/RULE.md delete mode 100644 .cursor/rules/unit-integration-testing.mdc diff --git a/.cursor/rules/docs-graphql.mdc b/.cursor/rules/docs-graphql.mdc deleted file mode 100644 index d8bcbc5d932..00000000000 --- a/.cursor/rules/docs-graphql.mdc +++ /dev/null @@ -1,267 +0,0 @@ ---- -description: Docs GraphQL Architecture -globs: apps/docs/resources/**/*.ts -alwaysApply: false ---- - -# Docs GraphQL Architecture - -## Overview - -The `/apps/docs/resources` folder contains the GraphQL endpoint architecture for the docs GraphQL endpoint at `/api/graphql`. It follows a modular pattern where each top-level query is organized into its own folder with consistent file structure. - -## Architecture Pattern - -Each GraphQL query follows this structure: - -``` -resources/ -├── queryObject/ -│ ├── queryObjectModel.ts # Data models and business logic -│ ├── queryObjectSchema.ts # GraphQL type definitions -│ ├── queryObjectResolver.ts # Query resolver and arguments -│ ├── queryObjectTypes.ts # TypeScript interfaces (optional) -│ └── queryObjectSync.ts # Functions for syncing repo content to the database (optional) -├── utils/ -│ ├── connections.ts # GraphQL connection/pagination utilities -│ └── fields.ts # GraphQL field selection utilities -├── rootSchema.ts # Main GraphQL schema with all queries -└── rootSync.ts # Root sync script for syncing to database -``` - -## Example queries - -1. **searchDocs** (`globalSearch/`) - Vector-based search across all docs content -2. **error** (`error/`) - Error code lookup for Supabase services -3. **schema** - GraphQL schema introspection - -## Key Files - -### `rootSchema.ts` -- Main GraphQL schema definition -- Imports all resolvers and combines them into the root query -- Defines the `RootQueryType` with all top-level fields - -### `utils/connections.ts` -- Provides `createCollectionType()` for paginated collections -- `GraphQLCollectionBuilder` for building collection responses -- Standard pagination arguments and edge/node patterns - -### `utils/fields.ts` -- `graphQLFields()` utility to analyze requested fields in resolvers -- Used for optimizing data fetching based on what fields are actually requested - -## Creating a New Top-Level Query - -To add a new GraphQL query, follow these steps: - -### 1. Create Query Folder Structure -```bash -mkdir resources/newQuery -touch resources/newQuery/newQueryModel.ts -touch resources/newQuery/newQuerySchema.ts -touch resources/newQuery/newQueryResolver.ts -``` - -### 2. Define GraphQL Schema (`newQuerySchema.ts`) -```typescript -import { GraphQLObjectType, GraphQLString } from 'graphql' - -export const GRAPHQL_FIELD_NEW_QUERY = 'newQuery' as const - -export const GraphQLObjectTypeNewQuery = new GraphQLObjectType({ - name: 'NewQuery', - description: 'Description of what this query returns', - fields: { - id: { - type: GraphQLString, - description: 'Unique identifier', - }, - // Add other fields... - }, -}) -``` - -### 3. Create Data Model (`newQueryModel.ts`) - -> [!NOTE] -> The data model should be agnostic to GraphQL. It may import argument types -> from `~/__generated__/graphql`, but otherwise all functions and classes -> should be unaware of whether they are called for GraphQL resolution. - -> [!TIP] -> The types in `~/__generated__/graphql` for a new endpoint will not exist -> until the code generation is run in the next step. - -```typescript -import { type RootQueryTypeNewQueryArgs } from '~/__generated__/graphql' -import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils' -import { Result } from '~/features/helpers.fn' -import { supabase } from '~/lib/supabase' - -export class NewQueryModel { - constructor(public readonly data: { - id: string - // other properties... - }) {} - - static async loadData( - args: RootQueryTypeNewQueryArgs, - requestedFields: Array - ): Promise> { - // Implement data fetching logic - const result = new Result( - await supabase() - .from('your_table') - .select('*') - // Add filters based on args - ) - .map((data) => data.map((item) => new NewQueryModel(item))) - .mapError(convertPostgrestToApiError) - - return result - } -} -``` - -### 4. Create Resolver (`newQueryResolver.ts`) -```typescript -import { GraphQLError, GraphQLNonNull, GraphQLString, type GraphQLResolveInfo } from 'graphql' -import { type RootQueryTypeNewQueryArgs } from '~/__generated__/graphql' -import { convertUnknownToApiError } from '~/app/api/utils' -import { Result } from '~/features/helpers.fn' -import { graphQLFields } from '../utils/fields' -import { NewQueryModel } from './newQueryModel' -import { GRAPHQL_FIELD_NEW_QUERY, GraphQLObjectTypeNewQuery } from './newQuerySchema' - -async function resolveNewQuery( - _parent: unknown, - args: RootQueryTypeNewQueryArgs, - _context: unknown, - info: GraphQLResolveInfo -): Promise { - return ( - await Result.tryCatchFlat( - resolveNewQueryImpl, - convertUnknownToApiError, - args, - info - ) - ).match( - (data) => data, - (error) => { - console.error(`Error resolving ${GRAPHQL_FIELD_NEW_QUERY}:`, error) - return new GraphQLError(error.isPrivate() ? 'Internal Server Error' : error.message) - } - ) -} - -async function resolveNewQueryImpl( - args: RootQueryTypeNewQueryArgs, - info: GraphQLResolveInfo -): Promise> { - const fieldsInfo = graphQLFields(info) - const requestedFields = Object.keys(fieldsInfo) - return await NewQueryModel.loadData(args, requestedFields) -} - -export const newQueryRoot = { - [GRAPHQL_FIELD_NEW_QUERY]: { - description: 'Description of what this query does', - args: { - id: { - type: new GraphQLNonNull(GraphQLString), - description: 'Required argument description', - }, - // Add other arguments... - }, - type: GraphQLObjectTypeNewQuery, // or createCollectionType() for lists - resolve: resolveNewQuery, - }, -} -``` - -### 5. Register in Root Schema -In `rootSchema.ts`, add your resolver: - -```typescript -// Import your resolver -import { newQueryRoot } from './newQuery/newQueryResolver' - -// Add to the query fields -export const rootGraphQLSchema = new GraphQLSchema({ - query: new GraphQLObjectType({ - name: 'RootQueryType', - fields: { - ...introspectRoot, - ...searchRoot, - ...errorRoot, - ...newQueryRoot, // Add this line - }, - }), - types: [ - GraphQLObjectTypeGuide, - GraphQLObjectTypeReferenceCLICommand, - GraphQLObjectTypeReferenceSDKFunction, - GraphQLObjectTypeTroubleshooting, - ], -}) -``` - -### 6. Update TypeScript Types -Run the GraphQL codegen to update TypeScript types: -```bash -pnpm run -F docs codegen:graphql -``` - -## Best Practices - -1. **Error Handling**: Error handling always uses the Result class, defined in apps/docs/features/helpers.fn.ts -2. **Field Optimization**: Use `graphQLFields()` to only fetch requested data -3. **Collections**: Use `createCollectionType()` for paginated lists -4. **Naming**: Use `GRAPHQL_FIELD_*` constants for field names -5. **Documentation**: Add GraphQL descriptions to all fields and types -6. **Database**: Use `supabase()` client for database operations with `convertPostgrestToApiError` - -## Testing - -Tests are located in apps/docs/app/api/graphql/tests. Each top-level query -should have its own test file, located at .test.ts. - -### Test data - -Test data uses a local database, seeded with the file at supabase/seed.sql. Add -any data required for running your new query. - -### Integration tests - -Integration tests import the POST function defined in -apps/docs/api/graphql/route.ts, then make a request to this function. - -For example: - -```ts -import { POST } from '../route' - -it('test name', async () => { - const query = ` - query { - ... - } - ` - const request = new Request('http://localhost/api/graphql', { - method: 'POST', - body: JSON.stringify({ query }), - }) - - const result = await POST(request) -}) -``` - -Include at least the following tests: - -1. A test that requests all fields (including nested fields) on the new query - object, and asserts that there are no errors, and the requested fields are - properly returned. -2. A test that triggers and error, and asserts that a GraphQL error is properly - returned. diff --git a/.cursor/rules/docs-test-requirements.mdc b/.cursor/rules/docs-test-requirements.mdc deleted file mode 100644 index f76ca2ba7cf..00000000000 --- a/.cursor/rules/docs-test-requirements.mdc +++ /dev/null @@ -1,71 +0,0 @@ ---- -description: Docs Testing Procedure -globs: apps/docs/**/*.test.ts -alwaysApply: false ---- - -# Docs Test Requirements - -Rules for running tests in the docs application, ensuring proper Supabase setup and test execution. - - -name: docs_test_requirements -description: Standards for running tests in the docs application with proper Supabase setup -filters: - # Match test files in the docs app - - type: file_extension - pattern: "\\.(test|spec)\\.(ts|tsx)$" - - type: path - pattern: "^apps/docs/.*" - # Match test execution events - - type: event - pattern: "test_execution" - -actions: - - type: suggest - message: | - Before running tests in the docs app: - - 1. Check Supabase status: - ```bash - pnpm supabase status - ``` - - 2. If Supabase is not running: - ```bash - pnpm supabase start - ``` - - 3. Reset the database to ensure clean state: - ```bash - pnpm supabase db reset --local - ``` - - 4. Run the tests: - ```bash - pnpm run -F docs test:local:unwatch - ``` - - Important notes: - - Always ensure Supabase is running before tests - - Database must be reset to ensure clean state - - Use test:local:unwatch to run tests without watch mode - - Tests are located in apps/docs/**/*.{test,spec}.{ts,tsx} - -examples: - - input: | - # Bad: Running tests without proper setup - pnpm run -F docs test - pnpm run -F docs test:local - - # Good: Proper test execution sequence - pnpm supabase status - pnpm supabase start # if not running - pnpm supabase db reset --local - pnpm run -F docs test:local:unwatch - output: "Correctly executed docs tests with proper Supabase setup" - -metadata: - priority: high - version: 1.0 - diff --git a/.cursor/rules/docs-embeddings-generation.md b/.cursor/rules/docs/docs-embeddings-generation/RULE.md similarity index 79% rename from .cursor/rules/docs-embeddings-generation.md rename to .cursor/rules/docs/docs-embeddings-generation/RULE.md index 22ce5fd4359..6eab6f71510 100644 --- a/.cursor/rules/docs-embeddings-generation.md +++ b/.cursor/rules/docs/docs-embeddings-generation/RULE.md @@ -1,3 +1,10 @@ +--- +description: "Docs: embeddings generation pipeline (apps/docs/scripts/search)" +globs: + - apps/docs/scripts/search/**/*.ts +alwaysApply: false +--- + # Documentation Embeddings Generation System ## Overview @@ -12,31 +19,34 @@ The documentation embeddings generation system processes various documentation s ## Architecture ### Main Entry Point -- `generate-embeddings.ts` - Main script that orchestrates the entire process + +- `apps/docs/scripts/search/generate-embeddings.ts` - Main script that orchestrates the entire process - Supports `--refresh` flag to force regeneration of all content ### Content Sources (`sources/` directory) #### Base Classes + - `BaseLoader` - Abstract class for loading content from different sources - `BaseSource` - Abstract class for processing and formatting content #### Source Types -1. **Markdown Sources** (`markdown.ts`) + +1. **Markdown Sources** (`apps/docs/scripts/search/sources/markdown.ts`) - Processes `.mdx` files from guides and documentation - Extracts frontmatter metadata and content sections -2. **Reference Documentation** (`reference-doc.ts`) +2. **Reference Documentation** (`apps/docs/scripts/search/sources/reference-doc.ts`) - **OpenAPI References** - Management API documentation from OpenAPI specs - **Client Library References** - JavaScript, Dart, Python, C#, Swift, Kotlin SDKs - **CLI References** - Command-line interface documentation - Processes YAML/JSON specs and matches with common sections -3. **GitHub Discussions** (`github-discussion.ts`) +3. **GitHub Discussions** (`apps/docs/scripts/search/sources/github-discussion.ts`) - Fetches troubleshooting discussions from GitHub using GraphQL API - Uses GitHub App authentication for access -4. **Partner Integrations** (`partner-integrations.ts`) +4. **Partner Integrations** (`apps/docs/scripts/search/sources/partner-integrations.ts`) - Fetches approved partner integration documentation from Supabase database - Technology integrations only (excludes agencies) @@ -56,4 +66,3 @@ The documentation embeddings generation system processes various documentation s - **`page`** table: Stores page metadata, content, checksum, version - **`page_section`** table: Stores individual sections with embeddings, token counts - diff --git a/.cursor/rules/docs/docs-graphql/RULE.md b/.cursor/rules/docs/docs-graphql/RULE.md new file mode 100644 index 00000000000..8b16f176dc4 --- /dev/null +++ b/.cursor/rules/docs/docs-graphql/RULE.md @@ -0,0 +1,132 @@ +--- +description: "Docs: GraphQL architecture for apps/docs/resources" +globs: + - apps/docs/resources/**/*.ts +alwaysApply: false +--- + +# Docs GraphQL Architecture + +## Overview + +The `apps/docs/resources` folder contains the GraphQL endpoint architecture for the docs GraphQL endpoint at `/api/graphql`. It follows a modular pattern where each top-level query is organized into its own folder with consistent file structure. + +## Architecture Pattern + +Each GraphQL query follows this structure: + +``` +resources/ +├── queryObject/ +│ ├── queryObjectModel.ts # Data models and business logic +│ ├── queryObjectSchema.ts # GraphQL type definitions +│ ├── queryObjectResolver.ts # Query resolver and arguments +│ ├── queryObjectTypes.ts # TypeScript interfaces (optional) +│ └── queryObjectSync.ts # Functions for syncing repo content to the database (optional) +├── utils/ +│ ├── connections.ts # GraphQL connection/pagination utilities +│ └── fields.ts # GraphQL field selection utilities +├── rootSchema.ts # Main GraphQL schema with all queries +└── rootSync.ts # Root sync script for syncing to database +``` + +## Example queries + +1. **searchDocs** (`globalSearch/`) - Vector-based search across all docs content +2. **error** (`error/`) - Error code lookup for Supabase services +3. **schema** - GraphQL schema introspection + +## Key Files + +### `rootSchema.ts` + +- Main GraphQL schema definition +- Imports all resolvers and combines them into the root query +- Defines the `RootQueryType` with all top-level fields + +### `utils/connections.ts` + +- Provides `createCollectionType()` for paginated collections +- `GraphQLCollectionBuilder` for building collection responses +- Standard pagination arguments and edge/node patterns + +### `utils/fields.ts` + +- `graphQLFields()` utility to analyze requested fields in resolvers +- Used for optimizing data fetching based on what fields are actually requested + +## Creating a New Top-Level Query + +To add a new GraphQL query, follow these steps: + +### 1. Create Query Folder Structure + +```bash +mkdir resources/newQuery +touch resources/newQuery/newQueryModel.ts +touch resources/newQuery/newQuerySchema.ts +touch resources/newQuery/newQueryResolver.ts +``` + +### 2. Define GraphQL Schema (`newQuerySchema.ts`) + +```typescript +import { GraphQLObjectType, GraphQLString } from 'graphql' + +export const GRAPHQL_FIELD_NEW_QUERY = 'newQuery' as const + +export const GraphQLObjectTypeNewQuery = new GraphQLObjectType({ + name: 'NewQuery', + description: 'Description of what this query returns', + fields: { + id: { + type: GraphQLString, + description: 'Unique identifier', + }, + // Add other fields... + }, +}) +``` + +### 3. Create Data Model (`newQueryModel.ts`) + +> [!NOTE] +> The data model should be agnostic to GraphQL. It may import argument types +> from `~/__generated__/graphql`, but otherwise all functions and classes +> should be unaware of whether they are called for GraphQL resolution. + +> [!TIP] +> The types in `~/__generated__/graphql` for a new endpoint will not exist +> until the code generation is run in the next step. + +```typescript +import { type RootQueryTypeNewQueryArgs } from '~/__generated__/graphql' +import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils' +import { Result } from '~/features/helpers.fn' +import { supabase } from '~/lib/supabase' + +export class NewQueryModel { + constructor( + public readonly data: { + id: string + // other properties... + } + ) {} + + static async loadData( + args: RootQueryTypeNewQueryArgs, + requestedFields: Array + ): Promise> { + // Implement data fetching logic + const result = new Result( + await supabase() + .from('your_table') + .select('*') + // Add filters based on args + ) + .map((data) => data.map((item) => new NewQueryModel(item))) + .mapError(convertPostgrestToApiError) + return result + } +} +``` diff --git a/.cursor/rules/docs/docs-test-requirements/RULE.md b/.cursor/rules/docs/docs-test-requirements/RULE.md new file mode 100644 index 00000000000..15cbda674e8 --- /dev/null +++ b/.cursor/rules/docs/docs-test-requirements/RULE.md @@ -0,0 +1,25 @@ +--- +description: "Docs: how to run tests locally (Supabase setup + correct commands)" +globs: + - apps/docs/**/*.{test,spec}.{ts,tsx} +alwaysApply: false +--- + +# Docs test requirements + +Before running tests for `apps/docs`, ensure local Supabase is available and the DB is in a known state. + +## Recommended sequence + +```bash +pnpm supabase status +pnpm supabase start # if not running +pnpm supabase db reset --local +pnpm run -F docs test:local:unwatch +``` + +## Notes + +- Always reset the local DB before running docs tests to avoid state leakage. +- Prefer `test:local:unwatch` for non-watch CI-like runs. + diff --git a/.cursor/rules/studio-ui.mdc b/.cursor/rules/studio-ui.mdc deleted file mode 100644 index 754cca1520b..00000000000 --- a/.cursor/rules/studio-ui.mdc +++ /dev/null @@ -1,409 +0,0 @@ ---- -description: How to generate pages and interfaces in Studio, a web interface for managing Supabase projects -globs: -alwaysApply: true ---- - -## Project Structure - -- Next.js app using pages router -- Pages go in @apps/studio/pages - - Project related pages go in @apps/studio/pages/projects/[ref] - - Organization related pages go in @apps/studio/pages/org/[slug] -- Studio specific components go in @apps/studio/components - - Studio specific generic UI components go in @apps/studio/components/ui - - Studio specific components related to individual pages go in @apps/studio/components/interfaces e.g. @apps/studio/components/interfaces/Auth -- Generic helper functions go in @apps/studio/lib -- Generic hooks go in @apps/studio/hooks - -## Component system - -Our primitive component system is in @packages/ui and is based off shadcn/ui components. These components can be shared across all @apps e.g. studio and docs. Do not introduce new ui components unless asked to. - -- UI components are imported from this package across apps e.g. import { Button, Badge } from 'ui' -- Some components have a _Shadcn_ namespace appended to component name e.g. import { Input*Shadcn* } from 'ui' -- We should be using _Shadcn_ components where possible -- Before composing interfaces, read @packages/ui/index.tsx file for a full list of available components - -## Styling - -We use Tailwind for styling. - -- You should never use tailwind classes for colours and instead use classes we've defined ourselves - - Backgrounds // most of the time you will not need to define a background - - 'bg' used for main app surface background - - 'bg-muted' for elevating content // you can use Card instead - - 'bg-warning' for highlighting information that needs to be acted on - - 'bg-destructive' for highlighting issues - - Text - - 'text-foreground' for primary text like headings - - 'text-foreground-light' for body text - - 'text-foreground-lighter' for subtle text - - 'text-warning' for calling out information that needs action - - 'text-destructive' for calling out when something went wrong -- When needing to apply typography styles, read @apps/studio/styles/typography.scss and use one of the available classes instead of hard coding classes e.g. use "heading-default" instead of "text-sm font-medium" -- When applying focus styles for keyboard navigation, read @apps/studio/styles/focus.scss for any appropriate classes for consistency with other focus styles - -## Page structure - -When creating a new page follow these steps: - -- Create the page in @apps/studio/pages -- Use the PageLayout component that has the following props - - ```jsx - export interface NavigationItem { - id?: string - label: string - href?: string - icon?: ReactNode - onClick?: () => void - badge?: string - active?: boolean - } - - interface PageLayoutProps { - children?: ReactNode - title?: string | ReactNode - subtitle?: string | ReactNode - icon?: ReactNode - breadcrumbs?: Array<{ - label?: string - href?: string - element?: ReactNode - }> - primaryActions?: ReactNode - secondaryActions?: ReactNode - navigationItems?: NavigationItem[] - className?: string - size?: 'default' | 'full' | 'large' | 'small' - isCompact?: boolean - } - ``` - -- If a page has page related actions, add them to primary and secondary action props e.g. Users page has "Create new user" action -- If a page is within an existing section (e.g. Auth), you should use the related layout component e.g. AuthLayout -- Create a new component in @apps/studio/components/interfaces for the contents of the page -- Use ScaffoldContainer if the page should be center aligned in a container -- Use ScaffoldSection, ScaffoldSectionTitle, ScaffoldSectionDescription if the page has multiple sections - -### Page example - -```jsx -import { MyPageComponent } from 'components/interfaces/MyPage/MyPageComponent' -import AuthLayout from './AuthLayout' -import DefaultLayout from 'components/layouts/DefaultLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' -import type { NextPageWithLayout } from 'types' - -const MyPage: NextPageWithLayout = () => { - return ( - - - - ) -} - -MyPage.getLayout = (page) => ( - - {page} - -) - -export default MyPage - -export const MyPageComponent = () => ( - -
- My page section - A brief description of the purpose of the page -
- // Content goes here -
-) -``` - -## Forms - -Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels. - -### Core Principles - -- Build forms with `react-hook-form` + `zod` -- Always use `FormItemLayout` instead of manually composing `FormItem`, `FormLabel`, `FormMessage`, and `FormDescription` -- Always wrap form inputs with `FormControl_Shadcn_` to ensure proper form integration -- Keep imports from `ui` with `_Shadcn_` suffixes -- Handle dirty state: Show cancel buttons and disable save buttons based on `form.formState.isDirty` -- Show loading states on submit buttons using the `loading` prop -- If the submit button is outside the form, add a `formId` variable outside the component, set it as `id` on the form element and `form` prop on the button - -### Layout Selection - -- **Page layouts**: Use `FormItemLayout` with `layout="flex-row-reverse"` for horizontal alignment. Forms should be wrapped in a `Card` with each form field in its own `CardContent`, and `CardFooter` for actions. The layout automatically handles consistent input widths (50% on md, 40% on xl, min-w-100). -- **Side panels (wide)**: Use `FormItemLayout` with `layout="horizontal"`. Use `SheetSection` to wrap each field group. -- **Side panels (narrow, size="sm" or below)**: Use `FormItemLayout` with `layout="vertical"` - -### Page Layout Form Example - -```tsx -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import * as z from 'zod' - -import { - Button, - Card, - CardContent, - CardFooter, - Form_Shadcn_, - FormField_Shadcn_, - FormControl_Shadcn_, - Input_Shadcn_, - Switch, -} from 'ui' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' - -const formSchema = z.object({ - name: z.string().min(1, 'Name is required'), - enableFeature: z.boolean(), -}) - -export function SettingsForm() { - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { name: '', enableFeature: false }, - mode: 'onSubmit', - reValidateMode: 'onBlur', - }) - - function onSubmit(values: z.infer) { - // handle mutation with onSuccess/onError toast - } - - return ( - -
- - - ( - - - - - - )} - /> - - - ( - - - - - - )} - /> - - - {form.formState.isDirty && ( - - )} - - - -
-
- ) -} -``` - -### Side Panel Form Example - -```tsx -import { zodResolver } from '@hookform/resolvers/zod' -import { useState } from 'react' -import { useForm } from 'react-hook-form' -import * as z from 'zod' - -import { - Button, - Form_Shadcn_, - FormField_Shadcn_, - FormControl_Shadcn_, - Input_Shadcn_, - Sheet, - SheetContent, - SheetFooter, - SheetHeader, - SheetSection, - SheetTitle, -} from 'ui' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' - -const formSchema = z.object({ - name: z.string().min(1, 'Name is required'), -}) - -const formId = 'sidepanel-form' - -export function CreateResourcePanel() { - const [open, setOpen] = useState(false) - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { name: '' }, - }) - - function onSubmit(values: z.infer) { - // handle mutation - setOpen(false) - } - - return ( - - - - Create Resource - - -
- - ( - - - - - - )} - /> - -
-
- - - - -
-
- ) -} -``` - -### Common Form Field Types - -- **Text Input**: `Input_Shadcn_` with `placeholder` -- **Password Input**: `Input_Shadcn_` with `type="password"` -- **Number Input**: `Input_Shadcn_` with `type="number"` and `onChange={(e) => field.onChange(Number(e.target.value))}` -- **Input with Units**: Wrap `Input_Shadcn_` with `PrePostTab` component: `` -- **Textarea**: `Textarea` component with `rows` and `className="resize-none"` -- **Switch**: `Switch` with `checked={field.value} onCheckedChange={field.onChange}` -- **Checkbox**: `Checkbox_Shadcn_` with label, use multiple for checkbox groups -- **Select**: `Select_Shadcn_` with `SelectTrigger_Shadcn_`, `SelectContent_Shadcn_`, `SelectItem_Shadcn_` -- **Multi-Select**: Use `MultiSelector` from `ui-patterns/multi-select` -- **Radio Group**: `RadioGroupStacked` with `RadioGroupStackedItem` for stacked options with descriptions -- **Date Picker**: `Calendar` inside `Popover_Shadcn_` with a trigger button -- **Copyable Input**: Use `Input` from `ui-patterns/DataInputs/Input` with `copy` and `readOnly` props -- **Field Array**: Use `useFieldArray` from `react-hook-form` for dynamic add/remove fields -- **Action Field**: Use `FormItemLayout` without form control, just buttons for navigation or performable actions. Wrap buttons in a div with `justify-end` to align them to the right - -## Cards - -- Use cards when needing to group related pieces of information -- Cards can have sections with CardContent -- Use CardFooter for actions -- Only use CardHeader and CardTitle if the card content has not been described by the surrounding content e.g. Page title or ScaffoldSectionTitle -- Use CardHeader and CardTitle when you are using multiple Cards to group related pieces of content e.g. Primary branch, Persistent branches, Preview branches - -## Sheets - -- Use a sheet when needing to reveal more complicated forms or information relating to an object and context switching away to a new page would be disruptive e.g. we list auth providers, clicking an auth provider opens a sheet with information about that provider and a form to enable, user can close sheet to go back to providers list -- Use `SheetContent` with `size="lg"` for forms that need horizontal layout -- Use `SheetHeader`, `SheetTitle`, `SheetSection`, and `SheetFooter` for consistent structure -- Place submit/cancel buttons in `SheetFooter` -- For forms in sheets, use `FormItemLayout` with `layout="horizontal"` for wider panels or `layout="vertical"` for narrow panels (size="sm" or below) -- See the Forms section for a complete side panel form example - -## React Query - -- When doing a mutation, always use the mutate function. Always use onSuccess and onError with a toast.success and toast.error. -- Use mutateAsync only if the mutation is part of multiple async actions. Wrap the mutateAsync call with try/catch block and add toast.success and toast.error. - -## Tables - -- Use the generic ui table components for most tables -- Tables are generally contained witin a card -- If a table has associated actions, they should go above on right hand side -- If a table has associated search or filters, they should go above on left hand side -- If a table is the main content of a page, and it does not have search or filters, you can add table actions to primary and secondary actions of PageLayout -- If a table is the main content of a page section, and it does not have search or filters, you can add table actions to the right of ScaffoldSectionTitle -- For simple lists of objects you can use ResourceList with ResourceListItem instead - -### Table example - -```jsx -import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from 'ui' -; - A list of your recent invoices. - - - Invoice - Status - Method - Amount - - - - - INV001 - Paid - Credit Card - $250.00 - - -
-``` - -## Alerts - -- Use Admonition component to alert users of important actions or restrictions in place -- Place the Admonition either at the top of the contents of the page (below page title) or at the top of the related ScaffoldSection , below ScaffoldTitle -- Use sparingly - -### Alert example - -```jsx - -``` diff --git a/.cursor/rules/studio/RULE.md b/.cursor/rules/studio/RULE.md new file mode 100644 index 00000000000..60e3c155802 --- /dev/null +++ b/.cursor/rules/studio/RULE.md @@ -0,0 +1,33 @@ +--- +description: 'Studio: index rule for architecture, style, and UI composition patterns' +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio + +Use the nested rules in this folder for focused guidance while working in `apps/studio/`. + +## Architecture and style + +- `studio/project-structure` +- `studio/component-system` +- `studio/styling` +- `studio/best-practices` + +## UI composition (Design System patterns) + +- `studio/layout` +- `studio/forms` +- `studio/tables` +- `studio/charts` +- `studio/empty-states` +- `studio/navigation` + +## Common UI building blocks + +- `studio/sheets` +- `studio/cards` +- `studio/alerts` +- `studio/react-query` diff --git a/.cursor/rules/studio/alerts/RULE.md b/.cursor/rules/studio/alerts/RULE.md new file mode 100644 index 00000000000..feb55b0564b --- /dev/null +++ b/.cursor/rules/studio/alerts/RULE.md @@ -0,0 +1,13 @@ +--- +description: "Studio: alert/admonition usage and placement" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio alerts + +- Use `Admonition` to call out important actions, restrictions, or critical context. +- Place at the top of a page’s content (below the page title) or at the top of the relevant section (below the section title). +- Use sparingly. + diff --git a/.cursor/rules/studio-best-practices.mdc b/.cursor/rules/studio/best-practices/RULE.md similarity index 97% rename from .cursor/rules/studio-best-practices.mdc rename to .cursor/rules/studio/best-practices/RULE.md index ebb06fc7e1f..357265450a8 100644 --- a/.cursor/rules/studio-best-practices.mdc +++ b/.cursor/rules/studio/best-practices/RULE.md @@ -1,9 +1,8 @@ --- -description: React best practices and coding standards for Studio +description: "Studio: React and TypeScript best practices for maintainable Studio code" globs: - - apps/studio/**/*.tsx - - apps/studio/**/*.ts -alwaysApply: true + - apps/studio/**/*.{ts,tsx} +alwaysApply: false --- # Studio Best Practices @@ -307,15 +306,15 @@ const Component = ({ onClose, onSave }: Props) => { ```tsx // ❌ Bad - creates new function every render - handleItemClick(item)} -/> + handleItemClick(item)} /> // ✅ Good - stable reference with useCallback -const handleItemClick = useCallback((item: Item) => { - // handle click -}, [dependencies]) +const handleItemClick = useCallback( + (item: Item) => { + // handle click + }, + [dependencies] +) ``` diff --git a/.cursor/rules/studio/cards/RULE.md b/.cursor/rules/studio/cards/RULE.md new file mode 100644 index 00000000000..efcb36733b1 --- /dev/null +++ b/.cursor/rules/studio/cards/RULE.md @@ -0,0 +1,14 @@ +--- +description: "Studio: Card usage for grouping related content and actions" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio cards + +- Use cards to group related pieces of information. +- Use `CardContent` for sections and `CardFooter` for actions. +- Only use `CardHeader`/`CardTitle` when the card content is not already described by surrounding content (page title, section title, etc). +- Prefer headers/titles when multiple cards represent distinct groups (e.g. multiple settings groups). + diff --git a/.cursor/rules/studio/charts/RULE.md b/.cursor/rules/studio/charts/RULE.md new file mode 100644 index 00000000000..558580db3e8 --- /dev/null +++ b/.cursor/rules/studio/charts/RULE.md @@ -0,0 +1,26 @@ +--- +description: "Studio: composable chart patterns built on Recharts and our chart presentational components" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio charts + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/charts.mdx` +- Demos: + - `apps/design-system/__registry__/default/block/chart-composed-demo.tsx` + - `apps/design-system/__registry__/default/block/chart-composed-basic.tsx` + - `apps/design-system/__registry__/default/block/chart-composed-states.tsx` + - `apps/design-system/__registry__/default/block/chart-composed-metrics.tsx` + - `apps/design-system/__registry__/default/block/chart-composed-actions.tsx` + - `apps/design-system/__registry__/default/block/chart-composed-table.tsx` + +## Best practices + +- Prefer provided chart building blocks over passing raw Recharts components to `ChartContent`. +- Use `useChart` context flags for consistent loading/disabled handling. +- Keep chart composition straightforward; avoid over-abstraction. + diff --git a/.cursor/rules/studio/component-system/RULE.md b/.cursor/rules/studio/component-system/RULE.md new file mode 100644 index 00000000000..b6433123d0d --- /dev/null +++ b/.cursor/rules/studio/component-system/RULE.md @@ -0,0 +1,16 @@ +--- +description: 'Studio: UI component system (packages/ui + shadcn primitives)' +globs: + - apps/studio/**/*.{ts,tsx} + - packages/ui/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio component system + +Our primitive component system lives in `packages/ui` and is based on shadcn/ui patterns. + +- Prefer using components exported from `ui` (e.g. `import { Button } from 'ui'`). +- Prefer `_Shadcn_`-suffixed components for form components e.g. `Input_Shadcn_`. +- Avoid introducing new primitives unless explicitly requested. +- Browse available exports in `packages/ui/index.tsx` before composing new UI. diff --git a/.cursor/rules/studio/empty-states/RULE.md b/.cursor/rules/studio/empty-states/RULE.md new file mode 100644 index 00000000000..266213fe9a9 --- /dev/null +++ b/.cursor/rules/studio/empty-states/RULE.md @@ -0,0 +1,25 @@ +--- +description: 'Studio: empty state patterns (presentational vs informational vs zero-results vs missing route)' +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio empty states + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/empty-states.mdx` +- Demos: + - `apps/design-system/registry/default/example/empty-state-presentational-icon.tsx` + - `apps/design-system/registry/default/example/empty-state-initial-state-informational.tsx` + - `apps/design-system/registry/default/example/empty-state-zero-items-table.tsx` + - `apps/design-system/registry/default/example/data-grid-empty-state.tsx` + - `apps/design-system/registry/default/example/empty-state-missing-route.tsx` + +## Quick guidance + +- Initial states: use presentational empty states when onboarding/value prop + a clear next action helps. +- Data-heavy lists: prefer informational empty states that match the list/table layout. +- Zero results: keep the UI consistent with the data state to avoid jarring transitions. +- Missing routes: prefer a centered `Admonition` pattern. diff --git a/.cursor/rules/studio/forms/RULE.md b/.cursor/rules/studio/forms/RULE.md new file mode 100644 index 00000000000..b9b729111b5 --- /dev/null +++ b/.cursor/rules/studio/forms/RULE.md @@ -0,0 +1,35 @@ +--- +description: "Studio: form patterns (page layouts + side panels) and react-hook-form conventions" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio forms + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/forms.mdx` +- Demos: + - `apps/design-system/registry/default/example/form-patterns-pagelayout.tsx` + - `apps/design-system/registry/default/example/form-patterns-sidepanel.tsx` + +## Requirements + +- Build forms with `react-hook-form` + `zod`. +- Use `FormItemLayout` instead of manually composing `FormItem`/`FormLabel`/`FormMessage`/`FormDescription`. +- Wrap inputs with `FormControl_Shadcn_`. +- Use `_Shadcn_` imports from `ui` for form primitives where available. + +## Layout selection + +- Page layouts: `FormItemLayout layout="flex-row-reverse"` inside `Card` (`CardContent` per field; `CardFooter` for actions). +- Side panels (wide): `FormItemLayout layout="horizontal"` inside `SheetSection`. +- Side panels (narrow, `size="sm"` or below): `FormItemLayout layout="vertical"`. + +## Actions and state + +- Handle dirty state (`form.formState.isDirty`) to show Cancel and to disable Save. +- Show loading on submit buttons via `loading`. +- When submit button is outside the `
`, set a stable `formId` and use the button’s `form` prop. + diff --git a/.cursor/rules/studio/layout/RULE.md b/.cursor/rules/studio/layout/RULE.md new file mode 100644 index 00000000000..b39f082b740 --- /dev/null +++ b/.cursor/rules/studio/layout/RULE.md @@ -0,0 +1,28 @@ +--- +description: 'Studio: page layout patterns (PageContainer/PageHeader/PageSection) and sizing guidance. Use to learn how to create or update existing pages in Studio.' +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio layout + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/layout.mdx` +- Demos: + - `apps/design-system/registry/default/example/page-layout-settings.tsx` + - `apps/design-system/registry/default/example/page-layout-list.tsx` + - `apps/design-system/registry/default/example/page-layout-list-simple.tsx` + - `apps/design-system/registry/default/example/page-layout-detail.tsx` + +## Guidelines + +- Build pages using `PageContainer`, `PageHeader`, and `PageSection` for consistent spacing and max-widths. +- Choose `size` based on content: + - Settings/config: `size="default"` + - List/table-heavy: `size="large"` + - Full-screen experiences: `size="full"` +- For list pages: + - If filters/search exist, align table actions with filters (avoid `PageHeaderAside`/`PageSectionAside` for those actions). + - If no filters/search, actions can go in `PageHeaderAside` or `PageSectionAside` depending on context. diff --git a/.cursor/rules/studio/navigation/RULE.md b/.cursor/rules/studio/navigation/RULE.md new file mode 100644 index 00000000000..eb37afda3fb --- /dev/null +++ b/.cursor/rules/studio/navigation/RULE.md @@ -0,0 +1,19 @@ +--- +description: "Studio: navigation patterns (page-level NavMenu + URL-driven navigation)" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio navigation + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/navigation.mdx` + +## NavMenu + +- Use `NavMenu` for a horizontal list of related views within a consistent page layout. +- Activating an item should trigger a URL change (no local-only tab state). +- See: `apps/design-system/content/docs/components/nav-menu.mdx` + diff --git a/.cursor/rules/studio/project-structure/RULE.md b/.cursor/rules/studio/project-structure/RULE.md new file mode 100644 index 00000000000..d87139dd5a3 --- /dev/null +++ b/.cursor/rules/studio/project-structure/RULE.md @@ -0,0 +1,19 @@ +--- +description: "Studio: project structure and where code lives" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio project structure + +- Studio is a Next.js app using the pages router. +- Pages live in `apps/studio/pages`. + - Project pages: `apps/studio/pages/projects/[ref]` + - Org pages: `apps/studio/pages/org/[slug]` +- Studio components live in `apps/studio/components`. + - Studio UI helpers: `apps/studio/components/ui` + - Interface/page components: `apps/studio/components/interfaces` (e.g. `apps/studio/components/interfaces/Auth`) +- Shared hooks: `apps/studio/hooks` +- Shared helpers: `apps/studio/lib` + diff --git a/.cursor/rules/studio/queries/RULE.md b/.cursor/rules/studio/queries/RULE.md new file mode 100644 index 00000000000..0e394b01d28 --- /dev/null +++ b/.cursor/rules/studio/queries/RULE.md @@ -0,0 +1,113 @@ +--- +description: 'Studio: data fetching conventions for queries/mutations (React Query hooks)' +globs: + - apps/studio/data/**/*.{ts,tsx} + - apps/studio/pages/**/*.{ts,tsx} + - apps/studio/components/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio queries & mutations (React Query) + +Follow the `apps/studio/data/` patterns used by edge functions: + +- Query hook: `apps/studio/data/edge-functions/edge-functions-query.ts` +- Mutation hook: `apps/studio/data/edge-functions/edge-functions-update-mutation.ts` +- Keys: `apps/studio/data/edge-functions/keys.ts` +- Page usage: `apps/studio/pages/project/[ref]/functions/index.tsx` + +## Organize query keys + +- Define a `keys.ts` per domain and export `*Keys` helpers (use array keys with `as const`). +- Do not inline query keys in components. + +Example: + +```ts +export const edgeFunctionsKeys = { + list: (projectRef: string | undefined) => ['projects', projectRef, 'edge-functions'] as const, + detail: (projectRef: string | undefined, slug: string | undefined) => + ['projects', projectRef, 'edge-function', slug, 'detail'] as const, +} +``` + +## Write a query hook + +- Export `Variables`, `Data`, and `Error` types from the file. +- Implement a `getX(variables, signal?)` function that: + - throws if required variables are missing + - passes the `signal` through to the fetcher for cancellation + - calls `handleError(error)` and returns `data` +- Wrap it in `useXQuery()` using `useQuery`, `UseCustomQueryOptions`, and a domain key helper. +- Gate with `enabled` so the query doesn’t run until required variables exist (and platform-only queries should include `IS_PLATFORM`). + +Template: + +```ts +export type XVariables = { projectRef?: string } +export type XError = ResponseError + +export async function getX({ projectRef }: XVariables, signal?: AbortSignal) { + if (!projectRef) throw new Error('projectRef is required') + const { data, error } = await get('/v1/projects/{ref}/x', { + params: { path: { ref: projectRef } }, + signal, + }) + if (error) handleError(error) + return data +} + +export type XData = Awaited> + +export const useXQuery = ( + { projectRef }: XVariables, + { enabled = true, ...options }: UseCustomQueryOptions = {} +) => + useQuery({ + queryKey: xKeys.list(projectRef), + queryFn: ({ signal }) => getX({ projectRef }, signal), + enabled: IS_PLATFORM && enabled && typeof projectRef !== 'undefined', + ...options, + }) +``` + +## Write a mutation hook + +- Export a `Variables` type that includes `projectRef`, identifiers (e.g. `slug`), and `payload`. +- Implement an `updateX(vars)` function that validates required variables and uses `handleError`. +- Prefer a `useXMutation()` wrapper that: + - accepts `UseCustomMutationOptions` (omit `mutationFn`) + - invalidates the relevant `list()` + `detail()` keys in `onSuccess` and `await`s them via `Promise.all` + - defaults to a `toast.error(...)` when `onError` isn’t provided + +Template: + +```ts +export const useXUpdateMutation = ({ onSuccess, onError, ...options } = {}) => { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: updateX, + async onSuccess(data, variables, context) { + await Promise.all([ + queryClient.invalidateQueries({ + queryKey: xKeys.detail(variables.projectRef, variables.slug), + }), + queryClient.invalidateQueries({ queryKey: xKeys.list(variables.projectRef) }), + ]) + await onSuccess?.(data, variables, context) + }, + async onError(error, variables, context) { + if (onError === undefined) toast.error(`Failed to update: ${error.message}`) + else onError(error, variables, context) + }, + ...options, + }) +} +``` + +## Component usage + +- Prefer React Query’s v5 flags: + - `isPending` for initial load (often aliased to `isLoading`) + - `isFetching` for background refetches +- Render states explicitly (pending → error → success), like `apps/studio/pages/project/[ref]/functions/index.tsx`. diff --git a/.cursor/rules/studio/sheets/RULE.md b/.cursor/rules/studio/sheets/RULE.md new file mode 100644 index 00000000000..f55b3958ec5 --- /dev/null +++ b/.cursor/rules/studio/sheets/RULE.md @@ -0,0 +1,23 @@ +--- +description: "Studio: side panels (Sheet) for context-preserving workflows" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio sheets + +Use a `Sheet` when switching to a new page would be disruptive and the user should keep context (e.g. selecting an item from a list to edit details). + +## Structure + +- Prefer `SheetContent` with `size="lg"` for forms that need horizontal layout. +- Use `SheetHeader`, `SheetTitle`, `SheetSection`, and `SheetFooter` for consistent structure. +- Place submit/cancel actions in `SheetFooter`. + +## Forms in sheets + +- Prefer `FormItemLayout`: + - `layout="horizontal"` for wider sheets + - `layout="vertical"` for narrow sheets (`size="sm"` or below) +- See `@studio/forms` for the canonical patterns and demos. diff --git a/.cursor/rules/studio/styling/RULE.md b/.cursor/rules/studio/styling/RULE.md new file mode 100644 index 00000000000..f1679fbc264 --- /dev/null +++ b/.cursor/rules/studio/styling/RULE.md @@ -0,0 +1,16 @@ +--- +description: "Studio: styling rules (Tailwind + semantic tokens + typography/focus utilities)" +globs: + - apps/studio/**/*.{ts,tsx,scss} +alwaysApply: false +--- + +# Studio styling + +- Use Tailwind. +- Do not hardcode Tailwind color tokens; use our semantic classes: + - backgrounds: `bg`, `bg-muted`, `bg-warning`, `bg-destructive` + - text: `text-foreground`, `text-foreground-light`, `text-foreground-lighter`, `text-warning`, `text-destructive` +- Use existing typography utilities from `apps/studio/styles/typography.scss` instead of recreating styles. +- Use existing focus utilities from `apps/studio/styles/focus.scss` for consistent keyboard focus styling. + diff --git a/.cursor/rules/studio/tables/RULE.md b/.cursor/rules/studio/tables/RULE.md new file mode 100644 index 00000000000..c610fe84268 --- /dev/null +++ b/.cursor/rules/studio/tables/RULE.md @@ -0,0 +1,29 @@ +--- +description: "Studio: table patterns (Table vs Data Table vs Data Grid) and placement of actions/filters" +globs: + - apps/studio/**/*.{ts,tsx} +alwaysApply: false +--- + +# Studio tables + +Use the Design System UI pattern docs as the source of truth: + +- Documentation: `apps/design-system/content/docs/ui-patterns/tables.mdx` +- Demos: + - `apps/design-system/registry/default/example/table-demo.tsx` + - `apps/design-system/registry/default/example/data-table-demo.tsx` + - `apps/design-system/registry/default/example/data-grid-demo.tsx` + +## Choose the right pattern + +- `Table`: simple, static, semantic table display. +- Data Table: TanStack-powered pattern for sorting/filtering/pagination; composed per use case. +- Data Grid: only when you need virtualization, column resizing, or complex cell editing. + +## Actions and filters placement + +- Actions: above the table, aligned right. +- Search/filters: above the table, aligned left. +- If the table is the primary page content and has no filters/search, actions can live in the page’s primary/secondary actions area. + diff --git a/.cursor/rules/e2e-testing.mdc b/.cursor/rules/testing/e2e-studio/RULE.md similarity index 98% rename from .cursor/rules/e2e-testing.mdc rename to .cursor/rules/testing/e2e-studio/RULE.md index 346ecf4b75d..354cd86ac20 100644 --- a/.cursor/rules/e2e-testing.mdc +++ b/.cursor/rules/testing/e2e-studio/RULE.md @@ -1,9 +1,9 @@ --- -description: E2E testing best practices for Playwright tests in Studio +description: "Testing: Playwright E2E best practices for Studio tests (avoid flake + race conditions)" globs: - e2e/studio/**/*.ts - e2e/studio/**/*.spec.ts -alwaysApply: true +alwaysApply: false --- # E2E Testing Best Practices diff --git a/.cursor/rules/testing/unit-integration/RULE.md b/.cursor/rules/testing/unit-integration/RULE.md new file mode 100644 index 00000000000..7248ee737a4 --- /dev/null +++ b/.cursor/rules/testing/unit-integration/RULE.md @@ -0,0 +1,10 @@ +--- +description: "Testing: unit/integration conventions for Studio test files" +globs: + - apps/studio/**/*.test.ts + - apps/studio/**/*.test.tsx +alwaysApply: false +--- + +Follow the guidelines in `apps/studio/tests/README.md` when writing tests for Studio. + diff --git a/.cursor/rules/unit-integration-testing.mdc b/.cursor/rules/unit-integration-testing.mdc deleted file mode 100644 index e3a659a7c53..00000000000 --- a/.cursor/rules/unit-integration-testing.mdc +++ /dev/null @@ -1,6 +0,0 @@ ---- -description: -globs: apps/studio/**/*.test.ts,apps/studio/**/*.test.tsx -alwaysApply: false ---- -Make sure to follow the guidelines in this file to write tests: [README.md](mdc:apps/studio/tests/README.md)