* fix(rules): scope exclusions to their rule group under TypeORM 1.0 Adopt TypeORM 1.0 where-clause null semantics (default throw; IsNull() to match NULL) instead of 0.3.x's silent-ignore footgun, fixing latent exclusion bugs surfaced by the typeorm 1.0.0 bump (#2983): - getExclusions: a group-scoped exclusion no longer leaks into other rule groups (and no longer returns duplicates); global exclusions still apply everywhere. - setExclusion: global exclusions subsume scoped ones — an item is global or scoped, never both. - removeExclusion: removing a global exclusion no longer writes a spurious collection-log entry; logs media + ownership. - getCollection: a null/blank id returns undefined instead of an arbitrary first collection. Bumps engines.node to TypeORM 1.0's floor and adds getter-property and exclusion-scoping regression coverage. * feat(ui): warn before a global exclusion replaces rule-group exclusions When adding a global exclusion for an item that already has rule-group exclusions, show a confirmation listing each "<item> — <rule group>" (the group links to its collection, reusing the backdrop's maintainerr-status data so labels/links match and stay fresh) before proceeding, since going global removes those scoped exclusions. Only triggers on the Add action, never on Remove. * test(ui): cover the global-exclusion warning in AddModal * fix(ui): clarify Add/Remove action labels in the media modal * fix(ui): don't block global exclusion when warning prefetch fails
15 KiB
Maintainerr - Custom Copilot Instructions
Important: Workspace Commands
When running yarn commands (build, test, etc.), always execute from the workspace root:
cd /workspaces/Maintainerr <--- ensure you are in the root workspace
yarn build | tail -20
yarn test | tail -20
Do NOT run from subdirectories unless specifically needed for package-specific commands.
Project Overview
Maintainerr is a media management application that helps users automatically manage their media libraries by creating rules to handle unused or unwatched content. It integrates with Plex, Jellyfin, Emby, *arr applications (Radarr/Sonarr), Overseerr/Jellyseerr, Tautulli, and Streamystats (Jellyfin only) to provide comprehensive media lifecycle management.
For the broader system architecture map, see ARCHITECTURE.md.
Documentation map
Standing rules — read before writing any code (they apply to all work):
- implementation.instructions.md — implementation rules and API-doc references.
- project-notes.instructions.md — non-obvious project knowledge, conventions, and gotchas (rule engine, Tailwind v4, migrations, naming) that isn't derivable from the code or git history.
Task-specific — read only when the task calls for it (don't load them every session):
- release-review.instructions.md — when auditing a release candidate before tagging.
- ARCHITECTURE.md — before changing cross-module boundaries.
When you add a doc, list it under the matching heading here. For how each agent (Claude, Copilot, Cursor, Codex) loads these docs, see README_AGENTS.md.
Repository Structure
This is a TypeScript monorepo managed with Turborepo and Yarn workspaces:
├── apps/
│ ├── server/ # Nest.js backend API
│ └── ui/ # Vite + React Router frontend application
├── packages/
│ └── contracts/ # Shared TypeScript types, DTOs, and interfaces
├── package.json # Root package with Turborepo scripts
└── turbo.json # Turborepo configuration
Technology Stack
Backend (apps/server/)
- Framework: Nest.js v11+ with TypeScript
- Database: TypeORM 1.x with SQLite (
better-sqlite3driver) - Testing: Jest with @suites for dependency mocking and unit testing
- API Documentation: Swagger/OpenAPI
- Validation: Zod v4+ schemas with nestjs-zod
- Architecture: Event-driven with schedulers and graceful shutdown support. See ARCHITECTURE.md for the system-level overview.
Frontend (apps/ui/)
- Build System: Vite 8+ for fast development and production builds
- Framework: React 19+ with React Router 7+ for client-side routing
- Styling: TailwindCSS with Headless UI components
- State Management: TanStack Query (React Query)
- Forms: React Hook Form with Zod validation
- UI Components: Custom components with Heroicons
- Page Metadata: React 19 Document Metadata support
- Environment Variables: Vite environment variables (import.meta.env.VITE_*)
Shared (packages/contracts/)
- Purpose: Shared TypeScript types, DTOs, validation schemas
- Technologies: Zod schemas, class-validator decorators, Nest.js decorators
Development Workflow
Key Commands
# Install dependencies
yarn install
# Development (all packages)
yarn dev
# Build entire project
yarn build
# Lint all packages
yarn lint
# Format code
yarn format
# Run tests
yarn test
# Type checking
yarn check-types
CI Workflow Commands
GitHub quality workflows include a separate YAML lint job:
yamllint -s .
The formatting, TypeScript lint, and test workflows use Node.js 24, then run:
corepack install
corepack enable
yarn --immutable
yarn format:check
yarn lint
yarn turbo build --filter="./packages/*"
yarn turbo test
The development Docker workflow builds the multi-stage Dockerfile for
linux/amd64 and linux/arm64.
Workspace MCP Servers
Project MCP server config lives in .codex/config.toml for Codex, .mcp.json
for Claude Code, and .vscode/mcp.json for VS Code. Keep all three files in
sync when adding or changing servers. The configured GitHub MCP server is
read-only, and Playwright MCP screenshots should be written under
.playwright-mcp/.
Package-Specific Commands
# Server development
yarn workspace @maintainerr/server dev
# UI development
yarn workspace @maintainerr/ui dev
# Contracts build (required by server/ui)
yarn workspace @maintainerr/contracts build
Coding Standards
Code Style
- ESLint: Strict TypeScript rules across all packages
- Prettier: Consistent formatting (run
yarn format) - Commits: Follow Conventional Commits specification. Commit types map to releases via semantic-release:
feat:→ minor releasefix:,perf:→ patch releaserefactor:,chore:,docs:,style:,test:→ no releaseBREAKING CHANGEfooter or!(e.g.feat!:) → major release
- Import Organization: Prefer absolute imports, group by type (external, internal, relative)
- String Handling: Avoid regex for simple prefix/suffix/substring checks or single-character trimming. Prefer string primitives such as
endsWith,startsWith,slice,substring, or direct character inspection to reduce unnecessary regex risk; see OWASP ReDoS guidance. Example:fix: avoid regex backtracking in disk path normalization (#2526).
TypeScript Guidelines
- Use strict type checking
- Prefer
interfacefor object shapes,typefor unions/computed types - Use Zod schemas for runtime validation (especially in contracts package)
- Leverage Nest.js decorators for API documentation and validation
Architecture Patterns
Backend Patterns
- Controllers: Handle HTTP requests, delegate to services
- Services: Business logic, integrate with external APIs
- DTOs: Use Zod schemas from contracts package
- Entities: TypeORM entities for database models
- Modules: Feature-based module organization
- Events: Use Nest.js EventEmitter for decoupled communication
Frontend Patterns
- Routing: React Router with
createBrowserRouterfor declarative routing - Routes: Explicit route configuration in
/src/router.tsxwith nested route support - Lazy Loading: Prefer route-level lazy loading for non-shell pages and lazy-load heavy optional UI dependencies such as Monaco-backed editors or markdown renderers behind shared loading boundaries
- Components: Reusable UI components in
/src/components - Hooks: Custom hooks for data fetching (TanStack Query) and navigation (useNavigate, useLocation)
- Forms: React Hook Form with Zod resolvers
- API: Axios client with TypeScript contracts
- Page Metadata: React Helmet Async for managing
<head>elements declaratively - Code Splitting: React.lazy with Suspense for dynamic imports
Shared Contracts
- DTOs: Request/response shapes with validation
- Events: Type-safe event definitions
- Enums: Shared constants and enumerations
File Organization
Server Structure
apps/server/src/
├── modules/ # Feature modules (collections, rules, settings, etc.)
├── common/ # Shared utilities, decorators, filters
├── database/ # TypeORM entities and migrations
├── events/ # Event definitions and handlers
└── main.ts # Application bootstrap
UI Structure
apps/ui/src/
├── components/ # Reusable UI components
├── hooks/ # Custom React hooks
├── pages/ # Page components for routes (DocsPage, PlexLoadingPage)
├── utils/ # Client-side utilities
├── test-utils/ # Vitest helpers (TanStack Query result builders, shared QueryClient)
├── styles/ # Global styles and Tailwind config
├── router.tsx # React Router configuration with route definitions
└── main.tsx # Application entry point
When mocking TanStack Query hooks in specs, use the result builders in
apps/ui/src/test-utils/queryResults.ts
(buildQuerySuccessResult, buildQueryLoadingResult, buildQueryErrorResult)
instead of casting partial objects with as unknown as ReturnType<typeof useFoo>.
See apps/ui/src/test-utils/README.md.
Refactoring Guidelines
When modifying existing code, follow these specific refactoring priorities:
Forms and UI Components
- React Hook Forms Migration: Forms that do not use React Hook Form should be refactored to use it, along with their Zod validation schemas and corresponding DTOs from the contracts package. See PR #1871 as a reference example.
- Form Components: Use existing form components from
apps/ui/src/components/Forms/(Input, Select, etc.) and create new ones in this directory if necessary, following the same patterns. - Component Naming: Component file names should follow the exported type name(s) rather than using generic
index.tsxfiles. For example, useUserSettingsForm.tsxinstead ofindex.tsx.
Server-Side Type Safety
- Strict Typing Evolution: Extra care should be taken in the server project. While strict type checking is not currently enabled (
strictNullChecks: false,noImplicitAny: false), we are moving toward stricter standards. - Type Specifications: When creating new code, specify explicit types including
undefinedwhen applicable, rather than relying on computed types. - Any Type Elimination: When encountering
anytypes during code changes, attempt to use proper types or create specific type definitions. This includes external API requests and responses. - Gradual Migration: Incrementally improve type safety without breaking existing functionality.
- Logging: In injectable server code, prefer
MaintainerrLoggerover raw NestLogger. Set the context once in the constructor. For paired caught-error logs, keepwarn/log/errormessages plain and put the throwable onlogger.debug(error); only uselogger.error('message', error)when the higher-level log should intentionally carry the throwable.
External Integrations
The application integrates with several external services:
- Plex: Media server API for collections and metadata
- Jellyfin/Emby: Media server APIs through the shared media-server abstraction
- Radarr/Sonarr: Movie/TV show management APIs
- Overseerr/Jellyseerr: Request management systems
- Tautulli: Plex analytics and statistics
- Streamystats: Jellyfin item-level analytics surfaced on media details
When working with these integrations:
- Use proper error handling and retry logic (axios-retry)
- Implement caching where appropriate (node-cache)
- Follow rate limiting best practices
- Use TypeScript interfaces for external API responses
External API Documentation
Reference the following OpenAPI specifications and API documentation when working with external service integrations:
Media Management Services
- Sonarr: OpenAPI Specification
- Radarr: OpenAPI Specification
- Tautulli: API Reference Documentation
Request Management Services
- Overseerr: API Documentation
- Jellyseerr: OpenAPI Specification
Media Server Services
These specifications provide comprehensive type definitions and endpoint documentation for creating robust integrations with proper TypeScript typing.
Testing Guidelines
Backend Testing
- Unit Tests: Jest with @suites for dependency mocking
- File Pattern:
*.spec.tsfiles alongside source code - Coverage: Focus on service logic and complex business rules
- Test Database: Use in-memory SQLite for integration tests
Frontend Testing
- Test Runner: Vitest with React Testing Library
- Keep tests focused on critical user flows
Local dev mocks & seeding (manual / Playwright testing)
For end-to-end checks of media-server-dependent flows (rules, collections,
overview, calendar, storage) without a real Plex/Jellyfin, the tools/dev/ folder
has three scripts that complement Playwright — Playwright drives the UI, these
provide the backend data:
tools/dev/fake-jellyfin.mjs— stateless mock Jellyfin (:8096).tools/dev/fake-plex.mjs— stateless mock Plex (:32400); covers the Plex-only getter paths (smart collections, watch history, accounts, ratings, shows/seasons/episodes) that the Jellyfin mock can't.tools/dev/seed-db.mjs— the only DB-touching script. Seeds settings, collections, and rule groups with rules covering ~all rule properties, plus notifications, cron, logs, exclusions, and overlays. Target a server withMEDIA_SERVER=plex|jellyfin(defaultjellyfin).
Workflow: start the matching mock, stop yarn dev (SQLite is single-writer),
run the seed, restart yarn dev. Inspect a getter's live output with
POST /api/rules/test {"mediaId","rulegroupId"} or run a rule with
POST /api/rules/:id/execute. Note: after editing server code, restart
yarn dev — a long-lived dev server can serve stale getter logic. Watchlist
and plex.tv user enrichment can't be mocked locally (they hit plex.tv) and
degrade gracefully.
Development Notes
Environment Setup
- Node.js: Version 20.19.0+, 22.13.0+, or 24.11.0+ (the floor is set by TypeORM 1.0.0's engine requirement; the Docker image ships Node 26)
- Package Manager: Yarn 4.11 (managed via corepack)
- Data Directory: Requires
data/folder with proper permissions for development
Key Configuration Files
turbo.json: Turborepo task configuration and cachingtsconfig.json: TypeScript configuration per package.yarnrc.yml: Yarn package manager configuration- Docker support available via
Dockerfile
Performance Considerations
- Use Turborepo caching for faster builds
- Leverage Vite's fast HMR and optimized production builds
- Implement proper database indexing in TypeORM entities
- Use React Query for efficient data fetching and caching
- Use React.lazy with Suspense for code splitting
- Optimize images and assets appropriately
Contributing Guidelines
Before contributing:
- Read
CONTRIBUTING.mdfor detailed guidelines - Follow the branching strategy (meaningful branch names)
- Ensure all tests pass:
yarn test - Verify linting:
yarn lint - Format code:
yarn format - Use conventional commit messages
Before suggesting or writing any code, you must read and follow .github/instructions/implementation.instructions.md.