🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
26 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
🔴 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)
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')->url(['id' => 123]);
// JavaScript (identical)
Rsx.Route('User_Controller', 'show').url({id: 123});
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
],
];
}
}
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 %>
🔴 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
- render → Template executes,
this.data = {}(empty) - on_render() → Hide uninitialized UI (sync)
- on_create() → Quick setup (sync)
- on_load() → Fetch data into
this.data(async) - on_ready() → DOM manipulation safe (async)
Double-render: If on_load() modifies this.data, component renders twice (empty → 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
MODELS & DATABASE
No Mass Assignment
// ✅ CORRECT
$user = new User_Model();
$user->email = $email;
$user->save();
// ❌ WRONG
User_Model::create(['email' => $email]);
Enums
public static $enums = [
'status_id' => [
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
],
];
// Usage
$user->status_id = User_Model::STATUS_ACTIVE;
echo $user->status_label; // "Active"
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
- Top-down: render, on_render (parent before children)
- Bottom-up: on_create, 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
public static $enums = [
'status_id' => [
1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
],
];
}
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 TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_status_id (status_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];
}
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
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.