mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
polish: tokenize remaining sheets, page headers, and widgets
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) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Translucent loading overlay used by feature views while their VM's
|
/// Translucent loading overlay used by feature views while their VM's
|
||||||
/// `load()` runs in the background. Shows a centered ProgressView with
|
/// `load()` runs in the background. Shows a centered ProgressView with
|
||||||
@@ -27,15 +28,15 @@ struct LoadingOverlay: ViewModifier {
|
|||||||
if isEmpty {
|
if isEmpty {
|
||||||
// Full cover: empty state. User has no data to look at,
|
// Full cover: empty state. User has no data to look at,
|
||||||
// so own the whole pane with the spinner.
|
// so own the whole pane with the spinner.
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: ScarfSpace.s3) {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.controlSize(.large)
|
.controlSize(.large)
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(.callout)
|
.scarfStyle(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.background(Color(NSColor.windowBackgroundColor))
|
.background(ScarfColor.backgroundPrimary)
|
||||||
} else {
|
} else {
|
||||||
// Stale-content refresh: top-trailing pill so the
|
// Stale-content refresh: top-trailing pill so the
|
||||||
// user sees data is being refreshed without losing
|
// user sees data is being refreshed without losing
|
||||||
@@ -47,13 +48,13 @@ struct LoadingOverlay: ViewModifier {
|
|||||||
ProgressView()
|
ProgressView()
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
Text(label)
|
Text(label)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.background(.thinMaterial, in: Capsule())
|
.background(.thinMaterial, in: Capsule())
|
||||||
.padding(8)
|
.padding(ScarfSpace.s2)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct CredentialPoolsView: View {
|
struct CredentialPoolsView: View {
|
||||||
@State private var viewModel: CredentialPoolsViewModel
|
@State private var viewModel: CredentialPoolsViewModel
|
||||||
@@ -12,23 +13,26 @@ struct CredentialPoolsView: View {
|
|||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
VStack(spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
header
|
||||||
header
|
ScrollView {
|
||||||
safetyNotice
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
if viewModel.isLoading {
|
safetyNotice
|
||||||
ProgressView().padding()
|
if viewModel.isLoading {
|
||||||
} else if viewModel.pools.isEmpty {
|
ProgressView().padding()
|
||||||
emptyState
|
} else if viewModel.pools.isEmpty {
|
||||||
} else {
|
emptyState
|
||||||
ForEach(viewModel.pools) { pool in
|
} else {
|
||||||
poolSection(pool)
|
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")
|
.navigationTitle("Credential Pools")
|
||||||
.loadingOverlay(
|
.loadingOverlay(
|
||||||
viewModel.isLoading,
|
viewModel.isLoading,
|
||||||
@@ -58,21 +62,26 @@ struct CredentialPoolsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack {
|
ScarfPageHeader(
|
||||||
if let msg = viewModel.message {
|
"Credential Pools",
|
||||||
Label(msg, systemImage: "info.circle.fill")
|
subtitle: "Shared OAuth + token pools rotated across runs."
|
||||||
.font(.caption)
|
) {
|
||||||
.foregroundStyle(.secondary)
|
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()
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
Button {
|
|
||||||
showAddSheet = true
|
|
||||||
} label: {
|
|
||||||
Label("Add Credential", systemImage: "plus")
|
|
||||||
}
|
|
||||||
.controlSize(.small)
|
|
||||||
Button("Reload") { viewModel.load() }
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct GatewayView: View {
|
struct GatewayView: View {
|
||||||
@State private var viewModel: GatewayViewModel
|
@State private var viewModel: GatewayViewModel
|
||||||
@@ -11,15 +12,22 @@ struct GatewayView: View {
|
|||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
VStack(spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 24) {
|
ScarfPageHeader(
|
||||||
serviceSection
|
"Messaging Gateway",
|
||||||
platformsSection
|
subtitle: "Outbound channel bridge — Discord, Telegram, Slack, etc."
|
||||||
pairingSection
|
)
|
||||||
|
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")
|
.navigationTitle("Messaging Gateway")
|
||||||
.onAppear { viewModel.load() }
|
.onAppear { viewModel.load() }
|
||||||
.onChange(of: fileWatcher.lastChangeDate) { viewModel.load() }
|
.onChange(of: fileWatcher.lastChangeDate) { viewModel.load() }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct MCPServerAddCustomView: View {
|
struct MCPServerAddCustomView: View {
|
||||||
let viewModel: MCPServersViewModel
|
let viewModel: MCPServersViewModel
|
||||||
@@ -16,13 +17,13 @@ struct MCPServerAddCustomView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Add Custom MCP Server")
|
Text("Add Custom MCP Server")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
Button("Add") {
|
Button("Add") {
|
||||||
submit()
|
submit()
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.disabled(!canSubmit)
|
.disabled(!canSubmit)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct MCPServerEditorView: View {
|
struct MCPServerEditorView: View {
|
||||||
@State var viewModel: MCPServerEditorViewModel
|
@State var viewModel: MCPServerEditorViewModel
|
||||||
@@ -11,7 +12,7 @@ struct MCPServerEditorView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Edit \(viewModel.server.name)")
|
Text("Edit \(viewModel.server.name)")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Text(viewModel.server.transport.displayName)
|
Text(viewModel.server.transport.displayName)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -30,7 +31,7 @@ struct MCPServerEditorView: View {
|
|||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.disabled(viewModel.isSaving)
|
.disabled(viewModel.isSaving)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct MCPServerPresetPickerView: View {
|
struct MCPServerPresetPickerView: View {
|
||||||
let viewModel: MCPServersViewModel
|
let viewModel: MCPServersViewModel
|
||||||
@@ -35,7 +36,7 @@ struct MCPServerPresetPickerView: View {
|
|||||||
}
|
}
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
(selectedPreset.map { Text(verbatim: $0.displayName) } ?? Text("Add from Preset"))
|
(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."))
|
(selectedPreset.map { Text(verbatim: $0.description) } ?? Text("Pick an MCP server to add."))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -132,7 +133,7 @@ struct MCPServerPresetPickerView: View {
|
|||||||
Button("Add Server") {
|
Button("Add Server") {
|
||||||
submit(preset: preset)
|
submit(preset: preset)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.disabled(!canSubmit(preset: preset))
|
.disabled(!canSubmit(preset: preset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct PersonalitiesView: View {
|
struct PersonalitiesView: View {
|
||||||
@State private var viewModel: PersonalitiesViewModel
|
@State private var viewModel: PersonalitiesViewModel
|
||||||
@@ -12,16 +13,35 @@ struct PersonalitiesView: View {
|
|||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
VStack(spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 20) {
|
ScarfPageHeader(
|
||||||
header
|
"Personalities",
|
||||||
activeSection
|
subtitle: "Per-personality model + prompt overrides defined in config.yaml."
|
||||||
listSection
|
) {
|
||||||
soulSection
|
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")
|
.navigationTitle("Personalities")
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.load()
|
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 {
|
private var activeSection: some View {
|
||||||
SettingsSection(title: "Active Personality", icon: "theatermasks.fill") {
|
SettingsSection(title: "Active Personality", icon: "theatermasks.fill") {
|
||||||
if viewModel.personalities.isEmpty {
|
if viewModel.personalities.isEmpty {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct DiscordSetupView: View {
|
struct DiscordSetupView: View {
|
||||||
@State private var viewModel: DiscordSetupViewModel
|
@State private var viewModel: DiscordSetupViewModel
|
||||||
@@ -57,7 +58,7 @@ struct DiscordSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct EmailSetupView: View {
|
struct EmailSetupView: View {
|
||||||
@State private var viewModel: EmailSetupViewModel
|
@State private var viewModel: EmailSetupViewModel
|
||||||
@@ -75,7 +76,7 @@ struct EmailSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct FeishuSetupView: View {
|
struct FeishuSetupView: View {
|
||||||
@State private var viewModel: FeishuSetupViewModel
|
@State private var viewModel: FeishuSetupViewModel
|
||||||
@@ -52,7 +53,7 @@ struct FeishuSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct HomeAssistantSetupView: View {
|
struct HomeAssistantSetupView: View {
|
||||||
@State private var viewModel: HomeAssistantSetupViewModel
|
@State private var viewModel: HomeAssistantSetupViewModel
|
||||||
@@ -72,7 +73,7 @@ struct HomeAssistantSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct IMessageSetupView: View {
|
struct IMessageSetupView: View {
|
||||||
@State private var viewModel: IMessageSetupViewModel
|
@State private var viewModel: IMessageSetupViewModel
|
||||||
@@ -61,7 +62,7 @@ struct IMessageSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct MatrixSetupView: View {
|
struct MatrixSetupView: View {
|
||||||
@State private var viewModel: MatrixSetupViewModel
|
@State private var viewModel: MatrixSetupViewModel
|
||||||
@@ -69,7 +70,7 @@ struct MatrixSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct MattermostSetupView: View {
|
struct MattermostSetupView: View {
|
||||||
@State private var viewModel: MattermostSetupViewModel
|
@State private var viewModel: MattermostSetupViewModel
|
||||||
@@ -52,7 +53,7 @@ struct MattermostSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct SignalSetupView: View {
|
struct SignalSetupView: View {
|
||||||
@State private var viewModel: SignalSetupViewModel
|
@State private var viewModel: SignalSetupViewModel
|
||||||
@@ -73,7 +74,7 @@ struct SignalSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
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:
|
case .none:
|
||||||
Button("Link Device") { viewModel.startLink() }.controlSize(.small)
|
Button("Link Device") { viewModel.startLink() }.controlSize(.small)
|
||||||
.disabled(!viewModel.signalCLIInstalled)
|
.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)
|
.disabled(!viewModel.signalCLIInstalled || viewModel.account.isEmpty)
|
||||||
case .link:
|
case .link:
|
||||||
Text("Linking…").font(.caption).foregroundStyle(.secondary)
|
Text("Linking…").font(.caption).foregroundStyle(.secondary)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct SlackSetupView: View {
|
struct SlackSetupView: View {
|
||||||
@State private var viewModel: SlackSetupViewModel
|
@State private var viewModel: SlackSetupViewModel
|
||||||
@@ -56,7 +57,7 @@ struct SlackSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct TelegramSetupView: View {
|
struct TelegramSetupView: View {
|
||||||
@State private var viewModel: TelegramSetupViewModel
|
@State private var viewModel: TelegramSetupViewModel
|
||||||
@@ -57,7 +58,7 @@ struct TelegramSetupView: View {
|
|||||||
Button("Reload") { viewModel.load() }
|
Button("Reload") { viewModel.load() }
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }
|
Button("Save") { viewModel.save() }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct WebhookSetupView: View {
|
struct WebhookSetupView: View {
|
||||||
@State private var viewModel: WebhookSetupViewModel
|
@State private var viewModel: WebhookSetupViewModel
|
||||||
@@ -53,7 +54,7 @@ struct WebhookSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
Button("Reload") { viewModel.load() }.controlSize(.small)
|
||||||
Button("Save") { viewModel.save() }.buttonStyle(.borderedProminent).controlSize(.small)
|
Button("Save") { viewModel.save() }.buttonStyle(ScarfPrimaryButton()).controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct WhatsAppSetupView: View {
|
struct WhatsAppSetupView: View {
|
||||||
@State private var viewModel: WhatsAppSetupViewModel
|
@State private var viewModel: WhatsAppSetupViewModel
|
||||||
@@ -56,7 +57,7 @@ struct WhatsAppSetupView: View {
|
|||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Reload") { viewModel.load() }.controlSize(.small)
|
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)
|
.controlSize(.small)
|
||||||
} else {
|
} else {
|
||||||
Button("Start Pairing") { viewModel.startPairing() }
|
Button("Start Pairing") { viewModel.startPairing() }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct PlatformsView: View {
|
struct PlatformsView: View {
|
||||||
@State private var viewModel: PlatformsViewModel
|
@State private var viewModel: PlatformsViewModel
|
||||||
@@ -13,12 +14,19 @@ struct PlatformsView: View {
|
|||||||
// HSplitView (not nested NavigationSplitView) because ContentView already
|
// HSplitView (not nested NavigationSplitView) because ContentView already
|
||||||
// hosts the outer NavigationSplitView — nesting them breaks layout on macOS.
|
// hosts the outer NavigationSplitView — nesting them breaks layout on macOS.
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HSplitView {
|
VStack(spacing: 0) {
|
||||||
platformList
|
ScarfPageHeader(
|
||||||
.frame(minWidth: 220, idealWidth: 240, maxWidth: 300)
|
"Platforms",
|
||||||
detail
|
subtitle: "Inbound channels the agent listens on. Set up tokens per platform."
|
||||||
.frame(minWidth: 480)
|
)
|
||||||
|
HSplitView {
|
||||||
|
platformList
|
||||||
|
.frame(minWidth: 220, idealWidth: 240, maxWidth: 300)
|
||||||
|
detail
|
||||||
|
.frame(minWidth: 480)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.background(ScarfColor.backgroundPrimary)
|
||||||
.navigationTitle("Platforms")
|
.navigationTitle("Platforms")
|
||||||
.onAppear { viewModel.load() }
|
.onAppear { viewModel.load() }
|
||||||
// Re-read config.yaml / .env / gateway_state.json when any of them
|
// Re-read config.yaml / .env / gateway_state.json when any of them
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct PluginsView: View {
|
struct PluginsView: View {
|
||||||
@State private var viewModel: PluginsViewModel
|
@State private var viewModel: PluginsViewModel
|
||||||
@@ -15,7 +16,6 @@ struct PluginsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
header
|
header
|
||||||
Divider()
|
|
||||||
if viewModel.isLoading && viewModel.plugins.isEmpty {
|
if viewModel.isLoading && viewModel.plugins.isEmpty {
|
||||||
ProgressView().padding()
|
ProgressView().padding()
|
||||||
} else if viewModel.plugins.isEmpty {
|
} else if viewModel.plugins.isEmpty {
|
||||||
@@ -24,6 +24,7 @@ struct PluginsView: View {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.background(ScarfColor.backgroundPrimary)
|
||||||
.navigationTitle("Plugins")
|
.navigationTitle("Plugins")
|
||||||
.loadingOverlay(
|
.loadingOverlay(
|
||||||
viewModel.isLoading,
|
viewModel.isLoading,
|
||||||
@@ -45,25 +46,28 @@ struct PluginsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack {
|
ScarfPageHeader(
|
||||||
if let msg = viewModel.message {
|
"Plugins",
|
||||||
Label(msg, systemImage: "info.circle.fill")
|
subtitle: "Hermes plugins discovered from `~/.hermes/plugins/`."
|
||||||
.font(.caption)
|
) {
|
||||||
.foregroundStyle(.green)
|
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()
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
Button {
|
|
||||||
installIdentifier = ""
|
|
||||||
showInstall = true
|
|
||||||
} label: {
|
|
||||||
Label("Install", systemImage: "plus")
|
|
||||||
}
|
|
||||||
.controlSize(.small)
|
|
||||||
Button("Reload") { viewModel.load() }
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var emptyState: some View {
|
private var emptyState: some View {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import AppKit
|
import AppKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
@@ -21,12 +22,19 @@ struct ProfilesView: View {
|
|||||||
@State private var pendingDelete: HermesProfile?
|
@State private var pendingDelete: HermesProfile?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HSplitView {
|
VStack(spacing: 0) {
|
||||||
listSection
|
ScarfPageHeader(
|
||||||
.frame(minWidth: 260, idealWidth: 300)
|
"Profiles",
|
||||||
detailSection
|
subtitle: "Named config bundles you can swap between."
|
||||||
.frame(minWidth: 400)
|
)
|
||||||
|
HSplitView {
|
||||||
|
listSection
|
||||||
|
.frame(minWidth: 260, idealWidth: 300)
|
||||||
|
detailSection
|
||||||
|
.frame(minWidth: 400)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.background(ScarfColor.backgroundPrimary)
|
||||||
.navigationTitle("Profiles")
|
.navigationTitle("Profiles")
|
||||||
.onAppear { viewModel.load() }
|
.onAppear { viewModel.load() }
|
||||||
.sheet(isPresented: $showCreate) { createSheet }
|
.sheet(isPresented: $showCreate) { createSheet }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Sheet for assigning a project to a folder in the sidebar. Folders
|
/// Sheet for assigning a project to a folder in the sidebar. Folders
|
||||||
/// are implicit — they exist because at least one project references
|
/// are implicit — they exist because at least one project references
|
||||||
@@ -55,11 +56,13 @@ struct MoveToFolderSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: ScarfSpace.s3) {
|
||||||
Text("Move \"\(project.name)\" to folder").font(.headline)
|
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.")
|
Text("Folders only affect how projects are grouped in Scarf's sidebar. Nothing on disk changes.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
Picker("Destination", selection: $mode) {
|
Picker("Destination", selection: $mode) {
|
||||||
@@ -77,8 +80,7 @@ struct MoveToFolderSheet: View {
|
|||||||
.pickerStyle(.inline)
|
.pickerStyle(.inline)
|
||||||
|
|
||||||
if case .new = mode {
|
if case .new = mode {
|
||||||
TextField("New folder name", text: $newFolderName)
|
ScarfTextField("New folder name", text: $newFolderName)
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
if canMove { commit() }
|
if canMove { commit() }
|
||||||
}
|
}
|
||||||
@@ -86,15 +88,16 @@ struct MoveToFolderSheet: View {
|
|||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
|
.buttonStyle(ScarfGhostButton())
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Move") { commit() }
|
Button("Move") { commit() }
|
||||||
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
.disabled(!canMove)
|
.disabled(!canMove)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding(ScarfSpace.s5)
|
||||||
.frame(minWidth: 420, minHeight: 320)
|
.frame(minWidth: 420, minHeight: 320)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Per-project Sessions tab (v2.3). Lives beside the Dashboard and
|
/// Per-project Sessions tab (v2.3). Lives beside the Dashboard and
|
||||||
/// Site tabs in the project view; populated from the session
|
/// Site tabs in the project view; populated from the session
|
||||||
@@ -59,9 +60,9 @@ struct ProjectSessionsView: View {
|
|||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Sessions in this project")
|
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.")
|
Text("Chats you start here get attributed automatically. Older CLI-started sessions live in the global Sessions sidebar.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
@@ -75,7 +76,7 @@ struct ProjectSessionsView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("New Chat", systemImage: "message.badge.filled.fill")
|
Label("New Chat", systemImage: "message.badge.filled.fill")
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// The "Slash Commands" tab on the per-project surface. Lists the
|
/// The "Slash Commands" tab on the per-project surface. Lists the
|
||||||
/// project-scoped commands stored at `<project>/.scarf/slash-commands/`
|
/// project-scoped commands stored at `<project>/.scarf/slash-commands/`
|
||||||
@@ -52,9 +53,9 @@ struct ProjectSlashCommandsView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text("Slash Commands")
|
Text("Slash Commands")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Text("`/<name>` shortcuts that expand into prompt templates. Stored at `<project>/.scarf/slash-commands/` so they ship with `.scarftemplate` bundles.")
|
Text("`/<name>` shortcuts that expand into prompt templates. Stored at `<project>/.scarf/slash-commands/` so they ship with `.scarftemplate` bundles.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(2)
|
.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.")
|
Text("Add reusable prompt templates here. Each command shows up in the chat slash menu when you're chatting in this project.")
|
||||||
} actions: {
|
} actions: {
|
||||||
Button("Add Command") { viewModel.beginNew() }
|
Button("Add Command") { viewModel.beginNew() }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +118,7 @@ struct ProjectSlashCommandsView: View {
|
|||||||
Text("Couldn't update slash commands")
|
Text("Couldn't update slash commands")
|
||||||
.font(.subheadline.weight(.semibold))
|
.font(.subheadline.weight(.semibold))
|
||||||
Text(message)
|
Text(message)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
@@ -165,7 +166,7 @@ private struct CommandRow: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(command.description)
|
Text(command.description)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
if let tags = command.tags, !tags.isEmpty {
|
if let tags = command.tags, !tags.isEmpty {
|
||||||
@@ -208,7 +209,7 @@ struct SlashCommandEditorSheet: View {
|
|||||||
Button("Save") {
|
Button("Save") {
|
||||||
Task { await viewModel.saveDraft() }
|
Task { await viewModel.saveDraft() }
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.disabled(saveDisabled)
|
.disabled(saveDisabled)
|
||||||
}
|
}
|
||||||
@@ -244,7 +245,7 @@ struct SlashCommandEditorSheet: View {
|
|||||||
.help("Lowercase letters, digits, and hyphens. Must start with a letter.")
|
.help("Lowercase letters, digits, and hyphens. Must start with a letter.")
|
||||||
if let nameError = nameValidationMessage {
|
if let nameError = nameValidationMessage {
|
||||||
Text(nameError)
|
Text(nameError)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
}
|
}
|
||||||
TextField("Description", text: Binding(
|
TextField("Description", text: Binding(
|
||||||
@@ -277,7 +278,7 @@ struct SlashCommandEditorSheet: View {
|
|||||||
|
|
||||||
Section("Prompt template") {
|
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.")
|
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)
|
.foregroundStyle(.secondary)
|
||||||
TextEditor(text: Binding(
|
TextEditor(text: Binding(
|
||||||
get: { viewModel.draft?.body ?? "" },
|
get: { viewModel.draft?.body ?? "" },
|
||||||
@@ -305,12 +306,12 @@ struct SlashCommandEditorSheet: View {
|
|||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Preview")
|
Text("Preview")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Text("Sample argument")
|
Text("Sample argument")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
TextField("(empty)", text: $sampleArgument)
|
TextField("(empty)", text: $sampleArgument)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@@ -324,7 +325,7 @@ struct SlashCommandEditorSheet: View {
|
|||||||
.textSelection(.enabled)
|
.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 `<!-- scarf-slash:<name> -->` marker.")
|
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 `<!-- scarf-slash:<name> -->` marker.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Sidebar view for the Projects feature. Renders the registry as:
|
/// Sidebar view for the Projects feature. Renders the registry as:
|
||||||
/// - A search field at the top (⌘F focus).
|
/// - A search field at the top (⌘F focus).
|
||||||
@@ -68,18 +69,18 @@ struct ProjectsSidebar: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "magnifyingglass")
|
Image(systemName: "magnifyingglass")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
TextField("Filter projects", text: $filterText)
|
TextField("Filter projects", text: $filterText)
|
||||||
.textFieldStyle(.plain)
|
.textFieldStyle(.plain)
|
||||||
.focused($searchFocused)
|
.focused($searchFocused)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
if !filterText.isEmpty {
|
if !filterText.isEmpty {
|
||||||
Button {
|
Button {
|
||||||
filterText = ""
|
filterText = ""
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
.foregroundStyle(.tertiary)
|
.foregroundStyle(.tertiary)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
}
|
}
|
||||||
@@ -122,7 +123,7 @@ struct ProjectsSidebar: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(folder, systemImage: "folder")
|
Label(folder, systemImage: "folder")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,7 +138,7 @@ struct ProjectsSidebar: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Archived (\(archivedVisible.count))", systemImage: "archivebox")
|
Label("Archived (\(archivedVisible.count))", systemImage: "archivebox")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +209,7 @@ struct ProjectsSidebar: View {
|
|||||||
|
|
||||||
Toggle(isOn: $showArchived) {
|
Toggle(isOn: $showArchived) {
|
||||||
Image(systemName: showArchived ? "archivebox.fill" : "archivebox")
|
Image(systemName: showArchived ? "archivebox.fill" : "archivebox")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
}
|
}
|
||||||
.toggleStyle(.button)
|
.toggleStyle(.button)
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Sheet for renaming a project in the registry. Preserves the
|
/// Sheet for renaming a project in the registry. Preserves the
|
||||||
/// project's `path`, `folder`, and `archived` fields — the rename
|
/// project's `path`, `folder`, and `archived` fields — the rename
|
||||||
@@ -45,15 +46,16 @@ struct RenameProjectSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: ScarfSpace.s3) {
|
||||||
Text("Rename project").font(.headline)
|
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.")
|
Text("The project directory on disk isn't changed — only the label Scarf shows in the sidebar.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
TextField("Project name", text: $newName)
|
ScarfTextField("Project name", text: $newName)
|
||||||
.textFieldStyle(.roundedBorder)
|
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
if validation.isValid {
|
if validation.isValid {
|
||||||
save()
|
save()
|
||||||
@@ -62,21 +64,22 @@ struct RenameProjectSheet: View {
|
|||||||
|
|
||||||
if let message = validation.message {
|
if let message = validation.message {
|
||||||
Label(message, systemImage: "exclamationmark.triangle.fill")
|
Label(message, systemImage: "exclamationmark.triangle.fill")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(ScarfColor.danger)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
|
.buttonStyle(ScarfGhostButton())
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Save") { save() }
|
Button("Save") { save() }
|
||||||
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
.disabled(!validation.isValid)
|
.disabled(!validation.isValid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding(ScarfSpace.s5)
|
||||||
.frame(minWidth: 420)
|
.frame(minWidth: 420)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import Charts
|
import Charts
|
||||||
|
|
||||||
// Flattened data point for Charts to avoid complex nested generic inference
|
// Flattened data point for Charts to avoid complex nested generic inference
|
||||||
@@ -27,15 +28,15 @@ struct ChartWidgetView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
chartContent
|
chartContent
|
||||||
.frame(height: 150)
|
.frame(height: 150)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct ListWidgetView: View {
|
struct ListWidgetView: View {
|
||||||
let widget: DashboardWidget
|
let widget: DashboardWidget
|
||||||
@@ -10,10 +11,10 @@ struct ListWidgetView: View {
|
|||||||
if let icon = widget.icon {
|
if let icon = widget.icon {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
}
|
}
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
if let items = widget.items {
|
if let items = widget.items {
|
||||||
@@ -32,8 +33,8 @@ struct ListWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func statusIcon(_ status: String?) -> String {
|
private func statusIcon(_ status: String?) -> String {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct ProgressWidgetView: View {
|
struct ProgressWidgetView: View {
|
||||||
let widget: DashboardWidget
|
let widget: DashboardWidget
|
||||||
@@ -14,7 +15,7 @@ struct ProgressWidgetView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
ProgressView(value: progressValue) {
|
ProgressView(value: progressValue) {
|
||||||
if let label = widget.label {
|
if let label = widget.label {
|
||||||
@@ -27,7 +28,7 @@ struct ProgressWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct StatWidgetView: View {
|
struct StatWidgetView: View {
|
||||||
let widget: DashboardWidget
|
let widget: DashboardWidget
|
||||||
@@ -14,10 +15,10 @@ struct StatWidgetView: View {
|
|||||||
if let icon = widget.icon {
|
if let icon = widget.icon {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.foregroundStyle(widgetColor)
|
.foregroundStyle(widgetColor)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
}
|
}
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
if let value = widget.value {
|
if let value = widget.value {
|
||||||
@@ -32,7 +33,7 @@ struct StatWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct TableWidgetView: View {
|
struct TableWidgetView: View {
|
||||||
let widget: DashboardWidget
|
let widget: DashboardWidget
|
||||||
@@ -7,7 +8,7 @@ struct TableWidgetView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
if let columns = widget.columns, let rows = widget.rows {
|
if let columns = widget.columns, let rows = widget.rows {
|
||||||
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 4) {
|
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 4) {
|
||||||
@@ -32,7 +33,7 @@ struct TableWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct TextWidgetView: View {
|
struct TextWidgetView: View {
|
||||||
let widget: DashboardWidget
|
let widget: DashboardWidget
|
||||||
@@ -7,7 +8,7 @@ struct TextWidgetView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
if let content = widget.content {
|
if let content = widget.content {
|
||||||
if widget.format == "markdown" {
|
if widget.format == "markdown" {
|
||||||
@@ -20,7 +21,7 @@ struct TextWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
struct WebviewWidgetView: View {
|
struct WebviewWidgetView: View {
|
||||||
@@ -29,7 +30,7 @@ struct WebviewWidgetView: View {
|
|||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if let url = webURL {
|
if let url = webURL {
|
||||||
WebViewRepresentable(url: url)
|
WebViewRepresentable(url: url)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
} else {
|
} else {
|
||||||
ContentUnavailableView {
|
ContentUnavailableView {
|
||||||
Label("Invalid URL", systemImage: "globe")
|
Label("Invalid URL", systemImage: "globe")
|
||||||
@@ -49,10 +50,10 @@ struct WebviewWidgetView: View {
|
|||||||
if let icon = widget.icon {
|
if let icon = widget.icon {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
}
|
}
|
||||||
Text(widget.title)
|
Text(widget.title)
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Spacer()
|
Spacer()
|
||||||
if let urlString = widget.url {
|
if let urlString = widget.url {
|
||||||
@@ -76,8 +77,8 @@ struct WebviewWidgetView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(.quaternary.opacity(0.5))
|
.background(ScarfColor.backgroundSecondary)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct QuickCommandsView: View {
|
struct QuickCommandsView: View {
|
||||||
@State private var viewModel: QuickCommandsViewModel
|
@State private var viewModel: QuickCommandsViewModel
|
||||||
@@ -12,19 +13,22 @@ struct QuickCommandsView: View {
|
|||||||
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
VStack(spacing: 0) {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
header
|
||||||
header
|
ScrollView {
|
||||||
intro
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
if viewModel.commands.isEmpty {
|
intro
|
||||||
emptyState
|
if viewModel.commands.isEmpty {
|
||||||
} else {
|
emptyState
|
||||||
list
|
} else {
|
||||||
|
list
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
|
||||||
}
|
}
|
||||||
|
.background(ScarfColor.backgroundPrimary)
|
||||||
.navigationTitle("Quick Commands")
|
.navigationTitle("Quick Commands")
|
||||||
.onAppear { viewModel.load() }
|
.onAppear { viewModel.load() }
|
||||||
.sheet(isPresented: $showAddSheet) {
|
.sheet(isPresented: $showAddSheet) {
|
||||||
@@ -46,28 +50,33 @@ struct QuickCommandsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack {
|
ScarfPageHeader(
|
||||||
if let msg = viewModel.message {
|
"Quick Commands",
|
||||||
Label(msg, systemImage: "checkmark.circle.fill")
|
subtitle: "Shell shortcuts hermes exposes in chat as `/command_name`."
|
||||||
.font(.caption)
|
) {
|
||||||
.foregroundStyle(.green)
|
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()
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
Button {
|
|
||||||
showAddSheet = true
|
|
||||||
} label: {
|
|
||||||
Label("Add Command", systemImage: "plus")
|
|
||||||
}
|
|
||||||
.controlSize(.small)
|
|
||||||
Button("Reload") { viewModel.load() }
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var intro: some View {
|
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.")
|
Text("Stored under `quick_commands:` in config.yaml.")
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var emptyState: some View {
|
private var emptyState: some View {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Sheet for adding a new remote server. Collects SSH connection details,
|
/// Sheet for adding a new remote server. Collects SSH connection details,
|
||||||
/// runs a "Test Connection" probe, and — on save — hands the persisted
|
/// runs a "Test Connection" probe, and — on save — hands the persisted
|
||||||
@@ -36,7 +37,7 @@ struct AddServerSheet: View {
|
|||||||
Image(systemName: "server.rack")
|
Image(systemName: "server.rack")
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
Text("Add Remote Server")
|
Text("Add Remote Server")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Small colored pill shown in the toolbar reflecting the server's reach-
|
/// Small colored pill shown in the toolbar reflecting the server's reach-
|
||||||
/// ability. Green = connected, yellow = probing, red = unreachable.
|
/// ability. Green = connected, yellow = probing, red = unreachable.
|
||||||
@@ -33,8 +34,8 @@ struct ConnectionStatusPill: View {
|
|||||||
.foregroundStyle(color)
|
.foregroundStyle(color)
|
||||||
.symbolRenderingMode(.hierarchical)
|
.symbolRenderingMode(.hierarchical)
|
||||||
labelText
|
labelText
|
||||||
.font(.caption)
|
.scarfStyle(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
@@ -51,10 +52,10 @@ struct ConnectionStatusPill: View {
|
|||||||
|
|
||||||
private var color: Color {
|
private var color: Color {
|
||||||
switch status.status {
|
switch status.status {
|
||||||
case .connected: return .green
|
case .connected: return ScarfColor.success
|
||||||
case .degraded: return .orange
|
case .degraded: return ScarfColor.warning
|
||||||
case .idle: return .yellow
|
case .idle: return ScarfColor.warning.opacity(0.7)
|
||||||
case .error: return .red
|
case .error: return ScarfColor.danger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +102,14 @@ struct ConnectionStatusPill: View {
|
|||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
HStack {
|
HStack {
|
||||||
Label(message, systemImage: "xmark.octagon.fill")
|
Label(message, systemImage: "xmark.octagon.fill")
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(ScarfColor.danger)
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Retry") {
|
Button("Retry") {
|
||||||
status.retry()
|
status.retry()
|
||||||
showDetails = false
|
showDetails = false
|
||||||
}
|
}
|
||||||
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// List of registered remote servers with add/remove actions. Rendered as a
|
/// List of registered remote servers with add/remove actions. Rendered as a
|
||||||
/// popover from the toolbar switcher.
|
/// popover from the toolbar switcher.
|
||||||
@@ -59,7 +60,7 @@ struct ManageServersView: View {
|
|||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Servers").font(.headline)
|
Text("Servers").scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button {
|
Button {
|
||||||
showAddSheet = true
|
showAddSheet = true
|
||||||
@@ -76,7 +77,7 @@ struct ManageServersView: View {
|
|||||||
Image(systemName: "server.rack")
|
Image(systemName: "server.rack")
|
||||||
.font(.system(size: 28))
|
.font(.system(size: 28))
|
||||||
.foregroundStyle(.secondary)
|
.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.")
|
Text("Click Add to connect to a remote Hermes installation over SSH.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Shown when a window is restored after the user removed the server it
|
/// 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
|
/// was bound to. Lets them open Local or any remaining registered server
|
||||||
@@ -33,7 +34,7 @@ struct MissingServerView: View {
|
|||||||
openWindow(value: ServerContext.local.id)
|
openWindow(value: ServerContext.local.id)
|
||||||
dismissWindow()
|
dismissWindow()
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
|
|
||||||
if !registry.entries.isEmpty {
|
if !registry.entries.isEmpty {
|
||||||
Menu {
|
Menu {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Toolbar control that shows the current window's server and exposes a
|
/// Toolbar control that shows the current window's server and exposes a
|
||||||
/// menu for opening *other* servers in additional windows. Multi-window is
|
/// menu for opening *other* servers in additional windows. Multi-window is
|
||||||
@@ -36,13 +37,15 @@ struct ServerSwitcherToolbar: View {
|
|||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
Circle()
|
Circle()
|
||||||
.fill(current.isRemote ? Color.blue : Color.green)
|
.fill(current.isRemote ? ScarfColor.info : ScarfColor.success)
|
||||||
.frame(width: 8, height: 8)
|
.frame(width: 8, height: 8)
|
||||||
Text(verbatim: current.displayName)
|
Text(verbatim: current.displayName)
|
||||||
.font(.callout)
|
.scarfStyle(.callout)
|
||||||
|
.foregroundStyle(ScarfColor.foregroundPrimary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
Image(systemName: "chevron.down")
|
Image(systemName: "chevron.down")
|
||||||
.font(.caption2)
|
.font(.system(size: 10))
|
||||||
|
.foregroundStyle(ScarfColor.foregroundMuted)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Two-column model browser sheet. Left column lists providers, right column
|
/// Two-column model browser sheet. Left column lists providers, right column
|
||||||
/// lists models for the selected provider. Supports filtering and a "Custom…"
|
/// lists models for the selected provider. Supports filtering and a "Custom…"
|
||||||
@@ -109,7 +110,7 @@ struct ModelPickerSheet: View {
|
|||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image(systemName: "cpu")
|
Image(systemName: "cpu")
|
||||||
Text("Select Model")
|
Text("Select Model")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
if !customMode {
|
if !customMode {
|
||||||
TextField("Search…", text: $searchText)
|
TextField("Search…", text: $searchText)
|
||||||
@@ -294,7 +295,7 @@ struct ModelPickerSheet: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("Sign in to Nous Portal", systemImage: "person.badge.key.fill")
|
Label("Sign in to Nous Portal", systemImage: "person.badge.key.fill")
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.controlSize(.regular)
|
.controlSize(.regular)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -361,7 +362,7 @@ struct ModelPickerSheet: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Cancel") { onCancel() }
|
Button("Cancel") { onCancel() }
|
||||||
Button("Select") { submitSelection() }
|
Button("Select") { submitSelection() }
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.disabled(!canSubmit)
|
.disabled(!canSubmit)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// In-app sign-in sheet for Nous Portal — hosts a ``NousAuthFlow`` and
|
/// 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
|
/// 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")
|
Image(systemName: "person.badge.key.fill")
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
Text("Sign in to Nous Portal")
|
Text("Sign in to Nous Portal")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
if case .waitingForApproval = flowState {
|
if case .waitingForApproval = flowState {
|
||||||
Button("Cancel") { dismiss() }
|
Button("Cancel") { dismiss() }
|
||||||
@@ -123,7 +124,7 @@ struct NousSignInSheet: View {
|
|||||||
VStack(alignment: .leading, spacing: 14) {
|
VStack(alignment: .leading, spacing: 14) {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("Approve in your browser")
|
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.")
|
Text("We opened the Nous Portal approval page. Confirm this code matches what it shows, then approve.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -176,7 +177,7 @@ struct NousSignInSheet: View {
|
|||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
.font(.system(size: 48))
|
.font(.system(size: 48))
|
||||||
Text("Signed in to Nous Portal")
|
Text("Signed in to Nous Portal")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Text("Your tools will now route through your subscription.")
|
Text("Your tools will now route through your subscription.")
|
||||||
.font(.callout)
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -193,7 +194,7 @@ struct NousSignInSheet: View {
|
|||||||
Image(systemName: "exclamationmark.triangle.fill")
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
Text(billingURL == nil ? "Sign-in didn't complete" : "Subscription required")
|
Text(billingURL == nil ? "Sign-in didn't complete" : "Subscription required")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(reason)
|
Text(reason)
|
||||||
@@ -209,7 +210,7 @@ struct NousSignInSheet: View {
|
|||||||
Label("Subscribe", systemImage: "creditcard")
|
Label("Subscribe", systemImage: "creditcard")
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.controlSize(.large)
|
.controlSize(.large)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
struct SkillsView: View {
|
struct SkillsView: View {
|
||||||
@State private var viewModel: SkillsViewModel
|
@State private var viewModel: SkillsViewModel
|
||||||
@@ -38,6 +39,10 @@ struct SkillsView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
ScarfPageHeader(
|
||||||
|
"Skills",
|
||||||
|
subtitle: "Pre-packaged prompt collections the agent can call into. \(viewModel.totalSkillCount) installed."
|
||||||
|
)
|
||||||
modePicker
|
modePicker
|
||||||
// v2.5 "What's New" pill — only renders when the diff has
|
// v2.5 "What's New" pill — only renders when the diff has
|
||||||
// changes against a non-empty prior snapshot (first launch
|
// changes against a non-empty prior snapshot (first launch
|
||||||
@@ -55,7 +60,8 @@ struct SkillsView: View {
|
|||||||
case .updates: updatesContent
|
case .updates: updatesContent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Skills (\(viewModel.totalSkillCount))")
|
.background(ScarfColor.backgroundPrimary)
|
||||||
|
.navigationTitle("Skills")
|
||||||
// SkillsViewModel.load() is async after the v2.5 ScarfCore
|
// SkillsViewModel.load() is async after the v2.5 ScarfCore
|
||||||
// promotion. Wrap in a Task here so the existing onAppear
|
// promotion. Wrap in a Task here so the existing onAppear
|
||||||
// contract (fire-and-forget) keeps working without making
|
// contract (fire-and-forget) keeps working without making
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Post-install configuration editor. Thin wrapper around the same
|
/// Post-install configuration editor. Thin wrapper around the same
|
||||||
@@ -73,7 +74,7 @@ struct ConfigEditorSheet: View {
|
|||||||
Text("Configuration saved").font(.title2.bold())
|
Text("Configuration saved").font(.title2.bold())
|
||||||
Button("Done") { dismiss() }
|
Button("Done") { dismiss() }
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.frame(minWidth: 560, minHeight: 280)
|
.frame(minWidth: 560, minHeight: 280)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// The configure form rendered for template install + post-install
|
/// 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.
|
// handles dismissal via the success view's Done button.
|
||||||
}
|
}
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
}
|
}
|
||||||
@@ -117,10 +118,10 @@ struct TemplateConfigSheet: View {
|
|||||||
private func fieldRow(_ field: TemplateConfigField) -> some View {
|
private func fieldRow(_ field: TemplateConfigField) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||||
Text(field.label).font(.headline)
|
Text(field.label).scarfStyle(.headline)
|
||||||
if field.required {
|
if field.required {
|
||||||
Text("*")
|
Text("*")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import SwiftUI
|
|||||||
import AppKit
|
import AppKit
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
|
|
||||||
/// Author-facing sheet for exporting an existing project as a
|
/// Author-facing sheet for exporting an existing project as a
|
||||||
/// `.scarftemplate`. Mirrors the profile-export flow: fill in a few fields,
|
/// `.scarftemplate`. Mirrors the profile-export flow: fill in a few fields,
|
||||||
@@ -89,7 +90,7 @@ struct TemplateExportSheet: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Button("Export…") { runExport() }
|
Button("Export…") { runExport() }
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.disabled(!canExport)
|
.disabled(!canExport)
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
@@ -97,7 +98,7 @@ struct TemplateExportSheet: View {
|
|||||||
|
|
||||||
private var metadataGroup: some View {
|
private var metadataGroup: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text("Metadata").font(.headline)
|
Text("Metadata").scarfStyle(.headline)
|
||||||
LabeledContent("Template ID") {
|
LabeledContent("Template ID") {
|
||||||
TextField("owner/name", text: $viewModel.templateId)
|
TextField("owner/name", text: $viewModel.templateId)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
@@ -137,7 +138,7 @@ struct TemplateExportSheet: View {
|
|||||||
private var requiredFilesGroup: some View {
|
private var requiredFilesGroup: some View {
|
||||||
let plan = viewModel.previewPlan()
|
let plan = viewModel.previewPlan()
|
||||||
return VStack(alignment: .leading, spacing: 6) {
|
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: "dashboard.json (\(plan.projectDir)/.scarf/dashboard.json)", ok: plan.dashboardPresent)
|
||||||
check(label: "README.md (\(plan.projectDir)/README.md)", ok: plan.readmePresent)
|
check(label: "README.md (\(plan.projectDir)/README.md)", ok: plan.readmePresent)
|
||||||
check(label: "AGENTS.md (\(plan.projectDir)/AGENTS.md)", ok: plan.agentsMdPresent)
|
check(label: "AGENTS.md (\(plan.projectDir)/AGENTS.md)", ok: plan.agentsMdPresent)
|
||||||
@@ -147,7 +148,7 @@ struct TemplateExportSheet: View {
|
|||||||
private var instructionsGroup: some View {
|
private var instructionsGroup: some View {
|
||||||
let plan = viewModel.previewPlan()
|
let plan = viewModel.previewPlan()
|
||||||
return VStack(alignment: .leading, spacing: 4) {
|
return VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("Agent-specific instructions (optional)").font(.headline)
|
Text("Agent-specific instructions (optional)").scarfStyle(.headline)
|
||||||
if plan.instructionFiles.isEmpty {
|
if plan.instructionFiles.isEmpty {
|
||||||
Text("No per-agent instruction files found in the project root.")
|
Text("No per-agent instruction files found in the project root.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -163,7 +164,7 @@ struct TemplateExportSheet: View {
|
|||||||
|
|
||||||
private var skillsGroup: some View {
|
private var skillsGroup: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text("Include Skills").font(.headline)
|
Text("Include Skills").scarfStyle(.headline)
|
||||||
if viewModel.availableSkills.isEmpty {
|
if viewModel.availableSkills.isEmpty {
|
||||||
Text("No skills found.")
|
Text("No skills found.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -186,7 +187,7 @@ struct TemplateExportSheet: View {
|
|||||||
|
|
||||||
private var cronGroup: some View {
|
private var cronGroup: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
Text("Include Cron Jobs").font(.headline)
|
Text("Include Cron Jobs").scarfStyle(.headline)
|
||||||
if viewModel.availableCronJobs.isEmpty {
|
if viewModel.availableCronJobs.isEmpty {
|
||||||
Text("No cron jobs found.")
|
Text("No cron jobs found.")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -214,7 +215,7 @@ struct TemplateExportSheet: View {
|
|||||||
|
|
||||||
private var memoryGroup: some View {
|
private var memoryGroup: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
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.")
|
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)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Preview-and-confirm sheet for installing a `.scarftemplate`. Honest
|
/// Preview-and-confirm sheet for installing a `.scarftemplate`. Honest
|
||||||
@@ -47,7 +48,7 @@ struct TemplateInstallSheet: View {
|
|||||||
private var idleView: some View {
|
private var idleView: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Text("No template loaded.")
|
Text("No template loaded.")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Button("Close") { dismiss() }
|
Button("Close") { dismiss() }
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
@@ -70,7 +71,7 @@ struct TemplateInstallSheet: View {
|
|||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
Text("Where should this project live?")
|
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.")
|
Text("Scarf will create a new folder inside the directory you pick, named after the template id.")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -169,7 +170,7 @@ struct TemplateInstallSheet: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Button("Install") { viewModel.confirmInstall() }
|
Button("Install") { viewModel.confirmInstall() }
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
}
|
}
|
||||||
@@ -353,7 +354,7 @@ struct TemplateInstallSheet: View {
|
|||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func section<Content: View>(title: String, subtitle: String?, @ViewBuilder content: () -> Content) -> some View {
|
private func section<Content: View>(title: String, subtitle: String?, @ViewBuilder content: () -> Content) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(title).font(.headline)
|
Text(title).scarfStyle(.headline)
|
||||||
if let subtitle {
|
if let subtitle {
|
||||||
Text(subtitle)
|
Text(subtitle)
|
||||||
.font(.caption.monospaced())
|
.font(.caption.monospaced())
|
||||||
@@ -391,7 +392,7 @@ struct TemplateInstallSheet: View {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Preview-and-confirm sheet for uninstalling a template-installed
|
/// Preview-and-confirm sheet for uninstalling a template-installed
|
||||||
@@ -41,7 +42,7 @@ struct TemplateUninstallSheet: View {
|
|||||||
private var idleView: some View {
|
private var idleView: some View {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: 16) {
|
||||||
Text("No template loaded.")
|
Text("No template loaded.")
|
||||||
.font(.headline)
|
.scarfStyle(.headline)
|
||||||
Button("Close") { dismiss() }
|
Button("Close") { dismiss() }
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
@@ -87,7 +88,7 @@ struct TemplateUninstallSheet: View {
|
|||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Button("Remove") { viewModel.confirmUninstall() }
|
Button("Remove") { viewModel.confirmUninstall() }
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
.tint(.red)
|
.tint(.red)
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
@@ -244,7 +245,7 @@ struct TemplateUninstallSheet: View {
|
|||||||
@ViewBuilder content: () -> Content
|
@ViewBuilder content: () -> Content
|
||||||
) -> some View {
|
) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(title).font(.headline)
|
Text(title).scarfStyle(.headline)
|
||||||
if let subtitle {
|
if let subtitle {
|
||||||
Text(subtitle)
|
Text(subtitle)
|
||||||
.font(.caption.monospaced())
|
.font(.caption.monospaced())
|
||||||
@@ -296,7 +297,7 @@ struct TemplateUninstallSheet: View {
|
|||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.keyboardShortcut(.defaultAction)
|
.keyboardShortcut(.defaultAction)
|
||||||
.buttonStyle(.borderedProminent)
|
.buttonStyle(ScarfPrimaryButton())
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.padding()
|
.padding()
|
||||||
@@ -316,7 +317,7 @@ struct TemplateUninstallSheet: View {
|
|||||||
Image(systemName: "folder.badge.questionmark")
|
Image(systemName: "folder.badge.questionmark")
|
||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
Text("Project folder kept")
|
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.")
|
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)
|
.font(.caption)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ScarfCore
|
import ScarfCore
|
||||||
|
import ScarfDesign
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
struct WebhooksView: View {
|
struct WebhooksView: View {
|
||||||
@@ -25,7 +26,6 @@ struct WebhooksView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
header
|
header
|
||||||
Divider()
|
|
||||||
if viewModel.isLoading && viewModel.webhooks.isEmpty {
|
if viewModel.isLoading && viewModel.webhooks.isEmpty {
|
||||||
ProgressView().padding()
|
ProgressView().padding()
|
||||||
} else if viewModel.webhookPlatformNotEnabled {
|
} else if viewModel.webhookPlatformNotEnabled {
|
||||||
@@ -36,6 +36,7 @@ struct WebhooksView: View {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.background(ScarfColor.backgroundPrimary)
|
||||||
.navigationTitle("Webhooks")
|
.navigationTitle("Webhooks")
|
||||||
.onAppear { viewModel.load() }
|
.onAppear { viewModel.load() }
|
||||||
.sheet(isPresented: $showAddSheet) { addSheet }
|
.sheet(isPresented: $showAddSheet) { addSheet }
|
||||||
@@ -52,25 +53,28 @@ struct WebhooksView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
HStack {
|
ScarfPageHeader(
|
||||||
if let msg = viewModel.message {
|
"Webhooks",
|
||||||
Label(msg, systemImage: "info.circle.fill")
|
subtitle: "HTTP receivers that trigger sessions on incoming events."
|
||||||
.font(.caption)
|
) {
|
||||||
.foregroundStyle(.green)
|
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()
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
Button {
|
|
||||||
resetAddForm()
|
|
||||||
showAddSheet = true
|
|
||||||
} label: {
|
|
||||||
Label("Subscribe", systemImage: "plus")
|
|
||||||
}
|
|
||||||
.controlSize(.small)
|
|
||||||
Button("Reload") { viewModel.load() }
|
|
||||||
.controlSize(.small)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
|
||||||
.padding(.vertical, 8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shown when hermes reports the webhook platform isn't enabled. Direct users
|
/// Shown when hermes reports the webhook platform isn't enabled. Direct users
|
||||||
|
|||||||
Reference in New Issue
Block a user