feat(ipc): wire TypingIpc state from InputBar API

This commit is contained in:
2026-05-26 13:38:21 +02:00
parent e01de0403a
commit 93bfd408bc
2 changed files with 38 additions and 10 deletions
+37 -10
View File
@@ -34,11 +34,13 @@ internal sealed class TypingIpc : IDisposable
private ChatInputState LastState; private ChatInputState LastState;
private bool HasState; private bool HasState;
private readonly Ui.Components.InputBar _inputBar;
private readonly ILogger<TypingIpc> _logger; private readonly ILogger<TypingIpc> _logger;
internal TypingIpc(Plugin plugin, ILogger<TypingIpc> logger) internal TypingIpc(Plugin plugin, Ui.Components.InputBar inputBar, ILogger<TypingIpc> logger)
{ {
Plugin = plugin; Plugin = plugin;
_inputBar = inputBar;
_logger = logger; _logger = logger;
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>( StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
@@ -62,26 +64,51 @@ internal sealed class TypingIpc : IDisposable
private ChatInputState BuildState() private ChatInputState BuildState()
{ {
// Input visibility and focus come back when the new chat layer
// exposes the matching state. The channel type still resolves
// from the active tab so IPC consumers can read it today.
var usedChannel = Plugin.CurrentTab.CurrentChannel; var usedChannel = Plugin.CurrentTab.CurrentChannel;
var inputChannel = usedChannel.UseTempChannel var inputChannel = usedChannel.UseTempChannel
? usedChannel.TempChannel ? usedChannel.TempChannel
: usedChannel.Channel; : usedChannel.Channel;
var channelType = inputChannel.ToChatType(); var channelType = inputChannel.ToChatType();
// `Plugin` here is the instance property on TypingIpc (TypingIpc.cs:18
// `private Plugin Plugin { get; }`), NOT the type `HellionChat.Plugin`.
// C# resolves `Plugin.MainWindow` as `this.Plugin.MainWindow` because
// the instance property shadows the type name inside this class. Do
// NOT switch to a type-qualified read (CS0120 — `MainWindow` is an
// instance member, not a static one). MainWindow is resolved in Phase-1
// (Plugin.cs:295) and never re-assigned, so `this.Plugin.MainWindow`
// is non-null by the time TypingIpc.Update() runs (HostedServices only
// start after Phase-1). The `null!`-suppression on the MainWindow
// property would let us drop the `?.`, but a null-safe read costs
// nothing at runtime and shields against a theoretical pre-Phase-1
// caller (a future IPC-pull that fires before HostedServices start).
// Defense in depth, no behaviour change for the production path.
var mainWindowOpen = this.Plugin.MainWindow?.IsOpen ?? false;
// Stale-state guard: InputBar._isFocused is only written in DrawInputField,
// which only runs while MainWindow is open. After the user closes the
// window, _isFocused freezes on the last value. _pendingMessage has the
// same stale problem — it is only cleared in TrySend (successful send)
// or ClearBuffer (manual reset), so closing MainWindow with non-empty
// buffer leaves PendingLength frozen above zero. Without gating all
// four state fields on mainWindowOpen, Cross-Plugin-Subscribers would
// keep seeing InputFocused: true / HasText: true / IsTyping: true /
// TextLength: N even though no input field exists.
var inputFocused = mainWindowOpen && _inputBar.IsFocused;
var hasText = mainWindowOpen && _inputBar.PendingLength > 0;
var textLength = mainWindowOpen ? _inputBar.PendingLength : 0;
return ( return (
InputVisible: false, InputVisible: mainWindowOpen,
InputFocused: false, InputFocused: inputFocused,
HasText: false, HasText: hasText,
IsTyping: false, IsTyping: hasText,
TextLength: 0, TextLength: textLength,
ChannelType: channelType ChannelType: channelType
); );
} }
private ChatInputState GetState() => BuildState(); internal ChatInputState GetState() => BuildState();
internal void Update() internal void Update()
{ {
+1
View File
@@ -103,6 +103,7 @@ internal static class PluginHostFactory
)); ));
services.AddSingleton(sp => new TypingIpc( services.AddSingleton(sp => new TypingIpc(
sp.GetRequiredService<Plugin>(), sp.GetRequiredService<Plugin>(),
sp.GetRequiredService<Ui.Components.InputBar>(),
sp.GetRequiredService<ILogger<TypingIpc>>() sp.GetRequiredService<ILogger<TypingIpc>>()
)); ));