mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Route command output to the conversation where the command was typed (#1363)
CommandProcessor results (/help text, errors like "unknown command", /msg confirmations) were always appended to the public timeline via addSystemMessage, so a command typed inside a DM appeared to do nothing until the user switched back to the public channel. handleCommand now routes .success/.error output to the open private chat when one is selected, falling back to the public timeline otherwise. The DM selection is read after processing so commands that switch chats (/msg) print into the conversation they just opened. Follow-up to #1354, which added /help and surfaced this routing gap. Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
jack
Claude Fable 5
parent
e191e9c6f2
commit
66536063ca
@@ -1435,7 +1435,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele
|
|||||||
|
|
||||||
/// Processes IRC-style commands starting with '/'.
|
/// Processes IRC-style commands starting with '/'.
|
||||||
/// - Parameter command: The full command string including the leading slash
|
/// - Parameter command: The full command string including the leading slash
|
||||||
/// - Note: Supports commands like /nick, /msg, /who, /slap, /clear, /help
|
/// - Note: Supports commands like /msg, /who, /slap, /clear, /help
|
||||||
@MainActor
|
@MainActor
|
||||||
func handleCommand(_ command: String) {
|
func handleCommand(_ command: String) {
|
||||||
let result = commandProcessor.process(command)
|
let result = commandProcessor.process(command)
|
||||||
@@ -1443,16 +1443,29 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, TransportEventDele
|
|||||||
switch result {
|
switch result {
|
||||||
case .success(let message):
|
case .success(let message):
|
||||||
if let msg = message {
|
if let msg = message {
|
||||||
addSystemMessage(msg)
|
addCommandOutput(msg)
|
||||||
}
|
}
|
||||||
case .error(let message):
|
case .error(let message):
|
||||||
addSystemMessage(message)
|
addCommandOutput(message)
|
||||||
case .handled:
|
case .handled:
|
||||||
// Command was handled, no message needed
|
// Command was handled, no message needed
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command output belongs in the conversation where the user typed the
|
||||||
|
/// command; the public timeline is invisible while a DM is open. The DM
|
||||||
|
/// selection is read *after* processing so commands that switch chats
|
||||||
|
/// (`/msg`) print into the conversation they just opened.
|
||||||
|
@MainActor
|
||||||
|
private func addCommandOutput(_ content: String) {
|
||||||
|
if let peerID = selectedPrivateChatPeer {
|
||||||
|
addLocalPrivateSystemMessage(content, to: peerID)
|
||||||
|
} else {
|
||||||
|
addSystemMessage(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Message Reception
|
// MARK: - Message Reception
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|||||||
@@ -263,6 +263,59 @@ struct ChatViewModelCommandTests {
|
|||||||
#expect(transport.sentPrivateMessages.isEmpty)
|
#expect(transport.sentPrivateMessages.isEmpty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test @MainActor
|
||||||
|
func handleCommand_outputRoutesToOpenPrivateChat() async {
|
||||||
|
let (viewModel, transport) = makeTestableViewModel()
|
||||||
|
let peerID = PeerID(str: "0000000000000002")
|
||||||
|
transport.simulateConnect(peerID, nickname: "Alice")
|
||||||
|
viewModel.selectedPrivateChatPeer = peerID
|
||||||
|
|
||||||
|
viewModel.handleCommand("/help")
|
||||||
|
|
||||||
|
#expect(viewModel.privateChats[peerID]?.last?.content == CommandProcessor.helpText)
|
||||||
|
#expect(!viewModel.messages.contains { $0.content == CommandProcessor.helpText })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test @MainActor
|
||||||
|
func handleCommand_errorRoutesToOpenPrivateChat() async {
|
||||||
|
let (viewModel, transport) = makeTestableViewModel()
|
||||||
|
let peerID = PeerID(str: "0000000000000002")
|
||||||
|
transport.simulateConnect(peerID, nickname: "Alice")
|
||||||
|
viewModel.selectedPrivateChatPeer = peerID
|
||||||
|
|
||||||
|
viewModel.handleCommand("/bogus")
|
||||||
|
|
||||||
|
let dmContents = viewModel.privateChats[peerID]?.map(\.content) ?? []
|
||||||
|
#expect(dmContents.contains { $0.hasPrefix("unknown command: /bogus") })
|
||||||
|
#expect(!viewModel.messages.contains { $0.content.hasPrefix("unknown command: /bogus") })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test @MainActor
|
||||||
|
func handleCommand_outputRoutesToPublicTimelineWithoutOpenDM() async {
|
||||||
|
let (viewModel, _) = makeTestableViewModel()
|
||||||
|
|
||||||
|
viewModel.handleCommand("/bogus")
|
||||||
|
|
||||||
|
#expect(viewModel.messages.last?.content.hasPrefix("unknown command: /bogus") == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test @MainActor
|
||||||
|
func handleCommand_msgSuccessLandsInNewlyOpenedChat() async {
|
||||||
|
let (viewModel, transport) = makeTestableViewModel()
|
||||||
|
let peerID = PeerID(str: "0000000000000002")
|
||||||
|
transport.simulateConnect(peerID, nickname: "Alice")
|
||||||
|
let resolved = await TestHelpers.waitUntil({
|
||||||
|
viewModel.getPeerIDForNickname("Alice") == peerID
|
||||||
|
}, timeout: TestConstants.defaultTimeout)
|
||||||
|
#expect(resolved)
|
||||||
|
|
||||||
|
viewModel.handleCommand("/msg Alice")
|
||||||
|
|
||||||
|
#expect(viewModel.selectedPrivateChatPeer == peerID)
|
||||||
|
#expect(viewModel.privateChats[peerID]?.last?.content == "started private chat with Alice")
|
||||||
|
#expect(!viewModel.messages.contains { $0.content == "started private chat with Alice" })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Composer Tests
|
// MARK: - Composer Tests
|
||||||
|
|||||||
Reference in New Issue
Block a user