From 9e7269e4fcdf5605ed9ce3511aba64947335c5c5 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 19 Dec 2025 01:46:34 +0000 Subject: [PATCH] Merge jqhtml class naming rules, add BEM child class check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../JqhtmlClassNaming_CodeQualityRule.php | 416 ++++++++++++++++++ .../JqhtmlComponentNaming_CodeQualityRule.php | 171 ------- .../JqhtmlRedundantClass_CodeQualityRule.php | 247 ----------- app/RSpade/man/scss.txt | 34 ++ docs/CLAUDE.dist.md | 2 + 5 files changed, 452 insertions(+), 418 deletions(-) create mode 100755 app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlClassNaming_CodeQualityRule.php delete mode 100755 app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlComponentNaming_CodeQualityRule.php delete mode 100755 app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlRedundantClass_CodeQualityRule.php diff --git a/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlClassNaming_CodeQualityRule.php b/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlClassNaming_CodeQualityRule.php new file mode 100755 index 000000000..e2e9adda9 --- /dev/null +++ b/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlClassNaming_CodeQualityRule.php @@ -0,0 +1,416 @@ +: + * - SCSS: .My_Component { &__element { ... } } compiles to .My_Component__element + * - HTML must use: class="My_Component__element" + * - NOT: class="my-component__element" (styles won't apply) + */ +class JqhtmlClassNaming_CodeQualityRule extends CodeQualityRule_Abstract +{ + /** + * Get the unique identifier for this rule + */ + public function get_id(): string + { + return 'JQHTML-CLASS-01'; + } + + /** + * Get the human-readable name of this rule + */ + public function get_name(): string + { + return 'JQHTML Class Naming'; + } + + /** + * Get the description of what this rule checks + */ + public function get_description(): string + { + return 'Validates component naming, redundant class attributes, and BEM child class naming in jqhtml templates'; + } + + /** + * Get file patterns this rule should check + */ + public function get_file_patterns(): array + { + return ['*.jqhtml', '*.js']; + } + + /** + * This rule runs during manifest scan for immediate feedback + */ + public function is_called_during_manifest_scan(): bool + { + return true; + } + + /** + * Check the file for violations + */ + public function check(string $file_path, string $contents, array $metadata = []): void + { + if (str_ends_with($file_path, '.jqhtml')) { + $this->check_jqhtml_file($file_path, $contents); + } + + if (str_ends_with($file_path, '.js')) { + $this->check_javascript_file($file_path, $contents, $metadata); + } + } + + /** + * Check jqhtml template files - single pass for all checks + */ + private function check_jqhtml_file(string $file_path, string $contents): void + { + $lines = explode("\n", $contents); + + // First, find the component name from the Define tag + $component_name = null; + $component_line_number = null; + + foreach ($lines as $idx => $line) { + // Match or + if (preg_match('/to_kebab_case($component_name); + $underscore_lower = strtolower($component_name); + + // Check 1: Component name must start with uppercase + if (!ctype_upper($component_name[0])) { + $this->add_violation( + $file_path, + $component_line_number, + "JQHTML component name '{$component_name}' must start with an uppercase letter", + trim($lines[$component_line_number - 1]), + "Change '{$component_name}' to '" . ucfirst($component_name) . "'. " . + "This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", + 'critical' + ); + } + + // Now scan all lines for class attribute issues + $line_number = 0; + foreach ($lines as $line) { + $line_number++; + + // Check 2: Redundant class on Define tag + if (preg_match('/]*class=["\']([^"\']*)["\']/', $line, $matches)) { + $class_attribute = $matches[1]; + $this->check_redundant_define_class($file_path, $line_number, $line, $component_name, $class_attribute, $kebab_case); + } + + // Check 3: BEM child classes using wrong case + // Find all class="..." attributes in this line + if (preg_match_all('/class=["\']([^"\']*)["\']/', $line, $all_class_matches)) { + foreach ($all_class_matches[1] as $class_attribute) { + $this->check_bem_child_classes($file_path, $line_number, $line, $component_name, $class_attribute, $kebab_case, $underscore_lower); + } + } + } + } + + /** + * Check for redundant class names on the Define tag + */ + private function check_redundant_define_class( + string $file_path, + int $line_number, + string $line, + string $component_name, + string $class_attribute, + string $kebab_case + ): void { + $classes = preg_split('/\s+/', trim($class_attribute)); + + foreach ($classes as $class) { + $class = trim($class); + if (empty($class)) { + continue; + } + + // Exact match with component name + if ($class === $component_name) { + $this->add_violation( + $file_path, + $line_number, + "Redundant class=\"{$component_name}\" on component {$component_name}. " . + "The component automatically gets class=\"{$component_name}\" assigned to it.", + trim($line), + $this->build_redundant_class_suggestion($component_name, $class_attribute, $class), + 'medium' + ); + } + // Kebab-case equivalent (only for multi-word components) + elseif (strpos($component_name, '_') !== false + && strtolower($class) === $kebab_case + && !$this->is_known_framework_class($class)) { + $this->add_violation( + $file_path, + $line_number, + "Unnecessary class=\"{$class}\" on component {$component_name}. " . + "The component automatically gets class=\"{$component_name}\" assigned to it. " . + "Use .{$component_name} in CSS/JS selectors instead of .{$class}", + trim($line), + $this->build_redundant_class_suggestion($component_name, $class_attribute, $class), + 'medium' + ); + } + } + } + + /** + * Check for BEM child classes using wrong case prefix + * + * Detects patterns like: + * my-component__element (should be My_Component__element) + * my-component--modifier (should be My_Component--modifier) + * my_component__element (should be My_Component__element) + */ + private function check_bem_child_classes( + string $file_path, + int $line_number, + string $line, + string $component_name, + string $class_attribute, + string $kebab_case, + string $underscore_lower + ): void { + // Only check multi-word component names (with underscores) + if (strpos($component_name, '_') === false) { + return; + } + + $classes = preg_split('/\s+/', trim($class_attribute)); + + foreach ($classes as $class) { + $class = trim($class); + if (empty($class)) { + continue; + } + + // Skip if class correctly starts with component name (PascalCase) + if (strpos($class, $component_name . '__') === 0 || strpos($class, $component_name . '--') === 0) { + continue; + } + + // Check for kebab-case prefix with BEM separator (__ or --) + // Pattern: my-component__something or my-component--something + if (preg_match('/^' . preg_quote($kebab_case, '/') . '(__|--)(.+)$/', $class, $matches)) { + $separator = $matches[1]; + $suffix = $matches[2]; + $correct_class = $component_name . $separator . $suffix; + + $this->add_violation( + $file_path, + $line_number, + "BEM class \"{$class}\" uses kebab-case prefix. " . + "SCSS compiles .{$component_name} { &{$separator}{$suffix} } to .{$correct_class}, so HTML must match.", + trim($line), + $this->build_bem_suggestion($component_name, $class, $correct_class), + 'high' + ); + continue; + } + + // Check for lowercase underscore prefix with BEM separator + // Pattern: my_component__something or my_component--something + if (preg_match('/^' . preg_quote($underscore_lower, '/') . '(__|--)(.+)$/', $class, $matches)) { + $separator = $matches[1]; + $suffix = $matches[2]; + $correct_class = $component_name . $separator . $suffix; + + $this->add_violation( + $file_path, + $line_number, + "BEM class \"{$class}\" uses lowercase prefix. " . + "SCSS compiles .{$component_name} { &{$separator}{$suffix} } to .{$correct_class}, so HTML must match.", + trim($line), + $this->build_bem_suggestion($component_name, $class, $correct_class), + 'high' + ); + } + } + } + + /** + * Check JavaScript files for Component subclasses + */ + private function check_javascript_file(string $file_path, string $contents, array $metadata = []): void + { + $lines = explode("\n", $contents); + + // Get JavaScript class from manifest metadata + $js_classes = []; + if (isset($metadata['class']) && isset($metadata['extension']) && $metadata['extension'] === 'js') { + $js_classes = [$metadata['class']]; + } + + // Check class definitions from metadata + if (!empty($js_classes)) { + $class_definitions = []; + foreach ($js_classes as $class_name) { + foreach ($lines as $idx => $line) { + if (preg_match('/class\s+' . preg_quote($class_name, '/') . '\s+/', $line)) { + $class_definitions[$class_name] = $idx + 1; + break; + } + } + } + + foreach ($class_definitions as $class_name => $line_num) { + if (Manifest::js_is_subclass_of($class_name, 'Component')) { + if (!ctype_upper($class_name[0])) { + $this->add_violation( + $file_path, + $line_num, + "JQHTML component class '{$class_name}' must start with an uppercase letter", + trim($lines[$line_num - 1]), + "Change '{$class_name}' to '" . ucfirst($class_name) . "'. " . + "This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", + 'critical' + ); + } + } + } + } + + // Check for component registration patterns + $line_number = 0; + foreach ($lines as $line) { + $line_number++; + + if (preg_match('/jqhtml\.component\([\'"]([a-zA-Z_][a-zA-Z0-9_]*)[\'"]/', $line, $matches)) { + $component_name = $matches[1]; + + if (!ctype_upper($component_name[0])) { + $this->add_violation( + $file_path, + $line_number, + "JQHTML component registration '{$component_name}' must use uppercase name", + trim($line), + "Change '{$component_name}' to '" . ucfirst($component_name) . "'. " . + "This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", + 'critical' + ); + } + } + } + } + + /** + * Convert PascalCase_With_Underscores to kebab-case + */ + private function to_kebab_case(string $component_name): string + { + return strtolower(str_replace('_', '-', $component_name)); + } + + /** + * Check if a class name is a known CSS framework class + */ + private function is_known_framework_class(string $class): bool + { + $bootstrap_prefixes = [ + 'card-', 'btn-', 'nav-', 'navbar-', 'form-', 'input-', 'list-', + 'table-', 'modal-', 'alert-', 'badge-', 'dropdown-', 'breadcrumb-', + 'pagination-', 'progress-', 'spinner-', 'toast-', 'tooltip-', + 'popover-', 'carousel-', 'accordion-', 'offcanvas-', 'placeholder-', + ]; + + foreach ($bootstrap_prefixes as $prefix) { + if (strpos($class, $prefix) === 0) { + return true; + } + } + + return false; + } + + /** + * Build suggestion for redundant class + */ + private function build_redundant_class_suggestion(string $component_name, string $class_attribute, string $redundant_class): string + { + $cleaned = $this->remove_class_from_attribute($class_attribute, $redundant_class); + + $lines = []; + $lines[] = "Remove '{$redundant_class}' from the class attribute. Component names are"; + $lines[] = "automatically added to the rendered element's class list by the jqhtml framework."; + $lines[] = ""; + $lines[] = "CURRENT:"; + $lines[] = " "; + $lines[] = ""; + if (empty($cleaned)) { + $lines[] = "CORRECTED:"; + $lines[] = " "; + } else { + $lines[] = "CORRECTED:"; + $lines[] = " "; + } + + return implode("\n", $lines); + } + + /** + * Build suggestion for BEM class naming + */ + private function build_bem_suggestion(string $component_name, string $wrong_class, string $correct_class): string + { + $lines = []; + $lines[] = "BEM child element classes must use the component's exact PascalCase name as prefix."; + $lines[] = ""; + $lines[] = "SCSS nesting like:"; + $lines[] = " .{$component_name} {"; + $lines[] = " &__element { ... }"; + $lines[] = " }"; + $lines[] = ""; + $lines[] = "Compiles to: .{$component_name}__element"; + $lines[] = ""; + $lines[] = "So HTML must match:"; + $lines[] = " WRONG: class=\"{$wrong_class}\""; + $lines[] = " CORRECT: class=\"{$correct_class}\""; + $lines[] = ""; + $lines[] = "This is a common mistake when following general web conventions where BEM uses"; + $lines[] = "kebab-case. In RSX, component class names are PascalCase, so BEM children must also be."; + + return implode("\n", $lines); + } + + /** + * Remove a class from the class attribute string + */ + private function remove_class_from_attribute(string $class_attribute, string $class_to_remove): string + { + $cleaned = preg_replace('/\b' . preg_quote($class_to_remove, '/') . '\b\s*/', '', $class_attribute); + $cleaned = preg_replace('/\s+/', ' ', $cleaned); + return trim($cleaned); + } +} diff --git a/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlComponentNaming_CodeQualityRule.php b/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlComponentNaming_CodeQualityRule.php deleted file mode 100755 index f21bb2318..000000000 --- a/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlComponentNaming_CodeQualityRule.php +++ /dev/null @@ -1,171 +0,0 @@ -check_jqhtml_file($file_path, $contents); - } - - // Check .js files for classes extending Component - if (str_ends_with($file_path, '.js')) { - $this->check_javascript_file($file_path, $contents, $metadata); - } - } - - /** - * Check jqhtml template files - */ - private function check_jqhtml_file(string $file_path, string $contents): void - { - $lines = explode("\n", $contents); - $line_number = 0; - - foreach ($lines as $line) { - $line_number++; - - // Look for tags - if (preg_match('//', $line, $matches)) { - $component_name = $matches[1]; - - // Check if first character is not uppercase - if (!ctype_upper($component_name[0])) { - $this->add_violation( - $file_path, - $line_number, - "JQHTML component name '{$component_name}' must start with an uppercase letter", - trim($line), - "Change '{$component_name}' to '" . ucfirst($component_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", - 'critical' - ); - } - } - } - } - - /** - * Check JavaScript files for Component subclasses - */ - private function check_javascript_file(string $file_path, string $contents, array $metadata = []): void - { - $lines = explode("\n", $contents); - $line_number = 0; - - // Get JavaScript class from manifest metadata - $js_classes = []; - if (isset($metadata['class']) && isset($metadata['extension']) && $metadata['extension'] === 'js') { - $js_classes = [$metadata['class']]; - } - - // If no classes in metadata, nothing to check for class definitions - if (!empty($js_classes)) { - // Find line numbers for each class - $class_definitions = []; - foreach ($js_classes as $class_name) { - // Find where this class is defined in the source - foreach ($lines as $idx => $line) { - if (preg_match('/class\s+' . preg_quote($class_name, '/') . '\s+/', $line)) { - $class_definitions[$class_name] = $idx + 1; - break; - } - } - } - - // Check each class to see if it's a JQHTML component - foreach ($class_definitions as $class_name => $line_num) { - // Use Manifest to check if this is a JQHTML component (handles indirect inheritance) - if (Manifest::js_is_subclass_of($class_name, 'Component')) { - // Check if first character is not uppercase - if (!ctype_upper($class_name[0])) { - $this->add_violation( - $file_path, - $line_num, - "JQHTML component class '{$class_name}' must start with an uppercase letter", - trim($lines[$line_num - 1]), - "Change '{$class_name}' to '" . ucfirst($class_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", - 'critical' - ); - } - } - } - } - - // Still check for component registration patterns - foreach ($lines as $line) { - $line_number++; - - // Also check for component registration patterns - if (preg_match('/jqhtml\.component\([\'"]([a-zA-Z_][a-zA-Z0-9_]*)[\'"]/', $line, $matches)) { - $component_name = $matches[1]; - - if (!ctype_upper($component_name[0])) { - $this->add_violation( - $file_path, - $line_number, - "JQHTML component registration '{$component_name}' must use uppercase name", - trim($line), - "Change '{$component_name}' to '" . ucfirst($component_name) . "'. This is a hard requirement of the jqhtml library - component names MUST start with an uppercase letter.", - 'critical' - ); - } - } - } - } -} diff --git a/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlRedundantClass_CodeQualityRule.php b/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlRedundantClass_CodeQualityRule.php deleted file mode 100755 index 65222eaf6..000000000 --- a/app/RSpade/CodeQuality/Rules/Jqhtml/JqhtmlRedundantClass_CodeQualityRule.php +++ /dev/null @@ -1,247 +0,0 @@ - // Exact match - * // Kebab-case equivalent - */ -class JqhtmlRedundantClass_CodeQualityRule extends CodeQualityRule_Abstract -{ - /** - * Get the unique identifier for this rule - */ - public function get_id(): string - { - return 'JQHTML-CLASS-01'; - } - - /** - * Get the human-readable name of this rule - */ - public function get_name(): string - { - return 'JQHTML Redundant Component Class Name'; - } - - /** - * Get the description of what this rule checks - */ - public function get_description(): string - { - return 'Detects when component class name (or kebab-case equivalent) is redundantly specified in class attribute'; - } - - /** - * Get file patterns this rule should check - */ - public function get_file_patterns(): array - { - return ['*.jqhtml']; - } - - /** - * This rule runs during manifest scan for immediate feedback - */ - public function is_called_during_manifest_scan(): bool - { - return true; - } - - /** - * Get the default severity level - */ - public function get_default_severity(): string - { - return 'medium'; - } - - /** - * Check the file for violations - */ - public function check(string $file_path, string $contents, array $metadata = []): void - { - $lines = explode("\n", $contents); - $line_number = 0; - - foreach ($lines as $line) { - $line_number++; - - // Look for patterns - // Match Define tag with component name and class attribute on same line - if (preg_match('/]*class=["\']([^"\']*)["\']/', $line, $matches)) { - $component_name = $matches[1]; - $class_attribute = $matches[2]; - $kebab_case = $this->to_kebab_case($component_name); - - // Split class attribute into individual classes - $classes = preg_split('/\s+/', trim($class_attribute)); - - foreach ($classes as $class) { - $class = trim($class); - if (empty($class)) { - continue; - } - - // Check for exact match with component name - if ($class === $component_name) { - $this->add_violation( - $file_path, - $line_number, - "Redundant class=\"{$component_name}\" on component {$component_name}. " . - "The component automatically gets class=\"{$component_name}\" assigned to it.", - trim($line), - $this->build_exact_match_suggestion($component_name, $class_attribute), - 'medium' - ); - } - // Check for kebab-case equivalent (case-insensitive) - // Only flag if component name has underscores (multi-word), since single-word - // lowercase classes like "card" could be legitimate external CSS framework classes - // Also skip known CSS framework class prefixes (Bootstrap, etc.) - elseif (strpos($component_name, '_') !== false - && strtolower($class) === $kebab_case - && !$this->is_known_framework_class($class)) { - $this->add_violation( - $file_path, - $line_number, - "Unnecessary class=\"{$class}\" on component {$component_name}. " . - "The component automatically gets class=\"{$component_name}\" assigned to it. " . - "Use .{$component_name} in CSS/JS selectors instead of .{$class}", - trim($line), - $this->build_kebab_case_suggestion($component_name, $class), - 'medium' - ); - } - } - } - } - } - - /** - * Convert PascalCase_With_Underscores to kebab-case - * - * My_Component -> my-component - * Breadcrumb_Nav -> breadcrumb-nav - */ - private function to_kebab_case(string $component_name): string - { - return strtolower(str_replace('_', '-', $component_name)); - } - - /** - * Check if a class name is a known CSS framework class - * - * These are legitimate uses of kebab-case classes from external frameworks - * like Bootstrap that happen to match component names. - */ - private function is_known_framework_class(string $class): bool - { - // Bootstrap class prefixes that are commonly used - $bootstrap_prefixes = [ - 'card-', - 'btn-', - 'nav-', - 'navbar-', - 'form-', - 'input-', - 'list-', - 'table-', - 'modal-', - 'alert-', - 'badge-', - 'dropdown-', - 'breadcrumb-', - 'pagination-', - 'progress-', - 'spinner-', - 'toast-', - 'tooltip-', - 'popover-', - 'carousel-', - 'accordion-', - 'offcanvas-', - 'placeholder-', - ]; - - foreach ($bootstrap_prefixes as $prefix) { - if (strpos($class, $prefix) === 0) { - return true; - } - } - - return false; - } - - /** - * Build suggestion for exact component name match - */ - private function build_exact_match_suggestion(string $component_name, string $class_attribute): string - { - $cleaned = $this->remove_class_from_attribute($class_attribute, $component_name); - - $lines = []; - $lines[] = "Remove '{$component_name}' from the class attribute. Component names are"; - $lines[] = "automatically added to the rendered element's class list by the jqhtml framework."; - $lines[] = ""; - $lines[] = "CURRENT:"; - $lines[] = " "; - $lines[] = ""; - if (empty($cleaned)) { - $lines[] = "CORRECTED:"; - $lines[] = " "; - } else { - $lines[] = "CORRECTED:"; - $lines[] = " "; - } - - return implode("\n", $lines); - } - - /** - * Build suggestion for kebab-case equivalent - */ - private function build_kebab_case_suggestion(string $component_name, string $redundant_class): string - { - $lines = []; - $lines[] = "The class \"{$redundant_class}\" is redundant because jqhtml components"; - $lines[] = "automatically receive their component name as a CSS class."; - $lines[] = ""; - $lines[] = "When you write:"; - $lines[] = " "; - $lines[] = ""; - $lines[] = "The rendered element becomes:"; - $lines[] = "
"; - $lines[] = ""; - $lines[] = "TO FIX:"; - $lines[] = " 1. Remove the redundant class from the Define tag:"; - $lines[] = " "; - $lines[] = ""; - $lines[] = " 2. Update any CSS/SCSS selectors to use the component class:"; - $lines[] = " .{$component_name} { ... } // Instead of .{$redundant_class}"; - $lines[] = ""; - $lines[] = " 3. Update any JavaScript selectors:"; - $lines[] = " \$('.{$component_name}') // Instead of \$('.{$redundant_class}')"; - - return implode("\n", $lines); - } - - /** - * Remove a class from the class attribute string - */ - private function remove_class_from_attribute(string $class_attribute, string $class_to_remove): string - { - $cleaned = preg_replace('/\b' . preg_quote($class_to_remove, '/') . '\b\s*/', '', $class_attribute); - $cleaned = preg_replace('/\s+/', ' ', $cleaned); - return trim($cleaned); - } -} diff --git a/app/RSpade/man/scss.txt b/app/RSpade/man/scss.txt index aa1aecde7..add898b58 100755 --- a/app/RSpade/man/scss.txt +++ b/app/RSpade/man/scss.txt @@ -285,6 +285,40 @@ HOW IT WORKS 2. It matches a valid Component subclass or Blade @rsx_id 3. The filename matches the associated file +BEM CHILD CLASSES + + When using BEM notation inside component SCSS, child element class + names must preserve the component's exact PascalCase class name as + the prefix. Do NOT convert to kebab-case. + + The SCSS nesting syntax compiles &__element to the parent selector + plus __element. Since the parent is .Component_Name, the result is + .Component_Name__element - and HTML must use that exact class. + + Correct: + + // SCSS + .DataGrid_Kanban { + &__loading { ... } + &__board { ... } + &__column { ... } + } + + // HTML (jqhtml template) +
+
+
+ + Wrong: + + // HTML - kebab-case does NOT match compiled CSS +
// No styles applied! +
// No styles applied! + + This is a common mistake when following general web conventions where + BEM uses kebab-case. In RSX, component class names are PascalCase, + so BEM children must also be PascalCase. + NO EXEMPTIONS There are NO exemptions to this rule for files in rsx/app/ or diff --git a/docs/CLAUDE.dist.md b/docs/CLAUDE.dist.md index 72def22ad..da63048f9 100644 --- a/docs/CLAUDE.dist.md +++ b/docs/CLAUDE.dist.md @@ -440,6 +440,8 @@ The process involves creating Action classes with @route decorators and converti **Enforcement**: SCSS in `rsx/app/` and `rsx/theme/components/` must wrap in a single component class matching the jqhtml/blade file. This works because all jqhtml components, SPA actions/layouts, and Blade views with `@rsx_id` automatically render with `class="Component_Name"` on their root element. `rsx/lib/` is for non-visual plumbing (validators, utilities). `rsx/theme/` (outside components/) holds primitives, variables, Bootstrap overrides. +**BEM Child Classes**: When using BEM notation, child element classes must use the component's exact class name as prefix. SCSS `.Component_Name { &__element }` compiles to `.Component_Name__element`, so HTML must match: `
` not `
`. No kebab-case conversion. + **Variables**: Define shared values (colors, spacing, border-radius) in `rsx/theme/variables.scss` or similar. These must be explicitly included before directory includes in bundle definitions. Component-local variables can be defined within the scoped rule. **Supplemental files**: Multiple SCSS files can target the same component (e.g., breakpoint-specific styles) if a primary file with matching filename exists.