Major refactoring cleanup: - Add new controller architecture (class-controller-*.php) - Add new settings-v2 UI (views/settings-v2/) - Add new CSS architecture (agentic-sidebar.css, tokens) - Add esbuild build pipeline (scripts/build.js, package.json) - Add composer dependencies (vendor/) - Add frontend src directory (assets/js/src/index.jsx) - Add documentation files - Remove old/obsolete files (class-settings.php, old CSS) This commits all pending changes from previous refactoring efforts.
24 KiB
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
- Critical Defects (Must Fix)
- Code Architecture Refactor
- System & Infrastructure Improvements
- Documentation & Cleanup
- 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:
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:
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 handleALTER 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:
.wpaw-sidebar-container, ... {
-apple-system, BlinkMacSystemFont, ...
}
Action:
- In
agentic-sidebar.css, fix the two affected blocks:.wpaw-sidebar-container, .wpaw-tab-content, .wpaw-command-area, .wpaw-settings-v2-wrap { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }.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:
- Extract one controller at a time. Do not move multiple controllers in one session.
- Create new file in
includes/class-controller-{name}.php. - Copy the target methods into the new class. Keep method signatures identical.
- Add
requires_onceinclass-gutenberg-sidebar.phpfor the new controller. - Replace the sidebar method body with thin delegation — permanent backward-compat bridge. Do not remove the wrapper yet:
Pass
/** * @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 ); }$this(the sidebar instance) so the controller retains full access to settings, context builder, provider manager, etc. — no refactoring of injected dependencies needed. - Keep all REST route registrations in the sidebar class. Do not move
register_rest_route()calls. The route → method mapping stays exactly as-is. - Update the autoloader map in
class-autoloader.phpto include the new controller class. - Test the extracted controller (see §5.2 Smoke Tests) before moving to the next one.
- 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:
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:
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:
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 animage_modelskey to the registry:'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 inwp_agentic_writer_activate()with:$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 legacydocs/features/hybrid-local-cloud-ai-provider-b09890.md→ already in legacydocs/features/DISTRIBUTION_STRATEGY.md→ already in legacydocs/guides/agentic-vibe-improved.md→ already in legacydocs/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_imagescron 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
- Created project structure (
src/directories) - Created
package.jsonwith React + Webpack dependencies - Created
webpack.config.js - Created
src/index.jsxentry point - Created
src/components/Sidebar.jsxmain container - Created tab components (ChatTab, ConfigTab, CostTab)
- Created shared components (TabNav, StatusBar)
- Created chat sub-components (MessageList, MessageInput, WelcomeScreen)
- Created SEO placeholders (AuditPanel, KeywordBar)
- Created Writing placeholders (EmptyState, AgentWorkspace)
- Build successful →
sidebar-built.js(148KB)
Phase 2: Core Infrastructure — COMPLETE
src/context/SidebarContext.jsx— Shared state provider with reducersrc/hooks/useSession.js— Session management hooksrc/hooks/usePostConfig.js— Post config state managementsrc/hooks/useWritingState.js— Writing state managementsrc/hooks/useStream.js— Streaming logic hooksrc/hooks/useLock.js— Session locking heartbeatsrc/hooks/useCost.js— Cost tracking statesrc/utils/formatting.js— Message formatting, markdownsrc/utils/blockUtils.js— Gutenberg block helperssrc/utils/planUtils.js— Plan parsing, section managementsrc/utils/seoUtils.js— SEO audit patternssrc/utils/streamUtils.js— SSE stream parsing
Phase 3-6: Feature Completion — COMPLETE
src/components/Messages.jsx— Message list/renderersrc/components/ClarificationFlow.jsx— Clarification wizardsrc/components/RefinementModal.jsx— Refinement confirmationsrc/components/RefineAllConfirm.jsx— Refine all confirmationsrc/components/ContextualAction.jsx— Contextual AI actionssrc/components/seo/AuditPanel.jsx— SEO audit displaysrc/components/seo/KeywordBar.jsx— Focus keyword inputsrc/components/writing/WritingEmptyState.jsx— Writing empty statesrc/components/writing/AgentWorkspaceCard.jsx— Workspace status cardsrc/components/chat/MarkdownRenderer.jsx— Markdown to HTMLsrc/components/chat/MentionAutocomplete.jsx— @-mention suggestions- Updated
src/components/Sidebar.jsxwith context integration - Updated
src/components/tabs/ChatTab.jsxwith full integration - Updated
src/index.jsxwith proper initialization
Phase 7: Integration & Testing — COMPLETE
- Build successful →
sidebar-built.js(153KB) - All components integrated with context provider
- 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:
- Incremental extraction - Extract one module at a time
- Preserve functionality - No behavior changes during refactor
- Maintain state coupling - Extract hooks before components
- 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