fix(client/settings): one marker per override, footers at footer size, air under Reset

The resolution marker hung off the Match-window toggle, which read as if that toggle
alone were overridden. Match-window, width and height are ONE override — they reset
together — so the marker belongs to the group, under the size control that ends it,
not to the first of the two rows that write it.

The new section footers inherited the app's 17pt body font, so the profile picker's
description sat visibly larger than every other description in Settings. Same style
as the footers that were already there.

And the marker carries a bordered button, which needs more air under the control
above it than a line of text does — most visible under the segmented refresh picker.
Added inside the marker, so every row that shows it is spaced alike.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 12:06:41 +02:00
co-authored by Claude Opus 5
parent e9abc1a61f
commit 8b17e72af0
4 changed files with 20 additions and 2 deletions
@@ -250,6 +250,8 @@ struct AddHostSheet: View {
}
} footer: {
Text("The settings a plain tap on this host streams with.")
.font(.geist(12, relativeTo: .caption))
.foregroundStyle(.secondary)
}
Section {
ForEach(profiles.profiles) { profile in
@@ -267,6 +269,8 @@ struct AddHostSheet: View {
Text("Pinned cards")
} footer: {
Text("A pinned profile gets its own card next to this host — one tap, no menu.")
.font(.geist(12, relativeTo: .caption))
.foregroundStyle(.secondary)
}
}
#endif
@@ -275,6 +275,10 @@ extension SettingsView {
.accessibilityLabel("Reset to Default settings")
.help("Stop overriding this — follow Default settings again")
}
// The marker carries a bordered button, which needs more air under the control or
// caption above it than a line of text would. Here rather than at each call site, so
// every row that shows it is spaced the same.
.padding(.top, 4)
}
}
@@ -36,16 +36,20 @@ extension SettingsView {
#if os(iOS) || os(macOS)
// Match-window (design/midstream-resolution-resize.md D1): follow the session
// window/scene, renegotiating the host mode on a resize. Off the explicit mode below.
// NO marker here even though this toggle writes one: match-window, width and
// height are ONE override (they are reset together), and hanging its marker off the
// first of the two controls that drive it read as if the toggle alone were
// overridden. It goes under the size control below, for the group.
described(effective.matchWindow
? "The host resizes its output to follow this window — the picture stays "
+ "pixel-exact (1:1) through every resize."
: "Stream at the fixed mode below; a window at a different size shows it scaled.",
field: OverlayField.resolution) {
: "Stream at the fixed mode below; a window at a different size shows it scaled.") {
Toggle("Match window", isOn: scoped(SettingsFields.matchWindow))
}
#endif
#if os(iOS)
iosResolutionWheel
overrideMarker(OverlayField.resolution)
iosRefreshRows
Button("Use this display's mode") { fillFromMainScreen() }
#elseif os(macOS)
@@ -57,6 +61,7 @@ extension SettingsView {
TextField("", value: scoped(SettingsFields.height), format: .number.grouping(.never))
.labelsHidden()
}
overrideMarker(OverlayField.resolution)
described("The host drives a real virtual output at exactly this size and refresh — "
+ "true pixels, no scaling.", field: "refresh_hz") {
TextField(
@@ -238,7 +238,12 @@ struct SettingsView: View {
Section {
scopeRow
} footer: {
// A List footer inherits the app's body font unless it's told otherwise, and
// 17pt Geist next to the 12pt footers everywhere else read as a different
// kind of text. Same style as every other footer in this surface.
Text(scopeCaption)
.font(.geist(12, relativeTo: .caption))
.foregroundStyle(.secondary)
}
ForEach(SettingsCategory.allCases) { category in
// On iPhone the split view collapses to a push list, but a selection List