Working state snapshot with debug settings and pending changes

Fix manifest helper delegator missing return statements

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-14 22:01:13 +00:00
parent d523f0f600
commit 45838aafd2
8 changed files with 270 additions and 47 deletions

View File

@@ -5,6 +5,7 @@ APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
# RSX Application Mode: development, debug, or production # RSX Application Mode: development, debug, or production
RSX_MODE=development
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
@@ -40,10 +41,15 @@ MAIL_FROM_NAME="${APP_NAME}"
# Debug Settings # Debug Settings
# SHOW_CONSOLE_DEBUG_CLI: Enable console_debug() output in CLI mode # SHOW_CONSOLE_DEBUG_CLI: Enable console_debug() output in CLI mode
SHOW_CONSOLE_DEBUG_CLI=false SHOW_CONSOLE_DEBUG_CLI=true
# SHOW_CONSOLE_DEBUG_HTTP: Enable console_debug() output in HTTP mode (browser console) # SHOW_CONSOLE_DEBUG_HTTP: Enable console_debug() output in HTTP mode (browser console)
SHOW_CONSOLE_DEBUG_HTTP=false SHOW_CONSOLE_DEBUG_HTTP=true
# Console Debug Configuration
CONSOLE_DEBUG_ENABLED=false
CONSOLE_DEBUG_FILTER_MODE=all
CONSOLE_DEBUG_BENCHMARK=true
# FORCE_REBUILD_EVERY_REQUEST: Clear build cache on each request (development only) # FORCE_REBUILD_EVERY_REQUEST: Clear build cache on each request (development only)
@@ -56,4 +62,3 @@ GATEKEEPER_SUBTITLE="This is a restricted development preview site. Please enter
SSR_FPC_ENABLED=true SSR_FPC_ENABLED=true
LOG_BROWSER_ERRORS=false LOG_BROWSER_ERRORS=false
AJAX_DISABLE_BATCHING=false AJAX_DISABLE_BATCHING=false
RSX_MODE=development

3
.gitignore vendored
View File

@@ -59,3 +59,6 @@ __pycache__
docs.dev/*/node_modules/ docs.dev/*/node_modules/
docs.dev/ace_reference/vendor/ docs.dev/ace_reference/vendor/
.rspade_last_commit_for_publish .rspade_last_commit_for_publish
# VS Code workspace file (user-specific)
rspade.code-workspace

View File

@@ -0,0 +1,146 @@
<?php
namespace App\RSpade\CodeQuality\Rules\Scss;
use App\RSpade\CodeQuality\Rules\CodeQualityRule_Abstract;
/**
* DataSidSelectorRule - Prohibits targeting [data-sid] in SCSS/CSS selectors
*
* PHILOSOPHY: $sid attributes are internal implementation details of jqhtml components.
* They exist for programmatic DOM access via this.$sid('name'), not for styling.
* Styling should use semantic BEM child classes instead.
*/
class DataSidSelector_CodeQualityRule extends CodeQualityRule_Abstract
{
public function get_id(): string
{
return 'SCSS-SID-01';
}
public function get_name(): string
{
return 'No data-sid CSS Selectors';
}
public function get_description(): string
{
return 'Prohibits targeting [data-sid] attributes in CSS selectors. Use BEM child classes instead.';
}
public function get_file_patterns(): array
{
return ['*.scss', '*.css'];
}
public function get_default_severity(): string
{
return 'critical';
}
/**
* Run at manifest-time for immediate feedback
*/
public function is_called_during_manifest_scan(): bool
{
return true;
}
/**
* Check SCSS/CSS files for [data-sid] selectors
*/
public function check(string $file_path, string $contents, array $metadata = []): void
{
// Skip vendor and node_modules
if (str_contains($file_path, '/vendor/') || str_contains($file_path, '/node_modules/')) {
return;
}
// Skip minified files
if (str_ends_with($file_path, '.min.css') || str_ends_with($file_path, '.min.scss')) {
return;
}
$lines = explode("\n", $contents);
foreach ($lines as $line_num => $line) {
$actual_line_number = $line_num + 1;
$trimmed = trim($line);
// Skip comments
if (str_starts_with($trimmed, '//') || str_starts_with($trimmed, '/*')) {
continue;
}
// Check for exception comment on same line or previous line
if (str_contains($line, '@' . $this->get_id() . '-EXCEPTION')) {
continue;
}
if ($line_num > 0 && str_contains($lines[$line_num - 1], '@' . $this->get_id() . '-EXCEPTION')) {
continue;
}
// Look for [data-sid= pattern (with or without quotes)
if (preg_match('/\[data-sid\s*[=~\|\^\$\*]/', $line, $matches)) {
// Extract the selector context for better error message
$code_snippet = trim($line);
if (strlen($code_snippet) > 80) {
$code_snippet = substr($code_snippet, 0, 77) . '...';
}
// Try to extract the sid name for the suggestion
$sid_name = '';
if (preg_match('/\[data-sid\s*=\s*["\']?([^"\'\]]+)/', $line, $sid_match)) {
$sid_name = $sid_match[1];
}
$suggestion = $this->get_suggestion($sid_name);
$this->add_violation(
$file_path,
$actual_line_number,
"Targeting [data-sid] in CSS is PROHIBITED",
$code_snippet,
$suggestion,
'critical'
);
}
}
}
/**
* Generate the fix suggestion with example
*/
private function get_suggestion(string $sid_name): string
{
$example_class = $sid_name ? "__$sid_name" : '__element_name';
return <<<SUGGESTION
\$sid attributes are internal implementation details of jqhtml components.
They exist for programmatic DOM access (this.\$sid('name')), NOT for styling.
WHY THIS IS WRONG:
- \$sid is an internal identifier, not a semantic class name
- Component internals may change without notice
- Breaks encapsulation - styles reach into component implementation
SOLUTION: Use BEM child classes instead.
Before (WRONG):
[data-sid="$sid_name"] {
background: transparent;
}
After (CORRECT):
&$example_class {
background: transparent;
}
Then in the jqhtml template, add the class to the element:
<button \$sid="$sid_name" class="Component_Name$example_class">
BEM child classes are semantic, documented, and part of the component's public API.
\$sid attributes are internal implementation that should never leak into CSS.
SUGGESTION;
}
}

