fix(ui/security): Enforce idempotent AI fetching, secure auth handler, and memory leak guards #45

Merged
mjasin merged 4 commits from fix/idempotent-ai-fetching into develop 2026-05-20 17:27:40 +00:00
3 changed files with 46 additions and 3 deletions
Showing only changes of commit a2a09b5e91 - Show all commits
@@ -51,9 +51,13 @@
private string _lastFetchedBlockId = string.Empty; private string _lastFetchedBlockId = string.Empty;
private KnowledgePacket? _packet; private KnowledgePacket? _packet;
private CancellationTokenSource? _streamCts; private CancellationTokenSource? _streamCts;
private bool _isInteractive;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (!_isInteractive)
return;
// Only re-fetch when the block context actually changes // Only re-fetch when the block context actually changes
if (string.IsNullOrEmpty(ContextBlockId) || ContextBlockId == _lastFetchedBlockId) if (string.IsNullOrEmpty(ContextBlockId) || ContextBlockId == _lastFetchedBlockId)
return; return;
@@ -62,6 +66,19 @@
await FetchAndStreamAsync(); await FetchAndStreamAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_isInteractive = true;
if (!string.IsNullOrEmpty(ContextBlockId))
{
_lastFetchedBlockId = ContextBlockId;
await FetchAndStreamAsync();
}
}
}
private async Task FetchAndStreamAsync() private async Task FetchAndStreamAsync()
{ {
// Cancel any in-progress stream // Cancel any in-progress stream
@@ -26,15 +26,40 @@
private GroundednessResult? _result; private GroundednessResult? _result;
private bool _isChecking; private bool _isChecking;
private bool _isInteractive;
private string _previousAnswer = string.Empty;
private string _previousContext = string.Empty;
protected override void OnParametersSet()
{
if (Answer != _previousAnswer || Context != _previousContext)
{
_result = null;
_previousAnswer = Answer;
_previousContext = Context;
}
}
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
if (!string.IsNullOrEmpty(Answer) && !string.IsNullOrEmpty(Context) && _result == null) if (_isInteractive && !string.IsNullOrEmpty(Answer) && !string.IsNullOrEmpty(Context) && _result == null)
{ {
await RunCheck(); await RunCheck();
} }
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_isInteractive = true;
if (!string.IsNullOrEmpty(Answer) && !string.IsNullOrEmpty(Context) && _result == null)
{
await RunCheck();
}
}
}
private async Task RunCheck() private async Task RunCheck()
{ {
_isChecking = true; _isChecking = true;
+3 -2
View File
@@ -27,11 +27,10 @@
private IJSObjectReference? _keydownHandler; private IJSObjectReference? _keydownHandler;
private DotNetObjectReference<Home>? _dotNetRef; private DotNetObjectReference<Home>? _dotNetRef;
protected override async Task OnInitializedAsync() protected override void OnInitialized()
{ {
QuizState.OnQuizRequested += HandleQuizRequestedAsync; QuizState.OnQuizRequested += HandleQuizRequestedAsync;
FocusMode.OnFocusModeChanged += HandleUpdate; FocusMode.OnFocusModeChanged += HandleUpdate;
await FocusMode.InitializeAsync();
} }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
@@ -65,11 +64,13 @@
{ {
if (firstRender) if (firstRender)
{ {
await FocusMode.InitializeAsync();
try { try {
_interopModule = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/focusInterop.js"); _interopModule = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/focusInterop.js");
_dotNetRef = DotNetObjectReference.Create(this); _dotNetRef = DotNetObjectReference.Create(this);
_keydownHandler = await _interopModule.InvokeAsync<IJSObjectReference>("attachKeyboardListener", _dotNetRef); _keydownHandler = await _interopModule.InvokeAsync<IJSObjectReference>("attachKeyboardListener", _dotNetRef);
} catch { } /* ignored dynamically */ } catch { } /* ignored dynamically */
StateHasChanged();
} }
} }