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, and MudSnackbarProvider for 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 ProjectId from the current URL segments.
  • Loads the project name from ProjectStore (or fetches from GET /api/projects if cache is empty).
  • Listens to NavigationManager.LocationChanged to 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 TypeJoin MethodLeave MethodUsed By
DocumentJoinDocumentAsync(Guid)LeaveDocumentAsync(Guid)DocumentEditor
ProjectJoinProjectAsync(int)LeaveProjectAsync(int)ProjectDashboard, DocumentsList

Events:

EventPayload TypeDescription
OnProgressDocumentProgressEventBlock-level progress updates
OnDocumentCompletedDocumentCompletedEventEntire document generation completed
OnErrorDocumentErrorEventError during generation
OnDocumentListChangedDocumentListChangedEventAny document in a project changed status
OnDocumentUpdatedDocumentUpdatedEventDirect document status update
OnBlockUpdatedBlockUpdatedEventSingle block updated
OnBlocksAddedBlocksAddedEventNew blocks added to a section
OnSectionsAddedSectionsAddedEventNew sections added under a parent
OnBlockRemovedBlockRemovedEventBlock removed
OnStateChanged(none)Simplified “something changed” notification

⚠️ Important: All components subscribing to DocumentStateService events must implement IDisposable or IAsyncDisposable to 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

PropertyTypeDescription
ProjectsList<GetProjectResponse>?Cached project list
HasDatabooltrue when Projects is non-null
MethodDescription
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

PropertyTypeDescription
ProjectDocumentsDictionary<int, List<DocumentSummaryResponse>>Document summary lists keyed by project ID
DocumentDetailsDictionary<Guid, DocumentResponse>Full document payloads keyed by document ID
MethodDescription
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: ClearForProject only removes summary lists. The DocumentDetails dictionary has no dedicated invalidation method; detail entries remain in memory for the session lifetime.


TemplateStore

File: StateContainer/TemplateStore.cs

PropertyTypeDescription
ProjectTemplatesDictionary<int, List<TemplateResponse>>Template lists keyed by project ID
MethodDescription
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

MutationCache ActionReason
New-project.razor after POST /api/projectsProjectStore.Clear()Forces re-fetch of project list
CreateTemplate.razor after POST /api/documents/templatesTemplateStore.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/instantiateDocumentStore.ClearForProject(ProjectId)Includes the new document
Full page reload or new tabNew store instancesResets all in-memory state