diff --git a/.cursor/README.md b/.cursor/README.md index 4cedaba667..deabe8cf24 100644 --- a/.cursor/README.md +++ b/.cursor/README.md @@ -1,6 +1,39 @@ -# Cursor Rules +# Appsmith Cursor Configuration -This directory contains configuration for cursor-specific rules and behaviors. +This directory contains configuration for Cursor AI tools, rules, and guidelines for the Appsmith project. + +## Directory Structure + +``` +.cursor/ +├── settings.json # Main configuration file +├── docs/ # Documentation +│ ├── guides/ # In-depth guides +│ ├── references/ # Quick references +│ └── practices/ # Best practices +├── rules/ # Rule definitions +│ ├── commit/ # Commit-related rules +│ ├── quality/ # Code quality rules +│ ├── testing/ # Testing rules +│ └── verification/ # Verification rules +└── hooks/ # Git hooks and scripts +``` + +## Key Features + +- **Commit Message Rules**: Guidelines for structured, informative commit messages +- **Code Quality Checks**: Automated validation of code quality standards +- **Testing Requirements**: Rules for test coverage and quality +- **Performance Guidelines**: Best practices for maintaining high performance +- **Documentation**: Comprehensive guides and references for the codebase + +## Usage + +- Use the rules in this directory to ensure consistent quality across the project +- Reference the documentation for best practices and technical details +- Hooks automate common tasks and enforce quality standards + +For more information, see the specific README files in each subdirectory. ## Commit Message Rules diff --git a/.cursor/docs/README.md b/.cursor/docs/README.md new file mode 100644 index 0000000000..01f6958d6e --- /dev/null +++ b/.cursor/docs/README.md @@ -0,0 +1,27 @@ +# Appsmith Documentation + +This directory contains comprehensive documentation for the Appsmith project, organized by type and purpose. + +## Structure + +- **guides/**: Detailed, in-depth guides on specific topics + - `performance.md`: Performance optimization best practices + - `testing.md`: Comprehensive guide to testing in Appsmith + - `verification.md`: Workflow for verifying changes + +- **references/**: Quick reference documents for daily use + - `codebase-map.md`: Overview of the codebase structure + - `testing-reference.md`: Quick testing reference + - `technical-details.md`: Technical specifications and architecture + +- **practices/**: Best practices for development + - `react-hooks.md`: Best practices for using React hooks + +## Using This Documentation + +- **New developers**: Start with the codebase map and technical details +- **Feature development**: Reference the testing guide and feature verification workflows +- **Bug fixing**: Consult the verification workflow and bug fix verification guidelines +- **Performance optimization**: Follow the performance optimization guide + +The documentation is designed to be comprehensive but approachable. If you need more specific information, check the individual files in each directory. \ No newline at end of file diff --git a/.cursor/docs/guides/performance.md b/.cursor/docs/guides/performance.md new file mode 100644 index 0000000000..c21f0ef6e7 --- /dev/null +++ b/.cursor/docs/guides/performance.md @@ -0,0 +1,353 @@ +# Appsmith Performance Optimization Guide + +This guide outlines approaches for identifying, analyzing, and resolving performance issues in the Appsmith codebase. + +## Identifying Performance Issues + +### Frontend Performance Metrics + +1. **Page Load Time** + - Initial page load + - Time to first paint + - Time to interactive + +2. **Rendering Performance** + - Component render times + - React render cycles + - Frame rate (FPS) + +3. **Network Performance** + - API request latency + - Payload sizes + - Number of requests + +4. **Memory Usage** + - Heap snapshots + - Memory leaks + - DOM node count + +### Backend Performance Metrics + +1. **Response Times** + - API endpoint latency + - Database query performance + - Worker thread utilization + +2. **Resource Utilization** + - CPU usage + - Memory consumption + - I/O operations + +3. **Database Performance** + - Query execution time + - Index utilization + - Connection pool efficiency + +4. **Concurrency** + - Request throughput + - Thread pool utilization + - Blocking operations + +## Performance Analysis Tools + +### Frontend Tools + +1. **Browser DevTools** + - Performance tab + - Network tab + - Memory tab + +2. **React DevTools** + - Component profiler + - Highlight updates + +3. **Lighthouse** + - Performance audits + - Optimization suggestions + +4. **Custom Timing** + ```javascript + // Performance measurement + performance.mark('start'); + // ...code to measure... + performance.mark('end'); + performance.measure('operation', 'start', 'end'); + console.log(performance.getEntriesByName('operation')[0].duration); + ``` + +### Backend Tools + +1. **Profilers** + - JProfiler + - VisualVM + - YourKit + +2. **Logging and Metrics** + - Log execution times + - Prometheus metrics + - Grafana dashboards + +3. **Load Testing** + - JMeter + - K6 + - Artillery + +## Common Performance Issues and Solutions + +### Frontend Performance Issues + +1. **Unnecessary Re-renders** + + Issue: + ```jsx + function Component() { + // This creates a new object on every render + const options = { value: 'example' }; + return ; + } + ``` + + Solution: + ```jsx + function Component() { + // Memoize object + const options = useMemo(() => ({ value: 'example' }), []); + return ; + } + ``` + +2. **Unoptimized List Rendering** + + Issue: + ```jsx + function ItemList({ items }) { + return ( +
+ {items.map(item => ( + + ))} +
+ ); + } + ``` + + Solution: + ```jsx + function ItemList({ items }) { + return ( +
+ {items.map(item => ( + + ))} +
+ ); + } + + // Memoize the Item component + const Item = React.memo(function Item({ data }) { + return
{data.name}
; + }); + ``` + +3. **Large Bundle Size** + + Issue: + - Importing entire libraries + - Not code-splitting + + Solution: + ```javascript + // Before + import { map, filter, reduce } from 'lodash'; + + // After + import map from 'lodash/map'; + import filter from 'lodash/filter'; + import reduce from 'lodash/reduce'; + + // Code splitting with React.lazy + const HeavyComponent = React.lazy(() => import('./HeavyComponent')); + ``` + +4. **Memory Leaks** + + Issue: + ```jsx + function Component() { + useEffect(() => { + const interval = setInterval(() => { + // Do something + }, 1000); + // No cleanup + }, []); + + return
Component
; + } + ``` + + Solution: + ```jsx + function Component() { + useEffect(() => { + const interval = setInterval(() => { + // Do something + }, 1000); + + // Cleanup + return () => clearInterval(interval); + }, []); + + return
Component
; + } + ``` + +### Backend Performance Issues + +1. **N+1 Query Problem** + + Issue: + ```java + List workspaces = workspaceRepository.findAll().collectList().block(); + for (Workspace workspace : workspaces) { + List apps = applicationRepository.findByWorkspaceId(workspace.getId()).collectList().block(); + workspace.setApplications(apps); + } + ``` + + Solution: + ```java + // Use join query or batch loading + List workspaces = workspaceRepository.findAllWithApplications().collectList().block(); + ``` + +2. **Missing Database Indexes** + + Issue: + ```java + // Query without proper index + Mono findByEmail(String email); + ``` + + Solution: + ```java + // Add index to database + @Document(collection = "users") + public class User { + @Indexed(unique = true) + private String email; + // ... + } + ``` + +3. **Blocking Operations in Reactive Streams** + + Issue: + ```java + return Mono.fromCallable(() -> { + // Blocking file I/O operation + return Files.readAllBytes(Paths.get("path/to/file")); + }); + ``` + + Solution: + ```java + return Mono.fromCallable(() -> { + // Blocking file I/O operation + return Files.readAllBytes(Paths.get("path/to/file")); + }).subscribeOn(Schedulers.boundedElastic()); + ``` + +4. **Inefficient Data Processing** + + Issue: + ```java + // Processing large amounts of data in memory + return repository.findAll() + .collectList() + .map(items -> { + // Process all items at once + return items.stream().map(this::transform).collect(Collectors.toList()); + }); + ``` + + Solution: + ```java + // Stream processing with backpressure + return repository.findAll() + .map(this::transform) + .collectList(); + ``` + +## Performance Optimization Workflow + +### Step 1: Establish Baselines + +1. Identify key metrics to track +2. Measure current performance +3. Set performance goals + +### Step 2: Identify Bottlenecks + +1. Use profiling tools +2. Analyze critical user paths +3. Focus on high-impact areas + +### Step 3: Optimize + +1. Make one change at a time +2. Measure impact of each change +3. Document optimizations + +### Step 4: Verify + +1. Compare to baseline metrics +2. Run performance tests +3. Check for regressions + +### Step 5: Monitor + +1. Set up continuous performance monitoring +2. Track trends over time +3. Set up alerts for degradations + +## Performance Testing Best Practices + +1. **Test with realistic data volumes** +2. **Simulate actual user behavior** +3. **Test on hardware similar to production** +4. **Include performance tests in CI/CD pipeline** +5. **Test in isolation and under load** +6. **Focus on critical user journeys** +7. **Set clear performance budgets** +8. **Compare results to previous baselines** + +## Performance Optimization Checklist + +### Frontend Checklist + +- [ ] Use React.memo for expensive components +- [ ] Implement proper keys for list items +- [ ] Memoize callbacks with useCallback +- [ ] Memoize computed values with useMemo +- [ ] Code-split large bundles +- [ ] Lazy load components and routes +- [ ] Optimize images and assets +- [ ] Minimize CSS and JS bundle sizes +- [ ] Use virtualization for large lists +- [ ] Implement proper cleanup in useEffect +- [ ] Avoid prop drilling with Context API +- [ ] Optimize Redux selectors + +### Backend Checklist + +- [ ] Add appropriate database indexes +- [ ] Use pagination for large result sets +- [ ] Optimize database queries +- [ ] Avoid N+1 query problem +- [ ] Use reactive programming correctly +- [ ] Handle blocking operations properly +- [ ] Implement caching where appropriate +- [ ] Optimize serialization/deserialization +- [ ] Use connection pooling +- [ ] Configure thread pools appropriately +- [ ] Monitor and optimize GC behavior \ No newline at end of file diff --git a/.cursor/docs/guides/testing.md b/.cursor/docs/guides/testing.md new file mode 100644 index 0000000000..6bad530576 --- /dev/null +++ b/.cursor/docs/guides/testing.md @@ -0,0 +1,513 @@ +# Appsmith Testing Guide + +This guide outlines best practices for writing tests for the Appsmith codebase. + +## Frontend Testing + +### Unit Tests with Jest + +Appsmith uses Jest for frontend unit tests. Unit tests should be written for individual components, utility functions, and Redux slices. + +#### Test File Structure + +Create test files with the `.test.ts` or `.test.tsx` extension in the same directory as the source file: + +``` +src/ + components/ + Button/ + Button.tsx + Button.test.tsx + utils/ + helpers.ts + helpers.test.ts +``` + +#### Writing React Component Tests + +```typescript +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import Button from "./Button"; + +describe("Button component", () => { + it("renders correctly with default props", () => { + render(); + expect(screen.getByText("Click me")).toBeInTheDocument(); + }); + + it("calls onClick handler when clicked", () => { + const handleClick = jest.fn(); + render(); + + fireEvent.click(screen.getByText("Click me")); + expect(handleClick).toHaveBeenCalledTimes(1); + }); +}); +``` + +#### Redux Testing + +```typescript +import { configureStore } from "@reduxjs/toolkit"; +import reducer, { + setUserInfo, + fetchUserInfo +} from "./userSlice"; + +describe("User reducer", () => { + it("should handle initial state", () => { + expect(reducer(undefined, { type: "unknown" })).toEqual({ + userInfo: null, + isLoading: false, + error: null + }); + }); + + it("should handle setUserInfo", () => { + const userInfo = { name: "Test User", email: "test@example.com" }; + expect( + reducer( + { userInfo: null, isLoading: false, error: null }, + setUserInfo(userInfo) + ) + ).toEqual({ + userInfo, + isLoading: false, + error: null + }); + }); +}); +``` + +### Testing Redux/React Safety Patterns + +Safety when accessing deeply nested properties in Redux state is critical for application reliability. Here are patterns for testing these safety mechanisms: + +#### Testing Redux Selectors with Incomplete State + +```typescript +import { configureStore } from '@reduxjs/toolkit'; +import reducer, { selectNestedData } from './dataSlice'; +import { renderHook } from '@testing-library/react-hooks'; +import { Provider } from 'react-redux'; +import { useSelector } from 'react-redux'; + +describe("selectNestedData", () => { + it("returns default value when state is incomplete", () => { + // Set up store with incomplete state + const store = configureStore({ + reducer: { + data: reducer, + }, + preloadedState: { + data: { + // Missing expected nested properties + }, + }, + }); + + // Wrap the hook with the Redux provider + const wrapper = ({ children }) => ( + {children} + ); + + // Render the hook with the selector + const { result } = renderHook(() => useSelector(selectNestedData), { wrapper }); + + // Verify the selector returns the fallback/default value + expect(result.current).toEqual(/* expected default value */); + }); + + it("returns actual data when state is complete", () => { + // Set up store with complete state + const expectedData = { value: "test" }; + const store = configureStore({ + reducer: { + data: reducer, + }, + preloadedState: { + data: { + entities: { + items: { + 123: { + details: expectedData, + }, + }, + }, + }, + }, + }); + + const wrapper = ({ children }) => ( + {children} + ); + + const { result } = renderHook(() => useSelector(state => + selectNestedData(state, '123') + ), { wrapper }); + + // Verify the selector returns the actual data + expect(result.current).toEqual(expectedData); + }); +}); +``` + +#### Testing Components with Error Boundaries + +```typescript +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ErrorBoundary } from 'react-error-boundary'; +import ComponentWithDeepAccess from './ComponentWithDeepAccess'; + +describe('ComponentWithDeepAccess with error boundary', () => { + it('renders fallback UI when data is invalid', () => { + // Define invalid data that would cause property access errors + const invalidData = { + // Missing required nested structure + }; + + const FallbackComponent = () =>
Error occurred
; + + render( + + + + ); + + // Verify the fallback component is rendered + expect(screen.getByText('Error occurred')).toBeInTheDocument(); + }); + + it('renders normally with valid data', () => { + // Define valid data with complete structure + const validData = { + user: { + profile: { + name: 'Test User' + } + } + }; + + const FallbackComponent = () =>
Error occurred
; + + render( + + + + ); + + // Verify the component renders normally + expect(screen.getByText('Test User')).toBeInTheDocument(); + }); +}); +``` + +#### Testing Safe Property Access Utilities + +```typescript +import { safeGet } from './propertyAccessUtils'; + +describe('safeGet utility', () => { + it('returns the value when the path exists', () => { + const obj = { + a: { + b: { + c: 'value' + } + } + }; + + expect(safeGet(obj, 'a.b.c')).toBe('value'); + }); + + it('returns default value when path does not exist', () => { + const obj = { + a: {} + }; + + expect(safeGet(obj, 'a.b.c', 'default')).toBe('default'); + }); + + it('handles array indices in path', () => { + const obj = { + users: [ + { id: 1, name: 'User 1' }, + { id: 2, name: 'User 2' } + ] + }; + + expect(safeGet(obj, 'users.1.name')).toBe('User 2'); + }); + + it('handles null and undefined input', () => { + expect(safeGet(null, 'a.b.c', 'default')).toBe('default'); + expect(safeGet(undefined, 'a.b.c', 'default')).toBe('default'); + }); +}); +``` + +### Integration Tests with Cypress + +Cypress is used for integration and end-to-end testing. These tests should verify the functionality of the application from a user's perspective. + +#### Test File Structure + +``` +cypress/ + integration/ + Editor/ + Canvas.spec.ts + PropertyPane.spec.ts + Workspace/ + Applications.spec.ts +``` + +#### Writing Cypress Tests + +```typescript +describe("Application Canvas", () => { + before(() => { + cy.visit("/applications/my-app/pages/page-1/edit"); + }); + + it("should allow adding a widget to the canvas", () => { + cy.get("[data-cy=entity-explorer]").should("be.visible"); + cy.get("[data-cy=widget-button]").drag("[data-cy=canvas-drop-zone]"); + cy.get("[data-cy=widget-card-button]").should("exist"); + }); + + it("should open property pane when widget is selected", () => { + cy.get("[data-cy=widget-card-button]").click(); + cy.get("[data-cy=property-pane]").should("be.visible"); + cy.get("[data-cy=property-pane-title]").should("contain", "Button"); + }); +}); +``` + +## Backend Testing + +### Unit Tests with JUnit + +Backend unit tests should validate individual components and services. + +#### Test File Structure + +``` +src/test/java/com/appsmith/server/ + services/ + ApplicationServiceTest.java + UserServiceTest.java + controllers/ + ApplicationControllerTest.java +``` + +#### Writing Java Unit Tests + +```java +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationServiceTest { + + @Autowired + private ApplicationService applicationService; + + @MockBean + private WorkspaceService workspaceService; + + @Test + public void testCreateApplication() { + // Arrange + Application application = new Application(); + application.setName("Test Application"); + + Workspace workspace = new Workspace(); + workspace.setId("workspace-id"); + + Mono workspaceMono = Mono.just(workspace); + when(workspaceService.findById(any())).thenReturn(workspaceMono); + + // Act + Mono result = applicationService.createApplication(application, "workspace-id"); + + // Assert + StepVerifier.create(result) + .assertNext(app -> { + assertThat(app.getId()).isNotNull(); + assertThat(app.getName()).isEqualTo("Test Application"); + assertThat(app.getWorkspaceId()).isEqualTo("workspace-id"); + }) + .verifyComplete(); + } +} +``` + +### Integration Tests + +Backend integration tests should verify interactions between different components of the system. + +```java +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ApplicationControllerIntegrationTest { + + @Autowired + private WebTestClient webTestClient; + + @Autowired + private ApplicationRepository applicationRepository; + + @Before + public void setUp() { + applicationRepository.deleteAll().block(); + } + + @Test + public void testGetAllApplications() { + // Test implementation + } +} +``` + +## Best Practices + +### General Test Guidelines + +1. **Test Isolation**: Each test should be independent of others. +2. **Test Coverage**: Aim for 80%+ coverage for critical code paths. +3. **Avoid Implementation Details**: Test behavior, not implementation. +4. **Concise Tests**: Keep tests focused on one behavior or functionality. +5. **Descriptive Names**: Use clear test names that describe what is being tested. + +### Redux/React Safety Best Practices + +1. **Always Check Property Existence**: Test edge cases where properties might not exist. +2. **Use Defensive Programming**: Design components and selectors to handle incomplete data gracefully. +3. **Test Error Boundaries**: Verify that error boundaries correctly catch and handle errors from property access. +4. **Test Default Values**: Ensure selectors return appropriate defaults when data is missing. +5. **Test Different State Permutations**: Create tests with various combinations of missing or incomplete state to ensure robustness. + +### Performance Considerations + +1. **Mock Heavy Dependencies**: Use mocks for API calls, databases, etc. +2. **Optimize Test Speed**: Keep tests fast to encourage frequent testing. +3. **Use Focused Tests**: Test only what needs to be tested. + +## Troubleshooting Tests + +### Common Issues + +1. **Flaky Tests**: Tests that sometimes pass and sometimes fail. + - Solution: Make tests more deterministic, avoid race conditions. + +2. **Memory Leaks**: Tests that consume increasing memory. + - Solution: Clean up resources, avoid global state. + +3. **Slow Tests**: Tests that take too long to run. + - Solution: Mock heavy dependencies, parallelize when possible. + +### React-Specific Issues + +1. **Component State Issues**: Components not updating as expected. + - Solution: Use `act()` for state updates, wait for async operations. + +2. **Redux State Access Errors**: Errors when accessing nested properties. + - Solution: Use optional chaining, lodash/get, or default values in selectors. + +3. **Rendering Errors**: Components not rendering as expected. + - Solution: Verify props, check for conditionals that might prevent rendering. + +## Advanced Testing Techniques + +### Property-Based Testing + +Test with a wide range of automatically generated inputs to find edge cases. + +### Snapshot Testing + +Useful for detecting unintended changes in UI components. + +### Visual Regression Testing + +Compare screenshots of components to detect visual changes. + +### Load and Performance Testing + +Test system behavior under high load or stress conditions. + +### A/B Testing + +Compare different implementations to determine which performs better. + +## Test Data Best Practices + +### Creating Test Fixtures + +- Create reusable fixtures for common test data +- Use descriptive names for test fixtures +- Keep test data minimal but sufficient + +### Mocking External Services + +- Mock external API calls and dependencies +- Use realistic mock responses +- Consider edge cases and error conditions + +## Testing Standards + +### Frontend Testing Standards + +1. Aim for 80%+ test coverage for utility functions +2. Test all Redux slices thoroughly +3. Focus on critical user journeys in integration tests +4. Test responsive behavior for key components +5. Include accessibility tests for UI components + +### Backend Testing Standards + +1. Test all public service methods +2. Test both successful and error cases +3. Test database interactions with real repositories +4. Test API endpoints with WebTestClient +5. Mock external services to isolate tests + +## Running Tests + +### Frontend Tests + +```bash +# Run all Jest tests +cd app/client +yarn run test:unit + +# Run a specific test file +yarn jest src/path/to/test.ts + +# Run Cypress tests +npx cypress run +``` + +### Backend Tests + +```bash +# Run all backend tests +cd app/server +./mvnw test + +# Run a specific test class +./mvnw test -Dtest=ApplicationServiceTest +``` + +## Best Practices for Test-Driven Development + +1. Write failing tests first +2. Start with simple test cases +3. Refactor after tests pass +4. Use descriptive test names +5. Keep tests independent +6. Avoid test interdependence +7. Test edge cases and error conditions +8. Keep tests fast +9. Avoid testing implementation details +10. Review and update tests when requirements change \ No newline at end of file diff --git a/.cursor/docs/guides/verification.md b/.cursor/docs/guides/verification.md new file mode 100644 index 0000000000..f339a04afd --- /dev/null +++ b/.cursor/docs/guides/verification.md @@ -0,0 +1,180 @@ +# Appsmith Verification Workflow + +This document outlines the process that Cursor should follow when verifying changes to the Appsmith codebase. + +## Bug Fix Verification + +When fixing a bug, follow these steps: + +1. **Reproduce the issue** + - Understand the reported bug and its root cause + - Create a minimal reproduction of the issue + - Identify the components affected + +2. **Write test(s) for the bug** + - Create failing tests that demonstrate the bug's existence + - For frontend bugs: write Jest unit tests and/or Cypress integration tests + - For backend bugs: write JUnit tests + +3. **Implement the fix** + - Make minimal, targeted changes to address the root cause + - Ensure the tests now pass + - Check for any unintended side effects + +4. **Verify the fix** + - Confirm the original issue is resolved + - Run the full test suite to ensure no regressions + - Check both development and production builds + +5. **Quality checks** + - Run type checking: `yarn run check-types` + - Run linting: `yarn run lint` + - Check for cyclic dependencies in client code + - For backend: run Spotless checks + - Verify Redux/React safety guidelines: + - Use optional chaining (`?.`) or lodash/get for deep property access + - Check for null/undefined before accessing nested properties + - Avoid direct deep property chains (e.g., `obj.prop1.prop2.prop3`) + - Handle potential nulls in Redux state access + +6. **Performance verification** + - Ensure the fix doesn't negatively impact performance + - Check for memory leaks or increased resource usage + - Verify response times aren't degraded + +7. **CI/CD verification** + - Ensure all GitHub workflow checks would pass with the changes + - Verify both client and server builds + +## Feature Implementation Verification + +When implementing a new feature, follow these steps: + +1. **Understand requirements** + - Clearly define the feature's acceptance criteria + - Identify all components that need to be modified + +2. **Design test approach** + - Plan unit, integration, and end-to-end tests before implementation + - Create test scenarios that cover the feature's functionality + - Consider edge cases and error handling + +3. **Implement test cases** + - Write tests for new functionality + - Include positive and negative test cases + - Cover edge cases and error conditions + +4. **Implement the feature** + - Develop the feature to pass the tests + - Follow code style and patterns established in the project + - Document the new functionality as needed + +5. **Verify against acceptance criteria** + - Confirm the feature meets all acceptance criteria + - Perform manual testing for user experience + - Get stakeholder sign-off if applicable + +6. **Quality checks** + - Same checks as for bug fixes + - Additional check for documentation updates if needed + - Verify UI/UX consistency + +7. **Performance testing** + - Check performance implications of the new feature + - Ensure the feature is optimized for efficiency + - Test under different load conditions if applicable + +8. **CI/CD verification** + - Same checks as for bug fixes + - Additional check for new assets or dependencies + +## Incrementally Learning From Changes + +For each code change, Cursor should: + +1. Analyze patterns in successful implementations +2. Record common pitfalls and how they were resolved +3. Update its understanding of the codebase structure +4. Note the relationships between components +5. Learn from test cases how different modules should interact +6. Understand the project's coding standards and conventions +7. Track performance considerations for different features +8. Maintain a knowledge graph of the codebase to provide better context + +## Pre-commit Verification Checklist + +Before considering a change complete, verify: + +- [ ] All tests pass locally +- [ ] No linting issues reported +- [ ] Type checking passes +- [ ] No performance degradation +- [ ] Code follows project conventions +- [ ] Documentation is updated if needed +- [ ] No sensitive data is included +- [ ] The change satisfies the original requirements +- [ ] GitHub workflows would pass if the changes were committed +- [ ] React/Redux code follows safety best practices + - [ ] Uses optional chaining or lodash/get for nested properties + - [ ] Handles potential null values in state access + - [ ] No direct deep object chaining without safety checks + - [ ] Redux selectors properly handle state structure changes + +## React/Redux Safety Guidelines + +When working with React and Redux code, follow these guidelines: + +### Safe Property Access + +- **Avoid direct deep property access**: + ```jsx + // Unsafe + const value = state.entities.users[userId].profile.preferences; + + // Safe - using optional chaining + const value = state.entities?.users?.[userId]?.profile?.preferences; + + // Safe - using lodash/get with default value + const value = get(state, `entities.users.${userId}.profile.preferences`, defaultValue); + ``` + +### Redux State Access + +- **Use selectors for all state access**: + ```jsx + // Define selector + const getUserPreferences = (state, userId) => + get(state, ['entities', 'users', userId, 'profile', 'preferences'], {}); + + // Use selector + const preferences = useSelector(state => getUserPreferences(state, userId)); + ``` + +### Error Boundary Usage + +- **Wrap components that access complex data structures**: + ```jsx + }> + + + ``` + +### Data Validation + +- **Validate data structure before usage**: + ```jsx + const isValidUserData = (userData) => + userData && + typeof userData === 'object' && + userData.profile !== undefined; + + // Use validation before accessing + if (isValidUserData(userData)) { + // Now safe to use userData.profile + } + ``` + +Following these guidelines will help prevent common issues like: +- Runtime errors from accessing properties of undefined +- Unexpected application crashes due to null property access +- Hard-to-debug errors in deeply nested state structures \ No newline at end of file diff --git a/.cursor/docs/practices/react-hooks.md b/.cursor/docs/practices/react-hooks.md new file mode 100644 index 0000000000..c4a2a0dd64 --- /dev/null +++ b/.cursor/docs/practices/react-hooks.md @@ -0,0 +1,202 @@ +# Lessons from Fixing Circular Dependencies in React Hooks + +## Background + +While working on the Appsmith codebase, we encountered a critical issue in the `useSyncParamsToPath` React hook that caused infinite re-renders and circular dependency problems. This hook was responsible for synchronizing URL paths and query parameters bidirectionally in the API panel. When a user changed the URL, parameters would get extracted and populated in the form, and when parameters were changed, the URL would get updated. + +## The Problem + +The initial implementation of the hook had several issues: + +1. **Improper property access**: The hook was directly accessing nested properties like `values.actionConfiguration.path` without properly handling the case where these nested paths might not exist. + +2. **Missing flexible configuration**: The hook couldn't be reused with different property paths since it had hardcoded property paths. + +3. **Circular updates**: When the hook updated the path, it would trigger a re-render which would then trigger the hook again, causing an infinite loop. + +4. **Missing safeguards**: The hook didn't have proper tracking of previous values or early exits to prevent unnecessary updates. + +## The Solution + +We implemented several patterns to fix these issues: + +### 1. Safe nested property access using lodash/get + +Instead of directly accessing nested properties: + +```jsx +// Before +const path = values.actionConfiguration?.path; +const queryParameters = values.actionConfiguration?.queryParameters; +``` + +We used lodash's `get` function with default values: + +```jsx +// After +import get from 'lodash/get'; + +const path = get(values, `${configProperty}.path`, ""); +const queryParameters = get(values, `${configProperty}.params`, []); +``` + +This approach provides several benefits: +- Safely handles undefined or null intermediate values +- Provides sensible default values +- Makes the property path configurable using the `configProperty` parameter + +### 2. Tracking previous values with useRef + +We implemented a pattern to track previous values and prevent unnecessary updates: + +```jsx +// Refs to track the last values to prevent infinite loops +const lastPathRef = useRef(""); +const lastParamsRef = useRef([]); + +useEffect( + function syncParamsEffect() { + // Early return if nothing has changed + if (path === lastPathRef.current && isEqual(queryParameters, lastParamsRef.current)) { + return; + } + + // Update refs to current values + lastPathRef.current = path; + lastParamsRef.current = [...queryParameters]; + + // Rest of the effect logic + }, + [formValues, dispatch, formName, configProperty], +); +``` + +### 3. Directional updates + +To prevent circular updates, we implemented a pattern where the hook would only process one update direction per effect execution: + +```jsx +// Only one sync direction per effect execution to prevent loops +// Path changed - update params from path if needed +if (pathChanged) { + // Logic to update params from path + // Exit early after updating + return; +} + +// Params changed - update path from params if needed +if (paramsChanged) { + // Logic to update path from params +} +``` + +### 4. Deep comparisons for complex objects + +For comparing arrays of parameters, we implemented custom comparison logic that compares the actual values rather than just checking references: + +```jsx +// Helper function to check if two arrays of params are functionally equivalent +const areParamsEquivalent = (params1: Property[], params2: Property[]): boolean => { + if (params1.length !== params2.length) return false; + + // Create a map of key-value pairs for easier comparison + const paramsMap1 = params1.reduce((map, param) => { + if (param.key) map[param.key] = param.value; + return map; + }, {} as Record); + + const paramsMap2 = params2.reduce((map, param) => { + if (param.key) map[param.key] = param.value; + return map; + }, {} as Record); + + return isEqual(paramsMap1, paramsMap2); +}; +``` + +## Key Takeaways for React Hook Development + +1. **Always use safe property access**: + - For deep nested properties, use lodash's `get` with default values + - Alternatively, use optional chaining (`?.`) but remember it doesn't provide default values + +2. **Track previous values to prevent infinite loops**: + - Use `useRef` to store previous values between renders + - Compare new values against previous values before making updates + +3. **Implement early exits**: + - If nothing has changed, return early from your hook + - Use deep equality checks for objects and arrays (e.g., `isEqual` from lodash) + +4. **Make effects unidirectional in a single execution**: + - In bidirectional sync, handle only one direction per effect execution + - Exit early after making updates in one direction + +5. **Make hooks flexible and reusable**: + - Use parameters for configuration (e.g., `configProperty`) + - Don't hardcode property paths or selectors + +6. **Test bidirectional hooks thoroughly**: + - Write tests for both directions of data flow + - Test edge cases (undefined values, empty arrays, etc.) + - Verify the hook prevents infinite loops with nearly identical input + +## Implementation Example + +The `useSyncParamsToPath` hook provides a real-world example of these patterns in action: + +```tsx +// Hook to sync query parameters with URL path in both directions +export const useSyncParamsToPath = (formName: string, configProperty: string) => { + const dispatch = useDispatch(); + const formValues = useSelector((state) => getFormData(state, formName)); + // Refs to track the last values to prevent infinite loops + const lastPathRef = useRef(""); + const lastParamsRef = useRef([]); + + useEffect( + function syncParamsEffect() { + if (!formValues || !formValues.values) return; + + const values = formValues.values; + const actionId = values.id; + + if (!actionId) return; + + // Correctly access nested properties using lodash's get + const path = get(values, `${configProperty}.path`, ""); + const queryParameters = get(values, `${configProperty}.params`, []); + + // Early return if nothing has changed + if (path === lastPathRef.current && isEqual(queryParameters, lastParamsRef.current)) { + return; + } + + // Check if params have changed but path hasn't - indicating params tab update + const paramsChanged = !isEqual(queryParameters, lastParamsRef.current); + const pathChanged = path !== lastPathRef.current; + + // Update refs to current values + lastPathRef.current = path; + lastParamsRef.current = [...queryParameters]; + + // Only one sync direction per effect execution to prevent loops + + // Path changed - update params from path if needed + if (pathChanged) { + // Logic to update params from path + // Exit early to prevent circular updates + return; + } + + // Params changed - update path from params if needed + if (paramsChanged) { + // Logic to update path from params + } + }, + [formValues, dispatch, formName, configProperty], + ); +}; +``` + +By implementing these patterns, we fixed the circular dependency and infinite loop issues while making the hook more reusable and robust. \ No newline at end of file diff --git a/.cursor/docs/references/codebase-map.md b/.cursor/docs/references/codebase-map.md new file mode 100644 index 0000000000..e4e7e6f152 --- /dev/null +++ b/.cursor/docs/references/codebase-map.md @@ -0,0 +1,226 @@ +# Appsmith Codebase Map + +This document provides a comprehensive overview of the Appsmith codebase structure to help Cursor AI better understand the organization and relationships between different components. + +## Project Overview + +Appsmith is a low-code platform that allows developers to build internal tools and dashboards by connecting to databases, APIs, and other data sources. The application consists of: + +1. A React-based frontend (client) +2. A Java Spring Boot backend (server) +3. Various plugins for connecting to external data sources +4. A self-contained deployment architecture + +## Directory Structure + +The codebase is organized into the following main directories: + +- `app/` - Contains the main application code + - `client/` - Frontend application (React) + - `server/` - Backend application (Java Spring Boot) + - `util/` - Shared utilities + - `monitoring/` - Monitoring and metrics + +## Frontend Architecture (app/client) + +The frontend is built with React, Redux, and TypeScript. Key directories include: + +### Core Structure (app/client/src) +- `actions/` - Redux actions +- `reducers/` - Redux reducers +- `sagas/` - Redux sagas for side effects and async operations +- `selectors/` - Redux selectors +- `store.ts` - Redux store configuration + +### UI Components +- `components/` - Reusable UI components +- `pages/` - Page-level components +- `widgets/` - Draggable widgets for the page builder +- `theme/` - Styling and theme definitions +- `icons/` - SVG icons and icon components + +### Data and APIs +- `api/` - API client and service functions +- `constants/` - Application constants and configuration +- `utils/` - Utility functions +- `entities/` - Data models and entity definitions + +### Edition-specific Code +- `ee/` - Enterprise Edition specific code +- `ce/` - Community Edition specific code + +### Testing +- `test/` - Test utilities and mocks +- `cypress/` - End-to-end testing with Cypress + +## Backend Architecture (app/server) + +The backend is built with Java Spring Boot and MongoDB. Key packages include: + +### Core Structure (app/server/appsmith-server/src/main/java/com/appsmith/server) +- `ServerApplication.java` - Main application entry point + +### API Layer +- `controllers/` - REST API controllers +- `dtos/` - Data Transfer Objects +- `exceptions/` - Custom exception classes + +### Business Logic +- `services/` - Business logic and service implementations +- `helpers/` - Helper classes and utilities +- `domains/` - Domain models + +### Data Access +- `repositories/` - Data access repositories +- `configurations/` - Database and application configuration + +### Features +- `applications/` - Application management +- `pages/` - Page management +- `actions/` - Action management (API, DB queries) +- `plugins/` - Plugin system for external integrations +- `datasources/` - Data source management +- `authentication/` - Authentication and authorization +- `organization/` - Organization management + +### Extensions +- `appsmith-plugins/` - Plugin implementations +- `appsmith-git/` - Git integration features +- `appsmith-interfaces/` - Core interfaces +- `appsmith-ai/` - AI features implementation + +## Key Concepts + +### Frontend Concepts + +1. **Widgets**: Draggable UI components that users can place on their pages +2. **Actions**: API calls, DB queries, or JS code that widgets can trigger +3. **Datasources**: Connections to external data sources like databases or APIs +4. **Pages**: Containers for widgets representing different views in an application +5. **Theme**: Visual styling applied to the entire application + +### Backend Concepts + +1. **Applications**: Container for pages and other resources +2. **Organizations**: Groups of users and applications +3. **Plugins**: Connectors to external services +4. **Actions**: Executable code blocks (API calls, DB queries) +5. **Datasources**: Connection configurations for external data systems + +## Code Patterns + +### Frontend Patterns + +1. **Redux for State Management**: + - Actions define state changes + - Reducers implement state updates + - Sagas handle side effects + - Selectors extract state + +2. **Component Structure**: + - Functional components with hooks + - Container/Presentation separation + - Styled-components for styling + - Typescript interfaces for type safety + +3. **API Communication**: + - Axios-based API clients + - Redux sagas for async operations + - Error handling middleware + +### Backend Patterns + +1. **Spring Boot Architecture**: + - Controller -> Service -> Repository pattern + - DTO pattern for API requests/responses + - Reactive programming with Reactor + +2. **Security**: + - JWT-based authentication + - RBAC (Role-Based Access Control) + - Permission checks with Spring Security + +3. **Database**: + - MongoDB as primary datastore + - Reactive repositories + +## Common Workflows + +### Frontend Development Workflow + +1. Define Redux actions in `actions/` +2. Implement reducers in `reducers/` +3. Create sagas for async operations in `sagas/` +4. Build UI components in `components/` or `pages/` +5. Connect components to Redux using selectors + +### Backend Development Workflow + +1. Define DTOs in `dtos/` +2. Create domain models in `domains/` +3. Implement repositories in `repositories/` +4. Add business logic in `services/` +5. Expose APIs in `controllers/` + +## Testing Approach + +### Frontend Testing +- Unit tests with Jest and React Testing Library +- End-to-end tests with Cypress +- Visual regression tests + +### Backend Testing +- Unit tests with JUnit +- Integration tests with Spring Boot Test +- API tests with RestAssured + +## Performance Considerations + +### Frontend Performance +- Memoization of heavy computations +- Code splitting for page loads +- Virtualization for large lists +- Optimized rendering with React.memo + +### Backend Performance +- Query optimization in MongoDB +- Caching strategies +- Reactive programming for non-blocking operations + +## Security Model + +1. **Authentication**: JWT-based auth with refresh tokens +2. **Authorization**: RBAC with granular permissions +3. **Data Isolation**: Multi-tenancy support + +## Enterprise vs Community Edition + +The codebase is separated into: +- `ee/` - Enterprise features +- `ce/` - Community features + +Key differences: +1. Enterprise: SSO, audit logs, role-based access +2. Community: Basic features, self-hosted option + +## Important Files + +### Frontend +- `client/src/index.tsx` - Application entry point +- `client/src/store.ts` - Redux store configuration +- `client/src/App.tsx` - Main application component + +### Backend +- `server/appsmith-server/src/main/java/com/appsmith/server/ServerApplication.java` - Main entry point +- `server/appsmith-server/src/main/resources/application.yml` - Application configuration + +## Development Guidelines + +1. Follow the established patterns in the existing codebase +2. Use TypeScript interfaces for type safety in frontend +3. Add appropriate tests for all new features +4. Document complex logic with comments +5. Use reactive programming patterns in backend +6. Follow established file naming conventions + +This map should help Cursor better understand the Appsmith codebase structure and provide more contextual assistance when working with the code. \ No newline at end of file diff --git a/.cursor/docs/references/technical-details.md b/.cursor/docs/references/technical-details.md new file mode 100644 index 0000000000..270cc194a9 --- /dev/null +++ b/.cursor/docs/references/technical-details.md @@ -0,0 +1,699 @@ +# Appsmith Technical Details + +This document provides in-depth technical information about the Appsmith codebase, focusing on implementation details, design patterns, and technologies used. This information will help Cursor AI better understand the code at a deeper level. + +## Technology Stack + +### Frontend +- **Framework**: React 17+ +- **State Management**: Redux with Redux-Saga +- **Language**: TypeScript 4+ +- **Styling**: Styled Components with Tailwind CSS +- **Build Tool**: Webpack +- **Testing**: Jest, React Testing Library, Cypress +- **Form Management**: Formik +- **API Client**: Axios +- **UI Components**: Custom component library + +### Backend +- **Framework**: Spring Boot 2.x +- **Language**: Java 11+ +- **Database**: MongoDB +- **Reactive Programming**: Project Reactor +- **Security**: Spring Security +- **API Documentation**: Swagger/OpenAPI +- **Caching**: Redis + +## Key Frontend Implementation Details + +### State Management + +The application uses Redux with a sophisticated structure: + +```typescript +// Example action +export const fetchDatasources = (applicationId: string) => ({ + type: ReduxActionTypes.FETCH_DATASOURCES, + payload: { applicationId }, +}); + +// Example reducer +const datasourceReducer = (state = initialState, action: ReduxAction) => { + switch (action.type) { + case ReduxActionTypes.FETCH_DATASOURCES_SUCCESS: + return { ...state, list: action.payload }; + // ... + } +}; + +// Example saga +function* fetchDatasourcesSaga(action: ReduxAction<{ applicationId: string }>) { + try { + const response = yield call(DatasourcesApi.fetchDatasources, action.payload.applicationId); + yield put({ + type: ReduxActionTypes.FETCH_DATASOURCES_SUCCESS, + payload: response.data, + }); + } catch (error) { + yield put({ + type: ReduxActionTypes.FETCH_DATASOURCES_ERROR, + payload: { error }, + }); + } +} +``` + +### Widget System + +Widgets are the building blocks of the application UI. They follow a standard structure: + +```typescript +export type WidgetProps = { + widgetId: string; + type: string; + widgetName: string; + parentId?: string; + renderMode: RenderMode; + version: number; + // ...other properties +}; + +export default class ButtonWidget extends BaseWidget { + static getPropertyPaneConfig() { + return [ + { + sectionName: "General", + children: [ + { + propertyName: "text", + label: "Label", + controlType: "INPUT_TEXT", + // ... + }, + // ...other properties + ], + }, + // ...other sections + ]; + } + + getPageView() { + return ( + + ); + } + + handleClick = () => { + if (this.props.onClick) { + super.executeAction({ + triggerPropertyName: "onClick", + dynamicString: this.props.onClick, + event: { + type: EventType.ON_CLICK, + // ... + }, + }); + } + }; +} +``` + +### Property Pane System + +The property pane is dynamically generated based on the widget configuration: + +```typescript +export const PropertyPaneView = (props: PropertyPaneViewProps) => { + const { config, panel } = props; + + // Render property sections + return ( + + {config.map((section) => ( + + {section.children.map((property) => ( + + ))} + + ))} + + ); +}; +``` + +### Data Binding + +The app uses a JS evaluation engine to bind data to widgets: + +```typescript +export function evaluateDynamicValue( + dynamicValue: string, + data: Record, +): any { + // Set up execution environment + const scriptToEvaluate = ` + function evaluation() { + const $ = ${JSON.stringify(data)}; + try { + return ${dynamicValue}; + } catch (e) { + return undefined; + } + } + evaluation(); + `; + + try { + return eval(scriptToEvaluate); + } catch (e) { + return undefined; + } +} +``` + +### API Integration + +The API client is set up with Axios and handles authentication: + +```typescript +const axiosInstance = axios.create({ + baseURL: "/api/v1", + headers: { + "Content-Type": "application/json", + }, +}); + +// Request interceptor for adding auth token +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem("AUTH_TOKEN"); + if (token) { + // Basic validation - check if token is a valid JWT format + if (token.split('.').length === 3) { + config.headers.Authorization = `Bearer ${token}`; + } else { + // Handle invalid token - could log user out or refresh token + store.dispatch(refreshToken()); + } + } + return config; +}); + +// Response interceptor for handling errors +axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + // Handle unauthorized + store.dispatch(logoutUser()); + } + return Promise.reject(error); + } +); +``` + +## Key Backend Implementation Details + +### Repository Pattern + +Mongo repositories use reactive programming: + +```java +@Repository +public interface DatasourceRepository extends ReactiveMongoRepository { + Mono findByNameAndOrganizationId(String name, String organizationId); + Flux findAllByOrganizationId(String organizationId); + Mono countByNameAndOrganizationId(String name, String organizationId); +} +``` + +### Service Layer + +Services handle business logic: + +```java +@Service +@RequiredArgsConstructor +public class DatasourceServiceImpl implements DatasourceService { + private final DatasourceRepository repository; + private final PluginService pluginService; + + @Override + public Mono create(Datasource datasource) { + return repository.save(datasource) + .flatMap(saved -> pluginService.getById(datasource.getPluginId()) + .map(plugin -> { + saved.setPlugin(plugin); + return saved; + })); + } + + // Other methods... +} +``` + +### Controller Layer + +Controllers expose REST APIs: + +```java +@RestController +@RequestMapping("/api/v1/datasources") +@RequiredArgsConstructor +public class DatasourceController { + private final DatasourceService service; + + @PostMapping + public Mono> create(@RequestBody DatasourceDTO dto) { + Datasource datasource = new Datasource(); + BeanUtils.copyProperties(dto, datasource); + + return service.create(datasource) + .map(created -> new ResponseDTO<>(HttpStatus.CREATED.value(), created, null)); + } + + // Other endpoints... +} +``` + +### Security Configuration + +Spring Security setup: + +```java +@Configuration +@EnableWebFluxSecurity +@EnableReactiveMethodSecurity +public class SecurityConfig { + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + return http + .csrf().disable() + .formLogin().disable() + .httpBasic().disable() + .authorizeExchange() + .pathMatchers("/api/v1/public/**").permitAll() + .pathMatchers("/api/v1/auth/**").permitAll() + .anyExchange().authenticated() + .and() + .addFilterAt(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) + .build(); + } + + // Other beans... +} +``` + +### Query Execution + +Action execution is handled in a structured way: + +```java +@Service +@RequiredArgsConstructor +public class ActionExecutionServiceImpl implements ActionExecutionService { + private final ActionExecutorFactory executorFactory; + + @Override + public Mono executeAction(ActionDTO action) { + ActionExecutor executor = executorFactory.getExecutor(action.getPluginType()); + + if (executor == null) { + return Mono.error(new AppsmithException(AppsmithError.UNSUPPORTED_PLUGIN_ACTION)); + } + + return executor.execute(action); + } +} +``` + +## Advanced Patterns + +### Code Splitting + +```typescript +// Lazy loading of components +const ApplicationPage = React.lazy(() => import("pages/Applications")); +const EditorPage = React.lazy(() => import("pages/Editor")); + +// Router setup +const routes = [ + { + path: "/applications", + component: ApplicationPage, + }, + { + path: "/app/editor/:applicationId/:pageId", + component: EditorPage, + }, + // ... +]; +``` + +### Plugin System + +The plugin system allows extensibility: + +```java +public interface PluginExecutor { + Mono execute(T connection, U datasourceConfiguration, Object executeActionDTO); + Mono datasourceCreate(U datasourceConfiguration); + void datasourceDestroy(T connection); + Set getHintMessages(U datasourceConfiguration); + // ... +} +``` + +### Reactive Caching + +```java +@Service +public class CacheableRepositoryHelper { + private final Map> cacheMap = new ConcurrentHashMap<>(); + + public Mono fetchFromCache(String cacheName, String key, Supplier> fetchFunction) { + Cache cache = cacheMap.computeIfAbsent(cacheName, k -> + Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build()); + + Object cachedValue = cache.getIfPresent(key); + if (cachedValue != null) { + return Mono.just((T) cachedValue); + } + + return fetchFunction.get() + .doOnNext(value -> cache.put(key, value)); + } +} +``` + +### Action Collection System + +Actions are grouped into collections for better organization: + +```java +@Document +public class ActionCollection { + @Id + private String id; + private String name; + private String applicationId; + private String organizationId; + private String pageId; + private List actions; + private List actionIds; + private String body; + // ... +} +``` + +## Common Code Patterns + +### Error Handling + +Frontend error handling: + +```typescript +// Global error boundary +export class AppErrorBoundary extends React.Component<{}, { hasError: boolean }> { + constructor(props: {}) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError() { + return { hasError: true }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + logError(error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ; + } + return this.props.children; + } +} +``` + +Backend error handling: + +```java +@ExceptionHandler(AppsmithException.class) +public Mono>> handleAppsmithException(AppsmithException exception) { + log.error("Application error: {}", exception.getMessage(), exception); + + ResponseDTO response = new ResponseDTO<>( + exception.getHttpStatus().value(), + null, + new ErrorDTO(exception.getAppErrorCode(), exception.getMessage()) + ); + + return Mono.just(ResponseEntity + .status(exception.getHttpStatus()) + .body(response)); +} +``` + +### Validation + +Frontend validation: + +```typescript +const validateWidgetName = (widgetName: string) => { + const nameRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/; + + if (!nameRegex.test(widgetName)) { + return "Widget name must start with a letter and can contain only letters, numbers, and underscore"; + } + + if (widgetName.length > 30) { + return "Widget name must be less than 30 characters"; + } + + return undefined; +}; +``` + +Backend validation: + +```java +@Validated +@Service +public class UserServiceImpl implements UserService { + @Override + public Mono create(@Valid UserDTO userDTO) { + // Implementation + } +} + +public class UserDTO { + @NotBlank(message = "Email is mandatory") + @Email(message = "Invalid email format") + private String email; + + @NotBlank(message = "Password is mandatory") + @Size(min = 8, message = "Password must be at least 8 characters") + private String password; + + // Other fields... +} +``` + +### Internationalization + +```typescript +// i18n setup +const i18n = createI18n({ + locale: getBrowserLocale(), + messages: { + en: enMessages, + fr: frMessages, + // Other languages... + }, +}); + +// Usage in components +const MyComponent = () => { + const { t } = useTranslation(); + + return ( +
+

{t('welcome.title')}

+

{t('welcome.message')}

+
+ ); +}; +``` + +## Enterprise-specific Features + +### Audit Logging + +```java +@Service +@ConditionalOnProperty(prefix = "appsmith", name = "audit.enabled", havingValue = "true") +public class AuditServiceImpl implements AuditService { + private final AuditRepository repository; + + @Override + public Mono log(String action, String resourceId, String resourceType, User user) { + AuditLog log = new AuditLog(); + log.setAction(action); + log.setResourceId(resourceId); + log.setResourceType(resourceType); + log.setUserId(user.getId()); + log.setUsername(user.getUsername()); + log.setTimestamp(Instant.now()); + + return repository.save(log); + } +} +``` + +### Role-Based Access Control + +```java +@Service +public class PermissionServiceImpl implements PermissionService { + + @Override + public Mono hasPermission(User user, String resourceId, PermissionType permission) { + return userGroupRepository.findByUserIdAndOrganizationId(user.getId(), user.getCurrentOrganizationId()) + .flatMap(userGroup -> { + if (userGroup.getRole() == UserRole.ORGANIZATION_ADMIN) { + return Mono.just(true); + } + + return resourcePermissionRepository + .findByResourceIdAndPermission(resourceId, permission) + .any(resourcePermission -> resourcePermission.getUserGroupId().equals(userGroup.getId())); + }); + } +} +``` + +### SSO Integration + +```java +@Configuration +@ConditionalOnProperty(prefix = "appsmith.oauth2", name = "enabled", havingValue = "true") +public class OAuth2Config { + + @Bean + public ReactiveClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>(); + + registrations.add(googleClientRegistration()); + registrations.add(githubClientRegistration()); + // Other providers... + + return new InMemoryReactiveClientRegistrationRepository(registrations); + } + + private ClientRegistration googleClientRegistration() { + return ClientRegistration.withRegistrationId("google") + .clientId(googleClientId) + .clientSecret(googleClientSecret) + .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUri("{baseUrl}/api/v1/oauth2/callback/{registrationId}") + .scope("openid", "email", "profile") + .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") + .tokenUri("https://www.googleapis.com/oauth2/v4/token") + .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo") + .userNameAttributeName(IdTokenClaimNames.SUB) + .jwkSetUri("https://www.googleapis.com/oauth2/v3/certs") + .clientName("Google") + .build(); + } +} +``` + +## Performance Optimization Techniques + +### Frontend Optimizations + +1. **Memoization**: +```typescript +const MemoizedComponent = React.memo(MyComponent); + +// Using useMemo for computed values +const computedValue = useMemo(() => { + return expensiveComputation(a, b); +}, [a, b]); + +// Using useCallback for functions +const handleClick = useCallback(() => { + doSomething(a, b); +}, [a, b]); +``` + +2. **Virtualization for Large Lists**: +```typescript +import { FixedSizeList } from 'react-window'; + +const MyList = ({ items }) => ( + + {({ index, style }) => ( +
+ {items[index].name} +
+ )} +
+); +``` + +### Backend Optimizations + +1. **Batch Processing**: +```java +@Service +public class BatchImportServiceImpl implements BatchImportService { + @Override + public Mono importEntities(List entities) { + return Flux.fromIterable(entities) + .flatMap(this::validateEntity) + .collectList() + .flatMap(validatedEntities -> + repository.saveAll(validatedEntities) + .collectList() + .map(savedEntities -> new ImportResult(savedEntities.size(), null)) + ); + } +} +``` + +2. **Query Optimization**: +```java +// Using MongoDB indexes +@Document +public class Application { + // ... + + @Indexed + private String name; + + @Indexed + private String organizationId; + + @CompoundIndex(def = "{'organizationId': 1, 'name': 1}", unique = true) + // ... +} +``` + +## This document should provide Cursor with a deeper understanding of the technical implementation details of Appsmith, allowing for more accurate and contextual assistance when working with the codebase. \ No newline at end of file diff --git a/.cursor/docs/references/testing-reference.md b/.cursor/docs/references/testing-reference.md new file mode 100644 index 0000000000..54d3b08bbe --- /dev/null +++ b/.cursor/docs/references/testing-reference.md @@ -0,0 +1,219 @@ +# Appsmith Testing Quick Reference + +## Testing Requirements + +### For Bug Fixes + +1. **Unit Tests (Required)** + - Reproduce the bug scenario + - Verify the fix works correctly + - Test edge cases and potential regressions + - Place in the same directory as the fixed code with `.test.ts` extension + +2. **End-to-End Tests (Required for user-facing changes)** + - Create a Cypress test that simulates the user action that would trigger the bug + - Verify the fix works correctly in the application context + - Place in `app/client/cypress/e2e/Regression/` + +3. **Redux/React Safety Tests (For Redux/React code)** + - Test with both valid and null/undefined state structures + - Verify that property access is handled safely + - Test edge cases where nested properties might not exist + +### For Feature Development + +1. **Unit Tests (Required)** + - Test each component or function individually + - Cover the main functionality, edge cases, and error conditions + - Place alongside the implemented code + +2. **Integration Tests (For complex features)** + - Test interactions between components + - Verify that data flows correctly between components + +3. **End-to-End Tests (For user-facing features)** + - Simulate user interactions with the feature + - Verify that the feature works correctly in the application context + +## Test File Locations + +- **Unit Tests:** Same directory as the code being tested (e.g., `Component.test.tsx`) +- **Cypress E2E Tests:** `app/client/cypress/e2e/Regression/[Category]/[Feature]_spec.js` +- **Backend Tests:** `app/server/src/test/java/com/appsmith/server/...` + +## Templates + +### Unit Test for Bug Fix (React Component) + +```typescript +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import MyComponent from "./MyComponent"; + +describe("MyComponent bug fix", () => { + it("should reproduce the bug scenario", () => { + // Arrange: Setup the conditions that trigger the bug + render(); + + // Act: Perform the action that triggers the bug + fireEvent.click(screen.getByText("Button")); + + // Assert: Verify the bug is fixed + expect(screen.getByText("Expected Result")).toBeInTheDocument(); + }); + + it("should maintain existing functionality", () => { + // Test that related functionality still works + render(); + expect(screen.getByText("Other Result")).toBeInTheDocument(); + }); +}); +``` + +### E2E Test for Bug Fix + +```javascript +describe("Feature Bug Fix", { tags: ["@tag.Bugfix", "@tag.Regression"] }, function() { + before(() => { + cy.login(); + cy.createTestWorkspace(); + }); + + it("should no longer exhibit the bug", () => { + // Steps to reproduce the bug + cy.get("[data-cy=element]").click(); + cy.get("[data-cy=other-element]").type("value"); + + // Verify the bug is fixed + cy.get("[data-cy=result]").should("have.text", "Expected Result"); + }); +}); +``` + +### Redux Safety Test + +```typescript +import { configureStore } from '@reduxjs/toolkit'; +import reducer, { selectUserData } from './userSlice'; +import { renderHook } from '@testing-library/react-hooks'; +import { Provider } from 'react-redux'; +import React from 'react'; +import { useSelector } from 'react-redux'; + +describe("Redux safety tests", () => { + // Test with missing nested properties + it("should handle missing nested properties safely", () => { + // Create store with incomplete state + const store = configureStore({ + reducer: { + user: reducer + }, + preloadedState: { + user: { + // Missing nested user data structure + } + } + }); + + // Test selector with missing data + const wrapper = ({ children }) => ( + {children} + ); + + const { result } = renderHook(() => useSelector(selectUserData), { wrapper }); + + // Should not throw an error and return default/fallback value + expect(result.current).toEqual(/* expected default value */); + }); + + it("should handle deep property access safely", () => { + // Similar setup but with different state permutations + // Test various incomplete state structures + }); +}); +``` + +### Unit Test for Redux Sagas + +```typescript +import { runSaga } from 'redux-saga'; +import { mySaga } from './mySaga'; + +describe("mySaga", () => { + it("should dispatch expected actions", async () => { + // Mock dependencies + const dispatched = []; + const mockStore = { + dispatch: (action) => dispatched.push(action), + getState: () => ({ data: 'mock data' }), + }; + + // Run the saga + await runSaga(mockStore, mySaga, { payload: { id: '123' } }).toPromise(); + + // Assert expected actions were dispatched + expect(dispatched).toEqual([ + { type: 'SOME_ACTION', payload: 'mock data' }, + ]); + }); +}); +``` + +## Best Practices + +1. **Test the User Experience** + - Focus on testing what the user sees and experiences + - Don't test implementation details unless necessary + +2. **Use Descriptive Test Names** + - Tests should clearly describe what they're testing + - Use format: `should [expected behavior] when [condition]` + +3. **Isolate Tests** + - Each test should be independent + - Don't rely on state from other tests + +4. **Test Edge Cases** + - Empty input, invalid input, boundary conditions + - Error states and recovery + - Null/undefined in Redux state trees + - Missing nested properties + +5. **Keep Tests Fast** + - Tests should run quickly to encourage frequent testing + - Use mocks for slow dependencies + +6. **Test Coverage Guidelines** + - 80%+ coverage for critical paths + - Focus on business logic rather than UI details + +7. **Redux/React Safety Testing** + - Test selectors with incomplete state structures + - Verify error boundaries catch property access errors + - Test with various state permutations to ensure robustness + +## Running Tests + +### Frontend Unit Tests + +```bash +cd app/client +yarn test # Run all tests +yarn test:watch # Run in watch mode +yarn test:coverage # Generate coverage report +``` + +### Cypress E2E Tests + +```bash +cd app/client +yarn cypress:open # Open Cypress UI +yarn cypress:run # Run headless +``` + +### Backend Tests + +```bash +cd app/server +./mvnw test +``` \ No newline at end of file diff --git a/.cursor/hooks/README.md b/.cursor/hooks/README.md new file mode 100644 index 0000000000..0755dfc4b5 --- /dev/null +++ b/.cursor/hooks/README.md @@ -0,0 +1,43 @@ +# Appsmith Cursor Hooks + +This directory contains hooks and scripts that automate tasks and enforce standards in the Appsmith development workflow. + +## Available Hooks + +### scripts/update-docs.sh +Automatically updates documentation based on code changes, ensuring that the documentation stays in sync with the codebase. + +## How Hooks Work + +Hooks are triggered by specific events in the development workflow, such as: +- Creating a pull request +- Pushing code to a branch +- Running specific commands + +Each hook performs specific actions to maintain code quality, enforce standards, or automate routine tasks. + +## Installing Hooks + +To install these hooks in your local development environment: + +1. Navigate to the root of the project +2. Run the following command: + ```bash + cp .cursor/hooks/scripts/* .git/hooks/ + chmod +x .git/hooks/* + ``` + +This will copy the hooks to your local Git hooks directory and make them executable. + +## Manual Execution + +You can also run these hooks manually as needed: + +```bash +# Update documentation based on code changes +.cursor/hooks/scripts/update-docs.sh +``` + +## Customizing Hooks + +If you need to customize a hook for your specific development environment, copy it to your local `.git/hooks` directory and modify it as needed. Avoid changing the hooks in this directory directly, as they will be overwritten when you pull changes from the repository. \ No newline at end of file diff --git a/.cursor/hooks/scripts/auto-update-docs.mdc b/.cursor/hooks/scripts/auto-update-docs.mdc new file mode 100644 index 0000000000..6006c96be0 --- /dev/null +++ b/.cursor/hooks/scripts/auto-update-docs.mdc @@ -0,0 +1,225 @@ +--- +description: +globs: +alwaysApply: true +--- +--- +description: +globs: +alwaysApply: true +--- + --- +name: Auto Update Documentation +description: Automatically updates Cursor documentation based on code changes +author: Cursor AI +version: 1.0.0 +tags: + - appsmith + - documentation + - maintenance + - automation +activation: + always: true + events: + - pull_request + - push + - command +triggers: + - pull_request.created + - pull_request.updated + - push.after + - command.update_docs +--- + +# Auto Update Documentation Rule + +This rule ensures that the Cursor documentation files are kept up-to-date as the codebase evolves. It analyzes code changes and automatically updates the relevant documentation files. + +## Functionality + +- Monitors changes to key parts of the codebase +- Updates the codebase map when structure changes +- Updates technical details when implementation changes +- Notifies developers when documentation needs manual review + +## Implementation + +```javascript +const fs = require('fs'); +const path = require('path'); + +// File paths +const codebaseMapPath = '.cursor/appsmith-codebase-map.md'; +const technicalDetailsPath = '.cursor/appsmith-technical-details.md'; +const cursorIndexPath = '.cursor/index.mdc'; + +/** + * Get modified files from a pull request or push + * @param {Object} event - The trigger event + * @returns {Array} List of modified file paths + */ +function getModifiedFiles(event) { + // For actual implementation, use the appropriate API to get modified files + // This is a simplified version + if (event.type === 'pull_request') { + return cursor.git.getChangedFiles(event.pullRequest.base, event.pullRequest.head); + } else if (event.type === 'push') { + return cursor.git.getChangedFiles(event.push.before, event.push.after); + } + return []; +} + +/** + * Checks if any structural files have been modified + * @param {Array} files - List of modified files + * @returns {boolean} True if structural files were modified + */ +function hasStructuralChanges(files) { + const structuralPatterns = [ + /^app\/[^\/]+\//, // Top-level app directories + /package\.json$/, // Package definitions + /pom\.xml$/, // Maven project files + /^app\/client\/src\/reducers\//, // Redux structure + /^app\/server\/appsmith-server\/src\/main\/java\/com\/appsmith\/server\//, // Main server structure + /^\.cursor\/rules\/.*\.mdc$/, // Cursor rule files + /^\.cursor\/.*\.mdc$/, // Top-level Cursor rule files + ]; + + return files.some(file => + structuralPatterns.some(pattern => pattern.test(file)) + ); +} + +/** + * Checks if any implementation files have been modified + * @param {Array} files - List of modified files + * @returns {boolean} True if implementation files were modified + */ +function hasImplementationChanges(files) { + const implementationPatterns = [ + /\.java$/, // Java files + /\.ts(x)?$/, // TypeScript files + /\.js(x)?$/, // JavaScript files + /^app\/client\/src\/sagas\//, // Redux sagas + /^app\/server\/appsmith-server\/src\/main\/java\/com\/appsmith\/server\/services/, // Server services + /^\.cursor\/rules\/.*\.mdc$/, // Cursor rule files + /^\.cursor\/.*\.mdc$/, // Top-level Cursor rule files + ]; + + return files.some(file => + implementationPatterns.some(pattern => pattern.test(file)) + ); +} + +/** + * Checks if any rule files have been modified + * @param {Array} files - List of modified files + * @returns {boolean} True if rule files were modified + */ +function hasRuleChanges(files) { + const rulePatterns = [ + /^\.cursor\/rules\/.*\.mdc$/, // Rules in the rules directory + /^\.cursor\/.*\.mdc$/ // Top-level rules + ]; + + return files.some(file => + rulePatterns.some(pattern => pattern.test(file)) + ); +} + +/** + * Updates a documentation file with a notice + * @param {string} filePath - Path to the documentation file + * @param {string} message - Message to add to the file + */ +function updateDocumentationFile(filePath, message) { + try { + let content = fs.readFileSync(filePath, 'utf8'); + const timestamp = new Date().toISOString(); + const updateNote = `\n\n> **Update Notice (${timestamp})**: ${message}`; + + // Add the update notice near the top, after any headers + const headerEndIndex = content.indexOf('\n\n'); + if (headerEndIndex !== -1) { + content = content.substring(0, headerEndIndex + 2) + updateNote + content.substring(headerEndIndex + 2); + fs.writeFileSync(filePath, content); + return true; + } + return false; + } catch (error) { + console.error(`Failed to update ${filePath}:`, error); + return false; + } +} + +/** + * Main function to handle documentation updates + * @param {Object} event - The trigger event + */ +function handleDocumentationUpdates(event) { + const modifiedFiles = getModifiedFiles(event); + let updates = 0; + + if (hasStructuralChanges(modifiedFiles)) { + const updated = updateDocumentationFile( + codebaseMapPath, + 'Structural changes detected in the codebase. This document may need to be updated to reflect the new structure.' + ); + if (updated) updates++; + } + + if (hasImplementationChanges(modifiedFiles)) { + const updated = updateDocumentationFile( + technicalDetailsPath, + 'Implementation changes detected in the codebase. This document may need to be updated to reflect the new implementation details.' + ); + if (updated) updates++; + } + + if (hasRuleChanges(modifiedFiles)) { + const updated = updateDocumentationFile( + cursorIndexPath, + 'Cursor rule changes detected. This document may need to be updated to reflect the new rules or rule updates.' + ); + if (updated) updates++; + } + + return { + success: true, + data: { + filesUpdated: updates, + message: updates > 0 ? 'Documentation update notices added.' : 'No documentation updates needed.' + } + }; +} + +// Register the event handlers +function activate() { + cursor.on('pull_request.created', handleDocumentationUpdates); + cursor.on('pull_request.updated', handleDocumentationUpdates); + cursor.on('push.after', handleDocumentationUpdates); + + cursor.registerCommand('update_docs', () => { + return handleDocumentationUpdates({ type: 'command' }); + }); +} + +module.exports = { + activate, + handleDocumentationUpdates +}; +``` + +## Usage + +This rule runs automatically on pull request creation/updates and after pushes. You can also manually trigger it with the command `update_docs`. + +When it detects significant changes to the codebase structure or implementation, it will add update notices to the top of the relevant documentation files, indicating that they may need to be reviewed and updated. + +## Configuration + +No specific configuration is required. The rule automatically monitors key file patterns that indicate structural or implementation changes. + +## Example + +After a significant refactoring of the frontend directory structure: diff --git a/.cursor/hooks/scripts/update-docs.sh b/.cursor/hooks/scripts/update-docs.sh new file mode 100755 index 0000000000..e3160b29d1 --- /dev/null +++ b/.cursor/hooks/scripts/update-docs.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# update-cursor-docs.sh +# Pre-commit hook script to update Cursor documentation based on code changes + +set -e + +CURSOR_DIR=".cursor" +CODEBASE_MAP="${CURSOR_DIR}/appsmith-codebase-map.md" +TECHNICAL_DETAILS="${CURSOR_DIR}/appsmith-technical-details.md" +RULES_DIR="${CURSOR_DIR}/rules" + +echo "🔍 Checking for updates to Cursor documentation..." + +# Function to check if file needs to be included in the commit +add_to_commit_if_changed() { + local file=$1 + if git diff --name-only --cached | grep -q "$file"; then + echo "✅ $file is already staged for commit" + elif git diff --name-only | grep -q "$file"; then + echo "📝 Adding modified $file to commit" + git add "$file" + fi +} + +# Get list of changed files in this commit +CHANGED_FILES=$(git diff --cached --name-only) + +# Check if we need to update the codebase map +update_codebase_map() { + local need_update=false + + # Check for directory structure changes + if echo "$CHANGED_FILES" | grep -q "^app/.*/$"; then + need_update=true + fi + + # Check for major file additions + if echo "$CHANGED_FILES" | grep -q -E '\.(java|tsx?|jsx?)$' | wc -l | grep -q -v "^0$"; then + need_update=true + fi + + if [ "$need_update" = true ]; then + echo "🔄 Updating Codebase Map documentation..." + + # Append a timestamp to the file to mark it as updated + echo -e "\n\n> Last updated: $(date)" >> "$CODEBASE_MAP" + + # In a real implementation, you would call an external script or Cursor API + # to analyze the codebase and update the map file + echo "ℹ️ Codebase Map should be manually reviewed to ensure accuracy" + + add_to_commit_if_changed "$CODEBASE_MAP" + else + echo "✅ Codebase Map does not need updates" + fi +} + +# Check if we need to update the technical details +update_technical_details() { + local need_update=false + + # Check for framework changes + if echo "$CHANGED_FILES" | grep -q -E 'package\.json|pom\.xml|build\.gradle'; then + need_update=true + fi + + # Check for core component changes + if echo "$CHANGED_FILES" | grep -q -E 'src/(components|widgets|services)/.*\.(tsx?|java)$'; then + need_update=true + fi + + if [ "$need_update" = true ]; then + echo "🔄 Updating Technical Details documentation..." + + # Append a timestamp to the file to mark it as updated + echo -e "\n\n> Last updated: $(date)" >> "$TECHNICAL_DETAILS" + + # In a real implementation, you would call an external script or Cursor API + # to analyze the codebase and update the technical details file + echo "ℹ️ Technical Details should be manually reviewed to ensure accuracy" + + add_to_commit_if_changed "$TECHNICAL_DETAILS" + else + echo "✅ Technical Details do not need updates" + fi +} + +# Check if we need to update Cursor rules +update_cursor_rules() { + local need_update=false + + # Update rules if specific patterns are found in changed files + if echo "$CHANGED_FILES" | grep -q -E 'app/client/src/(widgets|components)|app/server/.*/(controllers|services)/'; then + need_update=true + fi + + if [ "$need_update" = true ]; then + echo "🔄 Checking Cursor rules for updates..." + + # In a real implementation, you would call an external script or Cursor API + # to analyze rule relevance and update rules + + # Add timestamp to index.mdc to mark rules as checked + if [ -f "$RULES_DIR/index.mdc" ]; then + echo -e "\n\n> Rules checked: $(date)" >> "$RULES_DIR/index.mdc" + add_to_commit_if_changed "$RULES_DIR/index.mdc" + fi + + echo "ℹ️ Cursor rules should be manually reviewed to ensure they're up to date" + else + echo "✅ Cursor rules do not need updates" + fi +} + +# Main execution +update_codebase_map +update_technical_details +update_cursor_rules + +echo "✅ Cursor documentation check complete" + +exit 0 \ No newline at end of file diff --git a/.cursor/incremental_learning.json b/.cursor/incremental_learning.json new file mode 100644 index 0000000000..0ff5a3bcbc --- /dev/null +++ b/.cursor/incremental_learning.json @@ -0,0 +1,136 @@ +{ + "learning": { + "enabled": true, + "incremental": true, + "sources": [ + { + "type": "code_changes", + "patterns": ["**/*.java", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "strategy": "diff_based" + }, + { + "type": "test_additions", + "patterns": ["**/*.test.ts", "**/*.test.tsx", "**/*Test.java", "**/*Tests.java", "cypress/integration/**/*.spec.ts"], + "strategy": "full_context" + }, + { + "type": "workflow_modifications", + "patterns": [".github/workflows/*.yml"], + "strategy": "full_context" + }, + { + "type": "documentation_updates", + "patterns": ["**/*.md", "docs/**/*"], + "strategy": "full_context" + }, + { + "type": "build_changes", + "patterns": ["**/package.json", "**/pom.xml", "**/build.gradle", "Dockerfile", "docker-compose.yml"], + "strategy": "full_context" + } + ], + "indexing": { + "frequency": "on_change", + "depth": "full", + "include_dependencies": true + }, + "retention": { + "max_items_per_category": 1000, + "max_total_items": 10000, + "prioritization": "recency_and_relevance" + } + }, + "pattern_detection": { + "enabled": true, + "categories": [ + { + "name": "coding_patterns", + "description": "Common implementation patterns in the codebase", + "examples_to_track": 50 + }, + { + "name": "test_patterns", + "description": "Patterns for writing tests in this codebase", + "examples_to_track": 50 + }, + { + "name": "architectural_patterns", + "description": "Patterns related to the system's architecture", + "examples_to_track": 30 + }, + { + "name": "performance_optimizations", + "description": "Patterns for performance improvements", + "examples_to_track": 30 + }, + { + "name": "bug_fixes", + "description": "Common patterns in bug fixes", + "examples_to_track": 50 + } + ] + }, + "knowledge_graph": { + "enabled": true, + "entity_types": [ + "component", "service", "repository", "controller", "utility", + "model", "workflow", "test", "configuration" + ], + "relationship_types": [ + "imports", "extends", "implements", "calls", "uses", "tests", + "defines", "configures" + ], + "max_depth": 3, + "update_frequency": "on_change" + }, + "context_building": { + "strategies": { + "new_file": [ + "analyze_related_files", + "study_similar_patterns", + "check_test_coverage" + ], + "bug_fix": [ + "understand_root_cause", + "analyze_test_gaps", + "check_similar_issues" + ], + "feature_addition": [ + "understand_requirements", + "analyze_affected_components", + "plan_test_strategy" + ], + "refactoring": [ + "understand_current_implementation", + "identify_improvement_opportunities", + "ensure_test_coverage" + ] + } + }, + "verification_strategies": { + "bug_fix": [ + "run_targeted_tests", + "verify_fix_addresses_root_cause", + "check_regression_tests" + ], + "feature_addition": [ + "verify_meets_requirements", + "run_new_tests", + "check_for_performance_impact" + ], + "refactoring": [ + "verify_identical_behavior", + "check_test_coverage", + "verify_performance_impact" + ] + }, + "feedback_loop": { + "sources": [ + "code_review_comments", + "test_results", + "build_status", + "performance_metrics" + ], + "integration": "continuous" + } +} \ No newline at end of file diff --git a/.cursor/index.mdc b/.cursor/index.mdc new file mode 100644 index 0000000000..bd2d99dbaa --- /dev/null +++ b/.cursor/index.mdc @@ -0,0 +1,97 @@ +--- +description: +globs: +alwaysApply: true +--- +# Appsmith Cursor Rules + +```yaml +name: Appsmith Cursor Rules +description: A comprehensive set of rules for Cursor AI to enhance development for Appsmith +author: Cursor AI +version: 1.0.0 +tags: + - appsmith + - development + - quality + - verification +activation: + always: true + event: + - pull_request + - file_change + - command +triggers: + - pull_request.created + - pull_request.updated + - file.created + - file.modified + - command: "cursor_help" +``` + +## Overview + +This is the main entry point for Cursor AI rules for the Appsmith codebase. These rules help enforce consistent coding standards, verify bug fixes and features, generate appropriate tests, and optimize performance. + +## Directory Structure + +``` +.cursor/ +├── settings.json # Main configuration file +├── docs/ # Documentation +│ ├── guides/ # In-depth guides +│ ├── references/ # Quick references +│ └── practices/ # Best practices +├── rules/ # Rule definitions +│ ├── commit/ # Commit-related rules +│ ├── quality/ # Code quality rules +│ ├── testing/ # Testing rules +│ └── verification/ # Verification rules +└── hooks/ # Git hooks and scripts +``` + +## Available Rules + +### 1. Commit Rules + +- [Semantic PR Validator](rules/commit/semantic-pr-validator.mdc): Ensures pull request titles follow the Conventional Commits specification. +- [Semantic PR Guidelines](rules/commit/semantic-pr.md): Guidelines for writing semantic commit messages. + +### 2. Quality Rules + +- [Performance Optimizer](rules/quality/performance-optimizer.mdc): Identifies performance bottlenecks in code and suggests optimizations. +- [Pre-commit Quality Checks](rules/quality/pre-commit-checks.mdc): Quality checks that run before commits. + +### 3. Testing Rules + +- [Test Generator](rules/testing/test-generator.mdc): Analyzes code changes and generates appropriate test cases. + +### 4. Verification Rules + +- [Bug Fix Verifier](rules/verification/bug-fix-verifier.mdc): Guides developers through proper bug fixing steps. +- [Feature Verifier](rules/verification/feature-verifier.mdc): Verifies that new features are properly implemented and tested. +- [Feature Implementation Validator](rules/verification/feature-implementation-validator.mdc): Validates feature implementations. +- [Workflow Validator](rules/verification/workflow-validator.mdc): Validates development workflows. + +## Documentation + +- [Guides](docs/guides/): In-depth guides on specific topics +- [References](docs/references/): Quick reference documents +- [Practices](docs/practices/): Best practices for development + +## Command Examples + +- `validate_pr_title` - Check if a PR title follows conventional commits format +- `verify_bug_fix --pullRequest=123` - Verify a bug fix implementation +- `generate_tests --file=src/utils/helpers.js` - Generate tests for a specific file +- `optimize_performance --file=src/components/Table.tsx` - Analyze and optimize performance +- `validate_feature --pullRequest=123` - Validate a feature implementation +- `cursor_help` - Display available commands and provide guidance + +## Configuration + +The behavior of these rules can be customized in the `.cursor/settings.json` file. + +## Activation + +To activate all rules, run `cursor_help` in the command palette. This will display available commands and provide guidance on using the rules for your specific task. \ No newline at end of file diff --git a/.cursor/rules.json b/.cursor/rules.json index 85ce6771d3..b1e816c9f4 100644 --- a/.cursor/rules.json +++ b/.cursor/rules.json @@ -37,6 +37,70 @@ "usage": "cypress/e2e/..." } } + }, + "reactHooks": { + "bestPractices": { + "required": true, + "rules": { + "safePropertyAccess": { + "required": true, + "description": "Use lodash/get or optional chaining for nested property access", + "examples": ".cursor/docs/react_hooks_circular_dependency_lessons.md#1.-Safe-nested-property-access-using-lodash/get" + }, + "preventCircularDependencies": { + "required": true, + "description": "Use useRef to track previous values and implement directional updates", + "examples": ".cursor/docs/react_hooks_circular_dependency_lessons.md#2.-Tracking-previous-values-with-useRef" + }, + "earlyReturns": { + "required": true, + "description": "Implement early returns when values haven't changed to prevent unnecessary updates", + "examples": ".cursor/docs/react_hooks_circular_dependency_lessons.md#3.-Directional-updates" + }, + "deepComparisons": { + "required": true, + "description": "Use deep equality checks for comparing objects and arrays", + "examples": ".cursor/docs/react_hooks_circular_dependency_lessons.md#4.-Deep-comparisons-for-complex-objects" + } + }, + "analyzer": ".cursor/rules/react_hook_best_practices.mdc" + } + }, + "testingRequirements": { + "bugFixes": { + "required": true, + "tests": { + "unit": { + "required": true, + "description": "Unit tests must verify the specific fix and ensure no regressions" + }, + "e2e": { + "required": "for user-facing changes", + "description": "End-to-end tests must verify the fix works in the application context" + } + }, + "examples": { + "unit": ".cursor/rules/test_generator.mdc#Bug-Fix-Test-Example-(Unit-Test)", + "e2e": ".cursor/rules/test_generator.mdc#Bug-Fix-Test-Example-(E2E-Test)" + } + }, + "features": { + "required": true, + "tests": { + "unit": { + "required": true, + "description": "Unit tests must cover core functionality and edge cases" + }, + "integration": { + "required": "for complex features", + "description": "Integration tests must verify interactions between components" + }, + "e2e": { + "required": "for user-facing features", + "description": "End-to-end tests must verify the feature works in the application context" + } + } + } } } } diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md new file mode 100644 index 0000000000..4ebac2ac29 --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,43 @@ +# Appsmith Cursor Rules + +This directory contains the rules that Cursor AI uses to validate and improve code quality in the Appsmith project. + +## Rule Categories + +- **commit/**: Rules for validating commit messages and pull requests + - `semantic-pr.md`: Guidelines for semantic pull request titles + +- **quality/**: Rules for ensuring code quality + - `performance.mdc`: Rules for optimizing performance + - `pre-commit-checks.mdc`: Quality checks that run before commits + +- **testing/**: Rules for test coverage and quality + - `test-generator.mdc`: Automated test generation based on code changes + +- **verification/**: Rules for verifying changes and implementations + - `bug-fix-verifier.mdc`: Validation for bug fix implementations + - `feature-verifier.mdc`: Validation for feature implementations + - `workflow-validator.mdc`: Validation for development workflows + +## How Rules Work + +Each rule is defined in a Markdown Cursor (`.mdc`) file that includes: + +1. **Metadata**: Name, description, and trigger conditions +2. **Logic**: JavaScript code that implements the rule +3. **Documentation**: Usage examples and explanations + +Rules are automatically triggered based on events like: +- Creating or updating pull requests +- Modifying files +- Running specific commands in Cursor + +## Using Rules + +You can manually trigger rules using Cursor commands, such as: +- `validate_pr_title`: Check if a PR title follows conventions +- `verify_bug_fix`: Validate a bug fix implementation +- `generate_tests`: Generate tests for changed code +- `optimize_performance`: Analyze code for performance issues + +Refer to each rule's documentation for specific usage information. \ No newline at end of file diff --git a/.cursor/rules/commit/semantic-pr-validator.mdc b/.cursor/rules/commit/semantic-pr-validator.mdc new file mode 100644 index 0000000000..dc61687eaa --- /dev/null +++ b/.cursor/rules/commit/semantic-pr-validator.mdc @@ -0,0 +1,174 @@ +--- +description: +globs: +alwaysApply: true +--- +# Semantic PR Validator + +```yaml +name: Semantic PR Validator +description: Validates that PR titles follow the Conventional Commits specification +author: Cursor AI +version: 1.0.0 +tags: + - git + - pull-request + - semantic + - conventional-commits +activation: + always: true + event: + - pull_request + - pull_request_title_edit + - command +triggers: + - pull_request.created + - pull_request.edited + - command: "validate_pr_title" +``` + +## Rule Definition + +This rule ensures that pull request titles follow the [Conventional Commits](mdc:https:/www.conventionalcommits.org) specification. + +## Validation Logic + +```javascript +// Function to validate PR titles against Conventional Commits spec +function validatePRTitle(title) { + // Regular expression for conventional commits format + const conventionalCommitRegex = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-_]+\))?(!)?: [a-z0-9].+$/i; + + if (!conventionalCommitRegex.test(title)) { + return { + valid: false, + errors: [ + "PR title doesn't follow the Conventional Commits format: type(scope): description", + "Example valid titles:", + "- feat(widget): add new table component", + "- fix: resolve login issue", + "- docs(readme): update installation instructions" + ] + }; + } + + // Check for empty scope in parentheses + if (title.includes('()')) { + return { + valid: false, + errors: [ + "Empty scope provided. Either include a scope value or remove the parentheses." + ] + }; + } + + // Extract parts + const match = title.match(/^([a-z]+)(?:\(([a-z0-9-_]+)\))?(!)?:/i); + + if (!match || !match[1]) { + return { + valid: false, + errors: [ + "Failed to parse PR title format. Please follow the pattern: type(scope): description" + ] + }; + } + + const type = match[1].toLowerCase(); + + // Validate type + const validTypes = ["feat", "fix", "docs", "style", "refactor", + "perf", "test", "build", "ci", "chore", "revert"]; + + if (!validTypes.includes(type)) { + return { + valid: false, + errors: [ + `Invalid type "${type}". Valid types are: ${validTypes.join(', ')}` + ] + }; + } + + return { valid: true }; +} + +// Triggered when a PR is created or the title is changed +function onPRTitleChange(prTitle) { + const validation = validatePRTitle(prTitle); + + if (!validation.valid) { + return { + status: "failure", + message: "PR title doesn't follow Conventional Commits format", + details: validation.errors.join('\n'), + suggestions: [ + { + label: "Fix PR title format", + description: "Update title to follow type(scope): description format" + } + ] + }; + } + + return { + status: "success", + message: "PR title follows Conventional Commits format" + }; +} + +// Run on activation +function activate(context) { + // Register event handlers + context.on('pull_request.created', (event) => { + const prTitle = event.pull_request.title; + return onPRTitleChange(prTitle); + }); + + context.on('pull_request.edited', (event) => { + const prTitle = event.pull_request.title; + return onPRTitleChange(prTitle); + }); + + context.registerCommand('validate_pr_title', (args) => { + const prTitle = args.title || context.currentPR?.title; + if (!prTitle) { + return { + status: "error", + message: "No PR title provided" + }; + } + return onPRTitleChange(prTitle); + }); +} + +// Export the functions +module.exports = { + activate, + onPRTitleChange, + validatePRTitle +}; +``` + +## When It Runs + +This rule automatically runs in the following scenarios: +- When a new pull request is created +- When a pull request title is edited +- When a developer asks for validation via Cursor command: `validate_pr_title` + +## Usage Example + +To validate a PR title before submitting: + +1. Create a branch and make your changes +2. Prepare to create a PR +3. Use the command: `validate_pr_title` in Cursor +4. Cursor will check your title and suggest corrections if needed + +## Examples of Valid PR Titles + +- `feat(widgets): add new table widget capabilities` +- `fix(auth): resolve login redirect issue` +- `docs: update README with setup instructions` +- `refactor(api): simplify error handling logic` +- `chore: update dependencies to latest versions` \ No newline at end of file diff --git a/.cursor/rules/commit/semantic-pr.md b/.cursor/rules/commit/semantic-pr.md new file mode 100644 index 0000000000..2937d659c2 --- /dev/null +++ b/.cursor/rules/commit/semantic-pr.md @@ -0,0 +1,82 @@ +# Semantic PR Guidelines for Appsmith + +This guide outlines how to ensure your pull requests follow the Conventional Commits specification, which is enforced in this project using the [semantic-prs](https://github.com/Ezard/semantic-prs) GitHub app. + +## Current Configuration + +The project uses the following semantic PR configuration in `.github/semantic.yml`: + +```yaml +# Always validate the PR title, and ignore the commits +titleOnly: true +``` + +This means that only the PR title needs to follow the Conventional Commits spec, and commit messages are not validated. + +## Pull Request Title Format + +PR titles should follow this format: + +``` +type(scope): description +``` + +### Types + +Common types according to Conventional Commits: + +- `feat`: A new feature +- `fix`: A bug fix +- `docs`: Documentation changes +- `style`: Changes that don't affect the code's meaning (formatting, etc.) +- `refactor`: Code changes that neither fix a bug nor add a feature +- `perf`: Performance improvements +- `test`: Adding or fixing tests +- `build`: Changes to build process, dependencies, etc. +- `ci`: Changes to CI configuration files and scripts +- `chore`: Other changes that don't modify source or test files +- `revert`: Reverts a previous commit + +### Scope + +The scope is optional and represents the section of the codebase affected by the change (e.g., `client`, `server`, `widgets`, `plugins`). + +### Description + +A brief description of the changes. Should: +- Use imperative, present tense (e.g., "add" not "added" or "adds") +- Not capitalize the first letter +- Not end with a period + +## Examples of Valid PR Titles + +- `feat(widgets): add new table widget capabilities` +- `fix(auth): resolve login redirect issue` +- `docs: update README with new setup instructions` +- `refactor(api): simplify error handling logic` +- `chore: update dependencies to latest versions` + +## Examples of Invalid PR Titles + +- `Added new feature` (missing type) +- `fix - login bug` (improper format, missing scope) +- `feat(client): Added new component.` (description should use imperative mood and not end with period) + +## Automated Validation + +The semantic-prs GitHub app will automatically check your PR title when you create or update a pull request. If your PR title doesn't follow the conventions, the check will fail, and you'll need to update your title. + +## Cursor Assistance + +Cursor will help enforce these rules by: + +1. Suggesting conventional PR titles when creating branches +2. Validating PR titles against the conventional format +3. Providing feedback on non-compliant PR titles +4. Suggesting corrections for PR titles that don't meet the requirements + +## Resources + +- [Conventional Commits Specification](https://www.conventionalcommits.org/) +- [semantic-prs GitHub App](https://github.com/Ezard/semantic-prs) +- [Angular Commit Message Guidelines](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#commit) \ No newline at end of file diff --git a/.cursor/rules/index.md b/.cursor/rules/index.md new file mode 100644 index 0000000000..ba3949de5a --- /dev/null +++ b/.cursor/rules/index.md @@ -0,0 +1,45 @@ +# Appsmith Cursor Rules + +This index provides an overview of all the rules available for Cursor AI in the Appsmith project. + +## Commit Rules + +- [Semantic PR Validator](commit/semantic-pr-validator.mdc): Validates that PR titles follow the Conventional Commits specification +- [Semantic PR Guidelines](commit/semantic-pr.md): Guidelines for writing semantic PR titles and commit messages + +## Quality Rules + +- [Performance Optimizer](quality/performance-optimizer.mdc): Analyzes code for performance issues and suggests improvements +- [Pre-commit Quality Checks](quality/pre-commit-checks.mdc): Checks code quality before commits + +## Testing Rules + +- [Test Generator](testing/test-generator.mdc): Automatically generates appropriate tests for code changes + +## Verification Rules + +- [Bug Fix Verifier](verification/bug-fix-verifier.mdc): Guides developers through proper bug fixing steps and verifies fix quality +- [Feature Verifier](verification/feature-verifier.mdc): Verifies that new features are properly implemented and tested +- [Feature Implementation Validator](verification/feature-implementation-validator.mdc): Validates that new features are completely and correctly implemented +- [Workflow Validator](verification/workflow-validator.mdc): Validates development workflows + +## Available Commands + +| Command | Description | Rule | +|---------|-------------|------| +| `validate_pr_title` | Validates PR title format | Semantic PR Validator | +| `verify_bug_fix` | Verifies bug fix quality | Bug Fix Verifier | +| `validate_feature` | Validates feature implementation | Feature Implementation Validator | +| `verify_feature` | Verifies feature implementation quality | Feature Verifier | +| `generate_tests` | Generates tests for code changes | Test Generator | +| `optimize_performance` | Analyzes code for performance issues | Performance Optimizer | +| `update_docs` | Updates documentation based on code changes | [Auto Update Docs](../hooks/scripts/auto-update-docs.mdc) | + +## Triggering Rules + +Rules can be triggered: +1. Automatically based on events (PR creation, file modification, etc.) +2. Manually via commands in Cursor +3. From CI/CD pipelines + +See each rule's documentation for specific trigger conditions and parameters. \ No newline at end of file diff --git a/.cursor/rules/index.mdc b/.cursor/rules/index.mdc new file mode 100644 index 0000000000..c09ccef5f1 --- /dev/null +++ b/.cursor/rules/index.mdc @@ -0,0 +1,136 @@ +--- +description: +globs: +alwaysApply: true +--- + # Appsmith Cursor Rules + +```yaml +name: Appsmith Cursor Rules +description: A comprehensive set of rules for Cursor AI to enhance development for Appsmith +author: Cursor AI +version: 1.0.0 +tags: + - appsmith + - development + - quality + - verification +activation: + always: true + event: + - pull_request + - file_change + - command +triggers: + - pull_request.created + - pull_request.updated + - file.created + - file.modified + - command: "cursor_help" +``` + +## Overview + +This is the main entry point for Cursor AI rules for the Appsmith codebase. These rules help enforce consistent coding standards, verify bug fixes and features, generate appropriate tests, and optimize performance. + +## Available Rules + +### 1. [Semantic PR Validator](mdc:semantic_pr_validator.mdc) + +Ensures pull request titles follow the Conventional Commits specification. + +```javascript +// Example usage +const semanticPR = require('./semantic_pr_validator'); +const validation = semanticPR.onPRTitleChange('feat(widgets): add new table component'); +console.log(validation.status); // 'success' +``` + +### 2. [Bug Fix Verifier](mdc:bug_fix_verifier.mdc) + +Guides developers through the proper steps to fix bugs and verifies that fixes meet quality standards. + +```javascript +// Example usage +const bugFixVerifier = require('./bug_fix_verifier'); +const verification = bugFixVerifier.verifyBugFix(changedFiles, testFiles, issueDetails); +console.log(verification.score); // The verification score +``` + +### 3. [Test Generator](mdc:test_generator.mdc) + +Analyzes code changes and generates appropriate test cases to ensure proper test coverage. + +```javascript +// Example usage +const testGenerator = require('./test_generator'); +const testPlan = testGenerator.generateTests(changedFiles, codebase); +console.log(testPlan.summary); // 'Generated X test plans' +``` + +### 4. [Performance Optimizer](mdc:performance_optimizer.mdc) + +Identifies performance bottlenecks in code and suggests optimizations to improve efficiency. + +```javascript +// Example usage +const performanceOptimizer = require('./performance_optimizer'); +const analysis = performanceOptimizer.analyzePerformance(changedFiles, codebase); +console.log(analysis.score); // The performance score +``` + +### 5. [Feature Implementation Validator](mdc:feature_implementation_validator.mdc) + +Ensures new feature implementations meet all requirements, follow best practices, and include appropriate tests. + +```javascript +// Example usage +const featureValidator = require('./feature_implementation_validator'); +const validation = featureValidator.validateFeature( + implementationFiles, + codebase, + pullRequest +); +console.log(validation.score); // The feature implementation score +``` + +## Integration + +These rules are automatically integrated into Cursor AI when working with the Appsmith codebase. They will be triggered based on context and can also be manually invoked through commands. + +## Command Examples + +- `validate_pr_title` - Check if a PR title follows conventional commits format +- `verify_bug_fix --pullRequest=123` - Verify a bug fix implementation +- `generate_tests --file=src/utils/helpers.js` - Generate tests for a specific file +- `optimize_performance --file=src/components/Table.tsx` - Analyze and optimize performance +- `validate_feature --pullRequest=123` - Validate a feature implementation + +## Configuration + +The behavior of these rules can be customized in the `.cursor/settings.json` file. For example: + +```json +{ + "development": { + "gitWorkflow": { + "semanticPR": { + "enabled": true, + "titleFormat": "type(scope): description", + "validTypes": [ + "feat", "fix", "docs", "style", "refactor", + "perf", "test", "build", "ci", "chore", "revert" + ] + } + } + } +} +``` + +## Documentation + +For more detailed information about each rule and how to use it effectively, please refer to the individual rule files linked above. + +## Activation + +To activate all rules, run `cursor_help` in the command palette. This will display available commands and provide guidance on using the rules for your specific task. \ No newline at end of file diff --git a/.cursor/rules/quality/performance-optimizer.mdc b/.cursor/rules/quality/performance-optimizer.mdc new file mode 100644 index 0000000000..30092e01ab --- /dev/null +++ b/.cursor/rules/quality/performance-optimizer.mdc @@ -0,0 +1,358 @@ +--- +description: +globs: +alwaysApply: true +--- +# Performance Optimizer + +```yaml +name: Performance Optimizer +description: Analyzes code for performance issues and suggests improvements +author: Cursor AI +version: 1.0.0 +tags: + - performance + - optimization + - analysis +activation: + always: true + event: + - file_change + - pull_request + - command +triggers: + - file.modified + - pull_request.created + - pull_request.updated + - command: "optimize_performance" +``` + +## Rule Definition + +This rule analyzes code changes for potential performance issues and suggests optimizations. + +## Performance Analysis Logic + +```javascript +// Main function to analyze code for performance issues +function analyzePerformance(files, codebase) { + const issues = []; + const suggestions = []; + let score = 100; // Start with perfect score + + // Process each file + for (const file of files) { + const fileIssues = findPerformanceIssues(file, codebase); + if (fileIssues.length > 0) { + issues.push(...fileIssues); + + // Reduce score based on severity of issues + score -= fileIssues.reduce((total, issue) => total + issue.severity, 0); + + // Generate optimization suggestions + const fileSuggestions = generateOptimizationSuggestions(file, fileIssues, codebase); + suggestions.push(...fileSuggestions); + } + } + + // Ensure score doesn't go below 0 + score = Math.max(0, score); + + return { + score, + issues, + suggestions, + summary: generatePerformanceSummary(score, issues, suggestions) + }; +} + +// Find performance issues in a file +function findPerformanceIssues(file, codebase) { + const issues = []; + + // Check file based on its type + if (file.path.includes('.ts') || file.path.includes('.js')) { + issues.push(...findJavaScriptPerformanceIssues(file)); + } else if (file.path.includes('.java')) { + issues.push(...findJavaPerformanceIssues(file)); + } else if (file.path.includes('.css')) { + issues.push(...findCssPerformanceIssues(file)); + } + + return issues; +} + +// Find performance issues in JavaScript/TypeScript files +function findJavaScriptPerformanceIssues(file) { + const issues = []; + const content = file.content; + + // Check for common JavaScript performance issues + + // 1. Nested loops with high complexity (O(n²) or worse) + if (/for\s*\([^)]*\)\s*\{[^}]*for\s*\([^)]*\)/g.test(content)) { + issues.push({ + type: 'nested_loops', + lineNumber: findLineNumber(content, /for\s*\([^)]*\)\s*\{[^}]*for\s*\([^)]*\)/g), + description: 'Nested loops detected, which may cause O(n²) time complexity', + severity: 8, + suggestion: 'Consider refactoring to reduce time complexity, possibly using maps/sets' + }); + } + + // 2. Large array operations without memoization + if (/\.map\(.*\.filter\(|\.filter\(.*\.map\(/g.test(content)) { + issues.push({ + type: 'chained_array_operations', + lineNumber: findLineNumber(content, /\.map\(.*\.filter\(|\.filter\(.*\.map\(/g), + description: 'Chained array operations may cause performance issues with large datasets', + severity: 5, + suggestion: 'Consider combining operations or using a different data structure' + }); + } + + // 3. Frequent DOM manipulations + if (/document\.getElement(s?)By|querySelector(All)?/g.test(content) && + content.match(/document\.getElement(s?)By|querySelector(All)?/g).length > 5) { + issues.push({ + type: 'frequent_dom_manipulation', + lineNumber: findLineNumber(content, /document\.getElement(s?)By|querySelector(All)?/g), + description: 'Frequent DOM manipulations can cause layout thrashing', + severity: 7, + suggestion: 'Batch DOM manipulations or use DocumentFragment' + }); + } + + // 4. Memory leaks in event listeners + if (/addEventListener\(/g.test(content) && + !/removeEventListener\(/g.test(content)) { + issues.push({ + type: 'potential_memory_leak', + lineNumber: findLineNumber(content, /addEventListener\(/g), + description: 'Event listener without corresponding removal may cause memory leaks', + severity: 6, + suggestion: 'Add corresponding removeEventListener calls where appropriate' + }); + } + + // Add more JavaScript-specific performance checks here + + return issues; +} + +// Find performance issues in Java files +function findJavaPerformanceIssues(file) { + const issues = []; + const content = file.content; + + // Check for common Java performance issues + + // 1. Inefficient string concatenation + if (/String.*\+= |String.*= .*\+ /g.test(content)) { + issues.push({ + type: 'inefficient_string_concat', + lineNumber: findLineNumber(content, /String.*\+= |String.*= .*\+ /g), + description: 'Inefficient string concatenation in loops', + severity: 4, + suggestion: 'Use StringBuilder instead of string concatenation' + }); + } + + // 2. Unclosed resources + if (/new FileInputStream|new Connection/g.test(content) && + !/try\s*\([^)]*\)/g.test(content)) { + issues.push({ + type: 'unclosed_resources', + lineNumber: findLineNumber(content, /new FileInputStream|new Connection/g), + description: 'Resources may not be properly closed', + severity: 7, + suggestion: 'Use try-with-resources to ensure proper resource closure' + }); + } + + // Add more Java-specific performance checks here + + return issues; +} + +// Find performance issues in CSS files +function findCssPerformanceIssues(file) { + const issues = []; + const content = file.content; + + // Check for common CSS performance issues + + // 1. Overqualified selectors + if (/div\.[a-zA-Z0-9_-]+|ul\.[a-zA-Z0-9_-]+/g.test(content)) { + issues.push({ + type: 'overqualified_selectors', + lineNumber: findLineNumber(content, /div\.[a-zA-Z0-9_-]+|ul\.[a-zA-Z0-9_-]+/g), + description: 'Overqualified selectors may impact rendering performance', + severity: 3, + suggestion: 'Use more efficient selectors, avoiding element type with class' + }); + } + + // 2. Universal selectors + if (/\*\s*\{/g.test(content)) { + issues.push({ + type: 'universal_selector', + lineNumber: findLineNumber(content, /\*\s*\{/g), + description: 'Universal selectors can be very slow, especially in large documents', + severity: 5, + suggestion: 'Replace universal selectors with more specific ones' + }); + } + + // Add more CSS-specific performance checks here + + return issues; +} + +// Find line number for a regex match +function findLineNumber(content, regex) { + const match = content.match(regex); + if (!match) return 0; + + const index = content.indexOf(match[0]); + return content.substring(0, index).split('\n').length; +} + +// Generate optimization suggestions based on issues found +function generateOptimizationSuggestions(file, issues, codebase) { + const suggestions = []; + + for (const issue of issues) { + const suggestion = { + file: file.path, + issue: issue.type, + description: issue.suggestion, + lineNumber: issue.lineNumber, + code: issue.suggestion // This would be actual code in a real implementation + }; + + suggestions.push(suggestion); + } + + return suggestions; +} + +// Generate a summary of the performance analysis +function generatePerformanceSummary(score, issues, suggestions) { + const criticalIssues = issues.filter(issue => issue.severity >= 7).length; + const majorIssues = issues.filter(issue => issue.severity >= 4 && issue.severity < 7).length; + const minorIssues = issues.filter(issue => issue.severity < 4).length; + + return { + score, + issues: { + total: issues.length, + critical: criticalIssues, + major: majorIssues, + minor: minorIssues + }, + suggestions: suggestions.length, + recommendation: getPerformanceRecommendation(score) + }; +} + +// Get a recommendation based on the performance score +function getPerformanceRecommendation(score) { + if (score >= 90) { + return "Code looks good performance-wise. Only minor optimizations possible."; + } else if (score >= 70) { + return "Some performance issues found. Consider addressing them before deploying."; + } else if (score >= 50) { + return "Significant performance issues detected. Optimizations strongly recommended."; + } else { + return "Critical performance issues found. The code may perform poorly in production."; + } +} + +// Run on activation +function activate(context) { + // Register event handlers + context.on('file.modified', (event) => { + const file = context.getFileContent(event.file.path); + const codebase = context.getCodebase(); + return analyzePerformance([file], codebase); + }); + + context.on('pull_request.created', (event) => { + const files = context.getPullRequestFiles(event.pullRequest.id); + const codebase = context.getCodebase(); + return analyzePerformance(files, codebase); + }); + + context.on('pull_request.updated', (event) => { + const files = context.getPullRequestFiles(event.pullRequest.id); + const codebase = context.getCodebase(); + return analyzePerformance(files, codebase); + }); + + context.registerCommand('optimize_performance', (args) => { + const filePath = args.file || context.getCurrentFilePath(); + if (!filePath) { + return { + status: "error", + message: "No file specified" + }; + } + + const file = context.getFileContent(filePath); + const codebase = context.getCodebase(); + return analyzePerformance([file], codebase); + }); +} + +// Export functions +module.exports = { + activate, + analyzePerformance, + findPerformanceIssues, + generateOptimizationSuggestions, + generatePerformanceSummary +}; +``` + +## When It Runs + +This rule can be triggered: +- When code changes might impact performance +- When a pull request is created or updated +- When a developer runs the `optimize_performance` command in Cursor +- Before deploying to production environments + +## Usage Example + +1. Make code changes to a file +2. Run `optimize_performance` in Cursor +3. Review the performance analysis +4. Implement the suggested optimizations +5. Re-run the analysis to confirm improvements + +## Performance Optimization Best Practices + +### JavaScript/TypeScript + +- Avoid nested loops when possible +- Use appropriate data structures (Map, Set) for lookups +- Minimize DOM manipulations +- Use event delegation instead of multiple event listeners +- Memoize expensive function calls +- Use requestAnimationFrame for animations + +### Java + +- Use StringBuilder for string concatenation +- Use try-with-resources for proper resource management +- Avoid excessive object creation +- Choose appropriate collections (ArrayList, HashMap) based on use-case +- Use primitive types where possible instead of wrapper classes + +### CSS + +- Avoid universal selectors and deeply nested selectors +- Minimize the use of expensive properties (box-shadow, border-radius, etc.) +- Prefer class selectors over tag selectors +- Use CSS containment where appropriate diff --git a/.cursor/rules/quality/pre-commit-checks.mdc b/.cursor/rules/quality/pre-commit-checks.mdc new file mode 100644 index 0000000000..92f2e78a11 --- /dev/null +++ b/.cursor/rules/quality/pre-commit-checks.mdc @@ -0,0 +1,310 @@ +--- +description: +globs: +alwaysApply: true +--- + # Pre-Commit Quality Checks + +```yaml +name: Pre-Commit Quality Checks +description: Runs quality checks similar to GitHub Actions locally before commits +author: Cursor AI +version: 1.0.0 +tags: + - quality + - pre-commit + - testing + - linting +activation: + always: true + events: + - pre_commit + - command +triggers: + - pre_commit + - command: "run_quality_checks" +``` + +## Rule Definition + +This rule runs the same quality checks locally that would normally run in CI, preventing commits that would fail in the GitHub workflow quality-checks.yml. + +## Implementation + +```javascript +const { execSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +/** + * Determines which checks to run based on changed files + * @param {string[]} changedFiles - List of changed files + * @returns {Object} Object indicating which checks to run + */ +function determineChecksToRun(changedFiles) { + const checks = { + serverChecks: false, + clientChecks: false, + }; + + // Check if server files have changed + checks.serverChecks = changedFiles.some(file => + file.startsWith('app/server/') + ); + + // Check if client files have changed + checks.clientChecks = changedFiles.some(file => + file.startsWith('app/client/') + ); + + return checks; +} + +/** + * Gets a list of changed files in the current git staging area + * @returns {string[]} List of changed files + */ +function getChangedFiles() { + try { + const output = execSync('git diff --cached --name-only', { encoding: 'utf8' }); + return output.split('\n').filter(Boolean); + } catch (error) { + console.error('Error getting changed files:', error.message); + return []; + } +} + +/** + * Runs client-side quality checks + * @returns {Object} Results of the checks + */ +function runClientChecks() { + const results = { + success: true, + errors: [], + output: [] + }; + + try { + // Run client lint + console.log('Running client lint checks...'); + try { + const lintOutput = execSync('cd app/client && yarn lint', { encoding: 'utf8' }); + results.output.push('✅ Client lint passed'); + } catch (error) { + results.success = false; + results.errors.push('Client lint failed'); + results.output.push(`❌ Client lint failed: ${error.message}`); + } + + // Run client unit tests + console.log('Running client unit tests...'); + try { + const testOutput = execSync('cd app/client && yarn test', { encoding: 'utf8' }); + results.output.push('✅ Client unit tests passed'); + } catch (error) { + results.success = false; + results.errors.push('Client unit tests failed'); + results.output.push(`❌ Client unit tests failed: ${error.message}`); + } + + // Check for cyclic dependencies + console.log('Checking for cyclic dependencies...'); + try { + const cyclicCheckOutput = execSync('cd app/client && yarn check-circular-deps', { encoding: 'utf8' }); + results.output.push('✅ No cyclic dependencies found'); + } catch (error) { + results.success = false; + results.errors.push('Cyclic dependencies check failed'); + results.output.push(`❌ Cyclic dependencies found: ${error.message}`); + } + + // Run prettier check + console.log('Running prettier check...'); + try { + const prettierOutput = execSync('cd app/client && yarn prettier', { encoding: 'utf8' }); + results.output.push('✅ Prettier check passed'); + } catch (error) { + results.success = false; + results.errors.push('Prettier check failed'); + results.output.push(`❌ Prettier check failed: ${error.message}`); + } + } catch (error) { + results.success = false; + results.errors.push(`General error in client checks: ${error.message}`); + } + + return results; +} + +/** + * Runs server-side quality checks + * @returns {Object} Results of the checks + */ +function runServerChecks() { + const results = { + success: true, + errors: [], + output: [] + }; + + try { + // Run server unit tests + console.log('Running server unit tests...'); + try { + const testOutput = execSync('cd app/server && ./gradlew test', { encoding: 'utf8' }); + results.output.push('✅ Server unit tests passed'); + } catch (error) { + results.success = false; + results.errors.push('Server unit tests failed'); + results.output.push(`❌ Server unit tests failed: ${error.message}`); + } + + // Run server spotless check + console.log('Running server spotless check...'); + try { + const spotlessOutput = execSync('cd app/server && ./gradlew spotlessCheck', { encoding: 'utf8' }); + results.output.push('✅ Server spotless check passed'); + } catch (error) { + results.success = false; + results.errors.push('Server spotless check failed'); + results.output.push(`❌ Server spotless check failed: ${error.message}`); + } + } catch (error) { + results.success = false; + results.errors.push(`General error in server checks: ${error.message}`); + } + + return results; +} + +/** + * Runs all quality checks + * @param {Object} context - The execution context + * @returns {Object} Results of the checks + */ +function runQualityChecks(context) { + console.log('Running pre-commit quality checks...'); + + const changedFiles = getChangedFiles(); + if (!changedFiles.length) { + return { + status: 'success', + message: 'No files to check' + }; + } + + const checksToRun = determineChecksToRun(changedFiles); + const results = { + success: true, + output: ['Starting quality checks for staged files...'], + clientChecks: null, + serverChecks: null + }; + + // Run client checks if client files have changed + if (checksToRun.clientChecks) { + results.output.push('\n=== Client Checks ==='); + results.clientChecks = runClientChecks(); + results.output = results.output.concat(results.clientChecks.output); + results.success = results.success && results.clientChecks.success; + } + + // Run server checks if server files have changed + if (checksToRun.serverChecks) { + results.output.push('\n=== Server Checks ==='); + results.serverChecks = runServerChecks(); + results.output = results.output.concat(results.serverChecks.output); + results.success = results.success && results.serverChecks.success; + } + + // If no checks were run, note that + if (!checksToRun.clientChecks && !checksToRun.serverChecks) { + results.output.push('No client or server files were changed, skipping checks'); + } + + if (results.success) { + return { + status: 'success', + message: 'All quality checks passed', + details: results.output.join('\n') + }; + } else { + return { + status: 'failure', + message: 'Quality checks failed', + details: results.output.join('\n') + }; + } +} + +/** + * Register command and hooks + * @param {Object} context - The cursor context + */ +function activate(context) { + // Register pre-commit hook + context.on('pre_commit', (event) => { + return runQualityChecks(context); + }); + + // Register command for manual validation + context.registerCommand('run_quality_checks', () => { + return runQualityChecks(context); + }); +} + +module.exports = { + activate, + runQualityChecks +}; +``` + +## Usage + +This rule runs automatically on pre-commit events. You can also manually trigger it with the command `run_quality_checks`. + +### What It Checks + +1. **For Client-side Changes:** + - Runs linting checks + - Runs unit tests + - Checks for cyclic dependencies + - Runs prettier formatting validation + +2. **For Server-side Changes:** + - Runs unit tests + - Runs spotless formatting checks + +### Behavior + +- Only runs checks relevant to the files being committed (client and/or server) +- Prevents commits if any checks fail +- Provides detailed output about which checks passed or failed + +### Requirements + +- Node.js and yarn for client-side checks +- Java and Gradle for server-side checks +- Git for determining changed files + +### Customization + +You can customize which checks are run by modifying the `runClientChecks` and `runServerChecks` functions. + +### Example Output + +``` +Running pre-commit quality checks... +Starting quality checks for staged files... + +=== Client Checks === +✅ Client lint passed +✅ Client unit tests passed +✅ No cyclic dependencies found +✅ Prettier check passed + +=== Server Checks === +✅ Server unit tests passed +✅ Server spotless check passed +``` \ No newline at end of file diff --git a/.cursor/rules/quality/react-hook-best-practices.mdc b/.cursor/rules/quality/react-hook-best-practices.mdc new file mode 100644 index 0000000000..0519ecba6e --- /dev/null +++ b/.cursor/rules/quality/react-hook-best-practices.mdc @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.cursor/rules/testing/test-generator.mdc b/.cursor/rules/testing/test-generator.mdc new file mode 100644 index 0000000000..0f52759bb0 --- /dev/null +++ b/.cursor/rules/testing/test-generator.mdc @@ -0,0 +1,295 @@ +--- +description: +globs: +alwaysApply: true +--- +# Test Generator + +```yaml +name: Test Generator +description: Automatically generates appropriate tests for code changes +author: Cursor AI +version: 1.0.0 +tags: + - testing + - automation + - quality +activation: + always: true + event: + - file_change + - command +triggers: + - file.created + - file.modified + - command: "generate_tests" +``` + +## Rule Definition + +This rule analyzes code changes and generates appropriate test cases to ensure proper test coverage. + +## Test Generation Logic + +```javascript +// Main function to generate tests for code changes +function generateTests(files, codebase) { + const testPlans = []; + + // Process each changed file + for (const file of files) { + if (shouldGenerateTestsFor(file)) { + const testPlan = createTestPlan(file, codebase); + testPlans.push(testPlan); + } + } + + return { + testPlans, + summary: `Generated ${testPlans.length} test plans` + }; +} + +// Determine if we should generate tests for a file +function shouldGenerateTestsFor(file) { + // Skip test files, configuration files, etc. + if (file.path.includes('.test.') || file.path.includes('.spec.')) { + return false; + } + + // Skip certain file types + const skipExtensions = ['.md', '.json', '.yml', '.yaml', '.svg', '.png', '.jpg']; + if (skipExtensions.some(ext => file.path.endsWith(ext))) { + return false; + } + + return true; +} + +// Create a test plan for the file +function createTestPlan(file, codebase) { + const testType = determineTestType(file); + const testCases = analyzeFileForTestCases(file, codebase); + + return { + sourceFile: file.path, + testType, + testFile: generateTestFilePath(file, testType), + testCases, + testFramework: selectTestFramework(file) + }; +} + +// Determine the appropriate type of test +function determineTestType(file) { + if (file.path.includes('app/client')) { + if (file.path.includes('/components/')) { + return 'component'; + } else if (file.path.includes('/utils/')) { + return 'unit'; + } else if (file.path.includes('/api/')) { + return 'integration'; + } + return 'unit'; + } else if (file.path.includes('app/server')) { + if (file.path.includes('/controllers/')) { + return 'controller'; + } else if (file.path.includes('/services/')) { + return 'service'; + } else if (file.path.includes('/repositories/')) { + return 'repository'; + } + return 'unit'; + } + + return 'unit'; // Default +} + +// Analyze file to determine test cases needed +function analyzeFileForTestCases(file, codebase) { + // This would contain complex analysis of the file + // to determine appropriate test cases + const testCases = []; + + // Example test cases for different file types + if (file.path.includes('.tsx') || file.path.includes('.jsx')) { + testCases.push( + { type: 'render', description: 'should render correctly' }, + { type: 'props', description: 'should handle props correctly' }, + { type: 'interaction', description: 'should handle user interactions' } + ); + } else if (file.path.includes('.java')) { + testCases.push( + { type: 'normal', description: 'should execute successfully with valid input' }, + { type: 'exception', description: 'should handle exceptions with invalid input' } + ); + } + + return testCases; +} + +// Generate path for the test file +function generateTestFilePath(file, testType) { + if (file.path.includes('app/client')) { + const basePath = file.path.replace(/\.(ts|tsx|js|jsx)$/, ''); + return `${basePath}.test.${file.path.split('.').pop()}`; + } else if (file.path.includes('app/server')) { + return file.path.replace(/\.java$/, 'Test.java'); + } + + return file.path + '.test'; +} + +// Select appropriate test framework +function selectTestFramework(file) { + if (file.path.includes('app/client')) { + if (file.path.includes('/cypress/')) { + return 'cypress'; + } + return 'jest'; + } else if (file.path.includes('app/server')) { + return 'junit'; + } + + return 'jest'; // Default +} + +// Generate actual test code based on the test plan +function generateTestCode(testPlan) { + // This would create the actual test code based on the framework and test cases + // This is a placeholder that would contain complex logic to generate tests + return "// Generated test code would go here"; +} + +// Run on activation +function activate(context) { + // Register event handlers + context.on('file.created', (event) => { + const file = context.getFileContent(event.file.path); + const codebase = context.getCodebase(); + return generateTests([file], codebase); + }); + + context.on('file.modified', (event) => { + const file = context.getFileContent(event.file.path); + const codebase = context.getCodebase(); + return generateTests([file], codebase); + }); + + context.registerCommand('generate_tests', (args) => { + const filePath = args.file || context.getCurrentFilePath(); + if (!filePath) { + return { + status: "error", + message: "No file specified" + }; + } + + const file = context.getFileContent(filePath); + const codebase = context.getCodebase(); + return generateTests([file], codebase); + }); +} + +// Export functions +module.exports = { + activate, + generateTests, + generateTestCode, + shouldGenerateTestsFor, + createTestPlan +}; +``` + +## When It Runs + +This rule can be triggered: +- When code changes are made and tests need to be created +- When a new file is created +- When an existing file is modified +- When a developer runs the `generate_tests` command in Cursor +- When submitting a PR with code changes that lack tests + +## Usage Example + +1. Make code changes to a file +2. Run `generate_tests` in Cursor +3. Review the generated test plan +4. Accept or modify the suggested tests +5. Run the tests to verify your changes + +## Test Generation Best Practices + +### Frontend Tests + +For React components, tests should typically verify: + +- Component renders without crashing +- Props are correctly handled +- User interactions work as expected +- Edge cases are handled properly + +Example Jest test for a React component: + +```jsx +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import Button from './Button'; + +describe('Button component', () => { + it('renders correctly', () => { + render(