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

@@ -1464,6 +1464,9 @@ class Manifest
// so that abstract property is available for subclass filtering
static::__collate_files_by_classes();
// Build event handler index from attributes
static::__build_event_handler_index();
// =======================================================
// Phase 5: Process Modules - Run manifest support modules and build autoloader
// =======================================================
@@ -1641,352 +1644,12 @@ class Manifest
// ---- Private / Protected Methods:
// ------------------------------------------------------------------------
// DEAD CODE REMOVED: _generate_js_api_stubs()
// Stub generation now handled by Controller_BundleIntegration
// Controller stub generation now handled by Controller_BundleIntegration
// ------------------------------------------------------------------------
/**
* Generate JavaScript stub files for ORM models
* These stubs enable IDE autocomplete and provide relationship methods
*/
protected static function _generate_js_model_stubs(): void
{
$stub_dir = storage_path('rsx-build/js-model-stubs');
// Create directory if it doesn't exist
if (!is_dir($stub_dir)) {
mkdir($stub_dir, 0755, true);
}
// Track generated stub files for cleanup
$generated_stubs = [];
// Get all models from the manifest
$model_entries = static::get_extending('Rsx_Model_Abstract');
foreach ($model_entries as $model_entry) {
if (!isset($model_entry['fqcn'])) {
continue;
}
$fqcn = $model_entry['fqcn'];
$class_name = $model_entry['class'] ?? '';
// Skip if it extends Rsx_System_Model_Abstract
if (static::php_is_subclass_of($fqcn, 'Rsx_System_Model_Abstract')) {
continue;
}
// Model class MUST exist if it's in the manifest as a model
// If it doesn't exist, this indicates a serious problem with the manifest
if (!class_exists($fqcn)) {
shouldnt_happen("Model class {$fqcn} from manifest cannot be loaded. This should not happen - models extending Rsx_Model_Abstract must be loadable. File: " . ($model_entry['file'] ?? 'unknown'));
}
// Check if this is an abstract class
$reflection = new ReflectionClass($fqcn);
if ($reflection->isAbstract()) {
continue;
}
// Get model metadata from manifest
$file_path = $model_entry['file'] ?? '';
$metadata = isset(static::$data['data']['files'][$file_path]) ? static::$data['data']['files'][$file_path] : [];
// Generate stub filename and paths
$stub_filename = static::_sanitize_model_stub_filename($class_name) . '.js';
// Check if user has created their own JS class
$user_class_exists = static::_check_user_model_class_exists($class_name);
// Use Base_ prefix if user class exists
$stub_class_name = $user_class_exists ? 'Base_' . $class_name : $class_name;
$stub_filename = static::_sanitize_model_stub_filename($stub_class_name) . '.js';
$stub_relative_path = 'storage/rsx-build/js-model-stubs/' . $stub_filename;
$stub_full_path = base_path($stub_relative_path);
// Check if stub needs regeneration
$needs_regeneration = true;
if (file_exists($stub_full_path)) {
// Get mtime of source PHP file
$source_mtime = $metadata['mtime'] ?? 0;
$stub_mtime = filemtime($stub_full_path);
// Only regenerate if source is newer than stub
if ($stub_mtime >= $source_mtime) {
// Also check if the model metadata has changed
// by comparing a hash of enums, relationships, and columns
$model_metadata = [];
// Get relationships
$model_metadata['rel'] = $fqcn::get_relationships();
// Get enums
if (property_exists($fqcn, 'enums')) {
$model_metadata['enums'] = $fqcn::$enums ?? [];
}
// Get columns from models metadata if available
if (isset(static::$data['data']['models'][$class_name]['columns'])) {
$model_metadata['columns'] = static::$data['data']['models'][$class_name]['columns'];
}
$model_metadata_hash = md5(json_encode($model_metadata));
$old_metadata_hash = $metadata['model_metadata_hash'] ?? '';
if ($model_metadata_hash === $old_metadata_hash) {
$needs_regeneration = false;
}
// Store the hash for future comparisons
static::$data['data']['files'][$file_path]['model_metadata_hash'] = $model_metadata_hash;
}
}
if ($needs_regeneration) {
// Generate stub content
$stub_content = static::_generate_model_stub_content($fqcn, $class_name, $stub_class_name);
// Write stub file
file_put_contents($stub_full_path, $stub_content);
// Store the metadata hash for future comparisons if not already done
if (!isset(static::$data['data']['files'][$file_path]['model_metadata_hash'])) {
$model_metadata = [];
// Get relationships
$model_metadata['rel'] = $fqcn::get_relationships();
// Get enums
if (property_exists($fqcn, 'enums')) {
$model_metadata['enums'] = $fqcn::$enums ?? [];
}
// Get columns from models metadata if available
if (isset(static::$data['data']['models'][$class_name]['columns'])) {
$model_metadata['columns'] = static::$data['data']['models'][$class_name]['columns'];
}
static::$data['data']['files'][$file_path]['model_metadata_hash'] = md5(json_encode($model_metadata));
}
}
$generated_stubs[] = $stub_filename;
// Add js_stub property to manifest data
$metadata['js_stub'] = $stub_relative_path;
// Write the updated metadata back to the manifest
static::$data['data']['files'][$file_path]['js_stub'] = $stub_relative_path;
// Add the stub file itself to the manifest
$stat = stat($stub_full_path);
static::$data['data']['files'][$stub_relative_path] = [
'file' => $stub_relative_path,
'hash' => sha1_file($stub_full_path),
'mtime' => $stat['mtime'],
'size' => $stat['size'],
'extension' => 'js',
'class' => $stub_class_name,
'is_model_stub' => true, // Mark this as a generated model stub
'source_model' => $file_path, // Reference to the source model
];
}
// Clean up orphaned stub files
$existing_stubs = glob($stub_dir . '/*.js');
foreach ($existing_stubs as $existing_stub) {
$filename = basename($existing_stub);
if (!in_array($filename, $generated_stubs)) {
// Remove from disk
unlink($existing_stub);
// Remove from manifest
$stub_relative_path = 'storage/rsx-build/js-model-stubs/' . $filename;
if (isset(static::$data['data']['files'][$stub_relative_path])) {
unset(static::$data['data']['files'][$stub_relative_path]);
}
}
}
}
/**
* Check if user has created a JavaScript model class
*/
private static function _check_user_model_class_exists(string $model_name): bool
{
// Check if there's a JS file with this class name in the manifest
foreach (static::$data['data']['files'] as $file_path => $metadata) {
if (isset($metadata['extension']) && $metadata['extension'] === 'js') {
if (isset($metadata['class']) && $metadata['class'] === $model_name) {
// Don't consider our own stubs
if (!isset($metadata['is_stub']) && !isset($metadata['is_model_stub'])) {
return true;
}
}
}
}
return false;
}
/**
* Sanitize model name for use as a filename
*/
private static function _sanitize_model_stub_filename(string $model_name): string
{
// Replace underscores with hyphens and lowercase
// e.g., User_Model becomes user-model
return strtolower(str_replace('_', '-', $model_name));
}
/**
* Generate JavaScript stub content for a model
*/
private static function _generate_model_stub_content(string $fqcn, string $class_name, string $stub_class_name): string
{
// Get model instance to introspect
$model = new $fqcn();
// Get relationships
$relationships = $fqcn::get_relationships();
// Get enums
$enums = $fqcn::$enums ?? [];
// Get table name
$table = $model->getTable();
// Get columns from models metadata if available
$columns = [];
if (isset(static::$data['data']['models'][$class_name]['columns'])) {
$columns = static::$data['data']['models'][$class_name]['columns'];
}
// Start building the stub content
$content = "/**\n";
$content .= " * Auto-generated JavaScript stub for {$class_name}\n";
$content .= ' * Generated by RSX Manifest at ' . date('Y-m-d H:i:s') . "\n";
$content .= " * DO NOT EDIT - This file is automatically regenerated\n";
$content .= " */\n\n";
$content .= "class {$stub_class_name} extends Rsx_Js_Model {\n";
// Add static table property
$content .= " static table = '{$table}';\n\n";
// Add static model name for API calls
$content .= " static get name() {\n";
$content .= " return '{$class_name}';\n";
$content .= " }\n\n";
// Generate enum constants and methods
foreach ($enums as $column => $enum_values) {
// Generate constants
foreach ($enum_values as $value => $props) {
if (!empty($props['constant'])) {
$content .= " static {$props['constant']} = {$value};\n";
}
}
if (!empty($enum_values)) {
$content .= "\n";
}
// Generate enum value getter
$content .= " static {$column}_enum_val() {\n";
$content .= ' return ' . json_encode($enum_values, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
// Fix indentation in JSON output
$content = preg_replace('/\n/', "\n ", $content);
$content = rtrim($content) . ";\n";
$content .= " }\n\n";
// Generate enum label list
$content .= " static {$column}_label_list() {\n";
$content .= " const values = {};\n";
foreach ($enum_values as $value => $props) {
if (isset($props['label'])) {
$label = addslashes($props['label']);
$content .= " values[{$value}] = '{$label}';\n";
}
}
$content .= " return values;\n";
$content .= " }\n\n";
// Generate enum select method (for dropdowns)
$content .= " static {$column}_enum_select() {\n";
$content .= " const values = this.{$column}_enum_val();\n";
$content .= " const result = {};\n";
$content .= " // Sort by order property if present\n";
$content .= " const sorted = Object.entries(values)\n";
$content .= " .sort(([,a], [,b]) => (a.order || 0) - (b.order || 0));\n";
$content .= " for (const [key, val] of sorted) {\n";
$content .= " // Skip if selectable is false\n";
$content .= " if (val.selectable !== false) {\n";
$content .= " result[key] = val.label || key;\n";
$content .= " }\n";
$content .= " }\n";
$content .= " return result;\n";
$content .= " }\n\n";
}
// Generate relationship methods
foreach ($relationships as $relationship) {
$content .= " /**\n";
$content .= " * Fetch {$relationship} relationship\n";
$content .= " * @returns {Promise} Related model instance(s) or false\n";
$content .= " */\n";
$content .= " async {$relationship}() {\n";
$content .= " if (!this.id) {\n";
$content .= " shouldnt_happen('Cannot fetch relationship without id property');\n";
$content .= " }\n\n";
$content .= " const response = await $.ajax({\n";
$content .= " url: `/_fetch_rel/{$class_name}/\${this.id}/{$relationship}`,\n";
$content .= " method: 'POST',\n";
$content .= " dataType: 'json'\n";
$content .= " });\n\n";
$content .= " // Handle response based on type\n";
$content .= " if (response === false || response === null) {\n";
$content .= " return response;\n";
$content .= " }\n\n";
$content .= " // __MODEL SYSTEM: Enables automatic ORM instantiation when fetching from PHP models.\n";
$content .= " // PHP models add \"__MODEL\": \"ClassName\" to JSON, JavaScript uses it to create proper instances.\n";
$content .= " // This provides typed model objects instead of plain JSON, with methods and type checking.\n\n";
$content .= " // Use the recursive processor to scan response and instantiate any models found.\n";
$content .= " // This generated stub method delegates to the base class processor.\n";
$content .= " return Rsx_Js_Model._instantiate_models_recursive(response);\n";
$content .= " }\n\n";
}
$content .= "}\n\n";
// Export the class globally
$content .= "// Make the class globally available\n";
$content .= "window.{$stub_class_name} = {$stub_class_name};\n";
// If using Base_ prefix, also create alias without prefix for convenience
if ($stub_class_name !== $class_name) {
$content .= "\n// Create alias without Base_ prefix if user class doesn't exist\n";
$content .= "if (typeof window.{$class_name} === 'undefined') {\n";
$content .= " window.{$class_name} = {$stub_class_name};\n";
$content .= "}\n";
}
return $content;
}
/**
* Sanitize controller name for use as a filename
*/
private static function _sanitize_stub_filename(string $controller_name): string
{
// Replace underscores with hyphens and lowercase
// e.g., User_Controller becomes user-controller
return strtolower(str_replace('_', '-', $controller_name));
}
// ------------------------------------------------------------------------
// DEAD CODE REMOVED: _generate_stub_content()
// Stub generation now handled by Controller_BundleIntegration
// DEAD CODE REMOVED: _generate_js_model_stubs()
// Model stub generation now handled by Database_BundleIntegration
// ------------------------------------------------------------------------
/**
@@ -2160,6 +1823,86 @@ class Manifest
}
}
/**
* Build event handler index from OnEvent attributes
*
* Scans all PHP files for methods with #[OnEvent] attributes and builds
* an index of event_name => [handlers] for fast event dispatching.
*
* Index structure:
* ['event_handlers'] => [
* 'event.name' => [
* ['class' => 'Class_Name', 'method' => 'method_name', 'priority' => 100],
* ['class' => 'Other_Class', 'method' => 'other_method', 'priority' => 200],
* ]
* ]
*
* Handlers are sorted by priority (lower numbers execute first).
*
* @return void
*/
protected static function __build_event_handler_index()
{
static::$data['data']['event_handlers'] = [];
// Scan all PHP files for OnEvent attributes
foreach (static::$data['data']['files'] as $file_path => $metadata) {
// Only process PHP files
if (($metadata['extension'] ?? '') !== 'php') {
continue;
}
// Skip files without a class
if (empty($metadata['class'])) {
continue;
}
$class_name = $metadata['class'];
// Check public static methods for OnEvent attributes
if (isset($metadata['public_static_methods'])) {
foreach ($metadata['public_static_methods'] as $method_name => $method_data) {
// Check if method has OnEvent attribute
if (isset($method_data['attributes']['OnEvent'])) {
$on_event_attrs = $method_data['attributes']['OnEvent'];
// Process each OnEvent attribute instance (a method can have multiple)
foreach ($on_event_attrs as $attr_args) {
// Extract event name - could be positional or named
$event_name = $attr_args[0] ?? $attr_args['event'] ?? null;
if (!$event_name) {
continue; // Skip invalid attribute
}
// Extract priority - could be positional or named, defaults to 100
$priority = $attr_args[1] ?? $attr_args['priority'] ?? 100;
// Initialize event array if needed
if (!isset(static::$data['data']['event_handlers'][$event_name])) {
static::$data['data']['event_handlers'][$event_name] = [];
}
// Add handler to event
static::$data['data']['event_handlers'][$event_name][] = [
'class' => $class_name,
'method' => $method_name,
'priority' => $priority,
'file' => $file_path,
];
}
}
}
}
}
// Sort each event's handlers by priority (lower = earlier)
foreach (static::$data['data']['event_handlers'] as $event_name => &$handlers) {
usort($handlers, function ($a, $b) {
return $a['priority'] <=> $b['priority'];
});
}
}
/**
* Load changed PHP files and their dependencies
*