View File

@@ -535,7 +535,8 @@ class BundleCompiler
} }
// Handle class name includes (could be Asset Bundles with CDN assets) // Handle class name includes (could be Asset Bundles with CDN assets)
if (is_string($include) && Manifest::php_find_class($include)) { // Check manifest data directly since php_find_class throws if not found
if (is_string($include) && isset(Manifest::$data['data']['php_classes'][$include])) {
if (Manifest::php_is_subclass_of($include, 'Rsx_Asset_Bundle_Abstract')) { if (Manifest::php_is_subclass_of($include, 'Rsx_Asset_Bundle_Abstract')) {
$asset_def = $include::define(); $asset_def = $include::define();
if (!empty($asset_def['cdn_assets'])) { if (!empty($asset_def['cdn_assets'])) {

View File

@@ -189,9 +189,9 @@ class Manifest
public static $_manifest_compile_lock; public static $_manifest_compile_lock;
public static $_get_rsx_files_cache; public static $_get_rsx_files_cache = null;
public static bool $_has_changed_cache; public static array $_has_changed_cache = [];
public static bool $_has_manifest_ready = false; public static bool $_has_manifest_ready = false;
@@ -1209,7 +1209,7 @@ class Manifest
// move to lower soon // move to lower soon
public static function _validate_cached_data() public static function _validate_cached_data()
{ {
_Manifest_Cache_Helper::_validate_cached_data(); return _Manifest_Cache_Helper::_validate_cached_data();
} }
/** /**
@@ -1258,7 +1258,7 @@ class Manifest
*/ */
public static function _collate_files_by_classes() public static function _collate_files_by_classes()
{ {
_Manifest_Builder_Helper::_collate_files_by_classes(); return _Manifest_Builder_Helper::_collate_files_by_classes();
} }
/** /**
@@ -1281,7 +1281,7 @@ class Manifest
*/ */
public static function _build_event_handler_index() public static function _build_event_handler_index()
{ {
_Manifest_Builder_Helper::_build_event_handler_index(); return _Manifest_Builder_Helper::_build_event_handler_index();
} }
/** /**
@@ -1295,7 +1295,7 @@ class Manifest
*/ */
public static function _build_classless_php_files_index() public static function _build_classless_php_files_index()
{ {
_Manifest_Builder_Helper::_build_classless_php_files_index(); return _Manifest_Builder_Helper::_build_classless_php_files_index();
} }
/** /**

View File

@@ -153,8 +153,6 @@ class _Manifest_Cache_Helper
// If cache exists, check if anything changed // If cache exists, check if anything changed
$files = Manifest::_get_rsx_files(); $files = Manifest::_get_rsx_files();
$any_changes = false;
// Check for changed files // Check for changed files
foreach ($files as $file) { foreach ($files as $file) {
if (Manifest::_has_changed($file)) { if (Manifest::_has_changed($file)) {
@@ -163,7 +161,6 @@ class _Manifest_Cache_Helper
} }
// Check for deleted files // Check for deleted files
if (!$any_changes) {
$existing_files = array_flip($files); $existing_files = array_flip($files);
foreach (array_keys(Manifest::$data['data']['files']) as $cached_file) { foreach (array_keys(Manifest::$data['data']['files']) as $cached_file) {
@@ -173,15 +170,14 @@ class _Manifest_Cache_Helper
} }
if (!isset($existing_files[$cached_file])) { if (!isset($existing_files[$cached_file])) {
// Only show the message once per page load // Only show the message once per page load
if (!self::$__shown_rescan_message) { if (!Manifest::$__shown_rescan_message) {
console_debug('MANIFEST', '* Deleted file ' . $cached_file . ' is triggering manifest rescan *'); console_debug('MANIFEST', '* Deleted file ' . $cached_file . ' is triggering manifest rescan *');
self::$__shown_rescan_message = true; Manifest::$__shown_rescan_message = true;
} }
return false; return false;
} }
} }
}
return true; return true;
} }

View File

@@ -16,17 +16,15 @@ use App\RSpade\Core\Manifest\Manifest;
*/ */
class _Manifest_Scanner_Helper class _Manifest_Scanner_Helper
{ {
protected static $_get_rsx_files_cache; // Static properties are defined on Manifest class and accessed via Manifest::$property
protected static $_has_changed_cache = [];
protected static $__shown_rescan_message = false;
/** /**
* Get all files in configured scan directories (returns relative paths) * Get all files in configured scan directories (returns relative paths)
*/ */
public static function _get_rsx_files(): array public static function _get_rsx_files(): array
{ {
if (!empty(self::$_get_rsx_files_cache)) { if (!empty(Manifest::$_get_rsx_files_cache)) {
return self::$_get_rsx_files_cache; return Manifest::$_get_rsx_files_cache;
} }
$base_path = base_path(); $base_path = base_path();
@@ -128,7 +126,7 @@ class _Manifest_Scanner_Helper
$files = array_unique($files); $files = array_unique($files);
sort($files); sort($files);
self::$_get_rsx_files_cache = $files; Manifest::$_get_rsx_files_cache = $files;
return $files; return $files;
} }
@@ -138,17 +136,17 @@ class _Manifest_Scanner_Helper
*/ */
public static function _has_changed(string $file): bool public static function _has_changed(string $file): bool
{ {
if (isset(self::$_has_changed_cache[$file])) { if (isset(Manifest::$_has_changed_cache[$file])) {
return self::$_has_changed_cache[$file]; return Manifest::$_has_changed_cache[$file];
} }
if (!isset(Manifest::$data['data']['files'][$file])) { if (!isset(Manifest::$data['data']['files'][$file])) {
// Only show the message once per page load // Only show the message once per page load
if (!self::$__shown_rescan_message) { if (!Manifest::$__shown_rescan_message) {
console_debug('MANIFEST', '* New file ' . $file . ' is triggering manifest rescan *'); console_debug('MANIFEST', '* New file ' . $file . ' is triggering manifest rescan *');
self::$__shown_rescan_message = true; Manifest::$__shown_rescan_message = true;
} }
$_has_changed_cache[$file] = true; Manifest::$_has_changed_cache[$file] = true;
return true; return true;
} }
@@ -159,11 +157,11 @@ class _Manifest_Scanner_Helper
// Make sure file exists // Make sure file exists
if (!file_exists($absolute_path)) { if (!file_exists($absolute_path)) {
// Only show the message once per page load // Only show the message once per page load
if (!self::$__shown_rescan_message) { if (!Manifest::$__shown_rescan_message) {
console_debug('MANIFEST', '* File ' . $file . ' appears to be deleted in ' . $absolute_path . ', triggering manifest rescan *'); console_debug('MANIFEST', '* File ' . $file . ' appears to be deleted in ' . $absolute_path . ', triggering manifest rescan *');
self::$__shown_rescan_message = true; Manifest::$__shown_rescan_message = true;
} }
$_has_changed_cache[$file] = true; Manifest::$_has_changed_cache[$file] = true;
return true; return true;
} }
@@ -173,11 +171,11 @@ class _Manifest_Scanner_Helper
// Stage 1: Size check // Stage 1: Size check
if ($old['size'] != $current_size) { if ($old['size'] != $current_size) {
// Only show the message once per page load // Only show the message once per page load
if (!self::$__shown_rescan_message) { if (!Manifest::$__shown_rescan_message) {
console_debug('MANIFEST', '* File ' . $file . ' has changed size, triggering manifest rescan *'); console_debug('MANIFEST', '* File ' . $file . ' has changed size, triggering manifest rescan *');
self::$__shown_rescan_message = true; Manifest::$__shown_rescan_message = true;
} }
$_has_changed_cache[$file] = true; Manifest::$_has_changed_cache[$file] = true;
return true; return true;
} }
@@ -186,16 +184,16 @@ class _Manifest_Scanner_Helper
$current_mtime = filemtime($absolute_path); $current_mtime = filemtime($absolute_path);
if ($old['mtime'] != $current_mtime) { if ($old['mtime'] != $current_mtime) {
// Only show the message once per page load // Only show the message once per page load
if (!self::$__shown_rescan_message) { if (!Manifest::$__shown_rescan_message) {
console_debug('MANIFEST', '* File ' . $file . ' has changed mtime, triggering manifest rescan *'); console_debug('MANIFEST', '* File ' . $file . ' has changed mtime, triggering manifest rescan *');
self::$__shown_rescan_message = true; Manifest::$__shown_rescan_message = true;
} }
$_has_changed_cache[$file] = true; Manifest::$_has_changed_cache[$file] = true;
return true; return true;
} }
$_has_changed_cache[$file] = false; Manifest::$_has_changed_cache[$file] = false;
return false; return false;
} }

View File

@@ -73,6 +73,10 @@ class Rsx_Framework_Provider extends ServiceProvider
*/ */
public function register() public function register()
{ {
// Sanity check: .env symlink configuration
// The system/.env must be a symlink to ../.env for proper configuration sharing
$this->check_env_symlink();
// Merge framework config defaults // Merge framework config defaults
$package_config = base_path('config/rsx.php'); $package_config = base_path('config/rsx.php');
$this->mergeConfigFrom($package_config, 'rsx'); $this->mergeConfigFrom($package_config, 'rsx');
@@ -506,4 +510,74 @@ class Rsx_Framework_Provider extends ServiceProvider
rmdir($dir); rmdir($dir);
} }
/**
* Check that .env is properly symlinked
*
* The system/.env file must be a symlink to ../.env for proper configuration sharing.
* This ensures both the project root and Laravel see the same .env file.
*/
private function check_env_symlink(): void
{
// Only check if we're in the standard /var/www/html/system location
if (base_path() !== '/var/www/html/system') {
return;
}
$project_env = '/var/www/html/.env';
$system_env = '/var/www/html/system/.env';
// Both files must exist for this check to apply
if (!file_exists($project_env) || !file_exists($system_env)) {
return;
}
// system/.env must be a symlink
if (!is_link($system_env)) {
$this->fatal_env_symlink_error();
}
// The symlink must point to ../.env (relative path)
$link_target = readlink($system_env);
if ($link_target !== '../.env') {
$this->fatal_env_symlink_error();
}
}
/**
* Display fatal error for .env symlink misconfiguration
*/
private function fatal_env_symlink_error(): void
{
$message = <<<'ERROR'
================================================================================
FATAL ERROR: .env Symlink Misconfiguration
================================================================================
The file /var/www/html/system/.env must be a symlink to ../.env
This ensures both the project root and the Laravel framework share the same
environment configuration. Without this symlink, configuration changes in one
location won't be reflected in the other.
TO FIX THIS ISSUE:
cd /var/www/html/system
rm .env
ln -s ../.env .env
After creating the symlink, your .env setup should look like:
/var/www/html/.env (the actual .env file)
/var/www/html/system/.env -> ../.env (symlink)
================================================================================
ERROR;
// Use echo + die for maximum visibility (before any error handlers are set up)
echo $message;
die(1);
}
} }