mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
7ad78a5492
Prior commits tried to solve the "window grows whenever Chat or
Sessions is selected" bug by wrapping NavigationSplitView's detail
slot with an explicit frame (`205bb2c`). That broke the HSplitView
layout in Projects — the project list column, dashboard header,
tab bar, and Sessions-tab header all vanished. Scarf's convention
(PlatformsView.swift:12 calls it out explicitly) is to apply
size constraints on individual HSplitView columns, never on an
outer wrapper.
This commit:
- Reverts the broken ContentView.swift outer frame from `205bb2c`.
NavigationSplitView.detail goes back to its v2.2.1 shape.
- Caps the subtrees whose natural ideal heights are what was
actually pushing the window past the screen:
- RichChatView: `.frame(minHeight: 0, idealHeight: 500, maxHeight: .infinity)`
on the outer VStack. The message list uses a plain VStack
(deliberately, to dodge the LazyVStack whitespace bug — see
RichChatMessageList.swift:13-24), so its natural ideal grows
with every message. Capping idealHeight at 500 gives the
window a screen-safe starting size without limiting how tall
the view can flex when the user drags the window bigger.
- ProjectSessionsView: same treatment with `idealHeight: 400`.
Replaces the earlier `.frame(maxWidth: .infinity, maxHeight:
.infinity)` which set MAX but didn't influence what got
reported upward as ideal.
- Xcode regenerated Localizable.xcstrings during builds; riding
along.
`.frame(idealHeight:)` is the specific SwiftUI knob that overrides
a child's reported ideal on the way up — `maxHeight: .infinity`
alone doesn't. With `.windowResizability(.contentMinSize)` (still
in scarfApp, left alone), the window sizes itself to the reported
ideal on open and respects user drags above the content min. With
a screen-safe ideal, the window opens at a usable size and never
pushes past the desktop.
User-verified: window behaves correctly across section switches,
resize persists, chat input bar always visible.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
66 lines
2.8 KiB
Swift
66 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
struct RichChatView: View {
|
|
@Bindable var richChat: RichChatViewModel
|
|
var onSend: (String) -> Void
|
|
var isEnabled: Bool
|
|
@Environment(HermesFileWatcher.self) private var fileWatcher
|
|
@Environment(ChatViewModel.self) private var chatViewModel
|
|
|
|
/// In ACP mode, events drive updates directly — no DB polling needed.
|
|
private var isACPMode: Bool { chatViewModel.isACPConnected }
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
SessionInfoBar(
|
|
session: richChat.currentSession,
|
|
isWorking: richChat.isAgentWorking,
|
|
acpInputTokens: richChat.acpInputTokens,
|
|
acpOutputTokens: richChat.acpOutputTokens,
|
|
acpThoughtTokens: richChat.acpThoughtTokens
|
|
)
|
|
Divider()
|
|
|
|
// Always mount RichChatMessageList; empty state lives inside it.
|
|
// Swapping between a ContentUnavailableView and the ScrollView
|
|
// hierarchy on first message caused a full view tree rebuild,
|
|
// which manifests as a white flash.
|
|
RichChatMessageList(
|
|
groups: richChat.messageGroups,
|
|
isWorking: richChat.isAgentWorking,
|
|
isLoadingSession: chatViewModel.isPreparingSession,
|
|
scrollTrigger: richChat.scrollTrigger
|
|
)
|
|
|
|
Divider()
|
|
RichChatInputBar(
|
|
onSend: { text in
|
|
onSend(text)
|
|
},
|
|
isEnabled: isEnabled,
|
|
commands: richChat.availableCommands,
|
|
showCompressButton: richChat.supportsCompress && !richChat.hasBroaderCommandMenu
|
|
)
|
|
}
|
|
// `idealHeight: 500` caps what this subtree REPORTS as its ideal
|
|
// height. Load-bearing: RichChatMessageList uses a plain VStack
|
|
// (not LazyVStack — see RichChatMessageList.swift:13-24 for the
|
|
// rationale) inside a ScrollView, so its natural ideal grows
|
|
// with message count. Under the WindowGroup's
|
|
// `.windowResizability(.contentMinSize)` policy, that uncapped
|
|
// ideal would open the window at a height that exceeds the
|
|
// screen on long conversations, pushing the input bar below
|
|
// the visible desktop. `maxHeight: .infinity` still lets the
|
|
// view fill any larger offered space, and `minHeight: 0`
|
|
// allows it to shrink freely — the ideal cap only affects the
|
|
// initial-size hint reported up to the window.
|
|
.frame(minHeight: 0, idealHeight: 500, maxHeight: .infinity)
|
|
// DB polling fallback for terminal mode only — never overwrite ACP messages
|
|
.onChange(of: fileWatcher.lastChangeDate) {
|
|
if !isACPMode, !richChat.hasMessages, richChat.sessionId != nil {
|
|
richChat.scheduleRefresh()
|
|
}
|
|
}
|
|
}
|
|
}
|