kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update backend API guidelines

- Enhanced backend API documentation with detailed guidelines for Hono, Drizzle
ORM, and Better Auth.
- Added best practices for database schema definitions and migrations.
- Introduced new cursor rules for development and deployment processes.
- Improved frontend web development guidelines for React and TanStack integration.

+1599 -374
+231 -40
.cursor/rules/backend-api.mdc
··· 1 1 --- 2 - description: 3 - globs: 2 + description: Backend API development guidelines for Hono, Drizzle ORM, and Better Auth 3 + globs: apps/api/**/* 4 4 alwaysApply: false 5 5 --- 6 - # Backend API Structure and Patterns 6 + # Backend API Guidelines 7 + 8 + The Kaneo API is built with Hono, Drizzle ORM, PostgreSQL, and Better Auth. This document outlines conventions and patterns for backend development. 9 + 10 + ## Framework: Hono 11 + 12 + Hono is a lightweight web framework. Follow these patterns: 13 + 14 + ### Route Structure 15 + 16 + Routes are organized by feature in `apps/api/src/`: 17 + 18 + ```typescript 19 + // apps/api/src/task/index.ts 20 + import { Hono } from "hono"; 21 + import { describeRoute, resolver, validator } from "hono-openapi"; 22 + import * as v from "valibot"; 23 + import createTask from "./controllers/create-task"; 24 + import getTask from "./controllers/get-task"; 25 + 26 + const task = new Hono<{ 27 + Variables: { 28 + userId: string; 29 + }; 30 + }>() 31 + .get( 32 + "/:id", 33 + describeRoute({ 34 + operationId: "getTask", 35 + tags: ["Tasks"], 36 + description: "Get a specific task by ID", 37 + responses: { 38 + 200: { 39 + description: "Task details", 40 + content: { 41 + "application/json": { schema: resolver(taskSchema) }, 42 + }, 43 + }, 44 + }, 45 + }), 46 + validator("param", v.object({ id: v.string() })), 47 + async (c) => { 48 + const { id } = c.req.valid("param"); 49 + const task = await getTask(id); 50 + return c.json(task); 51 + }, 52 + ); 53 + 54 + export default task; 55 + ``` 56 + 57 + ### Best Practices 58 + 59 + 1. **Always use OpenAPI decorators**: Use `describeRoute` for all endpoints 60 + 2. **Validate inputs**: Use `validator` with Valibot schemas 61 + 3. **Extract controllers**: Keep route handlers thin, move logic to controller files 62 + 4. **Type safety**: Use Hono's type system for context variables 63 + 64 + ```typescript 65 + // Good: Thin route handler 66 + .get("/:id", validator("param", v.object({ id: v.string() })), async (c) => { 67 + const { id } = c.req.valid("param"); 68 + const task = await getTask(id); 69 + return c.json(task); 70 + }) 71 + 72 + // Bad: Business logic in route handler 73 + .get("/:id", async (c) => { 74 + const id = c.req.param("id"); 75 + const task = await db.select().from(taskTable).where(eq(taskTable.id, id)); 76 + // ... more logic 77 + return c.json(task); 78 + }) 79 + ``` 80 + 81 + ## Database: Drizzle ORM 82 + 83 + ### Schema Definition 84 + 85 + Define schemas in `apps/api/src/database/schema.ts`: 86 + 87 + ```typescript 88 + import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; 89 + import { createId } from "@paralleldrive/cuid2"; 90 + 91 + export const taskTable = pgTable("task", { 92 + id: text("id") 93 + .$defaultFn(() => createId()) 94 + .primaryKey(), 95 + title: text("title").notNull(), 96 + createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 97 + updatedAt: timestamp("updated_at", { mode: "date" }) 98 + .defaultNow() 99 + .$onUpdate(() => new Date()) 100 + .notNull(), 101 + }); 102 + ``` 103 + 104 + ### Best Practices 105 + 106 + 1. **Use CUID2 for IDs**: Always use `createId()` from `@paralleldrive/cuid2` 107 + 2. **Timestamps**: Always include `createdAt` and `updatedAt` with proper defaults 108 + 3. **Foreign Keys**: Use cascade deletes/updates where appropriate 109 + 4. **Indexes**: Add indexes for frequently queried columns 110 + 111 + ```typescript 112 + // Good: Proper schema with indexes 113 + export const taskTable = pgTable( 114 + "task", 115 + { 116 + id: text("id").$defaultFn(() => createId()).primaryKey(), 117 + projectId: text("project_id") 118 + .notNull() 119 + .references(() => projectTable.id, { 120 + onDelete: "cascade", 121 + onUpdate: "cascade", 122 + }), 123 + // ... 124 + }, 125 + (table) => [index("task_projectId_idx").on(table.projectId)], 126 + ); 127 + ``` 128 + 129 + ### Migrations 130 + 131 + 1. **Generate migrations**: `pnpm --filter @kaneo/api db:generate` 132 + 2. **Run migrations**: Automatically on API startup (see `apps/api/src/index.ts`) 133 + 3. **Migration files**: Stored in `apps/api/drizzle/` 134 + 135 + ## Authentication: Better Auth 136 + 137 + Better Auth handles authentication. Access user context via Hono variables: 7 138 8 - The backend API is located in [apps/api/](mdc:apps/api) and follows a modular, domain-driven architecture. 139 + ```typescript 140 + // User is available in context after auth middleware 141 + const userId = c.get("userId"); 142 + const user = c.get("user"); 143 + const session = c.get("session"); 144 + ``` 9 145 10 - ## Project Structure 146 + ### API Key Authentication 11 147 12 - ### Core Files 13 - - **Entry Point**: [apps/api/src/index.ts](mdc:apps/api/src/index.ts) - Main Hono application setup 14 - - **Database**: [apps/api/src/database/index.ts](mdc:apps/api/src/database/index.ts) - Database connection 15 - - **Schema**: [apps/api/src/database/schema.ts](mdc:apps/api/src/database/schema.ts) - Drizzle ORM schema definitions 16 - - **Config**: [apps/api/src/config/index.ts](mdc:apps/api/src/config/index.ts) - Environment configuration 148 + API keys are supported via Bearer token: 17 149 18 - ### Domain Modules 150 + ```typescript 151 + // Middleware in apps/api/src/index.ts handles this 152 + // Authorization: Bearer <api-key> 153 + ``` 154 + 155 + ## Validation: Valibot 156 + 157 + Use Valibot for request validation: 158 + 159 + ```typescript 160 + import * as v from "valibot"; 161 + 162 + validator( 163 + "json", 164 + v.object({ 165 + title: v.string(), 166 + description: v.optional(v.string()), 167 + dueDate: v.optional(v.string()), 168 + }), 169 + ) 170 + ``` 171 + 172 + ## Controller Pattern 173 + 174 + Controllers contain business logic and are imported into route files: 175 + 176 + ```typescript 177 + // apps/api/src/task/controllers/get-task.ts 178 + import db from "../../database"; 179 + import { taskTable } from "../../database/schema"; 180 + import { eq } from "drizzle-orm"; 181 + 182 + export default async function getTask(id: string) { 183 + const [task] = await db 184 + .select() 185 + .from(taskTable) 186 + .where(eq(taskTable.id, id)); 187 + 188 + if (!task) { 189 + throw new HTTPException(404, { message: "Task not found" }); 190 + } 191 + 192 + return task; 193 + } 194 + ``` 195 + 196 + ## Error Handling 197 + 198 + Use Hono's HTTPException for errors: 199 + 200 + ```typescript 201 + import { HTTPException } from "hono/http-exception"; 19 202 20 - Each domain follows the same structure pattern with `controllers/` and `index.ts`: 203 + if (!task) { 204 + throw new HTTPException(404, { message: "Task not found" }); 205 + } 206 + ``` 21 207 22 - - **Activity**: [apps/api/src/activity/](mdc:apps/api/src/activity) - Activity tracking and comments 23 - - **Task**: [apps/api/src/task/](mdc:apps/api/src/task) - Task management (CRUD, updates, exports) 24 - - **Project**: [apps/api/src/project/](mdc:apps/api/src/project) - Project management 25 - - **Workspace**: [apps/api/src/workspace/](mdc:apps/api/src/workspace) - Workspace management 26 - - **User**: [apps/api/src/user/](mdc:apps/api/src/user) - Authentication and user management 27 - - **GitHub Integration**: [apps/api/src/github-integration/](mdc:apps/api/src/github-integration) - GitHub app integration 28 - - **Time Entry**: [apps/api/src/time-entry/](mdc:apps/api/src/time-entry) - Time tracking 29 - - **Notification**: [apps/api/src/notification/](mdc:apps/api/src/notification) - User notifications 30 - - **Label**: [apps/api/src/label/](mdc:apps/api/src/label) - Task labeling system 208 + ## Events 31 209 32 - ## Architecture Patterns 210 + Publish events for activity tracking: 33 211 34 - ### Controller Pattern 35 - Each domain has controllers in `controllers/` directory that handle HTTP requests: 36 - - Controllers use Hono framework 37 - - Follow naming convention: `{action}-{resource}.ts` (e.g., `create-task.ts`) 38 - - Each controller exports a Hono route handler 212 + ```typescript 213 + import { publishEvent } from "../events"; 39 214 40 - ### Module Exports 41 - Each domain has an [index.ts](mdc:apps/api/src/task/index.ts) that exports all controllers as Hono routes. 215 + await publishEvent("task.status_changed", { 216 + taskId: task.id, 217 + userId: user, 218 + oldStatus: task.status, 219 + newStatus: status, 220 + title: task.title, 221 + type: "status_changed", 222 + }); 223 + ``` 42 224 43 - ### Authentication 44 - - Auth middleware: [apps/api/src/middlewares/auth.ts](mdc:apps/api/src/middlewares/auth.ts) 45 - - Session management in [apps/api/src/user/utils/](mdc:apps/api/src/user/utils) 225 + ## File Organization 46 226 47 - ### Database Migrations 48 - - Located in [apps/api/drizzle/](mdc:apps/api/drizzle) 49 - - Managed by Drizzle ORM 50 - - Configuration: [apps/api/drizzle.config.ts](mdc:apps/api/drizzle.config.ts) 227 + ``` 228 + apps/api/src/ 229 + ├── activity/ # Activity feature 230 + │ ├── controllers/ 231 + │ └── index.ts 232 + ├── auth.ts # Better Auth configuration 233 + ├── config/ # Configuration endpoints 234 + ├── database/ # Database schema and connection 235 + │ ├── schema.ts 236 + │ ├── relations.ts 237 + │ └── index.ts 238 + ├── events/ # Event publishing 239 + ├── task/ # Task feature 240 + │ ├── controllers/ 241 + │ └── index.ts 242 + └── index.ts # Main entry point 243 + ``` 51 244 52 - ## Key Utilities 245 + ## CORS Configuration 53 246 54 - - **Demo Data**: [apps/api/src/utils/create-demo-user.ts](mdc:apps/api/src/utils/create-demo-user.ts) 55 - - **Settings**: [apps/api/src/utils/get-settings.ts](mdc:apps/api/src/utils/get-settings.ts) 56 - - **Events**: [apps/api/src/events/index.ts](mdc:apps/api/src/events/index.ts) 247 + CORS is configured in `apps/api/src/index.ts`. Set `CORS_ORIGINS` environment variable for production.
+66
.cursor/rules/cursor-rules.mdc
··· 1 + --- 2 + description: How to add or edit Cursor rules in our project 3 + globs: 4 + alwaysApply: false 5 + --- 6 + # Cursor Rules Location 7 + 8 + How to add new cursor rules to the project 9 + 10 + 1. Always place rule files in PROJECT_ROOT/.cursor/rules/: 11 + ``` 12 + .cursor/rules/ 13 + ├── your-rule-name.mdc 14 + ├── another-rule.mdc 15 + └── ... 16 + ``` 17 + 18 + 2. Follow the naming convention: 19 + - Use kebab-case for filenames 20 + - Always use .mdc extension 21 + - Make names descriptive of the rule's purpose 22 + 23 + 3. Directory structure: 24 + ``` 25 + PROJECT_ROOT/ 26 + ├── .cursor/ 27 + │ └── rules/ 28 + │ ├── your-rule-name.mdc 29 + │ └── ... 30 + └── ... 31 + ``` 32 + 33 + 4. Never place rule files: 34 + - In the project root 35 + - In subdirectories outside .cursor/rules 36 + - In any other location 37 + 38 + 5. Cursor rules have the following structure: 39 + 40 + ```` 41 + --- 42 + description: Short description of the rule's purpose 43 + globs: optional/path/pattern/**/* 44 + alwaysApply: false 45 + --- 46 + # Rule Title 47 + 48 + Main content explaining the rule with markdown formatting. 49 + 50 + 1. Step-by-step instructions 51 + 2. Code examples 52 + 3. Guidelines 53 + 54 + Example: 55 + ```typescript 56 + // Good example 57 + function goodExample() { 58 + // Implementation following guidelines 59 + } 60 + 61 + // Bad example 62 + function badExample() { 63 + // Implementation not following guidelines 64 + } 65 + ``` 66 + ````
+267 -62
.cursor/rules/database-schema.mdc
··· 1 1 --- 2 - description: 3 - globs: 2 + description: Database schema conventions and patterns using Drizzle ORM and PostgreSQL 3 + globs: apps/api/src/database/**/* 4 4 alwaysApply: false 5 5 --- 6 - # Database Schema and Data Model 6 + # Database Schema Guidelines 7 + 8 + Kaneo uses Drizzle ORM with PostgreSQL. This document outlines schema conventions and best practices. 9 + 10 + ## Schema Definition 11 + 12 + All schemas are defined in `apps/api/src/database/schema.ts`: 13 + 14 + ```typescript 15 + import { pgTable, text, timestamp, boolean, integer, index } from "drizzle-orm/pg-core"; 16 + import { createId } from "@paralleldrive/cuid2"; 17 + 18 + export const taskTable = pgTable( 19 + "task", 20 + { 21 + id: text("id") 22 + .$defaultFn(() => createId()) 23 + .primaryKey(), 24 + projectId: text("project_id") 25 + .notNull() 26 + .references(() => projectTable.id, { 27 + onDelete: "cascade", 28 + onUpdate: "cascade", 29 + }), 30 + title: text("title").notNull(), 31 + description: text("description"), 32 + status: text("status").notNull().default("to-do"), 33 + createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 34 + updatedAt: timestamp("updated_at", { mode: "date" }) 35 + .defaultNow() 36 + .$onUpdate(() => new Date()) 37 + .notNull(), 38 + }, 39 + (table) => [ 40 + index("task_projectId_idx").on(table.projectId), 41 + ], 42 + ); 43 + ``` 44 + 45 + ## Naming Conventions 46 + 47 + 1. **Table names**: Use singular, lowercase with underscores: `task`, `workspace_user` 48 + 2. **Column names**: Use snake_case: `project_id`, `created_at`, `updated_at` 49 + 3. **Index names**: Format: `{table}_{column}_idx` 50 + 4. **Foreign key columns**: End with `_id`: `project_id`, `user_id` 51 + 52 + ```typescript 53 + // Good: Consistent naming 54 + export const workspaceUserTable = pgTable("workspace_user", { 55 + id: text("id").primaryKey(), 56 + workspaceId: text("workspace_id").notNull(), 57 + userId: text("user_id").notNull(), 58 + role: text("role").notNull(), 59 + }); 60 + 61 + // Bad: Inconsistent naming 62 + export const workspaceUserTable = pgTable("WorkspaceUsers", { 63 + ID: text("ID").primaryKey(), 64 + workspace: text("workspace").notNull(), 65 + user: text("user").notNull(), 66 + }); 67 + ``` 68 + 69 + ## Required Fields 70 + 71 + Every table should include: 72 + 73 + 1. **Primary Key**: `id` field using CUID2 74 + 2. **Timestamps**: `created_at` and `updated_at` with proper defaults 75 + 3. **Foreign Keys**: Proper references with cascade options 76 + 77 + ```typescript 78 + export const exampleTable = pgTable("example", { 79 + // Required: Primary key with CUID2 80 + id: text("id") 81 + .$defaultFn(() => createId()) 82 + .primaryKey(), 83 + 84 + // Required: Timestamps 85 + createdAt: timestamp("created_at", { mode: "date" }) 86 + .defaultNow() 87 + .notNull(), 88 + updatedAt: timestamp("updated_at", { mode: "date" }) 89 + .defaultNow() 90 + .$onUpdate(() => new Date()) 91 + .notNull(), 92 + 93 + // Foreign keys with cascade 94 + projectId: text("project_id") 95 + .notNull() 96 + .references(() => projectTable.id, { 97 + onDelete: "cascade", 98 + onUpdate: "cascade", 99 + }), 100 + }); 101 + ``` 102 + 103 + ## ID Generation 104 + 105 + Always use CUID2 for primary keys: 106 + 107 + ```typescript 108 + import { createId } from "@paralleldrive/cuid2"; 109 + 110 + id: text("id") 111 + .$defaultFn(() => createId()) 112 + .primaryKey(), 113 + ``` 114 + 115 + ## Timestamps 116 + 117 + Use consistent timestamp patterns: 118 + 119 + ```typescript 120 + // Created timestamp (never changes) 121 + createdAt: timestamp("created_at", { mode: "date" }) 122 + .defaultNow() 123 + .notNull(), 124 + 125 + // Updated timestamp (auto-updates on change) 126 + updatedAt: timestamp("updated_at", { mode: "date" }) 127 + .defaultNow() 128 + .$onUpdate(() => new Date()) 129 + .notNull(), 130 + 131 + // Optional timestamp (e.g., deleted_at) 132 + deletedAt: timestamp("deleted_at", { mode: "date" }), 133 + ``` 134 + 135 + ## Foreign Keys 136 + 137 + Always specify cascade behavior: 138 + 139 + ```typescript 140 + // Good: Explicit cascade behavior 141 + userId: text("user_id") 142 + .notNull() 143 + .references(() => userTable.id, { 144 + onDelete: "cascade", 145 + onUpdate: "cascade", 146 + }), 147 + 148 + // Bad: Missing cascade options 149 + userId: text("user_id").references(() => userTable.id), 150 + ``` 151 + 152 + ### Cascade Options 153 + 154 + - **`onDelete: "cascade"`**: Delete related records when parent is deleted 155 + - **`onUpdate: "cascade"`**: Update foreign key when parent ID changes 156 + - **`onDelete: "set null"`**: Set foreign key to null when parent is deleted (use nullable column) 157 + 158 + ## Indexes 159 + 160 + Add indexes for frequently queried columns: 161 + 162 + ```typescript 163 + export const taskTable = pgTable( 164 + "task", 165 + { 166 + // ... columns 167 + projectId: text("project_id").notNull(), 168 + userId: text("user_id"), 169 + status: text("status").notNull(), 170 + }, 171 + (table) => [ 172 + index("task_projectId_idx").on(table.projectId), 173 + index("task_userId_idx").on(table.userId), 174 + index("task_status_idx").on(table.status), 175 + ], 176 + ); 177 + ``` 7 178 8 - The application uses **PostgreSQL** with **Drizzle ORM** for type-safe database operations and migrations. 179 + ### When to Add Indexes 9 180 10 - ## Core Schema Files 181 + - Foreign key columns (always) 182 + - Columns used in WHERE clauses frequently 183 + - Columns used for sorting/ordering 184 + - Composite indexes for multi-column queries 11 185 12 - - **Schema Definition**: [apps/api/src/database/schema.ts](mdc:apps/api/src/database/schema.ts) - All table definitions 13 - - **Database Connection**: [apps/api/src/database/index.ts](mdc:apps/api/src/database/index.ts) - Database client setup 14 - - **Migration Config**: [apps/api/drizzle.config.ts](mdc:apps/api/drizzle.config.ts) - Drizzle configuration 186 + ## Relations 15 187 16 - ## Migration Management 188 + Define relations in `apps/api/src/database/relations.ts`: 17 189 18 - ### Migration Files 19 - - **Directory**: [apps/api/drizzle/](mdc:apps/api/drizzle) - Contains all migration files 20 - - **Journal**: [apps/api/drizzle/meta/_journal.json](mdc:apps/api/drizzle/meta/_journal.json) - Migration tracking 21 - - **Snapshots**: [apps/api/drizzle/meta/](mdc:apps/api/drizzle/meta) - Schema snapshots for each migration 190 + ```typescript 191 + import { relations } from "drizzle-orm"; 192 + import { taskTable } from "./schema"; 193 + import { projectTable } from "./schema"; 22 194 23 - ### Migration Commands 24 - ```bash 25 - # Generate migration 26 - npm run db:generate 195 + export const taskTableRelations = relations(taskTable, ({ one, many }) => ({ 196 + project: one(projectTable, { 197 + fields: [taskTable.projectId], 198 + references: [projectTable.id], 199 + }), 200 + labels: many(labelTable), 201 + })); 202 + ``` 203 + 204 + ## Data Types 205 + 206 + Use appropriate PostgreSQL types: 207 + 208 + ```typescript 209 + // Text (varchar) 210 + title: text("title").notNull(), 211 + 212 + // Optional text 213 + description: text("description"), 214 + 215 + // Boolean 216 + isActive: boolean("is_active").default(true).notNull(), 217 + 218 + // Integer 219 + position: integer("position").default(0), 27 220 28 - # Apply migrations 29 - npm run db:migrate 221 + // Timestamp 222 + createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 30 223 31 - # View database 32 - npm run db:studio 224 + // JSON (if needed) 225 + metadata: json("metadata").$type<{ key: string }>(), 33 226 ``` 34 227 35 - ## Data Model Overview 228 + ## Default Values 36 229 37 - Based on the project structure, the main entities likely include: 230 + Provide sensible defaults: 38 231 39 - ### Core Entities 40 - - **Users** - User accounts and authentication 41 - - **Workspaces** - Team/organization containers 42 - - **WorkspaceUsers** - User membership in workspaces 43 - - **Projects** - Project containers within workspaces 44 - - **Tasks** - Individual work items within projects 45 - - **Labels** - Task categorization and tagging 46 - - **TimeEntries** - Time tracking for tasks 47 - - **Activities** - Activity logs and comments 48 - - **Notifications** - User notification system 232 + ```typescript 233 + // Boolean defaults 234 + isActive: boolean("is_active").default(true).notNull(), 235 + 236 + // Integer defaults 237 + position: integer("position").default(0), 238 + 239 + // Text defaults 240 + status: text("status").notNull().default("to-do"), 49 241 50 - ### Integration Entities 51 - - **GitHubIntegrations** - GitHub app connections 52 - - **Settings** - Application and user settings 242 + // Timestamp defaults 243 + createdAt: timestamp("created_at", { mode: "date" }).defaultNow().notNull(), 244 + ``` 53 245 54 - ## Schema Patterns 246 + ## Migrations 55 247 56 - ### Primary Keys 57 - - Uses auto-incrementing integers or UUIDs 58 - - Consistent naming: `id` for primary keys 248 + ### Generating Migrations 59 249 60 - ### Foreign Keys 61 - - Clear naming convention for references 62 - - Proper cascading rules for data integrity 250 + After schema changes: 63 251 64 - ### Timestamps 65 - - `createdAt` and `updatedAt` fields on most entities 66 - - Consistent timestamp handling 252 + ```bash 253 + pnpm --filter @kaneo/api db:generate 254 + ``` 67 255 68 - ### Soft Deletes 69 - - May use `deletedAt` for soft deletion patterns 70 - - Preserves data integrity and audit trails 256 + This creates migration files in `apps/api/drizzle/`. 71 257 72 - ## Database Operations 258 + ### Running Migrations 73 259 74 - ### Query Examples 260 + Migrations run automatically on API startup (see `apps/api/src/index.ts`): 261 + 75 262 ```typescript 76 - // Import from schema 77 - import { db } from './database' 78 - import { tasks, projects } from './database/schema' 263 + await migrate(db, { 264 + migrationsFolder: `${process.cwd()}/drizzle`, 265 + }); 266 + ``` 267 + 268 + ### Migration Best Practices 269 + 270 + 1. **Never edit existing migrations**: Create new migrations for changes 271 + 2. **Test migrations**: Test both up and down migrations 272 + 3. **Backup first**: Always backup production data before migrations 273 + 4. **Review SQL**: Check generated SQL before applying 274 + 275 + ## Schema Export 276 + 277 + Export all schemas and relations from `apps/api/src/database/index.ts`: 278 + 279 + ```typescript 280 + export const schema = { 281 + taskTable, 282 + projectTable, 283 + // ... all tables 284 + taskTableRelations, 285 + projectTableRelations, 286 + // ... all relations 287 + }; 79 288 80 - // Type-safe queries with Drizzle 81 - const userTasks = await db 82 - .select() 83 - .from(tasks) 84 - .where(eq(tasks.userId, userId)) 289 + const db = drizzle(pool, { 290 + schema: schema, 291 + }); 85 292 ``` 86 293 87 - ### Transaction Patterns 88 - - Use database transactions for multi-table operations 89 - - Ensure data consistency across related entities 294 + This enables Drizzle's relational queries and type inference.
+383 -85
.cursor/rules/deployment-devops.mdc
··· 1 1 --- 2 - description: 3 - globs: 2 + description: Deployment and DevOps guidelines for Docker Compose and Kubernetes 3 + globs: compose.yml,charts/**/* 4 4 alwaysApply: false 5 5 --- 6 - # Deployment and DevOps 6 + # Deployment & DevOps Guidelines 7 + 8 + Kaneo supports multiple deployment methods: Docker Compose for development/testing and Kubernetes for production. This document outlines deployment patterns and best practices. 9 + 10 + ## Docker Compose 7 11 8 - This document covers the deployment infrastructure, containerization, and DevOps practices for the Kaneo project. 12 + ### Configuration 9 13 10 - ## Containerization 14 + The main Docker Compose configuration is in `compose.yml` at the project root. 11 15 12 - ### Docker Configuration 13 - - **API Dockerfile**: [apps/api/Dockerfile](mdc:apps/api/Dockerfile) - Backend API container 14 - - **Web Dockerfile**: [apps/web/Dockerfile](mdc:apps/web/Dockerfile) - Frontend application container 15 - - **Environment Script**: [apps/web/env.sh](mdc:apps/web/env.sh) - Runtime environment configuration 16 + ### Service Structure 17 + 18 + ```yaml 19 + services: 20 + postgres: 21 + image: postgres:16-alpine 22 + env_file: 23 + - .env 24 + ports: 25 + - "5432:5432" 26 + volumes: 27 + - postgres_data:/var/lib/postgresql/data 28 + restart: unless-stopped 29 + healthcheck: 30 + test: ["CMD-SHELL", "pg_isready -U kaneo -d kaneo"] 31 + interval: 10s 32 + timeout: 5s 33 + retries: 5 16 34 17 - ### Container Patterns 18 - - **Multi-stage builds** for optimized production images 19 - - **Node.js Alpine** base images for smaller footprint 20 - - **Environment variables** for runtime configuration 21 - - **Health checks** for container monitoring 35 + api: 36 + image: ghcr.io/usekaneo/api:latest 37 + ports: 38 + - "1337:1337" 39 + env_file: 40 + - .env 41 + depends_on: 42 + postgres: 43 + condition: service_healthy 44 + restart: unless-stopped 22 45 23 - ## Kubernetes Deployment 46 + web: 47 + image: ghcr.io/usekaneo/web:latest 48 + ports: 49 + - "5173:5173" 50 + env_file: 51 + - .env 52 + depends_on: 53 + - api 54 + restart: unless-stopped 55 + ``` 24 56 25 - ### Helm Charts 26 - The project uses **Helm** for Kubernetes deployments: 57 + ### Best Practices 27 58 28 - - **Chart Directory**: [charts/kaneo/](mdc:charts/kaneo) - Main Helm chart 29 - - **Chart.yaml**: [charts/kaneo/Chart.yaml](mdc:charts/kaneo/Chart.yaml) - Chart metadata 30 - - **Values**: [charts/kaneo/values.yaml](mdc:charts/kaneo/values.yaml) - Default configuration values 59 + 1. **Health Checks**: Always include health checks for databases 60 + 2. **Dependencies**: Use `depends_on` with `condition: service_healthy` for databases 61 + 3. **Volumes**: Use named volumes for persistent data 62 + 4. **Restart Policy**: Use `unless-stopped` for production-like behavior 63 + 5. **Environment Variables**: Use `.env` file, never hardcode secrets 31 64 32 - ### Kubernetes Resources 65 + ```yaml 66 + # Good: Proper health check and dependency 67 + services: 68 + api: 69 + depends_on: 70 + postgres: 71 + condition: service_healthy 33 72 34 - Located in [charts/kaneo/templates/](mdc:charts/kaneo/templates): 73 + postgres: 74 + healthcheck: 75 + test: ["CMD-SHELL", "pg_isready -U kaneo -d kaneo"] 76 + interval: 10s 77 + timeout: 5s 78 + retries: 5 79 + ``` 35 80 36 - - **Deployment**: [charts/kaneo/templates/deployment.yaml](mdc:charts/kaneo/templates/deployment.yaml) - Application deployment 37 - - **Service**: Application service definitions 38 - - **Ingress**: External traffic routing 39 - - **ConfigMap**: Configuration management 40 - - **Secret**: Sensitive data management 41 - - **HPA**: [charts/kaneo/templates/hpa.yaml](mdc:charts/kaneo/templates/hpa.yaml) - Horizontal Pod Autoscaler 81 + ## Kubernetes (Helm Charts) 42 82 43 - ### Deployment Features 44 - - **Horizontal Pod Autoscaling** for traffic-based scaling 45 - - **Rolling updates** for zero-downtime deployments 46 - - **Resource limits** and requests for efficient resource usage 47 - - **Liveness and readiness probes** for health monitoring 83 + ### Chart Structure 48 84 49 - ## Environment Configuration 85 + Helm charts are located in `charts/kaneo/`: 86 + 87 + ``` 88 + charts/kaneo/ 89 + ├── Chart.yaml # Chart metadata 90 + ├── values.yaml # Default values 91 + ├── README.md # Chart documentation 92 + └── templates/ 93 + ├── deployment.yaml # API and Web deployments 94 + ├── services.yaml # Service definitions 95 + ├── ingress.yaml # Ingress configuration 96 + ├── postgresql-deployment.yaml # PostgreSQL deployment 97 + ├── pvc.yaml # Persistent volume claims 98 + ├── hpa.yaml # Horizontal Pod Autoscaler 99 + └── serviceaccount.yaml # Service accounts 100 + ``` 101 + 102 + ### Deployment Patterns 103 + 104 + #### API Deployment 105 + 106 + ```yaml 107 + apiVersion: apps/v1 108 + kind: Deployment 109 + metadata: 110 + name: {{ include "kaneo.fullname" . }}-api 111 + spec: 112 + replicas: {{ .Values.api.replicaCount }} 113 + template: 114 + spec: 115 + containers: 116 + - name: api 117 + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag }}" 118 + env: 119 + - name: DATABASE_URL 120 + valueFrom: 121 + secretKeyRef: 122 + name: {{ include "kaneo.fullname" . }}-secrets 123 + key: database-url 124 + ``` 125 + 126 + #### Web Deployment 127 + 128 + ```yaml 129 + apiVersion: apps/v1 130 + kind: Deployment 131 + metadata: 132 + name: {{ include "kaneo.fullname" . }}-web 133 + spec: 134 + replicas: {{ .Values.web.replicaCount }} 135 + template: 136 + spec: 137 + containers: 138 + - name: web 139 + image: "{{ .Values.web.image.repository }}:{{ .Values.web.image.tag }}" 140 + env: 141 + - name: VITE_API_URL 142 + value: {{ .Values.web.apiUrl | quote }} 143 + ``` 144 + 145 + ### Best Practices 146 + 147 + 1. **Secrets Management**: Use Kubernetes Secrets, never hardcode 148 + 2. **ConfigMaps**: Use ConfigMaps for non-sensitive configuration 149 + 3. **Resource Limits**: Always set resource requests and limits 150 + 4. **Health Checks**: Include liveness and readiness probes 151 + 5. **Replicas**: Configure appropriate replica counts for high availability 152 + 153 + ```yaml 154 + # Good: Proper resource limits and health checks 155 + containers: 156 + - name: api 157 + resources: 158 + requests: 159 + memory: "256Mi" 160 + cpu: "100m" 161 + limits: 162 + memory: "512Mi" 163 + cpu: "500m" 164 + livenessProbe: 165 + httpGet: 166 + path: /api/health 167 + port: 1337 168 + initialDelaySeconds: 30 169 + periodSeconds: 10 170 + readinessProbe: 171 + httpGet: 172 + path: /api/health 173 + port: 1337 174 + initialDelaySeconds: 5 175 + periodSeconds: 5 176 + ``` 177 + 178 + ## Environment Variables 50 179 51 180 ### Development 52 - - **Local development** with Docker Compose (likely) 53 - - **Hot reloading** for faster iteration 54 - - **Development database** setup 181 + 182 + All environment variables in a single `.env` file at project root: 183 + 184 + ```bash 185 + # Database 186 + DATABASE_URL=postgresql://kaneo_user:kaneo_password@localhost:5432/kaneo 187 + POSTGRES_DB=kaneo 188 + POSTGRES_USER=kaneo_user 189 + POSTGRES_PASSWORD=kaneo_password 190 + 191 + # API 192 + KANEO_API_URL=http://localhost:1337 193 + AUTH_SECRET=your-secret-key 194 + 195 + # Web 196 + KANEO_CLIENT_URL=http://localhost:5173 197 + VITE_API_URL=http://localhost:1337 198 + ``` 55 199 56 200 ### Production 57 - - **Kubernetes cluster** deployment 58 - - **External database** (managed PostgreSQL) 59 - - **CDN integration** for static assets 60 - - **SSL/TLS termination** at ingress level 61 201 62 - ## CI/CD Pipeline 202 + Use Kubernetes Secrets and ConfigMaps: 63 203 64 - ### Build Process 65 - - **Monorepo builds** with optimized caching 66 - - **Multi-stage Docker builds** for production efficiency 67 - - **TypeScript compilation** and type checking 68 - - **Linting and formatting** with Biome 204 + ```yaml 205 + # Secret for sensitive data 206 + apiVersion: v1 207 + kind: Secret 208 + metadata: 209 + name: kaneo-secrets 210 + type: Opaque 211 + stringData: 212 + database-url: postgresql://user:pass@postgres:5432/kaneo 213 + auth-secret: your-secret-key 214 + 215 + # ConfigMap for non-sensitive data 216 + apiVersion: v1 217 + kind: ConfigMap 218 + metadata: 219 + name: kaneo-config 220 + data: 221 + api-url: https://api.example.com 222 + client-url: https://app.example.com 223 + ``` 224 + 225 + ## Database Migrations 226 + 227 + ### Automatic Migrations 228 + 229 + Migrations run automatically on API startup: 230 + 231 + ```typescript 232 + // apps/api/src/index.ts 233 + await migrate(db, { 234 + migrationsFolder: `${process.cwd()}/drizzle`, 235 + }); 236 + ``` 237 + 238 + ### Manual Migrations 69 239 70 - ### Deployment Strategy 71 - - **GitOps approach** with Helm charts 72 - - **Environment promotion** (dev → staging → production) 73 - - **Automated testing** before deployment 74 - - **Rollback capabilities** for quick recovery 240 + For production, consider running migrations separately: 241 + 242 + ```bash 243 + # In Kubernetes, use a Job 244 + kubectl run migrate-db --image=ghcr.io/usekaneo/api:latest -- \ 245 + node -e "require('./dist/migrate.js')" 246 + ``` 247 + 248 + ## Health Checks 249 + 250 + ### API Health Endpoint 75 251 76 - ## Monitoring and Observability 252 + The API exposes a health check endpoint: 77 253 78 - ### Application Monitoring 79 - - **Health check endpoints** in API 80 - - **Application metrics** collection 81 - - **Error tracking** and alerting 82 - - **Performance monitoring** 254 + ```typescript 255 + // apps/api/src/index.ts 256 + api.get("/health", (c) => { 257 + return c.json({ status: "ok" }); 258 + }); 259 + ``` 83 260 84 - ### Infrastructure Monitoring 85 - - **Kubernetes cluster** health monitoring 86 - - **Resource utilization** tracking 87 - - **Pod status** and availability metrics 88 - - **Network traffic** monitoring 261 + ### Kubernetes Probes 89 262 90 - ## Configuration Management 263 + ```yaml 264 + livenessProbe: 265 + httpGet: 266 + path: /api/health 267 + port: 1337 268 + initialDelaySeconds: 30 269 + periodSeconds: 10 91 270 92 - ### Environment Variables 93 - - **Database connection** strings 94 - - **API keys** and secrets 95 - - **Feature flags** for environment-specific behavior 96 - - **Third-party service** configurations 271 + readinessProbe: 272 + httpGet: 273 + path: /api/health 274 + port: 1337 275 + initialDelaySeconds: 5 276 + periodSeconds: 5 277 + ``` 97 278 98 - ### Secrets Management 99 - - **Kubernetes Secrets** for sensitive data 100 - - **External secret management** (vault, cloud providers) 101 - - **Secure key rotation** practices 102 - - **Encryption at rest** and in transit 279 + ## Scaling 103 280 104 - ## Scaling Strategies 281 + ### Horizontal Pod Autoscaling 105 282 106 - ### Horizontal Scaling 107 - - **Pod replication** based on CPU/memory usage 108 - - **Load balancing** across multiple instances 109 - - **Database connection pooling** 110 - - **Stateless application design** 283 + Configure HPA in `charts/kaneo/templates/hpa.yaml`: 111 284 112 - ### Performance Optimization 113 - - **CDN usage** for static assets 114 - - **Database query optimization** 115 - - **Caching strategies** (Redis, in-memory) 116 - - **Bundle optimization** for frontend assets 285 + ```yaml 286 + apiVersion: autoscaling/v2 287 + kind: HorizontalPodAutoscaler 288 + metadata: 289 + name: {{ include "kaneo.fullname" . }}-api 290 + spec: 291 + minReplicas: 2 292 + maxReplicas: 10 293 + metrics: 294 + - type: Resource 295 + resource: 296 + name: cpu 297 + target: 298 + type: Utilization 299 + averageUtilization: 70 300 + ``` 301 + 302 + ## Ingress 303 + 304 + ### TLS Configuration 305 + 306 + Always use TLS in production: 307 + 308 + ```yaml 309 + apiVersion: networking.k8s.io/v1 310 + kind: Ingress 311 + metadata: 312 + name: kaneo-ingress 313 + annotations: 314 + cert-manager.io/cluster-issuer: letsencrypt-prod 315 + spec: 316 + tls: 317 + - hosts: 318 + - app.example.com 319 + secretName: kaneo-tls 320 + rules: 321 + - host: app.example.com 322 + http: 323 + paths: 324 + - path: / 325 + pathType: Prefix 326 + backend: 327 + service: 328 + name: kaneo-web 329 + port: 330 + number: 80 331 + ``` 332 + 333 + ## Monitoring 334 + 335 + ### Logging 336 + 337 + - Use structured logging 338 + - Include request IDs for tracing 339 + - Log errors with context 340 + 341 + ### Metrics 342 + 343 + - Expose Prometheus metrics if needed 344 + - Monitor database connection pools 345 + - Track API response times 346 + 347 + ## Backup & Recovery 348 + 349 + ### Database Backups 350 + 351 + For PostgreSQL: 352 + 353 + ```bash 354 + # Backup 355 + kubectl exec -it postgres-pod -- pg_dump -U kaneo kaneo > backup.sql 356 + 357 + # Restore 358 + kubectl exec -i postgres-pod -- psql -U kaneo kaneo < backup.sql 359 + ``` 360 + 361 + ### Volume Backups 362 + 363 + Use volume snapshots for persistent data: 364 + 365 + ```yaml 366 + apiVersion: snapshot.storage.k8s.io/v1 367 + kind: VolumeSnapshot 368 + metadata: 369 + name: postgres-snapshot 370 + spec: 371 + source: 372 + persistentVolumeClaimName: postgres-pvc 373 + ``` 374 + 375 + ## Security 376 + 377 + ### Image Security 378 + 379 + - Use specific image tags, not `latest` 380 + - Scan images for vulnerabilities 381 + - Use minimal base images (Alpine Linux) 382 + 383 + ### Network Policies 384 + 385 + Restrict pod-to-pod communication: 386 + 387 + ```yaml 388 + apiVersion: networking.k8s.io/v1 389 + kind: NetworkPolicy 390 + metadata: 391 + name: kaneo-api-policy 392 + spec: 393 + podSelector: 394 + matchLabels: 395 + app: kaneo-api 396 + policyTypes: 397 + - Ingress 398 + - Egress 399 + ingress: 400 + - from: 401 + - podSelector: 402 + matchLabels: 403 + app: kaneo-web 404 + ports: 405 + - protocol: TCP 406 + port: 1337 407 + ``` 408 + 409 + ### Secrets Management 410 + 411 + - Never commit secrets to Git 412 + - Use Kubernetes Secrets or external secret managers 413 + - Rotate secrets regularly 414 + - Use least privilege principle
+296 -107
.cursor/rules/development-conventions.mdc
··· 1 1 --- 2 - description: 3 - globs: 4 - alwaysApply: false 2 + description: Development conventions, code style, and best practices for the Kaneo project 3 + globs: 4 + alwaysApply: true 5 5 --- 6 - # Development Conventions and Best Practices 6 + # Development Conventions 7 7 8 - This document outlines the coding standards, patterns, and conventions used throughout the Kaneo project. 8 + This document outlines coding standards, conventions, and best practices for the Kaneo project. 9 9 10 - ## Code Quality Tools 10 + ## Code Style 11 11 12 - - **Biome**: [biome.json](mdc:biome.json) - Linting and formatting configuration 13 - - **Commitlint**: [commitlint.config.js](mdc:commitlint.config.js) - Commit message conventions 14 - - **TypeScript**: Strict type checking across all applications 12 + ### Biome Configuration 13 + 14 + Kaneo uses **Biome** for linting and formatting. Configuration is in `biome.json`. 15 + 16 + #### Formatting Rules 17 + 18 + - **Indentation**: Tabs (not spaces) for TypeScript/TSX 19 + - **JavaScript**: Spaces for indentation (legacy support) 20 + - **Quote Style**: Double quotes (`"`) 21 + - **Semicolons**: Required 22 + 23 + ```typescript 24 + // Good: Tabs, double quotes, semicolons 25 + function example() { 26 + const value = "hello"; 27 + return value; 28 + } 15 29 16 - ## File and Directory Naming 30 + // Bad: Spaces, single quotes, no semicolons 31 + function example() { 32 + const value = 'hello' 33 + return value 34 + } 35 + ``` 17 36 18 - ### General Conventions 19 - - **kebab-case** for directory names: `github-integration/`, `time-entry/` 20 - - **kebab-case** for file names: `create-task.ts`, `sign-in-form.tsx` 21 - - **PascalCase** for React components: `TaskCard.tsx`, `SignInForm.tsx` 37 + #### Linting Rules 22 38 23 - ### Backend API Conventions 24 - - **Controllers**: `{action}-{resource}.ts` pattern 25 - - Examples: `create-task.ts`, `get-project.ts`, `delete-workspace.ts` 26 - - **Utils**: Descriptive names in `utils/` directories 27 - - **Domain Modules**: Each module has `controllers/` and `index.ts` 39 + Key Biome rules enabled: 28 40 29 - ### Frontend Conventions 30 - - **Components**: Organized by feature in `components/` 31 - - **Hooks**: Custom hooks in `hooks/mutations/` and `hooks/queries/` 32 - - **Fetchers**: API calls organized by domain in `fetchers/` 33 - - **Types**: TypeScript definitions in `types/` by domain 41 + - `noParameterAssign`: Don't reassign function parameters 42 + - `useAsConstAssertion`: Use `as const` for literal types 43 + - `useDefaultParameterLast`: Default parameters must be last 44 + - `useSelfClosingElements`: Use self-closing JSX elements 45 + - `useSingleVarDeclarator`: One variable per declaration 46 + - `noInferrableTypes`: Don't add explicit types that can be inferred 34 47 35 - ## Code Organization Patterns 48 + ```typescript 49 + // Good: Follows Biome rules 50 + const items = ["a", "b"] as const; 51 + function greet(name: string, greeting = "Hello") { 52 + return `${greeting}, ${name}`; 53 + } 54 + const x = 1; 55 + const y = 2; 36 56 37 - ### Backend Architecture 57 + // Bad: Violates Biome rules 58 + const items: string[] = ["a", "b"]; 59 + function greet(greeting = "Hello", name: string) { 60 + return `${greeting}, ${name}`; 61 + } 62 + const x = 1, y = 2; 38 63 ``` 39 - src/ 40 - ├── {domain}/ 41 - │ ├── controllers/ 42 - │ │ ├── create-{resource}.ts 43 - │ │ ├── get-{resource}.ts 44 - │ │ └── update-{resource}.ts 45 - │ ├── utils/ (optional) 46 - │ └── index.ts (exports routes) 64 + 65 + ### Running Linter 66 + 67 + ```bash 68 + # Check and auto-fix 69 + pnpm lint 70 + 71 + # Check only (no fixes) 72 + pnpm biome check . 47 73 ``` 48 74 49 - ### Frontend Architecture 75 + ## TypeScript Conventions 76 + 77 + ### Type Definitions 78 + 79 + - **Types**: Prefer types for all type definitions (object shapes, unions, intersections, computed types) 80 + - **Interfaces**: Only use interfaces when extending/merging is needed (rare) 81 + - **Inference**: Prefer type inference when types are obvious 82 + 83 + ```typescript 84 + // Good: Type for object shape 85 + type Task = { 86 + id: string; 87 + title: string; 88 + status: string; 89 + }; 90 + 91 + // Good: Type for union 92 + type Status = "to-do" | "in-progress" | "done"; 93 + 94 + // Good: Type inference 95 + const tasks: Task[] = []; // Explicit for arrays 96 + const count = tasks.length; // Inferred 50 97 ``` 51 - src/ 52 - ├── components/ 53 - │ ├── {feature}/ 54 - │ │ ├── index.tsx 55 - │ │ └── {sub-components}.tsx 56 - ├── fetchers/ 57 - │ └── {domain}/ 58 - ├── hooks/ 59 - │ ├── mutations/ 60 - │ └── queries/ 61 - └── types/ 62 - └── {domain}/ 98 + 99 + ### File Naming 100 + 101 + - **Components**: PascalCase: `TaskCard.tsx` 102 + - **Utilities**: kebab-case: `format-date.ts` 103 + - **Hooks**: camelCase with `use` prefix: `use-task.ts` 104 + - **Types**: kebab-case: `task-types.ts` 105 + 106 + ``` 107 + components/ 108 + ├── TaskCard.tsx # Component 109 + ├── task-card.tsx # Also acceptable 110 + └── utils/ 111 + └── format-date.ts # Utility function 112 + ``` 113 + 114 + ## Git Conventions 115 + 116 + ### Commit Messages 117 + 118 + Use [Conventional Commits](https://www.conventionalcommits.org/): 119 + 120 + ``` 121 + <type>: <description> 122 + 123 + [optional body] 124 + 125 + [optional footer] 126 + ``` 127 + 128 + #### Commit Types 129 + 130 + - `feat:` - New features 131 + - `fix:` - Bug fixes 132 + - `docs:` - Documentation changes 133 + - `refactor:` - Code refactoring (no feature/fix) 134 + - `test:` - Adding or updating tests 135 + - `chore:` - Maintenance tasks (deps, config, etc.) 136 + - `style:` - Code style changes (formatting, etc.) 137 + 138 + #### Examples 139 + 140 + ```bash 141 + feat: add bulk task operations 142 + fix: resolve calendar date selection bug 143 + docs: update deployment guide 144 + refactor: simplify task controller logic 145 + chore: update dependencies 146 + ``` 147 + 148 + ### Branch Naming 149 + 150 + Use descriptive branch names with prefixes: 151 + 152 + - `feat/` - New features 153 + - `fix/` - Bug fixes 154 + - `docs/` - Documentation 155 + - `refactor/` - Refactoring 156 + - `chore/` - Maintenance 157 + 158 + ```bash 159 + feat/bulk-task-operations 160 + fix/calendar-date-bug 161 + docs/update-deployment-guide 162 + ``` 163 + 164 + ## Import Organization 165 + 166 + Biome automatically organizes imports. Follow these patterns: 167 + 168 + ### Import Order 169 + 170 + 1. External packages 171 + 2. Internal packages (`@/` aliases) 172 + 3. Relative imports 173 + 174 + ```typescript 175 + // Good: Organized imports 176 + import { useState } from "react"; 177 + import { useQuery } from "@tanstack/react-query"; 178 + import { Button } from "@/components/ui/button"; 179 + import { TaskCard } from "./task-card"; 63 180 ``` 64 181 65 - ## TypeScript Conventions 182 + ### Import Style 66 183 67 - ### Import Organization 68 - 1. **External libraries** (React, Hono, etc.) 69 - 2. **Internal shared packages** (@kaneo/libs) 70 - 3. **Relative imports** (./components, ../utils) 184 + - Use named imports when possible 185 + - Group related imports 186 + - Remove unused imports (Biome does this automatically) 187 + 188 + ```typescript 189 + // Good: Named imports 190 + import { useState, useEffect } from "react"; 191 + import { Button, Input } from "@/components/ui"; 71 192 72 - ### Type Definitions 73 - - **Interfaces** for object shapes 74 - - **Types** for unions and computed types 75 - - **Enums** for constants with meaningful names 76 - - **Generics** for reusable type patterns 193 + // Avoid: Default imports when named available 194 + import React from "react"; // Prefer named imports 195 + ``` 77 196 78 - ## React Patterns 197 + ## File Structure 79 198 80 - ### Component Structure 81 - ```tsx 82 - // Props type 83 - type ComponentProps { 84 - prop: string 85 - } 199 + ### Component Files 86 200 87 - // Component with TypeScript 88 - function Component({ prop }: ComponentProps) { 89 - // Hooks first 90 - const [state, setState] = useState() 201 + ```typescript 202 + // 1. Imports (external, then internal) 203 + import { useState } from "react"; 204 + import { Button } from "@/components/ui/button"; 91 205 92 - // Event handlers 93 - const handleClick = () => {} 206 + // 2. Types 207 + type ComponentProps = { 208 + title: string; 209 + }; 94 210 95 - // Render 96 - return <div>{prop}</div> 211 + // 3. Component 212 + export function Component({ title }: ComponentProps) { 213 + // Implementation 97 214 } 98 215 216 + // 4. Exports (if needed) 99 217 export default Component; 100 218 ``` 101 219 102 - ### State Management 103 - - **TanStack Query** for server state 104 - - **Zustand** for client state: [apps/web/src/store/](mdc:apps/web/src/store) 105 - - **React Context** for theme and auth: [apps/web/src/components/providers/](mdc:apps/web/src/components/providers) 220 + ### API Controller Files 106 221 107 - ## API Design Patterns 222 + ```typescript 223 + // 1. Imports 224 + import db from "../../database"; 225 + import { taskTable } from "../../database/schema"; 108 226 109 - ### REST Conventions 110 - - **GET** `/api/{resource}` - List resources 111 - - **GET** `/api/{resource}/{id}` - Get single resource 112 - - **POST** `/api/{resource}` - Create resource 113 - - **PUT** `/api/{resource}/{id}` - Update resource 114 - - **DELETE** `/api/{resource}/{id}` - Delete resource 227 + // 2. Function 228 + export default async function getTask(id: string) { 229 + // Implementation 230 + } 231 + ``` 232 + 233 + ## Error Handling 115 234 116 - ### Response Format 235 + ### Backend 236 + 237 + Use Hono's HTTPException: 238 + 117 239 ```typescript 118 - // Success response 119 - { 120 - data: T, 121 - success: true 240 + import { HTTPException } from "hono/http-exception"; 241 + 242 + if (!task) { 243 + throw new HTTPException(404, { message: "Task not found" }); 122 244 } 245 + ``` 246 + 247 + ### Frontend 248 + 249 + Use try-catch with user-friendly messages: 123 250 124 - // Error response 125 - { 126 - error: string, 127 - success: false 251 + ```typescript 252 + import { toast } from "sonner"; 253 + 254 + try { 255 + await updateTask(taskId, data); 256 + toast.success("Task updated"); 257 + } catch (error) { 258 + toast.error( 259 + error instanceof Error ? error.message : "Failed to update task", 260 + ); 128 261 } 129 262 ``` 130 263 131 - ## Git Conventions 264 + ## Testing 132 265 133 - ### Commit Messages 134 - Following [Conventional Commits](mdc:https:/www.conventionalcommits.org): 135 - - `feat:` new features 136 - - `fix:` bug fixes 137 - - `docs:` documentation changes 138 - - `style:` formatting changes 139 - - `refactor:` code refactoring 140 - - `test:` adding tests 141 - - `chore:` maintenance tasks 266 + ### Test Structure 142 267 143 - ### Branch Naming 144 - - `feature/description` for new features 145 - - `fix/description` for bug fixes 146 - - `docs/description` for documentation 147 - - `refactor/description` for refactoring 268 + ```typescript 269 + describe("Feature", () => { 270 + it("should do something", () => { 271 + // Arrange 272 + const input = "test"; 273 + 274 + // Act 275 + const result = functionUnderTest(input); 276 + 277 + // Assert 278 + expect(result).toBe("expected"); 279 + }); 280 + }); 281 + ``` 282 + 283 + ## Documentation 284 + 285 + ### Code Comments 286 + 287 + - **Why, not what**: Explain reasoning, not obvious code 288 + - **Complex logic**: Comment complex algorithms or business rules 289 + - **TODOs**: Use `// TODO: description` for future work 290 + 291 + ```typescript 292 + // Good: Explains why 293 + // Use CUID2 instead of UUID for better database performance 294 + const id = createId(); 295 + 296 + // Bad: States the obvious 297 + // Create an ID 298 + const id = createId(); 299 + ``` 300 + 301 + ### README Files 302 + 303 + - Keep README files updated 304 + - Include setup instructions 305 + - Document environment variables 306 + - Provide examples 307 + 308 + ## Environment Variables 309 + 310 + - **Single `.env` file**: All variables in project root 311 + - **Documentation**: Document all variables in `ENVIRONMENT_SETUP.md` 312 + - **Never commit**: `.env` files are gitignored 313 + - **Defaults**: Provide sensible defaults in code when possible 314 + 315 + ## Performance 316 + 317 + ### Backend 318 + 319 + - Use database indexes for frequently queried columns 320 + - Limit query results with pagination 321 + - Use transactions for multi-step operations 322 + 323 + ### Frontend 324 + 325 + - Use React Query for caching and data fetching 326 + - Implement proper loading states 327 + - Lazy load routes when possible 328 + - Optimize images and assets 329 + 330 + ## Security 331 + 332 + - **Never commit secrets**: Use environment variables 333 + - **Validate inputs**: Always validate user input (Valibot on backend) 334 + - **Sanitize outputs**: Sanitize data before rendering 335 + - **Authentication**: Always check authentication in protected routes 336 + - **Authorization**: Verify user permissions before operations
+285 -51
.cursor/rules/frontend-web.mdc
··· 1 1 --- 2 - description: 3 - globs: 2 + description: Frontend web development guidelines for React, TanStack Router, and TanStack Query 3 + globs: apps/web/**/* 4 4 alwaysApply: false 5 5 --- 6 - # Frontend Web Application Structure 6 + # Frontend Web Guidelines 7 7 8 - The frontend web application is located in [apps/web/](mdc:apps/web) and is built with React, Vite, and TanStack Router. 8 + The Kaneo web application is built with React, TanStack Router, TanStack Query, and Vite. This document outlines conventions and patterns for frontend development. 9 9 10 - ## Project Structure 10 + ## Framework: React 11 11 12 - ### Core Files 13 - - **Entry Point**: [apps/web/src/main.tsx](mdc:apps/web/src/main.tsx) - React application root 14 - - **Root Route**: [apps/web/src/routes/__root.tsx](mdc:apps/web/src/routes/__root.tsx) - Root layout and providers 15 - - **Router Config**: [apps/web/src/tanstack/router.tsx](mdc:apps/web/src/tanstack/router.tsx) - TanStack Router setup 16 - - **Route Tree**: [apps/web/src/routeTree.gen.ts](mdc:apps/web/src/routeTree.gen.ts) - Auto-generated route tree 17 - - **Global Styles**: [apps/web/src/index.css](mdc:apps/web/src/index.css) - Tailwind CSS imports 12 + ### Component Structure 18 13 19 - ### Routing Structure (`src/routes/`) 20 - - **Dashboard**: [apps/web/src/routes/dashboard.tsx](mdc:apps/web/src/routes/dashboard.tsx) - Main dashboard layout 21 - - **Auth Routes**: [apps/web/src/routes/auth/](mdc:apps/web/src/routes/auth) - Sign in/up pages 22 - - **Home**: [apps/web/src/routes/index.tsx](mdc:apps/web/src/routes/index.tsx) - Landing page 14 + Components are organized in `apps/web/src/components/`: 23 15 24 - ### Components Architecture (`src/components/`) 16 + ```typescript 17 + // apps/web/src/components/task/task-card.tsx 18 + import { Task } from "@/types/task"; 25 19 26 - #### Feature Components 27 - - **Auth**: [apps/web/src/components/auth/](mdc:apps/web/src/components/auth) - Authentication forms and layouts 28 - - **Dashboard**: [apps/web/src/components/dashboard/](mdc:apps/web/src/components/dashboard) - Dashboard specific components 29 - - **Task Management**: 30 - - [apps/web/src/components/kanban-board/](mdc:apps/web/src/components/kanban-board) - Kanban board view 31 - - [apps/web/src/components/list-view/](mdc:apps/web/src/components/list-view) - List view for tasks 32 - - [apps/web/src/components/backlog-list-view/](mdc:apps/web/src/components/backlog-list-view) - Backlog view 33 - - [apps/web/src/components/task/](mdc:apps/web/src/components/task) - Task detail components 34 - - **Project**: [apps/web/src/components/project/](mdc:apps/web/src/components/project) - Project management components 35 - - **Team**: [apps/web/src/components/team/](mdc:apps/web/src/components/team) - Team management components 20 + type TaskCardProps = { 21 + task: Task; 22 + onUpdate?: (task: Task) => void; 23 + }; 36 24 37 - #### Common Components 38 - - **UI Library**: [apps/web/src/components/ui/](mdc:apps/web/src/components/ui) - Shadcn/ui components 39 - - **Common**: [apps/web/src/components/common/](mdc:apps/web/src/components/common) - Shared components (Editor, Logo, Sidebar) 40 - - **Providers**: [apps/web/src/components/providers/](mdc:apps/web/src/components/providers) - React context providers 25 + export function TaskCard({ task, onUpdate }: TaskCardProps) { 26 + return ( 27 + <div className="p-4 border rounded-lg"> 28 + <h3>{task.title}</h3> 29 + {/* ... */} 30 + </div> 31 + ); 32 + } 33 + ``` 41 34 42 - ## Data Management 35 + ### Best Practices 43 36 44 - ### API Integration (`src/fetchers/`) 45 - - **TanStack Query**: [apps/web/src/query-client/index.ts](mdc:apps/web/src/query-client/index.ts) - Query client setup 46 - - **Fetchers**: Domain-specific API calls organized by feature (task, project, user, etc.) 47 - - **Hooks**: 48 - - **Mutations**: [apps/web/src/hooks/mutations/](mdc:apps/web/src/hooks/mutations) - Data mutation hooks 49 - - **Queries**: [apps/web/src/hooks/queries/](mdc:apps/web/src/hooks/queries) - Data fetching hooks 37 + 1. **TypeScript**: Always type props and component return types 38 + 2. **Component naming**: Use PascalCase for component files and exports 39 + 3. **Props destructuring**: Destructure props in function parameters 40 + 4. **Conditional rendering**: Use ternary or logical operators clearly 50 41 51 - ### State Management (`src/store/`) 52 - - **Zustand Stores**: 53 - - [apps/web/src/store/project.ts](mdc:apps/web/src/store/project.ts) - Project state 54 - - [apps/web/src/store/workspace.ts](mdc:apps/web/src/store/workspace.ts) - Workspace state 55 - - [apps/web/src/store/user-preferences.ts](mdc:apps/web/src/store/user-preferences.ts) - User preferences 42 + ```typescript 43 + // Good: Typed component with clear structure 44 + type ButtonProps = { 45 + variant?: "primary" | "secondary"; 46 + children: React.ReactNode; 47 + onClick?: () => void; 48 + }; 56 49 57 - ## Type Definitions (`src/types/`) 58 - - **API Types**: [apps/web/src/types/api-response.ts](mdc:apps/web/src/types/api-response.ts) 59 - - **Domain Types**: Organized by feature (task, project, workspace, etc.) 50 + export function Button({ variant = "primary", children, onClick }: ButtonProps) { 51 + return ( 52 + <button 53 + className={cn("px-4 py-2", variant === "primary" && "bg-blue-500")} 54 + onClick={onClick} 55 + > 56 + {children} 57 + </button> 58 + ); 59 + } 60 60 61 - ## Utilities and Constants 62 - - **Utilities**: [apps/web/src/lib/](mdc:apps/web/src/lib) - Helper functions and utilities 63 - - **Constants**: [apps/web/src/constants/](mdc:apps/web/src/constants) - App constants (columns, colors, icons) 61 + // Bad: Untyped, unclear structure 62 + export function Button(props: any) { 63 + return <button onClick={props.onClick}>{props.children}</button>; 64 + } 65 + ``` 64 66 65 - ## Configuration 66 - - **Components**: [apps/web/components.json](mdc:apps/web/components.json) - Shadcn/ui configuration 67 - - **Package**: [apps/web/package.json](mdc:apps/web/package.json) - Dependencies and scripts 67 + ## Routing: TanStack Router 68 + 69 + ### File-Based Routing 70 + 71 + Routes are defined using file-based routing in `apps/web/src/routes/`: 72 + 73 + ```typescript 74 + // apps/web/src/routes/tasks/$taskId.tsx 75 + import { createFileRoute } from "@tanstack/react-router"; 76 + import { useTask } from "@/hooks/queries/task/use-task"; 77 + 78 + export const Route = createFileRoute("/tasks/$taskId")({ 79 + component: TaskPage, 80 + }); 81 + 82 + function TaskPage() { 83 + const { taskId } = Route.useParams(); 84 + const { data: task } = useTask(taskId); 85 + 86 + if (!task) return <div>Loading...</div>; 87 + 88 + return <div>{task.title}</div>; 89 + } 90 + ``` 91 + 92 + ### Route Patterns 93 + 94 + - **Layout routes**: Use `_layout.tsx` for shared layouts 95 + - **Auth routes**: Grouped in `auth/` directory 96 + - **Dynamic routes**: Use `$param` syntax for route parameters 97 + 98 + ```typescript 99 + // Layout route: apps/web/src/routes/_layout.tsx 100 + export const Route = createFileRoute("/_layout")({ 101 + component: Layout, 102 + }); 103 + 104 + // Dynamic route: apps/web/src/routes/tasks/$taskId.tsx 105 + export const Route = createFileRoute("/tasks/$taskId")({ 106 + component: TaskPage, 107 + }); 108 + ``` 109 + 110 + ## Data Fetching: TanStack Query 111 + 112 + ### Query Hooks 113 + 114 + Create query hooks in `apps/web/src/hooks/queries/`: 115 + 116 + ```typescript 117 + // apps/web/src/hooks/queries/task/use-task.ts 118 + import { useQuery } from "@tanstack/react-query"; 119 + import { getTask } from "@/fetchers/task/get-task"; 120 + 121 + export function useTask(taskId: string) { 122 + return useQuery({ 123 + queryKey: ["task", taskId], 124 + queryFn: () => getTask(taskId), 125 + }); 126 + } 127 + ``` 128 + 129 + ### Mutation Hooks 130 + 131 + Create mutation hooks in `apps/web/src/hooks/mutations/`: 132 + 133 + ```typescript 134 + // apps/web/src/hooks/mutations/task/use-update-task.ts 135 + import { useMutation, useQueryClient } from "@tanstack/react-query"; 136 + import { updateTask } from "@/fetchers/task/update-task"; 137 + import { toast } from "sonner"; 138 + 139 + export function useUpdateTask() { 140 + const queryClient = useQueryClient(); 141 + 142 + return useMutation({ 143 + mutationFn: updateTask, 144 + onSuccess: () => { 145 + queryClient.invalidateQueries({ queryKey: ["task"] }); 146 + toast.success("Task updated"); 147 + }, 148 + onError: (error) => { 149 + toast.error(error.message); 150 + }, 151 + }); 152 + } 153 + ``` 154 + 155 + ### Best Practices 156 + 157 + 1. **Query keys**: Use arrays with consistent structure: `["resource", id]` 158 + 2. **Invalidation**: Invalidate related queries after mutations 159 + 3. **Error handling**: Use toast notifications for user feedback 160 + 4. **Loading states**: Handle loading and error states in components 161 + 162 + ```typescript 163 + // Good: Proper query with error handling 164 + function TaskPage() { 165 + const { taskId } = Route.useParams(); 166 + const { data: task, isLoading, error } = useTask(taskId); 167 + 168 + if (isLoading) return <Skeleton />; 169 + if (error) return <Error message={error.message} />; 170 + if (!task) return <NotFound />; 171 + 172 + return <TaskDetails task={task} />; 173 + } 174 + ``` 175 + 176 + ## Fetchers 177 + 178 + API calls are abstracted in `apps/web/src/fetchers/`: 179 + 180 + ```typescript 181 + // apps/web/src/fetchers/task/get-task.ts 182 + import { apiClient } from "@/lib/api-client"; 183 + 184 + export async function getTask(taskId: string) { 185 + const response = await apiClient.get(`/api/task/${taskId}`); 186 + return response.data; 187 + } 188 + ``` 189 + 190 + ### Fetcher Organization 191 + 192 + Organize fetchers by feature, mirroring API structure: 193 + 194 + ``` 195 + apps/web/src/fetchers/ 196 + ├── task/ 197 + │ ├── get-task.ts 198 + │ ├── update-task.ts 199 + │ └── create-task.ts 200 + ├── project/ 201 + │ └── ... 202 + └── workspace/ 203 + └── ... 204 + ``` 205 + 206 + ## Styling: Tailwind CSS 207 + 208 + Use Tailwind CSS utility classes: 209 + 210 + ```typescript 211 + <div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow"> 212 + <h2 className="text-xl font-semibold">Title</h2> 213 + </div> 214 + ``` 215 + 216 + ### Utility Functions 217 + 218 + Use `cn()` utility for conditional classes: 219 + 220 + ```typescript 221 + import { cn } from "@/lib/cn"; 222 + 223 + <button 224 + className={cn( 225 + "px-4 py-2 rounded", 226 + isActive && "bg-blue-500 text-white", 227 + disabled && "opacity-50 cursor-not-allowed", 228 + )} 229 + > 230 + Click me 231 + </button> 232 + ``` 233 + 234 + ## Authentication 235 + 236 + Use Better Auth client for authentication: 237 + 238 + ```typescript 239 + import { authClient } from "@/lib/auth-client"; 240 + 241 + // Sign in 242 + await authClient.signIn.email({ 243 + email: "user@example.com", 244 + password: "password", 245 + }); 246 + 247 + // Get current user 248 + const session = await authClient.getSession(); 249 + ``` 250 + 251 + ## File Organization 252 + 253 + ``` 254 + apps/web/src/ 255 + ├── components/ # React components 256 + │ ├── ui/ # Base UI components 257 + │ └── ... 258 + ├── fetchers/ # API call functions 259 + ├── hooks/ # Custom React hooks 260 + │ ├── queries/ # TanStack Query hooks 261 + │ └── mutations/ # TanStack Query mutations 262 + ├── lib/ # Utility functions 263 + ├── routes/ # TanStack Router routes 264 + ├── store/ # Global state (Zustand) 265 + ├── types/ # TypeScript types 266 + └── main.tsx # Entry point 267 + ``` 268 + 269 + ## Type Safety 270 + 271 + Share types between frontend and backend when possible: 272 + 273 + ```typescript 274 + // apps/web/src/types/task.ts 275 + export type Task = { 276 + id: string; 277 + title: string; 278 + description?: string; 279 + status: string; 280 + priority: string; 281 + dueDate?: string; 282 + createdAt: string; 283 + }; 284 + ``` 285 + 286 + ## Error Handling 287 + 288 + Handle errors gracefully with user-friendly messages: 289 + 290 + ```typescript 291 + import { toast } from "sonner"; 292 + 293 + try { 294 + await updateTask(taskId, data); 295 + toast.success("Task updated"); 296 + } catch (error) { 297 + toast.error( 298 + error instanceof Error ? error.message : "Failed to update task", 299 + ); 300 + } 301 + ```
+71 -29
.cursor/rules/project-overview.mdc
··· 1 1 --- 2 - description: 3 - globs: 4 - alwaysApply: false 2 + description: Overview of the Kaneo project structure, architecture, and key technologies 3 + globs: 4 + alwaysApply: true 5 5 --- 6 - # Kaneo Project Overview 6 + # Project Overview 7 7 8 - Kaneo is a modern project management application built as a monorepo with multiple applications and shared packages. 8 + Kaneo is a self-hosted project management platform built with a focus on simplicity and performance. This document provides an overview of the project structure and architecture. 9 9 10 - ## Architecture 10 + ## Project Structure 11 11 12 - This is a **monorepo** containing: 12 + Kaneo is organized as a **monorepo** using pnpm workspaces: 13 13 14 - ### Applications (`apps/`) 15 - - **`api/`** - Backend API built with Hono framework and TypeScript 16 - - **`web/`** - Frontend React application with Vite and TanStack Router 17 - - **`docs/`** - Documentation website built with Next.js 14 + ``` 15 + kaneo/ 16 + ├── apps/ 17 + │ ├── api/ # Backend API (Hono/Node.js) 18 + │ ├── docs/ # Documentation site (Next.js) 19 + │ └── web/ # Frontend app (React/Vite) 20 + ├── packages/ # Shared code and configs 21 + │ ├── email/ # Email utilities 22 + │ ├── libs/ # Shared libraries 23 + │ └── typescript-config/ # TypeScript configurations 24 + ├── charts/ # Kubernetes Helm charts 25 + ├── .cursor/ # Cursor IDE rules 26 + └── compose.yml # Docker Compose configuration 27 + ``` 18 28 19 - ### Packages (`packages/`) 20 - - **`libs/`** - Shared TypeScript utilities and Hono extensions 21 - - **`typescript-config/`** - Shared TypeScript configurations 29 + ## Technology Stack 22 30 23 - ## Key Technologies 31 + ### Backend (API) 32 + - **Framework**: Hono (lightweight web framework) 33 + - **Runtime**: Node.js 34 + - **Database**: PostgreSQL with Drizzle ORM 35 + - **Authentication**: Better Auth 36 + - **Validation**: Valibot 37 + - **API Documentation**: OpenAPI (hono-openapi) 38 + - **Build Tool**: esbuild 24 39 25 - - **Backend**: Hono, TypeScript, Drizzle ORM, PostgreSQL 26 - - **Frontend**: React, Vite, TanStack Router, TanStack Query, Tailwind CSS 27 - - **Database**: PostgreSQL with Drizzle ORM migrations 28 - - **Deployment**: Docker, Kubernetes (Helm charts) 29 - - **Monorepo**: Turborepo for build orchestration 40 + ### Frontend (Web) 41 + - **Framework**: React 18+ 42 + - **Routing**: TanStack Router 43 + - **Data Fetching**: TanStack Query (React Query) 44 + - **Build Tool**: Vite 45 + - **Styling**: Tailwind CSS 46 + - **UI Components**: Custom component library 30 47 31 - ## Entry Points 48 + ### Development Tools 49 + - **Package Manager**: pnpm 50 + - **Linting/Formatting**: Biome 51 + - **TypeScript**: Strict mode enabled 52 + - **Git Hooks**: Husky 32 53 33 - - Backend API: [apps/api/src/index.ts](mdc:apps/api/src/index.ts) 34 - - Frontend Web App: [apps/web/src/main.tsx](mdc:apps/web/src/main.tsx) 35 - - Documentation: [apps/docs/app/layout.tsx](mdc:apps/docs/app/layout.tsx) 54 + ## Key Principles 36 55 37 - ## Configuration Files 56 + 1. **Simplicity First**: Features exist to solve real problems, not to impress 57 + 2. **Performance**: Fast load times and responsive interactions 58 + 3. **Self-Hosted**: Your data stays yours 59 + 4. **Open Source**: Free forever, MIT licensed 38 60 39 - - Database config: [apps/api/drizzle.config.ts](mdc:apps/api/drizzle.config.ts) 40 - - Database schema: [apps/api/src/database/schema.ts](mdc:apps/api/src/database/schema.ts) 41 - - Backend package.json: [apps/api/package.json](mdc:apps/api/package.json) 42 - - Frontend package.json: [apps/web/package.json](mdc:apps/web/package.json) 61 + ## Development Workflow 62 + 63 + 1. **Install dependencies**: `pnpm install` 64 + 2. **Start development**: `pnpm dev` (starts both API and web) 65 + 3. **Lint code**: `pnpm lint` (runs Biome) 66 + 4. **Database migrations**: `pnpm --filter @kaneo/api db:generate` and `db:migrate` 67 + 68 + ## Environment Setup 69 + 70 + All environment variables are configured in a single `.env` file at the project root. See `ENVIRONMENT_SETUP.md` for detailed configuration instructions. 71 + 72 + ## Deployment 73 + 74 + - **Docker Compose**: Quick setup for development/testing 75 + - **Kubernetes**: Production-ready Helm charts in `charts/` 76 + - **Self-Hosted**: Designed to run on your infrastructure 77 + 78 + ## Contributing 79 + 80 + See `CONTRIBUTING.md` for guidelines on: 81 + - Code style (Biome configuration) 82 + - Commit messages (Conventional Commits) 83 + - Project structure conventions 84 + - Getting help