# Fix — CMS Page Was Visible on Slave (No Backend Capability Check) ## Problem The `/cms` page (managing registered CMS instances — an Owner-only feature backed by `SlpModularCms.Modules.Master`) was gated purely by role (`Owner`), with no awareness of whether the *connected backend* actually has the Master module loaded. Before this feature, this was never an issue — every deployed instance always had `Modules.Master` loaded. Now that a Master-less slave instance exists, an Owner using the frontend against the slave could still see the nav link and open `/cms`, where its data calls (`GET /api/v1/CmsInstances`) would 404 against a backend that has no such controller. ## Fix ### Backend - `SlpModularCms.Core.Hosting.ModuleOrchestrator` — added `ModuleNames` (public `IReadOnlyList`), listing the names of modules actually discovered on this instance. - New `SlpModularCms.Core.Hosting.SystemController` — `GET /api/v1/System/capabilities` returns `{ "modules": [...] }` for whichever instance is asked. - `SlpModularCms.Api/Program.cs` and `SlpModularCms.Api.Slave/Program.cs` — registered the `ModuleOrchestrator` instance itself as a DI singleton (`builder.Services.AddSingleton(orchestrator)`) so the new controller can inject it. ### Frontend - `src/api/types.ts` — added `SystemCapabilities { modules: string[] }`. - `src/api/useSystemCapabilities.ts` — new React Query hook (`staleTime: Infinity` — a backend's module set never changes mid-session), calling `/api/v1/System/capabilities`. - `src/components/auth/ModuleGuard.tsx` — new guard component (mirrors `RoleGuard`), hides its children and shows a "not available on this instance" message when the required module isn't in the backend's capability list. - `src/router.tsx` — `/cms` route now wraps its page in `` (inside the existing ``). - `src/components/layout/Sidebar.tsx` — the CMS nav item now also requires `capabilities.modules.includes('Master')` before rendering. - `src/i18n/locales/{nl,en}/translation.json` — added `errors.featureUnavailableTitle` / `errors.featureUnavailable`. - `src/mocks/system/handlers.ts` — new MSW handler for `*/System/capabilities`, defaulting to `['Availability', 'Identity', 'Master']` so existing CMS-related tests keep passing unchanged; registered in `src/mocks/index.ts`. - `src/components/layout/Sidebar.test.tsx` — updated the "Owner sees ... CMS" assertion to `findByTestId` (now async, since nav-cms visibility depends on the capabilities fetch), and added a new regression test: "Owner does not see CMS when the backend has no Master module (slave instance)". ## Verification Performed - `dotnet build` / `dotnet test` — succeed; all 193 backend tests still pass (module relocation from the earlier fix is untouched; this only adds new code). - `pnpm build` — succeeds, no type errors. - `pnpm test` — 210/210 pass in isolation (209/210 in the full parallel run; the 1 failure is the same pre-existing flaky `AddCmsInstanceDialog.test.tsx` timeout seen earlier in this feature's Build and Test, unrelated to this change — confirmed passing 5/5 when re-run alone). - **Live verification against real running instances**: started both `SlpModularCms.Api` (master) and `SlpModularCms.Api.Slave` against the local SQL Server and curled the new endpoint directly: - Master: `curl https://localhost:7221/api/v1/System/capabilities` → `{"modules":["Availability","Identity","Master"]}` - Slave: `curl https://localhost:7222/api/v1/System/capabilities` → `{"modules":["Availability","Identity"]}` - Confirms the capability check reflects each instance's actual loaded modules, not just a hardcoded assumption.