Repository navigation
Design Decisions
Green means green. MapArr will never report a false positive. If the dashboard shows a healthy stack, it IS healthy. Severity signals are honest — we'd rather miss an edge case than cry wolf.
Lead with what it means for the user, one plain sentence before technical detail. Let the user move at their own pace. The CONFLICT_HANDRAILS constant in app.js enforces this.
The frontend is vanilla JavaScript (~8.5K LOC, single file). No React, no Vue, no build step. This is deliberate:
- Zero dependency risk — no npm supply chain concerns
- Instant load — no bundler, no tree-shaking needed
- Simple deployment — copy files, done
- The UI is a diagnostic tool, not a web app. It doesn't need component lifecycle management.
The trade-off is that app.js is large and will be split into ES modules in Stage 3 (task F1).
All state is ephemeral. MapArr reads compose files, analyzes them, and reports results. Nothing is persisted server-side. Frontend uses localStorage for UI preferences (collapsed sections, dismissed warnings, wizard completion).
This means:
- No migration headaches
- No backup concerns
- No data corruption risk
- Fresh analysis every time (always current)
27 services are "core" (arr apps + download clients + media servers). Everything else is "other" — shown on the dashboard for awareness but NOT analyzed.
Why not analyze everything? Because reclassifying services into core roles pulls them into the analysis pipeline, which generates noise conflicts. An Nginx reverse proxy doesn't need PUID/PGID analysis. Overseerr doesn't need path conflict detection.
The 27-service list is documented in README.md "Supported Services".
CONFLICT_CATEGORIES in analyzer.py is the single source of truth for all routing:
- Category A (path) → volume YAML tab, Apply Fix generates corrected volumes
- Category B (permission) → environment YAML tab, Apply Fix generates corrected env vars
- Category C (infrastructure) → guidance tab, no fix generated
- Category D (observation) → collapsed info section, no fix generated
Adding a new conflict type means adding it to this dict. The category determines everything downstream: tab selection, fix generation, health impact, UI rendering.
The Remote Path Mapping wizard is gated by TWO conditions:
- Must be a Category A conflict (path issue)
-
_calculate_rpm_mappings()must returnpossible=True
Condition 2 requires host path overlap — one service's host path must be a parent or child of another's. Sibling paths (e.g., /mnt/downloads and /mnt/media) cannot be bridged by RPM. This prevents the wizard from appearing when it can't actually help.
Before any destructive action (Apply Fix, Restart), cross-check inputs against known state. Warn mid-action. Always offer a "proceed anyway" escape hatch. The Revert button is the ultimate safety net.
After Apply Fix modifies a compose file:
- mtime-based staleness detection triggers
- Inline rescan runs automatically
- All media services expand in dashboard (pipeline override mode)
- Redeploy banner persists until restart confirmed
Different image families use different environment variable names for the same concept:
- LinuxServer.io:
PUID,PGID - Hotio:
PUID,PGID(same) - jlesage:
USER_ID,GROUP_ID - Plex:
PLEX_UID,PLEX_GID
_get_service_env_var_names() resolves the correct var names per family before generating fixes.
| Decision | Trade-off | Why |
|---|---|---|
| Single-file frontend | Hard to navigate | Will split in v1.6 (F1). Currently manageable. |
| No Docker SDK dependency | Can't inspect running containers | Keeps install simple. Docker socket optional. |
| YAML-only analysis | Misses runtime state | Compose files are the source of truth for path config. Runtime adds complexity without proportional value. |
| No multi-user support | Single-user tool | This is a local diagnostic tool, not a SaaS. Session locking deferred to v1.6 (B3). |
| No persistence | Fresh scan every time | Ensures results are always current. Cache planned for v1.6 (B4). |