mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-26 00:58:59 +03:00
* migrate web to eslint 10 flat config ESLint 10 dropped `.eslintrc` support, so `.eslintrc.cjs` is replaced with `eslint.config.js` and the lint scripts no longer pass `--ext` or `--ignore-path`. typescript-eslint moves to 8, react-hooks to 7, and react-refresh to 0.5, and the unused jest and vitest-globals plugins are removed. Lint behaves as it did before: catch variables aren't checked, unused disable directives aren't reported, and rules newly added to the recommended sets are off until the code passes them. typescript-eslint 8 flags constants used only in `typeof`, so those are now exported, or replaced with a union type where the export would trip react-refresh. * fix lint findings from the eslint 10 recommended rules Remove the rule overrides from the flat config migration and fix what they were hiding. Unused catch bindings are dropped, 20 disable directives that suppressed nothing are removed (react-hooks 5.2 and 7.1.1 report identical exhaustive-deps findings with inline config ignored), dead initial values are dropped, short-circuit calls become if statements or optional calls, rethrown errors pass `cause`, and the disabled "No recordings" tooltip in `ReviewTimeline` is removed along with its memo and the `getRecordingAvailability` prop. The 3 react-refresh warnings for files that export contexts or classes are left for a later refactor.
451 lines
13 KiB
Markdown
451 lines
13 KiB
Markdown
# Agent Instructions for Frigate NVR
|
|
|
|
This document provides coding guidelines and best practices for contributing to Frigate NVR, a complete and local NVR designed for Home Assistant with AI object detection.
|
|
|
|
## Project Overview
|
|
|
|
Frigate NVR is a realtime object detection system for IP cameras that uses:
|
|
|
|
- **Backend**: Python 3.13+ with FastAPI, OpenCV, TensorFlow/ONNX
|
|
- **Frontend**: React with TypeScript, Vite, TailwindCSS
|
|
- **Architecture**: Multiprocessing design with ZMQ and MQTT communication
|
|
- **Focus**: Minimal resource usage with maximum performance
|
|
|
|
## Code Review Guidelines
|
|
|
|
When reviewing code, do NOT comment on:
|
|
|
|
- Missing imports - Static analysis tooling catches these
|
|
- Code formatting - Ruff (Python) and Prettier (TypeScript/React) handle formatting
|
|
- Minor style inconsistencies already enforced by linters
|
|
|
|
## Python Backend Standards
|
|
|
|
### Python Requirements
|
|
|
|
- **Compatibility**: Python 3.13+
|
|
- **Language Features**: Use modern Python features:
|
|
- Pattern matching
|
|
- Type hints (comprehensive typing preferred)
|
|
- f-strings (preferred over `%` or `.format()`)
|
|
- Dataclasses
|
|
- Async/await patterns
|
|
|
|
### Code Quality Standards
|
|
|
|
- **Formatting**: Ruff (configured in `pyproject.toml`)
|
|
- **Linting**: Ruff with rules defined in project config
|
|
- **Type Checking**: Use type hints consistently
|
|
- **Testing**: unittest framework - use `python3 -u -m unittest` to run tests
|
|
- **Language**: American English for all code, comments, and documentation
|
|
- **Punctuation**: Do not use em dashes in documentation, comments, or strings; reword with standard punctuation (commas, colons, parentheses, or separate sentences)
|
|
|
|
### Logging Standards
|
|
|
|
- **Logger Pattern**: Use module-level logger
|
|
|
|
```python
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
```
|
|
|
|
- **Format Guidelines**:
|
|
- No periods at end of log messages
|
|
- No sensitive data (keys, tokens, passwords)
|
|
- Use lazy logging: `logger.debug("Message with %s", variable)`
|
|
- **Log Levels**:
|
|
- `debug`: Development and troubleshooting information
|
|
- `info`: Important runtime events (startup, shutdown, state changes)
|
|
- `warning`: Recoverable issues that should be addressed
|
|
- `error`: Errors that affect functionality but don't crash the app
|
|
- `exception`: Use in except blocks to include traceback
|
|
|
|
### Error Handling
|
|
|
|
- **Exception Types**: Choose most specific exception available
|
|
- **Try/Catch Best Practices**:
|
|
- Only wrap code that can throw exceptions
|
|
- Keep try blocks minimal - process data after the try/except
|
|
- Avoid bare exceptions except in background tasks
|
|
|
|
Bad pattern:
|
|
|
|
```python
|
|
try:
|
|
data = await device.get_data() # Can throw
|
|
# ❌ Don't process data inside try block
|
|
processed = data.get("value", 0) * 100
|
|
result = processed
|
|
except DeviceError:
|
|
logger.error("Failed to get data")
|
|
```
|
|
|
|
Good pattern:
|
|
|
|
```python
|
|
try:
|
|
data = await device.get_data() # Can throw
|
|
except DeviceError:
|
|
logger.error("Failed to get data")
|
|
return
|
|
|
|
# ✅ Process data outside try block
|
|
processed = data.get("value", 0) * 100
|
|
result = processed
|
|
```
|
|
|
|
### Async Programming
|
|
|
|
- **External I/O**: All external I/O operations must be async
|
|
- **Best Practices**:
|
|
- Avoid sleeping in loops - use `asyncio.sleep()` not `time.sleep()`
|
|
- Avoid awaiting in loops - use `asyncio.gather()` instead
|
|
- No blocking calls in async functions
|
|
- Use `asyncio.create_task()` for background operations
|
|
- **Thread Safety**: Use proper synchronization for shared state
|
|
|
|
### Documentation Standards
|
|
|
|
- **Module Docstrings**: Concise descriptions at top of files
|
|
```python
|
|
"""Utilities for motion detection and analysis."""
|
|
```
|
|
- **Function Docstrings**: Required for public functions and methods
|
|
|
|
```python
|
|
async def process_frame(frame: ndarray, config: Config) -> Detection:
|
|
"""Process a video frame for object detection.
|
|
|
|
Args:
|
|
frame: The video frame as numpy array
|
|
config: Detection configuration
|
|
|
|
Returns:
|
|
Detection results with bounding boxes
|
|
"""
|
|
```
|
|
|
|
- **Comment Style**:
|
|
- Explain the "why" not just the "what"
|
|
- Keep lines under 88 characters when possible
|
|
- Use clear, descriptive comments
|
|
|
|
### File Organization
|
|
|
|
- **API Endpoints**: `frigate/api/` - FastAPI route handlers
|
|
- **Configuration**: `frigate/config/` - Configuration parsing and validation
|
|
- **Detectors**: `frigate/detectors/` - Object detection backends
|
|
- **Events**: `frigate/events/` - Event management and storage
|
|
- **Utilities**: `frigate/util/` - Shared utility functions
|
|
|
|
## Frontend (React/TypeScript) Standards
|
|
|
|
### Internationalization (i18n)
|
|
|
|
- **CRITICAL**: Never write user-facing strings directly in components
|
|
- **Always use react-i18next**: Import and use the `t()` function
|
|
|
|
```tsx
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
function MyComponent() {
|
|
const { t } = useTranslation(["views/live"]);
|
|
return <div>{t("camera_not_found")}</div>;
|
|
}
|
|
```
|
|
|
|
- **Translation Files**: Add English strings to the appropriate json files in `web/public/locales/en`
|
|
- **Namespaces**: Organize translations by feature/view (e.g., `views/live`, `common`, `views/system`)
|
|
|
|
### Code Quality
|
|
|
|
- **Linting**: ESLint (see `web/eslint.config.js`)
|
|
- **Formatting**: Prettier with Tailwind CSS plugin
|
|
- **Type Safety**: TypeScript strict mode enabled
|
|
|
|
### Component Patterns
|
|
|
|
- **UI Components**: Use Radix UI primitives (in `web/src/components/ui/`)
|
|
- **Styling**: TailwindCSS with `cn()` utility for class merging
|
|
- **State Management**: React hooks (useState, useEffect, useCallback, useMemo)
|
|
- **Data Fetching**: Custom hooks with proper loading and error states
|
|
|
|
### ESLint Rules
|
|
|
|
Key rules enforced:
|
|
|
|
- `react-hooks/rules-of-hooks`: error
|
|
- `react-hooks/exhaustive-deps`: error
|
|
- `no-console`: error (use proper logging or remove)
|
|
- `@typescript-eslint/no-explicit-any`: warn (always use proper types instead of `any`)
|
|
- Unused variables must be prefixed with `_`
|
|
- Comma dangles required for multiline objects/arrays
|
|
|
|
### File Organization
|
|
|
|
- **Pages**: `web/src/pages/` - Route components
|
|
- **Views**: `web/src/views/` - Complex view components
|
|
- **Components**: `web/src/components/` - Reusable components
|
|
- **Hooks**: `web/src/hooks/` - Custom React hooks
|
|
- **API**: `web/src/api/` - API client functions
|
|
- **Types**: `web/src/types/` - TypeScript type definitions
|
|
|
|
## Testing Requirements
|
|
|
|
### Backend Testing
|
|
|
|
- **Framework**: Python unittest
|
|
- **Run Command**: `python3 -u -m unittest`
|
|
- **Location**: `frigate/test/`
|
|
- **Coverage**: Aim for comprehensive test coverage of core functionality
|
|
- **Pattern**: Use `TestCase` classes with descriptive test method names
|
|
```python
|
|
class TestMotionDetection(unittest.TestCase):
|
|
def test_detects_motion_above_threshold(self):
|
|
# Test implementation
|
|
```
|
|
|
|
### Test Best Practices
|
|
|
|
- Always have a way to test your work and confirm your changes
|
|
- Write tests for bug fixes to prevent regressions
|
|
- Test edge cases and error conditions
|
|
- Mock external dependencies (cameras, APIs, hardware)
|
|
- Use fixtures for test data
|
|
|
|
## Development Commands
|
|
|
|
### Python Backend
|
|
|
|
```bash
|
|
# Run all tests
|
|
python3 -u -m unittest
|
|
|
|
# Run specific test file
|
|
python3 -u -m unittest frigate.test.test_ffmpeg_presets
|
|
|
|
# Check formatting (Ruff)
|
|
ruff format --check frigate/
|
|
|
|
# Apply formatting
|
|
ruff format frigate/
|
|
|
|
# Run linter
|
|
ruff check frigate/
|
|
|
|
# Type check
|
|
python3 -u -m mypy --config-file frigate/mypy.ini frigate
|
|
|
|
# Regenerate the OpenAPI spec after adding, changing, or removing an API
|
|
# endpoint or its auth dependency — outputs docs/static/frigate-api.yaml,
|
|
# annotated with each endpoint's auth requirement (admin / any / camera /
|
|
# public). NEVER edit that file by hand. CI runs the --check variant and fails
|
|
# if it is out of date. (from repo root)
|
|
python3 generate_api_auth_spec.py
|
|
python3 generate_api_auth_spec.py --check
|
|
```
|
|
|
|
### Frontend (from web/ directory)
|
|
|
|
```bash
|
|
# Start dev server (AI agents should never run this directly unless asked)
|
|
npm run dev
|
|
|
|
# Build for production
|
|
npm run build
|
|
|
|
# Run linter
|
|
npm run lint
|
|
|
|
# Fix linting issues
|
|
npm run lint:fix
|
|
|
|
# Format code
|
|
npm run prettier:write
|
|
|
|
# E2E: first-time setup
|
|
npm install
|
|
npx playwright install chromium
|
|
|
|
# E2E: build the app and run all tests
|
|
npm run e2e:build && npm run e2e
|
|
|
|
# E2E: interactive UI for debugging
|
|
npm run e2e:ui
|
|
|
|
# E2E: run a specific spec
|
|
npx playwright test --config e2e/playwright.config.ts e2e/specs/live.spec.ts
|
|
|
|
# E2E: filter by name, or run only desktop/mobile
|
|
npx playwright test --config e2e/playwright.config.ts --grep="severity tab"
|
|
npx playwright test --config e2e/playwright.config.ts --project=desktop
|
|
|
|
# E2E: regenerate mock data after backend model changes (from repo root)
|
|
PYTHONPATH=. python3 web/e2e/fixtures/mock-data/generate-mock-data.py
|
|
|
|
# Regenerate config translations from Pydantic models — outputs to
|
|
# web/public/locales/en/config/{global,cameras}.json. NEVER edit those
|
|
# JSON files by hand; change the Pydantic field title/description and
|
|
# re-run this script. (from repo root)
|
|
python3 generate_config_translations.py
|
|
|
|
# Extract i18n keys from source into the locale files after adding
|
|
# new t() calls. Use the :ci variant to verify the locale files are
|
|
# in sync with source (fails if extraction would change anything).
|
|
npm run i18n:extract
|
|
npm run i18n:extract:ci
|
|
```
|
|
|
|
### Docker Development
|
|
|
|
AI agents should never run these commands directly unless instructed.
|
|
|
|
```bash
|
|
# Build local image
|
|
make local
|
|
|
|
# Build debug image
|
|
make debug
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### API Endpoint Pattern
|
|
|
|
```python
|
|
from fastapi import APIRouter, Request
|
|
from frigate.api.defs.tags import Tags
|
|
|
|
router = APIRouter(tags=[Tags.Events])
|
|
|
|
@router.get("/events")
|
|
async def get_events(request: Request, limit: int = 100):
|
|
"""Retrieve events from the database."""
|
|
# Implementation
|
|
```
|
|
|
|
After adding, changing, or removing an endpoint (or its auth dependency), regenerate the OpenAPI spec with `python3 generate_api_auth_spec.py` so `docs/static/frigate-api.yaml` stays in sync and the endpoint's auth requirement is documented. CI enforces this via the `--check` variant; never edit that file by hand.
|
|
|
|
### Configuration Access
|
|
|
|
```python
|
|
# Access Frigate configuration
|
|
config: FrigateConfig = request.app.frigate_config
|
|
camera_config = config.cameras["front_door"]
|
|
```
|
|
|
|
### Database Queries
|
|
|
|
```python
|
|
from frigate.models import Event
|
|
|
|
# Use Peewee ORM for database access
|
|
events = (
|
|
Event.select()
|
|
.where(Event.camera == camera_name)
|
|
.order_by(Event.start_time.desc())
|
|
.limit(limit)
|
|
)
|
|
```
|
|
|
|
## Common Anti-Patterns to Avoid
|
|
|
|
### ❌ Avoid These
|
|
|
|
```python
|
|
# Blocking operations in async functions
|
|
data = requests.get(url) # ❌ Use async HTTP client
|
|
time.sleep(5) # ❌ Use asyncio.sleep()
|
|
|
|
# Hardcoded strings in React components
|
|
<div>Camera not found</div> # ❌ Use t("camera_not_found")
|
|
|
|
# Missing error handling
|
|
data = await api.get_data() # ❌ No exception handling
|
|
|
|
# Bare exceptions in regular code
|
|
try:
|
|
value = await sensor.read()
|
|
except Exception: # ❌ Too broad
|
|
logger.error("Failed")
|
|
|
|
# Returning exceptions in JSON responses
|
|
except ValueError as e:
|
|
return JSONResponse(
|
|
content={"success": False, "message": str(e)},
|
|
)
|
|
```
|
|
|
|
### ✅ Use These Instead
|
|
|
|
```python
|
|
# Async operations
|
|
import aiohttp
|
|
async with aiohttp.ClientSession() as session:
|
|
async with session.get(url) as response:
|
|
data = await response.json()
|
|
|
|
await asyncio.sleep(5) # ✅ Non-blocking
|
|
|
|
# Translatable strings in React
|
|
const { t } = useTranslation();
|
|
<div>{t("camera_not_found")}</div> # ✅ Translatable
|
|
|
|
# Proper error handling
|
|
try:
|
|
data = await api.get_data()
|
|
except ApiException as err:
|
|
logger.error("API error: %s", err)
|
|
raise
|
|
|
|
# Specific exceptions
|
|
try:
|
|
value = await sensor.read()
|
|
except SensorException as err: # ✅ Specific
|
|
logger.exception("Failed to read sensor")
|
|
|
|
# Safe error responses
|
|
except ValueError:
|
|
logger.exception("Invalid parameters for API request")
|
|
return JSONResponse(
|
|
content={
|
|
"success": False,
|
|
"message": "Invalid request parameters",
|
|
},
|
|
)
|
|
```
|
|
|
|
## WebSocket Broadcasts
|
|
|
|
Outbound WebSocket broadcasts go through a per-recipient classifier in `frigate/comms/ws.py` that enforces camera-level access. **The classifier is fail-closed: any topic it doesn't recognize is dropped for every client.** New outbound topics must be classified there or they'll silently disappear.
|
|
|
|
## Project-Specific Conventions
|
|
|
|
### Configuration Files
|
|
|
|
- Main config: `config/config.yml`
|
|
|
|
### Directory Structure
|
|
|
|
- Backend code: `frigate/`
|
|
- Frontend code: `web/`
|
|
- Docker files: `docker/`
|
|
- Documentation: `docs/`
|
|
- Database migrations: `migrations/`
|
|
|
|
### Code Style Conformance
|
|
|
|
Always conform new and refactored code to the existing coding style in the project:
|
|
|
|
- Follow established patterns in similar files
|
|
- Match indentation and formatting of surrounding code
|
|
- Use consistent naming conventions (snake_case for Python, camelCase for TypeScript)
|
|
- Maintain the same level of verbosity in comments and docstrings
|
|
|
|
## Additional Resources
|
|
|
|
- Documentation: https://docs.frigate.video
|
|
- Main Repository: https://github.com/blakeblackshear/frigate
|
|
- Home Assistant Integration: https://github.com/blakeblackshear/frigate-hass-integration
|