rbin-task-flow 1.19.4 → 1.23.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/.claude/skills/rbin-coding-standards/SKILL.md +29 -0
- package/.claude/skills/rbin-coding-standards/reference.md +42 -0
- package/.claude/skills/rbin-git/SKILL.md +39 -0
- package/.claude/skills/task-flow-audit/SKILL.md +15 -0
- package/.claude/skills/task-flow-check/SKILL.md +15 -0
- package/.claude/skills/task-flow-estimate/SKILL.md +15 -0
- package/.claude/skills/task-flow-generate-flow/SKILL.md +15 -0
- package/.claude/skills/task-flow-improve-changes/SKILL.md +15 -0
- package/.claude/skills/task-flow-refactor/SKILL.md +14 -0
- package/.claude/skills/task-flow-report/SKILL.md +15 -0
- package/.claude/skills/task-flow-review/SKILL.md +14 -0
- package/.claude/skills/task-flow-run/SKILL.md +28 -0
- package/.claude/skills/task-flow-run/workflow.md +59 -0
- package/.claude/skills/task-flow-status/SKILL.md +13 -0
- package/.claude/skills/task-flow-sync/SKILL.md +30 -0
- package/.claude/skills/task-flow-sync/workflow.md +57 -0
- package/.claude/skills/task-flow-think/SKILL.md +17 -0
- package/.codex/config.toml +10 -0
- package/.cursor/rules/code_comments.mdc +4 -4
- package/.cursor/rules/coding_standards.mdc +57 -810
- package/.cursor/rules/commit_practices.mdc +5 -138
- package/.cursor/rules/cursor_rules.mdc +4 -3
- package/.cursor/rules/git_control.mdc +5 -86
- package/.cursor/rules/graphify-task-flow.mdc +31 -0
- package/.cursor/rules/rbin-git-policy.mdc +47 -0
- package/.cursor/rules/self_improve.mdc +3 -3
- package/.cursor/rules/task-flow-cursor.mdc +51 -0
- package/.cursor/rules/task-flow-sync.mdc +46 -0
- package/.cursor/rules/task_analysis.mdc +31 -179
- package/.cursor/rules/task_audit.mdc +6 -5
- package/.cursor/rules/task_check.mdc +2 -3
- package/.cursor/rules/task_estimate.mdc +3 -4
- package/.cursor/rules/task_execution.mdc +26 -138
- package/.cursor/rules/task_generate_flow.mdc +2 -3
- package/.cursor/rules/task_generation.mdc +22 -140
- package/.cursor/rules/task_improve_changes.mdc +3 -4
- package/.cursor/rules/task_refactor.mdc +4 -5
- package/.cursor/rules/task_report.mdc +3 -4
- package/.cursor/rules/task_review.mdc +4 -5
- package/.cursor/rules/task_status.mdc +4 -4
- package/.cursor/rules/task_work.mdc +23 -210
- package/.task-flow/AI-PLATFORMS.md +104 -0
- package/.task-flow/CODEX.md +141 -0
- package/.task-flow/CURSOR.md +94 -0
- package/.task-flow/GRAPHIFY.md +112 -0
- package/.task-flow/OPTIMIZATION-IMPLEMENTATION-TASKS.md +365 -0
- package/.task-flow/OPTIMIZATION-PLAN.md +264 -0
- package/.task-flow/README.md +19 -4
- package/.task-flow/docs/coding-standards-full.md +851 -0
- package/.task-flow/platforms/claude-code.md +352 -0
- package/.task-flow/platforms/codex.md +379 -0
- package/.task-flow/platforms/cursor.md +333 -0
- package/AGENTS.md +69 -31
- package/CLAUDE.md +56 -48
- package/README.md +86 -10
- package/bin/cli.js +41 -16
- package/lib/codex.js +45 -0
- package/lib/cursor.js +41 -0
- package/lib/gitignore.js +101 -0
- package/lib/graphify.js +118 -0
- package/lib/install.js +106 -52
- package/lib/profiles.js +110 -0
- package/lib/skills.js +34 -0
- package/lib/utils.js +38 -2
- package/package.json +6 -2
|
@@ -1,853 +1,100 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply:
|
|
2
|
+
description: RBIN coding standards checklist for TypeScript React Next.js Expo NestJS. Use when implementing or editing src/ or app/ code. Full reference on demand only.
|
|
3
|
+
globs: src/**,app/**
|
|
4
|
+
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Coding Standards
|
|
7
|
+
# Coding Standards — Checklist
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Apply when implementing Task Flow subtasks or feature code (this file is the **checklist**). For workflows and section index: invoke `@rbin-coding-standards` (`disable-model-invocation: true` — not auto). **Full guide:** [.task-flow/docs/coding-standards-full.md](mdc:.task-flow/docs/coding-standards-full.md) — **sections only**; never paste entire file into chat.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
All projects use TypeScript and follow this structure:
|
|
13
|
+
## Structure
|
|
16
14
|
|
|
17
15
|
```
|
|
18
16
|
src/
|
|
19
|
-
├── app/
|
|
20
|
-
├── features/
|
|
21
|
-
└── shared/
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### Allowed folders in shared/ and features/ (Front web & Mobile only)
|
|
25
|
-
|
|
26
|
-
In **front web** (Next.js, React, or other React-based web) or **mobile** (Expo, React Native) projects, only the following folder names may exist directly under `shared/` or under each feature in `features/`:
|
|
27
|
-
|
|
28
|
-
| Folder | Use |
|
|
29
|
-
|---------------|------------------------|
|
|
30
|
-
| `pages/` | Web only — page components |
|
|
31
|
-
| `screens/` | Mobile only — screen components |
|
|
32
|
-
| `components/` | UI components |
|
|
33
|
-
| `constants/` | Constants |
|
|
34
|
-
| `utils/` | Utilities |
|
|
35
|
-
| `validations/` | Validation helpers (e.g. `.validation.ts` for Zod) |
|
|
36
|
-
| `hooks/` | Custom hooks |
|
|
37
|
-
| `providers/` | Context providers |
|
|
38
|
-
| `styles/` | Styles |
|
|
39
|
-
| `types/` | TypeScript types |
|
|
40
|
-
| `libs/` | Library wrappers |
|
|
41
|
-
| `services/` | React Query / API wrappers |
|
|
42
|
-
| `use-cases/` | Pure API / business logic |
|
|
43
|
-
| `schemas/` | Zod (or similar) schemas |
|
|
44
|
-
|
|
45
|
-
**No other folder names** are allowed under `shared/` or under `features/[feature-name]/`. Use only `pages/` for web and `screens/` for mobile; do not mix both in the same project type.
|
|
46
|
-
|
|
47
|
-
### No subfolders in shared/ (Front web & Mobile only)
|
|
48
|
-
|
|
49
|
-
In **front web** (Next.js, React) or **mobile** (Expo, React Native) projects, each folder under `shared/` must be **flat**: no subfolders. Files assume the responsibility through clear naming.
|
|
50
|
-
|
|
51
|
-
- **shared/components/**: One file per component, directly in `shared/components/`. No nested folders (e.g. no `form/`, `inputs/`). Name files so the purpose is clear (e.g. `button.tsx`, `input-text.tsx`, `input-container.tsx`, `input-error.tsx`, `input-label.tsx`).
|
|
52
|
-
- **shared/hooks/**, **shared/utils/**, **shared/validations/**, **shared/constants/**, etc.: Same rule — only files, no subfolders. Use the file name to carry the responsibility. Validation helpers (e.g. `required-email.validation.ts`) live in `shared/validations/`.
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Tech Stack
|
|
57
|
-
|
|
58
|
-
### Front-end (Next.js / React)
|
|
59
|
-
- **Framework**: Next.js 15+ with App Router, or React (Vite, CRA, or similar). These rules apply to any React-based web project.
|
|
60
|
-
- **Language**: TypeScript (strict mode)
|
|
61
|
-
- **Styling**: Tailwind CSS v4 + `clsx` + `tailwind-merge` via `cn()` helper
|
|
62
|
-
- **UI Components**: shadcn/ui + `lucide-react` icons
|
|
63
|
-
- **Data Fetching**: `@tanstack/react-query`
|
|
64
|
-
- **Forms**: `react-hook-form` + `zod` + `@hookform/resolvers/zod`
|
|
65
|
-
- **Notifications**: `sonner`
|
|
66
|
-
- **ESLint**: `@rbinflow/eslint-config`
|
|
67
|
-
- **E2E Testing**: Cypress or equivalent browser E2E tooling
|
|
68
|
-
|
|
69
|
-
### Mobile (Expo / React Native)
|
|
70
|
-
- **Framework**: Expo with Expo Router, or React Native (bare or managed). These rules apply to both Expo and React Native.
|
|
71
|
-
- **Language**: TypeScript (strict mode)
|
|
72
|
-
- **Styling**: NativeWind + `clsx` + `tailwind-merge` via `cn()` helper
|
|
73
|
-
- **Data Fetching**: `@tanstack/react-query`
|
|
74
|
-
- **Forms**: `react-hook-form` + `zod` + `@hookform/resolvers/zod`
|
|
75
|
-
- **Build**: EAS (Expo Application Services)
|
|
76
|
-
- **ESLint**: `@rbinflow/eslint-config`
|
|
77
|
-
- **E2E Testing**: Detox
|
|
78
|
-
|
|
79
|
-
### Backend (NestJS)
|
|
80
|
-
- **Framework**: NestJS 10+
|
|
81
|
-
- **Language**: TypeScript (strict mode)
|
|
82
|
-
- **ORM**: Prisma
|
|
83
|
-
- **Database**: PostgreSQL
|
|
84
|
-
- **Auth**: JWT + Passport
|
|
85
|
-
- **Validation**: Zod (not class-validator/class-transformer)
|
|
86
|
-
- **Structure**: `src/app/` (controllers), `src/features/` (use-cases, repositories), `src/shared/` (guards, pipes, gateways)
|
|
87
|
-
|
|
88
|
-
### ESLint — @rbinflow/eslint-config
|
|
89
|
-
|
|
90
|
-
When the project uses `@rbinflow/eslint-config`, **do not add any other ESLint plugins or configs** — `@rbinflow/eslint-config` already configures everything. The `.eslintrc.cjs` file must stay minimal:
|
|
91
|
-
|
|
92
|
-
**Available configurations:**
|
|
93
|
-
|
|
94
|
-
| Project type | extends |
|
|
95
|
-
|--------------|---------|
|
|
96
|
-
| Node.js (no semicolons) | `@rbinflow/eslint-config/node` |
|
|
97
|
-
| Node.js (with semicolons) | `@rbinflow/eslint-config/node-with-semi` |
|
|
98
|
-
| React | `@rbinflow/eslint-config/react` |
|
|
99
|
-
| Next.js | `@rbinflow/eslint-config/next` |
|
|
100
|
-
| Expo | `@rbinflow/eslint-config/expo` |
|
|
101
|
-
|
|
102
|
-
**Example (.eslintrc.cjs):**
|
|
103
|
-
```javascript
|
|
104
|
-
module.exports = {
|
|
105
|
-
extends: ['@rbinflow/eslint-config/next'],
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Node.js variants:** `node` uses `semi: false`; `node-with-semi` uses `semi: true`. All other settings are identical. No extra `plugins`, `rules`, or `extends` beyond this.
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
113
|
-
## app/ — Routes Only (Front-end & Mobile) / Controllers Only (Backend)
|
|
114
|
-
|
|
115
|
-
The `app/` directory contains ONLY route definitions. Each file is a thin wrapper that imports and renders the feature page/screen.
|
|
116
|
-
|
|
117
|
-
### Next.js App Router — Route groups in app/
|
|
118
|
-
|
|
119
|
-
In **Next.js App Router** projects, organize `app/` with route groups:
|
|
120
|
-
|
|
121
|
-
- **`(public)/`** — Public routes (login, signup, landing, etc.)
|
|
122
|
-
- **`(private)/`** — Private routes (dashboard, profile, etc.; protect via layout or middleware)
|
|
123
|
-
- **`(server)/`** — API / server routes (Route Handlers, e.g. `route.ts`). **Any route under `/api` must live inside `(server)`** — i.e. `app/(server)/api/...`, not `app/api/...`.
|
|
124
|
-
|
|
125
|
-
Example structure:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
app/
|
|
129
|
-
├── (public)/
|
|
130
|
-
│ ├── login/page.tsx
|
|
131
|
-
│ └── signup/page.tsx
|
|
132
|
-
├── (private)/
|
|
133
|
-
│ ├── dashboard/page.tsx
|
|
134
|
-
│ └── admin/question/page.tsx
|
|
135
|
-
├── (server)/
|
|
136
|
-
│ └── api/ # /api/* routes live here
|
|
137
|
-
│ └── .../route.ts
|
|
138
|
-
├── layout.tsx
|
|
139
|
-
└── ...
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
**Front-end (Next.js App Router or React):**
|
|
143
|
-
```typescript
|
|
144
|
-
// src/app/(private)/dashboard/page.tsx
|
|
145
|
-
import { DashboardPage } from '@/features/dashboard/pages/dashboard.page'
|
|
146
|
-
|
|
147
|
-
export default function Dashboard() {
|
|
148
|
-
return <DashboardPage />
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Mobile (Expo Router or React Native):**
|
|
153
|
-
```typescript
|
|
154
|
-
// src/app/(private)/dashboard.tsx
|
|
155
|
-
import { DashboardScreen } from '@/features/dashboard/screens/dashboard.screen'
|
|
156
|
-
|
|
157
|
-
export default function Dashboard() {
|
|
158
|
-
return <DashboardScreen />
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**Backend (NestJS):**
|
|
163
|
-
```typescript
|
|
164
|
-
// src/app/account/controllers/create-session.controller.ts
|
|
165
|
-
import { Body, Controller, HttpCode, Post } from '@nestjs/common'
|
|
166
|
-
import { CreateSessionBodySchema, createSessionValidator } from '@/app/account/validators/create-session.validator'
|
|
167
|
-
import { CreateSessionUseCase } from '@/features/account/use-cases/create-session.use-case'
|
|
168
|
-
|
|
169
|
-
@Controller('/session')
|
|
170
|
-
export class CreateSessionController {
|
|
171
|
-
constructor(private createSessionUseCase: CreateSessionUseCase) {}
|
|
172
|
-
|
|
173
|
-
@Post()
|
|
174
|
-
@HttpCode(200)
|
|
175
|
-
async handle(@Body(createSessionValidator) data: CreateSessionBodySchema) {
|
|
176
|
-
return this.createSessionUseCase.execute(data)
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**Rules:**
|
|
182
|
-
- **Front-end/Mobile**: `page.tsx` / route files NEVER contain logic, state, or imports beyond the feature component
|
|
183
|
-
- **Backend**: `app/` contains controllers and validators only — no business logic
|
|
184
|
-
- **Next.js App Router**: Use `(public)/` for public routes, `(private)/` for private routes, `(server)/` for API/Route Handlers; **routes under `/api` must be under `app/(server)/api/`**, not `app/api/`. Other React front-ends and mobile may use only `(public)` and `(private)` as needed.
|
|
185
|
-
- `layout.tsx` files may contain auth guards and providers
|
|
186
|
-
- All business logic lives in `features/`
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## features/ — Feature Organization
|
|
191
|
-
|
|
192
|
-
Each feature is self-contained. Structure:
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
features/[feature-name]/
|
|
196
|
-
├── components/ # UI components specific to this feature
|
|
197
|
-
├── hooks/ # Context providers and custom hooks
|
|
198
|
-
├── pages/ # Page component (front-end) — orchestrator
|
|
199
|
-
├── screens/ # Screen component (mobile) — orchestrator
|
|
200
|
-
├── schemas/ # Zod validation schemas
|
|
201
|
-
├── services/ # React Query wrappers (useQuery / useMutation)
|
|
202
|
-
├── types/ # TypeScript types for this feature
|
|
203
|
-
├── use-cases/ # Pure API call logic
|
|
204
|
-
└── utils/ # Feature-specific utilities (optional)
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Backend Feature Structure
|
|
208
|
-
|
|
209
|
-
```
|
|
210
|
-
features/[feature-name]/
|
|
211
|
-
├── use-cases/ # Business logic
|
|
212
|
-
│ └── create-account.use-case.ts
|
|
213
|
-
├── repositories/ # Data access (abstract + Prisma implementation)
|
|
214
|
-
│ ├── account.repository.ts # Abstract class
|
|
215
|
-
│ └── prisma-account.repository.ts # Prisma implementation
|
|
216
|
-
├── types/ # TypeScript types
|
|
217
|
-
│ └── account.type.ts
|
|
218
|
-
└── utils/ # Feature-specific utilities (optional)
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
**Use Case (backend):**
|
|
222
|
-
```typescript
|
|
223
|
-
// src/features/account/use-cases/create-session.use-case.ts
|
|
224
|
-
import { Injectable, UnauthorizedException } from '@nestjs/common'
|
|
225
|
-
import { AccountRepository } from '@/features/account/repositories/account.repository'
|
|
226
|
-
import { EncryptionGateway } from '@/shared/gateways/encryption.gateway'
|
|
227
|
-
import { JwtGateway } from '@/shared/gateways/jwt.gateway'
|
|
228
|
-
|
|
229
|
-
@Injectable()
|
|
230
|
-
export class CreateSessionUseCase {
|
|
231
|
-
constructor(
|
|
232
|
-
private accountRepository: AccountRepository,
|
|
233
|
-
private encryption: EncryptionGateway,
|
|
234
|
-
private jwt: JwtGateway,
|
|
235
|
-
) {}
|
|
236
|
-
|
|
237
|
-
async execute(data: { email: string; password: string }) {
|
|
238
|
-
const account = await this.accountRepository.findByEmail(data.email)
|
|
239
|
-
if (!account) throw new UnauthorizedException('Invalid credentials')
|
|
240
|
-
|
|
241
|
-
const isValid = await this.encryption.validateHash({
|
|
242
|
-
value: data.password,
|
|
243
|
-
hashedValue: account.password,
|
|
244
|
-
})
|
|
245
|
-
if (!isValid) throw new UnauthorizedException('Invalid credentials')
|
|
246
|
-
|
|
247
|
-
return this.jwt.generateAuthTokens({ accountId: account.id })
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
**Zod validation in NestJS (not class-validator):**
|
|
253
|
-
```typescript
|
|
254
|
-
// src/app/account/validators/create-session.validator.ts
|
|
255
|
-
import { z } from 'zod'
|
|
256
|
-
import { ZodValidationPipe } from '@/shared/pipes/zod-validation.pipe'
|
|
257
|
-
|
|
258
|
-
const createSessionBodySchema = z.object({
|
|
259
|
-
email: z.string().email(),
|
|
260
|
-
password: z.string().min(6),
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
export type CreateSessionBodySchema = z.infer<typeof createSessionBodySchema>
|
|
264
|
-
export const createSessionValidator = new ZodValidationPipe(createSessionBodySchema)
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
```typescript
|
|
268
|
-
// src/shared/pipes/zod-validation.pipe.ts
|
|
269
|
-
import { BadRequestException, PipeTransform } from '@nestjs/common'
|
|
270
|
-
import { ZodError, ZodSchema } from 'zod'
|
|
271
|
-
|
|
272
|
-
export class ZodValidationPipe implements PipeTransform {
|
|
273
|
-
constructor(private schema: ZodSchema) {}
|
|
274
|
-
|
|
275
|
-
transform(value: unknown) {
|
|
276
|
-
try {
|
|
277
|
-
return this.schema.parse(value)
|
|
278
|
-
} catch (error) {
|
|
279
|
-
if (error instanceof ZodError) {
|
|
280
|
-
throw new BadRequestException({
|
|
281
|
-
message: 'Validation failed',
|
|
282
|
-
errors: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })),
|
|
283
|
-
})
|
|
284
|
-
}
|
|
285
|
-
throw new BadRequestException('Validation failed')
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
**Gateways (abstract services in shared/):**
|
|
292
|
-
```typescript
|
|
293
|
-
// src/shared/gateways/encryption.gateway.ts
|
|
294
|
-
export abstract class EncryptionGateway {
|
|
295
|
-
abstract createHash(value: string): Promise<string>
|
|
296
|
-
abstract validateHash(params: { value: string; hashedValue: string }): Promise<boolean>
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
Gateways abstract external concerns (encryption, JWT, date, email). Implementations live alongside in `shared/gateways/` and are bound via NestJS modules.
|
|
301
|
-
|
|
302
|
-
### Page / Screen — Orchestrator Pattern (Front-end/Mobile)
|
|
303
|
-
|
|
304
|
-
The page/screen is an orchestrator. It:
|
|
305
|
-
- Calls services (React Query hooks)
|
|
306
|
-
- Manages form state with `useForm`
|
|
307
|
-
- Passes data down to components as props
|
|
308
|
-
- Is NEVER a long file — delegates rendering to components
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
// src/features/dashboard/pages/dashboard.page.tsx
|
|
312
|
-
'use client'
|
|
313
|
-
|
|
314
|
-
import { DashboardRevenueCard } from '@/features/dashboard/components/dashboard-revenue-card'
|
|
315
|
-
import { DashboardActiveCard } from '@/features/dashboard/components/dashboard-active-card'
|
|
316
|
-
import { GetDashboardActiveService } from '@/features/dashboard/services/get-dashboard-active.service'
|
|
317
|
-
|
|
318
|
-
export function DashboardPage() {
|
|
319
|
-
const { data: active, isLoading, isError } = GetDashboardActiveService()
|
|
320
|
-
|
|
321
|
-
return (
|
|
322
|
-
<section className="grid grid-cols-2 gap-5 my-10">
|
|
323
|
-
<DashboardActiveCard data={active} isLoading={isLoading} isError={isError} />
|
|
324
|
-
<DashboardRevenueCard />
|
|
325
|
-
</section>
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Page-specific component naming (Front web & Mobile)
|
|
331
|
-
|
|
332
|
-
When a component is specific to a page (lives under `features/[feature]/components/`), it must use the **page prefix** in the file name and in the function name.
|
|
333
|
-
|
|
334
|
-
**File names (kebab-case):**
|
|
335
|
-
- Page: `[page-name].page.tsx` → e.g. `admin-question.page.tsx`
|
|
336
|
-
- Page-specific components: prefix = page name (no `.page`) → `admin-question-card.tsx`, `admin-question-card-item.tsx`
|
|
337
|
-
- A **child component** of another component stays in the same `components/` folder (same level as parent) and **inherits the parent prefix**: `admin-question-card-item.tsx` (child of `admin-question-card.tsx`). No subfolders.
|
|
338
|
-
|
|
339
|
-
**Function/component names (PascalCase):**
|
|
340
|
-
- Route in `app/`: one word from route + page name → `AdminQuestion` (export default)
|
|
341
|
-
- Page in features: page name + `Page` → `AdminQuestionPage`
|
|
342
|
-
- Components: PascalCase of the file name → `AdminQuestionCard`, `AdminQuestionCardItem`
|
|
343
|
-
|
|
344
|
-
| Location | File | Export name |
|
|
345
|
-
|----------|------|-------------|
|
|
346
|
-
| `app/(private)/admin/question/page.tsx` | (route file) | `AdminQuestion` (default) |
|
|
347
|
-
| `features/admin/question/pages/admin-question.page.tsx` | `admin-question.page.tsx` | `AdminQuestionPage` |
|
|
348
|
-
| `features/admin/question/components/admin-question-card.tsx` | `admin-question-card.tsx` | `AdminQuestionCard` |
|
|
349
|
-
| `features/admin/question/components/admin-question-card-item.tsx` | `admin-question-card-item.tsx` | `AdminQuestionCardItem` |
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
// src/app/(private)/admin/question/page.tsx
|
|
353
|
-
import { AdminQuestionPage } from '@/features/admin/question/pages/admin-question.page'
|
|
354
|
-
|
|
355
|
-
export default function AdminQuestion() {
|
|
356
|
-
return <AdminQuestionPage />
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
```typescript
|
|
361
|
-
// src/features/admin/question/pages/admin-question.page.tsx
|
|
362
|
-
'use client'
|
|
363
|
-
|
|
364
|
-
import { AdminQuestionCard } from '@/features/admin/question/components/admin-question-card'
|
|
365
|
-
|
|
366
|
-
export function AdminQuestionPage() {
|
|
367
|
-
return (
|
|
368
|
-
<section>
|
|
369
|
-
<AdminQuestionCard />
|
|
370
|
-
</section>
|
|
371
|
-
)
|
|
372
|
-
}
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
```typescript
|
|
376
|
-
// src/features/admin/question/components/admin-question-card.tsx
|
|
377
|
-
import { AdminQuestionCardItem } from '@/features/admin/question/components/admin-question-card-item'
|
|
378
|
-
|
|
379
|
-
export function AdminQuestionCard() {
|
|
380
|
-
return (
|
|
381
|
-
<ul>
|
|
382
|
-
<AdminQuestionCardItem />
|
|
383
|
-
</ul>
|
|
384
|
-
)
|
|
385
|
-
}
|
|
17
|
+
├── app/ # Routes only — thin wrappers
|
|
18
|
+
├── features/ # Domain logic per feature
|
|
19
|
+
└── shared/ # Cross-feature reuse (2+ features)
|
|
386
20
|
```
|
|
387
21
|
|
|
388
|
-
|
|
389
|
-
// src/features/admin/question/components/admin-question-card-item.tsx
|
|
390
|
-
export function AdminQuestionCardItem() {
|
|
391
|
-
return <li>...</li>
|
|
392
|
-
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### Components — Single Responsibility
|
|
396
|
-
|
|
397
|
-
Each component has a single responsibility. Components:
|
|
398
|
-
- Receive data via props (no direct service calls unless truly necessary)
|
|
399
|
-
- Follow the **page prefix** naming above when they are page-specific (inside a feature)
|
|
400
|
-
- Stay small and focused; child components stay in the same `components/` folder with the inherited prefix in the name
|
|
401
|
-
|
|
402
|
-
### Base components — no raw usage (Front web & Mobile)
|
|
403
|
-
|
|
404
|
-
Do **not** use raw `<button>`, `<input>`, or other base HTML/primitive elements scattered in the code. If a screen uses a button or an input, there must be a **reusable component** for it (in `shared/components/` or, when feature-specific, in the feature’s `components/`). This applies to all base-level UI: buttons, inputs, selects, checkboxes, textareas, links, etc.
|
|
405
|
-
|
|
406
|
-
- **Shared**: Use `shared/components/` for app-wide primitives (e.g. `button.tsx`, `input-text.tsx`, `input-select.tsx`). Pages and feature components import and use these.
|
|
407
|
-
- **Feature-specific**: If the variant is only used in one feature, create it in `features/[feature]/components/` and reuse it inside that feature; do not repeat the same JSX in multiple files.
|
|
408
|
-
- **Never**: Inline `<button>`, `<input type="text">`, etc. in a page or component without going through a defined component. Every button/input on a screen must come from a named, reusable component.
|
|
22
|
+
**`app/`:** `page.tsx` / route files import and render feature page/screen only — no business logic, no extra state.
|
|
409
23
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
## shared/ — Global Reusable Code
|
|
24
|
+
**Next.js App Router:** `(public)/`, `(private)/`, `(server)/` — API under `app/(server)/api/`, not `app/api/`.
|
|
413
25
|
|
|
414
|
-
|
|
26
|
+
**`features/[name]/` allowed folders:** `components/`, `hooks/`, `pages/` (web), `screens/` (mobile), `schemas/`, `services/`, `use-cases/`, `types/`, `constants/`, `utils/`, `validations/`, `providers/`, `styles/`, `libs/` — no other top-level names.
|
|
415
27
|
|
|
416
|
-
|
|
417
|
-
shared/
|
|
418
|
-
├── components/
|
|
419
|
-
│ ├── button.tsx
|
|
420
|
-
│ ├── card.tsx
|
|
421
|
-
│ ├── data-handler.tsx
|
|
422
|
-
│ ├── skeleton.tsx
|
|
423
|
-
│ ├── dialog.tsx
|
|
424
|
-
│ ├── input-text.tsx
|
|
425
|
-
│ ├── input-select.tsx
|
|
426
|
-
│ ├── input-container.tsx
|
|
427
|
-
│ ├── input-error.tsx
|
|
428
|
-
│ └── input-label.tsx
|
|
429
|
-
├── hooks/
|
|
430
|
-
│ ├── dialog.hook.tsx
|
|
431
|
-
│ └── drawer.hook.tsx
|
|
432
|
-
├── libs/
|
|
433
|
-
│ ├── axios.ts
|
|
434
|
-
│ ├── react-query.ts
|
|
435
|
-
│ └── tw-merge.ts
|
|
436
|
-
├── providers/
|
|
437
|
-
│ └── react-query-provider.tsx
|
|
438
|
-
├── types/
|
|
439
|
-
│ └── shared.ui.type.ts
|
|
440
|
-
├── constants/
|
|
441
|
-
│ └── server-routes.constants.ts
|
|
442
|
-
├── validations/
|
|
443
|
-
│ ├── required-email.validation.ts
|
|
444
|
-
│ ├── required-string.validation.ts
|
|
445
|
-
│ └── required-phone.validation.ts
|
|
446
|
-
└── utils/
|
|
447
|
-
└── error.util.ts
|
|
448
|
-
```
|
|
28
|
+
**`shared/` (web/mobile):** flat folders only — no subfolders; one file per concern (e.g. `shared/components/button.tsx`).
|
|
449
29
|
|
|
450
30
|
---
|
|
451
31
|
|
|
452
|
-
##
|
|
453
|
-
|
|
454
|
-
Use a `DataHandler` component to handle loading, error, and empty states declaratively instead of repeating `if (isLoading)` / `if (isError)` in every component.
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
// src/shared/components/data-handler.tsx
|
|
458
|
-
import { ReactNode } from 'react'
|
|
459
|
-
import { Skeleton } from '@/shared/components/skeleton'
|
|
32
|
+
## Stack (defaults)
|
|
460
33
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
34
|
+
| Layer | Use |
|
|
35
|
+
|-------|-----|
|
|
36
|
+
| Web | Next 15+ App Router or React; Tailwind + `cn()`; shadcn; react-query; RHF + zod |
|
|
37
|
+
| Mobile | Expo Router / RN; NativeWind + `cn()`; react-query; RHF + zod |
|
|
38
|
+
| Backend | NestJS 10+; Prisma; Zod pipes (not class-validator); `app/` controllers, `features/` use-cases |
|
|
467
39
|
|
|
468
|
-
|
|
469
|
-
if (isLoading) return skeleton ?? <Skeleton />
|
|
470
|
-
if (isError) return <ErrorState />
|
|
471
|
-
return <>{children}</>
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
Usage in pages:
|
|
476
|
-
```typescript
|
|
477
|
-
<DataHandler isLoading={isLoading} isError={isError} skeleton={<DashboardSkeleton />}>
|
|
478
|
-
<DashboardRevenueCard revenue={revenue} />
|
|
479
|
-
</DataHandler>
|
|
480
|
-
```
|
|
40
|
+
**ESLint:** `@rbinflow/eslint-config` only — no extra plugins in `.eslintrc.cjs`.
|
|
481
41
|
|
|
482
42
|
---
|
|
483
43
|
|
|
484
|
-
##
|
|
44
|
+
## Patterns (non-negotiable)
|
|
485
45
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
│ └── app-providers-client.tsx
|
|
497
|
-
└── i18n/ # Internationalization (optional)
|
|
498
|
-
```
|
|
46
|
+
1. **Page/screen = orchestrator** — short; compose components; no long JSX blocks.
|
|
47
|
+
2. **Components = single responsibility** — split when growing; page-prefixed names in feature `components/`.
|
|
48
|
+
3. **`cn()`** for all class merging — never template literal conditionals for Tailwind.
|
|
49
|
+
4. **API:** use-case (pure, no React) + service (react-query) — never `axios`/`api` in components.
|
|
50
|
+
5. **Forms:** zod schema + `react-hook-form` + `zodResolver` + **`Controller`** on inputs — no uncontrolled inputs.
|
|
51
|
+
6. **Types:** no `any`; API types `.api.type.ts`, domain `.type.ts`.
|
|
52
|
+
7. **UI primitives:** no raw `<button>`, `<input>`, etc. — use `shared/components/` or feature components.
|
|
53
|
+
8. **No barrel `index.ts`** re-exports — import from source file.
|
|
54
|
+
9. **No explanatory code comments** — `dev-logs/` for complex docs; separation comments `// ───` only.
|
|
55
|
+
10. **Git:** [rbin-git-policy.mdc](mdc:.cursor/rules/rbin-git-policy.mdc) — suggest only.
|
|
499
56
|
|
|
500
57
|
---
|
|
501
58
|
|
|
502
|
-
##
|
|
503
|
-
|
|
504
|
-
Compose all providers in a dedicated `AppProviders` component instead of nesting them in `layout.tsx`:
|
|
505
|
-
|
|
506
|
-
```typescript
|
|
507
|
-
// src/features/platform/providers/app-providers-client.tsx
|
|
508
|
-
'use client'
|
|
509
|
-
|
|
510
|
-
import { ReactNode } from 'react'
|
|
511
|
-
import { Toaster } from 'sonner'
|
|
512
|
-
import { AuthProvider } from '@/features/auth/hooks/authentication.hook'
|
|
513
|
-
import { DialogProvider } from '@/shared/hooks/dialog.hook'
|
|
514
|
-
|
|
515
|
-
export function AppProvidersClient({ children }: { children: ReactNode }) {
|
|
516
|
-
return (
|
|
517
|
-
<AuthProvider>
|
|
518
|
-
<DialogProvider>
|
|
519
|
-
{children}
|
|
520
|
-
<Toaster position="top-right" />
|
|
521
|
-
</DialogProvider>
|
|
522
|
-
</AuthProvider>
|
|
523
|
-
)
|
|
524
|
-
}
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
Then in the root layout:
|
|
528
|
-
```typescript
|
|
529
|
-
// src/app/layout.tsx
|
|
530
|
-
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
531
|
-
return (
|
|
532
|
-
<html lang="en">
|
|
533
|
-
<body>
|
|
534
|
-
<ReactQueryProvider>
|
|
535
|
-
<AppProvidersClient>{children}</AppProvidersClient>
|
|
536
|
-
</ReactQueryProvider>
|
|
537
|
-
</body>
|
|
538
|
-
</html>
|
|
539
|
-
)
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
---
|
|
544
|
-
|
|
545
|
-
## cn() — Tailwind Class Merging
|
|
546
|
-
|
|
547
|
-
Always use `cn()` for class names. Never use raw string concatenation or template literals with conditional classes.
|
|
548
|
-
|
|
549
|
-
```typescript
|
|
550
|
-
// src/shared/libs/tw-merge.ts
|
|
551
|
-
import { type ClassValue, clsx } from 'clsx'
|
|
552
|
-
import { twMerge } from 'tailwind-merge'
|
|
553
|
-
|
|
554
|
-
export function cn(...inputs: ClassValue[]) {
|
|
555
|
-
return twMerge(clsx(inputs))
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
Usage:
|
|
560
|
-
```typescript
|
|
561
|
-
import { cn } from '@/shared/libs/tw-merge'
|
|
562
|
-
|
|
563
|
-
className={cn(
|
|
564
|
-
'flex flex-col rounded-lg bg-white px-5',
|
|
565
|
-
isActive && 'border-2 border-accent-500',
|
|
566
|
-
className,
|
|
567
|
-
)}
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
---
|
|
571
|
-
|
|
572
|
-
## React Query — Service + Use Case Pattern
|
|
573
|
-
|
|
574
|
-
Split API calls into two layers:
|
|
575
|
-
|
|
576
|
-
**Use Case** — pure function, no React, just the API call:
|
|
577
|
-
```typescript
|
|
578
|
-
// src/features/auth/use-cases/session-create.use-case.ts
|
|
579
|
-
import { api } from '@/shared/libs/axios'
|
|
580
|
-
import { serverRoutes } from '@/shared/constants/server-routes.constants'
|
|
581
|
-
import { LoginSchema } from '@/features/auth/schemas/login.schema'
|
|
582
|
-
import { SessionType } from '@/features/auth/types/session.api.type'
|
|
583
|
-
|
|
584
|
-
export async function sessionCreateUseCase(data: LoginSchema): Promise<SessionType> {
|
|
585
|
-
const response = await api.post(`${serverRoutes.session}/create`, data)
|
|
586
|
-
return response.data
|
|
587
|
-
}
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
**Service** — React Query wrapper used in components:
|
|
591
|
-
```typescript
|
|
592
|
-
// src/features/auth/services/session-create.service.tsx
|
|
593
|
-
'use client'
|
|
594
|
-
|
|
595
|
-
import { useMutation } from '@tanstack/react-query'
|
|
596
|
-
import { sessionCreateUseCase } from '@/features/auth/use-cases/session-create.use-case'
|
|
597
|
-
import { SessionType } from '@/features/auth/types/session.api.type'
|
|
598
|
-
import { ServiceInput } from '@/shared/types/shared.ui.type'
|
|
599
|
-
import { handleError } from '@/shared/utils/error.util'
|
|
600
|
-
|
|
601
|
-
export function SessionCreateService({ onSuccess }: ServiceInput<SessionType>) {
|
|
602
|
-
return useMutation({
|
|
603
|
-
mutationFn: sessionCreateUseCase,
|
|
604
|
-
onSuccess: (data) => onSuccess?.(data),
|
|
605
|
-
onError: (error) => handleError(error),
|
|
606
|
-
})
|
|
607
|
-
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
**Query service:**
|
|
611
|
-
```typescript
|
|
612
|
-
// src/features/dashboard/services/get-dashboard-active.service.tsx
|
|
613
|
-
'use client'
|
|
59
|
+
## File naming (outside `app/`)
|
|
614
60
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
**Shared service input type:**
|
|
630
|
-
```typescript
|
|
631
|
-
// src/shared/types/shared.ui.type.ts
|
|
632
|
-
export type ServiceInput<TData = unknown> = {
|
|
633
|
-
onSuccess?: (data?: TData) => void
|
|
634
|
-
onError?: () => void
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
---
|
|
639
|
-
|
|
640
|
-
## Forms — React Hook Form + Zod
|
|
641
|
-
|
|
642
|
-
**Schema:**
|
|
643
|
-
```typescript
|
|
644
|
-
// src/features/auth/schemas/login.schema.ts
|
|
645
|
-
import { z } from 'zod'
|
|
646
|
-
import { requiredEmail } from '@/shared/validations/required-email.validation'
|
|
647
|
-
import { requiredString } from '@/shared/validations/required-string.validation'
|
|
648
|
-
|
|
649
|
-
export const loginSchema = z.object({
|
|
650
|
-
email: requiredEmail(),
|
|
651
|
-
password: requiredString({ field: 'password' }),
|
|
652
|
-
})
|
|
653
|
-
|
|
654
|
-
export type LoginSchema = z.infer<typeof loginSchema>
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
**Reusable validators in shared/:**
|
|
658
|
-
```typescript
|
|
659
|
-
// src/shared/validations/required-string.validation.ts
|
|
660
|
-
import { z } from 'zod'
|
|
661
|
-
|
|
662
|
-
export const requiredString = ({ field, min = 1 }: { field: string; min?: number }) =>
|
|
663
|
-
z.string({ message: `${field} is required` }).min(min, {
|
|
664
|
-
message: `${field} must be at least ${min} characters`,
|
|
665
|
-
})
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
**Form component:**
|
|
669
|
-
```typescript
|
|
670
|
-
// src/features/auth/components/login-form.tsx
|
|
671
|
-
'use client'
|
|
672
|
-
|
|
673
|
-
import { zodResolver } from '@hookform/resolvers/zod'
|
|
674
|
-
import { useForm } from 'react-hook-form'
|
|
675
|
-
import { LoginSchema, loginSchema } from '@/features/auth/schemas/login.schema'
|
|
676
|
-
import { SessionCreateService } from '@/features/auth/services/session-create.service'
|
|
677
|
-
import { InputText } from '@/shared/components/input-text'
|
|
678
|
-
import { Button } from '@/shared/components/button'
|
|
679
|
-
|
|
680
|
-
export function LoginForm() {
|
|
681
|
-
const { control, handleSubmit } = useForm<LoginSchema>({
|
|
682
|
-
resolver: zodResolver(loginSchema),
|
|
683
|
-
defaultValues: { email: '', password: '' },
|
|
684
|
-
})
|
|
685
|
-
|
|
686
|
-
const { mutate: createSession, isPending } = SessionCreateService({
|
|
687
|
-
onSuccess: () => { /* redirect */ },
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
return (
|
|
691
|
-
<form onSubmit={handleSubmit((data) => createSession(data))} className="flex flex-col gap-4">
|
|
692
|
-
<InputText name="email" control={control} placeholder="Email" />
|
|
693
|
-
<InputText name="password" control={control} type="password" placeholder="Password" />
|
|
694
|
-
<Button type="submit" isLoading={isPending}>Sign in</Button>
|
|
695
|
-
</form>
|
|
696
|
-
)
|
|
697
|
-
}
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
**Input uses `Controller` from RHF and is generic:**
|
|
701
|
-
```typescript
|
|
702
|
-
// src/shared/components/input-text.tsx
|
|
703
|
-
import { Control, Controller, FieldValues, Path, PathValue } from 'react-hook-form'
|
|
704
|
-
import { cn } from '@/shared/libs/tw-merge'
|
|
705
|
-
|
|
706
|
-
type InputTextProps<T extends FieldValues> = {
|
|
707
|
-
name: Path<T>
|
|
708
|
-
control: Control<T>
|
|
709
|
-
defaultValue?: PathValue<T, Path<T>>
|
|
710
|
-
placeholder?: string
|
|
711
|
-
type?: 'text' | 'password' | 'email'
|
|
712
|
-
disabled?: boolean
|
|
713
|
-
className?: string
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
export function InputText<T extends FieldValues>({
|
|
717
|
-
name, control, defaultValue = '' as PathValue<T, Path<T>>,
|
|
718
|
-
placeholder, type = 'text', disabled, className,
|
|
719
|
-
}: InputTextProps<T>) {
|
|
720
|
-
return (
|
|
721
|
-
<Controller
|
|
722
|
-
name={name}
|
|
723
|
-
control={control}
|
|
724
|
-
defaultValue={defaultValue}
|
|
725
|
-
render={({ field, fieldState: { error } }) => (
|
|
726
|
-
<div className={cn('flex flex-col gap-1', className)}>
|
|
727
|
-
<input
|
|
728
|
-
{...field}
|
|
729
|
-
type={type}
|
|
730
|
-
placeholder={placeholder}
|
|
731
|
-
disabled={disabled}
|
|
732
|
-
className={cn('border rounded-lg px-4 h-12 text-sm', error && 'border-red-500')}
|
|
733
|
-
/>
|
|
734
|
-
{error && <span className="text-xs text-red-500">{error.message}</span>}
|
|
735
|
-
</div>
|
|
736
|
-
)}
|
|
737
|
-
/>
|
|
738
|
-
)
|
|
739
|
-
}
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
---
|
|
743
|
-
|
|
744
|
-
## File Naming Conventions
|
|
745
|
-
|
|
746
|
-
### Rule for front (web & mobile): outside `app/` only
|
|
747
|
-
|
|
748
|
-
Every file outside the `app/` folder must follow:
|
|
749
|
-
|
|
750
|
-
- **Format**: lowercase, words separated by `-`, then `.tipo.ext` (suffix by role).
|
|
751
|
-
- **Examples**: `pagina-exemplo.page.tsx`, `login.schema.ts`, `session-create.use-case.ts`, `dashboard-active.type.ts`.
|
|
752
|
-
|
|
753
|
-
**Components are the exception**: do **not** use a `.component` suffix. Use only kebab-case + extension, e.g. `pagina-exemplo.tsx`, `dashboard-revenue-card.tsx`, `input-text.tsx`.
|
|
754
|
-
|
|
755
|
-
**Another exception**: files inside any `styles/` folder do not need to follow this pattern (e.g. CSS/SCSS modules may use their own convention).
|
|
756
|
-
|
|
757
|
-
### Suffix by role
|
|
758
|
-
|
|
759
|
-
| Role | Suffix | Example |
|
|
760
|
-
|------|--------|---------|
|
|
761
|
-
| Page component | `.page.tsx` | `pagina-exemplo.page.tsx` |
|
|
762
|
-
| Screen component (mobile) | `.screen.tsx` | `login.screen.tsx` |
|
|
763
|
-
| UI component | (no suffix) `.tsx` | `dashboard-revenue-card.tsx`, `input-text.tsx` |
|
|
764
|
-
| React Query service | `.service.tsx` | `session-create.service.tsx` |
|
|
765
|
-
| API call (pure) | `.use-case.ts` | `session-create.use-case.ts` |
|
|
766
|
-
| Zod schema | `.schema.ts` | `login.schema.ts` |
|
|
767
|
-
| Context/hook | `.hook.tsx` | `authentication.hook.tsx` |
|
|
768
|
-
| API response type | `.api.type.ts` | `session.api.type.ts` |
|
|
769
|
-
| Domain type | `.type.ts` | `dashboard-active.type.ts` |
|
|
770
|
-
| Utility | `.util.ts` | `error.util.ts` |
|
|
771
|
-
| Validation helper | `.validation.ts` | `required-email.validation.ts` (in `validations/`) |
|
|
61
|
+
| Role | Suffix / pattern | Example |
|
|
62
|
+
|------|------------------|---------|
|
|
63
|
+
| Page | `.page.tsx` | `dashboard.page.tsx` |
|
|
64
|
+
| Screen | `.screen.tsx` | `login.screen.tsx` |
|
|
65
|
+
| Component | kebab `.tsx` (no `.component`) | `dashboard-revenue-card.tsx` |
|
|
66
|
+
| Service | `.service.tsx` | `session-create.service.tsx` |
|
|
67
|
+
| Use case | `.use-case.ts` | `session-create.use-case.ts` |
|
|
68
|
+
| Schema | `.schema.ts` | `login.schema.ts` |
|
|
69
|
+
| Hook | `.hook.tsx` | `authentication.hook.tsx` |
|
|
70
|
+
| Type | `.type.ts` / `.api.type.ts` | `session.api.type.ts` |
|
|
71
|
+
| Util | `.util.ts` | `error.util.ts` |
|
|
72
|
+
| Validation | `.validation.ts` in `validations/` | `required-email.validation.ts` |
|
|
772
73
|
| Constants | `.constants.ts` | `server-routes.constants.ts` |
|
|
773
74
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
## TypeScript Types
|
|
777
|
-
|
|
778
|
-
- API response types: `[Name]Type` in a `.api.type.ts` file
|
|
779
|
-
- Domain/internal types: `[Name]Type` in a `.type.ts` file
|
|
780
|
-
- All types exported with `export type`
|
|
781
|
-
- No `any` — ever
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
// src/features/auth/types/session.api.type.ts
|
|
785
|
-
export type SessionType = {
|
|
786
|
-
accessToken: string
|
|
787
|
-
refreshToken: string
|
|
788
|
-
}
|
|
789
|
-
```
|
|
75
|
+
**Route export:** one word + page name (e.g. `AdminQuestion`); feature page `AdminQuestionPage`.
|
|
790
76
|
|
|
791
77
|
---
|
|
792
78
|
|
|
793
|
-
##
|
|
794
|
-
|
|
795
|
-
```typescript
|
|
796
|
-
// src/features/auth/hooks/authentication.hook.tsx
|
|
797
|
-
'use client'
|
|
79
|
+
## Platform feature
|
|
798
80
|
|
|
799
|
-
|
|
81
|
+
`features/platform/` — app shell, routes constants, `AppProviders` composition (not generic shared).
|
|
800
82
|
|
|
801
|
-
|
|
802
|
-
account: AccountType | null
|
|
803
|
-
isAuthenticated: boolean
|
|
804
|
-
signOut: () => Promise<void>
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const AuthContext = createContext<AuthContextData>({} as AuthContextData)
|
|
808
|
-
|
|
809
|
-
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
810
|
-
// state and derived values here via useMemo
|
|
811
|
-
return (
|
|
812
|
-
<AuthContext.Provider value={{ account, isAuthenticated, signOut }}>
|
|
813
|
-
{children}
|
|
814
|
-
</AuthContext.Provider>
|
|
815
|
-
)
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
export const useAuth = () => useContext(AuthContext)
|
|
819
|
-
```
|
|
83
|
+
**DataHandler** — use for loading/error/empty in pages (see full doc).
|
|
820
84
|
|
|
821
85
|
---
|
|
822
86
|
|
|
823
|
-
##
|
|
824
|
-
|
|
825
|
-
### Front-end (Next.js / React)
|
|
826
|
-
- **E2E**: Cypress or equivalent browser E2E tooling
|
|
827
|
-
- Tests live in `e2e/` or `cypress/` at project root
|
|
828
|
-
- Test files should follow the tool convention in use consistently
|
|
829
|
-
- Cover critical user flows: auth, main feature interactions, form submissions
|
|
87
|
+
## Audit / sync
|
|
830
88
|
|
|
831
|
-
|
|
832
|
-
-
|
|
833
|
-
- Tests live in `e2e/` at project root
|
|
834
|
-
- Test files: `[feature].test.ts`
|
|
835
|
-
- Cover critical flows: login, main navigation, core feature interactions
|
|
89
|
+
- **`task-flow: audit`:** score against this checklist; open [coding-standards-full.md](mdc:.task-flow/docs/coding-standards-full.md) only for deep dives or user-requested depth.
|
|
90
|
+
- **`task-flow: sync`:** subtask instructions follow checklist patterns (structure, naming, service+use-case) — not full doc.
|
|
836
91
|
|
|
837
92
|
---
|
|
838
93
|
|
|
839
|
-
##
|
|
94
|
+
## Quick invoke
|
|
840
95
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
6. **API logic in use-cases**: Components and services never call `axios`/`api` directly — always through a use-case function.
|
|
847
|
-
7. **Zod for all validation**: No manual validation — always define a schema and use `zodResolver`.
|
|
848
|
-
8. **Type everything**: No `any`. All props, params, and return values must be typed.
|
|
849
|
-
9. **Shared is truly shared**: Only put in `shared/` what is used by 2+ features.
|
|
850
|
-
10. **Naming reflects role**: File name + suffix must make the file's purpose immediately obvious.
|
|
851
|
-
11. **No barrel files for re-exports**: Do not use `index.ts` (or `index.tsx`) only to re-export other modules. Import directly from the source file (e.g. `from '@/shared/components/button'` not `from '@/shared/components'`). An `index.ts` is only allowed when it contains real logic or composition, not mere re-exports.
|
|
852
|
-
12. **Page-specific components use page prefix**: In features, the page file is `[page-name].page.tsx`; components specific to that page use the same prefix in file and function name (e.g. `admin-question-card.tsx` → `AdminQuestionCard`). Child components stay in the same `components/` folder and inherit the parent prefix (e.g. `admin-question-card-item.tsx` → `AdminQuestionCardItem`). App route exports `AdminQuestion`, feature page exports `AdminQuestionPage`.
|
|
853
|
-
13. **No raw base UI in screens**: Buttons, inputs, selects, and other base elements must not be used in isolation in the code. Create and reuse a component (in `shared/components/` or in the feature’s `components/`) for each usage; no inline `<button>` or `<input>` without a dedicated component.
|
|
96
|
+
| Need | Use |
|
|
97
|
+
|------|-----|
|
|
98
|
+
| Implementing code | `@rbin-coding-standards` |
|
|
99
|
+
| Full examples / Nest / gateways | Sections of `coding-standards-full.md` |
|
|
100
|
+
| Audit all categories | `@task-flow-audit` |
|