# ReaderLayout

- File ID: file:mm1vnb:readerlayout-razor
- Path: NexusReader.UI.Shared/Layout/ReaderLayout.razor
- Role: ui-component
- Layer: frontend

## Purpose

Razor layout component that renders the reader UI (main content, AI sidebar, footer) and manages runtime concerns: device/platform detection, event subscriptions for UI updates, and JS interop to initialize a layout resizer.

## Key Details

Renders an AI sidebar only for authorized users, subscribes to two services for UI updates, sets platform CSS based on device detection, and initializes a client-side resizer via JS interop on first render with error logging on failure.

## Symbols

- ReaderLayout: Razor layout component that renders the reader UI (main content, AI sidebar, footer) and manages runtime concerns: device/platform detection, event subscriptions for UI updates, and JS interop to initialize a layout resizer.
- OnInitialized(): Component initialization: subscribes to FocusMode and Quiz update events and determines device type to set platform CSS class.
- OnAfterRenderAsync(): After-render lifecycle: on first render, attempts to import a JS module and initialize a resizer; logs on failure.
- HandleUpdate(): Invokes a UI refresh on the component's synchronization context (wraps StateHasChanged in InvokeAsync).
- Dispose(): Component disposal: unsubscribes from FocusMode and Quiz update events to avoid memory leaks.

## Dependencies

- No resolved dependencies.

## Calls

- No resolved calls.

## Unresolved References

- dependency-target: NexusReader.Application.Abstractions.Services (PlatformService.GetDeviceContext usage)
- dependency-target: NexusReader.UI.Shared.Services (FocusMode, IQuizStateService referenced)
- dependency-target: IJSRuntime (JS interop import - framework)
- call-target: unknown
- call-target: unknown
- call-target: NexusReader.Application.Abstractions.Services (unknown path)
- call-target: unknown
- call-target: unknown
- interaction-target: if (context.IsSuccess)
- interaction-target: _isMobile = context.Value.DeviceType switch
- interaction-target: await JS.InvokeAsync<IJSObjectReference>("import", "./_content/.../layoutResizer.js"); then module.InvokeVoidAsync("initResizer", ".app-container", "#sidebar-resizer", "--sidebar-width") (JS interop)

## Coverage

- Symbols: 5
- Executable symbols: 4
- Executable symbols with control flow: 4
- Grounded edges: 5
- Unresolved references: 11
- Control-flow fragments: 4
- Unresolved jumps: 5
- Unsupported constructs: 0
- Control-flow coverage: 100%
- Required diagrams: 2
- Generated diagrams: 5

## Control Flow Fragments

- OnInitialized: 22 nodes, 24 edges, 3 jumps, 0 unsupported constructs
- OnAfterRenderAsync: 18 nodes, 19 edges, 0 jumps, 0 unsupported constructs
- HandleUpdate: 3 nodes, 2 edges, 0 jumps, 0 unsupported constructs
- Dispose: 6 nodes, 5 edges, 2 jumps, 0 unsupported constructs

## Diagrams

### ReaderLayout Neighborhood

```mermaid
flowchart LR
    class_15qe4r2_readerlayout("ReaderLayout")
    file_mm1vnb_readerlayout_razor("ReaderLayout")
    method_11o3hgs_oninitialized[["OnInitialized()"]]
    method_1m8qyhe_handleupdate[["HandleUpdate()"]]
    method_1r68c5i_onafterrenderasync[["OnAfterRenderAsync()"]]
    method_zb4rdj_dispose[["Dispose()"]]
```

### ReaderLayout Execution Overview

