# Focus Keyword Anchor System & Image Generation Fixes

**Date:** January 30, 2026  
**Version:** 1.0  
**Status:** Implementation Plan

---

## Table of Contents

1. [Executive Summary](#executive-summary)
2. [Part A: Focus Keyword Anchor System](#part-a-focus-keyword-anchor-system)
3. [Part B: Image Generation Fixes](#part-b-image-generation-fixes)
4. [Part C: UI Redesign](#part-c-ui-redesign)
5. [Implementation Priority](#implementation-priority)

---

## Executive Summary

This document addresses three interconnected issues:

| Issue | Root Cause | Solution |
|-------|------------|----------|
| Context loss during conversation | No persistent topic anchor | Focus Keyword as central context driver |
| Image tables not created | Activation hook not re-run | Manual table creation + version check |
| Image generation errors | Method name mismatch + missing public method | Fix method signatures |
| Image toolbar not showing | Script dependency or block attribute issues | Debug and fix filter |

---

# Part A: Focus Keyword Anchor System

## Problem Statement

The agent loses conversation context because:
- Generic topic passed to outline generation
- Chat history truncated to last 10 messages
- No persistent anchor for user's intent
- Recency bias causes LLM to focus on recent refinements

## Solution: Focus Keyword as Context Anchor

### Core Concept

```
┌─────────────────────────────────────────────────────────────┐
│  Focus Keyword = Single Source of Truth for Article Topic   │
│                                                             │
│  • Always visible at top of chat                            │
│  • Drives ALL API calls (clarity, planning, writing)        │
│  • Accumulates suggestions from each AI response            │
│  • User can select, change, or enter custom keyword         │
└─────────────────────────────────────────────────────────────┘
```

### UI Design

#### Location: Top of Chatbox (Replacing Context Indicator)

**Current UI:**
```
┌─────────────────────────────────────────────────────────────┐
│ [1 messages]  [$0.0221]  [~500 tokens]  ............  [↕]   │
└─────────────────────────────────────────────────────────────┘
```

**New UI (Compact - Default):**
```
┌─────────────────────────────────────────────────────────────┐
│ 🎯 [Switch Career Usia 30+ ▼]  [$0.02]  ............  [↕]   │
└─────────────────────────────────────────────────────────────┘
```

**New UI (Expanded - When textarea expanded):**
```
┌─────────────────────────────────────────────────────────────┐
│ 🎯 Focus Keyword                                            │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Switch Career Usia 30+                              [▼] │ │
│ └─────────────────────────────────────────────────────────┘ │
│                                                             │
│ Suggestions:                                                │
│ ○ Switch Career Usia 30+  (from response #1)               │
│ ○ Vibe Coding untuk Pemula  (from response #2)             │
│ ○ AI Web Design Tanpa Coding  (from response #3)           │
│ ○ Custom...                                                 │
│                                                             │
│ Session: $0.02  │  ~500 tokens                              │
└─────────────────────────────────────────────────────────────┘
```

### Dropdown Behavior

1. **Empty State**: Placeholder "Select or enter focus keyword..."
2. **After Response #1**: 1 suggestion appears
3. **After Response #2**: 2 suggestions (cumulative)
4. **Max 5 suggestions**: Older ones rotate out, "Show all" option
5. **Custom Option**: Always last, triggers text input
6. **Selection**: Immediately saves to `postConfig.focus_keyword`

### Data Flow

```
User Message
     │
     ▼
┌────────────────┐
│ AI Response    │
│ + Extract      │──────► Add to suggestions dropdown
│   keyword      │
└────────────────┘
     │
     ▼
User Selects Keyword (or AI auto-selects first suggestion)
     │
     ▼
┌────────────────────────────────────────────────────────────┐
│ ALL subsequent API calls include:                          │
│ {                                                          │
│   focus_keyword: "Switch Career Usia 30+",                 │
│   topic: "...",                                            │
│   chatHistory: [...],                                      │
│   ...                                                      │
│ }                                                          │
└────────────────────────────────────────────────────────────┘
     │
     ▼
Backend System Prompt includes:
"PRIMARY TOPIC: {focus_keyword}
All content must relate to this topic.
Recent conversation refinements should ENHANCE this topic, not replace it."
```

### Keyword Extraction Logic

```javascript
// In chat response handler
const extractFocusKeywordSuggestion = (aiResponse) => {
    // Method 1: AI explicitly suggests (preferred)
    // Look for pattern: "Focus Keyword Suggestion: ..."
    const explicitMatch = aiResponse.match(/focus keyword suggestion[:\s]+["']?([^"'\n]+)["']?/i);
    if (explicitMatch) return explicitMatch[1].trim();
    
    // Method 2: Extract from first heading or bold text
    const headingMatch = aiResponse.match(/^#+\s+(.+)$/m);
    if (headingMatch) return headingMatch[1].trim();
    
    // Method 3: Extract prominent phrase (first bold)
    const boldMatch = aiResponse.match(/\*\*([^*]+)\*\*/);
    if (boldMatch) return boldMatch[1].trim();
    
    return null;
};
```

### Backend Integration

#### 1. Update System Prompts

**File:** `includes/class-gutenberg-sidebar.php`

```php
// In stream_generate_plan() - Line ~1723
$focus_keyword = $post_config['focus_keyword'] ?? '';
$focus_keyword_instruction = '';
if (!empty($focus_keyword)) {
    $focus_keyword_instruction = "
PRIMARY TOPIC ANCHOR: \"{$focus_keyword}\"

CRITICAL: This article MUST be about \"{$focus_keyword}\".
- The title MUST include or relate to \"{$focus_keyword}\"
- All sections MUST support this primary topic
- Recent conversation refinements are meant to ENHANCE this topic, not REPLACE it
- If user discussed sub-topics (e.g., AI tools, web design), treat them as ASPECTS of the primary topic
";
}

$system_prompt = "You are an expert content strategist...

{$focus_keyword_instruction}

IMPORTANT CONSTRAINT: {$section_limit}
...";
```

#### 2. Update Chat Response to Include Keyword Suggestion

**File:** `includes/class-gutenberg-sidebar.php`

```php
// In handle_chat_request() - Add to system prompt
$system_prompt .= "

At the END of your response, if appropriate, suggest a focus keyword for the article in this format:
**Focus Keyword Suggestion:** [your suggested keyword]

The keyword should be:
- 2-5 words
- SEO-friendly
- Capture the main topic discussed
";
```

#### 3. Persist Focus Keyword

**File:** `includes/class-gutenberg-sidebar.php`

```php
// In handle_generate_plan() - Line ~1237
$focus_keyword = $post_config['focus_keyword'] ?? '';

// Save to post meta for persistence
if ($post_id > 0 && !empty($focus_keyword)) {
    update_post_meta($post_id, '_wpaw_focus_keyword', $focus_keyword);
}
```

### Frontend Implementation

#### 1. New State Variables

**File:** `assets/js/sidebar.js`

```javascript
// Add new state
const [focusKeywordSuggestions, setFocusKeywordSuggestions] = wp.element.useState([]);
const [selectedFocusKeyword, setSelectedFocusKeyword] = wp.element.useState('');

// Load from postConfig on mount
wp.element.useEffect(() => {
    if (postConfig.focus_keyword) {
        setSelectedFocusKeyword(postConfig.focus_keyword);
    }
}, [postConfig.focus_keyword]);
```

#### 2. Update postConfig When Keyword Changes

```javascript
const handleFocusKeywordChange = (keyword) => {
    setSelectedFocusKeyword(keyword);
    updatePostConfig('focus_keyword', keyword);
};
```

#### 3. Extract Suggestions from AI Responses

```javascript
// In streaming response handler, after accumulating content
const suggestion = extractFocusKeywordSuggestion(accumulatedContent);
if (suggestion && !focusKeywordSuggestions.includes(suggestion)) {
    setFocusKeywordSuggestions(prev => {
        const updated = [...prev, suggestion];
        // Keep max 5 suggestions
        return updated.slice(-5);
    });
    
    // Auto-select first suggestion if none selected
    if (!selectedFocusKeyword) {
        handleFocusKeywordChange(suggestion);
    }
}
```

#### 4. Render Focus Keyword Bar (Replacing Context Indicator)

```javascript
const renderFocusKeywordBar = () => {
    return wp.element.createElement('div', { 
        className: 'wpaw-focus-keyword-bar'
    },
        // Keyword dropdown
        wp.element.createElement('div', { className: 'wpaw-focus-keyword-wrapper' },
            wp.element.createElement('span', { className: 'wpaw-focus-keyword-icon' }, '🎯'),
            wp.element.createElement('select', {
                className: 'wpaw-focus-keyword-select',
                value: selectedFocusKeyword,
                onChange: (e) => {
                    if (e.target.value === '__custom__') {
                        // Show custom input
                        setShowCustomKeywordInput(true);
                    } else {
                        handleFocusKeywordChange(e.target.value);
                    }
                }
            },
                wp.element.createElement('option', { value: '' }, 'Select focus keyword...'),
                focusKeywordSuggestions.map((kw, idx) => 
                    wp.element.createElement('option', { key: idx, value: kw }, kw)
                ),
                wp.element.createElement('option', { value: '__custom__' }, '+ Custom keyword...')
            )
        ),
        
        // Cost display (compact)
        wp.element.createElement('span', { className: 'wpaw-cost-compact' },
            '$' + cost.session.toFixed(2)
        ),
        
        // Expand button
        wp.element.createElement('button', {
            className: 'wpaw-expand-btn',
            onClick: () => setIsTextareaExpanded(!isTextareaExpanded)
        }, isTextareaExpanded ? '↓' : '↑')
    );
};
```

### CSS Styling

```css
.wpaw-focus-keyword-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: #1e1e1e;
    border-bottom: 1px solid #3c3c3c;
    font-size: 12px;
}

.wpaw-focus-keyword-wrapper {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 1;
}

.wpaw-focus-keyword-select {
    flex: 1;
    background: #2c2c2c;
    border: 1px solid #3c3c3c;
    color: #fff;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    max-width: 200px;
}

.wpaw-focus-keyword-select:focus {
    border-color: #007cba;
    outline: none;
}

.wpaw-cost-compact {
    color: #a7aaad;
    font-size: 11px;
}

.wpaw-expand-btn {
    background: transparent;
    border: none;
    color: #a7aaad;
    cursor: pointer;
    padding: 4px;
}
```

---

# Part B: Image Generation Fixes

## Error #1: Missing Database Tables

### Symptoms
```
WordPress database error Unknown error 1146 for query 
SELECT * FROM wp_wpaw_images_variants...
```

### Root Cause
Tables are created in activation hook, but plugin was already active when code was added. Activation hook only runs on fresh activation.

### Fix: Add Version-Based Table Creation

**File:** `wp-agentic-writer.php`

```php
// Add after plugin initialization
add_action('plugins_loaded', 'wp_agentic_writer_maybe_create_tables');

function wp_agentic_writer_maybe_create_tables() {
    $current_version = get_option('wpaw_db_version', '0');
    $required_version = '1.1.0'; // Bump when adding new tables
    
    if (version_compare($current_version, $required_version, '<')) {
        // Create cost tracking table
        wp_agentic_writer_create_cost_table();
        
        // Create image management tables
        WP_Agentic_Writer_Image_Manager::get_instance()->create_tables();
        
        // Update version
        update_option('wpaw_db_version', $required_version);
    }
}
```

### Immediate Fix (Manual)

Run this SQL in phpMyAdmin or WP-CLI:

```sql
-- Table 1: wp_wpaw_images
CREATE TABLE IF NOT EXISTS `wp_wpaw_images` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `post_id` bigint(20) NOT NULL,
    `agent_image_id` varchar(50) NOT NULL,
    `placement` varchar(50) NOT NULL,
    `section_title` text,
    `prompt_initial` text,
    `prompt_refined` text,
    `alt_text_initial` text,
    `alt_text_refined` text,
    `image_model` varchar(100),
    `status` varchar(20) DEFAULT 'pending',
    `selected_variant_id` bigint(20) DEFAULT NULL,
    `attachment_id` bigint(20) DEFAULT NULL,
    `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
    `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_agent_image_id` (`agent_image_id`),
    KEY `idx_post_id` (`post_id`),
    KEY `idx_status` (`status`),
    KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Table 2: wp_wpaw_images_variants
CREATE TABLE IF NOT EXISTS `wp_wpaw_images_variants` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `agentic_image_id` bigint(20) NOT NULL,
    `post_id` bigint(20) NOT NULL,
    `variant_number` tinyint(3) NOT NULL,
    `prompt_used` text,
    `temp_url` text,
    `temp_path` text,
    `generation_model` varchar(100),
    `generation_cost` decimal(10,6) DEFAULT 0,
    `width` int(11) DEFAULT NULL,
    `height` int(11) DEFAULT NULL,
    `status` varchar(20) DEFAULT 'temp',
    `attachment_id` bigint(20) DEFAULT NULL,
    `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_agentic_image_id` (`agentic_image_id`),
    KEY `idx_post_id` (`post_id`),
    KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

---

## Error #2: Undefined Method `save_image_recommendation()`

### Symptoms
```
PHP Fatal error: Call to undefined method 
WP_Agentic_Writer_Image_Manager::save_image_recommendation()
```

### Root Cause

**Caller (class-gutenberg-sidebar.php:2185):**
```php
$image_manager->save_image_recommendation(
    $post_id,
    $agent_image_id,
    'section_' . $section_id,
    $heading,
    trim( $description ),
    trim( $description )
);
```

**Actual Method (class-image-manager.php:320):**
```php
private function save_image_recommendations( $post_id, $images ) {
    // Takes array of images, not individual params
}
```

### Issues:
1. Method name is plural (`save_image_recommendations`) vs singular (`save_image_recommendation`)
2. Method is `private`, not `public`
3. Method signature is different (expects array of images)

### Fix: Add Public Method with Correct Signature

**File:** `includes/class-image-manager.php`

Add after line 340:

```php
/**
 * Save single image recommendation to database.
 *
 * @param int    $post_id       Post ID.
 * @param string $agent_image_id Unique image identifier.
 * @param string $placement     Placement location.
 * @param string $section_title Section title.
 * @param string $prompt        Image prompt/description.
 * @param string $alt_text      Alt text for image.
 * @return int|false Insert ID or false on failure.
 */
public function save_image_recommendation( $post_id, $agent_image_id, $placement, $section_title, $prompt, $alt_text ) {
    global $wpdb;
    $table = $wpdb->prefix . 'wpaw_images';

    $settings = get_option( 'wp_agentic_writer_settings', array() );
    $image_model = $settings['image_model'] ?? 'openai/gpt-4o';

    $result = $wpdb->insert(
        $table,
        array(
            'post_id'          => $post_id,
            'agent_image_id'   => $agent_image_id,
            'placement'        => $placement,
            'section_title'    => $section_title,
            'prompt_initial'   => $prompt,
            'alt_text_initial' => $alt_text,
            'image_model'      => $image_model,
            'status'           => 'pending',
        ),
        array( '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
    );

    if ( $result ) {
        return $wpdb->insert_id;
    }

    return false;
}
```

---

## Error #3: Image Block Toolbar Not Showing

### Symptoms
No "Generate AI Image" button appears in image block toolbar.

### Potential Causes

1. **Script not loaded**: Check browser console for errors
2. **Block attribute missing**: `data-agent-image-id` not set on blocks
3. **Filter not applied**: WordPress filter may not be working

### Debug Steps

**Step 1: Check if script is loaded**
```javascript
// In browser console
console.log(typeof wp.hooks.applyFilters);
console.log(wp.hooks.hasFilter('editor.BlockEdit', 'wp-agentic-writer/image-generate-toolbar'));
```

**Step 2: Check if blocks have the attribute**
```javascript
// In browser console
wp.data.select('core/block-editor').getBlocks().forEach(block => {
    if (block.name === 'core/image') {
        console.log('Image block:', block.clientId, block.attributes);
    }
});
```

**Step 3: Verify attribute exists**

The issue may be that `data-agent-image-id` is stored in block HTML but not in attributes.

### Fix: Update Block Detection Logic

**File:** `assets/js/block-image-generate.js`

The current code checks `block.attributes['data-agent-image-id']`, but the attribute might be stored differently.

```javascript
// Current (may not work)
const agentImageId = block?.attributes?.['data-agent-image-id'];

// Fix: Also check innerHTML for the attribute
const hasAgentImageId = () => {
    if (block?.attributes?.['data-agent-image-id']) return true;
    
    // Check innerHTML
    const innerHTML = block?.attributes?.innerHTML || '';
    if (innerHTML.includes('data-agent-image-id')) return true;
    
    // Check original HTML
    const originalContent = block?.originalContent || '';
    if (originalContent.includes('data-agent-image-id')) return true;
    
    return false;
};

if (!hasAgentImageId()) {
    return wp.element.createElement(BlockEdit, props);
}
```

### Alternative Fix: Check for Placeholder Images

If the image block has no `url` but has `alt` text, it's likely a placeholder:

```javascript
const isPlaceholder = block?.name === 'core/image' && 
    !block?.attributes?.url && 
    block?.attributes?.alt;

if (!agentImageId && !isPlaceholder) {
    return wp.element.createElement(BlockEdit, props);
}
```

---

# Part C: UI Redesign

## Current Context Indicator Bar

**Location:** `assets/js/sidebar.js` - `renderContextIndicator()`

```
┌─────────────────────────────────────────────────────────────┐
│ [💬 1 messages]  [💰 $0.0221]  [~500 tokens]  .....  [↕]   │
└─────────────────────────────────────────────────────────────┘
```

## New Focus Keyword Bar Design

### Compact Mode (Default)

```
┌─────────────────────────────────────────────────────────────┐
│ 🎯 [Switch Career Usia 30+ ▼]     [$0.02]           [↕]    │
└─────────────────────────────────────────────────────────────┘
     │                                │                 │
     └─ Dropdown with suggestions     └─ Session cost   └─ Expand
```

### Expanded Mode (When textarea expanded)

```
┌─────────────────────────────────────────────────────────────┐
│ 🎯 FOCUS KEYWORD                                            │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Switch Career Usia 30+                              [▼] │ │
│ └─────────────────────────────────────────────────────────┘ │
│                                                             │
│ 📝 AI Suggestions:                                          │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ● Switch Career Usia 30+ (Response #1)                  │ │
│ │ ○ Vibe Coding untuk Pemula (Response #2)                │ │
│ │ ○ AI Web Design Tanpa Coding (Response #3)              │ │
│ │ ○ + Enter custom keyword...                             │ │
│ └─────────────────────────────────────────────────────────┘ │
│                                                             │
│ 💰 $0.02 this session  │  📊 ~500 tokens                    │
└─────────────────────────────────────────────────────────────┘
```

## Code Changes Required

### Replace `renderContextIndicator()` with `renderFocusKeywordBar()`

**File:** `assets/js/sidebar.js`

```javascript
// REMOVE this function
const renderContextIndicator = () => { ... }

// ADD this function
const renderFocusKeywordBar = () => {
    const hasKeyword = selectedFocusKeyword && selectedFocusKeyword.length > 0;
    
    if (isTextareaExpanded) {
        return renderExpandedFocusKeywordBar();
    }
    
    // Compact mode
    return wp.element.createElement('div', { 
        className: 'wpaw-focus-keyword-bar wpaw-compact'
    },
        wp.element.createElement('div', { className: 'wpaw-fk-left' },
            wp.element.createElement('span', { className: 'wpaw-fk-icon' }, '🎯'),
            wp.element.createElement('select', {
                className: 'wpaw-fk-select',
                value: selectedFocusKeyword || '',
                onChange: handleKeywordSelect,
                disabled: isLoading
            },
                wp.element.createElement('option', { value: '' }, 
                    hasKeyword ? 'Change keyword...' : 'Select focus keyword...'
                ),
                ...focusKeywordSuggestions.map((kw, i) => 
                    wp.element.createElement('option', { key: i, value: kw }, 
                        kw.length > 25 ? kw.substring(0, 25) + '...' : kw
                    )
                ),
                wp.element.createElement('option', { value: '__custom__' }, '+ Custom...')
            )
        ),
        wp.element.createElement('span', { className: 'wpaw-fk-cost' },
            '$' + (cost.session || 0).toFixed(2)
        ),
        wp.element.createElement('button', {
            className: 'wpaw-fk-expand',
            onClick: () => setIsTextareaExpanded(true),
            title: 'Expand'
        }, '↕')
    );
};

const renderExpandedFocusKeywordBar = () => {
    return wp.element.createElement('div', { 
        className: 'wpaw-focus-keyword-bar wpaw-expanded'
    },
        // Header
        wp.element.createElement('div', { className: 'wpaw-fk-header' },
            wp.element.createElement('span', null, '🎯 FOCUS KEYWORD'),
            wp.element.createElement('button', {
                className: 'wpaw-fk-collapse',
                onClick: () => setIsTextareaExpanded(false)
            }, '↓')
        ),
        
        // Main input
        wp.element.createElement('div', { className: 'wpaw-fk-main-input' },
            showCustomKeywordInput 
                ? wp.element.createElement('input', {
                    type: 'text',
                    className: 'wpaw-fk-custom-input',
                    placeholder: 'Enter custom focus keyword...',
                    value: customKeywordInput,
                    onChange: (e) => setCustomKeywordInput(e.target.value),
                    onKeyDown: (e) => {
                        if (e.key === 'Enter') {
                            handleFocusKeywordChange(customKeywordInput);
                            setShowCustomKeywordInput(false);
                        }
                    },
                    autoFocus: true
                })
                : wp.element.createElement('select', {
                    className: 'wpaw-fk-select-full',
                    value: selectedFocusKeyword || '',
                    onChange: handleKeywordSelect
                },
                    wp.element.createElement('option', { value: '' }, 'Select focus keyword...'),
                    ...focusKeywordSuggestions.map((kw, i) => 
                        wp.element.createElement('option', { key: i, value: kw }, kw)
                    ),
                    wp.element.createElement('option', { value: '__custom__' }, '+ Enter custom keyword...')
                )
        ),
        
        // Suggestions list
        focusKeywordSuggestions.length > 0 && wp.element.createElement('div', { 
            className: 'wpaw-fk-suggestions' 
        },
            wp.element.createElement('div', { className: 'wpaw-fk-suggestions-label' }, 
                '📝 AI Suggestions:'
            ),
            focusKeywordSuggestions.map((kw, i) => 
                wp.element.createElement('div', {
                    key: i,
                    className: 'wpaw-fk-suggestion-item' + (kw === selectedFocusKeyword ? ' selected' : ''),
                    onClick: () => handleFocusKeywordChange(kw)
                },
                    wp.element.createElement('span', { className: 'wpaw-fk-radio' }, 
                        kw === selectedFocusKeyword ? '●' : '○'
                    ),
                    wp.element.createElement('span', { className: 'wpaw-fk-suggestion-text' }, kw),
                    wp.element.createElement('span', { className: 'wpaw-fk-suggestion-source' }, 
                        '(Response #' + (i + 1) + ')'
                    )
                )
            )
        ),
        
        // Stats
        wp.element.createElement('div', { className: 'wpaw-fk-stats' },
            wp.element.createElement('span', null, '💰 $' + (cost.session || 0).toFixed(2) + ' this session'),
            wp.element.createElement('span', null, '│'),
            wp.element.createElement('span', null, '📊 ~' + (messages.length * 500) + ' tokens')
        )
    );
};
```

---

# Implementation Priority

## Phase 1: Critical Fixes (Day 1)

| Task | File | Priority |
|------|------|----------|
| Create missing database tables | Manual SQL or add version check | CRITICAL |
| Add `save_image_recommendation()` method | `class-image-manager.php` | CRITICAL |
| Fix image toolbar block detection | `block-image-generate.js` | HIGH |

## Phase 2: Focus Keyword System (Day 2-3)

| Task | File | Priority |
|------|------|----------|
| Add state variables for focus keyword | `sidebar.js` | HIGH |
| Implement keyword extraction from responses | `sidebar.js` | HIGH |
| Create `renderFocusKeywordBar()` | `sidebar.js` | HIGH |
| Add CSS styling | `sidebar.css` | MEDIUM |
| Update backend to use focus_keyword | `class-gutenberg-sidebar.php` | HIGH |

## Phase 3: Integration & Testing (Day 4)

| Task | Priority |
|------|----------|
| Test image generation flow end-to-end | HIGH |
| Test focus keyword persistence across modes | HIGH |
| Test context continuity with focus keyword | HIGH |
| Verify outline generation uses focus keyword | HIGH |

---

# Files to Modify Summary

| File | Changes |
|------|---------|
| `wp-agentic-writer.php` | Add `plugins_loaded` hook for table creation |
| `includes/class-image-manager.php` | Add public `save_image_recommendation()` method |
| `assets/js/block-image-generate.js` | Fix block detection logic |
| `assets/js/sidebar.js` | Replace context indicator with focus keyword bar |
| `assets/css/sidebar.css` | Add focus keyword bar styles |
| `includes/class-gutenberg-sidebar.php` | Update prompts to use focus_keyword |

---

# Testing Checklist

## Image Generation
- [ ] Tables exist in database
- [ ] No PHP errors on article generation
- [ ] Image toolbar button appears on placeholder images
- [ ] Modal opens when clicking toolbar button
- [ ] Image generation works end-to-end

## Focus Keyword System
- [ ] Focus keyword bar appears above chat input
- [ ] Suggestions accumulate after each AI response
- [ ] Selecting keyword updates postConfig
- [ ] Custom keyword input works
- [ ] Expanded view shows all suggestions
- [ ] Keyword persists after page refresh
- [ ] Outline generation uses selected keyword
- [ ] Context maintained across chat → planning → writing modes

---

**Document Status:** Ready for Implementation  
**Last Updated:** January 30, 2026
