eleven / messenger Public

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>
Paul Campbell d6fe324a55e70e92f02a1a208b3087e3aecf98e1 parent e74324c
4 files changed, +69 −40
  • ios/LChatMac/CLIToolSection.swift +24 −1
  • ios/LChatMac/LChatMacApp.swift +2 −0
  • ios/LChatMac/MacHomeSignIn.swift +6 −3
  • ios/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") {