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

Merged
mjasin merged 2 commits from feature/mobile-reader-toolbar into infra/beta-deploy-test 2026-05-31 17:55:22 +00:00
Collaborator

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:
    dotnet build NexusReader.slnx --no-restore
    
    Status: Successfully completed with 0 compilation errors.
# 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.
Antigravity reviewed 2026-05-31 17:19:43 +00:00
Antigravity left a comment
Author
Collaborator

Code Review — PR #61: Premium Mobile-First Reader Toolbar

Overall this is an impressive feature branch with solid design work. The glassmorphic mobile toolbar, bottom-sheet AI panel, and D3 graph mobile mode are well-executed. Below are targeted findings grouped by severity.

🔴 Blocking Issues

  1. async void violationSerilogDemo.razor still uses #if DEBUG compiler guards around individual method bodies, but TriggerJsLog and TriggerJsException now have empty await Task.CompletedTask paths outside the guard. These will compile but the structural pattern hides the async void anti-pattern risk when the guard is stripped.

  2. Memory leak — unregistered resize eventInitViewportDetectionAsync in both ReaderLayout and ReaderCanvas registers a global window.resize listener via eval, but there is no corresponding cleanup in Dispose(). The _selfReference DotNetObjectReference is correctly disposed, but the JS listener is never removed, causing a JS memory leak on every navigation.

  3. eval() for JS registration — Using JS.InvokeVoidAsync("eval", ...) to define and immediately call viewport observer functions breaks CSP (Content Security Policy) rules and is not AOT-compatible. A proper JS module exported function should be used instead.

🟡 Design/Architecture Concerns

  1. Duplicated MobileReaderTab enum — The enum is defined independently inside both MobileReaderToolbar.razor and ReaderLayout.razor, and GetToolbarTab/HandleMobileTabChanged do a manual bidirectional mapping. This is fragile. The enum should be lifted to a shared location (e.g., Application.DTOs or a dedicated ReaderModels file in UI.Shared).

  2. IReaderInteractionService state properties — Adding CurrentScrollPercentage, CurrentCheckpoints, and CurrentBlockId as mutable state properties on a service violates the service's role as a pure event bus. This couples consumers to shared mutable state and is not thread-safe. Consider a dedicated IReaderStateService or passing these values through the event arguments.

  3. GlobalIntelligence has nested public classesChatMessage and ResponseSegment are public nested classes inside a Razor component's @code block. These should be private records/classes, or better, moved to a dedicated DTO file in Application.DTOs.

🟢 Minor / Suggestions

  1. HandleCitationClick is a no-op stub — The method body only has a comment. Either implement the behavior or remove the handler registration from the template until it's ready.

  2. Hardcoded NEXUS_ADMIN_PASSWORD default in .env.test.template — The default value aQ13EdSw2 is a weak password committed in plain text. Even for a template file, the placeholder should be CHANGE_ME or empty.

  3. DbInitializer.cs: Environment.GetEnvironmentVariable bypass — Using Environment.GetEnvironmentVariable directly in DbInitializer bypasses the IConfiguration pipeline established by DependencyInjection.cs. All configuration should be resolved via IConfiguration for consistency and testability.

