HTMX Architecture
Overview
General Bots Suite uses HTMX for its user interface - a modern approach that delivers the interactivity of a single-page application without the complexity of JavaScript frameworks like React, Vue, or Angular.
Why HTMX?
- Simpler code, easier maintenance
- Server-rendered HTML (fast, SEO-friendly)
- Progressive enhancement
- No build step required
- Smaller payload than SPA frameworks
How HTMX Works
Traditional Web vs HTMX
Traditional (Full Page Reload):
User clicks → Browser requests full page → Server returns entire HTML → Browser replaces everything
HTMX (Partial Update):
User clicks → HTMX requests fragment → Server returns HTML snippet → HTMX updates only that part
Core Concept
HTMX extends HTML with attributes that define:
- What triggers the request (
hx-trigger) - Where to send it (
hx-get,hx-post) - What to update (
hx-target) - How to update it (
hx-swap)
HTMX Attributes Reference
Request Attributes
| Attribute | Purpose | Example |
|---|---|---|
hx-get | GET request to URL | hx-get="/api/tasks" |
hx-post | POST request | hx-post="/api/tasks" |
hx-put | PUT request | hx-put="/api/tasks/1" |
hx-patch | PATCH request | hx-patch="/api/tasks/1" |
hx-delete | DELETE request | hx-delete="/api/tasks/1" |
Trigger Attributes
| Attribute | Purpose | Example |
|---|---|---|
hx-trigger | Event that triggers request | hx-trigger="click" |
| Load on page | hx-trigger="load" | |
| Periodic polling | hx-trigger="every 5s" | |
| Keyboard event | hx-trigger="keyup changed delay:300ms" |
Target & Swap Attributes
| Attribute | Purpose | Example |
|---|---|---|
hx-target | Element to update | hx-target="#results" |
hx-swap | How to insert content | hx-swap="innerHTML" |
hx-swap="outerHTML" | ||
hx-swap="beforeend" | ||
hx-swap="afterbegin" |
Suite Architecture
File Structure
ui/suite/
├── index.html # Main entry point with navigation
├── base.html # Base template
├── home.html # Home page
├── default.gbui # Full desktop layout
├── single.gbui # Simple chat layout
├── designer.html # Visual dialog designer
├── editor.html # Code editor
├── settings.html # User settings
├── css/
│ ├── app.css # Application styles
│ ├── apps-extended.css # Extended app styles
│ ├── components.css # UI components
│ └── global.css # Global styles
├── js/
│ ├── htmx-app.js # HTMX application logic
│ ├── theme-manager.js # Theme switching
│ └── vendor/ # Third-party libraries
├── partials/ # Reusable HTML fragments
├── auth/ # Authentication views
├── attendant/ # Attendant interface
├── chat/
│ ├── chat.html # Chat component
│ ├── chat.css # Chat styles
│ └── projector.html # Projector view
├── drive/ # File manager
├── tasks/ # Task manager
├── mail/ # Email client
├── calendar/ # Calendar view
├── meet/ # Video meetings
├── paper/ # Document editor
├── research/ # AI search
├── analytics/ # Dashboards
├── sources/ # Prompts & templates
├── tools/ # Developer tools
└── monitoring/ # System monitoring
Loading Pattern
The Suite uses lazy loading - components load only when needed:
<!-- Main navigation in index.html -->
<a href="#chat"
data-section="chat"
hx-get="/ui/suite/chat/chat.html"
hx-target="#main-content"
hx-swap="innerHTML">
Chat
</a>
When user clicks “Chat”:
- HTMX requests
/ui/suite/chat/chat.html - Server returns the Chat HTML fragment
- HTMX inserts it into
#main-content - Only Chat code loads, not entire app
Component Patterns
1. Load on Page View
<!-- Tasks load immediately when component is shown -->
<div id="task-list"
hx-get="/api/tasks"
hx-trigger="load"
hx-swap="innerHTML">
<div class="loading">Loading tasks...</div>
</div>
2. Form Submission
<!-- Add task form -->
<form hx-post="/api/tasks"
hx-target="#task-list"
hx-swap="afterbegin"
hx-on::after-request="this.reset()">
<input type="text" name="text" placeholder="New task..." required>
<button type="submit">Add</button>
</form>
Flow:
- User types task, clicks Add
- HTMX POSTs form data to
/api/tasks - Server creates task, returns HTML for new task item
- HTMX inserts at beginning of
#task-list - Form resets automatically
3. Click Actions
<!-- Task item with actions -->
<div class="task-item" id="task-123">
<input type="checkbox"
hx-patch="/api/tasks/123"
hx-vals='{"completed": true}'
hx-target="#task-123"
hx-swap="outerHTML">
<span>Review quarterly report</span>
<button hx-delete="/api/tasks/123"
hx-target="#task-123"
hx-swap="outerHTML"
hx-confirm="Delete this task?">
🗑
</button>
</div>
4. Search with Debounce
<!-- Search input with 300ms delay -->
<input type="text"
name="q"
placeholder="Search..."
hx-get="/api/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#search-results"
hx-indicator="#search-spinner">
<span id="search-spinner" class="htmx-indicator">🔄</span>
<div id="search-results"></div>
Flow:
- User types in search box
- After 300ms of no typing, HTMX sends request
- Spinner shows during request
- Results replace
#search-resultscontent
5. Real-time Updates (WebSocket)
<!-- Chat with WebSocket -->
<div id="chat-app" hx-ext="ws" ws-connect="/ws">
<div id="messages"
hx-get="/api/sessions/current/history"
hx-trigger="load"
hx-swap="innerHTML">
</div>
<form ws-send>
<input name="content" type="text">
<button type="submit">Send</button>
</form>
</div>
Flow:
- WebSocket connects on load
- History loads via HTMX GET
- New messages sent via WebSocket (
ws-send) - Server pushes updates to all connected clients
6. Polling for Updates
<!-- Analytics that refresh every 30 seconds -->
<div class="metric-card"
hx-get="/api/analytics/messages/count"
hx-trigger="load, every 30s"
hx-swap="innerHTML">
<!-- Content updates automatically -->
</div>
7. Infinite Scroll
<!-- File list with infinite scroll -->
<div id="file-list">
<!-- Files here -->
<div hx-get="/api/files?page=2"
hx-trigger="revealed"
hx-swap="afterend">
Loading more...
</div>
</div>
API Response Patterns
Server Returns HTML Fragments
The server doesn’t return JSON - it returns ready-to-display HTML:
Request:
GET /api/tasks
Response:
<div class="task-item" id="task-1">
<input type="checkbox">
<span>Review quarterly report</span>
</div>
<div class="task-item" id="task-2">
<input type="checkbox">
<span>Update documentation</span>
</div>
Swap Strategies
| Strategy | Effect |
|---|---|
innerHTML | Replace contents of target |
outerHTML | Replace entire target element |
beforeend | Append inside target (at end) |
afterbegin | Prepend inside target (at start) |
beforebegin | Insert before target |
afterend | Insert after target |
delete | Delete target element |
none | Don’t swap (for side effects) |
CSS Integration
Loading Indicators
/* Hidden by default */
.htmx-indicator {
display: none;
}
/* Shown during request */
.htmx-request .htmx-indicator {
display: inline-block;
}
/* Or when indicator IS the requesting element */
.htmx-request.htmx-indicator {
display: inline-block;
}
Transition Effects
/* Fade in new content */
.htmx-settling {
opacity: 0;
}
.htmx-swapping {
opacity: 0;
transition: opacity 0.2s ease-out;
}
JavaScript Integration
HTMX Events
// After any HTMX swap
document.body.addEventListener('htmx:afterSwap', (e) => {
console.log('Content updated:', e.detail.target);
});
// Before request
document.body.addEventListener('htmx:beforeRequest', (e) => {
console.log('Sending request to:', e.detail.pathInfo.path);
});
// After request completes
document.body.addEventListener('htmx:afterRequest', (e) => {
if (e.detail.successful) {
console.log('Request succeeded');
} else {
console.error('Request failed');
}
});
// On WebSocket message
document.body.addEventListener('htmx:wsAfterMessage', (e) => {
console.log('Received:', e.detail.message);
});
Triggering HTMX from JavaScript
// Trigger an HTMX request programmatically
htmx.trigger('#task-list', 'load');
// Make an AJAX request
htmx.ajax('GET', '/api/tasks', {
target: '#task-list',
swap: 'innerHTML'
});
// Process new HTMX content
htmx.process(document.getElementById('new-content'));
Designer Page Architecture
The visual dialog designer uses a hybrid approach:
Canvas Management (JavaScript)
// State managed in JavaScript
const state = {
nodes: new Map(), // Node data
connections: [], // Connections between nodes
zoom: 1, // Canvas zoom level
pan: { x: 0, y: 0 } // Canvas position
};
File Operations (HTMX)
<!-- Load file via HTMX -->
<button hx-get="/api/v1/designer/files"
hx-target="#file-list-content">
Open File
</button>
<!-- Save via HTMX -->
<button hx-post="/api/v1/designer/save"
hx-include="#designer-data">
Save
</button>
Drag-and-Drop (JavaScript)
// Toolbox items are draggable
toolboxItems.forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('nodeType', item.dataset.nodeType);
});
});
// Canvas handles drop
canvas.addEventListener('drop', (e) => {
const nodeType = e.dataTransfer.getData('nodeType');
createNode(nodeType, e.clientX, e.clientY);
});
Performance Considerations
1. Minimize Request Size
Return only what’s needed:
<!-- Good: Return just the updated row -->
<tr id="row-123">...</tr>
<!-- Bad: Return entire table -->
<table>...</table>
2. Use Appropriate Triggers
<!-- Don't poll too frequently -->
hx-trigger="every 30s" <!-- Good for dashboards -->
hx-trigger="every 1s" <!-- Too frequent! -->
<!-- Debounce user input -->
hx-trigger="keyup changed delay:300ms" <!-- Good -->
hx-trigger="keyup" <!-- Too many requests -->
3. Lazy Load Heavy Content
<!-- Load tab content only when tab is clicked -->
<div role="tabpanel"
hx-get="/api/heavy-content"
hx-trigger="intersect once">
</div>
4. Use hx-boost for Navigation
<!-- Boost all links in nav -->
<nav hx-boost="true">
<a href="/page1">Page 1</a> <!-- Now uses HTMX -->
<a href="/page2">Page 2</a>
</nav>
Security
CSRF Protection
HTMX automatically includes CSRF tokens:
<meta name="csrf-token" content="abc123...">
// Configure HTMX to send CSRF token
document.body.addEventListener('htmx:configRequest', (e) => {
e.detail.headers['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').content;
});
Content Security
- Server validates all inputs
- HTML is sanitized before rendering
- Authentication checked on every request
Comparison: HTMX vs React
| Aspect | HTMX | React |
|---|---|---|
| Learning Curve | Low (HTML attributes) | High (JSX, hooks, state) |
| Bundle Size | ~14KB | ~40KB + app code |
| Build Step | None | Required |
| Server Load | More (renders HTML) | Less (returns JSON) |
| Client Load | Less | More |
| SEO | Excellent | Requires SSR |
| Complexity | Simple | Complex |
| Best For | Content sites, dashboards | Complex SPAs, offline apps |