Add SPA session validation and buglist, update migration docs

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-03 21:28:08 +00:00
parent 9be3dfc14e
commit cff287e870
24169 changed files with 10223 additions and 7120 deletions

3
app/RSpade/BuildUI/README.md Executable file
View File

@@ -0,0 +1,3 @@
Builds are fast now, and this service is no longer necessary or useful. This is kept as an archive in case we may want to use it at a future date.

View File

@@ -915,4 +915,83 @@ class Rsx {
div.textContent = text; div.textContent = text;
return div.innerHTML; return div.innerHTML;
} }
/**
* Validate current session state against server
*
* Called after SPA navigation to detect stale state requiring page refresh:
* 1. Codebase updates - build_key changed means new deployment
* 2. User changes - name, role, permissions updated by admin
* 3. Session changes - logged out, ACLs modified, session invalidated
*
* If any values differ from window.rsxapp, triggers a transparent page refresh
* using location.replace() to avoid polluting browser history.
*
* @returns {Promise<boolean>} True if session is valid, false if refresh triggered
*/
static async validate_session() {
try {
// Call the session validation endpoint
const server_state = await Spa_Session_Controller.get_state();
// Compare with current client state
const client_state = window.rsxapp;
let needs_refresh = false;
let refresh_reason = null;
// Check build_key - codebase was updated
if (server_state.build_key !== client_state.build_key) {
needs_refresh = true;
refresh_reason = 'codebase_updated';
console_debug('Rsx', 'Session validation: build_key changed, triggering refresh');
}
// Check session_hash - session was invalidated or changed
if (!needs_refresh && server_state.session_hash !== client_state.session_hash) {
needs_refresh = true;
refresh_reason = 'session_changed';
console_debug('Rsx', 'Session validation: session_hash changed, triggering refresh');
}
// Check user - user account details changed (null check for logout detection)
if (!needs_refresh) {
const server_user_id = server_state.user?.id ?? null;
const client_user_id = client_state.user?.id ?? null;
if (server_user_id !== client_user_id) {
needs_refresh = true;
refresh_reason = 'user_changed';
console_debug('Rsx', 'Session validation: user changed, triggering refresh');
}
}
// Check site - site context changed
if (!needs_refresh) {
const server_site_id = server_state.site?.id ?? null;
const client_site_id = client_state.site?.id ?? null;
if (server_site_id !== client_site_id) {
needs_refresh = true;
refresh_reason = 'site_changed';
console_debug('Rsx', 'Session validation: site changed, triggering refresh');
}
}
if (needs_refresh) {
// Use location.replace() to refresh without adding history entry
// This ensures back/forward navigation isn't polluted by the refresh
console.warn('[Rsx] Session validation failed (' + refresh_reason + '), refreshing page');
window.location.replace(window.location.href);
return false;
}
return true;
} catch (e) {
// On network error or endpoint failure, don't block navigation
// The user will eventually hit a stale state naturally
console.warn('[Rsx] Session validation failed (network error), continuing', e);
return true;
}
}
} }

View File

