From bea4fca94260ec213b822da478afe190827ee4a0 Mon Sep 17 00:00:00 2001 From: milind-soni Date: Sat, 29 Aug 2026 16:55:45 +0530 Subject: [PATCH] feat(ios): add tactile action feedback --- ios/App/ChatListView.swift | 25 ++++++++++++++++++++----- ios/App/ChatView.swift | 8 +++++++- ios/App/Island.swift | 1 + ios/App/NewGroupSheet.swift | 2 ++ ios/App/UpdatesSheet.swift | 1 + 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/ios/App/ChatListView.swift b/ios/App/ChatListView.swift index 3f6b0224fc..29fd10fe04 100644 --- a/ios/App/ChatListView.swift +++ b/ios/App/ChatListView.swift @@ -54,7 +54,10 @@ struct ChatListView: View { ForEach(searchHits) { hit in Button { Task { - if let chat = await session.open(hit) { path.append(chat) } + if let chat = await session.open(hit) { + Haptics.selection() + path.append(chat) + } } } label: { SearchHitRow(hit: hit) @@ -154,10 +157,14 @@ struct ChatListView: View { return } searching = true + defer { + if query == expected { searching = false } + } try? await Task.sleep(for: .milliseconds(250)) guard !Task.isCancelled, query == expected else { return } - searchHits = await session.search(expected) - searching = false + let hits = await session.search(expected) + guard !Task.isCancelled, query == expected else { return } + searchHits = hits } } } @@ -228,6 +235,7 @@ struct ChatListView: View { .buttonStyle(.plain) } Button { + Haptics.selection() showingNewGroup = true } label: { GroupTile(room: nil) @@ -280,10 +288,14 @@ struct ChatListView: View { .frame(height: 52) .glassCapsule() } else { - UpdatesPill(updates: session.state.updates) { showingUpdates = true } + UpdatesPill(updates: session.state.updates) { + Haptics.selection() + showingUpdates = true + } .frame(height: 52) GlassButton(systemImage: "magnifyingglass", size: 48, weight: .semibold) { + Haptics.selection() searchOpen = true searchFocused = true } @@ -291,7 +303,10 @@ struct ChatListView: View { GlassButton(systemImage: "square.and.pencil", size: 48, weight: .medium) { Task { - if let bot = await session.createBot() { path.append(Chat.bot(bot)) } + if let bot = await session.createBot() { + Haptics.success() + path.append(Chat.bot(bot)) + } } } .accessibilityLabel("New bot") diff --git a/ios/App/ChatView.swift b/ios/App/ChatView.swift index ed3af63916..9c8d7c45e7 100644 --- a/ios/App/ChatView.swift +++ b/ios/App/ChatView.swift @@ -755,6 +755,7 @@ struct MessageRow: View { HStack(spacing: 6) { ForEach(reactionGroups(reactions), id: \.emoji) { group in Button("\(group.emoji) \(group.count)") { + Haptics.selection() Task { await session.react(to: message, in: chat.threadId, emoji: group.emoji) } } .font(.system(size: 13)) @@ -784,7 +785,10 @@ struct MessageRow: View { } .contextMenu { ForEach(Self.reactionChoices, id: \.self) { emoji in - Button(emoji) { Task { await session.react(to: message, in: chat.threadId, emoji: emoji) } } + Button(emoji) { + Haptics.selection() + Task { await session.react(to: message, in: chat.threadId, emoji: emoji) } + } } if message.role == .user, message.kind == .text, case let .bot(bot) = chat { Divider() @@ -1063,6 +1067,7 @@ struct CardView: View { HStack(spacing: 8) { ForEach(card.options, id: \.self) { option in Button { + Haptics.selection() answering = true Task { await session.answer(chat: chat, card: card, choice: option) @@ -1091,6 +1096,7 @@ struct CardView: View { // never a string invented here. if card.allowKey != nil, let allow = allowChoice, case let .bot(bot) = chat { Button("Always allow this tool") { + Haptics.selection() answering = true Task { await session.alwaysAllow(bot: bot, card: card) diff --git a/ios/App/Island.swift b/ios/App/Island.swift index 38f84f8f55..2a26dc94a8 100644 --- a/ios/App/Island.swift +++ b/ios/App/Island.swift @@ -120,6 +120,7 @@ struct NeedsYouIsland: View { HStack(spacing: 8) { ForEach(card.options, id: \.self) { option in Button { + Haptics.selection() answering = true Task { await session.answer(chat: shown.chat, card: card, choice: option) diff --git a/ios/App/NewGroupSheet.swift b/ios/App/NewGroupSheet.swift index 4e53af6409..42564dd78d 100644 --- a/ios/App/NewGroupSheet.swift +++ b/ios/App/NewGroupSheet.swift @@ -26,6 +26,7 @@ struct NewGroupSheet: View { ForEach(bots) { bot in Button { if members.contains(bot.id) { members.remove(bot.id) } else { members.insert(bot.id) } + Haptics.selection() } label: { HStack(spacing: 12) { BotAvatarView(bot: bot, size: 36, state: .idle, animated: false) @@ -59,6 +60,7 @@ struct NewGroupSheet: View { // it defaults) follows the first bot you picked let ordered = bots.map(\.id).filter(members.contains) if let room = await session.createRoom(name: name, memberIds: ordered) { + Haptics.success() created(room) } creating = false diff --git a/ios/App/UpdatesSheet.swift b/ios/App/UpdatesSheet.swift index 4a3d306ff9..2b22c13336 100644 --- a/ios/App/UpdatesSheet.swift +++ b/ios/App/UpdatesSheet.swift @@ -96,6 +96,7 @@ private struct UpdateRow: View { HStack(spacing: 8) { ForEach(card.options, id: \.self) { option in Button { + Haptics.selection() answering = true Task { await session.answer(chat: update.chat, card: card, choice: option)