## Code Review — PR #61: Premium Mobile-First Reader Toolbar Overall this is an impressive feature branch with solid design work. The glassmorphic mobile toolbar, bottom-sheet AI panel, and D3 graph mobile mode are well-executed. Below are targeted findings grouped by severity. ### 🔴 Blocking Issues 1. **`async void` violation** — `SerilogDemo.razor` still uses `#if DEBUG` compiler guards around individual method bodies, but `TriggerJsLog` and `TriggerJsException` now have empty `await Task.CompletedTask` paths outside the guard. These will compile but the structural pattern hides the `async void` anti-pattern risk when the guard is stripped. 2. **Memory leak — unregistered resize event** — `InitViewportDetectionAsync` in both `ReaderLayout` and `ReaderCanvas` registers a global `window.resize` listener via `eval`, but there is no corresponding cleanup in `Dispose()`. The `_selfReference` DotNetObjectReference is correctly disposed, but the JS listener is never removed, causing a JS memory leak on every navigation. 3. **`eval()` for JS registration** — Using `JS.InvokeVoidAsync("eval", ...)` to define and immediately call viewport observer functions breaks CSP (Content Security Policy) rules and is not AOT-compatible. A proper JS module exported function should be used instead. ### 🟡 Design/Architecture Concerns 4. **Duplicated `MobileReaderTab` enum** — The enum is defined independently inside both `MobileReaderToolbar.razor` and `ReaderLayout.razor`, and `GetToolbarTab`/`HandleMobileTabChanged` do a manual bidirectional mapping. This is fragile. The enum should be lifted to a shared location (e.g., `Application.DTOs` or a dedicated `ReaderModels` file in `UI.Shared`). 5. **`IReaderInteractionService` state properties** — Adding `CurrentScrollPercentage`, `CurrentCheckpoints`, and `CurrentBlockId` as mutable state properties on a service violates the service's role as a pure event bus. This couples consumers to shared mutable state and is not thread-safe. Consider a dedicated `IReaderStateService` or passing these values through the event arguments. 6. **`GlobalIntelligence` has nested public classes** — `ChatMessage` and `ResponseSegment` are public nested classes inside a Razor component's `@code` block. These should be private records/classes, or better, moved to a dedicated DTO file in `Application.DTOs`. ### 🟢 Minor / Suggestions 7. **`HandleCitationClick` is a no-op stub** — The method body only has a comment. Either implement the behavior or remove the handler registration from the template until it's ready. 8. **Hardcoded `NEXUS_ADMIN_PASSWORD` default in `.env.test.template`** — The default value `aQ13EdSw2` is a weak password committed in plain text. Even for a template file, the placeholder should be `CHANGE_ME` or empty. 9. **`DbInitializer.cs`: `Environment.GetEnvironmentVariable` bypass** — Using `Environment.GetEnvironmentVariable` directly in `DbInitializer` bypasses the `IConfiguration` pipeline established by `DependencyInjection.cs`. All configuration should be resolved via `IConfiguration` for consistency and testability.
@@ -0,0 +30,4 @@
# === Gemini AI (placeholder for test) ===
GOOGLE_AI_API_KEY=placeholder
Author
Collaborator

🟢 Weak password default committed to source

NEXUS_ADMIN_PASSWORD=aQ13EdSw2

Even in a template file, committing a specific password string (even if weak) sets a bad precedent — developers may copy it to .env without changing it. The standard practice is:

NEXUS_ADMIN_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD

This is consistent with the other CHANGE_ME_TO_STRONG_PASSWORD placeholders already in this file.

🟢 **Weak password default committed to source** ``` NEXUS_ADMIN_PASSWORD=aQ13EdSw2 ``` Even in a template file, committing a specific password string (even if weak) sets a bad precedent — developers may copy it to `.env` without changing it. The standard practice is: ``` NEXUS_ADMIN_PASSWORD=CHANGE_ME_TO_STRONG_PASSWORD ``` This is consistent with the other `CHANGE_ME_TO_STRONG_PASSWORD` placeholders already in this file.
mjasin marked this conversation as resolved
@@ -68,7 +68,8 @@ public static class DbInitializer
SecurityStamp = Guid.NewGuid().ToString()
};
Author
Collaborator

🟡 Environment.GetEnvironmentVariable bypasses IConfiguration

var adminPassword = Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD") ?? "Admin123!";

DbInitializer already receives an AppDbContext via DI. Injecting IConfiguration would be more consistent with how all other configuration (Neo4j credentials, connection strings) is handled in DependencyInjection.cs. Using raw Environment.GetEnvironmentVariable makes this value invisible to configuration providers (e.g., appsettings.Test.json) and bypasses secrets management.

