Add system font size as a device attribute#488
Conversation
|
PR author is not in the allowed authors list. |
There was a problem hiding this comment.
ℹ️ No critical issues — one coverage gap worth a look, plus a note.
Reviewed changes — adds the user's system-wide Dynamic Type text-size setting as three device attributes and bumps the SDK to 4.16.2.
- Add
fontSize,fontScale,preferredContentSizeCategorytoDeviceHelper— three computed properties derived fromUIFontMetrics.default.scaledValue(for: 16.0)and the currentpreferredContentSizeCategory, each#if os(visionOS)-guarded with16/1.0/"unspecified"fallbacks. - Wire the three attributes into
getTemplateDevice()— passed through to theDeviceTemplate(...)init. - Add three non-optional Codable fields to
DeviceTemplate—fontSize: Int,fontScale: Double,preferredContentSizeCategory: String.DeviceTemplateis only ever constructed in-process and encoded viatoDictionary(), never decoded from persisted data, so the new non-optional fields carry no backward-compat risk. - Version bump
4.16.1→4.16.2— updated acrossConstants.swift,SuperwallKit.podspec, andCHANGELOG.md.
ℹ️ No test for the preferredContentSizeCategory token mapping
The new preferredContentSizeCategory switch is pure, deterministic logic that maps UIContentSizeCategory cases to a fixed set of dashboard tokens, and the token strings (e.g. .accessibilityExtraLarge → "accessibilityXLarge") are a contract the backend / audience filters depend on. There's no unit test locking that mapping in, and the PR checklist's tests box is unchecked, so a future rename or a mistyped token could silently ship. The CLAUDE.md workflow also asks for a unit test alongside new SDK functionality.
Technical details
# No test for the `preferredContentSizeCategory` token mapping
## Affected sites
- `Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift:227-249` — `preferredContentSizeCategory` switch mapping `UIContentSizeCategory` → token string.
## Required outcome
- A test that asserts the token strings emitted for each `UIContentSizeCategory` case, so an accidental token rename fails CI. The category source can be exercised by driving `UITraitCollection(preferredContentSizeCategory:)` through the mapping (extract the switch into a small pure function taking a `UIContentSizeCategory` if that makes it directly testable), covering at least the `xSmall`/`large`/`accessibilityXLarge`/`unspecified` boundaries.
## Open questions for the human
- Are these token strings a hard contract with the dashboard/backend? If so, a mapping test is the cheapest guard against a silent break.ℹ️ preferredContentSizeCategory reads main-thread-only UIKit off-thread (pre-existing pattern)
getTemplateDevice() is async with no main-thread guarantee, and the new preferredContentSizeCategory reads UIScreen.main.traitCollection / UIApplication.sharedApplication — both main-thread-only — potentially off the main thread. This is not a regression: the pre-existing interfaceStyle property already does the identical thing the same way, so this PR is consistent with the file's established style. Flagging only for awareness, since the nearby cached-ScreenMetrics code exists specifically because UIScreen is main-thread-only.
Technical details
# `preferredContentSizeCategory` reads main-thread-only UIKit off-thread
## Affected sites
- `Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift:227-249` — new property.
- `Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift:189-208` — pre-existing `interfaceStyle` doing the same off-thread `UIScreen.main.traitCollection` read.
- `Sources/SuperwallKit/Network/Device Helper/DeviceHelper.swift:125-162` — `makeScreenMetrics()` caches screen metrics on the main thread at init precisely because `UIScreen` is main-thread-only.
## Required outcome
- No change required for this PR (matches existing house style). If the SDK ever hardens off-thread UIKit access, `interfaceStyle` and `preferredContentSizeCategory` should be addressed together, ideally via the same main-thread-cached approach as `ScreenMetrics`.Claude Opus | 𝕏
Extract the UIContentSizeCategory-to-dashboard-token switch into a pure static DeviceHelper.contentSizeCategoryToken(for:) and lock every token via unit tests, since these tokens are a backend audience-filter contract.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the commits since the prior pullfrog review address that review's one actionable finding by making the Dynamic Type token mapping directly testable and locking it with tests.
- Extract token mapping into a pure
static contentSizeCategoryToken(for:)— theUIContentSizeCategory→ dashboard-tokenswitchwas pulled out of thepreferredContentSizeCategorycomputed property into a standalone pure function, so the mapping can be exercised without touching main-thread UIKit state. Behavior is unchanged;preferredContentSizeCategorynow just delegates. - Add
DeviceHelperTestscoverage for the token mapping — a test asserting all 12 explicitUIContentSizeCategorycases plus twodefaultcases (.unspecifiedand an unknown raw value), guarding the backend audience-filter contract against a silent token rename. Addsimport UIKitfor the category type.
The test expectations match the switch exactly (e.g. .extraLarge → xLarge, .accessibilityExtraLarge → accessibilityXLarge). The prior review's second finding (off-thread UIKit read) was explicitly marked "no change required" and remains consistent with the pre-existing interfaceStyle house style.
Claude Opus | 𝕏

Requested by Jake Mor · Slack thread
Changes in this pull request
Before: Device attributes did not report the user's system-wide text-size setting, so paywalls and audience filters had no visibility into whether a user has enlarged or shrunk their system text (Dynamic Type).
After: Device attributes now include three complementary Dynamic Type attributes:
fontScale— aDoublescale multiplier where1.0is the system default. Values below1.0indicate smaller text; values above1.0indicate larger or accessibility sizes. Rounded to two decimal places.fontSize— a normalized integer point/px value where16corresponds to the system default ("Large") text size, scaling down for smaller sizes and up for larger/accessibility sizes.preferredContentSizeCategory— a clean Dynamic Type token describing the selected size category (e.g.large).These give paywalls and audience filters both numeric (
fontScale,fontSize) and categorical (preferredContentSizeCategory) views of the user's text-size preference, and are consistent cross-platform.The
preferredContentSizeCategoryvalue is one of the following clean tokens:xSmall,small,medium,large,xLarge,xxLarge,xxxLarge,accessibilityMedium,accessibilityLarge,accessibilityXLarge,accessibilityXXLarge,accessibilityXXXLarge, orunspecified.How: Added
fontScaleandpreferredContentSizeCategorycomputed properties toDeviceHelper(alongside the existingfontSize), following the file's existing#if os(visionOS)guarding and UIKit house style.fontScalederives the scaled value of a 16pt reference base viaUIFontMetrics.default.scaledValue(for: 16.0) / 16.0, rounded to two decimals;fontSizerounds the same scaled value to a whole integer; andpreferredContentSizeCategorymapsUIApplication.sharedApplication?.preferredContentSizeCategory(falling back toUIScreen.main.traitCollection.preferredContentSizeCategory) to a clean token. On visionOS these return1.0,16, and"unspecified"respectively. All three attributes were added as fields on theDeviceTemplateCodable struct and wired throughgetTemplateDevice(). TheUIContentSizeCategory-to-token mapping is extracted into a pureDeviceHelper.contentSizeCategoryToken(for:)and locked by unit tests, since these tokens are a backend audience-filter contract.Checklist
CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.swiftlintin the main directory and fixed any issues.🤖 Generated with Claude Code