Migrate $name from Form_Field to input components Refactor form inputs: $name moves from Form_Field to input components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
36 KiB
=============================================================================== 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 existbin/publish- For releases, not testingrsx:bundle:compile/rsx:manifest:build/rsx:clean- Automatic- ANY "build", "compile", or "publish" command
Edit → Save → Refresh browser → Changes live (< 1 second)
Framework Updates
php artisan rsx:framework:pull # User-initiated only
Fail Loud - No Silent Fallbacks
ALWAYS fail visibly. No redundant fallbacks, silent failures, or alternative code paths.
// ❌ 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.
// ❌ 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.
No Field Aliasing
Field names must be identical across all layers. Database → PHP → JSON → JavaScript: same names, always.
// ❌ WRONG - renaming during serialization
return ['type_label' => $contact->type_id_label];
// ✅ CORRECT - names match source
return ['type_id_label' => $contact->type_id_label];
One string everywhere. Grep finds all usages. No mental mapping between layers.
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.
Class Overrides
To customize framework classes without modifying /system/, copy them to rsx/ with the same class name. The manifest automatically uses your version and renames the framework file to .upstream.
Common override targets (copy from system/app/RSpade/Core/Models/):
User_Model- Add custom fields, relationships, methodsUser_Profile_Model- Extend profile dataSite_Model- Add site-specific settings
cp system/app/RSpade/Core/Models/User_Model.php rsx/models/user_model.php
# Edit namespace to Rsx\Models, customize as needed
Details: php artisan rsx:man class_override
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.
$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
{
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
// Controller-wide auth (recommended)
public static function pre_dispatch(Request $request, array $params = []) {
if (!Session::is_logged_in()) return response_unauthorized();
return null;
}
Type-Safe URLs
MANDATORY: All URLs must be generated using Rsx::Route() - hardcoded URLs are forbidden.
// PHP - Controller (defaults to 'index' method)
Rsx::Route('User_Controller')
// PHP - Controller with explicit method. Passing an integer for param two implies the param two is 'id' (id=123)
Rsx::Route('User_Controller::show', 123);
// PHP - With query parameters. Extra params not defined in route itself become query string - automatically URL-encoded
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'});
- Unimplemented routes: Prefix method with
#→Rsx::Route('Feature::#index')generateshref="#"and bypasses validation
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
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)
@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
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/JavaScript - same syntax
Rsx::Route('Contacts_Index_Action') // /contacts
Rsx::Route('Contacts_View_Action', 123) // /contacts/123
Spa.dispatch('/contacts/123'); // Programmatic navigation
Spa.layout // Current layout instance
Spa.action() // Current action instance
URL Parameters
// 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
Three-state pattern for data-loading actions:
on_create() {
this.data.record = { name: '' }; // Stub
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: <Loading_Spinner> → <Universal_Error_Page_Component> → content. Details: 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.
Pattern recognition:
@rsx_id('View_Name')- required first linersx_body_class()- adds view class for CSS scoping@rsx_page_data()- pass data to JSon_app_ready()with page guard for JS (fires for ALL pages in bundle)
Detailed guidance in blade-views skill - auto-activates when building Blade pages.
SCSS ARCHITECTURE
Component-first: Every styled element is a component with scoped SCSS. No generic classes scattered across files.
Scoping rules:
- Files in
rsx/app/andrsx/theme/components/must wrap in single component class - Wrapper class matches JS class or Blade
@rsx_id - BEM children use PascalCase:
.Component_Name__element(NOT kebab-case)
Responsive breakpoints (Bootstrap defaults do NOT work):
- Tier 1:
mobile(0-1023px),desktop(1024px+) - Tier 2:
phone,tablet,desktop-sm,desktop-md,desktop-lg,desktop-xl - SCSS:
@include mobile { },@include desktop-xl { } - Classes:
.col-mobile-12,.d-tablet-none,.mobile-only - JS:
Responsive.is_mobile(),Responsive.is_phone()
Detailed guidance in scss skill - auto-activates when styling components.
BUNDLE SYSTEM
One bundle per module (rsx/app/(module)). Compiles JS/CSS automatically on request - no manual build steps.
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/theme', // Everything else from theme - but variables.scss will be first
'rsx/app/frontend', // Directory -
'rsx/models', // For JS stubs
],
];
}
}
<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 Component btn btn-primary">Save</button>
Interpolation: <%= escaped %> | <%!= unescaped %> | <% javascript %>
Conditional Attributes <input <% if (this.args.required) { %>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)
<Define:CSV_Renderer>
<%
if (!this.args.csv_data) throw new Error('csv_data required');
const rows = this.args.csv_data.split('\n').map(r => r.split(','));
this.toggle = () => { this.args.expanded = !this.args.expanded; this.render(); };
%>
<table>
<% for (let row of rows) { %>
<tr><% for (let cell of row) { %><td><%= cell %></td><% } %></tr>
<% } %>
</table>
<button @click=this.toggle>Toggle View</button>
</Define:CSV_Renderer>
Use inline JS for simple transformations/handlers. Create .js file when JS overwhelms template or needs external data.
State Management Rules (ENFORCED)
Quick Guide:
- Loading from API? → Use
this.datainon_load() - Need reload with different params? → Modify
this.args, callreload() - 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)
// 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, writethis.data - ❌ NO DOM access, NO
this.state, NO modifyingthis.args
Lifecycle
- on_create() → Setup defaults (sync) -
this.data.rows = []; this.data.loading = true; - render → Template executes
- on_render() → Hide uninitialized UI (sync)
- on_load() → Fetch data into
this.data(async) - on_ready() → DOM manipulation safe (async)
If on_load() modifies this.data, component renders twice (defaults → populated).
Component API
DOM Access:
| Method | Returns | Purpose |
|---|---|---|
this.$ |
jQuery | Root element (NOT this.$el) |
this.$sid('name') |
jQuery | Child with $sid="name" |
this.sid('name') |
Component/null | Child component instance |
reload() vs render():
reload() = on_load() → render() → on_ready() ← ALWAYS USE THIS
render() = template only (no on_ready) ← NEVER USE
After mutations, call this.reload() - the server round-trip is intentional:
async add_item() {
await Controller.add({name: 'Test'});
this.reload(); // Refreshes this.data via on_load(), reattaches handlers via on_ready()
}
Event handlers go in on_ready() - they auto-reattach after reload. WRONG: Event delegation like this.$.on('click', '[data-sid="btn"]', handler) to "survive" render calls - use reload() instead.
this.data rules (enforced): Writable only in on_create() (defaults) and on_load() (fetched data). Read-only elsewhere.
on_render(): Ignore - use on_ready() for post-render work.
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$sid="name"→ Scoped element IDattr="<%= expr %>"→ HTML attribute with interpolation
Key restrictions:
<Define>attributes are static - No<%= %>on the<Define>tag. For dynamic attributes on the root element, use inline JS:<% this.$.attr('data-id', this.args.id); %>$prefix= component args, NOT HTML attributes -<My_Component $data-id=123 />createsthis.args['data-id'], not adata-idDOM attribute- Conditional attributes use if-statements -
<% if (cond) { %>checked<% } %>not ternaries
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.
Custom Component Events
Fire: this.trigger('event_name', data) | Listen: this.sid('child').on('event_name', (component, data) => {})
Key difference from jQuery: Events fired BEFORE handler registration still trigger the callback when registered. This solves component lifecycle timing issues where child events fire before parent registers handlers. Never use this.$.trigger() for custom events (enforced by JQHTML-EVENT-01).
Dynamic Component Creation
To dynamically create/replace a component in 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
});
Class preservation: Only PascalCase component names (capital start, no __) are replaced. Utility classes (text-muted), BEM child classes (Parent__child), and all attributes are preserved.
Incremental Scaffolding
Undefined components work immediately - they render as div with the component name as a class.
<Dashboard>
<Stats_Panel />
<Recent_Activity />
</Dashboard>
Key Pitfalls
<Define>IS the element - usetag=""attributethis.datastarts{}- set defaults inon_create()this.datawritable only inon_create()andon_load()on_load(): onlythis.argsandthis.data(no DOM, nothis.state)this.statefor UI state,this.args+reload()for refetchController.method()not$.ajax()- #[Ajax_Endpoint] auto-callableon_create/render/stopsync;this.sid()→ component,$(el).component()→ component
FORM COMPONENTS
Forms use <Rsx_Form> with $data, $controller, $method for automatic data binding. Key components: Form_Field, Form_Field_Hidden, input types (Text_Input, Select_Input, Checkbox_Input).
Pattern recognition:
vals()dual-mode method for get/setForm_Utils.apply_form_errors()for validation- Action loads data, Controller saves it
$disabled=truestill returns values (unlike HTML)
Detailed guidance in forms skill - auto-activates when building forms.
MODALS
Built-in dialogs: Modal.alert(), Modal.confirm(), Modal.prompt(), Modal.select(), Modal.error().
Form modals: Modal.form({title, component, on_submit}) - component must implement vals().
Reusable modals: Extend Modal_Abstract, implement static show().
Detailed guidance in modals skill - auto-activates when building 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
// ✅ CORRECT
$user = new User_Model();
$user->email = $email;
$user->save();
// ❌ WRONG
User_Model::create(['email' => $email]);
Enums
Integer-backed enums with model-level mapping. Define in $enums array with constant, label, and custom properties.
Pattern recognition:
- BEM-style access:
$model->status_id__label,$model->status_id__badge - JS methods:
Model.status_id__enum(),Model.status_id__enum_select() - Static constants:
Model::STATUS_ACTIVE - Use BIGINT columns, run
rsx:migrate:document_modelsafter changes
Detailed guidance in model-enums skill - auto-activates when implementing enums.
Model Fetch
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
if (!Session::is_logged_in()) return false;
return static::find($id);
}
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_id__label); // Enum properties (BEM-style)
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.
fetch() is for SECURITY, not aliasing: The fetch() method exists to remove private data users shouldn't see. NEVER alias enum properties (e.g., type_label instead of type_id__label) or format dates server-side. Use the full BEM-style names and format dates on client with Rsx_Date/Rsx_Time.
Details: php artisan rsx:man model_fetch
Migrations
Forward-only, no rollbacks. Deterministic transformations against known state.
php artisan make:migration:safe create_users_table
php artisan migrate:begin
php artisan migrate
php artisan migrate:commit
NO defensive coding in migrations:
// ❌ 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.
Pattern recognition:
File_Attachment_Model::find_by_key()+can_user_assign_this_file()attach_to()(single/replaces) vsadd_to()(multiple/adds)get_attachment()/get_attachments()for retrievalget_url(),get_download_url(),get_thumbnail_url()
Detailed guidance in file-attachments skill - auto-activates when handling uploads.
POLYMORPHIC TYPE REFERENCES
Polymorphic *_type columns (fileable_type, taskable_type, etc.) store BIGINT integers in the database for efficiency. The framework maps between integer IDs and class names transparently.
// Model: Declare type ref columns
protected static $type_ref_columns = ['fileable_type'];
// Usage: Code works with class name strings - conversion is automatic
$attachment->fileable_type = 'Contact_Model'; // Stored as integer in DB
echo $attachment->fileable_type; // Reads as 'Contact_Model'
Simple Names Only: Always use class_basename($model), never get_class($model) (FQCNs)
Details: php artisan rsx:man polymorphic
AJAX ENDPOINTS
#[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: 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):
// 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:
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.
DATA FETCHING (CRITICAL)
DEFAULT: Use Model.fetch(id) for all single-record retrieval from JavaScript.
const user = await User_Model.fetch(1); // Throws if not found
const user = await User_Model.fetch_or_null(1); // Returns null if not found
Requires #[Ajax_Endpoint_Model_Fetch] on the model's fetch() method.
Auto-populates enum properties and enables lazy relationship loading.
If model not available in JS bundle: STOP and ask the developer. Bundles should include all models they need (rsx/models in include paths). Do not create workaround endpoints without approval.
Custom Ajax endpoints require developer approval and are only for:
- Aggregations, batch operations, or complex result sets
- System/root-only models intentionally excluded from bundle
- Queries beyond simple ID lookup
Details: php artisan rsx:man model_fetch
AUTHENTICATION
Always use Session - Static methods only. Never Laravel Auth or $_SESSION.
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".
DATE & TIME HANDLING
Two Classes - Strict Separation: Rsx_Time (datetimes with timezone) | Rsx_Date (calendar dates, no timezone)
String-Based: ISO strings, not Carbon. Never use $casts with 'date'/'datetime' - blocked by rsx:check.
Pattern recognition:
Rsx_Time::now(),Rsx_Time::format(),Rsx_Time::relative()Rsx_Date::today(),Rsx_Date::format(),Rsx_Date::is_past()Rsx_Time::to_database()for UTC storage- Functions throw if wrong type passed
Detailed guidance in date-time skill - auto-activates when working with dates/times.
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 (see below)
rsx:man <topic> # Documentation
db:query "SQL" --json
Testing Routes
rsx:debug /path - Uses Playwright to render pages with full JS execution.
rsx:debug /dashboard --user=1 # Authenticated user
rsx:debug /page --screenshot-path=/tmp/page.png # Capture screenshot
rsx:debug /contacts --eval="$('.btn').click(); await sleep(1000)" # Simulate interaction
rsx:debug / --eval="return Rsx_Time.now_iso()" # Get eval result (use return)
rsx:debug / --console --eval="console.log(Rsx_Date.today())" # Or console.log with --console
Options: --user=ID, --console, --screenshot-path, --screenshot-width=mobile|tablet|desktop-*, --dump-dimensions=".selector", --eval="js", --help
SPA routes ARE server routes. If you get 404, the route doesn't exist - check route definitions. Never dismiss as "SPA can't be tested server-side".
rsx:debug captures the fully-rendered final DOM state after all async operations, component lifecycles, and data loading complete. If the DOM doesn't match expectations, it's not a timing issue - what you see is what the user sees. Investigate the actual code, not the capture timing.
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.
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.
KEY REMINDERS
- Fail loud - No silent failures
- Static by default - Unless instances needed
- Path-agnostic - Reference by name
- Bundles required - For JavaScript
- Use Session - Never Laravel Auth
- No mass assignment - Explicit only
- Forward migrations - No rollbacks
- Don't run rsx:clean - Cache auto-invalidates
- All routes need auth checks - In pre_dispatch() or method body (@auth-exempt for public)
SKILLS (Auto-Activated)
Detailed guidance for specific tasks is available via Claude Code skills. These activate automatically when relevant - no action needed.
| Skill | Activates When |
|---|---|
forms |
Building forms, validation, vals() pattern |
modals |
Creating dialogs, Modal.form(), Modal_Abstract |
model-enums |
Implementing status_id, type_id, enum properties |
file-attachments |
Upload flow, attach_to(), thumbnails |
date-time |
Rsx_Time, Rsx_Date, timezone handling |
blade-views |
Server-rendered pages, @rsx_id, @rsx_page_data |
scss |
Component styling, BEM, responsive breakpoints |
ajax-error-handling |
response_error(), Form_Utils, error codes |
model-fetch |
Model.fetch(), lazy relationships, #[Ajax_Endpoint_Model_Fetch] |
jquery-extensions |
.click() override, .exists(), .shallowFind() |
background-tasks |
#[Task], #[Schedule], Task::dispatch() |
crud-patterns |
List/view/edit structure, DataGrid, dual-route actions |
js-decorators |
@route, @spa, @layout, @mutex, custom decorators |
event-hooks |
#[OnEvent], filters, gates, actions |
migrations |
Raw SQL, make:migration:safe, forward-only |
polymorphic |
Type refs, morphTo, Polymorphic_Field_Helper |
Framework skills: ~/.claude/skills/ (symlinked from system/docs/skills/) - READ-ONLY, do not modify
Project skills: /var/www/html/.claude/skills/ - Custom skills for this specific project go here
GETTING HELP
php artisan rsx:man <topic> # Framework documentation
php artisan list rsx # All commands
Topics: bundle_api, jqhtml, routing, migrations, console_debug, model_fetch, vs_code_extension, deployment, framework_divergences
PROJECT DOCUMENTATION
Project-specific man pages in /rsx/resource/man/*.txt. Create when features have non-obvious details or component interactions. See /rsx/resource/man/CLAUDE.md for format.