managarten/.claude/guidelines/code-style.md
Till JS ab387b9b3d chore: remove all NestJS backend references, replace with Hono/Bun
- Delete nestjs-backend.md guideline (replaced by hono-server.md)
- Delete Dockerfile.nestjs-base and Dockerfile.nestjs templates
- Delete stale BACKEND_ARCHITECTURE.md doc (NestJS-era, obsolete)
- Update CLAUDE.md, GUIDELINES.md, authentication.md to Hono/Bun first
- Update all app CLAUDE.md files: backend/ → server/, NestJS → Hono+Bun
- Update all app package.json files: @*/backend → @*/server
- Update docs: LOCAL_DEVELOPMENT, PORT_SCHEMA, ENVIRONMENT_VARIABLES,
  DATABASE_MIGRATIONS, MAC_MINI_SERVER, PROJECT_OVERVIEW
- Update scripts: generate-env.mjs, setup-databases.sh, build-app.sh
- Update CI/CD: cd-macmini.yml backend → server paths
- Update Astro docs site: @chat/backend → @chat/server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 16:52:25 +02:00

343 lines
9.2 KiB
Markdown

# Code Style Guidelines
## Formatting
### Prettier Configuration
All projects use the root `.prettierrc.json`:
```json
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-astro"]
}
```
### Key Rules
- **Tabs** for indentation (not spaces)
- **Single quotes** for strings
- **Trailing commas** in ES5-compatible positions
- **100 character** line width
- **Semicolons** required
## Naming Conventions
### Files & Directories
| Type | Convention | Example |
| -------------------- | ----------------- | ------------------------------------------------ |
| **Components** | PascalCase | `MessageBubble.svelte`, `ChatInput.tsx` |
| **Services** | kebab-case | `auth.service.ts`, `user-credits.service.ts` |
| **Schemas** | kebab-case | `users.schema.ts`, `batch-generations.schema.ts` |
| **Utilities** | kebab-case | `format-date.ts`, `string-utils.ts` |
| **Types/Interfaces** | kebab-case | `user.types.ts`, `api-response.ts` |
| **Constants** | kebab-case | `error-codes.ts`, `config.ts` |
| **Test files** | `.spec.ts` suffix | `auth.service.spec.ts` |
### Code Identifiers
| Type | Convention | Example |
| ------------------ | ------------------------------------------------ | ---------------------------------- |
| **Classes** | PascalCase | `UserService`, `AuthController` |
| **Interfaces** | PascalCase | `UserData`, `CreateEventDto` |
| **Type aliases** | PascalCase | `Result<T>`, `ErrorCode` |
| **Functions** | camelCase | `findById`, `createUser` |
| **Variables** | camelCase | `userId`, `isLoading` |
| **Constants** | SCREAMING_SNAKE_CASE | `MAX_FILE_SIZE`, `DEFAULT_TIMEOUT` |
| **Enums** | PascalCase (type), SCREAMING_SNAKE_CASE (values) | `ErrorCode.NOT_FOUND` |
| **Private fields** | camelCase (no underscore prefix) | `private db: Database` |
### Database Naming
| Type | Convention | Example |
| ---------------- | ---------------------- | ------------------------------- |
| **Tables** | snake_case, plural | `users`, `user_sessions` |
| **Columns** | snake_case | `user_id`, `created_at` |
| **Foreign keys** | `{entity}_id` | `user_id`, `folder_id` |
| **Booleans** | `is_` or `has_` prefix | `is_deleted`, `has_password` |
| **Timestamps** | `_at` suffix | `created_at`, `deleted_at` |
| **Indexes** | `idx_` prefix | `idx_user_id`, `idx_created_at` |
## TypeScript
### Strict Mode
All projects use strict TypeScript:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true
}
}
```
### Type Annotations
```typescript
// GOOD - Explicit return types for public APIs
async function findById(id: string): Promise<Result<User>> {
// ...
}
// GOOD - Interface for complex objects
interface CreateUserDto {
email: string;
name: string;
password: string;
}
// BAD - Avoid `any`
function process(data: any) {} // Never do this
// GOOD - Use `unknown` when type is truly unknown
function process(data: unknown) {
if (isUser(data)) {
// Now TypeScript knows it's a User
}
}
```
### Imports
```typescript
// Order: external → internal → relative
import { Injectable } from '@nestjs/common'; // 1. External
import { Result, ErrorCode } from '@manacore/shared-errors'; // 2. Internal packages
import { UserService } from '../services/user.service'; // 3. Relative
// Use named exports (not default)
export { UserService }; // GOOD
export default UserService; // AVOID
// Use type-only imports for types
import type { User } from './user.types';
```
## ESLint Rules
### Critical Rules (Errors)
```javascript
{
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": "error", // For public APIs
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": ["error", { "allow": ["warn", "error"] }],
}
```
### Recommended Rules (Warnings)
```javascript
{
"@typescript-eslint/no-floating-promises": "warn",
"@typescript-eslint/await-thenable": "warn",
"prefer-const": "warn",
}
```
## Comments
### When to Comment
```typescript
// GOOD - Explain WHY, not WHAT
// We use optimistic locking here because concurrent credit operations
// could otherwise result in race conditions and incorrect balances
const [updated] = await this.db
.update(balances)
.set({ amount: newAmount, version: sql`version + 1` })
.where(and(eq(balances.userId, userId), eq(balances.version, currentVersion)))
.returning();
// BAD - Explaining obvious code
// Loop through users
for (const user of users) {
}
// BAD - Outdated comment
// Returns the user's email <-- but function now returns full user object
function getUser() {}
```
### JSDoc for Public APIs
```typescript
/**
* Consumes credits from a user's balance.
*
* @param userId - The user's unique identifier
* @param amount - Number of credits to consume
* @param reason - Human-readable reason for the charge
* @returns Result with the updated balance or an error
*
* @example
* const result = await creditsService.consume(userId, 10, 'AI generation');
* if (!result.ok) {
* logger.error('Credit consumption failed', result.error);
* }
*/
async consume(userId: string, amount: number, reason: string): Promise<Result<Balance>> {
// ...
}
```
## Icons
All SvelteKit web apps use **Phosphor icons** via `@manacore/shared-icons` (re-exports `phosphor-svelte`).
### Usage
```svelte
<script>
import { Plus, Trash, MagnifyingGlass } from '@manacore/shared-icons';
</script>
<Plus size={20} />
<Trash size={16} class="text-red-500" />
<MagnifyingGlass size={20} weight="bold" />
```
### Rules
- **Never use inline SVG `<path d="...">` for icons.** Always use Phosphor components.
- Available weights: `thin`, `light`, `regular` (default), `bold`, `fill`, `duotone`
- Use `weight="fill"` for filled variants (e.g., filled heart, filled star)
- Size prop replaces Tailwind `w-N h-N` classes: `w-4 h-4``size={16}`, `w-5 h-5``size={20}`
- Browse icons at [phosphoricons.com](https://phosphoricons.com)
### Exceptions (inline SVG allowed)
- **Spinners/loading animations** (circle + arc with `stroke-opacity`)
- **Brand logos** (Mana, Google, app-specific logos)
- **Charts & data visualizations** (DonutChart, RadarChart, etc.)
- **Complex decorative illustrations** (maps, scenes)
- **Dynamic icon rendering** via `{@html}` (Icon.svelte wrappers)
## Code Organization
### File Size
- **Maximum**: ~300 lines per file
- **Ideal**: 100-200 lines
- Split large files into focused modules
### Function Size
- **Maximum**: ~50 lines per function
- **Ideal**: 10-25 lines
- Extract complex logic into helper functions
### Module Structure (Hono/Bun Server)
```
feature/
├── feature.routes.ts # HTTP routes
├── feature.service.ts # Business logic
├── feature.spec.ts # Tests
└── dto/
├── create-feature.dto.ts
└── update-feature.dto.ts
```
### Component Structure (Svelte/React)
```
components/
├── feature/
│ ├── FeatureList.svelte # Container component
│ ├── FeatureItem.svelte # Presentational component
│ └── feature.types.ts # Shared types
└── ui/
├── Button.svelte # Reusable UI
└── Input.svelte
```
## Anti-Patterns to Avoid
### 1. Magic Numbers/Strings
```typescript
// BAD
if (user.role === 'admin') {
}
if (credits < 10) {
}
// GOOD
const ROLES = { ADMIN: 'admin', USER: 'user' } as const;
const MIN_CREDITS_FOR_OPERATION = 10;
if (user.role === ROLES.ADMIN) {
}
if (credits < MIN_CREDITS_FOR_OPERATION) {
}
```
### 2. Nested Callbacks
```typescript
// BAD
getUser(id, (user) => {
getCredits(user.id, (credits) => {
updateBalance(credits, (result) => {
// ...
});
});
});
// GOOD
const user = await getUser(id);
const credits = await getCredits(user.id);
const result = await updateBalance(credits);
```
### 3. Mutating Parameters
```typescript
// BAD
function processUser(user: User): void {
user.name = user.name.trim(); // Mutates input
}
// GOOD
function processUser(user: User): User {
return { ...user, name: user.name.trim() }; // Returns new object
}
```
### 4. Boolean Trap
```typescript
// BAD - What does `true` mean?
createUser(email, password, true, false);
// GOOD - Use options object
createUser({
email,
password,
sendWelcomeEmail: true,
requireEmailVerification: false,
});
```
## Formatting Commands
```bash
# Format all files
pnpm format
# Check formatting without changes
pnpm format:check
# Format specific project
pnpm --filter @chat/server format
```