ui(macos): polish onboarding wording

This commit is contained in:
Peter Steinberger
2025-12-14 19:22:31 +00:00
parent 3d959c46d0
commit fb23717102
2 changed files with 53 additions and 52 deletions

View File

@@ -20,7 +20,7 @@ struct MasterDiscoveryInlineList: View {
} }
if self.discovery.masters.isEmpty { if self.discovery.masters.isEmpty {
Text("No masters found yet.") Text("No gateways found yet.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} else { } else {
@@ -86,7 +86,7 @@ struct MasterDiscoveryInlineList: View {
.fill(Color(NSColor.controlBackgroundColor))) .fill(Color(NSColor.controlBackgroundColor)))
} }
} }
.help("Click a discovered master to fill the SSH target.") .help("Click a discovered gateway to fill the SSH target.")
} }
private func suggestedSSHTarget(_ gateway: MasterDiscoveryModel.DiscoveredMaster) -> String? { private func suggestedSSHTarget(_ gateway: MasterDiscoveryModel.DiscoveredMaster) -> String? {

View File

@@ -212,8 +212,8 @@ struct OnboardingView: View {
Text("Welcome to Clawdis") Text("Welcome to Clawdis")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
"Your macOS menu bar companion for notifications, screenshots, and agent automation " + "Your macOS menu bar companion for notifications, screenshots, and agent automation. " +
"setup takes just a few minutes.") "Setup takes a few minutes.")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -255,8 +255,8 @@ struct OnboardingView: View {
Text("Where Clawdis runs") Text("Where Clawdis runs")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
"Clawdis has one primary Gateway (“master”) that runs continuously. " + "Clawdis uses a single Gateway (“master”) that stays running. Run it on this Mac, " +
"Connect locally or over SSH/Tailscale so the agent can work on any Mac.") "or connect to one on another Mac over SSH/Tailscale.")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -265,9 +265,9 @@ struct OnboardingView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
self.onboardingCard(spacing: 12, padding: 14) { self.onboardingCard(spacing: 12, padding: 14) {
Picker("Clawdis runs", selection: self.$state.connectionMode) { Picker("Gateway runs", selection: self.$state.connectionMode) {
Text("Local (this Mac)").tag(AppState.ConnectionMode.local) Text("This Mac").tag(AppState.ConnectionMode.local)
Text("Remote over SSH").tag(AppState.ConnectionMode.remote) Text("Remote (SSH)").tag(AppState.ConnectionMode.remote)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
.frame(width: 360) .frame(width: 360)
@@ -282,7 +282,7 @@ struct OnboardingView: View {
Text("SSH target") Text("SSH target")
.font(.callout.weight(.semibold)) .font(.callout.weight(.semibold))
.frame(width: labelWidth, alignment: .leading) .frame(width: labelWidth, alignment: .leading)
TextField("user@host[:22]", text: self.$state.remoteTarget) TextField("user@host[:port]", text: self.$state.remoteTarget)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
.frame(width: fieldWidth) .frame(width: fieldWidth)
} }
@@ -312,7 +312,7 @@ struct OnboardingView: View {
.padding(.top, 4) .padding(.top, 4)
} }
Text("Tip: enable Tailscale so your remote Clawdis stays reachable.") Text("Tip: keep Tailscale enabled so your gateway stays reachable.")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(1) .lineLimit(1)
@@ -345,7 +345,7 @@ struct OnboardingView: View {
Circle() Circle()
.fill(self.anthropicAuthConnected ? Color.green : Color.orange) .fill(self.anthropicAuthConnected ? Color.green : Color.orange)
.frame(width: 10, height: 10) .frame(width: 10, height: 10)
Text(self.anthropicAuthConnected ? "Anthropic OAuth connected" : "Not connected yet") Text(self.anthropicAuthConnected ? "Claude connected (OAuth)" : "Not connected yet")
.font(.headline) .font(.headline)
Spacer() Spacer()
} }
@@ -358,7 +358,7 @@ struct OnboardingView: View {
} }
Text( Text(
"This writes Pi-compatible credentials to `~/.pi/agent/oauth.json` (owner-only). " + "This lets Pi use Claude immediately. Credentials are stored at `~/.pi/agent/oauth.json` (owner-only). " +
"You can redo this anytime.") "You can redo this anytime.")
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
@@ -393,14 +393,14 @@ struct OnboardingView: View {
if self.anthropicAuthBusy { if self.anthropicAuthBusy {
ProgressView() ProgressView()
} else { } else {
Text("Open Claude login (OAuth)") Text("Open Claude sign-in (OAuth)")
} }
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.disabled(self.anthropicAuthBusy) .disabled(self.anthropicAuthBusy)
Button("Skip for now") { Button("Skip for now") {
self.anthropicAuthStatus = "Skipped. The agent may not respond until you authenticate." self.anthropicAuthStatus = "Skipped. Pi may not respond until you connect Claude."
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.disabled(self.anthropicAuthBusy) .disabled(self.anthropicAuthBusy)
@@ -408,12 +408,12 @@ struct OnboardingView: View {
if self.anthropicAuthPKCE != nil { if self.anthropicAuthPKCE != nil {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Text("Paste `code#state`") Text("Paste the `code#state` value")
.font(.headline) .font(.headline)
TextField("code#state", text: self.$anthropicAuthCode) TextField("code#state", text: self.$anthropicAuthCode)
.textFieldStyle(.roundedBorder) .textFieldStyle(.roundedBorder)
Button("Finish connection") { Button("Connect") {
Task { await self.finishAnthropicOAuth() } Task { await self.finishAnthropicOAuth() }
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
@@ -427,8 +427,8 @@ struct OnboardingView: View {
Text("API key (advanced)") Text("API key (advanced)")
.font(.headline) .font(.headline)
Text( Text(
"You can also use an Anthropic API key, but this is instructions-only for now " + "You can also use an Anthropic API key, but this UI is instructions-only for now " +
"(GUI-launched processes dont automatically inherit your shell env vars).") "(GUI apps dont automatically inherit your shell env vars).")
.font(.subheadline) .font(.subheadline)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -456,7 +456,7 @@ struct OnboardingView: View {
self.anthropicAuthPKCE = pkce self.anthropicAuthPKCE = pkce
let url = AnthropicOAuth.buildAuthorizeURL(pkce: pkce) let url = AnthropicOAuth.buildAuthorizeURL(pkce: pkce)
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
self.anthropicAuthStatus = "Opened browser. After approving, paste the `code#state` here." self.anthropicAuthStatus = "Browser opened. After approving, paste the `code#state` value here."
} catch { } catch {
self.anthropicAuthStatus = "Failed to start OAuth: \(error.localizedDescription)" self.anthropicAuthStatus = "Failed to start OAuth: \(error.localizedDescription)"
} }
@@ -478,7 +478,7 @@ struct OnboardingView: View {
let creds = try await AnthropicOAuth.exchangeCode(code: code, state: state, verifier: pkce.verifier) let creds = try await AnthropicOAuth.exchangeCode(code: code, state: state, verifier: pkce.verifier)
try PiOAuthStore.saveAnthropicOAuth(creds) try PiOAuthStore.saveAnthropicOAuth(creds)
self.refreshAnthropicOAuthStatus() self.refreshAnthropicOAuthStatus()
self.anthropicAuthStatus = "Connected. Pi can now use Claude via Anthropic OAuth." self.anthropicAuthStatus = "Connected. Pi can now use Claude."
} catch { } catch {
self.anthropicAuthStatus = "OAuth failed: \(error.localizedDescription)" self.anthropicAuthStatus = "OAuth failed: \(error.localizedDescription)"
} }
@@ -494,7 +494,7 @@ struct OnboardingView: View {
self.onboardingPage { self.onboardingPage {
Text("Identity") Text("Identity")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text("Name your agent, pick a theme, and well suggest an emoji.") Text("Name your agent, pick a vibe, and choose an emoji.")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -568,8 +568,7 @@ struct OnboardingView: View {
Text( Text(
"This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` " + "This writes your identity to `~/.clawdis/clawdis.json` and into `AGENTS.md` " +
"inside the workspace. " + "inside the workspace. " +
"Treat that workspace as the agents “memory” and consider making it a (private) git " + "Treat that workspace as the agents “memory” and consider making it a private git repo.")
"repo.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
@@ -589,10 +588,8 @@ struct OnboardingView: View {
Text("Install the gateway") Text("Install the gateway")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
""" "The Gateway is the WebSocket service that keeps Clawdis connected. " +
Clawdis now runs the WebSocket gateway from the global "clawdis" package. "Well install/update the `clawdis` npm package and verify Node is available.")
Install/update it here and well check Node for you.
""")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -636,7 +633,7 @@ struct OnboardingView: View {
if self.gatewayInstalling { if self.gatewayInstalling {
ProgressView() ProgressView()
} else { } else {
Text("Install / Update gateway") Text("Install or update gateway")
} }
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
@@ -654,8 +651,8 @@ struct OnboardingView: View {
.lineLimit(2) .lineLimit(2)
} else { } else {
Text( Text(
"Uses \"npm install -g clawdis@<version>\" on your PATH. " + "Runs `npm install -g clawdis@<version>` on your PATH. " +
"We keep the gateway on port 18789.") "The gateway listens on port 18789.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(2) .lineLimit(2)
@@ -681,7 +678,7 @@ struct OnboardingView: View {
self.onboardingPage { self.onboardingPage {
Text("Grant permissions") Text("Grant permissions")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text("Approve these once and the helper CLI reuses the same grants.") Text("These macOS permissions let Clawdis automate apps and capture context on this Mac.")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -718,7 +715,7 @@ struct OnboardingView: View {
self.onboardingPage { self.onboardingPage {
Text("Install the helper CLI") Text("Install the helper CLI")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text("Link `clawdis-mac` so scripts and the agent can talk to this app.") Text("Optional, but recommended: link `clawdis-mac` so scripts can talk to this app.")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -773,10 +770,8 @@ struct OnboardingView: View {
Text("Agent workspace") Text("Agent workspace")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
""" "Clawdis runs the agent from a dedicated workspace so it can load `AGENTS.md` " +
Clawdis runs the agent from a dedicated workspace so it can load AGENTS.md "and write files there without mixing into your other projects.")
and write files without touching your other folders.
""")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -844,7 +839,7 @@ struct OnboardingView: View {
} else { } else {
Text( Text(
"Tip: edit AGENTS.md in this folder to shape the assistants behavior. " + "Tip: edit AGENTS.md in this folder to shape the assistants behavior. " +
"For backup, make the workspace a (private) git repo so Clawds “memory” is versioned.") "For backup, make the workspace a private git repo so your agents “memory” is versioned.")
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(2) .lineLimit(2)
@@ -856,13 +851,11 @@ struct OnboardingView: View {
private func whatsappPage() -> some View { private func whatsappPage() -> some View {
self.onboardingPage { self.onboardingPage {
Text("Link WhatsApp or Telegram") Text("Connect WhatsApp or Telegram")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
Text( Text(
""" "Optional: WhatsApp uses a QR login for your personal account. Telegram uses a bot token. " +
WhatsApp uses a QR login for your personal account. Telegram uses a bot token. "Configure them on the machine where the gateway runs.")
Either (or both) is fine; configure them where the gateway runs.
""")
.font(.body) .font(.body)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
@@ -872,7 +865,7 @@ struct OnboardingView: View {
self.onboardingCard { self.onboardingCard {
self.featureRow( self.featureRow(
title: "Open a terminal", title: "Open a terminal",
subtitle: "Use the same host selected above. If remote, SSH in first.", subtitle: "Use the machine where the gateway runs. If remote, SSH in first.",
systemImage: "terminal") systemImage: "terminal")
Text("WhatsApp") Text("WhatsApp")
@@ -881,7 +874,7 @@ struct OnboardingView: View {
title: "Run `clawdis login --verbose`", title: "Run `clawdis login --verbose`",
subtitle: """ subtitle: """
Scan the QR code with WhatsApp on your phone. Scan the QR code with WhatsApp on your phone.
We only use your personal session; no cloud gateway involved. This links your personal session; no cloud gateway involved.
""", """,
systemImage: "qrcode.viewfinder") systemImage: "qrcode.viewfinder")
self.featureRow( self.featureRow(
@@ -900,7 +893,7 @@ struct OnboardingView: View {
self.featureRow( self.featureRow(
title: "Set `TELEGRAM_BOT_TOKEN`", title: "Set `TELEGRAM_BOT_TOKEN`",
subtitle: """ subtitle: """
Create a bot with @BotFather and set the token as an env var Create a bot with @BotFather and set the token as an env var,
(or `telegram.botToken` in `~/.clawdis/clawdis.json`). (or `telegram.botToken` in `~/.clawdis/clawdis.json`).
""", """,
systemImage: "key") systemImage: "key")
@@ -917,13 +910,21 @@ struct OnboardingView: View {
Text("All set") Text("All set")
.font(.largeTitle.weight(.semibold)) .font(.largeTitle.weight(.semibold))
self.onboardingCard { self.onboardingCard {
if self.state.connectionMode == .remote {
self.featureRow(
title: "Remote gateway checklist",
subtitle: """
On your gateway host: install/update the `clawdis` package and make sure Pi has credentials
(typically `~/.pi/agent/oauth.json`). Then connect again if needed.
""",
systemImage: "network")
Divider()
.padding(.vertical, 6)
}
self.featureRow( self.featureRow(
title: "Run the dashboard", title: "Open the menu bar panel",
subtitle: """ subtitle: "Click the Clawdis menu bar icon for quick chat and status.",
Use the CLI helper from your scripts, and reopen onboarding from Settings systemImage: "bubble.left.and.bubble.right")
if you add a new user.
""",
systemImage: "checkmark.seal")
self.featureRow( self.featureRow(
title: "Try Voice Wake", title: "Try Voice Wake",
subtitle: "Enable Voice Wake in Settings for hands-free commands with a live transcript overlay.", subtitle: "Enable Voice Wake in Settings for hands-free commands with a live transcript overlay.",