Skip to content

feat(react-windmod-preview): a Tailwind v4 + CSS Modules styling layer for Fluent's headless components - #36656

Open
Ray Knight (ArrayKnight) wants to merge 345 commits into
microsoft:masterfrom
ArrayKnight:styling/react-windmod
Open

Ray Knight (ArrayKnight) wants to merge 345 commits into
microsoft:masterfrom
ArrayKnight:styling/react-windmod

Conversation

@ArrayKnight

@ArrayKnight Ray Knight (ArrayKnight) commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

What this PR proposes

A Tailwind v4 + CSS Modules styling layer for Fluent's headless components, offered for official support and discussion of a sustainable landing plan. It adds preview packages without changing the styling system shipped by existing @fluentui/react-components consumers.

Following the maintainer feedback about review size, the independent fixes have been split into their own PRs. The remaining decision is whether the styling layer should be submitted in smaller in-tree pieces or maintained as a community package. The follow-up comments describe those options.

What ships

Package Role
@fluentui/react-tailwind-theme-preview Fluent tokens, seven theme stylesheets, Tailwind variants, cascade layers, and density scaling
@fluentui/react-windmod-preview Styled headless components across 53 family subpaths; 135 authored CSS Modules; per-component CSS delivery
@fluentui/postcss-tailwind-css-modules Public PostCSS plugin that preserves Tailwind group/peer markers under CSS Modules; used by this build pipeline and Storybook

The styling packages include a 64-entry migration guide and a shipped authoring skill. Checkbox, Radio, and Switch use a grid to align the indicator with the first label line, including wrapping labels. The CSS layer and scaling contracts are documented with the packages.

Independent fixes and current status

PR Fix Status
#36663 Breadcrumb distributive Omit Merged
#36664 ColorPicker zero-channel fallback Merged
#36665 Headless popover attribute spread order Merged
#36666 Dialog scrollbar gutter Open; preserves host stable both-edges gutters after review
#36667 TagPicker aside-width animation frame Open; ref-detach cleanup, valid zero handles, cancellation-aware React 18 verification
#36668 Tooltip arrow class Merged
#36669 MenuSplitGroup context values Merged
#36670 Headless context exports Merged
#36671 Storybook sandbox registration on Windows Open
#36672 API subpath generation on Windows Open
#36673 Preservation of declared static export subpaths Open
#36690 NavCategoryItem decorative icon accessibility Merged

This branch incorporates current upstream and the reviewed Dialog, TagPicker, and context-export fixes. The four StackShim compiler tests were leftovers from the earlier whole-library conversion: they referenced a CSS module that is absent from this PR. That obsolete test group is removed. The existing v8-to-v9 StackShim implementation remains intact. The PostCSS declarations now match the callable CommonJS export, with type fixtures for CommonJS and ESM consumers.