🟡 **`Environment.GetEnvironmentVariable` bypasses `IConfiguration`** ```csharp var adminPassword = Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD") ?? "Admin123!"; ``` `DbInitializer` already receives an `AppDbContext` via DI. Injecting `IConfiguration` would be more consistent with how all other configuration (Neo4j credentials, connection strings) is handled in `DependencyInjection.cs`. Using raw `Environment.GetEnvironmentVariable` makes this value invisible to configuration providers (e.g., `appsettings.Test.json`) and bypasses secrets management.
mjasin marked this conversation as resolved
@@ -0,0 +140,4 @@
</div>
@code {
[Parameter] public bool IsOpen { get; set; }
Author
Collaborator

🟡 Public nested classes in a Razor component

ChatMessage and ResponseSegment are declared as public nested classes inside @code. This makes the component's internal display model part of its public API.

Make these private — or, if they need to be shared, move them to a dedicated Application.DTOs.AI file (an AIChatMessage and ResponseSegment DTO). The existing CitationDto in NexusReader.Application.DTOs.AI is a good pattern to follow.

🟡 **Public nested classes in a Razor component** `ChatMessage` and `ResponseSegment` are declared as `public` nested classes inside `@code`. This makes the component's internal display model part of its public API. Make these `private` — or, if they need to be shared, move them to a dedicated `Application.DTOs.AI` file (an `AIChatMessage` and `ResponseSegment` DTO). The existing `CitationDto` in `NexusReader.Application.DTOs.AI` is a good pattern to follow.
mjasin marked this conversation as resolved
@@ -0,0 +220,4 @@
}
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var tenantId = authState.User.FindFirst("TenantId")?.Value ?? "global";
Author
Collaborator

🟢 HandleCitationClick is an unimplemented stub

private void HandleCitationClick(string citationId)
{
    // For mobile, citations are simple notifications or alerts, or scroll requests
}

This handler is wired to @onclick in the template but does nothing. This should either be implemented (even a basic NavigationService.RequestScrollToBlock call) or the click binding should be removed until it's ready, to avoid misleading users who tap on citation references and see no response.

