Skip to content

fonts+layout: Support font-variant-alternates - #45308

Merged
simonwuelker merged 2 commits into
servo:mainfrom
simonwuelker:font-variant-alternates
Jul 17, 2026
Merged

simonwuelker merged 2 commits into
servo:mainfrom
simonwuelker:font-variant-alternates

Conversation

@simonwuelker

@simonwuelker simonwuelker commented Jun 2, 2026

Copy link
Copy Markdown
Member

This change is more complicated than the other font-variant-* related PRs because font-variant-alternates requires the @font-feature-values rule to work.

I've added a FontFeatureValuesMap to LayoutContext to efficiently map from identifiers in font-variant-alternates to the font-specific indices. This map is lazily initialized when needed and invalidated whenever the stylesheets change.

To correctly perform lookups into the map we also need to compute the family name for a given font. This is platform specific code and I don't have access to macos/windows, so we only support freetype for now. Presumably adding support for more platforms would not be difficult. Note that for web fonts the property is supported on all systems, because those specify font-family inside the @font-face rule.

Testing: New tests start to pass
Depends on servo/stylo#377

// interface CSSFontFeatureValuesMap {
// maplike<CSSOMString, sequence<unsigned long>>;
// undefined set(CSSOMString featureValueName,
// (unsigned long or sequence<unsigned long>) values);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This interface is completely stubbed and only exists in this change because we'd run into an unimplemented!() inside cssrule.rs otherwise. There's nothing interesting here.

@simonwuelker simonwuelker changed the title fonts+layout: Support font-variant-alternates fonts+layout: Support font-variant-alternates for freetype systems Jun 2, 2026
@codecov-commenter

codecov-commenter commented Jun 2, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
924 1 923 0
View the top 1 failed test(s) by shortest run time
servo::webview::test_preferences_change
Stack Traces | 0.246s run time
thread 'test_preferences_change' (92824) panicked at .../servo/tests/webview.rs:1082:5:
assertion `left == right` failed
  left: Ok(Array([String("1"), String("3")]))
 right: Err(EvaluationFailure(None))
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
   4: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 89d67fa to 16679ba Compare June 2, 2026 13:38
@simonwuelker simonwuelker added the T-linux-wpt Do a try run of the WPT label Jun 2, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Jun 2, 2026
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

