feat(ui): implement premium mobile-first reader toolbar, bottom navigation, and auth ux stabilization (#61)

# Description

This Pull Request integrates the premium mobile-first layout enhancements, a responsive, full-bleed Reader Toolbar, and critical authorization flow stabilizations for the NexusReader Blazor application (targeting .NET 10 with Native AOT compatibility).

Resolves #62
Resolves #63
Resolves #15

## Key Changes

### 📱 1. Mobile-First Reader Layout & Toolbar
- **Full-Bleed Responsive Layout**: Redesigned `ReaderLayout` to feature a premium mobile-first three-tab bottom navigation system (Chapters, Graph, Assistant) and a glassmorphic floating action button (FAB) for the AI assistant.
- **Header & Escaping Routes**: Built `MobileReaderToolbar` with seamless exit paths back to the "Pulpit" (dashboard) and smooth transitions.
- **Custom Iconography**: Added the custom `NexusIcon` component supporting dynamic theme styling and responsive layouts without relying on external CSS frameworks.

### 🔐 2. Authentication Flow UX Stabilization
- **WASM Transition Hydration**: Implemented `AuthenticationStatePersister` and loading preloaders to eliminate authorization race conditions during Blazor WASM interactive hydration.
- **AOT-Compatible JWT Validation**: Integrated a robust, AOT-compatible `JwtTokenValidator` with unit tests (`JwtTokenValidatorTests.cs`) to cleanly parse claims without throwing performance-heavy runtime exceptions.
- **Secure Header Propagation**: Standardized token transmission in WASM (`AuthenticationHeaderHandler.cs`) and MAUI Hybrid client layers (`MobileAuthenticationHeaderHandler.cs`), ensuring cookies are correctly propagated.

### 📊 3. D3.js Knowledge Graph & Interaction
- **Dynamic Viewport Synchronization**: Refactored `knowledgeGraph.js` to ensure the SVG graph behaves correctly under flexbox containment, handles panel expansion/collapse gracefully, and avoids infinite loop redraws.
- **Interaction Hook**: Connected graph node clicks directly to chapter jumps via a new `IReaderInteractionService` abstraction.

### 🏗️ 4. Infrastructure & Central Package Management (CPM)
- **Beta Deployment Configuration**: Added `.env.test.template`, `docker-compose.test.yml`, and `appsettings.Test.json` with hardened environment security guards.
- **Docker-Compose Cache Optimization**: Maintained CPM consistency during multi-stage Docker builds.

## Verification & Build Results
- Run a successful local build check:
  ```bash
  dotnet build NexusReader.slnx --no-restore
  ```
  **Status**: Successfully completed with `0` compilation errors.

---------

Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Reviewed-on: #61
Co-authored-by: Antigravity <antigravity@google.com>
Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-05-31 17:55:21 +00:00
committed by Marek Jaisński
parent a90507ad8a
commit 21c9a66cce
27 changed files with 1963 additions and 238 deletions
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using NexusReader.Domain.Entities;
using System;
using System.Linq;
@@ -16,6 +17,7 @@ public static class DbInitializer
using var scope = serviceProvider.CreateScope();
var passwordHasher = scope.ServiceProvider.GetRequiredService<IPasswordHasher<NexusUser>>();
var dbContextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<AppDbContext>>();
var configuration = scope.ServiceProvider.GetService<IConfiguration>();
using var dbContext = await dbContextFactory.CreateDbContextAsync();
try
@@ -68,7 +70,10 @@ public static class DbInitializer
SecurityStamp = Guid.NewGuid().ToString()
};
var adminPassword = Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD") ?? "Admin123!";
var adminPassword = configuration?["Nexus:AdminPassword"]
?? configuration?["NEXUS_ADMIN_PASSWORD"]
?? Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD")
?? "Admin123!";
adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, adminPassword);
dbContext.Users.Add(adminUser);