mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
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:
@@ -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(
|
||||
|
||||
@@ -53,6 +53,7 @@ struct ChatView: View {
|
||||
}
|
||||
composer
|
||||
}
|
||||
.background(ScarfColor.backgroundPrimary.ignoresSafeArea())
|
||||
.navigationTitle("Chat")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user