Skip to content

Add system font size as a device attribute#488

Merged
yusuftor merged 6 commits into
developfrom
add-font-scale-device-attribute
Jul 3, 2026
Merged

Add system font size as a device attribute#488
yusuftor merged 6 commits into
developfrom
add-font-scale-device-attribute

Conversation

@claude

@claude claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

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 — a Double scale multiplier where 1.0 is the system default. Values below 1.0 indicate smaller text; values above 1.0 indicate larger or accessibility sizes. Rounded to two decimal places.
  • fontSize — a normalized integer point/px value where 16 corresponds 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 preferredContentSizeCategory value is one of the following clean tokens:

xSmall, small, medium, large, xLarge, xxLarge, xxxLarge, accessibilityMedium, accessibilityLarge, accessibilityXLarge, accessibilityXXLarge, accessibilityXXXLarge, or unspecified.

How: Added fontScale and preferredContentSizeCategory computed properties to DeviceHelper (alongside the existing fontSize), following the file's existing #if os(visionOS) guarding and UIKit house style. fontScale derives the scaled value of a 16pt reference base via UIFontMetrics.default.scaledValue(for: 16.0) / 16.0, rounded to two decimals; fontSize rounds the same scaled value to a whole integer; and preferredContentSizeCategory maps UIApplication.sharedApplication?.preferredContentSizeCategory (falling back to UIScreen.main.traitCollection.preferredContentSizeCategory) to a clean token. On visionOS these return 1.0, 16, and "unspecified" respectively. All three attributes were added as fields on the DeviceTemplate Codable struct and wired through getTemplateDevice(). The UIContentSizeCategory-to-token mapping is extracted into a pure DeviceHelper.contentSizeCategoryToken(for:) and locked by unit tests, since these tokens are a backend audience-filter contract.

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs on iOS.
  • Demo project builds and runs on Mac Catalyst.
  • Demo project builds and runs on visionOS.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run swiftlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

🤖 Generated with Claude Code

@claude claude Bot changed the title Add system font size (Dynamic Type) as a device attribute Add system font size as a device attribute Jul 3, 2026
@yusuftor yusuftor marked this pull request as ready for review July 3, 2026 11:57
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

@pullfrog pullfrog Bot 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.

ℹ️ 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, preferredContentSizeCategory to DeviceHelper — three computed properties derived from UIFontMetrics.default.scaledValue(for: 16.0) and the current preferredContentSizeCategory, each #if os(visionOS)-guarded with 16 / 1.0 / "unspecified" fallbacks.
  • Wire the three attributes into getTemplateDevice() — passed through to the DeviceTemplate(...) init.
  • Add three non-optional Codable fields to DeviceTemplatefontSize: Int, fontScale: Double, preferredContentSizeCategory: String. DeviceTemplate is only ever constructed in-process and encoded via toDictionary(), never decoded from persisted data, so the new non-optional fields carry no backward-compat risk.
  • Version bump 4.16.14.16.2 — updated across Constants.swift, SuperwallKit.podspec, and CHANGELOG.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`.

Pullfrog  | Fix it ➔View workflow run | Using 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.

@pullfrog pullfrog Bot 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.

✅ 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:) — the UIContentSizeCategory → dashboard-token switch was pulled out of the preferredContentSizeCategory computed property into a standalone pure function, so the mapping can be exercised without touching main-thread UIKit state. Behavior is unchanged; preferredContentSizeCategory now just delegates.
  • Add DeviceHelperTests coverage for the token mapping — a test asserting all 12 explicit UIContentSizeCategory cases plus two default cases (.unspecified and an unknown raw value), guarding the backend audience-filter contract against a silent token rename. Adds import UIKit for the category type.

The test expectations match the switch exactly (e.g. .extraLargexLarge, .accessibilityExtraLargeaccessibilityXLarge). 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.

Pullfrog  | View workflow run | Using Claude Opus𝕏

@yusuftor yusuftor merged commit 6d98fb5 into develop Jul 3, 2026
3 checks passed
@yusuftor yusuftor deleted the add-font-scale-device-attribute branch July 3, 2026 12:29
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