Files
rspade_system/app/RSpade/Lib/Flash/CLAUDE.md
root 9ebcc359ae 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>
2025-11-19 17:48:15 +00:00

13 KiB
Executable File

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

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:

// 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:

// Automatic in Ajax.js success handler
if (response.flash_alerts && Array.isArray(response.flash_alerts)) {
    Server_Side_Flash.process(response.flash_alerts);
}

Processing Logic:

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

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:

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:

#[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:

// 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):

Rsx::flash_success('Message');
Rsx::flash_error('Message');
Rsx::render_flash_alerts(); // In blade views

New:

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:

{
    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