Shared hosting (e.g. mijnhostingpartner.nl) typically allows only one
site/app-pool, so SlpModularCms.Api now serves everything itself:
'/' for the customer's public website (deployed separately, not part
of this repo), '/admin' for the CMS admin SPA, and '/api/v1' for the
API as before.
- Program.cs: static files from wwwroot + SPA fallbacks per path so
client-side routing works for both frontends.
- frontend/: builds with base '/admin/' in production (dev unchanged),
router basepath follows suit.
- SlpModularCms.Api.csproj: publish now builds the admin frontend and
copies its output into wwwroot/admin automatically.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wi5qHAuq8UbzN4NLUFeKkJ
Adds AGENTS.md as the canonical description of the Application/Tests/Clients
solution layout, mirrored into CLAUDE.md, .junie/guidelines.md, and
.github/copilot-instructions.md so Claude, Junie, and Copilot all pick it up.
Also un-ignores .junie/ so guidelines.md is tracked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wi5qHAuq8UbzN4NLUFeKkJ
Marks aidlc-state.md as closed and logs the commit/push handoff in
audit.md; these notes were written after the prior commit had already
been created.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave,
pnpm dev:all), per-instance browser tab titles, and a backend
capability check (SystemController + useSystemCapabilities +
ModuleGuard) so a Master-only page is hidden on a slave instance
instead of assuming every backend has every module.
Master/slave protocol fixes surfaced by actually running master and
slave side by side locally:
- Deactivating a CMS instance (Inactive) now releases the slave's
master gate instead of leaving it stuck on its last pushed status.
- The periodic integrity check now also re-pushes status to every
reachable slave (previously URL-verification only) and runs once
immediately on startup.
- Added the originally-specified (but never implemented) slave-pull
path: a slave now periodically polls its own status from the master
(GET /api/v1/SlaveStatus) and fails open to Available if the master
is unreachable for too long, complementing the existing push.
- The slave's own Settings page can no longer "successfully" change
local availability while the master controls it; it's now locked
with an explanatory banner and the backend rejects the write with
409 instead of silently no-op'ing it.
- CMS instance status badges now match the dashboard's color/icon
styling instead of a plain grey badge.
Also corrected the master-cms-module design docs to match this
as-built behavior, and flagged (without a full rewrite) a larger,
pre-existing divergence between its inception-stage application
design and what construction actually built.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Relocates ModuleOrchestrator, ServiceCollectionExtensions, and
ApiPrefixConvention from SlpModularCms.Api into SlpModularCms.Core.Hosting
so a new Master-less SlpModularCms.Api.Slave host project (ports 5285/7222)
can share the same bootstrap code without duplicating it. This lets a
developer run a master instance and a slave instance side by side locally
to test the master/slave connection, without touching the existing
master/slave protocol itself.
Relocates the two orchestrator/convention test files from
Modules.Identity.Tests to Core.Tests, dropping an incidental
ProjectReference to SlpModularCms.Api that existed only for those tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Finishes the master-cms-module feature (Units 1-4): runs Build and
Test across master-backend, slave-availability-extension and
frontend-cms-page, fixes a missing Availability EF migration for
MasterRegistration and a TanStack Query v5 mutation-callback type
break, adds the missing MasterModule appsettings section, and
documents the module in README.md. Also seeds a tech-debt-backlog
feature to track dead config and pre-existing/introduced frontend
lint findings for later cleanup.
Chrome 89+ schemeful same-site treats http://localhost and https://localhost
as different sites, blocking SameSite=Strict cookies on cross-origin fetch
(e.g. Vite on port 5173, API on port 7221).
Fix: make CookieSameSite configurable per environment in JwtSettings.
- Default: Strict (production)
- appsettings.Development.json: None (allows cross-origin cookie in dev)
- When SameSite=None, Secure is always forced (browser requirement)
Revert the earlier Vite proxy approach in favour of this backend config.
VITE_API_BASE_URL remains a freely configurable URL in .env.local.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Chrome 89+ treats http://localhost and https://localhost as different sites
(schemeful same-site). The refreshToken cookie (SameSite=Strict) was not
sent when the Vite dev server (HTTP) made cross-origin fetch calls to the
.NET backend (HTTPS), causing a 401 on every page reload.
Fix: route /api/* through the Vite dev server proxy so all requests stay
same-origin. The cookie is now always sent and auth sessions survive reloads.
- vite.config.ts: proxy /api → https://localhost:7221 (secure:false for dev cert)
- .env.example: document proxy vs direct mode, set default VITE_API_BASE_URL to empty
- config.ts: accept empty string as valid apiBaseUrl alongside full URLs
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add AvailabilityStatusDetails record and GetStatusDetailsAsync() to
IAvailabilityService; implement in PersistentAvailabilityService so the
admin-set reason stored in the database is returned alongside the status
- AvailabilityController.GetStatus() now returns the stored message instead
of hardcoded English strings
- Add messageAvailable / messageMaintenance / messageUnavailable translation
keys in en + nl so default messages are fully translatable
- AvailabilityStatusBadge: Available always shows the translated default;
Maintenance and Unavailable show the custom admin reason when set,
otherwise fall back to the translated default
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Eager-load both en and nl translation bundles at i18n init to eliminate
the async gap that caused English flash when Dutch was the detected language
- LanguageSwitcher: use i18n.language (synchronous) instead of resolvedLanguage
(asynchronous) so the visual selection is always correct after switching
- AvailabilityController: remove hardcoded English messages ("System is running
normally.") from GET /availability/status; return empty string so the frontend
translations control the display text
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add @tanstack/react-query 5.101.0; wrap app with QueryClientProvider
- Add AvailabilityStatus type and AvailabilityResponse to api/types.ts
- Implement useAvailabilityStatus (staleTime 30s, stale-on-error preserved)
- Add AvailabilityStatusBadge with green/amber/red states and stale indicator
- Replace DashboardPage placeholder card with live availability widget
- Add MSW availability handler; update test/utils with QueryClientProvider
- 55/55 tests pass (FR-05)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add /settings (Owner only) and /profile routes under authenticatedRoute
so sidebar stays visible and layout is preserved
- Create SettingsPage and ProfilePage placeholder components (coming soon)
- Remove Profile nav item from Sidebar — profile now accessible via UserMenu
- Add Profile link to UserMenu dropdown above logout
- Update Sidebar tests to reflect profile-free nav
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Functional design documents were written based on self-answered questions
instead of asking the user. Removed pre-written docs and restored the plan
with open questions for the user to answer.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
ApiPrefixConvention already prepends api/v1 to all controllers.
AuthController had it hardcoded too, resulting in the doubled route
api/v1/api/v1/auth/login which caused a 404 on login attempts.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Call changeLanguage(values.language) after successful owner creation so
the UI immediately switches to the language the owner chose
- Call _markSystemInitialized() to update the router cache before navigating
to /login; without this the guard saw initialized=false and redirected
back to /setup, blocking the redirect
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Destructure confirmPassword out of form values in SetupPage and
InviteCompletePage so it is never sent to the backend
- Add global cursor:pointer rule for buttons, selects, labels and links
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Router catch block now falls back to { initialized: false } instead of
silently continuing, preventing unwanted redirect to /login when the
API call fails at startup
- Add Name field to CreateOwnerRequest and propagate to ApplicationUser.DisplayName
so the owner's display name is stored during initial setup
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add /api/v1/ prefix to all Unit 2 API calls (Setup/status, Setup/owner,
Invitation/validate, Invitation/complete) to match backend ApiPrefixConvention
- Fix setup status endpoint to POST /api/v1/Setup/owner (not /Setup)
- Handle PascalCase 'Initialized' response from .NET backend without camelCase policy
- Update all MSW mock handlers to match corrected /api/v1/ URL patterns
- Remove unused imports from RouteGuard.test.tsx
Root cause: backend uses ApiPrefixConvention('api/v1') but frontend calls
were missing the prefix, and .NET defaults to PascalCase JSON serialization.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Fix invitation MSW handler: use 'valid-token' to match test fixtures
- Add data-testid='setup-success' to SetupPage success state
- Change invite email input from readOnly to disabled (matches test assertion)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>