mac: Settings — Profile first with account rows folded in, CLI tool moves to a new Advanced tab (#956)
Profile (server, display name, away, DM privacy) now leads the General tab as one combined section ending with the sign-in state + Sign Out rows — HomeSignInSection emits rows instead of its own 'Eleven' section, so identity and account read as one piece. The command-line tool gets a proper Advanced tab (with a quiet fallback for builds that don't embed the helper) instead of crowding General.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
4 files changed,
+69
−40
ios/LChatMac/CLIToolSection.swift+24 −1ios/LChatMac/LChatMacApp.swift+2 −0ios/LChatMac/MacHomeSignIn.swift+6 −3ios/LChatMac/MacSettingsView.swift+37 −36
diff --git a/ios/LChatMac/CLIToolSection.swift b/ios/LChatMac/CLIToolSection.swift| index f1e6de0..14e8dbe 100644 |
| --- a/ios/LChatMac/CLIToolSection.swift |
| +++ b/ios/LChatMac/CLIToolSection.swift |
| @@ -1,7 +1,7 @@ |
| import AppKit |
| import SwiftUI |
| -// 🤖 Settings → General → "Command-line tool": install the bundled eleven CLI |
| +// 🤖 Settings → Advanced → "Command-line tool": install the bundled eleven CLI |
| // (Contents/Helpers/eleven, embedded by the "Embed eleven CLI" build phase — |
| // see ios/project.yml and docs/eleven-cli.md) onto the user's PATH. |
| // |
| @@ -11,6 +11,29 @@ import SwiftUI |
| // even that fails — stock /usr/local/bin is root-owned — we fall back to |
| // showing the one Terminal command that does it, with a Copy button. A symlink |
| // (not a copy) means Sparkle updates update the CLI too. |
| +// The Advanced tab (Settings window, LChatMacApp): power-user machinery that |
| +// would crowd General — today just the CLI install (Paul, 2026-07-15). |
| +struct MacAdvancedSettingsView: View { |
| + var body: some View { |
| + Form { |
| + CLIToolSection() |
| + // CLIToolSection renders nothing when the helper isn't embedded |
| + // (a build without Go) — a wholly blank tab would read as broken, |
| + // so say the quiet part. |
| + if !FileManager.default.fileExists( |
| + atPath: Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/eleven").path) { |
| + Section { |
| + Text("The command-line tool isn't included in this build.") |
| + .font(.footnote).foregroundStyle(.secondary) |
| + } |
| + } |
| + } |
| + .formStyle(.grouped) |
| + // No frame — the Settings window's single fixed size lives on the |
| + // TabView (LChatMacApp), same as every other pane. |
| + } |
| +} |
| + |
| struct CLIToolSection: View { |
| @AppStorage("cliInstalledPath") private var installedPath = "" |
| @State private var note: String? |
diff --git a/ios/LChatMac/LChatMacApp.swift b/ios/LChatMac/LChatMacApp.swift| index 2fc67ae..d71f776 100644 |
| --- a/ios/LChatMac/LChatMacApp.swift |
| +++ b/ios/LChatMac/LChatMacApp.swift |
| @@ -136,6 +136,8 @@ struct LChatMacApp: App { |
| .tabItem { Label("General", systemImage: "gearshape") } |
| UpdatesSettingsView(updater: appDelegate.updaterController.updater) |
| .tabItem { Label("Updates", systemImage: "arrow.down.circle") } |
| + MacAdvancedSettingsView() |
| + .tabItem { Label("Advanced", systemImage: "wrench.and.screwdriver") } |
| } |
| .environmentObject(app) |
| // ONE fixed frame for the whole Settings window, here on the |
diff --git a/ios/LChatMac/MacHomeSignIn.swift b/ios/LChatMac/MacHomeSignIn.swift| index a16605f..22170b1 100644 |
| --- a/ios/LChatMac/MacHomeSignIn.swift |
| +++ b/ios/LChatMac/MacHomeSignIn.swift |
| @@ -227,15 +227,18 @@ final class MacHomeSignIn: ObservableObject { |
| } |
| } |
| -// The Settings section (MacSettingsView mounts it above Theme): one line of |
| -// state and one action, matching the pane's quiet register. |
| +// The sign-in state + Sign Out rows (MacSettingsView mounts them at the |
| +// BOTTOM of the combined Profile section — Paul, 2026-07-15): one line of |
| +// state and one action, matching the pane's quiet register. Rows, not a |
| +// Section — the caller owns the section so identity and account read as one |
| +// piece. |
| struct HomeSignInSection: View { |
| @EnvironmentObject var app: MacAppState |
| @ObservedObject var model: MacHomeSignIn |
| @State private var confirmSignOut = false |
| var body: some View { |
| - Section("Eleven") { |
| + Group { |
| if model.sessionExists { |
| LabeledContent("Signed in") { |
| Button("Sign Out…") { confirmSignOut = true } |
diff --git a/ios/LChatMac/MacSettingsView.swift b/ios/LChatMac/MacSettingsView.swift| index ca61143..9395c48 100644 |
| --- a/ios/LChatMac/MacSettingsView.swift |
| +++ b/ios/LChatMac/MacSettingsView.swift |
| @@ -42,42 +42,8 @@ struct MacSettingsView: View { |
| var body: some View { |
| Form { |
| - HomeSignInSection(model: homeSignIn) |
| - Section("Theme") { |
| - Picker("Chat theme", selection: $themeName) { |
| - ForEach(ChatTheme.presets, id: \.id) { preset in |
| - Text(preset.title).tag(preset.id) |
| - } |
| - } |
| - .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 { |
| - // Checkboxes, not switches: the grouped form style renders |
| - // Toggle as an iOS-style switch, but a Mac Settings window |
| - // conventionally uses checkboxes — and the checkbox style |
| - // makes the whole label clickable (Paul, 2026-07-15). |
| - Toggle("Convert common Markdown formatting as you type", isOn: $formatAsYouType) |
| - .toggleStyle(.checkbox) |
| - } header: { |
| - Text("Composer") |
| - } footer: { |
| - // verbatim: the hint TEACHES the syntax — the markers must show. |
| - Text(verbatim: "e.g. **bold**, *italic* (or _italic_) and ~strikethrough~") |
| - } |
| - CLIToolSection() |
| + // Profile first — who you are, then your account (the sign-in |
| + // state + Sign Out rows), one combined section (Paul, 2026-07-15). |
| Section("Profile") { |
| Picker("Server", selection: $selectedInstanceID) { |
| ForEach(app.instances) { inst in |
| @@ -121,6 +87,41 @@ struct MacSettingsView: View { |
| if let dmPrivacyNote { |
| Text(dmPrivacyNote).font(.footnote).foregroundStyle(.secondary) |
| } |
| + HomeSignInSection(model: homeSignIn) |
| + } |
| + Section("Theme") { |
| + Picker("Chat theme", selection: $themeName) { |
| + ForEach(ChatTheme.presets, id: \.id) { preset in |
| + Text(preset.title).tag(preset.id) |
| + } |
| + } |
| + .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 { |
| + // Checkboxes, not switches: the grouped form style renders |
| + // Toggle as an iOS-style switch, but a Mac Settings window |
| + // conventionally uses checkboxes — and the checkbox style |
| + // makes the whole label clickable (Paul, 2026-07-15). |
| + Toggle("Convert common Markdown formatting as you type", isOn: $formatAsYouType) |
| + .toggleStyle(.checkbox) |
| + } header: { |
| + Text("Composer") |
| + } footer: { |
| + // verbatim: the hint TEACHES the syntax — the markers must show. |
| + Text(verbatim: "e.g. **bold**, *italic* (or _italic_) and ~strikethrough~") |
| } |
| if hasPaidLevels { |
| Section("Membership") { |