Review caveats

  • Component coverage: headless RangeSlider (#36894) and the Calendar family—Calendar, CalendarDay, CalendarMonth and CalendarYear (#36752)—still need Windmod wrappers, public exports, token-based styling, interaction tests and paired visual coverage. The current 94-scene comparison suite covers neither family.
  • Calendar keyboard support: upstream documents that grid arrow navigation requires focusgroup V2 grid support unavailable in the currently released polyfill. Related upstream browser tests remain pending a compatible release; styled Calendar coverage must retain and independently assess that limitation.
  • Visual parity: the suite includes accepted allowances. Menu RTL has intermittently measured 442 differing pixels against its unchanged 432-pixel ceiling, including on pre-sync controls. A subsequent passing capture does not resolve that finding or establish blanket parity.
  • Composition and cascade: List's checkmark props target a native input. Nested selectable lists need a caller-provided click-propagation boundary for independent selection; the shared headless/Griffel behavior can select the enclosing row too. Icon styles must use the layered import; an additional unlayered copy overrides component rules. These contracts and other intentional differences are documented in the migration guide.
  • Tooling validation: full tooling sign-off remains outstanding. Local Windows path, CRLF, temporary-directory cleanup, snapshot, and subprocess-warning failures are recorded; the latest compiler-analyzer failures also reproduce against the prior source revision. Passing component checks do not establish that all repository tooling is green.
  • Performance evidence: the deep-dive measurements are historical. The runtime benchmark used the monolithic stylesheet, before per-component CSS delivery; its heap metric excludes CSSOM. Griffel was faster in the measured re-render case. These results have not been remeasured for the current head.

The separate TagPicker PR contains React 18 and React 19 regression coverage; Dialog's preservation tests cover inline and stylesheet-provided gutters. Review those fixes in their linked PRs above.

Remaining dependencies and decisions

Related issues

#36645, #36646, #36647, #36648, #36649, #36650, #36651, #36652, #36653, #36654, #36655, #36685.

The individual fix PRs own issue closure. The larger argument, historical measurements, and detailed findings remain in the linked deep dive.

Ray Knight (ArrayKnight) and others added 30 commits August 24, 2026 07:12
…e containers

A component rendered inside a providing container now reads the same look
contexts the reference implementation does: Button, CompoundButton,
MenuButton and ToggleButton take their size from ButtonContext, Link its
inline flag from LinkContext, Avatar its shape and size from AvatarContext,
and the field controls their size from FieldContext — narrowed to the look
key, since the base hooks already apply the aria half. Tag derives the
avatar shape and size its children consume. The contexts re-export from the
headless package so the provider instances connect; one shared helper folds
context into props ahead of destructuring, keeping local values authoritative
and letting context beat only the defaults.

Verified by container scenes that previously pinned these values and now
adjudicate them — message-bar and tag hold strict zero against the reference
and fail measurably when any fold is severed. 29 mutations caught, the full
visual sweep holds every scene, and the API report deltas are additive only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ng in the toolbar button variants

The toolbar's layer sits above the shared button layers, so its plain
declarations beat the forced-colors blocks below: the checked-subtle glyph
kept a brand hover colour the system palette replaces arbitrarily, and the
checked rest border swallowed the focus border colour. Both variants gain
trailing forced-colors re-asserts — the glyph takes the system Highlight
under hover and press, the checked root takes HighlightText border colour
under focus — repeating only what their resting blocks win back.

Verified by computed-style probes under forced-colors emulation across all
75 toolbar cells in four interaction states, including a widened focus walk
that reaches past the reference toolbar's roving tabindex: 22 divergences
before, zero after, zero newly broken, with the shared button layers
unmoved, a reference-vs-reference control at zero, and the baseline
reproduced exactly on revert. The normal-mode visual sweep holds every
scene, so the change is invisible outside forced-colors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ng for MenuButton and SplitButton

The open menu button's selected background, its focus border, and its
disabled colours all sat in blocks that outrank the shared button layer's
forced-colors rules, and the split button's divider edge declared a logical
border longhand that beats the physical focus shorthand below it — four
faces of the same cascade contest the reference resolves by emitting every
forced-colors rule last. Both modules gain trailing forced-colors re-asserts
inside the winning blocks, including the disabled colour crossing whose text
colour also restores the chevron glyph fill through currentColor.

Verified by computed-style probes under forced-colors emulation across all
250 menu-button and split-button cells with a widened focus walk: 106
divergences before, zero after, zero newly broken across every probed scene
and phase, with a reference-vs-reference control at zero and the baseline
reproduced exactly on revert. The survey now covers every module in the
package's upper layers; the two remaining divergences trace to reference
bugs and are recorded, not copied. The normal-mode visual sweep holds every
scene, so the change is invisible outside forced-colors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…lass-map keys to camelCase

The named font-size tokens across all seven themes become calc products of
the shared base-scale ratio, so text now scales with the root font size the
way spacing and stroke widths already do — identical at the default 16px
root. Icon glyph sizes gain their own named tokens. The CSS-module class-map
serializer and the storybook loader both export each kebab-case local under
a camelCase alias so styles hooks keep dot access.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ponents A-C

Arbitrary-property utilities become plain declarations, class-map locals go
kebab-case behind the camelCase aliases, multi-branch ternaries flatten to
if-returns, single-use props destructure in the parameter list with state
literals inlined, and transition-property adopts the reference shorthands.
No public API, ident-independent pixel, or behaviour change; the full visual
sweep holds every scene.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ponents D-M

Arbitrary-property utilities become plain declarations, class-map locals go
kebab-case behind the camelCase aliases, multi-branch ternaries flatten to
if-returns, single-use props destructure in the parameter list with state
literals inlined, and transition-property adopts the reference shorthands.
No public API, ident-independent pixel, or behaviour change; the full visual
sweep holds every scene.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ponents N-S

Arbitrary-property utilities become plain declarations, class-map locals go
kebab-case behind the camelCase aliases, multi-branch ternaries flatten to
if-returns, single-use props destructure in the parameter list with state
literals inlined, and transition-property adopts the reference shorthands.
No public API, ident-independent pixel, or behaviour change; the full visual
sweep holds every scene.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ponents T-Z

Arbitrary-property utilities become plain declarations, class-map locals go
kebab-case behind the camelCase aliases, multi-branch ternaries flatten to
if-returns, single-use props destructure in the parameter list with state
literals inlined, and transition-property adopts the reference shorthands.
No public API, ident-independent pixel, or behaviour change; the full visual
sweep holds every scene.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…-identical to Griffel)

