Skip to content

Commit a234afd

Browse files
committed
Merge branch 'master' into next
2 parents 062221d + 439f6fe commit a234afd

File tree

46 files changed

+1148
-244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1148
-244
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ cypress/downloads
2525
!.yarn/sdks
2626
!.yarn/versions
2727
.env
28+
.claude/settings.local.json

Agents.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
# React-Admin Agent Context
2+
3+
React-admin is a comprehensive frontend framework for building B2B and admin applications on top of REST/GraphQL APIs, using TypeScript, React, and Material UI. It's an open-source project maintained by Marmelab that provides a complete solution for B2B applications with data-driven interfaces.
4+
5+
## Architecture & Design Patterns
6+
7+
### Key Principles
8+
9+
- Designed for Single-Page Application (SPA) architecture
10+
- Provider-based abstraction for data fetching, auth, and i18n
11+
- "Batteries included but removable" - everything is replaceable
12+
- User Experience and Developer Experience are equally important
13+
- Backward compatibility prioritized over new features
14+
- Composition over Configuration - Use React patterns, not custom DSLs
15+
- Minimal API Surface - If it can be done in React, don't add to core
16+
- Standing on Giants' Shoulders - Use best-in-class libraries, don't reinvent the wheel
17+
18+
### Provider Pattern
19+
20+
React-admin uses adapters called "providers" for external integrations:
21+
22+
```typescript
23+
// Data Provider - abstracts API calls
24+
dataProvider.getList('posts', {
25+
pagination: { page: 1, perPage: 5 },
26+
sort: { field: 'title', order: 'ASC' },
27+
filter: { author_id: 12 }
28+
})
29+
30+
// Auth Provider - handles authentication
31+
authProvider.checkAuth()
32+
authProvider.login({ username, password })
33+
authProvider.getPermissions()
34+
35+
// i18n Provider - manages translations
36+
i18nProvider.translate('ra.action.save')
37+
```
38+
39+
### Hook-Based API
40+
41+
All functionality exposed through hooks following React patterns:
42+
43+
```typescript
44+
// Data hooks
45+
const { data, isLoading } = useGetList('posts', {
46+
pagination: { page: 1, perPage: 10 }
47+
});
48+
49+
// State management hooks
50+
const [filters, setFilters] = useFilterState();
51+
const [page, setPage] = usePaginationState();
52+
53+
// Auth hooks
54+
const { permissions } = usePermissions();
55+
const canAccess = useCanAccess({ resource: 'posts', action: 'edit' });
56+
```
57+
58+
### Headless Core
59+
60+
The `ra-core` package contains all logic without UI. UI components are in separate packages like `ra-ui-materialui`. This allows:
61+
62+
- Custom UIs using core hooks and controllers
63+
- Swapping UI libraries without changing core logic
64+
65+
### Controller-View Separation
66+
67+
- Controllers in `ra-core/src/controller/` handle business logic
68+
- Views in `ra-ui-materialui/src/` handle rendering
69+
- Controllers provide data and callbacks via hooks
70+
71+
### Context: Pull, Don’t Push
72+
73+
Communication between components can be challenging, especially in large React applications, where passing props down several levels can become cumbersome. React-admin addresses this issue using a pull model, where components expose props to their descendants via a context, and descendants can consume these props using custom hooks.
74+
75+
Whenever a react-admin component fetches data or defines a callback, it creates a context and places the data and callback in it.
76+
77+
## Codebase Organization
78+
79+
### Monorepo Structure
80+
81+
```
82+
react-admin/
83+
├── packages/ # Lerna-managed packages
84+
│ ├── ra-core/ # Core logic, hooks, controllers
85+
│ ├── ra-ui-materialui/ # Material UI components
86+
│ ├── react-admin/ # Main distribution package
87+
│ ├── ra-data-*/ # Data provider adapters
88+
│ ├── ra-i18n-*/ # i18n providers
89+
│ └── ra-language-*/ # Translation packages
90+
├── examples/ # Example applications
91+
│ ├── simple/ # E2E test app
92+
│ ├── demo/ # Full e-commerce demo
93+
│ ├── crm/ # CRM application
94+
│ └── tutorial/ # Tutorial app
95+
├── cypress/ # E2E test configuration
96+
├── docs/ # Jekyll documentation
97+
├── docs_headless/ # Astro + Starlight documentation for headless components
98+
└── scripts/ # Build scripts
99+
```
100+
101+
### Key ra-core Directories
102+
103+
- `src/auth/` - Authentication and authorization (54 files)
104+
- `src/controller/` - CRUD controllers and state management
105+
- `src/dataProvider/` - Data fetching and caching logic (70 files)
106+
- `src/form/` - Form handling (31 files)
107+
- `src/routing/` - Navigation and routing (26 files)
108+
- `src/i18n/` - Internationalization (30 files)
109+
110+
### Package Dependencies
111+
112+
- **Core**: React 18.3+, TypeScript 5.8+, lodash 4.17+, inflection 3.0+
113+
- **Routing**: React Router 6.28+
114+
- **Data**: TanStack Query 5.90+ (React Query)
115+
- **Forms**: React Hook Form 7.53+
116+
- **UI Components**: Material UI 5.16+
117+
- **Testing**: Jest 29.5+, Testing Library, Storybook, Cypress
118+
119+
## Development Practices
120+
121+
### TypeScript Requirements
122+
123+
- **Strict mode enabled** - no implicit any
124+
- **Complete type exports** - all public APIs must be typed
125+
- **Generic types** for flexibility in data providers and resources
126+
- **JSDoc comments** for better IDE support
127+
128+
```typescript
129+
// GOOD - Properly typed with generics
130+
export const useGetOne = <RecordType extends RaRecord = any>(
131+
resource: string,
132+
options?: UseGetOneOptions<RecordType>
133+
): UseGetOneResult<RecordType> => { ... }
134+
135+
// BAD - Using any without constraint
136+
export const useGetOne = (resource: any, options?: any): any => { ... }
137+
```
138+
139+
### Component Patterns
140+
141+
1. **Composition over configuration** - Use React composition patterns
142+
2. **Smart defaults** - Components should work out-of-the-box
143+
3. **Controlled and uncontrolled** modes supported
144+
4. **Props pass-through** - Spread additional props to root element
145+
146+
```jsx
147+
// Component composition example
148+
export const MyField = ({ source, ...props }) => {
149+
const record = useRecordContext();
150+
return (
151+
<TextField
152+
{...props} // Pass through all additional props
153+
value={record?.[source]}
154+
/>
155+
);
156+
};
157+
```
158+
159+
### File Organization
160+
- **Feature-based structure** within packages (not type-based)
161+
- **Co-location** - Tests (`.spec.tsx`) and stories (`.stories.tsx`) next to components
162+
- **Index exports** - Each directory has an index.ts exporting public API
163+
- **Flat structure** within features - avoid unnecessary nesting
164+
165+
### Documentation
166+
167+
Every new feature or API change must be documented. The documentation consists of Markdown files located in the `/docs/` directory and built with Jekyll, one file per component or hook.
168+
169+
All documentation files must include:
170+
171+
- A brief description of the component or hook
172+
- Usage examples
173+
- List of props or parameters (required props first, then in alphabetical order)
174+
- Detailed usage for each prop/parameter (in alphabetical order)
175+
- Recipes and advanced usage examples if applicable
176+
177+
Headless hooks and components (the ones in `ra-core`) are also documented in the `/docs_headless/` directory.
178+
179+
### Pre-commit Hooks
180+
181+
- Automatic test execution for modified files
182+
- Prettier formatting check
183+
- ESLint validation
184+
- TypeScript compilation
185+
186+
187+
### Development Workflow
188+
```bash
189+
# Initial setup
190+
make install # Install all dependencies
191+
192+
# After making changes
193+
make build # Build packages (TypeScript compilation)
194+
make test # run unit and e2e tests
195+
196+
# Before pushing changes
197+
make lint # Check code quality
198+
make prettier # Format code
199+
```
200+
201+
### Pull Request Process
202+
203+
1. **Target branch**: `next` for features, `master` for bug fixes or documentation changes
204+
2. **Required checks**:
205+
- All tests passing (`make test`)
206+
- Linting clean (`make lint`)
207+
- Prettier formatted (`make prettier`)
208+
- TypeScript compiles (`yarn typecheck`)
209+
210+
3. **Commit Messages**: Clear, descriptive messages focusing on "why"
211+
```
212+
fix: Prevent duplicate API calls in useGetList hook
213+
feat: Add support for custom row actions in Datagrid
214+
docs: Clarify dataProvider response format
215+
```
216+
217+
4. **Documentation**: Update relevant docs for API changes
218+
5. **Title**: Start with a verb (Add / Fix / Update / Remove), prefix with `[Doc]` or `[TypeScript]` if the change only concerns doc or types.
219+
220+
### Common Make Commands
221+
```bash
222+
make # Show all available commands
223+
make install # Install dependencies
224+
make build # Build all packages (CJS + ESM)
225+
make test # Run all tests
226+
make lint # Check code quality
227+
make prettier # Format code
228+
make run-simple # Run simple example
229+
make run-demo # Run demo application
230+
```
231+
232+
### Performance Considerations
233+
234+
- Use `React.memo()` for expensive components
235+
- Leverage `useMemo()` and `useCallback()` appropriately
236+
- Use `useEvent()` (an internal hook) for memoized event handlers
237+
- Implement pagination for large datasets
238+
- Use query caching via React Query
239+
240+
### Accessibility
241+
242+
- Follow WCAG guidelines
243+
- Ensure keyboard navigation works
244+
- Provide proper ARIA labels
245+
- Test with screen readers
246+
247+
### Browser Support
248+
- Modern browsers only (Chrome, Firefox, Safari, Edge)
249+
- No IE11 support
250+
- ES5 compilation target for compatibility
251+
252+
## Misc
253+
254+
- **Don't use `React.cloneElement()`** - it breaks composition
255+
- **Don't inspect children** - violates React patterns (exception: Datagrid)
256+
- **Don't add comments** when code is self-explanatory
257+
- **Don't add features** achievable in a few lines with pure React
258+
- **Don't skip tests** - they run automatically on commit
259+
- **Don't force push** to main/master branches
260+
261+
## Testing Requirements
262+
263+
All developments must include tests to ensure code quality and prevent regressions.
264+
265+
### Storybook
266+
267+
- **Location**: Stories alongside components as `*.stories.tsx`
268+
- **Coverage**: All components must have stories demonstrating usage for all props
269+
- **Mocking**: Use jest mocks sparingly, prefer integration tests
270+
- **Data**: Use mock data providers (e.g., FakeRest) for stories. Make realistic data scenarios as the stories are also used for screenshots and visual testing.
271+
272+
### Unit & Integration Testing (Jest)
273+
274+
- **Location**: Tests alongside source files as `*.spec.tsx`
275+
- **Test Cases**: Reuse the component's stories as test cases
276+
- **Assertions**: Use testing-library to render and assert on elements. Don't test implementation details or HTML attributes, use assertions based on user interactions and visible output.
277+
- **Commands**:
278+
```bash
279+
make test-unit # Run all unit and functional tests
280+
npx jest [pattern] # Run specific tests
281+
```
282+
283+
### E2E Testing (Cypress)
284+
285+
Kept minimal to critical user paths due to maintenance overhead.
286+
287+
- **Location**: `cypress/` directory
288+
- **Target**: Simple example app (`examples/simple/`)
289+
- **Coverage**: Critical user paths and interactions
290+
- **Commands**:
291+
292+
```bash
293+
make test-e2e # Run in CI mode
294+
# or for local testing with GUI:
295+
make run-simple # Start test app with vite
296+
make test-e2e-local # Run e2e tests with GUI
297+
```
298+
299+
### Static Analysis
300+
301+
```bash
302+
make lint # ESLint checks
303+
make prettier # Prettier formatting
304+
make build # TypeScript type checking
305+
```

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 5.12.3
4+
5+
* Fix optimistic query invalidation and avoid invalidating the same query twice ([#11017](https://github.com/marmelab/react-admin/pull/11017)) ([slax57](https://github.com/slax57))
6+
* Fix: Shift+Click deselection range not working properly in Datagrid ([#11012](https://github.com/marmelab/react-admin/pull/11012)) ([Jszigeti](https://github.com/Jszigeti))
7+
* [Doc] Document how to support line-breaks in notifications ([#11014](https://github.com/marmelab/react-admin/pull/11014)) ([slax57](https://github.com/slax57))
8+
* [Chore] Add Agents.md to help coding agents ([#11005](https://github.com/marmelab/react-admin/pull/11005)) ([fzaninotto](https://github.com/fzaninotto))
9+
310
## 5.12.2
411

512
* Fix middlewares might not be applied in `optimistic` and `undoable` modes when they are unregistered before the actual mutation is called ([#11007](https://github.com/marmelab/react-admin/pull/11007)) ([djhi](https://github.com/djhi))

cypress/e2e/list.cy.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,64 @@ describe('List Page', () => {
224224
.click({ shiftKey: true });
225225
cy.contains('6 items selected');
226226
});
227+
228+
it('should allow to deselect a range with shift key', () => {
229+
cy.contains('1-10 of 13'); // wait for data
230+
cy.get(ListPagePosts.elements.selectItem).eq(0).click();
231+
cy.get(ListPagePosts.elements.selectItem)
232+
.eq(4)
233+
.click({ shiftKey: true });
234+
cy.contains('5 items selected');
235+
cy.get(ListPagePosts.elements.selectedItem).should(els =>
236+
expect(els).to.have.length(5)
237+
);
238+
cy.get(ListPagePosts.elements.selectItem)
239+
.eq(2)
240+
.click({ shiftKey: true });
241+
cy.contains('2 items selected');
242+
cy.get(ListPagePosts.elements.selectedItem).should(els =>
243+
expect(els).to.have.length(2)
244+
);
245+
});
246+
247+
it('should allow alternating shift-select and shift-deselect', () => {
248+
cy.contains('1-10 of 13'); // wait for data
249+
cy.get(ListPagePosts.elements.selectItem).eq(0).click();
250+
cy.get(ListPagePosts.elements.selectItem)
251+
.eq(3)
252+
.click({ shiftKey: true });
253+
cy.contains('4 items selected');
254+
cy.get(ListPagePosts.elements.selectItem)
255+
.eq(4)
256+
.click({ shiftKey: true });
257+
cy.contains('5 items selected');
258+
cy.get(ListPagePosts.elements.selectItem)
259+
.eq(2)
260+
.click({ shiftKey: true });
261+
cy.contains('2 items selected');
262+
cy.get(ListPagePosts.elements.selectItem)
263+
.eq(4)
264+
.click({ shiftKey: true });
265+
cy.contains('5 items selected');
266+
cy.get(ListPagePosts.elements.selectedItem).should(els =>
267+
expect(els).to.have.length(5)
268+
);
269+
});
270+
271+
it('should support shift-deselect after select all then manual deselect', () => {
272+
cy.contains('1-10 of 13'); // wait for data
273+
ListPagePosts.toggleSelectAll();
274+
cy.contains('10 items selected');
275+
cy.get(ListPagePosts.elements.selectItem).eq(1).click();
276+
cy.contains('9 items selected');
277+
cy.get(ListPagePosts.elements.selectItem)
278+
.eq(3)
279+
.click({ shiftKey: true });
280+
cy.contains('7 items selected');
281+
cy.get(ListPagePosts.elements.selectedItem).should(els =>
282+
expect(els).to.have.length(7)
283+
);
284+
});
227285
});
228286

229287
describe('rowClick', () => {

0 commit comments

Comments
 (0)