Files
rspade_system/app/RSpade/man/scss.txt
2025-12-19 01:46:34 +00:00

447 lines
14 KiB
Plaintext
Executable File

NAME
scss - SCSS file organization and class scoping conventions
SYNOPSIS
Action-scoped SCSS for SPA actions and Blade views:
// rsx/app/frontend/dashboard/dashboard_index_action.scss
.Dashboard_Index_Action {
.card { ... }
.stats-grid { ... }
}
Component-scoped SCSS for theme components:
// rsx/theme/components/sidebar/sidebar_nav.scss
.Sidebar_Nav {
.nav-item { ... }
.nav-link { ... }
}
Variables may be declared outside the wrapper for sharing:
// rsx/app/frontend/frontend_spa_layout.scss
$sidebar-width: 215px;
$header-height: 57px;
.Frontend_Spa_Layout {
.sidebar { width: $sidebar-width; }
}
DESCRIPTION
RSX enforces a class-scoping convention for SCSS files to prevent CSS
conflicts and ensure styles are self-contained. Every SCSS file in
rsx/app/ or rsx/theme/components/ must wrap ALL rules inside a single
top-level class selector that matches its associated JavaScript class
or Blade view ID.
This is essentially manual CSS scoping - like CSS Modules but enforced
by convention. The benefit is predictable specificity, no conflicts
between pages/components, and self-documenting file organization.
Key principle: The SCSS filename must match the filename of its
associated .js (action/component) or .blade.php file.
COMPONENT-FIRST PHILOSOPHY
Every styled element should be a component. If an element needs custom
styles, it deserves a name, a jqhtml definition, and scoped SCSS. This
eliminates CSS spaghetti - generic classes like .page-header, .filter-bar,
.action-buttons scattered across files, overriding each other unpredictably.
Pattern Recognition:
When building a page, ask: "Is this structure unique, or a pattern?"
Pattern (shared structure):
A datagrid page with toolbar, tabs, filters, and search appears
on 8 different pages. Create Datagrid_Card once with slots, use
it everywhere. Changes propagate automatically.
Unique (one-off structure):
A project dashboard with custom widgets specific to that page.
Create Project_Dashboard for that page alone.
Decision heuristic: If you're about to copy-paste structural markup,
stop and extract a component.
Slot-Based Composition:
Use slots to separate structure from content. The component owns
layout and styling; pages provide the variable parts via slots.
// Datagrid_Card owns the structure
<Define:Datagrid_Card>
<div class="card">
<div class="card-header">
<%= content('toolbar') %>
</div>
<div class="card-body">
<%= content('body') %>
</div>
</div>
</Define:Datagrid_Card>
// Page provides content via slots
<Datagrid_Card>
<Slot:toolbar>
<button>Add New</button>
<Search_Input />
</Slot:toolbar>
<Slot:body>
<Contacts_Datagrid />
</Slot:body>
</Datagrid_Card>
This keeps pages declarative and components reusable.
What Remains Shared:
Only primitives should be shared/unscoped styles:
- Buttons (.btn-primary, .btn-secondary)
- Spacing utilities (.mb-3, .p-2)
- Typography (.text-muted, .fw-bold)
- Bootstrap overrides
Everything else - page layouts, card variations, custom UI patterns -
should be component-scoped SCSS.
SCOPING RULES
Files in rsx/app/**/*.scss and rsx/theme/components/**/*.scss:
Must be fully enclosed in a class matching either:
- A Component subclass (Spa_Action, Spa_Layout, or direct Component)
- A Blade view's @rsx_id value (server-rendered page styles)
The SCSS filename must match the associated JS or Blade file's
filename (with .scss extension instead of .js/.blade.php).
Example - SPA Action:
rsx/app/frontend/dashboard/Dashboard_Index_Action.js
rsx/app/frontend/dashboard/dashboard_index_action.scss
// dashboard_index_action.scss
.Dashboard_Index_Action {
// ALL styles nested here
}
Example - SPA Layout:
rsx/app/frontend/Frontend_Spa_Layout.js
rsx/app/frontend/frontend_spa_layout.scss
// frontend_spa_layout.scss
.Frontend_Spa_Layout {
// ALL layout styles nested here
.app-sidebar { ... }
.app-content { ... }
}
Example - Blade View:
rsx/app/login/login_index.blade.php // has @rsx_id('Login_Index')
rsx/app/login/login_index.scss
// login_index.scss
.Login_Index {
// ALL styles nested here
}
Example - Theme Component:
rsx/theme/components/sidebar/sidebar_nav.js
rsx/theme/components/sidebar/sidebar_nav.scss
// sidebar_nav.scss
.Sidebar_Nav {
// ALL styles nested here
}
Files elsewhere:
SCSS files outside these paths are not validated by this rule
and can be organized as needed (e.g., global utilities, variables
in rsx/theme/base/).
SCSS VARIABLES
SCSS variable declarations ($var: value;) are allowed OUTSIDE the
wrapper class. This enables variables to be shared when the file is
imported by other SCSS files.
Example - Variables outside wrapper:
// frontend_spa_layout.scss
$sidebar-width: 215px;
$header-height: 57px;
$mobile-breakpoint: 991.98px;
.Frontend_Spa_Layout {
.sidebar {
width: $sidebar-width;
}
.header {
height: $header-height;
}
}
The manifest scanner strips variable declarations before checking
for the wrapper class, so they do not cause validation failures.
VARIABLES-ONLY FILES
Files containing ONLY variable declarations and comments (no actual
CSS rules or selectors) are considered valid without a wrapper class.
These are typically partial files intended to be imported by others.
Example - Variables-only file (valid):
// _variables.scss
$primary-color: #0d6efd;
$secondary-color: #6c757d;
$border-radius: 0.375rem;
Such files are marked with scss_variables_only in the manifest and
skip wrapper validation entirely.
SUPPLEMENTAL SCSS FILES
When a single SCSS file becomes unwieldy, you can split styles into
multiple files. Supplemental SCSS files may have different filenames
as long as:
1. A primary SCSS file exists with the matching filename (e.g.,
frontend_spa_layout.scss for Frontend_Spa_Layout)
2. The supplemental file uses the SAME wrapper class as the primary
This allows organizing styles by breakpoint, feature, or logical
grouping while maintaining the scoping convention.
Example - Splitting by breakpoint:
rsx/app/frontend/
frontend_spa_layout.scss // Primary file (required)
frontend_spa_layout_mobile.scss // Supplemental - mobile styles
frontend_spa_layout_print.scss // Supplemental - print styles
// frontend_spa_layout.scss (primary)
.Frontend_Spa_Layout {
.sidebar { width: 215px; }
.header { height: 57px; }
}
// frontend_spa_layout_mobile.scss (supplemental)
.Frontend_Spa_Layout {
@media (max-width: 768px) {
.sidebar { width: 100%; }
.header { height: 48px; }
}
}
// frontend_spa_layout_print.scss (supplemental)
.Frontend_Spa_Layout {
@media print {
.sidebar { display: none; }
.no-print { display: none; }
}
}
The primary file MUST exist first. Without it, supplemental files
will fail validation with a filename mismatch error.
BENEFITS
No CSS Conflicts:
.notice-item in Dashboard_Index_Action won't affect .notice-item
in Calendar_Index_Action because they're in different scope wrappers.
Self-Documenting:
File name tells you exactly which action/component it styles.
Delete the action -> delete its SCSS -> no orphaned styles.
Simple Class Names:
Use .team-grid instead of .dashboard-index-action__team-grid.
The wrapper provides the scoping automatically.
Predictable Specificity:
All page/component styles get the same specificity boost from
being nested under their wrapper class.
Safe Refactoring:
Moving or renaming an action means moving/renaming its SCSS.
No hunting through global stylesheets for related rules.
HOW IT WORKS
jqhtml components and Spa_Action classes automatically add their
class name to the root DOM element. For example, a component defined
as <Define:Sidebar_Nav> will have class="Sidebar_Nav" on its root.
Blade views use @rsx_id('View_Name') which can be output to the DOM
for the same scoping effect.
The manifest scanner detects if an SCSS file is fully enclosed in
a single class rule by:
1. Removing comments
2. Stripping SCSS variable declarations ($var: value;)
3. Checking if remaining content matches pattern: .ClassName { ... }
4. Verifying bracket balance (all content inside the wrapper)
A code quality rule then validates:
1. The wrapper class exists (or file is variables-only)
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)
<div class="DataGrid_Kanban__loading">
<div class="DataGrid_Kanban__board">
<div class="DataGrid_Kanban__column">
Wrong:
// HTML - kebab-case does NOT match compiled CSS
<div class="datagrid-kanban__loading"> // No styles applied!
<div class="datagrid-kanban__board"> // 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
rsx/theme/components/. Every SCSS file in these directories must
be scoped to its associated action, layout, component, or view.
If a file cannot be associated with any of these (extremely rare),
it likely belongs elsewhere:
- rsx/theme/base/ for global utilities and variables
- rsx/theme/layouts/ for shared layout styles
- A dedicated partial imported via @use
Moving files outside the enforced directories requires explicit
developer approval and should be carefully considered. In 99% of
cases, the SCSS file should be properly scoped.
VALIDATION
The scoping rule is enforced at manifest build time. Violations
produce errors like:
SCSS file 'rsx/app/frontend/dashboard/dashboard.scss' must be
fully enclosed in a single class rule matching a Component
or Blade @rsx_id.
Expected: .Dashboard_Index_Action { ... }
Found: No wrapper class detected
Or for wrapper class mismatches:
SCSS wrapper class 'Frontend_Dashboard' does not match any
Component class or Blade @rsx_id
Or for filename mismatches:
SCSS filename 'styles.scss' must match associated Component
file 'dashboard_index_action'
EXAMPLES
Correct - SPA Action Styles:
// rsx/app/frontend/invoices/invoices_view_action.scss
.Invoices_View_Action {
.invoice-header {
display: flex;
justify-content: space-between;
}
.line-items {
.item-row {
border-bottom: 1px solid #eee;
}
}
@media (max-width: 768px) {
.invoice-header {
flex-direction: column;
}
}
}
Correct - Layout with Variables:
// rsx/app/frontend/frontend_spa_layout.scss
$sidebar-width: 215px;
$header-height: 57px;
.Frontend_Spa_Layout {
.app-sidebar {
width: $sidebar-width;
position: fixed;
}
.app-header {
height: $header-height;
}
}
Correct - Component Styles:
// rsx/theme/components/modal/rsx_modal.scss
.Rsx_Modal {
.modal-header {
border-bottom: 1px solid var(--border-color);
}
.modal-body {
padding: 1.5rem;
}
&.modal-lg {
.modal-dialog {
max-width: 800px;
}
}
}
Incorrect - Multiple Top-Level Rules:
// BAD: Multiple selectors at top level
.Dashboard_Index_Action {
.card { ... }
}
.sidebar { // ERROR: This is outside the wrapper
width: 200px;
}
Incorrect - No Wrapper:
// BAD: No wrapper class
.card {
padding: 1rem;
}
.stats-grid {
display: grid;
}
SEE ALSO
spa - SPA routing and actions
jqhtml - Component template system
coding_standards - General naming conventions
code_quality - Code quality rule system