Skip to content
Merged
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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Pollora 13.x → 13.32 upgrade prompt** (`upgrade-pollora-v13-32`), registered when Pollora 13.0 to 13.4.x is installed: dependencies and wp-packages, composer-patches 2 (`patches-relock`, `patches.lock.json`, checking that WordPress is patched), renamed Artisan commands, classes moved to `pollora/hook`, `pollora/option` and `pollora/ajax`, `Loop`/`Query` removal, `Translater` domain, blocks in `resources/views/blocks` and registered without a provider, skeleton files (`routes/web.php`, cache table, `.htaccess`), and the theme.json defects fixed in themes (`theme(static)`, the copy step, cyclic `@theme` values)
- **`pollora-abilities` skill**: the WordPress Abilities API with `#[Ability]` and the `Ability` facade
- `active_theme_info` reports `blocks_in_deprecated_directory`, the blocks still in `resources/blocks`

### Changed

- **Guidelines**: the facades that exist (no `Loop` or `Query`, which were removed; `Ability`, `PostQuery`, `MetaQuery`, `TaxQuery`, `Mail`, `Constant` added), `__()` routing, blocks, `pollora_register()`, the design tokens in `@theme`, the template marker, `#[Ajax]`, `#[Ability]`, `#[SkipDiscovery]`, and the full list of Artisan commands
- **13.x guidelines**: `illuminate/*` `^13.32`, WordPress 7, the extracted packages
- **`pollora-blocks` skill** rewritten for `resources/views/blocks`, dynamic Blade blocks by default (`render.blade.php`, `--static`), registration by Pollora without a service provider, and the migration from `resources/blocks`
- **`pollora-theming` skill**: `pollora_register()`, the login screen (`config/login.php`), `get_theme_file_uri()`, the template marker; `tailwind.config.js` dropped
- **`pollora-rest-api` skill**: AJAX handlers with `#[Ajax]`
- **12 → 13 upgrade prompt**: no longer adds a `copy-theme-json` build step, which froze a theme's palette; blocks section updated
- `pollora/framework` is constrained to `^12.0 || ^13.0` instead of `*`; `orchestra/testbench` accepts `^11.0`, so the tests run against Laravel 13 and framework 13 instead of Laravel 12 and framework 12.0

### Fixed

- `active_theme_info` listed no block for a theme made since v13.32: it read `resources/blocks` only. It reads `resources/views/blocks`, and counts a directory as a block only when it holds a `block.json`
- `pollora-post-types` skill: `pollora:make:post-type` (was `pollora:make-post-type`)

## [0.2.0] - 2026-06-23

### Added
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Nectar feeds AI agents with the context they need to write high-quality Pollora
| Feature | Description |
|---------|-------------|
| **AI Guidelines** | Pollora architecture, PHP attributes, WordPress routing, Blade theming — injected into your agent's context via Boost |
| **8 Agent Skills** | On-demand knowledge for post types, taxonomies, theming, hooks, blocks, REST API, scheduling, and modules |
| **9 Agent Skills** | On-demand knowledge for post types, taxonomies, theming, hooks, blocks, REST API and AJAX, scheduling, modules, and abilities |
| **10 MCP Tools** | Live introspection of your WordPress & Pollora environment directly from your AI agent |
| **Upgrade Prompts** | Step-by-step MCP prompts for upgrading between Pollora major versions (e.g., 12→13) |

Expand Down Expand Up @@ -112,18 +112,20 @@ Skills are activated on-demand when working on specific tasks:
| `pollora-taxonomies` | Creating custom taxonomies |
| `pollora-theming` | Theme development (Blade, Vite, Tailwind, assets) |
| `pollora-hooks` | Registering WordPress actions & filters |
| `pollora-blocks` | Gutenberg block development with JSX & Tailwind |
| `pollora-rest-api` | REST API endpoints with `#[WpRestRoute]` |
| `pollora-blocks` | Gutenberg blocks with JSX, Blade rendering & Tailwind |
| `pollora-rest-api` | REST API endpoints with `#[WpRestRoute]`, AJAX handlers with `#[Ajax]` |
| `pollora-scheduling` | Scheduled tasks with `#[Schedule]` |
| `pollora-abilities` | WordPress Abilities API with `#[Ability]` and the `Ability` facade |
| `pollora-modules` | Laravel Modules with auto-discovery |

