Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation Implement underscore prefix for system tables Fix JS syntax linter to support decorators and grant exception to Task system SPA: Update planning docs and wishlists with remaining features SPA: Document Navigation API abandonment and future enhancements Implement SPA browser integration with History API (Phase 1) Convert contacts view page to SPA action Convert clients pages to SPA actions and document conversion procedure SPA: Merge GET parameters and update documentation Implement SPA route URL generation in JavaScript and PHP Implement SPA bootstrap controller architecture Add SPA routing manual page (rsx:man spa) Add SPA routing documentation to CLAUDE.md Phase 4 Complete: Client-side SPA routing implementation Update get_routes() consumers for unified route structure Complete SPA Phase 3: PHP-side route type detection and is_spa flag Restore unified routes structure and Manifest_Query class Refactor route indexing and add SPA infrastructure Phase 3 Complete: SPA route registration in manifest Implement SPA Phase 2: Extract router code and test decorators Rename Jqhtml_Component to Component and complete SPA foundation setup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
360
app/RSpade/Lib/Flash/CLAUDE.md
Executable file
360
app/RSpade/Lib/Flash/CLAUDE.md
Executable file
@@ -0,0 +1,360 @@
|
||||
# Flash Alert System
|
||||
|
||||
The Flash system provides server-to-client messaging that persists across redirects and Ajax calls, with client-side queue persistence for seamless navigation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
**Data Flow:**
|
||||
1. PHP code creates flash message → stored in database with type_id and session_id
|
||||
2. Message expires after 1 minute if not rendered
|
||||
3. On page render OR Ajax response → messages retrieved and deleted from database
|
||||
4. Client receives messages via `window.rsxapp.flash_alerts` or `response.flash_alerts`
|
||||
5. `Server_Side_Flash.js` processes messages → calls `Flash_Alert.js` display methods
|
||||
6. Client queue persisted to sessionStorage (per-tab) → survives page navigations
|
||||
7. On page load → restore queue from sessionStorage (messages < 20 seconds old)
|
||||
|
||||
## PHP Side - Creating Flash Messages
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```php
|
||||
use App\RSpade\Lib\Flash\Flash_Alert;
|
||||
|
||||
// In controllers
|
||||
Flash_Alert::success('Account created successfully!');
|
||||
Flash_Alert::error('Invalid email address');
|
||||
Flash_Alert::info('Your session will expire in 5 minutes');
|
||||
Flash_Alert::warning('This action cannot be undone');
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
**Storage:**
|
||||
- Messages stored in `flash_alerts` table with `type_id` (enum), `message`, and `session_id`
|
||||
- Uses integer enum: 1=success, 2=error, 3=info, 4=warning (see Flash_Alert_Model)
|
||||
- Linked to session via `Session::get_session_id()`
|
||||
- No session = messages silently not created
|
||||
|
||||
**Retrieval:**
|
||||
- `Flash_Alert::get_pending_messages()` retrieves all pending messages for current session
|
||||
- Automatically deletes expired messages (> 1 minute old) before retrieval
|
||||
- Automatically deletes retrieved messages (one-time display)
|
||||
- Returns array: `[['type' => 'success', 'message' => '...'], ...]`
|
||||
|
||||
**Automatic Integration:**
|
||||
- Page rendering: Added to `window.rsxapp.flash_alerts` in bundle render (Rsx_Bundle_Abstract.php:290)
|
||||
- Ajax responses: Added to `response.flash_alerts` in both success and error responses (Ajax.php:347, 447)
|
||||
|
||||
## JavaScript Side - Displaying Flash Messages
|
||||
|
||||
### Client Components
|
||||
|
||||
**Flash_Alert.js** (`/system/app/RSpade/Lib/Flash/Flash_Alert.js`):
|
||||
- Client-side display component with queue system, auto-dismiss, animations
|
||||
- Methods: `Flash_Alert.success()`, `.error()`, `.info()`, `.warning()`
|
||||
- Features: 2.5s queue spacing, auto-dismiss (4s success, 6s others), click-to-dismiss
|
||||
- Queue persistence: Saves state to sessionStorage on queue changes, restores on page load
|
||||
- Stale message filtering: Only restores messages < 20 seconds old
|
||||
- Styling: Bootstrap alert classes with icons
|
||||
|
||||
**Server_Side_Flash.js** (`/system/app/RSpade/Lib/Flash/Server_Side_Flash.js`):
|
||||
- Bridge between server data and Flash_Alert display
|
||||
- Processes `flash_alerts` arrays from server
|
||||
- Restores persisted queue state from sessionStorage on framework init
|
||||
- Called automatically on page load (framework init hook)
|
||||
- Called automatically on Ajax responses (Ajax.js:190)
|
||||
|
||||
### How Client Processing Works
|
||||
|
||||
**Page Load:**
|
||||
```javascript
|
||||
// Automatic via _on_framework_core_init() hook
|
||||
// 1. Restore persisted queue from sessionStorage (only fresh messages)
|
||||
Flash_Alert._restore_queue_state();
|
||||
|
||||
// 2. Process new server messages
|
||||
if (window.rsxapp && window.rsxapp.flash_alerts) {
|
||||
Server_Side_Flash.process(window.rsxapp.flash_alerts);
|
||||
}
|
||||
```
|
||||
|
||||
**Ajax Responses:**
|
||||
```javascript
|
||||
// Automatic in Ajax.js success handler
|
||||
if (response.flash_alerts && Array.isArray(response.flash_alerts)) {
|
||||
Server_Side_Flash.process(response.flash_alerts);
|
||||
}
|
||||
```
|
||||
|
||||
**Processing Logic:**
|
||||
```javascript
|
||||
Server_Side_Flash.process(flash_alerts) {
|
||||
flash_alerts.forEach(alert => {
|
||||
const method = alert.type; // 'success', 'error', 'info', 'warning'
|
||||
Flash_Alert[method](alert.message); // Calls Flash_Alert.success(), etc.
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
**Table:** `flash_alerts`
|
||||
|
||||
```sql
|
||||
CREATE TABLE flash_alerts (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
session_id BIGINT NOT NULL,
|
||||
type_id BIGINT NOT NULL, -- 1=success, 2=error, 3=info, 4=warning
|
||||
message LONGTEXT NOT NULL,
|
||||
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3),
|
||||
created_by BIGINT,
|
||||
updated_by BIGINT,
|
||||
updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
INDEX idx_session_id (session_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
**Model:** `Flash_Alert_Model` with type_id enum definition
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
**Post-Redirect Flash:**
|
||||
```php
|
||||
public static function create_account(Request $request, array $params = []) {
|
||||
$user = new User_Model();
|
||||
$user->email = $request->input('email');
|
||||
$user->save();
|
||||
|
||||
Flash_Alert::success('Account created successfully!');
|
||||
return redirect(Rsx::Route('Dashboard_Controller'));
|
||||
}
|
||||
```
|
||||
|
||||
**Ajax Error Handling:**
|
||||
```php
|
||||
#[Ajax_Endpoint]
|
||||
public static function save_settings(Request $request, array $params = []) {
|
||||
if (!$request->input('email')) {
|
||||
Flash_Alert::error('Email is required');
|
||||
return response_form_error('Validation failed', ['email' => 'Required']);
|
||||
}
|
||||
|
||||
// Save settings...
|
||||
Flash_Alert::success('Settings saved!');
|
||||
return ['saved' => true];
|
||||
}
|
||||
```
|
||||
|
||||
**Multi-Step Workflows:**
|
||||
```php
|
||||
// Step 1
|
||||
Flash_Alert::info('Please verify your email before continuing');
|
||||
return redirect(Rsx::Route('Verify_Email_Controller'));
|
||||
|
||||
// Step 2 (after email verified)
|
||||
Flash_Alert::success('Email verified! Your account is now active');
|
||||
return redirect(Rsx::Route('Dashboard_Controller'));
|
||||
```
|
||||
|
||||
## Migration from Old System
|
||||
|
||||
**Old (removed):**
|
||||
```php
|
||||
Rsx::flash_success('Message');
|
||||
Rsx::flash_error('Message');
|
||||
Rsx::render_flash_alerts(); // In blade views
|
||||
```
|
||||
|
||||
**New:**
|
||||
```php
|
||||
use App\RSpade\Lib\Flash\Flash_Alert;
|
||||
|
||||
Flash_Alert::success('Message');
|
||||
Flash_Alert::error('Message');
|
||||
// No render call needed - automatic via window.rsxapp
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
**Why 1-minute expiration?**
|
||||
- Prevents stale messages from appearing hours/days later
|
||||
- Covers normal redirect timing (< 1 second)
|
||||
- Covers slow network conditions
|
||||
- Aggressive cleanup keeps database small
|
||||
|
||||
**Why delete on retrieval?**
|
||||
- One-time display semantics (flash = temporary)
|
||||
- Prevents duplicate display on page refresh
|
||||
- Simpler than tracking "displayed" status
|
||||
|
||||
**Why session-based?**
|
||||
- Links messages to specific user session
|
||||
- Automatic cleanup when session expires
|
||||
- No messages shown without session (no logged-out flash)
|
||||
|
||||
**Why both page render AND Ajax integration?**
|
||||
- Page render: Handles redirects (Flash_Alert::success() → redirect → display)
|
||||
- Ajax: Handles same-page updates (Flash_Alert::success() → Ajax response → display)
|
||||
- Unified API works everywhere
|
||||
|
||||
**Why sessionStorage persistence?**
|
||||
- Handles Ajax + immediate redirect scenario (message queued, then page navigates)
|
||||
- Per-tab isolation (messages don't leak across browser tabs)
|
||||
- Messages survive page navigation (up to 20 seconds)
|
||||
- Automatic cleanup (stale messages filtered out, removed on dismiss)
|
||||
- No server-side complexity (client handles queue restoration)
|
||||
|
||||
## Queue Persistence System
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
**Two-queue design:**
|
||||
- **Working queue** (`_queue`): Controls display timing and spacing (2.5s between alerts)
|
||||
- **Persistence queue** (`_persistence_queue`): Saved to sessionStorage, survives navigation
|
||||
|
||||
**Why two queues?**
|
||||
- Working queue: Messages removed when displayed → prevents duplicate displays
|
||||
- Persistence queue: Messages removed when fadeout starts → maintains state across navigation
|
||||
- Separation ensures proper timing while enabling seamless cross-page experience
|
||||
|
||||
### Storage Mechanism
|
||||
|
||||
**Storage type:** sessionStorage (per-tab, survives navigation, cleared on tab close)
|
||||
|
||||
**Storage key:** `rsx_flash_queue`
|
||||
|
||||
**Stored data structure:**
|
||||
```javascript
|
||||
{
|
||||
last_updated: 1234567890, // Timestamp of last save
|
||||
messages: [
|
||||
{
|
||||
message: "Success!",
|
||||
level: "success",
|
||||
timeout: null,
|
||||
position: "top",
|
||||
queued_at: 1234567890,
|
||||
fade_in_complete: false, // Set true after fade-in animation
|
||||
fadeout_start_time: null // Timestamp when fadeout should begin
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Message Lifecycle & State Tracking
|
||||
|
||||
**Lifecycle stages:**
|
||||
1. **Queued**: Added to both queues, saved to sessionStorage
|
||||
2. **Displaying** (fade-in): 400ms fade-in animation
|
||||
3. **Fade-in complete**: `fade_in_complete = true`, `fadeout_start_time` calculated and saved
|
||||
4. **Visible**: Display duration (4s success, 6s others)
|
||||
5. **Fadeout**: 1s opacity fade + 250ms slide up, removed from persistence queue
|
||||
6. **Removed**: Element removed from DOM
|
||||
|
||||
**State tracking:**
|
||||
- `fade_in_complete`: Marks when fade-in animation completes
|
||||
- `fadeout_start_time`: Absolute timestamp when fadeout should begin
|
||||
- `last_updated`: Queue-level timestamp for staleness check
|
||||
|
||||
**Why track timing state?**
|
||||
- Enables SPA-like experience: alerts maintain consistent timing across page navigations
|
||||
- Prevents timing restarts: navigating mid-alert doesn't reset the display duration
|
||||
- Allows immediate display: alerts that completed fade-in on previous page show instantly on next page
|
||||
|
||||
### When State Changes
|
||||
|
||||
**State saved to sessionStorage:**
|
||||
- New message queued (`Flash_Alert._show()`)
|
||||
- Fade-in completes (`_mark_fade_in_complete()`)
|
||||
- Fadeout scheduled (`_set_fadeout_start_time()`)
|
||||
- Message removed from persistence queue (`_remove_from_persistence_queue()`)
|
||||
|
||||
**State restored from sessionStorage:**
|
||||
- On page load via `Server_Side_Flash._on_framework_core_init()` hook
|
||||
- Before processing new server messages
|
||||
- Applies staleness filter and fadeout time filter
|
||||
|
||||
**State cleared:**
|
||||
- Fadeout begins for individual messages (removed from persistence queue)
|
||||
- Entire queue becomes stale (last_updated > 20 seconds)
|
||||
- All messages dismissed/removed (storage key deleted)
|
||||
|
||||
### Staleness & Filtering
|
||||
|
||||
**20-second rule:**
|
||||
- If `last_updated` timestamp is > 20 seconds old, entire queue is discarded
|
||||
- Prevents ancient messages from appearing after long delays
|
||||
- Based on queue-level timestamp, not individual message age
|
||||
|
||||
**Fadeout time filter:**
|
||||
- On restore, messages past their `fadeout_start_time` are discarded
|
||||
- Prevents "zombie" messages that should have already faded out
|
||||
- Only applies to messages with scheduled fadeouts (fade-in complete)
|
||||
|
||||
### Navigation Behavior
|
||||
|
||||
**On page navigation (beforeunload):**
|
||||
- Alerts still fading in: Hidden immediately (will restore and continue fade-in on next page)
|
||||
- Alerts fully visible: Remain visible during navigation (with scheduled fadeout)
|
||||
- Persistence queue: Unchanged (all state preserved to sessionStorage)
|
||||
|
||||
**On page load (restoration):**
|
||||
- Messages with `fade_in_complete = true`:
|
||||
- Displayed immediately (no fade-in animation, no queue delay)
|
||||
- Honor original `fadeout_start_time` (not restarted)
|
||||
- Displayed outside normal queue (doesn't block queue processing)
|
||||
- Messages with `fade_in_complete = false`:
|
||||
- Added to working queue for normal processing
|
||||
- Display with 2.5s spacing and full fade-in animation
|
||||
- Calculate new `fadeout_start_time` after fade-in completes
|
||||
|
||||
### Common Scenarios
|
||||
|
||||
**Scenario 1: Ajax + Redirect (primary use case)**
|
||||
1. Ajax response includes flash message
|
||||
2. JavaScript queues message and saves to sessionStorage
|
||||
3. Page redirects immediately (before message displays)
|
||||
4. New page loads, restores queue from sessionStorage
|
||||
5. Message displays normally with 2.5s queue spacing
|
||||
6. Message removed from storage when fadeout begins
|
||||
|
||||
**Scenario 2: Mid-animation navigation**
|
||||
1. Alert is fading in (fade_in_complete = false)
|
||||
2. User navigates away (alert hidden by beforeunload)
|
||||
3. New page loads, message restored to working queue
|
||||
4. Alert displays with normal queue timing and fade-in animation
|
||||
5. After fade-in completes, fade_in_complete = true saved
|
||||
|
||||
**Scenario 3: Visible alert navigation (seamless SPA-like)**
|
||||
1. Alert fully visible (fade_in_complete = true, fadeout_start_time set)
|
||||
2. User navigates away (alert remains visible during navigation)
|
||||
3. New page loads, message has fade_in_complete = true
|
||||
4. Alert displayed immediately (no animation, no delay)
|
||||
5. Honors original fadeout_start_time (e.g., if 2s remaining, fades out in 2s)
|
||||
6. Creates seamless experience: alert appears to "survive" navigation
|
||||
|
||||
**Scenario 4: Multiple messages + navigation**
|
||||
1. Queue has 3 messages, first one displaying (fade_in_complete = true)
|
||||
2. User navigates away
|
||||
3. New page loads:
|
||||
- First message (fade_in_complete = true): Shows immediately
|
||||
- Second message (fade_in_complete = false): Added to queue, displays in 2.5s
|
||||
- Third message (fade_in_complete = false): Added to queue, displays in 5s
|
||||
4. Queue processing continues normally for remaining messages
|
||||
|
||||
## File Locations
|
||||
|
||||
**PHP:**
|
||||
- `/system/app/RSpade/Lib/Flash/Flash_Alert.php` - Main Flash class
|
||||
- `/system/app/RSpade/Lib/Flash/Flash_Alert_Model.php` - Database model with type_id enum
|
||||
- Integration in `/system/app/RSpade/Core/Bundle/Rsx_Bundle_Abstract.php:290`
|
||||
- Integration in `/system/app/RSpade/Core/Ajax/Ajax.php:347, 447`
|
||||
|
||||
**JavaScript:**
|
||||
- `/system/app/RSpade/Lib/Flash/Flash_Alert.js` - Display component with persistence
|
||||
- `/system/app/RSpade/Lib/Flash/Flash_Alert.scss` - Styles
|
||||
- `/system/app/RSpade/Lib/Flash/Server_Side_Flash.js` - Server bridge
|
||||
- Integration in `/system/app/RSpade/Core/Js/Ajax.js:190`
|
||||
|
||||
**Database:**
|
||||
- Migration: `/system/database/migrations/2025_11_13_193740_modify_flash_alerts_table_for_type_based_system.php`
|
||||
Reference in New Issue
Block a user