Utility Debug

Debug Development Bar

A fixed-position developer panel that lives at the bottom of the screen during development. The markup is rendered server-side (PHP outputs the panel and its data); this JavaScript module wires the tab switching, collapse toggle, AJAX log clearing, and localStorage state persistence. Activate any [data-wf-debug] element automatically on DOMContentLoaded , or call new WojoDebug(el).init() manually.

Debug

PHP Version: 8.2.18

Page load time: 0.043 s

Memory usage: 4.2 MB  /  Peak: 5.1 MB

Route: /utility/debug  ·  Controller: UtilityController@debug

$_GET     = []
$_POST    = []
$_SESSION = ["user_id" => 42, "role" => "admin", "locale" => "en"]
SELECT * FROM users WHERE id = 42
SELECT r.name FROM roles r JOIN user_roles ur ON r.id = ur.role_id WHERE ur.user_id = 42
SELECT * FROM sessions WHERE user_id = 42 ORDER BY created_at DESC LIMIT 10
UPDATE sessions SET last_active = NOW() WHERE id = 9
HTML
<div class="wf-debug" data-wf-debug>
    <div class="wf-debug-wrapper">
        <div class="wf-debug-legend">
            <span class="wf-debug-label">Debug</span>
            <div class="wf-debug-arrow">
                <button class="wf-debug-toggle" data-wf-debug-toggle
                        type="button" aria-label="Collapse debug panel">
                    <i class="icon small chevron up"></i>
                </button>
            </div>
            <button class="wf-debug-tab" type="button" data-wf-panel="contentGeneral">General</button>
            <button class="wf-debug-tab" type="button" data-wf-panel="contentParams">Params (3)</button>
            <button class="wf-debug-tab" type="button" data-wf-panel="contentWarnings">Warnings (0)</button>
            <button class="wf-debug-tab" type="button" data-wf-panel="contentErrors">Errors (0)</button>
            <button class="wf-debug-tab" type="button" data-wf-panel="contentQueries">SQL Queries (4)</button>
            <button class="wf-debug-clear" type="button" data-wf-debug-clear
                    aria-label="Clear session log">
                <i class="icon cancel"></i>
            </button>
        </div>
        <div id="contentGeneral"  class="wf-debug-panel" data-wf-panel="contentGeneral">...</div>
        <div id="contentParams"   class="wf-debug-panel" data-wf-panel="contentParams">...</div>
        <div id="contentWarnings" class="wf-debug-panel" data-wf-panel="contentWarnings"></div>
        <div id="contentErrors"   class="wf-debug-panel" data-wf-panel="contentErrors"></div>
        <div id="contentQueries"  class="wf-debug-panel" data-wf-panel="contentQueries">...</div>
    </div>
</div>

HTML Structure

The panel is fully server-rendered. JavaScript only adds behaviour – tab switching, toggle animation, and AJAX clearing. The root element carries data-wf-debug to trigger auto-init. The component opens as position: fixed at the bottom of the viewport with z-index: 2000 .

Each tab button and its corresponding panel share the same value for data-wf-panel . The panel element's id must match that value exactly. The tab whose panel should be active on the first load gets is-active added server-side (or JavaScript restores the last open tab from localStorage).

HTML
<!-- Root element: fixed at bottom, triggers auto-init -->
<div class="wf-debug" data-wf-debug>

    <!-- Dark wrapper: full-width container -->
    <div class="wf-debug-wrapper">

        <!-- Legend bar: label + toggle arrow + tabs + clear -->
        <div class="wf-debug-legend">

            <span class="wf-debug-label">Debug</span>

            <div class="wf-debug-arrow">
                <button class="wf-debug-toggle" data-wf-debug-toggle
                        type="button" aria-label="Collapse debug panel">
                    <i class="icon small chevron up"></i>
                </button>
            </div>

            <!-- Tab buttons: data-wf-panel must match panel id -->
            <button class="wf-debug-tab" type="button"
                    data-wf-panel="contentGeneral">General</button>

            <!-- ... more tabs ... -->

            <!-- Clear button: AJAX log clear -->
            <button class="wf-debug-clear" type="button"
                    data-wf-debug-clear
                    data-wf-url="/api/api-debug-clear.php"
                    data-wf-ajax-action="clearLog"
                    aria-label="Clear session log">
                <i class="icon cancel"></i>
            </button>

        </div><!-- /.wf-debug-legend -->

        <!-- Panels: hidden by default, shown when is-active -->
        <div id="contentGeneral" class="wf-debug-panel"
             data-wf-panel="contentGeneral">...</div>

    </div><!-- /.wf-debug-wrapper -->
