[{"data":1,"prerenderedAt":3012},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Freference\u002Farchitecture":143,"\u002Fdocs\u002Freference\u002Farchitecture-surround":3007},[4],{"title":5,"path":6,"stem":7,"children":8,"page":32},"Docs","\u002Fdocs","docs",[9,33,58,79,112,117],{"title":10,"path":11,"stem":12,"children":13,"page":32},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002Fgetting-started",[14,18,23,28],{"title":10,"path":15,"stem":16,"order":17},"\u002Fdocs\u002Fgetting-started\u002F_dir","docs\u002Fgetting-started\u002F_dir",1,{"title":19,"path":20,"stem":21,"order":22},"Configuration Reference","\u002Fdocs\u002Fgetting-started\u002Fconfiguration","docs\u002Fgetting-started\u002Fconfiguration",2,{"title":24,"path":25,"stem":26,"order":27},"Deployment Guide","\u002Fdocs\u002Fgetting-started\u002Fdeployment","docs\u002Fgetting-started\u002Fdeployment",3,{"title":29,"path":30,"stem":31,"order":17},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","docs\u002Fgetting-started\u002Fquick-start",false,{"title":34,"path":35,"stem":36,"children":37,"page":32},"Guides","\u002Fdocs\u002Fguides","docs\u002Fguides",[38,41,45,49,54],{"title":34,"path":39,"stem":40,"order":22},"\u002Fdocs\u002Fguides\u002F_dir","docs\u002Fguides\u002F_dir",{"title":42,"path":43,"stem":44,"order":22},"Notifications","\u002Fdocs\u002Fguides\u002Fnotifications","docs\u002Fguides\u002Fnotifications",{"title":46,"path":47,"stem":48,"order":17},"Scoring Algorithm","\u002Fdocs\u002Fguides\u002Fscoring","docs\u002Fguides\u002Fscoring",{"title":50,"path":51,"stem":52,"order":53},"Sunset Mode","\u002Fdocs\u002Fguides\u002Fsunset-mode","docs\u002Fguides\u002Fsunset-mode",4,{"title":55,"path":56,"stem":57,"order":27},"Troubleshooting","\u002Fdocs\u002Fguides\u002Ftroubleshooting","docs\u002Fguides\u002Ftroubleshooting",{"title":59,"path":60,"stem":61,"children":62,"page":32},"Project","\u002Fdocs\u002Fproject","docs\u002Fproject",[63,67,71,75],{"title":59,"path":64,"stem":65,"order":66},"\u002Fdocs\u002Fproject\u002F_dir","docs\u002Fproject\u002F_dir",6,{"title":68,"path":69,"stem":70,"order":27},"Changelog","\u002Fdocs\u002Fproject\u002Fchangelog","docs\u002Fproject\u002Fchangelog",{"title":72,"path":73,"stem":74,"order":17},"Contributing","\u002Fdocs\u002Fproject\u002Fcontributing","docs\u002Fproject\u002Fcontributing",{"title":76,"path":77,"stem":78,"order":22},"Contributors","\u002Fdocs\u002Fproject\u002Fcontributors","docs\u002Fproject\u002Fcontributors",{"title":80,"path":81,"stem":82,"children":83,"page":32},"Reference","\u002Fdocs\u002Freference","docs\u002Freference",[84,87,108],{"title":80,"path":85,"stem":86,"order":27},"\u002Fdocs\u002Freference\u002F_dir","docs\u002Freference\u002F_dir",{"title":88,"path":89,"stem":90,"children":91,"page":32},"Api","\u002Fdocs\u002Freference\u002Fapi","docs\u002Freference\u002Fapi",[92,96,100,104],{"title":93,"path":94,"stem":95,"order":22},"API Reference","\u002Fdocs\u002Freference\u002Fapi\u002F_dir","docs\u002Freference\u002Fapi\u002F_dir",{"title":97,"path":98,"stem":99,"order":22},"API Examples","\u002Fdocs\u002Freference\u002Fapi\u002Fexamples","docs\u002Freference\u002Fapi\u002Fexamples",{"title":101,"path":102,"stem":103,"order":53},"API Versioning & Stability Guarantees","\u002Fdocs\u002Freference\u002Fapi\u002Fversioning","docs\u002Freference\u002Fapi\u002Fversioning",{"title":105,"path":106,"stem":107,"order":27},"Common Workflows","\u002Fdocs\u002Freference\u002Fapi\u002Fworkflows","docs\u002Freference\u002Fapi\u002Fworkflows",{"title":109,"path":110,"stem":111,"order":17},"Architecture","\u002Fdocs\u002Freference\u002Farchitecture","docs\u002Freference\u002Farchitecture",{"title":113,"path":114,"stem":115,"order":116},"Release Workflow","\u002Fdocs\u002Freleasing","docs\u002Freleasing",5,{"title":118,"path":119,"stem":120,"children":121,"order":17},"Security Policy","\u002Fdocs\u002Fsecurity","docs\u002Fsecurity\u002Findex",[122,123,127,131,134,137,140],{"title":118,"path":119,"stem":120,"order":17},{"title":124,"path":125,"stem":126,"order":53},"Security","\u002Fdocs\u002Fsecurity\u002F_dir","docs\u002Fsecurity\u002F_dir",{"title":128,"path":129,"stem":130,"order":22},"OWASP ZAP API Scan — Baseline Report","\u002Fdocs\u002Fsecurity\u002Fzap-baseline-20260310","docs\u002Fsecurity\u002Fzap-baseline-20260310",{"title":128,"path":132,"stem":133,"order":27},"\u002Fdocs\u002Fsecurity\u002Fzap-baseline-20260316","docs\u002Fsecurity\u002Fzap-baseline-20260316",{"title":128,"path":135,"stem":136,"order":53},"\u002Fdocs\u002Fsecurity\u002Fzap-baseline-20260323","docs\u002Fsecurity\u002Fzap-baseline-20260323",{"title":128,"path":138,"stem":139,"order":116},"\u002Fdocs\u002Fsecurity\u002Fzap-baseline-20260324","docs\u002Fsecurity\u002Fzap-baseline-20260324",{"title":128,"path":141,"stem":142},"\u002Fdocs\u002Fsecurity\u002Fzap-baseline-20260406","docs\u002Fsecurity\u002Fzap-baseline-20260406",{"id":144,"title":109,"body":145,"description":3000,"extension":3001,"links":3002,"meta":3003,"navigation":3004,"path":110,"seo":3005,"stem":111,"__hash__":3006},"docs\u002Fdocs\u002Freference\u002Farchitecture.md",{"type":146,"value":147,"toc":2971},"minimark",[148,157,162,167,170,305,309,312,487,491,592,596,599,606,703,708,723,985,988,998,1149,1157,1161,1168,1370,1374,1381,1426,1433,1437,1440,1583,1601,1605,1612,1733,1739,1743,1746,1827,1830,1856,1860,1869,1917,1923,1929,1937,1941,2271,2275,2282,2290,2293,2312,2316,2319,2323,2338,2527,2531,2534,2652,2656,2659,2729,2733,2737,2748,2816,2836,2840,2957,2961,2967],[149,150,151,152,156],"p",{},"Capacitarr is a single-container application that bundles a Go backend, a Nuxt 4 (Vue 3) frontend, and a SQLite database. The frontend is statically generated at build time and embedded into the Go binary via ",[153,154,155],"code",{},"go:embed",", producing a single self-contained executable.",[158,159,161],"h2",{"id":160},"high-level-overview","High-Level Overview",[163,164,166],"h3",{"id":165},"container-internals","Container Internals",[149,168,169],{},"The Docker container runs a Go backend that serves the embedded Nuxt frontend. Communication flows through REST API calls and real-time Server-Sent Events.",[171,172,177],"pre",{"className":173,"code":174,"language":175,"meta":176,"style":176},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","flowchart TB\n    FRONTEND[\"Nuxt 4 Frontend\u003Cbr\u002F>Vue 3 + Tailwind CSS 4 + shadcn-vue\"]\n\n    subgraph GO_BACKEND[\"Go Backend\"]\n        direction LR\n        BACKEND[\"Echo + GORM\u003Cbr\u002F>Service Layer\"]\n        ENGINE[\"Scoring Engine\u003Cbr\u002F>Weighted factors + protection rules\"]\n        POLLER[\"Engine Orchestrator\u003Cbr\u002F>Scheduled disk monitoring\"]\n        EVENT_BUS[\"Event Bus\u003Cbr\u002F>Typed pub\u002Fsub fan-out\"]\n        SSE[\"SSE Broadcaster\u003Cbr\u002F>Real-time event stream\"]\n\n        BACKEND --> ENGINE\n        BACKEND --> POLLER\n        BACKEND -.->|\"publish\"| EVENT_BUS\n        EVENT_BUS -.->|\"subscribe\"| SSE\n    end\n\n    DB[\"SQLite Database\u003Cbr\u002F>\u002Fconfig\u002Fcapacitarr.db\"]\n\n    FRONTEND -->|\"REST API\"| BACKEND\n    BACKEND --> DB\n    SSE -.->|\"Server-Sent Events\"| FRONTEND\n\n","mermaid","",[153,178,179,186,191,197,202,207,212,218,224,230,236,241,247,253,259,265,271,276,282,287,293,299],{"__ignoreMap":176},[180,181,183],"span",{"class":182,"line":17},"line",[180,184,185],{},"flowchart TB\n",[180,187,188],{"class":182,"line":22},[180,189,190],{},"    FRONTEND[\"Nuxt 4 Frontend\u003Cbr\u002F>Vue 3 + Tailwind CSS 4 + shadcn-vue\"]\n",[180,192,193],{"class":182,"line":27},[180,194,196],{"emptyLinePlaceholder":195},true,"\n",[180,198,199],{"class":182,"line":53},[180,200,201],{},"    subgraph GO_BACKEND[\"Go Backend\"]\n",[180,203,204],{"class":182,"line":116},[180,205,206],{},"        direction LR\n",[180,208,209],{"class":182,"line":66},[180,210,211],{},"        BACKEND[\"Echo + GORM\u003Cbr\u002F>Service Layer\"]\n",[180,213,215],{"class":182,"line":214},7,[180,216,217],{},"        ENGINE[\"Scoring Engine\u003Cbr\u002F>Weighted factors + protection rules\"]\n",[180,219,221],{"class":182,"line":220},8,[180,222,223],{},"        POLLER[\"Engine Orchestrator\u003Cbr\u002F>Scheduled disk monitoring\"]\n",[180,225,227],{"class":182,"line":226},9,[180,228,229],{},"        EVENT_BUS[\"Event Bus\u003Cbr\u002F>Typed pub\u002Fsub fan-out\"]\n",[180,231,233],{"class":182,"line":232},10,[180,234,235],{},"        SSE[\"SSE Broadcaster\u003Cbr\u002F>Real-time event stream\"]\n",[180,237,239],{"class":182,"line":238},11,[180,240,196],{"emptyLinePlaceholder":195},[180,242,244],{"class":182,"line":243},12,[180,245,246],{},"        BACKEND --> ENGINE\n",[180,248,250],{"class":182,"line":249},13,[180,251,252],{},"        BACKEND --> POLLER\n",[180,254,256],{"class":182,"line":255},14,[180,257,258],{},"        BACKEND -.->|\"publish\"| EVENT_BUS\n",[180,260,262],{"class":182,"line":261},15,[180,263,264],{},"        EVENT_BUS -.->|\"subscribe\"| SSE\n",[180,266,268],{"class":182,"line":267},16,[180,269,270],{},"    end\n",[180,272,274],{"class":182,"line":273},17,[180,275,196],{"emptyLinePlaceholder":195},[180,277,279],{"class":182,"line":278},18,[180,280,281],{},"    DB[\"SQLite Database\u003Cbr\u002F>\u002Fconfig\u002Fcapacitarr.db\"]\n",[180,283,285],{"class":182,"line":284},19,[180,286,196],{"emptyLinePlaceholder":195},[180,288,290],{"class":182,"line":289},20,[180,291,292],{},"    FRONTEND -->|\"REST API\"| BACKEND\n",[180,294,296],{"class":182,"line":295},21,[180,297,298],{},"    BACKEND --> DB\n",[180,300,302],{"class":182,"line":301},22,[180,303,304],{},"    SSE -.->|\"Server-Sent Events\"| FRONTEND\n",[163,306,308],{"id":307},"external-integrations","External Integrations",[149,310,311],{},"The engine orchestrator fetches data from external services, and the scoring engine sends deletion requests back to the *arr apps.",[171,313,315],{"className":173,"code":314,"language":175,"meta":176,"style":176},"flowchart LR\n    subgraph CAPACITARR[\"Capacitarr\"]\n        direction LR\n        POLLER[\"Engine Orchestrator\"]\n        ENGINE[\"Scoring Engine\"]\n    end\n\n    subgraph ARR_APPS[\"*arr Apps\"]\n        direction LR\n        SONARR[\"Sonarr\"]\n        RADARR[\"Radarr\"]\n        LIDARR[\"Lidarr\"]\n        READARR[\"Readarr\"]\n    end\n\n    subgraph MEDIA_SERVERS[\"Media Servers\"]\n        direction LR\n        PLEX[\"Plex\"]\n        JELLYFIN[\"Jellyfin\"]\n        EMBY[\"Emby\"]\n    end\n\n    subgraph ENRICHMENT[\"Enrichment\"]\n        direction LR\n        TAUTULLI[\"Tautulli\"]\n        JELLYSTAT[\"Jellystat\"]\n        SEERR[\"Seerr\"]\n        TRACEARR[\"Tracearr\"]\n    end\n\n    POLLER -->|\"Fetch media + disk space\"| ARR_APPS\n    POLLER -->|\"Fetch watch data\"| MEDIA_SERVERS\n    POLLER -->|\"Fetch requests + history\"| ENRICHMENT\n    ENGINE -->|\"Delete lowest-scored items\"| ARR_APPS\n",[153,316,317,322,327,331,336,341,345,349,354,358,363,368,373,378,382,386,391,395,400,405,410,414,418,424,429,435,441,447,453,458,463,469,475,481],{"__ignoreMap":176},[180,318,319],{"class":182,"line":17},[180,320,321],{},"flowchart LR\n",[180,323,324],{"class":182,"line":22},[180,325,326],{},"    subgraph CAPACITARR[\"Capacitarr\"]\n",[180,328,329],{"class":182,"line":27},[180,330,206],{},[180,332,333],{"class":182,"line":53},[180,334,335],{},"        POLLER[\"Engine Orchestrator\"]\n",[180,337,338],{"class":182,"line":116},[180,339,340],{},"        ENGINE[\"Scoring Engine\"]\n",[180,342,343],{"class":182,"line":66},[180,344,270],{},[180,346,347],{"class":182,"line":214},[180,348,196],{"emptyLinePlaceholder":195},[180,350,351],{"class":182,"line":220},[180,352,353],{},"    subgraph ARR_APPS[\"*arr Apps\"]\n",[180,355,356],{"class":182,"line":226},[180,357,206],{},[180,359,360],{"class":182,"line":232},[180,361,362],{},"        SONARR[\"Sonarr\"]\n",[180,364,365],{"class":182,"line":238},[180,366,367],{},"        RADARR[\"Radarr\"]\n",[180,369,370],{"class":182,"line":243},[180,371,372],{},"        LIDARR[\"Lidarr\"]\n",[180,374,375],{"class":182,"line":249},[180,376,377],{},"        READARR[\"Readarr\"]\n",[180,379,380],{"class":182,"line":255},[180,381,270],{},[180,383,384],{"class":182,"line":261},[180,385,196],{"emptyLinePlaceholder":195},[180,387,388],{"class":182,"line":267},[180,389,390],{},"    subgraph MEDIA_SERVERS[\"Media Servers\"]\n",[180,392,393],{"class":182,"line":273},[180,394,206],{},[180,396,397],{"class":182,"line":278},[180,398,399],{},"        PLEX[\"Plex\"]\n",[180,401,402],{"class":182,"line":284},[180,403,404],{},"        JELLYFIN[\"Jellyfin\"]\n",[180,406,407],{"class":182,"line":289},[180,408,409],{},"        EMBY[\"Emby\"]\n",[180,411,412],{"class":182,"line":295},[180,413,270],{},[180,415,416],{"class":182,"line":301},[180,417,196],{"emptyLinePlaceholder":195},[180,419,421],{"class":182,"line":420},23,[180,422,423],{},"    subgraph ENRICHMENT[\"Enrichment\"]\n",[180,425,427],{"class":182,"line":426},24,[180,428,206],{},[180,430,432],{"class":182,"line":431},25,[180,433,434],{},"        TAUTULLI[\"Tautulli\"]\n",[180,436,438],{"class":182,"line":437},26,[180,439,440],{},"        JELLYSTAT[\"Jellystat\"]\n",[180,442,444],{"class":182,"line":443},27,[180,445,446],{},"        SEERR[\"Seerr\"]\n",[180,448,450],{"class":182,"line":449},28,[180,451,452],{},"        TRACEARR[\"Tracearr\"]\n",[180,454,456],{"class":182,"line":455},29,[180,457,270],{},[180,459,461],{"class":182,"line":460},30,[180,462,196],{"emptyLinePlaceholder":195},[180,464,466],{"class":182,"line":465},31,[180,467,468],{},"    POLLER -->|\"Fetch media + disk space\"| ARR_APPS\n",[180,470,472],{"class":182,"line":471},32,[180,473,474],{},"    POLLER -->|\"Fetch watch data\"| MEDIA_SERVERS\n",[180,476,478],{"class":182,"line":477},33,[180,479,480],{},"    POLLER -->|\"Fetch requests + history\"| ENRICHMENT\n",[180,482,484],{"class":182,"line":483},34,[180,485,486],{},"    ENGINE -->|\"Delete lowest-scored items\"| ARR_APPS\n",[158,488,490],{"id":489},"technology-stack","Technology Stack",[492,493,494,510],"table",{},[495,496,497],"thead",{},[498,499,500,504,507],"tr",{},[501,502,503],"th",{},"Layer",[501,505,506],{},"Technology",[501,508,509],{},"Purpose",[511,512,513,528,541,554,566,579],"tbody",{},[498,514,515,522,525],{},[516,517,518],"td",{},[519,520,521],"strong",{},"Frontend",[516,523,524],{},"Nuxt 4, Vue 3, Tailwind CSS 4, shadcn-vue, Lucide, ECharts",[516,526,527],{},"Dashboard UI, analytics visualizations, rule builder, score visualization, real-time updates via SSE",[498,529,530,535,538],{},[516,531,532],{},[519,533,534],{},"Backend",[516,536,537],{},"Go, Echo, GORM",[516,539,540],{},"REST API, authentication, integration clients, scheduling",[498,542,543,548,551],{},[516,544,545],{},[519,546,547],{},"Service Layer",[516,549,550],{},"Go (custom)",[516,552,553],{},"Business logic, event publishing, dependency injection",[498,555,556,561,563],{},[516,557,558],{},[519,559,560],{},"Event System",[516,562,550],{},[516,564,565],{},"Typed event bus, activity persistence, SSE broadcast, notification dispatch",[498,567,568,573,576],{},[516,569,570],{},[519,571,572],{},"Database",[516,574,575],{},"SQLite",[516,577,578],{},"Configuration, approval queue, audit log, engine statistics",[498,580,581,586,589],{},[516,582,583],{},[519,584,585],{},"Container",[516,587,588],{},"Alpine Linux, multi-stage Docker build",[516,590,591],{},"Minimal runtime image (~30 MB)",[158,593,595],{"id":594},"backend-architecture","Backend Architecture",[163,597,547],{"id":598},"service-layer",[149,600,601,602,605],{},"All business logic lives in the service layer (",[153,603,604],{},"backend\u002Finternal\u002Fservices\u002F","). Route handlers are thin — they parse requests, call services, and return responses.",[171,607,609],{"className":173,"code":608,"language":175,"meta":176,"style":176},"flowchart TD\n    ROUTES[\"Route Handlers\u003Cbr\u002F>Parse request, call service, return response\"]\n    SERVICES[\"Service Layer\u003Cbr\u002F>Business logic + validation\"]\n    DB[\"*gorm.DB\u003Cbr\u002F>Injected, not global\"]\n    BUS[\"Event Bus\u003Cbr\u002F>Typed pub\u002Fsub\"]\n\n    subgraph SUBSCRIBERS[\"Subscribers\"]\n        ACTIVITY[\"ActivityPersister\u003Cbr\u002F>activity_events table\"]\n        NOTIF[\"NotificationDispatcher\u003Cbr\u002F>Discord \u002F Apprise\"]\n        SSE[\"SSE Broadcaster\u003Cbr\u002F>Browser tabs\"]\n    end\n\n    ROUTES -->|\"call\"| SERVICES\n    SERVICES -->|\"query \u002F persist\"| DB\n    SERVICES -.->|\"publish\"| BUS\n    BUS -.->|\"subscribe\"| ACTIVITY\n    BUS -.->|\"subscribe\"| NOTIF\n    BUS -.->|\"subscribe\"| SSE\n    ACTIVITY -.->|\"persist\"| DB\n\n",[153,610,611,616,621,626,631,636,640,645,650,655,660,664,668,673,678,683,688,693,698],{"__ignoreMap":176},[180,612,613],{"class":182,"line":17},[180,614,615],{},"flowchart TD\n",[180,617,618],{"class":182,"line":22},[180,619,620],{},"    ROUTES[\"Route Handlers\u003Cbr\u002F>Parse request, call service, return response\"]\n",[180,622,623],{"class":182,"line":27},[180,624,625],{},"    SERVICES[\"Service Layer\u003Cbr\u002F>Business logic + validation\"]\n",[180,627,628],{"class":182,"line":53},[180,629,630],{},"    DB[\"*gorm.DB\u003Cbr\u002F>Injected, not global\"]\n",[180,632,633],{"class":182,"line":116},[180,634,635],{},"    BUS[\"Event Bus\u003Cbr\u002F>Typed pub\u002Fsub\"]\n",[180,637,638],{"class":182,"line":66},[180,639,196],{"emptyLinePlaceholder":195},[180,641,642],{"class":182,"line":214},[180,643,644],{},"    subgraph SUBSCRIBERS[\"Subscribers\"]\n",[180,646,647],{"class":182,"line":220},[180,648,649],{},"        ACTIVITY[\"ActivityPersister\u003Cbr\u002F>activity_events table\"]\n",[180,651,652],{"class":182,"line":226},[180,653,654],{},"        NOTIF[\"NotificationDispatcher\u003Cbr\u002F>Discord \u002F Apprise\"]\n",[180,656,657],{"class":182,"line":232},[180,658,659],{},"        SSE[\"SSE Broadcaster\u003Cbr\u002F>Browser tabs\"]\n",[180,661,662],{"class":182,"line":238},[180,663,270],{},[180,665,666],{"class":182,"line":243},[180,667,196],{"emptyLinePlaceholder":195},[180,669,670],{"class":182,"line":249},[180,671,672],{},"    ROUTES -->|\"call\"| SERVICES\n",[180,674,675],{"class":182,"line":255},[180,676,677],{},"    SERVICES -->|\"query \u002F persist\"| DB\n",[180,679,680],{"class":182,"line":261},[180,681,682],{},"    SERVICES -.->|\"publish\"| BUS\n",[180,684,685],{"class":182,"line":267},[180,686,687],{},"    BUS -.->|\"subscribe\"| ACTIVITY\n",[180,689,690],{"class":182,"line":273},[180,691,692],{},"    BUS -.->|\"subscribe\"| NOTIF\n",[180,694,695],{"class":182,"line":278},[180,696,697],{},"    BUS -.->|\"subscribe\"| SSE\n",[180,699,700],{"class":182,"line":284},[180,701,702],{},"    ACTIVITY -.->|\"persist\"| DB\n",[704,705,707],"h4",{"id":706},"service-registry","Service Registry",[149,709,710,711,714,715,718,719,722],{},"All services accept ",[153,712,713],{},"*gorm.DB"," and ",[153,716,717],{},"*events.EventBus"," in their constructor and are registered on ",[153,720,721],{},"services.Registry",".",[492,724,725,738],{},[495,726,727],{},[498,728,729,732,735],{},[501,730,731],{},"Category",[501,733,734],{},"Service",[501,736,737],{},"Responsibilities",[511,739,740,753,763,773,783,793,806,816,826,836,846,856,866,876,889,902,912,925,935,945,955,965,975],{},[498,741,742,747,750],{},[516,743,744],{},[519,745,746],{},"Core",[516,748,749],{},"ApprovalService",[516,751,752],{},"Approve, reject, unsnooze queue items",[498,754,755,757,760],{},[516,756],{},[516,758,759],{},"DeletionService",[516,761,762],{},"Execute deletions, dry run, handle failures",[498,764,765,767,770],{},[516,766],{},[516,768,769],{},"DiskGroupService",[516,771,772],{},"Disk group CRUD, threshold management",[498,774,775,777,780],{},[516,776],{},[516,778,779],{},"EngineService",[516,781,782],{},"Trigger runs, get stats",[498,784,785,787,790],{},[516,786],{},[516,788,789],{},"SettingsService",[516,791,792],{},"Update preferences and thresholds",[498,794,795,800,803],{},[516,796,797],{},[519,798,799],{},"Data",[516,801,802],{},"AuditLogService",[516,804,805],{},"Create, upsert, dedup audit entries",[498,807,808,810,813],{},[516,809],{},[516,811,812],{},"BackupService",[516,814,815],{},"Settings export\u002Fimport and backup archive creation",[498,817,818,820,823],{},[516,819],{},[516,821,822],{},"DatabaseBackupService",[516,824,825],{},"Automatic VACUUM INTO backup with rotation and retention",[498,827,828,830,833],{},[516,829],{},[516,831,832],{},"DataService",[516,834,835],{},"Data reset operations",[498,837,838,840,843],{},[516,839],{},[516,841,842],{},"MetricsService",[516,844,845],{},"History, rollup, lifetime stats",[498,847,848,850,853],{},[516,849],{},[516,851,852],{},"RulesService",[516,854,855],{},"Custom rule CRUD, validation, and impact preview",[498,857,858,860,863],{},[516,859],{},[516,861,862],{},"PreviewService",[516,864,865],{},"Scored media preview cache, SSE-driven invalidation",[498,867,868,870,873],{},[516,869],{},[516,871,872],{},"MappingService",[516,874,875],{},"Media server TMDb ID → native ID mapping for cross-references",[498,877,878,883,886],{},[516,879,880],{},[519,881,882],{},"Analytics",[516,884,885],{},"WatchAnalyticsService",[516,887,888],{},"Dead content, stale content analytics",[498,890,891,896,899],{},[516,892,893],{},[519,894,895],{},"Sunset",[516,897,898],{},"SunsetService",[516,900,901],{},"Sunset queue CRUD, expiry processing, escalation, label management",[498,903,904,906,909],{},[516,905],{},[516,907,908],{},"PosterOverlayService",[516,910,911],{},"Poster overlay lifecycle (apply, restore, update all)",[498,913,914,919,922],{},[516,915,916],{},[519,917,918],{},"External",[516,920,921],{},"IntegrationService",[516,923,924],{},"CRUD, test connections, sync data",[498,926,927,929,932],{},[516,928],{},[516,930,931],{},"RecoveryService",[516,933,934],{},"Integration recovery with exponential backoff",[498,936,937,939,942],{},[516,938],{},[516,940,941],{},"AuthService",[516,943,944],{},"Login, change password, generate API keys",[498,946,947,949,952],{},[516,948],{},[516,950,951],{},"NotificationChannelService",[516,953,954],{},"CRUD for notification channels",[498,956,957,959,962],{},[516,958],{},[516,960,961],{},"NotificationDispatchService",[516,963,964],{},"Two-gate flush, digest, and alerts",[498,966,967,969,972],{},[516,968],{},[516,970,971],{},"VersionService",[516,973,974],{},"Update check via GitHub releases",[498,976,977,979,982],{},[516,978],{},[516,980,981],{},"MigrationService",[516,983,984],{},"1.x → 2.0 database migration",[163,986,707],{"id":987},"service-registry-1",[149,989,990,991,994,995,997],{},"All services are instantiated in ",[153,992,993],{},"main.go"," and held in a ",[153,996,721],{}," struct that is passed to route registration functions:",[171,999,1003],{"className":1000,"code":1001,"language":1002,"meta":176,"style":176},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","type Registry struct {\n    DB  *gorm.DB\n    Bus *events.EventBus\n    Cfg *config.Config\n\n    Approval             *ApprovalService\n    Backup               *BackupService\n    Deletion             *DeletionService\n    AuditLog             *AuditLogService\n    DiskGroup            *DiskGroupService\n    Engine               *EngineService\n    Preview              *PreviewService\n    Settings             *SettingsService\n    Integration          *IntegrationService\n    Auth                 *AuthService\n    NotificationChannel  *NotificationChannelService\n    NotificationDispatch *NotificationDispatchService\n    Data                 *DataService\n    Rules                *RulesService\n    Metrics              *MetricsService\n    Version              *VersionService\n    WatchAnalytics       *WatchAnalyticsService\n    Migration            *MigrationService\n    Sunset               *SunsetService\n    PosterOverlay        *PosterOverlayService\n    Mapping              *MappingService\n    Recovery             *RecoveryService\n    DatabaseBackup       *DatabaseBackupService\n}\n","go",[153,1004,1005,1010,1015,1020,1025,1029,1034,1039,1044,1049,1054,1059,1064,1069,1074,1079,1084,1089,1094,1099,1104,1109,1114,1119,1124,1129,1134,1139,1144],{"__ignoreMap":176},[180,1006,1007],{"class":182,"line":17},[180,1008,1009],{},"type Registry struct {\n",[180,1011,1012],{"class":182,"line":22},[180,1013,1014],{},"    DB  *gorm.DB\n",[180,1016,1017],{"class":182,"line":27},[180,1018,1019],{},"    Bus *events.EventBus\n",[180,1021,1022],{"class":182,"line":53},[180,1023,1024],{},"    Cfg *config.Config\n",[180,1026,1027],{"class":182,"line":116},[180,1028,196],{"emptyLinePlaceholder":195},[180,1030,1031],{"class":182,"line":66},[180,1032,1033],{},"    Approval             *ApprovalService\n",[180,1035,1036],{"class":182,"line":214},[180,1037,1038],{},"    Backup               *BackupService\n",[180,1040,1041],{"class":182,"line":220},[180,1042,1043],{},"    Deletion             *DeletionService\n",[180,1045,1046],{"class":182,"line":226},[180,1047,1048],{},"    AuditLog             *AuditLogService\n",[180,1050,1051],{"class":182,"line":232},[180,1052,1053],{},"    DiskGroup            *DiskGroupService\n",[180,1055,1056],{"class":182,"line":238},[180,1057,1058],{},"    Engine               *EngineService\n",[180,1060,1061],{"class":182,"line":243},[180,1062,1063],{},"    Preview              *PreviewService\n",[180,1065,1066],{"class":182,"line":249},[180,1067,1068],{},"    Settings             *SettingsService\n",[180,1070,1071],{"class":182,"line":255},[180,1072,1073],{},"    Integration          *IntegrationService\n",[180,1075,1076],{"class":182,"line":261},[180,1077,1078],{},"    Auth                 *AuthService\n",[180,1080,1081],{"class":182,"line":267},[180,1082,1083],{},"    NotificationChannel  *NotificationChannelService\n",[180,1085,1086],{"class":182,"line":273},[180,1087,1088],{},"    NotificationDispatch *NotificationDispatchService\n",[180,1090,1091],{"class":182,"line":278},[180,1092,1093],{},"    Data                 *DataService\n",[180,1095,1096],{"class":182,"line":284},[180,1097,1098],{},"    Rules                *RulesService\n",[180,1100,1101],{"class":182,"line":289},[180,1102,1103],{},"    Metrics              *MetricsService\n",[180,1105,1106],{"class":182,"line":295},[180,1107,1108],{},"    Version              *VersionService\n",[180,1110,1111],{"class":182,"line":301},[180,1112,1113],{},"    WatchAnalytics       *WatchAnalyticsService\n",[180,1115,1116],{"class":182,"line":420},[180,1117,1118],{},"    Migration            *MigrationService\n",[180,1120,1121],{"class":182,"line":426},[180,1122,1123],{},"    Sunset               *SunsetService\n",[180,1125,1126],{"class":182,"line":431},[180,1127,1128],{},"    PosterOverlay        *PosterOverlayService\n",[180,1130,1131],{"class":182,"line":437},[180,1132,1133],{},"    Mapping              *MappingService\n",[180,1135,1136],{"class":182,"line":443},[180,1137,1138],{},"    Recovery             *RecoveryService\n",[180,1140,1141],{"class":182,"line":449},[180,1142,1143],{},"    DatabaseBackup       *DatabaseBackupService\n",[180,1145,1146],{"class":182,"line":455},[180,1147,1148],{},"}\n",[149,1150,1151,1152,714,1154,1156],{},"Each service receives a ",[153,1153,713],{},[153,1155,717],{}," via constructor injection — no global state.",[163,1158,1160],{"id":1159},"capability-based-integration-interfaces","Capability-Based Integration Interfaces",[149,1162,1163,1164,1167],{},"Integration clients implement only the capability interfaces they support, replacing a monolithic ",[153,1165,1166],{},"Integration"," interface:",[492,1169,1170,1183],{},[495,1171,1172],{},[498,1173,1174,1177,1180],{},[501,1175,1176],{},"Interface",[501,1178,1179],{},"Description",[501,1181,1182],{},"Implementors",[511,1184,1185,1198,1211,1223,1235,1248,1261,1273,1285,1298,1310,1322,1334,1346,1358],{},[498,1186,1187,1192,1195],{},[516,1188,1189],{},[153,1190,1191],{},"Connectable",[516,1193,1194],{},"Connection testing",[516,1196,1197],{},"All integrations",[498,1199,1200,1205,1208],{},[516,1201,1202],{},[153,1203,1204],{},"MediaSource",[516,1206,1207],{},"List managed media items",[516,1209,1210],{},"Sonarr, Radarr, Lidarr, Readarr",[498,1212,1213,1218,1221],{},[516,1214,1215],{},[153,1216,1217],{},"DiskReporter",[516,1219,1220],{},"Disk usage reporting",[516,1222,1210],{},[498,1224,1225,1230,1233],{},[516,1226,1227],{},[153,1228,1229],{},"MediaDeleter",[516,1231,1232],{},"Delete media items",[516,1234,1210],{},[498,1236,1237,1242,1245],{},[516,1238,1239],{},[153,1240,1241],{},"WatchDataProvider",[516,1243,1244],{},"Play counts and history",[516,1246,1247],{},"Plex, Jellyfin, Emby",[498,1249,1250,1255,1258],{},[516,1251,1252],{},[153,1253,1254],{},"RequestProvider",[516,1256,1257],{},"Media request data",[516,1259,1260],{},"Seerr",[498,1262,1263,1268,1271],{},[516,1264,1265],{},[153,1266,1267],{},"WatchlistProvider",[516,1269,1270],{},"User watchlists\u002Ffavorites",[516,1272,1247],{},[498,1274,1275,1280,1283],{},[516,1276,1277],{},[153,1278,1279],{},"CollectionDataProvider",[516,1281,1282],{},"Collection memberships",[516,1284,1247],{},[498,1286,1287,1292,1295],{},[516,1288,1289],{},[153,1290,1291],{},"CollectionResolver",[516,1293,1294],{},"Resolve collection members for deletion",[516,1296,1297],{},"Radarr",[498,1299,1300,1305,1308],{},[516,1301,1302],{},[153,1303,1304],{},"RuleValueFetcher",[516,1306,1307],{},"Dynamic rule field values",[516,1309,1210],{},[498,1311,1312,1317,1320],{},[516,1313,1314],{},[153,1315,1316],{},"CollectionNameFetcher",[516,1318,1319],{},"Fetch collection names for autocomplete",[516,1321,1247],{},[498,1323,1324,1329,1332],{},[516,1325,1326],{},[153,1327,1328],{},"LabelDataProvider",[516,1330,1331],{},"Label memberships for enrichment",[516,1333,1247],{},[498,1335,1336,1341,1344],{},[516,1337,1338],{},[153,1339,1340],{},"LabelManager",[516,1342,1343],{},"Apply and remove labels on media items",[516,1345,1247],{},[498,1347,1348,1353,1356],{},[516,1349,1350],{},[153,1351,1352],{},"LabelNameFetcher",[516,1354,1355],{},"Fetch label names for autocomplete",[516,1357,1247],{},[498,1359,1360,1365,1368],{},[516,1361,1362],{},[153,1363,1364],{},"PosterManager",[516,1366,1367],{},"Upload and restore poster images",[516,1369,1247],{},[163,1371,1373],{"id":1372},"integration-registry","Integration Registry",[149,1375,1376,1377,1380],{},"The ",[153,1378,1379],{},"IntegrationRegistry"," provides runtime discovery of available integrations by capability:",[171,1382,1384],{"className":1000,"code":1383,"language":1002,"meta":176,"style":176},"registry.WatchProviders()          \u002F\u002F → [Plex, Jellyfin, Emby]\nregistry.MediaSources()            \u002F\u002F → [Sonarr, Radarr, Lidarr, Readarr]\nregistry.DiskReporters()           \u002F\u002F → [Sonarr, Radarr, Lidarr, Readarr]\nregistry.RequestProviders()        \u002F\u002F → [Seerr]\nregistry.CollectionDataProviders() \u002F\u002F → [Plex, Jellyfin, Emby]\nregistry.LabelDataProviders()      \u002F\u002F → [Plex, Jellyfin, Emby]\nregistry.LabelManagers()           \u002F\u002F → [Plex, Jellyfin, Emby]\nregistry.PosterManagers()          \u002F\u002F → [Plex, Jellyfin, Emby]\n",[153,1385,1386,1391,1396,1401,1406,1411,1416,1421],{"__ignoreMap":176},[180,1387,1388],{"class":182,"line":17},[180,1389,1390],{},"registry.WatchProviders()          \u002F\u002F → [Plex, Jellyfin, Emby]\n",[180,1392,1393],{"class":182,"line":22},[180,1394,1395],{},"registry.MediaSources()            \u002F\u002F → [Sonarr, Radarr, Lidarr, Readarr]\n",[180,1397,1398],{"class":182,"line":27},[180,1399,1400],{},"registry.DiskReporters()           \u002F\u002F → [Sonarr, Radarr, Lidarr, Readarr]\n",[180,1402,1403],{"class":182,"line":53},[180,1404,1405],{},"registry.RequestProviders()        \u002F\u002F → [Seerr]\n",[180,1407,1408],{"class":182,"line":116},[180,1409,1410],{},"registry.CollectionDataProviders() \u002F\u002F → [Plex, Jellyfin, Emby]\n",[180,1412,1413],{"class":182,"line":66},[180,1414,1415],{},"registry.LabelDataProviders()      \u002F\u002F → [Plex, Jellyfin, Emby]\n",[180,1417,1418],{"class":182,"line":214},[180,1419,1420],{},"registry.LabelManagers()           \u002F\u002F → [Plex, Jellyfin, Emby]\n",[180,1422,1423],{"class":182,"line":220},[180,1424,1425],{},"registry.PosterManagers()          \u002F\u002F → [Plex, Jellyfin, Emby]\n",[149,1427,1428,1429,1432],{},"Integration clients are created via a factory pattern (",[153,1430,1431],{},"integrations.CreateClient(config)",") and auto-registered. The poller and preview service use the registry to discover capabilities instead of hardcoded wiring.",[163,1434,1436],{"id":1435},"enrichment-pipeline","Enrichment Pipeline",[149,1438,1439],{},"Media items pass through a composable enrichment pipeline after fetching:",[171,1441,1443],{"className":173,"code":1442,"language":175,"meta":176,"style":176},"flowchart LR\n    FETCH[\"Fetch\u003Cbr\u002F>MediaSource.GetMediaItems()\"]\n\n    subgraph WATCH_DATA[\"Watch Data Enrichers\"]\n        direction LR\n        BULK[\"BulkWatchEnricher\u003Cbr\u002F>Play counts + last played\"]\n        TAUTULLI_E[\"TautulliEnricher\u003Cbr\u002F>Per-user watch data\"]\n        JELLYSTAT_E[\"JellystatEnricher\u003Cbr\u002F>Per-user watch data\"]\n        TRACEARR_E[\"TracearrEnricher\u003Cbr\u002F>Unified watch data\"]\n\n        BULK --> TAUTULLI_E --> JELLYSTAT_E --> TRACEARR_E\n    end\n\n    subgraph METADATA[\"Metadata Enrichers\"]\n        direction LR\n        REQUEST[\"RequestEnricher\u003Cbr\u002F>Seerr request status\"]\n        WATCHLIST[\"WatchlistEnricher\u003Cbr\u002F>Watchlist\u002Ffavorites\"]\n        COLLECTION[\"CollectionEnricher\u003Cbr\u002F>Collection memberships\"]\n        LABEL[\"LabelEnricher\u003Cbr\u002F>Media server labels\"]\n\n        REQUEST --> WATCHLIST --> COLLECTION --> LABEL\n    end\n\n    subgraph XREF_GROUP[\"Cross-Reference\"]\n        XREF[\"CrossReferenceEnricher\u003Cbr\u002F>Requestor watched?\"]\n    end\n\n    FETCH --> BULK\n    TRACEARR_E --> REQUEST\n    LABEL --> XREF\n",[153,1444,1445,1449,1454,1458,1463,1467,1472,1477,1482,1487,1491,1496,1500,1504,1509,1513,1518,1523,1528,1533,1537,1542,1546,1550,1555,1560,1564,1568,1573,1578],{"__ignoreMap":176},[180,1446,1447],{"class":182,"line":17},[180,1448,321],{},[180,1450,1451],{"class":182,"line":22},[180,1452,1453],{},"    FETCH[\"Fetch\u003Cbr\u002F>MediaSource.GetMediaItems()\"]\n",[180,1455,1456],{"class":182,"line":27},[180,1457,196],{"emptyLinePlaceholder":195},[180,1459,1460],{"class":182,"line":53},[180,1461,1462],{},"    subgraph WATCH_DATA[\"Watch Data Enrichers\"]\n",[180,1464,1465],{"class":182,"line":116},[180,1466,206],{},[180,1468,1469],{"class":182,"line":66},[180,1470,1471],{},"        BULK[\"BulkWatchEnricher\u003Cbr\u002F>Play counts + last played\"]\n",[180,1473,1474],{"class":182,"line":214},[180,1475,1476],{},"        TAUTULLI_E[\"TautulliEnricher\u003Cbr\u002F>Per-user watch data\"]\n",[180,1478,1479],{"class":182,"line":220},[180,1480,1481],{},"        JELLYSTAT_E[\"JellystatEnricher\u003Cbr\u002F>Per-user watch data\"]\n",[180,1483,1484],{"class":182,"line":226},[180,1485,1486],{},"        TRACEARR_E[\"TracearrEnricher\u003Cbr\u002F>Unified watch data\"]\n",[180,1488,1489],{"class":182,"line":232},[180,1490,196],{"emptyLinePlaceholder":195},[180,1492,1493],{"class":182,"line":238},[180,1494,1495],{},"        BULK --> TAUTULLI_E --> JELLYSTAT_E --> TRACEARR_E\n",[180,1497,1498],{"class":182,"line":243},[180,1499,270],{},[180,1501,1502],{"class":182,"line":249},[180,1503,196],{"emptyLinePlaceholder":195},[180,1505,1506],{"class":182,"line":255},[180,1507,1508],{},"    subgraph METADATA[\"Metadata Enrichers\"]\n",[180,1510,1511],{"class":182,"line":261},[180,1512,206],{},[180,1514,1515],{"class":182,"line":267},[180,1516,1517],{},"        REQUEST[\"RequestEnricher\u003Cbr\u002F>Seerr request status\"]\n",[180,1519,1520],{"class":182,"line":273},[180,1521,1522],{},"        WATCHLIST[\"WatchlistEnricher\u003Cbr\u002F>Watchlist\u002Ffavorites\"]\n",[180,1524,1525],{"class":182,"line":278},[180,1526,1527],{},"        COLLECTION[\"CollectionEnricher\u003Cbr\u002F>Collection memberships\"]\n",[180,1529,1530],{"class":182,"line":284},[180,1531,1532],{},"        LABEL[\"LabelEnricher\u003Cbr\u002F>Media server labels\"]\n",[180,1534,1535],{"class":182,"line":289},[180,1536,196],{"emptyLinePlaceholder":195},[180,1538,1539],{"class":182,"line":295},[180,1540,1541],{},"        REQUEST --> WATCHLIST --> COLLECTION --> LABEL\n",[180,1543,1544],{"class":182,"line":301},[180,1545,270],{},[180,1547,1548],{"class":182,"line":420},[180,1549,196],{"emptyLinePlaceholder":195},[180,1551,1552],{"class":182,"line":426},[180,1553,1554],{},"    subgraph XREF_GROUP[\"Cross-Reference\"]\n",[180,1556,1557],{"class":182,"line":431},[180,1558,1559],{},"        XREF[\"CrossReferenceEnricher\u003Cbr\u002F>Requestor watched?\"]\n",[180,1561,1562],{"class":182,"line":437},[180,1563,270],{},[180,1565,1566],{"class":182,"line":443},[180,1567,196],{"emptyLinePlaceholder":195},[180,1569,1570],{"class":182,"line":449},[180,1571,1572],{},"    FETCH --> BULK\n",[180,1574,1575],{"class":182,"line":455},[180,1576,1577],{},"    TRACEARR_E --> REQUEST\n",[180,1579,1580],{"class":182,"line":460},[180,1581,1582],{},"    LABEL --> XREF\n",[149,1584,1585,1586,1589,1590,1593,1594,1593,1597,1600],{},"Each enricher implements the ",[153,1587,1588],{},"Enricher"," interface (",[153,1591,1592],{},"Name()",", ",[153,1595,1596],{},"Priority()",[153,1598,1599],{},"Enrich(items)",") and is auto-discovered from the registry's capabilities. The pipeline currently includes 9 enrichers.",[163,1602,1604],{"id":1603},"pluggable-scoring-factors","Pluggable Scoring Factors",[149,1606,1607,1608,1611],{},"The scoring engine uses a ",[153,1609,1610],{},"ScoringFactor"," interface for each scoring dimension:",[492,1613,1614,1626],{},[495,1615,1616],{},[498,1617,1618,1621,1624],{},[501,1619,1620],{},"Factor",[501,1622,1623],{},"Weight Key",[501,1625,1179],{},[511,1627,1628,1643,1658,1673,1688,1703,1718],{},[498,1629,1630,1635,1640],{},[516,1631,1632],{},[153,1633,1634],{},"WatchHistoryFactor",[516,1636,1637],{},[153,1638,1639],{},"watch_history",[516,1641,1642],{},"Play count influence",[498,1644,1645,1650,1655],{},[516,1646,1647],{},[153,1648,1649],{},"RecencyFactor",[516,1651,1652],{},[153,1653,1654],{},"last_watched",[516,1656,1657],{},"Recency of last watch",[498,1659,1660,1665,1670],{},[516,1661,1662],{},[153,1663,1664],{},"FileSizeFactor",[516,1666,1667],{},[153,1668,1669],{},"file_size",[516,1671,1672],{},"Larger files scored higher for deletion",[498,1674,1675,1680,1685],{},[516,1676,1677],{},[153,1678,1679],{},"RatingFactor",[516,1681,1682],{},[153,1683,1684],{},"rating",[516,1686,1687],{},"Community\u002Fcritic ratings",[498,1689,1690,1695,1700],{},[516,1691,1692],{},[153,1693,1694],{},"LibraryAgeFactor",[516,1696,1697],{},[153,1698,1699],{},"time_in_library",[516,1701,1702],{},"Older items scored higher",[498,1704,1705,1710,1715],{},[516,1706,1707],{},[153,1708,1709],{},"SeriesStatusFactor",[516,1711,1712],{},[153,1713,1714],{},"series_status",[516,1716,1717],{},"Ended series scored higher",[498,1719,1720,1725,1730],{},[516,1721,1722],{},[153,1723,1724],{},"RequestPopularityFactor",[516,1726,1727],{},[153,1728,1729],{},"request_popularity",[516,1731,1732],{},"Requested content is protected",[149,1734,1735,1736,1738],{},"New factors can be added by implementing the ",[153,1737,1610],{}," interface and registering them — no changes to the evaluator loop.",[163,1740,1742],{"id":1741},"event-bus","Event Bus",[149,1744,1745],{},"The event bus uses a fan-out pattern with one goroutine per subscriber and buffered channels.",[171,1747,1749],{"className":1000,"code":1748,"language":1002,"meta":176,"style":176},"\u002F\u002F Event is the interface all typed events implement.\ntype Event interface {\n    EventType() string\n    EventMessage() string\n}\n\ntype EventBus struct {\n    mu          sync.RWMutex\n    subscribers map[chan Event]struct{}\n    closed      bool\n}\n\nfunc (b *EventBus) Publish(event Event)\nfunc (b *EventBus) Subscribe() chan Event\nfunc (b *EventBus) Unsubscribe(ch chan Event)\nfunc (b *EventBus) Close()\n",[153,1750,1751,1756,1761,1766,1771,1775,1779,1784,1789,1794,1799,1803,1807,1812,1817,1822],{"__ignoreMap":176},[180,1752,1753],{"class":182,"line":17},[180,1754,1755],{},"\u002F\u002F Event is the interface all typed events implement.\n",[180,1757,1758],{"class":182,"line":22},[180,1759,1760],{},"type Event interface {\n",[180,1762,1763],{"class":182,"line":27},[180,1764,1765],{},"    EventType() string\n",[180,1767,1768],{"class":182,"line":53},[180,1769,1770],{},"    EventMessage() string\n",[180,1772,1773],{"class":182,"line":116},[180,1774,1148],{},[180,1776,1777],{"class":182,"line":66},[180,1778,196],{"emptyLinePlaceholder":195},[180,1780,1781],{"class":182,"line":214},[180,1782,1783],{},"type EventBus struct {\n",[180,1785,1786],{"class":182,"line":220},[180,1787,1788],{},"    mu          sync.RWMutex\n",[180,1790,1791],{"class":182,"line":226},[180,1792,1793],{},"    subscribers map[chan Event]struct{}\n",[180,1795,1796],{"class":182,"line":232},[180,1797,1798],{},"    closed      bool\n",[180,1800,1801],{"class":182,"line":238},[180,1802,1148],{},[180,1804,1805],{"class":182,"line":243},[180,1806,196],{"emptyLinePlaceholder":195},[180,1808,1809],{"class":182,"line":249},[180,1810,1811],{},"func (b *EventBus) Publish(event Event)\n",[180,1813,1814],{"class":182,"line":255},[180,1815,1816],{},"func (b *EventBus) Subscribe() chan Event\n",[180,1818,1819],{"class":182,"line":261},[180,1820,1821],{},"func (b *EventBus) Unsubscribe(ch chan Event)\n",[180,1823,1824],{"class":182,"line":267},[180,1825,1826],{},"func (b *EventBus) Close()\n",[149,1828,1829],{},"When a service performs an action (e.g., approving an item, completing an engine run), it publishes a typed event to the bus. Three subscribers react to every event:",[1831,1832,1833,1844,1850],"ol",{},[1834,1835,1836,1839,1840,1843],"li",{},[519,1837,1838],{},"ActivityPersister"," — writes the event to the ",[153,1841,1842],{},"activity_events"," table for the dashboard feed",[1834,1845,1846,1849],{},[519,1847,1848],{},"NotificationDispatcher"," — filters events against notification channel subscriptions and delivers to Discord\u002FApprise",[1834,1851,1852,1855],{},[519,1853,1854],{},"SSEBroadcaster"," — serializes the event as an SSE message and pushes it to all connected browser tabs",[163,1857,1859],{"id":1858},"notification-dispatch","Notification Dispatch",[149,1861,1376,1862,1864,1865,1868],{},[153,1863,961],{}," uses a ",[519,1866,1867],{},"two-gate flush pattern"," to ensure cycle digest notifications contain complete data from both the evaluation phase and the deletion phase of an engine run.",[171,1870,1872],{"className":173,"code":1871,"language":175,"meta":176,"style":176},"flowchart LR\n    ENGINE_COMPLETE[\"EngineCompleteEvent\u003Cbr\u002F>Gate 1\"]\n    DELETION_BATCH[\"DeletionBatchCompleteEvent\u003Cbr\u002F>Gate 2\"]\n    FLUSH[\"Flush\u003Cbr\u002F>Build digest + dispatch\"]\n    CHANNELS[\"Discord \u002F Apprise\"]\n\n    ENGINE_COMPLETE -.->|\"evaluation stats\"| FLUSH\n    DELETION_BATCH -.->|\"deletion stats\"| FLUSH\n    FLUSH -->|\"deliver\"| CHANNELS\n",[153,1873,1874,1878,1883,1888,1893,1898,1902,1907,1912],{"__ignoreMap":176},[180,1875,1876],{"class":182,"line":17},[180,1877,321],{},[180,1879,1880],{"class":182,"line":22},[180,1881,1882],{},"    ENGINE_COMPLETE[\"EngineCompleteEvent\u003Cbr\u002F>Gate 1\"]\n",[180,1884,1885],{"class":182,"line":27},[180,1886,1887],{},"    DELETION_BATCH[\"DeletionBatchCompleteEvent\u003Cbr\u002F>Gate 2\"]\n",[180,1889,1890],{"class":182,"line":53},[180,1891,1892],{},"    FLUSH[\"Flush\u003Cbr\u002F>Build digest + dispatch\"]\n",[180,1894,1895],{"class":182,"line":116},[180,1896,1897],{},"    CHANNELS[\"Discord \u002F Apprise\"]\n",[180,1899,1900],{"class":182,"line":66},[180,1901,196],{"emptyLinePlaceholder":195},[180,1903,1904],{"class":182,"line":214},[180,1905,1906],{},"    ENGINE_COMPLETE -.->|\"evaluation stats\"| FLUSH\n",[180,1908,1909],{"class":182,"line":220},[180,1910,1911],{},"    DELETION_BATCH -.->|\"deletion stats\"| FLUSH\n",[180,1913,1914],{"class":182,"line":226},[180,1915,1916],{},"    FLUSH -->|\"deliver\"| CHANNELS\n",[149,1918,1919,1922],{},[519,1920,1921],{},"Cycle digests"," are batched summaries sent once per engine run. They include evaluated count, flagged count, deleted count, freed bytes, duration, and disk usage. The digest is only dispatched after both gates fire, ensuring deletion results are included.",[149,1924,1925,1928],{},[519,1926,1927],{},"Instant alerts"," fire immediately when their trigger event occurs — they are not batched. Alert types include engine errors, mode changes, server started, threshold breaches, update available, approval activity, and integration status (failure + recovery).",[149,1930,1931,1932,1936],{},"See ",[1933,1934,1935],"a",{"href":43},"notifications.md"," for the full user-facing guide.",[163,1938,1940],{"id":1939},"event-types","Event Types",[492,1942,1943,1952],{},[495,1944,1945],{},[498,1946,1947,1949],{},[501,1948,731],{},[501,1950,1951],{},"Events",[511,1953,1954,1978,2005,2026,2055,2091,2124,2142,2165,2206,2224,2242,2253],{},[498,1955,1956,1961],{},[516,1957,1958],{},[519,1959,1960],{},"Engine",[516,1962,1963,1593,1966,1593,1969,1593,1972,1593,1975],{},[153,1964,1965],{},"engine_start",[153,1967,1968],{},"engine_complete",[153,1970,1971],{},"engine_error",[153,1973,1974],{},"manual_run_triggered",[153,1976,1977],{},"enrichment_complete",[498,1979,1980,1985],{},[516,1981,1982],{},[519,1983,1984],{},"Settings",[516,1986,1987,1593,1990,1593,1993,1593,1996,1593,1999,1593,2002],{},[153,1988,1989],{},"engine_mode_changed",[153,1991,1992],{},"settings_changed",[153,1994,1995],{},"threshold_changed",[153,1997,1998],{},"threshold_breached",[153,2000,2001],{},"settings_exported",[153,2003,2004],{},"settings_imported",[498,2006,2007,2012],{},[516,2008,2009],{},[519,2010,2011],{},"Auth",[516,2013,2014,1593,2017,1593,2020,1593,2023],{},[153,2015,2016],{},"login",[153,2018,2019],{},"password_changed",[153,2021,2022],{},"username_changed",[153,2024,2025],{},"api_key_generated",[498,2027,2028,2032],{},[516,2029,2030],{},[519,2031,1166],{},[516,2033,2034,1593,2037,1593,2040,1593,2043,1593,2046,1593,2049,1593,2052],{},[153,2035,2036],{},"integration_added",[153,2038,2039],{},"integration_updated",[153,2041,2042],{},"integration_removed",[153,2044,2045],{},"integration_test",[153,2047,2048],{},"integration_test_failed",[153,2050,2051],{},"integration_recovered",[153,2053,2054],{},"integration_recovery_attempt",[498,2056,2057,2062],{},[516,2058,2059],{},[519,2060,2061],{},"Approval",[516,2063,2064,1593,2067,1593,2070,1593,2073,1593,2076,1593,2079,1593,2082,1593,2085,1593,2088],{},[153,2065,2066],{},"approval_approved",[153,2068,2069],{},"approval_rejected",[153,2071,2072],{},"approval_unsnoozed",[153,2074,2075],{},"approval_bulk_unsnoozed",[153,2077,2078],{},"approval_orphans_recovered",[153,2080,2081],{},"approval_queue_cleared",[153,2083,2084],{},"approval_dismissed",[153,2086,2087],{},"approval_queue_reconciled",[153,2089,2090],{},"approval_returned_to_pending",[498,2092,2093,2098],{},[516,2094,2095],{},[519,2096,2097],{},"Deletion",[516,2099,2100,1593,2103,1593,2106,1593,2109,1593,2112,1593,2115,1593,2118,1593,2121],{},[153,2101,2102],{},"deletion_success",[153,2104,2105],{},"deletion_failed",[153,2107,2108],{},"deletion_dry_run",[153,2110,2111],{},"deletion_batch_complete",[153,2113,2114],{},"deletion_progress",[153,2116,2117],{},"deletion_queued",[153,2119,2120],{},"deletion_cancelled",[153,2122,2123],{},"deletion_grace_period",[498,2125,2126,2131],{},[516,2127,2128],{},[519,2129,2130],{},"Rules",[516,2132,2133,1593,2136,1593,2139],{},[153,2134,2135],{},"rule_created",[153,2137,2138],{},"rule_updated",[153,2140,2141],{},"rule_deleted",[498,2143,2144,2148],{},[516,2145,2146],{},[519,2147,42],{},[516,2149,2150,1593,2153,1593,2156,1593,2159,1593,2162],{},[153,2151,2152],{},"notification_channel_added",[153,2154,2155],{},"notification_channel_updated",[153,2157,2158],{},"notification_channel_removed",[153,2160,2161],{},"notification_sent",[153,2163,2164],{},"notification_delivery_failed",[498,2166,2167,2171],{},[516,2168,2169],{},[519,2170,895],{},[516,2172,2173,1593,2176,1593,2179,1593,2182,1593,2185,1593,2188,1593,2191,1593,2194,1593,2197,1593,2200,1593,2203],{},[153,2174,2175],{},"sunset_created",[153,2177,2178],{},"sunset_cancelled",[153,2180,2181],{},"sunset_expired",[153,2183,2184],{},"sunset_rescheduled",[153,2186,2187],{},"sunset_escalated",[153,2189,2190],{},"sunset_misconfigured",[153,2192,2193],{},"sunset_saved",[153,2195,2196],{},"sunset_saved_cleaned",[153,2198,2199],{},"sunset_label_applied",[153,2201,2202],{},"sunset_label_removed",[153,2204,2205],{},"sunset_label_failed",[498,2207,2208,2213],{},[516,2209,2210],{},[519,2211,2212],{},"Poster Overlay",[516,2214,2215,1593,2218,1593,2221],{},[153,2216,2217],{},"poster_overlay_applied",[153,2219,2220],{},"poster_overlay_restored",[153,2222,2223],{},"poster_overlay_failed",[498,2225,2226,2231],{},[516,2227,2228],{},[519,2229,2230],{},"Preview",[516,2232,2233,1593,2236,1593,2239],{},[153,2234,2235],{},"preview_updated",[153,2237,2238],{},"preview_invalidated",[153,2240,2241],{},"analytics_updated",[498,2243,2244,2248],{},[516,2245,2246],{},[519,2247,799],{},[516,2249,2250],{},[153,2251,2252],{},"data_reset",[498,2254,2255,2260],{},[516,2256,2257],{},[519,2258,2259],{},"System",[516,2261,2262,1593,2265,1593,2268],{},[153,2263,2264],{},"server_started",[153,2266,2267],{},"update_available",[153,2269,2270],{},"version_check",[163,2272,2274],{"id":2273},"sse-server-sent-events","SSE (Server-Sent Events)",[149,2276,2277,2278,2281],{},"The frontend connects to ",[153,2279,2280],{},"GET \u002Fapi\u002Fv1\u002Fevents"," (authenticated, long-lived HTTP connection) to receive real-time updates. This replaces the previous polling-based approach.",[171,2283,2288],{"className":2284,"code":2286,"language":2287},[2285],"language-text","HTTP\u002F1.1 200 OK\nContent-Type: text\u002Fevent-stream\nCache-Control: no-cache\nConnection: keep-alive\n\nid: 1\nevent: engine_start\ndata: {\"message\":\"Engine run started in approval mode\",\"executionMode\":\"approval\"}\n\nid: 2\nevent: engine_complete\ndata: {\"message\":\"Engine run completed: evaluated 97, flagged 12\",\"evaluated\":97,\"flagged\":12}\n","text",[153,2289,2286],{"__ignoreMap":176},[149,2291,2292],{},"Key features:",[2294,2295,2296,2303,2306,2309],"ul",{},[1834,2297,2298,2299,2302],{},"Auto-increment event IDs for replay support via ",[153,2300,2301],{},"Last-Event-ID"," header",[1834,2304,2305],{},"In-memory ring buffer (last 100 events) for reconnection replay",[1834,2307,2308],{},"Keepalive comments every 30 seconds to prevent proxy timeouts",[1834,2310,2311],{},"Auto-reconnect with exponential backoff on the client side",[158,2313,2315],{"id":2314},"database-schema","Database Schema",[149,2317,2318],{},"The database uses two purpose-specific tables instead of a single overloaded table:",[163,2320,2322],{"id":2321},"approval-queue","Approval Queue",[149,2324,2325,2326,2329,2330,2333,2334,2337],{},"Active approval queue items with a state machine (",[153,2327,2328],{},"pending"," → ",[153,2331,2332],{},"approved","\u002F",[153,2335,2336],{},"rejected","):",[492,2339,2340,2352],{},[495,2341,2342],{},[498,2343,2344,2347,2350],{},[501,2345,2346],{},"Column",[501,2348,2349],{},"Type",[501,2351,1179],{},[511,2353,2354,2367,2380,2409,2421,2433,2445,2461,2473,2490,2503,2515],{},[498,2355,2356,2361,2364],{},[516,2357,2358],{},[153,2359,2360],{},"id",[516,2362,2363],{},"INTEGER",[516,2365,2366],{},"Primary key",[498,2368,2369,2374,2377],{},[516,2370,2371],{},[153,2372,2373],{},"media_name",[516,2375,2376],{},"TEXT",[516,2378,2379],{},"Item title",[498,2381,2382,2387,2389],{},[516,2383,2384],{},[153,2385,2386],{},"media_type",[516,2388,2376],{},[516,2390,2391,1593,2394,1593,2397,1593,2400,1593,2403,1593,2406],{},[153,2392,2393],{},"movie",[153,2395,2396],{},"show",[153,2398,2399],{},"season",[153,2401,2402],{},"episode",[153,2404,2405],{},"artist",[153,2407,2408],{},"book",[498,2410,2411,2416,2418],{},[516,2412,2413],{},[153,2414,2415],{},"reason",[516,2417,2376],{},[516,2419,2420],{},"Score explanation",[498,2422,2423,2428,2430],{},[516,2424,2425],{},[153,2426,2427],{},"score_details",[516,2429,2376],{},[516,2431,2432],{},"JSON-encoded score breakdown",[498,2434,2435,2440,2442],{},[516,2436,2437],{},[153,2438,2439],{},"size_bytes",[516,2441,2363],{},[516,2443,2444],{},"File size",[498,2446,2447,2452,2454],{},[516,2448,2449],{},[153,2450,2451],{},"integration_id",[516,2453,2363],{},[516,2455,2456,2457,2460],{},"FK to ",[153,2458,2459],{},"integration_configs"," (required)",[498,2462,2463,2468,2470],{},[516,2464,2465],{},[153,2466,2467],{},"external_id",[516,2469,2376],{},[516,2471,2472],{},"External ID in the integration",[498,2474,2475,2480,2482],{},[516,2476,2477],{},[153,2478,2479],{},"status",[516,2481,2376],{},[516,2483,2484,1593,2486,1593,2488],{},[153,2485,2328],{},[153,2487,2332],{},[153,2489,2336],{},[498,2491,2492,2497,2500],{},[516,2493,2494],{},[153,2495,2496],{},"snoozed_until",[516,2498,2499],{},"DATETIME",[516,2501,2502],{},"When snooze expires (rejected items)",[498,2504,2505,2510,2512],{},[516,2506,2507],{},[153,2508,2509],{},"created_at",[516,2511,2499],{},[516,2513,2514],{},"Row creation",[498,2516,2517,2522,2524],{},[516,2518,2519],{},[153,2520,2521],{},"updated_at",[516,2523,2499],{},[516,2525,2526],{},"Last state transition",[163,2528,2530],{"id":2529},"audit-log","Audit Log",[149,2532,2533],{},"Permanent, append-only history of deletions and dry-runs:",[492,2535,2536,2546],{},[495,2537,2538],{},[498,2539,2540,2542,2544],{},[501,2541,2346],{},[501,2543,2349],{},[501,2545,1179],{},[511,2547,2548,2558,2568,2579,2589,2599,2619,2629,2642],{},[498,2549,2550,2554,2556],{},[516,2551,2552],{},[153,2553,2360],{},[516,2555,2363],{},[516,2557,2366],{},[498,2559,2560,2564,2566],{},[516,2561,2562],{},[153,2563,2373],{},[516,2565,2376],{},[516,2567,2379],{},[498,2569,2570,2574,2576],{},[516,2571,2572],{},[153,2573,2386],{},[516,2575,2376],{},[516,2577,2578],{},"Media type",[498,2580,2581,2585,2587],{},[516,2582,2583],{},[153,2584,2415],{},[516,2586,2376],{},[516,2588,2420],{},[498,2590,2591,2595,2597],{},[516,2592,2593],{},[153,2594,2427],{},[516,2596,2376],{},[516,2598,2432],{},[498,2600,2601,2606,2608],{},[516,2602,2603],{},[153,2604,2605],{},"action",[516,2607,2376],{},[516,2609,2610,1593,2613,1593,2616],{},[153,2611,2612],{},"deleted",[153,2614,2615],{},"dry_run",[153,2617,2618],{},"dry_delete",[498,2620,2621,2625,2627],{},[516,2622,2623],{},[153,2624,2439],{},[516,2626,2363],{},[516,2628,2444],{},[498,2630,2631,2635,2637],{},[516,2632,2633],{},[153,2634,2451],{},[516,2636,2363],{},[516,2638,2456,2639,2641],{},[153,2640,2459],{}," (nullable — preserved on integration delete)",[498,2643,2644,2648,2650],{},[516,2645,2646],{},[153,2647,2509],{},[516,2649,2499],{},[516,2651,2514],{},[163,2653,2655],{"id":2654},"activity-events","Activity Events",[149,2657,2658],{},"Transient dashboard feed with 7-day retention:",[492,2660,2661,2671],{},[495,2662,2663],{},[498,2664,2665,2667,2669],{},[501,2666,2346],{},[501,2668,2349],{},[501,2670,1179],{},[511,2672,2673,2683,2695,2707,2719],{},[498,2674,2675,2679,2681],{},[516,2676,2677],{},[153,2678,2360],{},[516,2680,2363],{},[516,2682,2366],{},[498,2684,2685,2690,2692],{},[516,2686,2687],{},[153,2688,2689],{},"event_type",[516,2691,2376],{},[516,2693,2694],{},"Event type identifier",[498,2696,2697,2702,2704],{},[516,2698,2699],{},[153,2700,2701],{},"message",[516,2703,2376],{},[516,2705,2706],{},"Human-readable message",[498,2708,2709,2714,2716],{},[516,2710,2711],{},[153,2712,2713],{},"metadata",[516,2715,2376],{},[516,2717,2718],{},"Optional JSON payload",[498,2720,2721,2725,2727],{},[516,2722,2723],{},[153,2724,2509],{},[516,2726,2499],{},[516,2728,2514],{},[158,2730,2732],{"id":2731},"frontend-architecture","Frontend Architecture",[163,2734,2736],{"id":2735},"sse-integration","SSE Integration",[149,2738,2739,2740,2743,2744,2747],{},"The frontend uses a singleton ",[153,2741,2742],{},"useEventStream"," composable that maintains a single ",[153,2745,2746],{},"EventSource"," connection shared across all components:",[171,2749,2751],{"className":173,"code":2750,"language":175,"meta":176,"style":176},"flowchart LR\n    SSE_ENDPOINT[\"GET \u002Fapi\u002Fv1\u002Fevents\"]\n    EVENT_SOURCE[\"EventSource\u003Cbr\u002F>(singleton)\"]\n    ENGINE_CTL[\"useEngineControl\u003Cbr\u002F>Engine state updates\"]\n    APPROVAL[\"useApprovalQueue\u003Cbr\u002F>Queue refresh\"]\n    DASHBOARD[\"index.vue\u003Cbr\u002F>Activity feed + stats\"]\n    BANNER[\"ConnectionBanner\u003Cbr\u002F>Connection state\"]\n\n    SSE_ENDPOINT --> EVENT_SOURCE\n    EVENT_SOURCE --> ENGINE_CTL\n    EVENT_SOURCE --> APPROVAL\n    EVENT_SOURCE --> DASHBOARD\n    EVENT_SOURCE --> BANNER\n",[153,2752,2753,2757,2762,2767,2772,2777,2782,2787,2791,2796,2801,2806,2811],{"__ignoreMap":176},[180,2754,2755],{"class":182,"line":17},[180,2756,321],{},[180,2758,2759],{"class":182,"line":22},[180,2760,2761],{},"    SSE_ENDPOINT[\"GET \u002Fapi\u002Fv1\u002Fevents\"]\n",[180,2763,2764],{"class":182,"line":27},[180,2765,2766],{},"    EVENT_SOURCE[\"EventSource\u003Cbr\u002F>(singleton)\"]\n",[180,2768,2769],{"class":182,"line":53},[180,2770,2771],{},"    ENGINE_CTL[\"useEngineControl\u003Cbr\u002F>Engine state updates\"]\n",[180,2773,2774],{"class":182,"line":116},[180,2775,2776],{},"    APPROVAL[\"useApprovalQueue\u003Cbr\u002F>Queue refresh\"]\n",[180,2778,2779],{"class":182,"line":66},[180,2780,2781],{},"    DASHBOARD[\"index.vue\u003Cbr\u002F>Activity feed + stats\"]\n",[180,2783,2784],{"class":182,"line":214},[180,2785,2786],{},"    BANNER[\"ConnectionBanner\u003Cbr\u002F>Connection state\"]\n",[180,2788,2789],{"class":182,"line":220},[180,2790,196],{"emptyLinePlaceholder":195},[180,2792,2793],{"class":182,"line":226},[180,2794,2795],{},"    SSE_ENDPOINT --> EVENT_SOURCE\n",[180,2797,2798],{"class":182,"line":232},[180,2799,2800],{},"    EVENT_SOURCE --> ENGINE_CTL\n",[180,2802,2803],{"class":182,"line":238},[180,2804,2805],{},"    EVENT_SOURCE --> APPROVAL\n",[180,2807,2808],{"class":182,"line":243},[180,2809,2810],{},"    EVENT_SOURCE --> DASHBOARD\n",[180,2812,2813],{"class":182,"line":249},[180,2814,2815],{},"    EVENT_SOURCE --> BANNER\n",[2294,2817,2818,2824,2827,2830],{},[1834,2819,2820,2823],{},[153,2821,2822],{},"app.vue"," initializes the SSE connection on mount when authenticated",[1834,2825,2826],{},"Components subscribe to specific event types and react accordingly",[1834,2828,2829],{},"Engine state, approval queue, and activity feed update in real-time without polling",[1834,2831,2832,2835],{},[153,2833,2834],{},"ConnectionBanner.vue"," uses SSE connection state as the primary health indicator",[163,2837,2839],{"id":2838},"page-structure","Page Structure",[492,2841,2842,2854],{},[495,2843,2844],{},[498,2845,2846,2849,2852],{},[501,2847,2848],{},"Page",[501,2850,2851],{},"Route",[501,2853,509],{},[511,2855,2856,2868,2881,2894,2906,2918,2931,2944],{},[498,2857,2858,2861,2865],{},[516,2859,2860],{},"Dashboard",[516,2862,2863],{},[153,2864,2333],{},[516,2866,2867],{},"Disk groups, approval queue, activity feed, engine controls, sparklines",[498,2869,2870,2873,2878],{},[516,2871,2872],{},"Library",[516,2874,2875],{},[153,2876,2877],{},"\u002Flibrary",[516,2879,2880],{},"Browse (smart filters, virtual scrolling) + History (audit log) — 2 tabs",[498,2882,2883,2886,2891],{},[516,2884,2885],{},"Audit",[516,2887,2888],{},[153,2889,2890],{},"\u002Faudit",[516,2892,2893],{},"Full deletion and dry-run history",[498,2895,2896,2898,2903],{},[516,2897,2130],{},[516,2899,2900],{},[153,2901,2902],{},"\u002Frules",[516,2904,2905],{},"Cascading rule builder, drag-and-drop sort, rule impact badges",[498,2907,2908,2910,2915],{},[516,2909,1984],{},[516,2911,2912],{},[153,2913,2914],{},"\u002Fsettings",[516,2916,2917],{},"Preferences, integrations, notifications, auth",[498,2919,2920,2923,2928],{},[516,2921,2922],{},"Help",[516,2924,2925],{},[153,2926,2927],{},"\u002Fhelp",[516,2929,2930],{},"Scoring guide, FAQ, about section",[498,2932,2933,2936,2941],{},[516,2934,2935],{},"Login",[516,2937,2938],{},[153,2939,2940],{},"\u002Flogin",[516,2942,2943],{},"Authentication",[498,2945,2946,2949,2954],{},[516,2947,2948],{},"Migrate",[516,2950,2951],{},[153,2952,2953],{},"\u002Fmigrate",[516,2955,2956],{},"Optional 1.x → 2.0 database migration stepper",[158,2958,2960],{"id":2959},"project-structure","Project Structure",[171,2962,2965],{"className":2963,"code":2964,"language":2287},[2285],"capacitarr\u002F\n├── backend\u002F                        # Go backend\n│   ├── main.go                     # Application entrypoint, wiring\n│   ├── internal\u002F\n│   │   ├── config\u002F                 # Environment variable loading\n│   │   ├── cache\u002F                  # Generic TTL cache\n│   │   ├── db\u002F                     # SQLite models, schema migrations\n│   │   ├── engine\u002F                 # Scoring + rule evaluation\n│   │   ├── events\u002F                 # Event bus, typed events, SSE broadcaster, activity persister\n│   │   ├── integrations\u002F           # *arr, Plex, Jellyfin, Emby, Seerr, Tautulli, Jellystat, Tracearr clients + registry + enrichment pipeline\n│   │   ├── jobs\u002F                   # Cron scheduling (retention cleanup, time-series rollups)\n│   │   ├── notifications\u002F          # Discord, Apprise notification senders + HTTP client\n│   │   ├── poller\u002F                 # Engine orchestrator (scheduled disk monitoring)\n│   │   ├── migration\u002F              # 1.x → 2.0 database migration detection + import\n│   │   ├── services\u002F               # Service layer (business logic)\n│   │   ├── testutil\u002F               # Shared test helpers (in-memory DB, fixtures)\n│   │   └── logger\u002F                 # Structured logging\n│   └── routes\u002F                     # REST API handlers + middleware\n├── frontend\u002F                       # Nuxt 4 frontend\n│   ├── app\u002F\n│   │   ├── components\u002F             # Vue components (shadcn-vue based)\n│   │   ├── composables\u002F            # Vue composables (useEventStream, useEngineControl, etc.)\n│   │   ├── pages\u002F                  # Nuxt pages (dashboard, library, audit, rules, settings, help, login, migrate)\n│   │   ├── locales\u002F                # i18n translations (22 languages)\n│   │   ├── types\u002F                  # TypeScript type definitions\n│   │   └── assets\u002Fcss\u002F             # Tailwind CSS + theme variables\n│   └── nuxt.config.ts              # Nuxt configuration\n├── site\u002F                           # Project marketing site (Nuxt UI Pro)\n├── docs\u002F                           # Documentation\n│   └── reference\u002Fapi\u002F              # OpenAPI spec, examples, workflows\n├── scripts\u002F                        # Release utility scripts (Discord notify, Docker build\u002Fmirror)\n├── Dockerfile                      # Multi-stage build (Node → Go → Alpine)\n└── Makefile                        # CI\u002FCD targets (lint, test, security, build)\n",[153,2966,2964],{"__ignoreMap":176},[2968,2969,2970],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":176,"searchDepth":17,"depth":22,"links":2972},[2973,2977,2978,2990,2995,2999],{"id":160,"depth":22,"text":161,"children":2974},[2975,2976],{"id":165,"depth":27,"text":166},{"id":307,"depth":27,"text":308},{"id":489,"depth":22,"text":490},{"id":594,"depth":22,"text":595,"children":2979},[2980,2981,2982,2983,2984,2985,2986,2987,2988,2989],{"id":598,"depth":27,"text":547},{"id":987,"depth":27,"text":707},{"id":1159,"depth":27,"text":1160},{"id":1372,"depth":27,"text":1373},{"id":1435,"depth":27,"text":1436},{"id":1603,"depth":27,"text":1604},{"id":1741,"depth":27,"text":1742},{"id":1858,"depth":27,"text":1859},{"id":1939,"depth":27,"text":1940},{"id":2273,"depth":27,"text":2274},{"id":2314,"depth":22,"text":2315,"children":2991},[2992,2993,2994],{"id":2321,"depth":27,"text":2322},{"id":2529,"depth":27,"text":2530},{"id":2654,"depth":27,"text":2655},{"id":2731,"depth":22,"text":2732,"children":2996},[2997,2998],{"id":2735,"depth":27,"text":2736},{"id":2838,"depth":27,"text":2839},{"id":2959,"depth":22,"text":2960},"Capacitarr is a single-container application that bundles a Go backend, a Nuxt 4 (Vue 3) frontend, and a SQLite database. The frontend is statically generated at build time and embedded into the Go binary via go:embed, producing a single self-contained executable.","md",null,{},{"order":17},{"title":109,"description":3000},"qspQ1aK5vy58_IBF-3w0NotIUSsY2FI47DdrN7WKNqs",[3008,3010],{"title":105,"path":106,"stem":107,"description":3009,"order":27,"children":-1},"Multi-step guides for typical Capacitarr API tasks. Each workflow combines several API calls in sequence.",{"title":113,"path":114,"stem":115,"description":3011,"order":116,"children":-1},"Capacitarr uses a tag-triggered release pipeline powered by git-cliff, GoReleaser, and GitHub Actions. Releases follow Semantic Versioning and are driven by Conventional Commits.",1776649613243]