Skip to content

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

javascript
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

javascript
// 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

javascript
{
  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

MethodPurposeExample
get(key)Retrieve valuestate.get('activeView')
set(key, value)Update valuestate.set('activeView', 'chat')
subscribe(key, callback)Listen to changesstate.subscribe('activeView', fn)
unsubscribe(key, callback)Stop listeningReturns 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

javascript
// 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/#summary

4. Component System

Four core components, all following a consistent pattern:

Component Lifecycle

javascript
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

javascript
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

javascript
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

javascript
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

javascript
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

javascript
class AIAdapter {
  async sendMessage(message, context) {
    // Returns: { response: string, metadata: object }
  }
}

Built-in Adapters

Claude Adapter

javascript
import { ClaudeAdapter } from '@semanticintent/pace-pattern'

const adapter = new ClaudeAdapter({
  apiKey: 'sk-...',
  model: 'claude-3-sonnet-20240229'
})

OpenAI Adapter

javascript
import { OpenAIAdapter } from '@semanticintent/pace-pattern'

const adapter = new OpenAIAdapter({
  apiKey: 'sk-...',
  model: 'gpt-4'
})

Custom Adapter

javascript
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

EventWhenPayload
readyPACE fully initialized{ version: string }
navigateRoute changed{ view: string, params: object }
product:selectProduct clicked{ product: Product }
chat:messageUser sent message{ message: string }
chat:responseAI responded{ response: string }
state:changeState updated{ key: string, value: any }

Usage

javascript
pace.on('product:select', (payload) => {
  console.log('Selected:', payload.product.name)
})

pace.on('chat:response', (payload) => {
  console.log('AI said:', payload.response)
})

Custom Events

javascript
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

javascript
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

javascript
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

css
:root {
  --pace-primary: #667eea;
  --pace-accent: #764ba2;
  --pace-bg: #ffffff;
  --pace-text: #1a1a1a;
  --pace-font: Inter, system-ui, sans-serif;
}

Custom Theme

javascript
const pace = new PACE({
  theme: {
    primary: '#3b82f6',
    accent: '#8b5cf6',
    font: 'Roboto, sans-serif'
  }
})

Dark Mode

css
.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:

javascript
const debouncedSearch = debounce((query) => {
  catalog.search(query)
}, 300)

Lazy Loading

Products load on demand:

javascript
async loadProducts(url) {
  const response = await fetch(url)
  this.products = await response.json()
}

Key Takeaways

  1. PACE Orchestrator — Main controller
  2. State Manager — Reactive state with Observer pattern
  3. Router — Hash-based SPA navigation
  4. Component System — Four core components
  5. AI Adapter — Pluggable AI providers
  6. Event System — Inter-component communication
  7. Plugin System — Extensibility
  8. Theming — CSS custom properties
  9. Performance — No virtual DOM, direct updates

Next Steps


Understanding PACE.js architecture makes you a better PACE developer. 🧠