@@ -40,6 +40,9 @@ class Spa {
// Timer ID for 30-minute auto-disable // Timer ID for 30-minute auto-disable
static _spa_timeout_timer = null; static _spa_timeout_timer = null;
// Flag to track if initial load is complete (for session validation)
static _initial_load_complete = false;
/** /**
* Disable SPA navigation - all navigation becomes full page loads * Disable SPA navigation - all navigation becomes full page loads
* Call this when errors occur or forms are dirty * Call this when errors occur or forms are dirty
@@ -697,6 +700,30 @@ class Spa {
// this content renders, so we need a second attempt after the page is fully ready. // this content renders, so we need a second attempt after the page is fully ready.
console_debug('Spa', `Rendered ${action_name} with ${target_layouts.length} layout(s)`); console_debug('Spa', `Rendered ${action_name} with ${target_layouts.length} layout(s)`);
// Session validation after navigation
// Validates that client state (window.rsxapp) matches server state.
// Detects:
// 1. Codebase updates (build_key changed) - new deployment
// 2. User changes (name, role, permissions updated)
// 3. Session changes (logged out, ACLs modified)
//
// Only runs on subsequent navigations, not:
// - Initial page load (window.rsxapp is fresh from server)
// - Back/forward navigation (force: true) - restoring cached state
//
// On mismatch, triggers location.replace() for transparent refresh
// that doesn't pollute browser history.
if (Spa._initial_load_complete && !options.force) {
// Fire and forget - don't block navigation on validation
Rsx.validate_session();
}
// Mark initial load complete for subsequent navigations
if (!Spa._initial_load_complete) {
Spa._initial_load_complete = true;
}
} catch (error) { } catch (error) {
console.error('[Spa] Dispatch error:', error); console.error('[Spa] Dispatch error:', error);
// TODO: Better error handling - show error UI to user // TODO: Better error handling - show error UI to user

View File

@@ -0,0 +1,56 @@
<?php
namespace App\RSpade\Core\SPA;
use Illuminate\Http\Request;
use Session;
use App\RSpade\Core\Controller\Rsx_Controller_Abstract;
use App\RSpade\Core\Manifest\Manifest;
/**
* Spa_Session_Controller - Session validation endpoint for SPA navigation
*
* Called after SPA navigation to detect:
* 1. Codebase updates (build_key changed)
* 2. User account changes (user data changed)
* 3. Session invalidation (logged out, ACLs changed)
*
* If any values differ from what the client has in window.rsxapp,
* the client will trigger a page refresh to get fresh state.
*
* @auth-exempt Returns session state regardless of auth status
*/
class Spa_Session_Controller extends Rsx_Controller_Abstract
{
/**
* Return current session state for validation
*
* Returns the same values that would be set in window.rsxapp
* during a full page load, allowing client to detect staleness.
*/
#[Ajax_Endpoint]
public static function get_state(Request $request, array $params = [])
{
// Build key - detects codebase updates
$build_key = Manifest::get_build_key();
// Session hash - detects session changes (logout, new session)
$session_token = $_COOKIE['rsx'] ?? null;
$session_hash = $session_token
? hash_hmac('sha256', $session_token, config('app.key'))
: null;
// User data - detects user account changes (name, role, permissions)
$user = Session::get_user();
// Site data - detects site context changes
$site = Session::get_site();
return [
'build_key' => $build_key,
'session_hash' => $session_hash,
'user' => $user,
'site' => $site,
];
}
}

View File

@@ -102,14 +102,19 @@ class JqhtmlWebpackCompiler
// Compile via RPC server // Compile via RPC server
$compiled_js = static::_compile_via_rpc($file_path); $compiled_js = static::_compile_via_rpc($file_path);
// Don't add any comments - they break sourcemap line offsets // Extract template variable name and append registration call
// Just use the compiler output as-is // CRITICAL: Do NOT add extra newlines - they break sourcemap line offsets
// The registration is appended AFTER the sourcemap comment so it doesn't affect mappings
if (preg_match('/var\s+(template_\w+)\s*=/', $compiled_js, $matches)) {
$template_var = $matches[1];
// Append registration on same line as end of sourcemap (no extra newlines)
$compiled_js = rtrim($compiled_js) . "\njqhtml.register_template({$template_var});";
}
$wrapped_js = $compiled_js; $wrapped_js = $compiled_js;
// Ensure proper newline at end // Ensure exactly one newline at end (no extra)
if (!str_ends_with($wrapped_js, "\n")) { $wrapped_js = rtrim($wrapped_js) . "\n";
$wrapped_js .= "\n";
}
// Cache the compiled result // Cache the compiled result
file_put_contents($cache_file, $wrapped_js); file_put_contents($cache_file, $wrapped_js);

View File

@@ -49,11 +49,22 @@ class Jqhtml_Integration {
// ───────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────
// Tag Static Methods with Cache IDs // Tag Static Methods with Cache IDs
// //
// jqhtml caches component output based on args. When a function is passed // jqhtml caches component renders based on a hash of their args.
// as an arg (e.g., DataGrid's data_source), we need a stable string key. // Problem: Functions can't be serialized, so passing one (e.g., a
// DataGrid's data_source callback) would defeat caching entirely.
// //
// Without this: data_source: function() {...} → no cache (functions aren't serializable) // Solution: Tag every static method with a stable string identifier.
// With this: function._jqhtml_cache_id = "My_DataGrid.fetch_data" → cacheable // When jqhtml hashes component args, it uses _jqhtml_cache_id instead
// of the function reference, making the cache key deterministic.
//
// Example:
// <My_DataGrid $data_source=Controller.fetch />
//
// Without tagging: args hash includes [Function] → uncacheable
// With tagging: args hash includes "Controller.fetch" → cacheable
//
// This enables Ajax endpoints and other callbacks to be passed to
// components without breaking the automatic caching system.
// ───────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────
const all_classes = Manifest.get_all_classes(); const all_classes = Manifest.get_all_classes();
let methods_tagged = 0; let methods_tagged = 0;
@@ -106,20 +117,6 @@ class Jqhtml_Integration {
Rsx.trigger('jqhtml_ready'); Rsx.trigger('jqhtml_ready');
}); });
} }
// ═══════════════════════════════════════════════════════════════════════
// Utility Methods (pass-through to jqhtml runtime)
// ═══════════════════════════════════════════════════════════════════════
/** Get all registered component names. Useful for debugging/introspection. */
static get_component_names() {
return jqhtml.get_component_names();
}
/** Check if a component is registered by name. */
static has_component(name) {
return jqhtml.has_component(name);
}
} }
// Class is automatically made global by RSX manifest - no window assignment needed // Class is automatically made global by RSX manifest - no window assignment needed

