Files
rspade_system/docs/CLAUDE.dist.md
root ac082bce2a Add config() Go to Definition support to VS Code extension
Always include params in window.rsxapp to reduce state variations
Add request params to window.rsxapp global
Enhance module creation commands with clear nomenclature guidance
Add module/submodule/feature nomenclature clarification to docs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 19:13:57 +00:00

1260 lines
35 KiB
Markdown

<!--
===============================================================================
WARNING: READ-ONLY FRAMEWORK DOCUMENTATION
===============================================================================
This file contains RSpade framework documentation for AI/LLM assistants and is
maintained by the framework developers. It is marked read-only and will be
REPLACED during framework updates via `php artisan rsx:framework:pull`.
DO NOT modify this file directly. Any changes you make will be lost during the
next framework update.
For application-specific documentation and project context:
- Edit the CLAUDE.md file in your project root
- That file is NOT managed by the framework and persists across updates
This separation ensures:
- Framework documentation stays current with updates
- Your project-specific documentation is preserved
- AI assistants have access to both framework and application context
===============================================================================
-->
# RSpade Framework - AI/LLM Development Guide
**PURPOSE**: Essential directives for AI/LLM assistants developing RSX applications with RSpade.
## What is RSpade?
**Visual Basic-like development for PHP/Laravel.** Think: VB6 apps → VB6 runtime → Windows = RSX apps → RSpade runtime → Laravel.
**Philosophy**: Modern anti-modernization. While JavaScript fragments into complexity and React demands quarterly paradigm shifts, RSpade asks: "What if coding was easy again?" Business apps need quick builds and easy maintenance, not bleeding-edge architecture.
**Important**: RSpade is built on Laravel but diverges significantly. Do not assume Laravel patterns work in RSX without verification.
**Terminology**: **RSpade** = Complete framework | **RSX** = Your application code in `/rsx/`
---
## CRITICAL RULES
### 🔴 RSpade Builds Automatically - NEVER RUN BUILD COMMANDS
**RSpade is an INTERPRETED framework** - like Python or PHP, changes are automatically detected and compiled on-the-fly. There is NO manual build step.
**ABSOLUTELY FORBIDDEN** (unless explicitly instructed):
- `npm run compile` / `npm run build` - **DO NOT EXIST**
- `bin/publish` - Creates releases for OTHER developers (not for testing YOUR changes)
- `rsx:bundle:compile` - Bundles compile automatically in dev mode
- `rsx:manifest:build` - Manifest rebuilds automatically in dev mode
- ANY command with "build", "compile", or "publish"
**How it works**:
1. Edit JS/SCSS/PHP files
2. Refresh browser
3. Changes are live (< 1 second)
**If you find yourself wanting to run build commands**: STOP. You're doing something wrong. Changes are already live.
### 🔴 Framework Updates
```bash
php artisan rsx:framework:pull # 5-minute timeout required
```
Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. **For AI: Always use 5-minute timeout.**
### 🔴 Fail Loud - No Silent Fallbacks
**ALWAYS fail visibly.** No redundant fallbacks, silent failures, or alternative code paths.
```php
// ❌ CATASTROPHIC
try { $clean = Sanitizer::sanitize($input); }
catch (Exception $e) { $clean = $input; } // DISASTER
// ✅ CORRECT
$clean = Sanitizer::sanitize($input); // Let it throw
```
**SECURITY-CRITICAL**: If sanitization/validation/auth fails, NEVER continue. Always throw immediately.
### 🔴 No Defensive Coding
Core classes ALWAYS exist. Never check.
```javascript
// ❌ BAD: if (typeof Rsx !== 'undefined') { Rsx.Route(...) }
// ✅ GOOD: Rsx.Route(...)
```
### 🔴 Static-First Philosophy
Classes are namespacing tools. Use static unless instances needed (models, resources). Avoid dependency injection.
### 🔴 Git Workflow - Framework is READ-ONLY
**NEVER modify `/var/www/html/system/`** - It's like node_modules or the Linux kernel.
- **App repo**: `/var/www/html/.git` (you control)
- **Framework**: `/var/www/html/system/` (submodule, don't touch)
- **Your code**: `/var/www/html/rsx/` (all changes here)
**Commit discipline**: ONLY commit when explicitly asked. Commits are milestones, not individual changes.
### 🔴 DO NOT RUN `rsx:clean`
**RSpade's cache auto-invalidates on file changes.** Running `rsx:clean` causes 30-60 second rebuilds with zero benefit.
**When to use**: Only on catastrophic corruption, after framework updates (automatic), or when explicitly instructed.
**Correct workflow**: Edit → Save → Reload browser → See changes (< 1 second)
### 🔴 Trust Code Quality Rules
Each `rsx:check` rule has remediation text that tells AI assistants exactly what to do:
- Some rules say "fix immediately"
- Some rules say "present options and wait for decision"
AI should follow the rule's guidance precisely. Rules are deliberately written and well-reasoned.
---
## NAMING CONVENTIONS
**Enforced by `rsx:check`**:
| Context | Convention | Example |
|---------|------------|---------|
| PHP Methods/Variables | `underscore_case` | `user_name` |
| PHP Classes | `Like_This` | `User_Controller` |
| JavaScript Classes | `Like_This` | `User_Card` |
| Files | `lowercase_underscore` | `user_controller.php` |
| Database Tables | `lowercase_plural` | `users` |
| Constants | `UPPERCASE` | `MAX_SIZE` |
**Critical**: Never create same-name different-case files.
---
## DIRECTORY STRUCTURE
```
/var/www/html/
├── rsx/ # YOUR CODE
│ ├── app/ # Modules
│ ├── models/ # Database models
│ ├── public/ # Static files (web-accessible)
│ ├── resource/ # Framework-ignored
│ └── theme/ # Global assets
└── system/ # FRAMEWORK (read-only)
```
### Special Directories (Path-Agnostic)
**`resource/`** - ANY directory named this is framework-ignored. Store helpers, docs, third-party code. Exception: `/rsx/resource/config/` IS processed.
**`public/`** - ANY directory named this is web-accessible, framework-ignored. 5min cache, 30d with `?v=`.
### Path-Agnostic Loading
Classes found by name, not path. No imports needed.
```php
$user = User_Model::find(1); // Framework finds it
// NOT: use Rsx\Models\User_Model; // Auto-generated
```
---
## CONFIGURATION
**Two-tier system**:
- **Framework**: `/system/config/rsx.php` (never modify)
- **User**: `/rsx/resource/config/rsx.php` (your overrides)
Merged via `array_merge_deep()`. Common overrides: `development.auto_rename_files`, `bundle_aliases`, `console_debug`.
---
## ROUTING & CONTROLLERS
```php
class Frontend_Controller extends Rsx_Controller_Abstract
{
#[Auth('Permission::anybody()')]
#[Route('/', methods: ['GET'])]
public static function index(Request $request, array $params = [])
{
return rsx_view('Frontend_Index', [
'bundle' => Frontend_Bundle::render()
]);
}
}
```
**Rules**: Only GET/POST allowed. Use `:param` syntax. All routes MUST have `#[Auth]`.
### #[Auth] Attribute
```php
#[Auth('Permission::anybody()')] // Public
#[Auth('Permission::authenticated()')] // Require login
#[Auth('Permission::has_role("admin")')] // Custom
```
**Controller-wide**: Add to `pre_dispatch()`. Multiple attributes = all must pass.
### Type-Safe URLs
```php
// PHP
Rsx::Route('User_Controller', 'show', ['id' => 123]);
Rsx::Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
// JavaScript (identical)
Rsx.Route('User_Controller', 'show', {id: 123});
Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
```
---
## BLADE & VIEWS
```blade
@rsx_id('Frontend_Index') {{-- Every view starts with this --}}
<body class="{{ rsx_body_class() }}"> {{-- Adds view class --}}
@rsx_include('Component_Name') {{-- Include by name, not path --}}
```
### SCSS Pairing
```scss
/* frontend_index.scss - Same directory as view */
.Frontend_Index { /* Matches @rsx_id */
.content { padding: 20px; }
}
```
---
## JAVASCRIPT
### Auto-Initialization
```javascript
class Frontend_Index {
static async on_app_ready() {
// DOM ready
}
static async on_jqhtml_ready() {
// Components ready
}
}
```
**CRITICAL**: JavaScript only executes when bundle rendered.
---
## BUNDLE SYSTEM
**One bundle per page required.**
```php
class Frontend_Bundle extends Rsx_Bundle_Abstract
{
public static function define(): array
{
return [
'include' => [
'jquery', // Required
'lodash', // Required
'rsx/theme/variables.scss', // Order matters
'rsx/app/frontend', // Directory
'rsx/models', // For JS stubs
'/public/vendor/css/core.css', // Public directory asset (filemtime cache-busting)
],
];
}
}
```
Bundles support `/public/` prefix for including static assets from public directories with automatic cache-busting.
Auto-compiles on page reload in development.
```blade
<head>
{!! Frontend_Bundle::render() !!}
</head>
```
---
## JQHTML COMPONENTS
### Philosophy
For mechanical thinkers who see structure, not visuals. Write `<User_Card>` not `<div class="card">`. Name what things ARE.
### Template Syntax
**🔴 CRITICAL: `<Define>` IS the element, not a wrapper**
```jqhtml
<!-- ✅ CORRECT - Define becomes button -->
<Define:Save_Button tag="button" class="btn btn-primary">
Save
</Define:Save_Button>
<!-- Renders as: -->
<button class="Save_Button Jqhtml_Component btn btn-primary">Save</button>
```
**Interpolation**: `<%= escaped %>` | `<%!= unescaped %>` | `<% javascript %>`
**Conditional Attributes** (v2.2.162+): Apply attributes conditionally using `<% if (condition) { %>attr="value"<% } %>`
directly in attribute context. Works with static values, interpolations, and multiple conditions per element.
Example: `<input <% if (this.args.required) { %>required="required"<% } %> />`
### 🔴 CRITICAL on_load() Rules
**ONLY modify `this.data` - NO other properties, NO DOM manipulation**
```javascript
class User_Card extends Jqhtml_Component {
async on_load() {
// ✅ ONLY this.data modifications
this.data = await User_Controller.get_data({id: this.args.id});
this.data.loaded = true;
// ❌ FORBIDDEN
// this.state = {loading: false}; // NO other properties
// this.$id('title').text(...); // NO DOM manipulation
// $.ajax({url: '/api/...'}); // NO direct ajax
}
on_ready() {
// ✅ DOM manipulation here
this.$id('title').text(this.data.name);
}
}
```
### Lifecycle
1. **on_create()** → Setup default state BEFORE template (sync)
2. **render** → Template executes with initialized state
3. **on_render()** → Hide uninitialized UI (sync)
4. **on_load()** → Fetch data into `this.data` (async)
5. **on_ready()** → DOM manipulation safe (async)
**on_create() now runs first** - Initialize `this.data` properties here so templates can safely reference them:
```javascript
on_create() {
this.data.rows = []; // Prevents "not iterable" errors
this.data.loading = true; // Template can check loading state
}
```
**Double-render**: If `on_load()` modifies `this.data`, component renders twice (defaults → populated).
### Loading Pattern
```javascript
async on_load() {
const result = await Product_Controller.list({page: 1});
this.data.products = result.products;
this.data.loaded = true; // Simple flag at END
}
```
```jqhtml
<% if (!this.data.loaded) { %>
Loading...
<% } else { %>
<!-- Show data -->
<% } %>
```
**NEVER call `this.render()` in `on_load()` - automatic re-render happens.**
### Attributes
- **`$quoted="string"`** → String literal
- **`$unquoted=expression`** → JavaScript expression
- **`$id="name"`** → Scoped element ID
```javascript
this.$id('button').on('click', ...); // Access scoped element
```
### Common Pitfalls
1. `<Define>` IS the element - use `tag=""` attribute
2. `this.data` starts empty `{}`
3. ONLY modify `this.data` in `on_load()`
4. Use `Controller.method()` not `$.ajax()`
5. Blade components self-closing only
6. `on_create/render/destroy` must be sync
### Bundle Integration Required
```blade
{!! Frontend_Bundle::render() !!} {{-- Required for JS --}}
<User_Card user_id="123" /> {{-- Now JS executes --}}
```
For advanced topics: `php artisan rsx:man jqhtml`
---
## FORM COMPONENTS
Form components use the **vals() dual-mode pattern** for getting/setting values:
```javascript
class My_Form extends Jqhtml_Component {
vals(values) {
if (values) {
// Setter - populate form
this.$id('name').val(values.name || '');
this.$id('email').val(values.email || '');
return null;
} else {
// Getter - extract values
return {
name: this.$id('name').val(),
email: this.$id('email').val()
};
}
}
}
```
**Validation**: `Form_Utils.apply_form_errors(form.$, errors)` - Matches by `name` attribute.
---
## MODALS
**Basic dialogs**:
```javascript
await Modal.alert("File saved");
if (await Modal.confirm("Delete?")) { /* confirmed */ }
let name = await Modal.prompt("Enter name:");
```
**Form modals**:
```javascript
const result = await Modal.form({
title: "Edit User",
component: "User_Form",
component_args: {data: user},
on_submit: async (form) => {
const values = form.vals();
const response = await User_Controller.save(values);
if (response.errors) {
Form_Utils.apply_form_errors(form.$, response.errors);
return false; // Keep open
}
return response.data; // Close and return
}
});
```
**Requirements**: Form component must implement `vals()` and include `<div $id="error_container"></div>`.
Details: `php artisan rsx:man modals`
---
## JQUERY EXTENSIONS
RSpade extends jQuery with utility methods:
**Element existence**: `$('.element').exists()` instead of `.length > 0`
**Component traversal**: `this.$.shallowFind('.Widget')` - Finds child elements matching selector that don't have another element of the same class as a parent between them and the component. Prevents selecting widgets from nested child components.
```javascript
// Use case: Finding form widgets without selecting nested widgets
this.$.shallowFind('.Form_Field').each(function() {
// Only processes fields directly in this form,
// not fields in nested sub-forms
});
```
**Sibling component lookup**: `$('.element').closest_sibling('.Widget')` - Searches for elements within progressively higher ancestors. Like `.closest()` but searches within ancestors instead of matching them. Stops at body tag. Useful for component-to-component communication.
**Form validation**: `$('form').checkValidity()` instead of `$('form')[0].checkValidity()`
**Click override**: `.click()` automatically calls `e.preventDefault()`. Use `.click_allow_default()` for native behavior.
For complete details: `php artisan rsx:man jquery`
---
## MODELS & DATABASE
### No Mass Assignment
```php
// ✅ CORRECT
$user = new User_Model();
$user->email = $email;
$user->save();
// ❌ WRONG
User_Model::create(['email' => $email]);
```
### Enums
**🔴 CRITICAL: Enum columns MUST be integers in both database and model definition**
Enum columns store integer values in the database, NOT strings. The model definition maps those integers to constants and labels.
```php
// ✅ CORRECT - Integer keys map to constants
public static $enums = [
'status_id' => [
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
2 => ['constant' => 'STATUS_INACTIVE', 'label' => 'Inactive'],
3 => ['constant' => 'STATUS_PENDING', 'label' => 'Pending'],
],
'priority_id' => [
1 => ['constant' => 'PRIORITY_LOW', 'label' => 'Low'],
2 => ['constant' => 'PRIORITY_MEDIUM', 'label' => 'Medium'],
3 => ['constant' => 'PRIORITY_HIGH', 'label' => 'High'],
],
];
// ❌ WRONG - String keys are NOT allowed
public static $enums = [
'status' => [ // ❌ Column name should be status_id
'active' => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'], // ❌ String key
'inactive' => ['constant' => 'STATUS_INACTIVE', 'label' => 'Inactive'], // ❌ String key
],
];
// Usage
$user->status_id = User_Model::STATUS_ACTIVE; // Sets to 1
echo $user->status_label; // "Active"
echo $user->status_id; // 1 (integer)
```
**Migration Requirements**: Enum columns must be INT(11), NEVER VARCHAR:
```php
public function up()
{
DB::statement("
CREATE TABLE users (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
status_id INT(11) NOT NULL DEFAULT 1, -- ✅ CORRECT - Enum column
priority_id INT(11) NOT NULL DEFAULT 1, -- ✅ CORRECT - Enum column
is_active TINYINT(1) NOT NULL DEFAULT 1, -- Boolean field (0=false, 1=true)
INDEX idx_status_id (status_id),
INDEX idx_priority_id (priority_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
// ❌ WRONG - VARCHAR columns are NOT allowed for enums
CREATE TABLE users (
status VARCHAR(20) NOT NULL DEFAULT 'active' -- WRONG - Use INT(11) instead
);
```
**Column Type Guidelines**:
- **INT(11)** - ALL enum columns use this type
- **TINYINT(1)** - Boolean fields ONLY (stores 0 or 1, treated as true/false in PHP)
### Migrations
**Forward-only, no rollbacks.**
```bash
php artisan make:migration:safe create_users_table
php artisan migrate:begin
php artisan migrate
php artisan migrate:commit
```
---
## AJAX ENDPOINTS
```php
#[Ajax_Endpoint]
public static function get_data(Request $request, array $params = [])
{
return ['success' => true, 'data' => ...];
}
```
```javascript
const result = await Demo_Controller.get_data({user_id: 123});
```
### Model Fetch
```php
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
if (!RsxAuth::check()) return false;
return static::find($id);
}
```
```javascript
const user = await User_Model.fetch(1);
```
---
## AUTHENTICATION
**Always use RsxAuth**, never Laravel Auth or $_SESSION.
```php
RsxAuth::check(); // Is authenticated
RsxAuth::user(); // User model
RsxAuth::id(); // User ID
```
Sessions persist 365 days. Never implement "Remember Me".
---
## JAVASCRIPT DECORATORS
```javascript
/** @decorator */
function logCalls(target, key, descriptor) { /* ... */ }
class Service {
@logCalls
@mutex
async save() { /* ... */ }
}
```
---
## COMMANDS
### Module Creation
```bash
rsx:app:module:create <name> # /name
rsx:app:module:feature:create <m> <f> # /m/f
rsx:app:component:create --name=x # Component
```
**Nomenclature**: Module (top-level section with shared layout), Submodule (page group within module), Feature (CRUD group), Subfeature (individual CRUD operation). Example: `frontend/clients/edit` = module, feature, subfeature.
### Development
```bash
rsx:check # Code quality
rsx:debug /page # Test routes
rsx:man <topic> # Documentation
db:query "SQL" --json
```
### Debugging
- **rsx_dump_die()** - Debug output
- **console_debug("CHANNEL", ...)** - Channel logging
- **CONSOLE_DEBUG_FILTER=CHANNEL** - Filter output
---
## ERROR HANDLING
```php
if (!$expected) {
shouldnt_happen("Class {$expected} missing");
}
```
Use for "impossible" conditions that indicate broken assumptions.
---
## CODE QUALITY
**Professional UI**: Hover effects ONLY on buttons, links, form fields. Static elements remain static.
Run `rsx:check` before commits. Enforces naming, prohibits animations on non-actionable elements.
---
## MAIN_ABSTRACT MIDDLEWARE
Optional `/rsx/main.php`:
```php
class Main extends Main_Abstract
{
public function init() { } // Bootstrap once
public function pre_dispatch($request, $params) { return null; } // Before routes
public function unhandled_route($request, $params) { } // 404s
}
```
---
## KEY REMINDERS
1. **Fail loud** - No silent failures
2. **Static by default** - Unless instances needed
3. **Path-agnostic** - Reference by name
4. **Bundles required** - For JavaScript
5. **Use RsxAuth** - Never Laravel Auth
6. **No mass assignment** - Explicit only
7. **Forward migrations** - No rollbacks
8. **Don't run rsx:clean** - Cache auto-invalidates
9. **All routes need #[Auth]** - No exceptions
---
## GETTING HELP
```bash
php artisan rsx:man <topic> # Detailed docs
php artisan list rsx # All commands
```
**Topics**: bundle_api, jqhtml, routing, migrations, console_debug, model_fetch, vs_code_extension, deployment, framework_divergences
**Remember**: RSpade prioritizes simplicity and rapid development. When in doubt, choose the straightforward approach.
## CRITICAL RULES (EXPANDED)
### Git Working Directory Rules
```bash
# All code changes in /var/www/html/rsx
cd /var/www/html
php artisan rsx:check # Run commands from project root
git add -A # Stage from project root
git commit -m "Snapshot: description"
```
### When rsx:clean Actually Fails
**The Cost of Running rsx:clean:**
- Entire cache is destroyed (not just invalidated)
- 30-60 second rebuild on next request
- Timeouts during development
- No benefit (changes already visible without clearing)
## NAMING CONVENTIONS (EXPANDED)
### Class Naming Philosophy
All class names must identify:
1. **Module** (e.g., Frontend_, Backend_)
2. **Feature** (e.g., Users, Settings)
3. **Type** (e.g., _Controller, _Model)
Example: Frontend_Users_Index_Controller → Module: Frontend, Feature: Users, Subfeature: Index, Type: Controller
## DIRECTORY STRUCTURE (EXPANDED)
```
/var/www/html/ # Project root
├── rsx/ # YOUR APPLICATION CODE
│ ├── app/ # Application modules
│ │ ├── frontend/ # Public website (/)
│ │ ├── backend/ # Admin panel (/admin)
│ │ └── api/ # API endpoints
│ ├── lib/ # Shared libraries
│ ├── models/ # Database models
│ ├── public/ # Static files (web-accessible)
│ │ └── public_ignore.json # Patterns blocked from HTTP
│ ├── resource/ # Framework-ignored files
│ │ └── config/ # Configuration overrides
│ │ └── rsx.php # Merged with /system/config/rsx.php
│ ├── theme/ # Global theme assets
│ │ ├── variables.scss
│ │ ├── layouts/
│ │ └── components/
│ ├── main.php # App-wide middleware (optional)
│ └── permission.php # Authorization methods
└── system/ # FRAMEWORK CODE (do not modify)
├── app/RSpade/ # RSpade framework runtime
├── config/ # Framework configuration
├── storage/ # Build artifacts and caches
└── artisan # Laravel's artisan CLI
```
### Path-Agnostic Implications
- **Move files freely** - Location doesn't matter
- **No path management** - No use statements needed (auto-generated)
- **Name-based referencing** - Just use the class name
- **Uniqueness required** - Each class name must be unique
## CONFIGURATION (EXPANDED)
### Merging Example
```php
// Framework: /system/config/rsx.php
'bundle_aliases' => ['core' => CoreBundle::class]
// User: /rsx/resource/config/rsx.php
'bundle_aliases' => ['my-app' => MyAppBundle::class]
// Result: Both present
config('rsx.bundle_aliases'); // ['core' => CoreBundle, 'my-app' => MyAppBundle]
```
## ROUTING & CONTROLLERS (EXPANDED)
### Route Parameters
Use :param syntax (not Laravel's {param}):
```php
#[Route('/users/:id')]
public static function show_user(Request $request, array $params = [])
{
$user_id = $params['id']; // Route parameter
$tab = $params['tab']; // Query string parameter
}
```
### Controller-Wide Auth
```php
#[Auth('Permission::authenticated()')]
public static function pre_dispatch(Request $request, array $params = [])
{
return null; // Continue to route
}
```
### Custom Permission Methods
Add to /rsx/permission.php:
```php
class Permission extends Permission_Abstract
{
public static function has_role(Request $request, array $params, string $role): mixed
{
if (!RsxAuth::check()) return false;
return RsxAuth::user()->role === $role;
}
}
```
## BLADE & VIEWS (EXPANDED)
### View Functions
```php
// ✅ GOOD
return rsx_view('Frontend_Index', ['data' => $data]);
// ❌ AVOID
return view('rsx.app.frontend.frontend_index', ['data' => $data]);
```
### Why SCSS Class Wrapping
- **Scoping** - Prevents style conflicts between views
- **Specificity** - Styles only apply when that view is rendered
- **Organization** - Clear ownership of styles
## JAVASCRIPT (EXPANDED)
### JavaScript File Structure Rules
```javascript
// ✅ ALLOWED
const API_URL = "https://api.example.com";
function processData(data) { /* ... */ }
// ❌ NOT ALLOWED
const RANDOM = Math.random(); // Function call
let variable = 42; // Use const
```
## BUNDLE SYSTEM (EXPANDED)
### Unified Include System
The include array auto-detects:
1. **Module Aliases** - jquery, lodash, bootstrap5, jqhtml
2. **Bundle Aliases** - From config('rsx.bundle_aliases')
3. **Bundle Classes** - Full class names like Shared_Bundle
4. **Files** - Specific file paths
5. **Directories** - All files in directory
### Include Order Matters
```php
'include' => [
'rsx/theme/variables.scss', // Variables first
'rsx/theme/components', // Components using variables
'rsx/app/frontend', // Pages using components
],
```
## JQHTML COMPONENTS (EXPANDED)
### Incremental Scaffolding
**Undefined components work immediately** - they render as div with the component name as a class.
```blade
<Dashboard>
<Stats_Panel />
<Recent_Activity />
</Dashboard>
```
### Why on_load() Restrictions Exist
- this.data triggers automatic re-render when modified
- DOM may not be fully initialized during on_load()
- Setting other properties breaks the component lifecycle
### Execution Order
- **First:** on_create() runs before anything else (setup state)
- **Top-down:** render, on_render (parent before children)
- **Bottom-up:** on_load, on_ready (children before parent)
- **Parallel:** Siblings at same depth process simultaneously during on_load()
### this.args vs this.data
```javascript
// <User_Card $user_id="123" $theme="dark" />
class User_Card extends Jqhtml_Component {
async on_load() {
// this.args.user_id = "123" (from attribute)
// this.data starts as {} (empty)
this.data = await User_Controller.get_user({id: this.args.user_id});
}
}
```
## MODELS & DATABASE (EXPANDED)
### Model Definition
```php
class User_Model extends Rsx_Model_Abstract
{
protected $table = 'users';
protected $fillable = []; // Always empty - no mass assignment
// Enum columns - MUST use integer keys
public static $enums = [
'status_id' => [
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
2 => ['constant' => 'STATUS_INACTIVE', 'label' => 'Inactive'],
],
];
}
```
### Migration Example
```php
public function up()
{
DB::statement("
CREATE TABLE articles (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
status_id INT(11) NOT NULL DEFAULT 1, -- Enum column
priority_id INT(11) NOT NULL DEFAULT 1, -- Enum column
is_published TINYINT(1) NOT NULL DEFAULT 0, -- Boolean field (0 or 1)
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_status_id (status_id),
INDEX idx_priority_id (priority_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
```
## AJAX ENDPOINTS (EXPANDED)
### Creating Detailed Endpoints
```php
#[Auth('Permission::anybody()')]
#[Ajax_Endpoint]
public static function get_user_data(Request $request, array $params = [])
{
$user = User_Model::find($params['user_id']);
if (!$user) {
return ['success' => false, 'error' => 'User not found'];
}
return ['success' => true, 'user' => $user];
}
```
**Testing Ajax endpoints**: `php artisan rsx:ajax Controller action --site-id=1 --args='{"id":1}'`
Test endpoints behind auth/site scoping or invoke RPC calls from scripts. JSON-only output.
- Default: Raw response
- `--debug`: HTTP-like wrapper
- `--show-context`: Display context before JSON
### Model Fetch Security
```php
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
if (!RsxAuth::check()) return false;
$product = static::find($id);
if ($product && !Permission::can_view_product($product)) {
return false;
}
return $product;
}
```
## AUTHENTICATION (EXPANDED)
### RsxAuth Full API
```php
use App\RSpade\Core\Auth\RsxAuth;
if (RsxAuth::check()) {
$user = RsxAuth::user(); // User model or null
$user_id = RsxAuth::id(); // User ID or null
$session = RsxAuth::session(); // Session model or null
}
RsxAuth::login($user);
RsxAuth::logout();
```
### Where to Check Authentication
1. **Controller pre_dispatch()** - Protects all routes in controller
2. **/rsx/main.php pre_dispatch()** - Global/pattern-based protection
3. **Route method** - Individual route protection
## JAVASCRIPT DECORATORS (EXPANDED)
### Built-in @mutex Decorator
```javascript
class DataService {
@mutex
async save_data() {
// Only one save_data() call per instance at a time
}
@mutex('critical_operation')
async update_shared_resource() {
// Only one update across all instances
}
}
```
## COMMANDS (EXPANDED)
### Module Hierarchy
| Command | Creates | Route |
|---------|---------|-------|
| rsx:app:module:create <name> | Module + index | /name |
| rsx:app:module:feature:create <m> <f> | Feature | /m/f |
| rsx:app:submodule:create <m> <s> | Submodule + layout | /m/s |
| rsx:app:component:create --name=x | jqhtml component | N/A |
**Nomenclature**:
- **Module**: Top-level section sharing layout and common UI (frontend, admin)
- **Submodule**: Group of pages within a module (settings within frontend)
- **Feature**: CRUD page group (clients, tasks, users)
- **Subfeature**: Individual CRUD operation (edit, view, delete)
**Examples**:
- `frontend/clients/edit` = module, feature, subfeature
- `frontend/settings/profile/edit` = module, submodule, feature, subfeature
- `admin/sites` = module, feature
**Rule of thumb**: If it shares a layout, it's a module. If it's a group of pages within a module, it's a submodule. If it's CRUD operations, it's feature + subfeatures.
### Migration Commands
```bash
make:migration:safe name # Create whitelisted migration
migrate:begin # Start snapshot session
migrate # Run migrations
migrate:commit # Commit changes
```
## DEBUGGING TOOLS (EXPANDED)
### rsx:debug - Route Testing
```bash
php artisan rsx:debug /dashboard
php artisan rsx:debug /dashboard --user=1 # Test as specific user
php artisan rsx:debug /page --expect-element="#btn" # Verify element exists
```
### console_debug() - Channel-Based Logging
```php
console_debug("AUTH", "Login attempt", $user->id);
```
```javascript
console_debug('AJAX', 'Request sent', url, params);
```
## CODE QUALITY (EXPANDED)
### Key Quality Rules
- **NoAnimationsRule** - No CSS animations/hover on non-actionable elements
- **DuplicateCaseFilesRule** - Detect same-name different-case files (critical)
- **MassAssignmentRule** - Prohibit $fillable arrays
### Trust the Rule Text
Each rule's remediation message specifies exactly how to handle violations:
- What the problem is
- Why it matters
- How to fix it
- Whether to fix autonomously or present options
**For AI assistants**: Follow the rule's guidance precisely. Don't override with "common sense" - the rule text is authoritative and deliberately written.
## MAIN_ABSTRACT MIDDLEWARE (EXPANDED)
### Execution Order
1. Main::init() - Once at bootstrap
2. Main::pre_dispatch() - Before every route
3. Controller::pre_dispatch() - Before specific controller
4. Route method - If both return null
5. Main::unhandled_route() - If no route matches
## FRAMEWORK DIVERGENCES FROM LARAVEL
**CRITICAL**: RSpade diverges significantly from Laravel. Key differences:
- **No Eloquent ORM features** - Use explicit field assignment only
- **No mass assignment** - $fillable arrays always empty
- **No resource routes** - Only explicit GET/POST routes
- **No middleware classes** - Use Main_Abstract and pre_dispatch()
- **No service providers** - Framework handles all bootstrapping
- **No facades** - Direct class access only
- **No dependency injection** - Static methods preferred
When Laravel documentation conflicts with this guide, **this guide takes precedence**.
## ATTRIBUTES PHILOSOPHY
**PHP attributes are metadata only** - never define actual attribute classes.
```php
#[Route('/')] // Framework reads via reflection
#[Auth('...')] // Never create Route or Auth classes
#[Ajax_Endpoint] // Just metadata markers
```
Add stubs to `.vscode/attribute-stubs.php` for IDE support, but **never create the actual classes**.
## SILENT SUCCESS PHILOSOPHY
**Supplemental operations succeed silently** - Unix principle "no news is good news".
- Primary operations report success (what user explicitly requested)
- Supplemental checks/validations are silent when successful
- Maintenance operations quiet unless they fail
```php
// Primary operation - reports success
$this->info("Migration completed successfully");
// Supplemental operation - silent on success
$this->validate_cache(); // Only outputs if problem found
```
## BROWSER ERROR LOGGING
JavaScript errors auto-captured when `LOG_BROWSER_ERRORS=true` in `.env`.
```javascript
// Manual error logging
Debugger.log_error('Component failed', error, {user_id: 123});
// Automatic capture of uncaught errors
// Batched and rate-limited to prevent spam
```
## REFACTORING COMMANDS
```bash
# Rename PHP classes across entire codebase
php artisan rsx:refactor:rename_php_class Old_Class_Name New_Class_Name
# Automatically updates:
# - All PHP file references
# - All Blade file references
# - Renames the file itself
```
## BUNDLE include_routes
For scanning additional directories for routes:
```php
class Admin_Bundle extends Rsx_Bundle_Abstract
{
public static function define(): array
{
return [
'include' => [...],
'include_routes' => [
'rsx/lib/admin_tools', // Scan for #[Route] attributes
],
];
}
}
```
## DEVELOPMENT VS PRODUCTION
**Development (auto-detected)**:
- Cache auto-invalidates on file changes
- Detailed error messages with stack traces
- Source maps enabled for debugging
- Bundle recompilation on every request
**Production**:
- Pre-compiled bundles from cache
- Minimal error messages
- No source maps
- Optimized performance
Never manually switch modes - framework auto-detects based on environment.