Core Concepts
Understanding how PACE.js works under the hood.
Architecture Overview
PACE.js follows an MVC-inspired architecture with three core modules:
1. PACE Orchestrator
The main controller. It:
- Initializes all modules
- Manages component lifecycle
- Handles configuration
- Coordinates state, routing, and rendering
Lifecycle
Usage
const pace = new PACE({
container: '#app',
products: './products.json'
})
// Lifecycle events
pace.on('ready', () => {
console.log('PACE initialized')
})
pace.mount()2. State Manager
Reactive state management using the Observer pattern.
How It Works
// Create state
const state = new State({
activeView: 'product',
selectedProduct: null
})
// Subscribe to changes
state.subscribe('activeView', (newValue, oldValue) => {
console.log(`View changed: ${oldValue} → ${newValue}`)
})
// Update state
state.set('activeView', 'chat')
// Logs: "View changed: product → chat"State Structure
{
activeView: 'product' | 'about' | 'chat' | 'summary',
selectedProduct: Product | null,
chatHistory: Message[],
executiveSummaryData: {
productsDiscussed: string[],
userExpertise: 'beginner' | 'intermediate' | 'advanced',
suggestedNextSteps: string[]
},
filters: {
category: string | null,
search: string | null
}
}Methods
| Method | Purpose | Example |
|---|---|---|
get(key) | Retrieve value | state.get('activeView') |
set(key, value) | Update value | state.set('activeView', 'chat') |
subscribe(key, callback) | Listen to changes | state.subscribe('activeView', fn) |
unsubscribe(key, callback) | Stop listening | Returns unsubscribe function |
3. Router
Hash-based routing for single-page navigation.
Why Hash-Based?
- ✅ No server configuration needed
- ✅ Works on GitHub Pages, Netlify, etc.
- ✅ Instant navigation
- ✅ Shareable URLs
Routes
/#product → ProductCatalog
/#about → AboutPage
/#chat → ChatWidget
/#summary → ExecutiveSummary
/#product/:id → ProductCatalog (with selected product)Usage
// Navigate programmatically
router.navigate('chat')
// Navigate with parameters
router.navigate('product', { id: 'sql-mcp' })
// Listen to route changes
router.on('navigate', (route) => {
console.log('Navigated to:', route)
})Deep Linking
Share specific views:
https://yourdomain.com/#product/sql-mcp
https://yourdomain.com/#chat
https://yourdomain.com/#summary4. Component System
Four core components, all following a consistent pattern:
Component Lifecycle
class Component {
constructor(config, state) {
this.config = config
this.state = state
}
// Generate HTML
render() {
return `<div>...</div>`
}
// Attach event listeners
attachEvents() {
// DOM interaction
}
// Update component
update(newData) {
// Re-render logic
}
// Clean up
destroy() {
// Remove listeners
}
}The Four Components
ProductCatalog
import { ProductCatalog } from '@semanticintent/pace-pattern'
const catalog = new ProductCatalog(products, state)
const html = catalog.render()Responsibilities:
- Display products by category
- Handle search/filter
- Emit product selection events
AboutPage
import { AboutPage } from '@semanticintent/pace-pattern'
const about = new AboutPage(config, state)
const html = about.render()Responsibilities:
- Show origin story
- Display philosophy
- Provide context
ChatWidget
import { ChatWidget } from '@semanticintent/pace-pattern'
const chat = new ChatWidget(config, state)
const html = chat.render()Responsibilities:
- Handle user messages
- Communicate with AI adapter
- Display conversation history
- Emit chat events
ExecutiveSummary
import { ExecutiveSummary } from '@semanticintent/pace-pattern'
const summary = new ExecutiveSummary(config, state)
const html = summary.render()Responsibilities:
- Track conversation progress
- Display discussed products
- Suggest next steps
- Show user expertise level
5. AI Adapter Layer
Abstracts AI provider differences behind a consistent interface.
Adapter Interface
class AIAdapter {
async sendMessage(message, context) {
// Returns: { response: string, metadata: object }
}
}Built-in Adapters
Claude Adapter
import { ClaudeAdapter } from '@semanticintent/pace-pattern'
const adapter = new ClaudeAdapter({
apiKey: 'sk-...',
model: 'claude-3-sonnet-20240229'
})OpenAI Adapter
import { OpenAIAdapter } from '@semanticintent/pace-pattern'
const adapter = new OpenAIAdapter({
apiKey: 'sk-...',
model: 'gpt-4'
})Custom Adapter
class MyAdapter {
async sendMessage(message, context) {
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ message, context })
})
const data = await response.json()
return {
response: data.reply,
metadata: {
expertise: data.detectedExpertise,
products: data.mentionedProducts
}
}
}
}
const pace = new PACE({
aiAdapter: new MyAdapter()
})6. Event System
PACE.js uses a custom event emitter for inter-component communication.
Built-in Events
| Event | When | Payload |
|---|---|---|
ready | PACE fully initialized | { version: string } |
navigate | Route changed | { view: string, params: object } |
product:select | Product clicked | { product: Product } |
chat:message | User sent message | { message: string } |
chat:response | AI responded | { response: string } |
state:change | State updated | { key: string, value: any } |
Usage
pace.on('product:select', (payload) => {
console.log('Selected:', payload.product.name)
})
pace.on('chat:response', (payload) => {
console.log('AI said:', payload.response)
})Custom Events
pace.emit('custom:event', { data: 'anything' })
pace.on('custom:event', (payload) => {
console.log(payload.data)
})7. Plugin System
Extend PACE.js with custom functionality.
Plugin Structure
const myPlugin = {
name: 'my-plugin',
version: '1.0.0',
install(pace) {
// Access to PACE instance
pace.on('ready', () => {
console.log('Plugin initialized')
})
pace.on('chat:message', (payload) => {
// Track analytics
analytics.track('chat_message', {
message: payload.message
})
})
}
}
pace.use(myPlugin).mount()Example: Analytics Plugin
const analyticsPlugin = {
name: 'analytics',
install(pace) {
pace.on('product:select', ({ product }) => {
gtag('event', 'product_view', {
product_id: product.id,
product_name: product.name
})
})
pace.on('chat:message', ({ message }) => {
gtag('event', 'chat_message', {
message_length: message.length
})
})
}
}8. Theming System
PACE.js uses CSS custom properties for theming.
Default Theme Variables
:root {
--pace-primary: #667eea;
--pace-accent: #764ba2;
--pace-bg: #ffffff;
--pace-text: #1a1a1a;
--pace-font: Inter, system-ui, sans-serif;
}Custom Theme
const pace = new PACE({
theme: {
primary: '#3b82f6',
accent: '#8b5cf6',
font: 'Roboto, sans-serif'
}
})Dark Mode
.dark {
--pace-primary: #a78bfa;
--pace-accent: #c4b5fd;
--pace-bg: #1a1a2e;
--pace-text: #ffffff;
}9. Data Flow
10. Performance Optimizations
Virtual DOM? No.
PACE.js doesn't use a virtual DOM. Instead:
- Components render to strings
- Direct DOM updates for changes
- Event delegation for efficiency
Why?
- Simpler implementation
- Smaller bundle size
- Faster for small UIs (like PACE components)
Debouncing
Search input is debounced:
const debouncedSearch = debounce((query) => {
catalog.search(query)
}, 300)Lazy Loading
Products load on demand:
async loadProducts(url) {
const response = await fetch(url)
this.products = await response.json()
}Key Takeaways
- PACE Orchestrator — Main controller
- State Manager — Reactive state with Observer pattern
- Router — Hash-based SPA navigation
- Component System — Four core components
- AI Adapter — Pluggable AI providers
- Event System — Inter-component communication
- Plugin System — Extensibility
- Theming — CSS custom properties
- Performance — No virtual DOM, direct updates
Next Steps
- Components → — Deep dive into the four components
- API Reference → — Complete API docs
- Build Your First App → — Hands-on tutorial
Understanding PACE.js architecture makes you a better PACE developer. 🧠