</div><!-- /.wf-debug -->

Tab Panels

Each panel is a div.wf-debug-panel with a matching id and data-wf-panel . Panels are display: none by default; the active panel gets is-active and becomes scrollable at --wf-dbg-panel-height (default 200px). Use <p> tags for key–value rows and <pre> for multi-line code output.

General / Params panels

HTML
<div id="contentGeneral" class="wf-debug-panel" data-wf-panel="contentGeneral">
    <p>PHP Version: 8.2.18</p>
    <p>Page load: 0.043 s</p>
    <p>Memory: 4.2 MB / Peak: 5.1 MB</p>
</div>

<div id="contentParams" class="wf-debug-panel" data-wf-panel="contentParams">
    <pre>$_GET     = []
$_POST    = []
$_SESSION = ["user_id" => 42]</pre>
</div>

SQL Queries panel

The panel with data-wf-panel="contentQueries" automatically renders <pre> tags in the theme's query color ( --wf-dbg-query-color , default #db7de9 ) with a transparent background.

HTML
<div id="contentQueries" class="wf-debug-panel" data-wf-panel="contentQueries">
    <pre>SELECT * FROM users WHERE id = 42</pre>
    <pre>SELECT r.name FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = 42</pre>
</div>

Clear Button

The clear button sends a POST request to the URL stored in data-wf-url , with the data-wf-ajax-action value passed as the action body parameter. The request includes an X-Requested-With: XMLHttpRequest header. The button is disabled while the request is in flight to prevent double submission. Panels and localStorage state are not touched by the clear action.

HTML
<button class="wf-debug-clear" type="button"
        data-wf-debug-clear
        data-wf-url="/api/api-debug-clear.php"
        data-wf-ajax-action="clearLog"
        aria-label="Clear session log">
    <i class="icon cancel"></i>
</button>
Attribute Description
data-wf-debug-clear Marks this button as the clear trigger (required)
data-wf-url Server endpoint to POST to
data-wf-ajax-action Value sent as the action POST parameter

CSS Variables

The debug panel is intentionally dark-themed (independent of the page theme). All colors, sizes, and timing values are controlled by CSS custom properties on :root - override them in your stylesheet to customize the appearance without touching component CSS.

Variable Default Description
--wf-dbg-bg #030712 Panel background (very dark navy)
--wf-dbg-legend-bg #1e2939 Legend / tab bar background
--wf-dbg-alt-bg #101828 Alternate row / pre background
--wf-dbg-text --grey-color-200 Primary text color
--wf-dbg-muted --grey-color-500 Muted / inactive text color
--wf-dbg-border rgba(255,255,255,.07) Subtle border / hover tint
--wf-dbg-query-color #db7de9 SQL query text colour
--wf-dbg-shadow rgba(0,0,0,.4) Legend bar drop shadow colour
--wf-dbg-panel-height 200px Scrollable content area height
--wf-dbg-speed .18s Transition duration
--wf-dbg-ease ease Transition easing
CSS
:root {
    --wf-dbg-panel-height: 280px;       /* taller panel */
    --wf-dbg-query-color: #7dd3fc;      /* blue SQL queries */
    --wf-dbg-bg: #0a0a0a;              /* near-black background */
}

JavaScript API

Auto-init runs on DOMContentLoaded and stores the instance at el._wfDebug . For manual control, import WojoDebug and call init() - it returns the instance for chaining.

Initialization

JavaScript
// Auto-init (no code needed - runs on DOMContentLoaded):
// <div class="wf-debug" data-wf-debug>...</div>

