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>
34 KiB
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 EXISTbin/publish- Creates releases for OTHER developers (not for testing YOUR changes)rsx:bundle:compile- Bundles compile automatically in dev modersx:manifest:build- Manifest rebuilds automatically in dev mode- ANY command with "build", "compile", or "publish"
How it works:
- Edit JS/SCSS/PHP files
- Refresh browser
- 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
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.
// ❌ 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.
// ❌ 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.
$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
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
#[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
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
@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
/* frontend_index.scss - Same directory as view */
.Frontend_Index { /* Matches @rsx_id */
.content { padding: 20px; }
}
JAVASCRIPT
Auto-Initialization
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.
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.
<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
<!-- ✅ 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
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
- on_create() → Setup default state BEFORE template (sync)
- render → Template executes with initialized state
- on_render() → Hide uninitialized UI (sync)
- on_load() → Fetch data into
this.data(async) - on_ready() → DOM manipulation safe (async)
on_create() now runs first - Initialize this.data properties here so templates can safely reference them:
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
async on_load() {
const result = await Product_Controller.list({page: 1});
this.data.products = result.products;
this.data.loaded = true; // Simple flag at END
}
<% 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
this.$id('button').on('click', ...); // Access scoped element
Common Pitfalls
<Define>IS the element - usetag=""attributethis.datastarts empty{}- ONLY modify
this.datainon_load() - Use
Controller.method()not$.ajax() - Blade components self-closing only
on_create/render/destroymust be sync
Bundle Integration Required
{!! 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:
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:
await Modal.alert("File saved");
if (await Modal.confirm("Delete?")) { /* confirmed */ }
let name = await Modal.prompt("Enter name:");
Form modals:
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.
// 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
// ✅ 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.
// ✅ 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:
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.
php artisan make:migration:safe create_users_table
php artisan migrate:begin
php artisan migrate
php artisan migrate:commit
AJAX ENDPOINTS
#[Ajax_Endpoint]
public static function get_data(Request $request, array $params = [])
{
return ['success' => true, 'data' => ...];
}
const result = await Demo_Controller.get_data({user_id: 123});
Model Fetch
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
if (!RsxAuth::check()) return false;
return static::find($id);
}
const user = await User_Model.fetch(1);
AUTHENTICATION
Always use RsxAuth, never Laravel Auth or $_SESSION.
RsxAuth::check(); // Is authenticated
RsxAuth::user(); // User model
RsxAuth::id(); // User ID
Sessions persist 365 days. Never implement "Remember Me".
JAVASCRIPT DECORATORS
/** @decorator */
function logCalls(target, key, descriptor) { /* ... */ }
class Service {
@logCalls
@mutex
async save() { /* ... */ }
}
COMMANDS
Module Creation
rsx:app:module:create <name> # /name
rsx:app:module:feature:create <m> <f> # /m/f
rsx:app:component:create --name=x # Component
Development
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
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:
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
- Fail loud - No silent failures
- Static by default - Unless instances needed
- Path-agnostic - Reference by name
- Bundles required - For JavaScript
- Use RsxAuth - Never Laravel Auth
- No mass assignment - Explicit only
- Forward migrations - No rollbacks
- Don't run rsx:clean - Cache auto-invalidates
- All routes need #[Auth] - No exceptions
GETTING HELP
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
# 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:
- Module (e.g., Frontend_, Backend_)
- Feature (e.g., Users, Settings)
- 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
// 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}):
#[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
#[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:
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
// ✅ 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
// ✅ 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:
- Module Aliases - jquery, lodash, bootstrap5, jqhtml
- Bundle Aliases - From config('rsx.bundle_aliases')
- Bundle Classes - Full class names like Shared_Bundle
- Files - Specific file paths
- Directories - All files in directory
Include Order Matters
'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.
<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
// <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
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
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
#[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
#[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
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
- Controller pre_dispatch() - Protects all routes in controller
- /rsx/main.php pre_dispatch() - Global/pattern-based protection
- Route method - Individual route protection
JAVASCRIPT DECORATORS (EXPANDED)
Built-in @mutex Decorator
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 | Module + index | /name |
| rsx:app:module:feature:create | Feature | /m/f |
| rsx:app:submodule:create |
Submodule + layout | /m/s |
| rsx:app:component:create --name=x | jqhtml component | N/A |
Migration Commands
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
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
console_debug("AUTH", "Login attempt", $user->id);
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
- Main::init() - Once at bootstrap
- Main::pre_dispatch() - Before every route
- Controller::pre_dispatch() - Before specific controller
- Route method - If both return null
- 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.
#[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
// 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.
// 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
# 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:
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.