diff --git a/.vibe/config.toml b/.vibe/config.toml
index 6d57383..6a72eb0 100644
--- a/.vibe/config.toml
+++ b/.vibe/config.toml
@@ -84,6 +84,7 @@ fuzzy_threshold = 0.9
permission = "ask"
allowlist = [
"cat",
+ "cd",
"echo",
"file",
"find",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3800f3..d30868f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ All notable changes to Waygate will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.9.0] - 2026-05-25
+
+### Added
+- Themed text personalization: after the AI selects patterns, a second AI call rewrites all visible text content (headings, paragraphs, button labels) in each pattern's block markup to match the topic stated in the user's prompt; block comment markup, HTML tags, attributes, and CSS classes are preserved
+- `AI_Integration::rewrite_pattern_texts( $slugs, $theme )` — sends all selected patterns' raw block content in one batched AI call and returns a `slug → rewritten_content` map; falls back gracefully (returns empty array) on any AI or parse failure
+- `Pattern_Lab::get_pattern_content( $slug )` — fetches the raw block markup of a registered pattern from `WP_Block_Patterns_Registry`
+- `Pattern_Lab::create_page_from_content( $title, $block_contents, $status )` — creates a page from pre-rendered block strings instead of `wp:pattern` references; used when text personalization is active
+- `AI_Integration::generate_page()` now accepts a `bool $personalize_text = true` parameter; when `false`, the original single-call `wp:pattern` flow is used (faster, original placeholder text)
+- **Text personalization** checkbox in the admin form (checked by default); description reads "uncheck for faster generation with original placeholder text" so the speed trade-off is clear
+- `_waygate_personalized` post meta (`1`/`0`) stored on every generated page
+- Result notice now shows "Personalized to your topic" or "Original pattern placeholders" alongside title and pattern count
+- PHPUnit unit tests for `rewrite_pattern_texts()`, `get_pattern_content()`, and `create_page_from_content()` (18 new tests); `get_registered()` and `wp_kses_post()` stubs added to the test bootstrap
+
+### Security
+- AI-rewritten block content is now passed through `wp_kses_post()` in `create_page_from_content()` before insertion, stripping disallowed HTML (e.g. `
+```
+
+### 2. Add Filter for Personalize Text Default (Low Priority)
+
+**Current**: Default is hardcoded to `true` in `generate_page()` signature
+**Recommendation**: Make it filterable
+
+```php
+$personalize_text_default = apply_filters( 'waygate_personalize_text_default', true );
+public static function generate_page( string $description, bool $personalize_text = $personalize_text_default ): array
+```
+
+**Benefits**: Sites can set their preferred default via filter
+
+### 3. Add Filter for AI Rewrite Schema (Low Priority)
+
+**Current**: JSON schema for rewrite response is hardcoded
+**Recommendation**: Make schema filterable
+
+```php
+$schema = apply_filters( 'waygate_rewrite_pattern_texts_schema', $schema, $pattern_contents );
+```
+
+**Benefits**: Allows customization of expected response format
+
+### 4. Batch Size Limit for Rewrite (Low Priority)
+
+**Current**: All patterns are sent in one AI call regardless of count
+**Issue**: Very large pattern sets could hit token limits
+**Recommendation**: Add batch processing
+
+```php
+const MAX_REWRITE_BATCH = 10; // patterns per AI call
+
+public static function rewrite_pattern_texts( array $slugs, string $theme ): array {
+ $all_results = array();
+ $batches = array_chunk( $slugs, MAX_REWRITE_BATCH );
+
+ foreach ( $batches as $batch ) {
+ $results = self::rewrite_pattern_texts_batch( $batch, $theme );
+ $all_results = array_merge( $all_results, $results );
+ }
+
+ return $all_results;
+}
+```
+
+### 5. Add Character/Token Limit Warning (Low Priority)
+
+**Current**: No warning if user description is too long
+**Recommendation**: Add client-side validation
+
+```javascript
+// In the form
+