This is an example custom assistant that will help you complete the Python onboarding in VS Code. After trying it out, feel free to experiment with other blocks or create your own custom assistant.
# CAD Development Guidelines
This document provides essential information for developers working on the CAD project. It includes build instructions, testing guidelines, and development practices specific to this project.
## Project Overview
CAD is a web-based CAD solution that combines direct 3D modeling with script-based modeling in the OpenSCAD format. The application is designed to be fast and accessible, running smoothly on any device with a browser.
### Target Users
- Hobbyists
- Educators
- Small businesses
### Key Features
- Direct 3D modeling
- Script-based modeling (OpenSCAD format)
- Client-side rendering
- No server-side data storage
## Build and Configuration
### Prerequisites
- Node.js (latest LTS version recommended)
- pnpm (package manager)
### Setup
1. Clone the repository
2. Install dependencies:
```bash
pnpm install
```
### Development Server
Start the development server with hot module replacement (HMR):
```bash
pnpm dev
```
The application will be available at `http://localhost:5173` by default.
### Production Build
Create a production-ready build:
```bash
pnpm build
```
The build output will be in the `dist` directory.
### Preview Production Build
To preview the production build locally:
```bash
pnpm preview
```
## Testing
CAD uses Vitest with React Testing Library for testing. The test files are co-located with the source files they test, using the `.test.ts` or `.test.tsx` extension.
### Running Tests
- Run all tests once:
```bash
pnpm test
```
- Run tests in watch mode during development:
```bash
pnpm test:watch
```
- Run tests with coverage report:
```bash
pnpm test:coverage
```
### Writing Tests
CAD follows Test-Driven Development (TDD) principles with the Red-Green-Refactor cycle:
1. **RED**: Write a failing test that defines the expected behavior
2. **GREEN**: Write the minimum code necessary to make the test pass
3. **REFACTOR**: Clean up the code while ensuring tests still pass
#### Example Test
Here's an example of a utility function test:
```typescript
// src/utils/example.ts
export function add(a: number, b: number): number {
return a + b;
}
// src/utils/example.test.ts
import { describe, it, expect } from "vitest";
import { add } from "./example";
describe("add", () => {
it("adds two numbers correctly", () => {
expect(add(2, 3)).toBe(5);
});
});
```
#### Component Test Example
For React components, use React Testing Library:
```typescript
// Component test example
import { render, screen } from "@testing-library/react";
import Button from "./Button";
describe("Button", () => {
test("renders a basic button with text", () => {
render(<Button>Click Me</Button>);
expect(
screen.getByRole("button", { name: /click me/i })
).toBeInTheDocument();
});
});
```
### Test Setup
Global test setup is in `src/test/setup.ts`. This file includes:
- Jest DOM matchers for DOM testing
- Mock implementations for browser APIs (like `window.matchMedia`)
## Code Style and Development Practices
### TypeScript
- Strict type checking is enabled
- Explicit function return types are required
- Use TypeScript's type system to its full extent
### Code Formatting
The project uses ESLint and Prettier for code formatting and linting:
- **ESLint**: Enforces code quality rules and TypeScript best practices
- **Prettier**: Handles code formatting
- **EditorConfig**: Ensures consistent editor settings
Configuration files:
- `.eslintrc.js`: ESLint configuration (flat config format)
- `.prettierrc.json`: Prettier configuration
- `.editorconfig`: Editor settings
Key formatting rules:
- 2 spaces for indentation
- 80 character line length
- Double quotes for strings
- Semicolons required
- LF line endings
### Linting and Formatting Commands
- Lint the code:
```bash
pnpm lint
```
- Fix linting issues automatically:
```bash
pnpm lint:fix
```
- Format code with Prettier:
```bash
pnpm format
```
### Pre-commit Hooks
The project uses Husky and lint-staged to run linting and formatting on staged files before commits.
## Project Architecture
CAD is a React application built with:
- **React 19**: For UI components
- **TypeScript**: For type safety
- **Vite**: For fast builds and development
- **Three.js**: For 3D rendering
- **Material UI**: For UI components
- **React Router**: For navigation
- **Zustand**: For state management
### Directory Structure
- `src/`: Source code
- `components/`: React components
- `store/`: State management
- `utils/`: Utility functions
- `theme/`: Theming and styling
- `test/`: Test setup and utilities
### State Management
The application uses a combination of:
- React's built-in state management (useState, useContext)
- Zustand for global state management
## Zustand State Management
Zustand is a small, fast, and scalable state management solution used in CAD. It provides a simple API based on hooks and follows simplified flux principles without the boilerplate of other state management libraries.
### Core Concepts
#### Store Creation
A Zustand store is created using the `create` function, which returns a custom hook:
```typescript
import { create } from 'zustand';
const useStore = create((set) => ({
// State
count: 0,
// Actions
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
```
#### State Usage
Components can subscribe to the store by using the hook and selecting the specific state they need:
```typescript
function Counter() {
const count = useStore((state) => state.count);
return <div>{count}</div>;
}
function Controls() {
const { increment, decrement, reset } = useStore((state) => ({
increment: state.increment,
decrement: state.decrement,
reset: state.reset,
}));
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
```
### Best Practices
#### State Organization
1. **Atomic Selectors**: Select only the specific state slices you need to prevent unnecessary re-renders:
```typescript
// Good: Atomic selection
const count = useStore((state) => state.count);
// Avoid: Selecting the entire state
const state = useStore(); // Will re-render on any state change
```
2. **Shallow Selection**: When selecting multiple state values, use the `useShallow` helper to prevent unnecessary re-renders:
```typescript
import { useShallow } from 'zustand/react/shallow';
// Only re-renders when either count or user changes
const { count, user } = useStore(
useShallow((state) => ({
count: state.count,
user: state.user
}))
);
```
#### State Updates
1. **Immutable Updates**: Always update state immutably:
```typescript
// Good: Immutable update
set((state) => ({
items: [...state.items, newItem]
}));
// Bad: Mutating state directly
set((state) => {
state.items.push(newItem); // Don't do this!
return state;
});
```
2. **Partial Updates**: Zustand merges state by default, so you only need to include the properties you're updating:
```typescript
// Only updates the count property, other properties remain unchanged
set({ count: 5 });
```
3. **Computed Values**: Use selectors for derived state instead of storing computed values:
```typescript
// In component:
const completedTodos = useStore((state) =>
state.todos.filter(todo => todo.completed)
);
// Or as a memoized selector:
const useCompletedTodos = () => useStore((state) =>
state.todos.filter(todo => todo.completed)
);
```
### Slices and Nested State
#### Store Slices
For larger applications, divide your store into logical slices:
```typescript
// userSlice.ts
export interface UserState {
user: User | null;
isLoading: boolean;
error: string | null;
}
export interface UserActions {
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
export type UserSlice = UserState & UserActions;
export const createUserSlice: StateCreator<
AppState,
[],
[],
UserSlice
> = (set, get) => ({
// State
user: null,
isLoading: false,
error: null,
// Actions
login: async (credentials) => {
set({ isLoading: true, error: null });
try {
const user = await api.login(credentials);
set({ user, isLoading: false });
} catch (error) {
set({ error: error.message, isLoading: false });
}
},
logout: () => set({ user: null }),
});
```
#### Combining Slices
Combine multiple slices into a single store:
```typescript
// store.ts
import { create, StateCreator } from 'zustand';
import { UserSlice, createUserSlice } from './userSlice';
import { TodosSlice, createTodosSlice } from './todosSlice';
export interface AppState extends UserSlice, TodosSlice {}
export const useStore = create<AppState>()((...a) => ({
...createUserSlice(...a),
...createTodosSlice(...a),
}));
```
#### Nested Slices
For more complex state, use nested slices to organize related state:
```typescript
// Nested slices approach
interface AppState {
userSlice: UserSlice;
todosSlice: TodosSlice;
uiSlice: UISlice;
}
const useStore = create<AppState>()((set, get) => ({
userSlice: createUserSlice(
(userState) => set({ userSlice: { ...get().userSlice, ...userState } }),
() => get().userSlice
),
todosSlice: createTodosSlice(
(todosState) => set({ todosSlice: { ...get().todosSlice, ...todosState } }),
() => get().todosSlice
),
uiSlice: createUISlice(
(uiState) => set({ uiSlice: { ...get().uiSlice, ...uiState } }),
() => get().uiSlice
),
}));
// Usage with nested slices
const user = useStore((state) => state.userSlice.user);
const todos = useStore((state) => state.todosSlice.todos);
```
#### Choosing Between Flat and Nested Slices
When deciding between flat and nested slices, consider:
1. **Flat Slices (extending AppState)**:
- **Pros**: Simpler access to state properties, less nesting in selectors
- **Cons**: Potential naming conflicts, harder to group related state
2. **Nested Slices (properties in AppState)**:
- **Pros**: Better organization, clear boundaries between domains, easier to add/remove features
- **Cons**: More verbose access to state properties, more nesting in selectors
Choose the approach that best fits your application's complexity and organization needs. For larger applications with distinct domains, nested slices often provide better organization and maintainability.
### Performance Optimization
#### Preventing Re-renders
1. **Selective Subscriptions**: Only subscribe to the specific state you need:
```typescript
// Only re-renders when count changes
const count = useStore((state) => state.count);
```
2. **Equality Functions**: Customize the equality check for complex objects:
```typescript
const user = useStore(
(state) => state.user,
(a, b) => a.id === b.id // Only re-render if user ID changes
);
```
3. **Memoized Selectors**: Use memoization for expensive computations:
```typescript
import { useMemo } from 'react';
// Create a memoized selector
const useFilteredTodos = (filter) => {
const todos = useStore((state) => state.todos);
return useMemo(() =>
todos.filter(todo => todo.status === filter),
[todos, filter]
);
};
```
#### Transient Updates
For frequent updates that don't need to trigger re-renders (like mouse position tracking), use the `api.setState` method:
```typescript
const useMouseStore = create((set, get, api) => ({
position: { x: 0, y: 0 },
updatePosition: (x, y) => {
// Update state without triggering re-renders
api.setState({ position: { x, y } }, true);
},
}));
```
This is particularly useful for:
- Mouse movement tracking
- Scroll position updates
- Animation frames
- Any high-frequency updates where rendering on every change would be inefficient
The second parameter `true` in `api.setState({ ... }, true)` indicates that the update is "replace" mode rather than "merge" mode, but more importantly, it makes the update transient, meaning it won't trigger re-renders.
#### Subscribing to State Changes Outside Components
You can subscribe to state changes outside of React components using the `subscribe` method:
```typescript
// Subscribe to all state changes
const unsubscribe = useStore.subscribe(
(state) => console.log('State changed:', state)
);
// Later, when you no longer need the subscription
unsubscribe();
```
For more selective subscriptions, use the `subscribeWithSelector` middleware:
```typescript
import { subscribeWithSelector } from 'zustand/middleware';
const useStore = create(
subscribeWithSelector((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);
// Subscribe only to changes in the count property
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, previousCount) => console.log(`Count changed from ${previousCount} to ${count}`)
);
```
This is useful for:
- Syncing state with external systems
- Logging state changes
- Triggering side effects outside the React component lifecycle
### Common Pitfalls to Avoid
1. **Storing Non-Serializable Values**: Avoid storing functions, classes, or complex objects that can't be serialized if you're using persistence:
```typescript
// Bad: Storing non-serializable values
const useStore = create(persist((set) => ({
domNode: document.querySelector('#root'), // Don't do this!
regExp: /test/g, // Don't do this!
}), { name: 'my-storage' }));
// Better: Store only serializable data
const useStore = create(persist((set) => ({
elementId: 'root', // Store the ID instead of the DOM node
regExpPattern: 'test', // Store the pattern as a string
}), { name: 'my-storage' }));
```
2. **Overusing Global State**: Not everything needs to be in global state. Use local state for UI-specific state:
```typescript
// Good: Use local state for UI-specific state
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
// ...
}
```
3. **Zombie Child Problem**: Be aware of the "zombie child problem" where a component might use stale props because its parent re-rendered but it didn't. Zustand handles this correctly, but it's good to understand the issue:
```typescript
// This is safe with Zustand, but would be problematic with some other state managers
function Parent() {
const createTodo = useStore((state) => state.createTodo);
return <Child createTodo={createTodo} />;
}
function Child({ createTodo }) {
const todos = useStore((state) => state.todos);
return (
<>
<button onClick={createTodo}>Create Todo</button>
{todos.map(todo => <Todo key={todo.id} {...todo} />)}
</>
);
}
```
4. **Selector Referential Equality**: Be careful with selectors that return objects or arrays, as they create new references on each render:
```typescript
// Bad: Creates a new array reference on each render
const todoIds = useStore((state) => state.todos.map(todo => todo.id));
// Better: Use useShallow for arrays and objects
const todoIds = useStore(
useShallow((state) => state.todos.map(todo => todo.id))
);
```
5. **Synchronous Updates Outside React Events**: In pre-React 18, updating state outside React event handlers can cause synchronous updates. Use `unstable_batchedUpdates` to batch updates:
```typescript
import { unstable_batchedUpdates } from 'react-dom';
// For updates outside React event handlers in pre-React 18
function handleExternalEvent(data) {
unstable_batchedUpdates(() => {
useStore.getState().updateFromExternalData(data);
});
}
```
6. **Deeply Nested Updates**: Avoid deeply nested state structures that are difficult to update immutably. If you need to work with deeply nested objects, consider these approaches:
```typescript
// Difficult to update immutably with manual spreading
set((state) => ({
users: {
...state.users,
[userId]: {
...state.users[userId],
settings: {
...state.users[userId].settings,
theme: 'dark'
}
}
}
}));
// Better: Flatten state or use one of these approaches:
```
#### Approaches for Handling Deeply Nested State
1. **Using Immer**: Simplifies updates with a mutable syntax that produces immutable updates:
```typescript
import { produce } from 'immer';
// With Immer middleware
const useStore = create(
immer((set) => ({
users: {},
updateTheme: (userId, theme) =>
set((state) => {
// Can write "mutating" code thanks to Immer
state.users[userId].settings.theme = theme;
})
}))
);
// Or with produce directly
set(produce((state) => {
state.users[userId].settings.theme = 'dark';
}));
```
2. **Using optics-ts**: Provides a functional approach to updating nested state:
```typescript
import * as O from 'optics-ts';
set(O.modify(
O.optic<State>().path("users", userId, "settings", "theme")
)(() => 'dark'));
```
3. **Using Ramda**: Another functional approach for updating nested state:
```typescript
import * as R from 'ramda';
set(R.modifyPath(
["users", userId, "settings", "theme"],
() => 'dark'
));
```
4. **Flattening State**: Restructure your state to avoid deep nesting:
```typescript
// Instead of:
{
users: {
[userId]: {
settings: {
theme: 'dark'
}
}
}
}
// Consider:
{
users: {
[userId]: { /* user properties */ }
},
userSettings: {
[userId]: { theme: 'dark' }
}
}
```
4. **Circular Dependencies**: Avoid circular dependencies between slices:
```typescript
// Bad: Circular dependency
// userSlice.ts
import { todosSlice } from './todosSlice';
// todosSlice.ts
import { userSlice } from './userSlice';
```
### Testing Zustand Stores
Create a test utility to create a fresh store for each test:
```typescript
// test-utils.ts
import { act } from 'react-dom/test-utils';
const createTestStore = () => {
const store = createStore();
const initialState = store.getState();
return {
store,
initialState,
setState: (partialState) => act(() => store.setState(partialState)),
getState: () => store.getState(),
};
};
// In tests
test('increment should increase count by 1', () => {
const { store, getState } = createTestStore();
act(() => {
store.getState().increment();
});
expect(getState().count).toBe(1);
});
```
### Middleware
Zustand supports middleware for adding functionality to stores:
1. **Persist**: Save and rehydrate state from storage:
```typescript
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
const useStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'count-storage',
storage: createJSONStorage(() => localStorage),
// Optionally, only persist specific parts of the state
partialize: (state) => ({ count: state.count }),
}
)
);
```
2. **Immer**: Simplify immutable state updates:
```typescript
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const useStore = create(
immer((set) => ({
todos: [],
addTodo: (text) => set((state) => {
// Can write "mutating" code thanks to Immer
state.todos.push({ id: Date.now(), text, completed: false });
}),
}))
);
```
3. **Devtools**: Connect to Redux DevTools for debugging:
```typescript
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create(
devtools((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);
```
4. **subscribeWithSelector**: Enable selective subscriptions:
```typescript
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
const useStore = create(
subscribeWithSelector((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);
// Subscribe only to changes in the count property
useStore.subscribe(
(state) => state.count,
(count) => console.log(`Count changed to ${count}`)
);
```
#### Combining Middleware
You can combine multiple middleware by nesting them:
```typescript
import { create } from 'zustand';
import { devtools, persist, createJSONStorage } from 'zustand/middleware';
const useStore = create(
devtools(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'count-storage',
storage: createJSONStorage(() => localStorage),
}
),
{ name: 'Count Store' }
)
);
```
Note that the order of middleware matters. In the example above, devtools wraps persist, so you'll see the persist actions in the Redux DevTools.
### Styling
The project uses Material UI with emotion for styling:
- Theme customization is in the `src/theme` directory
- Components use MUI's styling system
## Implementation Patterns
The following patterns are commonly used in the CAD project:
### Memoized Selectors
Use memoization to optimize selectors that compute derived state from the store.
```typescript
// Simple memoization function
function memoize<T, R>(fn: (arg: T) => R): (arg: T) => R {
let lastArg: T | undefined = undefined;
let lastResult: R | undefined = undefined;
return (arg: T): R => {
// If the argument is the same object reference, return the memoized result
if (lastArg === arg) {
return lastResult as R;
}
// Otherwise, compute the new result and store it
const result = fn(arg);
lastArg = arg;
lastResult = result;
return result;
};
}
// Memoized selector
export const selectMemoizedCameraPosition = memoize((state: AppState): THREE.Vector3 =>
state.cameraPosition
);
```
**When to use**: When selecting or computing derived state from the store, especially when the computation is expensive or when referential equality is important for preventing unnecessary re-renders.
### Component Composition
Build complex components by composing smaller, focused components.
```typescript
const ComplexComponent = () => (
<Wrapper>
<Header title="Title" />
<Content>
<Sidebar />
<Main />
</Content>
<Footer />
</Wrapper>
)
```
**When to use**: When building complex UI components that can be broken down into logical, reusable parts.
### Custom Hooks
Extract stateful logic into reusable functions.
```typescript
const useModelTransform = (modelId) => {
const [position, setPosition] = useState([0, 0, 0]);
const [rotation, setRotation] = useState([0, 0, 0]);
const [scale, setScale] = useState([1, 1, 1]);
// Logic for transforming model
return { position, rotation, scale, setPosition, setRotation, setScale };
}
```
**When to use**: When the same stateful logic needs to be used across multiple components.
### State Management with Zustand
Use Zustand for global state management with minimal boilerplate.
```typescript
const useStore = create((set) => ({
models: [],
selectedModelId: null,
addModel: (model) => set((state) => ({ models: [...state.models, model] })),
selectModel: (id) => set({ selectedModelId: id })
}));
```
**When to use**: For managing global application state with minimal boilerplate.
### TypeScript Return Types
Add explicit return types to all functions, including arrow functions and useEffect cleanup functions.
```typescript
// Arrow function with return type
const handleClick = (e: React.MouseEvent<HTMLButtonElement>): void => {
e.preventDefault();
console.log('Button clicked');
};
// useEffect cleanup function with return type
useEffect(() => {
document.addEventListener('click', handleClick);
return (): void => {
document.removeEventListener('click', handleClick);
};
}, [handleClick]);
```
**When to use**: For all functions in the codebase, especially those with complex return types or when TypeScript's type inference might be ambiguous. Always add return types to functions exported from a module.
## Three.js Optimization
CAD uses Three.js for 3D rendering. The following optimization techniques should be used to ensure good performance:
### Rendering Optimizations
#### Level of Detail (LOD)
Display different mesh complexities based on camera distance.
```javascript
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);
lod.addLevel(mediumDetailMesh, 50);
lod.addLevel(lowDetailMesh, 200);
```
**When to use**: For complex models that are viewed from varying distances.
#### Instancing
Render many instances of the same geometry with different transforms.
```javascript
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
for (let i = 0; i < 1000; i++) {
const matrix = new THREE.Matrix4();
matrix.setPosition(x, y, z);
instancedMesh.setMatrixAt(i, matrix);
}
```
**When to use**: When rendering many similar objects like trees, buildings, or particles.
#### Object Merging
Combine multiple geometries into a single geometry.
```javascript
const geometries = [geo1, geo2, geo3];
const mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries(geometries);
const mesh = new THREE.Mesh(mergedGeometry, material);
```
**When to use**: For static objects that use the same material and don't need individual manipulation.
### Geometry Optimizations
#### Buffer Geometry
Use BufferGeometry instead of Geometry.
```javascript
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([...]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
```
**When to use**: Always use BufferGeometry for better performance.
#### Geometry Disposal
Dispose of geometries when no longer needed.
```javascript
function cleanup() {
geometry.dispose();
material.dispose();
texture.dispose();
}
```
**When to use**: When removing objects from the scene or replacing them.
### Material Optimizations
#### Material Reuse
Share materials between multiple meshes.
```javascript
const material = new THREE.MeshStandardMaterial();
const mesh1 = new THREE.Mesh(geo1, material);
const mesh2 = new THREE.Mesh(geo2, material);
```
**When to use**: When multiple objects have the same appearance.
#### Simpler Materials
Use simpler materials when full PBR isn't needed.
```javascript
// Instead of MeshStandardMaterial
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
```
**When to use**: When lighting and reflections aren't critical.
### React Three Fiber Optimizations
#### Object References
Use refs to avoid recreating objects.
```jsx
const meshRef = useRef();
const geoRef = useRef();
<mesh ref={meshRef}>
<boxGeometry ref={geoRef} args={[1, 1, 1]} />
</mesh>
```
**When to use**: For objects that need to be accessed or modified imperatively.
#### Memoization
Memoize components and values to prevent unnecessary re-renders.
```jsx
const MemoizedComponent = React.memo(({ position }) => (
<mesh position={position}>
<boxGeometry />
</mesh>
));
const position = useMemo(() => [x, y, z], [x, y, z]);
```
**When to use**: For expensive computations or to prevent re-renders.
### Monitoring and Debugging
#### Stats Panel
Monitor FPS and rendering statistics.
```jsx
import { Stats } from '@react-three/drei';
<Stats />
```
**When to use**: During development to identify performance issues.
#### Performance Monitor
Monitor performance and adjust quality automatically.
```jsx
import { PerformanceMonitor } from '@react-three/drei';
<PerformanceMonitor onIncline={() => setQuality('high')} onDecline={() => setQuality('low')} />
```
**When to use**: For adaptive quality based on device performance.
## Accessibility Guidelines
CAD aims for WCAG AA compliance. The following guidelines should be followed to ensure accessibility:
### Implementation Guidelines
- Use Material UI components which have built-in accessibility features
- Ensure keyboard navigation for all interactive elements
- Provide text alternatives for 3D models and visualizations
- Test with screen readers (NVDA, JAWS, VoiceOver)
- Implement ARIA attributes for custom components
- Maintain focus management during modal dialogs and dynamic content changes
### Current Status
#### Perceivable
- ✅ Info and Relationships: Using semantic HTML elements and ARIA roles
- ✅ Contrast (Minimum): Using Material UI theme with accessible contrast ratios
- ✅ Resize Text: Using relative units for text sizing
- 🔄 Non-text Content: Need to add alt text to all 3D model thumbnails
#### Operable
- ✅ Focus Order: Tab order follows logical sequence
- ✅ Focus Visible: Using Material UI focus styles
- 🔄 Keyboard: 3D viewport navigation needs keyboard controls
#### Understandable
- ✅ Language of Page: Using lang attribute on html element
- ✅ Error Identification: Form validation with clear error messages
#### Robust
- ✅ Parsing: Valid HTML structure
- 🔄 Name, Role, Value: Need to add ARIA attributes to custom 3D controls
### Testing Tools
- axe DevTools
- Lighthouse
- WAVE
- Screen readers (NVDA, JAWS, VoiceOver)
- Keyboard-only navigation testing
## Browser Compatibility
CAD supports the following browsers with varying levels of compatibility:
### Support Levels
- **Full**: All features work as expected
- **Partial**: Core functionality works, but some features may have issues
- **Minimal**: Basic viewing works, but interactive features may not function properly
- **Unsupported**: Application does not function properly
### Desktop Browsers
| Browser | Version | Support Level | Notes |
|---------|---------|--------------|-------|
| Chrome | 90+ | Full | Recommended browser for best performance |
| Chrome | 80-89 | Full | May have minor performance issues with complex models |
| Chrome | 70-79 | Partial | Some WebGL2 features may not work properly |
| Firefox | 90+ | Full | Good performance with most features |
| Firefox | 80-89 | Partial | May have performance issues with complex models |
| Safari | 15+ | Full | WebGL2 support is good |
| Safari | 14 | Partial | Limited WebGL2 support |
| Safari | 13 | Minimal | Basic viewing only, interactive features may not work |
| Edge | 90+ (Chromium) | Full | Same as Chrome |
| Edge | Legacy (EdgeHTML) | Unsupported | Not supported |
### Mobile Browsers
| Browser | Version | Support Level | Notes |
|---------|---------|--------------|-------|
| Chrome for Android | 90+ | Full | Performance depends on device capabilities |
| Safari iOS | 15+ | Full | Good performance on newer devices |
| Safari iOS | 14 | Partial | Some WebGL limitations |
| Samsung Internet | 14+ | Full | Based on Chromium, good compatibility |
### Feature Support
| Feature | Description | Chrome | Firefox | Safari | Edge |
|---------|-------------|--------|---------|--------|------|
| WebGL2 | WebGL 2.0 support for 3D rendering | Full | Full | Partial (full in 15+) | Full |
| WebAssembly | WebAssembly support for OpenSCAD.js | Full | Full | Full | Full |
| IndexedDB | IndexedDB for client-side storage | Full | Full | Partial (size limitations) | Full |
| Web Workers | Web Workers for background processing | Full | Full | Full | Full |
### Known Issues
1. **Safari 14**: WebGL context loss with large models
- Workaround: Implement level of detail (LOD) for complex models
2. **Firefox (All versions)**: Slower performance with complex CSG operations
- Workaround: Optimize geometry before boolean operations
3. **Mobile browsers (All)**: Limited memory for large models
- Workaround: Implement progressive loading and geometry simplification
No Prompts configured
No Data configured
No MCP Servers configured