Files
rspade_system/docs/CLAUDE.dist.md
2025-10-22 04:07:04 +00:00

26 KiB

RSpade Framework - AI/LLM Development Guide

PURPOSE: Essential directives for AI/LLM assistants developing RSX applications with RSpade.

What is RSpade?

Visual Basic-like development for PHP/Laravel. Think: VB6 apps → VB6 runtime → Windows = RSX apps → RSpade runtime → Laravel.

Philosophy: Modern anti-modernization. While JavaScript fragments into complexity and React demands quarterly paradigm shifts, RSpade asks: "What if coding was easy again?" Business apps need quick builds and easy maintenance, not bleeding-edge architecture.

Important: RSpade is built on Laravel but diverges significantly. Do not assume Laravel patterns work in RSX without verification.

Terminology: RSpade = Complete framework | RSX = Your application code in /rsx/


CRITICAL RULES

🔴 Framework Updates

php artisan rsx:framework:pull  # 5-minute timeout required

Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. For AI: Always use 5-minute timeout.

🔴 Fail Loud - No Silent Fallbacks

ALWAYS fail visibly. No redundant fallbacks, silent failures, or alternative code paths.

// ❌ CATASTROPHIC
try { $clean = Sanitizer::sanitize($input); }
catch (Exception $e) { $clean = $input; }  // DISASTER

// ✅ CORRECT
$clean = Sanitizer::sanitize($input);  // Let it throw

SECURITY-CRITICAL: If sanitization/validation/auth fails, NEVER continue. Always throw immediately.

🔴 No Defensive Coding

Core classes ALWAYS exist. Never check.

// ❌ BAD: if (typeof Rsx !== 'undefined') { Rsx.Route(...) }
// ✅ GOOD: Rsx.Route(...)

🔴 Static-First Philosophy

Classes are namespacing tools. Use static unless instances needed (models, resources). Avoid dependency injection.

🔴 Git Workflow - Framework is READ-ONLY

NEVER modify /var/www/html/system/ - It's like node_modules or the Linux kernel.

  • App repo: /var/www/html/.git (you control)
  • Framework: /var/www/html/system/ (submodule, don't touch)
  • Your code: /var/www/html/rsx/ (all changes here)

Commit discipline: ONLY commit when explicitly asked. Commits are milestones, not individual changes.

🔴 DO NOT RUN rsx:clean

RSpade's cache auto-invalidates on file changes. Running rsx:clean causes 30-60 second rebuilds with zero benefit.

When to use: Only on catastrophic corruption, after framework updates (automatic), or when explicitly instructed.

Correct workflow: Edit → Save → Reload browser → See changes (< 1 second)


NAMING CONVENTIONS

Enforced by rsx:check:

Context Convention Example
PHP Methods/Variables underscore_case user_name
PHP Classes Like_This User_Controller
JavaScript Classes Like_This User_Card
Files lowercase_underscore user_controller.php
Database Tables lowercase_plural users
Constants UPPERCASE MAX_SIZE

Critical: Never create same-name different-case files.


DIRECTORY STRUCTURE

/var/www/html/
├── rsx/                        # YOUR CODE
│   ├── app/                    # Modules
│   ├── models/                 # Database models
│   ├── public/                 # Static files (web-accessible)
│   ├── resource/               # Framework-ignored
│   └── theme/                  # Global assets
└── system/                     # FRAMEWORK (read-only)

Special Directories (Path-Agnostic)

resource/ - ANY directory named this is framework-ignored. Store helpers, docs, third-party code. Exception: /rsx/resource/config/ IS processed.

public/ - ANY directory named this is web-accessible, framework-ignored. 5min cache, 30d with ?v=.

Path-Agnostic Loading

Classes found by name, not path. No imports needed.

$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
{
    #[Auth('Permission::anybody()')]
    #[Route('/', methods: ['GET'])]
    public static function index(Request $request, array $params = [])
    {
        return rsx_view('Frontend_Index', [
            'bundle' => Frontend_Bundle::render()
        ]);
    }
}

Rules: Only GET/POST allowed. Use :param syntax. All routes MUST have #[Auth].

#[Auth] Attribute

#[Auth('Permission::anybody()')]           // Public
#[Auth('Permission::authenticated()')]     // Require login
#[Auth('Permission::has_role("admin")')]   // Custom

Controller-wide: Add to pre_dispatch(). Multiple attributes = all must pass.

Type-Safe URLs

// PHP
Rsx::Route('User_Controller', 'show')->url(['id' => 123]);

// JavaScript (identical)
Rsx.Route('User_Controller', 'show').url({id: 123});

BLADE & VIEWS

@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 --}}

SCSS Pairing

/* frontend_index.scss - Same directory as view */
.Frontend_Index {  /* Matches @rsx_id */
    .content { padding: 20px; }
}

JAVASCRIPT

Auto-Initialization

class Frontend_Index {
    static async on_app_ready() {
        // DOM ready
    }

    static async on_jqhtml_ready() {
        // Components ready
    }
}

CRITICAL: JavaScript only executes when bundle rendered.


BUNDLE SYSTEM

One bundle per page required.

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
            ],
        ];
    }
}

Auto-compiles on page reload in development.

<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 Jqhtml_Component btn btn-primary">Save</button>

Interpolation: <%= escaped %> | <%== unescaped %> | <% javascript %>

🔴 CRITICAL on_load() Rules

ONLY modify this.data - NO other properties, NO DOM manipulation

class User_Card extends Jqhtml_Component {
    async on_load() {
        // ✅ ONLY this.data modifications
        this.data = await User_Controller.get_data({id: this.args.id});
        this.data.loaded = true;

        // ❌ FORBIDDEN
        // this.state = {loading: false};      // NO other properties
        // this.$id('title').text(...);        // NO DOM manipulation
        // $.ajax({url: '/api/...'});          // NO direct ajax
    }

    on_ready() {
        // ✅ DOM manipulation here
        this.$id('title').text(this.data.name);
    }
}

Lifecycle

  1. render → Template executes, this.data = {} (empty)
  2. on_render() → Hide uninitialized UI (sync)
  3. on_create() → Quick setup (sync)
  4. on_load() → Fetch data into this.data (async)
  5. on_ready() → DOM manipulation safe (async)

Double-render: If on_load() modifies this.data, component renders twice (empty → populated).

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
  • $id="name" → Scoped element ID
this.$id('button').on('click', ...);  // Access scoped element

Common Pitfalls

  1. <Define> IS the element - use tag="" attribute
  2. this.data starts empty {}
  3. ONLY modify this.data in on_load()
  4. Use Controller.method() not $.ajax()
  5. Blade components self-closing only
  6. on_create/render/destroy must be sync

Bundle Integration Required

{!! Frontend_Bundle::render() !!}  {{-- Required for JS --}}
<User_Card user_id="123" />       {{-- Now JS executes --}}

For advanced topics: php artisan rsx:man jqhtml


MODELS & DATABASE

No Mass Assignment

// ✅ CORRECT
$user = new User_Model();
$user->email = $email;
$user->save();

// ❌ WRONG
User_Model::create(['email' => $email]);

Enums

public static $enums = [
    'status_id' => [
        1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
    ],
];

// Usage
$user->status_id = User_Model::STATUS_ACTIVE;
echo $user->status_label;  // "Active"

Migrations

Forward-only, no rollbacks.

php artisan make:migration:safe create_users_table
php artisan migrate:begin
php artisan migrate
php artisan migrate:commit

AJAX ENDPOINTS

#[Ajax_Endpoint]
public static function get_data(Request $request, array $params = [])
{
    return ['success' => true, 'data' => ...];
}
const result = await Demo_Controller.get_data({user_id: 123});

Model Fetch

#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
    if (!RsxAuth::check()) return false;
    return static::find($id);
}
const user = await User_Model.fetch(1);

AUTHENTICATION

Always use RsxAuth, never Laravel Auth or $_SESSION.

RsxAuth::check();     // Is authenticated
RsxAuth::user();      // User model
RsxAuth::id();        // User ID

Sessions persist 365 days. Never implement "Remember Me".


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
rsx:man <topic>    # Documentation
db:query "SQL" --json

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.

Run rsx:check before commits. Enforces naming, prohibits animations on non-actionable elements.


MAIN_ABSTRACT MIDDLEWARE

Optional /rsx/main.php:

class Main extends Main_Abstract
{
    public function init() { }                   // Bootstrap once
    public function pre_dispatch($request, $params) { return null; } // Before routes
    public function unhandled_route($request, $params) { }          // 404s
}

KEY REMINDERS

  1. Fail loud - No silent failures
  2. Static by default - Unless instances needed
  3. Path-agnostic - Reference by name
  4. Bundles required - For JavaScript
  5. Use RsxAuth - Never Laravel Auth
  6. No mass assignment - Explicit only
  7. Forward migrations - No rollbacks
  8. Don't run rsx:clean - Cache auto-invalidates
  9. All routes need #[Auth] - No exceptions

GETTING HELP

php artisan rsx:man <topic>  # Detailed docs
php artisan list rsx          # All commands

Topics: bundle_api, jqhtml, routing, migrations, console_debug, model_fetch, vs_code_extension, deployment, framework_divergences

Remember: RSpade prioritizes simplicity and rapid development. When in doubt, choose the straightforward approach.

CRITICAL RULES (EXPANDED)

Git Working Directory Rules

# All code changes in /var/www/html/rsx
cd /var/www/html
php artisan rsx:check   # Run commands from project root
git add -A              # Stage from project root
git commit -m "Snapshot: description"

When rsx:clean Actually Fails

The Cost of Running rsx:clean:

  • Entire cache is destroyed (not just invalidated)
  • 30-60 second rebuild on next request
  • Timeouts during development
  • No benefit (changes already visible without clearing)

NAMING CONVENTIONS (EXPANDED)

Class Naming Philosophy

All class names must identify:

  1. Module (e.g., Frontend_, Backend_)
  2. Feature (e.g., Users, Settings)
  3. Type (e.g., _Controller, _Model)

Example: Frontend_Users_Index_Controller → Module: Frontend, Feature: Users, Subfeature: Index, Type: Controller

DIRECTORY STRUCTURE (EXPANDED)

/var/www/html/                     # Project root
├── rsx/                           # YOUR APPLICATION CODE
│   ├── app/                       # Application modules
│   │   ├── frontend/              # Public website (/)
│   │   ├── backend/               # Admin panel (/admin)
│   │   └── api/                   # API endpoints
│   ├── lib/                       # Shared libraries
│   ├── models/                    # Database models
│   ├── public/                    # Static files (web-accessible)
│   │   └── public_ignore.json     # Patterns blocked from HTTP
│   ├── resource/                  # Framework-ignored files
│   │   └── config/                # Configuration overrides
│   │       └── rsx.php            # Merged with /system/config/rsx.php
│   ├── theme/                     # Global theme assets
│   │   ├── variables.scss
│   │   ├── layouts/
│   │   └── components/
│   ├── main.php                   # App-wide middleware (optional)
│   └── permission.php             # Authorization methods
│
└── system/                        # FRAMEWORK CODE (do not modify)
    ├── app/RSpade/                # RSpade framework runtime
    ├── config/                    # Framework configuration
    ├── storage/                   # Build artifacts and caches
    └── artisan                    # Laravel's artisan CLI

Path-Agnostic Implications

  • Move files freely - Location doesn't matter
  • No path management - No use statements needed (auto-generated)
  • Name-based referencing - Just use the class name
  • Uniqueness required - Each class name must be unique

CONFIGURATION (EXPANDED)

Merging Example

// Framework: /system/config/rsx.php
'bundle_aliases' => ['core' => CoreBundle::class]

// User: /rsx/resource/config/rsx.php  
'bundle_aliases' => ['my-app' => MyAppBundle::class]

// Result: Both present
config('rsx.bundle_aliases'); // ['core' => CoreBundle, 'my-app' => MyAppBundle]

ROUTING & CONTROLLERS (EXPANDED)

Route Parameters

Use :param syntax (not Laravel's {param}):

#[Route('/users/:id')]
public static function show_user(Request $request, array $params = [])
{
    $user_id = $params['id'];  // Route parameter
    $tab = $params['tab'];      // Query string parameter
}

Controller-Wide Auth

#[Auth('Permission::authenticated()')]
public static function pre_dispatch(Request $request, array $params = [])
{
    return null;  // Continue to route
}

Custom Permission Methods

Add to /rsx/permission.php:

class Permission extends Permission_Abstract
{
    public static function has_role(Request $request, array $params, string $role): mixed
    {
        if (!RsxAuth::check()) return false;
        return RsxAuth::user()->role === $role;
    }
}

BLADE & VIEWS (EXPANDED)

View Functions

// ✅ GOOD
return rsx_view('Frontend_Index', ['data' => $data]);

// ❌ AVOID
return view('rsx.app.frontend.frontend_index', ['data' => $data]);

Why SCSS Class Wrapping

  • Scoping - Prevents style conflicts between views
  • Specificity - Styles only apply when that view is rendered
  • Organization - Clear ownership of styles

JAVASCRIPT (EXPANDED)

JavaScript File Structure Rules

// ✅ ALLOWED
const API_URL = "https://api.example.com";
function processData(data) { /* ... */ }

// ❌ NOT ALLOWED
const RANDOM = Math.random();  // Function call
let variable = 42;             // Use const

BUNDLE SYSTEM (EXPANDED)

Unified Include System

The include array auto-detects:

  1. Module Aliases - jquery, lodash, bootstrap5, jqhtml
  2. Bundle Aliases - From config('rsx.bundle_aliases')
  3. Bundle Classes - Full class names like Shared_Bundle
  4. Files - Specific file paths
  5. Directories - All files in directory

Include Order Matters

'include' => [
    'rsx/theme/variables.scss',  // Variables first
    'rsx/theme/components',      // Components using variables
    'rsx/app/frontend',          // Pages using components
],

JQHTML COMPONENTS (EXPANDED)

Incremental Scaffolding

Undefined components work immediately - they render as div with the component name as a class.

<Dashboard>
  <Stats_Panel />
  <Recent_Activity />
</Dashboard>

Why on_load() Restrictions Exist

  • this.data triggers automatic re-render when modified
  • DOM may not be fully initialized during on_load()
  • Setting other properties breaks the component lifecycle

Execution Order

  • Top-down: render, on_render (parent before children)
  • Bottom-up: on_create, on_load, on_ready (children before parent)
  • Parallel: Siblings at same depth process simultaneously during on_load()

this.args vs this.data

// <User_Card $user_id="123" $theme="dark" />
class User_Card extends Jqhtml_Component {
    async on_load() {
        // this.args.user_id = "123" (from attribute)
        // this.data starts as {} (empty)
        this.data = await User_Controller.get_user({id: this.args.user_id});
    }
}

MODELS & DATABASE (EXPANDED)

Model Definition

class User_Model extends Rsx_Model_Abstract
{
    protected $table = 'users';
    protected $fillable = [];  // Always empty - no mass assignment

    public static $enums = [
        'status_id' => [
            1 => ['constant' => 'STATUS_ACTIVE', 'label' => 'Active'],
        ],
    ];
}

Migration Example

public function up()
{
    DB::statement("
        CREATE TABLE articles (
            id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
            title VARCHAR(255) NOT NULL,
            status_id TINYINT(1) NOT NULL DEFAULT 1,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_status_id (status_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    ");
}

AJAX ENDPOINTS (EXPANDED)

Creating Detailed Endpoints

#[Auth('Permission::anybody()')]
#[Ajax_Endpoint]
public static function get_user_data(Request $request, array $params = [])
{
    $user = User_Model::find($params['user_id']);
    if (!$user) {
        return ['success' => false, 'error' => 'User not found'];
    }
    return ['success' => true, 'user' => $user];
}

Model Fetch Security

#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
    if (!RsxAuth::check()) return false;
    $product = static::find($id);
    if ($product && !Permission::can_view_product($product)) {
        return false;
    }
    return $product;
}

AUTHENTICATION (EXPANDED)

RsxAuth Full API

use App\RSpade\Core\Auth\RsxAuth;

if (RsxAuth::check()) {
    $user = RsxAuth::user();     // User model or null
    $user_id = RsxAuth::id();    // User ID or null
    $session = RsxAuth::session(); // Session model or null
}

RsxAuth::login($user);
RsxAuth::logout();

Where to Check Authentication

  1. Controller pre_dispatch() - Protects all routes in controller
  2. /rsx/main.php pre_dispatch() - Global/pattern-based protection
  3. Route method - Individual route protection

JAVASCRIPT DECORATORS (EXPANDED)

Built-in @mutex Decorator

class DataService {
    @mutex
    async save_data() {
        // Only one save_data() call per instance at a time
    }

    @mutex('critical_operation')
    async update_shared_resource() {
        // Only one update across all instances
    }
}

COMMANDS (EXPANDED)

Module Hierarchy

Command Creates Route
rsx:app:module:create Module + index /name
rsx:app:module:feature:create Feature /m/f
rsx:app:submodule:create Submodule + layout /m/s
rsx:app:component:create --name=x jqhtml component N/A

Migration Commands

make:migration:safe name       # Create whitelisted migration
migrate:begin                  # Start snapshot session
migrate                        # Run migrations
migrate:commit                 # Commit changes

DEBUGGING TOOLS (EXPANDED)

rsx:debug - Route Testing

php artisan rsx:debug /dashboard
php artisan rsx:debug /dashboard --user=1          # Test as specific user
php artisan rsx:debug /page --expect-element="#btn" # Verify element exists

console_debug() - Channel-Based Logging

console_debug("AUTH", "Login attempt", $user->id);
console_debug('AJAX', 'Request sent', url, params);

CODE QUALITY (EXPANDED)

Key Quality Rules

  • NoAnimationsRule - No CSS animations/hover on non-actionable elements
  • DuplicateCaseFilesRule - Detect same-name different-case files (critical)
  • MassAssignmentRule - Prohibit $fillable arrays

MAIN_ABSTRACT MIDDLEWARE (EXPANDED)

Execution Order

  1. Main::init() - Once at bootstrap
  2. Main::pre_dispatch() - Before every route
  3. Controller::pre_dispatch() - Before specific controller
  4. Route method - If both return null
  5. Main::unhandled_route() - If no route matches

FRAMEWORK DIVERGENCES FROM LARAVEL

CRITICAL: RSpade diverges significantly from Laravel. Key differences:

  • No Eloquent ORM features - Use explicit field assignment only
  • No mass assignment - $fillable arrays always empty
  • No resource routes - Only explicit GET/POST routes
  • No middleware classes - Use Main_Abstract and pre_dispatch()
  • No service providers - Framework handles all bootstrapping
  • No facades - Direct class access only
  • No dependency injection - Static methods preferred

When Laravel documentation conflicts with this guide, this guide takes precedence.

ATTRIBUTES PHILOSOPHY

PHP attributes are metadata only - never define actual attribute classes.

#[Route('/')]           // Framework reads via reflection
#[Auth('...')]          // Never create Route or Auth classes
#[Ajax_Endpoint]        // Just metadata markers

Add stubs to .vscode/attribute-stubs.php for IDE support, but never create the actual classes.

SILENT SUCCESS PHILOSOPHY

Supplemental operations succeed silently - Unix principle "no news is good news".

  • Primary operations report success (what user explicitly requested)
  • Supplemental checks/validations are silent when successful
  • Maintenance operations quiet unless they fail
// Primary operation - reports success
$this->info("Migration completed successfully");

// Supplemental operation - silent on success
$this->validate_cache();  // Only outputs if problem found

BROWSER ERROR LOGGING

JavaScript errors auto-captured when LOG_BROWSER_ERRORS=true in .env.

// Manual error logging
Debugger.log_error('Component failed', error, {user_id: 123});

// Automatic capture of uncaught errors
// Batched and rate-limited to prevent spam

REFACTORING COMMANDS

# Rename PHP classes across entire codebase
php artisan rsx:refactor:rename_php_class Old_Class_Name New_Class_Name

# Automatically updates:
# - All PHP file references
# - All Blade file references  
# - Renames the file itself

BUNDLE include_routes

For scanning additional directories for routes:

class Admin_Bundle extends Rsx_Bundle_Abstract
{
    public static function define(): array
    {
        return [
            'include' => [...],
            'include_routes' => [
                'rsx/lib/admin_tools',  // Scan for #[Route] attributes
            ],
        ];
    }
}

DEVELOPMENT VS PRODUCTION

Development (auto-detected):

  • Cache auto-invalidates on file changes
  • Detailed error messages with stack traces
  • Source maps enabled for debugging
  • Bundle recompilation on every request

Production:

  • Pre-compiled bundles from cache
  • Minimal error messages
  • No source maps
  • Optimized performance

Never manually switch modes - framework auto-detects based on environment.