# 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 BLANKET TRY/CATCH**: Use try/catch only for expected failures (file uploads, external APIs, user input parsing). NEVER wrap database operations or entire functions "just in case". ```php // ❌ WRONG - Defensive "on error resume" try { $user->save(); $result = process_data($user); return $result; } catch (Exception $e) { throw new Exception("Failed: " . $e->getMessage(), 0, $e); } // ✅ CORRECT - Let exceptions bubble $user->save(); $result = process_data($user); return $result; ``` Exception handlers format errors for different contexts (Ajax JSON, CLI, HTML). Don't wrap exceptions with generic messages - let them bubble to the global handler. ### 🔴 No Defensive Coding Core classes ALWAYS exist. Never check. ```javascript // ❌ BAD: if (typeof Rsx !== 'undefined') { Rsx.Route('Controller::method') } // ✅ GOOD: Rsx.Route('Controller::method') ``` ### 🔴 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` | ### File Prefix Grouping Files sharing a common prefix are a related set. When renaming, maintain the grouping across ALL files with that prefix. **Example** - `rsx/app/frontend/calendar/`: ``` frontend_calendar_event.scss frontend_calendar_event_controller.php frontend_calendar_event.blade.php frontend_calendar_event.js ``` **Critical**: Never create same-name different-case files (e.g., `user.php` and `User.php`). --- ## 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 **MANDATORY**: All URLs must be generated using `Rsx::Route()` - hardcoded URLs are forbidden. ```php // PHP - Controller (defaults to 'index' method) Rsx::Route('User_Controller') // PHP - Controller with explicit method Rsx::Route('User_Controller::show', 123); // PHP - With query parameters Rsx::Route('Login_Controller::logout', ['redirect' => '/dashboard']); // Generates: /logout?redirect=%2Fdashboard // JavaScript (identical syntax) Rsx.Route('User_Controller') Rsx.Route('User_Controller::show', 123); Rsx.Route('Login_Controller::logout', {redirect: '/dashboard'}); ``` **Signature**: `Rsx::Route($action, $params = null)` / `Rsx.Route(action, params = null)` - `$action` - Controller class, SPA action, or "Class::method" (defaults to 'index' if no `::` present) - `$params` - Integer sets 'id', array/object provides named params **Query Parameters**: Extra params become query string - automatically URL-encoded **Enforcement**: `rsx:check` will flag hardcoded URLs like `/login` or `/logout?redirect=...` and require you to use `Rsx::Route()`. Do it right the first time to avoid rework. --- ## SPA (SINGLE PAGE APPLICATION) ROUTING Client-side routing for authenticated application areas. One PHP bootstrap controller, multiple JavaScript actions that navigate without page reloads. ### SPA Components **1. PHP Bootstrap Controller (ONE per feature/bundle)** ```php class Frontend_Spa_Controller extends Rsx_Controller_Abstract { #[SPA] #[Auth('Permission::authenticated()')] public static function index(Request $request, array $params = []) { return rsx_view(SPA); } } ``` **CRITICAL**: One #[SPA] per feature/bundle (e.g., `/app/frontend`, `/app/root`, `/app/login`). Bundles separate features to save bandwidth, reduce processing time, and segregate confidential code (e.g., root admin from unauthorized users). The #[SPA] bootstrap performs server-side auth checks with failure/redirect before loading client-side actions. Typically one #[SPA] per feature at `rsx/app/(feature)/(feature)_spa_controller::index`. **2. JavaScript Actions (MANY)** ```javascript @route('/contacts') @layout('Frontend_Layout') @spa('Frontend_Spa_Controller::index') @title('Contacts') // Optional browser title class Contacts_Index_Action extends Spa_Action { async on_load() { this.data.contacts = await Frontend_Contacts_Controller.datagrid_fetch(); } } ``` **3. Layout** ```javascript class Frontend_Layout extends Spa_Layout { on_action(url, action_name, args) { // Called after action created, before on_ready // Access this.action immediately this.update_navigation(url); } } ``` Layout template must have `$id="content"` element where actions render. ### URL Generation & Navigation ```php // PHP/JavaScript - same syntax Rsx::Route('Contacts_Index_Action') // /contacts Rsx::Route('Contacts_View_Action', 123) // /contacts/123 ``` ```javascript Spa.dispatch('/contacts/123'); // Programmatic navigation Spa.layout // Current layout instance Spa.action // Current action instance ``` ### URL Parameters ```javascript // URL: /contacts/123?tab=history @route('/contacts/:id') class Contacts_View_Action extends Spa_Action { on_create() { console.log(this.args.id); // "123" (route param) console.log(this.args.tab); // "history" (query param) } } ``` ### File Organization ``` /rsx/app/frontend/ ├── Frontend_Spa_Controller.php # Single SPA bootstrap ├── Frontend_Layout.js ├── Frontend_Layout.jqhtml └── contacts/ ├── frontend_contacts_controller.php # Ajax endpoints only ├── Contacts_Index_Action.js # /contacts ├── Contacts_Index_Action.jqhtml ├── Contacts_View_Action.js # /contacts/:id └── Contacts_View_Action.jqhtml ``` **Use SPA for:** Authenticated areas, dashboards, admin panels **Avoid for:** Public pages (SEO needed), simple static pages Details: `php artisan rsx:man spa` --- ## CONVERTING BLADE PAGES TO SPA ACTIONS **7-step procedure to convert server-side pages to client-side SPA:** **1. Create Action Files** ```bash # In feature directory touch Feature_Index_Action.js Feature_Index_Action.jqhtml ``` **2. Create Action Class (.js)** ```javascript @route('/path') @layout('Frontend_Spa_Layout') @spa('Frontend_Spa_Controller::index') @title('Page Title') class Feature_Index_Action extends Spa_Action { full_width = true; // For DataGrid pages async on_load() { this.data.items = await Feature_Controller.fetch_items(); } } ``` **3. Convert Template (.jqhtml)** **Blade → jqhtml syntax:** - `{{ $var }}` → `<%= this.data.var %>` - `{!! $html !!}` → `<%!= this.data.html %>` - `@if($cond)` → `<% if (this.data.cond) { %>` - `@foreach($items as $item)` → `<% for (let item of this.data.items) { %>` - `@endforeach` → `<% } %>` - `{{-- comment --}}` → `<%-- comment --%>` - `{{ Rsx::Route('Class') }}` → `<%= Rsx.Route('Class') %>` ```jqhtml Title <% for (let item of this.data.items) { %>
<%= item.name %>
<% } %>
``` **4. Update Controller** ```php // Change #[Route] to #[SPA] #[SPA] public static function index(Request $request, array $params = []) { return rsx_view(SPA); } // Add Ajax endpoints #[Ajax_Endpoint] public static function fetch_items(Request $request, array $params = []): array { return ['items' => Feature_Model::all()]; } ``` **5. Update Route References** **Search entire codebase for old route references:** ```bash grep -r "Feature_Controller::method" rsx/app/ ``` Find/replace in all files: - `Rsx::Route('Feature_Controller::method')` → `Rsx::Route('Feature_Action')` - `Rsx.Route('Feature_Controller::method')` → `Rsx.Route('Feature_Action')` - Hardcoded `/feature/method/123` → `Rsx.Route('Feature_Action', 123)` **Check:** DataGrids, dashboards, save endpoints, navigation, breadcrumbs **6. Archive Old Files** ```bash mkdir -p rsx/resource/archive/frontend/feature/ mv feature_index.blade.php rsx/resource/archive/frontend/feature/ ``` **7. Test** ```bash php artisan rsx:debug /path ``` Verify: No JS errors, page renders, data loads. **Common Patterns:** **DataGrid (no data loading):** ```javascript class Items_Index_Action extends Spa_Action { full_width = true; async on_load() {} // DataGrid loads own data } ``` **Detail page (load data):** ```javascript @route('/items/:id') class Items_View_Action extends Spa_Action { async on_load() { this.data.item = await Items_Controller.get({id: this.args.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 --}} ``` **NO inline styles, scripts, or event handlers** in Blade views: - No `