Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions Packages/Sources/RxCodeChatKit/ChatMessageListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@ public struct ChatMessageListView: View {

extension View {
func chatMessageListRowStyle() -> some View {
#if os(macOS)
padding(EdgeInsets(top: 8, leading: 20, bottom: 24, trailing: 20))
.listRowInsets(EdgeInsets(top: 8, leading: 20, bottom: 24, trailing: 20))
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
#else
listRowInsets(EdgeInsets(top: 8, leading: 20, bottom: 24, trailing: 20))
.listRowSeparator(.hidden)
.listRowBackground(Color.clear)
#endif
}
}

Expand Down
75 changes: 37 additions & 38 deletions Packages/Sources/RxCodeChatKit/MessageListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,52 +23,51 @@ struct MessageListView: View {

var body: some View {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(alignment: .leading, spacing: 0) {
messageRows(settledItems[...])

// Streaming view is outside the settled rows — text deltas don't
// affect settled layout.
if !windowState.focusMode {
StreamingMessageView {
rebuildSettledItems()
if anchor.isNearBottom { scrollToBottomDebounced(proxy) }
}
// Suppress layout animations when switching sessions so the pulse indicator
// doesn't visually jump as StreamingMessageView changes height.
.animation(.none, value: windowState.currentSessionId)
.chatMessageListRowStyle()
}

if chatBridge.isStreaming && !chatBridge.hasPendingPlanDecision {
// Hide the spinner/dots while the CLI is paused waiting on the
// user's plan decision — the model isn't actually generating
// tokens, so showing "in progress" is misleading.
HStack(alignment: .top, spacing: 0) {
StreamingIndicatorView(
isThinking: chatBridge.isThinking,
startDate: chatBridge.streamingStartDate,
agentProvider: chatBridge.agentProvider,
outputTokens: chatBridge.liveOutputTokens
)
Spacer(minLength: 40)
}
.chatMessageListRowStyle()
List {
messageRows(settledItems[...])

// Streaming view is outside VStack — text deltas don't affect settled layout
if !windowState.focusMode {
StreamingMessageView {
rebuildSettledItems()
if anchor.isNearBottom { scrollToBottomDebounced(proxy) }
}
// Suppress layout animations when switching sessions so the pulse indicator
// doesn't visually jump as StreamingMessageView changes height.
.animation(.none, value: windowState.currentSessionId)
.chatMessageListRowStyle()
}

if !chatBridge.isStreaming && !settledItems.isEmpty {
WebPreviewButton(messages: settledItems)
.id("web-preview")
.chatMessageListRowStyle()
if chatBridge.isStreaming && !chatBridge.hasPendingPlanDecision {
// Hide the spinner/dots while the CLI is paused waiting on the
// user's plan decision — the model isn't actually generating
// tokens, so showing "in progress" is misleading.
HStack(alignment: .top, spacing: 0) {
StreamingIndicatorView(
isThinking: chatBridge.isThinking,
startDate: chatBridge.streamingStartDate,
agentProvider: chatBridge.agentProvider,
outputTokens: chatBridge.liveOutputTokens
)
Spacer(minLength: 40)
}
.chatMessageListRowStyle()
}

Color.clear.frame(height: 1)
.id(Self.bottomAnchorID)
if !chatBridge.isStreaming && !settledItems.isEmpty {
WebPreviewButton(messages: settledItems)
.id("web-preview")
.chatMessageListRowStyle()
}
.frame(maxWidth: .infinity, alignment: .leading)

Color.clear.frame(height: 1)
.id(Self.bottomAnchorID)
.chatMessageListRowStyle()
}
.listStyle(.plain)
.contentMargins(.top, 16, for: .scrollContent)
.scrollContentBackground(.hidden)
.environment(\.defaultMinListRowHeight, 0)
.opacity(isSessionReady ? 1 : 0)
.defaultScrollAnchor(.bottom)
.onScrollGeometryChange(for: ScrollSample.self) { geo in
Expand Down
10 changes: 9 additions & 1 deletion Packages/Sources/RxCodeCore/RunProfile/RunTaskExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ public enum RunTaskExecutor {
/// Note: banner text is intentionally plain ASCII without SGR styling.
/// SwiftTerm renders SGR 2 (dim) as low-contrast on dark backgrounds,
/// which made the banners invisible in earlier versions.
public static func buildWrapperScript(profile: RunProfile, projectPath: String) -> String {
public static func buildWrapperScript(
profile: RunProfile,
projectPath: String,
outputLogPath: String? = nil
) -> String {
let cwd = resolveWorkingDirectory(profile.bash.workingDirectory, projectPath: projectPath)

let afterSteps = profile.afterSteps.filter { !$0.command.trimmingCharacters(in: .whitespaces).isEmpty }
Expand All @@ -86,6 +90,10 @@ public enum RunTaskExecutor {
// weirdness) can't leak back here.
lines.append("__rxcode_user_path=$(/bin/zsh -ic 'printf %s \"$PATH\"' 2>/dev/null)")
lines.append("[ -n \"$__rxcode_user_path\" ] && export PATH=\"$__rxcode_user_path\"")
if let outputLogPath {
lines.append(": > \(shellEscape(outputLogPath))")
lines.append("exec > >(tee -a \(shellEscape(outputLogPath))) 2>&1")
}
lines.append("")
// Trap is registered first so it fires for any subsequent failure —
// including a failing `cd` into a stale working directory.
Expand Down
Loading