From 23dd8becb9b725cf24a366472d0c074f1965807f Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Sat, 25 Apr 2026 14:39:13 +0200 Subject: [PATCH] polish: tokenize remaining sheets, page headers, and widgets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — Page headers for the 9 non-mockup feature views: Skills, Gateway, Platforms, Personalities, QuickCommands, CredentialPools, Plugins, Webhooks, Profiles. Each now ships a ScarfPageHeader with title + subtitle + tokenized trailing actions (ScarfPrimary / Secondary / Ghost buttons), wrapped in .fixedSize so labels can't wrap at narrow widths. Outer .background(ScarfColor.backgroundPrimary). Phase 2 — Modal sheets: ModelPickerSheet, NousSignInSheet, RenameProjectSheet, MoveToFolderSheet, the five Templates sheets (TemplateInstall / TemplateConfig / TemplateExport / TemplateUninstall / ConfigEditorSheet), three MCPServer sheets (AddCustom / Editor / PresetPicker), AddServerSheet, ManageServersView, MissingServerView. .font(.headline) -> .scarfStyle(.headline); .buttonStyle(.borderedProminent) -> ScarfPrimaryButton(); raw text fields where touched -> ScarfTextField; cancel buttons -> ScarfGhostButton. Phase 3 — All 12 platform setup views (Discord / Email / Feishu / HomeAssistant / IMessage / Matrix / Mattermost / Signal / Slack / Telegram / Webhook / WhatsApp). Connect buttons swapped to ScarfPrimaryButton. Phase 4 — All 7 project dashboard widgets (Chart / List / Progress / Stat / Table / Text / Webview). .font(.caption) -> .scarfStyle(.caption); .background(.quaternary.opacity(0.5)) -> ScarfColor.backgroundSecondary; RoundedRectangle(cornerRadius: 8) -> ScarfRadius.lg. Phase 5 — Project sub-views: ProjectSessionsView, ProjectsSidebar, ProjectSlashCommandsView. Same token sweep. Phase 6 — Common chrome: - LoadingOverlay: .font(.callout/caption) -> .scarfStyle; secondary foreground -> ScarfColor.foregroundMuted; window-background -> ScarfColor.backgroundPrimary. - ServerSwitcherToolbar: status dot + label tokenized. - ConnectionStatusPill: status colors -> ScarfColor.success/warning/ danger; error sheet header -> ScarfPrimaryButton retry. Build green on both Mac (scarf) and iOS (scarf mobile) schemes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Features/Common/LoadingOverlay.swift | 15 ++--- .../Views/CredentialPoolsView.swift | 63 +++++++++++-------- .../Features/Gateway/Views/GatewayView.swift | 22 ++++--- .../Views/MCPServerAddCustomView.swift | 5 +- .../Views/MCPServerEditorView.swift | 5 +- .../Views/MCPServerPresetPickerView.swift | 5 +- .../Views/PersonalitiesView.swift | 51 ++++++++------- .../PlatformSetup/DiscordSetupView.swift | 3 +- .../Views/PlatformSetup/EmailSetupView.swift | 3 +- .../Views/PlatformSetup/FeishuSetupView.swift | 3 +- .../HomeAssistantSetupView.swift | 3 +- .../PlatformSetup/IMessageSetupView.swift | 3 +- .../Views/PlatformSetup/MatrixSetupView.swift | 3 +- .../PlatformSetup/MattermostSetupView.swift | 3 +- .../Views/PlatformSetup/SignalSetupView.swift | 5 +- .../Views/PlatformSetup/SlackSetupView.swift | 3 +- .../PlatformSetup/TelegramSetupView.swift | 3 +- .../PlatformSetup/WebhookSetupView.swift | 3 +- .../PlatformSetup/WhatsAppSetupView.swift | 5 +- .../Platforms/Views/PlatformsView.swift | 18 ++++-- .../Features/Plugins/Views/PluginsView.swift | 40 ++++++------ .../Profiles/Views/ProfilesView.swift | 18 ++++-- .../Projects/Views/MoveToFolderSheet.swift | 19 +++--- .../Projects/Views/ProjectSessionsView.swift | 7 ++- .../Views/ProjectSlashCommandsView.swift | 23 +++---- .../Projects/Views/ProjectsSidebar.swift | 13 ++-- .../Projects/Views/RenameProjectSheet.swift | 23 ++++--- .../Views/Widgets/ChartWidgetView.swift | 7 ++- .../Views/Widgets/ListWidgetView.swift | 9 +-- .../Views/Widgets/ProgressWidgetView.swift | 7 ++- .../Views/Widgets/StatWidgetView.swift | 9 +-- .../Views/Widgets/TableWidgetView.swift | 7 ++- .../Views/Widgets/TextWidgetView.swift | 7 ++- .../Views/Widgets/WebviewWidgetView.swift | 11 ++-- .../Views/QuickCommandsView.swift | 63 +++++++++++-------- .../Servers/Views/AddServerSheet.swift | 3 +- .../Servers/Views/ConnectionStatusPill.swift | 18 +++--- .../Servers/Views/ManageServersView.swift | 5 +- .../Servers/Views/MissingServerView.swift | 3 +- .../Servers/Views/ServerSwitcherToolbar.swift | 9 ++- .../Views/Components/ModelPickerSheet.swift | 7 ++- .../Views/Components/NousSignInSheet.swift | 11 ++-- .../Features/Skills/Views/SkillsView.swift | 8 ++- .../Templates/Views/ConfigEditorSheet.swift | 3 +- .../Templates/Views/TemplateConfigSheet.swift | 7 ++- .../Templates/Views/TemplateExportSheet.swift | 15 ++--- .../Views/TemplateInstallSheet.swift | 11 ++-- .../Views/TemplateUninstallSheet.swift | 11 ++-- .../Webhooks/Views/WebhooksView.swift | 40 ++++++------ 49 files changed, 373 insertions(+), 265 deletions(-) diff --git a/scarf/scarf/Features/Common/LoadingOverlay.swift b/scarf/scarf/Features/Common/LoadingOverlay.swift index 8405065..befebeb 100644 --- a/scarf/scarf/Features/Common/LoadingOverlay.swift +++ b/scarf/scarf/Features/Common/LoadingOverlay.swift @@ -1,4 +1,5 @@ import SwiftUI +import ScarfDesign /// Translucent loading overlay used by feature views while their VM's /// `load()` runs in the background. Shows a centered ProgressView with @@ -27,15 +28,15 @@ struct LoadingOverlay: ViewModifier { if isEmpty { // Full cover: empty state. User has no data to look at, // so own the whole pane with the spinner. - VStack(spacing: 12) { + VStack(spacing: ScarfSpace.s3) { ProgressView() .controlSize(.large) Text(label) - .font(.callout) - .foregroundStyle(.secondary) + .scarfStyle(.callout) + .foregroundStyle(ScarfColor.foregroundMuted) } .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(NSColor.windowBackgroundColor)) + .background(ScarfColor.backgroundPrimary) } else { // Stale-content refresh: top-trailing pill so the // user sees data is being refreshed without losing @@ -47,13 +48,13 @@ struct LoadingOverlay: ViewModifier { ProgressView() .controlSize(.small) Text(label) - .font(.caption) - .foregroundStyle(.secondary) + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) } .padding(.horizontal, 10) .padding(.vertical, 5) .background(.thinMaterial, in: Capsule()) - .padding(8) + .padding(ScarfSpace.s2) } Spacer() } diff --git a/scarf/scarf/Features/CredentialPools/Views/CredentialPoolsView.swift b/scarf/scarf/Features/CredentialPools/Views/CredentialPoolsView.swift index 953e41c..8b3c06b 100644 --- a/scarf/scarf/Features/CredentialPools/Views/CredentialPoolsView.swift +++ b/scarf/scarf/Features/CredentialPools/Views/CredentialPoolsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct CredentialPoolsView: View { @State private var viewModel: CredentialPoolsViewModel @@ -12,23 +13,26 @@ struct CredentialPoolsView: View { var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - header - safetyNotice - if viewModel.isLoading { - ProgressView().padding() - } else if viewModel.pools.isEmpty { - emptyState - } else { - ForEach(viewModel.pools) { pool in - poolSection(pool) + VStack(spacing: 0) { + header + ScrollView { + VStack(alignment: .leading, spacing: 16) { + safetyNotice + if viewModel.isLoading { + ProgressView().padding() + } else if viewModel.pools.isEmpty { + emptyState + } else { + ForEach(viewModel.pools) { pool in + poolSection(pool) + } } } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Credential Pools") .loadingOverlay( viewModel.isLoading, @@ -58,21 +62,26 @@ struct CredentialPoolsView: View { } private var header: some View { - HStack { - if let msg = viewModel.message { - Label(msg, systemImage: "info.circle.fill") - .font(.caption) - .foregroundStyle(.secondary) + ScarfPageHeader( + "Credential Pools", + subtitle: "Shared OAuth + token pools rotated across runs." + ) { + HStack(spacing: ScarfSpace.s2) { + if let msg = viewModel.message { + Label(msg, systemImage: "info.circle.fill") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) + } + Button("Reload") { viewModel.load() } + .buttonStyle(ScarfGhostButton()) + Button { + showAddSheet = true + } label: { + Label("Add Credential", systemImage: "plus") + } + .buttonStyle(ScarfPrimaryButton()) } - Spacer() - Button { - showAddSheet = true - } label: { - Label("Add Credential", systemImage: "plus") - } - .controlSize(.small) - Button("Reload") { viewModel.load() } - .controlSize(.small) + .fixedSize(horizontal: true, vertical: false) } } diff --git a/scarf/scarf/Features/Gateway/Views/GatewayView.swift b/scarf/scarf/Features/Gateway/Views/GatewayView.swift index 0833a8e..74b4296 100644 --- a/scarf/scarf/Features/Gateway/Views/GatewayView.swift +++ b/scarf/scarf/Features/Gateway/Views/GatewayView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct GatewayView: View { @State private var viewModel: GatewayViewModel @@ -11,15 +12,22 @@ struct GatewayView: View { var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 24) { - serviceSection - platformsSection - pairingSection + VStack(spacing: 0) { + ScarfPageHeader( + "Messaging Gateway", + subtitle: "Outbound channel bridge — Discord, Telegram, Slack, etc." + ) + ScrollView { + VStack(alignment: .leading, spacing: 24) { + serviceSection + platformsSection + pairingSection + } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Messaging Gateway") .onAppear { viewModel.load() } .onChange(of: fileWatcher.lastChangeDate) { viewModel.load() } diff --git a/scarf/scarf/Features/MCPServers/Views/MCPServerAddCustomView.swift b/scarf/scarf/Features/MCPServers/Views/MCPServerAddCustomView.swift index e51b70d..88da9cc 100644 --- a/scarf/scarf/Features/MCPServers/Views/MCPServerAddCustomView.swift +++ b/scarf/scarf/Features/MCPServers/Views/MCPServerAddCustomView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct MCPServerAddCustomView: View { let viewModel: MCPServersViewModel @@ -16,13 +17,13 @@ struct MCPServerAddCustomView: View { VStack(spacing: 0) { HStack { Text("Add Custom MCP Server") - .font(.headline) + .scarfStyle(.headline) Spacer() Button("Cancel") { dismiss() } Button("Add") { submit() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .disabled(!canSubmit) } .padding() diff --git a/scarf/scarf/Features/MCPServers/Views/MCPServerEditorView.swift b/scarf/scarf/Features/MCPServers/Views/MCPServerEditorView.swift index 58a38f5..4062c9f 100644 --- a/scarf/scarf/Features/MCPServers/Views/MCPServerEditorView.swift +++ b/scarf/scarf/Features/MCPServers/Views/MCPServerEditorView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct MCPServerEditorView: View { @State var viewModel: MCPServerEditorViewModel @@ -11,7 +12,7 @@ struct MCPServerEditorView: View { HStack { VStack(alignment: .leading, spacing: 2) { Text("Edit \(viewModel.server.name)") - .font(.headline) + .scarfStyle(.headline) Text(viewModel.server.transport.displayName) .font(.caption) .foregroundStyle(.secondary) @@ -30,7 +31,7 @@ struct MCPServerEditorView: View { Text("Save") } } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .keyboardShortcut(.defaultAction) .disabled(viewModel.isSaving) } diff --git a/scarf/scarf/Features/MCPServers/Views/MCPServerPresetPickerView.swift b/scarf/scarf/Features/MCPServers/Views/MCPServerPresetPickerView.swift index 33bbbad..ac5eb6a 100644 --- a/scarf/scarf/Features/MCPServers/Views/MCPServerPresetPickerView.swift +++ b/scarf/scarf/Features/MCPServers/Views/MCPServerPresetPickerView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct MCPServerPresetPickerView: View { let viewModel: MCPServersViewModel @@ -35,7 +36,7 @@ struct MCPServerPresetPickerView: View { } VStack(alignment: .leading, spacing: 2) { (selectedPreset.map { Text(verbatim: $0.displayName) } ?? Text("Add from Preset")) - .font(.headline) + .scarfStyle(.headline) (selectedPreset.map { Text(verbatim: $0.description) } ?? Text("Pick an MCP server to add.")) .font(.caption) .foregroundStyle(.secondary) @@ -132,7 +133,7 @@ struct MCPServerPresetPickerView: View { Button("Add Server") { submit(preset: preset) } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .disabled(!canSubmit(preset: preset)) } } diff --git a/scarf/scarf/Features/Personalities/Views/PersonalitiesView.swift b/scarf/scarf/Features/Personalities/Views/PersonalitiesView.swift index fe74334..0d432ab 100644 --- a/scarf/scarf/Features/Personalities/Views/PersonalitiesView.swift +++ b/scarf/scarf/Features/Personalities/Views/PersonalitiesView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct PersonalitiesView: View { @State private var viewModel: PersonalitiesViewModel @@ -12,16 +13,35 @@ struct PersonalitiesView: View { var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 20) { - header - activeSection - listSection - soulSection + VStack(spacing: 0) { + ScarfPageHeader( + "Personalities", + subtitle: "Per-personality model + prompt overrides defined in config.yaml." + ) { + HStack(spacing: ScarfSpace.s2) { + if let msg = viewModel.message { + Label(msg, systemImage: "checkmark.circle.fill") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.success) + } + Button("Edit config.yaml") { viewModel.openConfigInEditor() } + .buttonStyle(ScarfGhostButton()) + Button("Reload") { viewModel.load(); soulDraft = viewModel.soulMarkdown } + .buttonStyle(ScarfSecondaryButton()) + } + .fixedSize(horizontal: true, vertical: false) + } + ScrollView { + VStack(alignment: .leading, spacing: 20) { + activeSection + listSection + soulSection + } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Personalities") .onAppear { viewModel.load() @@ -29,21 +49,6 @@ struct PersonalitiesView: View { } } - private var header: some View { - HStack { - if let msg = viewModel.message { - Label(msg, systemImage: "checkmark.circle.fill") - .font(.caption) - .foregroundStyle(.green) - } - Spacer() - Button("Edit config.yaml") { viewModel.openConfigInEditor() } - .controlSize(.small) - Button("Reload") { viewModel.load(); soulDraft = viewModel.soulMarkdown } - .controlSize(.small) - } - } - private var activeSection: some View { SettingsSection(title: "Active Personality", icon: "theatermasks.fill") { if viewModel.personalities.isEmpty { diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/DiscordSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/DiscordSetupView.swift index fd386f6..e27ceab 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/DiscordSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/DiscordSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct DiscordSetupView: View { @State private var viewModel: DiscordSetupViewModel @@ -57,7 +58,7 @@ struct DiscordSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/EmailSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/EmailSetupView.swift index 73a3856..fff6347 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/EmailSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/EmailSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct EmailSetupView: View { @State private var viewModel: EmailSetupViewModel @@ -75,7 +76,7 @@ struct EmailSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/FeishuSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/FeishuSetupView.swift index dc97058..034ee44 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/FeishuSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/FeishuSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct FeishuSetupView: View { @State private var viewModel: FeishuSetupViewModel @@ -52,7 +53,7 @@ struct FeishuSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/HomeAssistantSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/HomeAssistantSetupView.swift index 05cd2ab..1d3400a 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/HomeAssistantSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/HomeAssistantSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct HomeAssistantSetupView: View { @State private var viewModel: HomeAssistantSetupViewModel @@ -72,7 +73,7 @@ struct HomeAssistantSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/IMessageSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/IMessageSetupView.swift index 0560632..09f8bba 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/IMessageSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/IMessageSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct IMessageSetupView: View { @State private var viewModel: IMessageSetupViewModel @@ -61,7 +62,7 @@ struct IMessageSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/MatrixSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/MatrixSetupView.swift index 273eee7..d24d629 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/MatrixSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/MatrixSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct MatrixSetupView: View { @State private var viewModel: MatrixSetupViewModel @@ -69,7 +70,7 @@ struct MatrixSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/MattermostSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/MattermostSetupView.swift index cd97c27..e6b6a66 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/MattermostSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/MattermostSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct MattermostSetupView: View { @State private var viewModel: MattermostSetupViewModel @@ -52,7 +53,7 @@ struct MattermostSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/SignalSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/SignalSetupView.swift index 5f1718f..76d0982 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/SignalSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/SignalSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct SignalSetupView: View { @State private var viewModel: SignalSetupViewModel @@ -73,7 +74,7 @@ struct SignalSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } @@ -87,7 +88,7 @@ struct SignalSetupView: View { case .none: Button("Link Device") { viewModel.startLink() }.controlSize(.small) .disabled(!viewModel.signalCLIInstalled) - Button("Start Daemon") { viewModel.startDaemon() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Start Daemon") { viewModel.startDaemon() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) .disabled(!viewModel.signalCLIInstalled || viewModel.account.isEmpty) case .link: Text("Linking…").font(.caption).foregroundStyle(.secondary) diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/SlackSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/SlackSetupView.swift index 528e707..032a572 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/SlackSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/SlackSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct SlackSetupView: View { @State private var viewModel: SlackSetupViewModel @@ -56,7 +57,7 @@ struct SlackSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/TelegramSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/TelegramSetupView.swift index 04011b7..ae0c168 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/TelegramSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/TelegramSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct TelegramSetupView: View { @State private var viewModel: TelegramSetupViewModel @@ -57,7 +58,7 @@ struct TelegramSetupView: View { Button("Reload") { viewModel.load() } .controlSize(.small) Button("Save") { viewModel.save() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .controlSize(.small) } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/WebhookSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/WebhookSetupView.swift index 9b3a3f0..4833b58 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/WebhookSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/WebhookSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct WebhookSetupView: View { @State private var viewModel: WebhookSetupViewModel @@ -53,7 +54,7 @@ struct WebhookSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformSetup/WhatsAppSetupView.swift b/scarf/scarf/Features/Platforms/Views/PlatformSetup/WhatsAppSetupView.swift index 8e8797b..43a69f4 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformSetup/WhatsAppSetupView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformSetup/WhatsAppSetupView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct WhatsAppSetupView: View { @State private var viewModel: WhatsAppSetupViewModel @@ -56,7 +57,7 @@ struct WhatsAppSetupView: View { } Spacer() Button("Reload") { viewModel.load() }.controlSize(.small) - Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small) + Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small) } } @@ -71,7 +72,7 @@ struct WhatsAppSetupView: View { .controlSize(.small) } else { Button("Start Pairing") { viewModel.startPairing() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .controlSize(.small) } } diff --git a/scarf/scarf/Features/Platforms/Views/PlatformsView.swift b/scarf/scarf/Features/Platforms/Views/PlatformsView.swift index d9330c8..928270b 100644 --- a/scarf/scarf/Features/Platforms/Views/PlatformsView.swift +++ b/scarf/scarf/Features/Platforms/Views/PlatformsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct PlatformsView: View { @State private var viewModel: PlatformsViewModel @@ -13,12 +14,19 @@ struct PlatformsView: View { // HSplitView (not nested NavigationSplitView) because ContentView already // hosts the outer NavigationSplitView — nesting them breaks layout on macOS. var body: some View { - HSplitView { - platformList - .frame(minWidth: 220, idealWidth: 240, maxWidth: 300) - detail - .frame(minWidth: 480) + VStack(spacing: 0) { + ScarfPageHeader( + "Platforms", + subtitle: "Inbound channels the agent listens on. Set up tokens per platform." + ) + HSplitView { + platformList + .frame(minWidth: 220, idealWidth: 240, maxWidth: 300) + detail + .frame(minWidth: 480) + } } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Platforms") .onAppear { viewModel.load() } // Re-read config.yaml / .env / gateway_state.json when any of them diff --git a/scarf/scarf/Features/Plugins/Views/PluginsView.swift b/scarf/scarf/Features/Plugins/Views/PluginsView.swift index 097e4ff..0181855 100644 --- a/scarf/scarf/Features/Plugins/Views/PluginsView.swift +++ b/scarf/scarf/Features/Plugins/Views/PluginsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct PluginsView: View { @State private var viewModel: PluginsViewModel @@ -15,7 +16,6 @@ struct PluginsView: View { var body: some View { VStack(spacing: 0) { header - Divider() if viewModel.isLoading && viewModel.plugins.isEmpty { ProgressView().padding() } else if viewModel.plugins.isEmpty { @@ -24,6 +24,7 @@ struct PluginsView: View { list } } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Plugins") .loadingOverlay( viewModel.isLoading, @@ -45,25 +46,28 @@ struct PluginsView: View { } private var header: some View { - HStack { - if let msg = viewModel.message { - Label(msg, systemImage: "info.circle.fill") - .font(.caption) - .foregroundStyle(.green) + ScarfPageHeader( + "Plugins", + subtitle: "Hermes plugins discovered from `~/.hermes/plugins/`." + ) { + HStack(spacing: ScarfSpace.s2) { + if let msg = viewModel.message { + Label(msg, systemImage: "info.circle.fill") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.success) + } + Button("Reload") { viewModel.load() } + .buttonStyle(ScarfGhostButton()) + Button { + installIdentifier = "" + showInstall = true + } label: { + Label("Install", systemImage: "plus") + } + .buttonStyle(ScarfPrimaryButton()) } - Spacer() - Button { - installIdentifier = "" - showInstall = true - } label: { - Label("Install", systemImage: "plus") - } - .controlSize(.small) - Button("Reload") { viewModel.load() } - .controlSize(.small) + .fixedSize(horizontal: true, vertical: false) } - .padding(.horizontal) - .padding(.vertical, 8) } private var emptyState: some View { diff --git a/scarf/scarf/Features/Profiles/Views/ProfilesView.swift b/scarf/scarf/Features/Profiles/Views/ProfilesView.swift index c1134d2..2d9478c 100644 --- a/scarf/scarf/Features/Profiles/Views/ProfilesView.swift +++ b/scarf/scarf/Features/Profiles/Views/ProfilesView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign import AppKit import UniformTypeIdentifiers @@ -21,12 +22,19 @@ struct ProfilesView: View { @State private var pendingDelete: HermesProfile? var body: some View { - HSplitView { - listSection - .frame(minWidth: 260, idealWidth: 300) - detailSection - .frame(minWidth: 400) + VStack(spacing: 0) { + ScarfPageHeader( + "Profiles", + subtitle: "Named config bundles you can swap between." + ) + HSplitView { + listSection + .frame(minWidth: 260, idealWidth: 300) + detailSection + .frame(minWidth: 400) + } } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Profiles") .onAppear { viewModel.load() } .sheet(isPresented: $showCreate) { createSheet } diff --git a/scarf/scarf/Features/Projects/Views/MoveToFolderSheet.swift b/scarf/scarf/Features/Projects/Views/MoveToFolderSheet.swift index cdaff79..ef46eac 100644 --- a/scarf/scarf/Features/Projects/Views/MoveToFolderSheet.swift +++ b/scarf/scarf/Features/Projects/Views/MoveToFolderSheet.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Sheet for assigning a project to a folder in the sidebar. Folders /// are implicit — they exist because at least one project references @@ -55,11 +56,13 @@ struct MoveToFolderSheet: View { } var body: some View { - VStack(alignment: .leading, spacing: 12) { - Text("Move \"\(project.name)\" to folder").font(.headline) + VStack(alignment: .leading, spacing: ScarfSpace.s3) { + Text("Move \"\(project.name)\" to folder") + .scarfStyle(.headline) + .foregroundStyle(ScarfColor.foregroundPrimary) Text("Folders only affect how projects are grouped in Scarf's sidebar. Nothing on disk changes.") - .font(.caption) - .foregroundStyle(.secondary) + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) .fixedSize(horizontal: false, vertical: true) Picker("Destination", selection: $mode) { @@ -77,8 +80,7 @@ struct MoveToFolderSheet: View { .pickerStyle(.inline) if case .new = mode { - TextField("New folder name", text: $newFolderName) - .textFieldStyle(.roundedBorder) + ScarfTextField("New folder name", text: $newFolderName) .onSubmit { if canMove { commit() } } @@ -86,15 +88,16 @@ struct MoveToFolderSheet: View { HStack { Button("Cancel") { dismiss() } + .buttonStyle(ScarfGhostButton()) .keyboardShortcut(.cancelAction) Spacer() Button("Move") { commit() } + .buttonStyle(ScarfPrimaryButton()) .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) .disabled(!canMove) } } - .padding() + .padding(ScarfSpace.s5) .frame(minWidth: 420, minHeight: 320) } diff --git a/scarf/scarf/Features/Projects/Views/ProjectSessionsView.swift b/scarf/scarf/Features/Projects/Views/ProjectSessionsView.swift index d33ecb8..a4940e7 100644 --- a/scarf/scarf/Features/Projects/Views/ProjectSessionsView.swift +++ b/scarf/scarf/Features/Projects/Views/ProjectSessionsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Per-project Sessions tab (v2.3). Lives beside the Dashboard and /// Site tabs in the project view; populated from the session @@ -59,9 +60,9 @@ struct ProjectSessionsView: View { HStack(spacing: 12) { VStack(alignment: .leading, spacing: 2) { Text("Sessions in this project") - .font(.headline) + .scarfStyle(.headline) Text("Chats you start here get attributed automatically. Older CLI-started sessions live in the global Sessions sidebar.") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -75,7 +76,7 @@ struct ProjectSessionsView: View { } label: { Label("New Chat", systemImage: "message.badge.filled.fill") } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .padding() } diff --git a/scarf/scarf/Features/Projects/Views/ProjectSlashCommandsView.swift b/scarf/scarf/Features/Projects/Views/ProjectSlashCommandsView.swift index 20ab348..707b18a 100644 --- a/scarf/scarf/Features/Projects/Views/ProjectSlashCommandsView.swift +++ b/scarf/scarf/Features/Projects/Views/ProjectSlashCommandsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// The "Slash Commands" tab on the per-project surface. Lists the /// project-scoped commands stored at `/.scarf/slash-commands/` @@ -52,9 +53,9 @@ struct ProjectSlashCommandsView: View { HStack { VStack(alignment: .leading, spacing: 2) { Text("Slash Commands") - .font(.headline) + .scarfStyle(.headline) Text("`/` shortcuts that expand into prompt templates. Stored at `/.scarf/slash-commands/` so they ship with `.scarftemplate` bundles.") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -83,7 +84,7 @@ struct ProjectSlashCommandsView: View { Text("Add reusable prompt templates here. Each command shows up in the chat slash menu when you're chatting in this project.") } actions: { Button("Add Command") { viewModel.beginNew() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { @@ -117,7 +118,7 @@ struct ProjectSlashCommandsView: View { Text("Couldn't update slash commands") .font(.subheadline.weight(.semibold)) Text(message) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) } Spacer() @@ -165,7 +166,7 @@ private struct CommandRow: View { } } Text(command.description) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) .lineLimit(2) if let tags = command.tags, !tags.isEmpty { @@ -208,7 +209,7 @@ struct SlashCommandEditorSheet: View { Button("Save") { Task { await viewModel.saveDraft() } } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .keyboardShortcut(.defaultAction) .disabled(saveDisabled) } @@ -244,7 +245,7 @@ struct SlashCommandEditorSheet: View { .help("Lowercase letters, digits, and hyphens. Must start with a letter.") if let nameError = nameValidationMessage { Text(nameError) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.orange) } TextField("Description", text: Binding( @@ -277,7 +278,7 @@ struct SlashCommandEditorSheet: View { Section("Prompt template") { Text("Use `{{argument}}` to substitute the user's input. `{{argument | default: \"…\"}}` provides a fallback when the user invokes the command without arguments.") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) TextEditor(text: Binding( get: { viewModel.draft?.body ?? "" }, @@ -305,12 +306,12 @@ struct SlashCommandEditorSheet: View { VStack(alignment: .leading, spacing: 8) { HStack { Text("Preview") - .font(.headline) + .scarfStyle(.headline) Spacer() } HStack { Text("Sample argument") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) TextField("(empty)", text: $sampleArgument) .textFieldStyle(.roundedBorder) @@ -324,7 +325,7 @@ struct SlashCommandEditorSheet: View { .textSelection(.enabled) } Text("This is the prompt Hermes will receive. The user sees the literal `/\(viewModel.draft?.name ?? "name")` they typed in their own bubble; the expanded body goes to the agent with a `` marker.") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } diff --git a/scarf/scarf/Features/Projects/Views/ProjectsSidebar.swift b/scarf/scarf/Features/Projects/Views/ProjectsSidebar.swift index 183d5b7..28a967a 100644 --- a/scarf/scarf/Features/Projects/Views/ProjectsSidebar.swift +++ b/scarf/scarf/Features/Projects/Views/ProjectsSidebar.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Sidebar view for the Projects feature. Renders the registry as: /// - A search field at the top (⌘F focus). @@ -68,18 +69,18 @@ struct ProjectsSidebar: View { HStack { Image(systemName: "magnifyingglass") .foregroundStyle(.secondary) - .font(.caption) + .scarfStyle(.caption) TextField("Filter projects", text: $filterText) .textFieldStyle(.plain) .focused($searchFocused) - .font(.caption) + .scarfStyle(.caption) if !filterText.isEmpty { Button { filterText = "" } label: { Image(systemName: "xmark.circle.fill") .foregroundStyle(.tertiary) - .font(.caption) + .scarfStyle(.caption) } .buttonStyle(.borderless) } @@ -122,7 +123,7 @@ struct ProjectsSidebar: View { } } label: { Label(folder, systemImage: "folder") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) } } @@ -137,7 +138,7 @@ struct ProjectsSidebar: View { } } label: { Label("Archived (\(archivedVisible.count))", systemImage: "archivebox") - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) } } @@ -208,7 +209,7 @@ struct ProjectsSidebar: View { Toggle(isOn: $showArchived) { Image(systemName: showArchived ? "archivebox.fill" : "archivebox") - .font(.caption) + .scarfStyle(.caption) } .toggleStyle(.button) .buttonStyle(.borderless) diff --git a/scarf/scarf/Features/Projects/Views/RenameProjectSheet.swift b/scarf/scarf/Features/Projects/Views/RenameProjectSheet.swift index bce8453..5a26179 100644 --- a/scarf/scarf/Features/Projects/Views/RenameProjectSheet.swift +++ b/scarf/scarf/Features/Projects/Views/RenameProjectSheet.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Sheet for renaming a project in the registry. Preserves the /// project's `path`, `folder`, and `archived` fields — the rename @@ -45,15 +46,16 @@ struct RenameProjectSheet: View { } var body: some View { - VStack(alignment: .leading, spacing: 12) { - Text("Rename project").font(.headline) + VStack(alignment: .leading, spacing: ScarfSpace.s3) { + Text("Rename project") + .scarfStyle(.headline) + .foregroundStyle(ScarfColor.foregroundPrimary) Text("The project directory on disk isn't changed — only the label Scarf shows in the sidebar.") - .font(.caption) - .foregroundStyle(.secondary) + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) .fixedSize(horizontal: false, vertical: true) - TextField("Project name", text: $newName) - .textFieldStyle(.roundedBorder) + ScarfTextField("Project name", text: $newName) .onSubmit { if validation.isValid { save() @@ -62,21 +64,22 @@ struct RenameProjectSheet: View { if let message = validation.message { Label(message, systemImage: "exclamationmark.triangle.fill") - .font(.caption) - .foregroundStyle(.red) + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.danger) } HStack { Button("Cancel") { dismiss() } + .buttonStyle(ScarfGhostButton()) .keyboardShortcut(.cancelAction) Spacer() Button("Save") { save() } + .buttonStyle(ScarfPrimaryButton()) .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) .disabled(!validation.isValid) } } - .padding() + .padding(ScarfSpace.s5) .frame(minWidth: 420) } diff --git a/scarf/scarf/Features/Projects/Views/Widgets/ChartWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/ChartWidgetView.swift index 525c0da..6db058d 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/ChartWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/ChartWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign import Charts // Flattened data point for Charts to avoid complex nested generic inference @@ -27,15 +28,15 @@ struct ChartWidgetView: View { var body: some View { VStack(alignment: .leading, spacing: 6) { Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) chartContent .frame(height: 150) } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } @ViewBuilder diff --git a/scarf/scarf/Features/Projects/Views/Widgets/ListWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/ListWidgetView.swift index a8c82d5..eefb721 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/ListWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/ListWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct ListWidgetView: View { let widget: DashboardWidget @@ -10,10 +11,10 @@ struct ListWidgetView: View { if let icon = widget.icon { Image(systemName: icon) .foregroundStyle(.secondary) - .font(.caption) + .scarfStyle(.caption) } Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) } if let items = widget.items { @@ -32,8 +33,8 @@ struct ListWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } private func statusIcon(_ status: String?) -> String { diff --git a/scarf/scarf/Features/Projects/Views/Widgets/ProgressWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/ProgressWidgetView.swift index e89c392..da82bac 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/ProgressWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/ProgressWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct ProgressWidgetView: View { let widget: DashboardWidget @@ -14,7 +15,7 @@ struct ProgressWidgetView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) ProgressView(value: progressValue) { if let label = widget.label { @@ -27,7 +28,7 @@ struct ProgressWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } } diff --git a/scarf/scarf/Features/Projects/Views/Widgets/StatWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/StatWidgetView.swift index 7ebf5ee..0e6696f 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/StatWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/StatWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct StatWidgetView: View { let widget: DashboardWidget @@ -14,10 +15,10 @@ struct StatWidgetView: View { if let icon = widget.icon { Image(systemName: icon) .foregroundStyle(widgetColor) - .font(.caption) + .scarfStyle(.caption) } Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) } if let value = widget.value { @@ -32,7 +33,7 @@ struct StatWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } } diff --git a/scarf/scarf/Features/Projects/Views/Widgets/TableWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/TableWidgetView.swift index 1f69285..f58e43a 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/TableWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/TableWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct TableWidgetView: View { let widget: DashboardWidget @@ -7,7 +8,7 @@ struct TableWidgetView: View { var body: some View { VStack(alignment: .leading, spacing: 6) { Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) if let columns = widget.columns, let rows = widget.rows { Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 4) { @@ -32,7 +33,7 @@ struct TableWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } } diff --git a/scarf/scarf/Features/Projects/Views/Widgets/TextWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/TextWidgetView.swift index 4d26216..b0f1c2e 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/TextWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/TextWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct TextWidgetView: View { let widget: DashboardWidget @@ -7,7 +8,7 @@ struct TextWidgetView: View { var body: some View { VStack(alignment: .leading, spacing: 4) { Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) if let content = widget.content { if widget.format == "markdown" { @@ -20,7 +21,7 @@ struct TextWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } } diff --git a/scarf/scarf/Features/Projects/Views/Widgets/WebviewWidgetView.swift b/scarf/scarf/Features/Projects/Views/Widgets/WebviewWidgetView.swift index 41c5f9d..373b4e3 100644 --- a/scarf/scarf/Features/Projects/Views/Widgets/WebviewWidgetView.swift +++ b/scarf/scarf/Features/Projects/Views/Widgets/WebviewWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign import WebKit struct WebviewWidgetView: View { @@ -29,7 +30,7 @@ struct WebviewWidgetView: View { VStack(spacing: 0) { if let url = webURL { WebViewRepresentable(url: url) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } else { ContentUnavailableView { Label("Invalid URL", systemImage: "globe") @@ -49,10 +50,10 @@ struct WebviewWidgetView: View { if let icon = widget.icon { Image(systemName: icon) .foregroundStyle(.secondary) - .font(.caption) + .scarfStyle(.caption) } Text(widget.title) - .font(.caption) + .scarfStyle(.caption) .foregroundStyle(.secondary) Spacer() if let urlString = widget.url { @@ -76,8 +77,8 @@ struct WebviewWidgetView: View { } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) - .background(.quaternary.opacity(0.5)) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .background(ScarfColor.backgroundSecondary) + .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg)) } } diff --git a/scarf/scarf/Features/QuickCommands/Views/QuickCommandsView.swift b/scarf/scarf/Features/QuickCommands/Views/QuickCommandsView.swift index ce77be8..bca3417 100644 --- a/scarf/scarf/Features/QuickCommands/Views/QuickCommandsView.swift +++ b/scarf/scarf/Features/QuickCommands/Views/QuickCommandsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct QuickCommandsView: View { @State private var viewModel: QuickCommandsViewModel @@ -12,19 +13,22 @@ struct QuickCommandsView: View { var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - header - intro - if viewModel.commands.isEmpty { - emptyState - } else { - list + VStack(spacing: 0) { + header + ScrollView { + VStack(alignment: .leading, spacing: 16) { + intro + if viewModel.commands.isEmpty { + emptyState + } else { + list + } } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Quick Commands") .onAppear { viewModel.load() } .sheet(isPresented: $showAddSheet) { @@ -46,28 +50,33 @@ struct QuickCommandsView: View { } private var header: some View { - HStack { - if let msg = viewModel.message { - Label(msg, systemImage: "checkmark.circle.fill") - .font(.caption) - .foregroundStyle(.green) + ScarfPageHeader( + "Quick Commands", + subtitle: "Shell shortcuts hermes exposes in chat as `/command_name`." + ) { + HStack(spacing: ScarfSpace.s2) { + if let msg = viewModel.message { + Label(msg, systemImage: "checkmark.circle.fill") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.success) + } + Button("Reload") { viewModel.load() } + .buttonStyle(ScarfGhostButton()) + Button { + showAddSheet = true + } label: { + Label("Add Command", systemImage: "plus") + } + .buttonStyle(ScarfPrimaryButton()) } - Spacer() - Button { - showAddSheet = true - } label: { - Label("Add Command", systemImage: "plus") - } - .controlSize(.small) - Button("Reload") { viewModel.load() } - .controlSize(.small) + .fixedSize(horizontal: true, vertical: false) } } private var intro: some View { - Text("Quick commands are shell shortcuts hermes exposes in chat as `/command_name`. They live under `quick_commands:` in config.yaml.") - .font(.caption) - .foregroundStyle(.secondary) + Text("Stored under `quick_commands:` in config.yaml.") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) } private var emptyState: some View { diff --git a/scarf/scarf/Features/Servers/Views/AddServerSheet.swift b/scarf/scarf/Features/Servers/Views/AddServerSheet.swift index bc27c59..396db86 100644 --- a/scarf/scarf/Features/Servers/Views/AddServerSheet.swift +++ b/scarf/scarf/Features/Servers/Views/AddServerSheet.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Sheet for adding a new remote server. Collects SSH connection details, /// runs a "Test Connection" probe, and — on save — hands the persisted @@ -36,7 +37,7 @@ struct AddServerSheet: View { Image(systemName: "server.rack") .font(.title2) Text("Add Remote Server") - .font(.headline) + .scarfStyle(.headline) Spacer() } .padding(.horizontal, 20) diff --git a/scarf/scarf/Features/Servers/Views/ConnectionStatusPill.swift b/scarf/scarf/Features/Servers/Views/ConnectionStatusPill.swift index a4640c2..1340ec4 100644 --- a/scarf/scarf/Features/Servers/Views/ConnectionStatusPill.swift +++ b/scarf/scarf/Features/Servers/Views/ConnectionStatusPill.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Small colored pill shown in the toolbar reflecting the server's reach- /// ability. Green = connected, yellow = probing, red = unreachable. @@ -33,8 +34,8 @@ struct ConnectionStatusPill: View { .foregroundStyle(color) .symbolRenderingMode(.hierarchical) labelText - .font(.caption) - .foregroundStyle(.secondary) + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.foregroundMuted) .lineLimit(1) } .padding(.horizontal, 4) @@ -51,10 +52,10 @@ struct ConnectionStatusPill: View { private var color: Color { switch status.status { - case .connected: return .green - case .degraded: return .orange - case .idle: return .yellow - case .error: return .red + case .connected: return ScarfColor.success + case .degraded: return ScarfColor.warning + case .idle: return ScarfColor.warning.opacity(0.7) + case .error: return ScarfColor.danger } } @@ -101,13 +102,14 @@ struct ConnectionStatusPill: View { VStack(alignment: .leading, spacing: 10) { HStack { Label(message, systemImage: "xmark.octagon.fill") - .foregroundStyle(.red) - .font(.headline) + .foregroundStyle(ScarfColor.danger) + .scarfStyle(.headline) Spacer() Button("Retry") { status.retry() showDetails = false } + .buttonStyle(ScarfPrimaryButton()) } Divider() diff --git a/scarf/scarf/Features/Servers/Views/ManageServersView.swift b/scarf/scarf/Features/Servers/Views/ManageServersView.swift index 78684ae..726394f 100644 --- a/scarf/scarf/Features/Servers/Views/ManageServersView.swift +++ b/scarf/scarf/Features/Servers/Views/ManageServersView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// List of registered remote servers with add/remove actions. Rendered as a /// popover from the toolbar switcher. @@ -59,7 +60,7 @@ struct ManageServersView: View { private var header: some View { HStack { - Text("Servers").font(.headline) + Text("Servers").scarfStyle(.headline) Spacer() Button { showAddSheet = true @@ -76,7 +77,7 @@ struct ManageServersView: View { Image(systemName: "server.rack") .font(.system(size: 28)) .foregroundStyle(.secondary) - Text("No remote servers").font(.headline) + Text("No remote servers").scarfStyle(.headline) Text("Click Add to connect to a remote Hermes installation over SSH.") .font(.caption) .foregroundStyle(.secondary) diff --git a/scarf/scarf/Features/Servers/Views/MissingServerView.swift b/scarf/scarf/Features/Servers/Views/MissingServerView.swift index 7425106..24b8f8b 100644 --- a/scarf/scarf/Features/Servers/Views/MissingServerView.swift +++ b/scarf/scarf/Features/Servers/Views/MissingServerView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Shown when a window is restored after the user removed the server it /// was bound to. Lets them open Local or any remaining registered server @@ -33,7 +34,7 @@ struct MissingServerView: View { openWindow(value: ServerContext.local.id) dismissWindow() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) if !registry.entries.isEmpty { Menu { diff --git a/scarf/scarf/Features/Servers/Views/ServerSwitcherToolbar.swift b/scarf/scarf/Features/Servers/Views/ServerSwitcherToolbar.swift index 4befab8..4af8fe1 100644 --- a/scarf/scarf/Features/Servers/Views/ServerSwitcherToolbar.swift +++ b/scarf/scarf/Features/Servers/Views/ServerSwitcherToolbar.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Toolbar control that shows the current window's server and exposes a /// menu for opening *other* servers in additional windows. Multi-window is @@ -36,13 +37,15 @@ struct ServerSwitcherToolbar: View { } label: { HStack(spacing: 6) { Circle() - .fill(current.isRemote ? Color.blue : Color.green) + .fill(current.isRemote ? ScarfColor.info : ScarfColor.success) .frame(width: 8, height: 8) Text(verbatim: current.displayName) - .font(.callout) + .scarfStyle(.callout) + .foregroundStyle(ScarfColor.foregroundPrimary) .lineLimit(1) Image(systemName: "chevron.down") - .font(.caption2) + .font(.system(size: 10)) + .foregroundStyle(ScarfColor.foregroundMuted) } .padding(.horizontal, 8) .padding(.vertical, 4) diff --git a/scarf/scarf/Features/Settings/Views/Components/ModelPickerSheet.swift b/scarf/scarf/Features/Settings/Views/Components/ModelPickerSheet.swift index f66b447..d9df990 100644 --- a/scarf/scarf/Features/Settings/Views/Components/ModelPickerSheet.swift +++ b/scarf/scarf/Features/Settings/Views/Components/ModelPickerSheet.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign /// Two-column model browser sheet. Left column lists providers, right column /// lists models for the selected provider. Supports filtering and a "Custom…" @@ -109,7 +110,7 @@ struct ModelPickerSheet: View { HStack(spacing: 8) { Image(systemName: "cpu") Text("Select Model") - .font(.headline) + .scarfStyle(.headline) Spacer() if !customMode { TextField("Search…", text: $searchText) @@ -294,7 +295,7 @@ struct ModelPickerSheet: View { } label: { Label("Sign in to Nous Portal", systemImage: "person.badge.key.fill") } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .controlSize(.regular) } } @@ -361,7 +362,7 @@ struct ModelPickerSheet: View { Spacer() Button("Cancel") { onCancel() } Button("Select") { submitSelection() } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .disabled(!canSubmit) } .padding() diff --git a/scarf/scarf/Features/Settings/Views/Components/NousSignInSheet.swift b/scarf/scarf/Features/Settings/Views/Components/NousSignInSheet.swift index 3c5a2b9..2e5237b 100644 --- a/scarf/scarf/Features/Settings/Views/Components/NousSignInSheet.swift +++ b/scarf/scarf/Features/Settings/Views/Components/NousSignInSheet.swift @@ -1,5 +1,6 @@ import SwiftUI import AppKit +import ScarfDesign /// In-app sign-in sheet for Nous Portal — hosts a ``NousAuthFlow`` and /// renders one of four sub-views keyed on `flow.state`. Reached from the @@ -69,7 +70,7 @@ struct NousSignInSheet: View { Image(systemName: "person.badge.key.fill") .foregroundStyle(.tint) Text("Sign in to Nous Portal") - .font(.headline) + .scarfStyle(.headline) Spacer() if case .waitingForApproval = flowState { Button("Cancel") { dismiss() } @@ -123,7 +124,7 @@ struct NousSignInSheet: View { VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 4) { Text("Approve in your browser") - .font(.headline) + .scarfStyle(.headline) Text("We opened the Nous Portal approval page. Confirm this code matches what it shows, then approve.") .font(.callout) .foregroundStyle(.secondary) @@ -176,7 +177,7 @@ struct NousSignInSheet: View { .foregroundStyle(.green) .font(.system(size: 48)) Text("Signed in to Nous Portal") - .font(.headline) + .scarfStyle(.headline) Text("Your tools will now route through your subscription.") .font(.callout) .foregroundStyle(.secondary) @@ -193,7 +194,7 @@ struct NousSignInSheet: View { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.orange) Text(billingURL == nil ? "Sign-in didn't complete" : "Subscription required") - .font(.headline) + .scarfStyle(.headline) } Text(reason) @@ -209,7 +210,7 @@ struct NousSignInSheet: View { Label("Subscribe", systemImage: "creditcard") .frame(maxWidth: .infinity) } - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .controlSize(.large) } diff --git a/scarf/scarf/Features/Skills/Views/SkillsView.swift b/scarf/scarf/Features/Skills/Views/SkillsView.swift index c635731..7d48c81 100644 --- a/scarf/scarf/Features/Skills/Views/SkillsView.swift +++ b/scarf/scarf/Features/Skills/Views/SkillsView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign struct SkillsView: View { @State private var viewModel: SkillsViewModel @@ -38,6 +39,10 @@ struct SkillsView: View { var body: some View { VStack(spacing: 0) { + ScarfPageHeader( + "Skills", + subtitle: "Pre-packaged prompt collections the agent can call into. \(viewModel.totalSkillCount) installed." + ) modePicker // v2.5 "What's New" pill — only renders when the diff has // changes against a non-empty prior snapshot (first launch @@ -55,7 +60,8 @@ struct SkillsView: View { case .updates: updatesContent } } - .navigationTitle("Skills (\(viewModel.totalSkillCount))") + .background(ScarfColor.backgroundPrimary) + .navigationTitle("Skills") // SkillsViewModel.load() is async after the v2.5 ScarfCore // promotion. Wrap in a Task here so the existing onAppear // contract (fire-and-forget) keeps working without making diff --git a/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift b/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift index 4de099c..df9676c 100644 --- a/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift +++ b/scarf/scarf/Features/Templates/Views/ConfigEditorSheet.swift @@ -1,4 +1,5 @@ import ScarfCore +import ScarfDesign import SwiftUI /// Post-install configuration editor. Thin wrapper around the same @@ -73,7 +74,7 @@ struct ConfigEditorSheet: View { Text("Configuration saved").font(.title2.bold()) Button("Done") { dismiss() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .frame(maxWidth: .infinity, maxHeight: .infinity) .frame(minWidth: 560, minHeight: 280) diff --git a/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift index e2c9155..f6fe879 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateConfigSheet.swift @@ -1,4 +1,5 @@ import ScarfCore +import ScarfDesign import SwiftUI /// The configure form rendered for template install + post-install @@ -106,7 +107,7 @@ struct TemplateConfigSheet: View { // handles dismissal via the success view's Done button. } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .padding(16) } @@ -117,10 +118,10 @@ struct TemplateConfigSheet: View { private func fieldRow(_ field: TemplateConfigField) -> some View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .firstTextBaseline, spacing: 4) { - Text(field.label).font(.headline) + Text(field.label).scarfStyle(.headline) if field.required { Text("*") - .font(.headline) + .scarfStyle(.headline) .foregroundStyle(.red) } Spacer() diff --git a/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift index 8d5b987..0062d4a 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateExportSheet.swift @@ -2,6 +2,7 @@ import SwiftUI import AppKit import UniformTypeIdentifiers import ScarfCore +import ScarfDesign /// Author-facing sheet for exporting an existing project as a /// `.scarftemplate`. Mirrors the profile-export flow: fill in a few fields, @@ -89,7 +90,7 @@ struct TemplateExportSheet: View { Spacer() Button("Export…") { runExport() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .disabled(!canExport) } .padding(.top, 8) @@ -97,7 +98,7 @@ struct TemplateExportSheet: View { private var metadataGroup: some View { VStack(alignment: .leading, spacing: 8) { - Text("Metadata").font(.headline) + Text("Metadata").scarfStyle(.headline) LabeledContent("Template ID") { TextField("owner/name", text: $viewModel.templateId) .textFieldStyle(.roundedBorder) @@ -137,7 +138,7 @@ struct TemplateExportSheet: View { private var requiredFilesGroup: some View { let plan = viewModel.previewPlan() return VStack(alignment: .leading, spacing: 6) { - Text("Required Files").font(.headline) + Text("Required Files").scarfStyle(.headline) check(label: "dashboard.json (\(plan.projectDir)/.scarf/dashboard.json)", ok: plan.dashboardPresent) check(label: "README.md (\(plan.projectDir)/README.md)", ok: plan.readmePresent) check(label: "AGENTS.md (\(plan.projectDir)/AGENTS.md)", ok: plan.agentsMdPresent) @@ -147,7 +148,7 @@ struct TemplateExportSheet: View { private var instructionsGroup: some View { let plan = viewModel.previewPlan() return VStack(alignment: .leading, spacing: 4) { - Text("Agent-specific instructions (optional)").font(.headline) + Text("Agent-specific instructions (optional)").scarfStyle(.headline) if plan.instructionFiles.isEmpty { Text("No per-agent instruction files found in the project root.") .font(.caption) @@ -163,7 +164,7 @@ struct TemplateExportSheet: View { private var skillsGroup: some View { VStack(alignment: .leading, spacing: 6) { - Text("Include Skills").font(.headline) + Text("Include Skills").scarfStyle(.headline) if viewModel.availableSkills.isEmpty { Text("No skills found.") .font(.caption) @@ -186,7 +187,7 @@ struct TemplateExportSheet: View { private var cronGroup: some View { VStack(alignment: .leading, spacing: 6) { - Text("Include Cron Jobs").font(.headline) + Text("Include Cron Jobs").scarfStyle(.headline) if viewModel.availableCronJobs.isEmpty { Text("No cron jobs found.") .font(.caption) @@ -214,7 +215,7 @@ struct TemplateExportSheet: View { private var memoryGroup: some View { VStack(alignment: .leading, spacing: 6) { - Text("Memory Appendix (optional)").font(.headline) + Text("Memory Appendix (optional)").scarfStyle(.headline) Text("Markdown that will be appended to the installer's MEMORY.md, wrapped in template-specific markers so it can be removed cleanly later.") .font(.caption) .foregroundStyle(.secondary) diff --git a/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift index db332e7..085533f 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateInstallSheet.swift @@ -1,5 +1,6 @@ import AppKit import ScarfCore +import ScarfDesign import SwiftUI /// Preview-and-confirm sheet for installing a `.scarftemplate`. Honest @@ -47,7 +48,7 @@ struct TemplateInstallSheet: View { private var idleView: some View { VStack(spacing: 16) { Text("No template loaded.") - .font(.headline) + .scarfStyle(.headline) Button("Close") { dismiss() } } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -70,7 +71,7 @@ struct TemplateInstallSheet: View { Divider() } Text("Where should this project live?") - .font(.headline) + .scarfStyle(.headline) Text("Scarf will create a new folder inside the directory you pick, named after the template id.") .font(.subheadline) .foregroundStyle(.secondary) @@ -169,7 +170,7 @@ struct TemplateInstallSheet: View { .foregroundStyle(.secondary) Button("Install") { viewModel.confirmInstall() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .padding(.top, 8) } @@ -353,7 +354,7 @@ struct TemplateInstallSheet: View { @ViewBuilder private func section(title: String, subtitle: String?, @ViewBuilder content: () -> Content) -> some View { VStack(alignment: .leading, spacing: 4) { - Text(title).font(.headline) + Text(title).scarfStyle(.headline) if let subtitle { Text(subtitle) .font(.caption.monospaced()) @@ -391,7 +392,7 @@ struct TemplateInstallSheet: View { dismiss() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .frame(maxWidth: .infinity, maxHeight: .infinity) } diff --git a/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift b/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift index 1f23d4e..062fe9c 100644 --- a/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift +++ b/scarf/scarf/Features/Templates/Views/TemplateUninstallSheet.swift @@ -1,4 +1,5 @@ import ScarfCore +import ScarfDesign import SwiftUI /// Preview-and-confirm sheet for uninstalling a template-installed @@ -41,7 +42,7 @@ struct TemplateUninstallSheet: View { private var idleView: some View { VStack(spacing: 16) { Text("No template loaded.") - .font(.headline) + .scarfStyle(.headline) Button("Close") { dismiss() } } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -87,7 +88,7 @@ struct TemplateUninstallSheet: View { .foregroundStyle(.secondary) Button("Remove") { viewModel.confirmUninstall() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) .tint(.red) } .padding(.top, 8) @@ -244,7 +245,7 @@ struct TemplateUninstallSheet: View { @ViewBuilder content: () -> Content ) -> some View { VStack(alignment: .leading, spacing: 4) { - Text(title).font(.headline) + Text(title).scarfStyle(.headline) if let subtitle { Text(subtitle) .font(.caption.monospaced()) @@ -296,7 +297,7 @@ struct TemplateUninstallSheet: View { dismiss() } .keyboardShortcut(.defaultAction) - .buttonStyle(.borderedProminent) + .buttonStyle(ScarfPrimaryButton()) } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() @@ -316,7 +317,7 @@ struct TemplateUninstallSheet: View { Image(systemName: "folder.badge.questionmark") .foregroundStyle(.orange) Text("Project folder kept") - .font(.headline) + .scarfStyle(.headline) } Text("These files weren't installed by the template (the agent or you created them after install), so Scarf left them in place along with the folder itself.") .font(.caption) diff --git a/scarf/scarf/Features/Webhooks/Views/WebhooksView.swift b/scarf/scarf/Features/Webhooks/Views/WebhooksView.swift index 3355136..8785eed 100644 --- a/scarf/scarf/Features/Webhooks/Views/WebhooksView.swift +++ b/scarf/scarf/Features/Webhooks/Views/WebhooksView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign import AppKit struct WebhooksView: View { @@ -25,7 +26,6 @@ struct WebhooksView: View { var body: some View { VStack(spacing: 0) { header - Divider() if viewModel.isLoading && viewModel.webhooks.isEmpty { ProgressView().padding() } else if viewModel.webhookPlatformNotEnabled { @@ -36,6 +36,7 @@ struct WebhooksView: View { list } } + .background(ScarfColor.backgroundPrimary) .navigationTitle("Webhooks") .onAppear { viewModel.load() } .sheet(isPresented: $showAddSheet) { addSheet } @@ -52,25 +53,28 @@ struct WebhooksView: View { } private var header: some View { - HStack { - if let msg = viewModel.message { - Label(msg, systemImage: "info.circle.fill") - .font(.caption) - .foregroundStyle(.green) + ScarfPageHeader( + "Webhooks", + subtitle: "HTTP receivers that trigger sessions on incoming events." + ) { + HStack(spacing: ScarfSpace.s2) { + if let msg = viewModel.message { + Label(msg, systemImage: "info.circle.fill") + .scarfStyle(.caption) + .foregroundStyle(ScarfColor.success) + } + Button("Reload") { viewModel.load() } + .buttonStyle(ScarfGhostButton()) + Button { + resetAddForm() + showAddSheet = true + } label: { + Label("Subscribe", systemImage: "plus") + } + .buttonStyle(ScarfPrimaryButton()) } - Spacer() - Button { - resetAddForm() - showAddSheet = true - } label: { - Label("Subscribe", systemImage: "plus") - } - .controlSize(.small) - Button("Reload") { viewModel.load() } - .controlSize(.small) + .fixedSize(horizontal: true, vertical: false) } - .padding(.horizontal) - .padding(.vertical, 8) } /// Shown when hermes reports the webhook platform isn't enabled. Direct users