From e9abc1a61fe1245454bafa43215766bd06b9f948 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 29 Jul 2026 10:15:59 +0200 Subject: [PATCH] fix(client/settings): Reset belongs on the row's edge, not in the caption's column MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The override marker sat inside the caption's width rule, so its Reset stopped where the text stops — stranded mid-row, short of the switch it undoes. The rule is for TEXT: Reset is a control, and it lines up with the row's own control above it. The marker now spans the cell and only the caption is capped and inset. The caption's reserved column widens with it (60 → 76): it should stop visibly short of the control, not graze it. Co-Authored-By: Claude Opus 5 (1M context) --- .../Settings/SettingsView+Sections.swift | 1 - .../Settings/SettingsView+Support.swift | 36 ++++++++++--------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Sections.swift b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Sections.swift index dc1577fe..bd0168bd 100644 --- a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Sections.swift +++ b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Sections.swift @@ -134,7 +134,6 @@ extension SettingsView { .labelsHidden() .pickerStyle(.segmented) overrideMarker("refresh_hz") - .modifier(CaptionWidth()) } } else { // A device with a single supported rate (e.g. 60 Hz) has nothing to pick. diff --git a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Support.swift b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Support.swift index d3cb7f4c..01a48d6d 100644 --- a/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Support.swift +++ b/clients/apple/Sources/PunktfunkClient/Settings/SettingsView+Support.swift @@ -8,19 +8,20 @@ import AppKit import PunktfunkKit import SwiftUI -/// How wide a `described` row's caption (and its override marker) may run. +/// How wide a `described` row's caption may run. TEXT only — the override marker's Reset is a +/// control and belongs on the row's trailing edge, not in the caption's column. /// /// Two limits, because they solve different problems. The 360pt CAP is about reading: past roughly /// 46 characters a line stops scanning well, and on a wide Mac window or an iPad detail pane an /// uncapped caption runs the full cell. The trailing INSET is about collision: an iOS grouped row -/// puts its switch or value about 60pt in from the trailing edge, and a caption laid out to the -/// full cell width runs its last line straight under that control — which is what the cap alone -/// never fixed, because an iPhone cell is narrower than the cap in the first place. +/// puts its switch or value in from the trailing edge, and a caption laid out to the full cell +/// width runs its last line straight under that control — which is what the cap alone never +/// fixed, because an iPhone cell is narrower than the cap in the first place. struct CaptionWidth: ViewModifier { #if os(iOS) - /// Reserve the control column. Sized to a `UISwitch` (51pt) plus breathing room — the widest - /// of the trailing accessories these rows use. - private static let trailingInset: CGFloat = 60 + /// Reserve the control column. A `UISwitch` is 51pt, and the rest is breathing room — the + /// caption should stop visibly short of the control, not graze it. + private static let trailingInset: CGFloat = 76 #else // macOS lays the control out inline and its cells are wider than the cap; nothing to clear. private static let trailingInset: CGFloat = 0 @@ -52,17 +53,18 @@ extension SettingsView { ) -> some View { VStack(alignment: .leading, spacing: 5) { content() - VStack(alignment: .leading, spacing: 5) { - Text(caption) - .font(.geist(13, relativeTo: .footnote)) - .foregroundStyle(.secondary) - // Wrap, never truncate, in Form cells. - .fixedSize(horizontal: false, vertical: true) - if let field { - overrideMarker(field) - } + Text(caption) + .font(.geist(13, relativeTo: .footnote)) + .foregroundStyle(.secondary) + // Wrap, never truncate, in Form cells. + .fixedSize(horizontal: false, vertical: true) + .modifier(CaptionWidth()) + if let field { + // Full cell width, deliberately: the marker's Reset is a CONTROL, and it belongs + // on the same trailing edge as the row's own control above it. Sharing the + // caption's reserved column left it stranded mid-row. + overrideMarker(field) } - .modifier(CaptionWidth()) } .padding(.vertical, 2) }