render-create 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +45 -0
- package/dist/commands/check.d.ts +8 -0
- package/dist/commands/check.js +96 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +1201 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.js +126 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.js +4 -0
- package/dist/utils.d.ts +53 -0
- package/dist/utils.js +142 -0
- package/package.json +65 -0
- package/templates/LINTING_SETUP.md +205 -0
- package/templates/README_TEMPLATE.md +68 -0
- package/templates/STYLE_GUIDE.md +241 -0
- package/templates/assets/favicon.png +0 -0
- package/templates/assets/favicon.svg +17 -0
- package/templates/biome.json +43 -0
- package/templates/cursor/rules/drizzle.mdc +165 -0
- package/templates/cursor/rules/fastify.mdc +132 -0
- package/templates/cursor/rules/general.mdc +112 -0
- package/templates/cursor/rules/nextjs.mdc +89 -0
- package/templates/cursor/rules/python.mdc +89 -0
- package/templates/cursor/rules/react.mdc +200 -0
- package/templates/cursor/rules/sqlalchemy.mdc +205 -0
- package/templates/cursor/rules/tailwind.mdc +139 -0
- package/templates/cursor/rules/typescript.mdc +112 -0
- package/templates/cursor/rules/vite.mdc +169 -0
- package/templates/cursor/rules/workflows.mdc +349 -0
- package/templates/docker-compose.example.yml +55 -0
- package/templates/drizzle/db-index.ts +15 -0
- package/templates/drizzle/drizzle.config.ts +10 -0
- package/templates/drizzle/schema.ts +12 -0
- package/templates/env.example +15 -0
- package/templates/fastapi/app/__init__.py +1 -0
- package/templates/fastapi/app/config.py +12 -0
- package/templates/fastapi/app/database.py +16 -0
- package/templates/fastapi/app/models.py +13 -0
- package/templates/fastapi/main.py +22 -0
- package/templates/fastify/index.ts +40 -0
- package/templates/github/CODEOWNERS +10 -0
- package/templates/github/ISSUE_TEMPLATE/bug_report.md +39 -0
- package/templates/github/ISSUE_TEMPLATE/feature_request.md +23 -0
- package/templates/github/PULL_REQUEST_TEMPLATE.md +25 -0
- package/templates/gitignore/node.gitignore +41 -0
- package/templates/gitignore/python.gitignore +49 -0
- package/templates/multi-api/README.md +60 -0
- package/templates/multi-api/gitignore +28 -0
- package/templates/multi-api/node-api/drizzle.config.ts +10 -0
- package/templates/multi-api/node-api/package-simple.json +13 -0
- package/templates/multi-api/node-api/package.json +16 -0
- package/templates/multi-api/node-api/src/db/index.ts +13 -0
- package/templates/multi-api/node-api/src/db/schema.ts +9 -0
- package/templates/multi-api/node-api/src/index-simple.ts +36 -0
- package/templates/multi-api/node-api/src/index.ts +50 -0
- package/templates/multi-api/node-api/tsconfig.json +20 -0
- package/templates/multi-api/python-api/app/__init__.py +1 -0
- package/templates/multi-api/python-api/app/config.py +12 -0
- package/templates/multi-api/python-api/app/database.py +16 -0
- package/templates/multi-api/python-api/app/models.py +13 -0
- package/templates/multi-api/python-api/main-simple.py +25 -0
- package/templates/multi-api/python-api/main.py +44 -0
- package/templates/multi-api/python-api/requirements-simple.txt +3 -0
- package/templates/multi-api/python-api/requirements.txt +8 -0
- package/templates/next/globals.css +126 -0
- package/templates/next/layout.tsx +34 -0
- package/templates/next/next.config.static.ts +10 -0
- package/templates/next/page-fullstack.tsx +120 -0
- package/templates/next/page.tsx +72 -0
- package/templates/presets.json +581 -0
- package/templates/ruff.toml +30 -0
- package/templates/tsconfig.base.json +17 -0
- package/templates/vite/index.css +127 -0
- package/templates/vite/vite.config.ts +7 -0
- package/templates/worker/py/cron.py +53 -0
- package/templates/worker/py/worker.py +95 -0
- package/templates/worker/py/workflow.py +73 -0
- package/templates/worker/ts/cron.ts +49 -0
- package/templates/worker/ts/worker.ts +84 -0
- package/templates/worker/ts/workflow.ts +67 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: React patterns for state, forms, icons, and default UI style
|
|
3
|
+
globs: ["**/*.tsx", "**/*.jsx"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Conventions
|
|
7
|
+
|
|
8
|
+
## Default UI Style: Brutalist
|
|
9
|
+
|
|
10
|
+
Unless the user specifies a different design direction, use this style:
|
|
11
|
+
|
|
12
|
+
### Core Principles:
|
|
13
|
+
|
|
14
|
+
- **Background**: Black (`bg-black` or `bg-zinc-950`)
|
|
15
|
+
- **Text/Accents**: White (`text-white`)
|
|
16
|
+
- **Borders**: Sharp, 1-2px white borders (`border border-white`)
|
|
17
|
+
- **Corners**: NO rounded corners (`rounded-none` always)
|
|
18
|
+
- **Gradients**: NONE - flat colors only
|
|
19
|
+
- **Shadows**: Minimal or none
|
|
20
|
+
|
|
21
|
+
### Component Examples:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
// Button
|
|
25
|
+
<button className="bg-black text-white border border-white px-4 py-2 rounded-none hover:bg-white hover:text-black transition-colors">
|
|
26
|
+
Click me
|
|
27
|
+
</button>
|
|
28
|
+
|
|
29
|
+
// Card
|
|
30
|
+
<div className="bg-black border border-white p-6 rounded-none">
|
|
31
|
+
<h2 className="text-white text-xl font-bold">Title</h2>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
// Input
|
|
35
|
+
<input className="bg-black text-white border border-white px-3 py-2 rounded-none focus:outline-none focus:ring-1 focus:ring-white" />
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Typography:
|
|
39
|
+
|
|
40
|
+
- Use system fonts or monospace for brutalist feel
|
|
41
|
+
- Bold headings, regular body text
|
|
42
|
+
- High contrast only
|
|
43
|
+
|
|
44
|
+
### What to AVOID:
|
|
45
|
+
|
|
46
|
+
- `rounded-*` classes (except `rounded-none`)
|
|
47
|
+
- `bg-gradient-*` classes
|
|
48
|
+
- Soft shadows (`shadow-sm`, `shadow-md`)
|
|
49
|
+
- Pastel or muted colors
|
|
50
|
+
- Decorative elements
|
|
51
|
+
|
|
52
|
+
### Required Footer:
|
|
53
|
+
|
|
54
|
+
Always include a footer with Render docs and GitHub project links:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<footer className="border-t border-white py-4 mt-auto">
|
|
58
|
+
<div className="flex justify-center gap-6 text-sm">
|
|
59
|
+
<a
|
|
60
|
+
href="https://docs.render.com"
|
|
61
|
+
target="_blank"
|
|
62
|
+
rel="noopener noreferrer"
|
|
63
|
+
className="text-white hover:underline"
|
|
64
|
+
>
|
|
65
|
+
Render Docs
|
|
66
|
+
</a>
|
|
67
|
+
<a
|
|
68
|
+
href="https://github.com/render-examples/PROJECT_NAME"
|
|
69
|
+
target="_blank"
|
|
70
|
+
rel="noopener noreferrer"
|
|
71
|
+
className="text-white hover:underline"
|
|
72
|
+
>
|
|
73
|
+
GitHub
|
|
74
|
+
</a>
|
|
75
|
+
</div>
|
|
76
|
+
</footer>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Replace `PROJECT_NAME` with the actual repository name.
|
|
80
|
+
|
|
81
|
+
## State Management
|
|
82
|
+
|
|
83
|
+
Use **React hooks + Context** (no external libs unless needed):
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Simple local state
|
|
87
|
+
const [count, setCount] = useState(0);
|
|
88
|
+
|
|
89
|
+
// Complex local state
|
|
90
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
91
|
+
|
|
92
|
+
// Shared state via Context
|
|
93
|
+
const UserContext = createContext<UserContextType | null>(null);
|
|
94
|
+
|
|
95
|
+
export function UserProvider({ children }: { children: React.ReactNode }) {
|
|
96
|
+
const [user, setUser] = useState<User | null>(null);
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<UserContext.Provider value={{ user, setUser }}>
|
|
100
|
+
{children}
|
|
101
|
+
</UserContext.Provider>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function useUser() {
|
|
106
|
+
const context = useContext(UserContext);
|
|
107
|
+
if (!context) {
|
|
108
|
+
throw new Error("useUser must be used within UserProvider");
|
|
109
|
+
}
|
|
110
|
+
return context;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Forms with React Hook Form
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
"use client";
|
|
118
|
+
|
|
119
|
+
import { useForm } from "react-hook-form";
|
|
120
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
121
|
+
import { z } from "zod";
|
|
122
|
+
|
|
123
|
+
const formSchema = z.object({
|
|
124
|
+
email: z.string().email("Invalid email"),
|
|
125
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
type FormData = z.infer<typeof formSchema>;
|
|
129
|
+
|
|
130
|
+
export function LoginForm() {
|
|
131
|
+
const {
|
|
132
|
+
register,
|
|
133
|
+
handleSubmit,
|
|
134
|
+
formState: { errors, isSubmitting },
|
|
135
|
+
} = useForm<FormData>({
|
|
136
|
+
resolver: zodResolver(formSchema),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const onSubmit = async (data: FormData) => {
|
|
140
|
+
console.log(data);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
145
|
+
<div>
|
|
146
|
+
<input
|
|
147
|
+
{...register("email")}
|
|
148
|
+
type="email"
|
|
149
|
+
placeholder="Email"
|
|
150
|
+
className="w-full bg-black text-white border border-white px-3 py-2 rounded-none"
|
|
151
|
+
/>
|
|
152
|
+
{errors.email && (
|
|
153
|
+
<p className="text-red-500 text-sm mt-1">{errors.email.message}</p>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<button
|
|
158
|
+
type="submit"
|
|
159
|
+
disabled={isSubmitting}
|
|
160
|
+
className="w-full bg-white text-black px-4 py-2 rounded-none hover:bg-gray-200 disabled:opacity-50"
|
|
161
|
+
>
|
|
162
|
+
{isSubmitting ? "Loading..." : "Submit"}
|
|
163
|
+
</button>
|
|
164
|
+
</form>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Icons with Lucide React
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { Search, Menu, X, ChevronRight, Loader2 } from "lucide-react";
|
|
173
|
+
|
|
174
|
+
// Basic usage
|
|
175
|
+
<Search className="w-5 h-5" />
|
|
176
|
+
|
|
177
|
+
// With color
|
|
178
|
+
<Menu className="w-6 h-6 text-white" />
|
|
179
|
+
|
|
180
|
+
// Loading spinner
|
|
181
|
+
<Loader2 className="w-5 h-5 animate-spin" />
|
|
182
|
+
|
|
183
|
+
// In buttons
|
|
184
|
+
<button className="flex items-center gap-2">
|
|
185
|
+
<Search className="w-4 h-4" />
|
|
186
|
+
Search
|
|
187
|
+
</button>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Common Lucide Icons
|
|
191
|
+
|
|
192
|
+
| Icon | Use Case |
|
|
193
|
+
|------|----------|
|
|
194
|
+
| `Search` | Search inputs |
|
|
195
|
+
| `Menu`, `X` | Mobile nav toggle |
|
|
196
|
+
| `ChevronRight/Left/Up/Down` | Navigation, accordions |
|
|
197
|
+
| `Loader2` | Loading states (add `animate-spin`) |
|
|
198
|
+
| `Check`, `X` | Success/error states |
|
|
199
|
+
| `Plus`, `Minus` | Add/remove actions |
|
|
200
|
+
| `ExternalLink` | External links |
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SQLAlchemy conventions for FastAPI with PostgreSQL
|
|
3
|
+
globs: ["**/models/**/*.py", "**/db/**/*.py", "**/*_model.py"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SQLAlchemy Conventions
|
|
7
|
+
|
|
8
|
+
## Project Structure
|
|
9
|
+
|
|
10
|
+
### Small projects: Single models file
|
|
11
|
+
```
|
|
12
|
+
project/
|
|
13
|
+
├── app.py
|
|
14
|
+
├── config.py
|
|
15
|
+
├── models.py # All models
|
|
16
|
+
└── db.py # Database connection
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Larger projects: Split by domain
|
|
20
|
+
```
|
|
21
|
+
project/
|
|
22
|
+
├── app.py
|
|
23
|
+
├── config.py
|
|
24
|
+
├── db.py
|
|
25
|
+
├── models/
|
|
26
|
+
│ ├── __init__.py # Re-exports all models
|
|
27
|
+
│ ├── user.py
|
|
28
|
+
│ ├── post.py
|
|
29
|
+
│ └── comment.py
|
|
30
|
+
└── alembic/ # Migrations
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Database Connection (Async)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
# db.py
|
|
37
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
38
|
+
from sqlalchemy.orm import sessionmaker, declarative_base
|
|
39
|
+
|
|
40
|
+
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"
|
|
41
|
+
|
|
42
|
+
engine = create_async_engine(DATABASE_URL, echo=True)
|
|
43
|
+
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
44
|
+
|
|
45
|
+
Base = declarative_base()
|
|
46
|
+
|
|
47
|
+
async def get_db():
|
|
48
|
+
async with async_session() as session:
|
|
49
|
+
yield session
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Model Definition
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# models/user.py
|
|
56
|
+
from sqlalchemy import Column, String, DateTime
|
|
57
|
+
from sqlalchemy.dialects.postgresql import UUID
|
|
58
|
+
from sqlalchemy.sql import func
|
|
59
|
+
import uuid
|
|
60
|
+
|
|
61
|
+
from db import Base
|
|
62
|
+
|
|
63
|
+
class User(Base):
|
|
64
|
+
__tablename__ = "users"
|
|
65
|
+
|
|
66
|
+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
67
|
+
email = Column(String, unique=True, nullable=False, index=True)
|
|
68
|
+
name = Column(String, nullable=False)
|
|
69
|
+
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
70
|
+
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Pydantic Schemas
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# schemas/user.py
|
|
77
|
+
from pydantic import BaseModel, EmailStr
|
|
78
|
+
from datetime import datetime
|
|
79
|
+
from uuid import UUID
|
|
80
|
+
|
|
81
|
+
class UserBase(BaseModel):
|
|
82
|
+
email: EmailStr
|
|
83
|
+
name: str
|
|
84
|
+
|
|
85
|
+
class UserCreate(UserBase):
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
class UserResponse(UserBase):
|
|
89
|
+
id: UUID
|
|
90
|
+
created_at: datetime
|
|
91
|
+
updated_at: datetime | None
|
|
92
|
+
|
|
93
|
+
class Config:
|
|
94
|
+
from_attributes = True # Allows ORM model conversion
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## FastAPI Integration
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# app.py
|
|
101
|
+
from fastapi import FastAPI, Depends, HTTPException
|
|
102
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
103
|
+
from sqlalchemy import select
|
|
104
|
+
|
|
105
|
+
from db import get_db
|
|
106
|
+
from models.user import User
|
|
107
|
+
from schemas.user import UserCreate, UserResponse
|
|
108
|
+
|
|
109
|
+
app = FastAPI()
|
|
110
|
+
|
|
111
|
+
@app.post("/users", response_model=UserResponse)
|
|
112
|
+
async def create_user(user: UserCreate, db: AsyncSession = Depends(get_db)):
|
|
113
|
+
db_user = User(**user.model_dump())
|
|
114
|
+
db.add(db_user)
|
|
115
|
+
await db.commit()
|
|
116
|
+
await db.refresh(db_user)
|
|
117
|
+
return db_user
|
|
118
|
+
|
|
119
|
+
@app.get("/users/{user_id}", response_model=UserResponse)
|
|
120
|
+
async def get_user(user_id: str, db: AsyncSession = Depends(get_db)):
|
|
121
|
+
result = await db.execute(select(User).where(User.id == user_id))
|
|
122
|
+
user = result.scalar_one_or_none()
|
|
123
|
+
if not user:
|
|
124
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
125
|
+
return user
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Async Queries
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from sqlalchemy import select, update, delete
|
|
132
|
+
|
|
133
|
+
# Select all
|
|
134
|
+
result = await db.execute(select(User))
|
|
135
|
+
users = result.scalars().all()
|
|
136
|
+
|
|
137
|
+
# Select with filter
|
|
138
|
+
result = await db.execute(select(User).where(User.email == email))
|
|
139
|
+
user = result.scalar_one_or_none()
|
|
140
|
+
|
|
141
|
+
# Insert
|
|
142
|
+
new_user = User(email="test@example.com", name="Test")
|
|
143
|
+
db.add(new_user)
|
|
144
|
+
await db.commit()
|
|
145
|
+
|
|
146
|
+
# Update
|
|
147
|
+
await db.execute(
|
|
148
|
+
update(User).where(User.id == user_id).values(name="Updated")
|
|
149
|
+
)
|
|
150
|
+
await db.commit()
|
|
151
|
+
|
|
152
|
+
# Delete
|
|
153
|
+
await db.execute(delete(User).where(User.id == user_id))
|
|
154
|
+
await db.commit()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Relations
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from sqlalchemy import ForeignKey
|
|
161
|
+
from sqlalchemy.orm import relationship
|
|
162
|
+
|
|
163
|
+
class Post(Base):
|
|
164
|
+
__tablename__ = "posts"
|
|
165
|
+
|
|
166
|
+
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
|
167
|
+
title = Column(String, nullable=False)
|
|
168
|
+
author_id = Column(UUID(as_uuid=True), ForeignKey("users.id"))
|
|
169
|
+
|
|
170
|
+
author = relationship("User", back_populates="posts")
|
|
171
|
+
|
|
172
|
+
class User(Base):
|
|
173
|
+
# ... other fields
|
|
174
|
+
posts = relationship("Post", back_populates="author")
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Alembic Migrations
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Initialize
|
|
181
|
+
alembic init alembic
|
|
182
|
+
|
|
183
|
+
# Generate migration
|
|
184
|
+
alembic revision --autogenerate -m "Add users table"
|
|
185
|
+
|
|
186
|
+
# Apply migrations
|
|
187
|
+
alembic upgrade head
|
|
188
|
+
|
|
189
|
+
# Rollback
|
|
190
|
+
alembic downgrade -1
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### alembic/env.py (async)
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
197
|
+
from models import Base # Import your Base
|
|
198
|
+
|
|
199
|
+
target_metadata = Base.metadata
|
|
200
|
+
|
|
201
|
+
async def run_async_migrations():
|
|
202
|
+
connectable = create_async_engine(DATABASE_URL)
|
|
203
|
+
async with connectable.connect() as connection:
|
|
204
|
+
await connection.run_sync(do_run_migrations)
|
|
205
|
+
```
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Tailwind CSS conventions with brutalist default style
|
|
3
|
+
globs: ["**/*.tsx", "**/*.jsx", "**/tailwind.config.*"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tailwind CSS Conventions
|
|
7
|
+
|
|
8
|
+
## Default UI Style: Brutalist
|
|
9
|
+
|
|
10
|
+
Unless the user specifies a different design direction, use this style:
|
|
11
|
+
|
|
12
|
+
### Core Palette:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// Background
|
|
16
|
+
className="bg-black" // or bg-zinc-950
|
|
17
|
+
|
|
18
|
+
// Text
|
|
19
|
+
className="text-white"
|
|
20
|
+
|
|
21
|
+
// Borders
|
|
22
|
+
className="border border-white"
|
|
23
|
+
|
|
24
|
+
// Corners - ALWAYS use rounded-none
|
|
25
|
+
className="rounded-none"
|
|
26
|
+
|
|
27
|
+
// NO gradients, NO soft shadows
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Brutalist Component Patterns:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// Button
|
|
34
|
+
<button className="bg-black text-white border border-white px-4 py-2 rounded-none hover:bg-white hover:text-black transition-colors">
|
|
35
|
+
|
|
36
|
+
// Card
|
|
37
|
+
<div className="bg-black border border-white p-6 rounded-none">
|
|
38
|
+
|
|
39
|
+
// Input
|
|
40
|
+
<input className="bg-black text-white border border-white px-3 py-2 rounded-none focus:outline-none focus:ring-1 focus:ring-white" />
|
|
41
|
+
|
|
42
|
+
// Link
|
|
43
|
+
<a className="text-white hover:underline">
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### What to AVOID:
|
|
47
|
+
|
|
48
|
+
- `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-full` - use `rounded-none`
|
|
49
|
+
- `bg-gradient-*` - use flat colors
|
|
50
|
+
- `shadow-sm`, `shadow-md`, `shadow-lg` - minimal/no shadows
|
|
51
|
+
- Pastel colors - use high contrast black/white
|
|
52
|
+
|
|
53
|
+
## Class Organization
|
|
54
|
+
|
|
55
|
+
Organize classes in **logical groups** (layout → spacing → sizing → colors → effects):
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Good: Logical grouping
|
|
59
|
+
<div className="
|
|
60
|
+
flex items-center justify-between
|
|
61
|
+
p-4 gap-2
|
|
62
|
+
w-full max-w-md
|
|
63
|
+
bg-black text-white
|
|
64
|
+
border border-white rounded-none
|
|
65
|
+
hover:bg-white hover:text-black transition-colors
|
|
66
|
+
">
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Single line for simple elements
|
|
71
|
+
<button className="px-4 py-2 bg-black text-white border border-white rounded-none">
|
|
72
|
+
Click me
|
|
73
|
+
</button>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## When to Extract Components
|
|
77
|
+
|
|
78
|
+
**Inline classes first**, extract when:
|
|
79
|
+
- Pattern repeats 3+ times
|
|
80
|
+
- Complex responsive/state logic
|
|
81
|
+
- Needs props for variants
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// After extraction (component)
|
|
85
|
+
function Button({ children }: { children: React.ReactNode }) {
|
|
86
|
+
return (
|
|
87
|
+
<button className="px-4 py-2 bg-black text-white border border-white rounded-none hover:bg-white hover:text-black transition-colors">
|
|
88
|
+
{children}
|
|
89
|
+
</button>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Conditional Classes
|
|
95
|
+
|
|
96
|
+
Use `cn()` utility (from `clsx` + `tailwind-merge`):
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import { cn } from "@/lib/utils";
|
|
100
|
+
|
|
101
|
+
function Button({ variant = "primary", className, ...props }) {
|
|
102
|
+
return (
|
|
103
|
+
<button
|
|
104
|
+
className={cn(
|
|
105
|
+
"px-4 py-2 rounded-none font-medium transition-colors border",
|
|
106
|
+
variant === "primary" && "bg-black text-white border-white hover:bg-white hover:text-black",
|
|
107
|
+
variant === "outline" && "bg-transparent text-white border-white hover:bg-white hover:text-black",
|
|
108
|
+
className
|
|
109
|
+
)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Responsive Design
|
|
117
|
+
|
|
118
|
+
Mobile-first approach:
|
|
119
|
+
```tsx
|
|
120
|
+
<div className="
|
|
121
|
+
p-4 text-sm
|
|
122
|
+
md:p-6 md:text-base
|
|
123
|
+
lg:p-8 lg:text-lg
|
|
124
|
+
">
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Dark Mode
|
|
128
|
+
|
|
129
|
+
For brutalist style, dark mode is the default. If supporting light mode:
|
|
130
|
+
```tsx
|
|
131
|
+
<div className="bg-black dark:bg-black text-white dark:text-white">
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Avoid
|
|
135
|
+
|
|
136
|
+
- Don't use `@apply` excessively (defeats Tailwind's purpose)
|
|
137
|
+
- Don't create utility classes that duplicate Tailwind
|
|
138
|
+
- Don't use rounded corners (brutalist style)
|
|
139
|
+
- Don't use gradients (brutalist style)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: TypeScript-specific conventions
|
|
3
|
+
globs: ["**/*.ts", "**/*.tsx"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TypeScript Conventions
|
|
7
|
+
|
|
8
|
+
## Functions
|
|
9
|
+
|
|
10
|
+
Use **named functions** for exports:
|
|
11
|
+
```typescript
|
|
12
|
+
// Good
|
|
13
|
+
export function handleRequest(req: Request): Response {
|
|
14
|
+
// ...
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Avoid
|
|
18
|
+
export const handleRequest = (req: Request): Response => {
|
|
19
|
+
// ...
|
|
20
|
+
};
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Arrow functions are fine for:
|
|
24
|
+
- Callbacks: `items.map((item) => item.id)`
|
|
25
|
+
- Simple helpers: `const clean = (s: string) => s.trim()`
|
|
26
|
+
|
|
27
|
+
## If Statements
|
|
28
|
+
|
|
29
|
+
Always use braces:
|
|
30
|
+
```typescript
|
|
31
|
+
// Good
|
|
32
|
+
if (condition) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Bad
|
|
37
|
+
if (condition) return value;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Naming
|
|
41
|
+
|
|
42
|
+
- Variables/functions: `camelCase`
|
|
43
|
+
- Types/interfaces/classes: `PascalCase`
|
|
44
|
+
- Constants: `SCREAMING_SNAKE_CASE`
|
|
45
|
+
|
|
46
|
+
## Type Validation with Zod
|
|
47
|
+
|
|
48
|
+
Use Zod for runtime validation of external data (API inputs, env vars, config):
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { z } from "zod";
|
|
52
|
+
|
|
53
|
+
// Define schema
|
|
54
|
+
const UserSchema = z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
email: z.string().email(),
|
|
57
|
+
age: z.number().int().positive().optional(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Infer TypeScript type from schema
|
|
61
|
+
type User = z.infer<typeof UserSchema>;
|
|
62
|
+
|
|
63
|
+
// Validate
|
|
64
|
+
const result = UserSchema.safeParse(input);
|
|
65
|
+
if (!result.success) {
|
|
66
|
+
return res.status(400).json({ error: result.error.flatten() });
|
|
67
|
+
}
|
|
68
|
+
const user = result.data; // Fully typed
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### When to Use Zod
|
|
72
|
+
|
|
73
|
+
- API request bodies
|
|
74
|
+
- Query parameters
|
|
75
|
+
- Environment variables
|
|
76
|
+
- External API responses
|
|
77
|
+
- Configuration files
|
|
78
|
+
|
|
79
|
+
### When NOT to Use Zod
|
|
80
|
+
|
|
81
|
+
- Internal function calls (trust TypeScript)
|
|
82
|
+
- Database query results (trust your ORM)
|
|
83
|
+
|
|
84
|
+
## Error Handling
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
try {
|
|
88
|
+
const result = await apiCall();
|
|
89
|
+
return res.json(result);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("[module] Error:", error);
|
|
92
|
+
return res.status(500).json({ error: "Internal server error" });
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Project Structure
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
src/
|
|
100
|
+
├── index.ts # Entry point: server, middleware, routes
|
|
101
|
+
├── config.ts # Environment variables, validation schemas
|
|
102
|
+
├── utils.ts # Helpers, error mapping
|
|
103
|
+
├── types.ts # Shared types
|
|
104
|
+
└── [feature].ts # Feature modules
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Linting
|
|
108
|
+
|
|
109
|
+
Run before committing:
|
|
110
|
+
```bash
|
|
111
|
+
npm run check # biome check --write src/
|
|
112
|
+
```
|