# 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