TagGroup is mostly wiring: one flex row with size-stepped column gaps, and
the look channel its children read. The headless context publishes no
appearance or size, so the group fills the reference context for any Tag
implementation inside it, and a local context carries the same pair to this
package's Tag, whose defaults now yield to the group exactly as the
reference resolves them — proven byte-identical out of a group across a
160-pair identity matrix. Dismiss and disabled behaviour were already wired
base-hook to base-hook. Selection interaction belongs to the interaction
tags and ships with them.

Verified pixel-identical to the Griffel implementation at strict zero on
both the new scene and the Tag guard scene, 24 planned and review mutations
killed with one gate-owned, and byte-idempotent API reports across all 76.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
# Conflicts:
#	packages/react-components/react-windmod-preview/library/src/components/Tag/Tag.tsx
…identical to Griffel)

Persona lays its avatar and up to four text lines on a named grid whose
line names are load-bearing only when theirs is the first rendered row —
the visual gate covers that with sparse ladders that make each name carry
the layout in turn. The avatar renders through this package's Avatar via
the slot channel the reference uses, composing identically with the
surrounding avatar context, and a consumer-supplied avatar object no
longer leaks onto the root as a stringified attribute — the one place
this component corrects the headless layer rather than mirroring it.

Verified pixel-identical to the Griffel implementation at strict zero over
76 cells including right-to-left, coinless and centred bands, 27 mutations
killed, and byte-idempotent API reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…y and InteractionTagSecondary (headless + Tailwind, pixel-identical to Griffel)

The interaction tags split a Tag into two focusables: a primary action that
carries the selection behaviour the tag group defers here, and a secondary
dismiss button. The reference implementation styles the primary's inner
slots by importing Tag's own style buckets, so Tag's slot presentation is
extracted into one shared plain function both components consume — any
drift now fails both components' specs. The family reads the group look
context, provides its own, and derives the avatar sizing its media slot
imposes, all through the landed context architecture.

Verified pixel-identical to the Griffel implementation at strict zero on
its own scene and both guard scenes, a twelve-pass computed-style parity
matrix covering hover, press, keyboard focus and forced colors in both
directions, and 98 mutations across the planned, review and spec tables
all accounted for, with byte-idempotent API reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…iving story and README section

Overflow ships no styled port because the overflow machinery is renderless —
there is nothing to style. The story proves the claim live: eight Buttons in
a resizable strip hide and restore through the reference overflow engine on
both the recommended barrel path and the Griffel-free headless path, with an
overflow-count trigger. The README records the measured compatibility, the
two unlayered consumer declarations only the headless path needs, and the
lint-sanctioned import route through the components barrel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
MIGRATION.md sits at the package root so it ships in the tarball, and walks
a react-components consumer through the port: the subpath import map for
all 79 components, the parity contract, the twenty-eight deliberate
differences each with its consumer guidance — root-font-size scaling,
cascade layers, native focus modality, mount persistence, the anchored
components' browser support — the not-shipped list, and the upstream
issues that affect both libraries. Every code snippet compiles strict
against the built package and every subpath in the map resolves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
… win on the PopoverSurface slot

The surface slot spelled its popover attribute after the consumer props
spread, so the prop was silently discarded — popover exclusivity then capped
a page at one open Popover where eight should show, and a consumer switching
to manual dismiss was ignored. It moves into the slot's defaultProps, the
placement the Tooltip hook already uses: the hook supplies the default, the
consumer wins.

