feat(ios): Mac-style page backgrounds + Dashboard + Chat redesign

iOS now uses ScarfColor.backgroundPrimary throughout instead of the
default iOS systemGroupedBackground. List-based screens add
.scrollContentBackground(.hidden) + the rust background underneath;
list rows use ScarfColor.backgroundSecondary as their card surface.
Applied to: Projects, Memory, Cron, Settings, Skills/Installed, and
the Servers root.

Dashboard rewritten in Mac-style cards (no more native iOS list):
- ScrollView + VStack with rust background
- Activity stat grid (2-col LazyVGrid) with bordered rust-tinted
  cards: Sessions / Messages / Tool Calls / Tokens (with in/out sub-
  label).
- Recent sessions card — bordered, ScarfColor.backgroundSecondary,
  inline session rows with 1px dividers, "See all" nav to Sessions
  sub-tab.
- Error banner styled with ScarfColor.warning tinted card per Mac.
- Sessions sub-tab keeps a List view but renders against rust
  background with ScarfColor.backgroundSecondary row backgrounds.

Chat redesigned to match the Mac chat reference:
- User bubble: rust accent fill with ScarfColor.onAccent text and
  uneven rounded corners (top/bottom-leading + top-trailing 14px;
  bottom-trailing 4px) — visually pinches to the sender side, same
  as Mac.
- Assistant bubble: rust gradient sparkles avatar tile (24x24)
  alongside a ScarfColor.backgroundSecondary bordered card.
- ToolCallCard: kind-tinted border + uppercase tracked label
  (READ/EDIT/EXECUTE/FETCH/BROWSER) using ScarfColor.success/info/
  warning/Tool.web/Tool.search; expanded JSON in a bordered
  ScarfColor.backgroundSecondary panel.
