Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions skills/wp-interactivity-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Use this skill when the user mentions:
- Interactivity API, `@wordpress/interactivity`,
- `data-wp-interactive`, `data-wp-on--*`, `data-wp-bind--*`, `data-wp-context`,
- block `viewScriptModule` / module-based view scripts,
- hydration issues or “directives don’t fire”.
- hydration issues or "directives don't fire",
- **client-side navigation**, `@wordpress/interactivity-router`, `data-wp-router-region`.

## Inputs required

Expand Down Expand Up @@ -134,16 +135,55 @@ Verify the repo supports the required module build path:
- if it uses `@wordpress/scripts`, prefer its conventions.
- if it uses custom bundling, confirm module output is supported.

### 6) Debug common failure modes
### 6) Client-side navigation with the Router
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBF this could be asked about all of the existing committed skills, but what's the point of dumping all this information here if it's available via references?


If “nothing happens” on interaction:
**CRITICAL: The router automatically intercepts `<a>` links. Do NOT add custom click handlers.**

When using `@wordpress/interactivity-router` for SPA-like navigation:

1. Enqueue the router: `wp_enqueue_script_module('my-nav', '...', ['@wordpress/interactivity', '@wordpress/interactivity-router'])`
2. **Mark module as compatible (WordPress 6.9+ REQUIRED):**
```php
wp_interactivity()->add_client_navigation_support_to_script_module('my-nav');
```
3. Add `data-wp-router-region="region-id"` to the content area to be swapped
4. Use **plain `<a href="...">` links** - NO `data-wp-on--click` handlers!

The router automatically:
- Intercepts all same-origin link clicks
- Fetches the target page
- Swaps ONLY the matching router region content
- Updates browser URL via History API

**WordPress 6.9+ CRITICAL:** Script modules must be marked as compatible with client-side navigation:
- For blocks: use `"supports": { "interactivity": { "clientNavigation": true } }` in block.json
- For manual modules: call `wp_interactivity()->add_client_navigation_support_to_script_module('module-id')`

Without this, the router will NOT intercept link clicks. The script tag must have `data-wp-router-options` attribute.

Reference: https://make.wordpress.org/core/2025/11/12/interactivity-apis-client-navigation-improvements-in-wordpress-6-9/

**Common mistake:** Adding `data-wp-on--click="actions.navigate"` to nav elements. This breaks the router. Remove it.

See `references/router.md` for full details.

### 7) Debug common failure modes

If "nothing happens" on interaction:

- confirm the `viewScriptModule` is enqueued/loaded,
- confirm the DOM element has `data-wp-interactive`,
- confirm the store namespace matches the directives value,
- confirm the store namespace matches the directive's value,
- confirm there are no JS errors before hydration.

See `references/debugging.md`.
If client-side **navigation** isn't working:
- confirm `@wordpress/interactivity-router` is enqueued,
- confirm `data-wp-router-region` exists on both source and target pages with SAME ID,
- **confirm script module has `data-wp-router-options` attribute** (WP 6.9+) - if missing, call `wp_interactivity()->add_client_navigation_support_to_script_module('your-module')`,
- **remove any custom `data-wp-on--click` handlers from navigation links**,
- links should be plain `<a href>` tags.

See `references/debugging.md` and `references/router.md`.

## Verification

Expand Down Expand Up @@ -174,6 +214,7 @@ See `references/debugging.md`.

- If repo build constraints are unclear, ask: "Is this using `@wordpress/scripts` or a custom bundler (webpack/vite)?"
- Consult:
- `references/router.md` - **READ THIS FIRST for navigation issues**
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid yelling out our models since this needs to service more than just Anthropic

- `references/server-side-rendering.md`
- `references/directives-quickref.md`
- `references/debugging.md`
152 changes: 152 additions & 0 deletions skills/wp-interactivity-api/references/router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Interactivity Router (`@wordpress/interactivity-router`)

## Key Concept: Automatic Link Interception

**The router automatically intercepts ALL same-origin `<a>` link clicks.** You do NOT need custom click handlers.

## How It Works

When `@wordpress/interactivity-router` is enqueued and `data-wp-router-region` exists on the page:

1. User clicks any `<a href="...">` link (same-origin)
2. Router intercepts the click automatically (calls `preventDefault`)
3. Fetches the target page via AJAX
4. Finds the matching `data-wp-router-region` on the new page
5. Swaps ONLY that region's innerHTML
6. Updates the browser URL via History API
7. Sidebar, header, footer stay intact

## Required Setup

```php
// 1. Enqueue the router module
wp_enqueue_script_module(
'my-navigation',
get_stylesheet_directory_uri() . '/assets/js/navigation.js',
['@wordpress/interactivity', '@wordpress/interactivity-router'],
'1.0.0'
);

// 2. Mark module as compatible with client-side navigation (WordPress 6.9+)
// This is REQUIRED for manually registered script modules!
wp_interactivity()->add_client_navigation_support_to_script_module('my-navigation');
```

```php
// 3. Add router region to the content area (via filter or directly in template)
<main data-wp-interactive="my/namespace" data-wp-router-region="main-content">
<!-- This content gets swapped on navigation -->
</main>
```

```html
<!-- 4. Use regular links - NO directives needed! -->
<nav>
<a href="/page-1/">Page 1</a>
<a href="/page-2/">Page 2</a>
</nav>
```

## WordPress 6.9+ Requirements

**CRITICAL: Script modules must be marked as compatible with client-side navigation.**

For blocks, this is automatic when `block.json` has:
```json
{
"supports": {
"interactivity": { "clientNavigation": true }
}
}
```

For manually registered script modules (themes/plugins), you MUST call:
```php
wp_interactivity()->add_client_navigation_support_to_script_module('my-module');
```

This adds `data-wp-router-options='{"loadOnClientNavigation":true}'` to the script tag.

**Without this, the router will not intercept link clicks.**

Reference: https://make.wordpress.org/core/2025/11/12/interactivity-apis-client-navigation-improvements-in-wordpress-6-9/

## Common Mistakes

### ❌ WRONG: Adding click handlers to links

```html
<!-- DON'T DO THIS -->
<nav data-wp-interactive="my/nav" data-wp-on--click="actions.navigate">
<a href="/page/">Link</a>
</nav>
```

```javascript
// DON'T DO THIS
store('my/nav', {
actions: {
navigate(e) {
e.preventDefault();
routerActions.navigate(e.target.href);
}
}
});
```

### ✅ CORRECT: Plain links, router does the work

```html
<!-- DO THIS -->
<nav>
<a href="/page/">Link</a>
</nav>
```

```javascript
// Just register an empty store if needed, or don't register one at all
import { store } from '@wordpress/interactivity';
store('my/namespace', {});
```

## When You DO Need Custom Handlers

Only use custom navigation handlers for:

- Non-link elements (buttons, divs) that should navigate
- Conditional navigation (confirm dialogs, form validation)
- Links that need special processing before navigation

```javascript
// Example: Button that navigates
store('my/namespace', {
actions: {
*navigateToProfile() {
const { actions } = yield import('@wordpress/interactivity-router');
yield actions.navigate('/profile/');
}
}
});
```

## Debugging

If navigation isn't working:

1. **Check router is enqueued**: View page source, search for `interactivity-router`
2. **Check region exists**: Search HTML for `data-wp-router-region`
3. **Check target page has same region ID**: Both pages need matching region IDs
4. **Check for custom handlers**: Remove any `data-wp-on--click` from nav elements
5. **Check console errors**: Router logs navigation events
6. **Check script module has router options (WP 6.9+)**: Your script tag should have `data-wp-router-options` attribute. If missing, call `wp_interactivity()->add_client_navigation_support_to_script_module('your-module')`

## Multiple Router Regions

You can have multiple independent regions:

```html
<aside data-wp-router-region="sidebar">...</aside>
<main data-wp-router-region="main">...</main>
```

Each region swaps independently based on the target page's matching regions.