From 46cc119c81151ec7db3b1826d54c5098fefc3433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Fri, 5 Jun 2026 11:44:47 +0200 Subject: [PATCH] fix(reader): resolve PR #69 code review comments under #issuecomment-429 --- README.md | 10 ++++ .../Molecules/IntelligenceToolbar.razor | 2 +- .../Molecules/IntelligenceToolbar.razor.css | 20 ++++---- .../Components/Organisms/ReaderCanvas.razor | 21 +++++++- .../Services/KnowledgeCoordinator.cs | 4 ++ src/NexusReader.UI.Shared/wwwroot/app.css | 50 +------------------ .../wwwroot/js/selectionHandler.js | 29 +++++++++-- 7 files changed, 71 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index fb573bc..3f8ac51 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,13 @@ Run test suite: ```bash dotnet test --no-restore ``` + +## 🗄️ Database Migrations + +Automatic database migrations at startup (`MigrateAsync()`) have been disabled to ensure compatibility with Native AOT compilation and prevent locking issues in multi-instance environments. + +To apply database migrations locally, run the EF Core migration command from the solution root: + +```bash +dotnet ef database update --project src/NexusReader.Infrastructure --startup-project src/NexusReader.Web +``` diff --git a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor index f3fc843..840acdc 100644 --- a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor +++ b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor @@ -61,7 +61,7 @@ private Task HandleUpdate() => InvokeAsync(StateHasChanged); - private async Task HandleThemeChangedAsync() => await InvokeAsync(StateHasChanged); + private Task HandleThemeChangedAsync() => InvokeAsync(StateHasChanged); public void Dispose() { diff --git a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css index 9de714f..a0abe27 100644 --- a/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css +++ b/src/NexusReader.UI.Shared/Components/Molecules/IntelligenceToolbar.razor.css @@ -82,41 +82,39 @@ /* Light mode overrides */ .theme-light .intelligence-toolbar { - background: #ffffff; + background: #f5f5f4; border-right: 1px solid rgba(0, 0, 0, 0.08); - box-shadow: inset -1px 0 0 rgba(0,0,0,0.02); + box-shadow: inset -2px 0 10px rgba(0, 0, 0, 0.02); } .theme-light .toolbar-item { - color: #71717a; + color: #78716c; } .theme-light .toolbar-item:hover { color: #10b981; background: rgba(16, 185, 129, 0.05); - box-shadow: 0 0 15px rgba(16, 185, 129, 0.15); - filter: drop-shadow(0 0 5px rgba(16, 185, 129, 0.2)); + box-shadow: 0 0 10px rgba(16, 185, 129, 0.1); + filter: none; } .theme-light .toolbar-item.active { color: #10b981; background: rgba(16, 185, 129, 0.08); - box-shadow: 0 0 20px rgba(16, 185, 129, 0.25); - filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.3)); + box-shadow: 0 0 15px rgba(16, 185, 129, 0.15); + filter: none; } .theme-light .toolbar-item.active::after { background: #10b981; - box-shadow: 0 0 8px rgba(16, 185, 129, 0.5); + box-shadow: none; } .theme-light .toolbar-item.focus-active { color: #10b981; - filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.3)); + filter: none; } - - .theme-light .toolbar-separator { background: rgba(0, 0, 0, 0.08); } diff --git a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor index 98d2323..736442a 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor +++ b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor @@ -99,6 +99,7 @@ private bool _isMobile = false; private DotNetObjectReference? _selfReference; private IJSObjectReference? _viewportModule; + private IJSObjectReference? _selectionModule; protected override async Task OnInitializedAsync() { @@ -201,10 +202,13 @@ { try { - var module = await JS.InvokeAsync("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js"); + if (_selectionModule == null) + { + _selectionModule = await JS.InvokeAsync("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js"); + } if (_selfReference != null) { - await module.InvokeVoidAsync("initSelectionListener", _selfReference, _containerRef); + await _selectionModule.InvokeVoidAsync("initSelectionListener", _selfReference, _containerRef); } } catch (Exception ex) @@ -440,6 +444,19 @@ InteractionService.OnTextSelected -= HandleTextSelected; SyncService.OnProgressReceived -= HandleSyncProgressReceived; + try + { + if (_selectionModule != null) + { + await _selectionModule.InvokeVoidAsync("destroySelectionListener"); + await _selectionModule.DisposeAsync(); + } + } + catch (Exception ex) + { + Logger.LogDebug(ex, "Failed to destroy JS selection listener."); + } + try { if (_viewportModule != null) diff --git a/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs b/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs index 5caf2f8..5d90270 100644 --- a/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs +++ b/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs @@ -233,6 +233,10 @@ public sealed partial class KnowledgeCoordinator : IDisposable, IAsyncDisposable { SelectionSummary = result.Value.Summary; } + else + { + _logger.LogWarning("[KnowledgeCoordinator] Selection summary request failed: {Errors}", string.Join(", ", result.Errors.Select(e => e.Message))); + } } finally { diff --git a/src/NexusReader.UI.Shared/wwwroot/app.css b/src/NexusReader.UI.Shared/wwwroot/app.css index 73af85f..cbe822d 100644 --- a/src/NexusReader.UI.Shared/wwwroot/app.css +++ b/src/NexusReader.UI.Shared/wwwroot/app.css @@ -176,52 +176,6 @@ --nexus-accent: #10b981; } - -/* Scoped Component overrides for Light Mode (Bypassing Blazor CSS isolation) */ -.theme-light .intelligence-toolbar { - background: #f5f5f4 !important; - border-right: 1px solid rgba(0, 0, 0, 0.08) !important; - box-shadow: inset -2px 0 10px rgba(0, 0, 0, 0.02) !important; -} - -.theme-light .intelligence-toolbar .toolbar-item { - color: #78716c !important; -} - -.theme-light .intelligence-toolbar .toolbar-item:hover { - color: #10b981 !important; - background: rgba(16, 185, 129, 0.05) !important; - box-shadow: 0 0 10px rgba(16, 185, 129, 0.1) !important; - filter: none !important; -} - -.theme-light .intelligence-toolbar .toolbar-item.active { - color: #10b981 !important; - background: rgba(16, 185, 129, 0.08) !important; - box-shadow: 0 0 15px rgba(16, 185, 129, 0.15) !important; - filter: none !important; -} - -.theme-light .intelligence-toolbar .toolbar-item.active::after { - background: #10b981 !important; - box-shadow: none !important; -} - -.theme-light .intelligence-toolbar .toolbar-item.focus-active { - color: #10b981 !important; - filter: none !important; -} - -.theme-light .intelligence-toolbar .toolbar-item.logout-item { - border-top: 1px solid rgba(0, 0, 0, 0.08) !important; - color: #a8a29e !important; -} - -.theme-light .intelligence-toolbar .toolbar-item.logout-item:hover { - color: #ef4444 !important; - filter: none !important; -} - .theme-light .knowledge-graph-container svg { background: radial-gradient(circle, #ffffff 0%, #e8e4da 100%) !important; } @@ -350,14 +304,14 @@ body { /* Platform Specific Tweaks */ -.platform-mobile .nexus-button { +.platform-mobile .nexus-btn { min-height: var(--touch-target-size); min-width: var(--touch-target-size); font-size: 1.1rem; padding: 12px 24px; } -.platform-desktop .nexus-button { +.platform-desktop .nexus-btn { min-height: 36px; font-size: 0.9rem; padding: 8px 16px; diff --git a/src/NexusReader.UI.Shared/wwwroot/js/selectionHandler.js b/src/NexusReader.UI.Shared/wwwroot/js/selectionHandler.js index ec78f9c..0c990fe 100644 --- a/src/NexusReader.UI.Shared/wwwroot/js/selectionHandler.js +++ b/src/NexusReader.UI.Shared/wwwroot/js/selectionHandler.js @@ -54,11 +54,17 @@ export function positionToolbar() { below: below }; } +let currentHandleSelection = null; +let currentMouseUpHandler = null; +let currentContainer = null; export function initSelectionListener(dotNetHelper, container) { if (!container) return; console.log("[SelectionHandler] Initializing..."); + + // Clean up any existing listeners first + destroySelectionListener(); const handleSelection = () => { const selection = window.getSelection(); @@ -104,9 +110,26 @@ export function initSelectionListener(dotNetHelper, container) { } }; - // Use multiple triggers for maximum reliability - document.addEventListener('selectionchange', handleSelection); - container.addEventListener('mouseup', () => setTimeout(handleSelection, 10)); + const mouseUpHandler = () => setTimeout(handleSelection, 10); + + currentHandleSelection = handleSelection; + currentMouseUpHandler = mouseUpHandler; + currentContainer = container; + + document.addEventListener('selectionchange', currentHandleSelection); + currentContainer.addEventListener('mouseup', currentMouseUpHandler); +} + +export function destroySelectionListener() { + if (currentHandleSelection) { + document.removeEventListener('selectionchange', currentHandleSelection); + currentHandleSelection = null; + } + if (currentMouseUpHandler && currentContainer) { + currentContainer.removeEventListener('mouseup', currentMouseUpHandler); + currentMouseUpHandler = null; + currentContainer = null; + } } export function getSelectionText() {