diff --git a/apps/ios/.swiftlint.yml b/apps/ios/.swiftlint.yml index d88d6e49a964..aeaf5b272f75 100644 --- a/apps/ios/.swiftlint.yml +++ b/apps/ios/.swiftlint.yml @@ -2,8 +2,24 @@ parent_config: ../../config/swiftlint.yml included: - Sources - - ../shared/ClawdisNodeKit/Sources + - ShareExtension + - ActivityWidget + - WatchApp + - ../shared/OpenClawKit/Sources/OpenClawChatUI + +excluded: + - ../macos type_body_length: warning: 900 error: 1300 + +custom_rules: + openclaw_design_colors: + name: "OpenClaw design colors" + excluded: + - Sources/Design/OpenClawBrand.swift + - ../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift + regex: '(Color\.accentColor|(^|[^A-Za-z0-9_])\.accentColor\b|Color\.(red|green|orange|cyan|blue|yellow|purple|pink)\b|\.(foregroundStyle|tint|fill|stroke|strokeBorder|background)\(\s*\.(red|green|orange|cyan|blue|yellow|purple|pink)\b|Color\(red:\s*0\s*/\s*255\.0,\s*green:\s*122\s*/\s*255\.0,\s*blue:\s*255\s*/\s*255\.0\))' + message: "Use OpenClawBrand or OpenClawChatTheme design tokens instead of raw accent/status colors." + severity: error diff --git a/apps/ios/ActivityWidget/OpenClawLiveActivity.swift b/apps/ios/ActivityWidget/OpenClawLiveActivity.swift index a733bb650ead..2b5b04a48346 100644 --- a/apps/ios/ActivityWidget/OpenClawLiveActivity.swift +++ b/apps/ios/ActivityWidget/OpenClawLiveActivity.swift @@ -74,23 +74,30 @@ struct OpenClawLiveActivity: Widget { private func statusIcon(state: OpenClawActivityAttributes.ContentState) -> some View { if state.isConnecting { Image(systemName: "arrow.triangle.2.circlepath") - .foregroundStyle(.cyan) + .foregroundStyle(OpenClawActivityStyle.info) } else if state.isDisconnected { Image(systemName: "wifi.slash") - .foregroundStyle(.red) + .foregroundStyle(OpenClawActivityStyle.danger) } else if state.isIdle { Image(systemName: "checkmark") - .foregroundStyle(.green) + .foregroundStyle(OpenClawActivityStyle.ok) } else { Image(systemName: "exclamationmark.triangle.fill") - .foregroundStyle(.orange) + .foregroundStyle(OpenClawActivityStyle.warn) } } private func dotColor(state: OpenClawActivityAttributes.ContentState) -> Color { - if state.isDisconnected { return .red } - if state.isConnecting { return .cyan } - if state.isIdle { return .green } - return .orange + if state.isDisconnected { return OpenClawActivityStyle.danger } + if state.isConnecting { return OpenClawActivityStyle.info } + if state.isIdle { return OpenClawActivityStyle.ok } + return OpenClawActivityStyle.warn } } + +private enum OpenClawActivityStyle { + static let info = Color(red: 0, green: 122 / 255.0, blue: 1) + static let danger = Color(red: 185 / 255.0, green: 28 / 255.0, blue: 28 / 255.0) + static let ok = Color(red: 34 / 255.0, green: 197 / 255.0, blue: 94 / 255.0) + static let warn = Color(red: 245 / 255.0, green: 158 / 255.0, blue: 11 / 255.0) +} diff --git a/apps/ios/Sources/Design/AgentProTab+Skills.swift b/apps/ios/Sources/Design/AgentProTab+Skills.swift index 4a461eec2dbf..f9068ac38969 100644 --- a/apps/ios/Sources/Design/AgentProTab+Skills.swift +++ b/apps/ios/Sources/Design/AgentProTab+Skills.swift @@ -506,7 +506,7 @@ extension AgentProTab { func skillEditorSwitchIndicator(isOn: Bool) -> some View { Capsule() - .fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35)) + .fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) .frame(width: 52, height: 32) .overlay(alignment: isOn ? .trailing : .leading) { Circle() diff --git a/apps/ios/Sources/Design/AgentProTab.swift b/apps/ios/Sources/Design/AgentProTab.swift index b7a7411fe621..fad9a1f7b723 100644 --- a/apps/ios/Sources/Design/AgentProTab.swift +++ b/apps/ios/Sources/Design/AgentProTab.swift @@ -105,7 +105,7 @@ struct AgentProTab: View { var color: Color { switch self { case .online: OpenClawBrand.ok - case .ready: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0) + case .ready: OpenClawBrand.info } } } diff --git a/apps/ios/Sources/Design/ChatProTab.swift b/apps/ios/Sources/Design/ChatProTab.swift index 5cd40ac6876c..236d81a839c2 100644 --- a/apps/ios/Sources/Design/ChatProTab.swift +++ b/apps/ios/Sources/Design/ChatProTab.swift @@ -277,7 +277,7 @@ struct ChatProTab: View { } private var chatUserAccent: Color { - self.colorScheme == .light ? Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0) : OpenClawBrand.accent + self.colorScheme == .light ? OpenClawBrand.info : OpenClawBrand.accent } private var activeAgent: AgentSummary? { diff --git a/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift b/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift index 0ad46971c547..12fe7a34d37d 100644 --- a/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift +++ b/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift @@ -1036,7 +1036,7 @@ struct IPadSkillProposalRow: View { .padding(.horizontal, 14) .padding(.vertical, 10) .background( - self.isSelected ? Color.red.opacity(0.08) : Color.clear, + self.isSelected ? OpenClawBrand.danger.opacity(0.08) : Color.clear, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) } } diff --git a/apps/ios/Sources/Design/OpenClawBrand.swift b/apps/ios/Sources/Design/OpenClawBrand.swift index a5a0598b03d3..d302b2928ea4 100644 --- a/apps/ios/Sources/Design/OpenClawBrand.swift +++ b/apps/ios/Sources/Design/OpenClawBrand.swift @@ -47,11 +47,13 @@ enum AppAppearancePreference: String, CaseIterable, Identifiable { } enum OpenClawBrand { - static let accent = Color(uiColor: UIColor { traits in + static let uiAccent = UIColor { traits in traits.userInterfaceStyle == .dark ? UIColor(red: 198 / 255.0, green: 62 / 255.0, blue: 56 / 255.0, alpha: 1) : UIColor(red: 183 / 255.0, green: 56 / 255.0, blue: 51 / 255.0, alpha: 1) - }) + } + + static let accent = Color(uiColor: Self.uiAccent) static let accentHot = Color(uiColor: UIColor { traits in traits.userInterfaceStyle == .dark ? UIColor(red: 232 / 255.0, green: 92 / 255.0, blue: 86 / 255.0, alpha: 1) @@ -64,6 +66,7 @@ enum OpenClawBrand { }) static let ok = Color(red: 34 / 255.0, green: 197 / 255.0, blue: 94 / 255.0) static let warn = Color(red: 245 / 255.0, green: 158 / 255.0, blue: 11 / 255.0) + static let info = Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0) static let graphite = Color(uiColor: UIColor { traits in traits.userInterfaceStyle == .dark ? UIColor(red: 20 / 255.0, green: 22 / 255.0, blue: 24 / 255.0, alpha: 1) diff --git a/apps/ios/Sources/Design/SettingsProTabSections.swift b/apps/ios/Sources/Design/SettingsProTabSections.swift index 3ecfde4c23df..5893cb3d3e30 100644 --- a/apps/ios/Sources/Design/SettingsProTabSections.swift +++ b/apps/ios/Sources/Design/SettingsProTabSections.swift @@ -119,7 +119,7 @@ extension SettingsProTab { self.gatewayActionButton( title: "Diagnose", icon: "cross.case", - color: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0), + color: OpenClawBrand.info, isBusy: self.isRefreshingGateway) { Task { await self.runDiagnostics() } @@ -476,7 +476,7 @@ extension SettingsProTab { self.gatewayActionButton( title: "Run Diagnostics", icon: "cross.case", - color: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0), + color: OpenClawBrand.info, isBusy: self.isRefreshingGateway) { Task { await self.runDiagnostics() } @@ -1040,7 +1040,7 @@ extension SettingsProTab { func settingsSwitchIndicator(isOn: Bool) -> some View { Capsule() - .fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35)) + .fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) .frame(width: 52, height: 32) .overlay(alignment: isOn ? .trailing : .leading) { Circle() diff --git a/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift b/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift index 25a8a07dd089..92c74c4741f6 100644 --- a/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift +++ b/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift @@ -90,7 +90,7 @@ private struct ExecApprovalPromptCard: View { if let errorText = self.normalized(self.errorText) { Text(errorText) .font(.footnote) - .foregroundStyle(.red) + .foregroundStyle(OpenClawBrand.danger) } if self.isResolving { diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift b/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift index 9687c1161878..e1616fb68285 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift @@ -40,7 +40,7 @@ struct OnboardingIntroStep: View { HStack(alignment: .top, spacing: 12) { Image(systemName: "exclamationmark.triangle.fill") .font(.title3.weight(.semibold)) - .foregroundStyle(.orange) + .foregroundStyle(OpenClawBrand.warn) .frame(width: 24) .padding(.top, 2) @@ -177,7 +177,7 @@ struct OnboardingModeRow: View { } Spacer() Image(systemName: self.selected ? "checkmark.circle.fill" : "circle") - .foregroundStyle(self.selected ? Color.accentColor : Color.secondary) + .foregroundStyle(self.selected ? OpenClawBrand.accent : Color.secondary) } .contentShape(Rectangle()) } diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift index d5e7ad792749..061c67ed11c1 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift @@ -378,7 +378,7 @@ struct OnboardingWizardView: View { private func onboardingSwitchIndicator(isOn: Bool) -> some View { Capsule() - .fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35)) + .fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) .frame(width: 52, height: 32) .overlay(alignment: isOn ? .trailing : .leading) { Circle() @@ -575,7 +575,7 @@ struct OnboardingWizardView: View { Image(systemName: "checkmark.circle.fill") .font(.system(size: 64)) - .foregroundStyle(.green) + .foregroundStyle(OpenClawBrand.ok) .padding(.bottom, 20) Text("Connected") diff --git a/apps/ios/Sources/OpenClawApp.swift b/apps/ios/Sources/OpenClawApp.swift index 72a431360f59..11bb32bfecca 100644 --- a/apps/ios/Sources/OpenClawApp.swift +++ b/apps/ios/Sources/OpenClawApp.swift @@ -632,6 +632,7 @@ struct OpenClawApp: App { var body: some Scene { WindowGroup { RootTabs() + .tint(OpenClawBrand.accent) .preferredColorScheme(self.appearancePreference.colorScheme) .environment(self.appModel) .environment(self.appModel.voiceWake) @@ -686,6 +687,7 @@ struct OpenClawApp: App { .flatMap(\.windows) .forEach { window in window.overrideUserInterfaceStyle = style + window.tintColor = OpenClawBrand.uiAccent } } } diff --git a/apps/ios/Sources/Voice/TalkPermissionPromptView.swift b/apps/ios/Sources/Voice/TalkPermissionPromptView.swift index 446d75109ace..d701f5fb16a3 100644 --- a/apps/ios/Sources/Voice/TalkPermissionPromptView.swift +++ b/apps/ios/Sources/Voice/TalkPermissionPromptView.swift @@ -35,7 +35,7 @@ struct TalkPermissionPromptView: View { HStack(alignment: .top, spacing: 12) { Image(systemName: self.iconSystemName) .font(.title3.weight(.semibold)) - .foregroundStyle(self.requestIsPending ? Color.orange : Color.accentColor) + .foregroundStyle(self.requestIsPending ? OpenClawBrand.warn : OpenClawBrand.accent) .frame(width: 28, height: 28) VStack(alignment: .leading, spacing: 6) { @@ -51,7 +51,7 @@ struct TalkPermissionPromptView: View { if let failureMessage = self.state.failureMessage { Label(failureMessage, systemImage: "exclamationmark.triangle.fill") .font(.footnote) - .foregroundStyle(.red) + .foregroundStyle(OpenClawBrand.danger) .fixedSize(horizontal: false, vertical: true) } @@ -99,7 +99,7 @@ struct TalkPermissionPromptView: View { .overlay { if self.style == .card || self.style == .sheet { RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.accentColor.opacity(0.20), lineWidth: 1) + .stroke(OpenClawBrand.accent.opacity(0.20), lineWidth: 1) } } .task(id: self.pollTaskKey) { diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift index 8d40b277e1d4..0e8b99827d07 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift @@ -283,7 +283,7 @@ struct OpenClawChatComposer: View { } .padding(.horizontal, 8) .padding(.vertical, 5) - .background(Color.accentColor.opacity(0.08)) + .background(OpenClawChatTheme.accent.opacity(0.08)) .clipShape(Capsule()) } } @@ -550,7 +550,7 @@ struct OpenClawChatComposer: View { .frame(width: self.sendButtonSize, height: self.sendButtonSize) .background( RoundedRectangle(cornerRadius: self.sendButtonCornerRadius, style: .continuous) - .fill(Color.red)) + .fill(OpenClawChatTheme.danger)) .contentShape(RoundedRectangle(cornerRadius: self.sendButtonCornerRadius, style: .continuous)) .accessibilityLabel("Stop response") .disabled(self.viewModel.isAborting) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift index 0ebf3cdce60c..4b0c60916e95 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMarkdownRenderer.swift @@ -57,7 +57,7 @@ private struct ChatMarkdownStyle: ViewModifier { } private var inlineStyle: InlineStyle { - let linkColor: Color = self.context == .user ? self.textColor : .accentColor + let linkColor: Color = self.context == .user ? self.textColor : OpenClawChatTheme.accent let codeScale: CGFloat = self.variant == .compact ? 0.85 : 0.9 return InlineStyle() .code(.monospaced, .fontScale(codeScale)) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift index 4167874d59ff..83a44e7384a5 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatMessageViews.swift @@ -25,7 +25,7 @@ struct ChatAgentAvatar: View { .fill( LinearGradient( colors: [ - (self.tint ?? Color.accentColor).opacity(0.95), + (self.tint ?? OpenClawChatTheme.accent).opacity(0.95), Color(red: 38 / 255.0, green: 40 / 255.0, blue: 43 / 255.0), ], startPoint: .topLeading, @@ -33,7 +33,7 @@ struct ChatAgentAvatar: View { .overlay( Circle() .strokeBorder(Color.white.opacity(0.18), lineWidth: 1)) - .shadow(color: (self.tint ?? Color.accentColor).opacity(0.18), radius: 8, y: 4) + .shadow(color: (self.tint ?? OpenClawChatTheme.accent).opacity(0.18), radius: 8, y: 4) .accessibilityLabel(self.name.map { "\($0) avatar" } ?? "Agent avatar") } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift index 69e728e45efe..46de465f54ea 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift @@ -152,6 +152,18 @@ enum OpenClawChatTheme { #endif } + static var accent: Color { + self.userBubble + } + + static var danger: Color { + #if os(macOS) + Color(nsColor: .systemRed) + #else + Color(uiColor: .systemRed) + #endif + } + static var assistantBubble: Color { #if os(macOS) Color(nsColor: self.assistantBubbleDynamicNSColor) diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView+Previews.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView+Previews.swift index 47a8fe853ea5..f104a2622fe4 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView+Previews.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView+Previews.swift @@ -235,7 +235,7 @@ private struct OpenClawChatPreviewTransport: OpenClawChatTransport { showsSessionSwitcher: false, style: .onboarding, markdownVariant: .standard, - userAccent: .blue) + userAccent: OpenClawChatTheme.accent) } private struct OpenClawChatPreview: View { @@ -250,7 +250,7 @@ private struct OpenClawChatPreview: View { showsSessionSwitcher: true, style: .standard, markdownVariant: .standard, - userAccent: .blue, + userAccent: OpenClawChatTheme.accent, showsAssistantTrace: true) } } diff --git a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift index 0d44c13317e1..b6306241b720 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatView.swift @@ -372,7 +372,7 @@ public struct OpenClawChatView: View { systemImage: "bubble.left.and.bubble.right.fill", title: self.emptyStateTitle, message: self.emptyStateMessage, - tint: .accentColor, + tint: OpenClawChatTheme.accent, actionTitle: nil, action: nil) .padding(.horizontal, 24)