🔨 Triggering try run (#26823561905) for Linux (WPT)

@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 16679ba to 05d237d Compare June 2, 2026 14:09
@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Test results for linux-wpt from try job (#26823561905):

Flaky unexpected result (51)
  • OK /IndexedDB/idbdatabase_deleteObjectStore.any.worker.html (#43776)
    • PASS [expected FAIL] subtest: Deleted object store's name should be removed from database's list. Attempting to use a deleted IDBObjectStore should throw an InvalidStateError
  • OK /IndexedDB/transaction-create_in_versionchange.any.html (#44200)
    • FAIL [expected PASS] subtest: Attempt to create new transactions inside a versionchange transaction

      assert_array_equals: events expected property 4 to be "open_rq.success: [object IDBDatabase]" but got "complete_count.success: 2" (expected array ["versionchange_add.success: 1", "versionchange_count.success: 0", "versionchange_add2.success: 2", "versionchange_txn.complete", "open_rq.success: [object IDBDatabase]", "complete_count.success: 2", "complete2_get.success: 1"] got ["versionchange_add.success: 1", "versionchange_count.success: 0", "versionchange_add2.success: 2", "versionchange_txn.complete", "complete_count.success: 2", "open_rq.success: [object IDBDatabase]", "complete2_get.success: 1"])
      

  • ERROR [expected OK] /_mozilla/mozilla/iframe-contentDocument.html
  • CRASH [expected OK] /_webgl/conformance/ogles/GL/functions/functions_105_to_112.html
  • CRASH [expected OK] /content-security-policy/meta/sandbox-iframe.html (#43478)
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • TIMEOUT [expected PASS] /css/css-image-animation/image-animation-body-background-no-propagation-paused.html
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-site destination
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.html (#29056)
    • PASS [expected FAIL] subtest: Cross-origin navigation started from unload handler must be ignored
  • OK /html/browsers/windows/browsing-context-names/duplicate-name-order.html (#34623)
    • PASS [expected FAIL] subtest: Duplicate name lookup order
  • OK /html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html (#36489)
    • PASS [expected FAIL] subtest: Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.
  • CRASH [expected ERROR] /html/semantics/forms/the-select-element/customizable-select/select-appearance-button-after-span.html
  • OK /html/semantics/scripting-1/the-script-element/execution-timing/077.html (#22139)
    • FAIL [expected PASS] subtest: adding several types of scripts through the DOM and removing some of them confuses scheduler

      assert_array_equals: expected property 1 to be "Script #1 ran" but got "Script #3 ran" (expected array ["Script #2 ran", "Script #1 ran", "Script #3 ran", "Script #4 ran"] got ["Script #2 ran", "Script #3 ran", "Script #4 ran", "Script #1 ran"])
      

  • OK /html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.worker.html (#33909)
    • FAIL [expected PASS] subtest: Revoking a blob URL immediately after calling import will not fail

      promise_test: Unhandled rejection with value: object "TypeError: Module fetching failed"
      

  • OK /html/webappapis/user-prompts/print-during-unload.html (#35944)
    • FAIL [expected PASS] subtest: print() during unload

      assert_array_equals: expected property 1 to be "destination" but got "error: window.print is not a function" (expected array ["start", "destination"] got ["start", "error: window.print is not a function"])
      

  • ERROR [expected TIMEOUT] /performance-timeline/not-restored-reasons/abort-block-bfcache.window.html (#42802)
  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#44693)
    • NOTRUN [expected PASS] subtest: touch two-finger pan on 'touch-action: pan-x pan-y'
    • NOTRUN [expected FAIL] subtest: touch two-finger pan on 'touch-action: pinch-zoom'
  • OK /resource-timing/buffer-full-add-then-clear.html (#40819)
    • FAIL [expected PASS] subtest: Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one

      assert_equals: Number of entries does not match the expected value. expected 3 but got 0
      

  • CRASH [expected OK] /resource-timing/render-blocking-status-link.html (#41664)
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
  • OK /visual-viewport/resize-event-order.html (#41981)
    • PASS [expected FAIL] subtest: Popup: DOMWindow resize fired before VisualViewport.
  • TIMEOUT [expected OK] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • TIMEOUT [expected FAIL] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe

      Test timed out
      

  • CRASH [expected OK] /workers/SharedWorker-exception-propagation.html
  • CRASH [expected OK] /workers/SharedWorker-extendedLifetime-data.html
  • CRASH [expected OK] /workers/Worker-creation-happens-in-parallel.https.html
  • CRASH [expected OK] /workers/Worker-formdata.any.worker.html
  • CRASH [expected OK] /workers/WorkerGlobalScope_ErrorEvent_colno.htm
  • CRASH [expected OK] /workers/WorkerLocation_pathname.htm
  • CRASH [expected OK] /workers/WorkerLocation_search_empty.htm
  • CRASH [expected OK] /workers/baseurl/alpha/xhr-in-sharedworker.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/number-arguments.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/same-origin.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/unresolvable-url.html
  • CRASH [expected OK] /workers/constructors/Worker/AbstractWorker.onerror.html
  • CRASH [expected OK] /workers/constructors/Worker/constructor-utf16-bom.html
  • CRASH [expected OK] /workers/constructors/Worker/terminate.html
  • CRASH [expected OK] /workers/examples/fetch_tests_from_worker.html
  • CRASH [expected OK] /workers/examples/general.worker.html
  • CRASH [expected OK] /workers/interfaces/DedicatedWorkerGlobalScope/postMessage/event-ports-dedicated.html
  • CRASH [expected OK] /workers/interfaces/DedicatedWorkerGlobalScope/postMessage/message-event.html
  • CRASH [expected OK] /workers/interfaces/DedicatedWorkerGlobalScope/postMessage/second-argument-undefined.html
  • CRASH [expected OK] /workers/interfaces/WorkerGlobalScope/location/setting-members.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/importScripts/005.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/importScripts/catch.sub.any.worker.html
  • CRASH [expected ERROR] /workers/interfaces/WorkerUtils/importScripts/report-error-redirect-to-cross-origin.sub.any.sharedworker.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/navigator/006.html
  • CRASH [expected OK] /workers/modules/dedicated-worker-import-data-url-cross-origin.html
  • CRASH [expected OK] /workers/modules/dedicated-worker-import-meta.html
  • CRASH [expected OK] /workers/modules/dedicated-worker-options-credentials.html
  • CRASH [expected OK] /workers/same-site-cookies/third-party.default.tentative.sub.https.window.html
Stable unexpected results that are known to be intermittent (16)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • PASS [expected TIMEOUT] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted cursive (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted fantasy (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted monospace (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted system-ui (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(nastaliq) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-monospace (drawing text in a canvas)
    • And 1 more unexpected results...
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html (#43152)
    • FAIL [expected PASS] subtest: moveBefore(): cross-origin iframe is preserved: remove self

      assert_equals: iframe does not fire a second load event expected 1 but got 0
      

  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-mode
    • FAIL [expected PASS] subtest: sec-fetch-dest

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Cross-site
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • PASS [expected FAIL] subtest: Link with onclick navigation and href navigation
  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • PASS [expected FAIL] subtest: load event does not fire on window.open('about:blank')
  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html (#44028)
  • CRASH [expected TIMEOUT] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • FAIL [expected PASS] subtest: opener of discarded auxiliary browsing context

      assert_object_equals: property "get" expected function "function opener() {
          [native code]
      }" got function "function opener() {
          [native code]
      }"
      

  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected PASS] subtest: Non-HTMLElement should not support autofocus

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus should support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus including no focusable descendants should be skipped
    • NOTRUN [expected FAIL] subtest: Area element should support autofocus
  • OK /html/semantics/scripting-1/the-script-element/module/choice-of-error-1.html (#44058)
    • FAIL [expected PASS] subtest: Parse errors in different files should be reported depending on different roots

      assert_not_equals: two different parse errors should be reported got disallowed value object "SyntaxError: expected expression, got '%'"
      

  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html (#45151)
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • TIMEOUT [expected OK] /infrastructure/testdriver/click_nested.html (#43887)
    • NOTRUN [expected FAIL] subtest: TestDriver click method with multiple windows and nested iframe
  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (iframe)
  • OK /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (link)
    • FAIL [expected PASS] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)

      assert_equals: expected 54.94 but got 54.93
      

  • OK /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • FAIL [expected PASS] subtest: Navigate a frame via area with javascript:-urls in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

Stable unexpected results (3)
  • OK /_mozilla/mozilla/interfaces.https.html
    • FAIL [expected PASS] subtest: Interfaces exposed on the window

      assert_true: If this is failing: DANGER, are you sure you want to expose the new interface CSSFontFeatureValuesRule to all webpages as a property on the global? expected true got false
      

  • OK /css/css-fonts/font-feature-values-sameobject.tentative.html
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesMap attributes in CSSFontFeatureValuesRule should be [SameObject]
  • OK /css/css-fonts/idlharness.html
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface: existence and properties of interface object
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface object length
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface object name
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface: existence and properties of interface prototype object
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface: existence and properties of interface prototype object's "constructor" property
    • PASS [expected FAIL] subtest: CSSFontFeatureValuesRule interface: existence and properties of interface prototype object's @@unscopables property

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

⚠️ Try run (#26823561905) failed!

@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 05d237d to d4b9d80 Compare June 2, 2026 14:45

@mrobinson mrobinson left a comment

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 haven't had time to look at the context.rs changes in detail, but some initial comments below. And one, more general question: Fonts can have multiple, localized family names. Is this API supposed to work with all localized names or just the first name listed in the font tables?

Comment thread components/fonts/platform/freetype/freetype_face.rs Outdated
Comment thread components/fonts/shapers/mod.rs Outdated
Comment thread components/fonts/shapers/mod.rs Outdated
Comment thread components/fonts/font.rs
Comment thread components/fonts/platform/freetype/font.rs Outdated
Comment thread components/layout/context.rs Outdated
@servo-highfive servo-highfive added the S-needs-rebase There are merge conflict errors. label Jun 5, 2026
@simonwuelker

simonwuelker commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

Is this API supposed to work with all localized names

I don't think so. At least in gecko they only consider one single family name:

https://searchfox.org/firefox-main/rev/5f6d282f6c9b4146bfe5f54b3d906c4e9ffd33cb/gfx/thebes/gfxHarfBuzzShaper.cpp#1412-1414

@nicoburns

Copy link
Copy Markdown
Contributor

Is this API supposed to work with all localized names or just the first name listed in the font tables?

I don't think so. At least in gecko they only consider one single family name:

https://searchfox.org/firefox-main/rev/5f6d282f6c9b4146bfe5f54b3d906c4e9ffd33cb/gfx/thebes/gfxHarfBuzzShaper.cpp#1412-1414

Skrifa has a function that first looks for en-US, then en, and then falls back to the first in the list.

@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 2fc0dc4 to 8296054 Compare June 5, 2026 21:24
@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-needs-rebase There are merge conflict errors. labels Jun 5, 2026
@simonwuelker

Copy link
Copy Markdown
Member Author

Is this API supposed to work with all localized names or just the first name listed in the font tables?

I don't think so. At least in gecko they only consider one single family name:
https://searchfox.org/firefox-main/rev/5f6d282f6c9b4146bfe5f54b3d906c4e9ffd33cb/gfx/thebes/gfxHarfBuzzShaper.cpp#1412-1414

Skrifa has a function that first looks for en-US, then en, and then falls back to the first in the list.

Unfortunately we can't use this without a a read_fonts::FontRef1 which we can't get for all platforms currently, despite skrifa really only needing the data for the name table (which we can get from all platforms).

I've written essentially the same but fully platform independent.

Footnotes

  1. https://docs.rs/skrifa/latest/skrifa/string/struct.LocalizedStrings.html#method.new

@simonwuelker simonwuelker changed the title fonts+layout: Support font-variant-alternates for freetype systems fonts+layout: Support font-variant-alternates Jun 5, 2026
@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 8296054 to 7d37a5f Compare June 5, 2026 21:31
@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-needs-rebase There are merge conflict errors. labels Jun 5, 2026
@nicoburns

Copy link
Copy Markdown
Contributor

Unfortunately we can't use this without a a read_fonts::FontRef1 which we can't get for all platforms currently, despite skrifa really only needing the data for the name table (which we can get from all platforms).

I was more suggesting that we might want to use the same algorithm. That being said, the Fontations project are generally quite open to small PRs, so if you wanted to PR a constructor that takes Name instead of FontRef I imagine that would be accepted.

@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 2b7bd5e to 0ba4679 Compare July 16, 2026 19:14
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 16, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 16, 2026
@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Jul 16, 2026
@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 0ba4679 to 729f613 Compare July 16, 2026 22:42
@simonwuelker
simonwuelker enabled auto-merge July 16, 2026 22:42
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 16, 2026
@jdm
jdm added this pull request to the merge queue Jul 17, 2026
@servo-highfive servo-highfive added S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. and removed S-needs-rebase There are merge conflict errors. labels Jul 17, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jul 17, 2026
@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Jul 17, 2026
@simonwuelker
simonwuelker force-pushed the font-variant-alternates branch from 729f613 to af4b46c Compare July 17, 2026 10:00
@servo-highfive servo-highfive removed the S-needs-rebase There are merge conflict errors. label Jul 17, 2026
@simonwuelker
simonwuelker added this pull request to the merge queue Jul 17, 2026
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 17, 2026
nicoburns pushed a commit to nicoburns/stylo that referenced this pull request Jul 17, 2026
Merged via the queue into servo:main with commit feaa23c Jul 17, 2026
31 checks passed
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Jul 17, 2026
@simonwuelker
simonwuelker deleted the font-variant-alternates branch July 17, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-layout/2020 https://github.com/servo/servo/wiki/Layout-2020 A-platform/fonts S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants