* 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.
13 KiB
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 unittestto 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
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 informationinfo: Important runtime events (startup, shutdown, state changes)warning: Recoverable issues that should be addressederror: Errors that affect functionality but don't crash the appexception: 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:
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:
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()nottime.sleep() - Avoid awaiting in loops - use
asyncio.gather()instead - No blocking calls in async functions
- Use
asyncio.create_task()for background operations
- Avoid sleeping in loops - use
- Thread Safety: Use proper synchronization for shared state
Documentation Standards
-
Module Docstrings: Concise descriptions at top of files
"""Utilities for motion detection and analysis.""" -
Function Docstrings: Required for public functions and methods
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()functionimport { 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: errorreact-hooks/exhaustive-deps: errorno-console: error (use proper logging or remove)@typescript-eslint/no-explicit-any: warn (always use proper types instead ofany)- 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
TestCaseclasses with descriptive test method namesclass 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
# 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)
# 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.
# Build local image
make local
# Build debug image
make debug
Common Patterns
API Endpoint Pattern
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
# Access Frigate configuration
config: FrigateConfig = request.app.frigate_config
camera_config = config.cameras["front_door"]
Database Queries
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
# 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
# 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