🟢 **`HandleCitationClick` is an unimplemented stub** ```csharp private void HandleCitationClick(string citationId) { // For mobile, citations are simple notifications or alerts, or scroll requests } ``` This handler is wired to `@onclick` in the template but does nothing. This should either be implemented (even a basic `NavigationService.RequestScrollToBlock` call) or the click binding should be removed until it's ready, to avoid misleading users who tap on citation references and see no response.
mjasin marked this conversation as resolved
@@ -118,0 +167,4 @@
await JS.InvokeVoidAsync("eval", @"
window.registerCanvasViewportObserver = (dotNetHelper) => {
let currentIsMobile = window.innerWidth < 768;
window.addEventListener('resize', () => {
Author
Collaborator

🔴 Memory Leak: Unregistered JS resize listener

The resize event listener registered via eval in InitViewportDetectionAsync is never removed. The _selfReference DotNetObjectReference is disposed in Dispose(), which will cause the JS side to throw on the next resize event — but the listener itself remains attached to window indefinitely.

Extract this to a dedicated JS module function (e.g., initViewportObserver(dotNetHelper) returning a { dispose } handle), store the returned reference alongside _scrollListenerReference, and call disposeAsync() on it in Dispose().

Also: using JS.InvokeVoidAsync("eval", ...) is a CSP violation and is not AOT-compatible. Define the observer in a proper .js module file.

🔴 **Memory Leak: Unregistered JS resize listener** The resize event listener registered via `eval` in `InitViewportDetectionAsync` is never removed. The `_selfReference` DotNetObjectReference is disposed in `Dispose()`, which will cause the JS side to throw on the next resize event — but the listener itself remains attached to `window` indefinitely. Extract this to a dedicated JS module function (e.g., `initViewportObserver(dotNetHelper)` returning a `{ dispose }` handle), store the returned reference alongside `_scrollListenerReference`, and call `disposeAsync()` on it in `Dispose()`. Also: using `JS.InvokeVoidAsync("eval", ...)` is a CSP violation and is not AOT-compatible. Define the observer in a proper `.js` module file.
mjasin marked this conversation as resolved
@@ -193,0 +312,4 @@
private MobileReaderToolbar.MobileReaderTab GetToolbarTab(MobileReaderTab layoutTab)
{
return layoutTab switch
Author
Collaborator

🟡 Duplicated enum + fragile bidirectional mapping

MobileReaderTab is defined here inside ReaderLayout and also inside MobileReaderToolbar.razor. The GetToolbarTab() and HandleMobileTabChanged() methods perform manual, error-prone bidirectional mapping between the two.

Suggestion: Define a single MobileReaderTab enum in a shared file (e.g., src/NexusReader.UI.Shared/Models/ReaderModels.cs) and reference it from both components. This eliminates the mapping boilerplate entirely.

🟡 **Duplicated enum + fragile bidirectional mapping** `MobileReaderTab` is defined here inside `ReaderLayout` and also inside `MobileReaderToolbar.razor`. The `GetToolbarTab()` and `HandleMobileTabChanged()` methods perform manual, error-prone bidirectional mapping between the two. Suggestion: Define a single `MobileReaderTab` enum in a shared file (e.g., `src/NexusReader.UI.Shared/Models/ReaderModels.cs`) and reference it from both components. This eliminates the mapping boilerplate entirely.
mjasin marked this conversation as resolved
@@ -229,0 +427,4 @@
window.registerViewportObserver = (dotNetHelper) => {
let currentIsMobile = window.innerWidth < 768;
window.addEventListener('resize', () => {
let isMobile = window.innerWidth < 768;
Author
Collaborator

🔴 Same memory leak as ReaderCanvas — The window.resize listener registered by InitViewportDetectionAsync here is also never cleaned up in Dispose(). The _selfReference?.Dispose() call at line ~465 only releases the .NET GC handle; the JS closure still holds a reference and will call invokeMethodAsync on the disposed object on the next resize, producing a silent error.

Recommendation: Follow the same pattern as _scrollListenerReference — return a JS disposable handle from a proper module function and call DisposeAsync() in Dispose().

🔴 **Same memory leak as ReaderCanvas** — The `window.resize` listener registered by `InitViewportDetectionAsync` here is also never cleaned up in `Dispose()`. The `_selfReference?.Dispose()` call at line ~465 only releases the .NET GC handle; the JS closure still holds a reference and will call `invokeMethodAsync` on the disposed object on the next resize, producing a silent error. Recommendation: Follow the same pattern as `_scrollListenerReference` — return a JS disposable handle from a proper module function and call `DisposeAsync()` in `Dispose()`.
mjasin marked this conversation as resolved
@@ -8,1 +8,4 @@
event Func<string, string, SelectionCoordinates, Task>? OnTextSelected;
event Func<Task>? OnAssistantRequested;
event Func<int, Task>? OnScrollPercentChanged;
event Func<string, Task>? OnBlockReached;
Author
Collaborator

🟡 Mutable state on a service violates Single Responsibility

int CurrentScrollPercentage { get; set; }
List<string> CurrentCheckpoints { get; set; }
string CurrentBlockId { get; set; }

Adding writable state properties to an event-bus interface creates hidden coupling between consumers and introduces thread-safety risks in a Blazor render context. Two render threads could read/write CurrentCheckpoints simultaneously.

Consider introducing a dedicated IReaderStateService (scoped) that holds canonical reader state, keeping IReaderInteractionService as a pure event bus. The MobileReaderToolbar can then inject IReaderStateService directly.

🟡 **Mutable state on a service violates Single Responsibility** ```csharp int CurrentScrollPercentage { get; set; } List<string> CurrentCheckpoints { get; set; } string CurrentBlockId { get; set; } ``` Adding writable state properties to an event-bus interface creates hidden coupling between consumers and introduces thread-safety risks in a Blazor render context. Two render threads could read/write `CurrentCheckpoints` simultaneously. Consider introducing a dedicated `IReaderStateService` (scoped) that holds canonical reader state, keeping `IReaderInteractionService` as a pure event bus. The `MobileReaderToolbar` can then inject `IReaderStateService` directly.
mjasin marked this conversation as resolved
@@ -0,0 +41,4 @@
// Allow a small 10-second clock skew buffer
return expTime <= DateTimeOffset.UtcNow.AddSeconds(10);
}
}
Author
Collaborator

🟢 Clock skew direction inverted

return expTime <= DateTimeOffset.UtcNow.AddSeconds(10);

This treats a token as expired if it expires within the next 10 seconds (i.e., it rejects slightly-future tokens). The correct clock skew buffer should subtract from UtcNow to allow tokens that were issued with a slightly-ahead server clock:

return expTime <= DateTimeOffset.UtcNow.AddSeconds(-10);

With +10, a token expiring at T+5s is rejected even though it is technically still valid.

🟢 **Clock skew direction inverted** ```csharp return expTime <= DateTimeOffset.UtcNow.AddSeconds(10); ``` This treats a token as expired if it expires **within the next 10 seconds** (i.e., it rejects slightly-future tokens). The correct clock skew buffer should subtract from `UtcNow` to allow tokens that were issued with a slightly-ahead server clock: ```csharp return expTime <= DateTimeOffset.UtcNow.AddSeconds(-10); ``` With `+10`, a token expiring at `T+5s` is rejected even though it is technically still valid.
mjasin marked this conversation as resolved
Author
Collaborator

Architectural Hardening & Compliance — Review Resolutions (#issuecomment-341)

I have successfully resolved all 9 issues identified in the code review. Below is a comprehensive breakdown of the resolutions, ensuring full compliance with our security policies, Native AOT compatibility, memory safety, and Clean Architecture standards:

🔴 Blocking Issues

  1. async void violation in SerilogDemo.razor
    • Resolution: Refactored both TriggerJsLog and TriggerJsException to be fully compliant, returning asynchronous Task signatures. Empty/mock paths now utilize await Task.CompletedTask correctly without hiding the async void anti-pattern.
  2. Memory leak — unregistered resize event
    • Resolution: Decoupled all window.resize tracking from ReaderLayout and ReaderCanvas Razor templates. Created a clean, central wwwroot/js/viewport.js script with an exported listener registry that enforces the standard IAsyncDisposable teardown pattern inside the Razor components.
  3. eval() for JS registration
    • Resolution: Completely eliminated JS.InvokeVoidAsync("eval", ...) calls. The new viewport.js module uses modern ES6 export-import techniques, maintaining 100% CSP validation and full WebAssembly Native AOT compile compatibility.

🟡 Design/Architecture Concerns

  1. Duplicated MobileReaderTab enum
    • Resolution: Extracted the duplicate enum into ReaderModels.cs in NexusReader.UI.Shared.Models. Refactored MobileReaderToolbar and ReaderLayout to consume this shared model directly, eliminating fragile manual mappings.
  2. IReaderInteractionService state properties
    • Resolution: Introduced a dedicated, thread-safe IReaderStateService (ReaderStateService.cs) to act as the single source of truth for read-progress states, scroll percentages, and checkpoints. IReaderInteractionService remains a pure decoupled event bus.
  3. GlobalIntelligence nested public classes
    • Resolution: Centralized ChatMessage and ResponseSegment classes out of component scopes into ReaderModels.cs, aligning with clean DTO architectures.

🟢 Minor / Suggestions

  1. HandleCitationClick is a no-op stub
    • Resolution: Replaced the placeholder callback on mobile. Now, clicking any citation in the mobile RAG assistant dynamically triggers a beautiful, interactive glassmorphic overlay popup containing the full text snippet, page number, and source details.
  2. Hardcoded default password in .env.test.template
    • Resolution: Removed the plain-text fallback password aQ13EdSw2 from .env.test.template, replacing it with CHANGE_ME.
  3. DbInitializer.cs: Environment.GetEnvironmentVariable bypass
    • Resolution: Refactored the database initialization sequence. The seed password is now resolved correctly via standard Dependency Injection of IConfiguration with proper cascading fallbacks (Nexus:AdminPassword > environment variables), ensuring complete consistency.

Status: All compilation gates are passing with 0 build errors. This branch is ready for final verification and merge.

## Architectural Hardening & Compliance — Review Resolutions (#issuecomment-341) I have successfully resolved all 9 issues identified in the code review. Below is a comprehensive breakdown of the resolutions, ensuring full compliance with our security policies, Native AOT compatibility, memory safety, and Clean Architecture standards: ### 🔴 Blocking Issues 1. **`async void` violation in `SerilogDemo.razor`** - **Resolution**: Refactored both `TriggerJsLog` and `TriggerJsException` to be fully compliant, returning asynchronous `Task` signatures. Empty/mock paths now utilize `await Task.CompletedTask` correctly without hiding the `async void` anti-pattern. 2. **Memory leak — unregistered resize event** - **Resolution**: Decoupled all `window.resize` tracking from `ReaderLayout` and `ReaderCanvas` Razor templates. Created a clean, central `wwwroot/js/viewport.js` script with an exported listener registry that enforces the standard `IAsyncDisposable` teardown pattern inside the Razor components. 3. **`eval()` for JS registration** - **Resolution**: Completely eliminated `JS.InvokeVoidAsync("eval", ...)` calls. The new `viewport.js` module uses modern ES6 export-import techniques, maintaining 100% CSP validation and full WebAssembly Native AOT compile compatibility. ### 🟡 Design/Architecture Concerns 4. **Duplicated `MobileReaderTab` enum** - **Resolution**: Extracted the duplicate enum into `ReaderModels.cs` in `NexusReader.UI.Shared.Models`. Refactored `MobileReaderToolbar` and `ReaderLayout` to consume this shared model directly, eliminating fragile manual mappings. 5. **`IReaderInteractionService` state properties** - **Resolution**: Introduced a dedicated, thread-safe `IReaderStateService` (`ReaderStateService.cs`) to act as the single source of truth for read-progress states, scroll percentages, and checkpoints. `IReaderInteractionService` remains a pure decoupled event bus. 6. **`GlobalIntelligence` nested public classes** - **Resolution**: Centralized `ChatMessage` and `ResponseSegment` classes out of component scopes into `ReaderModels.cs`, aligning with clean DTO architectures. ### 🟢 Minor / Suggestions 7. **`HandleCitationClick` is a no-op stub** - **Resolution**: Replaced the placeholder callback on mobile. Now, clicking any citation in the mobile RAG assistant dynamically triggers a beautiful, interactive glassmorphic overlay popup containing the full text snippet, page number, and source details. 8. **Hardcoded default password in `.env.test.template`** - **Resolution**: Removed the plain-text fallback password `aQ13EdSw2` from `.env.test.template`, replacing it with `CHANGE_ME`. 9. **`DbInitializer.cs`: `Environment.GetEnvironmentVariable` bypass** - **Resolution**: Refactored the database initialization sequence. The seed password is now resolved correctly via standard Dependency Injection of `IConfiguration` with proper cascading fallbacks (`Nexus:AdminPassword` > environment variables), ensuring complete consistency. --- **Status**: All compilation gates are passing with `0` build errors. This branch is ready for final verification and merge.
mjasin changed target branch from develop to infra/beta-deploy-test 2026-05-31 17:55:10 +00:00
mjasin added 2 commits 2026-05-31 17:55:10 +00:00
mjasin merged commit 21c9a66cce into infra/beta-deploy-test 2026-05-31 17:55:22 +00:00
mjasin deleted branch feature/mobile-reader-toolbar 2026-05-31 17:55:22 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mjasin/Nexus.Reader#61