```mermaid
flowchart TD
    class_15qe4r2_readerlayout("ReaderLayout")
    method_11o3hgs_oninitialized[["OnInitialized()"]]
    method_1m8qyhe_handleupdate[["HandleUpdate()"]]
    method_1r68c5i_onafterrenderasync[["OnAfterRenderAsync()"]]
    method_zb4rdj_dispose[["Dispose()"]]
    unresolved_unresolved_1d3viv2_interaction_target{"Unresolved: if (context.IsSuccess)"}
    unresolved_unresolved_1f9xkjs_call_target{"Unresolved: FocusMode.OnFocusModeChanged += HandleUpdate (event subscribe)"}
    unresolved_unresolved_1wlsy2d_call_target{"Unresolved: GetDeviceContext"}
    unresolved_unresolved_1y5usm8_call_target{"Unresolved: QuizService.OnQuizUpdated += HandleUpdate (event subscribe)"}
    unresolved_unresolved_na1slj_call_target{"Unresolved: FocusMode.OnFocusModeChanged -= HandleUpdate (event unsubscribe)"}
    unresolved_unresolved_pm6tjt_interaction_target{"Unresolved: await JS.InvokeAsync<IJSObjectReference>(\\"import\\", \\"./_content/.../layoutResizer.js\\"); then module.InvokeVoidAsync(\\"initResizer\\", \\".app-container\\", \\"#sidebar-resizer\\", \\"--sidebar-width\\") (JS interop)"}
    unresolved_unresolved_vgq4n3_call_target{"Unresolved: QuizService.OnQuizUpdated -= HandleUpdate (event unsubscribe)"}
    unresolved_unresolved_xu9xg_interaction_target{"Unresolved: _isMobile = context.Value.DeviceType switch"}
    class_15qe4r2_readerlayout -->|contains| method_11o3hgs_oninitialized
    class_15qe4r2_readerlayout -->|contains| method_1m8qyhe_handleupdate
    class_15qe4r2_readerlayout -->|contains| method_1r68c5i_onafterrenderasync
    class_15qe4r2_readerlayout -->|contains| method_zb4rdj_dispose
    class_15qe4r2_readerlayout -->|unresolved interaction| unresolved_unresolved_1d3viv2_interaction_target
    class_15qe4r2_readerlayout -->|unresolved interaction| unresolved_unresolved_pm6tjt_interaction_target
    class_15qe4r2_readerlayout -->|unresolved interaction| unresolved_unresolved_xu9xg_interaction_target
    method_11o3hgs_oninitialized -->|unresolved call| unresolved_unresolved_1f9xkjs_call_target
    method_11o3hgs_oninitialized -->|unresolved call| unresolved_unresolved_1wlsy2d_call_target
    method_11o3hgs_oninitialized -->|unresolved call| unresolved_unresolved_1y5usm8_call_target
    method_zb4rdj_dispose -->|unresolved call| unresolved_unresolved_na1slj_call_target
    method_zb4rdj_dispose -->|unresolved call| unresolved_unresolved_vgq4n3_call_target
```

### OnInitialized() Control Flow

_Warnings: diagram method_11o3hgs_oninitialized__fragment_diagram exceeds preferred complexity thresholds (22 nodes, 24 edges)._

