Skip to content

Design Principles

Technical architecture and design philosophy of PACE.js.


Philosophy

PACE.js embodies a simple philosophy:

"AI-native storefronts should be as easy to build as static websites, but as powerful as modern web applications."

Core Beliefs

  1. Configuration Over Code - Developers configure, not implement
  2. Convention Over Configuration - Sensible defaults for everything
  3. Progressive Enhancement - Start simple, add complexity as needed
  4. Zero Dependencies - Pure vanilla JavaScript, works everywhere
  5. Framework Agnostic - Integrates with anything
  6. Developer Experience First - 5 minutes to "Hello World"

Design Principles

1. Simplicity First

Problem: Modern frameworks have steep learning curves.

Solution: PACE.js is just JavaScript - if you know HTML/CSS/JS, you know PACE.

javascript
// This is all you need
const pace = new PACE({
  container: '#app',
  products: './products.json'
})

pace.mount()

No build tools. No compilation. No configuration files.


2. Lightweight by Design

Target: < 15KB minified + gzipped

How:

  • Zero dependencies
  • Minimal abstractions
  • Tree-shakeable ES modules
  • No polyfills (modern browsers only)

Comparison:

  • React + React DOM: ~42KB
  • Vue 3: ~34KB
  • Alpine.js: ~15KB
  • PACE.js: ~15KB

Why it matters:

  • Faster page loads
  • Better mobile performance
  • Lower bandwidth costs
  • Improved SEO

3. Pattern-Driven Development

PACE.js encodes the PACE Pattern into reusable code.

The Pattern:

Product       → Discovery & Browse
About         → Context & Trust
Chat          → Guided Assistance
Executive     → Insights & Intelligence

The Framework:

javascript
{
  components: {
    product: ProductCatalog,
    about: AboutPage,
    chat: ChatWidget,
    executiveSummary: ExecutiveSummary
  }
}

Benefit: Developers implement the pattern automatically by using the framework.


4. Configuration Over Code

Traditional approach:

javascript
// 200+ lines of component code
class ProductList extends Component {
  constructor() { ... }
  render() { ... }
  handleClick() { ... }
  // ... more boilerplate
}

PACE approach:

javascript
// 5 lines of configuration
const pace = new PACE({
  container: '#app',
  products: './products.json'
})

95% less code. Same functionality.


5. Progressive Enhancement

Start simple, add features as needed:

Level 1: Minimal

javascript
new PACE({
  container: '#app',
  products: './products.json'
}).mount()

Level 2: Add AI

javascript
new PACE({
  container: '#app',
  products: './products.json',
  aiAdapter: new ClaudeAdapter({ apiKey: '...' })
}).mount()

Level 3: Add Customization

javascript
new PACE({
  container: '#app',
  products: './products.json',
  aiAdapter: new ClaudeAdapter({ apiKey: '...' }),
  theme: { primary: '#3b82f6' },
  greeting: 'Custom greeting',
  plugins: [analyticsPlugin, i18nPlugin]
}).mount()

You never pay for features you don't use.


Architecture

MVC-Inspired Structure

┌─────────────────────────────────────┐
│           PACE Orchestrator         │
│         (Main Controller)           │
└─────────────────────────────────────┘

    ┌─────────┼─────────┐
    ↓         ↓         ↓
┌────────┐ ┌──────┐ ┌────────────┐
│ State  │ │Router│ │ Components │
│Manager │ │      │ │            │
└────────┘ └──────┘ └────────────┘
    ↓         ↓         ↓
┌────────────────────────────────┐
│          View Layer            │
│         (HTML/CSS)             │
└────────────────────────────────┘

Separation of concerns:

  • PACE — Orchestrates everything
  • State — Manages data
  • Router — Handles navigation
  • Components — Render UI

Component Lifecycle

Each component:

  1. Constructs with config
  2. Renders HTML
  3. Attaches event listeners
  4. Responds to user actions
  5. Updates state
  6. Re-renders if needed
  7. Cleans up on destroy

Observer Pattern

State management uses the Observer pattern:

javascript
// State notifies observers when changed
state.set('activeView', 'chat')

// Observers automatically update
state.subscribe('activeView', (newView) => {
  updateUI(newView)
})

Benefits:

  • Reactive updates
  • Decoupled components
  • Easy to reason about
  • Simple to test

Dependency Injection

Components receive dependencies via constructor:

javascript
class ChatWidget {
  constructor(config, state, aiAdapter) {
    this.config = config      // Configuration
    this.state = state        // Shared state
    this.aiAdapter = aiAdapter // AI provider
  }
}

Benefits:

  • Testable (inject mocks)
  • Flexible (swap implementations)
  • Clear dependencies
  • No global state

Extensibility

Plugin System

javascript
const analyticsPlugin = {
  name: 'analytics',
  install(pace) {
    pace.on('product:select', ({ product }) => {
      gtag('event', 'product_view', {
        product_id: product.id
      })
    })
  }
}

pace.use(analyticsPlugin)

