Skip to content

feat(ui): add app-wide text size / UI scale option - #406

Open
xejarque wants to merge 1 commit into
Avi0n:mainfrom
xejarque:feat/ui-scale
Open

feat(ui): add app-wide text size / UI scale option#406
xejarque wants to merge 1 commit into
Avi0n:mainfrom
xejarque:feat/ui-scale

Conversation

@xejarque

@xejarque xejarque commented Aug 20, 2026

Copy link
Copy Markdown

Summary

Adds a "Text Size" picker under Settings → Appearance with four presets (Default, Large, Extra Large, Extra Extra Large) that scales the whole app UI proportionally — useful on high-DPI displays (e.g. MacBook Retina via "Designed for iPad") and for users who want a bigger interface.

How it works

  • iOS: scales via the SwiftUI .dynamicTypeSize environment override, which propagates to semantic fonts, SF Symbols, and @ScaledMetric values.
  • Mac "Designed for iPad": Apple disables Dynamic Type on that runtime (neither \.dynamicTypeSize nor \.sizeCategory scale Text/List), so a render-time zoom (UIScaleContainer) scales the whole window using the compensating-frame trick — layout is done at windowSize / scale, then rendered at scale, keeping scroll metrics and hit targets consistent. No-op on iOS.
  • Converts key layout metrics (chat input, theme cards, onboarding, path editing) to @ScaledMetric(relativeTo: .body) so icons and spacing grow at the same rate as text. Safe by construction: at the default preset the scale is 1.0×, so there is zero visual change for existing users.

Details

  • Persistence: new PersistenceKeys.appUITextSizePreference + BackupUserDefaults mapping (backup/restore included), following the exact colorSchemePreference pattern (missing key → .system without write-back; unknown raw value → .system + corrective overwrite).
  • Localization: added Settings.Appearance.TextSize.* strings in all 10 languages; L10n.swift regenerated via SwiftGen.
  • Tests: 8 new ThemeService tests covering default/unknown/refresh/restore paths and the dynamicTypeSize/uiScale mappings.

Verification

  • xcodebuild build passes (iOS Simulator).
  • Manually verified on Mac "Designed for iPad": switching presets scales the entire UI (text, icons, lists).
  • Manually verified on iOS Simulator: Dynamic Type path scales text and metrics proportionally.

Files changed (36)

  • MC1/Theme/AppUITextSizePreference.swift (new) — preference enum + dynamicTypeSize/uiScale mappings
  • MC1/Views/Components/UIScaleContainer.swift (new) — render zoom for Mac
  • MC1/Theme/ThemeService.swift — state + persistence + refresh
  • MC1/MC1App.swift — root environment override + zoom
  • MC1/Views/Appearance/AppearanceView.swift — Text Size picker
  • 20+ views: @ScaledMetric conversions
  • MC1Services: PersistenceKeys + BackupUserDefaults
  • Localization + L10n.swift
  • MC1Tests/ThemeServiceTests.swift

Tested on

  • iOS [version]
  • iPadOS [version]
  • macOS [version]

Checklist

  • This PR was discussed with the maintainer either via GitHub issue or other means (Also check the box if this PR is small enough not to need discussion e.g. typo fix)
  • I have read CONTRIBUTING.md
  • Testing steps are documented above.
  • This change is not low effort and I took the time to test it

Copilot AI lite review requested due to automatic review settings August 20, 2026 10:07
Adds a "Text Size" picker under Settings → Appearance with four presets
(Default, Large, Extra Large, Extra Extra Large) that scales the whole
app UI proportionally.

- iOS: scales via the SwiftUI `.dynamicTypeSize` environment override,
  which propagates to semantic fonts, SF Symbols and `@ScaledMetric`.
- Mac "Designed for iPad": Dynamic Type is disabled by the platform, so
  a render-time zoom (`UIScaleContainer`) scales the entire window using
  the compensating-frame trick to keep scroll metrics and hit targets
  consistent. No-op on iOS.
- Converts key layout metrics (chat input, theme cards, onboarding,
  path editing) to `@ScaledMetric(relativeTo: .body)` so icons and
  spacing grow at the same rate as text; safe by construction at the
  default preset (1.0x).
- Persists via `PersistenceKeys` + `BackupUserDefaults` (backup/restore
  included), following the existing `colorSchemePreference` pattern.
- Localized in all 10 languages; regenerated L10n via SwiftGen.
- Adds ThemeService tests for default/unknown/refresh/restore paths and
  the dynamicTypeSize/uiScale mappings.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an app-wide UI text size / scale preference and wires it through persistence, backup/restore, settings UI, and a set of layout metrics so the interface scales proportionally across the app (with a special render-scale path for “Designed for iPad” on macOS where Dynamic Type is disabled).

Changes:

  • Introduces AppUITextSizePreference with Dynamic Type + Mac UI scale mappings, persisted via ThemeService and PersistenceKeys, and included in BackupUserDefaults.
  • Adds a “Text Size” picker under Settings → Appearance and applies the chosen preference at the app root.
  • Updates multiple views’ layout constants to @ScaledMetric(relativeTo: .body) so spacing/controls scale with the preference, and adds test coverage for preference persistence/refresh/restore behavior.

