stanfordspezi/spezillm
Reviewed against Rams quality heuristics: accessibility, color, typography, spacing, components, motion, UX, and craft.
22 files reviewed·July 31, 2026
More findings
Verdict
A clean visual surface sits on top of an interaction layer built from gesture recognizers instead of buttons, and that gap is the whole story. The biggest risk is a fog service list that's effectively unselectable for VoiceOver users, a critical blocker hiding under otherwise tidy styling.
Files Rams reviewed
Sources/SpeziLLM/Views/LLMChatView.swift
Sources/SpeziLLM/Views/LLMChatViewDisabledModifier.swift
Sources/SpeziLLM/Views/LLMChatViewSchema.swift
Sources/SpeziLLMFog/Discovery/LLMFogDiscoverySelectionView.swift
Sources/SpeziLLMFog/Onboarding/LLMFogDiscoveryAuthorizationView.swift
Sources/SpeziLLMLocalDownload/LLMLocalDownloadView.swift
Tests/UITests/TestApp/ContentView.swift
Tests/UITests/TestApp/LLMFog/Onboarding/AccountOnboardingView.swift
Tests/UITests/TestApp/LLMFog/Onboarding/LLMFogAuthTokenOnboardingView.swift
Tests/UITests/TestApp/LLMFog/Onboarding/LLMFogDiscoveryAuthOnboardingView.swift
Tests/UITests/TestApp/LLMFog/Onboarding/LLMFogDiscoverySelectionOnboardingView.swift
Tests/UITests/TestApp/LLMLocal/LLMLocalChatTestView.swift
Tests/UITests/TestApp/LLMLocal/LLMLocalTestView.swift
Tests/UITests/TestApp/LLMLocal/Onboarding/LLMLocalOnboardingWelcomeView.swift
Tests/UITests/TestApp/LLMOpenAI/LLMOpenAILikeChatTestView.swift
Tests/UITests/TestApp/LLMOpenAI/LLMOpenAIOnboardingView.swift
Tests/UITests/TestApp/LLMOpenAIRealtime/LLMOpenAIRealtimeOnboardingView.swift
Tests/UITests/TestApp/LLMOpenAIRealtime/LLMOpenAIRealtimeTestView.swift
Tests/UITests/TestApp/LLMFog/LLMFogChatTestView.swift
Tests/UITests/TestApp/LLMFog/LLMFogTestView.swift
Tests/UITests/TestApp/LLMLocal/Onboarding/LLMLocalOnboardingDownloadView.swift
Sources/GeneratedOpenAIClient/AuthToken/LLMAuthTokenCollector.swift
Accessibility
Sources/SpeziLLMFog/Discovery/LLMFogDiscoverySelectionView.swift:203
Fog service rows use tap gesture instead of a button, blocking selection
Each service row in LLMFogDiscoverySelectionView is an HStack with .contentShape(Rectangle()).onTapGesture{} toggling `selectedService`. The row has no button semantics, no focus, and no accessibility trait, unlike the adjacent info button which is a proper `Button`. VoiceOver and keyboard/Switch Control users land on the row but have no way to activate it.
Why it matters
Any user relying on VoiceOver or an external keyboard cannot select a fog service at all, which blocks the entire discovery flow for that user group.
Fix
Replace the onTapGesture toggle with a native Button so the row inherits focus, activation, and accessibility trait handling for free.
.padding(.vertical, 4)
.contentShape(Rectangle())
.onTapGesture {
// toggle selection
if self.selectedService == service {
self.selectedService = nil
} else {
self.selectedService = service
}
}.padding(.vertical, 4)
.contentShape(Rectangle())
.accessibilityElement(children: .combine)
.onTapGesture {
toggleSelection(for: service)
}
.accessibilityAddTraits(self.selectedService == service ? .isSelected : [])
.accessibilityAction {
toggleSelection(for: service)
}Sources/SpeziLLMFog/Discovery/LLMFogDiscoverySelectionView.swift:175
Selected fog service row never announces selection state to VoiceOver
Selection is only conveyed by a conditionally-rendered checkmark Image with its own `accessibilityLabel("Selected service")` (line 199). The row itself carries no `.isSelected` accessibility trait, so VoiceOver reads an extra floating label rather than announcing the row as selected.
Why it matters
A screen reader user navigating row by row gets no consistent 'selected' state on the row, so they can't tell which service is currently chosen without hunting for a separate checkmark announcement.
Fix
Add the .isSelected accessibility trait to the row container instead of relying on a child image's label.
if self.selectedService == service {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityLabel("Selected service")
} if self.selectedService == service {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
.accessibilityHidden(true)
}
}
.accessibilityAddTraits(self.selectedService == service ? .isSelected : [])Sources/SpeziLLMFog/Discovery/LLMFogDiscoverySelectionView.swift:183
Info-circle button has no explicit tap target sizing
The info button in the service row renders only `Image(systemName: "info.circle")` wrapped in `.buttonStyle(.plain)`, with no `.frame` or padding applied. An SF Symbol at default caption/body scale renders well under the 44x44pt minimum touch target.
Why it matters
A small, unpadded tap target next to a full-row tap gesture makes it easy to miss the info action or mis-trigger the row's own tap handler, which increases mis-taps for users with motor impairments.
Fix
Give the button an explicit minimum 44x44pt frame so the tap target meets platform touch guidelines regardless of the icon's intrinsic size.
Button {
self.infoService = service
} label: {
Image(systemName: "info.circle")
.accessibilityLabel("More information about service")
}
.buttonStyle(.plain)Button {
self.infoService = service
} label: {
Image(systemName: "info.circle")
.accessibilityLabel("More information about service")
.frame(minWidth: 44, minHeight: 44)
.contentShape(Rectangle())
}
.buttonStyle(.plain)UX
Sources/SpeziLLMLocalDownload/LLMLocalDownloadView.swift:99
Back navigation hides with no cancel path during download
`.navigationBarBackButtonHidden(isDownloading)` removes the back button whenever `isDownloading` is true, and no cancel button or alternate exit control exists elsewhere in the view.
Why it matters
A user who starts a large model download and wants out is stuck on the screen until it finishes or errors, with no way to back out of a long-running operation they didn't anticipate.
Fix
Pair the hidden back button with an explicit cancel action that stops the download and restores navigation.
.map(state: downloadManager.state, to: $viewState)
.viewStateAlert(state: $viewState)
.navigationBarBackButtonHidden(isDownloading).map(state: downloadManager.state, to: $viewState)
.viewStateAlert(state: $viewState)
.navigationBarBackButtonHidden(isDownloading)
.toolbar {
if isDownloading {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { downloadManager.cancelDownload() }
}
}
}Typography
Color
Spacing
Components
Motion
Craft
Working well
- The metadata preview and endpoint fallback text consistently use .secondary foreground color and .caption/.caption2 semantic type styles rather than hardcoded sizes or colors, which keeps the row's visual hierarchy consistent with the rest of the system.
- The decorative shippingbox Image in the download informational view is correctly marked .accessibilityHidden(true) while the adjacent downloadDescription text carries the same meaning to VoiceOver, avoiding redundant announcements.
- The discovery spinner carries an explicit accessibilityLabel ("Searching for available fog nodes"), so VoiceOver users get a meaningful description instead of an unlabeled ProgressView.
Scored July 31, 2026 with Rams Engine v0.0.3 · Engine changelog
This page is an automated design review of stanfordspezi/spezillm’s UI code: 22 files read against 291 versioned rules covering accessibility, color, typography, spacing, components, UX, motion, and craft. The score is out of 100; any confirmed critical issue caps it at 59.
More design scores
Score your own repo.
Free on public repos, no account. The same engine that scored this page reads your UI code and mints a score page like this one.
Public repos only. The full engine reviews the UI code and mints a public score page — we email you the link too. Already-scored repos open instantly.