diff --git a/.gitignore b/.gitignore index cc1a82e2a..148918225 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ coverage .DS_Store **/.DS_Store ui/src/ui/__screenshots__/ +ui/playwright-report/ +ui/test-results/ # Bun build artifacts *.bun-build diff --git a/CHANGELOG.md b/CHANGELOG.md index f5a79a236..e9cd78247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,7 +41,7 @@ - Android Chat UI: use `onPrimary` for user bubble text to preserve contrast (thanks @Syhids). - Control UI: sync sidebar navigation with the URL for deep-linking, and auto-scroll chat to the latest message. - Control UI: disable Web Chat + Talk when no iOS/Android node is connected; refreshed Web Chat styling and keyboard send. -- macOS Web Chat: improve empty/error states, focus message field on open, and keep pill/send inside the input field. +- macOS Web Chat: improve empty/error states, focus message field on open, keep pill/send inside the input field, and make the composer pill edge-to-edge with square top corners. - macOS: bundle Control UI assets into the app relay so the packaged app can serve them (thanks @mbelinky). - Talk Mode: wait for chat history to surface the assistant reply before starting TTS (macOS/iOS/Android). - iOS Talk Mode: fix chat completion wait to time out even if no events arrive (prevents “Thinking…” hangs). diff --git a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift index d1a4f64a4..41b1d792b 100644 --- a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift +++ b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift @@ -37,20 +37,44 @@ struct ClawdisChatComposer: View { self.editor } .padding(self.composerPadding) - .background( - RoundedRectangle(cornerRadius: 18, style: .continuous) + .background { + let cornerRadius: CGFloat = 18 + + #if os(macOS) + if self.style == .standard { + let shape = UnevenRoundedRectangle( + cornerRadii: RectangleCornerRadii( + topLeading: 0, + bottomLeading: cornerRadius, + bottomTrailing: cornerRadius, + topTrailing: 0), + style: .continuous) + shape + .fill(ClawdisChatTheme.composerBackground) + .overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1)) + .shadow(color: .black.opacity(0.12), radius: 12, y: 6) + } else { + let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + shape + .fill(ClawdisChatTheme.composerBackground) + .overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1)) + .shadow(color: .black.opacity(0.12), radius: 12, y: 6) + } + #else + let shape = RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) + shape .fill(ClawdisChatTheme.composerBackground) - .overlay( - RoundedRectangle(cornerRadius: 18, style: .continuous) - .strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1)) - .shadow(color: .black.opacity(0.12), radius: 12, y: 6)) + .overlay(shape.strokeBorder(ClawdisChatTheme.composerBorder, lineWidth: 1)) + .shadow(color: .black.opacity(0.12), radius: 12, y: 6) + #endif + } #if os(macOS) - .onDrop(of: [.fileURL], isTargeted: nil) { providers in - self.handleDrop(providers) - } - .onAppear { - self.shouldFocusTextView = true - } + .onDrop(of: [.fileURL], isTargeted: nil) { providers in + self.handleDrop(providers) + } + .onAppear { + self.shouldFocusTextView = true + } #endif } diff --git a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift index c2fcb4834..d4b21ecba 100644 --- a/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift +++ b/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift @@ -21,7 +21,7 @@ public struct ClawdisChatView: View { static let outerPaddingHorizontal: CGFloat = 6 static let outerPaddingVertical: CGFloat = 0 static let composerPaddingHorizontal: CGFloat = 0 - static let stackSpacing: CGFloat = 6 + static let stackSpacing: CGFloat = 0 static let messageSpacing: CGFloat = 6 static let messageListPaddingTop: CGFloat = 0 static let messageListPaddingBottom: CGFloat = 4 @@ -79,13 +79,33 @@ public struct ClawdisChatView: View { private var messageList: some View { ZStack { ScrollView { - LazyVStack(spacing: Layout.messageSpacing) { - self.messageListRows + #if os(macOS) + VStack(spacing: 0) { + LazyVStack(spacing: Layout.messageSpacing) { + self.messageListRows + } + + Color.clear + .frame(height: 0) + .id(self.scrollerBottomID) } // Use scroll targets for stable auto-scroll without ScrollViewReader relayout glitches. .scrollTargetLayout() .padding(.top, Layout.messageListPaddingTop) .padding(.horizontal, Layout.messageListPaddingHorizontal) + #else + LazyVStack(spacing: Layout.messageSpacing) { + self.messageListRows + + Color.clear + .frame(height: Layout.messageListPaddingBottom + 1) + .id(self.scrollerBottomID) + } + // Use scroll targets for stable auto-scroll without ScrollViewReader relayout glitches. + .scrollTargetLayout() + .padding(.top, Layout.messageListPaddingTop) + .padding(.horizontal, Layout.messageListPaddingHorizontal) + #endif } // Keep the scroll pinned to the bottom for new messages. .scrollPosition(id: self.$scrollPosition, anchor: .bottom) @@ -147,10 +167,6 @@ public struct ClawdisChatView: View { ChatStreamingAssistantBubble(text: text) .frame(maxWidth: .infinity, alignment: .leading) } - - Color.clear - .frame(height: Layout.messageListPaddingBottom + 1) - .id(self.scrollerBottomID) } private var visibleMessages: [ClawdisChatMessage] {