## Upgrade Assistance

Nectar provides MCP upgrade prompts that guide AI agents through Pollora major version upgrades. Prompts are **automatically registered** when the current project version matches.
Nectar provides MCP upgrade prompts that guide AI agents through Pollora version upgrades. Prompts are **automatically registered** when the current project version matches.

| Prompt | Available when | Covers |
|--------|---------------|--------|
| `upgrade-pollora-v13` | Pollora 12.x detected | Loop facade removal, config registration removal, CSRF middleware rename, Vite theme.json setup, WordPress 7.0, and more |
| `upgrade-pollora-v13-32` | Pollora 13.0 to 13.4.x detected | Dependencies and composer-patches 2, renamed commands, extracted Hook/Option/Ajax packages, Loop/Query removal, blocks in `resources/views/blocks`, skeleton files (`routes/web.php`, cache table, `.htaccess`), theme.json from `@theme` |

The upgrade prompt includes:
- Step-by-step process (assess → safety net → analyze → apply → update deps → clean up)
Expand Down Expand Up @@ -157,7 +159,7 @@ return [

- PHP ^8.3
- Laravel 12.x or 13.x
- Pollora Framework (any version)
- Pollora Framework 12.x or 13.x
- Laravel Boost ^2.0

## License
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
"require": {
"php": "^8.3",
"laravel/boost": "^2.0",
"pollora/framework": "*"
"pollora/framework": "^12.0 || ^13.0"
},
"require-dev": {
"driftingly/rector-laravel": "^2.3",
"laravel/pint": "^1.0",
"orchestra/testbench": "^9.0|^10.0",
"orchestra/testbench": "^9.0|^10.0|^11.0",
"pestphp/pest": "^3.0",
"phpstan/phpstan": "^2.0"
},
Expand Down
17 changes: 11 additions & 6 deletions resources/boost/guidelines/13/core.blade.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
## Pollora 13.x Specifics

Pollora 13.x targets **Laravel 13.x** and **PHP 8.3+**.
Pollora 13.x targets **Laravel 13.x** and **PHP 8.3+**. Since v13.32 the framework version tracks the Laravel release it targets.

### Laravel 13 Compatibility

- Uses `illuminate/*` packages at `^13.0`
- Requires `illuminate/*` `^13.32`
- Supports Pest 3.x for testing
- PHPStan level 5 with WordPress and Laravel extensions
- Rector with Laravel-specific rules

### WordPress 6.9 Integration
### WordPress 7 Integration

- WordPress installed at `public/cms/`
- WordPress 7.x (`johnpbloch/wordpress` `^7.0`) installed at `public/cms/`
- Content directory at `public/content/`
- Full Site Editing support via `theme.json`
- Gutenberg blocks with Vite + JSX/TSX + Tailwind CSS v4
- Full Site Editing support via `theme.json`, generated from the theme's `@theme` tokens
- Gutenberg blocks with Vite + JSX/TSX + Tailwind CSS v4, rendered with Blade
- Abilities API through `pollora/abilities` (WordPress 6.9+)

### Extracted Packages

Some modules now live in their own packages, installed with the framework: `pollora/hook` (hook domain and adapters), `pollora/option`, `pollora/ajax`, `pollora/abilities`.

### Discovery System Enhancements

Expand Down
41 changes: 32 additions & 9 deletions resources/boost/guidelines/core.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ class ItemAPI {}
// Scheduled tasks
#[Schedule(Every::DAY)]
public function dailyCleanup(): void {}

// AJAX handlers — logged-in users only unless access says otherwise
#[Ajax('load_more', access: AjaxAccess::ALL)]
public function loadMore(): void {}

// WordPress Abilities API (WP 6.9+) — a class implementing AbilityHandler
#[Ability(name: 'acme/create-post', description: 'Creates a post.', category: 'acme-content')]
final class CreatePost implements AbilityHandler {}

// Keep a class out of discovery entirely (or all but some: except: [...])
#[SkipDiscovery]
class InternalHelper {}
</code-snippet>
@endverbatim

Expand All @@ -74,33 +86,44 @@ public function dailyCleanup(): void {}
├── resources/
│ ├── assets/ # JS, CSS, fonts, images
│ └── views/ # Blade templates
├── functions.php # Theme registration entry point
│ └── blocks/ # Gutenberg blocks, registered automatically
├── functions.php # pollora_register(ModuleType::Theme)
├── style.css # WordPress theme metadata
├── theme.json # Block editor settings
├── theme.json # Base editor settings; the build adds the @theme tokens
└── vite.config.js # Vite build config
```

Themes use Vite for asset bundling with HMR, Tailwind CSS v4, and the `Asset` facade for script/style registration. Theme classes use the `Theme\{ThemeName}\` namespace.
Themes use Vite for asset bundling with HMR, Tailwind CSS v4, and the `Asset` facade for script/style registration. Theme classes use the `Theme\{ThemeName}\` namespace. A plugin registers with `pollora_register(ModuleType::Plugin, 'my-plugin', __DIR__)` (`use Pollora\Modules\Domain\Enums\ModuleType;`).

Design tokens (colours, font sizes, fonts, radii) live in the `@theme static` block of `app.css` with concrete values; the build writes them into the `theme.json` the editor reads. `get_theme_file_uri()` returns the built URL of a theme asset.

### Key Conventions

- **Never call WordPress registration functions directly** — use attributes and discovery
- **Use Blade directives** from Sage Directives: `@posts`, `@title`, `@content`, `@permalink`, `@published`
- **WordPress objects** (`WP_Post`, `WP_Query`, `WP_User`) are auto-injected via type hints in controller methods
- **Facades**: `Action`, `Filter`, `Query`, `Asset`, `Theme`, `PostType`, `Taxonomy`, `Option`, `Loop`, `Ajax`
- **Facades**: `Action`, `Filter`, `Ajax`, `Asset`, `Theme`, `PostType`, `Taxonomy`, `Option`, `Ability`, `PostQuery`, `MetaQuery`, `TaxQuery`, `Mail`, `Constant`. There is no `Loop` or `Query` facade: use Sage Directives in Blade, WordPress functions in PHP
- **Translations**: `__('Text', 'my-domain')` goes to WordPress's catalogues; `__('key', ['name' => $x])` goes to Laravel; `__('Text')` tries Laravel, then WordPress's `default` domain
- **Blocks**: live in `resources/views/blocks/{slug}`, render with `render.blade.php` by default, and are registered by Pollora — never write a `BlocksServiceProvider`
- **Which template answered?** With `WP_DEBUG` on, every page carries `<!-- pollora:template="single" path="themes/x/resources/views/single.blade.php" -->` (hierarchy responses only, not `Route::wp()` or Laravel routes)
- **CSRF**: WordPress endpoints are excluded from Laravel CSRF — WordPress uses its own nonce system
- **Modules**: Use `nwidart/laravel-modules` for large projects — discovery works inside modules automatically

### Available Artisan Commands

- `pollora:install` — Full project installation
- `pollora:make:theme` — Generate a new theme
- `pollora:make:block` — Generate a Gutenberg block
- `pollora:make:post-type` — Generate a post type class
- `pollora:make:action` / `pollora:make:filter` — Generate hook classes
- `pollora:install` — Full project installation (`--theme` to pick the generated theme; runs without interaction)
- `pollora:env:setup` — Install and configure WordPress
- `pollora:make:theme` / `pollora:theme:delete` / `pollora:theme:status` — Themes
- `pollora:make:plugin` / `pollora:plugin:list` / `pollora:plugin:status` — Plugins
- `pollora:make:block` — Generate a Gutenberg block (dynamic Blade by default, `--static` for `save.jsx`)
- `pollora:make:post-type` / `pollora:make:taxonomy` — Generate post type and taxonomy classes
- `pollora:make:action` / `pollora:make:filter` / `pollora:make:hook` — Generate hook classes
- `pollora:make:model` / `pollora:make:wp-cli` — Generate an Eloquent model or a WP-CLI command class
- `discovery:run` / `discovery:clear` — Manage component discovery cache
- `pollora:status` — Show framework status

Commands use the colon convention since v13.32; the former dashed names (`pollora:make-theme`…) still work as aliases.

### Pollora Nectar MCP Tools

When available, use the `pollora-nectar` MCP server for live introspection:
Expand Down
83 changes: 83 additions & 0 deletions resources/boost/skills/pollora-abilities/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: pollora-abilities
description: Declare WordPress Abilities API abilities (WordPress 6.9+) with the Pollora Ability facade or the #[Ability] attribute, so AI agents and automation tools can discover and invoke site features.
---

# Pollora Abilities

## When to use this skill
Use this skill when exposing site functionality to AI agents or automation tools through the WordPress Abilities API (WordPress 6.9+). Pollora wraps it in `pollora/abilities`, installed with the framework. On an older WordPress, declarations are accepted and never published.

An ability is not an MCP tool: the MCP Adapter plugin publishes abilities over MCP, and core exposes them at `/wp-json/wp-abilities/v1/abilities`. Declare the ability once; consumers pick it up.

## Categories

Every ability belongs to a category that must exist. Declare it once, typically in a service provider. Slugs are global to the install and core claims several: prefix yours.

```php
use Pollora\Support\Facades\Ability;

Ability::category('acme-content', 'Editorial', 'Posts and pages.');
```

## Declarative API (preferred for anything non-trivial)

```php
use Pollora\Abilities\Domain\Contracts\AbilityHandler;
use Pollora\Abilities\Domain\Model\Behaviour;
use Pollora\Abilities\Domain\Model\Input;
use Pollora\Abilities\Domain\Schema\SchemaBuilder;
use Pollora\Attributes\Ability;

#[Ability(
name: 'acme/create-post',
description: 'Creates a post from a title and a status.',
category: 'acme-content',
behaviour: Behaviour::Creates,
)]
final class CreatePost implements AbilityHandler
{
public function schema(SchemaBuilder $schema): void
{
$schema->string('title', 'Title of the post to create.', required: true);
$schema->enum('status', 'Publication status.', ['draft', 'publish'], default: 'draft');
}

public function authorize(Input $input): mixed
{
return current_user_can('edit_posts')
?: new WP_Error('forbidden', 'You cannot create posts.', ['status' => 403]);
}

public function handle(Input $input): mixed
{
return ['id' => wp_insert_post([
'post_title' => $input->string('title'),
'post_status' => $input->string('status', 'draft'),
])];
}
}
```

The class is discovered anywhere discovery scans and built through the container. A class with `#[Ability]` that does not implement `AbilityHandler` is logged as an error. A missing category is declared for you.

