- AGENTS.md is the single documentation index; the Claude, Copilot, Cursor, and Codex entrypoints route to it and name the standing rules directly so they can't be missed. - Split standing rules (read every session) from task-specific docs (read on demand); scope release-review's Copilot applyTo to release artifacts so it no longer loads on every interaction. - Add project-notes.instructions.md (non-obvious project knowledge and conventions for handoff) and README_AGENTS.md (the wiring map). - Move dev mocks + DB seed to tools/dev/ (fix seed-db repo-root resolution); add the seeded-DB + Playwright step to the release-review checklist.
12 KiB
Architecture Overview
This document gives contributors a fast map of Maintainerr's architecture. It is tool-neutral and should support manual development, IDE workflows, and automation equally. It is intentionally high level: use it to find the right area of the codebase, then follow the local module, tests, and contracts for exact behaviour.
Refresh this file when adding or removing top-level workspaces, server modules, global UI providers, integration surfaces, persistence flows, or deployment entry points.
Project Structure
Maintainerr is a TypeScript monorepo managed with Turborepo and Yarn workspaces.
Maintainerr/
|-- apps/
| |-- server/ # Nest API, jobs, integrations, persistence, logs
| `-- ui/ # Vite React UI, routes, client API calls
|-- packages/
| `-- contracts/ # Shared DTOs, Zod schemas, enums, and types
|-- docs/ # Feature-level technical notes
|-- docker/ # Docker helper configuration
|-- tools/ # Release and maintenance scripts
| `-- dev/ # Local dev mocks (fake Plex/Jellyfin) and DB seed
|-- .codex/config.toml # Codex project MCP server config
|-- .mcp.json # Claude Code project MCP server config
|-- .vscode/mcp.json # VS Code MCP server config mirror
|-- Dockerfile # Multi-stage production image
|-- README.md # Product overview and installation entry point
|-- CONTRIBUTING.md # Contributor setup and process
`-- turbo.json # Workspace task graph
High-Level System Flow
flowchart LR
User["Browser user"] --> UI["React UI<br/>apps/ui"]
UI -->|"Axios REST calls"| API["Nest API<br/>apps/server"]
API -->|"SSE /api/events/stream"| UI
API --> DB[("SQLite<br/>maintainerr.sqlite")]
API --> Data[("Data directory<br/>logs, posters, overlays")]
API --> Media["Media-server abstraction"]
Media --> Plex["Plex adapter"]
Media --> Jellyfin["Jellyfin adapter"]
Media --> Emby["Emby adapter"]
API --> Servarr["Radarr / Sonarr"]
API --> Seerr["Seerr"]
API --> Tautulli["Tautulli"]
API --> Streamystats["Streamystats"]
API --> Metadata["TMDB / TVDB"]
API --> GitHub["GitHub releases"]
In production, the Nest server also serves the built UI from
apps/server/dist/ui. In development, Vite and Nest run through the workspace
yarn dev task.
Core Components
UI
apps/ui owns client routing, UI state, and API calls. It uses Vite, React,
React Router, TanStack Query, React Hook Form, TailwindCSS, and Headless UI.
src/router.tsxis the route source of truth. It uses eager shell routes and lazy feature pages with preload support.src/main.tsxwires the global provider stack:QueryClientProvider -> EventsProvider -> TaskStatusProvider -> SearchContextProvider -> RouterProvider.src/utils/ApiHandler.tsxcentralises API base path handling and Axios REST helpers.src/contexts/events-context.tsxopens the reconnecting SSE stream and exportsuseEventfor typed feature subscriptions.src/contexts/taskstatus-context.tsxcombines initial task status queries with live rule and collection handler events.src/contexts/search-context.tsxowns global search text shared across searchable list views.src/components/Common/contains shared UI primitives. Prefer existing buttons, loading boundaries, modals, tables, and feedback patterns before adding new ones.
Server
apps/server owns the Nest application, background jobs, persistence, external
integrations, and production static serving.
src/main.tsbootstraps Nest, validates the data directory, configures base path handling, Swagger, CORS, logging, and graceful shutdown.src/app/app.module.tswires TypeORM, events, static serving, and feature modules.src/modules/settings/stores user configuration and coordinates media server switching.src/modules/api/media-server/provides the server-agnostic media server interface, factory, controller, and shared utilities.src/modules/api/media-server/plex/,src/modules/api/media-server/jellyfin/, andsrc/modules/api/media-server/emby/contain server-specific adapters, constants, mappers, caching, and SDK/API calls.- Other
src/modules/api/submodules wrap integration clients and helper APIs, including Plex legacy routes, Servarr, Seerr, Tautulli, Streamystats, TMDB, TVDB, GitHub, external API, internal API, and shared request/cache helpers. src/modules/rules/evaluates rule groups against media-server and external service data.src/modules/collections/tracks matched media, exclusions, collection logs, posters, and collection handling actions.src/modules/actions/contains the Radarr and Sonarr action handlers for destructive or state-changing Servarr actions such as delete, unmonitor, and quality profile changes.src/modules/tasks/creates and tracks scheduled jobs.src/modules/events/exposes server-sent events for rule and collection job progress.src/modules/overlays/,src/modules/metadata/,src/modules/notifications/,src/modules/logging/, andsrc/modules/storage-metrics/own their respective feature areas.
Contracts
packages/contracts owns shared DTOs, Zod schemas, enums, and cross-package
types. Add request/response shapes here only when they are deliberately shared
between the UI and server. Keep new contracts minimal and validate external
input at system boundaries.
Data Stores
Maintainerr stores local state in SQLite through TypeORM.
- Development database:
data/maintainerr.sqlite. - Production database:
${DATA_DIR}/maintainerr.sqlite, defaulting to/opt/data/maintainerr.sqlite. - TypeORM loads entities with
autoLoadEntitiesand runs migrations automatically on server start. - Migration files live in
apps/server/src/database/migrations/. apps/server/src/datasource-config.tsis the TypeORM CLI data source used for migration generation and execution.- Schema changes must use the TypeORM workflow in typeorm_instructions.txt; do not hand-write SQL migrations.
The data directory also stores operational files such as logs, custom
collection posters, overlay images, original artwork backups, and other
runtime assets. The Docker image exposes /opt/data as a volume.
External Integrations
Maintainerr integrates with:
- Plex, Jellyfin, and Emby through the media-server abstraction.
- Radarr and Sonarr for unmonitoring, deleting, and quality profile actions.
- Seerr-compatible services for request cleanup.
- Tautulli for Plex analytics and rule data.
- Streamystats for Jellyfin item-level analytics surfaced on the media modal. Authentication reuses the configured Jellyfin API key. Emby is not supported upstream.
- TMDB and TVDB for metadata resolution.
- GitHub for release/version checks.
- Notification providers such as Discord, Slack, Telegram, Pushover, Gotify, ntfy, email, Pushbullet, LunaSea, and webhooks.
When changing an external integration, confirm current behaviour from the official API documentation before coding. Keep third-party secrets in settings or environment variables; never hard-code tokens or keys.
Deployment and Runtime
The production Docker image builds all workspaces, focuses production
dependencies, copies the UI build into the server distribution, and starts the
Nest server through docker/start.sh.
Important runtime environment variables include:
DATA_DIR: production data directory, default/opt/data.UI_PORT: HTTP listen port, default6246.UI_HOSTNAME: HTTP bind host, default0.0.0.0.BASE_PATH: optional subdirectory mount path for both API and UI serving.GITHUB_TOKEN: optional token for higher GitHub API rate limits.VERSION_TAGandGIT_SHA: release metadata surfaced by the app.DEBUG: influences default log level during migration seeding.
Security Notes
- Treat configured integration tokens and API keys as secrets.
- Keep user-provided URLs and external API responses validated or normalised at system boundaries.
- Prefer typed DTOs and Zod schemas for request/response data.
- Avoid logging raw secrets. Use existing secret masking helpers where available.
- Keep destructive actions explicit: delete, unmonitor, and quality-profile changes should remain tied to collection/rule configuration and action handlers.
Development and Testing
Run workspace commands from the repository root.
yarn install
yarn dev
yarn lint
yarn check-types
yarn test
yarn build
Closest quality gates should run in this order where applicable: lint, typecheck, tests, then build. For doc-only changes, a targeted Prettier check is usually enough.
GitHub CI mirrors the root workspace workflow on Node.js 24: the formatting
and TypeScript lint jobs run corepack install, corepack enable, and
yarn --immutable, while the quality workflow also includes a separate
yaml-lint job running yamllint -s .; the test workflow builds packages/*
before yarn turbo test. The development image workflow builds the production
Dockerfile for linux/amd64 and linux/arm64.
Testing conventions:
- Server tests use Jest, with
*.spec.tsfiles near the code underapps/server/src. - UI tests use Vitest and React Testing Library.
- Contracts use TypeScript checks and package-level linting.
- Project MCP server config lives in
.codex/config.toml,.mcp.json, and.vscode/mcp.json; keep them in sync. The GitHub MCP server is read-only, and Playwright screenshots should be saved under.playwright-mcp/. - End-to-end checks of media-server-dependent flows use the dev mocks and DB
seed under
tools/dev/(fake-plex.mjs/fake-jellyfin.mjs+seed-db.mjs) to drive the UI with Playwright against deterministic data; seeAGENTS.mdfor the workflow.
See CONTRIBUTING.md for setup, branching, and pull request expectations.
Architecture Guardrails
- Keep
modules/api/media-server/server-agnostic. The shared interface, factory, controller, and utilities must not import Plex or Jellyfin types. - Put server-specific logic under the matching adapter directory (
plex/,jellyfin/, oremby/). - Use
supportsFeature()for conditional media-server capabilities. - Implement every new media-server interface method for all supported media servers. Put partial support behind feature checks, not optional interface holes.
- Keep mappers focused on type conversion, not business decisions.
- Prefer shared settings feedback, loading, and button components in the UI.
- Avoid layout shift in shell and settings flows.
- Preserve established rule
nameandhumanNameconventions across media servers. - Keep migrations safe, reversible, and generated through TypeORM.
Feature References
docs/collection-poster.mddescribes custom collection poster storage, media-server support, and switch behaviour.docs/overlay-feature.mddescribes overlay templates, rendering, storage, scheduling, and provider integration.README.mddescribes product capabilities, installation, API compatibility, and supported services.
Glossary
- Arr: Shorthand for Radarr and Sonarr.
- Collection handler: Background logic that applies configured actions to media after it has spent the configured duration in a Maintainerr collection.
- Contracts: Shared package containing DTOs, schemas, enums, and types used by both UI and server.
- Data directory: Runtime directory containing the SQLite database and local files such as logs, posters, and overlays.
- Media-server abstraction: Server-side interface that lets Maintainerr support Plex, Jellyfin, and Emby without leaking their implementation details into shared code.
- Rule group: A configured set of rules that selects media and links it to a Maintainerr collection.
- Seerr: The request-management integration family covering Overseerr, Jellyseerr, and Seerr-compatible APIs.
- SSE: Server-sent events used for live rule and collection job updates.