From f325ec5ef9134f6c9922f3bd27983155f712780d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:44:40 +0000 Subject: [PATCH 1/4] [LUM-718] perf: flatten inner view hierarchies to reduce layout depth Reduce per-cell layout depth by flattening redundant modifier chains and view wrappers in chat bubble inner views: - Remove unnecessary Group wrappers around switch statements in CompactPermissionChip and ChatBubbleToolStatusView.compactPermissionChip (ViewBuilder handles switches natively since Swift 5.3) - Remove single-child VStack wrapper around stepTitle Text in StepDetailRow - Flatten nested VStack > VStack in stepDetailContent technical details (both had identical spacing: VSpacing.xs) - Combine .background() + .clipShape() + .overlay() into single .background {} with fill+stroke in outputBlock - Replace VStack > HStack > Spacer with HStack + .frame(alignment:) in ChatBubbleToolStatusView trailing status - Merge two .padding() calls into single EdgeInsets in CodeBlockView header Estimated depth reduction: ~8-10 layout nodes per cell. Co-Authored-By: Jason Zhou --- .../Features/Chat/AssistantProgressView.swift | 73 +++++++++---------- .../Chat/ChatBubbleToolStatusView.swift | 36 ++++----- .../Features/Chat/MarkdownSegmentView.swift | 3 +- 3 files changed, 50 insertions(+), 62 deletions(-) diff --git a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift index 21025128f80..a9b65367141 100644 --- a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift @@ -901,13 +901,11 @@ private struct StepDetailRow: View { } // Title (reason-first, then skillLabel for skill_execute, then fallback) - VStack(alignment: .leading, spacing: VSpacing.xxs) { - Text(stepTitle) - .font(VFont.labelDefault) - .foregroundStyle(stepTitleColor) - .lineLimit(1) - .truncationMode(.tail) - } + Text(stepTitle) + .font(VFont.labelDefault) + .foregroundStyle(stepTitleColor) + .lineLimit(1) + .truncationMode(.tail) Spacer() @@ -970,23 +968,21 @@ private struct StepDetailRow: View { .foregroundStyle(VColor.contentTertiary) .textCase(.uppercase) - VStack(alignment: .leading, spacing: VSpacing.xs) { - if let reason = toolCall.reasonDescription, !reason.isEmpty { - Text(toolCall.actionDescription) - .font(VFont.labelDefault) - .foregroundStyle(VColor.contentSecondary) - } - Text(toolCall.friendlyName) + if let reason = toolCall.reasonDescription, !reason.isEmpty { + Text(toolCall.actionDescription) .font(VFont.labelDefault) .foregroundStyle(VColor.contentSecondary) - if !resolvedInputFull.isEmpty { - outputBlock( - text: resolvedInputFull, - attributedText: nil, - copyText: resolvedInputFull, - copyLabel: "Copy input" - ) - } + } + Text(toolCall.friendlyName) + .font(VFont.labelDefault) + .foregroundStyle(VColor.contentSecondary) + if !resolvedInputFull.isEmpty { + outputBlock( + text: resolvedInputFull, + attributedText: nil, + copyText: resolvedInputFull, + copyLabel: "Copy input" + ) } } .padding(.horizontal, VSpacing.lg) @@ -1054,12 +1050,11 @@ private struct StepDetailRow: View { outputTextView(text: text, attributedText: attributedText, isError: isError) .padding(EdgeInsets(top: VSpacing.sm, leading: VSpacing.sm, bottom: VSpacing.sm, trailing: VSpacing.sm + VSpacing.xl)) .frame(maxWidth: .infinity, alignment: .leading) - .background(VColor.surfaceOverlay.opacity(0.6)) - .clipShape(RoundedRectangle(cornerRadius: VRadius.sm)) - .overlay( + .background { RoundedRectangle(cornerRadius: VRadius.sm) + .fill(VColor.surfaceOverlay.opacity(0.6)) .stroke(VColor.borderBase, lineWidth: 0.5) - ) + } ChatEquatableButton( config: ChatButtonConfig( @@ -1168,20 +1163,18 @@ private struct CompactPermissionChip: View { var body: some View { HStack(spacing: VSpacing.xxs) { - Group { - switch state { - case .approved: - VIconView(.circleCheck, size: 10) - .foregroundStyle(chipColor) - case .denied: - VIconView(.circleAlert, size: 10) - .foregroundStyle(chipColor) - case .timedOut: - VIconView(.clock, size: 10) - .foregroundStyle(chipColor) - default: - EmptyView() - } + switch state { + case .approved: + VIconView(.circleCheck, size: 10) + .foregroundStyle(chipColor) + case .denied: + VIconView(.circleAlert, size: 10) + .foregroundStyle(chipColor) + case .timedOut: + VIconView(.clock, size: 10) + .foregroundStyle(chipColor) + default: + EmptyView() } Text(state == .approved || state == .denied ? label : "Timed Out") diff --git a/clients/macos/vellum-assistant/Features/Chat/ChatBubbleToolStatusView.swift b/clients/macos/vellum-assistant/Features/Chat/ChatBubbleToolStatusView.swift index 6fdb66d4ed5..1d2a3cfc6fb 100644 --- a/clients/macos/vellum-assistant/Features/Chat/ChatBubbleToolStatusView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/ChatBubbleToolStatusView.swift @@ -59,14 +59,12 @@ extension ChatBubble { inlineToolCallImages(from: message.toolCalls) } else if !effectiveConfirmations.isEmpty, !inlineToolProgressRenderedInContent { // No tool display needed — only show permission chips. - VStack(alignment: .leading, spacing: 0) { - HStack(alignment: .center, spacing: VSpacing.sm) { - ForEach(Array(effectiveConfirmations.enumerated()), id: \.offset) { _, confirmation in - compactPermissionChip(confirmation) - } - Spacer() + HStack(alignment: .center, spacing: VSpacing.sm) { + ForEach(Array(effectiveConfirmations.enumerated()), id: \.offset) { _, confirmation in + compactPermissionChip(confirmation) } } + .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, VSpacing.xxs) } } @@ -86,20 +84,18 @@ extension ChatBubble { let chipColor: Color = isApproved ? VColor.primaryBase : isDenied ? VColor.systemNegativeStrong : VColor.contentTertiary return HStack(spacing: VSpacing.xs) { - Group { - switch confirmation.state { - case .approved: - VIconView(.circleCheck, size: 12) - .foregroundStyle(chipColor) - case .denied: - VIconView(.circleAlert, size: 12) - .foregroundStyle(chipColor) - case .timedOut: - VIconView(.clock, size: 12) - .foregroundStyle(chipColor) - default: - EmptyView() - } + switch confirmation.state { + case .approved: + VIconView(.circleCheck, size: 12) + .foregroundStyle(chipColor) + case .denied: + VIconView(.circleAlert, size: 12) + .foregroundStyle(chipColor) + case .timedOut: + VIconView(.clock, size: 12) + .foregroundStyle(chipColor) + default: + EmptyView() } Text(isApproved || isDenied ? "\(confirmation.toolCategory)" : diff --git a/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift b/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift index 69b36c0dc58..968148effed 100644 --- a/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift @@ -828,8 +828,7 @@ private struct CodeBlockView: View, Equatable { .opacity(isHovered ? 1 : 0) .animation(VAnimation.fast, value: isHovered) } - .padding(.horizontal, VSpacing.sm) - .padding(.top, VSpacing.xs) + .padding(EdgeInsets(top: VSpacing.xs, leading: VSpacing.sm, bottom: 0, trailing: VSpacing.sm)) } let codeLineCount = code.utf8.reduce(1) { $0 + ($1 == 0x0A ? 1 : 0) } From 314800716edcb9dec91d404937a788b2eac1a62d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:21:09 +0000 Subject: [PATCH 2/4] [LUM-718] Restore .clipShape() to prevent content overflow at rounded corners The .clipShape(RoundedRectangle) is needed to clip scrollable content (e.g., 500+ line outputs in the ScrollView) to the rounded background. Without it, text near corners visually bleeds past the rounded border. Co-Authored-By: Jason Zhou --- .../vellum-assistant/Features/Chat/AssistantProgressView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift index a9b65367141..07d92674b5d 100644 --- a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift @@ -1055,6 +1055,7 @@ private struct StepDetailRow: View { .fill(VColor.surfaceOverlay.opacity(0.6)) .stroke(VColor.borderBase, lineWidth: 0.5) } + .clipShape(RoundedRectangle(cornerRadius: VRadius.sm)) ChatEquatableButton( config: ChatButtonConfig( From bf7c69b2964f102bdf2773fdac8c92059228ecab Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:29:27 +0000 Subject: [PATCH 3/4] [LUM-718] Separate stroke into overlay to prevent clipping The stroke was inside .background {} which gets clipped by .clipShape(), halving the visible border width (0.25pt instead of 0.5pt). Move the stroke to .overlay() after .clipShape() so the full border is drawn on top, matching the existing pattern at line 758-762. Co-Authored-By: Jason Zhou --- .../Features/Chat/AssistantProgressView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift index 07d92674b5d..5740fa34632 100644 --- a/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/AssistantProgressView.swift @@ -1050,12 +1050,15 @@ private struct StepDetailRow: View { outputTextView(text: text, attributedText: attributedText, isError: isError) .padding(EdgeInsets(top: VSpacing.sm, leading: VSpacing.sm, bottom: VSpacing.sm, trailing: VSpacing.sm + VSpacing.xl)) .frame(maxWidth: .infinity, alignment: .leading) - .background { + .background( RoundedRectangle(cornerRadius: VRadius.sm) .fill(VColor.surfaceOverlay.opacity(0.6)) - .stroke(VColor.borderBase, lineWidth: 0.5) - } + ) .clipShape(RoundedRectangle(cornerRadius: VRadius.sm)) + .overlay( + RoundedRectangle(cornerRadius: VRadius.sm) + .stroke(VColor.borderBase, lineWidth: 0.5) + ) ChatEquatableButton( config: ChatButtonConfig( From 19cee44159896359d5b1b78e75b48c909317159b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:32:43 +0000 Subject: [PATCH 4/4] [LUM-718] Add hit testing and accessibility guards to opacity-hidden copy buttons Pre-existing issue: VCopyButton instances used .opacity(isHovered ? 1 : 0) without .allowsHitTesting or .accessibilityHidden guards. When opacity is 0, the button was still tappable and visible to VoiceOver. Added both guards to the two VCopyButton instances in CodeBlockView (language header and no-language overlay). Co-Authored-By: Jason Zhou --- .../vellum-assistant/Features/Chat/MarkdownSegmentView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift b/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift index 968148effed..98c62bb9dcf 100644 --- a/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift +++ b/clients/macos/vellum-assistant/Features/Chat/MarkdownSegmentView.swift @@ -826,6 +826,8 @@ private struct CodeBlockView: View, Equatable { Spacer() VCopyButton(text: code, size: .compact) .opacity(isHovered ? 1 : 0) + .allowsHitTesting(isHovered) + .accessibilityHidden(!isHovered) .animation(VAnimation.fast, value: isHovered) } .padding(EdgeInsets(top: VSpacing.xs, leading: VSpacing.sm, bottom: 0, trailing: VSpacing.sm)) @@ -868,6 +870,8 @@ private struct CodeBlockView: View, Equatable { if language == nil || language?.isEmpty == true { VCopyButton(text: code, size: .compact) .opacity(isHovered ? 1 : 0) + .allowsHitTesting(isHovered) + .accessibilityHidden(!isHovered) .animation(VAnimation.fast, value: isHovered) .padding(VSpacing.xs) }