feat(ios): rust page background + dashboard switch-server button

Sweeps the rust ScarfDesign page background onto the screens that
were still rendering against the iOS default: Skills/Hub, Skills/Updates,
all three project sub-views, and Skill Detail. Lists adopt
.scrollContentBackground(.hidden) + ScarfColor.backgroundPrimary, with
.listRowBackground(ScarfColor.backgroundSecondary) on rows so the
Mac-style elevated-card density carries through.

Adds a "Switch server" toolbar button to Dashboard's top-right, threaded
through ScarfGoTabRoot from the connected-server host. One tap soft-
disconnects and returns to the server list — same code path the System
tab already exposes, just reachable without first navigating away from
Dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-25 15:30:39 +02:00
parent 295f2dfefc
commit 21e3cc9361
9 changed files with 69 additions and 2 deletions
+12 -1
View File
@@ -1,6 +1,7 @@
import SwiftUI
import ScarfCore
import ScarfIOS
import ScarfDesign
/// ScarfGo's primary navigation surface. v2.5 expands the original
/// 4-tab layout (Chat | Dashboard | Memory | More) to 5 primary tabs
@@ -46,7 +47,7 @@ struct ScarfGoTabRoot: View {
TabView(selection: $coordinator.selectedTab) {
// 1 Dashboard: stats + recent sessions.
NavigationStack {
DashboardView(config: config, key: key)
DashboardView(config: config, key: key, onSoftDisconnect: onSoftDisconnect)
}
.tabItem {
Label("Dashboard", systemImage: "gauge.with.needle")
@@ -142,11 +143,14 @@ private struct SystemTab: View {
List {
Section("Server") {
LabeledContent("Host", value: config.host)
.listRowBackground(ScarfColor.backgroundSecondary)
if let user = config.user {
LabeledContent("User", value: user)
.listRowBackground(ScarfColor.backgroundSecondary)
}
if let port = config.port {
LabeledContent("Port", value: String(port))
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
@@ -157,18 +161,21 @@ private struct SystemTab: View {
Label("Memory", systemImage: "brain.head.profile")
}
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
NavigationLink {
CronListView(config: config)
} label: {
Label("Cron jobs", systemImage: "clock.arrow.circlepath")
}
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
NavigationLink {
SettingsView(config: config)
} label: {
Label("Settings", systemImage: "gearshape.fill")
}
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
}
Section {
@@ -189,6 +196,7 @@ private struct SystemTab: View {
}
}
.disabled(isDisconnecting || isForgetting)
.listRowBackground(ScarfColor.backgroundSecondary)
} footer: {
Text("Closes the live connection. Your key and host details stay on this device; tapping the server from the list reconnects with no re-onboarding.")
.font(.caption)
@@ -209,12 +217,15 @@ private struct SystemTab: View {
}
}
.disabled(isForgetting || isDisconnecting)
.listRowBackground(ScarfColor.backgroundSecondary)
} footer: {
Text("Removes this server's SSH key and host info from the device. You'll need to add the public key back to `~/.ssh/authorized_keys` to reconnect.")
.font(.caption)
}
}
.scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle("System")
.navigationBarTitleDisplayMode(.inline)
.confirmationDialog(
+1
View File
@@ -53,6 +53,7 @@ struct ChatView: View {
}
composer
}
.background(ScarfColor.backgroundPrimary.ignoresSafeArea())
.navigationTitle("Chat")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
+30 -1
View File
@@ -10,11 +10,17 @@ import ScarfDesign
struct DashboardView: View {
let config: IOSServerConfig
let key: SSHKeyBundle
/// Soft-disconnect closure threaded down from the connected-server
/// host. Surfaced in the nav bar as a "Switch server" button so
/// users can hop back to the server list without first navigating
/// to the System tab.
let onSoftDisconnect: (@MainActor () async -> Void)?
@Environment(\.scarfGoCoordinator) private var coordinator
@State private var vm: IOSDashboardViewModel
@State private var selectedSection: Section = .overview
@State private var sessionProjectFilter: String? = nil
@State private var isDisconnecting = false
enum Section: Hashable { case overview, sessions }
@@ -24,10 +30,12 @@ struct DashboardView: View {
init(
config: IOSServerConfig,
key: SSHKeyBundle
key: SSHKeyBundle,
onSoftDisconnect: (@MainActor () async -> Void)? = nil
) {
self.config = config
self.key = key
self.onSoftDisconnect = onSoftDisconnect
let ctx = config.toServerContext(id: Self.contextID)
_vm = State(initialValue: IOSDashboardViewModel(context: ctx))
}
@@ -53,6 +61,27 @@ struct DashboardView: View {
.background(ScarfColor.backgroundPrimary.ignoresSafeArea())
.navigationTitle(config.displayName)
.navigationBarTitleDisplayMode(.large)
.toolbar {
if let onSoftDisconnect {
ToolbarItem(placement: .topBarTrailing) {
Button {
Task {
isDisconnecting = true
await onSoftDisconnect()
}
} label: {
if isDisconnecting {
ProgressView()
} else {
Label("Switch server", systemImage: "rectangle.portrait.and.arrow.right")
}
}
.disabled(isDisconnecting)
.accessibilityLabel("Switch server")
.accessibilityHint("Disconnects from this server and returns to the server list")
}
}
}
.refreshable { await vm.refresh() }
.overlay {
if vm.isLoading, vm.recentSessions.isEmpty {
@@ -67,6 +67,7 @@ struct ProjectDetailView: View {
Divider()
tabContent
}
.background(ScarfColor.backgroundPrimary)
.navigationTitle(project.name)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -23,6 +23,7 @@ struct ProjectSessionsView_iOS: View {
Divider()
content
}
.background(ScarfColor.backgroundPrimary)
.task(id: project.id) {
// Rebuild the VM when the project changes so stale state
// from a previously-selected project doesn't bleed
@@ -116,9 +117,12 @@ struct ProjectSessionsView_iOS: View {
}
.buttonStyle(.plain)
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
.scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
}
}
@@ -13,5 +13,7 @@ struct ProjectSiteView: View {
WebviewWidgetView(widget: widget, fullCanvas: true)
.padding(.horizontal, 8)
.padding(.vertical, 8)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(ScarfColor.backgroundPrimary)
}
}
@@ -15,6 +15,7 @@ struct HubBrowseView: View {
Divider()
content
}
.background(ScarfColor.backgroundPrimary)
}
@ViewBuilder
@@ -94,9 +95,12 @@ struct HubBrowseView: View {
vm.installHubSkill(hubSkill)
}
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
.scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
}
}
}
@@ -32,6 +32,7 @@ struct SkillDetailView: View {
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
}
.listRowBackground(ScarfColor.backgroundSecondary)
// v2.5 design-md prereq banner. Only when this is the
// design-md skill AND `which npx` came back missing.
@@ -53,6 +54,7 @@ struct SkillDetailView: View {
}
.padding(.vertical, 4)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
// v2.5 Spotify auth note. iOS doesn't run the OAuth flow
@@ -76,6 +78,7 @@ struct SkillDetailView: View {
}
.padding(.vertical, 4)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
// v2.5 SKILL.md frontmatter chip rows. Each section
@@ -86,16 +89,19 @@ struct SkillDetailView: View {
Section("Allowed tools") {
chipRow(tools)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
if let related = skill.relatedSkills, !related.isEmpty {
Section("Related skills") {
chipRow(related)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
if let deps = skill.dependencies, !deps.isEmpty {
Section("Dependencies") {
chipRow(deps)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
if !vm.missingConfig.isEmpty {
@@ -118,6 +124,7 @@ struct SkillDetailView: View {
}
.padding(.vertical, 6)
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
if !skill.files.isEmpty {
@@ -139,6 +146,7 @@ struct SkillDetailView: View {
}
.buttonStyle(.plain)
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
}
@@ -159,9 +167,12 @@ struct SkillDetailView: View {
.textSelection(.enabled)
}
}
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
.scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle(skill.name)
.navigationBarTitleDisplayMode(.inline)
.task {
@@ -15,6 +15,7 @@ struct UpdatesView: View {
Divider()
content
}
.background(ScarfColor.backgroundPrimary)
}
@ViewBuilder
@@ -80,9 +81,12 @@ struct UpdatesView: View {
}
.padding(.vertical, 4)
.scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
}
}
.scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
}
}
}