Plugin capabilities:

  • Listen to all PACE events
  • Modify configuration
  • Add custom components
  • Extend state
  • Integrate external services

Adapter Pattern

AI providers are swappable via adapters:

javascript
// Claude
const claude = new ClaudeAdapter({ apiKey: '...' })

// OpenAI
const openai = new OpenAIAdapter({ apiKey: '...' })

// Custom
const custom = new MyCustomAdapter()

// Use any adapter
new PACE({ aiAdapter: claude })

Adapter interface:

typescript
interface AIAdapter {
  sendMessage(message: string, context?: object): Promise<{
    response: string
    metadata?: object
  }>
}

Theme System

Themes use CSS custom properties:

javascript
// JavaScript configuration
new PACE({
  theme: {
    primary: '#3b82f6',
    accent: '#8b5cf6'
  }
})

// Applies CSS variables
:root {
  --pace-primary: #3b82f6;
  --pace-accent: #8b5cf6;
}

Custom themes:

  • Override CSS variables
  • Provide custom stylesheets
  • Extend default styles
  • Full control over appearance

Integration Patterns

Standalone

html
<div id="app"></div>
<script type="module">
  import { PACE } from 'pace.js'
  new PACE({ container: '#app' }).mount()
</script>

React

tsx
function App() {
  return <PACEWrapper config={{ ... }} />
}

Vue

vue
<template>
  <PACEComponent :config="config" />
</template>

Svelte

svelte
<PACE {config} />

All frameworks supported via wrapper components.


Framework Comparison

vs React

AspectReactPACE.js
Size42KB15KB
DependenciesManyZero
Learning curveSteepGentle
Build requiredYesNo
Use caseGeneralSpecific (PACE)

When to use:

  • React — Complex apps, team familiarity
  • PACE.js — Conversational storefronts

vs Alpine.js

AspectAlpinePACE.js
Size15KB15KB
ApproachReactive attributesConfiguration
ComponentsNone4 built-in
AI integrationDIYBuilt-in
PatternNonePACE

When to use:

  • Alpine — General reactivity
  • PACE.js — PACE Pattern specifically

Performance

Bundle Size Breakdown

Total: 15KB minified + gzipped

Core:              5KB
State:             2KB
Router:            1KB
Components:        6KB
Utils:             1KB

Benchmarks

Initial load:

  • Parse time: < 10ms
  • Time to interactive: < 100ms
  • First render: < 50ms

Runtime:

  • State update: < 1ms
  • Component re-render: < 5ms
  • Route transition: < 10ms

Tested on M1 MacBook Pro, 4x CPU throttle


Best Practices

1. Keep Components Pure

javascript
// ✅ Good - pure function
render() {
  return `<div>${this.products.map(p => p.name)}</div>`
}

// ❌ Bad - side effects
render() {
  fetch('/api/products') // Side effect!
  return `<div>...</div>`
}

2. Use State for Sharing

javascript
// ✅ Good
state.set('selectedProduct', product)

// ❌ Bad
window.selectedProduct = product

3. Subscribe Responsibly

javascript
// ✅ Good - unsubscribe
const unsub = state.subscribe('view', callback)
return () => unsub()

// ❌ Bad - memory leak
state.subscribe('view', callback)

4. Validate Configuration

javascript
// ✅ Good
if (!config.container) {
  throw new Error('container is required')
}

// ❌ Bad - silent failure
this.container = config.container || '#app'

Security Considerations

XSS Prevention

javascript
// ✅ Good - sanitize user input
const safe = DOMPurify.sanitize(userInput)

// ❌ Bad - raw HTML
innerHTML = userMessage

API Key Protection

javascript
// ✅ Good - environment variables
apiKey: process.env.CLAUDE_API_KEY

// ❌ Bad - hardcoded
apiKey: 'sk-ant-1234567890'

Content Security Policy

html
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self' 'unsafe-inline'">

Testing Strategy

Unit Tests

javascript
describe('State', () => {
  it('notifies subscribers', () => {
    const state = new State()
    const spy = jest.fn()

    state.subscribe('key', spy)
    state.set('key', 'value')

    expect(spy).toHaveBeenCalledWith('value', undefined)
  })
})

Integration Tests

javascript
describe('PACE', () => {
  it('renders product catalog', async () => {
    const pace = new PACE({
      container: document.body,
      products: mockProducts
    })

    pace.mount()

    expect(document.querySelector('.pace-product-catalog')).toBeTruthy()
  })
})

Future Enhancements

Planned Features

  • Server-side rendering — SSR support for Next.js/Nuxt
  • Streaming responses — Real-time AI responses
  • Voice interface — Voice input/output
  • Multi-language — i18n/l10n support
  • A/B testing — Built-in experimentation
  • Analytics — First-party analytics

Community Requests

  • Vue 3 official adapter
  • Svelte official adapter
  • WordPress plugin
  • Shopify integration
  • Django template tags

Design principles that scale from prototype to production. 🏗️