The id spelled alongside it deliberately stays put. Popover.types documents
`<Popover id>` as the surface's id channel, and usePopoverTrigger points
aria-details at that same value, so letting a surface-level id win would
leave the trigger referencing an element that no longer exists.
PopoverSurface.test.tsx already guards that linkage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…urface (headless + Tailwind, visually matching Griffel within a documented allowance)

The surface renders inline and rises through the native top layer, so it
stays inside the provider and reads the theme live — no portal, no variable
re-declaration. Positioning is CSS anchor positioning end to end; the
browser's popover and dialog defaults that Griffel's portaled div never met
are each neutralised or proven inert, including the modal backdrop. The
pointer arrow sits a fixed inset from the surface edge, where the reference
centres it on the target — an accepted consequence of the headless layer's
CSS-only positioning, recorded with its worst case in the migration notes,
alongside the top-layer drop-shadow rasterization the scene's documented
allowance bounds with a reference-free control. Centred placements are
pixel-exact.

Verified across fourteen placement, size and appearance cells, nineteen
mutations killed including two owned by the visual gate alone, focus-trap
behaviour checks, and byte-idempotent API reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
… win on the MenuPopover and Listbox slots

Both surfaces pinned their popover attribute where a consumer's prop could
not reach it. useMenuPopover spread `popover: 'auto'` over the base state's
root after the base hook had already merged props, and useListbox assigned
`state.root.popover = 'auto'` outright after its hook returned. Because every
auto popover on a page closes the others, that capped a page at one open Menu
and one open listbox: measured, three listboxes give one open surface, and
three Menus give one. The default now merges before the base state rather than
over it, which is the order slot.always already uses for defaultProps — the
hook supplies 'auto', a consumer spelling popover="manual" wins, and the same
three-cell pages then show three open surfaces.

Nothing else rides either assignment. Unlike PopoverSurface, where an id was
spelled alongside the attribute and had to stay put, these two sites set the
popover attribute alone: the listbox's id and its aria-labelledby linkage are
owned by useListboxSlot, which this does not touch.

Light dismiss under the default is unchanged, and a closed listbox still stays
mounted at display:none.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…while a dialog locks scroll

lockDocumentScroll sets body.style.overflow = 'visible clip' and nothing else, so
the page scrollbar disappears without its space being reserved and every fixed or
centred element on the page shifts sideways by half the scrollbar width when a
modal opens. Measured on a scrolling 1280px page: a centred marker sits at x 332.5
with the dialog closed and jumps to 340 when it opens, where the Griffel equivalent
stays at 332.5 because react-dialog's useDisableBodyScroll also reserves the gutter.

The reservation goes on the document element, not on body. `overflow` propagates
from body to the viewport — which is why the lock works at all with `<html>` left
`visible` — but `scrollbar-gutter` does not: measured, `scrollbar-gutter: stable`
on body alongside the clip reserves nothing and leaves the same 7.5px jump. The
docblock's promise to leave `<html>` untouched cannot be kept and still fix this;
padding body instead was measured too and does not help, because it narrows body
rather than the initial containing block a top-layer surface resolves against.

`stable` reserves a gutter whether or not the page had a scrollbar, so writing it
unconditionally would introduce the mirror-image defect: a 7.5px shift on a page
that never scrolled. It is written only when the scrollbar is actually taking
layout width, read before the lock removes it. That reads 0 under overlay
scrollbars as well, where nothing needs reserving and Griffel's own height-based
guard over-reserves.

Unlock restores the previous inline gutter next to the previous overflow, so a host
application that had already set one gets it back, and the existing lockCount
reference count scopes both to the outermost dialog. Non-modal dialogs never take
the lock and are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…, DialogTitle, DialogHeader, DialogBody and DialogActions (headless + Tailwind, visually matching Griffel within a documented allowance)

The surface is a real native dialog: modal and alert open through showModal
with a styled backdrop that tracks the theme, non-modal through the popover
channel, and the reference grid merges directly onto the dialog element with
its tracks measured identical. The browser attribute autofocus is stamped
through a ref callback because React never renders it and the native
focusing steps would otherwise override the intended target — focus lands on
the same element the reference picks in every band. Scrolling content lives
in the body element with the header and actions static, exactly as the
reference arranges it; the scrollable area runs two pixels shorter because a
merged element's grid resolves inside its own content box where the
reference's separate scroller overflows its parent by the border width — an
accepted, documented difference bounded to a single translated box.

