Files
rspade_system/docs/CLAUDE.dist.md
root 8c8fb8e902 Mark PHP version compatibility fallback as legitimate in Php_Fixer
Add public directory asset support to bundle system
Fix PHP Fixer to replace ALL Rsx\ FQCNs with simple class names

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 17:18:10 +00:00

1245 lines
34 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
```
### 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 |
### 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.