Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Backend Architecture
This guide is a short backend architecture reference for CRUX backend apps.
The current backend is based on AdonisJS v6. Use the official AdonisJS folder structure as the default baseline:
https://v6-docs.adonisjs.com/guides/getting-started/folder-structure
## Main Idea
Keep the backend close to the standard AdonisJS structure. Add project-specific folders only when they make ownership clearer.
## Default Folders
| Folder | Purpose |
| ----------- | -------------------------------------------------------------------------------------------- |
| `app/` | Application code: controllers, services, middleware, policies, validators, and domain logic. |
| `start/` | Boot lifecycle files such as routes, kernel setup, env loading, and event registration. |
| `config/` | Runtime configuration for AdonisJS and installed packages. |
| `database/` | Migrations and seeders. |
| `bin/` | Entrypoints for server, console, and tests. |
| `public/` | Static files and generated public assets. |
| `tests/` | Backend tests. |
## Basic Flow
```txt
start/routes.ts
-> controller
-> validator / policy
-> service
-> model / database / external service
```
Controllers should handle HTTP concerns. Services should hold business logic. Validators should own request validation. Policies and abilities should own authorization.
## Module Folders
If a backend app has reusable or generated modules, keep each module's files together when that improves maintainability.
Example:
```txt
app/base_modules/ModuleName/
controller/
services/
validator/
policy/
routes.ts
```
This is an app convention, not an AdonisJS requirement. Prefer the default AdonisJS structure unless a module boundary is useful.
## Rules
- Follow AdonisJS conventions first.
- Keep HTTP logic out of services.
- Keep business logic out of controllers.
- Validate request input before calling services.
- Use policies or abilities for protected operations.
- Document module-specific architecture in module docs, not in this general guide.
@@ -0,0 +1,392 @@
# CRUX React Frontend Architecture
This guide describes the recommended React frontend architecture for apps in the CRUX monorepo.
Its main goal is to make frontend development easier, more structured, and consistent across teams, so that every developer can write and understand each other's code quickly.
## Table of Contents
- [Base Concepts](#base-concepts)
- [Disclaimer](#disclaimer)
- [Core Principles](#core-principles)
- [Recommended Layering](#recommended-layering)
- [Architecture Scope](#architecture-scope)
- [Layer Dependency Rules](#layer-dependency-rules)
- [Entities Layer](#1-entities-layer)
- [Data Layer (Data Access Layer)](#2-data-layer-data-access-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities-2)
- [Rules and Guidelines](#rules-and-guidelines)
- [Infrastructure Layer](#infrastructure-layer)
- [Reference Data Layer](#reference-data-layer)
- [Providers Layer](#3-providers-layer)
- [Pages Layer](#4-pages-layer)
- [View Layer](#5-view-layer)
- [view/components/](#viewcomponents)
- [view/forms/](#viewforms)
- [view/widgets/](#viewwidgets)
- [Shared Layer](#6-shared-layer)
- [Responsibilities](#responsibilities-4)
- [Rules](#rules-2)
- [Full documentation](#full-documentation)
## Base Concepts
- **Purpose:** Provide a shared frontend foundation with predefined packages, utilities, and patterns for React apps and frontend modules in this monorepo.
- **Goals:**
- Reduce boilerplate code.
- Standardize folder structure, file naming, and coding practices in the company context.
- Assist in onboarding of new developers.
- **Included:**
- Preconfigured `React` + `TypeScript` setup.
- Common utilities and helper functions.
- Shared UI components, hooks, and state management helpers.
- Integrated i18n, form validation, and API utilities.
## Disclaimer
This architecture guide is intended as a **recommendation**, not a strict rulebook. **Feel free to adapt and modify it based on your team’s needs and project context**.
Key points to keep in mind:
1. **Maintainability comes first**.
The main goal of this structure is to help someone new to the project quickly understand where to make changes.
It’s about finding the right place to touch the code now, rather than strictly following a pattern for its own sake.
**Ultimately, the main goal is to make development easier for everyone**
2. **Naming debates are inevitable**.
Any architecture guide may spark discussions about file, folder, or function naming. Use this guide as a reference, but remember that practicality and clarity are more important than strict adherence.
3. **Flexibility is allowed**.
- You can adjust layers, module structure, or naming conventions if it improves readability or reduces complexity.
- New patterns or exceptions can be added as the project evolves.
In short: this guide exists to make the project easier to understand and maintain, not to create unnecessary friction. Use judgment and prioritize clarity over rigid consistency.
---
Цей посібник з архітектури є **рекомендацією**, а не суворим зводом правил. **Не соромтеся адаптувати та змінювати його відповідно до потреб вашої команди та контексту проєкту**.
Ключові моменти, які варто мати на увазі:
1. **Насамперед — зручність підтримки**.
Головна мета цієї структури — допомогти новому учаснику проєкту швидко зрозуміти, де вносити зміни.
Йдеться про те, щоб знайти правильне місце в коді просто зараз, а не сліпо дотримуватися патерну заради самого патерну.
**Зрештою, головна мета — зробити розробку простішою для всіх.**
2. **Дискусії щодо назв неминучі**.
Будь-який посібник з архітектури може спровокувати суперечки щодо назв файлів, папок або функцій. Використовуйте цей посібник як орієнтир, але пам'ятайте, що практичність і зрозумілість важливіші за суворе дотримання правил.
3. **Гнучкість допускається**.
- Ви можете коригувати шари, структуру модулів або угоди щодо найменування, якщо це покращує читабельність або зменшує складність.
- У міру розвитку проєкту можна додавати нові патерни або винятки.
Коротко: цей посібник існує для того, щоб проєкт був зрозумілішим і простішим у підтримці, а не для того, щоб створювати зайві труднощі. Керуйтеся здоровим глуздом і надавайте перевагу ясності, а не жорсткій послідовності.
## Core Principles
1. Blocks are composable and isolated
- Each block (component, form, widget) has a single responsibility.
- Higher-level blocks (widgets, pages) compose lower-level blocks.
2. Data vs. UI separation
- `data` handles API, state, and business data.
- `view` handles UI and interaction.
- No API logic in UI components — UI calls hooks from `data`.
3. Entities are global types/interfaces
- Placing them in `entities`.
- Any block (data hook, form, widget, page) can use these.
4. Shared
- shared/ = globally reusable components, hooks, utilities.
5. Infrastructure
- infrastructure/ = app-wide technical adapters such as HTTP clients, React Query, storage adapters, and formatting service wrappers.
6. Reference data
- refdata/ = app-wide API-backed lookup datasets shared by multiple modules.
## Recommended Layering
```plaintext
src/
├── data/ # API, react-query hooks, DTOs
├── entities/ # global types and interfaces
├── infrastructure/ # technical adapters: HTTP, query, storage, error handling
├── providers/ # global providers (router, query, session, i18n, theme)
├── refdata/ # API-backed common reference datasets
├── shared/ # reusable global components, hooks, libs, assets, styles
├── view/ # UI layer (blocks)
│ ├── components/ # Feature-level reusable components (ProductCard, LoginButton, VehicleSearchInput)
│ ├── forms/ # smart form components (ProductForm, UserForm)
│ └── widgets/ # composed smart, multi-entity components (DashboardStatsWidget, UserProfileWidget)
└── pages/ # route-level pages
```
## Architecture Scope
The layer structure can be used at three levels:
| Scope | Example | Meaning |
| --------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App-level architecture | `apps/admin/src/app`, `apps/admin/src/shared` | Global app shell, providers, routing, layout, infrastructure, reusable app-wide building blocks. |
| Core module architecture | `apps/admin/src/core/FileManager` | Built-in product modules shipped with the admin template. Each large core module can own its own `data`, `entities`, `providers`, `pages`, `view`, and `shared`. |
| Project module architecture | `apps/admin/src/modules/*` | Generated or project-specific modules. Each module should follow the same layer rules inside its own folder when it becomes more than a simple page. |
For small modules, do not create empty layers only to match the template. Add a layer when the module has real ownership for that layer.
### Admin App Interpretation
`apps/admin` is a reusable admin panel template, not a simple single-purpose React app.
The app shell owns cross-cutting concerns:
- routing and route registration
- global providers
- layouts
- i18n setup
- shared UI
- technical infrastructure
- module registration
Admin app infrastructure that is shared by every module lives in `apps/admin/src/infrastructure`.
Examples include HTTP clients, React Query helpers, storage adapters, and error normalization.
Admin-wide API-backed reference datasets live in `apps/admin/src/refdata`.
Examples include country lists, common option datasets, and other reusable data that comes from the backend but is not owned by one feature module.
Each feature module owns its feature concerns:
- API calls and query hooks in module `data`
- module domain types in module `entities`
- module-local contexts and stores in module `providers`
- route-level screens in module `pages`
- module UI in module `view`
- module-only helpers in module `shared`
If a helper or component becomes useful across multiple modules, move it upward to `apps/admin/src/shared` or a package under `packages/`.
## Layer Dependency Rules
Each layer has defined set of layers it is allowed to import from.
Imports must only flow **downward** — a lower layer never knows about a higher one.
Violating this creates circular dependencies and couples layers that should be independent.
```mermaid
graph TD
pages["pages/"]
view["view/"]
data["data/"]
providers["providers/"]
infrastructure["infrastructure/"]
refdata["refdata/"]
entities["entities/"]
shared["shared/"]
pages --> view
pages --> data
pages --> refdata
pages --> providers
pages --> infrastructure
pages --> entities
pages --> shared
view --> data
view --> refdata
view --> infrastructure
view --> entities
view --> shared
refdata --> infrastructure
refdata --> shared
providers --> data
providers --> refdata
providers --> infrastructure
providers --> shared
data --> infrastructure
data --> entities
data --> shared
infrastructure --> shared
```
| Layer | Can import from |
| ----------------- | --------------------------------------------------------------------------- |
| `shared/` | nothing — zero app-layer dependencies |
| `entities/` | nothing — zero app-layer dependencies |
| `infrastructure/` | `shared/`, external packages, app runtime config |
| `refdata/` | `infrastructure/`, `shared/` |
| `data/` | `infrastructure/`, `entities/`, `shared/` |
| `providers/` | `infrastructure/`, `data/`, `refdata/`, `shared/` |
| `view/` | `data/`, `refdata/`, `entities/`, `shared/`, generic infrastructure helpers |
| `pages/` | all layers |
### Rules
1. **`shared/` has no imports** — it is infrastructure and UI primitives only. It must not reference any domain type or application layer. If a utility needs a domain type, it does not belong in `shared/`.
2. **`entities/` has no imports** — No logic, no React, no external libraries.
3. **`data/` never imports from `view/` or `pages/`** — data hooks and API functions are completely independent of the UI.
4. **`view/` never imports from `pages/`** — components, forms, and widgets do not depend on page-level composition.
5. **The boundary between `shared/ui/` and `view/components/` is enforced by this rule** — if a component needs to import from `entities/`, it cannot live in `shared/`.
6. **`infrastructure/` contains technical adapters, not feature logic** — HTTP clients, React Query, storage adapters, and formatting service wrappers live here.
7. **`refdata/` contains app-wide lookup data** — country lists, languages, currencies, and other shared backend datasets live here instead of inside one feature module.
### 1. **Entities** Layer
The `entities` layer contains global types and interfaces that define your domain models.
- Define entity types for your business objects: User, Product, Message
- Define enums or constants
- No Framework
- No API or hook logic
### 2. **Data** layer (Data Access Layer)
#### Purpose
The `data` layer is responsible for all interactions with external or internal data sources and state management hooks.
It is the only layer that knows how to fetch, mutate, and map data, while higher layers (`forms`, `widgets`, `pages`) just consume the hooks or data from this layer.
#### Responsibilities
- Handle API calls
- Provide React Query hooks or other state management hooks for data fetching/mutations
- Define DTOs (Data Transfer Objects)
- Provide DTO mappers (map raw API data to app entities or view models)
- Define types/interfaces specific to API responses
- Isolate side-effects like HTTP requests, error handling, or caching
#### Rules and Guidelines
1. Only data and state logic belongs here
- No UI, forms, or layout logic
2. Use React Query (or other state library) for fetching/mutations
- Expose hooks, not raw API functions, to the view layer
3. DTOs + Mappers
- Map API response to internal entities/ types
- Keep external API structure isolated from your app
- Mappers should be covered by unit tests because they define the data-layer contract between API DTOs and app entities
4. Types/Interfaces
- Define API-specific types
- Use entities/ for app-wide types
5. No direct imports from view/ or pages/
- data/ is completely independent
### Infrastructure Layer
The `infrastructure` layer contains app-wide technical adapters.
In the admin app, it lives in `apps/admin/src/infrastructure/`.
Examples:
- configured HTTP clients and interceptors
- React Query helpers, response types, and error normalization
- storage or browser adapters
- app-wide formatting service wrappers such as date and name formatters
This layer should stay feature-agnostic. Feature API modules should use infrastructure clients instead of creating their own clients.
### Reference Data Layer
The `refdata` layer contains app-wide reference datasets.
In the admin app, it lives in `apps/admin/src/refdata/`.
Examples:
- countries
- languages
- currencies
- time zones
- common option dictionaries
Use this layer for backend-backed lookup data shared by multiple modules. If the data belongs to one feature module, keep it in that module's `data/` layer.
### 3. **Providers** layer
The `providers` layer is responsible for global context, app-wide state, and services that need to be available throughout the React application.
It sets up everything that any component or page might need: routing, state management, internationalization, theme, notifications, etc.
### 4. **Pages** layer
- `pages` = the highest-level blocks
- Compose `widgets` + `forms` + `components` + `data` into full pages
- Only page-level layout, routing, and orchestration logic lives here
- Example: `UserPage` → uses `UserProfileWidget` + `UserActivityWidget`
### 5. **View** layer
1. **view/components/**
- “Dumb” components
- Pure UI, no business logic
- Example: `ProductCard`, `UserAvatar`
2. **view/forms/**
- Form components
- Encapsulate form state, validation, and submit logic
- Use hooks from `data/` for mutations/queries
- Example: `CreateProductForm` → uses `useCreateProduct()` from `data/hooks`
3. **view/widgets/**
- Composed components
- Combine `components` + `forms` + `shared/ui`
- Feature blocks that can be reused on multiple pages
- Example: `UserProfileWidget` → contains `UserAvatar`, `UserForm`, `UserStatsTable`
### 6. **Shared** layer
The `shared` layer contains global reusable building blocks for the frontend:
- Components, hooks, utilities, assets, styles
- Not tied to a specific feature or entity
- Can be used by `view`, `pages`, `widgets`, or `forms`
This is where common "Lego" blocks live.
#### Responsibilities
- Provide reusable UI components (`Button`, `Input`, `Modal`, `Card`)
- Provide reusable hooks (`useToggle`, `useDebounce`, `useLocalStorage`)
- Provide utility functions (`formatDate`, `errorHandler`, http helpers)
- Provide assets (icons, images, fonts)
- Provide global styles (CSS variables, global CSS, Tailwind overrides)
#### Rules
- Components here are dumb, reusable, feature-agnostic
- Hooks here are generic, not tied to a single entity or feature
- Assets can be imported anywhere
- Styles are global or utility-level, not component-specific
## Full documentation
- [Entities Layer](./layers/entities.md)
- [Data Layer](./layers/data.md)
- [Infrastructure Layer](./layers/infrastructure.md)
- [Reference Data Layer](./layers/refdata.md)
- [Providers Layer](./layers/providers.md)
- [View Layer](./layers/view.md)
- [Shared Layer](./layers/shared.md)
- [File & Folder Naming Convention](./naming.md)
@@ -0,0 +1,260 @@
# Data Layer
## Table of Contents
- [Data Layer](#data-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Data Layer Organization Approaches](#data-layer-organization-approaches)
- [By Module](#by-module)
- [By Entity](#by-entity)
- [Data Flow](#data-flow)
- [DTO Types](#1-dto-types)
- [API Layer](#2-api-layer)
- [Mapper](#3-mapper)
- [Mapper Testing](#mapper-testing)
- [Query Hooks](#4-query-hooks)
- [Pagination](#pagination)
## Purpose
The `data` layer is responsible for **all data access and data transformation** in the application.
It is the only layer that knows:
- how to call APIs
- how to map API responses (DTOs) to application entities
- how to handle caching, queries, and mutations
Higher layers (`forms`, `widgets`, `pages`) **do not interact with APIs directly**.
Instead, they consume **hooks provided by the data layer**.
## Responsibilities
The `data/` layer is responsible for:
- performing **API calls**
- defining **DTO types (Data Transfer Objects)**
- converting DTOs → **entities** using **mappers**
- providing **React Query hooks**
- handling **side effects** (requests, caching, errors)
- defining **API-related types**
## Data Layer Organization Approaches
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces
|
| --- pagination/
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
There are two main ways to structure the `data` layer:
- By Module — grouped by type of file (API, hooks, DTOs, mappers, types)
- By Entity — grouped by business feature/entity (user, product, message)
| Approach | Best For | Pros | Cons |
| --------- | ------------------------------- | -------------------------------------------- | ---------------------------------------------- |
| By Module | Small apps, simple data layers | Easy to find all API/hooks/DTOs in one place | Files for one feature scattered; hard to scale |
| By Entity | Medium/large apps, scalable CRM | Encapsulated, modular, easier to maintain | Harder to see all APIs/hooks globally |
### By Module
```plaintext
data/
| --- api/ # API request functions
| | --- userAPI.ts
| | --- productAPI.ts
| | --- messageAPI.ts
|
| --- hooks/ # React Query hooks or other data hooks
| | --- useUserQuery.ts
| | --- useCreateProductMutation.ts
| | --- useMessages.ts
|
| --- dto/ # API DTO types
| | --- userDTO.ts
| | --- productDTO.ts
| | --- messageDTO.ts
|
├ --- mappers/ # DTO → entity mappers
| | --- userMapper.ts
| | --- productMapper.ts
|
| --- types/ # API types/interfaces
```
### By Entity
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces (optional)
|
| --- product/
| | --- api.ts
| | --- hooks.ts
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
## Data Flow
### 1. DTO Types
```ts
export type UserDTO = {
id: number;
full_name: string;
email_address: string;
};
export type CreateUserDTO = {
full_name: string;
email_address: string;
};
```
### 2. API Layer
API functions or class methods perform HTTP requests. API always returns DTO objects.
```ts
export class UserApi {
private http: HttpClient;
async getDetailed(id: number): Promise<UserDTO> {
return this.http.get(`/users/${id}`);
}
async getList(): Promise<UserDTO[]> {
return this.http.get(`/users`);
}
async create(dto: CreateUserDTO): Promise<UserDTO> {
return this.http.post(`/users`, dto);
}
}
```
### 3. Mapper
Mappers convert `DTO` to `Entity`
```ts
import { User } from "@/entities/user";
export class UserMapper {
toEntity(dto: UserDTO): User {
return {
id: dto.id,
name: dto.full_name,
email: dto.email_address,
};
}
toEntities(dtos: UserDTO[]): User[] {
return dtos.map((dto) => this.toEntity(dto));
}
toPaginated(dto: PaginatedDataDTO<UserDTO[]>): PaginatedResult<User[]> => {
return {
meta: this.paginationMetaMapper.toMeta(dto.meta),
data: this.toEntities(dto.data),
};
};
}
```
### Mapper Testing
Mappers are part of the data layer contract and should have unit tests.
Mapper tests are required when a mapper:
- renames fields from API DTOs to app entities
- normalizes values, paths, dates, sizes, statuses, enums, or defaults
- maps nested DTOs through other mappers
- maps arrays, paginated data, or optional API fields
- protects the app from backend response shape changes
These tests should focus only on transformation behavior. Do not mock React, render components, or call real APIs in mapper tests.
Recommended test coverage:
- `toEntity` maps a representative DTO to the expected entity
- `toEntities` maps arrays consistently
- optional or missing DTO fields use the correct defaults
- nested mappers preserve the expected entity shape
- enum/status casts are covered with expected values
For apps that keep tests outside production source, use a test folder at the app root:
```plaintext
apps/admin/
├── src/
│ └── core/FileManager/data/mappers/entryMapper.ts
└── tests/
└── unit/core/FileManager/data/mappers/entryMapper.test.ts
```
### 4. Query Hooks
Hooks combine API + mapper + React Query.
```ts
export const useUserDetailed = (id: number) => {
const api = new UserApi(http);
const mapper = new UserMapper();
return useQuery({
queryKey: ["user", id],
queryFn: async () => {
const dto = await api.getDetailed(id);
return mapper.toEntity(dto);
},
});
};
```
### Pagination
Pagination is not **entity-specific**, so it should live in a shared data module.
```ts
export type PaginatedMetaDTO = {
total: number;
perPage: number;
currentPage: number;
lastPage: number;
};
export type PaginatedDTO<T> = {
data: T;
meta: PaginatedMetaDTO;
};
export class PaginationMapper {
toMeta(dto: PaginatedMetaDTO) {
return {
total: dto.total,
perPage: dto.perPage,
currentPage: dto.currentPage,
lastPage: dto.lastPage,
};
}
}
```
@@ -0,0 +1,53 @@
# Entities
## Purpose
The `entities` layer defines the **core domain models of the application**.
Entities represent **business objects** used throughout the system, such as `User`, `Product`, `Project`, or `Message`.
This layer contains **pure type definitions** that describe how data is structured inside the application.
Entities are **stable, reusable models** that can be used across any layer.
```plaintext
entities/
| --- user.ts # { id: string; name: string; email: string }
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
| --- enums.ts # { UserRole, ProductCategory }
```
or
```plaintext
entities/
| --- user/
| | --- interfaces.ts # { id: string; name: string; email: string }
| | --- types.ts # { id: string; name: string; email: string }
| | --- enums.ts # { UserRole }
| | --- rules.ts # MIN_ALLOWED_AGE = 18
| | --- index.ts # export all entity types
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
```
## Responsibilities
The entities layer is responsible for:
- defining **domain models**
- defining **entity-related enums or constants**
- providing **shared types used across the application**
```ts
export type User = {
id: number;
name: string;
email: string;
};
export enum UserRole {
ADMIN = "admin",
SIMLPE_JACK = "simple_jack",
}
```
@@ -0,0 +1,106 @@
# Infrastructure Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `infrastructure/` layer contains app-wide technical adapters and integration helpers.
Use this layer for code that connects the frontend application to technical systems or shared runtime services, not for feature-specific business logic.
## Responsibilities
The `infrastructure/` layer is responsible for:
- HTTP clients and request/response interceptors
- React Query shared helpers, response types, query key helpers, and error normalization
- storage, browser, or runtime adapters
- shared formatting/service adapters such as date or name formatting services
- app-wide technical configuration wrappers
## Recommended Structure
```plaintext
infrastructure/
├── axios/
│ ├── index.ts # configured axios client
│ ├── interceptors.ts # request/response interceptors
│ └── axios.d.ts # axios type augmentation
│
├── reactQuery/
│ ├── index.ts
│ ├── errors.ts # query error handling helpers
│ ├── helpers.ts # query key and pagination helpers
│ ├── queryParams.ts # table/query-param mapping helpers
│ └── types.ts # shared API response/query types
│
├── IntlDate.ts # date formatting service adapter
└── IntlName.ts # name formatting service adapter
```
## Rules
1. Keep infrastructure feature-agnostic.
- No module-specific business rules
- No page or component composition
- No entity mapping that belongs in a module `data/mapper`
2. API modules should use infrastructure clients instead of creating their own clients.
```ts
import { axiosClient } from "#infrastructure/axios";
```
3. React Query helpers that are shared across modules belong here.
Examples: query key builders, list response types, pagination helpers, error extraction helpers.
4. UI layers must not call raw HTTP clients directly.
View and page code should call data-layer hooks. Direct `#infrastructure/axios` usage belongs in API/data modules, not in components.
5. Generic formatting adapters may be consumed by UI when they are not tied to a feature.
Examples: `IntlDate`, `IntlName`.
6. Prefer dependency injection for app-state coupling.
If an interceptor needs auth or query-client behavior, keep that coupling narrow and documented. Avoid importing random feature modules into infrastructure.
## Examples
API modules use the configured HTTP client:
```ts
import { axiosClient } from "#infrastructure/axios";
export class UserAPI {
getDetailed(id: number) {
return axiosClient.get(`/users/${id}`).then((response) => response.data);
}
}
```
Data/query modules use shared query helpers:
```ts
import {
createKeyBuilder,
getPaginationInfo,
} from "#infrastructure/reactQuery";
export const usersCacheKey = createKeyBuilder("USERS", "LIST");
```
UI can consume generic app-wide formatters:
```ts
import { IntlDate } from "#infrastructure/IntlDate";
```
@@ -0,0 +1,96 @@
# Providers Layer
## Table of Contents
- [Providers Layer](#providers-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [App Provider Composition](#app-provider-composition)
- [Example: AppProviders](#example-appproviders)
## Purpose
The `providers` layer is responsible for **global application context and environment configuration**.
It acts as the **composition root of the application**, where all global React providers are initialized and combined.
Providers make global services available across the entire application, such as:
- routing
- data fetching clients
- theme configuration
- authentication/session context
- internationalization
- notifications
- global state management
Instead of importing these providers in many places, they are **composed once and applied at the application root**.
## Responsibilities
The `providers` layer is responsible for:
- initializing global React context providers
- configuring global services
- composing multiple providers into a single entry point
- exposing the provider tree to the application root
## Recommended Structure
```plaintext
providers/
|
| --- AppProviders.tsx # main provider composition
|
| --- QueryProvider.tsx # React Query setup
| --- RouterProvider.tsx # routing configuration
| --- ThemeProvider.tsx # theme context
| --- AuthProvider.tsx # authentication/session provider
| --- I18nProvider.tsx # internationalization provider
|
| --- index.ts # re-export providers (optional)
```
## App Provider Composition
Instead of wrapping the application with multiple providers manually, they are composed in a single root provider component.
This centralizes the provider tree and simplifies application initialization.
Example usage in the application entry point:
```tsx
import { AppProviders } from "@/providers/AppProviders";
ReactDOM.createRoot(document.getElementById("root")!).render(
<AppProviders>
<App />
</AppProviders>,
);
```
### Example: AppProviders
This component acts as the global application wrapper.
```tsx
import { ReactNode, FC, PropsWithChildren } from "react";
import { QueryProvider } from "./QueryProvider";
import { ThemeProvider } from "./ThemeProvider";
import { AuthProvider } from "./AuthProvider";
import { RouterProvider } from "./RouterProvider";
import { I18nProvider } from "./I18nProvider";
export const AppProviders: FC<PropsWithChildren> = ({ children }) => {
return (
<QueryProvider>
<I18nProvider>
<AuthProvider>
<ThemeProvider>
<RouterProvider>{children}</RouterProvider>
</ThemeProvider>
</AuthProvider>
</I18nProvider>
</QueryProvider>
);
};
```
@@ -0,0 +1,109 @@
# Reference Data Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `refdata/` layer contains app-wide reference datasets that are reused across modules.
Reference data is data that usually comes from the backend, but is not owned by one feature module.
Examples:
- countries
- languages
- currencies
- time zones
- common option dictionaries
- shared lookup datasets
## Responsibilities
The `refdata/` layer is responsible for:
- fetching common reference datasets
- exposing reusable query hooks for those datasets
- defining cache keys for reference data
- keeping app-wide lookup data out of feature modules
## Recommended Structure
For small datasets, a single file per dataset is enough:
```plaintext
refdata/
├── countries.ts
├── languages.ts
└── currencies.ts
```
For larger datasets, use a folder:
```plaintext
refdata/
└── countries/
├── api.ts
├── dto.ts
├── mapper.ts
├── queries.ts
└── keys.ts
```
## Rules
1. Use `refdata/` only for app-wide datasets.
If a dataset belongs to one feature module, keep it in that module's `data/` layer.
2. `refdata/` may use infrastructure.
Reference data hooks can use `#infrastructure/axios` and `#infrastructure/reactQuery`.
3. `refdata/` should not import from `view/`, `pages/`, or feature modules.
It should remain reusable across the app.
4. Expose hooks or small service functions.
Consumers should not need to know backend endpoints or cache key details.
5. Add mappers when backend DTOs differ from app-facing reference data.
If a reference dataset performs DTO transformation, test the mapper the same way module data mappers are tested.
## Examples
```ts
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { axiosClient } from "#infrastructure/axios";
import { createKeyBuilder } from "#infrastructure/reactQuery";
export const countriesCacheKey = createKeyBuilder(
"REF_DATA",
"COUNTRIES",
"LIST",
);
interface CountryData {
code: string;
name: string;
}
export const useCountries = () => {
return useQuery({
queryKey: countriesCacheKey.store(),
placeholderData: keepPreviousData,
queryFn: async () => {
return axiosClient
.get<Array<CountryData>>("/datasets/countries")
.then((data) => data.data);
},
});
};
```
@@ -0,0 +1,252 @@
# Shared Layer
## Table of Contents
- [Purpose](#purpose)
- [Recommended Structure](#recommended-structure)
- [UI Components](#ui-components)
- [Purpose](#ui-components-purpose)
- [Core Principles](#ui-components-core-principles)
1. [Simple Components](#ui-components-core-principles-1)
2. [Composable Design](#ui-components-core-principles-2)
3. [Easily Customizable](#ui-components-core-principles-3)
4. [No Business Logic](#ui-components-core-principles-4)
- [Styling Strategy](#ui-components-styling-strategy)
- [Styling Philosophy](#ui-components-styling-strategy-philosophy)
- [Technology Stack](#ui-components-styling-strategy-tech-stack)
- [Hooks](#hooks)
- [Utilities](#utilities)
- [Libraries](#libraries)
- [Assets](#assets)
- [Styles](#styles)
## Purpose
The `shared` layer contains **global reusable building blocks used across the entire application**.
The goal is to keep these utilities **framework-level and reusable across the entire application**.
```plaintext
shared/
├── ui/ # dumb or basic UI components
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── widgets/ # reusable composed UI blocks
│ ├── DataGrid/
│ └── screens/
│
├── forms/ # reusable form composition helpers
│ └── composed/
│
├── editors/ # reusable editor integrations
│ ├── tinymce/
│ └── image_input/
│
├── hooks/ # reusable hooks
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/ # pure helpers & utilities
│ └── formatDate.ts
│
├── assets/ # images, icons, fonts
│ ├── icons/
│ └── fonts/
│
└── styles/ # global CSS, variables, Tailwind overrides
├── globals.css
└── variables.css
```
## Recommended Structure
```plaintext
shared/
|
| --- ui/ # primitive UI components
|
| --- widgets/ # reusable composed UI blocks
|
| --- forms/ # reusable form composition
|
| --- editors/ # reusable editor wrappers
|
| --- hooks/ # generic reusable hooks
|
| --- utils/ # pure helper functions
|
| --- lib/ # pure helpers and utility services
|
| --- assets/ # images, icons, fonts
|
| --- styles/ # global styling
|
| --- constants/ # global constants
```
### UI Components
#### Purpose {#ui-components-purpose}
The goal of the UI layer is to provide simple, composable, and customizable components that can be reused across the application.
- components are simple building blocks
- components are composable
- components are fully customizable
- components are not tied to business logic
UI components should behave like Lego blocks that can be combined into larger UI structures.
#### Core Principles {#ui-components-core-principles}
##### 1. Simple Components {#ui-components-core-principles-1}
Examples:
- Button
- Input
- Card
- Modal
- Dropdown
##### 2. Composable Design {#ui-components-core-principles-2}
Components must support composition.
Composition allows developers to build complex UI without modifying base components.
Example:
```tsx
<Card>
<CardHeader />
<CardContent />
<CardFooter />
</Card>
```
```tsx
<Button>
<Icon />
Save
</Button>
```
##### 3. Easily Customizable {#ui-components-core-principles-3}
UI components should allow customization through:
- className
- variants
- props
- composition
```tsx
<Button variant="outline" size="lg">
Save
</Button>
```
##### 4. No Business Logic {#ui-components-core-principles-4}
UI components must remain presentation-only. May include local state.
They should NOT:
- call API
- use React Query
- contain form submission logic
- contain domain-specific logic
#### Styling Strategy {#ui-components-styling-strategy}
##### Styling Philosophy {#ui-components-styling-strategy-philosophy}
Styling follows these principles:
- Utility-first styling
- Design tokens for consistency
- Component-level styling
- Variants instead of style duplication
- Minimal custom CSS
##### Technology Stack {#ui-components-styling-strategy-tech-stack}
- **TailwindCSS** - primary styling system
- **class-variance-authority (CVA)** - component variants
- **CSS variables** - design tokens
- **Radix primitives** - accessible UI building blocks
Custom CSS should be minimal and centralized.
### Hooks
The `shared/hooks/` folder contains generic reusable hooks.
These hooks must not depend on entities or business logic.
```plaintext
shared/hooks/
| --- useDebounce.ts
| --- useToggle.ts
| --- usePrevious.ts
| --- useLocalStorage.ts
```
### Utilities
The `shared/utils/` folder contains pure helper functions.
These functions should be stateless and reusable
```plaintext
shared/utils/
| --- formatDate.ts
| --- formatCurrency.ts
| --- generateId.ts
| --- sleep.ts
```
### Libraries
The `shared/lib/` folder contains pure helpers and utility services.
Examples include:
- date formatting helpers
- name formatting helpers
- URL/file path helpers
- phone-number formatting helpers
```plaintext
shared/lib/
| --- time/
| --- phone_number/
| --- resolveFileURL.ts
```
### Assets
The `shared/assets/` folder stores static resources used across the application.
```plaintext
shared/assets/
| --- icons/
| --- fonts/
```
### Styles
The `shared/styles/` folder contains global styling resources.
Typical usage:
- CSS variables
- Tailwind configuration extensions
- global resets
```plaintext
shared/styles/
| --- globals.css
| --- variables.css
| --- tailwind.css
```
@@ -0,0 +1,345 @@
# Views Layer
## Table of Contents
- [Views Layer](#views-layer)
- [Components](#components)
- [Components Purpose](#components-purpose)
- [Components Responsibilities](#components-responsibilities)
- [What Components should NOT do](#what-components-should-not-do)
- [Widgets](#widgets)
- [Widget Purpose](#widget-purpose)
- [Widget Characteristics](#widget-characteristics)
- [Widget Responsibilities](#widget-responsibilities)
- [Forms](#forms)
- [Form Architecture](#form-architecture)
- [Form Logic](#form-logic)
- [Form Validation](#form-validation)
- [Form Values](#form-values)
- [Form Types](#form-types)
- [Submit Functions](#submit-functions)
- [Purpose](#submit-functions-purpose)
- [API Example](#api-example)
- [Responsibilities](#responsibilities)
- [Composed Form Elements](#composed-form-elements)
- [Example: InputSwitch](#example-inputswitch)
- [Usage in a Form](#usage-in-a-form)
## Components
### Components Purpose
Components are reusable UI blocks used to build user interfaces.
They encapsulate presentation and simple UI behavior, but do not contain business logic or data fetching. `UserAvatar`, `ProductBadge`
Components are designed to be small, composable, and reusable across multiple parts of the application.
### Components Responsibilities:
- render UI
- accept props
- handle simple UI state (hover, toggle, open/close)
- compose other components
### What **Components** should NOT do
Components should not contain logic such as:
- API calls
- mutations
- complex state management
- business workflows
## Widgets
### Widget Purpose
Widgets are **self-contained UI blocks that implement a specific piece of functionality**.
Unlike simple components, widgets combine **UI, logic, and interactions** to deliver a complete user-facing feature.
Widgets are typically used to **compose pages** and represent meaningful parts of the interface such as panels, sections, or complex interactive blocks. Widgets should remain modular building blocks.
### Widget Characteristics
A widget usually:
- encapsulates **UI and feature logic**
- composes **multiple components**
- may include **forms, dialogs, or actions**
- can interact with **data**
- exposes a **simple API for pages**
Widgets should hide their internal implementation and provide a **clean interface** to the outside.
### Widget Responsibilities
Widgets may:
- orchestrate UI interactions
- manage local UI state
- connect to features or data hooks
- combine multiple components/forms into a functional block
- may fetch data or rely on data passed in props
- A **widget** can use another **widget** if:
- the nested widget represents a clear functional sub-block
- the parent widget is grouping or orchestrating UI
- the dependency does not create tight coupling
```ts
export const ProjectOverviewWidget = ({ projectId }: Props) => {
return (
<div>
<ProjectStatsWidget projectId={projectId} />
<ProjectActivityWidget projectId={projectId} />
<ProjectMembersWidget projectId={projectId} />
</div>
);
};
```
## Forms
### Form Architecture
```plaintext
view/forms/SomeForm/
| --- form.ts
| --- Component.tsx
| --- submit.ts
| --- constants.ts # Optional
| --- schema.ts # Optional validation schema. Good for large forms or multistep forms
| --- index.ts
```
| File | Responsibility |
| --------------- | ---------------------------------- |
| `form.ts` | validation schema, types, defaults |
| `Component.tsx` | UI + react-hook-form integration |
| `submit.ts` | Submission logic |
| `constants.ts` | form IDs, UI constants |
| `index.ts` | public exports |
### Form Logic
`form.ts` contains pure form logic.
It should include:
- validation schema
- form types
- default values
- helper templates
#### Form Validation
```ts
export const validationSchema = z.object({
first_name: z.string().min(3),
last_name: z.string().min(3),
});
```
If you use `i18n` you can create function that returns validation schema.
```ts
function createValidationSchema(
translationFn: TFunction<"translation", undefined>, // Depend on libraty you use
) {
return z.object({
first_name: z.string({ message: translationFn("required") }),
last_name: z.string({ message: translationFn("required") }),
});
}
```
> [! WARNING]
> Dont include `i18n` singleton last_name: z.string().min(1, i18n.t('required'))
> It will not translate errors when language state change
#### Form Values
Default values should be defined using factory functions.
Avoid inline default objects.
```ts
export function createFormValues(): FormValues {
return {
first_name: "",
last_name: "",
};
}
```
```ts
export function toFormValues(data: SomeData): FormValues {
return {
first_name: data?.first_name ?? "",
last_name: data?.last_name ?? "",
};
}
```
#### Form Types
Define types for form values and submit functiuons
```ts
export type FormValues = z.infer<typeof validationSchema>;
export type SubmitFunc = (
values: FormValues,
handlers: UseFormReturn<FormValues>,
) => void;
```
#### Submit Functions
##### Purpose {#submit-functions-purpose}
The `useSubmit` hook standardizes form submission logic:
- Handles create and update operations
- Integrates with `data/` layer hooks (`useCreate`, `useUpdate`)
- Automatically handles validation errors from the API
- Invalidates lists or detailed queries to refresh UI
- Exposes a unified API for forms
This pattern allows keep submission logic centralized.
##### API Example
```ts
interface Params {
onSuccess?: (id?: number) => void;
}
export const useSubmit = (params?: Params) => {
const onSuccess = params?.onSuccess;
const { mutate: createMutation, isPending: isCreatePending } = useCreate();
const { mutate: updateMutation, isPending: isUpdateending } = useUpdate();
const handleSubmit: SubmitFunc = (values, handlers) => {
if (values.id) {
handleUpdate(values, handlers);
} else {
handleCreate(values, handlers);
}
};
const handleCreate: SubmitFunc = (values, handlers) => {
createMutation(values, {
onSuccess() {
invalidateList();
if (typeof onSuccess === "function") onSuccess();
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
});
};
const handleUpdate: SubmitFunc = (values, handlers) => {
if (!values.id) return;
updateMutation(
{ ...values, id: values.id },
{
onSuccess() {
invalidateList();
if (values.id) invalidateDetailed(values.id);
if (typeof onSuccess === "function") {
onSuccess(values.id ?? undefined);
}
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
},
);
};
return {
submit: handleSubmit,
isSubmitting: isCreatePending || isUpdateending,
};
};
```
##### Responsibilities
1. Determine operation type
- If values.id exists -> update
- Otherwise -> create
2. Call the appropriate mutation
- `createMutation` or `updateMutation` from `data` layer
- Hooks handle API calls internally
3. Handle success
- Invalidate queries (list/detail)
- Trigger optional `onSuccess` callback
4. Handle errors
- Extract validation errors from API
- Set errors on form fields using `handlers.setError`
5. Expose state
- `submit` -> function to call from form
- `isSubmitting` -> combines pending state from both mutations
#### Composed Form Elements
To avoid repetitive boilerplate code when building forms, we encourages creating composed form elements that integrate React Hook Form with shared UI components.
- Combines label, input, validation message, and layout in one reusable component.
- Can encapsulate any input type: text, switch, select, date, textarea, etc.
- Works seamlessly with FormProvider for context propagation.
##### Example: InputSwitch
InputSwitch wraps a Switch component into a reusable form field:
```tsx
<InputSwitch
name="notificationsEnabled"
label="Enable notifications"
description="Toggle notifications on/off"
required
/>
```
Key points:
- Uses `useFormContext` to access form control.
- Wraps `FormField`, `FormItem`, `FormLabel`, `FormMessage`, and `FormControl`.
- Handles required and disabled states.
- This allows you to reuse the same input type across forms without writing repetitive boilerplate.
##### Usage in a Form
```tsx
<FormProvider {...form}>
<Component.FormElement
id={MAIN_FORM_ID}
onSubmit={form.handleSubmit((values) => onSubmit(values, form))}
>
<Component.FormGrid columns={2}>
<Component.GridElement colSpan={1}>
<Component.InputSwitch
name="notificationsEnabled"
required
label="Enable notifications"
/>
</Component.GridElement>
</Component.FormGrid>
</Component.FormElement>
</FormProvider>
```
@@ -0,0 +1,249 @@
# File & Folder Naming
## Table of Contents
- [Naming Convention](#naming-convention)
- [Folders](#folders)
- [Component Files](#component-files)
- [Hook Files](#hook-files)
- [Utility & Helper Files](#utility--helper-files)
- [Other TypeScript Files](#other-typescript-files)
- [Examples by Layer](#examples-by-layer)
- [Case-Only Rename Problem (macOS + Git)](#case-only-rename-problem-macos--git)
- [Required: Correct Rename Procedure](#required-correct-rename-procedure)
- [Making Git Case-Sensitive on macOS](#making-git-case-sensitive-on-macos)
- [Important Reality Check](#important-reality-check)
## Naming Convention
We follow **standard React community naming conventions**:
These conventions apply to frontend apps and to module folders inside those apps. For example, `apps/admin/src/core/FileManager` may use PascalCase because it is a product module folder, while its layer folders such as `data`, `entities`, `providers`, `pages`, `view`, and `shared` stay lowercase.
| Type | Convention | Example |
| ---------------------- | -------------------------------------------- | ------------------------------------- |
| Folders | lowercase (PascalCase for component folders) | `user/`, `shared/`, `CreateUserForm/` |
| React component files | PascalCase | `UserProfile.tsx`, `Button.tsx` |
| Hook files | camelCase (`use` prefix) | `useDebounce.ts`, `useToggle.ts` |
| Utility / helper files | camelCase | `formatDate.ts`, `generateId.ts` |
| API / service files | camelCase (acronyms uppercase) | `userAPI.ts`, `productAPI.ts` |
| DTO / mapper files | camelCase (acronyms uppercase) | `userDTO.ts`, `userMapper.ts` |
| Type / interface files | camelCase | `types.ts`, `dto.ts`, `interfaces.ts` |
| Constants files | camelCase | `constants.ts`, `formConstants.ts` |
| Config / entry files | camelCase | `index.ts`, `queryClient.ts` |
### Folders
Layer and infrastructure folders use **lowercase**.
Component, form, and widget folders use **PascalCase** — matching the component they contain.
```
src/
data/ # lowercase — layer folder
user/ # lowercase — entity folder
product/
view/ # lowercase — layer folder
components/
forms/
CreateUserForm/ # PascalCase — component folder
widgets/
UserProfileWidget/
shared/
ui/
hooks/
lib/
```
### Component Files
React component files use **PascalCase** — matching the name of the exported component.
```
UserProfile.tsx
ProductCard.tsx
LoginButton.tsx
UserProfileWidget.tsx
CreateProductForm.tsx
```
### Hook Files
Hook files use **camelCase** and always start with `use`.
```
useDebounce.ts
useToggle.ts
useLocalStorage.ts
useUserQuery.ts
useCreateProductMutation.ts
```
### Utility & Helper Files
Utility and helper files use **camelCase**.
```
formatDate.ts
formatCurrency.ts
generateId.ts
sleep.ts
```
### Other TypeScript Files
All other `.ts` files (APIs, DTOs, mappers, types, config) use **camelCase**, with acronyms kept uppercase.
```
userAPI.ts
userDTO.ts
userMapper.ts
types.ts
constants.ts
queryClient.ts
axiosInstance.ts
```
### Examples by Layer
#### `shared/`
```plaintext
shared/
├── ui/
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── hooks/
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/
│ ├── axios/
│ │ └── axiosInstance.ts
│ └── query/
│ └── queryClient.ts
│
└── utils/
├── formatDate.ts
└── formatCurrency.ts
```
#### `data/`
```plaintext
data/
├── user/
│ ├── userAPI.ts
│ ├── hooks.ts # or useUserQuery.ts, useCreateUserMutation.ts
│ ├── userDTO.ts
│ └── userMapper.ts
│
└── product/
├── productAPI.ts
├── hooks.ts
├── productDTO.ts
└── productMapper.ts
```
#### `providers/`
```plaintext
providers/
├── AppProviders.tsx
├── QueryProvider.tsx
├── RouterProvider.tsx
├── ThemeProvider.tsx
├── AuthProvider.tsx
├── I18nProvider.tsx
└── index.ts
```
#### `view/`
```plaintext
view/
├── components/
│ ├── UserAvatar.tsx
│ └── ProductCard.tsx
│
├── widgets/
│ └── UserProfileWidget/
│ ├── index.ts
│ └── UserProfileWidget.tsx
│
└── forms/
└── CreateUserForm/
├── form.ts
├── Component.tsx
├── submit.ts
└── index.ts
```
## Case-Only Rename Problem (macOS + Git)
macOS file systems are **case-insensitive by default**, while Git is **case-sensitive internally**.
Renaming a file like `api.ts` → `Api.ts` may:
- Not be detected by Git
- Break CI (Linux environments are case-sensitive)
- Cause inconsistent repository state
### REQUIRED: Correct Rename Procedure
If you **must rename a file or folder by case only**, you are required to perform a two-step rename.
#### Step 1 — Rename to Temporary Name
```bash
git mv userApi.ts userApi-temp.ts
git commit -m "chore: temporary rename userApi.ts to userApi-temp.ts"
```
#### Step 2 — Rename to Final Name
```bash
git mv userApi-temp.ts UserApi.ts
git commit -m "chore: rename userApi-temp.ts to UserApi.ts (case change)"
```
### Rules
- Never rename by case only in one step
- Always use an intermediate name
- Always commit both steps
- Applies to files and folders
- Do not rely only on IDE rename
- Always verify with `git status`
### Making Git Case-Sensitive on macOS
By default, Git on macOS usually has:
`core.ignorecase=true`
This makes Git ignore case-only changes.
#### Set Case Sensitivity for This Repository
```bash
git config core.ignorecase false
```
#### Verify:
```bash
git config core.ignorecase
```
It should return: `false`
### Important Reality Check
Even if `core.ignorecase=false`:
- macOS file system is still case-insensitive
- Case-only renames can still behave unexpectedly
- The two-step rename process is still the safest approach