Skip to content

feat(sr): add configurable font-family transform#1016

Merged
fuzzybinary merged 2 commits into
DataDog:developfrom
nottmey:feat/smart-font-family-handling
May 7, 2026
Merged

feat(sr): add configurable font-family transform#1016
fuzzybinary merged 2 commits into
DataDog:developfrom
nottmey:feat/smart-font-family-handling

Conversation

@nottmey
Copy link
Copy Markdown
Contributor

@nottmey nottmey commented Apr 30, 2026

What and why?

  • Add DatadogSessionReplayConfiguration.fontFamilyTransform (FontFamilyStrategy / FontFamilyTransformConfig) so captured font families can be rewritten to web-compatible CSS stacks in the processor isolate before upload (default FontFamilyStrategy.none for backwards compatibility; opt into FontFamilyStrategy.smart for normalization). In smart mode, FontFamilyTransformConfig.rules[''] sets the stack when the captured family is empty or ends up empty after sentinel stripping.

How?

Review checklist

  • This pull request has appropriate unit and / or integration tests
  • This pull request references a Github or JIRA issue

@nottmey nottmey requested a review from a team as a code owner April 30, 2026 10:44
@nottmey nottmey changed the title feat(session_replay): add configurable font-family transform feat(session-replay): add configurable font-family transform Apr 30, 2026
Copy link
Copy Markdown
Member

@fuzzybinary fuzzybinary left a comment

Choose a reason for hiding this comment

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

Minor suggestions if you want to take care of them.

I'm going to actually ask @gonzalezreal to review as I think he has familiarity with how iOS handles this and will be able to give me more insight on whether or not we need separate iOS / Android fallback values.

Comment on lines +84 to +90
if (lower == 'blinkmacsystemfont') {
return 'BlinkMacSystemFont';
}
if (lower == '-apple-system') {
return '-apple-system';
}
return lower;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: You could use pattern matching for this:

return switch (lower) {
  'blinkmacsystemfont' => 'BlinkMacSystemFont',
  '-apple-system' => '-apple-system',
  _ => lower,
};

which I find very slightly cleaner.

Comment on lines +130 to +131
case FontFamilyStrategy.fallback:
return kIosParityFontStack;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this only needed on iOS? Is there an Android fallback we should be using as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The kIosParityFontStack mirrors the iOS native SDK's Fallback.fontFamily (REPLAY-1421). The Android SDK uses "Roboto, sans-serif" — same effective result without the Apple-specific prefixes. Since the replay player is a web browser, the -apple-system, BlinkMacSystemFont prefixes are harmless CSS fallbacks that improve rendering on Safari/Chrome macOS. Roboto already serves as the Android (and general web) fallback. No separate Android stack needed — one stack covers both platforms.

-> update the naming (now defaultReplayFontStack) and comment

}
}

SRTextWireframe rewrite(SRTextWireframe wireframe) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it makes sense for this method to be called apply and to potentially change the name of the String transform method. I can see a situation where we might need multiple of these Transforms in the future, and having them all implement a method called apply makes sense to me, whereas rewrite doesn't quite seem like the right term for generalized application of a transform.

I'll leave it to your discretion if you want to change it though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  • rewrite(SRTextWireframe) → apply(SRTextWireframe) — the wireframe-level method now called apply, which is the generalized name that future transforms can all share
  • apply(String) → transformFamily(String) — the string-level helper renamed to transformFamily so it no longer collides and better describes what it does (transforms the font family string specifically)
  • Updated the one call site in processor_worker.dart and all 27 references in tests

Comment on lines +187 to +189
if (stripped.isEmpty) {
continue;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be above the check in _flutterFontSentinels?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, good call!


import 'dart:isolate';

import '../../datadog_session_replay.dart';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be below the package imports... I'm surprised the linter didn't complain about that....

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated, neither flutter_lints nor the underlying package:lints/recommended.yaml seem to enable directives_ordering

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

enabled it for sr

import 'dart:async';
import 'dart:convert';

import '../../datadog_session_replay.dart';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same here, this should move down to be with the other relative imports.

Comment on lines +52 to +53
/// wireframes in a background isolate, which cannot serialize Dart closures—use
/// [FontFamilyTransformConfig.rules] instead.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// wireframes in a background isolate, which cannot serialize Dart closures—use
/// [FontFamilyTransformConfig.rules] instead.
/// wireframes in a background isolate, which cannot serialize Dart closures
/// use [FontFamilyTransformConfig.rules] instead.

Comment on lines +66 to +71
/// Always emits the single hardcoded CSS stack
/// `-apple-system, BlinkMacSystemFont, Roboto, sans-serif`,
/// regardless of the captured family. Matches the current iOS SDK
/// behavior and is the safest choice if you do not want any
/// Flutter font names leaving the device.
fallback,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does it make sense to check the current Platform and supply an Android fallback as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated the comment, we already use a android compatible fallback

Comment on lines +3 to +6
## Unreleased

* Add `DatadogSessionReplayConfiguration.fontFamilyTransform` (`FontFamilyStrategy` / `FontFamilyTransformConfig`) so captured font families can be rewritten to web-compatible CSS stacks in the processor isolate before upload (default `FontFamilyStrategy.none` for backwards compatibility; opt into `FontFamilyStrategy.smart` for normalization). In smart mode, `FontFamilyTransformConfig.rules['']` sets the stack when the captured family is empty or ends up empty after sentinel stripping.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We pull CHANGELOG items from the commit message now. I can add in the extra details during the release.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok, removed

@fuzzybinary
Copy link
Copy Markdown
Member

If you want, we could actually setup a golden test for this as well. Since goldens can only render one specific font, you could take any of the tests that render text and setup the transformer to make the switch.

@nottmey nottmey force-pushed the feat/smart-font-family-handling branch 2 times, most recently from 9b6b366 to 779b8a2 Compare May 7, 2026 14:51
@nottmey nottmey changed the title feat(session-replay): add configurable font-family transform feat(sr): add configurable font-family transform May 7, 2026
Add FontFamilyStrategy and FontFamilyTransformConfig to rewrite text
wireframe font families in the processor isolate. Smart mode normalizes
Flutter-specific names for web replay; default strategy is none to
preserve prior behavior. Document in CHANGELOG.

Made-with: Cursor
@nottmey nottmey force-pushed the feat/smart-font-family-handling branch from 779b8a2 to ba9f0b3 Compare May 7, 2026 15:22
@nottmey nottmey requested a review from fuzzybinary May 7, 2026 15:23
fuzzybinary
fuzzybinary previously approved these changes May 7, 2026
@nottmey
Copy link
Copy Markdown
Contributor Author

nottmey commented May 7, 2026

@fuzzybinary I'm not sure whether goldens make sense here, created a separate PR for it: #1028 (commit 1103307)

Co-authored-by: Cursor <cursoragent@cursor.com>
@fuzzybinary fuzzybinary merged commit d50ba33 into DataDog:develop May 7, 2026
11 checks passed
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.

3 participants