mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 02:26:37 +00:00
fix(layout): cap RichChatView/ProjectSessions idealHeight; revert broken detail wrap
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>
This commit is contained in:
@@ -17,32 +17,6 @@ struct ContentView: View {
|
||||
.navigationSplitViewColumnWidth(min: 180, ideal: 240, max: 360)
|
||||
} detail: {
|
||||
detailView
|
||||
// The detail column's size is what NavigationSplitView
|
||||
// reports up to the window. Without a bound here, the
|
||||
// reported ideal is derived from the currently-rendered
|
||||
// section's natural intrinsic size — and some sections
|
||||
// (Chat with a fully-materialized message list, the
|
||||
// v2.3 per-project Sessions tab) have intrinsic heights
|
||||
// that exceed the screen. With `.windowResizability
|
||||
// (.contentMinSize)` in scarfApp, the window is forced
|
||||
// at least that tall, pushing its bottom edge past the
|
||||
// visible desktop and hiding the input bar.
|
||||
//
|
||||
// This frame pins the detail's reported ideal at a
|
||||
// modest 900×600 — small enough to fit any reasonable
|
||||
// screen — while allowing it to expand freely to
|
||||
// whatever the user drags the window to. `minHeight: 0`
|
||||
// is load-bearing: it overrides the "my child's min is
|
||||
// huge" chain so NavigationSplitView doesn't carry a
|
||||
// massive min up to the window.
|
||||
.frame(
|
||||
minWidth: 500,
|
||||
idealWidth: 900,
|
||||
maxWidth: .infinity,
|
||||
minHeight: 300,
|
||||
idealHeight: 600,
|
||||
maxHeight: .infinity
|
||||
)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
ServerSwitcherToolbar()
|
||||
|
||||
@@ -42,6 +42,19 @@ struct RichChatView: View {
|
||||
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 {
|
||||
|
||||
@@ -20,13 +20,17 @@ struct ProjectSessionsView: View {
|
||||
Divider()
|
||||
content
|
||||
}
|
||||
// Without this clamp the inner List's intrinsic height grows
|
||||
// with its row count and the enclosing VStack pushes the
|
||||
// window itself past the screen. Other tabs handle this via
|
||||
// their own container (widgetsTab = ScrollView, siteTab =
|
||||
// explicit maxHeight) — match the siteTab pattern here so
|
||||
// the List scrolls internally.
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
// `idealHeight: 400` caps what this subtree reports as its
|
||||
// ideal height. Without it, the inner List's row-materialised
|
||||
// intrinsic height bubbles up through NavigationSplitView's
|
||||
// detail slot and, under `.windowResizability(.contentMinSize)`,
|
||||
// opens the window at a height that exceeds the screen on
|
||||
// busy projects — the Sessions tab header + "New Chat" button
|
||||
// end up below the visible desktop edge. `maxHeight: .infinity`
|
||||
// still lets the List fill any taller offered space, and
|
||||
// `minHeight: 0` allows it to shrink. Mirrors the same pattern
|
||||
// applied in RichChatView.
|
||||
.frame(minHeight: 0, idealHeight: 400, maxHeight: .infinity)
|
||||
.task(id: project.id) {
|
||||
// Rebuild the VM when the project changes so stale state
|
||||
// from a previously-selected project doesn't bleed
|
||||
|
||||
@@ -1028,6 +1028,10 @@
|
||||
"comment" : "A message that appears when a memory block is no longer present in MEMORY.md.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"A project named \"%@\" already exists." : {
|
||||
"comment" : "A warning message that appears in a Rename Project sheet if the user-provided name is a duplicate of an existing project. The argument is the name of the duplicate project.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"A QR code will appear below. Scan it with WhatsApp on your phone. The session is saved to ~/.hermes/platforms/whatsapp/ so you won't need to scan again after restarts." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -1391,6 +1395,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Add a project" : {
|
||||
"comment" : "A button that adds a new project.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Add a project folder to get started. Create a .scarf/dashboard.json file in your project to define widgets." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -2521,6 +2529,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Archived (%lld)" : {
|
||||
"comment" : "A label that opens a group of archived projects.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Args (one per line)" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -3785,6 +3797,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Chats you start here get attributed automatically. Older CLI-started sessions live in the global Sessions sidebar." : {
|
||||
"comment" : "A description of the purpose of the Sessions tab.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Check" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -6874,6 +6890,10 @@
|
||||
},
|
||||
"Description" : {
|
||||
|
||||
},
|
||||
"Destination" : {
|
||||
"comment" : "A label for the folder picker in the move-to-folder sheet.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Details" : {
|
||||
"localizations" : {
|
||||
@@ -8802,6 +8822,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Filter projects" : {
|
||||
"comment" : "A label for a search field in the sidebar.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Filter servers..." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -9006,6 +9030,10 @@
|
||||
"comment" : "A placeholder for a comma-separated list of tags.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Folders only affect how projects are grouped in Scarf's sidebar. Nothing on disk changes." : {
|
||||
"comment" : "A description of how folders affect project grouping.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Full copy of active profile (all state)" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -9698,6 +9726,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Hide archived projects" : {
|
||||
"comment" : "A toggle that hides archived projects.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Hide details" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -12186,6 +12218,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Move" : {
|
||||
"comment" : "A button that moves a project to a folder.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Move \"%@\" to folder" : {
|
||||
"comment" : "A heading for a dialog that lets the user move a project to a folder.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Move to Folder…" : {
|
||||
"comment" : "A context menu action that moves a project to a folder.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"msgs" : {
|
||||
"comment" : "A label for the number of messages in a session.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"my_server" : {
|
||||
|
||||
},
|
||||
@@ -12309,6 +12357,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"New Chat" : {
|
||||
"comment" : "A button that starts a new chat session.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"New folder name" : {
|
||||
|
||||
},
|
||||
"New folder…" : {
|
||||
"comment" : "A label for a new folder name.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"New name for '%@'" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -13327,6 +13386,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"No project selected" : {
|
||||
"comment" : "A label that indicates that no project is selected.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"No Projects" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -15458,6 +15521,10 @@
|
||||
},
|
||||
"Project folder kept" : {
|
||||
|
||||
},
|
||||
"Project name" : {
|
||||
"comment" : "A label for a text field that lets the user enter a project name.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Project Name" : {
|
||||
"localizations" : {
|
||||
@@ -16870,6 +16937,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rename project" : {
|
||||
"comment" : "A title for a sheet that renames a project.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Rename Session" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -16949,6 +17020,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Rename…" : {
|
||||
|
||||
},
|
||||
"required" : {
|
||||
|
||||
@@ -19379,6 +19453,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Sessions in this project" : {
|
||||
"comment" : "A heading for the list of sessions in a project.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Set as default — open this server when Scarf launches." : {
|
||||
"comment" : "A tooltip for the star button in the Manage Servers view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@@ -19623,6 +19701,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Show archived projects" : {
|
||||
"comment" : "A toggle that shows/hides archived projects.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Show details" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -21435,6 +21517,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"The project directory on disk isn't changed — only the label Scarf shows in the sidebar." : {
|
||||
"comment" : "A description of the project name field.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"The remote's SSH fingerprint no longer matches what your `~/.ssh/known_hosts` file expected. This usually means the remote was reinstalled — or, less commonly, that someone is intercepting the connection." : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -22422,6 +22508,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Top Level" : {
|
||||
"comment" : "A folder in the sidebar.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Top Tools" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
@@ -22582,6 +22672,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Unarchive" : {
|
||||
"comment" : "A button that unarchives a project.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Uninstall" : {
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
|
||||
Reference in New Issue
Block a user