mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +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)
|
.navigationSplitViewColumnWidth(min: 180, ideal: 240, max: 360)
|
||||||
} detail: {
|
} detail: {
|
||||||
detailView
|
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 {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigation) {
|
ToolbarItem(placement: .navigation) {
|
||||||
ServerSwitcherToolbar()
|
ServerSwitcherToolbar()
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ struct RichChatView: View {
|
|||||||
showCompressButton: richChat.supportsCompress && !richChat.hasBroaderCommandMenu
|
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
|
// DB polling fallback for terminal mode only — never overwrite ACP messages
|
||||||
.onChange(of: fileWatcher.lastChangeDate) {
|
.onChange(of: fileWatcher.lastChangeDate) {
|
||||||
if !isACPMode, !richChat.hasMessages, richChat.sessionId != nil {
|
if !isACPMode, !richChat.hasMessages, richChat.sessionId != nil {
|
||||||
|
|||||||
@@ -20,13 +20,17 @@ struct ProjectSessionsView: View {
|
|||||||
Divider()
|
Divider()
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
// Without this clamp the inner List's intrinsic height grows
|
// `idealHeight: 400` caps what this subtree reports as its
|
||||||
// with its row count and the enclosing VStack pushes the
|
// ideal height. Without it, the inner List's row-materialised
|
||||||
// window itself past the screen. Other tabs handle this via
|
// intrinsic height bubbles up through NavigationSplitView's
|
||||||
// their own container (widgetsTab = ScrollView, siteTab =
|
// detail slot and, under `.windowResizability(.contentMinSize)`,
|
||||||
// explicit maxHeight) — match the siteTab pattern here so
|
// opens the window at a height that exceeds the screen on
|
||||||
// the List scrolls internally.
|
// busy projects — the Sessions tab header + "New Chat" button
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
// 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) {
|
.task(id: project.id) {
|
||||||
// Rebuild the VM when the project changes so stale state
|
// Rebuild the VM when the project changes so stale state
|
||||||
// from a previously-selected project doesn't bleed
|
// 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.",
|
"comment" : "A message that appears when a memory block is no longer present in MEMORY.md.",
|
||||||
"isCommentAutoGenerated" : true
|
"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." : {
|
"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" : {
|
"localizations" : {
|
||||||
"de" : {
|
"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." : {
|
"Add a project folder to get started. Create a .scarf/dashboard.json file in your project to define widgets." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -2521,6 +2529,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Archived (%lld)" : {
|
||||||
|
"comment" : "A label that opens a group of archived projects.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Args (one per line)" : {
|
"Args (one per line)" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"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" : {
|
"Check" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -6874,6 +6890,10 @@
|
|||||||
},
|
},
|
||||||
"Description" : {
|
"Description" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Destination" : {
|
||||||
|
"comment" : "A label for the folder picker in the move-to-folder sheet.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Details" : {
|
"Details" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -8802,6 +8822,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Filter projects" : {
|
||||||
|
"comment" : "A label for a search field in the sidebar.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Filter servers..." : {
|
"Filter servers..." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -9006,6 +9030,10 @@
|
|||||||
"comment" : "A placeholder for a comma-separated list of tags.",
|
"comment" : "A placeholder for a comma-separated list of tags.",
|
||||||
"isCommentAutoGenerated" : true
|
"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)" : {
|
"Full copy of active profile (all state)" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -9698,6 +9726,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Hide archived projects" : {
|
||||||
|
"comment" : "A toggle that hides archived projects.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Hide details" : {
|
"Hide details" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"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" : {
|
"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 '%@'" : {
|
"New name for '%@'" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -13327,6 +13386,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"No project selected" : {
|
||||||
|
"comment" : "A label that indicates that no project is selected.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"No Projects" : {
|
"No Projects" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -15458,6 +15521,10 @@
|
|||||||
},
|
},
|
||||||
"Project folder kept" : {
|
"Project folder kept" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Project name" : {
|
||||||
|
"comment" : "A label for a text field that lets the user enter a project name.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Project Name" : {
|
"Project Name" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -16870,6 +16937,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Rename project" : {
|
||||||
|
"comment" : "A title for a sheet that renames a project.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Rename Session" : {
|
"Rename Session" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -16949,6 +17020,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Rename…" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"required" : {
|
"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." : {
|
"Set as default — open this server when Scarf launches." : {
|
||||||
"comment" : "A tooltip for the star button in the Manage Servers view.",
|
"comment" : "A tooltip for the star button in the Manage Servers view.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
@@ -19623,6 +19701,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Show archived projects" : {
|
||||||
|
"comment" : "A toggle that shows/hides archived projects.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Show details" : {
|
"Show details" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"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." : {
|
"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" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -22422,6 +22508,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Top Level" : {
|
||||||
|
"comment" : "A folder in the sidebar.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Top Tools" : {
|
"Top Tools" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
@@ -22582,6 +22672,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Unarchive" : {
|
||||||
|
"comment" : "A button that unarchives a project.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Uninstall" : {
|
"Uninstall" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"de" : {
|
"de" : {
|
||||||
|
|||||||
Reference in New Issue
Block a user