## Imperative API

```php
Ability::define('acme/get-posts')
->description('Returns the most recent posts, newest first.')
->category('acme-content')
->input(fn (SchemaBuilder $schema) => $schema
->integer('limit', 'How many posts to return.', default: 10, minimum: 1, maximum: 100))
->can(fn (Input $input): bool => current_user_can('edit_posts'))
->using(fn (Input $input): array => /* ... */ []);
```

Nothing is registered until `using()` or `handledBy()` supplies a body. Pollora queues declarations and publishes them on WordPress's abilities init hooks — never hook those yourself.

## Rules

- **Names** are `namespace/slug`, lowercase alphanumerics and single dashes; a bare slug, an empty label or description is refused at declaration.
- **Permissions default to refusing**: without `can()` / `authorize()` the ability refuses everything. The check receives the same input as the body — check `edit_post` on the given id rather than a blanket `edit_posts`. Return a `WP_Error` to explain a refusal.
- **Behaviour** (advisory hints for clients): `Reads` (default), `Creates`, `Updates`, `Deletes` — facade `->reads()`, `->creates()`, `->updates()`, `->deletes()`. Declare it truthfully; the permission check is what protects the site.
- **Input** is a defensive reader: `$input->string()`, `integer(default:, max:)`, `float()`, `id()`, `boolean()`, `stringList()` never throw on a missing or malformed value.
Loading
Loading