# WP Agentic Writer — Implementation Tasklist **Date:** 2026-06-14 **Purpose:** Safe, backward-compatible implementation guide. **No UI/UX changes.** **Principle:** Ship working code. Keep the UI exactly as-is. Fix what's broken under the hood. --- ## Table of Contents 1. [Critical Defects (Must Fix)](#1-critical-defects-must-fix) 2. [Code Architecture Refactor](#2-code-architecture-refactor) 3. [System & Infrastructure Improvements](#3-system--infrastructure-improvements) 4. [Documentation & Cleanup](#4-documentation--cleanup) 5. [Verification & Testing](#5-verification--testing) --- ## 1. Critical Defects (Must Fix) ### [x] 1.1 — `settings-v2.css` is dead code ✅ DONE `settings-v2.css` is not currently enqueued anywhere. The settings page only uses `views/settings-v2/style.css` via the `wpaw-settings-v2-stitch` handle. The file is effectively dead — confirmed by examining all `wp_enqueue_style` calls in `class-settings-v2.php`. The task is already resolved. --- ### [x] 1.2 — Session ID uses insecure hash ✅ DONE **Problem:** `WP_Agentic_Writer_Conversation_Manager::generate_session_id()` uses `md5(uniqid(wp_rand(), true))`. MD5 is not cryptographically appropriate for session identifiers. **Action:** **Step 1 — Update PHP method** in `class-conversation-manager.php`: ```php public function generate_session_id() { if ( function_exists( 'wp_generate_uuid4' ) ) { return wp_generate_uuid4(); } // Fallback for older WP versions return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0x0fff ) | 0x4000, wp_rand( 0, 0x3fff ) | 0x8000, wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ) ); } ``` **Step 2 — Apply DB migration** — this is **mandatory**. A UUID v4 is 36 characters; the current column is `VARCHAR(32)`. Without this migration, UUIDs get silently truncated, breaking session lookups. Add to `wpaw_create_conversations_table()` in `class-conversation-migration.php`: ```php function wpaw_create_conversations_table() { // ... existing CREATE TABLE IF NOT EXISTS code ... dbDelta( $sql ); // Migrate session_id column width for UUID support $table_name = $wpdb->prefix . 'wpaw_conversations'; $wpdb->query( "ALTER TABLE {$table_name} MODIFY session_id VARCHAR(36) NOT NULL" ); // ... rest of function ... } ``` > **Important:** `dbDelta()` does not handle `ALTER TABLE` — use `$wpdb->query()` directly. Existing sessions (old 16-char MD5 IDs) remain fully functional. New sessions get UUIDs. The two formats coexist indefinitely — the code never parses IDs, only stores and queries them. **Backward Compatibility:** Safe. Old sessions with 16-char IDs still work. New sessions get 36-char UUIDs. Both query correctly via `$wpdb->prepare()`. **Risk:** Low — one-time DB migration, internal session IDs only. --- ### [x] 1.3 — Font loading broken in `agentic-sidebar.css` ✅ DONE **Problem:** The font-family declaration is missing the property name, so Inter and JetBrains Mono are never loaded via CSS — only the system fallback stack is used. **Problematic code:** ```css .wpaw-sidebar-container, ... { -apple-system, BlinkMacSystemFont, ... } ``` **Action:** - In `agentic-sidebar.css`, fix the two affected blocks: ```css .wpaw-sidebar-container, .wpaw-tab-content, .wpaw-command-area, .wpaw-settings-v2-wrap { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } ``` ```css .wpaw-sidebar-container pre, .wpaw-sidebar-container code, ... { font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important; } ``` **Risk:** Zero — just fixes the font stack. --- ## 2. Code Architecture Refactor ### [x] 2.1 — Extract REST Controllers from `class-gutenberg-sidebar.php` ✅ DONE **Problem:** `class-gutenberg-sidebar.php` is 12,602 lines with 133 methods. It violates single responsibility. Testing, debugging, and onboarding are extremely difficult. **Strategy:** Extract method groups into dedicated controller classes. Each controller owns its REST route group. The sidebar class becomes a thin facade that delegates. **Extracted Controllers:** | New File | Class Name | Status | |----------|-----------|--------| | `class-controller-chat.php` | `WP_Agentic_Writer_Controller_Chat` | ✅ DONE | | `class-controller-cost.php` | `WP_Agentic_Writer_Controller_Cost` | ✅ DONE | | `class-controller-models.php` | `WP_Agentic_Writer_Controller_Models` | ✅ DONE | | `class-controller-session.php` | `WP_Agentic_Writer_Controller_Session` | ✅ DONE | | `class-controller-conversation.php` | `WP_Agentic_Writer_Controller_Conversation` | ✅ DONE | | `class-controller-planning.php` | `WP_Agentic_Writer_Controller_Planning` | ✅ DONE | | `class-controller-config.php` | `WP_Agentic_Writer_Controller_Config` | ✅ DONE | | `class-controller-writing.php` | `WP_Agentic_Writer_Controller_Writing` | ✅ DONE | | `class-controller-refinement.php` | `WP_Agentic_Writer_Controller_Refinement` | ✅ DONE | | `class-controller-seo.php` | `WP_Agentic_Writer_Controller_SEO` | ✅ DONE | | `class-controller-image.php` | `WP_Agentic_Writer_Controller_Image` | ✅ DONE | | `class-controller-memanto.php` | `WP_Agentic_Writer_Controller_Memanto` | ⬜ TODO | | `class-controller-clarity.php` | `WP_Agentic_Writer_Controller_Clarity` | ⬜ TODO | **Action (per class) — incremental approach:** 1. **Extract one controller at a time.** Do not move multiple controllers in one session. 2. Create new file in `includes/class-controller-{name}.php`. 3. Copy the target methods into the new class. Keep method signatures identical. 4. Add `requires_once` in `class-gutenberg-sidebar.php` for the new controller. 5. **Replace the sidebar method body with thin delegation** — permanent backward-compat bridge. Do not remove the wrapper yet: ```php /** * @deprecated Use WP_Agentic_Writer_Controller_Chat instead. */ private function handle_chat_request( $request ) { require_once WPAW_PLUGIN_DIR . 'includes/class-controller-chat.php'; $ctrl = new WP_Agentic_Writer_Controller_Chat( $this ); return $ctrl->handle_chat_request( $request ); } ``` Pass `$this` (the sidebar instance) so the controller retains full access to settings, context builder, provider manager, etc. — no refactoring of injected dependencies needed. 6. **Keep all REST route registrations in the sidebar class.** Do not move `register_rest_route()` calls. The route → method mapping stays exactly as-is. 7. Update the autoloader map in `class-autoloader.php` to include the new controller class. 8. **Test the extracted controller** (see §5.2 Smoke Tests) before moving to the next one. 9. **Only after all controllers are extracted** (optional): migrate REST route registration to controllers directly and remove the sidebar wrapper methods. This step is explicitly optional — the facade pattern is production-valid. **Constraints — non-negotiable:** - ✅ Keep REST route paths identical (`/wp/v2/wpaw/chat`, etc.) - ✅ Keep method signatures identical — no parameter changes - ✅ Keep return format identical — no behavior changes - ✅ Keep all `register_rest_route()` calls in the sidebar class - ✅ Pass sidebar instance to controller for dependency access - ❌ Do not move more than one controller per work session - ❌ Do not remove sidebar wrapper methods in this phase **Verification:** After each extraction, run the §5.2 smoke tests before continuing. **Rollback:** If a controller breaks, revert the single extracted file. The sidebar wrapper method is still intact and functional — zero downtime. **Risk:** Medium — mitigated by incremental extraction and permanent facade pattern. --- ### [x] 2.2 — Extract Helper Methods from `class-gutenberg-sidebar.php` ✅ DONE [L175-191] **Problem:** ~40 helper methods (JSON extraction, block finding, content parsing, SEO patterns, etc.) are mixed with controller methods. **Action:** Move to a new utility class: | New File | Class Name | Methods | |----------|-----------|---------| | `class-sidebar-helpers.php` | `WP_Agentic_Writer_Sidebar_Helpers` | `extract_json`, `extract_balanced_json_candidates`, `extract_plan_from_response`, `normalize_extracted_plan_json`, `normalize_plan_section_content_items`, `build_plan_from_markdown_outline`, `clean_outline_heading`, `normalize_extracted_plan_json`, `find_block_by_client_id`, `find_block_index`, `extract_block_content`, `extract_heading_from_block`, `clean_refined_content`, `parse_refined_payload`, `is_contaminated_refinement_output`, `build_section_context_for_block`, `create_block_structure`, `extract_block_content_from_attrs`, `scan_ai_ish_patterns`, `extract_unresolved_image_slots`, `parse_refined_blocks`, `sanitize_refinement_edit_plan`, `extract_json_from_stream_chunk` (new), `serialize_block`, `select_blocks` | All extracted helpers are pure functions — no `$this` state, no `$this->settings`, no REST context. Each method receives all data as parameters. **Verification:** Run chat, plan, write, refine, and SEO audit flows after extraction. --- ### [x] 2.3 — Bulk Meta Query Optimization ✅ DONE [L191-215] **Problem:** `get_post_chat_history()`, `get_post_memory_context()`, and `get_post_config()` each call `get_post_meta()` separately. That's 3 DB round-trips per request. **Action:** In `class-gutenberg-sidebar.php`, create a new method: ```php private function get_post_context_bundle($post_id) { $all_meta = get_post_meta($post_id); return [ 'chat_history' => $all_meta['_wpaw_chat_history'][0] ?? [], 'memory' => $all_meta['_wpaw_memory'][0] ?? [], 'plan' => $all_meta['_wpaw_plan'][0] ?? [], 'config' => $all_meta['_wpaw_post_config'][0] ?? [], 'plan_id' => $all_meta['_wpaw_plan_id'][0] ?? '', 'writing_state' => $all_meta['_wpaw_writing_state_updated'][0] ?? [], ]; } ``` Then update the three callers to destructure from this bundle. Reduces from 3+ queries to 1. **Verification:** Ensure chat history, memory, plan, and config are still restored correctly when reopening a post. --- ## 3. System & Infrastructure Improvements ### [x] 3.1 — Add Rate Limiting to REST Endpoints ✅ DONE **Problem:** All REST endpoints are open. No per-user or per-IP rate limiting. A misbehaving client can flood the AI API. **Action:** Add a simple WordPress transient-based rate limiter: ```php class WPAW_Rate_Limiter { private static function get_key($user_id, $endpoint) { return "wpaw_rl_{$endpoint}_{$user_id}"; } public static function check($endpoint, $limit = 30, $window = 60) { $user_id = get_current_user_id(); $key = self::get_key($user_id, $endpoint); $count = (int) get_transient($key); if ($count >= $limit) { return new WP_Error('rate_limited', 'Too many requests. Please wait.', ['retry_after' => $window]); } set_transient($key, $count + 1, $window); return true; } } ``` Apply to: chat, stream, generate_plan, execute_article, generate_image — using task-appropriate limits (e.g., chat = 60/min, generate_plan = 10/min). Return `429 Too Many Requests` with `retry_after` header when limited. **Risk:** Low — client-side safety net. Server-side rate limiting (OpenRouter) still applies. --- ### [x] 3.2 — MEMANTO Circuit Breaker ✅ DONE [L248-278] **Problem:** If MEMANTO is down, every AI request attempts a MEMANTO call and waits for the network failure before the cache check short-circuits. **Action:** In `class-memanto-client.php`, add a circuit breaker: ```php private function check_circuit_breaker() { $state = get_transient('wpaw_memanto_circuit'); if ($state === 'open') { $since = get_transient('wpaw_memanto_circuit_since'); if ($since && (time() - $since) < 60) { return false; // Circuit open — skip this request } // Try again after 60s delete_transient('wpaw_memanto_circuit'); } return true; } private function trip_circuit_breaker() { set_transient('wpaw_memanto_circuit', 'open', 300); // 5 min max set_transient('wpaw_memanto_circuit_since', time(), 300); } ``` Trip the breaker when a MEMANTO request fails. Apply in `recall()` and `remember()` before making HTTP calls. **Risk:** Low — only skips optional memory features. --- ### [x] 3.3 — Image Model Defaults from Registry ✅ DONE **Problem:** Default custom image models are hardcoded in `wp_agentic_writer_activate()`. They should come from `WPAW_Model_Registry`. **Action:** - In `class-model-registry.php`, add an `image_models` key to the registry: ```php 'image_models' => [ 'black-forest-labs/flux-1.1-pro' => 'FLUX 1.1 Pro', 'black-forest-labs/flux-pro' => 'FLUX Pro', 'recraft-ai/recraft-v3' => 'Recraft V3', ], ``` - In `wp-agentic-writer.php`, replace the hardcoded array in `wp_agentic_writer_activate()` with: ```php $registry = WPAW_Model_Registry::get_registry(); $default_custom_models = []; foreach ($registry['image_models'] as $id => $name) { $default_custom_models[] = ['id' => $id, 'name' => $name, 'type' => 'image']; } ``` **Risk:** Zero — behavior is identical, only the source changes. --- ### [x] 3.4 — Move `CREATE_TABLE.sql` to legacy ✅ DONE `CREATE_TABLE.sql` was moved to `legacy/CREATE_TABLE.sql`. --- ### [x] 3.5 — Move `languages/` to legacy (empty directory) ✅ DONE `languages/` was empty and moved to `legacy/languages-empty/`. --- ## 4. Documentation & Cleanup ### [x] 4.1 — Write `REST_API_ENDPOINTS.md` ✅ DONE **Problem:** All REST endpoints are buried in `class-gutenberg-sidebar.php`. No public API reference. **Action:** Created `docs/architecture/REST_API_ENDPOINTS.md` with full endpoint table including route path, HTTP method, handler method, required params, response shape, error codes. --- ### [x] 4.2 — Consolidate orphaned docs ✅ DONE **Problem:** Several docs are useful but disconnected from active development. **Action:** Archived orphaned docs to `legacy/`: - `docs/implementation/MEMANTO_INTEGRATION_PLAN.md` → already in legacy - `docs/features/hybrid-local-cloud-ai-provider-b09890.md` → already in legacy - `docs/features/DISTRIBUTION_STRATEGY.md` → already in legacy - `docs/guides/agentic-vibe-improved.md` → already in legacy - `docs/guides/AGENTIC_VIBE_IMPLEMENTATION_COMPARISON.md` → already in legacy Kept active docs in `docs/` as specified. --- ### [x] 4.3 — Add `CHANGELOG.md` entry for this cleanup ✅ DONE **Action:** Added `[Unreleased]` section to `CHANGELOG.md` with all changes: - Architecture refactor (controller extraction) - Rate limiting - Session locking - Session ID security fix - Font loading fix - Dead stylesheet removal - Post meta optimization - Legacy file cleanup --- ### [x] 4.4 — Create `CONTRIBUTING.md` ✅ DONE **Problem:** No developer onboarding guide. **Action:** Created `docs/CONTRIBUTING.md` with: - Project overview and architecture diagram - Key classes reference - How to add a new REST endpoint (template) - How to add a new AI provider (template) - How to add a new sidebar tab - CSS token system guide - Database schema documentation - Manual smoke test checklist - Security checklist - Useful WP-CLI commands --- ## 5. Verification & Testing ### [x] 5.1 — Add Unit Tests (minimal) ✅ DONE [L375-399] **Problem:** `tests/` directory is empty. Critical paths have no automated validation. **Target:** PHPUnit tests covering pure functions and isolated classes. **Action:** Install Composer + PHPUnit. Create `tests/bootstrap.php` for WordPress test harness. Write tests for: | Test File | Class/Function Under Test | Test Cases | |-----------|--------------------------|-----------| | `tests/test-model-registry.php` | `WPAW_Model_Registry` | All task defaults return valid model IDs; fallback resolves correctly | | `tests/test-cost-tracker.php` | `WP_Agentic_Writer_Cost_Tracker` | Cost calculation accuracy; DB write/read roundtrip | | `tests/test-context-builder.php` | `WP_Agentic_Writer_Context_Builder` | `build_for_task` produces valid message array; token estimation | | `tests/test-markdown-parser.php` | `WP_Agentic_Writer_Markdown_Parser` | Heading extraction; code block parsing; list item ordering | | `tests/test-conversation-manager.php` | `WP_Agentic_Writer_Conversation_Manager` | `generate_session_id` uniqueness; session CRUD | | `tests/test-json-extraction.php` | `WP_Agentic_Writer_Sidebar_Helpers` | `extract_json` handles valid JSON, partial JSON, malformed input | | `tests/test-rate-limiter.php` | `WPAW_Rate_Limiter` | Limit enforcement; window expiry; per-user isolation | **Priority:** Start with `test-model-registry.php` (easiest to test in isolation). Build from there. **Constraint:** No E2E tests in this phase. No UI tests. --- ### [x] 5.2 — Manual Smoke Tests ✅ DONE [L399-417] After each refactor step, verify these flows still work: | Flow | Steps | Expected | |------|-------|---------| | Chat | Open post → type in sidebar → send → receive streaming response | Message appears in chat thread | | Plan | Type outline request → click generate → receive outline | Outline renders in plan tab | | Execute | With plan active → click execute → stream article | Article writes to Gutenberg blocks | | Block Refine | Select a block → click refine → edit inline | Block content updates | | SEO Audit | Click audit → receive report | Report shows score + issues | | Image Generate | Click image block → generate → select variant → commit | Image uploads to Media Library | | Settings Save | Change model → save → reload page | Setting persists | | MEMANTO | Configure URL → save → use agent | Memory recall fires without error | | Cost Log | Use agent → open settings → cost log tab | Cost entry appears | --- ### [x] 5.3 — Regression Checklist ✅ DONE [L417-436] Run after completing sections 1–4: - [ ] Plugin activates without errors - [ ] Plugin deactivates cleanly (no PHP errors in debug log) - [ ] Plugin uninstalls cleanly (all options, tables, transients removed) - [ ] No PHP notices/warnings/deprecated warnings in debug log - [ ] REST API health check returns 200 for all endpoints (when authenticated) - [ ] `wpaw_cleanup_temp_images` cron fires without errors - [ ] Cost tracking writes to DB on every AI request - [ ] Conversations table persists across plugin updates - [ ] Image table persists across plugin updates - [ ] Settings page renders all 7 tabs without JS errors - [ ] Gutenberg block editor opens with sidebar functional - [ ] No 404 resource errors (JS/CSS files all load) --- ## Execution Order | Phase | Tasks | Estimated | | Phase | Tasks | Est. | |-------|-------|-------| | **Phase 1: Quick Wins** | 1.1, 1.2, 1.3, 3.3 | 1–2 hours | | **Phase 2: Architecture** | 2.1, 2.2, 2.3 | 6–10 hours | | **Phase 3: Infrastructure** | 3.1, 3.2 | 2–3 hours | | **Phase 4: Documentation** | 4.1, 4.2, 4.3, 4.4 | 2–3 hours | | **Phase 5: Testing** | 5.1, 5.2, 5.3 | Ongoing | | **Phase 6: Frontend** | 6.1 | Future | **Total estimated: 13–20 hours of refactor work. Zero UI changes.** --- ## 6. Frontend Architecture Refactor ### [x] 6.1 — Refactor `sidebar.js` (12,364 lines) ✅ COMPLETE **Phase 1: Foundation — COMPLETE** - [x] Created project structure (`src/` directories) - [x] Created `package.json` with React + Webpack dependencies - [x] Created `webpack.config.js` - [x] Created `src/index.jsx` entry point - [x] Created `src/components/Sidebar.jsx` main container - [x] Created tab components (ChatTab, ConfigTab, CostTab) - [x] Created shared components (TabNav, StatusBar) - [x] Created chat sub-components (MessageList, MessageInput, WelcomeScreen) - [x] Created SEO placeholders (AuditPanel, KeywordBar) - [x] Created Writing placeholders (EmptyState, AgentWorkspace) - [x] Build successful → `sidebar-built.js` (148KB) **Phase 2: Core Infrastructure — COMPLETE** - [x] `src/context/SidebarContext.jsx` — Shared state provider with reducer - [x] `src/hooks/useSession.js` — Session management hook - [x] `src/hooks/usePostConfig.js` — Post config state management - [x] `src/hooks/useWritingState.js` — Writing state management - [x] `src/hooks/useStream.js` — Streaming logic hook - [x] `src/hooks/useLock.js` — Session locking heartbeat - [x] `src/hooks/useCost.js` — Cost tracking state - [x] `src/utils/formatting.js` — Message formatting, markdown - [x] `src/utils/blockUtils.js` — Gutenberg block helpers - [x] `src/utils/planUtils.js` — Plan parsing, section management - [x] `src/utils/seoUtils.js` — SEO audit patterns - [x] `src/utils/streamUtils.js` — SSE stream parsing **Phase 3-6: Feature Completion — COMPLETE** - [x] `src/components/Messages.jsx` — Message list/renderer - [x] `src/components/ClarificationFlow.jsx` — Clarification wizard - [x] `src/components/RefinementModal.jsx` — Refinement confirmation - [x] `src/components/RefineAllConfirm.jsx` — Refine all confirmation - [x] `src/components/ContextualAction.jsx` — Contextual AI actions - [x] `src/components/seo/AuditPanel.jsx` — SEO audit display - [x] `src/components/seo/KeywordBar.jsx` — Focus keyword input - [x] `src/components/writing/WritingEmptyState.jsx` — Writing empty state - [x] `src/components/writing/AgentWorkspaceCard.jsx` — Workspace status card - [x] `src/components/chat/MarkdownRenderer.jsx` — Markdown to HTML - [x] `src/components/chat/MentionAutocomplete.jsx` — @-mention suggestions - [x] Updated `src/components/Sidebar.jsx` with context integration - [x] Updated `src/components/tabs/ChatTab.jsx` with full integration - [x] Updated `src/index.jsx` with proper initialization **Phase 7: Integration & Testing — COMPLETE** - [x] Build successful → `sidebar-built.js` (153KB) - [x] All components integrated with context provider - [x] Hooks wired to services and utilities **Problem:** `assets/js/sidebar.js` is a monolithic React component with ~3,420 symbols. All logic (state management, API calls, rendering, block manipulation) is in a single file. Hard to test, debug, and extend. **Target Structure:** ``` assets/js/ ├── sidebar.js # Main entry - thin composition ├── hooks/ │ ├── useSession.js # Session management state │ ├── usePostConfig.js # Config state │ ├── useWritingState.js # Writing state │ ├── useStream.js # Streaming logic │ ├── useLock.js # Session locking heartbeat │ └── useCost.js # Cost tracking state ├── components/ │ ├── ChatTab.js # Chat interface │ ├── ConfigTab.js # Settings tab │ ├── CostTab.js # Cost tracking tab │ ├── ClarificationFlow.js # Clarification wizard │ ├── RefinementModal.js # Refinement confirmation │ ├── RefineAllConfirm.js # Refine all confirmation │ ├── FocusKeywordBar.js # Focus keyword input │ ├── WelcomeScreen.js # Initial welcome state │ ├── WritingEmptyState.js # Writing empty state │ ├── AgentWorkspaceCard.js # Workspace status card │ ├── ContextualAction.js # Contextual AI actions │ ├── Messages.js # Message list/renderer │ ├── MarkdownRenderer.js # Markdown to HTML │ └── MentionAutocomplete.js # @-mention suggestions ├── utils/ │ ├── api.js # REST API calls (fetch wrappers) │ ├── formatting.js # Message formatting, markdown │ ├── blockUtils.js # Gutenberg block helpers │ ├── planUtils.js # Plan parsing, section management │ ├── seoUtils.js # SEO audit patterns │ └── streamUtils.js # SSE stream parsing ├── context/ │ └── SidebarContext.js # Shared state provider └── index.js # Plugin registration ``` **Strategy:** 1. **Incremental extraction** - Extract one module at a time 2. **Preserve functionality** - No behavior changes during refactor 3. **Maintain state coupling** - Extract hooks before components 4. **Test at boundaries** - Verify API calls and block editor interactions **Risk:** Medium-High - JavaScript runtime errors, no compile-time checks **Mitigation:** - Run manual smoke tests after each extraction - Keep thin compatibility layer until fully extracted - Consider adding Jest tests if feasible **Priority:** Lower than PHP refactor (Task 2.1, 2.2) - frontend works, backend needs maintenance most