{"path":"NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","purpose":"Razor UI component that renders an ebook reader canvas, manages chapter loading, selection/highlight UX, JS observers/selection interop, and synchronizes reading progress across devices.","classification":{"role":"ui-component","layer":"frontend","confidence":0.9,"evidence":["Middleware pattern","Frontend path heuristic","Razor markup rendering blocks and UI elements (lines 18-56)","Injects UI and coordination services (lines 7-16)","Implements IDisposable and lifecycle methods (OnInitializedAsync, OnAfterRenderAsync)"]},"className":"ReaderCanvas","methods":[{"name":"OnInitializedAsync","line":71,"endLine":81,"signature":"() -> Task","purpose":"Component initialization: clears coordinator and subscribes to theme, navigation, interaction and sync events.","calls":[{"targetFile":"unknown","targetMethod":"ClearAsync","callLine":73,"paramSummary":"none"}],"actions":[{"id":"oninitializedasync_await_73_0","kind":"await","label":"Waits for async work","line":73,"detail":"await Coordinator.ClearAsync();","visibility":"secondary-visible","confidence":0.81},{"id":"subscribe-event_74","kind":"mapping","label":"Subscribe theme change","line":74,"detail":"ThemeService.OnThemeChanged += HandleUpdate","visibility":"detail-only","confidence":0.7},{"id":"subscribe-event_75","kind":"mapping","label":"Subscribe navigation change","line":75,"detail":"NavigationService.OnNavigationChanged += OnNavigationChanged","visibility":"detail-only","confidence":0.7},{"id":"subscribe-event_77","kind":"mapping","label":"Subscribe interaction events","line":77,"detail":"InteractionService.OnScrollToBlockRequested/OnHighlightBlockRequested/OnTextSelected += handlers","visibility":"detail-only","confidence":0.7},{"id":"subscribe-event_80","kind":"mapping","label":"Subscribe sync progress","line":80,"detail":"SyncService.OnProgressReceived += HandleSyncProgressReceived","visibility":"detail-only","confidence":0.7}]},{"name":"OnParametersSetAsync","line":83,"endLine":86,"signature":"() -> Task","purpose":"Triggered when component parameters are set; loads the chapter for the current navigation index.","calls":[{"targetFile":"self","targetMethod":"LoadChapterAsync","callLine":85,"paramSummary":"NavigationService.CurrentChapterIndex"}],"actions":[{"id":"orchestration_85","kind":"mapping","label":"Kick off chapter load","line":85,"detail":"Delegates to LoadChapterAsync with current index","visibility":"detail-only","confidence":0.7},{"id":"onparameterssetasync_await_85_0","kind":"await","label":"Waits for async work","line":85,"detail":"await LoadChapterAsync(NavigationService.CurrentChapterIndex);","visibility":"secondary-visible","confidence":0.81}]},{"name":"OnNavigationChanged","line":88,"endLine":95,"signature":"() -> Task","purpose":"Handles navigation changes by resetting selection/JS state and reloading the chapter.","calls":[{"targetFile":"self","targetMethod":"LoadChapterAsync","callLine":93,"paramSummary":"NavigationService.CurrentChapterIndex"}],"actions":[{"id":"state-reset_90","kind":"mapping","label":"Reset JS init and selection state","line":90,"detail":"_isJsInitialized=false; _selectedText=''; _selectionCoords=null","visibility":"detail-only","confidence":0.7},{"id":"onnavigationchanged_await_93_0","kind":"await","label":"Waits for async work","line":93,"detail":"await LoadChapterAsync(NavigationService.CurrentChapterIndex);","visibility":"secondary-visible","confidence":0.81},{"id":"ui-refresh_94","kind":"mapping","label":"Request UI update","line":94,"detail":"StateHasChanged()","visibility":"detail-only","confidence":0.7}]},{"name":"OnAfterRenderAsync","line":97,"endLine":115,"signature":"(firstRender: bool) -> Task","purpose":"After render hook: initializes sync, processes full page with coordinator on first render, and initializes JS observers/listeners when needed.","calls":[{"targetFile":"unknown","targetMethod":"InitializeAsync","callLine":101,"paramSummary":"none (SyncService.InitializeAsync)"},{"targetFile":"unknown","targetMethod":"ProcessFullPageAsync","callLine":105,"paramSummary":"GetFullPageContent()"},{"targetFile":"self","targetMethod":"InitializeObserverAsync","callLine":112,"paramSummary":"none"},{"targetFile":"self","targetMethod":"InitializeSelectionListenerAsync","callLine":113,"paramSummary":"none"}],"actions":[{"id":"onafterrenderasync_branch_99_0","kind":"branch","label":"Evaluates branch condition","line":99,"detail":"if (firstRender)","conditionSummary":"firstRender","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"guard-clause_99","kind":"guard-clause","label":"First render initialization","line":99,"conditionSummary":"firstRender == true","outcomeLabels":["init sync","process page"],"visibility":"detail-only","confidence":0.7},{"id":"onafterrenderasync_await_101_1","kind":"await","label":"Waits for async work","line":101,"detail":"await SyncService.InitializeAsync();","visibility":"secondary-visible","confidence":0.81},{"id":"state-change_102","kind":"mapping","label":"Enable interactivity","line":102,"detail":"_isInteractive = true","visibility":"detail-only","confidence":0.7},{"id":"onafterrenderasync_branch_103_2","kind":"branch","label":"Evaluates branch condition","line":103,"detail":"if (ViewModel != null)","conditionSummary":"ViewModel != null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"onafterrenderasync_await_105_3","kind":"await","label":"Waits for async work","line":105,"detail":"await Coordinator.ProcessFullPageAsync(GetFullPageContent());","visibility":"secondary-visible","confidence":0.81},{"id":"onafterrenderasync_branch_109_4","kind":"branch","label":"Evaluates branch condition","line":109,"detail":"if (ViewModel != null && !_isJsInitialized)","conditionSummary":"ViewModel != null && !_isJsInitialized","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"guard-clause_109","kind":"guard-clause","label":"JS initialization guard","line":109,"conditionSummary":"ViewModel != null && !_isJsInitialized","outcomeLabels":["init JS observer/listener"],"visibility":"detail-only","confidence":0.7},{"id":"onafterrenderasync_await_112_5","kind":"await","label":"Waits for async work","line":112,"detail":"await InitializeObserverAsync();","visibility":"secondary-visible","confidence":0.81},{"id":"onafterrenderasync_await_113_6","kind":"await","label":"Waits for async work","line":113,"detail":"await InitializeSelectionListenerAsync();","visibility":"secondary-visible","confidence":0.81}]},{"name":"InitializeSelectionListenerAsync","line":117,"endLine":128,"signature":"() -> Task","purpose":"Loads a JS module and registers a DOM selection listener via JS interop; logs warning on failure.","calls":[{"targetFile":"unknown","targetMethod":"InvokeAsync","callLine":121,"paramSummary":"\"import' './_content/.../selectionHandler.js'\" and returns module"},{"targetFile":"unknown","targetMethod":"InvokeVoidAsync","callLine":122,"paramSummary":"\"initSelectionListener\", DotNetObjectReference.Create(this), _containerRef"}],"actions":[{"id":"initializeselectionlistenerasync_try_119_0","kind":"try","label":"Begins protected execution","line":119,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"try-catch_119","kind":"mapping","label":"Fallback on JS init error","line":119,"detail":"catch exception -> Logger.LogWarning(...)","visibility":"detail-only","confidence":0.7},{"id":"external-call_121","kind":"external-call","label":"Import JS selection module","line":121,"detail":"JS.InvokeAsync('import', './_content/.../selectionHandler.js')","visibility":"detail-only","confidence":0.7},{"id":"initializeselectionlistenerasync_await_121_1","kind":"await","label":"Waits for async work","line":121,"detail":"var module = await JS.InvokeAsync(\"import\", \"./_content/NexusReader.UI.Shared/js/selectionHandler.js\");","visibility":"secondary-visible","confidence":0.81},{"id":"initializeselectionlistenerasync_await_122_2","kind":"await","label":"Waits for async work","line":122,"detail":"await module.InvokeVoidAsync(\"initSelectionListener\", DotNetObjectReference.Create(this), _containerRef);","visibility":"secondary-visible","confidence":0.81},{"id":"initializeselectionlistenerasync_catch_124_3","kind":"catch","label":"Handles exception path","line":124,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"initializeselectionlistenerasync_log_126_4","kind":"log","label":"Logs runtime state","line":126,"detail":"Logger.LogWarning(ex, \"Failed to initialize JS selection listener. Text selection will be unavailable.\");","visibility":"secondary-visible","confidence":0.92}]},{"name":"InitializeObserverAsync","line":130,"endLine":141,"signature":"() -> Task","purpose":"Loads JS observer module and initializes scroll/visibility observer; logs warning on failure.","calls":[{"targetFile":"unknown","targetMethod":"InvokeAsync","callLine":134,"paramSummary":"\"import' './_content/.../readerObserver.js'\" and returns module"},{"targetFile":"unknown","targetMethod":"InvokeVoidAsync","callLine":135,"paramSummary":"\"initObserver\", DotNetObjectReference.Create(this), \".reader-flow-container\", \".block-wrapper\""}],"actions":[{"id":"initializeobserverasync_try_132_0","kind":"try","label":"Begins protected execution","line":132,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"try-catch_132","kind":"mapping","label":"Fallback on observer init error","line":132,"detail":"catch exception -> Logger.LogWarning(...)","visibility":"detail-only","confidence":0.7},{"id":"external-call_134","kind":"external-call","label":"Import JS observer module","line":134,"detail":"JS.InvokeAsync('import', './_content/.../readerObserver.js')","visibility":"detail-only","confidence":0.7},{"id":"initializeobserverasync_await_134_1","kind":"await","label":"Waits for async work","line":134,"detail":"var module = await JS.InvokeAsync(\"import\", \"./_content/NexusReader.UI.Shared/js/readerObserver.js\");","visibility":"secondary-visible","confidence":0.81},{"id":"initializeobserverasync_await_135_2","kind":"await","label":"Waits for async work","line":135,"detail":"await module.InvokeVoidAsync(\"initObserver\", DotNetObjectReference.Create(this), \".reader-flow-container\", \".block-wrapper\");","visibility":"secondary-visible","confidence":0.81},{"id":"initializeobserverasync_catch_137_3","kind":"catch","label":"Handles exception path","line":137,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"initializeobserverasync_log_139_4","kind":"log","label":"Logs runtime state","line":139,"detail":"Logger.LogWarning(ex, \"Failed to initialize JS scroll observer. Reading progress sync will be unavailable.\");","visibility":"secondary-visible","confidence":0.92}]},{"name":"HandleBlockReached","line":143,"endLine":159,"signature":"(blockId: string, content: string) -> Task","purpose":"Invoked from JS when a block becomes visible; notifies coordinator and updates sync progress if ViewModel present.","calls":[{"targetFile":"unknown","targetMethod":"OnBlockReachedAsync","callLine":146,"paramSummary":"blockId, content (Coordinator)"},{"targetFile":"unknown","targetMethod":"UpdateProgressAsync","callLine":152,"paramSummary":"blockId, ViewModel.EbookId, progress, ViewModel.ChapterTitle, ViewModel.CurrentChapterIndex (SyncService)"}],"actions":[{"id":"external-invocation_146","kind":"mapping","label":"Notify coordinator of block reached","line":146,"detail":"Coordinator.OnBlockReachedAsync(blockId, content)","visibility":"detail-only","confidence":0.7},{"id":"handleblockreached_await_146_0","kind":"await","label":"Waits for async work","line":146,"detail":"await Coordinator.OnBlockReachedAsync(blockId, content);","visibility":"secondary-visible","confidence":0.81},{"id":"handleblockreached_branch_148_1","kind":"branch","label":"Evaluates branch condition","line":148,"detail":"if (ViewModel != null)","conditionSummary":"ViewModel != null","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"guard-clause_148","kind":"guard-clause","label":"Only update sync when ViewModel exists","line":148,"conditionSummary":"ViewModel != null","outcomeLabels":["compute progress","call UpdateProgressAsync"],"visibility":"detail-only","confidence":0.7},{"id":"computation_150","kind":"mapping","label":"Compute progress percent","line":150,"detail":"progress = ((CurrentChapterIndex +1)/TotalChapters)*100","visibility":"detail-only","confidence":0.7},{"id":"handleblockreached_await_152_2","kind":"await","label":"Waits for async work","line":152,"detail":"await SyncService.UpdateProgressAsync(","visibility":"secondary-visible","confidence":0.81}]},{"name":"HandleSyncProgressReceived","line":161,"endLine":167,"signature":"(blockId: string, timestamp: DateTime) -> Task","purpose":"Receives progress from other devices, logs it, scrolls to the corresponding node and requests UI update.","calls":[{"targetFile":"self","targetMethod":"ScrollToNodeAsync","callLine":165,"paramSummary":"blockId"}],"actions":[{"id":"log_163","kind":"log","label":"Log incoming sync progress","line":163,"detail":"Logger.LogInformation with blockId and timestamp","visibility":"detail-only","confidence":0.7},{"id":"handlesyncprogressreceived_log_163_0","kind":"log","label":"Logs runtime state","line":163,"detail":"Logger.LogInformation(\"[Sync] Received progress from another device: block {BlockId} at {Timestamp}\", blockId, timestamp);","visibility":"detail-only","confidence":0.92},{"id":"ui-action_165","kind":"mapping","label":"Scroll to block","line":165,"detail":"ScrollToNodeAsync(blockId) then StateHasChanged()","visibility":"detail-only","confidence":0.7},{"id":"handlesyncprogressreceived_await_165_1","kind":"await","label":"Waits for async work","line":165,"detail":"await ScrollToNodeAsync(blockId);","visibility":"secondary-visible","confidence":0.81},{"id":"handlesyncprogressreceived_await_166_2","kind":"await","label":"Waits for async work","line":166,"detail":"await InvokeAsync(StateHasChanged);","visibility":"secondary-visible","confidence":0.81}]},{"name":"HandleTextSelected","line":169,"endLine":177,"signature":"(text: string, blockId: string, coords: SelectionCoordinates) -> Task","purpose":"JS-invokable handler that records selected text, block id and coordinates and triggers UI update.","calls":[],"actions":[{"id":"log_172","kind":"log","label":"Debug log selection","line":172,"detail":"Logger.LogDebug with blockId","visibility":"detail-only","confidence":0.7},{"id":"handletextselected_log_172_0","kind":"log","label":"Logs runtime state","line":172,"detail":"Logger.LogDebug(\"[ReaderCanvas] Text selected in block {BlockId}\", blockId);","visibility":"detail-only","confidence":0.92},{"id":"state-change_173","kind":"mapping","label":"Store selection","line":173,"detail":"_selectedText, _selectedBlockId, _selectionCoords set","visibility":"detail-only","confidence":0.7},{"id":"ui-refresh_176","kind":"mapping","label":"Request UI update","line":176,"detail":"InvokeAsync(StateHasChanged)","visibility":"detail-only","confidence":0.7},{"id":"handletextselected_await_176_1","kind":"await","label":"Waits for async work","line":176,"detail":"await InvokeAsync(StateHasChanged);","visibility":"secondary-visible","confidence":0.81}]},{"name":"HandleSelectionCleared","line":179,"endLine":185,"signature":"() -> Task","purpose":"Clears current text selection state and requests UI update.","calls":[],"actions":[{"id":"state-change_182","kind":"mapping","label":"Clear selection state","line":182,"detail":"_selectedText = string.Empty; _selectionCoords = null","visibility":"detail-only","confidence":0.7},{"id":"ui-refresh_184","kind":"mapping","label":"Update UI","line":184,"detail":"InvokeAsync(StateHasChanged)","visibility":"detail-only","confidence":0.7},{"id":"handleselectioncleared_await_184_0","kind":"await","label":"Waits for async work","line":184,"detail":"await InvokeAsync(StateHasChanged);","visibility":"secondary-visible","confidence":0.81}]},{"name":"HandleScrollRequested","line":187,"endLine":190,"signature":"(blockId: string) -> Task","purpose":"Handler that triggers a scroll to a specific block node.","calls":[{"targetFile":"self","targetMethod":"ScrollToNodeAsync","callLine":189,"paramSummary":"blockId"}],"actions":[{"id":"orchestration_189","kind":"mapping","label":"Delegate scroll","line":189,"detail":"Calls ScrollToNodeAsync","visibility":"detail-only","confidence":0.7},{"id":"handlescrollrequested_await_189_0","kind":"await","label":"Waits for async work","line":189,"detail":"await ScrollToNodeAsync(blockId);","visibility":"secondary-visible","confidence":0.81}]},{"name":"HandleHighlightRequested","line":192,"endLine":202,"signature":"(blockId: string) -> Task","purpose":"Highlights a block temporarily by setting a highlighted id, triggers UI updates and clears it after a delay.","calls":[{"targetFile":"unknown","targetMethod":"Delay","callLine":196,"paramSummary":"3000ms (Task.Delay)"}],"actions":[{"id":"state-change_194","kind":"mapping","label":"Set highlighted block","line":194,"detail":"_highlightedBlockId = blockId; InvokeAsync(StateHasChanged)","visibility":"detail-only","confidence":0.7},{"id":"handlehighlightrequested_await_195_0","kind":"await","label":"Waits for async work","line":195,"detail":"await InvokeAsync(StateHasChanged);","visibility":"secondary-visible","confidence":0.81},{"id":"timed-fallback_196","kind":"mapping","label":"Clear highlight after timeout","line":196,"detail":"await Task.Delay(3000); if (_highlightedBlockId == blockId) clear and update UI","visibility":"detail-only","confidence":0.7},{"id":"handlehighlightrequested_await_196_1","kind":"await","label":"Waits for async work","line":196,"detail":"await Task.Delay(3000);","visibility":"secondary-visible","confidence":0.81},{"id":"handlehighlightrequested_branch_197_2","kind":"branch","label":"Evaluates branch condition","line":197,"detail":"if (_highlightedBlockId == blockId)","conditionSummary":"_highlightedBlockId == blockId","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"handlehighlightrequested_await_200_3","kind":"await","label":"Waits for async work","line":200,"detail":"await InvokeAsync(StateHasChanged);","visibility":"secondary-visible","confidence":0.81}]},{"name":"GetFullPageContent","line":204,"endLine":210,"signature":"() -> string","purpose":"Builds a plain-text representation of all text blocks (IDs + content) for coordinator processing or full-page use.","calls":[],"actions":[{"id":"getfullpagecontent_guard-clause_206_0","kind":"guard-clause","label":"Guards early exit or rejection path","line":206,"detail":"if (ViewModel == null) return string.Empty;","conditionSummary":"ViewModel == null","outcomeLabels":["exit","continue"],"visibility":"primary-visible","confidence":0.9},{"id":"guard-clause_206","kind":"guard-clause","label":"Return empty when no ViewModel","line":206,"conditionSummary":"ViewModel == null","outcomeLabels":["return ''","build content"],"visibility":"detail-only","confidence":0.7},{"id":"mapping_207","kind":"mapping","label":"Concatenate text blocks","line":207,"detail":"Joins TextSegmentBlock contents with IDs into single string","visibility":"detail-only","confidence":0.7},{"id":"getfullpagecontent_return_207_1","kind":"return","label":"Returns result","line":207,"detail":"return string.Join(\"\\n\\n\", ViewModel.Blocks","visibility":"detail-only","confidence":0.7},{"id":"getfullpagecontent_mapping_209_2","kind":"mapping","label":"Maps data or transforms shape","line":209,"detail":".Select(b => $\"[ID: {b.Id}]\\n{b.Content}\"));","visibility":"detail-only","confidence":0.74}]},{"name":"LoadChapterAsync","line":212,"endLine":250,"signature":"(index: int) -> Task","purpose":"Loads a reader page (chapter) via Mediator, updates view model and navigation metadata, handles errors and UI loading state, and triggers coordinator/process sync when interactive.","calls":[{"targetFile":"unknown","targetMethod":"GetAuthenticationStateAsync","callLine":218,"paramSummary":"none (AuthStateProvider)"},{"targetFile":"unknown","targetMethod":"Send","callLine":230,"paramSummary":"GetReaderPageQuery(ebookId, index, userId) via Mediator"},{"targetFile":"unknown","targetMethod":"UpdateMetadataAsync","callLine":234,"paramSummary":"ViewModel.CurrentChapterIndex, ViewModel.TotalChapters, ViewModel.ChapterTitle (NavigationService)"},{"targetFile":"unknown","targetMethod":"ProcessFullPageAsync","callLine":238,"paramSummary":"GetFullPageContent() (Coordinator)"}],"actions":[{"id":"state-change_214","kind":"mapping","label":"Enter loading state","line":214,"detail":"_isLoadingChapter=true; StatusMessage='Wczytywanie treści...'; StateHasChanged()","visibility":"detail-only","confidence":0.7},{"id":"loadchapterasync_await_218_0","kind":"await","label":"Waits for async work","line":218,"detail":"var authState = await AuthStateProvider.GetAuthenticationStateAsync();","visibility":"secondary-visible","confidence":0.81},{"id":"loadchapterasync_repository-read_219_1","kind":"repository-read","label":"Reads repository or persistence state","line":219,"detail":"var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;","visibility":"secondary-visible","confidence":0.86},{"id":"persistence-guard_221","kind":"mapping","label":"Guard for no ebook selected","line":221,"conditionSummary":"ebookId == Guid.Empty","outcomeLabels":["set error status and return"],"visibility":"detail-only","confidence":0.7},{"id":"loadchapterasync_branch_222_2","kind":"branch","label":"Evaluates branch condition","line":222,"detail":"if (ebookId == Guid.Empty)","conditionSummary":"ebookId == Guid.Empty","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"loadchapterasync_return_227_3","kind":"return","label":"Returns result","line":227,"detail":"return;","visibility":"detail-only","confidence":0.7},{"id":"external-query_230","kind":"mapping","label":"Query reader page via mediator","line":230,"detail":"Mediator.Send(GetReaderPageQuery)","visibility":"detail-only","confidence":0.7},{"id":"loadchapterasync_await_230_4","kind":"await","label":"Waits for async work","line":230,"detail":"var result = await Mediator.Send(new GetReaderPageQuery(ebookId, index, userId));","visibility":"secondary-visible","confidence":0.81},{"id":"loadchapterasync_branch_231_5","kind":"branch","label":"Evaluates branch condition","line":231,"detail":"if (result.IsSuccess)","conditionSummary":"result.IsSuccess","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"branch_231","kind":"branch","label":"Handle result success/failure","line":231,"detail":"If result.IsSuccess -> set ViewModel and update metadata; else set error StatusMessage and log","visibility":"detail-only","confidence":0.7},{"id":"loadchapterasync_await_234_6","kind":"await","label":"Waits for async work","line":234,"detail":"await NavigationService.UpdateMetadataAsync(ViewModel.CurrentChapterIndex, ViewModel.TotalChapters, ViewModel.ChapterTitle);","visibility":"secondary-visible","confidence":0.81},{"id":"loadchapterasync_branch_236_7","kind":"branch","label":"Evaluates branch condition","line":236,"detail":"if (_isInteractive)","conditionSummary":"_isInteractive","outcomeLabels":["true","false"],"visibility":"secondary-visible","confidence":0.78},{"id":"conditional-orchestration_236","kind":"mapping","label":"Process full page when interactive","line":236,"detail":"Coordinator.ProcessFullPageAsync(GetFullPageContent())","conditionSummary":"_isInteractive == true","visibility":"detail-only","confidence":0.7},{"id":"loadchapterasync_await_238_8","kind":"await","label":"Waits for async work","line":238,"detail":"await Coordinator.ProcessFullPageAsync(GetFullPageContent());","visibility":"secondary-visible","confidence":0.81},{"id":"loadchapterasync_fallback_241_9","kind":"fallback","label":"Falls back to alternate path","line":241,"detail":"else","outcomeLabels":["fallback"],"visibility":"primary-visible","confidence":0.84},{"id":"loadchapterasync_repository-read_244_10","kind":"repository-read","label":"Reads repository or persistence state","line":244,"detail":"StatusMessage = $\"Błąd: {result.Errors.FirstOrDefault()?.Message ?? \"Nie udało się wczytać treści\"}\";","visibility":"secondary-visible","confidence":0.86},{"id":"loadchapterasync_log_245_11","kind":"log","label":"Logs runtime state","line":245,"detail":"Logger.LogError(\"Failed to load chapter {Index} for ebook {EbookId}: {Errors}\", index, ebookId, string.Join(\", \", result.Errors.Select(e => e.Message)));","visibility":"secondary-visible","confidence":0.92},{"id":"loadchapterasync_mapping_245_12","kind":"mapping","label":"Maps data or transforms shape","line":245,"detail":"Logger.LogError(\"Failed to load chapter {Index} for ebook {EbookId}: {Errors}\", index, ebookId, string.Join(\", \", result.Errors.Select(e => e.Message)));","visibility":"detail-only","confidence":0.74},{"id":"state-change_248","kind":"mapping","label":"Exit loading state","line":248,"detail":"_isLoadingChapter = false; StateHasChanged()","visibility":"detail-only","confidence":0.7}]},{"name":"ScrollToNodeAsync","line":252,"endLine":262,"signature":"(id: string) -> Task","purpose":"Uses JS interop to scroll a DOM element into view by id with smooth behavior and logs warnings on failure.","calls":[{"targetFile":"unknown","targetMethod":"InvokeVoidAsync","callLine":256,"paramSummary":"eval script: document.getElementById(id)?.scrollIntoView({ behavior: 'smooth', block: 'center' }) (IJSRuntime)"}],"actions":[{"id":"scrolltonodeasync_try_254_0","kind":"try","label":"Begins protected execution","line":254,"detail":"try","visibility":"primary-visible","confidence":0.84},{"id":"external-call_256","kind":"external-call","label":"Scroll DOM node via JS eval","line":256,"detail":"JS.InvokeVoidAsync('eval', script)","visibility":"detail-only","confidence":0.7},{"id":"scrolltonodeasync_await_256_1","kind":"await","label":"Waits for async work","line":256,"detail":"await JS.InvokeVoidAsync(\"eval\", $\"document.getElementById('{id}')?.scrollIntoView({{ behavior: 'smooth', block: 'center' }});\");","visibility":"secondary-visible","confidence":0.81},{"id":"scrolltonodeasync_catch_258_2","kind":"catch","label":"Handles exception path","line":258,"detail":"catch (Exception ex)","conditionSummary":"Exception ex","outcomeLabels":["handled exception"],"visibility":"primary-visible","confidence":0.86},{"id":"try-catch_258","kind":"mapping","label":"Log on scroll failure","line":258,"detail":"catch -> Logger.LogWarning(...)","visibility":"detail-only","confidence":0.7},{"id":"scrolltonodeasync_log_260_3","kind":"log","label":"Logs runtime state","line":260,"detail":"Logger.LogWarning(ex, \"Failed to scroll to node {NodeId}.\", id);","visibility":"secondary-visible","confidence":0.92}]},{"name":"HandleUpdate","line":264,"endLine":264,"signature":"() -> Task","purpose":"Lightweight helper that requests a StateHasChanged invocation asynchronously.","calls":[],"actions":[{"id":"ui-refresh_264","kind":"mapping","label":"Invoke StateHasChanged","line":264,"detail":"InvokeAsync(StateHasChanged)","visibility":"detail-only","confidence":0.7}]},{"name":"Dispose","line":266,"endLine":275,"signature":"() -> void","purpose":"Unsubscribes all previously registered event handlers to avoid leaks when component is disposed.","calls":[],"actions":[{"id":"unsubscribe-event_268","kind":"mapping","label":"Unsubscribe theme change","line":268,"detail":"ThemeService.OnThemeChanged -= HandleUpdate","visibility":"detail-only","confidence":0.7},{"id":"unsubscribe-event_269","kind":"mapping","label":"Unsubscribe navigation change","line":269,"detail":"NavigationService.OnNavigationChanged -= OnNavigationChanged","visibility":"detail-only","confidence":0.7},{"id":"unsubscribe-event_271","kind":"mapping","label":"Unsubscribe interaction events","line":271,"detail":"InteractionService.OnScrollToBlockRequested/OnHighlightBlockRequested/OnTextSelected -= handlers","visibility":"detail-only","confidence":0.7},{"id":"unsubscribe-event_274","kind":"mapping","label":"Unsubscribe sync progress","line":274,"detail":"SyncService.OnProgressReceived -= HandleSyncProgressReceived","visibility":"detail-only","confidence":0.7}]}],"types":[],"serviceRegistrations":[],"startupActions":[],"dependencies":["NexusReader.Application.Queries.Reader (GetReaderPageQuery)","NexusReader.UI.Shared.Services (Coordinator, NavigationService, InteractionService, SyncService, ThemeService, FocusMode, IReaderInteractionService, IReaderNavigationService, IThemeService)","MediatR (Mediator.Send)","JS modules: ./_content/NexusReader.UI.Shared/js/selectionHandler.js","JS modules: ./_content/NexusReader.UI.Shared/js/readerObserver.js"],"patterns":["JSInterop (observer & selection)","Event subscription/unsubscription","Coordinator-orchestration","Mediator-based query (CQRS query to load page)","Progress synchronization across devices"],"domainConcepts":["Ebook / Chapter (CurrentChapterIndex, TotalChapters, ChapterTitle)","Block / TextSegmentBlock (block.Id, Content)","Selection (selected text, block id, coordinates)","Reading progress synchronization"],"keyDetails":"Component heavily coordinates between JS observer modules, a KnowledgeCoordinator, SyncService and navigation; it guards against missing ebook selection, handles JS initialization failures with logged warnings, and temporarily highlights blocks with a timed clear.","orchestrationMethods":[{"name":"OnAfterRenderAsync","line":97,"confidence":0.98,"reason":"Coordinates 4 downstream calls with 3 architectural actions.","actionKinds":["branch","guard-clause","await","mapping"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown","self","self"]},{"name":"LoadChapterAsync","line":212,"confidence":0.98,"reason":"Coordinates 4 downstream calls with 7 architectural actions.","actionKinds":["mapping","await","repository-read","branch","return","fallback","log"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown","unknown","unknown"]},{"name":"InitializeSelectionListenerAsync","line":117,"confidence":0.73,"reason":"Coordinates 2 downstream calls with 1 architectural actions.","actionKinds":["try","mapping","external-call","await","catch","log"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown"]},{"name":"InitializeObserverAsync","line":130,"confidence":0.73,"reason":"Coordinates 2 downstream calls with 1 architectural actions.","actionKinds":["try","mapping","external-call","await","catch","log"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown"]},{"name":"HandleBlockReached","line":143,"confidence":0.73,"reason":"Coordinates 2 downstream calls with 1 architectural actions.","actionKinds":["mapping","await","branch","guard-clause"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown"]},{"name":"HandleHighlightRequested","line":192,"confidence":0.65,"reason":"Contains 1 architectural actions relevant to business execution.","actionKinds":["mapping","await","branch"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown"]},{"name":"ScrollToNodeAsync","line":252,"confidence":0.65,"reason":"Contains 1 architectural actions relevant to business execution.","actionKinds":["try","external-call","await","catch","mapping","log"],"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown"]}],"typedContracts":[],"persistenceInteractions":[{"methodName":"LoadChapterAsync","line":219,"kind":"persistence-read","detail":"var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;","evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"methodName":"LoadChapterAsync","line":244,"kind":"persistence-read","detail":"StatusMessage = $\"Błąd: {result.Errors.FirstOrDefault()?.Message ?? \"Nie udało się wczytać treści\"}\";","evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]}],"externalInteractions":[{"methodName":"InitializeSelectionListenerAsync","line":121,"kind":"external-call","detail":"JS.InvokeAsync('import', './_content/.../selectionHandler.js')","evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"methodName":"InitializeObserverAsync","line":134,"kind":"external-call","detail":"JS.InvokeAsync('import', './_content/.../readerObserver.js')","evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"methodName":"ScrollToNodeAsync","line":256,"kind":"external-call","detail":"JS.InvokeVoidAsync('eval', script)","evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]}],"evidenceAnchors":[{"kind":"orchestration-method","label":"OnAfterRenderAsync","line":97,"summary":"Coordinates 4 downstream calls with 3 architectural actions.","confidence":0.98,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown","self","self"]},{"kind":"orchestration-method","label":"LoadChapterAsync","line":212,"summary":"Coordinates 4 downstream calls with 7 architectural actions.","confidence":0.98,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown","unknown","unknown"]},{"kind":"orchestration-method","label":"InitializeSelectionListenerAsync","line":117,"summary":"Coordinates 2 downstream calls with 1 architectural actions.","confidence":0.73,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown"]},{"kind":"orchestration-method","label":"InitializeObserverAsync","line":130,"summary":"Coordinates 2 downstream calls with 1 architectural actions.","confidence":0.73,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor","unknown","unknown"]},{"kind":"persistence","label":"LoadChapterAsync","line":219,"summary":"var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;","confidence":0.82,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"kind":"persistence","label":"LoadChapterAsync","line":244,"summary":"StatusMessage = $\"Błąd: {result.Errors.FirstOrDefault()?.Message ?? \"Nie udało się wczytać treści\"}\";","confidence":0.82,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"kind":"external-call","label":"InitializeSelectionListenerAsync","line":121,"summary":"JS.InvokeAsync('import', './_content/.../selectionHandler.js')","confidence":0.8,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"kind":"external-call","label":"InitializeObserverAsync","line":134,"summary":"JS.InvokeAsync('import', './_content/.../readerObserver.js')","confidence":0.8,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]},{"kind":"external-call","label":"ScrollToNodeAsync","line":256,"summary":"JS.InvokeVoidAsync('eval', script)","confidence":0.8,"evidencePaths":["NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor"]}],"cacheMetadata":{"schemaVersion":2,"analysisVersion":"2026-05-23.cache-v1","contentChecksum":"4ff31efd56f83c24f716a755c4fa19b4b2f45158259500e9087949ec8a54972d","sourceByteSize":9949,"analyzedAt":"2026-05-23T16:28:46.761Z","technology":"dotnet"}}