Verified across four scenes — three at strict zero and the scroll scene
wholly accounted to the accepted delta — 30 mutation rows with one proven
semantic no-op, focus and dismiss behaviour checks, and byte-idempotent API
reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…ss + Tailwind, pixel-identical to Griffel)

First half of the drawer family: the base Drawer plus header, header title,
header navigation, body and footer. Scroll-state hairlines key on the
headless scroll stamps, and the scroll scene parks mid-scroll so both
genuinely paint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
…less + Tailwind, pixel-identical to Griffel)

The overlay drawer rides the native dialog with the browser defaults
neutralised down to a single border-none — restating the reference border
colour turned out to manufacture the one divergence the plan had accepted,
so removing it closed that gap. Slide positions author logical properties
where the reference uses physical ones with a build transform, measured
equal in both directions; the resting shadow exists only in the reference's
motion end state, so the scenes finish the animations into exactly the rest
the reference shows. The inline drawer participates in layout with its
position-keyed separator.

Verified pixel-identical at strict zero across all seven scenes — six
overlay viewports and a fourteen-cell inline grid — with fifty mutation
rows killed across jest, probe and visual ownership, and byte-idempotent
API reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
# Conflicts:
#	packages/react-components/react-windmod-preview/library/config/tests.cjs
…Group (headless + Tailwind, pixel-identical to Griffel)

The combobox anchors its listbox surface with pure CSS anchor positioning,
matching the reference's JavaScript-measured width through the anchor's
border box — resolved synchronously where the reference waits two frames,
settling equal. The never-shown popover surface carries five browser
defaults neutralised, including the anchor association itself on a
standalone listbox. Appearance reproduces the reference combobox's own
fork of the input machinery — importing the finished input interactive
buckets was measured to diverge — and the selected-option checkmark holds
its row space through visibility, with the reference's three-valued glyph
honoured including its empty multiselect form. Cascade order follows the
reference's specificity outcomes on all twelve order-dependent pairs.

Verified pixel-identical at strict zero across six scenes three times
each, twenty-five mutation rows killed with browser-owned rows proven
observable, an eleven-pass interaction parity matrix at zero, and
byte-idempotent API reports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
# Conflicts:
#	packages/react-components/react-windmod-preview/library/etc/react-windmod-preview.api.md
#	packages/react-components/react-windmod-preview/library/package.json
#	packages/react-components/react-windmod-preview/library/src/index.ts
…wer and combobox merges

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wmpBCYJpDJCLXcScCWz1i
@ArrayKnight
Ray Knight (ArrayKnight) requested a review from a team as a code owner September 14, 2026 16:22
@ArrayKnight
Ray Knight (ArrayKnight) requested a review from a team as a code owner October 23, 2026 00:22
@ArrayKnight

Ray Knight (ArrayKnight) commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks Dmytro Kirpa (@dmytrokirpa). The independent fixes are split into their own PRs, one per linked issue:

For the styling layer, I'd like to understand which concern determines the next step:

  1. Review surface: I can split this into a theme package, build infrastructure, and component-family batches, each with its own validation and documented exceptions. The proposed submission order gives a starting point.
  2. Long-term maintenance: the goal is an incremental migration path that could eventually reduce reliance on Griffel. Visual parity is a target, with the review caveats called out in the PR description. Is there interest in that direction, and what scope could the team sustain?

If a community package is the preferred route, would the team support a discoverability pointer in the docs, and consider the headless public API and DOM structure a stable contract for an external styling layer?

@ArrayKnight

Copy link
Copy Markdown
Contributor Author

Dmytro Kirpa (@dmytrokirpa) If review size is the main concern, the proposed submission order is:

  1. Theme package: Fluent tokens, theme stylesheets, the generator, and its checks. This is useful independently of the component layer.
  2. PostCSS plugin and build infrastructure, with Button as the first complete example: the public plugin can be reviewed separately if that makes the first component PR easier to assess.
  3. Component-family batches: ordered after the families whose state selectors they depend on, with relevant tests, visual comparisons, migration guidance, and known exceptions in each submission.
  4. Cross-package documentation and migration guidance: shared documentation accompanies the change it explains; the final pass connects the pieces.

I would recut the batches against the current tree once there is agreement on this approach and a review cadence the team can sustain. The independent fix PRs can proceed separately.

If long-term maintenance of the styling layer is the main concern, the adoption/community-package decision in that comment comes first.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants