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

13 KiB
Executable File

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.