View File

@@ -1,3 +1,3 @@
.Component_Init { ._Component_Init {
display:none; display: none;
} }

View File

@@ -1,236 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JQHTML Compilation Error</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
margin: 0;
padding: 0;
background: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
}
.error-header {
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
padding: 30px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.error-title {
color: #e74c3c;
font-size: 24px;
margin: 0 0 10px 0;
font-weight: 600;
}
.error-file {
color: #666;
font-size: 14px;
margin: 10px 0;
font-family: 'Courier New', monospace;
}
.error-location {
color: #999;
font-size: 13px;
}
.error-message {
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.error-message h3 {
color: #555;
font-size: 16px;
margin: 0 0 15px 0;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
.error-text {
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
margin: 0;
padding: 20px;
background: #f8f8f8;
border-radius: 4px;
border-left: 4px solid #e74c3c;
color: #444;
}
.error-context {
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.error-context h3 {
color: #555;
font-size: 16px;
margin: 0 0 15px 0;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
.code-context {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
background: #2d2d2d;
color: #f8f8f2;
padding: 20px;
border-radius: 4px;
overflow-x: auto;
white-space: pre;
}
.error-line {
background: rgba(231, 76, 60, 0.2);
display: inline-block;
width: 100%;
padding: 0 5px;
margin: 0 -5px;
}
.line-number {
color: #999;
display: inline-block;
width: 40px;
text-align: right;
padding-right: 10px;
border-right: 1px solid #444;
margin-right: 10px;
}
.error-suggestion {
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.error-suggestion h3 {
color: #27ae60;
font-size: 16px;
margin: 0 0 15px 0;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
.suggestion-text {
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
white-space: pre-wrap;
padding: 20px;
background: #f0f9f4;
border-radius: 4px;
border-left: 4px solid #27ae60;
color: #2c5f2d;
}
.error-trace {
background: #fff;
border: 1px solid #e1e1e1;
border-radius: 8px;
padding: 25px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.error-trace h3 {
color: #555;
font-size: 16px;
margin: 0 0 15px 0;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 500;
}
.trace-list {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.8;
}
.trace-item {
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
}
.trace-item:last-child {
border-bottom: none;
}
.trace-number {
color: #999;
display: inline-block;
width: 30px;
}
.trace-file {
color: #666;
}
.trace-function {
color: #3498db;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="error-header">
<h1 class="error-title">JQHTML Template Compilation Error</h1>
<div class="error-file">{{ $exception->getFile() }}</div>
@if($exception->getLine())
<div class="error-location">Line {{ $exception->getLine() }}@if(method_exists($exception, 'getColumn') && $exception->getColumn()), Column {{ $exception->getColumn() }}@endif</div>
@endif
</div>
<div class="error-message">
<h3>Error Message</h3>
<pre class="error-text">{{ $exception->getMessage() }}</pre>
</div>
@if(method_exists($exception, 'getContext') && $exception->getContext())
<div class="error-context">
<h3>Code Context</h3>
<pre class="code-context">{{ $exception->getContext() }}</pre>
</div>
@endif
@if(method_exists($exception, 'getSuggestion') && $exception->getSuggestion())
<div class="error-suggestion">
<h3>How to Fix</h3>
<pre class="suggestion-text">{{ $exception->getSuggestion() }}</pre>
</div>
@endif
@if(app()->environment('local', 'development'))
<div class="error-trace">
<h3>Stack Trace</h3>
<div class="trace-list">
@foreach($exception->getTrace() as $index => $trace)
@if($index < 10)
<div class="trace-item">
<span class="trace-number">#{{ $index }}</span>
<span class="trace-file">{{ $trace['file'] ?? 'unknown' }}:{{ $trace['line'] ?? '?' }}</span>
@if(isset($trace['class']))
<span class="trace-function">{{ $trace['class'] }}{{ $trace['type'] ?? '::' }}{{ $trace['function'] }}()</span>
@elseif(isset($trace['function']))
<span class="trace-function">{{ $trace['function'] }}()</span>
@endif
</div>
@endif
@endforeach
</div>
</div>
@endif
</div>
</body>
</html>

View File

@@ -488,6 +488,27 @@ NAVIGATION
Spa.action.reload(); // Reload current action Spa.action.reload(); // Reload current action
Spa.layout.update_nav(); // Call layout method Spa.layout.update_nav(); // Call layout method
SESSION VALIDATION
After each SPA navigation (except initial load and back/forward), the client
validates its state against the server by calling Rsx.validate_session().
This detects three types of staleness:
1. Codebase updates - build_key changed due to new deployment
2. User changes - account details modified (name, role, permissions)
3. Session changes - logged out, ACLs updated, session invalidated
On mismatch, triggers location.replace() for a transparent page refresh
that doesn't pollute browser history. The user sees the page reload with
fresh state, or is redirected to login if session was invalidated.
Validation is fire-and-forget (non-blocking) and fails silently on network
errors to avoid disrupting navigation. Stale state will be detected on
subsequent navigations or when the 30-minute SPA timeout triggers.
Manual validation:
// Returns true if valid, false if refresh triggered
const valid = await Rsx.validate_session();
FILE ORGANIZATION FILE ORGANIZATION
Standard SPA Module Structure: Standard SPA Module Structure:
/rsx/app/frontend/ /rsx/app/frontend/

File diff suppressed because one or more lines are too long

View File

@@ -32,14 +32,11 @@ const formatting_provider_1 = require("./formatting_provider");
const definition_provider_1 = require("./definition_provider"); const definition_provider_1 = require("./definition_provider");
const config_1 = require("./config"); const config_1 = require("./config");
const laravel_completion_provider_1 = require("./laravel_completion_provider"); const laravel_completion_provider_1 = require("./laravel_completion_provider");
const blade_spacer_1 = require("./blade_spacer");
const blade_client_1 = require("./blade_client");
const convention_method_provider_1 = require("./convention_method_provider"); const convention_method_provider_1 = require("./convention_method_provider");
const comment_file_reference_provider_1 = require("./comment_file_reference_provider"); const comment_file_reference_provider_1 = require("./comment_file_reference_provider");
const jqhtml_lifecycle_provider_1 = require("./jqhtml_lifecycle_provider"); const jqhtml_lifecycle_provider_1 = require("./jqhtml_lifecycle_provider");
const combined_semantic_provider_1 = require("./combined_semantic_provider"); const combined_semantic_provider_1 = require("./combined_semantic_provider");
const php_attribute_provider_1 = require("./php_attribute_provider"); const php_attribute_provider_1 = require("./php_attribute_provider");
const blade_component_provider_1 = require("./blade_component_provider");
const auto_rename_provider_1 = require("./auto_rename_provider"); const auto_rename_provider_1 = require("./auto_rename_provider");
const folder_color_provider_1 = require("./folder_color_provider"); const folder_color_provider_1 = require("./folder_color_provider");
const git_status_provider_1 = require("./git_status_provider"); const git_status_provider_1 = require("./git_status_provider");
@@ -216,14 +213,6 @@ async function activate(context) {
// Register formatting provider // Register formatting provider
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider({ language: 'php' }, formatting_provider)); context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider({ language: 'php' }, formatting_provider));
console.log('RSpade formatter registered for PHP files'); console.log('RSpade formatter registered for PHP files');
// Initialize Blade language configuration (indentation, auto-closing)
(0, blade_client_1.init_blade_language_config)();
console.log('Blade language configuration initialized');
// Register Blade auto-spacing on text change
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument((event) => {
(0, blade_spacer_1.blade_spacer)(event, vscode.window.activeTextEditor);
}));
console.log('Blade auto-spacing enabled');
// Register definition provider for JavaScript/TypeScript and PHP/Blade/jqhtml files // Register definition provider for JavaScript/TypeScript and PHP/Blade/jqhtml files
context.subscriptions.push(vscode.languages.registerDefinitionProvider([ context.subscriptions.push(vscode.languages.registerDefinitionProvider([
{ language: 'javascript' }, { language: 'javascript' },
@@ -265,10 +254,6 @@ async function activate(context) {
const php_attribute_provider = new php_attribute_provider_1.PhpAttributeSemanticTokensProvider(); const php_attribute_provider = new php_attribute_provider_1.PhpAttributeSemanticTokensProvider();
context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider([{ language: 'php' }], php_attribute_provider, new vscode.SemanticTokensLegend(['conventionMethod']))); context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider([{ language: 'php' }], php_attribute_provider, new vscode.SemanticTokensLegend(['conventionMethod'])));
console.log('PHP attribute provider registered for PHP files'); console.log('PHP attribute provider registered for PHP files');
// Register Blade component provider for uppercase component tags
const blade_component_provider = new blade_component_provider_1.BladeComponentSemanticTokensProvider();
context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider([{ language: 'blade' }, { pattern: '**/*.blade.php' }], blade_component_provider, new vscode.SemanticTokensLegend(['class', 'jqhtmlTagAttribute'])));
console.log('Blade component provider registered for Blade files');
// Debug client disabled // Debug client disabled
// debug_client = new DebugClient(formatting_provider as any); // debug_client = new DebugClient(formatting_provider as any);
// debug_client.start().catch(error => { // debug_client.start().catch(error => {

File diff suppressed because one or more lines are too long

View File

View File

@@ -29,7 +29,7 @@ const ide_bridge_client_1 = require("./ide_bridge_client");
/** /**
* JQHTML lifecycle methods that are called automatically by the framework * JQHTML lifecycle methods that are called automatically by the framework
*/ */
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy']; const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy', 'cache_id'];
/** /**
* Convention methods that are called automatically by the RSX framework * Convention methods that are called automatically by the RSX framework
*/ */
@@ -53,6 +53,7 @@ const LIFECYCLE_DOCS = {
on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.', on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.',
on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.', on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.',
on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.', on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
cache_id: 'Returns a unique cache key for this component instance. Used by framework to cache/restore component state. Return null to disable caching.',
}; };
/** /**
* Cache for subclass checks * Cache for subclass checks

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,7 @@
"name": "rspade-framework", "name": "rspade-framework",
"displayName": "RSpade Framework Support", "displayName": "RSpade Framework Support",
"description": "VS Code extension for RSpade framework with code folding, formatting, and namespace management", "description": "VS Code extension for RSpade framework with code folding, formatting, and namespace management",
"version": "0.1.218", "version": "0.1.219",
"publisher": "rspade", "publisher": "rspade",
"engines": { "engines": {
"vscode": "^1.74.0" "vscode": "^1.74.0"

View File

@@ -4,7 +4,7 @@ import { IdeBridgeClient } from './ide_bridge_client';
/** /**
* JQHTML lifecycle methods that are called automatically by the framework * JQHTML lifecycle methods that are called automatically by the framework
*/ */
const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy']; const JQHTML_LIFECYCLE_METHODS = ['on_render', 'on_create', 'on_load', 'on_ready', 'on_destroy', 'cache_id'];
/** /**
* Convention methods that are called automatically by the RSX framework * Convention methods that are called automatically by the RSX framework
@@ -30,6 +30,7 @@ const LIFECYCLE_DOCS: { [key: string]: string } = {
on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.', on_load: 'Data loading phase - fetch async data. NO DOM manipulation allowed, only update `this.data`. Template re-renders after load. MUST be async.',
on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.', on_ready: 'Final setup phase - all data loaded. Children complete before parent. DOM manipulation allowed. Can be sync or async.',
on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.', on_destroy: 'Component destruction phase - cleanup resources. Called before component is removed. MUST be synchronous.',
cache_id: 'Returns a unique cache key for this component instance. Used by framework to cache/restore component state. Return null to disable caching.',
}; };
/** /**

View File

@@ -893,7 +893,7 @@ Details: `php artisan rsx:man model_fetch`
### Migrations ### Migrations
**Forward-only, no rollbacks.** **Forward-only, no rollbacks. Deterministic transformations against known state.**
```bash ```bash
php artisan make:migration:safe create_users_table php artisan make:migration:safe create_users_table
@@ -902,6 +902,19 @@ php artisan migrate
php artisan migrate:commit php artisan migrate:commit
``` ```
**NO defensive coding in migrations:**
```php
// ❌ WRONG - conditional logic
$fk_exists = DB::select("SELECT ... FROM information_schema...");
if (!empty($fk_exists)) { DB::statement("ALTER TABLE foo DROP FOREIGN KEY bar"); }
// ✅ CORRECT - direct statements
DB::statement("ALTER TABLE foo DROP FOREIGN KEY bar");
DB::statement("ALTER TABLE foo DROP COLUMN baz");
```
No `IF EXISTS`, no `information_schema` queries, no fallbacks. Know current state, write exact transformation. Failures fail loud - snapshot rollback exists for recovery.
--- ---
## FILE ATTACHMENTS ## FILE ATTACHMENTS

86
node_modules/.package-lock.json generated vendored
View File

@@ -2211,9 +2211,9 @@
} }
}, },
"node_modules/@jqhtml/core": { "node_modules/@jqhtml/core": {
"version": "2.3.4", "version": "2.3.9",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.4.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.9.tgz",
"integrity": "sha512-RQLviu8NcIxrnd1cUOE8HNPmwWT/kVliwHl91zqYaYw3cXstF2PG9OxbRtiUWAdbOxuqJSLkELrXLG+4cr1o9w==", "integrity": "sha512-wgx/AqeCNPdsyaX+6POTEKEA9WePIgVhVsvjbff/TvJLhnqh5jC63FiHZ/BwVUBBWdt+k2GQ9PRQWf/tLdNX/A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
@@ -2237,9 +2237,9 @@
} }
}, },
"node_modules/@jqhtml/parser": { "node_modules/@jqhtml/parser": {
"version": "2.3.4", "version": "2.3.9",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.4.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.9.tgz",
"integrity": "sha512-jFc7RtmxMdt8ewJmuq6lw74c3dMHqaZ9BXUX3czwtU4w1ZxkLkppEhTmrm+wtQ7/rdK6jsfvL4W++wrXQtoclA==", "integrity": "sha512-c3/wE3RZcEiyZxplwlbMhuWQZAZmFOKgjImqSdh4v9NphEGorQoVlHjGVBhVtribNh8V8n8XdQNrWB7MlchuMg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
@@ -2277,21 +2277,21 @@
} }
}, },
"node_modules/@jqhtml/vscode-extension": { "node_modules/@jqhtml/vscode-extension": {
"version": "2.3.4", "version": "2.3.9",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.4.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.9.tgz",
"integrity": "sha512-AU3M2yD4tiGKcfkW2cBtO26yKzdJj5+Af1BBoblnr1zqPzlTyLaZapoLPL/bEpSwnaFB5I3XqKuBj2sKSDE30Q==", "integrity": "sha512-lvtwyDtaE5sOFEC9Iy9MQYp9Lmaj3QtYcz6GLUSxX24BP01xGvQl5eeKWpeZemnqfg28ppWbAR0CeP8ypP1ekg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"vscode": "^1.74.0" "vscode": "^1.74.0"
} }
}, },
"node_modules/@jqhtml/webpack-loader": { "node_modules/@jqhtml/webpack-loader": {
"version": "2.3.4", "version": "2.3.7",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.3.4.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/webpack-loader/-/webpack-loader-2.3.7.tgz",
"integrity": "sha512-QYanMVtRQ6Y5Xuw/AfZFdbSwMWnLBKo53/mpQtOEqdY1+cMCcbt/C3oxz8fJaCzAXWmxOZVqxU7kNxZ1YihRtQ==", "integrity": "sha512-dQJdWfqgU/JaJXHSwcd53k0sTuoCGxzjFRb4cHiJZfdwm14g3X8WKRMzM4rhdExiQwvW9QFZ9rrfV4TPD8sHNQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jqhtml/parser": "2.3.4", "@jqhtml/parser": "2.3.7",
"@types/loader-utils": "^2.0.6", "@types/loader-utils": "^2.0.6",
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@types/webpack": "^5.28.5", "@types/webpack": "^5.28.5",
@@ -2306,6 +2306,26 @@
"webpack": "^5.0.0" "webpack": "^5.0.0"
} }
}, },
"node_modules/@jqhtml/webpack-loader/node_modules/@jqhtml/parser": {
"version": "2.3.7",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.7.tgz",
"integrity": "sha512-wSfi5vP7IfBl8wgbGc2+is/blrIR50ySH4E9x2hK2M2SQtgo4MltQy6LtfZIHZt4e/hKuRxID7y3BE5aT5Xx/g==",
"license": "MIT",
"dependencies": {
"@types/jest": "^29.5.11",
"@types/jquery": "^3.5.32",
"@types/node": "^20.10.5",
"jest": "^29.7.0",
"source-map": "^0.7.4",
"typescript": "^5.3.3"
},
"bin": {
"jqhtml-compile": "bin/jqhtml-compile"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -5657,9 +5677,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.260", "version": "1.5.262",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
"integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==", "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/elliptic": { "node_modules/elliptic": {
@@ -5742,9 +5762,9 @@
} }
}, },
"node_modules/envinfo": { "node_modules/envinfo": {
"version": "7.20.0", "version": "7.21.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.20.0.tgz", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz",
"integrity": "sha512-+zUomDcLXsVkQ37vUqWBvQwLaLlj8eZPSi61llaEFAVBY5mhcXdaSw1pSJVl4yTYD5g/gEfpNl28YYk4IPvrrg==", "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"envinfo": "dist/cli.js" "envinfo": "dist/cli.js"
@@ -8912,9 +8932,9 @@
"optional": true "optional": true
}, },
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "1.3.1", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==",
"license": "(BSD-3-Clause OR GPL-2.0)", "license": "(BSD-3-Clause OR GPL-2.0)",
"engines": { "engines": {
"node": ">= 6.13.0" "node": ">= 6.13.0"
@@ -9468,12 +9488,12 @@
} }
}, },
"node_modules/playwright": { "node_modules/playwright": {
"version": "1.56.1", "version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright-core": "1.56.1" "playwright-core": "1.57.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
@@ -9486,9 +9506,9 @@
} }
}, },
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.56.1", "version": "1.57.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
@@ -10724,12 +10744,12 @@
} }
}, },
"node_modules/rollup-plugin-dts": { "node_modules/rollup-plugin-dts": {
"version": "6.2.3", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.2.3.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.3.0.tgz",
"integrity": "sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==", "integrity": "sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==",
"license": "LGPL-3.0-only", "license": "LGPL-3.0-only",
"dependencies": { "dependencies": {
"magic-string": "^0.30.17" "magic-string": "^0.30.21"
}, },
"engines": { "engines": {
"node": ">=16" "node": ">=16"

0
node_modules/@asamuzakjp/css-color/dist/browser/css-color.min.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/browser/css-color.min.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/cjs/index.cjs generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/cjs/index.cjs.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/cjs/index.d.cts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/index.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/index.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/index.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/cache.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/color.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/color.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/common.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/common.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/constant.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/convert.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-calc.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-gradient.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/css-var.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/relative-color.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/resolve.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/typedef.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/util.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/dist/esm/js/util.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/commonjs/index.d.ts generated vendored Normal file → Executable file
View File

View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/commonjs/index.js generated vendored Normal file → Executable file
View File

View File

View File

View File

View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/index.d.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/index.d.ts.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/index.js generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/index.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/index.min.js generated vendored Normal file → Executable file
View File

View File

0
node_modules/@asamuzakjp/css-color/node_modules/lru-cache/dist/esm/package.json generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/index.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/cache.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/color.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/common.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/constant.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/convert.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/css-calc.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/css-gradient.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/css-var.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/relative-color.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/resolve.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/typedef.ts generated vendored Normal file → Executable file
View File

0
node_modules/@asamuzakjp/css-color/src/js/util.ts generated vendored Normal file → Executable file
View File

0
node_modules/@babel/code-frame/lib/index.js generated vendored Normal file → Executable file
View File

0
node_modules/@babel/code-frame/lib/index.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/corejs2-built-ins.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/corejs3-shipped-proposals.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/native-modules.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/overlapping-plugins.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/plugin-bugfixes.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/compat-data/data/plugins.json generated vendored Normal file → Executable file
View File

0
node_modules/@babel/core/lib/config/cache-contexts.js generated vendored Normal file → Executable file
View File

0
node_modules/@babel/core/lib/config/cache-contexts.js.map generated vendored Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More