feat(ios): implement branded typography design system

Add OpenClaw design system foundations:
- Custom typography tokens (OpenClawType) using Plus Jakarta Sans,
  DM Sans, and JetBrains Mono with Dynamic Type scaling
- Spacing and radius token enums (OpenClawSpacing, OpenClawRadius)
- Additive brand color tokens (accentPressed, teal, void, obsidian,
  slate, textPrimary, textSecondary)
- Shared components: OpenClawToggleIndicator, OpenClawStatusBadge,
  OpenClawPrimaryButtonStyle, OpenClawSecondaryButtonStyle
- Bundled font files with OFL license provenance

Migrate all iOS views to OpenClawType tokens. No behavioral, navigation,
or routing changes — this is purely visual/typographic.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
joelnishanth
2026-07-02 13:59:22 -07:00
committed by Josh Avant
parent 1fee4a5daf
commit 39b703dfcf
52 changed files with 921 additions and 495 deletions
@@ -122,9 +122,9 @@ struct AgentProDreamingDestination: View {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.headline)
.font(OpenClawType.headline)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -139,7 +139,7 @@ struct AgentProDreamingDestination: View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Memory State")
.font(.headline)
.font(OpenClawType.headline)
Spacer()
ProValuePill(value: self.dreamingValue, color: self.dreamingColor)
}
@@ -156,7 +156,7 @@ struct AgentProDreamingDestination: View {
}
if let storeError = self.normalized(self.overview?.dreaming?.storeError) {
Text(storeError)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -170,9 +170,9 @@ struct AgentProDreamingDestination: View {
HStack {
VStack(alignment: .leading, spacing: 3) {
Text("Maintenance")
.font(.headline)
.font(OpenClawType.headline)
Text("Refresh reads live state. Maintenance actions update the gateway diary/artifacts.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -203,7 +203,7 @@ struct AgentProDreamingDestination: View {
if let dreamActionStatusText {
Text(dreamActionStatusText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(3)
}
@@ -225,10 +225,10 @@ struct AgentProDreamingDestination: View {
ProIconBadge(systemName: "book.pages", color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 2) {
Text(diary.path)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.dreamDiaryUpdatedLabel(diary))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -287,7 +287,7 @@ struct AgentProDreamingDestination: View {
.lineLimit(1)
.minimumScaleFactor(0.75)
}
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.primary)
.padding(.horizontal, 10)
.frame(height: 34)
@@ -300,22 +300,24 @@ struct AgentProDreamingDestination: View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .firstTextBaseline) {
Text(day.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Spacer(minLength: 8)
Text("\(day.entryCount) \(day.entryCount == 1 ? "entry" : "entries")")
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(OpenClawBrand.accent)
}
Text(day.body)
.font(.caption.monospaced())
.font(OpenClawType.monoSmall)
.foregroundStyle(.primary)
.lineLimit(120)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(10)
.background(Color.primary.opacity(0.045), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.background(
Color.primary.opacity(0.045),
in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous))
}
private func selectedDreamDiaryDay(from days: [DreamDiaryDay]) -> DreamDiaryDay? {
@@ -362,21 +364,21 @@ struct AgentProDreamingDestination: View {
ProIconBadge(systemName: "text.page", color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 4) {
Text(self.dreamingEntryTitle(entry))
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(entry.snippet)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(4)
.textSelection(.enabled)
Text(self.dreamingEntryDetail(entry))
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
Text("\(entry.totalSignalCount)")
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(OpenClawBrand.accent)
.lineLimit(1)
}
@@ -428,21 +430,21 @@ struct AgentProDreamingDestination: View {
color: phase.status.enabled == false ? .secondary : OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 4) {
Text(phase.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(self.dreamingPhaseDetail(phase.status))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
if let cron = self.normalized(phase.status.cron) {
Text(cron)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
Spacer(minLength: 8)
Text(self.dreamingPhaseState(phase.status))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(phase.status.managedCronPresent == true ? OpenClawBrand.accent : .secondary)
.lineLimit(1)
}
@@ -455,9 +457,9 @@ struct AgentProDreamingDestination: View {
ProIconBadge(systemName: icon, color: .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -468,16 +470,18 @@ struct AgentProDreamingDestination: View {
private func detailMetric(label: String, value: String) -> some View {
VStack(alignment: .leading, spacing: 3) {
Text(label)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
Text(value)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
.minimumScaleFactor(0.8)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.background(
Color.primary.opacity(0.055),
in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous))
}
private func dreamingEntryTitle(_ entry: DreamingEntryLite) -> String {
@@ -56,9 +56,9 @@ struct AgentProNodesDestination: View {
ProIconBadge(systemName: "display", color: self.instancesColor)
VStack(alignment: .leading, spacing: 3) {
Text("Instances")
.font(.headline)
.font(OpenClawType.headline)
Text(self.instancesDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -73,7 +73,7 @@ struct AgentProNodesDestination: View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Presence")
.font(.headline)
.font(OpenClawType.headline)
Spacer()
ProValuePill(value: self.instancesValue, color: self.instancesColor)
}
@@ -134,27 +134,27 @@ struct AgentProNodesDestination: View {
ProIconBadge(systemName: Self.presenceIcon(entry), color: Self.presenceColor(entry))
VStack(alignment: .leading, spacing: 4) {
Text(Self.presenceLabel(entry) ?? "Instance")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(Self.presenceDetail(entry))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
if let meta = Self.presenceMeta(entry) {
Text(meta)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
Spacer(minLength: 8)
Text(Self.presenceState(entry))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(Self.presenceColor(entry))
.lineLimit(1)
if showsChevron {
Image(systemName: "chevron.right")
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
.foregroundStyle(.secondary)
.padding(.top, 2)
}
@@ -173,9 +173,9 @@ struct AgentProNodesDestination: View {
ProIconBadge(systemName: Self.presenceIcon(entry), color: Self.presenceColor(entry))
VStack(alignment: .leading, spacing: 3) {
Text(Self.presenceLabel(entry) ?? "Instance")
.font(.headline)
.font(OpenClawType.headline)
Text(Self.presenceDetail(entry))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -233,7 +233,7 @@ struct AgentProNodesDestination: View {
.disabled(normalized == "n/a")
.accessibilityLabel("Copy \(title)")
}
.font(.subheadline)
.font(OpenClawType.subhead)
.padding(.vertical, 10)
}
@@ -243,14 +243,14 @@ struct AgentProNodesDestination: View {
ProCard {
if values.isEmpty {
Text("None reported.")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .leading)
} else {
VStack(alignment: .leading, spacing: 8) {
ForEach(values, id: \.self) { value in
Text(value)
.font(.caption.monospaced())
.font(OpenClawType.monoSmall)
.textSelection(.enabled)
.frame(maxWidth: .infinity, alignment: .leading)
}
@@ -264,16 +264,18 @@ struct AgentProNodesDestination: View {
private func detailMetric(label: String, value: String) -> some View {
VStack(alignment: .leading, spacing: 3) {
Text(label)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
Text(value)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
.minimumScaleFactor(0.8)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.background(
Color.primary.opacity(0.055),
in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous))
}
private func emptyRow(icon: String, title: String, detail: String) -> some View {
@@ -281,9 +283,9 @@ struct AgentProNodesDestination: View {
ProIconBadge(systemName: icon, color: .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -8,7 +8,7 @@ extension AgentProTab {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Scheduler")
.font(.headline)
.font(OpenClawType.headline)
Spacer()
ProValuePill(
value: self.overview?.cronStatus?.enabled == true ? "on" : "off",
@@ -23,7 +23,7 @@ extension AgentProTab {
}
if let cronActionStatusText {
Text(cronActionStatusText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -83,14 +83,14 @@ extension AgentProTab {
color: job.enabled ? OpenClawBrand.accent : .secondary)
VStack(alignment: .leading, spacing: 4) {
Text(job.name)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.cronJobDetail(job))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
Text(self.cronScheduleSummary(job))
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
HStack(spacing: 8) {
@@ -118,7 +118,7 @@ extension AgentProTab {
.controlSize(.small)
} else {
Text(self.cronJobState(job))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(job.enabled ? OpenClawBrand.accent : .secondary)
.lineLimit(1)
}
@@ -199,9 +199,9 @@ extension AgentProTab {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.headline)
.font(OpenClawType.headline)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -5,16 +5,18 @@ extension AgentProTab {
func detailMetric(label: String, value: String) -> some View {
VStack(alignment: .leading, spacing: 3) {
Text(label)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
Text(value)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
.minimumScaleFactor(0.8)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
.background(
Color.primary.opacity(0.055),
in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous))
}
func emptyDetailRow(icon: String, title: String, detail: String) -> some View {
@@ -22,9 +24,9 @@ extension AgentProTab {
ProIconBadge(systemName: icon, color: .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -36,7 +36,7 @@ extension AgentProTab {
TextField("Search agents", text: self.$agentSearchText)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(.subheadline)
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
.frame(height: 38)
.transition(.move(edge: .top).combined(with: .opacity))
@@ -77,7 +77,7 @@ extension AgentProTab {
}
} label: {
Image(systemName: "xmark.circle.fill")
.font(.title3)
.font(OpenClawType.title3)
.foregroundStyle(.secondary)
.frame(width: 44, height: 44)
.contentShape(Circle())
@@ -185,7 +185,7 @@ extension AgentProTab {
if let overviewErrorText {
Text(overviewErrorText)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(OpenClawBrand.warn)
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
@@ -245,9 +245,9 @@ extension AgentProTab {
ProIconBadge(systemName: "person.2.slash", color: .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(self.emptyAgentsTitle)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(self.emptyAgentsDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer()
@@ -266,12 +266,12 @@ extension AgentProTab {
VStack(alignment: .leading, spacing: 3) {
Text(self.agentName(for: agent))
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.primary)
.lineLimit(1)
Text(self.agentDetail(for: agent))
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -281,7 +281,7 @@ extension AgentProTab {
if isActive {
Image(systemName: "checkmark")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(OpenClawBrand.accent)
.frame(width: 24, height: 44)
.accessibilityHidden(true)
@@ -302,7 +302,7 @@ extension AgentProTab {
{
Button(action: action) {
Image(systemName: systemName)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.frame(width: AgentLayout.filterHeight, height: AgentLayout.filterHeight)
}
.buttonBorderShape(.circle)
@@ -342,20 +342,20 @@ extension AgentProTab {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
Text(value)
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(color)
.lineLimit(1)
if showsChevron {
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
}
}
@@ -410,15 +410,15 @@ extension AgentProTab {
ProValuePill(value: value, color: color)
if showsChevron {
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
Text(detail)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(2)
.multilineTextAlignment(.leading)
@@ -434,11 +434,11 @@ extension AgentProTab {
ProIconBadge(systemName: "clock.badge.questionmark", color: .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(self.gatewayConnected ? "No scheduled jobs" : "Cron unavailable")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(self.gatewayConnected
? "The gateway has no visible cron jobs."
: "Connect a gateway to load scheduled work.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer()
@@ -452,16 +452,16 @@ extension AgentProTab {
color: job.enabled ? OpenClawBrand.accent : .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(job.name)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.cronJobDetail(job))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
Text(self.cronJobState(job))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(job.enabled ? OpenClawBrand.accent : .secondary)
.lineLimit(1)
}
@@ -9,9 +9,9 @@ extension AgentProTab {
HStack(alignment: .firstTextBaseline) {
VStack(alignment: .leading, spacing: 3) {
Text(self.activeAgentName)
.font(.headline)
.font(OpenClawType.headline)
Text(self.skillPolicySummary)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -41,12 +41,12 @@ extension AgentProTab {
if let skillMutationStatusText {
Text(skillMutationStatusText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.accent)
}
if let skillMutationErrorText {
Text(skillMutationErrorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -59,12 +59,12 @@ extension AgentProTab {
VStack(alignment: .leading, spacing: 10) {
HStack(spacing: 10) {
Image(systemName: "magnifyingglass")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
TextField("Search skills", text: self.$skillFilter)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(.subheadline)
.font(OpenClawType.subhead)
if !self.skillFilter.isEmpty {
Button {
self.skillFilter = ""
@@ -94,9 +94,9 @@ extension AgentProTab {
ProIconBadge(systemName: "square.and.arrow.down", color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 2) {
Text("Install Skills")
.font(.headline)
.font(OpenClawType.headline)
Text("Search ClawHub and install into this workspace.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -114,7 +114,7 @@ extension AgentProTab {
TextField("Search ClawHub", text: self.$clawHubQuery)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(.subheadline)
.font(OpenClawType.subhead)
.submitLabel(.search)
.onSubmit {
Task { await self.searchClawHubSkills() }
@@ -126,7 +126,7 @@ extension AgentProTab {
}
if let clawHubErrorText {
Text(clawHubErrorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
if !self.clawHubResults.isEmpty {
@@ -151,10 +151,10 @@ extension AgentProTab {
ProIconBadge(systemName: "sparkles", color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 3) {
Text(result.displayName)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(result.summary ?? result.slug)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -277,21 +277,21 @@ extension AgentProTab {
ProIconBadge(systemName: self.isSkillAllowed(skill) ? "checkmark.circle" : "nosign", color: status.color)
VStack(alignment: .leading, spacing: 4) {
Text(skill.displayName)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.normalized(skill.description) ?? self.normalized(skill.source) ?? "Workspace skill")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
if let missing = skill.missingSummary {
Text("Missing: \(missing)")
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
.lineLimit(1)
}
if let install = skill.installSummary {
Text("Setup: \(install)")
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -321,7 +321,7 @@ extension AgentProTab {
.accessibilityLabel("Edit \(skill.displayName)")
}
Text(busy ? "saving" : status.text)
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(status.color)
.lineLimit(1)
}
@@ -424,9 +424,9 @@ extension AgentProTab {
color: status.color)
VStack(alignment: .leading, spacing: 3) {
Text(skill.displayName)
.font(.headline)
.font(OpenClawType.headline)
Text(self.normalized(skill.description) ?? self.normalized(skill.source) ?? "Workspace skill")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(3)
}
@@ -451,7 +451,7 @@ extension AgentProTab {
if let primaryEnv = skill.primaryEnv, !primaryEnv.isEmpty {
VStack(alignment: .leading, spacing: 8) {
Text("API key")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
SecureField(primaryEnv, text: self.skillAPIKeyBinding(for: skill))
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
@@ -465,14 +465,14 @@ extension AgentProTab {
.disabled(self.isSkillConfigBusy(skill))
if let homepage = skill.homepageURL {
Link("Get key", destination: homepage)
.font(.caption)
.font(OpenClawType.caption)
}
}
}
if let message = self.skillConfigMessages[skill.effectiveSkillKey] {
Text(message.text)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(message.kind == .success ? OpenClawBrand.accent : OpenClawBrand.warn)
}
}
@@ -494,7 +494,7 @@ extension AgentProTab {
HStack {
Text(title)
Spacer(minLength: 8)
self.skillEditorSwitchIndicator(isOn: isOn)
OpenClawToggleIndicator(isOn: isOn)
}
.contentShape(Rectangle())
}
@@ -504,31 +504,18 @@ extension AgentProTab {
.accessibilityValue(isOn ? "On" : "Off")
}
func skillEditorSwitchIndicator(isOn: Bool) -> some View {
Capsule()
.fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
.frame(width: 52, height: 32)
.overlay(alignment: isOn ? .trailing : .leading) {
Circle()
.fill(Color.white)
.frame(width: 28, height: 28)
.padding(2)
.shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1)
}
}
func skillEditorSetup(_ skill: SkillStatusEntryLite) -> some View {
ProCard(radius: AgentLayout.cardRadius) {
VStack(alignment: .leading, spacing: 10) {
Text("Setup")
.font(.headline)
.font(OpenClawType.headline)
if let missing = skill.missingSummary {
Text("Missing: \(missing)")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(OpenClawBrand.warn)
} else {
Text("No missing requirements reported.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
if let install = skill.install?.first {
@@ -553,7 +540,7 @@ extension AgentProTab {
self.detailMetric(label: "Source", value: self.normalized(skill.source) ?? "unknown")
if let filePath = self.normalized(skill.filePath) {
Text(filePath)
.font(.caption2.monospaced())
.font(OpenClawType.monoSmall)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -8,7 +8,7 @@ extension AgentProTab {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Totals")
.font(.headline)
.font(OpenClawType.headline)
Spacer()
ProValuePill(
value: "\(self.overview?.usage?.days ?? 31)d",
@@ -67,14 +67,14 @@ extension AgentProTab {
ProIconBadge(systemName: "calendar", color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 3) {
Text(day.date)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text("\(Self.compactNumber(day.totalTokens ?? 0)) tokens")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
Text(Self.currency(day.totalCost ?? 0))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(OpenClawBrand.accent)
}
.padding(.vertical, 10)
+24 -39
View File
@@ -79,7 +79,6 @@ struct ChatProTab: View {
composerChrome: .clean,
isComposerEnabled: self.gatewayConnected,
messagePlaceholder: self.messagePlaceholder,
emptyAssistantIntro: "What would you like to work on?",
talkControl: self.talkControl)
.id(ObjectIdentifier(viewModel))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
@@ -87,9 +86,9 @@ struct ChatProTab: View {
ProCard {
VStack(alignment: .leading, spacing: 8) {
Text("Chat is preparing")
.font(.headline)
.font(OpenClawType.headline)
Text("The operator session will attach when the gateway is ready.")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
}
}
@@ -108,8 +107,8 @@ struct ChatProTab: View {
OpenClawAdaptiveHeaderRow(
title: self.headerDisplayTitle,
subtitle: self.headerDisplaySubtitle,
titleFont: .headline.weight(.semibold),
subtitleFont: .caption,
titleFont: OpenClawType.headline,
subtitleFont: OpenClawType.caption,
subtitleLineLimit: 1)
{
HStack(spacing: 11) {
@@ -119,25 +118,26 @@ struct ChatProTab: View {
self.headerIdentityBadge
}
} accessory: {
self.connectionStatusButton
self.connectionPillButton
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
.padding(.bottom, 2)
.padding(.bottom, 4)
}
@ViewBuilder
private var headerIdentityBadge: some View {
if self.showsAgentBadge {
Text(self.agentBadge)
.font(.system(size: self.agentBadge.count > 2 ? 11 : 14, weight: .bold, design: .rounded))
.font(.system(size: self.agentBadge.count > 2 ? 13 : 16, weight: .bold, design: .rounded))
.foregroundStyle(.white)
.minimumScaleFactor(0.6)
.lineLimit(1)
.frame(width: 32, height: 32)
.frame(width: 38, height: 38)
.background(
Circle()
.fill(OpenClawBrand.accent))
.fill(OpenClawBrand.accent.gradient))
.overlay(Circle().strokeBorder(.white.opacity(0.18), lineWidth: 1))
.shadow(color: OpenClawBrand.accent.opacity(0.14), radius: 5, y: 2)
} else {
ProIconBadge(systemName: "bubble.left", color: OpenClawBrand.accent)
}
@@ -196,44 +196,29 @@ struct ChatProTab: View {
}
@ViewBuilder
private var connectionStatusButton: some View {
private var connectionPillButton: some View {
if let openSettings {
Button(action: openSettings) {
self.connectionStatusIcon
self.connectionPill
}
.buttonStyle(.plain)
.contentShape(Circle())
.accessibilityLabel(self.gatewayAccessibilityLabel)
.buttonBorderShape(.capsule)
.openClawGlassButton()
.accessibilityHint("Opens Settings / Gateway")
.accessibilityIdentifier("chat-gateway-status")
} else {
self.connectionStatusIcon
.accessibilityLabel(self.gatewayAccessibilityLabel)
self.connectionPill
}
}
private var connectionStatusIcon: some View {
Image(systemName: self.gatewayStatusSymbol)
.font(.subheadline.weight(.semibold))
.foregroundStyle(self.gatewayPillColor)
.frame(width: 44, height: 44)
}
private var gatewayStatusSymbol: String {
switch self.gatewayDisplayState {
case .connected:
self.gatewayConnected ? "checkmark.circle.fill" : "exclamationmark.circle"
case .connecting:
"arrow.trianglehead.2.clockwise.rotate.90"
case .error:
"exclamationmark.triangle.fill"
case .disconnected:
"wifi.slash"
private var connectionPill: some View {
HStack(spacing: 6) {
ProStatusDot(color: self.gatewayPillColor)
Text(Self.gatewayPillTitle(state: self.gatewayDisplayState, isGatewayUsable: self.gatewayConnected))
.font(OpenClawType.captionSemiBold)
.lineLimit(1)
}
}
private var gatewayAccessibilityLabel: String {
"Gateway: \(Self.gatewayPillTitle(state: self.gatewayDisplayState, isGatewayUsable: self.gatewayConnected))"
.foregroundStyle(self.gatewayPillColor)
.padding(.horizontal, 4)
.frame(height: 30)
}
private var gatewayConnected: Bool {
@@ -42,27 +42,27 @@ struct CommandSessionRow: View {
var body: some View {
HStack(alignment: .center, spacing: 12) {
Image(systemName: self.item.icon)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(self.item.color)
.frame(width: 30, height: 30)
.background {
RoundedRectangle(cornerRadius: 9, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)
.fill(self.item.color.opacity(0.12))
}
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(self.item.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
.minimumScaleFactor(0.82)
Spacer(minLength: 6)
Text(self.item.trailing)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
}
HStack(spacing: 8) {
Text(self.item.detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
Spacer(minLength: 6)
@@ -71,7 +71,7 @@ struct CommandSessionRow: View {
.frame(width: 68)
}
Text(self.progressLabel)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(self.item.color)
.lineLimit(1)
.frame(width: 48, alignment: .trailing)
@@ -97,7 +97,7 @@ struct CommandSessionRow: View {
struct CommandViewMoreRow: View {
var body: some View {
Label("View More", systemImage: "chevron.right")
.font(.subheadline.weight(.bold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(OpenClawBrand.accent)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
@@ -113,19 +113,19 @@ struct CommandEmptyStateRow: View {
var body: some View {
HStack(spacing: 10) {
Image(systemName: self.icon)
.font(.caption.weight(.bold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(OpenClawBrand.ok)
.frame(width: 30, height: 30)
.background {
RoundedRectangle(cornerRadius: 8, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)
.fill(OpenClawBrand.ok.opacity(0.10))
}
VStack(alignment: .leading, spacing: 2) {
Text(self.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.detail)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -135,7 +135,7 @@ struct CommandCenterTab: View {
} accessory: {
Button(action: self.openSettings) {
Image(systemName: "gearshape.fill")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.frame(width: OpenClawProMetric.compactControlSize, height: OpenClawProMetric.compactControlSize)
}
.openClawGlassButton()
@@ -195,15 +195,15 @@ struct CommandCenterTab: View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 5) {
Image(systemName: icon)
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
.foregroundStyle(color)
Text(title)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Text(value)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(title == "Connection" ? color : .primary)
.lineLimit(1)
.minimumScaleFactor(0.72)
@@ -275,7 +275,7 @@ struct CommandCenterTab: View {
private func cardHeader(title: String) -> some View {
HStack(spacing: 8) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.secondary)
Spacer(minLength: 8)
}
@@ -659,9 +659,9 @@ struct CommandSessionsScreen: View {
VStack(alignment: .leading, spacing: 4) {
Text("Sessions")
.font(.system(size: 27, weight: .bold, design: .rounded))
.font(OpenClawType.title2)
Text(self.headerDetail)
.font(.caption.weight(.medium))
.font(OpenClawType.captionMedium)
.foregroundStyle(.secondary)
}
}
@@ -673,7 +673,7 @@ struct CommandSessionsScreen: View {
VStack(spacing: 0) {
HStack(spacing: 8) {
Text("Recent sessions")
.font(.subheadline.weight(.bold))
.font(OpenClawType.subheadSemiBold)
Spacer(minLength: 8)
if self.isLoading {
ProgressView()
@@ -64,7 +64,7 @@ struct IPadSkillWorkshopScreen: View {
ScrollView {
self.presentedProposalDetail(proposalID: route.proposalID)
.padding(.horizontal, OpenClawProMetric.pagePadding)
.padding(.vertical, 16)
.padding(.vertical, OpenClawSpacing.space4)
}
.background(OpenClawProBackground())
.navigationTitle("Proposal")
@@ -130,12 +130,12 @@ struct IPadSkillWorkshopScreen: View {
}
if let noticeText {
Text(noticeText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.accent)
}
if let errorText {
Text(errorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -149,9 +149,9 @@ struct IPadSkillWorkshopScreen: View {
HStack(alignment: .firstTextBaseline, spacing: 10) {
VStack(alignment: .leading, spacing: 3) {
Text("\(self.filteredProposals.count) proposals")
.font(.headline)
.font(OpenClawType.headline)
Text(self.statusFilterLabel)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -186,12 +186,12 @@ struct IPadSkillWorkshopScreen: View {
}
if let noticeText {
Text(noticeText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.accent)
}
if let errorText {
Text(errorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -202,12 +202,12 @@ struct IPadSkillWorkshopScreen: View {
private var proposalSearchField: some View {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
TextField("Search proposals", text: self.$query)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(.subheadline)
.font(OpenClawType.subhead)
if !self.query.isEmpty {
Button {
self.query = ""
@@ -223,7 +223,7 @@ struct IPadSkillWorkshopScreen: View {
private var agentScopeMenu: some View {
HStack(spacing: 8) {
Text("Agent")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
Menu {
Button("Default agent") {
@@ -237,10 +237,10 @@ struct IPadSkillWorkshopScreen: View {
} label: {
HStack(spacing: 6) {
Text(self.agentScopeLabel)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Image(systemName: "chevron.up.chevron.down")
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
@@ -416,9 +416,9 @@ struct IPadSkillWorkshopScreen: View {
ProIconBadge(systemName: "hammer", color: proposal.statusColor)
VStack(alignment: .leading, spacing: 4) {
Text(proposal.title)
.font(.headline)
.font(OpenClawType.headline)
Text(proposal.description)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
@@ -432,23 +432,23 @@ struct IPadSkillWorkshopScreen: View {
if let content = proposal.content, !content.isEmpty {
Text(content)
.font(.caption.monospaced())
.font(OpenClawType.monoSmall)
.foregroundStyle(.secondary)
.lineLimit(16)
.textSelection(.enabled)
} else {
Text("Select refresh to load the proposal body.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
if !proposal.supportFiles.isEmpty {
VStack(alignment: .leading, spacing: 6) {
Text("Support files")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
ForEach(proposal.supportFiles, id: \.path) { file in
Text(file.path)
.font(.caption2.monospaced())
.font(OpenClawType.monoSmall)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -579,7 +579,7 @@ struct IPadSkillWorkshopScreen: View {
Image(systemName: "lock.shield")
.foregroundStyle(OpenClawBrand.warn)
Text("Admin scope required.")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
Spacer(minLength: 8)
}
@@ -951,11 +951,11 @@ private struct IPadSkillProposalKanbanCard: View {
color: self.proposal.statusColor)
VStack(alignment: .leading, spacing: 4) {
Text(self.proposal.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(self.isSelected ? OpenClawBrand.accent : .primary)
.lineLimit(2)
Text(self.proposal.description)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(3)
}
@@ -964,7 +964,7 @@ private struct IPadSkillProposalKanbanCard: View {
ProValuePill(value: self.proposal.status, color: self.proposal.statusColor)
Spacer(minLength: 4)
Text(self.proposal.ageLabel)
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(.secondary)
}
}
@@ -1000,10 +1000,10 @@ private struct IPadSkillProposalKanbanCard: View {
.disabled(self.isInspecting)
}
}
.padding(12)
.padding(OpenClawSpacing.space3)
.background(
self.isSelected ? OpenClawBrand.accent.opacity(0.08) : Color.clear,
in: RoundedRectangle(cornerRadius: 8, style: .continuous))
in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous))
.contentShape(Rectangle())
.contextMenu {
Button("Inspect", action: self.inspect)
@@ -1027,24 +1027,24 @@ struct IPadSkillProposalRow: View {
ProIconBadge(systemName: self.isBusy ? "hourglass" : "hammer", color: self.proposal.statusColor)
VStack(alignment: .leading, spacing: 4) {
Text(self.proposal.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(self.isSelected ? OpenClawBrand.accent : .primary)
.lineLimit(1)
Text(self.proposal.description)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
Spacer(minLength: 8)
Text(self.proposal.ageLabel)
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
.background(
self.isSelected ? OpenClawBrand.danger.opacity(0.08) : Color.clear,
in: RoundedRectangle(cornerRadius: 8, style: .continuous))
in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous))
}
}
@@ -105,12 +105,12 @@ struct IPadWorkboardScreen: View {
self.boardScopeMenu
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
TextField("Search cards", text: self.$query)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(.subheadline)
.font(OpenClawType.subhead)
if !self.query.isEmpty {
Button {
self.query = ""
@@ -164,12 +164,12 @@ struct IPadWorkboardScreen: View {
if let dispatchSummaryText {
Text(dispatchSummaryText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.accent)
}
if let errorText {
Text(errorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -182,7 +182,7 @@ struct IPadWorkboardScreen: View {
VStack(alignment: .leading, spacing: 9) {
HStack(alignment: .firstTextBaseline, spacing: 10) {
Text("\(self.filteredCards.count) cards")
.font(.headline)
.font(OpenClawType.headline)
Spacer(minLength: 8)
self.compactRefreshButton
}
@@ -206,19 +206,19 @@ struct IPadWorkboardScreen: View {
}
} else {
Text(Self.compactWriteUnavailableMessage(canRead: self.canRead))
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(2)
}
if let dispatchSummaryText {
Text(dispatchSummaryText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.accent)
}
if let errorText {
Text(errorText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -231,7 +231,7 @@ struct IPadWorkboardScreen: View {
Task { await self.loadCards(force: true) }
} label: {
Image(systemName: "arrow.clockwise")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.frame(width: 32, height: 32)
}
.buttonStyle(.plain)
@@ -266,19 +266,21 @@ struct IPadWorkboardScreen: View {
} label: {
HStack(spacing: 8) {
Image(systemName: "rectangle.stack")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
Text(self.boardScopeLabel)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.lineLimit(1)
Spacer(minLength: 4)
Image(systemName: "chevron.up.chevron.down")
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
}
.padding(.horizontal, 10)
.frame(height: 32)
.background(Color.primary.opacity(0.06), in: RoundedRectangle(cornerRadius: 8, style: .continuous))
.background(
Color.primary.opacity(0.06),
in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous))
.overlay {
RoundedRectangle(cornerRadius: 8, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)
.strokeBorder(Color.primary.opacity(0.08), lineWidth: 1)
}
}
@@ -313,7 +315,7 @@ struct IPadWorkboardScreen: View {
self.selectedStatus = status
} label: {
Text(IPadWorkboardDefaults.label(for: status))
.font(.caption2.weight(.semibold))
.font(OpenClawType.caption2SemiBold)
.lineLimit(1)
.padding(.horizontal, 10)
.frame(height: 30)
@@ -339,7 +341,7 @@ struct IPadWorkboardScreen: View {
private var boardScopeMenu: some View {
HStack(spacing: 8) {
Text("Board")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
Menu {
Button("All boards") {
@@ -353,10 +355,10 @@ struct IPadWorkboardScreen: View {
} label: {
HStack(spacing: 6) {
Text(self.boardScopeLabel)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Image(systemName: "chevron.up.chevron.down")
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
@@ -370,7 +372,7 @@ struct IPadWorkboardScreen: View {
private var statusMenu: some View {
HStack(spacing: 8) {
Text("Status")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
Menu {
Button("Active") {
@@ -384,9 +386,9 @@ struct IPadWorkboardScreen: View {
} label: {
HStack(spacing: 6) {
Text(IPadWorkboardDefaults.label(for: self.selectedStatus))
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Image(systemName: "chevron.up.chevron.down")
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
}
.frame(maxWidth: .infinity, alignment: .trailing)
}
@@ -975,10 +977,10 @@ private struct IPadWorkboardKanbanCard: View {
ProIconBadge(systemName: self.icon, color: self.color)
VStack(alignment: .leading, spacing: 4) {
Text(self.card.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(2)
Text(self.detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(3)
}
@@ -986,7 +988,7 @@ private struct IPadWorkboardKanbanCard: View {
if !self.card.labels.isEmpty {
Text(self.card.labels.prefix(3).joined(separator: ", "))
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -1025,7 +1027,7 @@ private struct IPadWorkboardKanbanCard: View {
ProValuePill(value: IPadWorkboardDefaults.label(for: self.card.status), color: self.color)
}
}
.padding(12)
.padding(OpenClawSpacing.space3)
.contentShape(Rectangle())
}
@@ -1078,10 +1080,10 @@ struct IPadWorkboardQueueRow: View {
ProIconBadge(systemName: self.icon, color: self.color)
VStack(alignment: .leading, spacing: 5) {
Text(self.card.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(2)
Text(self.detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -1097,7 +1099,7 @@ struct IPadWorkboardQueueRow: View {
self.actionMenuItems
} label: {
Image(systemName: self.isBusy ? "hourglass" : "ellipsis.circle")
.font(.system(size: 19, weight: .semibold))
.font(OpenClawType.headline)
.frame(width: 36, height: 36)
.contentShape(Rectangle())
}
+16 -2
View File
@@ -61,6 +61,13 @@ enum OpenClawBrand {
static let uiAccentForeground = adaptiveUIColor(light: (183, 56, 51), dark: (255, 107, 102))
static let uiAccentHot = adaptiveUIColor(light: (204, 75, 69), dark: (232, 92, 86))
static let uiAccentHotForeground = adaptiveUIColor(light: (166, 55, 50), dark: (255, 123, 115))
static let uiAccentPressed = adaptiveUIColor(light: (148, 40, 36), dark: (148, 40, 36))
static let uiTeal = adaptiveUIColor(light: (0, 196, 176), dark: (0, 196, 176))
static let uiVoid = adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17))
static let uiObsidian = adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28))
static let uiSlate = adaptiveUIColor(light: (242, 243, 247), dark: (28, 31, 43))
static let uiTextPrimary = adaptiveUIColor(light: (11, 12, 17), dark: (242, 239, 232))
static let uiTextSecondary = adaptiveUIColor(light: (90, 94, 110), dark: (168, 170, 191))
static let uiOK = adaptiveUIColor(light: (19, 122, 62), dark: (48, 209, 88))
static let uiWarn = adaptiveUIColor(light: (154, 87, 0), dark: (255, 214, 10))
static let uiDanger = adaptiveUIColor(light: (185, 28, 28), dark: (252, 165, 165))
@@ -70,12 +77,19 @@ enum OpenClawBrand {
static let accentForeground = Color(uiColor: Self.uiAccentForeground)
static let accentHot = Color(uiColor: Self.uiAccentHot)
static let accentHotForeground = Color(uiColor: Self.uiAccentHotForeground)
static let accentPressed = Color(uiColor: Self.uiAccentPressed)
static let teal = Color(uiColor: Self.uiTeal)
static let void = Color(uiColor: Self.uiVoid)
static let obsidian = Color(uiColor: Self.uiObsidian)
static let slate = Color(uiColor: Self.uiSlate)
static let textPrimary = Color(uiColor: Self.uiTextPrimary)
static let textSecondary = Color(uiColor: Self.uiTextSecondary)
static let danger = Color(uiColor: Self.uiDanger)
static let ok = Color(uiColor: Self.uiOK)
static let warn = Color(uiColor: Self.uiWarn)
static let info = Color(uiColor: Self.uiInfo)
static let graphite = Color(uiColor: adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17)))
static let graphiteElevated = Color(uiColor: adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28)))
static let graphite = void
static let graphiteElevated = obsidian
static var sheetBackground: LinearGradient {
LinearGradient(
@@ -112,15 +112,15 @@ struct OpenClawDocsScreen: View {
ProIconBadge(systemName: icon, color: OpenClawBrand.accent)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
Image(systemName: "arrow.up.right")
.font(.caption.weight(.bold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 14)
@@ -8,6 +8,23 @@ enum OpenClawProMetric {
static let bottomScrollInset: CGFloat = 96
}
enum OpenClawSpacing {
static let space1: CGFloat = 4
static let space2: CGFloat = 8
static let space3: CGFloat = 12
static let space4: CGFloat = 16
static let space6: CGFloat = 24
static let space8: CGFloat = 32
}
enum OpenClawRadius {
static let xs: CGFloat = 8
static let sm: CGFloat = 10
static let md: CGFloat = 12
static let lg: CGFloat = 16
static let xl: CGFloat = 20
}
struct OpenClawProBackground: View {
var body: some View {
Color(uiColor: .systemGroupedBackground)
@@ -24,18 +41,18 @@ struct ProSectionHeader: View {
var body: some View {
HStack {
Text(self.title)
.font(.footnote.weight(.medium))
.font(OpenClawType.footnoteMedium)
.foregroundStyle(.secondary)
.textCase(self.uppercase ? .uppercase : nil)
Spacer()
if let actionTitle {
if let action {
Button(actionTitle, action: action)
.font(.footnote.weight(.medium))
.foregroundStyle(OpenClawBrand.accentForeground)
.font(OpenClawType.footnoteMedium)
.foregroundStyle(OpenClawBrand.accent)
} else {
Text(actionTitle)
.font(.footnote.weight(.medium))
.font(OpenClawType.footnoteMedium)
.foregroundStyle(.secondary)
}
}
@@ -69,7 +86,7 @@ private struct ProPanelBackground: View {
let isProminent: Bool
var body: some View {
let shape = RoundedRectangle(cornerRadius: self.radius, style: .continuous)
let shape = RoundedRectangle(cornerRadius: radius, style: .continuous)
shape
.fill(self.fill)
.overlay {
@@ -96,7 +113,7 @@ private struct ProInsetSurfaceModifier: ViewModifier {
let radius: CGFloat
func body(content: Content) -> some View {
let shape = RoundedRectangle(cornerRadius: self.radius, style: .continuous)
let shape = RoundedRectangle(cornerRadius: radius, style: .continuous)
content.background {
shape
.fill(Color(uiColor: .tertiarySystemGroupedBackground))
@@ -166,26 +183,26 @@ extension View {
radius: CGFloat = OpenClawProMetric.cardRadius,
isProminent: Bool = false) -> some View
{
self.modifier(ProPanelSurfaceModifier(
modifier(ProPanelSurfaceModifier(
tint: tint,
radius: radius,
isProminent: isProminent))
}
func proInsetSurface(tint: Color, radius: CGFloat) -> some View {
self.modifier(ProInsetSurfaceModifier(tint: tint, radius: radius))
modifier(ProInsetSurfaceModifier(tint: tint, radius: radius))
}
func openClawGlassButton(prominent: Bool = false, tint: Color? = nil) -> some View {
self.modifier(OpenClawGlassButtonModifier(prominent: prominent, tint: tint))
modifier(OpenClawGlassButtonModifier(prominent: prominent, tint: tint))
}
func openClawTabBarBehavior() -> some View {
self.modifier(OpenClawTabBarBehaviorModifier())
modifier(OpenClawTabBarBehaviorModifier())
}
func openClawGlassSurface(radius: CGFloat = OpenClawProMetric.controlRadius) -> some View {
self.modifier(OpenClawGlassSurfaceModifier(radius: radius))
modifier(OpenClawGlassSurfaceModifier(radius: radius))
}
}
@@ -218,11 +235,11 @@ struct ProIconBadge: View {
var body: some View {
Image(systemName: self.systemName)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(self.color)
.frame(width: 30, height: 30)
.background {
RoundedRectangle(cornerRadius: 8, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)
.fill(self.color.opacity(0.12))
}
}
@@ -255,9 +272,9 @@ struct OpenClawSidebarRevealButton: View {
}
var body: some View {
let button = Button(action: self.headerAction.action) {
let button = Button(action: headerAction.action) {
Image(systemName: self.headerAction.systemName)
.font(.system(size: 16, weight: .semibold))
.font(OpenClawType.subhead)
.frame(
width: OpenClawProMetric.compactControlSize,
height: OpenClawProMetric.compactControlSize)
@@ -267,7 +284,7 @@ struct OpenClawSidebarRevealButton: View {
.openClawGlassButton(tint: OpenClawBrand.accent)
.accessibilityLabel(self.headerAction.accessibilityLabel)
if let accessibilityIdentifier = self.headerAction.accessibilityIdentifier {
if let accessibilityIdentifier = headerAction.accessibilityIdentifier {
button.accessibilityIdentifier(accessibilityIdentifier)
} else {
button
@@ -324,16 +341,16 @@ struct OpenClawNoticeBanner: View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text(self.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.multilineTextAlignment(.leading)
Spacer(minLength: 0)
Text(self.ownerLabel)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
}
Text(self.message)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
@@ -367,12 +384,12 @@ struct OpenClawNoticeBanner: View {
switch detail {
case let .accent(value):
Text(value)
.font(.caption.weight(.medium))
.font(OpenClawType.captionMedium)
.foregroundStyle(self.tint)
.fixedSize(horizontal: false, vertical: true)
case let .requestID(value):
Text("Request ID: \(value)")
.font(.system(.caption, design: .monospaced).weight(.medium))
.font(OpenClawType.monoSmall)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -383,8 +400,8 @@ struct OpenClawNoticeBanner: View {
struct OpenClawAdaptiveHeaderRow<Leading: View, Accessory: View>: View {
let title: String
let subtitle: String?
var titleFont: Font = .title3.weight(.semibold)
var subtitleFont: Font = .subheadline
var titleFont: Font = OpenClawType.title3
var subtitleFont: Font = OpenClawType.subhead
var subtitleLineLimit: Int? = 2
@ViewBuilder let leading: Leading
@ViewBuilder let accessory: Accessory
@@ -392,8 +409,8 @@ struct OpenClawAdaptiveHeaderRow<Leading: View, Accessory: View>: View {
init(
title: String,
subtitle: String? = nil,
titleFont: Font = .title3.weight(.semibold),
subtitleFont: Font = .subheadline,
titleFont: Font = OpenClawType.title3,
subtitleFont: Font = OpenClawType.subhead,
subtitleLineLimit: Int? = 2,
@ViewBuilder leading: () -> Leading,
@ViewBuilder accessory: () -> Accessory)
@@ -465,6 +482,117 @@ struct OpenClawAdaptiveHeaderRow<Leading: View, Accessory: View>: View {
}
}
/// Shared switch indicator replacing the 3 duplicated capsule toggles.
/// Native Toggle only hits the switch edge on iOS 26; this full-width button approach
/// gives the whole row a tap target.
struct OpenClawToggleIndicator: View {
let isOn: Bool
var body: some View {
Capsule()
.fill(self.isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
.frame(width: 52, height: 32)
.overlay(alignment: self.isOn ? .trailing : .leading) {
Circle()
.fill(Color.white)
.frame(width: 28, height: 28)
.padding(2)
.shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1)
}
}
}
enum OpenClawStatusTone {
case ok
case warn
case danger
case info
case accent
case teal
case muted
var color: Color {
switch self {
case .ok: OpenClawBrand.ok
case .warn: OpenClawBrand.warn
case .danger: OpenClawBrand.danger
case .info: OpenClawBrand.info
case .accent: OpenClawBrand.accent
case .teal: OpenClawBrand.teal
case .muted: OpenClawBrand.textSecondary
}
}
}
struct OpenClawStatusBadge: View {
@Environment(\.colorScheme) private var colorScheme
let label: String
let tone: OpenClawStatusTone
var body: some View {
HStack(spacing: OpenClawSpacing.space1 + 2) {
Circle()
.fill(self.tone.color)
.frame(width: 7, height: 7)
.shadow(color: self.tone.color.opacity(0.55), radius: 3)
Text(self.label)
.font(OpenClawType.caption2SemiBold)
.foregroundStyle(self.tone.color)
}
.padding(.horizontal, OpenClawSpacing.space2)
.padding(.vertical, 5)
.background {
Capsule()
.fill(self.tone.color.opacity(self.colorScheme == .dark ? 0.14 : 0.10))
}
}
}
struct OpenClawPrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(OpenClawType.headline)
.foregroundStyle(.white)
.frame(maxWidth: .infinity, minHeight: 48)
.background {
RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)
.fill(
configuration.isPressed
? OpenClawBrand.accentPressed
: OpenClawBrand.accent)
}
.animation(.easeOut(duration: 0.15), value: configuration.isPressed)
}
}
struct OpenClawSecondaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(OpenClawType.headline)
.foregroundStyle(OpenClawBrand.textPrimary)
.frame(maxWidth: .infinity, minHeight: 48)
.background {
RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)
.fill(Color(uiColor: .secondarySystemBackground))
.overlay {
RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)
.strokeBorder(Color(uiColor: .separator).opacity(0.35), lineWidth: 1)
}
}
.opacity(configuration.isPressed ? 0.82 : 1)
}
}
extension View {
func openClawPrimaryButton() -> some View {
self.buttonStyle(OpenClawPrimaryButtonStyle())
}
func openClawSecondaryButton() -> some View {
self.buttonStyle(OpenClawSecondaryButtonStyle())
}
}
struct ProStatusDot: View {
var color: Color
@@ -482,7 +610,7 @@ struct ProValuePill: View {
var body: some View {
Text(self.value)
.font(.footnote.weight(.semibold))
.font(OpenClawType.footnoteSemiBold)
.foregroundStyle(self.color)
.lineLimit(1)
.padding(.horizontal, 8)
@@ -531,18 +659,8 @@ struct OpenClawGatewayCompactPill: View {
@Environment(NodeAppModel.self) private var appModel
var body: some View {
HStack(spacing: 6) {
Image(systemName: self.icon)
.font(.caption.weight(.semibold))
Text(self.title)
.font(.caption.weight(.semibold))
.lineLimit(1)
}
.foregroundStyle(self.color)
.padding(.horizontal, 4)
.frame(minHeight: 30)
.fixedSize(horizontal: true, vertical: false)
.accessibilityLabel("Gateway \(self.title)")
OpenClawStatusBadge(label: self.title, tone: self.tone)
.accessibilityLabel("Gateway \(self.title)")
}
private var title: String {
@@ -558,29 +676,16 @@ struct OpenClawGatewayCompactPill: View {
}
}
private var color: Color {
private var tone: OpenClawStatusTone {
switch GatewayStatusBuilder.build(appModel: self.appModel) {
case .connected:
OpenClawBrand.ok
.ok
case .connecting:
OpenClawBrand.accent
.accent
case .error:
OpenClawBrand.warn
.warn
case .disconnected:
.secondary
}
}
private var icon: String {
switch GatewayStatusBuilder.build(appModel: self.appModel) {
case .connected:
"checkmark.circle.fill"
case .connecting:
"arrow.triangle.2.circlepath"
case .error:
"exclamationmark.triangle.fill"
case .disconnected:
"wifi.slash"
.muted
}
}
}
@@ -596,7 +701,7 @@ struct ProMetricTile: View {
VStack(alignment: .leading, spacing: 10) {
HStack {
Image(systemName: self.icon)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(self.color)
.frame(width: 24, height: 24)
.background(self.color.opacity(self.colorScheme == .dark ? 0.18 : 0.10), in: Circle())
@@ -605,11 +710,11 @@ struct ProMetricTile: View {
VStack(alignment: .leading, spacing: 2) {
Text(self.value)
.font(.headline.weight(.bold))
.font(OpenClawType.headline)
.lineLimit(1)
.minimumScaleFactor(0.72)
Text(self.title)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -666,10 +771,10 @@ struct ProPanelHeader: View {
var body: some View {
HStack(spacing: 8) {
Text(self.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
if let value {
Text(value)
.font(.caption2.weight(.bold))
.font(OpenClawType.caption2Bold)
.foregroundStyle(.secondary)
}
Spacer(minLength: 8)
@@ -687,11 +792,11 @@ struct ProPanelHeader: View {
Button(action: action) {
Image(systemName: actionIcon)
}
.accessibilityLabel(self.actionAccessibilityLabel ?? self.actionTitle ?? self.title)
.accessibilityLabel(self.actionAccessibilityLabel ?? actionTitle ?? self.title)
.disabled(self.isActionDisabled)
} else if let actionTitle {
Button(actionTitle, action: action)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.disabled(self.isActionDisabled)
}
}
@@ -712,10 +817,10 @@ struct ProStatusRow: View {
ProIconBadge(systemName: self.icon, color: self.color)
VStack(alignment: .leading, spacing: 4) {
Text(self.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -726,7 +831,7 @@ struct ProStatusRow: View {
}
if let actionTitle, let action {
Button(actionTitle, action: action)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.buttonStyle(.bordered)
.controlSize(.mini)
}
@@ -0,0 +1,107 @@
import SwiftUI
import UIKit
enum OpenClawType {
// Display Plus Jakarta Sans
static let title1 = scaledDisplay(name: Display.extraBold, size: 34, relativeTo: .largeTitle)
static let title2 = scaledDisplay(name: Display.bold, size: 28, relativeTo: .title1)
static let title3 = scaledDisplay(name: Display.bold, size: 22, relativeTo: .title2)
static let headline = scaledDisplay(name: Display.semiBold, size: 17, relativeTo: .headline)
// Body DM Sans
static let body = scaledBody(name: Body.regular, size: 17, relativeTo: .body)
static let callout = scaledBody(name: Body.regular, size: 16, relativeTo: .callout)
static let subhead = scaledBody(name: Body.medium, size: 15, relativeTo: .subheadline)
static let subheadSemiBold = scaledDisplay(name: Display.semiBold, size: 15, relativeTo: .subheadline)
static let footnote = scaledBody(name: Body.regular, size: 13, relativeTo: .footnote)
static let footnoteMedium = scaledBody(name: Body.medium, size: 13, relativeTo: .footnote)
static let footnoteSemiBold = scaledBody(name: Body.semiBold, size: 13, relativeTo: .footnote)
static let caption = scaledBody(name: Body.regular, size: 12, relativeTo: .caption1)
static let captionMedium = scaledBody(name: Body.medium, size: 12, relativeTo: .caption1)
static let captionSemiBold = scaledBody(name: Body.semiBold, size: 12, relativeTo: .caption1)
static let caption2 = scaledBody(name: Body.regular, size: 11, relativeTo: .caption2)
static let caption2Medium = scaledBody(name: Body.medium, size: 11, relativeTo: .caption2)
static let caption2SemiBold = scaledBody(name: Body.semiBold, size: 11, relativeTo: .caption2)
static let caption2Bold = scaledDisplay(name: Display.bold, size: 11, relativeTo: .caption2)
static let title2SemiBold = scaledDisplay(name: Display.semiBold, size: 28, relativeTo: .title1)
// Mono JetBrains Mono
static let mono = scaledMono(name: Mono.regular, size: 14, relativeTo: .body)
static let monoSmall = scaledMono(name: Mono.regular, size: 12, relativeTo: .caption1)
static let monoFootnote = scaledMono(name: Mono.regular, size: 13, relativeTo: .footnote)
static let monoHeadline = scaledMono(name: Mono.medium, size: 17, relativeTo: .headline)
/// PostScript names for bundled fonts. Keep aligned with `UIAppFonts` in `project.yml`.
static let registeredPostScriptNames: [String] = [
Display.light,
Display.regular,
Display.medium,
Display.semiBold,
Display.bold,
Display.extraBold,
Body.light,
Body.regular,
Body.italic,
Body.medium,
Body.semiBold,
Mono.regular,
Mono.medium,
Mono.semiBold,
]
private enum Display {
static let light = "PlusJakartaSans-Light"
static let regular = "PlusJakartaSans-Regular"
static let medium = "PlusJakartaSans-Medium"
static let semiBold = "PlusJakartaSans-SemiBold"
static let bold = "PlusJakartaSans-Bold"
static let extraBold = "PlusJakartaSans-ExtraBold"
}
private enum Body {
static let light = "DMSans-Light"
static let regular = "DMSans-Regular"
static let italic = "DMSans-Italic"
static let medium = "DMSans-Medium"
static let semiBold = "DMSans-SemiBold"
}
private enum Mono {
static let regular = "JetBrainsMono-Regular"
static let medium = "JetBrainsMono-Medium"
static let semiBold = "JetBrainsMono-SemiBold"
}
private static func scaledDisplay(
name: String,
size: CGFloat,
relativeTo textStyle: UIFont.TextStyle) -> Font
{
self.scaledFont(name: name, size: size, relativeTo: textStyle)
}
private static func scaledBody(
name: String,
size: CGFloat,
relativeTo textStyle: UIFont.TextStyle) -> Font
{
self.scaledFont(name: name, size: size, relativeTo: textStyle)
}
private static func scaledMono(
name: String,
size: CGFloat,
relativeTo textStyle: UIFont.TextStyle) -> Font
{
self.scaledFont(name: name, size: size, relativeTo: textStyle)
}
private static func scaledFont(
name: String,
size: CGFloat,
relativeTo textStyle: UIFont.TextStyle) -> Font
{
let base = UIFont(name: name, size: size) ?? UIFont.systemFont(ofSize: size)
let scaled = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: base)
return Font(scaled)
}
}
@@ -36,9 +36,9 @@ struct SettingsChannelsDestination: View {
ProIconBadge(systemName: "point.3.connected.trianglepath.dotted", color: self.summaryColor)
VStack(alignment: .leading, spacing: 3) {
Text("Channels / Integrations")
.font(.headline)
.font(OpenClawType.headline)
Text(self.summaryDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
@@ -346,14 +346,14 @@ private struct SettingsChannelRow: View {
ProIconBadge(systemName: self.entry.systemImage, color: self.entry.color)
VStack(alignment: .leading, spacing: 4) {
Text(self.entry.label)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(self.entry.detailText)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
if let lastError = self.entry.lastError {
Text(lastError)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(OpenClawBrand.warn)
.lineLimit(2)
}
@@ -384,9 +384,9 @@ private struct SettingsChannelRow: View {
.frame(width: 28, height: 28)
VStack(alignment: .leading, spacing: 2) {
Text(account.displayName)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
Text(account.detailText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -628,7 +628,7 @@ private struct SettingsChannelsStatesPreview: View {
{
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.secondary)
ProCard(padding: 0, radius: SettingsLayout.cardRadius) {
VStack(spacing: 0) {
+3 -3
View File
@@ -285,12 +285,12 @@ struct HostedPushRelayDisclosureSheet: View {
ScrollView {
VStack(alignment: .leading, spacing: 18) {
Image(systemName: "network")
.font(.title2.weight(.semibold))
.font(OpenClawType.title2SemiBold)
.foregroundStyle(OpenClawBrand.accentForeground)
Text("Enable OpenClaw Hosted Push Relay?")
.font(.title3.weight(.semibold))
.font(OpenClawType.title3)
Text(self.message)
.font(.body)
.font(OpenClawType.body)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -17,9 +17,9 @@ extension SettingsProTab {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.headline)
.font(OpenClawType.headline)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -100,9 +100,9 @@ extension SettingsProTab {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -123,11 +123,11 @@ extension SettingsProTab {
func detailRow(_ label: String, value: String) -> some View {
HStack {
Text(label)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
Spacer(minLength: 8)
Text(value)
.font(.caption)
.font(OpenClawType.caption)
.lineLimit(1)
.truncationMode(.middle)
}
@@ -427,7 +427,11 @@ extension SettingsProTab {
} else {
self.locationModeRaw = previous
self.previousLocationModeRaw = previous
self.locationStatusText = "Location permission was not granted."
if mode == .always {
self.locationStatusText = "Always Allow was not granted. Open iOS Settings → OpenClaw → Location to enable it."
} else {
self.locationStatusText = "Location permission was not granted. Check iOS Settings → OpenClaw → Location."
}
self.refreshLocationPermissionSummary(
desiredMode: OpenClawLocationMode(rawValue: previous) ?? .off)
}
@@ -1,3 +1,4 @@
import CoreLocation
import OpenClawKit
import SwiftUI
@@ -76,10 +77,10 @@ extension SettingsProTab {
VStack(alignment: .leading, spacing: 3) {
Text("Gateway")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.gatewaySummaryDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
@@ -190,10 +191,10 @@ extension SettingsProTab {
ProIconBadge(systemName: icon, color: color)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
if let detail, !detail.isEmpty {
Text(detail)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.lineLimit(1)
}
@@ -315,12 +316,12 @@ extension SettingsProTab {
ProIconBadge(systemName: "bell.slash.fill", color: OpenClawBrand.warn)
VStack(alignment: .leading, spacing: 4) {
Text("Notifications are off")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(
"""
Enable Notifications to receive approval notifications while OpenClaw is not open.
""")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
@@ -356,7 +357,7 @@ extension SettingsProTab {
if let errorText = self.appModel.pendingExecApprovalPromptErrorText {
Text(errorText)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(OpenClawBrand.danger)
}
@@ -397,9 +398,9 @@ extension SettingsProTab {
ProIconBadge(systemName: "checkmark.shield.fill", color: OpenClawBrand.ok)
VStack(alignment: .leading, spacing: 3) {
Text("No approvals waiting")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(self.approvalEmptyDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -533,7 +534,7 @@ extension SettingsProTab {
.disabled(self.notificationStatus == .checking || self.isRequestingNotificationAuthorization)
Text(self.notificationStatusDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
@@ -541,11 +542,11 @@ extension SettingsProTab {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "network")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(OpenClawBrand.accent)
.frame(width: 22, height: 22)
Text(self.notificationRelayDetail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
@@ -641,9 +642,9 @@ extension SettingsProTab {
Button(action: action) {
HStack(spacing: 7) {
Image(systemName: isBusy ? "hourglass" : icon)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
Text(title)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.lineLimit(1)
.minimumScaleFactor(0.76)
}
@@ -669,9 +670,9 @@ extension SettingsProTab {
ProIconBadge(systemName: icon, color: isOn.wrappedValue ? OpenClawBrand.accent : .secondary)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(detail)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -692,9 +693,9 @@ extension SettingsProTab {
.accent)
VStack(alignment: .leading, spacing: 3) {
Text("Location")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text("Controls whether location can be shared with gateway tools.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -714,13 +715,17 @@ extension SettingsProTab {
.disabled(self.isChangingLocationMode)
Text(self.locationPermissionDetailText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(
self.locationPermissionSummary.needsAttention ? OpenClawBrand.warn : .secondary)
if self.locationModeRaw == OpenClawLocationMode.always.rawValue {
self.locationAlwaysGuidance
}
if let locationPermissionWarningText {
Text(locationPermissionWarningText)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -728,11 +733,62 @@ extension SettingsProTab {
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
@ViewBuilder
private var locationAlwaysGuidance: some View {
let osStatus = CLLocationManager().authorizationStatus
switch osStatus {
case .authorizedAlways:
Label {
Text("Always Allow is active.")
} icon: {
Image(systemName: "checkmark.circle")
}
.font(OpenClawType.caption)
.foregroundStyle(OpenClawBrand.ok)
case .authorizedWhenInUse:
VStack(alignment: .leading, spacing: 6) {
Label {
Text("iOS is set to While Using.")
+ Text(" Change to Always in Settings for reliable background automations.")
} icon: {
Image(systemName: "exclamationmark.triangle")
}
.font(OpenClawType.caption)
.foregroundStyle(OpenClawBrand.warn)
self.openSettingsButton
}
case .denied, .restricted:
VStack(alignment: .leading, spacing: 6) {
Label {
Text("Location access is denied.")
+ Text(" Enable it in Settings.")
} icon: {
Image(systemName: "xmark.circle")
}
.font(OpenClawType.caption)
.foregroundStyle(OpenClawBrand.danger)
self.openSettingsButton
}
default:
EmptyView()
}
}
private var openSettingsButton: some View {
Button("Open Settings") {
guard let url = URL(
string: UIApplication.openSettingsURLString) else { return }
UIApplication.shared.open(url)
}
.font(OpenClawType.caption)
.buttonStyle(.bordered)
}
var agentSelectionCard: some View {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 10) {
Text("Default Agent")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Picker("Agent", selection: self.$selectedAgentPickerId) {
Text("Default").tag("")
let defaultId = (self.appModel.gatewayDefaultAgentId ?? "")
@@ -743,7 +799,7 @@ extension SettingsProTab {
}
}
Text("Used for new Chat and Talk sessions.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
}
@@ -754,7 +810,7 @@ extension SettingsProTab {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 12) {
Text("Setup Code")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
TextField("Paste setup code", text: self.$setupCode)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
@@ -780,13 +836,13 @@ extension SettingsProTab {
}
if let status = self.setupStatusLine {
Text(status)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
if let warning = self.tailnetWarningText {
Text(warning)
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(OpenClawBrand.warn)
}
}
@@ -798,10 +854,10 @@ extension SettingsProTab {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 12) {
Text("Discovered Gateways")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
if self.gatewayController.gateways.isEmpty {
Text("No gateways found yet. Use manual setup if Bonjour is blocked.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
} else {
ForEach(self.gatewayController.gateways) { gateway in
@@ -820,9 +876,9 @@ extension SettingsProTab {
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 3) {
Text(verbatim: gateway.name)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
Text(verbatim: self.gatewayDetailLines(gateway).joined(separator: ""))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
@@ -983,7 +1039,7 @@ extension SettingsProTab {
}
.buttonStyle(.bordered)
Text(self.appModel.lastShareEventText)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
}
@@ -1050,7 +1106,7 @@ extension SettingsProTab {
Spacer(minLength: 8)
self.settingsSwitchIndicator(isOn: isOn.wrappedValue)
}
.font(.subheadline)
.font(OpenClawType.subhead)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
@@ -1083,9 +1139,9 @@ extension SettingsProTab {
.lineLimit(1)
.truncationMode(.middle)
Image(systemName: "chevron.right")
.font(.caption.weight(.semibold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
}
.font(.subheadline)
.font(OpenClawType.subhead)
}
}
@@ -36,7 +36,7 @@ struct SettingsApprovalRow: View {
var body: some View {
HStack(spacing: 10) {
Image(systemName: self.item.icon)
.font(.caption.weight(.bold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.white)
.frame(width: 30, height: 30)
.background {
@@ -45,16 +45,16 @@ struct SettingsApprovalRow: View {
}
VStack(alignment: .leading, spacing: 2) {
Text(self.item.title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.lineLimit(1)
Text(self.item.detail)
.font(.caption2.weight(.medium))
.font(OpenClawType.caption2Medium)
.foregroundStyle(.secondary)
.lineLimit(1)
}
Spacer(minLength: 8)
Text(self.item.priority)
.font(.caption.weight(.bold))
.font(OpenClawType.captionSemiBold)
.foregroundStyle(self.item.color)
.padding(.horizontal, 9)
.padding(.vertical, 5)
@@ -298,7 +298,7 @@ private struct SettingsGatewayStatesPreview: View {
{
VStack(alignment: .leading, spacing: 8) {
Text(title)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.secondary)
content()
}
@@ -344,11 +344,11 @@ private struct SettingsGatewayStatesPreview: View {
private func factRow(_ label: String, value: String) -> some View {
HStack {
Text(label)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
Spacer(minLength: 8)
Text(value)
.font(.caption.weight(.medium))
.font(OpenClawType.captionMedium)
.lineLimit(1)
.truncationMode(.middle)
}
@@ -372,7 +372,7 @@ private struct SettingsGatewayStatesPreview: View {
self.previewButton("Connect", systemImage: "link", isBusy: false)
}
Text("Discovered gateways and manual setup live here when the gateway has not connected yet.")
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
}
}
+6 -6
View File
@@ -134,17 +134,17 @@ struct TalkProTab: View {
VStack(spacing: 5) {
Text(self.state.title)
.font(.title3.weight(.bold))
.font(OpenClawType.title3)
.multilineTextAlignment(.center)
Text(self.heroSubtitle)
.font(.subheadline.weight(.medium))
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
}
Button(action: self.handlePrimaryAction) {
Label(self.state.primaryButtonTitle, systemImage: self.state.primaryButtonIcon)
.font(.subheadline.weight(.bold))
.font(OpenClawType.subheadSemiBold)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
@@ -171,7 +171,7 @@ struct TalkProTab: View {
accessibilityIdentifier: "talk-background-listening-control")
Button(action: self.openVoiceSettings) {
Image(systemName: "slider.horizontal.3")
.font(.system(size: 17, weight: .semibold))
.font(OpenClawType.headline)
.frame(width: 44, height: 44)
}
.buttonBorderShape(.circle)
@@ -194,7 +194,7 @@ struct TalkProTab: View {
isOn.wrappedValue.toggle()
} label: {
Image(systemName: systemImage)
.font(.system(size: 17, weight: .semibold))
.font(OpenClawType.headline)
.contentTransition(.symbolEffect(.replace))
.frame(width: 44, height: 44)
}
@@ -518,7 +518,7 @@ private struct TalkProOrb: View {
.frame(width: 92, height: 44)
.opacity(self.showsWaveform ? 1 : 0)
Image(systemName: self.systemImage)
.font(.system(size: 34, weight: .bold))
.font(OpenClawType.title1)
.foregroundStyle(self.color)
.opacity(self.showsWaveform ? 0.20 : 1)
}
@@ -43,12 +43,12 @@ struct TalkRuntimeIssueDetailsSheet: View {
Section {
VStack(alignment: .leading, spacing: 10) {
Text(self.issue.fallbackBannerTitle)
.font(.title3.weight(.semibold))
.font(OpenClawType.title3)
Text(self.issue.fallbackBannerMessage)
.font(.body)
.font(OpenClawType.body)
.foregroundStyle(.secondary)
Text(self.issue.displayMessage)
.font(.footnote.weight(.semibold))
.font(OpenClawType.footnoteSemiBold)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -57,7 +57,7 @@ struct TalkRuntimeIssueDetailsSheet: View {
Section("Technical details") {
Text(verbatim: self.issue.technicalDetails)
.font(.system(.footnote, design: .monospaced))
.font(OpenClawType.monoFootnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
Button("Copy diagnostics") {
@@ -69,7 +69,7 @@ struct TalkRuntimeIssueDetailsSheet: View {
if let copyFeedback {
Section {
Text(copyFeedback)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
Font Licenses and Provenance
=============================
All bundled fonts are licensed under the SIL Open Font License, Version 1.1.
The OFL permits bundling in software provided the copyright notice and license
accompany the distribution.
Plus Jakarta Sans
-----------------
Copyright 2020 The Plus Jakarta Sans Project Authors
(https://github.com/tokotype/PlusJakartaSans)
License: SIL Open Font License 1.1
Source: https://github.com/tokotype/PlusJakartaSans
Version: 2.7.1
DM Sans
-------
Copyright 2014-2022 Indian Type Foundry
Copyright 2014-2022 Jonny Pinhorn
(https://github.com/googlefonts/dm-fonts)
License: SIL Open Font License 1.1
Source: https://github.com/googlefonts/dm-fonts
JetBrains Mono
--------------
Copyright 2020 The JetBrains Mono Project Authors
(https://github.com/JetBrains/JetBrainsMono)
License: SIL Open Font License 1.1
Source: https://github.com/JetBrains/JetBrainsMono
Version: 2.304
---
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -60,17 +60,19 @@ private struct ExecApprovalPromptCard: View {
VStack(alignment: .leading, spacing: 14) {
VStack(alignment: .leading, spacing: 6) {
Text("Exec approval required")
.font(.headline)
.font(OpenClawType.headline)
Text("Review this exec request before continuing. Your decision will be sent back to the gateway.")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
}
Text(self.prompt.commandText)
.font(.system(size: 15, weight: .regular, design: .monospaced))
.font(OpenClawType.mono)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
.background(.black.opacity(0.14), in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.background(
.black.opacity(0.14),
in: RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous))
VStack(alignment: .leading, spacing: 8) {
if let host = self.normalized(self.prompt.host) {
@@ -89,7 +91,7 @@ private struct ExecApprovalPromptCard: View {
if let errorText = self.normalized(self.errorText) {
Text(errorText)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(OpenClawBrand.danger)
}
@@ -98,7 +100,7 @@ private struct ExecApprovalPromptCard: View {
ProgressView()
.progressViewStyle(.circular)
Text("Resolving…")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
}
@@ -181,10 +183,10 @@ private struct ExecApprovalPromptMetadataRow: View {
var body: some View {
VStack(alignment: .leading, spacing: 2) {
Text(self.label)
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
Text(self.value)
.font(.footnote)
.font(OpenClawType.footnote)
.textSelection(.enabled)
}
}
@@ -19,10 +19,10 @@ struct GatewayDiscoveryDebugLogView: View {
ForEach(self.gatewayController.discoveryDebugLog) { entry in
VStack(alignment: .leading, spacing: 2) {
Text(Self.formatTime(entry.ts))
.font(.caption)
.font(OpenClawType.caption)
.foregroundStyle(.secondary)
Text(entry.message)
.font(.callout)
.font(OpenClawType.callout)
.textSelection(.enabled)
}
.padding(.vertical, 4)
@@ -89,12 +89,12 @@ struct GatewayProblemDetailsSheet: View {
Section {
VStack(alignment: .leading, spacing: 10) {
Text(self.problem.title)
.font(.title3.weight(.semibold))
.font(OpenClawType.title3)
Text(self.problem.message)
.font(.body)
.font(OpenClawType.body)
.foregroundStyle(.secondary)
Text(self.ownerSummary)
.font(.footnote.weight(.semibold))
.font(OpenClawType.footnoteSemiBold)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
@@ -104,7 +104,7 @@ struct GatewayProblemDetailsSheet: View {
if let requestId = self.problem.requestId {
Section("Request") {
Text(verbatim: requestId)
.font(.system(.body, design: .monospaced))
.font(OpenClawType.mono)
.textSelection(.enabled)
Button("Copy request ID") {
UIPasteboard.general.string = requestId
@@ -116,7 +116,7 @@ struct GatewayProblemDetailsSheet: View {
if let actionCommand = self.problem.actionCommand {
Section("Gateway command") {
Text(verbatim: actionCommand)
.font(.system(.body, design: .monospaced))
.font(OpenClawType.mono)
.textSelection(.enabled)
Button("Copy command") {
UIPasteboard.general.string = actionCommand
@@ -131,7 +131,7 @@ struct GatewayProblemDetailsSheet: View {
Label("Open docs", systemImage: "book")
}
Text(verbatim: docsURL.absoluteString)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -140,7 +140,7 @@ struct GatewayProblemDetailsSheet: View {
if let technicalDetails = self.problem.technicalDetails {
Section("Technical details") {
Text(verbatim: technicalDetails)
.font(.system(.footnote, design: .monospaced))
.font(OpenClawType.monoFootnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -149,7 +149,7 @@ struct GatewayProblemDetailsSheet: View {
if let copyFeedback {
Section {
Text(copyFeedback)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
}
@@ -15,7 +15,7 @@ struct GatewayQuickSetupSheet: View {
NavigationStack {
VStack(alignment: .leading, spacing: 16) {
Text("Connect to a Gateway?")
.font(.title2.bold())
.font(OpenClawType.title2)
if let gatewayProblem = self.appModel.lastGatewayProblem {
GatewayProblemBanner(
@@ -46,7 +46,6 @@ struct GatewayQuickSetupSheet: View {
await MainActor.run {
self.connecting = false
self.connectError = err
// If we kicked off a connect, leave the sheet up so the user can see status evolve.
}
}
} label: {
@@ -60,14 +59,13 @@ struct GatewayQuickSetupSheet: View {
Text("Connect")
}
}
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.openClawPrimaryButton()
.disabled(self.connecting)
if let connectError {
Text(connectError)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -76,9 +74,8 @@ struct GatewayQuickSetupSheet: View {
self.dismiss()
} label: {
Text("Not now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.openClawSecondaryButton()
.disabled(self.connecting)
self.fullRowToggle("Dont show this again", isOn: self.$quickSetupDismissed)
@@ -173,10 +170,10 @@ private struct GatewayQuickSetupCandidatePanel: View {
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(verbatim: self.name)
.font(.system(.headline, design: .monospaced))
.font(OpenClawType.monoHeadline)
.foregroundStyle(.primary)
Text(verbatim: self.debugID)
.font(.system(.footnote, design: .monospaced))
.font(OpenClawType.monoFootnote)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 2) {
@@ -187,7 +184,7 @@ private struct GatewayQuickSetupCandidatePanel: View {
Text(verbatim: "Node: \(self.nodeStatusText)")
Text(verbatim: "Operator: \(self.operatorStatusText)")
}
.font(.system(.footnote, design: .monospaced))
.font(OpenClawType.monoFootnote)
.foregroundStyle(.secondary)
}
.frame(maxWidth: Self.readableMonospaceWidth, alignment: .leading)
@@ -195,7 +192,7 @@ private struct GatewayQuickSetupCandidatePanel: View {
.padding(.horizontal, 16)
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 14))
.background(OpenClawBrand.obsidian)
.clipShape(RoundedRectangle(cornerRadius: OpenClawRadius.lg))
}
}
@@ -50,14 +50,14 @@ private struct NotificationPermissionGuidanceCard: View {
VStack(alignment: .leading, spacing: 14) {
VStack(alignment: .leading, spacing: 6) {
Text("Notifications are off")
.font(.headline)
.font(OpenClawType.headline)
Text(
"""
Exec approvals can only be reviewed while OpenClaw is open and connected.
Enable Notifications to receive approval notifications while OpenClaw is not open.
""")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
+17
View File
@@ -88,6 +88,23 @@
<string>$(OPENCLAW_PUSH_MODE)</string>
<key>OpenClawPushRelayBaseURL</key>
<string>$(OPENCLAW_PUSH_RELAY_BASE_URL)</string>
<key>UIAppFonts</key>
<array>
<string>PlusJakartaSans-Light.ttf</string>
<string>PlusJakartaSans-Regular.ttf</string>
<string>PlusJakartaSans-Medium.ttf</string>
<string>PlusJakartaSans-SemiBold.ttf</string>
<string>PlusJakartaSans-Bold.ttf</string>
<string>PlusJakartaSans-ExtraBold.ttf</string>
<string>DMSans-Light.ttf</string>
<string>DMSans-Regular.ttf</string>
<string>DMSans-Italic.ttf</string>
<string>DMSans-Medium.ttf</string>
<string>DMSans-SemiBold.ttf</string>
<string>JetBrainsMono-Regular.ttf</string>
<string>JetBrainsMono-Medium.ttf</string>
<string>JetBrainsMono-SemiBold.ttf</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
@@ -11,15 +11,16 @@ struct OnboardingIntroStep: View {
.padding(.bottom, 18)
Text("Welcome to OpenClaw")
.font(.largeTitle.weight(.bold))
.font(OpenClawType.title1)
.foregroundStyle(OpenClawBrand.textPrimary)
.multilineTextAlignment(.center)
.padding(.bottom, 10)
Text("Turn this device into a secure OpenClaw node for chat, voice, camera, and device tools.")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.horizontal, OpenClawSpacing.space8)
.padding(.bottom, 24)
VStack(alignment: .leading, spacing: 14) {
@@ -27,42 +28,43 @@ struct OnboardingIntroStep: View {
Label("Choose device permissions", systemImage: "hand.raised")
Label("Use OpenClaw from your phone", systemImage: "message.fill")
}
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(18)
.background {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(uiColor: .secondarySystemBackground))
RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous)
.fill(OpenClawBrand.slate)
}
.padding(.horizontal, 24)
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.bottom, 16)
HStack(alignment: .top, spacing: 12) {
Image(systemName: "exclamationmark.triangle.fill")
.font(.title3.weight(.semibold))
.font(OpenClawType.title3)
.foregroundStyle(OpenClawBrand.warn)
.frame(width: 24)
.padding(.top, 2)
VStack(alignment: .leading, spacing: 6) {
Text("Security notice")
.font(.headline)
.font(OpenClawType.headline)
.foregroundStyle(OpenClawBrand.textPrimary)
Text(
"The connected OpenClaw agent can use device capabilities you enable, "
+ "such as camera, microphone, photos, contacts, calendar, and location. "
+ "Continue only if you trust the gateway and agent you connect to.")
.font(.footnote)
.foregroundStyle(.secondary)
.font(OpenClawType.footnote)
.foregroundStyle(OpenClawBrand.textSecondary)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(18)
.background {
RoundedRectangle(cornerRadius: 20, style: .continuous)
.fill(Color(uiColor: .secondarySystemBackground))
RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous)
.fill(OpenClawBrand.slate)
}
.padding(.horizontal, 24)
.padding(.horizontal, OpenClawSpacing.space6)
Spacer()
@@ -70,11 +72,9 @@ struct OnboardingIntroStep: View {
self.onContinue()
} label: {
Text("Continue")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.padding(.horizontal, 24)
.openClawPrimaryButton()
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.bottom, 48)
}
}
@@ -95,34 +95,35 @@ struct OnboardingWelcomeStep: View {
.padding(.bottom, 20)
Text("Connect Gateway")
.font(.largeTitle.weight(.bold))
.font(OpenClawType.title1)
.foregroundStyle(OpenClawBrand.textPrimary)
.padding(.bottom, 8)
Text("Scan a QR code from your OpenClaw gateway or continue with manual setup.")
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.horizontal, OpenClawSpacing.space8)
VStack(alignment: .leading, spacing: 8) {
Text("How to pair")
.font(.headline)
.font(OpenClawType.headline)
Text("In your OpenClaw chat, run")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
Text("/pair qr")
.font(.system(.footnote, design: .monospaced).weight(.semibold))
.font(OpenClawType.monoFootnote)
Text("Then scan the QR code here to connect this device.")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.padding(OpenClawSpacing.space4)
.background {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(Color(uiColor: .secondarySystemBackground))
RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous)
.fill(OpenClawBrand.slate)
}
.padding(.horizontal, 24)
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.top, 20)
Spacer()
@@ -132,28 +133,24 @@ struct OnboardingWelcomeStep: View {
self.onScanQRCode()
} label: {
Label("Scan QR Code", systemImage: "qrcode")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.openClawPrimaryButton()
Button {
self.onManualSetup()
} label: {
Text("Set Up Manually")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.controlSize(.large)
.openClawSecondaryButton()
}
.padding(.horizontal, 24)
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.bottom, 12)
Text(self.statusLine)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.bottom, 48)
}
}
@@ -170,9 +167,9 @@ struct OnboardingModeRow: View {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(self.title)
.font(.body.weight(.semibold))
.font(OpenClawType.headline)
Text(self.subtitle)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
Spacer()
@@ -129,9 +129,9 @@ struct OnboardingWizardView: View {
ToolbarItem(placement: .principal) {
VStack(spacing: 2) {
Text(self.step.title)
.font(.headline)
.font(OpenClawType.headline)
Text(self.step.manualProgressTitle)
.font(.caption2)
.font(OpenClawType.caption2)
.foregroundStyle(.secondary)
}
}
@@ -374,7 +374,7 @@ struct OnboardingWizardView: View {
HStack {
Text(title)
Spacer(minLength: 8)
self.onboardingSwitchIndicator(isOn: isOn.wrappedValue)
OpenClawToggleIndicator(isOn: isOn.wrappedValue)
}
.contentShape(Rectangle())
}
@@ -383,19 +383,6 @@ struct OnboardingWizardView: View {
.accessibilityValue(isOn.wrappedValue ? "On" : "Off")
}
private func onboardingSwitchIndicator(isOn: Bool) -> some View {
Capsule()
.fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
.frame(width: 52, height: 32)
.overlay(alignment: isOn ? .trailing : .leading) {
Circle()
.fill(Color.white)
.frame(width: 28, height: 28)
.padding(2)
.shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1)
}
}
@ViewBuilder
private var connectStep: some View {
if let selectedMode {
@@ -445,7 +432,7 @@ struct OnboardingWizardView: View {
Text(gateway.name)
if let host = gateway.lanHost ?? gateway.tailnetDns {
Text(host)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
}
@@ -517,11 +504,11 @@ struct OnboardingWizardView: View {
})
} else if self.issue.needsAuthToken {
Text("Gateway rejected credentials. Scan a fresh QR code or update token/password.")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
} else {
Text("Auth token looks valid.")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
}
@@ -586,18 +573,19 @@ struct OnboardingWizardView: View {
.padding(.bottom, 20)
Text("Connected")
.font(.largeTitle.weight(.bold))
.font(OpenClawType.title1)
.foregroundStyle(OpenClawBrand.textPrimary)
.padding(.bottom, 8)
let server = self.appModel.gatewayServerName ?? "gateway"
Text(server)
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
.padding(.bottom, 4)
if let addr = self.appModel.gatewayRemoteAddress {
Text(addr)
.font(.subheadline)
.font(OpenClawType.subhead)
.foregroundStyle(.secondary)
}
@@ -607,11 +595,9 @@ struct OnboardingWizardView: View {
self.onClose()
} label: {
Text("Open OpenClaw")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.controlSize(.large)
.padding(.horizontal, 24)
.openClawPrimaryButton()
.padding(.horizontal, OpenClawSpacing.space6)
.padding(.bottom, 48)
}
}
@@ -646,7 +632,7 @@ extension OnboardingWizardView {
if let setupCodeStatus, !setupCodeStatus.isEmpty {
Text(setupCodeStatus)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
}
} header: {
@@ -75,17 +75,15 @@ struct PrivacyAccessSectionView: View {
HStack {
Label(title, systemImage: icon)
Spacer()
Text(status)
.font(.footnote.weight(.medium))
.foregroundStyle(self.statusColor(for: status))
OpenClawStatusBadge(label: status, tone: self.statusTone(for: status))
.accessibilityIdentifier("privacy-access-\(title)-status")
}
Text(detail)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
if let actionTitle, let action {
Button(actionTitle, action: action)
.font(.footnote)
.font(OpenClawType.footnote)
.buttonStyle(.bordered)
.accessibilityIdentifier("privacy-access-\(title)-action")
}
@@ -93,16 +91,16 @@ struct PrivacyAccessSectionView: View {
.padding(.vertical, 2)
}
private func statusColor(for status: String) -> Color {
private func statusTone(for status: String) -> OpenClawStatusTone {
switch status {
case "Allowed", "Limited":
OpenClawBrand.ok
.ok
case "Not Set":
OpenClawBrand.warn
.warn
case "Add-Only":
OpenClawBrand.warn
.warn
default:
OpenClawBrand.danger
.danger
}
}
+2 -2
View File
@@ -6,11 +6,11 @@ struct VoiceWakeToast: View {
var body: some View {
HStack(spacing: 10) {
Image(systemName: "mic.fill")
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.primary)
Text(self.command)
.font(.subheadline.weight(.semibold))
.font(OpenClawType.subheadSemiBold)
.foregroundStyle(.primary)
.lineLimit(1)
.truncationMode(.tail)
@@ -34,7 +34,7 @@ struct TalkPermissionPromptView: View {
VStack(alignment: .leading, spacing: self.style == .sheet ? 16 : 12) {
HStack(alignment: .top, spacing: 12) {
Image(systemName: self.iconSystemName)
.font(.title3.weight(.semibold))
.font(OpenClawType.title3)
.foregroundStyle(self.requestIsPending ? OpenClawBrand.warn : OpenClawBrand.accent)
.frame(width: 28, height: 28)
@@ -42,7 +42,7 @@ struct TalkPermissionPromptView: View {
Text(self.titleText)
.font(self.style == .sheet ? .title3.weight(.semibold) : .headline)
Text(self.messageText)
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
@@ -50,7 +50,7 @@ struct TalkPermissionPromptView: View {
if let failureMessage = self.state.failureMessage {
Label(failureMessage, systemImage: "exclamationmark.triangle.fill")
.font(.footnote)
.font(OpenClawType.footnote)
.foregroundStyle(OpenClawBrand.danger)
.fixedSize(horizontal: false, vertical: true)
}
@@ -58,7 +58,7 @@ struct TalkPermissionPromptView: View {
if let requestId = self.state.requestId {
LabeledContent("Request ID") {
Text(requestId)
.font(.caption.monospaced())
.font(OpenClawType.monoSmall)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
@@ -92,13 +92,13 @@ struct TalkPermissionPromptView: View {
.padding(self.style == .card || self.style == .sheet ? 16 : 0)
.background {
if self.style == .card || self.style == .sheet {
RoundedRectangle(cornerRadius: 12, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous)
.fill(.thinMaterial)
}
}
.overlay {
if self.style == .card || self.style == .sheet {
RoundedRectangle(cornerRadius: 12, style: .continuous)
RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous)
.stroke(OpenClawBrand.accent.opacity(0.20), lineWidth: 1)
}
}
@@ -0,0 +1,27 @@
import Testing
import UIKit
@testable import OpenClaw
struct OpenClawTypographyTests {
@Test func `bundled fonts load from app bundle`() {
for name in OpenClawType.registeredPostScriptNames {
#expect(UIFont(name: name, size: 12) != nil, "Missing bundled font: \(name)")
}
}
@Test func `dynamic type scales display fonts`() {
guard let base = UIFont(name: "PlusJakartaSans-ExtraBold", size: 34) else {
Issue.record("PlusJakartaSans-ExtraBold should be bundled")
return
}
let defaultTraits = UITraitCollection(preferredContentSizeCategory: .large)
let largeTraits = UITraitCollection(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge)
let metrics = UIFontMetrics(forTextStyle: .largeTitle)
let defaultSize = metrics.scaledFont(for: base, compatibleWith: defaultTraits).pointSize
let largeSize = metrics.scaledFont(for: base, compatibleWith: largeTraits).pointSize
#expect(largeSize > defaultSize)
}
}
+17
View File
@@ -63,6 +63,8 @@ targets:
- path: Resources/Licenses
type: folder
buildPhase: resources
- path: Sources/Fonts
buildPhase: resources
resources:
- path: Resources/Localizable.xcstrings
dependencies:
@@ -177,6 +179,21 @@ targets:
NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for talk mode and voice wake.
NSSupportsLiveActivities: true
ITSAppUsesNonExemptEncryption: false
UIAppFonts:
- PlusJakartaSans-Light.ttf
- PlusJakartaSans-Regular.ttf
- PlusJakartaSans-Medium.ttf
- PlusJakartaSans-SemiBold.ttf
- PlusJakartaSans-Bold.ttf
- PlusJakartaSans-ExtraBold.ttf
- DMSans-Light.ttf
- DMSans-Regular.ttf
- DMSans-Italic.ttf
- DMSans-Medium.ttf
- DMSans-SemiBold.ttf
- JetBrainsMono-Regular.ttf
- JetBrainsMono-Medium.ttf
- JetBrainsMono-SemiBold.ttf
OpenClawPushMode: "$(OPENCLAW_PUSH_MODE)"
OpenClawPushRelayBaseURL: "$(OPENCLAW_PUSH_RELAY_BASE_URL)"
UISupportedInterfaceOrientations: