Skip to content

Architecture

Ryan Shepherd edited this page May 21, 2026 · 5 revisions

Architecture

How HOF is built. Read this if you're contributing, extending, or just nosy.

the stack

Layer Tech Why
PHP architecture PSR-4 + Composer, PHP 8.2+ Real namespaces. Autoloading. IDE autocomplete that works.
Build tooling Vite 8 + Node 22+ Fast HMR, native ESM, no webpack pain
Public runtime Vanilla JS (no framework) Lean payload. Filters touch a small DOM subtree; we don't need a framework.
Admin UI React 18 + @wordpress/components Native WP feel, zero design debt
Ask facet Anthropic Claude Haiku 4.5 via tool use Conversational multi-turn intent → editable filter chips; BYOK so cost is owner-borne
Dev env docker compose via OrbStack One command to a working WP + Woo
Database Custom indexed tables Optimized for facet lookups
Tests PHPUnit, Vitest, Playwright Unit, integration, end-to-end

why these choices

Vanilla JS on the public runtime

The public-facing facet bar is a small, self-contained UI sitting on top of a product listing page that already has its own framework (WooCommerce, the theme, jQuery, whatever). Adding React or Preact just to manage a few hundred lines of DOM is the wrong trade. The HOF public runtime is plain ES modules, ~16 kB gzipped, no virtual DOM. Filters update via direct DOM manipulation in a tight refresh loop, and a small pub/sub store keeps facets synchronized with the URL.

This is the opposite of what the original planning spec said (Preact + Signals). Shipping changed our minds — for what HOF actually does on the storefront, a framework is dead weight.

React 18 + @wordpress/components in the admin

The admin is different. We need date pickers, modals, SlotFill, all the WordPress-native primitives. @wordpress/components ships them, auto-themes to WP version, and means the HOF admin feels like part of WordPress, not a wrapper grafted on. The admin SPA itself is React 18 — sticking with WP core's React version avoids two parallel React copies on the page.

PSR-4 over the procedural plugin pattern

Because it's 2026. Because contributors deserve to find things. Because class Hooked_On_Facets_Public_Frontend_Helper_Functions_etc is a war crime.

Vite over webpack

Native ESM in dev. Build time you can ignore. HMR that actually works for plugin development. Webpack served us well; it's time to let it rest.

BYOK for the Ask facet

The Ask facet calls Anthropic's API on every turn. We refuse to be the credit-card-in-the-middle for that traffic. Each site owner enters their own API key in the WP admin; it lives in wp_options and never reaches the browser. Cost stays small (~$0.0001–$0.0005 per turn) and stays on the owner.

directory layout

hooked-on-facets/
├── composer.json
├── package.json
├── vite.config.js
├── hooked-on-facets.php          # Plugin bootstrap
├── readme.txt                    # WP.org-style readme
├── docker-compose.yml            # Local WP + Woo + MySQL dev env
├── src/                          # PSR-4 → HOF\
│   ├── Core/                     # Plugin lifecycle, container
│   ├── Indexer/                  # Index build + maintenance
│   ├── Query/                    # Facet query engine
│   ├── Facets/                   # Renderer + registry
│   ├── Ai/                       # NlFilter + BYOK settings
│   ├── Admin/                    # Admin screens (PHP side)
│   ├── Api/                      # REST controllers (HOF prefix)
│   ├── Frontend/                 # AssetLoader, shortcodes
│   └── Integrations/             # Woo, ACF, page builders
├── admin/                        # React 18 admin SPA
│   └── src/
│       ├── App.jsx
│       └── components/
├── public/                       # Vanilla JS public runtime
│   └── src/
│       ├── main.js
│       ├── two-d-slider.js
│       ├── ai-search.js
│       └── swiper.js
├── assets/
│   └── dist/                     # Vite build output (gitignored)
└── tests/
    ├── unit/                     # PHPUnit + Vitest
    └── e2e/                      # Playwright

dev environment

A complete working WordPress + WooCommerce + MySQL 8 environment runs in Docker via OrbStack.

git clone https://github.com/Shepdesign/hooked-on-facets.git
cd hooked-on-facets
composer install
npm install
docker compose up -d
npm run dev

That gets you:

  • WordPress at http://localhost:8080 (bound to 127.0.0.1)
  • MySQL 8 at 127.0.0.1:3306
  • Vite dev server at :5173 with HMR
  • HOF mounted into wp-content/plugins/hooked-on-facets

Ports bind to localhost only — the dev stack isn't reachable from your network.

Sample data (a Woo store with ~500 products across various categories) loads on first boot.

extensibility points

HOF exposes its surface area through:

  • PHP hooksapply_filters, do_action at all the points you'd want
  • JavaScript events — emitted on a window.hofPublic store
  • REST endpoints — fully versioned under /wp-json/hof/v1/
  • Facet kind/display registry — register your own kinds and displays via PHP

Full reference: Developer Guide

Clone this wiki locally