- ReasoningDisclosure: warning-tinted card with REASONING uppercase
  label.

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:20:20 +02:00
parent de611c5343
commit 295f2dfefc
8 changed files with 302 additions and 132 deletions
+108 -33
View File
@@ -1068,14 +1068,28 @@ private struct MessageBubble: View {
if message.isUser { if message.isUser {
Text(message.content) Text(message.content)
.font(.body) .font(.body)
.padding(.horizontal, 12) .padding(.horizontal, 14)
.padding(.vertical, 8) .padding(.vertical, 10)
.foregroundStyle(.white) .foregroundStyle(ScarfColor.onAccent)
.background(Color.accentColor) .background(
.clipShape(RoundedRectangle(cornerRadius: 14)) UnevenRoundedRectangle(cornerRadii:
.init(topLeading: 14, bottomLeading: 14, bottomTrailing: 4, topTrailing: 14))
.fill(ScarfColor.accent)
)
.textSelection(.enabled) .textSelection(.enabled)
.contextMenu { messageContextMenu } .contextMenu { messageContextMenu }
} else { } else {
HStack(alignment: .top, spacing: 8) {
// Assistant avatar rust gradient sparkles tile,
// matches the Mac side and the ScarfChatView reference.
RoundedRectangle(cornerRadius: 7, style: .continuous)
.fill(ScarfGradient.brand)
.frame(width: 24, height: 24)
.overlay(
Image(systemName: "sparkles")
.foregroundStyle(.white)
.font(.system(size: 10, weight: .semibold))
)
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
ForEach(Array(ChatContentFormatter.segments(for: message.content).enumerated()), id: \.offset) { _, segment in ForEach(Array(ChatContentFormatter.segments(for: message.content).enumerated()), id: \.offset) { _, segment in
switch segment { switch segment {
@@ -1089,13 +1103,20 @@ private struct MessageBubble: View {
} }
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
.padding(.vertical, 8) .padding(.vertical, 10)
.foregroundStyle(Color.primary) .foregroundStyle(ScarfColor.foregroundPrimary)
.background(ScarfColor.backgroundSecondary) .background(
.clipShape(RoundedRectangle(cornerRadius: 14)) RoundedRectangle(cornerRadius: ScarfRadius.xl, style: .continuous)
.fill(ScarfColor.backgroundSecondary)
)
.overlay(
RoundedRectangle(cornerRadius: ScarfRadius.xl, style: .continuous)
.strokeBorder(ScarfColor.border, lineWidth: 1)
)
.contextMenu { messageContextMenu } .contextMenu { messageContextMenu }
} }
} }
}
/// Shared context-menu actions for user + assistant bubbles. /// Shared context-menu actions for user + assistant bubbles.
/// Copy is the most-used action; Share hands off to the system /// Copy is the most-used action; Share hands off to the system
@@ -1206,64 +1227,118 @@ private struct ReasoningDisclosure: View {
.textSelection(.enabled) .textSelection(.enabled)
.padding(.top, 4) .padding(.top, 4)
} label: { } label: {
Label("Thinking…", systemImage: "brain") HStack(spacing: 5) {
Image(systemName: "brain")
.font(.caption) .font(.caption)
.foregroundStyle(ScarfColor.foregroundMuted) Text("REASONING")
.font(.caption2)
.fontWeight(.semibold)
.tracking(0.5)
} }
.padding(.horizontal, 6) .foregroundStyle(ScarfColor.warning)
}
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 7)
.fill(ScarfColor.warning.opacity(0.10))
.overlay(
RoundedRectangle(cornerRadius: 7)
.strokeBorder(ScarfColor.warning.opacity(0.30), lineWidth: 1)
)
)
} }
} }
/// Expanding card for a single `HermesToolCall` shows function name /// Expanding card for a single `HermesToolCall` kind-tinted with
/// + summary collapsed; full JSON arguments expanded. /// uppercase tracked label, matches the Mac ToolCallCard treatment.
private struct ToolCallCard: View { private struct ToolCallCard: View {
let call: HermesToolCall let call: HermesToolCall
@State private var isExpanded = false @State private var isExpanded = false
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 6) {
Button { Button {
withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() } withAnimation(.easeInOut(duration: 0.15)) { isExpanded.toggle() }
} label: { } label: {
HStack(spacing: 6) { HStack(spacing: 8) {
Image(systemName: iconName) HStack(spacing: 4) {
.foregroundStyle(.tint) Image(systemName: call.toolKind.icon)
.foregroundStyle(toolColor)
.font(.caption2)
Text(toolLabel)
.font(.caption2)
.fontWeight(.semibold)
.tracking(0.4)
.foregroundStyle(toolColor)
}
Text(call.functionName) Text(call.functionName)
.font(.caption.monospaced()) .font(.caption.monospaced())
.foregroundStyle(.primary) .fontWeight(.semibold)
.foregroundStyle(ScarfColor.foregroundPrimary)
Text(call.argumentsSummary.prefix(60)) Text(call.argumentsSummary.prefix(60))
.font(.caption) .font(.caption.monospaced())
.foregroundStyle(ScarfColor.foregroundMuted) .foregroundStyle(ScarfColor.foregroundMuted)
.lineLimit(1) .lineLimit(1)
Spacer() .truncationMode(.middle)
Spacer(minLength: 4)
Image(systemName: isExpanded ? "chevron.up" : "chevron.down") Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
.font(.caption2) .font(.caption2)
.foregroundStyle(.tertiary) .foregroundStyle(ScarfColor.foregroundFaint)
} }
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(
RoundedRectangle(cornerRadius: 7)
.fill(toolColor.opacity(0.10))
.overlay(
RoundedRectangle(cornerRadius: 7)
.strokeBorder(toolColor.opacity(0.30), lineWidth: 1)
)
)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
if isExpanded { if isExpanded {
Text(call.arguments) Text(call.arguments)
.font(.caption2.monospaced()) .font(.caption2.monospaced())
.foregroundStyle(ScarfColor.foregroundMuted) .foregroundStyle(ScarfColor.foregroundPrimary)
.textSelection(.enabled) .textSelection(.enabled)
.padding(.top, 2)
}
}
.padding(8) .padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 7)
.fill(Color(.tertiarySystemBackground)) .fill(ScarfColor.backgroundSecondary)
)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: 7)
.strokeBorder(Color(.separator), lineWidth: 0.5) .strokeBorder(ScarfColor.border, lineWidth: 1)
) )
)
.padding(.leading, 4)
}
}
} }
private var iconName: String { private var toolLabel: String {
call.toolKind.icon switch call.toolKind {
case .read: return "READ"
case .edit: return "EDIT"
case .execute: return "EXECUTE"
case .fetch: return "FETCH"
case .browser: return "BROWSER"
case .other: return "TOOL"
}
}
private var toolColor: Color {
switch call.toolKind {
case .read: return ScarfColor.success
case .edit: return ScarfColor.info
case .execute: return ScarfColor.warning
case .fetch: return ScarfColor.Tool.web
case .browser: return ScarfColor.Tool.search
case .other: return ScarfColor.foregroundMuted
}
} }
} }
+3 -1
View File
@@ -26,7 +26,7 @@ struct CronListView: View {
if let err = vm.lastError { if let err = vm.lastError {
Section { Section {
Label(err, systemImage: "exclamationmark.triangle.fill") Label(err, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange) .foregroundStyle(ScarfColor.warning)
} }
} }
@@ -62,6 +62,8 @@ struct CronListView: View {
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle("Cron jobs") .navigationTitle("Cron jobs")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
+142 -66
View File
@@ -3,10 +3,10 @@ import ScarfCore
import ScarfIOS import ScarfIOS
import ScarfDesign import ScarfDesign
/// iOS Dashboard shows session count, token usage, cost, and the /// iOS Dashboard adopts the Mac-style card layout (status row +
/// last 5 sessions pulled from the remote Hermes SQLite snapshot. /// stats grid + recent-sessions card) instead of a native iOS list.
/// Every data source routes through `ServerContext CitadelServerTransport` /// Sessions sub-tab keeps a List view for scrolling density but
/// so the same services that drive the Mac Dashboard power this one. /// renders against the rust page background.
struct DashboardView: View { struct DashboardView: View {
let config: IOSServerConfig let config: IOSServerConfig
let key: SSHKeyBundle let key: SSHKeyBundle
@@ -16,17 +16,8 @@ struct DashboardView: View {
@State private var selectedSection: Section = .overview @State private var selectedSection: Section = .overview
@State private var sessionProjectFilter: String? = nil @State private var sessionProjectFilter: String? = nil
/// Two top-level surfaces in the Dashboard. Overview = stats +
/// 5 most-recent sessions for glance. Sessions = the 25-session
/// deeper list with a project filter. Split added in pass-2 per
/// user feedback the old single-List layout grew too busy
/// once we started adding project badges, and users wanted a
/// way to slice by project.
enum Section: Hashable { case overview, sessions } enum Section: Hashable { case overview, sessions }
/// Stable ID used when building the `ServerContext` tied to the
/// config's host+user tuple so re-launching the app without reset
/// yields the same ID (important for the snapshot cache dir).
private static let contextID: ServerID = ServerID( private static let contextID: ServerID = ServerID(
uuidString: "00000000-0000-0000-0000-0000000000A1" uuidString: "00000000-0000-0000-0000-0000000000A1"
)! )!
@@ -48,18 +39,18 @@ struct DashboardView: View {
Text("Sessions").tag(Section.sessions) Text("Sessions").tag(Section.sessions)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
.padding(.horizontal, 16) .padding(.horizontal, ScarfSpace.s4)
.padding(.top, 8) .padding(.top, ScarfSpace.s2)
.padding(.bottom, 4) .padding(.bottom, ScarfSpace.s1)
Group { Group {
switch selectedSection { switch selectedSection {
case .overview: overviewList case .overview: overviewContent
case .sessions: sessionsList case .sessions: sessionsList
} }
} }
} }
.scarfGoListDensity() .background(ScarfColor.backgroundPrimary.ignoresSafeArea())
.navigationTitle(config.displayName) .navigationTitle(config.displayName)
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
.refreshable { await vm.refresh() } .refreshable { await vm.refresh() }
@@ -68,62 +59,159 @@ struct DashboardView: View {
ProgressView("Loading dashboard…") ProgressView("Loading dashboard…")
.padding() .padding()
.background(.regularMaterial) .background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 10)) .clipShape(RoundedRectangle(cornerRadius: ScarfRadius.lg))
} }
} }
.task { await vm.load() } .task { await vm.load() }
} }
// MARK: - Overview // MARK: - Overview (Mac-style cards)
@ViewBuilder @ViewBuilder
private var overviewList: some View { private var overviewContent: some View {
List { ScrollView {
VStack(alignment: .leading, spacing: ScarfSpace.s5) {
if let err = vm.lastError { if let err = vm.lastError {
SwiftUI.Section { errorBanner(err)
VStack(alignment: .leading, spacing: 8) { }
Label("Connection issue", systemImage: "exclamationmark.triangle.fill")
statsSection
if !vm.recentSessions.isEmpty {
recentSessionsSection
}
}
.padding(.horizontal, ScarfSpace.s4)
.padding(.vertical, ScarfSpace.s4)
.frame(maxWidth: .infinity, alignment: .topLeading)
}
.background(ScarfColor.backgroundPrimary)
}
private func errorBanner(_ err: String) -> some View {
HStack(alignment: .top, spacing: ScarfSpace.s2) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(ScarfColor.warning) .foregroundStyle(ScarfColor.warning)
VStack(alignment: .leading, spacing: 4) {
Text("Connection issue")
.font(.headline) .font(.headline)
.foregroundStyle(ScarfColor.foregroundPrimary)
Text(err) Text(err)
.font(.callout) .font(.callout)
.foregroundStyle(ScarfColor.foregroundMuted) .foregroundStyle(ScarfColor.foregroundMuted)
.fixedSize(horizontal: false, vertical: true)
Button("Retry") { Button("Retry") {
Task { await vm.refresh() } Task { await vm.refresh() }
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
.padding(.top, 4)
}
Spacer(minLength: 0)
}
.padding(ScarfSpace.s3)
.background(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.fill(ScarfColor.warning.opacity(0.10))
.overlay(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.strokeBorder(ScarfColor.warning.opacity(0.30), lineWidth: 1)
)
)
}
private var statsSection: some View {
VStack(alignment: .leading, spacing: ScarfSpace.s2) {
Text("Activity")
.font(.headline)
.foregroundStyle(ScarfColor.foregroundPrimary)
LazyVGrid(
columns: [GridItem(.flexible(), spacing: ScarfSpace.s3),
GridItem(.flexible(), spacing: ScarfSpace.s3)],
spacing: ScarfSpace.s3
) {
statCard(label: "Sessions", value: "\(vm.stats.totalSessions)")
statCard(label: "Messages", value: "\(vm.stats.totalMessages)")
statCard(label: "Tool Calls", value: "\(vm.stats.totalToolCalls)")
statCard(
label: "Tokens",
value: formatTokens(vm.stats.totalInputTokens + vm.stats.totalOutputTokens),
sub: tokenSub
)
} }
.padding(.vertical, 4)
} }
} }
SwiftUI.Section("Activity") { private var tokenSub: String? {
statRow("Total sessions", value: "\(vm.stats.totalSessions)") let inT = vm.stats.totalInputTokens
statRow("Total messages", value: "\(vm.stats.totalMessages)") let outT = vm.stats.totalOutputTokens
statRow("Tool calls", value: "\(vm.stats.totalToolCalls)") guard inT + outT > 0 else { return nil }
return "\(formatTokens(inT)) in · \(formatTokens(outT)) out"
} }
SwiftUI.Section("Tokens") { private func statCard(label: String, value: String, sub: String? = nil) -> some View {
statRow("Input", value: formatTokens(vm.stats.totalInputTokens)) VStack(alignment: .leading, spacing: 4) {
statRow("Output", value: formatTokens(vm.stats.totalOutputTokens)) Text(label)
statRow("Reasoning", value: formatTokens(vm.stats.totalReasoningTokens)) .font(.caption)
.fontWeight(.semibold)
.textCase(.uppercase)
.tracking(0.5)
.foregroundStyle(ScarfColor.foregroundMuted)
Text(value)
.font(.system(size: 22, weight: .semibold, design: .monospaced))
.foregroundStyle(ScarfColor.foregroundPrimary)
.lineLimit(1)
.minimumScaleFactor(0.7)
if let sub {
Text(sub)
.font(.caption2)
.foregroundStyle(ScarfColor.foregroundFaint)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(ScarfSpace.s3)
.background(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.fill(ScarfColor.backgroundSecondary)
)
.overlay(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.strokeBorder(ScarfColor.border, lineWidth: 1)
)
} }
if !vm.recentSessions.isEmpty { private var recentSessionsSection: some View {
SwiftUI.Section { VStack(alignment: .leading, spacing: ScarfSpace.s2) {
ForEach(vm.recentSessions) { session in
sessionRow(session)
}
} header: {
HStack { HStack {
Text("Recent sessions") Text("Recent sessions")
.font(.headline)
.foregroundStyle(ScarfColor.foregroundPrimary)
Spacer() Spacer()
Button("See all") { selectedSection = .sessions } Button("See all") { selectedSection = .sessions }
.font(.caption) .font(.caption)
.textCase(nil) .foregroundStyle(ScarfColor.accent)
.buttonStyle(.plain)
}
VStack(spacing: 0) {
ForEach(Array(vm.recentSessions.enumerated()), id: \.element.id) { idx, session in
sessionRow(session)
.padding(.horizontal, ScarfSpace.s3)
.padding(.vertical, ScarfSpace.s2 + 2)
if idx < vm.recentSessions.count - 1 {
Rectangle()
.fill(ScarfColor.border)
.frame(height: 1)
} }
} }
} }
.background(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.fill(ScarfColor.backgroundSecondary)
)
.overlay(
RoundedRectangle(cornerRadius: ScarfRadius.lg, style: .continuous)
.strokeBorder(ScarfColor.border, lineWidth: 1)
)
} }
} }
@@ -134,8 +222,8 @@ struct DashboardView: View {
VStack(spacing: 0) { VStack(spacing: 0) {
if !vm.allProjects.isEmpty { if !vm.allProjects.isEmpty {
filterBar filterBar
.padding(.horizontal, 12) .padding(.horizontal, ScarfSpace.s3)
.padding(.bottom, 8) .padding(.vertical, ScarfSpace.s2)
} }
List { List {
@@ -149,20 +237,20 @@ struct DashboardView: View {
: "No sessions for that project yet. Try another filter or start a chat in that project.") : "No sessions for that project yet. Try another filter or start a chat in that project.")
) )
.listRowSeparator(.hidden) .listRowSeparator(.hidden)
.listRowBackground(Color.clear)
} else { } else {
ForEach(filtered) { session in ForEach(filtered) { session in
sessionRow(session) sessionRow(session)
.listRowBackground(ScarfColor.backgroundSecondary)
} }
} }
} }
.listStyle(.plain)
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
} }
} }
/// Project filter control rendered above the Sessions list. Uses
/// a Menu instead of a segmented Picker because there can be many
/// projects segments don't scale past 34 options on a phone.
/// Shows the active filter as the button label (tappable to
/// change); an explicit "All projects" entry clears the filter.
@ViewBuilder @ViewBuilder
private var filterBar: some View { private var filterBar: some View {
HStack { HStack {
@@ -191,16 +279,16 @@ struct DashboardView: View {
.font(.caption2) .font(.caption2)
} }
.font(.caption) .font(.caption)
.foregroundStyle(.tint) .foregroundStyle(ScarfColor.accent)
.padding(.horizontal, 10) .padding(.horizontal, 10)
.padding(.vertical, 6) .padding(.vertical, 6)
.background(.tint.opacity(0.1), in: Capsule()) .background(ScarfColor.accentTint, in: Capsule())
} }
Spacer() Spacer()
} }
} }
// MARK: - Row helpers // MARK: - Row helper
@ViewBuilder @ViewBuilder
private func sessionRow(_ session: HermesSession) -> some View { private func sessionRow(_ session: HermesSession) -> some View {
@@ -211,7 +299,7 @@ struct DashboardView: View {
Text(session.displayTitle) Text(session.displayTitle)
.font(.body) .font(.body)
.lineLimit(2) .lineLimit(2)
.foregroundStyle(.primary) .foregroundStyle(ScarfColor.foregroundPrimary)
HStack(spacing: 12) { HStack(spacing: 12) {
Label(session.source, systemImage: session.sourceIcon) Label(session.source, systemImage: session.sourceIcon)
.font(.caption) .font(.caption)
@@ -230,11 +318,11 @@ struct DashboardView: View {
if let projectName = vm.projectName(for: session) { if let projectName = vm.projectName(for: session) {
Label(projectName, systemImage: "folder.fill") Label(projectName, systemImage: "folder.fill")
.font(.caption2) .font(.caption2)
.foregroundStyle(.tint) .foregroundStyle(ScarfColor.accent)
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
.padding(.vertical, 2) .padding(.vertical, 2)
.padding(.horizontal, 6) .padding(.horizontal, 6)
.background(.tint.opacity(0.12), in: Capsule()) .background(ScarfColor.accentTint, in: Capsule())
} }
} }
.padding(.vertical, 2) .padding(.vertical, 2)
@@ -244,18 +332,6 @@ struct DashboardView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
} }
@ViewBuilder
private func statRow(_ label: String, value: String) -> some View {
LabeledContent(label) {
Text(value)
.monospacedDigit()
.foregroundStyle(ScarfColor.foregroundMuted)
}
}
/// Mirror of `ScarfCore.formatTokens` inlined here rather than
/// exported from ScarfCore because it's currently wrapped in
/// `#if canImport(SQLite3)` (from the M0d InsightsViewModel move).
private func formatTokens(_ count: Int) -> String { private func formatTokens(_ count: Int) -> String {
if count >= 1_000_000 { if count >= 1_000_000 {
return String(format: "%.1fM", Double(count) / 1_000_000) return String(format: "%.1fM", Double(count) / 1_000_000)
@@ -24,16 +24,22 @@ struct MemoryListView: View {
Section { Section {
memoryRow(.memory, context: ctx) memoryRow(.memory, context: ctx)
.scarfGoCompactListRow() .scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
memoryRow(.user, context: ctx) memoryRow(.user, context: ctx)
.scarfGoCompactListRow() .scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
memoryRow(.soul, context: ctx) memoryRow(.soul, context: ctx)
.scarfGoCompactListRow() .scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
} footer: { } footer: {
Text("MEMORY.md and USER.md live under `~/.hermes/memories/`. SOUL.md lives at `~/.hermes/SOUL.md`.") Text("MEMORY.md and USER.md live under `~/.hermes/memories/`. SOUL.md lives at `~/.hermes/SOUL.md`.")
.font(.caption) .font(.caption)
.foregroundStyle(ScarfColor.foregroundMuted)
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle("Memory") .navigationTitle("Memory")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
@@ -67,6 +67,7 @@ struct ProjectsListView: View {
Section { Section {
ForEach(topLevel) { project in ForEach(topLevel) { project in
projectRow(project) projectRow(project)
.listRowBackground(ScarfColor.backgroundSecondary)
} }
} }
} }
@@ -74,11 +75,14 @@ struct ProjectsListView: View {
Section(folder) { Section(folder) {
ForEach(visibleProjects.filter { $0.folder == folder }) { project in ForEach(visibleProjects.filter { $0.folder == folder }) { project in
projectRow(project) projectRow(project)
.listRowBackground(ScarfColor.backgroundSecondary)
} }
} }
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
} }
private func projectRow(_ project: ProjectEntry) -> some View { private func projectRow(_ project: ProjectEntry) -> some View {
+3 -1
View File
@@ -24,7 +24,7 @@ struct ServerListView: View {
Section { Section {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
Label("Something went wrong", systemImage: "exclamationmark.triangle.fill") Label("Something went wrong", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange) .foregroundStyle(ScarfColor.warning)
.font(.headline) .font(.headline)
Text(err) Text(err)
.font(.callout) .font(.callout)
@@ -60,6 +60,8 @@ struct ServerListView: View {
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle("Servers") .navigationTitle("Servers")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
+3 -1
View File
@@ -29,7 +29,7 @@ struct SettingsView: View {
if let err = vm.lastError { if let err = vm.lastError {
Section { Section {
Label(err, systemImage: "exclamationmark.triangle.fill") Label(err, systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange) .foregroundStyle(ScarfColor.warning)
} }
} }
@@ -49,6 +49,8 @@ struct SettingsView: View {
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.navigationTitle("Settings") .navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.refreshable { await vm.load() } .refreshable { await vm.load() }
@@ -45,11 +45,14 @@ struct InstalledSkillsListView: View {
} }
} }
.scarfGoCompactListRow() .scarfGoCompactListRow()
.listRowBackground(ScarfColor.backgroundSecondary)
} }
} }
} }
} }
.scarfGoListDensity() .scarfGoListDensity()
.scrollContentBackground(.hidden)
.background(ScarfColor.backgroundPrimary)
.searchable(text: $vm.searchText, placement: .navigationBarDrawer(displayMode: .always)) .searchable(text: $vm.searchText, placement: .navigationBarDrawer(displayMode: .always))
} }
} }