Fix code quality violations for publish
Remove unused blade settings pages not linked from UI Convert remaining frontend pages to SPA actions Convert settings user_settings and general to SPA actions Convert settings profile pages to SPA actions Convert contacts and projects add/edit pages to SPA actions Convert clients add/edit page to SPA action with loading pattern Refactor component scoped IDs from $id to $sid Fix jqhtml comment syntax and implement universal error component system Update all application code to use new unified error system Remove all backwards compatibility - unified error system complete Phase 5: Remove old response classes Phase 3-4: Ajax response handler sends new format, old helpers deprecated Phase 2: Add client-side unified error foundation Phase 1: Add server-side unified error foundation Add unified Ajax error response system with constants 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,63 @@ class Frontend_Clients_Edit {
|
||||
|
||||
---
|
||||
|
||||
## 2025-11-20: Unified Ajax Error Response System
|
||||
|
||||
**Component**: Ajax error handling
|
||||
**Impact**: Medium - Existing error handling code continues to work, but new pattern recommended
|
||||
|
||||
### Change
|
||||
|
||||
Replaced fragmented error response helpers (`response_form_error()`, `response_auth_required()`, etc.) with unified `response_error()` function using constants for error codes. Same constant names on server and client for zero mental translation.
|
||||
|
||||
### Previous Pattern
|
||||
|
||||
```php
|
||||
// Server - different functions for each error type
|
||||
return response_form_error('Validation failed', ['email' => 'Invalid']);
|
||||
return response_not_found('Project not found');
|
||||
return response_unauthorized('Permission denied');
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Client - string matching with different names
|
||||
if (error.type === 'form_error') { ... }
|
||||
if (error.type === 'not_found') { ... }
|
||||
```
|
||||
|
||||
### New Pattern
|
||||
|
||||
```php
|
||||
// Server - single function with constants
|
||||
return response_error(Ajax::ERROR_VALIDATION, ['email' => 'Invalid']);
|
||||
return response_error(Ajax::ERROR_NOT_FOUND, 'Project not found');
|
||||
return response_error(Ajax::ERROR_UNAUTHORIZED); // Auto-message
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Client - same constant names
|
||||
if (e.code === Ajax.ERROR_VALIDATION) { ... }
|
||||
if (e.code === Ajax.ERROR_NOT_FOUND) { ... }
|
||||
```
|
||||
|
||||
### Migration Steps
|
||||
|
||||
1. **Update server-side error responses** to use `response_error()` with constants
|
||||
2. **Update client-side error checks** to use `e.code === Ajax.ERROR_*` instead of `e.type === 'string'`
|
||||
3. **Read updated documentation**: `php artisan rsx:man ajax_error_handling`
|
||||
|
||||
Old helpers still work but are deprecated. Framework code being updated to new pattern.
|
||||
|
||||
### Benefits
|
||||
|
||||
- Same constant names server and client (`Ajax::ERROR_NOT_FOUND` = `Ajax.ERROR_NOT_FOUND`)
|
||||
- IDE autocomplete for error codes
|
||||
- Refactor-safe (rename constant updates both sides)
|
||||
- Auto-generated messages for common errors
|
||||
- Simpler API (one function instead of many)
|
||||
|
||||
---
|
||||
|
||||
## Template for Future Entries
|
||||
|
||||
```
|
||||
|
||||
@@ -26,6 +26,11 @@ This separation ensures:
|
||||
|
||||
**PURPOSE**: Essential directives for AI/LLM assistants developing RSX applications with RSpade.
|
||||
|
||||
## CRITICAL: Questions vs Commands
|
||||
|
||||
- **Questions get answers, NOT actions** - "Is that fire?" gets "Yes" not "Let me run through it". User has a plan, don't take destructive action when asked a question.
|
||||
- **Commands get implementation** - Clear directives result in code changes
|
||||
|
||||
## What is RSpade?
|
||||
|
||||
**Visual Basic-like development for PHP/Laravel.** Think: VB6 apps → VB6 runtime → Windows = RSX apps → RSpade runtime → Laravel.
|
||||
@@ -314,7 +319,7 @@ class Frontend_Layout extends Spa_Layout {
|
||||
}
|
||||
```
|
||||
|
||||
Layout template must have `$id="content"` element where actions render.
|
||||
Layout template must have `$sid="content"` element where actions render.
|
||||
|
||||
### URL Generation & Navigation
|
||||
|
||||
@@ -363,6 +368,30 @@ class Contacts_View_Action extends Spa_Action {
|
||||
|
||||
Details: `php artisan rsx:man spa`
|
||||
|
||||
### View Action Pattern (Loading Data)
|
||||
|
||||
For SPA actions that load data (view/edit CRUD pages), use the three-state pattern:
|
||||
|
||||
```javascript
|
||||
on_create() {
|
||||
this.data.record = { name: '' }; // Stub prevents undefined errors
|
||||
this.data.error_data = null;
|
||||
this.data.loading = true;
|
||||
}
|
||||
async on_load() {
|
||||
try {
|
||||
this.data.record = await Controller.get({id: this.args.id});
|
||||
} catch (e) {
|
||||
this.data.error_data = e;
|
||||
}
|
||||
this.data.loading = false;
|
||||
}
|
||||
```
|
||||
|
||||
Template uses three states: `<Loading_Spinner>` → `<Universal_Error_Page_Component>` → content.
|
||||
|
||||
**Details**: `php artisan rsx:man view_action_patterns`
|
||||
|
||||
---
|
||||
|
||||
## CONVERTING BLADE PAGES TO SPA ACTIONS
|
||||
@@ -415,7 +444,7 @@ class Feature_Index_Action extends Spa_Action {
|
||||
```php
|
||||
// Remove #[Route] method completely. Add Ajax endpoints:
|
||||
#[Ajax_Endpoint]
|
||||
public static function fetch_items(Request $request, array $params = []): array {
|
||||
public static function fetch_items(Request $request, array $params = []) {
|
||||
return ['items' => Feature_Model::all()];
|
||||
}
|
||||
```
|
||||
@@ -615,6 +644,10 @@ For mechanical thinkers who see structure, not visuals. Write `<User_Card>` not
|
||||
directly in attribute context. Works with static values, interpolations, and multiple conditions per element.
|
||||
Example: `<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
|
||||
|
||||
### 🔴 State Management Rules (ENFORCED)
|
||||
|
||||
**this.args** - Component arguments (read-only in on_load(), modifiable elsewhere)
|
||||
@@ -702,21 +735,21 @@ async on_load() {
|
||||
|
||||
- **`$quoted="string"`** → String literal
|
||||
- **`$unquoted=expression`** → JavaScript expression
|
||||
- **`$id="name"`** → Scoped element ID
|
||||
- **`$sid="name"`** → Scoped element ID
|
||||
|
||||
### Component Access
|
||||
|
||||
**this.$id(name)** → jQuery object (for DOM):
|
||||
**this.$sid(name)** → jQuery object (for DOM):
|
||||
```javascript
|
||||
this.$id('button').on('click', ...);
|
||||
this.$sid('button').on('click', ...);
|
||||
```
|
||||
|
||||
**this.id(name)** → Component instance (for methods):
|
||||
**this.sid(name)** → Component instance (for methods):
|
||||
```javascript
|
||||
const comp = this.id('child'); // ✅ Returns component
|
||||
const comp = this.sid('child'); // ✅ Returns component
|
||||
await comp.reload();
|
||||
|
||||
const comp = this.id('child').component(); // ❌ WRONG
|
||||
const comp = this.sid('child').component(); // ❌ WRONG
|
||||
```
|
||||
|
||||
### Incremental Scaffolding
|
||||
@@ -741,7 +774,7 @@ const comp = this.id('child').component(); // ❌ WRONG
|
||||
7. Use `Controller.method()` not `$.ajax()`
|
||||
8. Blade components self-closing only
|
||||
9. `on_create/render/stop` must be sync
|
||||
10. Use this.id() for components, NOT this.id().component()
|
||||
10. Use this.sid() for components, NOT this.sid().component()
|
||||
|
||||
### Bundle Integration Required
|
||||
|
||||
@@ -786,11 +819,11 @@ class My_Form extends Component {
|
||||
vals(values) {
|
||||
if (values) {
|
||||
// Setter - populate form
|
||||
this.$id('name').val(values.name || '');
|
||||
this.$sid('name').val(values.name || '');
|
||||
return null;
|
||||
} else {
|
||||
// Getter - extract values
|
||||
return {name: this.$id('name').val()};
|
||||
return {name: this.$sid('name').val()};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -829,7 +862,7 @@ const result = await Modal.form({
|
||||
});
|
||||
```
|
||||
|
||||
**Requirements**: Form component must implement `vals()` and include `<div $id="error_container"></div>`.
|
||||
**Requirements**: Form component must implement `vals()` and include `<div $sid="error_container"></div>`.
|
||||
|
||||
**Modal Classes** (for complex/reusable modals):
|
||||
```javascript
|
||||
@@ -963,47 +996,49 @@ Details: `php artisan rsx:man file_upload`
|
||||
|
||||
## AJAX ENDPOINTS
|
||||
|
||||
**Return data directly - framework auto-wraps as `{success: true, data: ...}`**
|
||||
|
||||
```php
|
||||
#[Ajax_Endpoint]
|
||||
public static function fetch_client(Request $request, array $params = []) {
|
||||
$client = Client_Model::find($params['id']);
|
||||
if (!$client) throw new \Exception('Not found');
|
||||
return $client; // ✅ Return data directly
|
||||
public static function method(Request $request, array $params = []) {
|
||||
return $data; // Success - framework wraps as {_success: true, _ajax_return_value: ...}
|
||||
}
|
||||
```
|
||||
|
||||
**❌ NEVER**: `return ['success' => true, 'data' => $client]` - framework adds this
|
||||
**Call:** `await Controller.method({param: value})`
|
||||
|
||||
**Special returns** (bypass auto-wrap):
|
||||
- `['errors' => [...]]` - Form validation errors
|
||||
- `['redirect' => '/path']` - Client-side navigation
|
||||
- `['error' => 'msg']` - Operation failure
|
||||
### Error Responses
|
||||
|
||||
Use `response_error(Ajax::ERROR_CODE, $metadata)`:
|
||||
|
||||
```php
|
||||
// Form validation
|
||||
if (empty($params['name'])) {
|
||||
return ['errors' => ['name' => 'Required']];
|
||||
}
|
||||
// Not found
|
||||
return response_error(Ajax::ERROR_NOT_FOUND, 'Project not found');
|
||||
|
||||
// Success with redirect
|
||||
Flash_Alert::success('Saved');
|
||||
return ['redirect' => Rsx::Route('View_Action', $id)];
|
||||
// Validation
|
||||
return response_error(Ajax::ERROR_VALIDATION, [
|
||||
'email' => 'Invalid',
|
||||
'name' => 'Required'
|
||||
]);
|
||||
|
||||
// Permission error
|
||||
if (!can_delete()) {
|
||||
return ['error' => 'Permission denied'];
|
||||
// Auto-message
|
||||
return response_error(Ajax::ERROR_UNAUTHORIZED);
|
||||
```
|
||||
|
||||
**Codes:** `ERROR_VALIDATION`, `ERROR_NOT_FOUND`, `ERROR_UNAUTHORIZED`, `ERROR_AUTH_REQUIRED`, `ERROR_FATAL`, `ERROR_GENERIC`
|
||||
|
||||
**Client:**
|
||||
```javascript
|
||||
try {
|
||||
const data = await Controller.get(id);
|
||||
} catch (e) {
|
||||
if (e.code === Ajax.ERROR_NOT_FOUND) {
|
||||
// Handle
|
||||
} else {
|
||||
alert(e.message); // Generic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Flash Alerts in Ajax Endpoints
|
||||
|
||||
Use server-side success alerts ONLY with redirects (no on-screen element to show message).
|
||||
|
||||
**Client-side**: Use `Flash_Alert.error()`, `Flash_Alert.warning()` on case-by-case basis.
|
||||
Unhandled errors auto-show flash alert.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user