=============================================================================== FRAMEWORK DOCUMENTATION - READ ONLY =============================================================================== This file: /var/www/html/system/docs/CLAUDE.dist.md (symlinked to ~/.claude/CLAUDE.md) Your file: /var/www/html/CLAUDE.md This file is replaced during `php artisan rsx:framework:pull`. Do not edit it. For application-specific notes, edit /var/www/html/CLAUDE.md instead. DOCUMENTATION SIZE CONSTRAINTS ------------------------------ Claude Code recommends combined CLAUDE.md files under 40kb - already tight for framework + application documentation. Every line must justify its existence. When editing /var/www/html/CLAUDE.md: - Terse, not verbose - no filler words or redundant explanations - Complete, not partial - include all critical information - Patterns over prose - code examples beat paragraphs - Reference this file's tone and density as the standard =============================================================================== # RSpade Framework - AI/LLM Development Guide **PURPOSE**: Essential directives for AI/LLM assistants developing RSX applications with RSpade. ## CRITICAL: Questions vs Commands - **Questions get answers, NOT actions** - "Is that fire?" gets "Yes" not "Let me run through it". User has a plan, don't take destructive action when asked a question. - **Commands get implementation** - Clear directives result in code changes ## What is RSpade? **Visual Basic-like development for PHP/Laravel.** Think: VB6 apps → VB6 runtime → Windows = RSX apps → RSpade runtime → Laravel. **Philosophy**: RSpade is rebellion against JavaScript fatigue. Like VB6 made Windows programming accessible, RSpade makes web development simple again. No build steps, no config hell, just code and reload. **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 ### CRITICAL: RSpade Builds Automatically - NEVER RUN BUILD COMMANDS **RSpade is INTERPRETED** - changes compile on-the-fly. NO manual build steps. **FORBIDDEN** (unless explicitly instructed): - `npm run compile/build` - Don't exist - `bin/publish` - For releases, not testing - `rsx:bundle:compile` / `rsx:manifest:build` - Automatic - ANY "build", "compile", or "publish" command Edit → Save → Refresh browser → Changes live (< 1 second) ### Framework Updates ```bash php artisan rsx:framework:pull # User-initiated only ``` Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. Only run when requested by user. ### Fail Loud - No Silent Fallbacks **ALWAYS fail visibly.** No redundant fallbacks, silent failures, or alternative code paths. ```php // ❌ WRONG try { $clean = Sanitizer::sanitize($input); } catch (Exception $e) { $clean = $input; } // DISASTER // ✅ CORRECT $clean = Sanitizer::sanitize($input); // Let it throw ``` **SECURITY-CRITICAL**: Never continue after security failures. Only catch expected failures (file uploads, APIs, user input). Let exceptions bubble to 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.jqhtml frontend_calendar_event.js ``` **Critical**: Never create same-name different-case files (e.g., `user.php` and `User.php`). ### Component Naming Pattern Input components follow: `{Supertype}_{Variant}_{Supertype}` → e.g., `Select_Country_Input`, `Select_State_Input`, `Select_Ajax_Input` --- ## DIRECTORY STRUCTURE ``` /var/www/html/ ├── rsx/ # YOUR CODE │ ├── app/ # Modules │ ├── models/ # Database models │ ├── services/ # Background tasks, external integrations │ ├── public/ # Static files (web-accessible) │ ├── resource/ # Framework-ignored │ └── theme/ # Global assets └── system/ # FRAMEWORK (read-only) ``` **Services**: Extend `Rsx_Service_Abstract` for non-HTTP functionality like scheduled tasks or external system integrations. ### 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 { public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) return response_unauthorized(); return null; } #[Route('/', methods: ['GET'])] public static function index(Request $request, array $params = []) { return rsx_view('Frontend_Index', [ 'bundle' => Frontend_Bundle::render() ]); } } ``` **Rules**: Only GET/POST. Use `:param` syntax. Manual auth checks in pre_dispatch or method body. ### Authentication Pattern ```php // Controller-wide auth (recommended) public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) return response_unauthorized(); return null; } // Public endpoints: add @auth-exempt to class docblock /** @auth-exempt Public route */ ``` **Code quality**: PHP-AUTH-01 rule verifies auth checks exist. Use `@auth-exempt` for public routes. ### 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 - **Unimplemented routes**: Prefix method with `#` → `Rsx::Route('Feature::#index')` generates `href="#"` and bypasses validation **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 module with auth in pre_dispatch ```php public static function pre_dispatch(Request $request, array $params = []) { if (!Session::is_logged_in()) return response_unauthorized(); return null; } #[SPA] public static function index(Request $request, array $params = []) { return rsx_view(SPA); } ``` One #[SPA] per module at `rsx/app/(module)/(module)_spa_controller::index`. Segregates code by permission level. **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 `$sid="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 Pattern: `/rsx/app/(module)/(feature)/` - **Module**: Major functionality (login, frontend, root) - **Feature**: Screen within module (contacts, reports, invoices) - **Submodule**: Feature grouping (settings), often with sublayouts ``` /rsx/app/frontend/ # Module ├── Frontend_Spa_Controller.php # Single SPA bootstrap ├── Frontend_Layout.js ├── Frontend_Layout.jqhtml └── contacts/ # Feature ├── 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 ### Sublayouts **Sublayouts** are `Spa_Layout` classes for nested persistent UI (e.g., settings sidebar). Use multiple `@layout` decorators - first is outermost: `@layout('Frontend_Spa_Layout')` then `@layout('Settings_Layout')`. Each must have `$sid="content"`. Layouts persist when unchanged; only differing parts recreated. All receive `on_action(url, action_name, args)` with final action info. Details: `php artisan rsx:man spa` ### View Action Pattern (Loading Data) For SPA actions that load data (view/edit CRUD pages), use the three-state pattern: ```javascript on_create() { this.data.record = { name: '' }; // Stub prevents undefined errors this.data.error_data = null; this.data.loading = true; } async on_load() { try { this.data.record = await Controller.get({id: this.args.id}); } catch (e) { this.data.error_data = e; } this.data.loading = false; } ``` Template uses three states: `` → `` → content. **Details**: `php artisan rsx:man view_action_patterns` --- ## CONVERTING BLADE PAGES TO SPA ACTIONS For converting server-side Blade pages to client-side SPA actions, see `php artisan rsx:man blade_to_spa`. The process involves creating Action classes with @route decorators and converting templates from Blade to jqhtml syntax. --- ## BLADE & VIEWS **Note**: SPA pages are the preferred standard. Use Blade only for SEO-critical public pages or authentication flows. ```blade @rsx_id('Frontend_Index') {{-- Every view starts with this --}} {{-- Adds view class --}} ``` **NO inline styles, scripts, or event handlers** - Use companion `.scss` and `.js` files. **jqhtml components** work fully in Blade (no slots). ### SCSS Pairing For page/component-specific styles, wrap all rules in a class matching the component name: ```scss /* my_component.scss - Scoped to component */ .My_Component { /* Matches component class or @rsx_id */ .content { padding: 20px; } /* All component-specific rules here */ } ``` **Convention**: Each Blade view, jqhtml action, or component automatically gets its name as a class on its root element, enabling scoped styling. ### JavaScript for Blade Pages Unlike SPA actions (which use component lifecycle), Blade pages use static `on_app_ready()` with a page guard: ```javascript class My_Page { // Matches @rsx_id('My_Page') static on_app_ready() { if (!$('.My_Page').exists()) return; // Guard required - fires for ALL pages in bundle // Page code here } } ``` ### Passing Data to JavaScript Use `@rsx_page_data` for page-specific data needed by JavaScript (IDs, config, etc.): ```blade @rsx_page_data(['user_id' => $user->id, 'mode' => 'edit']) @section('content') @endsection ``` Access in JavaScript: ```javascript const user_id = window.rsxapp.page_data.user_id; ``` Use when data doesn't belong in DOM attributes. Multiple calls merge together. --- ## JAVASCRIPT **CRITICAL**: JavaScript only executes when bundle rendered. See "JavaScript for Blade Pages" in BLADE & VIEWS section for the `on_app_ready()` pattern. --- ## BUNDLE SYSTEM **One bundle per page required.** Compiles JS/CSS automatically on request - no manual build steps. ```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 ], ]; } } ``` ```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"<% } %> />` **Inline Logic**: `<% this.handler = () => action(); %>` then `@click=this.handler` - No JS file needed for simple components **Event Handlers**: `@click=this.method` (unquoted) - Methods defined inline or in companion .js **Validation**: `<% if (!this.args.required) throw new Error('Missing arg'); %>` - Fail loud in template ### Simple Components (No JS File Needed) For simple components without external data or complex state, write JS directly in the template: ```jqhtml <% // Validate input if (!this.args.csv_data) throw new Error('csv_data required'); // Parse CSV const rows = this.args.csv_data.split('\n').map(r => r.split(',')); // Define click handler inline this.toggle = () => { this.args.expanded = !this.args.expanded; this.render(); }; %> <% for (let row of rows) { %> <% for (let cell of row) { %> <% } %> <% } %>
<%= cell %>
``` **When to use inline JS**: Simple data transformations, conditionals, loops, basic event handlers **When to create .js file**: External data loading, complex state management, multiple methods, or when JS overwhelms the template (should look mostly like HTML with some logic, not JS with some HTML) ### State Management Rules (ENFORCED) **Quick Guide:** - Loading from API? → Use `this.data` in `on_load()` - Need reload with different params? → Modify `this.args`, call `reload()` - UI state (toggles, selections)? → Use `this.state` **this.args** - Component arguments (read-only in on_load(), modifiable elsewhere) **this.data** - Ajax-loaded data (writable ONLY in on_create() and on_load()) **this.state** - Arbitrary component state (modifiable anytime) ```javascript // WITH Ajax data class Users_List extends Component { on_create() { this.data.users = []; // Defaults } async on_load() { this.data.users = await User_Controller.fetch({filter: this.args.filter}); } on_ready() { // Change filter → reload this.args.filter = 'new'; this.reload(); } } // WITHOUT Ajax data class Toggle_Button extends Component { on_create() { this.state = {is_open: false}; } on_ready() { this.$.on('click', () => { this.state.is_open = !this.state.is_open; }); } } ``` **on_load() restrictions** (enforced): - ✅ Read `this.args`, write `this.data` - ❌ NO DOM access, NO `this.state`, NO modifying `this.args` ### 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 - **`$sid="name"`** → Scoped element ID ### Component Access **$sid** attribute = "scoped ID" - unique within component instance From within component methods: - **this.$** → jQuery selector for the component element itself - **this.$sid(name)** → jQuery selector for child element with `$sid="name"` - **this.sid(name)** → Component instance of child (or null if not a component) - **$(selector).component()** → Get component instance from jQuery element - **`await $(selector).component().ready()`** → Await component initialization. Rarely needed - `on_ready()` auto-waits for children created during render. Use for dynamically created components or Blade page JS interaction. ### Dynamic Component Creation To dynamically create/replace a component in JavaScript: ```javascript // Destroys existing component (if any) and creates new one in its place $(selector).component('Component_Name', { arg1: value1, arg2: value2 }); // Example: render a component into a container this.$sid('result_container').component('My_Component', { data: myData, some_option: true }); ``` ### Incremental Scaffolding **Undefined components work immediately** - they render as div with the component name as a class. ```blade ``` ### Key Pitfalls (ABSOLUTE RULES) 1. `` IS the element - use `tag=""` attribute 2. `this.data` starts empty `{}` - MUST set defaults in `on_create()` 3. ONLY modify `this.data` in `on_create()` and `on_load()` (enforced by framework) 4. `on_load()` can ONLY access `this.args` and `this.data` (no DOM, no `this.state`) 5. Use `this.state = {}` in `on_create()` for UI state (not from Ajax) 6. Use `this.args` for reload parameters, call `reload()` to re-fetch 7. Use `Controller.method()` not `$.ajax()` - PHP methods with #[Ajax_Endpoint] auto-callable from JS 8. `on_create/render/stop` must be sync 9. `this.sid()` returns component instance, `$(selector).component()` converts jQuery to component ### Bundle Integration Required ```blade {!! Frontend_Bundle::render() !!} {{-- Required for JS --}} {{-- Now JS executes --}} ``` For advanced topics: `php artisan rsx:man jqhtml` --- ## FORM COMPONENTS **Form fields** (`` with `$data`, `$controller`, `$method`): ```blade ``` - **Form_Field** - Standard formatted field with label, errors, help text - **Form_Field_Hidden** - Single-tag hidden input (extends Form_Field_Abstract) - **Form_Field_Abstract** - Base class for custom formatting (advanced) **Disabled fields**: Use `$disabled=true` attribute on input components to disable fields. Unlike standard HTML, disabled fields still return values via `vals()` (useful for read-only data that should be submitted). ```blade ``` **Form component classes** use the **vals() dual-mode pattern**: ```javascript class My_Form extends Component { vals(values) { if (values) { // Setter - populate form this.$sid('name').val(values.name || ''); return null; } else { // Getter - extract values return {name: this.$sid('name').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 `
`. **Modal Classes** (for complex/reusable modals): ```javascript // Define modal class class Add_User_Modal extends Modal_Abstract { static async show() { const result = await Modal.form({...}); return result || false; } } // Use from page JS const user = await Add_User_Modal.show(); if (user) { // Orchestrate post-modal actions grid.reload(); await Next_Modal.show(user.id); } ``` Pattern: Extend `Modal_Abstract`, implement static `show()`, return data or `false`. Page JS orchestrates flow, modal classes encapsulate UI. Details: `php artisan rsx:man modals` --- ## JQUERY EXTENSIONS | Method | Purpose | |--------|---------| | `.exists()` | Check element exists (instead of `.length > 0`) | | `.shallowFind(selector)` | Find children without nested component interference | | `.closest_sibling(selector)` | Search within ancestor hierarchy | | `.checkValidity()` | Form validation helper | | `.click()` | Auto-prevents default | | `.click_allow_default()` | Native click behavior | 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: Read `php artisan rsx:man enum` for complete documentation before implementing.** Integer-backed enums with model-level mapping to constants, labels, and custom properties. ```php class Project_Model extends Rsx_Model_Abstract { public static $enums = [ 'status_id' => [ 1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active', 'badge' => 'bg-success'], 2 => ['constant' => 'STATUS_ARCHIVED', 'label' => 'Archived', 'selectable' => false], ], ]; } // Usage $project->status_id = Project_Model::STATUS_ACTIVE; echo $project->status_label; // "Active" echo $project->status_badge; // "bg-success" (custom property) ``` **Migration:** Use BIGINT for enum columns, TINYINT(1) for booleans. Run `rsx:migrate:document_models` after adding enums. ### Model Fetch ```php #[Ajax_Endpoint_Model_Fetch] public static function fetch($id) { if (!Session::is_logged_in()) return false; return static::find($id); } ``` ```javascript const project = await Project_Model.fetch(1); // Throws if not found const maybe = await Project_Model.fetch_or_null(999); // Returns null if not found console.log(project.status_label); // Enum properties populated console.log(Project_Model.STATUS_ACTIVE); // Static enum constants // Lazy relationships (requires #[Ajax_Endpoint_Model_Fetch] on relationship method) const client = await project.client(); // belongsTo → Model or null const tasks = await project.tasks(); // hasMany → Model[] ``` **Security**: Both `fetch()` and relationships require `#[Ajax_Endpoint_Model_Fetch]` attribute. Related models must also implement `fetch()` with this attribute. Details: `php artisan rsx:man model_fetch` ### Migrations **Forward-only, no rollbacks. Deterministic transformations against known state.** ```bash php artisan make:migration:safe create_users_table php artisan migrate:begin php artisan migrate php artisan migrate:commit ``` **NO defensive coding in migrations:** ```php // ❌ WRONG - conditional logic $fk_exists = DB::select("SELECT ... FROM information_schema..."); if (!empty($fk_exists)) { DB::statement("ALTER TABLE foo DROP FOREIGN KEY bar"); } // ✅ CORRECT - direct statements DB::statement("ALTER TABLE foo DROP FOREIGN KEY bar"); DB::statement("ALTER TABLE foo DROP COLUMN baz"); ``` No `IF EXISTS`, no `information_schema` queries, no fallbacks. Know current state, write exact transformation. Failures fail loud - snapshot rollback exists for recovery. --- ## FILE ATTACHMENTS Files upload UNATTACHED → validate → assign via API. Session-based validation prevents cross-user file assignment. ```php // Controller: Assign uploaded file $attachment = File_Attachment_Model::find_by_key($params['photo_key']); if ($attachment && $attachment->can_user_assign_this_file()) { $attachment->attach_to($user, 'profile_photo'); // Single (replaces) $attachment->add_to($project, 'documents'); // Multiple (adds) } // Model: Retrieve attachments $photo = $user->get_attachment('profile_photo'); $documents = $project->get_attachments('documents'); // Display $photo->get_thumbnail_url('cover', 128, 128); $photo->get_url(); $photo->get_download_url(); ``` **Endpoints:** `POST /_upload`, `GET /_download/:key`, `GET /_thumbnail/:key/:type/:width/:height` Details: `php artisan rsx:man file_upload` --- ## AJAX ENDPOINTS ```php #[Ajax_Endpoint] public static function method(Request $request, array $params = []) { return $data; // Success - framework wraps as {_success: true, _ajax_return_value: ...} } ``` **PHP→JS Auto-mapping:** ```php // PHP: My_Controller class #[Ajax_Endpoint] public static function save(Request $request, array $params = []) { return ['id' => 123]; } // JS: Automatically callable const result = await My_Controller.save({name: 'Test'}); console.log(result.id); // 123 ``` ### Error Responses Use `response_error(Ajax::ERROR_CODE, $metadata)`: ```php // Not found return response_error(Ajax::ERROR_NOT_FOUND, 'Project not found'); // Validation return response_error(Ajax::ERROR_VALIDATION, [ 'email' => 'Invalid', 'name' => 'Required' ]); // Auto-message return response_error(Ajax::ERROR_UNAUTHORIZED); ``` **Codes:** `ERROR_VALIDATION`, `ERROR_NOT_FOUND`, `ERROR_UNAUTHORIZED`, `ERROR_AUTH_REQUIRED`, `ERROR_FATAL`, `ERROR_GENERIC` **Client:** ```javascript try { const data = await Controller.get(id); } catch (e) { if (e.code === Ajax.ERROR_NOT_FOUND) { // Handle } else { alert(e.message); // Generic } } ``` Unhandled errors auto-show flash alert. --- ## BROWSER STORAGE **Rsx_Storage** - Scoped sessionStorage/localStorage with automatic fallback and quota management. All keys automatically scoped by session, user, site, and build. Gracefully handles unavailable storage and quota exceeded errors. Storage is volatile - use only for non-critical data (caching, UI state, transient messages). `Rsx_Storage.session_set(key, value)` / `Rsx_Storage.session_get(key)` / `Rsx_Storage.local_set(key, value)` / `Rsx_Storage.local_get(key)` Details: `php artisan rsx:man storage` --- ## AUTHENTICATION **Always use Session** - Static methods only. Never Laravel Auth or $_SESSION. ```php Session::is_logged_in(); // Returns true if user logged in Session::get_user(); // Returns user model or null Session::get_user_id(); // Returns user ID or null Session::get_site(); // Returns site model Session::get_site_id(); // Returns current site ID Session::get_session_id(); // Returns session 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 (see below) rsx:man # Documentation db:query "SQL" --json ``` ### Testing Routes **`rsx:debug /path`** - Preferred method for testing routes Uses Playwright to render the page and show rendered output, JavaScript errors, and console messages. ```bash rsx:debug /clients # Test route rsx:debug /dashboard --user=1 # Simulate authenticated user rsx:debug /contacts --console # Show console.log output rsx:debug /page --screenshot-path=/tmp/page.png --screenshot-width=mobile # Capture screenshot rsx:debug /path --help # Show all options ``` Screenshot presets: mobile, iphone-mobile, tablet, desktop-small, desktop-medium, desktop-large Use this instead of manually browsing, especially for SPA pages and Ajax-heavy features. **CRITICAL: SPA routes ARE server routes.** The server knows all SPA routes. `rsx:debug` uses Playwright to fully render pages including all JavaScript and SPA navigation. If you get a 404, the route genuinely doesn't exist - check your URL pattern and route definitions. Never dismiss 404s as "SPA routes can't be tested server-side" - this analysis is incorrect. ### 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. **z-index**: Bootstrap defaults + 1100 (modal children), 1200 (flash alerts), 9000+ (system). Details: `rsx:man zindex` 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 Session** - 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 checks** - In pre_dispatch() or method body (@auth-exempt for public) --- ## 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.