feat(ios): adopt ScarfDesign across the iOS app

AccentColor.colorset repointed to BrandRust hex (light + dark) so the
tab bar, every .tint, every default button, and every navigation
accent across all 5 tabs read rust automatically. Single-line fix,
biggest visible change.

ScarfDesign now imported across all 27 iOS view files. Color sweep
applies the same patterns as the Mac side, with two iOS-specific
deviations documented in CLAUDE.md:

- ScarfPageHeader is NOT retrofitted onto iOS tab roots. iOS uses
  .navigationTitle(...) + .navigationBarTitleDisplayMode(.large) as
  its native page-header pattern; stacking ScarfPageHeader on top
  creates double titles. ScarfPageHeader is reserved for sub-views
  without a native large-title bar.

- Only .borderedProminent → ScarfPrimaryButton. .bordered and .plain
  stay native because .bordered is the iOS convention for non-primary
  buttons and inherits rust through AccentColor automatically.

Dynamic Type policy (locked + documented in CLAUDE.md): preserve
.font(.headline)/.body/.caption semantic tokens for body copy, list
rows, error messages, and chat content (anything read for content).
Use ScarfFont only for status badges, chip labels, intentional fixed-
size display elements. Mass-swapping ScarfFont on iOS would regress
accessibility for users on .accessibility2 / .xSmall.

Files touched (27 view files + AccentColor + CLAUDE.md):

- Color sweep: .foregroundStyle(.secondary) → ScarfColor.foregroundMuted,
  Color(.secondarySystemBackground) → ScarfColor.backgroundSecondary,
  status colors (.orange/.green/.red) → ScarfColor.warning/success/danger
  in: Dashboard, Skills (root + Installed + Hub + Updates + Detail),
  Projects (root + Detail + Sessions + Site + 8 widgets), Memory
  (List + Editor), Cron, Settings (root + Editor), Servers, Chat
  (root + Picker + Slash browser), Onboarding.

- Primary button swap (5 files): Chat, Projects/Sessions, Skills/
  Updates, Skills/Hub, Onboarding.

- CLAUDE.md: appended "iOS Dynamic Type policy" + "iOS page chrome"
  guidance under the existing Design System section.

Both Mac (scarf) and iOS (scarf mobile) schemes build green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-25 15:08:46 +02:00
parent 23dd8becb9
commit de611c5343
30 changed files with 141 additions and 96 deletions
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.722",
"green" : "0.525",
"red" : "0.227"
"blue" : "0.165",
"green" : "0.353",
"red" : "0.761"
}
},
"idiom" : "universal"
@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.902",
"green" : "0.690",
"red" : "0.400"
"blue" : "0.376",
"green" : "0.576",
"red" : "0.910"
}
},
"idiom" : "universal"
+22 -21
View File
@@ -1,6 +1,7 @@
import SwiftUI
import ScarfCore
import ScarfIOS
import ScarfDesign
import os
// The Chat feature on iOS is gated on `canImport(SQLite3)` because
@@ -205,7 +206,7 @@ struct ChatView: View {
ProgressView()
Text("Agent is thinking…")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
@@ -241,7 +242,7 @@ struct ChatView: View {
.foregroundStyle(.tertiary)
Text("Ask Hermes something")
.font(.headline)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text("Connected to \(config.displayName)")
.font(.caption)
.foregroundStyle(.tertiary)
@@ -265,7 +266,7 @@ struct ChatView: View {
.foregroundStyle(.tertiary)
Text("Session resumed")
.font(.headline)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text("Hermes has the context for this session, but the transcript isn't cached locally. Send a message to continue.")
.font(.caption)
.foregroundStyle(.tertiary)
@@ -349,7 +350,7 @@ struct ChatView: View {
}
Text(err)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
.lineLimit(showErrorDetails ? nil : 2)
}
@@ -380,7 +381,7 @@ struct ChatView: View {
ScrollView(.vertical) {
Text(details)
.font(.caption2.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -413,7 +414,7 @@ struct ChatView: View {
VStack(alignment: .leading, spacing: 1) {
Text("Project chat")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
HStack(spacing: 6) {
Text(projectName)
.font(.callout.weight(.medium))
@@ -475,7 +476,7 @@ struct ChatView: View {
.controlSize(.large)
Text("Connecting to \(config.displayName)")
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.padding(24)
.background(.regularMaterial)
@@ -493,12 +494,12 @@ struct ChatView: View {
Text(message)
.font(.callout)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.padding(.horizontal)
Button("Retry") {
Task { await controller.start() }
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
}
.padding()
.background(.regularMaterial)
@@ -1090,7 +1091,7 @@ private struct MessageBubble: View {
.padding(.horizontal, 12)
.padding(.vertical, 8)
.foregroundStyle(Color.primary)
.background(Color(.secondarySystemBackground))
.background(ScarfColor.backgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 14))
.contextMenu { messageContextMenu }
}
@@ -1154,7 +1155,7 @@ private struct CodeBlockView: View {
if let lang = language, !lang.isEmpty {
Text(lang.uppercased())
.font(.caption2.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
Spacer()
Button(expanded ? "Collapse" : "Expand") {
@@ -1170,7 +1171,7 @@ private struct CodeBlockView: View {
.font(.caption2)
}
.buttonStyle(.plain)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
ScrollView(.horizontal, showsIndicators: true) {
@@ -1200,14 +1201,14 @@ private struct ReasoningDisclosure: View {
DisclosureGroup(isExpanded: $isExpanded) {
Text(reasoning)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.italic()
.textSelection(.enabled)
.padding(.top, 4)
} label: {
Label("Thinking…", systemImage: "brain")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.padding(.horizontal, 6)
}
@@ -1232,7 +1233,7 @@ private struct ToolCallCard: View {
.foregroundStyle(.primary)
Text(call.argumentsSummary.prefix(60))
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(1)
Spacer()
Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
@@ -1245,7 +1246,7 @@ private struct ToolCallCard: View {
if isExpanded {
Text(call.arguments)
.font(.caption2.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
.padding(.top, 2)
}
@@ -1281,10 +1282,10 @@ private struct ToolResultRow: View {
HStack(spacing: 6) {
Image(systemName: "arrow.turn.down.right")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text("Tool output")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text(message.content.prefix(80))
.font(.caption2)
.foregroundStyle(.tertiary)
@@ -1299,7 +1300,7 @@ private struct ToolResultRow: View {
if isExpanded {
Text(message.content)
.font(.caption2.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
.padding(.top, 2)
}
@@ -1336,7 +1337,7 @@ private struct PermissionSheet: View {
.textSelection(.enabled)
Text("Kind: \(permission.kind)")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.padding(.vertical, 4)
}
@@ -1358,7 +1359,7 @@ private struct PermissionSheet: View {
if idx < 9 {
Text("\(idx + 1).")
.font(.body.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
Text(opt.name)
Spacer()
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Sheet presented from ChatView's "+" toolbar button. Offers two
/// modes:
@@ -45,7 +46,7 @@ struct ProjectPickerSheet: View {
.foregroundStyle(.primary)
Text("No project — agent runs in your home directory.")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
}
@@ -64,7 +65,7 @@ struct ProjectPickerSheet: View {
} else if projects.isEmpty {
Text("No Scarf projects registered yet. Create one in the Mac app's Projects sidebar.")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
} else {
ForEach(sortedVisibleProjects) { project in
Button {
@@ -82,7 +83,7 @@ struct ProjectPickerSheet: View {
.foregroundStyle(.primary)
Text(project.path)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(1)
.truncationMode(.middle)
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Read-only sheet that lists every project-scoped slash command
/// available in the current project chat. Surfaced from the chat
@@ -62,16 +63,16 @@ private struct CommandRow: View {
if let hint = command.argumentHint, !hint.isEmpty {
Text(hint)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
Spacer()
Image(systemName: "chevron.right")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
Text(command.description)
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(2)
if let model = command.model, !model.isEmpty {
Label(model, systemImage: "cpu")
@@ -130,7 +131,7 @@ private struct CommandDetailSheet: View {
.textSelection(.enabled)
Text("`{{argument}}` is replaced with whatever you type after `/\(command.name)`. The agent receives the expanded body — never the literal slash.")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
}
.padding()
@@ -149,7 +150,7 @@ private struct CommandDetailSheet: View {
VStack(alignment: .leading, spacing: 2) {
Text(label)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text(value)
.font(mono ? .system(.body, design: .monospaced) : .body)
}
+4 -3
View File
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// iOS Cron screen. M6 gained: toggle-enabled, swipe-to-delete,
/// "+" toolbar editor sheet, and row-tap edit existing job.
@@ -36,7 +37,7 @@ struct CronListView: View {
.font(.headline)
Text("Tap \(Image(systemName: "plus.circle.fill")) to create one, or manage them from the Mac app.")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.padding(.vertical, 4)
}
@@ -130,7 +131,7 @@ private struct CronRow: View {
Text("DISABLED")
.font(.caption2)
.fontWeight(.bold)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.padding(.horizontal, 4)
.padding(.vertical, 1)
.background(Color(.secondarySystemFill))
@@ -139,7 +140,7 @@ private struct CronRow: View {
}
Text(CronScheduleFormatter.humanReadable(from: job.schedule))
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text("Next: \(CronScheduleFormatter.formatNextRun(iso: job.nextRunAt))")
.font(.caption2)
.foregroundStyle(.tertiary)
@@ -1,6 +1,7 @@
import SwiftUI
import ScarfCore
import ScarfIOS
import ScarfDesign
/// iOS Dashboard shows session count, token usage, cost, and the
/// last 5 sessions pulled from the remote Hermes SQLite snapshot.
@@ -82,11 +83,11 @@ struct DashboardView: View {
SwiftUI.Section {
VStack(alignment: .leading, spacing: 8) {
Label("Connection issue", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.foregroundStyle(ScarfColor.warning)
.font(.headline)
Text(err)
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Button("Retry") {
Task { await vm.refresh() }
}
@@ -214,16 +215,16 @@ struct DashboardView: View {
HStack(spacing: 12) {
Label(session.source, systemImage: session.sourceIcon)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
if let started = session.startedAt {
Text(started, format: .relative(presentation: .numeric))
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
if session.apiCallCount > 0 {
Label("\(session.apiCallCount)", systemImage: "network")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
if let projectName = vm.projectName(for: session) {
@@ -248,7 +249,7 @@ struct DashboardView: View {
LabeledContent(label) {
Text(value)
.monospacedDigit()
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Editor for a single memory file (MEMORY.md / USER.md / SOUL.md).
/// Owns an `IOSMemoryViewModel` instance, renders its `text` in a
@@ -67,7 +68,7 @@ struct MemoryEditorView: View {
.foregroundStyle(.orange)
Text(err)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Spacer()
}
.padding(.horizontal, 12)
+2 -1
View File
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Entry screen for the Memory feature. Three rows: MEMORY.md,
/// USER.md, and SOUL.md (persona). SOUL lives in the Personalities
@@ -124,7 +125,7 @@ struct MemoryListView: View {
.fontWeight(.medium)
Text(kind.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
.padding(.vertical, 4)
@@ -1,6 +1,7 @@
import SwiftUI
import ScarfCore
import ScarfIOS
import ScarfDesign
/// Owns the `OnboardingViewModel` and renders the current step.
/// Each step gets its own small view; the view switch is driven by
@@ -129,7 +130,7 @@ private struct KeySourceStep: View {
.bold()
Text("Scarf authenticates to your Hermes host with an SSH key. You can generate a new one on this device, or import one you already use.")
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.padding(.horizontal)
VStack(spacing: 12) {
@@ -140,7 +141,7 @@ private struct KeySourceStep: View {
.frame(maxWidth: .infinity)
.padding()
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
Button {
vm.pickKeyChoice(.importExisting)
@@ -167,7 +168,7 @@ private struct GenerateKeyStep: View {
ProgressView()
.scaleEffect(1.2)
Text("Generating Ed25519 keypair…")
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
.task {
await vm.generateKey()
@@ -221,7 +222,7 @@ private struct ShowPublicKeyStep: View {
.bold()
Text("Append the line below to `~/.ssh/authorized_keys` on the Hermes host. Once added, tap **I've added this key** to test the connection.")
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
if let bundle = vm.keyBundle {
Text(OnboardingLogic.authorizedKeysLine(for: bundle))
@@ -229,7 +230,7 @@ private struct ShowPublicKeyStep: View {
.textSelection(.enabled)
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(.secondarySystemBackground))
.background(ScarfColor.backgroundSecondary)
.clipShape(RoundedRectangle(cornerRadius: 10))
Button(copied ? "Copied" : "Copy") {
@@ -257,7 +258,7 @@ private struct ShowPublicKeyStep: View {
}
.padding(.vertical, 8)
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
.disabled(vm.isWorking)
}
.padding()
@@ -273,7 +274,7 @@ private struct TestConnectionStep: View {
ProgressView()
.scaleEffect(1.2)
Text("Testing connection to \(vm.host)")
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
}
@@ -286,7 +287,7 @@ private struct TestFailedStep: View {
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Label("Connection failed", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
.foregroundStyle(ScarfColor.warning)
.font(.title3)
.bold()
Text(reason)
@@ -301,7 +302,7 @@ private struct TestFailedStep: View {
Button("Retry") {
Task { await vm.runConnectionTest() }
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
}
}
.padding()
@@ -314,12 +315,12 @@ private struct ConnectedStep: View {
VStack(spacing: 16) {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 64))
.foregroundStyle(.green)
.foregroundStyle(ScarfColor.success)
Text("Connected")
.font(.title2)
.bold()
Text("Loading dashboard…")
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Per-project detail view, presented when a row in `ProjectsListView`
/// is tapped. Mirrors the Mac three-tab layout (Dashboard | Site |
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// iOS twin of the Mac per-project Sessions tab. Reuses the
/// ScarfCore-side `ProjectSessionsViewModel` (promoted from the Mac
@@ -50,7 +51,7 @@ struct ProjectSessionsView_iOS: View {
.fontWeight(.semibold)
Text("Chats you start here are attributed automatically.")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
@@ -60,7 +61,7 @@ struct ProjectSessionsView_iOS: View {
Label("New Chat", systemImage: "message.badge.filled.fill")
.labelStyle(.iconOnly)
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
.controlSize(.small)
.accessibilityLabel("Start new chat in \(project.name)")
}
@@ -96,7 +97,7 @@ struct ProjectSessionsView_iOS: View {
.foregroundStyle(.tertiary)
Text(hint ?? "No sessions yet.")
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 32)
@@ -129,7 +130,7 @@ private struct ProjectSessionRow_iOS: View {
var body: some View {
HStack(spacing: 10) {
Image(systemName: iconForSource(session.source))
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.frame(width: 22)
VStack(alignment: .leading, spacing: 2) {
Text(displayTitle)
@@ -144,7 +145,7 @@ private struct ProjectSessionRow_iOS: View {
.foregroundStyle(.tertiary)
Text(started)
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
}
@@ -154,7 +155,7 @@ private struct ProjectSessionRow_iOS: View {
.font(.caption.monospaced())
Text("msgs")
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
.padding(.vertical, 4)
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Full-canvas webview wrapper for the Site sub-tab. Reuses the
/// `WebviewWidgetView` representable in its `fullCanvas: true` mode so
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Top-level Projects tab. Lists registered Scarf projects from
/// `~/.hermes/scarf/projects.json`. Folder groupings + archive flags
@@ -94,7 +95,7 @@ struct ProjectsListView: View {
.foregroundStyle(.primary)
Text(project.path)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(1)
.truncationMode(.middle)
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
import Charts
// Flattened data point for Charts to avoid complex nested generic inference
@@ -28,7 +29,7 @@ struct ChartWidgetView: View {
VStack(alignment: .leading, spacing: 6) {
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
chartContent
.frame(height: 150)
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// iOS dashboard layout. ScrollView of sections; each section is a
/// `LazyVGrid` whose column count is clamped to the device's
@@ -23,7 +24,7 @@ struct DashboardWidgetsView: View {
if let description = dashboard.description, !description.isEmpty {
Text(description)
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.padding(.horizontal)
}
ForEach(dashboard.sections) { section in
@@ -104,7 +105,7 @@ struct WidgetView: View {
VStack(alignment: .leading, spacing: 4) {
Label(widget.title, systemImage: "questionmark.app.dashed")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Text("Widget type \"\(widget.type)\" isn't supported in this version of Scarf yet.")
.font(.caption2)
.foregroundStyle(.tertiary)
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
struct ListWidgetView: View {
let widget: DashboardWidget
@@ -9,12 +10,12 @@ struct ListWidgetView: View {
HStack(spacing: 4) {
if let icon = widget.icon {
Image(systemName: icon)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.font(.caption)
}
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
if let items = widget.items {
ForEach(items) { item in
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
struct ProgressWidgetView: View {
let widget: DashboardWidget
@@ -15,12 +16,12 @@ struct ProgressWidgetView: View {
VStack(alignment: .leading, spacing: 8) {
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
ProgressView(value: progressValue) {
if let label = widget.label {
Text(label)
.font(.caption2)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
.tint(parseColor(widget.color))
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
struct StatWidgetView: View {
let widget: DashboardWidget
@@ -18,7 +19,7 @@ struct StatWidgetView: View {
}
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
if let value = widget.value {
Text(value.displayString)
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
struct TableWidgetView: View {
let widget: DashboardWidget
@@ -8,7 +9,7 @@ struct TableWidgetView: View {
VStack(alignment: .leading, spacing: 6) {
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
if let columns = widget.columns, let rows = widget.rows {
ScrollView(.horizontal, showsIndicators: false) {
Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 4) {
@@ -16,7 +17,7 @@ struct TableWidgetView: View {
ForEach(columns, id: \.self) { col in
Text(col)
.font(.caption.bold())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
Divider()
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
struct TextWidgetView: View {
let widget: DashboardWidget
@@ -8,7 +9,7 @@ struct TextWidgetView: View {
VStack(alignment: .leading, spacing: 4) {
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
if let content = widget.content {
if widget.format == "markdown" {
// SwiftUI's built-in inline markdown via AttributedString.
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
import WebKit
/// iOS twin of Mac's `WebviewWidgetView`. Same two modes (inline card
@@ -52,12 +53,12 @@ struct WebviewWidgetView: View {
HStack {
if let icon = widget.icon {
Image(systemName: icon)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.font(.caption)
}
Text(widget.title)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Spacer()
if let urlString = widget.url {
Text(urlString)
+3 -2
View File
@@ -1,6 +1,7 @@
import SwiftUI
import ScarfCore
import ScarfIOS
import ScarfDesign
/// ScarfGo's root surface when the user has at least one server
/// configured. Replaces the pre-M9 "boot straight into Dashboard"
@@ -27,7 +28,7 @@ struct ServerListView: View {
.font(.headline)
Text(err)
.font(.callout)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
HStack(spacing: 12) {
Button("Dismiss") { model.clearLastError() }
@@ -151,7 +152,7 @@ private struct ServerListRow: View {
.foregroundStyle(.primary)
Text(hostLine)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
Spacer()
Image(systemName: "chevron.right")
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Sheet for editing a single Hermes config value. Renders the
/// appropriate control for each supported key:
+4 -3
View File
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// iOS Settings screen. Read-only browser of `~/.hermes/config.yaml`
/// as it currently stands on the remote, grouped into sections that
@@ -84,7 +85,7 @@ struct SettingsView: View {
.foregroundStyle(.primary)
Text(currentValue(for: spec.key))
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(1)
.truncationMode(.middle)
}
@@ -213,7 +214,7 @@ struct SettingsView: View {
ForEach(vm.config.security.blocklistDomains.prefix(5), id: \.self) { domain in
Text(domain)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
if vm.config.security.blocklistDomains.count > 5 {
Text("+ \(vm.config.security.blocklistDomains.count - 5) more")
@@ -265,7 +266,7 @@ struct SettingsView: View {
} else {
Text(vm.rawYAML)
.font(.caption2.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Browse / search the Hermes skills hub. Source picker is a `Menu`
/// (more compact than Mac's segmented Picker on a phone-width screen).
@@ -21,7 +22,7 @@ struct HubBrowseView: View {
VStack(spacing: 8) {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
TextField("Search skills…", text: $vm.hubQuery)
.textFieldStyle(.roundedBorder)
.submitLabel(.search)
@@ -51,7 +52,7 @@ struct HubBrowseView: View {
} label: {
Label("Search", systemImage: "magnifyingglass")
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
.controlSize(.small)
.disabled(vm.isHubLoading)
Button {
@@ -83,7 +84,7 @@ struct HubBrowseView: View {
} label: {
Label("Browse top skills", systemImage: "books.vertical")
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
.disabled(vm.isHubLoading)
}
} else {
@@ -128,7 +129,7 @@ private struct HubSkillRow: View {
if !skill.description.isEmpty {
Text(skill.description)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(3)
}
}
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Installed skills sub-tab. Category-grouped list; tapping a row
/// pushes `SkillDetailView` for that skill. Filtering uses the VM's
@@ -40,7 +41,7 @@ struct InstalledSkillsListView: View {
.font(.body)
Text("\(skill.files.count) file\(skill.files.count == 1 ? "" : "s")")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
.scarfGoCompactListRow()
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Installed skill detail. Shows location + required-config warning
/// banner + file picker + content viewer. Edit and Uninstall buttons
@@ -28,7 +29,7 @@ struct SkillDetailView: View {
LabeledContent("Category", value: skill.category)
Text(skill.path)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.textSelection(.enabled)
}
@@ -43,7 +44,7 @@ struct SkillDetailView: View {
.font(.callout.weight(.medium))
Text(hint)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
}
} icon: {
@@ -66,7 +67,7 @@ struct SkillDetailView: View {
.font(.callout.weight(.medium))
Text("Run `hermes auth spotify` from the Scarf macOS app or a shell — it opens your browser to complete the OAuth flow. Once authorised, this skill picks up the credentials from `~/.hermes/auth.json` automatically.")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
}
} icon: {
@@ -108,7 +109,7 @@ struct SkillDetailView: View {
.fontWeight(.semibold)
Text("Add these keys to ~/.hermes/config.yaml:")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
ForEach(vm.missingConfig, id: \.self) { key in
Text("\(key)")
.font(.caption.monospaced())
@@ -224,7 +225,7 @@ struct SkillDetailView: View {
ForEach(items, id: \.self) { item in
Text(item)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
.padding(.horizontal, 8)
.padding(.vertical, 3)
.background(.secondary.opacity(0.12), in: Capsule())
+3 -2
View File
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// iOS Skills tab 3-tab segmented surface mirroring the Mac
/// `SkillsView`. Owns one `SkillsViewModel` (ScarfCore-side, unified
@@ -136,7 +137,7 @@ struct SkillsView: View {
}
Text(msg)
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Spacer()
}
.padding(.horizontal, 16)
@@ -148,7 +149,7 @@ struct SkillsView: View {
.controlSize(.small)
Text("Working…")
.font(.caption)
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Spacer()
}
.padding(.horizontal, 16)
@@ -1,5 +1,6 @@
import SwiftUI
import ScarfCore
import ScarfDesign
/// Updates sub-tab. Mirrors Mac: Check button populates `vm.updates`;
/// Update All button is enabled only when there's at least one
@@ -24,7 +25,7 @@ struct UpdatesView: View {
} label: {
Label("Check for Updates", systemImage: "arrow.triangle.2.circlepath")
}
.buttonStyle(.borderedProminent)
.buttonStyle(ScarfPrimaryButton())
.controlSize(.small)
.disabled(vm.isHubLoading)
@@ -66,7 +67,7 @@ struct UpdatesView: View {
HStack(spacing: 6) {
Text(update.currentVersion)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.foregroundStyle(ScarfColor.foregroundMuted)
Image(systemName: "arrow.right")
.font(.caption2)
.foregroundStyle(.tertiary)