# 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 --}} {{-- 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 {!! Frontend_Bundle::render() !!} ``` --- ## JQHTML COMPONENTS ### Philosophy For mechanical thinkers who see structure, not visuals. Write `` not `
`. Name what things ARE. ### Template Syntax **🔴 CRITICAL: `` IS the element, not a wrapper** ```jqhtml Save ``` **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: `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 { %> <% } %> ``` **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. `` 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 --}} {{-- 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 `
`. 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 rsx:app:module:feature:create # /m/f rsx:app:component:create --name=x # Component ``` ### Development ```bash rsx:check # Code quality rsx:debug /page # Test routes rsx:man # 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 # 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 ``` ### 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 // 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 | 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 ```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.