feat(ui): #17 ChatPanel input field + slash commands + reply-to-last-tell
ChatPanel gains an Enter-to-submit input field via the I.1 InputTextSubmit widget. Submitted text routes through ChatInputParser to a SendChatCmd published on ctx.Commands; LiveCommandBus (I.3) handles the wire send + ChatLog echo. Recognised prefixes (ported from holtburger commands.rs): /say msg or no prefix -> Say /t Name msg or /tell -> Tell (first whitespace token = target) /r msg -> Tell (target = LastIncomingTellSender) /g msg -> General /f msg -> Fellowship /a msg -> Allegiance /m msg -> Monarch /p msg -> Patron /v msg -> Vassals /cv msg -> CoVassals /lfg msg -> Lfg /trade msg -> Trade /role msg -> Roleplay /society msg -> Society /olthoi msg -> Olthoi Edge cases: empty / whitespace / cmd-without-message / /r without prior tell -> null (no-op). Unknown /xyz prefix -> Say with literal text (matches holtburger's Talk(command) default arm). ChatVM.LastIncomingTellSender populated only on incoming Tell entries; discriminated by SenderGuid != 0 (OnSelfSent echoes always carry 0). 32 new tests: - ChatInputParserTests: 22 covering every prefix + edge case - ChatVMLastTellSenderTests: 6 covering capture + skip rules - ChatPanelInputTests: 6 using FakePanelRenderer + recording ICommandBus to assert publish behaviour UI.Abstractions.Tests: 60 -> 92. Solution total: 934 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8e6e5a0b61
commit
f14296c75f
6 changed files with 710 additions and 10 deletions
|
|
@ -1,23 +1,37 @@
|
|||
namespace AcDream.UI.Abstractions.Panels.Chat;
|
||||
|
||||
/// <summary>
|
||||
/// Second real UI panel — shows the tail of the chat log.
|
||||
/// Exercises <see cref="IPanelRenderer.Text"/> + <see cref="IPanelRenderer.Separator"/>
|
||||
/// on a non-trivial render pattern (N lines, not a single widget) —
|
||||
/// proving the D.2a abstraction contract holds for more than the vitals
|
||||
/// HUD before we grow the panel catalog further.
|
||||
/// The chat panel. Shows the tail of <see cref="ChatLog"/> + an input
|
||||
/// field at the bottom that submits on Enter.
|
||||
///
|
||||
/// <para>
|
||||
/// D.2a scope: show the last <see cref="ChatVM.DefaultDisplayLimit"/>
|
||||
/// lines with a separator above the tail and each entry as a single
|
||||
/// <c>Text</c> call. No input field (outbound chat already has wire
|
||||
/// support via <c>SendChat</c> — a text-input widget on <see cref="IPanelRenderer"/>
|
||||
/// lands with the first panel that actually needs one, not here).
|
||||
/// Phase I.4 added the input field and slash-command parsing. Supported
|
||||
/// prefixes (alias-matched against the verb token, not by string-prefix
|
||||
/// — so <c>/general</c> is NOT <c>/g</c>):
|
||||
/// <list type="bullet">
|
||||
/// <item><c>/say <msg></c> or no prefix → Say (default)</item>
|
||||
/// <item><c>/t</c> / <c>/tell <name> <msg></c> → whisper</item>
|
||||
/// <item><c>/r</c> / <c>/reply <msg></c> → reply to most recent
|
||||
/// INCOMING Tell (uses <see cref="ChatVM.LastIncomingTellSender"/>;
|
||||
/// drops the message if no Tell has arrived yet)</item>
|
||||
/// <item><c>/g, /f, /a, /m, /p, /v, /cv, /lfg, /trade, /role, /society,
|
||||
/// /olthoi <msg></c> → corresponding channel</item>
|
||||
/// <item>unknown <c>/xyz hello</c> → Say with the literal text intact
|
||||
/// (matches holtburger fall-through)</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Empty / whitespace-only / target-but-no-message inputs are silently
|
||||
/// dropped — the input field clears and no command goes out.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class ChatPanel : IPanel
|
||||
{
|
||||
private const int InputBufferMaxLen = 512;
|
||||
|
||||
private readonly ChatVM _vm;
|
||||
private string _input = string.Empty;
|
||||
|
||||
public ChatPanel(ChatVM vm)
|
||||
{
|
||||
|
|
@ -59,6 +73,22 @@ public sealed class ChatPanel : IPanel
|
|||
}
|
||||
}
|
||||
|
||||
// Phase I.4: input field. Backend implementation clears _input
|
||||
// on submit per the IPanelRenderer contract.
|
||||
renderer.Separator();
|
||||
if (renderer.InputTextSubmit("##chatinput", ref _input, InputBufferMaxLen, out var submitted)
|
||||
&& submitted is not null)
|
||||
{
|
||||
var parsed = ChatInputParser.Parse(submitted.Trim(), ChatChannelKind.Say, _vm.LastIncomingTellSender);
|
||||
if (parsed is { } p)
|
||||
{
|
||||
ctx.Commands.Publish(new SendChatCmd(p.Channel, p.TargetName, p.Text));
|
||||
}
|
||||
// Defensive: if the backend ever forgot to clear on submit,
|
||||
// do it here. Cheap; no harm if already empty.
|
||||
_input = string.Empty;
|
||||
}
|
||||
|
||||
renderer.End();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue