Add datetime system (Rsx_Time/Rsx_Date) and .expect file documentation system

Tighten CLAUDE.dist.md for LLM audience - 15% size reduction
Add Repeater_Simple_Input component for managing lists of simple values
Add Polymorphic_Field_Helper for JSON-encoded polymorphic form fields
Fix incorrect data-sid selector in route-debug help example
Fix Form_Utils to use component.$sid() instead of data-sid selector
Add response helper functions and use _message as reserved metadata key

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-24 21:47:53 +00:00
parent eb3ccd722d
commit 1b57ec2785
76 changed files with 4778 additions and 289 deletions

1336
docs/CLAUDE.archive.12.24.25.md Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@ When editing /var/www/html/CLAUDE.md:
**FORBIDDEN** (unless explicitly instructed):
- `npm run compile/build` - Don't exist
- `bin/publish` - For releases, not testing
- `rsx:bundle:compile` / `rsx:manifest:build` - Automatic
- `rsx:bundle:compile` / `rsx:manifest:build` / `rsx:clean` - Automatic
- ANY "build", "compile", or "publish" command
Edit → Save → Refresh browser → Changes live (< 1 second)
@@ -62,8 +62,6 @@ Edit → Save → Refresh browser → Changes live (< 1 second)
php artisan rsx:framework:pull # User-initiated only
```
Updates take 2-5 minutes. Includes code pull, manifest rebuild, bundle recompilation. Only run when requested by user.
### Fail Loud - No Silent Fallbacks
**ALWAYS fail visibly.** No redundant fallbacks, silent failures, or alternative code paths.
@@ -132,14 +130,6 @@ cp system/app/RSpade/Core/Models/User_Model.php rsx/models/user_model.php
Details: `php artisan rsx:man class_override`
### DO NOT RUN `rsx:clean`
**RSpade's cache auto-invalidates on file changes.** Running `rsx:clean` causes 30-60 second rebuilds with zero benefit.
**When to use**: Only on catastrophic corruption, after framework updates (automatic), or when explicitly instructed.
**Correct workflow**: Edit → Save → Reload browser → See changes (< 1 second)
### Trust Code Quality Rules
Each `rsx:check` rule has remediation text that tells AI assistants exactly what to do:
@@ -258,12 +248,8 @@ public static function pre_dispatch(Request $request, array $params = []) {
return null;
}
// Public endpoints: add @auth-exempt to class docblock
/** @auth-exempt Public route */
```
**Code quality**: PHP-AUTH-01 rule verifies auth checks exist. Use `@auth-exempt` for public routes.
### Type-Safe URLs
**MANDATORY**: All URLs must be generated using `Rsx::Route()` - hardcoded URLs are forbidden.
@@ -272,10 +258,10 @@ public static function pre_dispatch(Request $request, array $params = []) {
// PHP - Controller (defaults to 'index' method)
Rsx::Route('User_Controller')
// PHP - Controller with explicit method
// 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
// 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
@@ -284,16 +270,9 @@ Rsx.Route('User_Controller')
Rsx.Route('User_Controller::show', 123);
Rsx.Route('Login_Controller::logout', {redirect: '/dashboard'});
```
**Signature**: `Rsx::Route($action, $params = null)` / `Rsx.Route(action, params = null)`
- `$action` - Controller class, SPA action, or "Class::method" (defaults to 'index' if no `::` present)
- `$params` - Integer sets 'id', array/object provides named params
- **Unimplemented routes**: Prefix method with `#``Rsx::Route('Feature::#index')` generates `href="#"` and bypasses validation
**Query Parameters**: Extra params become query string - automatically URL-encoded
**Enforcement**: `rsx:check` will flag hardcoded URLs like `/login` or `/logout?redirect=...`
and require you to use `Rsx::Route()`. Do it right the first time to avoid rework.
**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.
---
@@ -399,29 +378,24 @@ Pattern: `/rsx/app/(module)/(feature)/`
Details: `php artisan rsx:man spa`
### View Action Pattern (Loading Data)
### View Action Pattern
For SPA actions that load data (view/edit CRUD pages), use the three-state pattern:
Three-state pattern for data-loading actions:
```javascript
on_create() {
this.data.record = { name: '' }; // Stub prevents undefined errors
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;
}
try { this.data.record = await Controller.get({id: this.args.id}); }
catch (e) { this.data.error_data = e; }
this.data.loading = false;
}
```
Template uses three states: `<Loading_Spinner>``<Universal_Error_Page_Component>` → content.
**Details**: `php artisan rsx:man view_action_patterns`
Template: `<Loading_Spinner>``<Universal_Error_Page_Component>` → content. Details: `rsx:man view_action_patterns`
---
@@ -446,42 +420,27 @@ The process involves creating Action classes with @route decorators and converti
### SCSS Component-First Architecture
**Philosophy**: Every styled element is a component. If it needs custom styles, give it a name, a jqhtml definition, and scoped SCSS. This eliminates CSS spaghetti - generic classes overriding each other unpredictably across files.
SCSS in `rsx/app/` and `rsx/theme/components/` must wrap in a single component class matching the jqhtml/blade file. Components auto-render with `class="Component_Name"` on root. `rsx/lib/` is non-visual. `rsx/theme/` (outside components/) holds primitives, variables, Bootstrap overrides.
**Recognition**: When building a page, ask: "Is this structure unique, or a pattern?" A datagrid page with toolbar, tabs, filters, and search is a *pattern* - create `Datagrid_Card` once with slots, use it everywhere. A one-off project dashboard is *unique* - create `Project_Dashboard` for that page. If you're about to copy-paste structural markup, stop and extract a component.
**BEM**: Child classes use component's exact name as prefix. `.Component_Name { &__element }` → HTML: `<div class="Component_Name__element">` (no kebab-case).
**Composition**: Use slots to separate structure from content. The component owns layout and styling; pages provide the variable parts via slots. This keeps pages declarative and components reusable.
**Enforcement**: SCSS in `rsx/app/` and `rsx/theme/components/` must wrap in a single component class matching the jqhtml/blade file. This works because all jqhtml components, SPA actions/layouts, and Blade views with `@rsx_id` automatically render with `class="Component_Name"` on their root element. `rsx/lib/` is for non-visual plumbing (validators, utilities). `rsx/theme/` (outside components/) holds primitives, variables, Bootstrap overrides.
**BEM Child Classes**: When using BEM notation, child element classes must use the component's exact class name as prefix. SCSS `.Component_Name { &__element }` compiles to `.Component_Name__element`, so HTML must match: `<div class="Component_Name__element">` not `<div class="component-name__element">`. No kebab-case conversion.
**Variables**: Define shared values (colors, spacing, border-radius) in `rsx/theme/variables.scss` or similar. These must be explicitly included before directory includes in bundle definitions. Component-local variables can be defined within the scoped rule.
**Supplemental files**: Multiple SCSS files can target the same component (e.g., breakpoint-specific styles) if a primary file with matching filename exists.
**Variables**: `rsx/theme/variables.scss` - must be included before directory includes in bundles. Multiple SCSS files can target same component if primary file exists.
Details: `php artisan rsx:man scss`
### Responsive Breakpoints
RSX replaces Bootstrap's default breakpoints (xs/sm/md/lg/xl/xxl) with semantic device names.
RSX replaces Bootstrap breakpoints with semantic names. **Bootstrap defaults (col-md-6, d-lg-none) do NOT work.**
**Tier 1 - Semantic**:
- `mobile`: 0 - 1023px (phone + tablet)
- `desktop`: 1024px+
| Tier 1 | Range | Tier 2 | Range |
|--------|-------|--------|-------|
| `mobile` | 0-1023px | `phone` | 0-799px |
| `desktop` | 1024px+ | `tablet` | 800-1023px |
| | | `desktop-sm/md/lg` | 1024+ |
**Tier 2 - Granular**:
- `phone`: 0 - 799px | `tablet`: 800 - 1023px | `desktop-sm`: 1024 - 1699px | `desktop-md`: 1700 - 2199px | `desktop-lg`: 2200px+
**SCSS Mixins**: `@include mobile { }`, `@include desktop { }`, `@include phone { }`, `@include tablet { }`, `@include desktop-sm { }`, etc.
**Bootstrap Classes**: `.col-mobile-6`, `.col-desktop-4`, `.d-mobile-none`, `.d-tablet-block`, `.col-phone-12 .col-tablet-6 .col-desktop-sm-4`
**Utility Classes**: `.mobile-only`, `.desktop-only`, `.phone-only`, `.hide-mobile`, `.hide-tablet`
**Note**: Bootstrap's default classes like `.col-md-6` or `.d-lg-none` do NOT work - use the RSX breakpoint names instead.
**JS Detection**: `Responsive.is_mobile()`, `Responsive.is_desktop()` (Tier 1 - broad); `Responsive.is_phone()`, `Responsive.is_tablet()`, `Responsive.is_desktop_sm()`, `Responsive.is_desktop_md()`, `Responsive.is_desktop_lg()` (Tier 2 - specific ranges)
**SCSS**: `@include mobile { }`, `@include desktop { }`, `@include phone { }`, etc.
**Classes**: `.col-mobile-6`, `.d-desktop-none`, `.mobile-only`, `.hide-tablet`
**JS**: `Responsive.is_mobile()`, `Responsive.is_phone()`, `Responsive.is_desktop_sm()`, etc.
Details: `php artisan rsx:man responsive`
@@ -519,15 +478,9 @@ Use when data doesn't belong in DOM attributes. Multiple calls merge together.
---
## JAVASCRIPT
**CRITICAL**: JavaScript only executes when bundle rendered. See "JavaScript for Blade Pages" in BLADE & VIEWS section for the `on_app_ready()` pattern.
---
## BUNDLE SYSTEM
**One bundle per page required.** Compiles JS/CSS automatically on request - no manual build steps.
**One bundle per module (rsx/app/(module)).** Compiles JS/CSS automatically on request - no manual build steps.
```php
class Frontend_Bundle extends Rsx_Bundle_Abstract
@@ -539,7 +492,8 @@ class Frontend_Bundle extends Rsx_Bundle_Abstract
'jquery', // Required
'lodash', // Required
'rsx/theme/variables.scss', // Order matters
'rsx/app/frontend', // Directory
'rsx/theme', // Everything else from theme - but variables.scss will be first
'rsx/app/frontend', // Directory -
'rsx/models', // For JS stubs
],
];
@@ -576,48 +530,30 @@ For mechanical thinkers who see structure, not visuals. Write `<User_Card>` not
```
**Interpolation**: `<%= escaped %>` | `<%!= unescaped %>` | `<% javascript %>`
**Conditional Attributes** (v2.2.162+): Apply attributes conditionally using `<% if (condition) { %>attr="value"<% } %>`
directly in attribute context. Works with static values, interpolations, and multiple conditions per element.
Example: `<input <% if (this.args.required) { %>required="required"<% } %> />`
**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 Needed)
For simple components without external data or complex state, write JS directly in the template:
### Simple Components (No JS File)
```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>
<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)
Use inline JS for simple transformations/handlers. Create .js file when JS overwhelms template or needs external data.
### State Management Rules (ENFORCED)
@@ -665,90 +601,43 @@ class Toggle_Button extends Component {
### Lifecycle
1. **on_create()** → Setup default state BEFORE template (sync)
2. **render** → Template executes with initialized state
1. **on_create()** → Setup defaults (sync) - `this.data.rows = []; this.data.loading = true;`
2. **render** → Template executes
3. **on_render()** → Hide uninitialized UI (sync)
4. **on_load()** → Fetch data into `this.data` (async)
5. **on_ready()** → DOM manipulation safe (async)
**on_create() now runs first** - Initialize `this.data` properties here so templates can safely reference them:
If `on_load()` modifies `this.data`, component renders twice (defaults → populated).
```javascript
on_create() {
this.data.rows = []; // Prevents "not iterable" errors
this.data.loading = true; // Template can check loading state
}
```
### Component API
**Double-render**: If `on_load()` modifies `this.data`, component renders twice (defaults → populated).
### Component API - CRITICAL FOR LLM AGENTS
This section clarifies common misunderstandings. Read carefully.
**DOM Access Methods:**
**DOM Access:**
| Method | Returns | Purpose |
|--------|---------|---------|
| `this.$` | jQuery | Root element of component (stable, survives redraws) |
| `this.$sid('name')` | jQuery | Child element with `$sid="name"` (always returns jQuery, even if empty) |
| `this.sid('name')` | Component or null | Child component instance (null if not found or not a component) |
| `this.$.find('.class')` | jQuery | Standard jQuery find (use when `$sid` isn't appropriate) |
**WRONG:** `this.$el` - This does not exist. Use `this.$`
**The reload() Paradigm - MANDATORY:**
| `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()
render() = template redraw only (NO on_ready)
reload() = on_load() → render() → on_ready() ← ALWAYS USE THIS
render() = template only (no on_ready) ← NEVER USE
```
**LLM agents must ALWAYS use `reload()`, NEVER call `render()` directly.**
When you need to refresh a component after a mutation (add, edit, delete), call `this.reload()`. Yes, this makes another server call via `on_load()`. This is intentional. The extra round-trip is acceptable - our server is fast and the paradigm simplicity is worth it.
**WRONG approach (do NOT do this):**
After mutations, call `this.reload()` - the server round-trip is intentional:
```javascript
// ❌ BAD - Trying to be "efficient" by skipping server round-trip
async add_item() {
const new_item = await Controller.add({name: 'Test'});
this.data.items.push(new_item); // ERROR: Cannot modify this.data outside on_load
this.render(); // WRONG: Event handlers will break, on_ready won't run
}
```
**CORRECT approach:**
```javascript
// ✅ GOOD - Clean, consistent, reliable
async add_item() {
await Controller.add({name: 'Test'});
this.reload(); // Calls on_load() to refresh this.data, then on_ready() for handlers
this.reload(); // Refreshes this.data via on_load(), reattaches handlers via on_ready()
}
```
**Event Handlers - Set Up in 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.
Event handlers must be registered in `on_ready()`. Since `on_ready()` runs after every `reload()`, handlers automatically reattach when the DOM is redrawn.
**this.data rules (enforced):** Writable only in `on_create()` (defaults) and `on_load()` (fetched data). Read-only elsewhere.
```javascript
on_ready() {
this.$sid('save_btn').click(() => this.save());
this.$sid('delete_btn').click(() => this.delete());
}
```
**WRONG:** Event delegation to avoid `reload()`. If you find yourself writing `this.$.on('click', '[data-sid="btn"]', handler)` to "survive" render calls, you're doing it wrong. Use `reload()` and let `on_ready()` reattach handlers.
**this.data Modification Rules (ENFORCED):**
- `on_create()`: Set defaults only (e.g., `this.data.items = []`)
- `on_load()`: Fetch and assign from server (e.g., `this.data.items = await Controller.list()`)
- **Everywhere else**: Read-only. Attempting to modify `this.data` outside these methods throws an error.
**on_render() - LLM Should Not Use:**
`on_render()` exists for human developers doing performance optimization. LLM agents should pretend it doesn't exist. Use `on_ready()` for all post-render DOM work.
**on_render():** Ignore - use `on_ready()` for post-render work.
### Loading Pattern
@@ -824,26 +713,15 @@ this.$sid('result_container').component('My_Component', {
</Dashboard>
```
### Key Pitfalls (ABSOLUTE RULES)
### Key Pitfalls
1. `<Define>` IS the element - use `tag=""` attribute
2. `this.data` starts empty `{}` - MUST set defaults in `on_create()`
3. ONLY modify `this.data` in `on_create()` and `on_load()` (enforced by framework)
4. `on_load()` can ONLY access `this.args` and `this.data` (no DOM, no `this.state`)
5. Use `this.state = {}` in `on_create()` for UI state (not from Ajax)
6. Use `this.args` for reload parameters, call `reload()` to re-fetch
7. Use `Controller.method()` not `$.ajax()` - PHP methods with #[Ajax_Endpoint] auto-callable from JS
8. `on_create/render/stop` must be sync
9. `this.sid()` returns component instance, `$(selector).component()` converts jQuery to component
### Bundle Integration Required
```blade
{!! Frontend_Bundle::render() !!} {{-- Required for JS --}}
<User_Card user_id="123" /> {{-- Now JS executes --}}
```
For advanced topics: `php artisan rsx:man jqhtml`
- `<Define>` IS the element - use `tag=""` attribute
- `this.data` starts `{}` - set defaults in `on_create()`
- `this.data` writable only in `on_create()` and `on_load()`
- `on_load()`: only `this.args` and `this.data` (no DOM, no `this.state`)
- `this.state` for UI state, `this.args` + `reload()` for refetch
- `Controller.method()` not `$.ajax()` - #[Ajax_Endpoint] auto-callable
- `on_create/render/stop` sync; `this.sid()` → component, `$(el).component()` → component
---
@@ -952,55 +830,69 @@ Details: `php artisan rsx:man polymorphic`
## MODALS
**Basic dialogs**:
### Built-in Dialog Types
| Method | Returns | Description |
|--------|---------|-------------|
| `Modal.alert(body)` | `void` | Simple notification |
| `Modal.alert(title, body, buttonLabel?)` | `void` | Alert with title |
| `Modal.confirm(body)` | `boolean` | Yes/no confirmation |
| `Modal.confirm(title, body, confirmLabel?, cancelLabel?)` | `boolean` | Confirmation with labels |
| `Modal.prompt(body)` | `string\|false` | Text input |
| `Modal.prompt(title, body, default?, multiline?)` | `string\|false` | Prompt with options |
| `Modal.select(body, options)` | `string\|false` | Dropdown selection |
| `Modal.select(title, body, options, default?, placeholder?)` | `string\|false` | Select with options |
| `Modal.error(error, title?)` | `void` | Error with red styling |
| `Modal.unclosable(title, body)` | `void` | Modal user cannot close |
```javascript
await Modal.alert("File saved");
if (await Modal.confirm("Delete?")) { /* confirmed */ }
let name = await Modal.prompt("Enter name:");
const name = await Modal.prompt("Enter name:");
const choice = await Modal.select("Choose:", [{value: 'a', label: 'A'}, {value: 'b', label: 'B'}]);
await Modal.error("Something went wrong");
```
**Form modals**:
### Form Modals
```javascript
const result = await Modal.form({
title: "Edit User",
component: "User_Form",
component_args: {data: user},
on_submit: async (form) => {
const values = form.vals();
const response = await User_Controller.save(values);
const response = await User_Controller.save(form.vals());
if (response.errors) {
Form_Utils.apply_form_errors(form.$, response.errors);
return false; // Keep open
}
return response.data; // Close and return
}
});
```
**Requirements**: Form component must implement `vals()` and include `<div $sid="error_container"></div>`.
Form component must implement `vals()` and include `<div $sid="error_container"></div>`.
### Modal Classes
For complex/reusable modals, create dedicated classes:
**Modal Classes** (for complex/reusable modals):
```javascript
// Define modal class
class Add_User_Modal extends Modal_Abstract {
static async show() {
const result = await Modal.form({...});
return result || false;
return await Modal.form({...}) || false;
}
}
// Use from page JS
// Usage
const user = await Add_User_Modal.show();
if (user) {
// Orchestrate post-modal actions
grid.reload();
await Next_Modal.show(user.id);
}
```
Pattern: Extend `Modal_Abstract`, implement static `show()`, return data or `false`. Page JS orchestrates flow, modal classes encapsulate UI.
Pattern: Extend `Modal_Abstract`, implement static `show()`, return data or `false`.
Details: `php artisan rsx:man modals`
@@ -1222,16 +1114,6 @@ Details: `php artisan rsx:man model_fetch`
---
## BROWSER STORAGE
**Rsx_Storage** - Scoped sessionStorage/localStorage with automatic fallback and quota management. All keys automatically scoped by session, user, site, and build. Gracefully handles unavailable storage and quota exceeded errors. Storage is volatile - use only for non-critical data (caching, UI state, transient messages).
`Rsx_Storage.session_set(key, value)` / `Rsx_Storage.session_get(key)` / `Rsx_Storage.local_set(key, value)` / `Rsx_Storage.local_get(key)`
Details: `php artisan rsx:man storage`
---
## AUTHENTICATION
**Always use Session** - Static methods only. Never Laravel Auth or $_SESSION.
@@ -1249,6 +1131,51 @@ 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)
### Rsx_Time - Moments in Time
```php
use App\RSpade\Core\Time\Rsx_Time;
Rsx_Time::now(); // Current time in user's timezone
Rsx_Time::now_iso(); // ISO 8601 format: 2025-12-24T15:30:00-06:00
Rsx_Time::format($datetime); // "Dec 24, 2025 3:30 PM"
Rsx_Time::format_short($datetime); // "Dec 24, 3:30 PM"
Rsx_Time::to_database($datetime); // UTC for storage
Rsx_Time::get_user_timezone(); // User's timezone or default
```
```javascript
Rsx_Time.now(); // Current moment (timezone-aware)
Rsx_Time.format(datetime); // Formatted for display
Rsx_Time.relative(datetime); // "2 hours ago", "in 3 days"
```
### Rsx_Date - Calendar Dates
```php
use App\RSpade\Core\Time\Rsx_Date;
Rsx_Date::today(); // "2025-12-24" (user's timezone)
Rsx_Date::format($date); // "Dec 24, 2025"
Rsx_Date::is_today($date); // Boolean
Rsx_Date::is_past($date); // Boolean
```
**Key Principle**: Functions throw if wrong type passed (datetime to date function or vice versa).
### Server Time Sync
Client time syncs automatically via rsxapp data on page load and AJAX responses. No manual sync required.
### User Timezone
Stored in `login_users.timezone` (IANA format). Falls back to `config('rsx.datetime.default_timezone')`.
Details: `php artisan rsx:man time`
---
## JAVASCRIPT DECORATORS
```javascript
@@ -1285,30 +1212,19 @@ db:query "SQL" --json
### Testing Routes
**`rsx:debug /path`** - Preferred method for testing routes
Uses Playwright to render the page and show rendered output, JavaScript errors, and console messages.
**`rsx:debug /path`** - Uses Playwright to render pages with full JS execution.
```bash
rsx:debug /clients # Test route
rsx:debug /dashboard --user=1 # Simulate authenticated user
rsx:debug /contacts --console # Show console.log output
rsx:debug /page --screenshot-path=/tmp/page.png --screenshot-width=mobile # Capture screenshot
rsx:debug /page --dump-dimensions=".card" # Add position/size data attributes to elements
rsx:debug /path --help # Show all options
# Simulate user interactions with --eval (executes before DOM capture)
rsx:debug /contacts --user=1 --eval="$('.page-link[data-page=\"2\"]').click(); await sleep(2000)"
rsx:debug /form --eval="$('#name').val('test'); $('form').submit(); await sleep(500)"
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
```
Screenshot presets: mobile, iphone-mobile, tablet, desktop-small, desktop-medium, desktop-large
Options: `--user=ID`, `--console`, `--screenshot-path`, `--screenshot-width=mobile|tablet|desktop-*`, `--dump-dimensions=".selector"`, `--eval="js"`, `--help`
The `--eval` option runs JavaScript after page load but before DOM capture. Use `await sleep(ms)` to wait for async operations. This is powerful for testing pagination, form submissions, and other interactive behavior.
Use this instead of manually browsing, especially for SPA pages and Ajax-heavy features.
**CRITICAL: SPA routes ARE server routes.** The server knows all SPA routes. `rsx:debug` uses Playwright to fully render pages including all JavaScript and SPA navigation. If you get a 404, the route genuinely doesn't exist - check your URL pattern and route definitions. Never dismiss 404s as "SPA routes can't be tested server-side" - this analysis is incorrect.
**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".
### Debugging
@@ -1340,21 +1256,6 @@ Run `rsx:check` before commits. Enforces naming, prohibits animations on non-act
---
## MAIN_ABSTRACT MIDDLEWARE
Optional `/rsx/main.php`:
```php
class Main extends Main_Abstract
{
public function init() { } // Bootstrap once
public function pre_dispatch($request, $params) { return null; } // Before routes
public function unhandled_route($request, $params) { } // 404s
}
```
---
## KEY REMINDERS
1. **Fail loud** - No silent failures
@@ -1382,14 +1283,4 @@ php artisan list rsx # All commands
## PROJECT DOCUMENTATION
Project-specific technical documentation lives in `/rsx/resource/man/`. These are man-page-style text files documenting features specific to your application that build on or extend the framework.
**When to create a project man page**:
- Feature has non-obvious implementation details
- Multiple components interact in ways that need explanation
- Configuration options or patterns need documentation
- AI agents or future developers need reference material
**Format**: Plain text files (`.txt`) following Unix man page conventions. See `/rsx/resource/man/CLAUDE.md` for writing guidelines.
**Remember**: RSpade prioritizes simplicity and rapid development. When in doubt, choose the straightforward approach.
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.