```mermaid
---
config:
  flowchart:
    defaultRenderer: "elk"
---
flowchart TD
    method_11o3hgs_oninitialized__call_75_FocusMode_OnFocusModeChanged_HandleUpdate_event_subscribe_[["FocusMode.OnFocusModeChanged += HandleUpdate (event subscribe)"]]
    method_11o3hgs_oninitialized__call_76_QuizService_OnQuizUpdated_HandleUpdate_event_subscribe_[["QuizService.OnQuizUpdated += HandleUpdate (event subscribe)"]]
    method_11o3hgs_oninitialized__call_78_GetDeviceContext[["GetDeviceContext"]]
    method_11o3hgs_oninitialized__action_75_event_subscribe_75__linear("Subscribe to focus mode changes")
    method_11o3hgs_oninitialized__action_76_event_subscribe_76__linear("Subscribe to quiz updates")
    method_11o3hgs_oninitialized__action_79_guard_clause_79__condition{"context.IsSuccess"}
    method_11o3hgs_oninitialized__action_79_guard_clause_79__false("skip")
    method_11o3hgs_oninitialized__action_79_guard_clause_79__merge("merge")
    method_11o3hgs_oninitialized__action_79_guard_clause_79__true("apply device type mapping")
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__condition{"context.IsSuccess"}
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__false("false")
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__merge("merge")
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__true("true")
    method_11o3hgs_oninitialized__action_81_branch_81__condition{"Device type mapping"}
    method_11o3hgs_oninitialized__action_81_branch_81__false("false")
    method_11o3hgs_oninitialized__action_81_branch_81__merge("merge")
    method_11o3hgs_oninitialized__action_81_branch_81__true("true")
    method_11o3hgs_oninitialized__action_87_state_mutation_87__linear("Set platform CSS class")
    method_11o3hgs_oninitialized__entry(["OnInitialized entry"])
    method_11o3hgs_oninitialized__action_79_oninitialized_repository_read_79_1__linear[("Reads repository or persistence state")]
    method_11o3hgs_oninitialized__action_81_oninitialized_repository_read_81_2__linear[("Reads repository or persistence state")]
    method_11o3hgs_oninitialized__implicit_return(["return"])
    method_11o3hgs_oninitialized__action_75_event_subscribe_75__linear -->|delegate HandleUpdate| method_11o3hgs_oninitialized__call_75_FocusMode_OnFocusModeChanged_HandleUpdate_event_subscribe_
    method_11o3hgs_oninitialized__action_76_event_subscribe_76__linear -->|delegate HandleUpdate| method_11o3hgs_oninitialized__call_76_QuizService_OnQuizUpdated_HandleUpdate_event_subscribe_
    method_11o3hgs_oninitialized__action_79_guard_clause_79__condition -->|skip| method_11o3hgs_oninitialized__action_79_guard_clause_79__false
    method_11o3hgs_oninitialized__action_79_guard_clause_79__condition -->|apply device type mapping| method_11o3hgs_oninitialized__action_79_guard_clause_79__true
    method_11o3hgs_oninitialized__action_79_guard_clause_79__false -->|next| method_11o3hgs_oninitialized__action_79_guard_clause_79__merge
    method_11o3hgs_oninitialized__action_79_guard_clause_79__merge -->|next| method_11o3hgs_oninitialized__action_81_oninitialized_repository_read_81_2__linear
    method_11o3hgs_oninitialized__action_79_guard_clause_79__true -->|next| method_11o3hgs_oninitialized__action_79_guard_clause_79__merge
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__condition -->|false| method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__false
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__condition -->|true| method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__true
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__false -->|next| method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__merge
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__merge -->|next| method_11o3hgs_oninitialized__action_79_guard_clause_79__condition
    method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__true -->|next| method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__merge
    method_11o3hgs_oninitialized__action_79_oninitialized_repository_read_79_1__linear -->|next| method_11o3hgs_oninitialized__action_79_oninitialized_branch_79_0__condition
    method_11o3hgs_oninitialized__action_81_branch_81__condition -->|false| method_11o3hgs_oninitialized__action_81_branch_81__false
    method_11o3hgs_oninitialized__action_81_branch_81__condition -->|true| method_11o3hgs_oninitialized__action_81_branch_81__true
    method_11o3hgs_oninitialized__action_81_branch_81__false -->|next| method_11o3hgs_oninitialized__action_81_branch_81__merge
    method_11o3hgs_oninitialized__action_81_branch_81__merge -->|next| method_11o3hgs_oninitialized__action_87_state_mutation_87__linear
    method_11o3hgs_oninitialized__action_81_branch_81__true -->|next| method_11o3hgs_oninitialized__action_81_branch_81__merge
    method_11o3hgs_oninitialized__action_81_oninitialized_repository_read_81_2__linear -->|next| method_11o3hgs_oninitialized__action_81_branch_81__condition
    method_11o3hgs_oninitialized__action_87_state_mutation_87__linear -->|return| method_11o3hgs_oninitialized__implicit_return
    method_11o3hgs_oninitialized__call_75_FocusMode_OnFocusModeChanged_HandleUpdate_event_subscribe_ -->|next| method_11o3hgs_oninitialized__action_76_event_subscribe_76__linear
    method_11o3hgs_oninitialized__call_76_QuizService_OnQuizUpdated_HandleUpdate_event_subscribe_ -->|no params returns context with IsSuccess and Value.DeviceType| method_11o3hgs_oninitialized__call_78_GetDeviceContext
    method_11o3hgs_oninitialized__call_78_GetDeviceContext -->|next| method_11o3hgs_oninitialized__action_79_oninitialized_repository_read_79_1__linear
    method_11o3hgs_oninitialized__entry -->|next| method_11o3hgs_oninitialized__action_75_event_subscribe_75__linear
```

### OnAfterRenderAsync() Control Flow

