Skip to content

Conversation

@gaoachao
Copy link
Collaborator

@gaoachao gaoachao commented Sep 16, 2025

Summary by CodeRabbit

  • New Features
    • Added Node.js bindings for the CSS scope transform, enabling usage from JavaScript.
  • Refactor
    • Consolidated shared utilities into a common package used across React transforms.
  • Chores
    • Expanded workspace to include new transform crates and dependencies.
    • Updated import paths to use shared utilities.
    • Added tooling ignores for snapshot files in nested transform test directories.
    • Introduced a placeholder changeset file (no runtime impact).

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@gaoachao gaoachao requested a review from hzy as a code owner September 16, 2025 12:44
@changeset-bot
Copy link

changeset-bot bot commented Sep 16, 2025

🦋 Changeset detected

Latest commit: f59fcae

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

📝 Walkthrough

Walkthrough

Adds two new Rust crates under packages/react/transform/crates (swc_plugin_css_scope and swc_plugins_shared), wires them into the workspace, and relocates N-API bindings for CSS scope into a dedicated module. Updates imports to use the shared utilities crate. Expands ESLint and dprint ignore patterns and adds a placeholder changeset.

Changes

Cohort / File(s) Summary
Workspace and dependencies
Cargo.toml, packages/react/transform/Cargo.toml
Adds packages/react/transform/crates/* to workspace; introduces workspace deps napi/napi-derive; switches local crate to workspace-managed napi deps; adds path deps swc_plugin_css_scope and swc_plugins_shared.
Lint and formatter ignores
eslint.config.js, packages/react/.dprint.jsonc
Extends ignore/exclude globs to cover packages/react/transform/**/tests/__swc_snapshots__/** and transform/**/tests/__swc_snapshots__/**.
Shared utilities crate
packages/react/transform/crates/swc_plugins_shared/Cargo.toml, .../lib.rs
Adds new crate swc_plugins_shared with workspace-managed deps and exposes pub mod utils;.
CSS scope plugin crate and N-API module
packages/react/transform/crates/swc_plugin_css_scope/Cargo.toml, .../lib.rs, .../napi.rs
Introduces swc_plugin_css_scope crate; moves CSS scope N-API bindings into new pub mod napi; removes inline N-API impls from lib; adds N-API enum, config object, conversions, and visitor wrapper.
Import path updates in transform
packages/react/transform/src/lib.rs, .../src/css.rs, .../src/swc_plugin_dynamic_import/mod.rs, .../src/swc_plugin_snapshot/mod.rs
Updates imports to use swc_plugin_css_scope::napi::{...} and swc_plugins_shared::utils::{calc_hash,jsonify}; removes local utils module exposure.
Changeset placeholder
.changeset/fine-places-boil.md
Adds empty front-matter changeset file.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

framework:React

Suggested reviewers

  • PupilTong
  • Yradex

Poem

I hop through crates where plugins grow,
New paths for N-API now neatly flow.
Shared utils sparkle, hashes align,
Snapshots ignored—so tidy, so fine.
With rustling leaves of config’s art,
I thump approval with a coder’s heart. 🐇✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/extract-swc-plugin-css-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "refactor: extract swc_plugin_css_scope" is concise and accurately captures the primary change in the diff — extracting and reorganizing the swc_plugin_css_scope crate (new crate files, workspace/Cargo.toml updates, and related API/module moves). It is a single clear sentence that conveys the main intent for reviewers.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

@gaoachao gaoachao force-pushed the refactor/extract-swc-plugin-css-scope branch from f5b68f0 to 3e43c86 Compare September 16, 2025 12:50
@gaoachao gaoachao force-pushed the refactor/extract-swc-plugin-css-scope branch from f24ecdf to f59fcae Compare September 16, 2025 12:54
@codecov
Copy link

codecov bot commented Sep 16, 2025

Codecov Report

❌ Patch coverage is 0% with 71 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...eact/transform/crates/swc_plugin_css_scope/napi.rs 0.00% 71 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (9)
packages/react/transform/crates/swc_plugins_shared/lib.rs (1)

1-1: Consider re‑exporting stable utilities at the crate root.

Re‑exporting selected helpers (e.g., pub use utils::{calc_hash, jsonify};) reduces import churn and keeps API stable if utils/* reorganizes later.

packages/react/transform/crates/swc_plugin_css_scope/napi.rs (2)

10-15: Use #[napi(string_enum)] to remove unsafe N-API conversions.

The derive handles string mapping and TS types, eliminating manual unsafe impls.

Apply:

-#[derive(Clone, Copy, Debug)]
+#[napi(string_enum)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 pub enum CSSScope {
   All,
   None,
   Modules,
 }
-
-impl napi::bindgen_prelude::FromNapiValue for CSSScope {
-  unsafe fn from_napi_value(
-    env: napi::bindgen_prelude::sys::napi_env,
-    napi_val: napi::bindgen_prelude::sys::napi_value,
-  ) -> napi::bindgen_prelude::Result<Self> {
-    let val = <&str>::from_napi_value(env, napi_val).map_err(|e| {
-      napi::bindgen_prelude::error!(
-        e.status,
-        "Failed to convert napi value into enum `{}`. {}",
-        "CSSScope",
-        e,
-      )
-    })?;
-    match val {
-      "all" => Ok(CSSScope::All),
-      "none" => Ok(CSSScope::None),
-      "modules" => Ok(CSSScope::Modules),
-      _ => Err(napi::bindgen_prelude::error!(
-        napi::bindgen_prelude::Status::InvalidArg,
-        "value `{}` does not match any variant of enum `{}`",
-        val,
-        "CSSScope"
-      )),
-    }
-  }
-}
-
-impl napi::bindgen_prelude::ToNapiValue for CSSScope {
-  unsafe fn to_napi_value(
-    env: napi::bindgen_prelude::sys::napi_env,
-    val: Self,
-  ) -> napi::bindgen_prelude::Result<napi::bindgen_prelude::sys::napi_value> {
-    match val {
-      CSSScope::All => <&str>::to_napi_value(env, "all"),
-      CSSScope::None => <&str>::to_napi_value(env, "none"),
-      CSSScope::Modules => <&str>::to_napi_value(env, "modules"),
-    }
-  }
-}

Also applies to: 17-55


10-15: Nit: derive Eq/PartialEq for CSSScope (useful in tests).

Already adding with the enum refactor above; if you keep manual impls, still consider deriving.

packages/react/transform/crates/swc_plugins_shared/Cargo.toml (1)

9-14: Trim swc_core features to only what's required.

Cargo.toml enables many swc_core features, but this crate only uses swc_core::ecma::ast::* (packages/react/transform/crates/swc_plugins_shared/utils.rs). Remove non‑AST features (ecma_codegen, ecma_minifier, ecma_transforms_typescript, ecma_transforms_react, ecma_transforms_optimization, ecma_quote) from this crate and keep only the minimal features that expose AST types (e.g., base, ecma_utils); re-enable other features only in crates that need them. Verify by running cargo build or cargo tree -e features.

packages/react/transform/crates/swc_plugin_css_scope/lib.rs (4)

70-77: Avoid float-to-int cast for css_id base.

Use an integer literal to prevent inadvertent float conversions.

-        + 1e6 as usize,
+        + 1_000_000usize,

125-146: Compile the CSS filename regex once.

Regex::new(..) per module visit is avoidable; cache it with once_cell::sync::Lazy.

+ use once_cell::sync::Lazy;
+ static CSS_FILE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\.(scss|sass|css|less)$").unwrap());
...
-    let re = Regex::new(r"\.(scss|sass|css|less)$").unwrap();
...
-      if re.is_match(import_decl.src.value.to_string().as_str()) {
+      if CSS_FILE_RE.is_match(import_decl.src.value.to_string().as_str()) {

128-131: Fix misleading comment.

Condition specifiers.is_empty() means side‑effects import; current comment says named/default/namespace.

-      if matches!(self.cfg.mode, CSSScope::Modules) && import_decl.specifiers.is_empty() {
-        // Is named/default/namespace import, nothing to do
+      if matches!(self.cfg.mode, CSSScope::Modules) && import_decl.specifiers.is_empty() {
+        // Side-effects import in Modules mode: skip (not treated as CSS Module)
         continue;
       }

148-157: Ensure comments is always provided when this pass is used.

add_leading is called unconditionally when has_css_import; if comments was None, this would panic. Current call sites pass Some(&comments), but this contract should be documented at the constructor or guarded.

Add an assert in new:

 pub fn new(cfg: CSSScopeVisitorConfig, comments: Option<C>) -> Self {
   CSSScopeVisitor {
+    // Safety: downstream relies on comments for emitting @jsxCSSId
+    // assert!(comments.is_some(), "CSSScopeVisitor requires comments");
packages/react/transform/src/swc_plugin_snapshot/mod.rs (1)

26-27: Unify worklet hash to shared calc_hash (use 5 hex chars)

swc_plugins_shared::utils::calc_hash returns 5 hex chars while packages/react/transform/src/swc_plugin_worklet/hash.rs implements a local calc_hash that returns 4 — this causes inconsistent IDs; switch the worklet file to reuse the shared implementation.

Affected locations:

  • packages/react/transform/src/swc_plugin_worklet/hash.rs (local fn calc_hash, ~line 22)
  • packages/react/transform/crates/swc_plugins_shared/utils.rs (pub fn calc_hash, ~line 63)

Proposed change (apply in packages/react/transform/src/swc_plugin_worklet/hash.rs):

- use sha1::{Digest, Sha1};
- use hex;
+ use swc_plugins_shared::utils::calc_hash;

- fn calc_hash(s: &str) -> String {
-     let mut hasher = Sha1::new();
-     hasher.update(s.as_bytes());
-     let sum = hasher.finalize();
-
-     hex::encode(sum)[0..4].to_string()
- }
+ // Reuse shared implementation for consistency (5 chars).
+ // pub(crate) use calc_hash from swc_plugins_shared
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b741a0 and f59fcae.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .changeset/fine-places-boil.md (1 hunks)
  • Cargo.toml (2 hunks)
  • eslint.config.js (1 hunks)
  • packages/react/.dprint.jsonc (1 hunks)
  • packages/react/transform/Cargo.toml (1 hunks)
  • packages/react/transform/crates/swc_plugin_css_scope/Cargo.toml (1 hunks)
  • packages/react/transform/crates/swc_plugin_css_scope/lib.rs (1 hunks)
  • packages/react/transform/crates/swc_plugin_css_scope/napi.rs (1 hunks)
  • packages/react/transform/crates/swc_plugins_shared/Cargo.toml (1 hunks)
  • packages/react/transform/crates/swc_plugins_shared/lib.rs (1 hunks)
  • packages/react/transform/src/css.rs (1 hunks)
  • packages/react/transform/src/lib.rs (2 hunks)
  • packages/react/transform/src/swc_plugin_dynamic_import/mod.rs (1 hunks)
  • packages/react/transform/src/swc_plugin_snapshot/mod.rs (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
.changeset/*.md

📄 CodeRabbit inference engine (AGENTS.md)

For contributions, always generate a changeset and commit the resulting markdown file(s)

Files:

  • .changeset/fine-places-boil.md
🧠 Learnings (5)
📓 Common learnings
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1714
File: packages/react/transform/Cargo.toml:19-19
Timestamp: 2025-09-10T11:42:36.855Z
Learning: In packages/react/transform/Cargo.toml, the crate uses serde derive macros (#[derive(Serialize, Deserialize)]) in multiple files including src/esbuild.rs and src/swc_plugin_extract_str/mod.rs, so the "derive" feature is required when migrating to workspace dependencies.
📚 Learning: 2025-09-10T11:42:36.855Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1714
File: packages/react/transform/Cargo.toml:19-19
Timestamp: 2025-09-10T11:42:36.855Z
Learning: In packages/react/transform/Cargo.toml, the crate uses serde derive macros (#[derive(Serialize, Deserialize)]) in multiple files including src/esbuild.rs and src/swc_plugin_extract_str/mod.rs, so the "derive" feature is required when migrating to workspace dependencies.

Applied to files:

  • packages/react/transform/crates/swc_plugins_shared/Cargo.toml
  • packages/react/transform/crates/swc_plugin_css_scope/Cargo.toml
  • Cargo.toml
  • packages/react/transform/Cargo.toml
📚 Learning: 2025-09-12T09:43:04.810Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, empty changeset files (containing only `---\n\n---`) are used for internal changes that modify src/** files but don't require meaningful release notes, such as private package changes or testing-only modifications. This satisfies CI requirements without generating user-facing release notes.

Applied to files:

  • .changeset/fine-places-boil.md
📚 Learning: 2025-08-06T13:28:57.182Z
Learnt from: colinaaa
PR: lynx-family/lynx-stack#1453
File: vitest.config.ts:49-61
Timestamp: 2025-08-06T13:28:57.182Z
Learning: In the lynx-family/lynx-stack repository, the file `packages/react/testing-library/src/vitest.config.js` is source code for the testing library that gets exported for users, not a test configuration that should be included in the main vitest projects array.

Applied to files:

  • eslint.config.js
📚 Learning: 2025-09-12T09:43:04.810Z
Learnt from: gaoachao
PR: lynx-family/lynx-stack#1736
File: .changeset/spotty-experts-smoke.md:1-3
Timestamp: 2025-09-12T09:43:04.810Z
Learning: In the lynx-family/lynx-stack repository, private packages (marked with "private": true in package.json) like lynx-js/react-transform don't require meaningful changeset entries even when their public APIs change, since they are not published externally and only affect internal development.

Applied to files:

  • packages/react/.dprint.jsonc
🧬 Code graph analysis (6)
packages/react/transform/src/css.rs (1)
packages/react/transform/crates/swc_plugins_shared/utils.rs (1)
  • jsonify (6-61)
packages/react/transform/src/swc_plugin_dynamic_import/mod.rs (1)
packages/react/transform/crates/swc_plugins_shared/utils.rs (1)
  • jsonify (6-61)
packages/react/transform/src/lib.rs (2)
packages/react/transform/crates/swc_plugins_shared/utils.rs (1)
  • calc_hash (63-69)
packages/react/transform/src/swc_plugin_worklet/hash.rs (1)
  • calc_hash (22-28)
packages/react/transform/crates/swc_plugin_css_scope/napi.rs (2)
packages/react/transform/src/lib.rs (11)
  • from_napi_value (87-110)
  • from_napi_value (148-161)
  • from_napi_value (192-222)
  • to_napi_value (114-124)
  • to_napi_value (165-179)
  • to_napi_value (226-235)
  • from (131-133)
  • from (186-188)
  • default (137-144)
  • default (273-295)
  • new (315-317)
packages/react/transform/crates/swc_plugin_css_scope/lib.rs (5)
  • default (45-50)
  • new (70-81)
  • visit_mut_expr (88-96)
  • n (111-121)
  • visit_mut_module (98-158)
packages/react/transform/src/swc_plugin_snapshot/mod.rs (2)
packages/react/transform/src/css.rs (1)
  • get_string_inline_style_from_literal (14-64)
packages/react/transform/crates/swc_plugins_shared/utils.rs (1)
  • calc_hash (63-69)
packages/react/transform/crates/swc_plugin_css_scope/lib.rs (1)
packages/react/transform/crates/swc_plugins_shared/utils.rs (1)
  • calc_hash (63-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build / Build (Ubuntu)
  • GitHub Check: build / Build (Windows)
  • GitHub Check: test-rust / Test (Ubuntu)
🔇 Additional comments (14)
.changeset/fine-places-boil.md (1)

1-3: Placeholder changeset is acceptable for internal refactors.

Matches repo practice for non-user‑facing changes. Please tick the “changeset added” checkbox in the PR description to keep process signals consistent.

eslint.config.js (1)

50-51: LGTM: expanded snapshot ignores.

Keeps ESLint away from nested swc_snapshots under transform/**.

packages/react/.dprint.jsonc (1)

16-16: LGTM: align formatter excludes with ESLint.

Avoids formatting churn in nested SWC snapshots.

packages/react/transform/crates/swc_plugin_css_scope/napi.rs (2)

77-86: Config N-API shape looks correct.

ts_type on mode yields the intended union type in d.ts; filename as String is fine.


122-131: Thin wrapper delegating to CoreVisitor is clean.

Good separation of N-API surface from core logic.

Cargo.toml (1)

6-7: Workspace wiring OK — napi4 compatible with CI Node targets

  • Adding members under crates/* is fine.
  • CI workflows target Node 22 and 24; Node‑API v4 is supported from Node.js v10.16.0 onward, so those CI targets are compatible. (nodejs.org)
packages/react/transform/crates/swc_plugin_css_scope/Cargo.toml (1)

9-14: Reconsider/remove internal swc_core features: __visit & __testing_transform

rg only finds these in the crate's Cargo.toml (packages/react/transform/crates/swc_plugin_css_scope/Cargo.toml:13); no references in the crate source. Drop them unless required by macros/build-scripts or transitive usage — verify by removing and running a local cargo check or tools like cargo-shear / cargo-udeps.

packages/react/transform/src/css.rs (1)

10-10: LGTM: switched to shared jsonify.

Matches the shared crate API; no functional change.

packages/react/transform/src/lib.rs (3)

65-65: LGTM: CSS Scope imports now come from the new crate module.

swc_plugin_css_scope::napi::{CSSScopeVisitor, CSSScopeVisitorConfig} matches the extracted layout.


75-75: LGTM: centralized calc_hash.

Consistent with other modules pulling from swc_plugins_shared.


238-270: Public N-API surface: css_scope TypeScript typing present.
Confirmed packages/react/transform/crates/swc_plugin_css_scope/napi.rs includes #[napi(ts_type = "'all' | 'none' | 'modules'")] on CSSScopeVisitorConfig.mode — no breaking API change.

packages/react/transform/crates/swc_plugin_css_scope/lib.rs (1)

12-15: Good split: shared calc_hash and pub mod napi.

Separation of N-API glue is clean and aligns with the new shared utils crate.

packages/react/transform/src/swc_plugin_dynamic_import/mod.rs (1)

19-19: Move to shared utils — verified

rg shows only swc_plugins_shared::utils::jsonify imports at packages/react/transform/src/css.rs:10 and packages/react/transform/src/swc_plugin_dynamic_import/mod.rs:19; no crate::utils::jsonify remains.

packages/react/transform/Cargo.toml (1)

14-15: Approve — workspace deps & new crates wired correctly.

  • Verified: transform/Cargo.toml uses napi = { workspace = true } and napi-derive = { workspace = true }.
  • Found crates swc_plugin_css_scope and swc_plugins_shared.
  • swc_plugins_shared/utils.rs exposes pub fn jsonify and pub fn calc_hash.
  • packages/react/transform/Cargo.toml keeps serde = { workspace = true, features = ["derive"] }.

@relativeci
Copy link

relativeci bot commented Sep 17, 2025

Web Explorer

#5255 Bundle Size — 365.07KiB (-0.03%).

f59fcae(current) vs 75693e4 main#5246(baseline)

Bundle metrics  Change 2 changes
                 Current
#5255
     Baseline
#5246
No change  Initial JS 145.65KiB 145.65KiB
No change  Initial CSS 31.89KiB 31.89KiB
Change  Cache Invalidation 16.35% 16.33%
No change  Chunks 8 8
No change  Assets 8 8
Change  Modules 220(+1.38%) 217
No change  Duplicate Modules 16 16
No change  Duplicate Code 3.37% 3.37%
No change  Packages 4 4
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Improvement 1 improvement
                 Current
#5255
     Baseline
#5246
Improvement  JS 239.16KiB (-0.04%) 239.25KiB
No change  Other 94.02KiB 94.02KiB
No change  CSS 31.89KiB 31.89KiB

Bundle analysis reportBranch refactor/extract-swc-plugin-css-...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link

relativeci bot commented Sep 17, 2025

React Example

#5262 Bundle Size — 237.52KiB (0%).

f59fcae(current) vs 75693e4 main#5253(baseline)

Bundle metrics  no changes
                 Current
#5262
     Baseline
#5253
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 162 162
No change  Duplicate Modules 65 65
No change  Duplicate Code 46.71% 46.71%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#5262
     Baseline
#5253
No change  IMG 145.76KiB 145.76KiB
No change  Other 91.76KiB 91.76KiB

Bundle analysis reportBranch refactor/extract-swc-plugin-css-...Project dashboard


Generated by RelativeCIDocumentationReport issue

@codspeed-hq
Copy link

codspeed-hq bot commented Sep 17, 2025

CodSpeed Performance Report

Merging #1762 will degrade performances by 7.96%

Comparing refactor/extract-swc-plugin-css-scope (f59fcae) with main (75693e4)1

Summary

⚡ 1 improvement
❌ 2 regressions
✅ 53 untouched

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Benchmark BASE HEAD Change
transform 1000 effects 30.4 ms 33.1 ms -7.96%
transform 1000 view elements 46.6 ms 39.7 ms +17.45%
basic-performance-small-css 6.9 ms 7.5 ms -7.93%

Footnotes

  1. No successful run was found on main (8b741a0) during the generation of this report, so 75693e4 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Collaborator

@colinaaa colinaaa left a comment

Choose a reason for hiding this comment

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

👍

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.

4 participants