Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages Replace #[Auth] attributes with manual auth checks and code quality rule Remove on_jqhtml_ready lifecycle method from framework Complete ACL system with 100-based role indexing and /dev/acl tester WIP: ACL system implementation with debug instrumentation Convert rsx:check JS linting to RPC socket server Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature Reorganize wishlists: priority order, mark sublayouts complete, add email Update model_fetch docs: mark MVP complete, fix enum docs, reorganize Comprehensive documentation overhaul: clarity, compression, and critical rules Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null() Add JS ORM relationship lazy-loading and fetch array handling Add JS ORM relationship fetching and CRUD documentation Fix ORM hydration and add IDE resolution for Base_* model stubs Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework Enhance JS ORM infrastructure and add Json_Tree class name badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,25 @@
|
||||
<!--
|
||||
===============================================================================
|
||||
WARNING: READ-ONLY FRAMEWORK DOCUMENTATION
|
||||
FRAMEWORK DOCUMENTATION - READ ONLY
|
||||
===============================================================================
|
||||
|
||||
This file contains RSpade framework documentation for AI/LLM assistants and is
|
||||
maintained by the framework developers. It is marked read-only and will be
|
||||
REPLACED during framework updates via `php artisan rsx:framework:pull`.
|
||||
This file: /var/www/html/system/docs/CLAUDE.dist.md (symlinked to ~/.claude/CLAUDE.md)
|
||||
Your file: /var/www/html/CLAUDE.md
|
||||
|
||||
DO NOT modify this file directly. Any changes you make will be lost during the
|
||||
next framework update.
|
||||
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.
|
||||
|
||||
For application-specific documentation and project context:
|
||||
- Edit the CLAUDE.md file in your project root
|
||||
- That file is NOT managed by the framework and persists across updates
|
||||
DOCUMENTATION SIZE CONSTRAINTS
|
||||
------------------------------
|
||||
Claude Code recommends combined CLAUDE.md files under 40kb - already tight for
|
||||
framework + application documentation. Every line must justify its existence.
|
||||
|
||||
This separation ensures:
|
||||
- Framework documentation stays current with updates
|
||||
- Your project-specific documentation is preserved
|
||||
- AI assistants have access to both framework and application context
|
||||
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
|
||||
|
||||
@@ -35,7 +34,7 @@ This separation ensures:
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
@@ -45,38 +44,32 @@ This separation ensures:
|
||||
|
||||
## CRITICAL RULES
|
||||
|
||||
### 🔴 RSpade Builds Automatically - NEVER RUN BUILD COMMANDS
|
||||
### CRITICAL: 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.
|
||||
**RSpade is INTERPRETED** - changes compile on-the-fly. NO manual build steps.
|
||||
|
||||
**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"
|
||||
**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
|
||||
|
||||
**How it works**:
|
||||
1. Edit JS/SCSS/PHP files
|
||||
2. Refresh browser
|
||||
3. Changes are live (< 1 second)
|
||||
Edit → Save → Refresh browser → Changes live (< 1 second)
|
||||
|
||||
**If you find yourself wanting to run build commands**: STOP. You're doing something wrong. Changes are already live.
|
||||
|
||||
### 🔴 Framework Updates
|
||||
### Framework Updates
|
||||
|
||||
```bash
|
||||
php artisan rsx:framework:pull # 5-minute timeout required
|
||||
php artisan rsx:framework:pull # User-initiated only
|
||||
```
|
||||
|
||||
Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. **For AI: Always use 5-minute timeout.**
|
||||
Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. Only run when requested by user.
|
||||
|
||||
### 🔴 Fail Loud - No Silent Fallbacks
|
||||
### Fail Loud - No Silent Fallbacks
|
||||
|
||||
**ALWAYS fail visibly.** No redundant fallbacks, silent failures, or alternative code paths.
|
||||
|
||||
```php
|
||||
// ❌ CATASTROPHIC
|
||||
// ❌ WRONG
|
||||
try { $clean = Sanitizer::sanitize($input); }
|
||||
catch (Exception $e) { $clean = $input; } // DISASTER
|
||||
|
||||
@@ -84,29 +77,9 @@ catch (Exception $e) { $clean = $input; } // DISASTER
|
||||
$clean = Sanitizer::sanitize($input); // Let it throw
|
||||
```
|
||||
|
||||
**SECURITY-CRITICAL**: If sanitization/validation/auth fails, NEVER continue. Always throw immediately.
|
||||
**SECURITY-CRITICAL**: Never continue after security failures. Only catch expected failures (file uploads, APIs, user input). Let exceptions bubble to global handler.
|
||||
|
||||
**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
|
||||
### No Defensive Coding
|
||||
|
||||
Core classes ALWAYS exist. Never check.
|
||||
|
||||
@@ -115,11 +88,11 @@ Core classes ALWAYS exist. Never check.
|
||||
// ✅ GOOD: Rsx.Route('Controller::method')
|
||||
```
|
||||
|
||||
### 🔴 Static-First Philosophy
|
||||
### Static-First Philosophy
|
||||
|
||||
Classes are namespacing tools. Use static unless instances needed (models, resources). Avoid dependency injection.
|
||||
|
||||
### 🔴 Git Workflow - Framework is READ-ONLY
|
||||
### Git Workflow - Framework is READ-ONLY
|
||||
|
||||
**NEVER modify `/var/www/html/system/`** - It's like node_modules or the Linux kernel.
|
||||
|
||||
@@ -129,7 +102,7 @@ Classes are namespacing tools. Use static unless instances needed (models, resou
|
||||
|
||||
**Commit discipline**: ONLY commit when explicitly asked. Commits are milestones, not individual changes.
|
||||
|
||||
### 🔴 DO NOT RUN `rsx:clean`
|
||||
### DO NOT RUN `rsx:clean`
|
||||
|
||||
**RSpade's cache auto-invalidates on file changes.** Running `rsx:clean` causes 30-60 second rebuilds with zero benefit.
|
||||
|
||||
@@ -137,7 +110,7 @@ Classes are namespacing tools. Use static unless instances needed (models, resou
|
||||
|
||||
**Correct workflow**: Edit → Save → Reload browser → See changes (< 1 second)
|
||||
|
||||
### 🔴 Trust Code Quality Rules
|
||||
### Trust Code Quality Rules
|
||||
|
||||
Each `rsx:check` rule has remediation text that tells AI assistants exactly what to do:
|
||||
- Some rules say "fix immediately"
|
||||
@@ -168,12 +141,16 @@ Files sharing a common prefix are a related set. When renaming, maintain the gro
|
||||
```
|
||||
frontend_calendar_event.scss
|
||||
frontend_calendar_event_controller.php
|
||||
frontend_calendar_event.blade.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
|
||||
@@ -183,12 +160,15 @@ frontend_calendar_event.js
|
||||
├── 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.
|
||||
@@ -221,7 +201,12 @@ Merged via `array_merge_deep()`. Common overrides: `development.auto_rename_file
|
||||
```php
|
||||
class Frontend_Controller extends Rsx_Controller_Abstract
|
||||
{
|
||||
#[Auth('Permission::anybody()')]
|
||||
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 = [])
|
||||
{
|
||||
@@ -232,17 +217,22 @@ class Frontend_Controller extends Rsx_Controller_Abstract
|
||||
}
|
||||
```
|
||||
|
||||
**Rules**: Only GET/POST allowed. Use `:param` syntax. All routes MUST have `#[Auth]`.
|
||||
**Rules**: Only GET/POST. Use `:param` syntax. Manual auth checks in pre_dispatch or method body.
|
||||
|
||||
### #[Auth] Attribute
|
||||
### Authentication Pattern
|
||||
|
||||
```php
|
||||
#[Auth('Permission::anybody()')] // Public
|
||||
#[Auth('Permission::authenticated()')] // Require login
|
||||
#[Auth('Permission::has_role("admin")')] // Custom
|
||||
// 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 */
|
||||
```
|
||||
|
||||
**Controller-wide**: Add to `pre_dispatch()`. Multiple attributes = all must pass.
|
||||
**Code quality**: PHP-AUTH-01 rule verifies auth checks exist. Use `@auth-exempt` for public routes.
|
||||
|
||||
### Type-Safe URLs
|
||||
|
||||
@@ -283,17 +273,19 @@ Client-side routing for authenticated application areas. One PHP bootstrap contr
|
||||
|
||||
### SPA Components
|
||||
|
||||
**1. PHP Bootstrap Controller (ONE per feature/bundle)**
|
||||
**1. PHP Bootstrap Controller** - ONE per module with auth in pre_dispatch
|
||||
```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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
```
|
||||
**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`.
|
||||
One #[SPA] per module at `rsx/app/(module)/(module)_spa_controller::index`. Segregates code by permission level.
|
||||
|
||||
**2. JavaScript Actions (MANY)**
|
||||
```javascript
|
||||
@@ -350,12 +342,17 @@ class Contacts_View_Action extends Spa_Action {
|
||||
|
||||
### 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/
|
||||
/rsx/app/frontend/ # Module
|
||||
├── Frontend_Spa_Controller.php # Single SPA bootstrap
|
||||
├── Frontend_Layout.js
|
||||
├── Frontend_Layout.jqhtml
|
||||
└── contacts/
|
||||
└── contacts/ # Feature
|
||||
├── frontend_contacts_controller.php # Ajax endpoints only
|
||||
├── Contacts_Index_Action.js # /contacts
|
||||
├── Contacts_Index_Action.jqhtml
|
||||
@@ -366,6 +363,10 @@ class Contacts_View_Action extends Spa_Action {
|
||||
**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)
|
||||
@@ -396,149 +397,47 @@ Template uses three states: `<Loading_Spinner>` → `<Universal_Error_Page_Compo
|
||||
|
||||
## 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
|
||||
<Define:Feature_Index_Action>
|
||||
<Page>
|
||||
<Page_Header><Page_Title>Title</Page_Title></Page_Header>
|
||||
<% for (let item of this.data.items) { %>
|
||||
<div><%= item.name %></div>
|
||||
<% } %>
|
||||
</Page>
|
||||
</Define:Feature_Index_Action>
|
||||
```
|
||||
|
||||
**4. Update Controller - Remove server-side route entirely:**
|
||||
```php
|
||||
// Remove #[Route] method completely. Add Ajax endpoints:
|
||||
#[Ajax_Endpoint]
|
||||
public static function fetch_items(Request $request, array $params = []) {
|
||||
return ['items' => Feature_Model::all()];
|
||||
}
|
||||
```
|
||||
|
||||
**CRITICAL**: Do NOT add `#[SPA]` to feature controllers. The `#[SPA]` attribute only exists in the bootstrap controller (e.g., `Frontend_Spa_Controller::index`). Feature controllers should only contain `#[Ajax_Endpoint]` methods for data fetching.
|
||||
|
||||
**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});
|
||||
}
|
||||
}
|
||||
```
|
||||
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 --}}
|
||||
|
||||
<body class="{{ rsx_body_class() }}"> {{-- Adds view class --}}
|
||||
|
||||
@rsx_include('Component_Name') {{-- Include by name, not path --}}
|
||||
```
|
||||
|
||||
**NO inline styles, scripts, or event handlers** in Blade views:
|
||||
- No `<style>` tags
|
||||
- No `<script>` tags
|
||||
- No inline event handlers: `onclick=""`, `onchange=""`, etc. are **forbidden**
|
||||
- Use companion `.scss` and `.js` files instead
|
||||
- Exception: jqhtml templates may use `@click` directive syntax (jqhtml-specific feature)
|
||||
|
||||
**Why no inline handlers**:
|
||||
- Violates separation of concerns (HTML structure vs behavior)
|
||||
- Makes code harder to maintain and test
|
||||
- `rsx:check` will flag inline event handlers and require refactoring
|
||||
|
||||
**Correct pattern**:
|
||||
```php
|
||||
// Blade view - NO event handlers
|
||||
<button id="submit-btn" class="btn btn-primary">Submit</button>
|
||||
|
||||
// Companion .js file
|
||||
$('#submit-btn').click(() => {
|
||||
// Handle click
|
||||
});
|
||||
```
|
||||
**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
|
||||
/* frontend_index.scss - Same directory as view */
|
||||
.Frontend_Index { /* Matches @rsx_id */
|
||||
/* 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -565,27 +464,13 @@ Use when data doesn't belong in DOM attributes. Multiple calls merge together.
|
||||
|
||||
## 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.
|
||||
**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.**
|
||||
**One bundle per page required.** Compiles JS/CSS automatically on request - no manual build steps.
|
||||
|
||||
```php
|
||||
class Frontend_Bundle extends Rsx_Bundle_Abstract
|
||||
@@ -599,17 +484,12 @@ class Frontend_Bundle extends Rsx_Bundle_Abstract
|
||||
'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
|
||||
<head>
|
||||
{!! Frontend_Bundle::render() !!}
|
||||
@@ -626,7 +506,7 @@ For mechanical thinkers who see structure, not visuals. Write `<User_Card>` not
|
||||
|
||||
### Template Syntax
|
||||
|
||||
**🔴 CRITICAL: `<Define>` IS the element, not a wrapper**
|
||||
**CRITICAL: `<Define>` IS the element, not a wrapper**
|
||||
|
||||
```jqhtml
|
||||
<!-- ✅ CORRECT - Define becomes button -->
|
||||
@@ -648,17 +528,51 @@ Example: `<input <% if (this.args.required) { %>required="required"<% } %> />`
|
||||
**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
|
||||
|
||||
### 🔴 State Management Rules (ENFORCED)
|
||||
### Simple Components (No JS File Needed)
|
||||
|
||||
For simple components without external data or complex state, write JS directly in the template:
|
||||
|
||||
```jqhtml
|
||||
<Define:CSV_Renderer>
|
||||
<%
|
||||
// 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(); };
|
||||
%>
|
||||
|
||||
<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>
|
||||
```
|
||||
|
||||
**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)
|
||||
|
||||
**Quick Guide:**
|
||||
- Loading from API? Use `this.data` in `on_load()`
|
||||
- Need to reload with new params? Modify `this.args`, call `reload()`
|
||||
- UI state (toggles, counters, etc)? Use `this.state`
|
||||
|
||||
```javascript
|
||||
// WITH Ajax data
|
||||
class Users_List extends Component {
|
||||
@@ -739,17 +653,27 @@ async on_load() {
|
||||
|
||||
### Component Access
|
||||
|
||||
**this.$sid(name)** → jQuery object (for DOM):
|
||||
```javascript
|
||||
this.$sid('button').on('click', ...);
|
||||
```
|
||||
**$sid** attribute = "scoped ID" - unique within component instance
|
||||
|
||||
**this.sid(name)** → Component instance (for methods):
|
||||
```javascript
|
||||
const comp = this.sid('child'); // ✅ Returns component
|
||||
await comp.reload();
|
||||
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.
|
||||
|
||||
const comp = this.sid('child').component(); // ❌ WRONG
|
||||
### 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
|
||||
@@ -763,18 +687,17 @@ const comp = this.sid('child').component(); // ❌ WRONG
|
||||
</Dashboard>
|
||||
```
|
||||
|
||||
### Common Pitfalls
|
||||
### Key Pitfalls (ABSOLUTE RULES)
|
||||
|
||||
1. `<Define>` IS the element - use `tag=""` attribute
|
||||
2. `this.data` starts empty `{}`, set defaults in on_create()
|
||||
3. ONLY modify `this.data` in on_create() and on_load() (enforced)
|
||||
4. on_load() can ONLY access this.args and this.data (enforced)
|
||||
5. Use `this.state = {}` in on_create() for component state (not loaded from Ajax)
|
||||
6. Use this.args for reload parameters, reload() to re-fetch
|
||||
7. Use `Controller.method()` not `$.ajax()`
|
||||
8. Blade components self-closing only
|
||||
9. `on_create/render/stop` must be sync
|
||||
10. Use this.sid() for components, NOT this.sid().component()
|
||||
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
|
||||
|
||||
@@ -891,27 +814,16 @@ Details: `php artisan rsx:man modals`
|
||||
|
||||
## JQUERY EXTENSIONS
|
||||
|
||||
RSpade extends jQuery with utility methods:
|
||||
| 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 |
|
||||
|
||||
**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`
|
||||
Details: `php artisan rsx:man jquery`
|
||||
|
||||
---
|
||||
|
||||
@@ -931,7 +843,7 @@ User_Model::create(['email' => $email]);
|
||||
|
||||
### Enums
|
||||
|
||||
**🔴 Read `php artisan rsx:man enum` for complete documentation before implementing.**
|
||||
**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.
|
||||
|
||||
@@ -953,6 +865,32 @@ 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.**
|
||||
@@ -1003,7 +941,18 @@ public static function method(Request $request, array $params = []) {
|
||||
}
|
||||
```
|
||||
|
||||
**Call:** `await Controller.method({param: 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
|
||||
|
||||
@@ -1052,31 +1001,17 @@ Details: `php artisan rsx:man storage`
|
||||
|
||||
---
|
||||
|
||||
### 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.
|
||||
**Always use Session** - Static methods only. Never Laravel Auth or $_SESSION.
|
||||
|
||||
```php
|
||||
RsxAuth::check(); // Is authenticated
|
||||
RsxAuth::user(); // User model
|
||||
RsxAuth::id(); // User ID
|
||||
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".
|
||||
@@ -1159,6 +1094,8 @@ Use for "impossible" conditions that indicate broken assumptions.
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
@@ -1184,11 +1121,11 @@ class Main extends Main_Abstract
|
||||
2. **Static by default** - Unless instances needed
|
||||
3. **Path-agnostic** - Reference by name
|
||||
4. **Bundles required** - For JavaScript
|
||||
5. **Use RsxAuth** - Never Laravel Auth
|
||||
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]** - No exceptions
|
||||
9. **All routes need auth checks** - In pre_dispatch() or method body (@auth-exempt for public)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user