ios+mac: text size — ⌘+/⌘− + Settings slider on Mac, Dynamic Type on iOS (#420)
* ios+mac: text size — ⌘+/⌘− + Settings slider on Mac, Dynamic Type on iOS MessageBubble reads its size from a new \.chatFontSize environment value (default 16 = old behaviour); bold/italic/inline-code/fence/quote sizes keep their ratios, and the rich-text memo keys on size so ⌘+ rebuilds runs instead of replaying the old size. Mac: chatTextSize pref (13pt default — Mac iMessage's size, matching the web's new 13px) over the web's seven-stop ladder. View menu gains Make Text Bigger/Smaller (⌘+/⌘−, plus an AppDelegate monitor mapping the unshifted ⌘= press) and Default Text Size (⌘0); Settings → General gains the A——A slider. The native feed + composer take the pref via the environment; the webview fallback gets it as the page's --msg-font-size variable (removed at the default so the web's own slider still rules), re-applied after navigations. iOS: the native feed honours the system Text Size — a @ScaledMetric body size (17 at default, the web's touch size) feeds \.chatFontSize, and the composer's UITextView/placeholder set adjustsFontForContentSizeCategory so they live-track the setting. The webview surfaces get Dynamic Type from the web PR's -apple-system-body CSS with no bridging. No changelog entry yet — it rides the PR that mints the next iOS/Mac release, per convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac: Settings window — one fixed 600×560 frame on the TabView, same size across tabs Per-pane frames (420×400 General, 420×100 Updates) made the window resize on every tab switch. The TabView now owns the single frame; panes size to fill, and the Updates pane goes grouped to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac: default text size 13 → 14 — the ladder's middle stop, matching Messages.app Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac: text-size ladder tops out at 16 — default one from the end (9…14, 16) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * mac: 15pt default — ladder 10…16, default second from top Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> ---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
10 files changed,
+162
−23
ios/LChat/Views/ComposerField.swift+4 −0ios/LChat/Views/NativeChatScreen.swift+4 −0ios/LChatCore/Sources/LChatCore/MessageBubble.swift+34 −11ios/LChatMac/ChatFeedWebView.swift+18 −0ios/LChatMac/LChatMacApp.swift+33 −0ios/LChatMac/MacSettingsView.swift+25 −5ios/LChatMac/NativeChatView.swift+4 −0ios/LChatMac/NativeFeedView.swift+2 −1ios/LChatMac/TextSize.swift+35 −0ios/LChatMac/UpdaterUI.swift+3 −6
diff --git a/ios/LChat/Views/ComposerField.swift b/ios/LChat/Views/ComposerField.swift| index bc99d20..400a079 100644 |
| --- a/ios/LChat/Views/ComposerField.swift |
| +++ b/ios/LChat/Views/ComposerField.swift |
| @@ -33,6 +33,9 @@ struct ComposerField: UIViewRepresentable { |
| tv.onEditLast = onEditLast |
| tv.delegate = context.coordinator |
| tv.font = .preferredFont(forTextStyle: .body) |
| + // Live-track the system Text Size (Dynamic Type) — preferredFont only |
| + // sizes at creation without this. |
| + tv.adjustsFontForContentSizeCategory = true |
| tv.backgroundColor = .clear |
| tv.textContainerInset = .zero |
| tv.textContainer.lineFragmentPadding = 0 |
| @@ -136,6 +139,7 @@ final class GrowingTextView: UITextView { |
| override init(frame: CGRect, textContainer: NSTextContainer?) { |
| super.init(frame: frame, textContainer: textContainer) |
| placeholderLabel.font = .preferredFont(forTextStyle: .body) |
| + placeholderLabel.adjustsFontForContentSizeCategory = true |
| placeholderLabel.textColor = .placeholderText |
| placeholderLabel.numberOfLines = 1 |
| addSubview(placeholderLabel) |
diff --git a/ios/LChat/Views/NativeChatScreen.swift b/ios/LChat/Views/NativeChatScreen.swift| index 99674c0..97b6402 100644 |
| --- a/ios/LChat/Views/NativeChatScreen.swift |
| +++ b/ios/LChat/Views/NativeChatScreen.swift |
| @@ -124,6 +124,9 @@ struct NativeChatScreen: View { |
| @Environment(\.dismiss) private var dismiss |
| @Environment(\.scenePhase) private var scenePhase |
| @AppStorage("nativeTheme") private var themeName = "classic" |
| + // Dynamic Type: read at the system body size (17 at the default setting — |
| + // also the web's touch size), so Settings → Text Size scales the feed. |
| + @ScaledMetric(relativeTo: .body) private var chatFontSize: CGFloat = 17 |
| @State private var avatarPop = false |
| // Whether the bottom sentinel is on screen — gates the scroll pins. |
| @State private var bottomVisible = false |
| @@ -505,6 +508,7 @@ struct NativeChatScreen: View { |
| onCopy: { UIPasteboard.general.string = m.text }, |
| onReact: { model.toggleReaction(m.id, emoji: $0) }), |
| unfurl: { await model.unfurl($0) }) |
| + .environment(\.chatFontSize, chatFontSize) |
| if !m.mine { Spacer(minLength: 40) } |
| } |
| // Web-parity breathing room: a touch more air between bubbles, |
diff --git a/ios/LChatCore/Sources/LChatCore/MessageBubble.swift b/ios/LChatCore/Sources/LChatCore/MessageBubble.swift| index f78ae87..359bd31 100644 |
| --- a/ios/LChatCore/Sources/LChatCore/MessageBubble.swift |
| +++ b/ios/LChatCore/Sources/LChatCore/MessageBubble.swift |
| @@ -18,6 +18,21 @@ private extension View { |
| } |
| } |
| +// The feed's reading size, in points — web parity: --msg-font-size in |
| +// style.css (15px desktop default, 17px touch). The Mac injects its Text Size |
| +// pref (View menu ⌘+/⌘−, Settings slider); iOS injects the Dynamic Type body |
| +// size so the system Text Size setting scales the feed. The default is the old |
| +// fixed 16, so a surface that doesn't inject renders exactly as before. |
| +private struct ChatFontSizeKey: EnvironmentKey { |
| + static let defaultValue: CGFloat = 16 |
| +} |
| +public extension EnvironmentValues { |
| + var chatFontSize: CGFloat { |
| + get { self[ChatFontSizeKey.self] } |
| + set { self[ChatFontSizeKey.self] = newValue } |
| + } |
| +} |
| + |
| // The action catalog a bubble offers — one definition feeding both the hover |
| // strip and the right-click menu (the design doc's "consistent actions"). |
| public struct MessageActions { |
| @@ -87,6 +102,7 @@ public struct MessageBubble: View { |
| public let unfurl: ((URL) async -> LinkPreview?)? |
| @Environment(\.colorScheme) private var scheme |
| + @Environment(\.chatFontSize) private var fontSize |
| public init(message: ChatFeedModel.Message, showSender: Bool, |
| theme: ChatTheme = .classic, onJump: ((Int64) -> Void)? = nil, |
| @@ -262,7 +278,9 @@ public struct MessageBubble: View { |
| Text(name).font(.system(size: 11)).foregroundStyle(.secondary) |
| } |
| Text(reply.snippet ?? "") |
| - .font(.system(size: 13)) |
| + // two notches under the reading size at every size, like |
| + // the web's .reply-quote-text (calc(var(--msg-font-size) - 2px)) |
| + .font(.system(size: fontSize - 2)) |
| .foregroundStyle(.secondary) |
| .lineLimit(1) |
| .frame(maxWidth: 220, alignment: message.mine ? .trailing : .leading) |
| @@ -378,9 +396,11 @@ public struct MessageBubble: View { |
| switch block { |
| case .p(let text): |
| Text(richText(text)) |
| - // The web inherits the browser's 16px default (style.css sets |
| - // no body font-size), so 16pt keeps the two feeds identical. |
| - .font(.system(size: 16)) |
| + // The reading size comes from \.chatFontSize (web parity: |
| + // style.css --msg-font-size) — the Mac's pref or iOS's |
| + // Dynamic Type body size; derived sizes below keep the fixed |
| + // ratios they've always had against it. |
| + .font(.system(size: fontSize)) |
| .lineSpacing(2) |
| .tint(ink) // links inherit the bubble's ink (style.css), underlined |
| case .code(let text, _): |
| @@ -388,7 +408,7 @@ public struct MessageBubble: View { |
| // lines scrolling rather than wrapping (.msg .bubble pre). |
| ScrollView(.horizontal, showsIndicators: false) { |
| Text(text) |
| - .font(.system(size: 13.5, design: .monospaced)) |
| + .font(.system(size: fontSize * (13.5 / 16), design: .monospaced)) |
| } |
| .padding(EdgeInsets(top: 8, leading: 10, bottom: 8, trailing: 10)) |
| .background(RoundedRectangle(cornerRadius: 8, style: .continuous) |
| @@ -396,7 +416,7 @@ public struct MessageBubble: View { |
| case .quote(let text): |
| // "> " blockquote — quiet left rule, slightly dimmed text. |
| Text(richText(text)) |
| - .font(.system(size: 16)) |
| + .font(.system(size: fontSize)) |
| .lineSpacing(2) |
| .tint(ink) |
| .opacity(0.75) |
| @@ -437,10 +457,13 @@ public struct MessageBubble: View { |
| } |
| private func richText(_ text: String) -> AttributedString { |
| - if let hit = Self.richMemo[text] { return hit } |
| + // The built runs bake in \.chatFontSize, so it's part of the key — a |
| + // ⌘+ / Text Size change must rebuild, not replay the old size. |
| + let key = "\(fontSize)|\(text)" |
| + if let hit = Self.richMemo[key] { return hit } |
| if Self.richMemo.count > 600 { Self.richMemo.removeAll() } |
| let built = buildRichText(text) |
| - Self.richMemo[text] = built |
| + Self.richMemo[key] = built |
| return built |
| } |
| @@ -452,11 +475,11 @@ public struct MessageBubble: View { |
| out += AttributedString(s) |
| case .bold(let s): |
| var a = AttributedString(s) |
| - a.font = .system(size: 16, weight: .bold) |
| + a.font = .system(size: fontSize, weight: .bold) |
| out += a |
| case .italic(let s): |
| var a = AttributedString(s) |
| - a.font = .system(size: 16).italic() |
| + a.font = .system(size: fontSize).italic() |
| out += a |
| case .strike(let s): |
| var a = AttributedString(s) |
| @@ -466,7 +489,7 @@ public struct MessageBubble: View { |
| // Narrow no-break spaces give the chip its side padding — an |
| // AttributedString background can't pad a run (web: 1px 5px). |
| var a = AttributedString("\u{202F}\(s)\u{202F}") |
| - a.font = .system(size: 14.7, design: .monospaced) // 0.92em of 16 |
| + a.font = .system(size: fontSize * 0.92, design: .monospaced) // 0.92em (style.css) |
| a.backgroundColor = ink.opacity(message.mine ? 0.22 : 0.08) |
| out += a |
| case .link(let s, let url): |
diff --git a/ios/LChatMac/ChatFeedWebView.swift b/ios/LChatMac/ChatFeedWebView.swift| index ef9e838..f0c9030 100644 |
| --- a/ios/LChatMac/ChatFeedWebView.swift |
| +++ b/ios/LChatMac/ChatFeedWebView.swift |
| @@ -28,9 +28,23 @@ struct ChatFeedWebView: NSViewRepresentable { |
| let chat: SavedChat |
| let app: MacAppState |
| let isActive: Bool |
| + // Re-runs updateNSView when the Text Size pref changes so the live page |
| + // re-renders at the new size without a reload. |
| + @AppStorage(ChatTextSize.key) private var chatTextSize = ChatTextSize.defaultSize |
| func makeCoordinator() -> Coordinator { Coordinator(chat: chat, app: app) } |
| + // Drive the web feed's text size from the native pref via the page's |
| + // --msg-font-size variable (style.css). At the default the override is |
| + // REMOVED, so the web's own theme-sheet slider (a browser-local pref) |
| + // still rules until the Mac pref is deliberately moved. |
| + static func textSizeJS(_ size: Double) -> String { |
| + let px = Int(ChatTextSize.snap(size)) |
| + return px == Int(ChatTextSize.defaultSize) |
| + ? "document.documentElement.style.removeProperty('--msg-font-size')" |
| + : "document.documentElement.style.setProperty('--msg-font-size','\(px)px')" |
| + } |
| + |
| func makeNSView(context: Context) -> WKWebView { |
| let cfg = WKWebViewConfiguration() |
| cfg.websiteDataStore = .default() // persist localStorage/session per origin |
| @@ -88,6 +102,7 @@ struct ChatFeedWebView: NSViewRepresentable { |
| // chat switch re-registered the visible webview. |
| if isActive { app.activeWebView = web } |
| web.registerForDraggedTypes([.fileURL]) |
| + web.evaluateJavaScript(Self.textSizeJS(chatTextSize), completionHandler: nil) |
| } |
| // Runs on every page load. (1) Window drag: start a native window-drag when the |
| @@ -326,6 +341,9 @@ struct ChatFeedWebView: NSViewRepresentable { |
| // WKWebView clobbers the view's registered drag types during navigation, |
| // so re-assert file-drop registration after every page load. |
| webView.registerForDraggedTypes([.fileURL]) |
| + // A navigation wipes inline styles — re-apply the text-size pref. |
| + webView.evaluateJavaScript(ChatFeedWebView.textSizeJS(ChatTextSize.current), |
| + completionHandler: nil) |
| // Only the instance's OWN origin counts — for the join fallback AND the |
| // identity extraction. The page can navigate to another origin (a link, |
| // or the passkey-backup flow visiting the home server): "no session" on |
diff --git a/ios/LChatMac/LChatMacApp.swift b/ios/LChatMac/LChatMacApp.swift| index 904eaf0..0c57831 100644 |
| --- a/ios/LChatMac/LChatMacApp.swift |
| +++ b/ios/LChatMac/LChatMacApp.swift |
| @@ -24,6 +24,8 @@ extension Logger { |
| struct LChatMacApp: App { |
| @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate |
| @StateObject private var app = MacAppState() |
| + // Observed here only so the View-menu items' enabled state tracks the pref. |
| + @AppStorage(ChatTextSize.key) private var chatTextSize = ChatTextSize.defaultSize |
| init() { |
| // Match the web feed's text rendering: style.css sets |
| @@ -69,6 +71,22 @@ struct LChatMacApp: App { |
| Button("Find Chat…") { app.focusSearchTick += 1 } |
| .keyboardShortcut("f", modifiers: .command) |
| } |
| + // View → Make Text Bigger/Smaller (Messages' menu), stepping the |
| + // same ladder as the Settings slider and the web's. The menu key |
| + // equivalent is "+", which strictly means ⌘⇧=; AppDelegate's key |
| + // monitor also maps the unshifted ⌘= press — what "cmd and plus" |
| + // physically is on most keyboards — like Messages/Safari accept. |
| + CommandGroup(before: .toolbar) { |
| + Button("Make Text Bigger") { ChatTextSize.bump(+1) } |
| + .keyboardShortcut("+", modifiers: .command) |
| + .disabled(ChatTextSize.snap(chatTextSize) >= ChatTextSize.stops.last!) |
| + Button("Make Text Smaller") { ChatTextSize.bump(-1) } |
| + .keyboardShortcut("-", modifiers: .command) |
| + .disabled(ChatTextSize.snap(chatTextSize) <= ChatTextSize.stops.first!) |
| + Button("Default Text Size") { ChatTextSize.reset() } |
| + .keyboardShortcut("0", modifiers: .command) |
| + Divider() |
| + } |
| // ⌘1…⌘9 jump straight to a server, like browser/Slack tabs. |
| CommandMenu("Servers") { |
| Button("Quick Switch…") { app.showPalette = true } |
| @@ -92,6 +110,11 @@ struct LChatMacApp: App { |
| .tabItem { Label("Updates", systemImage: "arrow.down.circle") } |
| } |
| .environmentObject(app) |
| + // ONE fixed frame for the whole Settings window, here on the |
| + // TabView — per-pane frames made the window resize on every tab |
| + // switch (and shrink-and-stick after visiting the short Updates |
| + // pane). Panes size to fill; a grouped Form scrolls if it must. |
| + .frame(width: 600, height: 560) |
| } |
| } |
| } |
| @@ -105,6 +128,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent |
| startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) |
| func applicationDidFinishLaunching(_ notification: Notification) { |
| + // "cmd and plus": the View menu's ⌘+ equivalent strictly needs the |
| + // shifted key (⌘⇧=), but people press ⌘= and expect bigger text — |
| + // Messages and Safari both accept it, so map it here. (⌘− needs no |
| + // help; "-" is unshifted already.) |
| + NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in |
| + guard event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command, |
| + event.charactersIgnoringModifiers == "=" else { return event } |
| + ChatTextSize.bump(+1) |
| + return nil |
| + } |
| let center = UNUserNotificationCenter.current() |
| center.delegate = self |
| center.requestAuthorization(options: [.alert, .sound]) { _, _ in } |
diff --git a/ios/LChatMac/MacSettingsView.swift b/ios/LChatMac/MacSettingsView.swift| index 31f86a7..e8e6493 100644 |
| --- a/ios/LChatMac/MacSettingsView.swift |
| +++ b/ios/LChatMac/MacSettingsView.swift |
| @@ -7,6 +7,7 @@ import SwiftUI |
| struct MacSettingsView: View { |
| @EnvironmentObject var app: MacAppState |
| @AppStorage("nativeTheme") private var themeName = "classic" |
| + @AppStorage(ChatTextSize.key) private var textSize = ChatTextSize.defaultSize |
| @State private var name = "" |
| @State private var saving = false |
| @@ -31,6 +32,19 @@ struct MacSettingsView: View { |
| } |
| .pickerStyle(.radioGroup) |
| } |
| + Section { |
| + // The iMessage A——A slider: seven stops over the same ladder |
| + // as the web slider and the View menu's ⌘+/⌘−. |
| + HStack(spacing: 10) { |
| + Text("A").font(.system(size: 11)).foregroundStyle(.secondary) |
| + Slider(value: textSizeIndex, |
| + in: 0...Double(ChatTextSize.stops.count - 1), step: 1) |
| + Text("A").font(.system(size: 19)).foregroundStyle(.secondary) |
| + } |
| + .accessibilityLabel("Text size") |
| + } header: { |
| + Text("Text size") |
| + } |
| Section("Profile") { |
| Picker("Server", selection: $selectedInstanceID) { |
| ForEach(app.instances) { inst in |
| @@ -56,11 +70,8 @@ struct MacSettingsView: View { |
| } |
| } |
| .formStyle(.grouped) |
| - // Fixed height (not just width): the Settings window sizes per tab, and |
| - // after visiting the short Updates pane a height-flexible pane stays |
| - // shrunk instead of regrowing. The grouped Form scrolls if it ever |
| - // outgrows this. |
| - .frame(width: 420, height: 400) |
| + // No frame here — the Settings window's single fixed size lives on the |
| + // TabView (LChatMacApp), so every tab shares it. |
| .onAppear { |
| selectedInstanceID = instance?.id |
| name = instance?.screenName ?? "" |
| @@ -71,6 +82,15 @@ struct MacSettingsView: View { |
| } |
| } |
| + // The slider moves by stop index; the pref stores the point size itself |
| + // (snap() forgives any stored off-ladder value). |
| + private var textSizeIndex: Binding<Double> { |
| + Binding( |
| + get: { Double(ChatTextSize.stops.firstIndex(of: ChatTextSize.snap(textSize)) ?? 5) }, |
| + set: { textSize = ChatTextSize.stops[Int($0.rounded())] } |
| + ) |
| + } |
| + |
| private func save() { |
| guard let inst = instance else { return } |
| let trimmed = name.trimmingCharacters(in: .whitespaces) |
diff --git a/ios/LChatMac/NativeChatView.swift b/ios/LChatMac/NativeChatView.swift| index 74afece..fbc7a94 100644 |
| --- a/ios/LChatMac/NativeChatView.swift |
| +++ b/ios/LChatMac/NativeChatView.swift |
| @@ -36,6 +36,7 @@ struct NativeChatView: View { |
| @State private var dragStartWidth: Double? |
| @Environment(\.colorScheme) private var scheme |
| @AppStorage("nativeTheme") private var themeName = "classic" |
| + @AppStorage(ChatTextSize.key) private var chatTextSize = ChatTextSize.defaultSize |
| private var theme: ChatTheme { .named(themeName) } |
| private var collapsed: Bool { listWidth < 170 } |
| @@ -348,6 +349,9 @@ struct NativeChatView: View { |
| }) |
| .id(thread.id + "|" + thread.threadKeyB64 + "|" |
| + String(UInt(bitPattern: ObjectIdentifier(model).hashValue))) |
| + // The Text Size pref (View menu ⌘+/⌘−, Settings slider) drives the |
| + // feed's reading size — bubbles + composer; the list stays put. |
| + .environment(\.chatFontSize, CGFloat(ChatTextSize.snap(chatTextSize))) |
| } |
| private func feedHeader(_ t: ChatThread) -> some View { |
diff --git a/ios/LChatMac/NativeFeedView.swift b/ios/LChatMac/NativeFeedView.swift| index c30732e..a320116 100644 |
| --- a/ios/LChatMac/NativeFeedView.swift |
| +++ b/ios/LChatMac/NativeFeedView.swift |
| @@ -8,6 +8,7 @@ import AVFoundation |
| // (via .id above) to the picked thread. |
| struct NativeFeedView: View { |
| @StateObject private var model: ChatFeedModel |
| + @Environment(\.chatFontSize) private var chatFontSize |
| private let isGroup: Bool |
| private let roster: [RosterEntry] |
| private let myUserId: String |
| @@ -774,7 +775,7 @@ struct NativeFeedView: View { |
| handlePaste(providers) |
| } |
| .textFieldStyle(.plain) |
| - .font(.system(size: 16)) // the feed's reading size, like the web composer |
| + .font(.system(size: chatFontSize)) // the feed's reading size, like the web composer |
| .lineLimit(1...9) // grows to ~9 lines, then scrolls within itself |
| .padding(.horizontal, 14) |
| .padding(.vertical, 9) |
diff --git a/ios/LChatMac/TextSize.swift b/ios/LChatMac/TextSize.swift| new file mode 100644 |
| index 0000000..6003f0e |
| --- /dev/null |
| +++ b/ios/LChatMac/TextSize.swift |
| @@ -0,0 +1,35 @@ |
| +import Foundation |
| + |
| +// 🤖 The Mac's chat Text Size preference, in points — the same seven-stop |
| +// ladder as the web slider (TEXT_SIZES in ui.js). The default sits ONE FROM |
| +// THE TOP: 16 is the only workable bigger size (beyond that bubbles read as |
| +// shouting), so the room to taste lies below. One UserDefaults key that |
| +// @AppStorage views observe; the View-menu commands, the ⌘= key monitor and |
| +// the Settings slider all step through here so every entry point moves the |
| +// same ladder. |
| +enum ChatTextSize { |
| + static let stops: [Double] = [10, 11, 12, 13, 14, 15, 16] |
| + static let defaultSize: Double = 15 |
| + static let key = "chatTextSize" |
| + |
| + static var current: Double { |
| + let v = UserDefaults.standard.double(forKey: key) |
| + return v > 0 ? snap(v) : defaultSize |
| + } |
| + |
| + // Nearest stop, so a value stored by a future (or hand-edited) ladder |
| + // still lands somewhere sane. |
| + static func snap(_ v: Double) -> Double { |
| + stops.min(by: { abs($0 - v) < abs($1 - v) }) ?? defaultSize |
| + } |
| + |
| + static func bump(_ direction: Int) { |
| + guard let i = stops.firstIndex(of: current) else { return } |
| + let j = min(max(i + direction, 0), stops.count - 1) |
| + UserDefaults.standard.set(stops[j], forKey: key) |
| + } |
| + |
| + static func reset() { |
| + UserDefaults.standard.set(defaultSize, forKey: key) |
| + } |
| +} |
diff --git a/ios/LChatMac/UpdaterUI.swift b/ios/LChatMac/UpdaterUI.swift| index 8cbd033..7671e66 100644 |
| --- a/ios/LChatMac/UpdaterUI.swift |
| +++ b/ios/LChatMac/UpdaterUI.swift |
| @@ -53,11 +53,8 @@ struct UpdatesSettingsView: View { |
| updater.automaticallyDownloadsUpdates = enabled |
| } |
| } |
| - .padding(20) |
| - // A FIXED frame, matching the General pane's width: SwiftUI's Settings |
| - // window sizes itself per tab, and a content-sized pane doesn't reliably |
| - // regrow the window when you switch back to a taller tab — explicit |
| - // heights make every switch land on the right size. |
| - .frame(width: 420, height: 100) |
| + // Grouped like the General pane; the TabView in LChatMacApp owns the |
| + // Settings window's one fixed frame, so no size is pinned here. |
| + .formStyle(.grouped) |
| } |
| } |