```mermaid
flowchart TD
    method_1r68c5i_onafterrenderasync__action_100_onafterrenderasync_await_100_3__await[["Waits for async work"]]
    method_1r68c5i_onafterrenderasync__action_102_onafterrenderasync_catch_102_4__catch("catch Exception ex")
    method_1r68c5i_onafterrenderasync__action_104_logging_104__linear("Log JS initialization failure")
    method_1r68c5i_onafterrenderasync__action_104_onafterrenderasync_log_104_5__linear("Logs runtime state")
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__condition{"firstRender == true"}
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__false("skip")
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__merge("merge")
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__true("initialize JS resizer")
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__condition{"firstRender"}
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__false("false")
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__merge("merge")
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__true("true")
    method_1r68c5i_onafterrenderasync__action_97_onafterrenderasync_try_97_1__try("Begins protected execution")
    method_1r68c5i_onafterrenderasync__action_97_try_catch_97__linear("JS interop initialization with error handling")
    method_1r68c5i_onafterrenderasync__action_99_onafterrenderasync_await_99_2__await[["Waits for async work"]]
    method_1r68c5i_onafterrenderasync__entry(["OnAfterRenderAsync entry"])
    method_1r68c5i_onafterrenderasync__action_99_external_call_99__linear("Import JS module and call init function")
    method_1r68c5i_onafterrenderasync__implicit_return(["return"])
    method_1r68c5i_onafterrenderasync__action_100_onafterrenderasync_await_100_3__await -->|exception| method_1r68c5i_onafterrenderasync__action_102_onafterrenderasync_catch_102_4__catch
    method_1r68c5i_onafterrenderasync__action_102_onafterrenderasync_catch_102_4__catch -->|next| method_1r68c5i_onafterrenderasync__action_104_onafterrenderasync_log_104_5__linear
    method_1r68c5i_onafterrenderasync__action_104_logging_104__linear -->|return| method_1r68c5i_onafterrenderasync__implicit_return
    method_1r68c5i_onafterrenderasync__action_104_onafterrenderasync_log_104_5__linear -->|next| method_1r68c5i_onafterrenderasync__action_104_logging_104__linear
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__condition -->|skip| method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__false
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__condition -->|initialize JS resizer| method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__true
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__false -->|next| method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__merge
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__merge -->|next| method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__condition
    method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__true -->|next| method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__merge
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__condition -->|false| method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__false
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__condition -->|true| method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__true
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__false -->|next| method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__merge
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__merge -->|next| method_1r68c5i_onafterrenderasync__action_97_try_catch_97__linear
    method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__true -->|next| method_1r68c5i_onafterrenderasync__action_95_onafterrenderasync_branch_95_0__merge
    method_1r68c5i_onafterrenderasync__action_97_onafterrenderasync_try_97_1__try -->|next| method_1r68c5i_onafterrenderasync__action_99_onafterrenderasync_await_99_2__await
    method_1r68c5i_onafterrenderasync__action_97_try_catch_97__linear -->|next| method_1r68c5i_onafterrenderasync__action_97_onafterrenderasync_try_97_1__try
    method_1r68c5i_onafterrenderasync__action_99_external_call_99__linear -->|next| method_1r68c5i_onafterrenderasync__action_100_onafterrenderasync_await_100_3__await
    method_1r68c5i_onafterrenderasync__action_99_onafterrenderasync_await_99_2__await -->|next| method_1r68c5i_onafterrenderasync__action_99_external_call_99__linear
    method_1r68c5i_onafterrenderasync__entry -->|next| method_1r68c5i_onafterrenderasync__action_95_guard_clause_95__condition
```

### Dispose() Control Flow

```mermaid
flowchart TD
    method_zb4rdj_dispose__call_113_FocusMode_OnFocusModeChanged_HandleUpdate_event_unsubscribe_[["FocusMode.OnFocusModeChanged -= HandleUpdate (event unsubscribe)"]]
    method_zb4rdj_dispose__call_114_QuizService_OnQuizUpdated_HandleUpdate_event_unsubscribe_[["QuizService.OnQuizUpdated -= HandleUpdate (event unsubscribe)"]]
    method_zb4rdj_dispose__action_113_event_unsubscribe_113__linear("Unsubscribe from focus mode changes")
    method_zb4rdj_dispose__action_114_event_unsubscribe_114__linear("Unsubscribe from quiz updates")
    method_zb4rdj_dispose__entry(["Dispose entry"])
    method_zb4rdj_dispose__implicit_return(["return"])
    method_zb4rdj_dispose__action_113_event_unsubscribe_113__linear -->|delegate HandleUpdate| method_zb4rdj_dispose__call_113_FocusMode_OnFocusModeChanged_HandleUpdate_event_unsubscribe_
    method_zb4rdj_dispose__action_114_event_unsubscribe_114__linear -->|delegate HandleUpdate| method_zb4rdj_dispose__call_114_QuizService_OnQuizUpdated_HandleUpdate_event_unsubscribe_
    method_zb4rdj_dispose__call_113_FocusMode_OnFocusModeChanged_HandleUpdate_event_unsubscribe_ -->|next| method_zb4rdj_dispose__action_114_event_unsubscribe_114__linear
    method_zb4rdj_dispose__call_114_QuizService_OnQuizUpdated_HandleUpdate_event_unsubscribe_ -->|return| method_zb4rdj_dispose__implicit_return
    method_zb4rdj_dispose__entry -->|next| method_zb4rdj_dispose__action_113_event_unsubscribe_113__linear
```

## Diagram Validation

- Status: warn
- Warning: Control-flow extraction left 5 unresolved jumps.
- Warning: control-flow: diagram method_11o3hgs_oninitialized__fragment_diagram exceeds preferred complexity thresholds (22 nodes, 24 edges).
