Files
rspade_system/node_modules/@jqhtml/router/LLM_REFERENCE.md
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

9.5 KiB
Executable File

JQHTML SPA Router System - LLM Reference

Core Architecture

  • Static router class Jqhtml_Router manages all navigation
  • Routes are ES6 classes extending Jqhtml_Route
  • Layouts are ES6 classes extending Jqhtml_Layout
  • SPA container is ES6 class extending Jqhtml_SPA
  • Hash routing (#/path) automatically enabled for file:// protocol
  • Standard routing for http:// and https:// protocols

Route Definition

Basic Route Class

class HomeRoute extends Jqhtml_Route {
  static routes = ['/'];  // URL patterns this route handles
  static layout = 'MainLayout';  // Layout to use (optional)
  static meta = { name: 'home', requiresAuth: false };  // Metadata
  
  async on_render() {
    // Route component render logic
  }
  
  async pre_dispatch() {
    // Return false to cancel navigation
    // Return string URL to redirect
    return true;
  }
  
  async post_dispatch() {
    // Called after route fully loaded
  }
}

Route Patterns

static routes = [
  '/',                     // Exact match
  '/users',                // Static path
  '/users/:id',            // Parameter capture
  '/posts/:id/comments',   // Multiple segments
  '/admin/:section/:id'    // Multiple parameters
];

Parameter Access

class UserRoute extends Jqhtml_Route {
  static routes = ['/users/:id'];
  
  async on_load() {
    // URL: /users/123?tab=profile
    const userId = this.args.id;        // '123' from URL pattern
    const tab = this.args.tab;          // 'profile' from query string
    const hash = this.args.hash;        // Fragment after #
  }
}

Layout System

Layout Definition

class MainLayout extends Jqhtml_Layout {
  async on_render() {
    this.$.html(`
      <header>Navigation</header>
      <main $id="content"></main>  <!-- REQUIRED: Routes render here -->
      <footer>Footer</footer>
    `);
  }
  
  async on_route_change(old_route, new_route) {
    // Update active navigation state
    this.$find('.nav-link').removeClass('active');
    this.$find(`[href="${new_route.path}"]`).addClass('active');
  }
  
  should_rerender() {
    return false;  // Layouts persist across route changes
  }
  
  async pre_dispatch(route_info) {
    // Can intercept routing at layout level
    if (route_info.meta.requiresAuth && !this.data.user) {
      return '/login';  // Redirect
    }
    return true;
  }
}

Layout Persistence

  • Layouts never re-render during route changes within same layout
  • Layout instance persists until different layout needed
  • $id="content" element required for route injection point

SPA Application Container

Basic SPA Setup

class App extends Jqhtml_SPA {
  async on_create() {
    // Register all routes
    HomeRoute.init();
    UserRoute.init();
    AdminRoute.init();
    
    // Register layouts
    Jqhtml_Router.register_layout('MainLayout', MainLayout);
    Jqhtml_Router.register_layout('AdminLayout', AdminLayout);
    
    // Set default layout
    Jqhtml_Router.set_default_layout('MainLayout');
    
    // Initialize router
    await this.init_router({
      default_layout: 'MainLayout'
    });
  }
  
  async pre_dispatch(route_info) {
    // Global route guard
    // Runs before layout.pre_dispatch()
    return true;
  }
  
  async post_dispatch(route_info) {
    // Global post-navigation logic
    // Runs after layout.post_dispatch()
  }
}

// Mount application
$('#app').append(new App().$);

Navigation Methods

Programmatic Navigation

// Navigate to URL
Jqhtml_Router.dispatch('/users/123');

// Navigate with replace (no history entry)
Jqhtml_Router.replace('/login');

// Navigate with parameters
const url = Jqhtml_Router.url('user-profile', { id: 123, tab: 'settings' });
Jqhtml_Router.dispatch(url);

// From within route component
this.dispatch({ id: 456 });  // Merges with current args
  • All <a> tags automatically intercepted for same-origin navigation
  • External links and target="_blank" not intercepted
  • Ctrl/Cmd+Click opens in new tab (not intercepted)

URL Building

// Build URL from route name
Jqhtml_Router.url('user-profile', { id: 123 });  // '/users/123'

// Build URL from pattern
Jqhtml_Router.build_url('/users/:id', { id: 123, tab: 'info' });  // '/users/123?tab=info'

// From route class
UserRoute.url({ id: 123 });  // '/users/123'

Dispatch Lifecycle

Execution Order

  1. URL Matching - Parse URL, extract parameters
  2. App.pre_dispatch() - Global guard, can cancel/redirect
  3. Layout Resolution - Create/reuse layout based on route
  4. Layout.pre_dispatch() - Layout-level guard
  5. Route Instantiation - Create route component with args
  6. Route.pre_dispatch() - Route-level guard
  7. Layout._render_route() - Inject route into layout
  8. Route Lifecycle - Standard component lifecycle
  9. Layout.on_route_change() - Notify layout of change
  10. Route.post_dispatch() - Route complete
  11. Layout.post_dispatch() - Layout handling complete
  12. App.post_dispatch() - Global post-navigation

Guard Return Values

async pre_dispatch(route_info) {
  return true;        // Continue navigation
  return false;       // Cancel navigation
  return '/login';    // Redirect to different route
}

Router State

Access Current State

// Static access
Jqhtml_Router.state = {
  route: 'UserRoute',      // Current route component name
  layout: 'MainLayout',    // Current layout name
  url: '/users/123',       // Current URL path
  args: { id: '123' },     // Current parameters
  hash: 'section'          // Current hash fragment
};

// Get current route info
const current = Jqhtml_Router.current_route_info;
// { url, path, args, hash, meta, component_name, component_class, layout }

// From SPA class
Jqhtml_SPA.state;  // Same as Jqhtml_Router.state
Jqhtml_SPA.current_route;  // Current route info

Hash Routing (file:// Protocol)

Automatic Detection

// Router automatically uses hash routing for file:// URLs
// file:///app/index.html#/users/123
// Translates to route: /users/123

// Hash routing format
window.location.href = 'file:///path/index.html#/users/123';
// Router sees: /users/123

// Regular routing format (http/https)
window.location.href = 'https://example.com/users/123';
// Router sees: /users/123

Manual Hash Control

if (Jqhtml_Router.use_hash_routing) {
  // In hash mode
  window.location.hash = '#/users/123';
} else {
  // In regular mode
  window.history.pushState({}, '', '/users/123');
}

Route Metadata & Named Routes

Named Route Pattern

class UserProfileRoute extends Jqhtml_Route {
  static routes = ['/users/:id/profile'];
  static meta = { 
    name: 'user-profile',  // Reference name
    requiresAuth: true,
    title: 'User Profile'
  };
}

// Navigate by name
Jqhtml_Router.url('user-profile', { id: 123 });
UserProfileRoute.dispatch({ id: 123 });

Advanced Patterns

Nested Route Components

class DashboardRoute extends Jqhtml_Route {
  async on_render() {
    this.$.html(`
      <div class="dashboard">
        <Sidebar />
        <div $id="dashboard-content">
          <!-- Sub-routes could render here -->
        </div>
      </div>
    `);
  }
}

Route-Level Data Loading

class UserRoute extends Jqhtml_Route {
  async on_load() {
    // Runs during component load phase
    // Data fetching happens in parallel with siblings
    const userId = this.args.id;
    this.data.user = await fetch(`/api/users/${userId}`).then(r => r.json());
  }
}

Authentication Flow

class AuthLayout extends Jqhtml_Layout {
  async pre_dispatch(route_info) {
    const token = localStorage.getItem('auth_token');
    if (!token && route_info.meta.requiresAuth) {
      // Store intended destination
      sessionStorage.setItem('redirect_after_login', route_info.url);
      return '/login';  // Redirect to login
    }
    return true;
  }
}

class LoginRoute extends Jqhtml_Route {
  async handleLogin() {
    const token = await authenticate(this.data.credentials);
    localStorage.setItem('auth_token', token);
    
    // Redirect to intended destination or home
    const redirect = sessionStorage.getItem('redirect_after_login') || '/';
    sessionStorage.removeItem('redirect_after_login');
    Jqhtml_Router.dispatch(redirect);
  }
}

Query String Handling

// URL: /search?q=jqhtml&category=docs&page=2
class SearchRoute extends Jqhtml_Route {
  static routes = ['/search'];
  
  async on_load() {
    const query = this.args.q;        // 'jqhtml'
    const category = this.args.category;  // 'docs'
    const page = parseInt(this.args.page) || 1;  // 2
  }
  
  nextPage() {
    // Navigate with updated query params
    this.dispatch({ 
      page: (parseInt(this.args.page) || 1) + 1 
    });
  }
}

Critical Invariants

  1. Layouts must have element with $id="content" for route injection
  2. Route dispatch is asynchronous and can be cancelled at multiple points
  3. Layout instances persist across same-layout route changes
  4. Routes are destroyed and recreated on each navigation
  5. Hash routing auto-enabled for file:// protocol, cannot be disabled
  6. All same-origin link clicks are intercepted unless explicitly prevented
  7. Router state is global singleton, only one router instance exists
  8. Route parameters are always strings, cast as needed
  9. Query parameters merge with route parameters in args object
  10. Guard functions execute in strict order: app → layout → route