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