Refactor filename naming system and apply convention-based renames

Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-13 19:10:02 +00:00
parent fc494c1e08
commit 77b4d10af8
28155 changed files with 2191860 additions and 12967 deletions

View File

@@ -81,6 +81,26 @@ $clean = Sanitizer::sanitize($input); // Let it throw
**SECURITY-CRITICAL**: If sanitization/validation/auth fails, NEVER continue. Always throw immediately.
**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
Core classes ALWAYS exist. Never check.
@@ -135,6 +155,24 @@ AI should follow the rule's guidance precisely. Rules are deliberately written a
| Database Tables | `lowercase_plural` | `users` |
| Constants | `UPPERCASE` | `MAX_SIZE` |
### File Prefix Grouping
**Critical insight**: Files with the same prefix are conceptually grouped and should stay grouped!
When multiple files share a common prefix in the same directory, they form a visual group that indicates they work together. This grouping must be preserved when renaming files.
**Example** - `rsx/app/frontend/calendar/`:
```
frontend_calendar_event.scss
frontend_calendar_event_controller.php
frontend_calendar_event.blade.php
frontend_calendar_event.js
```
All share prefix `frontend_calendar_event` → they're a related set! If renaming to remove redundancy, ALL files in the group must be renamed consistently to maintain the visual relationship.
**Why this matters**: Developers scan file listings and use shared prefixes to quickly identify which files work together. Breaking this pattern creates cognitive overhead and makes the codebase harder to navigate.
**Critical**: Never create same-name different-case files.
---
@@ -209,16 +247,31 @@ class Frontend_Controller extends Rsx_Controller_Abstract
### Type-Safe URLs
**MANDATORY**: All URLs must be generated using `Rsx::Route()` - hardcoded URLs are forbidden.
```php
// PHP
// PHP - Basic usage
Rsx::Route('User_Controller', 'show', ['id' => 123]);
Rsx::Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
// JavaScript (identical)
// PHP - With query parameters (third argument)
Rsx::Route('Login_Controller', 'logout', ['redirect' => '/dashboard']);
// Generates: /logout?redirect=%2Fdashboard
// JavaScript (identical syntax)
Rsx.Route('User_Controller', 'show', {id: 123});
Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
Rsx.Route('Login_Controller', 'logout', {redirect: '/dashboard'});
```
**Query Parameters**: Pass associative array/object as third argument:
- Keys become query parameter names
- Values are automatically URL-encoded
- Framework handles proper URL construction
**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.
---
## BLADE & VIEWS
@@ -231,6 +284,29 @@ Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
@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
});
```
### SCSS Pairing
```scss
@@ -240,6 +316,25 @@ Rsx.Route('User_Controller', 'show', 123); // Integer shorthand for 'id'
}
```
### Passing Data to JavaScript
Use `@rsx_page_data` for page-specific data needed by JavaScript (IDs, config, etc.):
```blade
@rsx_page_data(['user_id' => $user->id, 'mode' => 'edit'])
@section('content')
<!-- Your HTML -->
@endsection
```
Access in JavaScript:
```javascript
const user_id = window.rsxapp.page_data.user_id;
```
Use when data doesn't belong in DOM attributes. Multiple calls merge together.
---
## JAVASCRIPT
@@ -403,7 +498,7 @@ this.$id('button').on('click', ...); // Access scoped element
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
6. `on_create/render/stop` must be sync
### Bundle Integration Required
@@ -418,7 +513,30 @@ For advanced topics: `php artisan rsx:man jqhtml`
## FORM COMPONENTS
Form components use the **vals() dual-mode pattern** for getting/setting values:
**Form fields** (`<Rsx_Form>` with `$data`, `$controller`, `$method`):
```blade
<Rsx_Form $data="{!! json_encode($form_data) !!}" $controller="Controller" $method="save">
<Form_Field $name="email" $label="Email" $required=true>
<Text_Input $type="email" />
</Form_Field>
<Form_Field_Hidden $name="id" />
</Rsx_Form>
```
- **Form_Field** - Standard formatted field with label, errors, help text
- **Form_Field_Hidden** - Single-tag hidden input (extends Form_Field_Abstract)
- **Form_Field_Abstract** - Base class for custom formatting (advanced)
**Disabled fields**: Use `$disabled=true` attribute on input components to disable fields. Unlike standard HTML, disabled fields still return values via `vals()` (useful for read-only data that should be submitted).
```blade
<Text_Input $type="email" $disabled=true />
<Select_Input $options="{!! json_encode($options) !!}" $disabled=true />
<Checkbox_Input $label="Subscribe" $disabled=true />
```
**Form component classes** use the **vals() dual-mode pattern**:
```javascript
class My_Form extends Jqhtml_Component {
@@ -426,14 +544,10 @@ class My_Form extends Jqhtml_Component {
if (values) {
// Setter - populate form
this.$id('name').val(values.name || '');
this.$id('email').val(values.email || '');
return null;
} else {
// Getter - extract values
return {
name: this.$id('name').val(),
email: this.$id('email').val()
};
return {name: this.$id('name').val()};
}
}
}
@@ -474,6 +588,27 @@ const result = await Modal.form({
**Requirements**: Form component must implement `vals()` and include `<div $id="error_container"></div>`.
**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;
}
}
// Use from page JS
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.
Details: `php artisan rsx:man modals`
---
@@ -593,6 +728,113 @@ php artisan migrate:commit
---
## FILE ATTACHMENTS
**Upload Flow**: Files upload UNATTACHED → validate → assign via API
### Security Model
Session-based validation prevents cross-user file assignment:
- Files get `session_id` automatically on upload
- `can_user_assign_this_file()` validates: not assigned, same site, same session
- User-provided `fileable_*` params are ignored during upload
### Attachment API
```php
// Find attachment by key from form submission
$attachment = File_Attachment_Model::find_by_key($params['photo_key']);
// Validate user can assign this file
if ($attachment && $attachment->can_user_assign_this_file()) {
// Single file (replaces existing in category)
$attachment->attach_to($user, 'profile_photo');
// OR multiple files (adds to collection)
$attachment->add_to($project, 'documents');
}
// Remove assignment without deleting file
$attachment->detach();
```
### Model Helpers
```php
// Get single attachment (for profile photos, logos, etc)
$photo = $user->get_attachment('profile_photo');
if ($photo) {
echo $photo->get_thumbnail_url('cover', 128, 128);
}
// Get multiple attachments (for documents, galleries, etc)
$documents = $project->get_attachments('documents');
foreach ($documents as $doc) {
echo $doc->file_name;
}
```
### Display
```php
$photo->get_thumbnail_url('cover', 128, 128) // Thumbnail
$photo->get_url() // Full file
$photo->get_download_url() // Force download
```
### Endpoints
- `POST /_upload` - Upload file (returns attachment key)
- `GET /_download/:key` - Download file
- `GET /_thumbnail/:key/:type/:width/:height` - Generate thumbnail
### Complete Workflow
```javascript
// Frontend: Upload file
$.ajax({
url: '/_upload',
type: 'POST',
data: formData,
success: function(response) {
$('#photo_key').val(response.attachment.key);
}
});
```
```php
// Backend: Save form with attachment
#[Ajax_Endpoint]
public static function save(Request $request, array $params = []) {
$user = Session::get_user();
if (!empty($params['photo_key'])) {
$attachment = File_Attachment_Model::find_by_key($params['photo_key']);
if ($attachment && $attachment->can_user_assign_this_file()) {
$attachment->attach_to($user, 'profile_photo');
}
}
return ['success' => true];
}
```
```blade
{{-- Display attachment --}}
@php
$photo = $user->get_attachment('profile_photo');
@endphp
@if($photo)
<img src="{{ $photo->get_thumbnail_url('cover', 128, 128) }}">
@else
<div>No photo</div>
@endif
```
**Details**: `php artisan rsx:man file_upload`
---
## AJAX ENDPOINTS
```php
@@ -839,6 +1081,31 @@ public static function show_user(Request $request, array $params = [])
}
```
**IMPORTANT**: `$params` contains ONLY GET and route parameters. POST data is accessed via `$request`:
```php
public static function update(Request $request, array $params = [])
{
$user_id = $params['id']; // From URL route: /users/:id
$sort = $params['sort']; // From query: ?sort=name
$name = $request->input('name'); // From POST body
$email = $request->post('email'); // From POST body
}
```
### File Responses
Use Response facade for file downloads/viewing:
```php
// Force download
return Response::download($path, $filename, $headers);
// Display inline (browser)
return Response::file($path, $headers);
```
### Controller-Wide Auth
```php