Initial commit: YouTube Studio Flow (backend, frontend, infrastructure, docs)
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
# 05 - Code Conventions
|
||||
|
||||
Language and style conventions for all TypeScript/TSX code in YouTube Studio Flow. These apply to both the frontend and backend unless noted otherwise. For CSS-specific rules, see [[02 - CSS Conventions]].
|
||||
|
||||
---
|
||||
|
||||
## Comments
|
||||
|
||||
Default to writing no comments.
|
||||
|
||||
Only add a comment when the **why** is non-obvious: a hidden constraint, a subtle invariant, a browser quirk, a workaround for a specific external bug, or behavior that would surprise a competent reader encountering it for the first time. If removing the comment wouldn't cause confusion, don't write it.
|
||||
|
||||
**Never write:**
|
||||
- Comments that describe what the code does (the code already does that)
|
||||
- Multi-paragraph docstrings on functions or classes
|
||||
- Multi-line comment blocks
|
||||
- Cross-reference notes ("added for issue #123", "used by the sync feature")
|
||||
- Section dividers (`// --- helpers ---`)
|
||||
|
||||
**Acceptable:**
|
||||
|
||||
```typescript
|
||||
// BullMQ silently drops jobs if Redis evicts keys — must use noeviction policy
|
||||
const client = new Redis({ maxmemoryPolicy: 'noeviction' });
|
||||
|
||||
// Single-pass replacement avoids re-substituting inside already-replaced values
|
||||
const result = template.replace(pattern, (match) => tokens[match] ?? match);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No premature abstraction
|
||||
|
||||
Add abstractions exactly when they are needed, not before. Three similar lines of code is better than a helper function introduced speculatively for hypothetical future use.
|
||||
|
||||
If a pattern appears twice, note it. If it appears three times with meaningful variation, consider abstracting. If the abstraction would be more complex than the repetition, don't.
|
||||
|
||||
---
|
||||
|
||||
## Error handling scope
|
||||
|
||||
Only validate and handle errors at system boundaries:
|
||||
- User input (request bodies, form submissions)
|
||||
- External API calls (YouTube API, Google OAuth)
|
||||
- Queue job payloads at the processor entry point
|
||||
|
||||
Trust internal code. Trust Prisma's type guarantees. Do not add defensive `try/catch` around internal service calls for errors that cannot happen under normal operation.
|
||||
|
||||
---
|
||||
|
||||
## No backwards-compat shims
|
||||
|
||||
When changing or removing behavior, change or remove it. Do not leave:
|
||||
- Unused variables prefixed with `_` to signal "formerly used"
|
||||
- Re-exports of deleted types for "compatibility"
|
||||
- `// removed` comments where code used to be
|
||||
- Feature flags gating old vs. new behavior
|
||||
|
||||
---
|
||||
|
||||
## TypeScript
|
||||
|
||||
- Use strict TypeScript throughout. `"strict": true` is set in both `tsconfig.json` files.
|
||||
- Avoid `any`. The only acceptable uses are at Prisma enum boundaries where the type system cannot express a legitimate constraint, and when interfacing with genuinely untyped external data (e.g. raw OAuth token payloads).
|
||||
- Use `as any` sparingly. If you reach for it, consider whether a type assertion (`as SpecificType`) or a type guard is more appropriate.
|
||||
- Prefer explicit return types on exported functions and service methods. Inference is acceptable for small private helpers.
|
||||
- Use `type` for object shapes and union types. Use `interface` for contracts intended to be extended or implemented.
|
||||
|
||||
```typescript
|
||||
// Object shape — use type
|
||||
type VideoFilters = {
|
||||
search?: string;
|
||||
status?: PrivacyStatus;
|
||||
page: number;
|
||||
};
|
||||
|
||||
// Extendable contract — use interface
|
||||
interface RenderInput {
|
||||
videoId: string;
|
||||
blockOrder: string[];
|
||||
blockOverrides: Record<string, BlockOverride>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Naming conventions
|
||||
|
||||
| Context | Convention | Examples |
|
||||
|---|---|---|
|
||||
| React components | PascalCase | `VideoTable`, `BlockEditor` |
|
||||
| CSS module classes | camelCase | `styles.clickableRow`, `styles.headerCell` |
|
||||
| TypeScript interfaces and types | PascalCase | `VideoFilters`, `RenderInput` |
|
||||
| Frontend API functions | camelCase verb + noun | `fetchVideos`, `updateVideo`, `createBlock` |
|
||||
| Backend service methods | camelCase verb + noun | `findAll`, `findOne`, `create`, `update`, `remove` |
|
||||
| Queue job name strings | kebab-case | `'lint'`, `'youtube-sync'`, `'bulk-metadata'` |
|
||||
| TanStack Query keys | array of strings | `['videos']`, `['video', id]`, `['blocks']` |
|
||||
| Zustand store files | camelCase with `use` prefix | `useAuthStore.ts`, `useUIStore.ts` |
|
||||
|
||||
---
|
||||
|
||||
## Import order
|
||||
|
||||
Organize imports in this order within any TypeScript or TSX file. An empty line between each group:
|
||||
|
||||
1. React imports
|
||||
2. Next.js imports (`next/navigation`, `next/image`, etc.)
|
||||
3. Third-party libraries (`@tanstack/react-query`, `lucide-react`, etc.)
|
||||
4. Internal aliases (`@/components/...`, `@/lib/...`, `@/store/...`)
|
||||
5. Relative imports (`../utils`, `./helpers`)
|
||||
6. Style imports (CSS modules, always last)
|
||||
|
||||
```typescript
|
||||
import { useState, useRef, forwardRef } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { Save, X } from 'lucide-react';
|
||||
|
||||
import Modal from '@/components/shared/Modal';
|
||||
import { updateVideo } from '@/lib/api';
|
||||
import { useAuthStore } from '@/store/useAuthStore';
|
||||
|
||||
import { formatDate } from '../utils/date';
|
||||
|
||||
import styles from './VideoEditor.module.css';
|
||||
import f from '@/components/shared/FormField.module.css';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## No dead code
|
||||
|
||||
Remove unused imports as you work. Remove unused variables. Remove unreachable branches. Delete unused files rather than leaving them in place.
|
||||
|
||||
Do not leave `console.log` statements in committed code. Use the logger (`nestjs/common` `Logger` on the backend) for intentional output.
|
||||
|
||||
---
|
||||
|
||||
## Validation
|
||||
|
||||
**Backend:** Use `class-validator` decorators on DTO classes. Every request body that reaches a controller must go through a validated DTO.
|
||||
|
||||
```typescript
|
||||
export class CreateBlockDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@IsEnum(BlockType)
|
||||
type: BlockType;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
content?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**Frontend:** Validate at the form submission boundary only. Inside a component, trust the types from `@/lib/api.ts`. Do not add runtime type checks on data returned by the API.
|
||||
Reference in New Issue
Block a user