Files
rspade_system/app/RSpade/man/scss.txt
2025-12-12 01:13:50 +00:00

413 lines
13 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
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