# Integration contract

This is the integration contract for developers wiring the connector
into a custom WordPress theme or another plugin's search surface. For
stock WordPress + a stock WooCommerce theme, the four swap-points wire
themselves automatically — you don't need to do anything here.

## The four swap-points

| # | Surface | What WP / Woo does | Connector helper |
|---|---|---|---|
| 1 | Full-page search (SERP) | `WP_Query::is_search()` -> normal post loop. The plugin's `pre_get_posts` + `posts_pre_query` intercept the query for indexable post types (or, in commerce mode, the Woo product query). | `numinix_seekmodo_run_search($params)` |
| 2 | Indexer cron | `wp_cron` event `seekmodo_wp_index_tick` walks indexable post types in 500-doc batches. Delta hooks: `save_post`, `transition_post_status`, `before_delete_post`, plus `woocommerce_update_product` / stock changes in commerce mode. | `numinix_seekmodo_run_bulk_upsert($docs, $collection)` |
| 3 | Click beacon | REST `POST /wp-json/seekmodo/v1/click` mirrors a click to the gateway after writing the local row. A small `seekmodo-beacon.js` is enqueued on search and result templates. | `numinix_seekmodo_mirror_click($kw, $id, $pos, $bot, $opts)` |
| 4 | Type-ahead | REST `GET /wp-json/seekmodo/v1/typeahead?q=...` returns ranked rows. Ships with an optional shortcode `[seekmodo_search]` and a Gutenberg block. | `numinix_seekmodo_run_typeahead($q, $max)` + `numinix_seekmodo_mirror_typeahead_click(...)` |
| 5 | Local index search | On-server inverted index for Enhanced Native (no subscription). Grouped / secondary queries via `'seekmodo' => true` or Relevanssi compat. | `numinix_seekmodo_local_search($params)` + `seekmodo_do_query($query)` |

Every helper short-circuits to `null` (or a no-op) when the connector
is disabled — MODE=off, breaker open, credentials missing, or domain
lock mismatched. **Callers do not need to check `numinix_seekmodo_enabled()`
themselves** — return `null` from `numinix_seekmodo_run_search` means
"use your native code path".

## Boot order

`seekmodo-wordpress.php` registers `Bootstrap::boot()` on
`plugins_loaded` priority 5. Inside `boot()`:

```
Options::register()             # must run first; everything else reads it
ContentSearchSwap::register()   # pre_get_posts + posts_pre_query
ContentIndexer::register()      # save_post / transition_post_status / before_delete_post
ContentTypeahead::register()    # REST route
PostTypeRegistry::register()    # seekmodo_indexable_post_types filter init
[ if WooCommerce active ]
  CommerceSearchSwap::register()
  CommerceIndexer::register()
  CommerceTypeahead::register()
ClickBeacon::register()         # REST route + beacon.js enqueue
EventsQueue::register()         # custom table + flush queue
RestRoutes::register()          # /wp-json/seekmodo/v1/* parent
IndexCron::register()           # wp_cron action wiring
ConfigPullCron::register()
EventsFlushCron::register()
[ if is_admin() ]
  SettingsPage::register()
  ConnectFlow::register()
  HealthWidget::register()
  ModeBadge::register()
[ if WP_CLI ]
  Commands::register()
```

Themes & other plugins almost never need to look at the underlying
classes — call the procedural helpers and trust the boot order.

## Versioning rules

- **Adding a new `numinix_seekmodo_*` helper** is fine; old callers
  don't know about it.
- **Adding optional arguments** to an existing helper requires that
  the new argument has a non-breaking default (current call shape stays
  valid).
- **Renaming** a helper is forbidden; ship a new helper and leave the
  old one as a thin alias.
- **Changing the return shape** of an existing helper requires a
  major-version bump and a deprecation period.

The procedural helpers in `src/Helpers/functions.php` are the
**stable public surface**. Anything under `Numinix\SeekmodoWordPress\*`
namespaces is internal and may change between minor versions.

## Local index (Enhanced Native)

The on-server inverted index is **self-service** after plugin activation:

1. Tables are created automatically on activate.
2. A full build is **queued automatically** (cron within ~2 minutes, or sooner via a one-shot event).
3. Each cron tick re-indexes changed posts; no manual delta runs are required.

Optional one-shot commands (same as the admin **Build index now** button):

```bash
wp seekmodo local_index build
wp seekmodo local_index status
wp seekmodo local_index doctor
```

If the index is still empty, operators see a dashboard notice and the **Seekmodo health** widget shows `building…` with the WP-CLI command to run immediately.

### Grouped / secondary searches (FNHA-style)

Standard Seekmodo swap only hooks the main search query. For theme-owned
grouped sections (Pages / News / Stories), flag each `WP_Query`:

```php
new WP_Query([
    's'        => $term,
    'seekmodo' => true,  // or 'relevanssi' => true with compat enabled
    'meta_query' => [ /* unchanged */ ],
]);
```

Or call the API directly:

```php
$result = numinix_seekmodo_local_search([
    'q'          => 'benefits',
    'post_type'  => 'page',
    'meta_query' => $meta_query,
    'page'       => 1,
    'page_size'  => 6,
]);
```

Enable Relevanssi migration compat (FNHA):

```php
add_filter('seekmodo_relevanssi_compat_enabled', '__return_true');
add_filter('seekmodo_local_index_content', 'fnha_design_relevanssi_post_content', 10, 2);
```

Gateway-paired tenants (Trek, POCO) keep gateway-first behavior; local index
is used only when the gateway path fails or index mode is `primary`.
