Layouts, Services & State
This page documents the application layouts, client-side services, and in-memory state containers that form the infrastructure of the MILTON.Client frontend.
Layouts (Layout/)
MainLayout
File: Layout/MainLayout.razor
The root layout for pages that do not belong to a specific project (e.g., Home.razor, New-project.razor, NotFound.razor).
Provides:
- MudBlazor theme provider with dark mode enabled by default.
MudPopoverProvider,MudDialogProvider, andMudSnackbarProviderfor overlays.- Blazor error UI overlay.
Theming: Defines a complete MudTheme with light and dark palettes. The dark palette follows a Catppuccin-inspired colour scheme. Typography uses the Inter font family stack.
ProjectLayout
File: Layout/ProjectLayout.razor
The layout for all project-scoped pages. Wraps the content in a full application shell with an app bar, collapsible sidebar, and main content area.
Provides:
- App bar: Home button, hamburger menu toggle, project name display, and dark/light mode toggle.
- Sidebar (
MudDrawer, mini variant with hover expand): Navigation links to Dashboard, Templates, Documents, Settings, and a “New Template” quick action. - Main content: Padded area where routed page content renders.
- Theme: Same MudTheme as
MainLayout.
Behaviour:
- Parses the
ProjectIdfrom the current URL segments. - Loads the project name from
ProjectStore(or fetches fromGET /api/projectsif cache is empty). - Listens to
NavigationManager.LocationChangedto re-resolve the project name when navigating between projects.
ReconnectModal
File: Layout/ReconnectModal.razor
An HTML <dialog> overlay shown when the Blazor SignalR circuit disconnects. Provides:
- Automatic rejoin attempt with animated indicator.
- Retry countdown for repeated failures.
- Manual “Retry” and “Resume” buttons for user intervention.
RedirectToLogin
File: Layout/RedirectToLogin.razor
Displayed when an unauthenticated user accesses a protected page. Automatically redirects to the authentication endpoint. If a previous login attempt failed (detected via authAttempted=1 query parameter), shows a manual “Retry login” button instead of auto-redirecting.
Services (Services/)
DocumentStateService
File: Services/DocumentStateService.cs
A scoped service that manages a persistent SignalR connection to the DocumentHub on the notification service. Exposes C# events so Blazor components can react to real-time document processing updates without full page reloads.
Connection:
- Connects to the notification service URL injected by Aspire (
services:notificationservice:https:0), falling back to the API origin for standalone WASM. - Automatic reconnect with escalating delays: 0s → 2s → 5s → 10s → 30s.
- Re-joins document and project groups automatically after reconnect.
- Safe to call
StartAsync()multiple times — only connects once.
Hub Groups:
| Group Type | Join Method | Leave Method | Used By |
|---|---|---|---|
| Document | JoinDocumentAsync(Guid) | LeaveDocumentAsync(Guid) | DocumentEditor |
| Project | JoinProjectAsync(int) | LeaveProjectAsync(int) | ProjectDashboard, DocumentsList |
Events:
| Event | Payload Type | Description |
|---|---|---|
OnProgress | DocumentProgressEvent | Block-level progress updates |
OnDocumentCompleted | DocumentCompletedEvent | Entire document generation completed |
OnError | DocumentErrorEvent | Error during generation |
OnDocumentListChanged | DocumentListChangedEvent | Any document in a project changed status |
OnDocumentUpdated | DocumentUpdatedEvent | Direct document status update |
OnBlockUpdated | BlockUpdatedEvent | Single block updated |
OnBlocksAdded | BlocksAddedEvent | New blocks added to a section |
OnSectionsAdded | SectionsAddedEvent | New sections added under a parent |
OnBlockRemoved | BlockRemovedEvent | Block removed |
OnStateChanged | (none) | Simplified “something changed” notification |
⚠️ Important: All components subscribing to
DocumentStateServiceevents must implementIDisposableorIAsyncDisposableto unregister their event handlers. Failing to do so creates memory leaks where destroyed components continue to receive events.
State Containers (StateContainer/)
The client uses scoped in-memory stores registered in Program.cs as an L1 cache. Each browser tab gets its own set of store instances. The cache survives navigation between routed pages but resets on full page reload or new browser tab.
builder.Services.AddScoped<ProjectStore>();
builder.Services.AddScoped<DocumentStore>();
builder.Services.AddScoped<TemplateStore>();ProjectStore
File: StateContainer/ProjectStore.cs
| Property | Type | Description |
|---|---|---|
Projects | List<GetProjectResponse>? | Cached project list |
HasData | bool | true when Projects is non-null |
| Method | Description |
|---|---|
Clear() | Sets Projects to null, forcing a re-fetch on next access |
Consumers: Home.razor, ProjectLayout.razor, ProjectDashboard.razor, ProjectConfig.razor
DocumentStore
File: StateContainer/DocumentStore.cs
| Property | Type | Description |
|---|---|---|
ProjectDocuments | Dictionary<int, List<DocumentSummaryResponse>> | Document summary lists keyed by project ID |
DocumentDetails | Dictionary<Guid, DocumentResponse> | Full document payloads keyed by document ID |
| Method | Description |
|---|---|
HasDataForProject(int) | Checks if summaries exist for a project |
ClearForProject(int) | Removes the summary list for a project |
Consumers: ProjectDashboard.razor, DocumentsList.razor, DocumentEditor.razor, CreateTemplate.razor, EditTemplate.razor, InstantiateTemplateDialog.razor
Note:
ClearForProjectonly removes summary lists. TheDocumentDetailsdictionary has no dedicated invalidation method; detail entries remain in memory for the session lifetime.
TemplateStore
File: StateContainer/TemplateStore.cs
| Property | Type | Description |
|---|---|---|
ProjectTemplates | Dictionary<int, List<TemplateResponse>> | Template lists keyed by project ID |
| Method | Description |
|---|---|
HasDataForProject(int) | Checks if templates exist for a project |
ClearForProject(int) | Removes the template list for a project |
Consumers: ProjectDashboard.razor, Templates.razor, EditTemplate.razor
Cache Invalidation Summary
| Mutation | Cache Action | Reason |
|---|---|---|
New-project.razor after POST /api/projects | ProjectStore.Clear() | Forces re-fetch of project list |
CreateTemplate.razor after POST /api/documents/templates | TemplateStore.ClearForProject(ProjectId) | Includes the new template |
EditTemplate.razor after PUT /api/documents/templates/{id} | TemplateStore.ClearForProject(ProjectId) | Prevents stale template data |
InstantiateTemplateDialog after POST /api/documents/templates/instantiate | DocumentStore.ClearForProject(ProjectId) | Includes the new document |
| Full page reload or new tab | New store instances | Resets all in-memory state |