mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 02:26:37 +00:00
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:
@@ -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/`
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user