# 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