Reviewed changes

Copilot reviewed 35 out of 36 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
MC1Tests/ThemeServiceTests.swift Adds tests covering text-size preference persistence, refresh, and backup/restore downgrade behavior.
MC1Services/Sources/MC1Services/Utilities/PersistenceKeys.swift Adds a new UserDefaults key for the text-size preference raw value.
MC1Services/Sources/MC1Services/Services/BackupUserDefaults.swift Adds backup/restore mapping for the text-size preference.
MC1/Views/Support/Sections/ThemesPurchaseSection.swift Converts theme grid/card metrics to @ScaledMetric for proportional scaling.
MC1/Views/Support/Components/ThemePreviewCard.swift Scales badge spacing/padding with @ScaledMetric.
MC1/Views/Support/Components/ThemeBundleCard.swift Scales card corner radius with @ScaledMetric.
MC1/Views/PathEditing/PathEditingSheet.swift Scales row tap target min height with @ScaledMetric.
MC1/Views/PathEditing/PathEditCTALabel.swift Scales CTA icon spacing/size with @ScaledMetric.
MC1/Views/PathEditing/AddHopPickerView.swift Scales picker row spacing/badges/tap targets with @ScaledMetric.
MC1/Views/Onboarding/WelcomeView.swift Scales onboarding hero size and spacing with @ScaledMetric.
MC1/Views/Onboarding/TroubleshootingSheet.swift Scales title stack spacing with @ScaledMetric.
MC1/Views/Onboarding/RegionStepView.swift Scales onboarding layout metrics (spacing/padding/corner radius/hit targets) with @ScaledMetric.
MC1/Views/Onboarding/PulsingAntenna.swift Scales icon and hero frame size with @ScaledMetric.
MC1/Views/Onboarding/PresetStepView.swift Scales preset onboarding layout metrics with @ScaledMetric.
MC1/Views/Onboarding/PermissionsView.swift Scales permissions onboarding layout metrics with @ScaledMetric.
MC1/Views/Onboarding/NoDeviceSheet.swift Scales sheet spacing/padding/hit targets with @ScaledMetric.
MC1/Views/Onboarding/DeviceScanView.swift Scales device scan layout metrics with @ScaledMetric.
MC1/Views/Components/UIScaleContainer.swift Adds render-time zoom container for “Designed for iPad” on macOS.
MC1/Views/Chats/Components/ChatShareMenu.swift Scales chat share control sizing and uses semantic font weight.
MC1/Views/Chats/Components/ChatInputBar.swift Scales input control height/corner radius/border width and updates background helper signature accordingly.
MC1/Views/Appearance/Components/ThemeSelectionCard.swift Scales theme selection card metrics with @ScaledMetric.
MC1/Views/Appearance/AppearanceView.swift Adds “Text Size” section/picker and scales theme grid layout metrics.
MC1/Theme/ThemeService.swift Adds state + persistence + refresh logic for the text-size preference.
MC1/Theme/AppUITextSizePreference.swift Introduces the preference enum and its Dynamic Type / UI scale mappings.
MC1/Resources/Localization/zh-Hans.lproj/Settings.strings Adds localized “Text Size” strings (Simplified Chinese).
MC1/Resources/Localization/uk.lproj/Settings.strings Adds localized “Text Size” strings (Ukrainian).
MC1/Resources/Localization/ru.lproj/Settings.strings Adds localized “Text Size” strings (Russian).
MC1/Resources/Localization/pl.lproj/Settings.strings Adds localized “Text Size” strings (Polish).
MC1/Resources/Localization/nl.lproj/Settings.strings Adds localized “Text Size” strings (Dutch).
MC1/Resources/Localization/it.lproj/Settings.strings Adds localized “Text Size” strings (Italian).
MC1/Resources/Localization/fr.lproj/Settings.strings Adds localized “Text Size” strings (French).
MC1/Resources/Localization/es.lproj/Settings.strings Adds localized “Text Size” strings (Spanish).
MC1/Resources/Localization/en.lproj/Settings.strings Adds localized “Text Size” strings (English).
MC1/Resources/Localization/de.lproj/Settings.strings Adds localized “Text Size” strings (German).
MC1/Resources/Generated/L10n.swift Regenerates SwiftGen accessors for new localization keys.
MC1/MC1App.swift Applies global UI scale and Dynamic Type behavior at the app root.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +15 to +18
/// `nil` means "defer to the system" — the value passed to `.dynamicTypeSize(_:)`.
/// `.system` maps to `nil` so the app keeps its current appearance: the root
/// override falls back to the `.large` preset, which `dynamicTypeSize(_:)`
/// treats as "no override" (no visual change for existing users).
Comment thread MC1/MC1App.swift
Comment on lines 93 to +95
.preferredColorScheme(appState.themeService.effectiveColorScheme)
.uiScale(appState.themeService.uiTextSizePreference.uiScale)
.environment(\.dynamicTypeSize, appState.themeService.uiTextSizePreference.dynamicTypeSize ?? .large)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants