diff --git a/CLAUDE.md b/CLAUDE.md index 67a00ed..3288cc8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,6 +34,23 @@ All app UI uses the typed token bundle in [scarf/Packages/ScarfDesign/](scarf/Pa - **Reference**: full screen mockups live at `design/static-site/ui-kit/*.jsx` (open `design/static-site/index.html` in a browser). The `ScarfChatView.ChatRootView` reference component in the package is a 3-pane chat redesign target — usable for previews but not yet swapped into the live chat (the existing `RichChatView` machinery still owns the real ACP pipeline). - **Don't**: introduce purple/violet tones (we shifted to rust); use yellow `#F0AD4E` for success (that's `.warning` — `.success` is green); bypass the type scale with `.font(.system(size: 13.5))`; ship terminal/syntax-highlight palettes through ScarfColor (those are content semantics, keep them inline). +### iOS Dynamic Type policy + +iOS users can scale text via Settings → Accessibility → Display & Text Size. ScarfFont uses fixed point sizes; adopting it blanket on iOS would regress accessibility on `.accessibility2` / `.xSmall` users. iOS-specific rule: + +- **Use `ScarfFont` only for**: status badges, chip labels, intentional-display elements (e.g., onboarding step titles, header chrome that's meant to be a fixed visual size). +- **Keep `.font(.headline)` / `.body` / `.caption` semantic tokens for**: list-row primary + secondary text, body copy, error messages, chat content — anything the user reads. + +Decision tree per text element: "is this read for content?" → semantic token. "Is this chrome / a label / a badge?" → ScarfFont. + +Mac doesn't have this constraint and adopts ScarfFont everywhere. The iOS app already clamps Dynamic Type at the scene root (`ScarfIOSApp.swift`: `.dynamicTypeSize(.xSmall ... .accessibility2)`) — keep that. + +### iOS page chrome + +Don't retrofit `ScarfPageHeader` over iOS tab roots. iOS uses `.navigationTitle(...)` + `.navigationBarTitleDisplayMode(.large)` as its native page-header pattern; stacking ScarfPageHeader on top creates double titles. Use ScarfPageHeader only on iOS sub-views without a native large-title bar (rare). + +iOS button styling: only swap `.borderedProminent` → `ScarfPrimaryButton`. **Leave `.bordered` native** — it's the iOS convention and inherits rust through `AccentColor.colorset` automatically. Same for `.plain` (used as compact tap targets in lists). + ## Key Paths - Hermes home: `~/.hermes/` diff --git a/scarf/Scarf iOS/Assets.xcassets/AccentColor.colorset/Contents.json b/scarf/Scarf iOS/Assets.xcassets/AccentColor.colorset/Contents.json index b60ec08..82cf3f3 100644 --- a/scarf/Scarf iOS/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/scarf/Scarf iOS/Assets.xcassets/AccentColor.colorset/Contents.json @@ -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" diff --git a/scarf/Scarf iOS/Chat/ChatView.swift b/scarf/Scarf iOS/Chat/ChatView.swift index af6e80d..15cee78 100644 --- a/scarf/Scarf iOS/Chat/ChatView.swift +++ b/scarf/Scarf iOS/Chat/ChatView.swift @@ -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() diff --git a/scarf/Scarf iOS/Chat/ProjectPickerSheet.swift b/scarf/Scarf iOS/Chat/ProjectPickerSheet.swift index 53b79ed..eb85f11 100644 --- a/scarf/Scarf iOS/Chat/ProjectPickerSheet.swift +++ b/scarf/Scarf iOS/Chat/ProjectPickerSheet.swift @@ -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) } diff --git a/scarf/Scarf iOS/Chat/ProjectSlashCommandsBrowser.swift b/scarf/Scarf iOS/Chat/ProjectSlashCommandsBrowser.swift index 42b9e5c..7945aaf 100644 --- a/scarf/Scarf iOS/Chat/ProjectSlashCommandsBrowser.swift +++ b/scarf/Scarf iOS/Chat/ProjectSlashCommandsBrowser.swift @@ -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) } diff --git a/scarf/Scarf iOS/Cron/CronListView.swift b/scarf/Scarf iOS/Cron/CronListView.swift index a798324..682128f 100644 --- a/scarf/Scarf iOS/Cron/CronListView.swift +++ b/scarf/Scarf iOS/Cron/CronListView.swift @@ -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) diff --git a/scarf/Scarf iOS/Dashboard/DashboardView.swift b/scarf/Scarf iOS/Dashboard/DashboardView.swift index 2c85c2d..f1aaf2c 100644 --- a/scarf/Scarf iOS/Dashboard/DashboardView.swift +++ b/scarf/Scarf iOS/Dashboard/DashboardView.swift @@ -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) } } diff --git a/scarf/Scarf iOS/Memory/MemoryEditorView.swift b/scarf/Scarf iOS/Memory/MemoryEditorView.swift index ac5b5fa..59f481c 100644 --- a/scarf/Scarf iOS/Memory/MemoryEditorView.swift +++ b/scarf/Scarf iOS/Memory/MemoryEditorView.swift @@ -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) diff --git a/scarf/Scarf iOS/Memory/MemoryListView.swift b/scarf/Scarf iOS/Memory/MemoryListView.swift index 4889729..672d05d 100644 --- a/scarf/Scarf iOS/Memory/MemoryListView.swift +++ b/scarf/Scarf iOS/Memory/MemoryListView.swift @@ -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) diff --git a/scarf/Scarf iOS/Onboarding/OnboardingRootView.swift b/scarf/Scarf iOS/Onboarding/OnboardingRootView.swift index 73e008a..cba8921 100644 --- a/scarf/Scarf iOS/Onboarding/OnboardingRootView.swift +++ b/scarf/Scarf iOS/Onboarding/OnboardingRootView.swift @@ -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) } } } diff --git a/scarf/Scarf iOS/Projects/ProjectDetailView.swift b/scarf/Scarf iOS/Projects/ProjectDetailView.swift index 6c23089..c9dbca3 100644 --- a/scarf/Scarf iOS/Projects/ProjectDetailView.swift +++ b/scarf/Scarf iOS/Projects/ProjectDetailView.swift @@ -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 | diff --git a/scarf/Scarf iOS/Projects/ProjectSessionsView_iOS.swift b/scarf/Scarf iOS/Projects/ProjectSessionsView_iOS.swift index 140d82d..6f9fdeb 100644 --- a/scarf/Scarf iOS/Projects/ProjectSessionsView_iOS.swift +++ b/scarf/Scarf iOS/Projects/ProjectSessionsView_iOS.swift @@ -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) diff --git a/scarf/Scarf iOS/Projects/ProjectSiteView.swift b/scarf/Scarf iOS/Projects/ProjectSiteView.swift index dab33bd..f96d5d0 100644 --- a/scarf/Scarf iOS/Projects/ProjectSiteView.swift +++ b/scarf/Scarf iOS/Projects/ProjectSiteView.swift @@ -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 diff --git a/scarf/Scarf iOS/Projects/ProjectsListView.swift b/scarf/Scarf iOS/Projects/ProjectsListView.swift index 1018db4..3f68e19 100644 --- a/scarf/Scarf iOS/Projects/ProjectsListView.swift +++ b/scarf/Scarf iOS/Projects/ProjectsListView.swift @@ -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) } diff --git a/scarf/Scarf iOS/Projects/Widgets/ChartWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/ChartWidgetView.swift index 525c0da..808cbe2 100644 --- a/scarf/Scarf iOS/Projects/Widgets/ChartWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/ChartWidgetView.swift @@ -1,5 +1,6 @@ import SwiftUI import ScarfCore +import ScarfDesign import Charts // Flattened data point for Charts to avoid complex nested generic inference @@ -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) } diff --git a/scarf/Scarf iOS/Projects/Widgets/DashboardWidgetsView.swift b/scarf/Scarf iOS/Projects/Widgets/DashboardWidgetsView.swift index 18e821e..1e12b9a 100644 --- a/scarf/Scarf iOS/Projects/Widgets/DashboardWidgetsView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/DashboardWidgetsView.swift @@ -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) diff --git a/scarf/Scarf iOS/Projects/Widgets/ListWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/ListWidgetView.swift index a8c82d5..2b24a76 100644 --- a/scarf/Scarf iOS/Projects/Widgets/ListWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/ListWidgetView.swift @@ -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 diff --git a/scarf/Scarf iOS/Projects/Widgets/ProgressWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/ProgressWidgetView.swift index e89c392..e00cf26 100644 --- a/scarf/Scarf iOS/Projects/Widgets/ProgressWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/ProgressWidgetView.swift @@ -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)) diff --git a/scarf/Scarf iOS/Projects/Widgets/StatWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/StatWidgetView.swift index 7ebf5ee..add307b 100644 --- a/scarf/Scarf iOS/Projects/Widgets/StatWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/StatWidgetView.swift @@ -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) diff --git a/scarf/Scarf iOS/Projects/Widgets/TableWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/TableWidgetView.swift index caaa3b2..2b0e1c0 100644 --- a/scarf/Scarf iOS/Projects/Widgets/TableWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/TableWidgetView.swift @@ -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() diff --git a/scarf/Scarf iOS/Projects/Widgets/TextWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/TextWidgetView.swift index ebbe2ff..04a4286 100644 --- a/scarf/Scarf iOS/Projects/Widgets/TextWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/TextWidgetView.swift @@ -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. diff --git a/scarf/Scarf iOS/Projects/Widgets/WebviewWidgetView.swift b/scarf/Scarf iOS/Projects/Widgets/WebviewWidgetView.swift index 3c7fa6e..de2c2ac 100644 --- a/scarf/Scarf iOS/Projects/Widgets/WebviewWidgetView.swift +++ b/scarf/Scarf iOS/Projects/Widgets/WebviewWidgetView.swift @@ -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) diff --git a/scarf/Scarf iOS/Servers/ServerListView.swift b/scarf/Scarf iOS/Servers/ServerListView.swift index 115356e..6527999 100644 --- a/scarf/Scarf iOS/Servers/ServerListView.swift +++ b/scarf/Scarf iOS/Servers/ServerListView.swift @@ -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") diff --git a/scarf/Scarf iOS/Settings/SettingEditorSheet.swift b/scarf/Scarf iOS/Settings/SettingEditorSheet.swift index af2de18..bca6cd0 100644 --- a/scarf/Scarf iOS/Settings/SettingEditorSheet.swift +++ b/scarf/Scarf iOS/Settings/SettingEditorSheet.swift @@ -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: diff --git a/scarf/Scarf iOS/Settings/SettingsView.swift b/scarf/Scarf iOS/Settings/SettingsView.swift index 96bbf7b..e84a9b5 100644 --- a/scarf/Scarf iOS/Settings/SettingsView.swift +++ b/scarf/Scarf iOS/Settings/SettingsView.swift @@ -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) } diff --git a/scarf/Scarf iOS/Skills/Hub/HubBrowseView.swift b/scarf/Scarf iOS/Skills/Hub/HubBrowseView.swift index 55ed1ba..3938a05 100644 --- a/scarf/Scarf iOS/Skills/Hub/HubBrowseView.swift +++ b/scarf/Scarf iOS/Skills/Hub/HubBrowseView.swift @@ -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) } } diff --git a/scarf/Scarf iOS/Skills/Installed/InstalledSkillsListView.swift b/scarf/Scarf iOS/Skills/Installed/InstalledSkillsListView.swift index c4402fe..4971228 100644 --- a/scarf/Scarf iOS/Skills/Installed/InstalledSkillsListView.swift +++ b/scarf/Scarf iOS/Skills/Installed/InstalledSkillsListView.swift @@ -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() diff --git a/scarf/Scarf iOS/Skills/Installed/SkillDetailView.swift b/scarf/Scarf iOS/Skills/Installed/SkillDetailView.swift index 92ed87b..e096cb5 100644 --- a/scarf/Scarf iOS/Skills/Installed/SkillDetailView.swift +++ b/scarf/Scarf iOS/Skills/Installed/SkillDetailView.swift @@ -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()) diff --git a/scarf/Scarf iOS/Skills/SkillsView.swift b/scarf/Scarf iOS/Skills/SkillsView.swift index 610103b..8fe8eef 100644 --- a/scarf/Scarf iOS/Skills/SkillsView.swift +++ b/scarf/Scarf iOS/Skills/SkillsView.swift @@ -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) diff --git a/scarf/Scarf iOS/Skills/Updates/UpdatesView.swift b/scarf/Scarf iOS/Skills/Updates/UpdatesView.swift index 2996e00..3c5854c 100644 --- a/scarf/Scarf iOS/Skills/Updates/UpdatesView.swift +++ b/scarf/Scarf iOS/Skills/Updates/UpdatesView.swift @@ -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)