# Content vertical (WordPress core)

The `content` vertical wires the connector into WordPress's native
search loop. It is enabled by default on every install and serves
sites where you do NOT have WooCommerce (or where you have Woo but
also want regular post / page search to be Seekmodo-powered).

## Scope on first install

Indexed post types default to:

```
post, page, ...get_post_types(['public' => true, '_builtin' => false])
```

i.e. all built-in posts and pages plus every public Custom Post Type.
Override via filter (preferred — survives reinstalls):

```php
add_filter('seekmodo_indexable_post_types', function (array $types): array {
    // Drop Pages, add a hypothetical 'kb_article' if it exists.
    $types = array_diff($types, ['page']);
    $types[] = 'kb_article';
    return array_values(array_unique($types));
});
```

Or set the option directly:

```bash
wp option update seekmodo_wp_indexable_post_types '["post","page","kb_article"]' --format=json
```

## Search swap

The plugin hooks `pre_get_posts` (priority 9, before most themes) and
`posts_pre_query` (priority 10) for queries where:

- `is_search()` is true, AND
- `get_search_query()` is non-empty, AND
- the query's `post_type` is within the indexable allowlist, AND
- `numinix_seekmodo_enabled()` returns true.

If those gates pass, the swap fires `numinix_seekmodo_run_search()`.
On success the swap returns a pre-ranked `WP_Post[]` from
`posts_pre_query` (skipping the SQL pass entirely); on failure / null
it falls through to WP's native LIKE search.

In `MODE=shadow` the swap still calls the gateway but always returns
`null` so WP's native result is what visitors see — the shadow logger
captures the divergence asynchronously.

## Indexer

Full reindex:

```bash
wp seekmodo index --full
```

Delta:

```bash
wp seekmodo index --delta
```

Runs incremental on `seekmodo_wp_index_tick` (every 15 min). Delta
hooks watch:

- `save_post` (insert/update; only post statuses with `'publish' === $status`)
- `transition_post_status` (publish ↔ unpublish ↔ trash)
- `before_delete_post` (hard delete)
- `attachment_updated` (for `'attachment'` post type if included)

Each delta hook just stamps a per-post `seekmodo_dirty` postmeta; the
cron tick picks them up in batches of 500. This avoids latency spikes
on the editor's "Update" click.

## Document shape

Stock shape pushed to the gateway's `index` endpoint:

```json
{
  "id": 4321,
  "post_type": "post",
  "title": "Spruce Lake trail conditions",
  "excerpt": "...",
  "content_plain": "...",
  "permalink": "https://trek.ca/spruce-lake/",
  "thumbnail_url": "https://trek.ca/.../thumb.jpg",
  "taxonomies": {"category": ["trails"], "post_tag": ["bc", "summer"]},
  "date_published_unix": 1717286400,
  "author_id": 7,
  "is_sticky": false,
  "extra": { /* anything the seekmodo_indexer_doc filter adds */ }
}
```

Themes that want to add custom fields (e.g. ACF blocks, custom
taxonomies, computed scores) attach via the per-doc filter:

```php
add_filter('seekmodo_indexer_doc', function (array $doc, \WP_Post $post): array {
    $doc['extra']['route_difficulty'] = (int) get_post_meta($post->ID, 'route_difficulty', true);
    return $doc;
}, 10, 2);
```
