Skip to content

spike(GMT-1715): Panda CSS styling proof of concept - #3402

Draft
dreamwasp wants to merge 20 commits into
cass-gmt-1709from
cass-GMT-1715
Draft

spike(GMT-1715): Panda CSS styling proof of concept#3402
dreamwasp wants to merge 20 commits into
cass-gmt-1709from
cass-GMT-1715

Conversation

@dreamwasp

Copy link
Copy Markdown
Contributor

GMT-1715 — Panda CSS styling spike

Self-contained proof of concept exploring Panda CSS as an Emotion replacement for the 1.0 reboot. Lives in spikes/panda-styling-poc/ and is isolated from the yarn workspaces (its own package.json/node_modules; nothing in packages/* changes). Grounded in the real packages/gamut/src/Button and the packages/styleguide/.storybook theme/colorMode switchers.

Not intended to merge — a spike to de-risk the styling-engine decision. See spikes/panda-styling-poc/README.md for the full findings log.

Questions this answers

  • Do the variance-style helpers (css/variant/states/system props) + type-safe theme tokens survive on Panda?
  • Do the styled factory, GamutProvider, ColorMode, and Background still work?
  • Can we swap themes + color modes at runtime like Emotion does now (the Storybook switchers)?
  • What changes for consumers beyond styled coming from Gamut?

Findings

  • Type-safe tokens — native + stronger; bad token / variant / size all fail tsc (strictTokens).
  • styled factory — works, re-exported from the Gamut facade (never @emotion/styled).
  • GamutProvider / ColorMode / Background — port cleanly and mostly shrink (no Emotion cache / ThemeProvider / <Global> injection; consumers import one static stylesheet).
  • Runtime theme + colorMode switching — works via data-panda-theme + data-color-mode attribute flips; every theme×mode combo is pre-generated statically (zero runtime), matching the Storybook switchers.
  • Zero-runtime + bundlingtsc, panda cssgen, and vite build all pass.

Consumer surface (beyond styled from Gamut)

Concentrated and codemoddable/documentable: the css prop, JS-resolved color values (charts/canvas, _getColorValue), theme customization, SSR wiring (removed), and @emotion/jest test matchers.

Still NOT proven

  • Rspack + Module Federation with a shared stylesheet (platform — the highest-risk unknown).
  • Runtime-dynamic non-token styles; chart color resolution.

Run it

cd spikes/panda-styling-poc
npm install
npm run build   # codegen + cssgen + vite build
npm run dev     # click the theme + colorMode switchers

🤖 Generated with Claude Code

…me/colorMode switching

Self-contained spike (spikes/panda-styling-poc, isolated from workspaces)
exploring Panda CSS as an Emotion replacement. Grounded in the real
Button + Storybook switchers.

Findings (see spike README):
- Type-safe tokens: native + stronger; bad token/variant/size fail tsc.
- styled JSX factory works and is re-exported from the Gamut facade (not Emotion).
- GamutProvider/ColorMode/Background port and mostly shrink (no cache/ThemeProvider/Global).
- Runtime theme (core/admin) + colorMode (light/dark) switching works via
  data-panda-theme + data-color-mode attribute flips — matches the Storybook
  switchers; combined theme×mode combos pre-generated statically (zero runtime).
- Consumer surface beyond `styled`: css-prop, JS-resolved colors (charts),
  theme customization, test matchers — all codemoddable/documentable.

Not proven: Rspack + Module Federation (spike #2), runtime-dynamic non-token
styles, chart color resolution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nx-cloud

nx-cloud Bot commented Jul 30, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 4b570a6


☁️ Nx Cloud last updated this comment at 2026-08-03 15:42:34 UTC

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

⚠️ JUnit XML file not found

The CLI was unable to find any JUnit XML files to upload.
For more help, visit our troubleshooting guide.

@github-actions

Copy link
Copy Markdown
Contributor

dreamwasp and others added 11 commits July 31, 2026 08:35
…page; Background static color-mode context

- Register spikes/* as a yarn workspace; add nx project.json
  (codegen/cssgen/typecheck/build/dev). Run via `yarn nx run panda-styling-poc:*`.
- Add prop-based authoring (Box + system props + css prop) so no `className` is
  needed; add authoring-comparison.tsx mapping today's css/variant/states idioms.
- Fix Background to own STATIC color-mode context (contrast-selected mode +
  background-current) per the ColorMode/theming skills; ColorMode = ambient mode.
- Add an example page (src/App.tsx): Button recipe variant×size, variant()/states()
  demos, theme+colorMode switchers, static <Background bg="navy">.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…yledDynamic)

Makes the external styling API compatible under zero-runtime by covering the two
residuals the mono/platform usage survey found:

- getColorValue(alias, mode, theme): reads a resolved color VALUE in JS for
  non-CSS consumers (charts/canvas). Derived from a new single source of truth
  (src/tokens.source.ts) that ALSO builds the Panda CSS vars, so it can't drift.
- styledDynamic(Tag)(props => styles): keeps the styled(fn) authoring shape for
  genuinely dynamic values; applies computed styles inline (that component only),
  strips $-transient props from the DOM.

panda.config now derives tokens/themes/recipe from tokens.source. Example page
adds a prop-driven <Meter> (styledDynamic) and an SVG chart (getColorValue),
both theme+mode aware. typecheck + build green via nx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nt runtime-style cases

- staticCss now emits all button variants (recipes.button ['*']) so dynamically-
  selected variants (variant={x} in a map) ship — a design-system requirement
  Panda's usage scanner otherwise tree-shakes away.
- README: table of when runtime styles are genuinely needed (per Panda docs) and
  the sanctioned mechanism for each (inline CSS var + token(), staticCss,
  getColorValue, variant/states, injectTheme), plus the CSP caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…line CSS var)

Adds a swatch demo: a static class reads `background: var(--swatch)`; only the
single `--swatch` custom property is set inline, pointed at a TOKEN var via
token.var(`colors.${alias}`). Result stays theme/mode-aware (recolors on
switcher flip), unlike getColorValue's raw hex. Comment notes the CSP trade-off
(inline attr still hits style-src; finite sets should use variant()).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Panda tokens are now derived directly from @codecademy/gamut-styles (workspace
dep) instead of hand-made values:
- colors from corePalette; semantic light/dark from coreTheme/adminTheme .modes
- real spacing / fontSize / fontFamily / fontWeight / lineHeight / borderRadii scales
- getColorValue reads the same coreTheme/adminTheme .modes + corePalette (no drift)
- real Apercu/Suisse web fonts via src/fonts.css (Codecademy CDN); page uses fontFamily base
- Background bg now takes real palette tokens (navy-800, etc.)
- removed the hand-made src/tokens.source.ts

Generated CSS carries production values (hyper-500 #3A10E5, navy-800 #10162F,
text-accent/primary-inverse/border-tertiary aliases). typecheck + build green via nx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… invented sizes scale

Button height/minWidth aren't Gamut design tokens — author them as raw values
with Panda's `[value]` escape hatch (e.g. height: '[40px]') inside the recipe,
instead of inventing a sizes token scale. strictTokens still guards colors/
spacing/fontSize (which ARE tokens). Also fixes swatch minWidth '56' (which had
started resolving to Panda's default --sizes-56 = 14rem) to '[56px]'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…vider/ColorMode

Prove the external component API doesn't change:
- Add FillButton/StrokeButton/TextButton/CTAButton/IconButton with the real prop
  surface (variant, size, icon + iconPosition, href, disabled, system props),
  backed by per-atom Panda recipes + a polymorphic ButtonBase (href → <a>).
  IconButton keeps icon/aria-label/tip (ToolTip wrapper omitted in spike).
- Separate provider vs mode per mono's idiom: GamutProvider selects the theme
  (data-panda-theme), a single <ColorMode> sets ambient mode, <Background> handles
  static themed surfaces. ColorMode used once.
- Example page: Button-atoms showcase (incl. icon + anchor href) + FillButton grid.

typecheck + build green; all five gmt-*-button recipes emit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Instead of importing the real Emotion ToolTip / @Codecademy/gamut, rebuild it in
Panda (src/gamut/ToolTip.tsx) — same API (info/placement/alignment/closeOnClick/
id + trigger child), authored with Panda css, hover/focus + aria-describedby +
role=tooltip. IconButton now wraps it and takes tip + tipProps, matching gamut's
prop surface. Keeps the spike self-contained and Emotion-free. Simplified to
top-center placement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…de examples

Add spikes/panda-styling-poc/BREAKING-CHANGES.md: 8 consumer-facing breaking
changes (styled import, styled(css/variant/states) → recipe/props, string token
values + sizes gotcha, css prop, JS color-value reads, provider + static sheet,
theme customization, test matchers) with before/after examples, severity, codemod
notes, and mono/platform impact — plus what does NOT break (~95%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng detail

Add §2a — Gamut's OWN styling migration, from a survey of packages/gamut/src
(109 styled, 69 variant, 42 states, 87 css, 97 system.*, 110 StyleProps, 7
template-literal styled, 3 keyframes, 0 css-prop). Per-pattern Panda mapping +
effort table with real file examples, and before/after for the hard cases:
variant (Badge), keyframes (Shimmer), and the dynamic prop-interpolation +
useCurrentMode gradient (Shimmer). Net: ~90% mechanical; the 🟥 work is the
template-literal styled components + dynamic/useCurrentMode styles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aries

Minimal MF host+remote (both using a Panda-built Gamut Button) validating whether
Module Federation affects the styling engine. Host renders variant=primary; remote
exposes a Widget using variant=danger (host never renders it). Both build via
rspack ModuleFederationPlugin.

Finding: MF doesn't block a Panda swap. It adds two satisfiable requirements —
(1) Gamut ships a COMPLETE static sheet (staticCss all variants), imported once by
the host; proven: host CSS emits gmt-button--variant_danger though host only uses
primary. (2) Gamut a shared singleton across host+remotes (version skew is the
risk). Panda's deterministic content-based class names make it SAFER across MF
than per-build-hashed CSS Modules. Emotion (today) self-styles at runtime but risks
multiple caches across remotes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dreamwasp

Copy link
Copy Markdown
Contributor Author

GMT-1715 spike summary — can Gamut move Emotion → Panda for 1.0?

Short answer: yes, and no blocker was found. Every make-or-break question is validated below. The remaining input is a decision (consumers' RSC roadmap + appetite to migrate in the 1.0 window), not an unknown.

Two spikes on this branch (isolated yarn/nx workspaces under spikes/, Emotion-free):

  • spikes/panda-styling-poc — the styling model, on the real Gamut Core theme + fonts
  • spikes/panda-mf-pocRspack + Module Federation (platform's setup)

What's validated ✅

  • External component API is unchanged. Full Button atom surface reproduced — FillButton/StrokeButton/TextButton/CTAButton/IconButton with variant/size/icon+iconPosition/href/disabled/system props; ToolTip rebuilt in Panda; <Box>/system props/semantic tokens unchanged. The one change consumers make is importing styled from Gamut instead of Emotion.
  • Type-safe tokens — native + stronger (strictTokens + generated ColorToken; bad token/variant fails tsc).
  • Runtime theme + colorMode switching — via data-panda-theme + data-color-mode attribute flips; matches the Storybook switchers (all theme×mode combos pre-generated, zero runtime).
  • Provider model matches mono's idiomGamutProvider selects the theme, one <ColorMode> sets ambient mode, <Background> handles static themed surfaces.
  • Rspack + Module Federation (was the highest-risk unknown) — a real host+remote build proves a host styles a remote's component (a variant the host never renders) via the complete static sheet. Requires: Gamut ships a complete staticCss sheet imported once by the host + Gamut as a shared singleton. Panda is safer across MF than CSS Modules (deterministic class names).

The real costs (documented, not blockers) ⚠️

See spikes/panda-styling-poc/BREAKING-CHANGES.md (8 changes, before/after, severity, mono/platform impact). The two 🟥 high-severity ones:

  1. Dynamic prop-interpolated styled(...) → convert to variant()/states() (codemoddable) or the styledDynamic() escape hatch. Largest surface (mono ~825 files); also internal (§2a: 109 styled / 69 variant / 42 states in gamut/src — ~90% mechanical).
  2. JS reads of resolved color values (charts/canvas) → the getColorValue() escape hatch (mono's 6 @nivo files; platform already hand-rolls this).

Both escape hatches (getColorValue, styledDynamic) + a themeable CSS-var pattern are prototyped in the spike.

Decision docs

~/code/base camp/reboot/styling-engine-rfc.md (options, no rec), reboot-recommendation.md (opinionated: Panda + tsdown), breaking-changes.md, rspack-mf-spike.md. To be moved to Confluence when it's time to socialize.

Still open (a decision, not a spike)

Consumers' RSC roadmap through the ~2028 1.0 runway + appetite to do the migration now. If RSC is off the table and risk appetite is low, "keep Emotion for 1.0 behind the facade" is the honest alternative.

Not for merge — a spike branch to de-risk the decision.

@codecademydev

Copy link
Copy Markdown
Collaborator

📬 Published Alpha Packages:

Package Version npm Diff
@codecademy/gamut 72.3.1-alpha.b5b214.0 npm diff
@codecademy/gamut-icons 9.57.11-alpha.b5b214.0 npm diff
@codecademy/gamut-illustrations 0.58.16-alpha.b5b214.0 npm diff
@codecademy/gamut-kit 3.0.11-alpha.b5b214.0 npm diff
@codecademy/gamut-patterns 0.10.35-alpha.b5b214.0 npm diff
@codecademy/gamut-styles 20.0.3-alpha.b5b214.0 npm diff
@codecademy/gamut-tests 6.0.6-alpha.b5b214.0 npm diff
@codecademy/variance 0.26.2-alpha.b5b214.0 npm diff
eslint-plugin-gamut 2.4.4-alpha.b5b214.0 npm diff

dreamwasp and others added 8 commits August 6, 2026 10:18
The 7 gaps found auditing the spike's Fill/Stroke/Text/Icon/CTA buttons
against the real Gamut source, per reboot/button-api-parity.md:

- focus-ring `::before` pseudo-element, colored per variant, mirroring
  ButtonSelectors.OUTLINE / OUTLINE_FOCUS_VISIBLE
- `aria-disabled` now styled via an overridden `disabled` condition
- CTAButton's hard drop-shadow treatment (grows on hover, flattens on
  active/disabled) plus its `variant` prop
- `aria-hidden` on decorative icons
- corrected IconButton `large` glyph size
- narrowed the `size` prop so it stops leaking Panda's responsive type

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rvives

Answers the gap reboot/panda-via-gamut-option-b.md left open: whether
existing Emotion-authored call sites can keep working while Emotion leaves
the tree entirely. They can.

`styled(C)(css(...), variant(...), states(...))` is preserved exactly,
because `variance` already resolves styles at runtime and Emotion's only
real job was merging the results and injecting a class. `variance` is
reused UNTOUCHED; only that last layer is replaced.

src/gamut/engine/
- props.ts   css/variant/states/system.* rebuilt on the REAL Gamut prop
             config, zero Emotion imports. Also attaches `propNames` so
             `styled` filters state props off the DOM automatically —
             `styledOptions([...])` lists no longer need porting.
- sheet.ts   the injector: deterministic FNV-1a hashing (SSR can't
             mismatch), @layer gamut.consumer for override precedence, the
             focusVisible stylis-plugin behaviour reimplemented, CSP nonce
             support, extractStyles() replacing extractCriticalToChunks.
- styled.tsx composed shape, withComponent (379 uses in mono), and a small
             CSS parser so styled.tag`...` template literals work too.
- theme.tsx  augmentable theme registry replacing the
             `declare module '@emotion/react'` augmentation.

src/proof/
- parity.tsx 21 checks against a call site copied VERBATIM from mono's
             OAuthButtons/elements.tsx, plus every case static extraction
             provably cannot reach (prop functions, theme.x access,
             computed enum keys, ternaries, template literals). 21/21 pass.
             The "zero Emotion" check reads its own bundle rather than
             asserting.
- measure.ts prebuilt atomics vs runtime injector, sized off the real prop
             config: only 47 of 126 props have a closed value space, so the
             hybrid is forced, not chosen. Base atomics 9.2kB gzip;
             +breakpoints 58.4kB; injector ~86B/class, crossing over at
             ~6,000 distinct combinations.

Headline: codemod-feasibility.md's MANUAL (420) and UNSUPPORTED (234)
buckets are artifacts of static extraction, not of the call sites. A
runtime engine evaluates all of them, taking mono from ~654 hand-ported
sites to 0.

Findings: ~/code/base camp/reboot/panda-via-gamut-option-a.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ute PoC

Three additions, closing three open items from
reboot/panda-via-gamut-option-a.md.

1. spikes/styling-perf-poc — APPROXIMATE perf comparison (labelled as such
   throughout). Both arms share the SAME real variance style functions, so
   the delta isolates the injection layer. Emotion vs the engine:
   SSR within 1-4% in both cold and warm phases, across a realistic
   low-cardinality tree AND an adversarial high-cardinality one; the engine
   emits 19-29% LESS CSS because it flattens composed styles into one class
   per component instead of several.

   Two fairness bugs found and fixed while building it: the engine arm was
   doing less work than Emotion (real gamut-styles states/variant don't
   expose propNames, so nothing was filtered off the DOM), and the first
   high-cardinality tree used strides that made all values a function of
   index % 11 — 11 distinct styles wearing a 200-style costume.

   Emotion's SSR CSS is NOT in cache.inserted (values are `true`) — it is
   inlined as <style data-emotion> tags in the markup. Harness extracts
   from there and strips the tags before comparing tree sizes.

2. spikes/panda-mf-poc — extended from the static path to the RUNTIME
   engine. src/federation/verify.cjs loads two independently bundled engine
   copies with one shared React, modelling MF that shares react but not
   gamut (front's real config). 8/8 checks:
     - deterministic hashing => identical class names across copies, so
       duplicated CSS is idempotent, never conflicting
     - React context does NOT cross the boundary: a remote with its own copy
       cannot see the host's ThemeProvider and silently falls back
       => Gamut MUST be a shared singleton. Requirement, not nicety.
     - in a real DOM the copies converge on one <style data-gamut> tag
     - jsdom rejects @layer, so insertRule throws and the appendChild
       fallback runs — which is the path mono's jest suite will take

   The rspack build now shares '@gamut-engine' as a singleton and renders
   engine components in both host and remote.

3. spikes/panda-styling-poc/src/precompute — the answer to "can internal
   authoring stay as-is while Panda runs underneath". It can, by EXECUTING
   the authoring rather than parsing it. authoring.ts keeps every construct
   that defeats a parse-based codemod (templateVariants, computed enum keys,
   transitionConcat() calls, ternaries); generate.ts runs it, enumerates the
   variant/state space, and emits defineRecipe config that Panda turns into
   real static CSS. Verified: .gmt-gen-fill-button--variant_primary and
   friends appear in gamut-static.css.

   Enumeration is only possible because variance closes over its config, so
   props.ts now exposes variantMeta/stateMeta. declValue is shared with the
   runtime injector so the static and runtime paths cannot drift.

   Internal authoring changes required: NONE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answers "can system props stay runtime while consumers still get a
zero-runtime path?" The useful distinction turned out not to be runtime vs
zero-runtime but PER-INSTANCE vs PER-DEFINITION:

  tier 1  inline system props          resolve per render, per instance
  tier 2  styled(...) at module scope  resolve ONCE per theme+variant combo
  tier 3  tier 2 + Panda in the app    resolve at build time, zero JS

Tiers 1 and 2 need zero build configuration; tier 3 is opt-in per app via
Option B. Worked examples of when to reach for each are in the spike README.

Implementation:
- css() is now marked `staticStyle` (reads only the theme), so `styled` can
  tell which style fns are predictable. Every fn predictable => the class is
  a pure function of (theme, declared prop values) and gets memoised. A
  hand-written (props) => ({...}) opts the component out, since it may read
  anything.
- Memo key is built from the props actually present, not by walking the
  126-entry consumed set.

Measured (spikes/styling-perf-poc, still an APPROXIMATION): warm median
0.82x low-cardinality / 0.87x high-cardinality vs Emotion, with identical
class counts. Not a "faster than Emotion" claim — see the spike caveats.

FIXED A REAL SSR BUG this exposed: the memo skips inject(), so the second
SSR response shipped markup whose class names had no CSS behind them. Split
class RESOLUTION (memoisable, process-wide) from rule REGISTRATION (per
response): sheet.ts now tracks `rules` / `inSheet` / `emitted` separately and
styled calls register() on a cache hit. This also corrects the earlier claim
that GamutProvider's `cache` prop becomes a plain no-op — the per-request
concern is real, Gamut just handles it internally now instead of making apps
thread a cache through _document.tsx.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… README

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the missing half of the three-tier docs: WHEN you legitimately need a
runtime value, and what to do about it.

Corrects a bad example I had written. `<Box width={`${pct}%`} />` was listed
as GOOD; it is not. The injector hashes the resolved style object, so a
continuous value emits a NEW CLASS PER VALUE — animating 0->100 leaves 100
rules that are never reused. This is the only way a consumer can make the
runtime path grow without bound, and it is invisible until someone profiles
CSS size.

New measure.ts section C proves the fix: 100 distinct widths ->
  baked into the style object: 100 classes
  via a CSS custom property:     1 class

So the guidance is one pattern for every legitimate runtime case
(state-driven, DOM-measured, data-supplied, animated, arbitrary calc):
STATIC CLASS, DYNAMIC VALUE — the class stays a tier-2 constant and the value
rides an inline `style` custom property. Also SSR-clean, since the class name
is constant and the varying part is a plain HTML attribute.

Also: tier 3 now has real code (the ~12-line panda.config.ts + prebuild
script) rather than only prose, and the two tier-numbering axes in the README
are disambiguated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
I flagged this risk and then asked whether to test it instead of testing it.
Testing it.

`states()` deep-merges every active state in DECLARATION order. Panda emits
one INDEPENDENT class per active boolean variant, so the winner is decided by
STYLESHEET order. A disagreement is a silent visual regression — no error, no
type failure, just a different colour. mono has 62 states() sites.

src/precompute/verify-fidelity.ts authors two states on the same property and
activates both:

  states({ warning: { bg: 'yellow' }, error: { bg: 'red' } })

PASS — both paths resolve to `red`. Panda emits boolean variants in
declaration order, so stylesheet order and merge order coincide.

The test discriminates rather than passing by luck: declaration order is
warning->error while ALPHABETICAL order is error->warning, so had Panda sorted
the variants, `warning` would have landed last and won, giving `yellow` and
failing. Verified via the emitted stylesheet indices (55182 vs 55263), not by
reasoning about it.

This is a property of Panda's emission order, not a guarantee in the authoring
model — hence a regression test rather than an assumption.

Also confirms the variant/states -> defineRecipe mapping through to CSS:
states({ fullWidth: { width: '100%' } }) becomes
.gmt-gen-fill-button--fullWidth_true { inline-size: 100% }

Still untested: three-way overlaps, and overlaps between a variant() and a
states() on the same property. Recorded in the writeup's open items.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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