# AJAX Runtime Developer Guide

This guide documents the runtime behavior in custom/adminTmpl/scripts/ajax-runtime.php for engineers who maintain or extend the navigation system.

## 1. Runtime Goal

The runtime provides shell-based AJAX navigation where only the main content region is replaced.

Design target:

- Keep header, sidebar, and footer stable.
- Replace only .app-main on route change.
- Preserve browser history and title updates.
- Fall back to hard navigation when constraints are not met.

## 2. Where It Runs

Runtime script location:

- custom/adminTmpl/scripts/ajax-runtime.php

Loaded by:

- custom/adminTmpl/scripts.php

Shell page:

- dist/adminTmpl.php

Runtime starts only when all boot conditions pass.

## 3. Boot Conditions

Boot is aborted when any of these is true:

1. Feature flag disabled.
2. Current URL is outside AJAX scope.
3. Current document does not contain .app-main.

Feature flags:

- ADMINLTE_AJAX_NAV_ENABLED controls enable or disable.
- ADMINLTE_AJAX_NAV_DEBUG enables debug logs.

## 4. Core Configuration

Runtime config object contains:

- enabled
- debug
- mainSelector set to .app-main
- blockedPaths set of routes that must full navigate

blockedPaths is the policy boundary for full page routes.

## 5. Function Reference

### log

Purpose:

- Conditional debug logger.

Behavior:

- Writes console debug entries only when debug flag is enabled.

### normalizeChromeLinks

Purpose:

- Normalize links in sidebar and header to absolute URLs.

Behavior:

- Scans .app-sidebar and .app-header anchors.
- Skips hash, mailto, tel, and javascript pseudo links.
- Converts eligible href values using shell base URL.

Why important:

- Prevents relative-path drift after history URL changes.

### normalizePath

Purpose:

- Canonical path normalization.

Behavior:

- Parses URL pathname.
- Removes trailing slash except root.

### isAjaxScopeUrl

Purpose:

- Eligibility check for AJAX navigation.

Rules:

- Same origin only.
- Not in blockedPaths.
- Must be /adminTmpl.php or end with .html.

### matchesRoute

Purpose:

- Generic matcher for route hooks.

Supported matcher types:

- string
- RegExp
- function

### registerPageHook

Purpose:

- Register route-specific lifecycle handlers.

Input:

- matcher
- hooks object with init and or dispose handlers

### runRouteHooks

Purpose:

- Execute matching hook handlers.

Behavior:

- Resolves current path.
- Evaluates each registry entry.
- Executes hook safely with try and catch.

### wirePilotHooks

Purpose:

- Default hook wiring for dashboard and mailbox routes.

Current behavior:

- Logs init and dispose diagnostics for known routes.

### dispatchLifecycleEvent

Purpose:

- Emit CustomEvent lifecycle notifications.

Events emitted by runtime:

- ajax:beforeNavigate
- ajax:pageDispose
- ajax:contentReplaced
- ajax:pageInit
- ajax:navigateError

### hardNavigate

Purpose:

- Force full browser navigation.

### focusMainRegion

Purpose:

- Accessibility focus handoff after content swap.

Behavior:

- Ensures tabindex exists on .app-main.
- Moves focus to .app-main without scroll jump.

### navigate

Purpose:

- Primary navigation pipeline.

Inputs:

- url
- mode push or pop

High-level workflow:

1. Validate scope eligibility.
2. Validate source .app-main exists.
3. Abort previous in-flight request.
4. Start new fetch with AbortController.
5. Parse destination HTML.
6. Extract destination .app-main.
7. Dispatch dispose lifecycle and route hooks.
8. Replace source .app-main with destination .app-main.
9. Update document title.
10. Push history state when mode is push.
11. Dispatch init lifecycle and route hooks.
12. Focus main region.

Error handling:

- AbortError exits silently.
- Other failures emit ajax:navigateError then fall back to hard navigation.

Race handling:

- requestToken ensures only latest request can apply updates.

### shouldIntercept

Purpose:

- Decide if a click should be intercepted.

Rejects when:

- Event already prevented.
- Non-left click.
- Modifier keys used.
- target is not _self.
- download link.
- href is empty, hash, mailto, or tel.
- URL not eligible for AJAX scope.

### onDocumentClick

Purpose:

- Delegated click interceptor.

Behavior:

- Finds nearest anchor from event target.
- Applies shouldIntercept.
- Prevents default and calls navigate in push mode.

### onPopState

Purpose:

- Browser back and forward handling.

Behavior:

- Runs navigate in pop mode for eligible scope URLs.

## 6. Runtime State Model

Internal state variables:

- activeController for cancellation of in-flight request
- requestToken for latest-request-wins safety
- routeHookRegistry for route lifecycle behavior

Global export:

- window.ADMINLTE_AJAX_NAV with registerPageHook

## 7. Programmer Requirements

To make a page AJAX-compatible:

1. Response HTML must contain .app-main.
2. URL must pass scope checks.
3. Route must not be in blockedPaths.
4. Page should include title for proper title updates.

To keep shell stable:

1. Header, sidebar, footer stay outside .app-main.
2. Main content must stay inside .app-main.

## 8. Extension Workflow

### Add a blocked full-navigation route

1. Add normalized route path to blockedPaths.
2. Validate by clicking route from shell.

### Add route-specific init or cleanup logic

1. Register matcher with registerPageHook.
2. Add init for attach behavior after swap.
3. Add dispose for cleanup before replacement.
4. Validate repeated navigation for leaks or duplicate listeners.

### Add observability

1. Enable debug flag before runtime boot.
2. Watch console logs and lifecycle events.

## 9. Testing Workflow

Recommended sequence:

1. Syntax check runtime file with php -l.
2. Run fast smoke profile.
3. Run extended smoke profile.
4. Manually test sidebar clicks, back and forward, and one blocked route.
5. Test rapid multi-click race behavior.

## 10. Common Failure Modes And Fixes

Full reload on expected AJAX route:

- Check runtime loaded and enabled.
- Check route not blocked.
- Check destination has .app-main.
- Check same-origin and extension scope.

Path duplication after several clicks:

- Confirm normalizeChromeLinks executes at startup.
- Confirm links are in sidebar or header containers.

Title not updating:

- Ensure destination page contains title element.

Duplicate page listeners or memory leak:

- Move page setup to init hook.
- Move cleanup to dispose hook.

## 11. Change Safety Checklist

Before merge:

1. Runtime file syntax passes.
2. Basic and extended smoke pass.
3. Mailbox and one non-mailbox route verified.
4. Blocked route still full navigates.
5. Back and forward still operate in AJAX mode.

## 12. Related Files

- custom/adminTmpl/scripts/ajax-runtime.php
- custom/adminTmpl/scripts.php
- custom/adminTmpl/sections/sidebar.php
- dist/adminTmpl.php
- ADMINTMPL-MAIN-LOADING-DESIGN.md
- ADMINTMPL-FAQ.md
