Files
enoch85 787e30d0e1 fix(rules): scope exclusions to their rule group under TypeORM 1.0 (#2991)
* 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
2026-05-27 23:32:11 +02:00

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):

Task-specific — read only when the task calls for it (don't load them every session):

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-sqlite3 driver)
  • 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 release
    • fix:, perf: → patch release
    • refactor:, chore:, docs:, style:, test: → no release
    • BREAKING CHANGE footer 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 interface for object shapes, type for 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 createBrowserRouter for declarative routing
  • Routes: Explicit route configuration in /src/router.tsx with 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.tsx files. For example, use UserSettingsForm.tsx instead of index.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 undefined when applicable, rather than relying on computed types.
  • Any Type Elimination: When encountering any types 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 MaintainerrLogger over raw Nest Logger. Set the context once in the constructor. For paired caught-error logs, keep warn/log/error messages plain and put the throwable on logger.debug(error); only use logger.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

Request Management Services

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.ts files 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 with MEDIA_SERVER=plex|jellyfin (default jellyfin).

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 caching
  • tsconfig.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:

  1. Read CONTRIBUTING.md for detailed guidelines
  2. Follow the branching strategy (meaningful branch names)
  3. Ensure all tests pass: yarn test
  4. Verify linting: yarn lint
  5. Format code: yarn format
  6. Use conventional commit messages

Before suggesting or writing any code, you must read and follow .github/instructions/implementation.instructions.md.