// Access auto-init instance:
const dbg = document.querySelector('[data-wf-debug]')._wfDebug

// Manual init:
import { WojoDebug } from './wf-debug.js'
const dbg = new WojoDebug(el).init()

// Custom storage key (default: 'wf-debug-state'):
const dbg = new WojoDebug(el, { storageKey: 'my-debug' }).init()
// - or via HTML attribute:
// <div class="wf-debug" data-wf-debug data-wf-storage-key="my-debug">

Methods

JavaScript
const dbg = document.querySelector('[data-wf-debug]')._wfDebug

// Open a specific tab panel by its ID:
dbg.open('contentQueries')

// Collapse all panels (same as clicking the toggle arrow):
dbg.close()

// Remove all event listeners (teardown):
dbg.destroy()
Method / Option Description
new WojoDebug(el[, opts]) Constructor. opts.storageKey overrides the localStorage key (default: 'wf-debug-state')
.init() Wires all event listeners and restores the last panel from localStorage. Returns the instance.
.open(panelId) Activates the tab and panel matching panelId, sets data-wf-open on the root, saves state to localStorage
.close() Removes is-active from all tabs and panels, removes data-wf-open, saves 'none' to localStorage
.destroy() Removes all event listeners attached by this instance
data-wf-storage-key HTML attribute on the root element - overrides storageKey option

Events

All events are dispatched on the root .wf-debug element and bubble. The wf:debug-clear event is cancelable - call e.preventDefault() to abort the AJAX request before it fires.

JavaScript
const panel = document.querySelector('[data-wf-debug]')

// Panel opened - tab switched
panel.addEventListener('wf:debug-open', e => {
    console.log('opened panel:', e.detail.panelId)
})

// Panel collapsed (toggle arrow clicked)
panel.addEventListener('wf:debug-close', () => {
    console.log('debug panel collapsed')
})

// Before AJAX clear - cancelable
panel.addEventListener('wf:debug-clear', e => {
    // e.detail: { url, action, btn }
    if (!confirm('Clear the debug log?')) {
        e.preventDefault()   // abort the request
    }
})

// After successful AJAX clear
panel.addEventListener('wf:debug-cleared', e => {
    // e.detail: { url, action, data }
    console.log('log cleared, server response:', e.detail.data)
})
Event Cancelable detail Fired when
wf:debug-open No { panelId } A tab panel is opened
wf:debug-close No - Toggle arrow collapses all panels
wf:debug-clear Yes { url, action, btn } Clear button clicked, before the POST request
wf:debug-cleared No { url, action, data } Server responded successfully to the clear POST

Class Reference

Class / Attribute Element Description
Root
wf-debug div Root element - position: fixed, bottom of viewport, full width, z-index: 2000
data-wf-debug Root Triggers auto-init on DOMContentLoaded
data-wf-storage-key Root Custom localStorage key for state persistence (default: wf-debug-state)
data-wf-open Root Present when any panel is open (JS-managed); rotates toggle arrow 180°
Layout
wf-debug-wrapper div Inner wrapper with dark background and horizontal padding
wf-debug-legend div Horizontal bar containing the label, toggle, tabs, and clear button
Legend elements
wf-debug-label span Static "Debug" label text
wf-debug-arrow div Toggle button wrapper
wf-debug-toggle button Collapse/expand button - icon rotates 180° when panel is open
data-wf-debug-toggle Toggle button Marks the toggle button for the click handler
wf-debug-tab button Tab button; data-wf-panel value must match the target panel's id
is-active Tab / Panel Active state - set by JS; can be pre-set server-side for the default open tab
wf-debug-clear button AJAX clear button (red, opacity 0.8)
data-wf-debug-clear Clear button Marks the clear button; data-wf-url + data-wf-ajax-action configure the POST
Panels
wf-debug-panel div Content panel – hidden (display:none) until is-active; scrollable at --wf-dbg-panel-height
data-wf-panel Tab / Panel Links tab buttons to panels – value must match the panel's id attribute
Show More