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.
106 KiB
User
audit @MIGRATION_MAP.md by comparing what's built in @sidebar.js
Assistant
The user wants me to audit the MIGRATION_MAP.md file by comparing it against what's actually built in sidebar.js. Let me analyze the migration map and check what's implemented vs. what remains to be done.Looking at the MIGRATION_MAP.md and the sidebar.js file outline, I need to compare:
- State Inventory & Context Architecture - Check if all the states mentioned are actually present in sidebar.js
- UI Component Mapping - Check if all render functions exist in sidebar.js
- Business Logic & Feature Epics - Check if all the functions exist
Let me go through systematically:
1. State Inventory Check
From MIGRATION_MAP.md, the contexts mentioned:
SessionContext:
currentSessionId,availableSessions,isSessionActionLoading✓ (L145-147)sessionLock,lockHeartbeatRef,tabIdRef✓ (L152-160)isHydratingSessionRef,memantoRestore,memantoRestoreFetchedRef✓ (L347, L359, L366)
ChatContext:
-
messages,isLoading,showWelcome,welcomeKeywordInput,welcomeStartMode✓ (L142, L144, L350-352) -
input,isTextareaExpanded,inputRef,streamTargetRef✓ (L143, L334-336) -
messagesSaveTimeoutRef,lastPersistedMessagesRef✓ (L345-346) -
inClarification,questions,currentQuestionIndex,answers,detectedLanguage,clarificationMode✓ (L246-251) -
focusKeywordSuggestions,selectedFocusKeyword,showCustomKeywordInput,customKeywordInput✓ (L339-344) -
AgentContext:
agentModeand related execution state includingactiveOperation,executionStopped, and various abort/ref controllers are present (L149, L256-278). The writing state tracking for section indexing and resume tokens is also there (L280-286).
EditorContext: Editor locking state is managed with isEditorLocked and isRefinementLocked controlling access (L224-226), while lockedEditableNodesRef and lockedBlockIdsRef track which elements are protected (L227-229). Undo functionality is preserved through aiUndoStack and a maximum stack size constant (L355-356).
SettingsContext: Configuration management includes postConfig with loading and saving states, plus error handling (L182-185). Cost tracking covers session usage, daily totals, and monthly consumption (L191-194). Budget limits and provider details are also stored (L196, L201).
SEO audit capabilities: The SEO audit state tracks whether auditing is active, whether meta descriptions are being generated, and which fix is currently highlighted (L240-243).
Mention and slash command autocomplete: Both systems have their respective visibility flags, query tracking, available options, and cursor positions all defined (L324-333).
Textarea expansion state: A dedicated flag tracks whether the input area is expanded (L334).
2. UI Component Mapping Check
The main sidebar component renders across multiple tabs: the primary agentic writer interface (L38), a global status bar showing navigation (L11449-11583), and the chat tab (L11586-12018). Within the chat interface, there are separate rendering functions for the welcome screen (L9005-9182), an empty state for writing (L9185-9251), and a focus keyword bar (L9254-9458).
The messaging system includes message rendering (L9997-10913) with individual message content handling (L10271-10276), plus specialized cards for agent workspace display (L9460-9627), context indicators, and contextual actions (L9633-9994). A clarification modal is also part of the interface (L8477-8828).
For the configuration tab, there's a dedicated renderer (L10918-11433), and the cost tracking tab has its own rendering function (L12058-12304). A confirmation modal for refinement actions is present (L6389-6453).
Several core utilities support the system: the markdown rendering pipeline (L10015-10270) with inline conversion capability, a focus keyword extraction method (L1090-1141), mention and slash command options (L6456-6578 and L2713-2747), and the slash command parser (L2690-2712). Mentions are handled through extraction (L2537-2550), normalization (L2527-2536), and insertion logic (L6702-6726).
For editing operations, the system manages title refinement (L2570-2689) and block insertions (L278-2870), with block identification via index lookup (L2748-2758). Refinement is controlled through confirmation dialogs (L729-742, L743-750) and applied through dedicated handlers (L5800-6388). The block editor integrates with WordPress's dispatch system (L843) and handles editor state management, including input blocking (L927-943) and keyboard events (L945-960).
Mutation tracking through events (L962-968) and undo snapshots (L753-774) preserve state history. Configuration is built from user answers (L1032-1069) and updated through dedicated functions (L1029-1031). Writing operations maintain state through normalization (L500-509) and persistence (L511-538), with partial updates supported (L540-549).
AI content generation includes comprehensive detection for various patterns—AI-generated text identification (L5534-5537, L5538-5589), generic heading detection (L5640-5641), and slang recognition (L5521-5533). The system tracks refinement candidates through diagnosis (L5623-5703) and identifies affected blocks via mentions (L5705-5797). Block operations span retrieval (L5363-5390), list item extraction (L5391-5420), heading context (L5454-5470), and paragraph analysis (L5471-5494).
AI slop detection involves systematic checks across multiple text patterns with defined rules, then selecting blocks most likely containing such content. The workspace captures editor state through snapshot building (L1600-1637) and updates via subscription (L1640-1648), while plan execution manages section tracking (L3697-3709) and status updates (L3829-3851).
Insertion logic determines appropriate placement by analyzing the document structure and finding matching sections, while completion checks evaluate whether all tasks have finished. Plans themselves are dynamically created from structured input, previewed with contextual details, and normalized to ensure consistency. The system maintains plan messages with version tracking, supports revision workflows, and can execute plans either fully or partially with resume capabilities. Block serialization handles transformation between internal and external representations.
Block operations encompass creation from plan definitions, formatting adjustments, transformation from serialized data, and intelligent reformatting. The editor supports undo functionality to reverse AI-generated changes, with snapshot capture and restoration mechanisms. Session management tracks active writing sessions through state persistence and hydration.
Chat operations include message persistence to storage, history loading with backward compatibility, session enumeration with filtering by status, and session switching by identifier. Contextual actions are rendered based on active plan state. The system implements multi-tab synchronization through session locking mechanisms that acquire and release access controls, with background heartbeat monitoring to maintain lock validity across tab lifecycle events.
When a tab loses focus or closes, the lock is released and ownership transfers to the next active session. Session data is cleaned up through sanitization before persistence, with snapshot restoration handled during initialization. Unload handlers ensure all pending changes are flushed before the page exits. Keyword extraction pulls suggestions from conversation context, with dedicated handlers managing both general focus keywords and specific keyword selections.
The system supports welcome flow initiation, SEO analysis with targeted fixes, and metadata generation. Intent detection classifies user queries, while optimized context builds summaries with cost tracking. Additional utilities handle plan runtime summaries, agent intent classification, decision-making logic, chat history summarization, and intent-based detection with full conversation analysis.
The audit reveals several gaps between the migration map and current implementation. The RefineConfirmModal and MentionAutocomplete components lack proper rendering—they're referenced in the mapping but missing actual UI functions. Session management shows a critical gap: memantoRestore is defined but its loading function isn't visible in the outline, and loadPostSessions exists without corresponding save functionality. The local-wp skill appears disconnected from the migration plan entirely. I should verify these findings by examining the actual sidebar.js file more closely to confirm whether these components are genuinely absent or simply not visible in the current view.
I'll audit the MIGRATION_MAP.md against the actual sidebar.js implementation to identify gaps, inconsistencies, and completeness. Let me do a systematic comparison.
Tool Call: Search files for regex renderRefineAllConfirmModal|renderMentionAutocomplete|showMentionAutocomplete|showSlashAutocomplete
Status: Completed
Found 12 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const showMentionAutocomplete › L322-326
// Mention autocomplete state
const [showMentionAutocomplete, setShowMentionAutocomplete] =
React.useState(false);
const [mentionQuery, setMentionQuery] = React.useState("");
const AgenticWriterSidebar › const showSlashAutocomplete › L327-331
const [mentionOptions, setMentionOptions] = React.useState([]);
const [mentionCursorIndex, setMentionCursorIndex] = React.useState(0);
const [showSlashAutocomplete, setShowSlashAutocomplete] =
React.useState(false);
const [slashQuery, setSlashQuery] = React.useState("");
const AgenticWriterSidebar › const renderRefineAllConfirmModal › L6387-6391
}
};
const renderRefineAllConfirmModal = () => {
if (!refineAllConfirm.isOpen) {
return null;
const AgenticWriterSidebar › const handleInsertMention › L6593-6603
setTimeout(() => {
const inputNode = inputRef.current?.textarea || inputRef.current;
if (inputNode) {
inputNode.focus();
inputNode.selectionStart = inputNode.selectionEnd =
inputNode.value.length;
}
const mentionOptionsList = getMentionOptions("");
setMentionOptions(mentionOptionsList);
setShowMentionAutocomplete(mentionOptionsList.length > 0);
1 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const handleInputChange › L6626-6649
if (mentionMatch) {
const query = mentionMatch[1];
setMentionQuery(query);
const options = getMentionOptions(query);
setMentionOptions(options);
setShowMentionAutocomplete(options.length > 0);
setMentionCursorIndex(0);
setShowSlashAutocomplete(false);
setSlashOptions([]);
} else if (slashMatch) {
const query = slashMatch[1];
setSlashQuery(query);
const options = getSlashOptions(query);
setSlashOptions(options);
setShowSlashAutocomplete(options.length > 0);
setSlashCursorIndex(0);
setShowMentionAutocomplete(false);
setMentionOptions([]);
} else {
setShowMentionAutocomplete(false);
setMentionOptions([]);
setShowSlashAutocomplete(false);
setSlashOptions([]);
}
const AgenticWriterSidebar › const handleKeyDown › L6653-6691
const handleKeyDown = (e) => {
if (!showMentionAutocomplete && !showSlashAutocomplete) {
if (e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
sendMessage();
}
return;
}
if (showMentionAutocomplete && e.keyCode === 40) {
// Down arrow
e.preventDefault();
setMentionCursorIndex((prev) => (prev + 1) % mentionOptions.length);
} else if (showMentionAutocomplete && e.keyCode === 38) {
// Up arrow
e.preventDefault();
setMentionCursorIndex(
(prev) => (prev - 1 + mentionOptions.length) % mentionOptions.length,
);
} else if (showMentionAutocomplete && e.keyCode === 13) {
// Enter
e.preventDefault();
if (mentionOptions[mentionCursorIndex]) {
insertMention(mentionOptions[mentionCursorIndex]);
}
} else if (showSlashAutocomplete && e.keyCode === 40) {
// Down arrow
e.preventDefault();
setSlashCursorIndex((prev) => (prev + 1) % slashOptions.length);
} else if (showSlashAutocomplete && e.keyCode === 38) {
// Up arrow
e.preventDefault();
setSlashCursorIndex(
(prev) => (prev - 1 + slashOptions.length) % slashOptions.length,
);
} else if (showSlashAutocomplete && e.keyCode === 13) {
// Enter
e.preventDefault();
if (slashOptions[slashCursorIndex]) {
insertSlashCommand(slashOptions[slashCursorIndex]);
8 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const handleKeyDown › L6693-6698
} else if (e.keyCode === 27) {
// Escape
e.preventDefault();
setShowMentionAutocomplete(false);
setShowSlashAutocomplete(false);
}
const AgenticWriterSidebar › const insertMention › L6715-6719
setInput(newValue);
setShowMentionAutocomplete(false);
setMentionOptions([]);
const AgenticWriterSidebar › const insertSlashCommand › L6740-6750
setInput(newValue);
setShowSlashAutocomplete(false);
setSlashOptions([]);
if (option.insertText.endsWith("@")) {
const mentionOptionsList = getMentionOptions("");
setMentionQuery("");
setMentionOptions(mentionOptionsList);
setShowMentionAutocomplete(mentionOptionsList.length > 0);
setMentionCursorIndex(0);
}
const AgenticWriterSidebar › const renderChatTab › L11751-11755
}),
),
showMentionAutocomplete &&
mentionOptions.length > 0 &&
wp.element.createElement(
const AgenticWriterSidebar › const renderChatTab › L11812-11816
}),
),
showSlashAutocomplete &&
slashOptions.length > 0 &&
wp.element.createElement(
const AgenticWriterSidebar › const renderChatTab › L12012-12016
),
),
renderRefineAllConfirmModal(),
),
),
Tool Call: Search files for regex loadPostSessions|persistSessionMessages|savePostSessions
Status: Completed
Found 8 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const persistSessionMessages › L1780-1784
);
const persistSessionMessages = React.useCallback(
async (
forceSessionId = null,
const AgenticWriterSidebar › L2053-2063
messagesSaveTimeoutRef.current = setTimeout(() => {
// Re-check at fire time: skip if hydrating or messages are now empty
if (isHydratingSessionRef.current) {
return;
}
const latestMsgs =
messagesRef && messagesRef.current ? messagesRef.current : [];
if (latestMsgs.length === 0) {
return;
}
persistSessionMessages(currentSessionId, false, latestMsgs);
1 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › L2069-2073
}
};
}, [currentSessionId, messages, persistSessionMessages]);
React.useEffect(() => {
const AgenticWriterSidebar › const loadChatHistory › const sessions › L2080-2090
try {
const headers = {
"X-WP-Nonce": wpAgenticWriter.nonce,
};
let historyMessages = [];
let resolvedSessionId = "";
// Primary source: merged sessions list (post sessions + unassigned sessions).
const sessions = await loadPostSessions();
if (sessions.length > 0) {
if (sessions.length > 0) {
114 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const loadPostSessions › L2299-2303
}, [postId, memantoRestore.restored, isConfigLoading]);
const loadPostSessions = async () => {
const headers = {
"X-WP-Nonce": wpAgenticWriter.nonce,
const AgenticWriterSidebar › const clearChatContext › L4824-4828
setCurrentSessionId(data.session_id);
}
await loadPostSessions();
setMessages([]);
setInClarification(false);
const AgenticWriterSidebar › const startNewConversation › L8878-8884
systemMessage: "",
});
// loadPostSessions filters out sessions with 0 messages, so the brand-new
// session won't appear. Load existing sessions, then prepend the new one.
const existingSessions = await loadPostSessions();
if (data?.session_id) {
setAvailableSessions((prev) => {
const AgenticWriterSidebar › const deleteConversationSession › const sessions › L8936-8940
throw new Error("Failed to delete session");
}
const sessions = await loadPostSessions();
if (currentSessionId === sessionId) {
const replacement = sessions[0]?.session_id || "";
Tool Call: Search files for regex loadMemantoRestore|fetchMemantoRestore|memantoRestore.*function
Status: Completed
No matches found
Assistant
So I see that: 1. `renderRefineAllConfirmModal` EXISTS - it's at L6387-6391 2. `showMentionAutocomplete` and `showSlashAutocomplete` are state variables, not separate components - they're rendered inline in `renderChatTab` 3. `loadPostSessions` exists but there's no `savePostSessions` function (but there is `persistSessionMessages`) 4. No `loadMemantoRestore` function foundLet me search for more specific items:
Tool Call: Search files for regex fetchMemanto|memantoRestore.*=
Status: Completed
Found 3 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const memantoRestore › L357-361
// MEMANTO memory restore state
const [memantoRestore, setMemantoRestore] = React.useState({
restored: false,
summary: "",
const AgenticWriterSidebar › const memantoRestoreFetchedRef › L364-368
systemMessage: "",
});
const memantoRestoreFetchedRef = React.useRef(false);
React.useEffect(() => {
if (agentMode === "writing" && !isLoading) {
const AgenticWriterSidebar › L2214-2224
React.useEffect(() => {
if (!postId || memantoRestoreFetchedRef.current) {
return;
}
memantoRestoreFetchedRef.current = true;
fetch(`${wpAgenticWriter.apiUrl}/memanto/restore?post_id=${postId}`, {
headers: { "X-WP-Nonce": wpAgenticWriter.nonce },
})
.then((r) => (r.ok ? r.json() : Promise.reject(r)))
.then((data) => {
15 lines remaining in ancestor node. Read the file to see all.
Tool Call: Search files for regex getMentionOptions|getSlashOptions
Status: Completed
Found 6 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const getSlashOptions › L2711-2715
return null;
};
const getSlashOptions = (query) => {
const options = [
{
const AgenticWriterSidebar › const getMentionOptions › L6454-6458
// Get mention options for autocomplete
const getMentionOptions = (query) => {
const allBlocks = select("core/block-editor").getBlocks();
const selectedBlockId =
const AgenticWriterSidebar › const handleInsertMention › const mentionOptionsList › L6593-6603
setTimeout(() => {
const inputNode = inputRef.current?.textarea || inputRef.current;
if (inputNode) {
inputNode.focus();
inputNode.selectionStart = inputNode.selectionEnd =
inputNode.value.length;
}
const mentionOptionsList = getMentionOptions("");
setMentionOptions(mentionOptionsList);
setShowMentionAutocomplete(mentionOptionsList.length > 0);
1 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › L6608-6612
return () =>
window.removeEventListener("wpaw:insert-mention", handleInsertMention);
}, [getMentionOptions]);
// Handle input change for mention detection
const AgenticWriterSidebar › const handleInputChange › const options › L6626-6644
if (mentionMatch) {
const query = mentionMatch[1];
setMentionQuery(query);
const options = getMentionOptions(query);
setMentionOptions(options);
setShowMentionAutocomplete(options.length > 0);
setMentionCursorIndex(0);
setShowSlashAutocomplete(false);
setSlashOptions([]);
} else if (slashMatch) {
const query = slashMatch[1];
setSlashQuery(query);
const options = getSlashOptions(query);
setSlashOptions(options);
setShowSlashAutocomplete(options.length > 0);
setSlashCursorIndex(0);
setShowMentionAutocomplete(false);
setMentionOptions([]);
} else {
const AgenticWriterSidebar › const insertSlashCommand › const mentionOptionsList › L6744-6750
if (option.insertText.endsWith("@")) {
const mentionOptionsList = getMentionOptions("");
setMentionQuery("");
setMentionOptions(mentionOptionsList);
setShowMentionAutocomplete(mentionOptionsList.length > 0);
setMentionCursorIndex(0);
}
Tool Call: Search files for regex classifyAgentIntent|decideAgentAction
Status: Completed
Found 3 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const classifyAgentIntent › L4220-4224
return plan?.id || plan?.meta?.id || plan?.title || "";
};
const classifyAgentIntent = (message) => {
const text = String(message || "").toLowerCase();
const outlinePattern =
const AgenticWriterSidebar › const decideAgentAction › L4259-4271
return "chat";
};
const decideAgentAction = (message) => {
const intent = classifyAgentIntent(message);
const planSummary = getPlanRuntimeSummary();
const refineableBlocks = getRefineableBlocks();
const hasContent = refineableBlocks.length > 0;
const hasPlan = Boolean(currentPlanRef.current && planSummary.total > 0);
if (intent === "generate_meta") {
return {
action: "generate_meta",
mode: "seo",
const AgenticWriterSidebar › const sendMessage › const agentRoute › L6773-6777
}
const agentRoute = decideAgentAction(userMessage);
const effectiveAgentMode = agentRoute.mode || agentMode || "chat";
if (
Assistant
Good. Now I see: 1. `getMentionOptions` exists at L6454 2. `getSlashOptions` exists at L2711 3. `classifyAgentIntent` and `decideAgentAction` existLet me check a few more things to complete the audit - the slash command parsing, the memanto restore fetching, and check for any inconsistencies between the migration map and actual implementation.
Tool Call: Read file assets/js/sidebar.js (lines 2214-2210)
Status: Completed
2214 React.useEffect(() => {
Tool Call: Search files for regex parseInsertCommand
Status: Completed
Found 2 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const parseInsertCommand › L2688-2692
}
};
const parseInsertCommand = (text) => {
const commands = [
{ mode: "add_below", regex: /^\s*(?:\/)?add below\b[:\-]?\s*/i },
const AgenticWriterSidebar › const sendMessage › const parsedCommand › L6824-6828
}
const parsedCommand = parseInsertCommand(userMessage);
const commandMessage = parsedCommand
? parsedCommand.message
Tool Call: Search files for regex normalizeWritingState|normalizePlanActions
Status: Completed
Found 10 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const normalizeWritingState › L498-502
}, [postId]);
const normalizeWritingState = (state = {}) => ({
status: state.status || "idle",
current_section_index: Number(state.current_section_index || 0),
const AgenticWriterSidebar › const saveWritingState › const nextState › L512-522
async (statePatch) => {
if (!postId) {
return;
}
const nextState = normalizeWritingState(statePatch);
try {
const response = await fetch(
`${wpAgenticWriter.apiUrl}/writing-state/${postId}`,
{
method: "POST",
14 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const persistWritingStatePatch › const next › L542-546
setWritingState((prev) => {
const next = normalizeWritingState({ ...prev, ...patch });
saveWritingState(next);
return next;
});
const AgenticWriterSidebar › L567-569
if (!cancelled) {
setWritingState(normalizeWritingState(data));
}
const AgenticWriterSidebar › const normalizePlanActions › L3533-3537
return wp.blocks.createBlock(blockType, { content: content });
};
const normalizePlanActions = (plan) => {
if (!plan || !plan.actions) {
return [];
const AgenticWriterSidebar › const applyEditPlan › const actions › L5143-5153
const applyEditPlan = (plan) => {
const actions = normalizePlanActions(plan);
if (actions.length === 0) {
setPendingEditPlan(null);
return;
}
const {
replaceBlocks,
insertBlocks,
removeBlocks,
87 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const cancelEditPlan › const planActions › L5251-5253
const planActions = pendingEditPlan
? normalizePlanActions(pendingEditPlan)
: [];
const AgenticWriterSidebar › const handleChatRefinement › const actions › L6049-6059
} else if (data.type === "edit_plan") {
setPendingEditPlan(data.plan);
// Render Diff Blocks in Gutenberg
const { insertBlocks, updateBlockAttributes } =
dispatch("core/block-editor");
const allBlocks = select("core/block-editor").getBlocks();
const existingIds = new Set(allBlocks.map((b) => b.clientId));
const newDiffIds = [];
const actions = normalizePlanActions(data.plan);
113 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const renderMessages › const actions › L10647-10657
if (message.type === "edit_plan") {
const plan = message.plan || pendingEditPlan;
const isPlanActive =
Boolean(pendingEditPlan) && plan === pendingEditPlan;
const actions = normalizePlanActions(plan);
const allBlocks = select("core/block-editor").getBlocks();
const existingIds = new Set(
allBlocks.map((block) => block.clientId),
);
const previewActions = actions.filter((action) => {
if (action.action === "keep") {
110 lines remaining in ancestor node. Read the file to see all.
Matches in wp-agentic-writer/assets/js/src/utils/state.js
function normalizeWritingState( ) › L27-35
export function normalizeWritingState(state = {}) {
return {
sections: state.sections || [],
currentSectionIndex: state.currentSectionIndex ?? 0,
isExecuting: state.isExecuting ?? false,
isStopping: state.isStopping ?? false,
lastSaved: state.lastSaved || null,
};
}
Assistant
Interesting - I see there's a `wp-agentic-writer/assets/js/src/utils/state.js` file that has a `normalizeWritingState` function. This suggests there's a React/src directory. Let me check what files exist in the src directory.Tool Call: List the wp-agentic-writer/assets/js directory's contents
Status: Completed
Folders:
wp-agentic-writer/assets/js/node_modules wp-agentic-writer/assets/js/src wp-agentic-writer/assets/js/vendor
Files:
wp-agentic-writer/assets/js/README.md wp-agentic-writer/assets/js/block-image-generate.js wp-agentic-writer/assets/js/block-refine.js wp-agentic-writer/assets/js/image-modal.js wp-agentic-writer/assets/js/package-lock.json wp-agentic-writer/assets/js/package.json wp-agentic-writer/assets/js/settings-v2-stitch.js wp-agentic-writer/assets/js/sidebar-built.js wp-agentic-writer/assets/js/sidebar-built.js.LICENSE.txt wp-agentic-writer/assets/js/sidebar-test.js wp-agentic-writer/assets/js/sidebar-utils.js wp-agentic-writer/assets/js/sidebar.js wp-agentic-writer/assets/js/webpack.config.js
Tool Call: Search files for regex resolveBlockMentions|extractBlockPreview
Status: Completed
Showing matches 1-20 (there were more matches found; use offset: 20 to see next page):
Matches in wp-agentic-writer/assets/js/sidebar-utils.js
const extractBlockPreview › L182-188
const extractBlockPreview = (block) => {
if (!block) return '';
const content = block.innerHTML || block.content || '';
const text = content.replace(/<[^>]+>/g, '').trim();
return truncateText(text, 100);
};
L203-213
window.WPAWUtils = {
escapeHtml,
normalizeMessageContent,
truncateText,
markdownToHtml,
extractCodeBlocks,
debounce,
parseOutlinePlan,
parseFaqSchema,
extractBlockPreview,
toTextValue,
1 lines remaining in ancestor node. Read the file to see all.
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const extractBlockPreview › L1562-1566
};
const extractBlockPreview = (block) => {
const direct = toTextValue(
block.attributes?.content ||
const AgenticWriterSidebar › const getBlockPreviewById › L1588-1598
const getBlockPreviewById = (clientId) => {
if (!clientId) {
return "";
}
const allBlocks = select("core/block-editor").getBlocks();
const block = allBlocks.find((entry) => entry.clientId === clientId);
if (!block) {
return "";
}
return extractBlockPreview(block);
};
const AgenticWriterSidebar › const buildWorkspaceSnapshot › const selectedPreview › L1617-1619
const selectedPreview = selectedBlock
? extractBlockPreview(selectedBlock)
: "";
const AgenticWriterSidebar › const buildWorkspaceSnapshot › blockCount › const preview › L1623-1629
blockCount: allBlocks.filter((block) => {
const preview = extractBlockPreview(block);
return (
preview ||
(Array.isArray(block.innerBlocks) && block.innerBlocks.length > 0)
);
}).length,
const AgenticWriterSidebar › const resolveTargetBlockId › const resolved › L2760-2765
if (mentionTokens.length > 0) {
const resolved = resolveBlockMentions(mentionTokens);
if (resolved.length > 0) {
return resolved[0];
}
}
const AgenticWriterSidebar › const insertRefinementBlock › const listItemText › L2825-2832
if (initialTargetBlock?.name === "core/list-item") {
const listItemText = extractBlockPreview(initialTargetBlock);
if (listItemText) {
refinementMessage = refinementMessage
? `${refinementMessage}\n\nAdd a short description for: "${listItemText}".`
: `Add a short description for: "${listItemText}".`;
}
}
const AgenticWriterSidebar › const getBlockContentForContext › const content › L5443-5453
const getBlockContentForContext = (blockId) => {
const block = blockId
? select("core/block-editor").getBlock(blockId)
: null;
if (!block) {
return "";
}
const content = extractBlockPreview(block);
return content ? content.trim() : "";
};
const AgenticWriterSidebar › const getHeadingContextForBlock › L5464-5466
if (allBlocks[i].name === "core/heading") {
return extractBlockPreview(allBlocks[i]) || "";
}
const AgenticWriterSidebar › const getNearbyParagraphContext › const preview › L5482-5487
if (allBlocks[i].name === "core/paragraph") {
const preview = extractBlockPreview(allBlocks[i]);
if (preview) {
snippets.push(preview.trim());
}
}
const AgenticWriterSidebar › const getContextFromMentions › const mentionIds › L5495-5503
const getContextFromMentions = (mentionTokens, excludeId) => {
const mentionIds = resolveBlockMentions(mentionTokens).filter(
(id) => id && id !== excludeId,
);
const uniqueIds = [...new Set(mentionIds)];
return uniqueIds
.map((id) => getBlockContentForContext(id))
.filter((content) => content);
};
const AgenticWriterSidebar › const selectLikelySlangBlocks › const matches › const content › L5527-5530
const matches = textBlocks.filter((block) => {
const content = (extractBlockPreview(block) || "").toLowerCase();
return quotedTerms.some((term) => content.includes(term));
});
const AgenticWriterSidebar › const getAiSlopFindingsForBlock › const content › L5538-5548
const getAiSlopFindingsForBlock = (block) => {
const content = (extractBlockPreview(block) || "").trim();
if (!content) {
return [];
}
const findings = [];
const rules = [
{
label: "formulaic contrast phrase",
pattern: /\b(bukan sekadar|not just)\b/i,
41 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const buildRefinementDiagnosis › const content › L5646-5656
blocks.forEach((block) => {
const content = extractBlockPreview({
name: block.name,
attributes: block.attributes || {},
innerBlocks: block.innerBlocks || [],
});
const trimmed = (content || "").trim();
if (!trimmed) {
return;
}
if (
15 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const resolveBlockMentions › L5703-5707
};
const resolveBlockMentions = (mentions) => {
const allBlocks = select("core/block-editor").getBlocks();
const selectedBlockId =
const AgenticWriterSidebar › const handleChatRefinement › const blocksToRefine › L5819-5823
// Resolve to block client IDs
const blocksToRefine = blocksOverride || resolveBlockMentions(mentions);
const hasAllMention = mentions.some(
(token) => normalizeMentionToken(token.replace("@", "")) === "all",
const AgenticWriterSidebar › const getMentionOptions › const content › L6510-6520
allBlocks.forEach((block) => {
if (!block.name || !block.name.startsWith("core/")) {
return;
}
const typeName = block.name.replace("core/", "");
blockCounters[typeName] = (blockCounters[typeName] || 0) + 1;
const blockLabel = `@${typeName}-${blockCounters[typeName]}`;
const content = extractBlockPreview(block);
const contentLower = content.toLowerCase();
55 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const getMentionOptions › const itemContent › L6542-6552
innerItems.forEach((itemBlock, itemIndex) => {
if (itemBlock.name !== "core/list-item") {
return;
}
listItemIndex += 1;
const itemLabel = `@listitem-${listItemIndex}`;
const explicitLabel = `@list-${listBlockIndex}.list-item-${itemIndex}`;
const itemContent = extractBlockPreview(itemBlock);
const itemLower = itemContent.toLowerCase();
if (
21 lines remaining in ancestor node. Read the file to see all.
const AgenticWriterSidebar › const sendMessage › const targetIds › L6869-6871
const targetIds = hasMentions
? resolveBlockMentions(mentionTokens)
: getRefineableBlocks().map((block) => block.clientId);
Tool Call: Search files for regex getAllTextRefineableBlocks|selectLikelySlangBlocks
Status: Completed
Found 4 matches:
Matches in wp-agentic-writer/assets/js/sidebar.js
const AgenticWriterSidebar › const getAllTextRefineableBlocks › L5517-5531
return [...new Set(terms)];
};
const getAllTextRefineableBlocks = () =>
getRefineableBlocks({ textOnly: true });
const selectLikelySlangBlocks = (message) => {
const textBlocks = getAllTextRefineableBlocks();
const quotedTerms = extractQuotedTermsFromMessage(message);
if (!quotedTerms.length) {
return textBlocks;
}
const matches = textBlocks.filter((block) => {
const content = (extractBlockPreview(block) || "").toLowerCase();
return quotedTerms.some((term) => content.includes(term));
});
// Fallback to all text blocks when heuristic finds nothing.
const AgenticWriterSidebar › const selectLikelyAiSlopBlocks › L5590-5593
const selectLikelyAiSlopBlocks = (
message,
candidateBlocks = getAllTextRefineableBlocks(),
) => {
const AgenticWriterSidebar › const resolveBlockMentions › L5749-5754
case "all":
// @all intentionally targets text-based content blocks only.
getAllTextRefineableBlocks().forEach((block) => {
resolved.push(block.clientId);
});
break;
const AgenticWriterSidebar › const handleChatRefinement › const likelyBlocks › L5827-5829
const likelyBlocks = isAiSlopRequest(message)
? selectLikelyAiSlopBlocks(message)
: selectLikelySlangBlocks(message);
Assistant
Good, now I see there's a `src` directory. Let me check what's in there to understand the migration progress.Tool Call: List the wp-agentic-writer/assets/js/src directory's contents
Status: Completed
Folders:
wp-agentic-writer/assets/js/src/components wp-agentic-writer/assets/js/src/context wp-agentic-writer/assets/js/src/hooks wp-agentic-writer/assets/js/src/services wp-agentic-writer/assets/js/src/styles wp-agentic-writer/assets/js/src/utils
Files:
wp-agentic-writer/assets/js/src/index.jsx
Tool Call: Search files for regex .
Status: Completed
Showing matches 1-14 (there were more matches found; use offset: 20 to see next page):
Matches in wp-agentic-writer/assets/js/src/components/ClarificationFlow.jsx
L1-13
/**
* ClarificationFlow Component
*
* Step-by-step clarification wizard for content generation.
*
* @package WP_Agentic_Writer
*/
import { createElement, useState, useCallback } from 'react';
/**
* Default steps for clarification.
*/
L14-19
const DEFAULT_STEPS = [
{ id: 'topic', label: 'Topic', question: 'What is the main topic?' },
{ id: 'audience', label: 'Audience', question: 'Who is your target audience?' },
{ id: 'tone', label: 'Tone', question: 'What tone should the content have?' },
{ id: 'length', label: 'Length', question: 'How long should the content be?' },
];
L21-29
/**
* ClarificationFlow component.
*
* @param {Object} props Component props
* @param {Array} props.steps Custom steps (optional)
* @param {Function} props.onComplete Callback when flow completes
* @param {Function} props.onCancel Callback when flow is cancelled
* @returns {JSX.Element}
*/
L30-154
export function ClarificationFlow({
steps = DEFAULT_STEPS,
onComplete,
onCancel,
}) {
const [currentStep, setCurrentStep] = useState(0);
const [answers, setAnswers] = useState({});
const [isComplete, setIsComplete] = useState(false);
const step = steps[currentStep];
const isLastStep = currentStep === steps.length - 1;
const progress = ((currentStep + 1) / steps.length) * 100;
// Handle answer change
const handleAnswer = useCallback((value) => {
setAnswers((prev) => ({
...prev,
[step.id]: value,
}));
}, [step]);
// Handle next step
const handleNext = useCallback(() => {
if (isLastStep) {
setIsComplete(true);
onComplete?.(answers);
} else {
setCurrentStep((prev) => prev + 1);
}
}, [isLastStep, answers, onComplete]);
// Handle previous step
const handleBack = useCallback(() => {
if (currentStep > 0) {
setCurrentStep((prev) => prev - 1);
}
}, [currentStep]);
// Handle skip
const handleSkip = useCallback(() => {
if (isLastStep) {
onComplete?.(answers);
} else {
setCurrentStep((prev) => prev + 1);
}
}, [isLastStep, answers, onComplete]);
if (isComplete) {
return createElement(
'div',
{ className: 'wpaw-clarification-complete' },
createElement('span', { className: 'wpaw-complete-icon' }, '✅'),
createElement('p', null, 'Ready to generate!')
);
}
return createElement(
'div',
{ className: 'wpaw-clarification-flow' },
// Progress bar
createElement(
'div',
{ className: 'wpaw-progress-bar' },
createElement('div', {
className: 'wpaw-progress-fill',
style: { width: `${progress}%` },
})
),
// Step indicator
createElement(
'div',
{ className: 'wpaw-step-indicator' },
createElement('span', { className: 'wpaw-step-current' }, currentStep + 1),
createElement('span', { className: 'wpaw-step-total' }, ` of ${steps.length}`)
),
// Step content
createElement(
'div',
{ className: 'wpaw-step-content' },
createElement('h3', { className: 'wpaw-step-label' }, step.label),
createElement('p', { className: 'wpaw-step-question' }, step.question),
createElement('input', {
type: 'text',
className: 'wpaw-step-input',
value: answers[step.id] || '',
onChange: (e) => handleAnswer(e.target.value),
placeholder: `Enter ${step.label.toLowerCase()}...`,
autoFocus: true,
})
),
// Navigation
createElement(
'div',
{ className: 'wpaw-step-actions' },
currentStep > 0 &&
createElement(
'button',
{ className: 'wpaw-btn wpaw-btn-secondary', onClick: handleBack },
'Back'
),
createElement(
'button',
{ className: 'wpaw-btn wpaw-btn-ghost', onClick: onCancel },
'Cancel'
),
createElement(
'button',
{ className: 'wpaw-btn wpaw-btn-ghost', onClick: handleSkip },
'Skip'
),
createElement(
'button',
{
className: 'wpaw-btn wpaw-btn-primary',
onClick: handleNext,
disabled: !answers[step.id],
},
isLastStep ? 'Generate' : 'Next'
)
)
);
}
export default ClarificationFlow;
Matches in wp-agentic-writer/assets/js/src/components/ContextualAction.jsx
L1-7
/**
* ContextualAction Component
*
* Contextual AI action buttons based on selected content.
*
* @package WP_Agentic_Writer
*/
L9-11
/**
* Available contextual actions.
*/
L12-58
const ACTIONS = [
{
id: 'refine',
label: 'Refine',
icon: '🔄',
description: 'Improve the selected text',
},
{
id: 'expand',
label: 'Expand',
icon: '📝',
description: 'Add more detail',
},
{
id: 'shorten',
label: 'Shorten',
icon: '✂️',
description: 'Make it more concise',
},
{
id: 'summarize',
label: 'Summarize',
icon: '📋',
description: 'Create a summary',
},
{
id: 'rewrite',
label: 'Rewrite',
icon: '🔁',
description: 'Rewrite completely',
},
{
id: 'translate',
label: 'Translate',
icon: '🌐',
description: 'Translate to another language',
},
];
/**
* ContextualAction component.
*
* @param {Object} props Component props
* @param {string} props.selectedText Currently selected text
* @param {Function} props.onAction Callback when action is selected
* @returns {JSX.Element|null}
*/
L59-91
export function ContextualAction({
selectedText,
onAction,
}) {
const hasSelection = selectedText && selectedText.trim().length > 0;
if (!hasSelection) return null;
return createElement(
'div',
{ className: 'wpaw-contextual-action' },
createElement(
'div',
{ className: 'wpaw-action-bar' },
ACTIONS.map((action) =>
createElement(
'button',
{
key: action.id,
className: 'wpaw-action-btn',
onClick: () => onAction?.(action.id),
title: action.description,
},
createElement('span', { className: 'wpaw-action-icon' }, action.icon),
createElement('span', { className: 'wpaw-action-label' }, action.label)
)
)
)
);
}
export default ContextualAction;
Matches in wp-agentic-writer/assets/js/src/components/Messages.jsx
L1-11
/**
* Messages Component
*
* Message list and rendering for the WP Agentic Writer sidebar.
*
* @package WP_Agentic_Writer
*/
import { createElement } from "react";
import { MarkdownRenderer } from "./chat/MarkdownRenderer";
import { formatTimestamp } from "../utils/formatting";
L13-21
/**
* Messages component.
*
* @param {Object} props Component props
* @param {Array} props.messages Message array
* @param {boolean} props.isLoading Loading state
* @param {Function} props.onMessageClick Message click callback
* @returns {JSX.Element}
*/
L22-88
export function Messages({ messages, isLoading, onMessageClick }) {
return createElement(
"div",
{ className: "wpaw-messages" },
// Message list
createElement(
"div",
{ className: "wpaw-message-list" },
messages.map((message, index) =>
createElement(
"div",
{
key: message.id || index,
className: `wpaw-message wpaw-message-${message.role}`,
onClick: () => onMessageClick?.(message),
},
// Content
createElement(
"div",
{ className: "wpaw-message-content" },
message.role !== "user" &&
createElement(
"div",
{ className: "wpaw-message-header" },
createElement(
"span",
{ className: "wpaw-message-role" },
message.role,
),
message.timestamp &&
createElement(
"span",
{ className: "wpaw-message-time" },
formatTimestamp(message.timestamp),
),
),
createElement(
"div",
{ className: "wpaw-message-body" },
createElement(MarkdownRenderer, { content: message.content }),
),
),
),
),
// Loading indicator
isLoading &&
createElement(
"div",
{ className: "wpaw-message wpaw-message-loading" },
createElement(
"div",
{ className: "wpaw-message-content" },
createElement(
"div",
{ className: "wpaw-typing-indicator" },
createElement("span", null, "●"),
createElement("span", null, "●"),
createElement("span", null, "●"),
),
),
),
),
);
}
export default Messages;
Matches in wp-agentic-writer/assets/js/src/components/RefineAllConfirm.jsx
L1-7
/**
* RefineAllConfirm Component
*
* Confirmation dialog for "Refine All" operations.
*
* @package WP_Agentic_Writer
*/
L9-18
/**
* RefineAllConfirm component.
*
* @param {Object} props Component props
* @param {boolean} props.isOpen Modal open state
* @param {number} props.sectionCount Number of sections to refine
* @param {Function} props.onConfirm Confirm callback
* @param {Function} props.onCancel Cancel callback
* @returns {JSX.Element|null}
*/
L19-78
export function RefineAllConfirm({
isOpen,
sectionCount = 0,
onConfirm,
onCancel,
}) {
if (!isOpen) return null;
return createElement(
'div',
{ className: 'wpaw-modal-overlay', onClick: onCancel },
createElement(
'div',
{
className: 'wpaw-modal wpaw-confirm-modal',
onClick: (e) => e.stopPropagation(),
},
// Header
createElement(
'div',
{ className: 'wpaw-modal-header' },
createElement('span', { className: 'wpaw-confirm-icon' }, '🔄'),
createElement('h2', { className: 'wpaw-modal-title' }, 'Refine All Sections?')
),
// Content
createElement(
'div',
{ className: 'wpaw-modal-content' },
createElement(
'p',
null,
`This will regenerate content for all ${sectionCount} sections in your plan. This action cannot be undone.`
),
createElement(
'p',
{ className: 'wpaw-warning' },
'Your current content will be replaced.'
)
),
// Footer
createElement(
'div',
{ className: 'wpaw-modal-footer' },
createElement(
'button',
{ className: 'wpaw-btn wpaw-btn-secondary', onClick: onCancel },
'Cancel'
),
createElement(
'button',
{ className: 'wpaw-btn wpaw-btn-danger', onClick: onConfirm },
'Refine All'
)
)
)
);
}
export default RefineAllConfirm;
Assistant
Excellent! Now I have a clearer picture. There's a React/src directory with some migrated components. Let me explore the full directory structure and check what components exist in the src/components folder.Tool Call: List the wp-agentic-writer/assets/js/src/components directory's contents
Status: Completed
Folders:
wp-agentic-writer/assets/js/src/components/chat wp-agentic-writer/assets/js/src/components/seo wp-agentic-writer/assets/js/src/components/shared wp-agentic-writer/assets/js/src/components/tabs wp-agentic-writer/assets/js/src/components/writing
Files:
wp-agentic-writer/assets/js/src/components/ClarificationFlow.jsx wp-agentic-writer/assets/js/src/components/ContextualAction.jsx wp-agentic-writer/assets/js/src/components/Messages.jsx wp-agentic-writer/assets/js/src/components/RefineAllConfirm.jsx wp-agentic-writer/assets/js/src/components/RefinementModal.jsx wp-agentic-writer/assets/js/src/components/Sidebar.jsx
Tool Call: List the wp-agentic-writer/assets/js/src/context directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/context/SidebarContext.jsx
Tool Call: List the wp-agentic-writer/assets/js/src/hooks directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/hooks/useCost.js wp-agentic-writer/assets/js/src/hooks/useLock.js wp-agentic-writer/assets/js/src/hooks/usePostConfig.js wp-agentic-writer/assets/js/src/hooks/useSession.js wp-agentic-writer/assets/js/src/hooks/useStream.js wp-agentic-writer/assets/js/src/hooks/useWritingState.js
Assistant
Now let me check the subdirectories and see what's in the Sidebar.jsx and SidebarContext.jsx to understand the migration status.Tool Call: List the wp-agentic-writer/assets/js/src/components/chat directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/components/chat/MarkdownRenderer.jsx wp-agentic-writer/assets/js/src/components/chat/MentionAutocomplete.jsx wp-agentic-writer/assets/js/src/components/chat/MessageInput.jsx wp-agentic-writer/assets/js/src/components/chat/MessageList.jsx wp-agentic-writer/assets/js/src/components/chat/WelcomeScreen.jsx
Tool Call: Read file assets/js/src/components/Sidebar.jsx (lines 1-100)
Status: Completed
1 /**
2 * Sidebar Component
3 *
4 * Main container for the WP Agentic Writer Gutenberg sidebar.
5 *
6 * @package WP_Agentic_Writer
7 */
8
9 import { createElement, useState, useEffect, useCallback, useRef } from "react";
10 import { ChatTab } from "./tabs/ChatTab";
11 import { ConfigTab } from "./tabs/ConfigTab";
12 import { CostTab } from "./tabs/CostTab";
13 import { StatusBar } from "./shared/StatusBar";
14 import { SidebarProvider, useSidebar } from "../context/SidebarContext";
15 import { useSession } from "../hooks/useSession";
16 import { api } from "../services/api";
17 import { formatAiErrorMessage } from "../utils/errors";
18 import { parsePlan } from "../utils/planUtils";
19
20 // No TABS array needed, navigation is in StatusBar
21
22 /**
23 * Inner Sidebar component (uses context).
24 *
25 * @param {Object} props Component props
26 * @param {number} props.postId WordPress post ID
27 * @returns {JSX.Element}
28 */
29 function InnerSidebar({ postId }) {
30 const [activeTab, setActiveTab] = useState("chat");
31
32 // Welcome screen state - starts TRUE (show welcome by default)
33 const [showWelcome, setShowWelcome] = useState(true);
34
35 // Session data
36 const [orphanSessions, setOrphanSessions] = useState([]);
37 const [activeSessions, setActiveSessions] = useState([]);
38
39 const {
40 messages,
41 setMessages,
42 addMessage,
43 updateLastMessage,
44 isLoading,
45 setLoading,
46 isStreaming,
47 setStreaming,
48 error,
49 setError,
50 writingState,
51 setWritingState,
52 } = useSidebar();
53 const { sessionId } = useSession();
54 const streamController = useRef(null);
55
56 // Determine if this is a new/auto-draft post
57 const isNewPost = !postId || postId === 0;
58
59 // Fetch orphan/unassigned sessions for continuation
60 const fetchOrphanSessions = useCallback(async () => {
61 try {
62 const [active, completed] = await Promise.all([
63 api.getSessionsByStatus("active"),
64 api.getSessionsByStatus("completed"),
65 ]);
66
67 // Filter to orphan sessions (no post or auto-draft)
68 const filterOrphans = (sessions) => {
69 return (sessions || []).filter((session) => {
70 const pid = session.postId || session.post_id || 0;
71 const status = session.postStatus || "";
72 return pid === 0 || status === "auto-draft";
73 });
74 };
75
76 setOrphanSessions(filterOrphans(active).concat(filterOrphans(completed)));
77 } catch (err) {
78 console.error("[WPAW] Failed to fetch orphan sessions:", err);
79 setOrphanSessions([]);
80 }
81 }, []);
82
83 // Load existing session history if post has messages
84 const loadExistingSession = useCallback(async () => {
85 if (!postId || isNewPost) return;
86
87 setLoading(true);
88 try {
89 const data = await api.getChatHistory(postId);
90 if (data.messages && data.messages.length > 0) {
91 setMessages(data.messages);
92 setShowWelcome(false); // Hide welcome when loading existing session
93 }
94 if (data.plan) {
95 setWritingState({ plan: parsePlan(data.plan) });
96 }
97 } catch (err) {
98 // Silently fail - post might not have history
99 console.log("[WPAW] No existing session to load");
100 } finally {
Tool Call: Read file assets/js/src/context/SidebarContext.jsx
Status: Completed
1 /**
2 * Sidebar Context
3 *
4 * Shared state provider for the WP Agentic Writer sidebar.
5 *
6 * @package WP_Agentic_Writer
7 */
8
9 import { createContext, useContext, useReducer, useCallback } from 'react';
10
11 // Action types
12 export const ActionTypes = {
13 SET_POST_ID: 'SET_POST_ID',
14 SET_POST_CONFIG: 'SET_POST_CONFIG',
15 UPDATE_POST_CONFIG: 'UPDATE_POST_CONFIG',
16 SET_MESSAGES: 'SET_MESSAGES',
17 ADD_MESSAGE: 'ADD_MESSAGE',
18 UPDATE_LAST_MESSAGE: 'UPDATE_LAST_MESSAGE',
19 SET_LOADING: 'SET_LOADING',
20 SET_ERROR: 'SET_ERROR',
21 SET_STREAMING: 'SET_STREAMING',
22 SET_WRITING_STATE: 'SET_WRITING_STATE',
23 SET_COST_DATA: 'SET_COST_DATA',
24 SET_SESSION_LOCKED: 'SET_SESSION_LOCKED',
25 RESET_STATE: 'RESET_STATE',
26 };
27
28 // Initial state
29 const initialState = {
30 postId: null,
31 postConfig: null,
32 messages: [],
33 isLoading: false,
34 isStreaming: false,
35 error: null,
36 writingState: {
37 sections: [],
38 currentSectionIndex: 0,
39 isExecuting: false,
40 isStopping: false,
41 lastSaved: null,
42 },
43 costData: null,
44 sessionLocked: false,
45 };
46
47 // Reducer
48 function sidebarReducer(state, action) {
49 switch (action.type) {
50 case ActionTypes.SET_POST_ID:
51 return { ...state, postId: action.payload };
52
53 case ActionTypes.SET_POST_CONFIG:
54 return { ...state, postConfig: action.payload };
55
56 case ActionTypes.UPDATE_POST_CONFIG:
57 return {
58 ...state,
59 postConfig: { ...state.postConfig, ...action.payload },
60 };
61
62 case ActionTypes.SET_MESSAGES:
63 return { ...state, messages: action.payload };
64
65 case ActionTypes.ADD_MESSAGE:
66 return { ...state, messages: [...state.messages, action.payload] };
67
68 case ActionTypes.UPDATE_LAST_MESSAGE:
69 return {
70 ...state,
71 messages: state.messages.map((msg, idx) =>
72 idx === state.messages.length - 1
73 ? { ...msg, ...action.payload }
74 : msg
75 ),
76 };
77
78 case ActionTypes.SET_LOADING:
79 return { ...state, isLoading: action.payload };
80
81 case ActionTypes.SET_ERROR:
82 return { ...state, error: action.payload };
83
84 case ActionTypes.SET_STREAMING:
85 return { ...state, isStreaming: action.payload };
86
87 case ActionTypes.SET_WRITING_STATE:
88 return {
89 ...state,
90 writingState: { ...state.writingState, ...action.payload },
91 };
92
93 case ActionTypes.SET_COST_DATA:
94 return { ...state, costData: action.payload };
95
96 case ActionTypes.SET_SESSION_LOCKED:
97 return { ...state, sessionLocked: action.payload };
98
99 case ActionTypes.RESET_STATE:
100 return { ...initialState };
101
102 default:
103 return state;
104 }
105 }
106
107 // Create context
108 const SidebarContext = createContext(null);
109
110 // Provider component
111 export function SidebarProvider({ children }) {
112 const [state, dispatch] = useReducer(sidebarReducer, initialState);
113
114 // Action creators
115 const setPostId = useCallback((postId) => {
116 dispatch({ type: ActionTypes.SET_POST_ID, payload: postId });
117 }, []);
118
119 const setPostConfig = useCallback((config) => {
120 dispatch({ type: ActionTypes.SET_POST_CONFIG, payload: config });
121 }, []);
122
123 const updatePostConfig = useCallback((updates) => {
124 dispatch({ type: ActionTypes.UPDATE_POST_CONFIG, payload: updates });
125 }, []);
126
127 const setMessages = useCallback((messages) => {
128 dispatch({ type: ActionTypes.SET_MESSAGES, payload: messages });
129 }, []);
130
131 const addMessage = useCallback((message) => {
132 dispatch({
133 type: ActionTypes.ADD_MESSAGE,
134 payload: {
135 id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
136 timestamp: Date.now(),
137 ...message,
138 },
139 });
140 }, []);
141
142 const updateLastMessage = useCallback((updates) => {
143 dispatch({ type: ActionTypes.UPDATE_LAST_MESSAGE, payload: updates });
144 }, []);
145
146 const setLoading = useCallback((isLoading) => {
147 dispatch({ type: ActionTypes.SET_LOADING, payload: isLoading });
148 }, []);
149
150 const setError = useCallback((error) => {
151 dispatch({ type: ActionTypes.SET_ERROR, payload: error });
152 }, []);
153
154 const setStreaming = useCallback((isStreaming) => {
155 dispatch({ type: ActionTypes.SET_STREAMING, payload: isStreaming });
156 }, []);
157
158 const setWritingState = useCallback((writingState) => {
159 dispatch({ type: ActionTypes.SET_WRITING_STATE, payload: writingState });
160 }, []);
161
162 const setCostData = useCallback((costData) => {
163 dispatch({ type: ActionTypes.SET_COST_DATA, payload: costData });
164 }, []);
165
166 const setSessionLocked = useCallback((sessionLocked) => {
167 dispatch({ type: ActionTypes.SET_SESSION_LOCKED, payload: sessionLocked });
168 }, []);
169
170 const resetState = useCallback(() => {
171 dispatch({ type: ActionTypes.RESET_STATE });
172 }, []);
173
174 const value = {
175 // State
176 ...state,
177 // Actions
178 setPostId,
179 setPostConfig,
180 updatePostConfig,
181 setMessages,
182 addMessage,
183 updateLastMessage,
184 setLoading,
185 setError,
186 setStreaming,
187 setWritingState,
188 setCostData,
189 setSessionLocked,
190 resetState,
191 };
192
193 return (
194 <SidebarContext.Provider value={value}>
195 {children}
196 </SidebarContext.Provider>
197 );
198 }
199
200 // Hook to use the context
201 export function useSidebar() {
202 const context = useContext(SidebarContext);
203 if (!context) {
204 throw new Error('useSidebar must be used within a SidebarProvider');
205 }
206 return context;
207 }
208
209 export default SidebarContext;
Assistant
Now I have a good picture. Let me check the remaining directories and also verify a few more critical items from the MIGRATION_MAP.Tool Call: List the wp-agentic-writer/assets/js/src/components/tabs directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/components/tabs/ChatTab.jsx wp-agentic-writer/assets/js/src/components/tabs/ConfigTab.jsx wp-agentic-writer/assets/js/src/components/tabs/CostTab.jsx
Tool Call: List the wp-agentic-writer/assets/js/src/components/shared directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/components/shared/StatusBar.jsx
Tool Call: List the wp-agentic-writer/assets/js/src/utils directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/utils/blockUtils.js wp-agentic-writer/assets/js/src/utils/constants.js wp-agentic-writer/assets/js/src/utils/errors.js wp-agentic-writer/assets/js/src/utils/formatting.js wp-agentic-writer/assets/js/src/utils/planUtils.js wp-agentic-writer/assets/js/src/utils/seoUtils.js wp-agentic-writer/assets/js/src/utils/state.js wp-agentic-writer/assets/js/src/utils/streamUtils.js
Tool Call: List the wp-agentic-writer/assets/js/src/services directory's contents
Status: Completed
Files:
wp-agentic-writer/assets/js/src/services/api.js
Assistant
Now let me look at the api.js to see what endpoints are implemented in the React version vs the legacy sidebar.js.Tool Call: Read file assets/js/src/services/api.js
Status: Completed
1 /**
2 * API Client Service
3 *
4 * Centralized REST API calls for the WP Agentic Writer plugin.
5 *
6 * @package WP_Agentic_Writer
7 */
8
9 import { formatAiErrorMessage } from "../utils/errors";
10
11 /**
12 * API client class for making REST requests.
13 */
14 export class ApiClient {
15 constructor() {
16 this.baseUrl =
17 window.wpAgenticWriter?.apiUrl || "/wp-json/wp-agentic-writer/v1";
18 this.sessionId = window.wpAgenticWriter?.sessionId || "";
19 }
20
21 /**
22 * Make a GET request.
23 *
24 * @param {string} endpoint API endpoint
25 * @param {Object} params Query parameters
26 * @returns {Promise<Object>} Response data
27 */
28 async get(endpoint, params = {}) {
29 const url = new URL(`${this.baseUrl}${endpoint}`, window.location.origin);
30 Object.entries(params).forEach(([key, value]) => {
31 if (value !== undefined && value !== null) {
32 url.searchParams.append(key, value);
33 }
34 });
35
36 const response = await fetch(url.toString(), {
37 method: "GET",
38 headers: this.getHeaders(),
39 });
40
41 return this.handleResponse(response);
42 }
43
44 /**
45 * Make a POST request.
46 *
47 * @param {string} endpoint API endpoint
48 * @param {Object} data Request body
49 * @returns {Promise<Object>} Response data
50 */
51 async post(endpoint, data = {}) {
52 const response = await fetch(`${this.baseUrl}${endpoint}`, {
53 method: "POST",
54 headers: this.getHeaders(),
55 body: JSON.stringify(data),
56 });
57
58 return this.handleResponse(response);
59 }
60
61 /**
62 * Make a streaming POST request.
63 *
64 * @param {string} endpoint API endpoint
65 * @param {Object} data Request body
66 * @param {Function} onChunk Callback for each chunk
67 * @param {Function} onComplete Callback when complete
68 * @param {Function} onError Callback on error
69 * @returns {AbortController} Controller for cancellation
70 */
71 streamPost(endpoint, data, onChunk, onComplete, onError) {
72 const controller = new AbortController();
73
74 fetch(`${this.baseUrl}${endpoint}`, {
75 method: "POST",
76 headers: this.getHeaders(),
77 body: JSON.stringify(data),
78 signal: controller.signal,
79 })
80 .then((response) => {
81 if (!response.ok) {
82 throw new Error(`HTTP error: ${response.status}`);
83 }
84 return response.body.getReader();
85 })
86 .then((reader) => {
87 const decoder = new TextDecoder();
88 const processChunk = ({ done, value }) => {
89 if (done) {
90 onComplete?.();
91 return;
92 }
93
94 const chunk = decoder.decode(value, { stream: true });
95 const lines = chunk.split("\n");
96
97 for (const line of lines) {
98 if (line.startsWith("data: ")) {
99 try {
100 const data = JSON.parse(line.slice(6));
101 onChunk?.(data);
102 } catch (e) {
103 // Ignore parse errors for partial JSON
104 }
105 }
106 }
107
108 reader.read().then(processChunk);
109 };
110
111 reader.read().then(processChunk);
112 })
113 .catch((error) => {
114 if (error.name !== "AbortError") {
115 onError?.(error);
116 }
117 });
118
119 return controller;
120 }
121
122 /**
123 * Get headers for API requests.
124 *
125 * @returns {Object} Headers object
126 */
127 getHeaders() {
128 const headers = {
129 "Content-Type": "application/json",
130 "X-WPAW-Session": this.sessionId,
131 };
132 // Include WP REST nonce for authentication.
133 const nonce = window.wpAgenticWriter?.nonce;
134 if (nonce) {
135 headers["X-WP-Nonce"] = nonce;
136 }
137 return headers;
138 }
139
140 /**
141 * Handle API response.
142 *
143 * @param {Response} response Fetch response
144 * @returns {Promise<Object>} Parsed response
145 */
146 async handleResponse(response) {
147 if (!response.ok) {
148 let errorMessage = `HTTP error: ${response.status}`;
149 try {
150 const errorData = await response.json();
151 if (errorData.message) {
152 errorMessage = errorData.message;
153 }
154 } catch (e) {
155 // Use default error message
156 }
157 throw new Error(errorMessage);
158 }
159
160 return response.json();
161 }
162 }
163
164 // Singleton instance
165 let apiClientInstance = null;
166
167 export function getApiClient() {
168 if (!apiClientInstance) {
169 apiClientInstance = new ApiClient();
170 }
171 return apiClientInstance;
172 }
173
174 // Convenience methods
175 export const api = {
176 /**
177 * Fetch post configuration.
178 *
179 * @param {number} postId Post ID
180 * @returns {Promise<Object>} Post config
181 */
182 async getPostConfig(postId) {
183 return getApiClient().get(`/post-config/${postId}`);
184 },
185
186 /**
187 * Save post configuration.
188 *
189 * @param {number} postId Post ID
190 * @param {Object} config Config data
191 * @returns {Promise<Object>} Updated config
192 */
193 async savePostConfig(postId, config) {
194 return getApiClient().post(`/post-config/${postId}`, { config });
195 },
196
197 /**
198 * Fetch chat history.
199 *
200 * @param {number} postId Post ID
201 * @returns {Promise<Object>} Chat history data
202 */
203 async getChatHistory(postId) {
204 return getApiClient().get(`/chat-history/${postId}`);
205 },
206
207 /**
208 * Send chat message.
209 *
210 * @param {Object} params Chat parameters
211 * @param {Function} onChunk Streaming callback
212 * @param {Function} onComplete Complete callback
213 * @param {Function} onError Error callback
214 * @returns {AbortController} Controller
215 */
216 streamChat(params, onChunk, onComplete, onError) {
217 return getApiClient().streamPost(
218 "/chat",
219 params,
220 onChunk,
221 onComplete,
222 onError,
223 );
224 },
225
226 /**
227 * Run SEO audit.
228 *
229 * @param {number} postId Post ID
230 * @returns {Promise<Object>} Audit results
231 */
232 async runSeoAudit(postId) {
233 return getApiClient().get(`/seo-audit/${postId}`);
234 },
235
236 /**
237 * Get cost tracking data.
238 *
239 * @param {number} postId Post ID
240 * @returns {Promise<Object>} Cost data
241 */
242 async getCostTracking(postId) {
243 return getApiClient().get(`/cost-tracking/${postId}`);
244 },
245
246 /**
247 * Fetch AI capabilities.
248 *
249 * @returns {Promise<Object>} Capabilities data
250 */
251 async getCapabilities() {
252 return getApiClient().get("/ai-capabilities");
253 },
254
255 /**
256 * Get orphan sessions (sessions without a post or with auto-draft posts).
257 * Returns empty array if endpoint doesn't exist.
258 *
259 * @returns {Promise<Object>} Orphan sessions data
260 */
261 async getOrphanSessions() {
262 try {
263 return await getApiClient().get("/conversations");
264 } catch (error) {
265 // Return empty if endpoint doesn't exist
266 return { sessions: [] };
267 }
268 },
269
270 /**
271 * Get sessions by status.
272 *
273 * @param {string} status Session status (active, completed, etc.)
274 * @returns {Promise<Array>} Sessions list
275 */
276 async getSessionsByStatus(status) {
277 try {
278 const data = await getApiClient().get("/conversations", { status });
279 return data.sessions || data || [];
280 } catch (error) {
281 return [];
282 }
283 },
284
285 /**
286 * Delete a session by ID.
287 *
288 * @param {string} sessionId Session ID
289 * @returns {Promise<Object>} Success response
290 */
291 async deleteSession(sessionId) {
292 return getApiClient().post(`/conversations/${sessionId}/delete`);
293 },
294 };
Assistant
Now I have a comprehensive picture. Let me also check what files are in the hooks directory and check for any remaining important items.Tool Call: Read file assets/js/src/hooks/useSession.js
Status: Completed
1 /**
2 * useSession Hook
3 *
4 * Session management state for the WP Agentic Writer sidebar.
5 *
6 * @package WP_Agentic_Writer
7 */
8
9 import { useState, useEffect, useCallback } from 'react';
10
11 /**
12 * Get session data from wpAgenticWriter global.
13 */
14 function getSessionData() {
15 if (typeof window.wpAgenticWriter === 'undefined') {
16 return { sessionId: '', apiUrl: '', nonce: '' };
17 }
18 return {
19 sessionId: window.wpAgenticWriter.sessionId || '',
20 apiUrl: window.wpAgenticWriter.apiUrl || '/wp-json/wp-agentic-writer/v1',
21 nonce: window.wpAgenticWriter.nonce || '',
22 debug: window.wpAgenticWriter.debug || false,
23 settings: window.wpAgenticWriter.settings || {},
24 };
25 }
26
27 /**
28 * useSession hook.
29 *
30 * Manages session state including session ID, API URL, and authentication.
31 *
32 * @returns {Object} Session state and actions
33 */
34 export function useSession() {
35 const [sessionId] = useState(() => getSessionData().sessionId);
36 const [apiUrl] = useState(() => getSessionData().apiUrl);
37 const [nonce] = useState(() => getSessionData().nonce);
38 const [debug] = useState(() => getSessionData().debug);
39 const [settings] = useState(() => getSessionData().settings);
40 const [isActive, setIsActive] = useState(true);
41
42 // Log debug message
43 const log = useCallback(
44 (message, data) => {
45 if (debug) {
46 console.log(`[WPAW Session] ${message}`, data || '');
47 }
48 },
49 [debug]
50 );
51
52 // Validate session
53 const validateSession = useCallback(() => {
54 if (!sessionId) {
55 log('No session ID available');
56 return false;
57 }
58 return true;
59 }, [sessionId, log]);
60
61 // Cleanup on unmount
62 useEffect(() => {
63 return () => {
64 setIsActive(false);
65 };
66 }, []);
67
68 return {
69 sessionId,
70 apiUrl,
71 nonce,
72 debug,
73 settings,
74 isActive,
75 validateSession,
76 log,
77 };
78 }
79
80 export default useSession;
Tool Call: Read file assets/js/src/hooks/useStream.js
Status: Completed
1 /**
2 * useStream Hook
3 *
4 * Streaming logic for the WP Agentic Writer sidebar.
5 *
6 * @package WP_Agentic_Writer
7 */
8
9 import { useState, useCallback, useRef } from 'react';
10 import { api } from '../services/api';
11
12 /**
13 * useStream hook.
14 *
15 * Manages streaming state for AI responses.
16 *
17 * @param {Object} options Hook options
18 * @param {Function} options.onChunk Callback for each chunk
19 * @param {Function} options.onComplete Callback when complete
20 * @param {Function} options.onError Callback on error
21 * @returns {Object} Stream state and actions
22 */
23 export function useStream(options = {}) {
24 const { onChunk, onComplete, onError } = options;
25
26 const [isStreaming, setIsStreaming] = useState(false);
27 const [streamContent, setStreamContent] = useState('');
28 const [streamError, setStreamError] = useState(null);
29 const controllerRef = useRef(null);
30
31 // Start streaming
32 const startStream = useCallback(
33 (endpoint, params) => {
34 // Cancel any existing stream
35 if (controllerRef.current) {
36 controllerRef.current.abort();
37 }
38
39 setIsStreaming(true);
40 setStreamContent('');
41 setStreamError(null);
42
43 controllerRef.current = api.streamChat(
44 params,
45 // onChunk
46 (data) => {
47 if (data.content) {
48 setStreamContent((prev) => prev + data.content);
49 onChunk?.(data);
50 }
51 if (data.done) {
52 setIsStreaming(false);
53 onComplete?.(data);
54 }
55 },
56 // onComplete
57 () => {
58 setIsStreaming(false);
59 onComplete?.();
60 },
61 // onError
62 (error) => {
63 setIsStreaming(false);
64 setStreamError(error.message);
65 onError?.(error);
66 }
67 );
68
69 return controllerRef.current;
70 },
71 [onChunk, onComplete, onError]
72 );
73
74 // Stop streaming
75 const stopStream = useCallback(() => {
76 if (controllerRef.current) {
77 controllerRef.current.abort();
78 controllerRef.current = null;
79 }
80 setIsStreaming(false);
81 }, []);
82
83 // Reset state
84 const reset = useCallback(() => {
85 stopStream();
86 setStreamContent('');
87 setStreamError(null);
88 }, [stopStream]);
89
90 return {
91 isStreaming,
92 streamContent,
93 streamError,
94 startStream,
95 stopStream,
96 reset,
97 };
98 }
99
100 export default useStream;
Assistant
Now I have enough information to provide a comprehensive audit report. Let me compile the findings.Now I have a complete picture. Here's my comprehensive audit:
MIGRATION_MAP.md Audit Report
Summary
The migration is ~25-30% complete at the component/structure level. The React architecture is scaffolded, but most of the complex business logic from the 12,347-line sidebar.js monolith has NOT been migrated yet.
✅ What's Correctly Mapped & Implemented
1. State Inventory (Context Architecture)
| MIGRATION_MAP | sidebar.js | React/src |
|---|---|---|
| SessionContext states | ✅ All present | ⚠️ Partial (via useSession.js) |
| ChatContext states | ✅ All present | ⚠️ Partial (via SidebarContext.jsx) |
| AgentContext states | ✅ All present | ❌ Not migrated |
| EditorContext states | ✅ All present | ❌ Not migrated |
| SettingsContext states | ✅ All present | ⚠️ Partial |
| SEO states | ✅ All present | ❌ Not migrated |
2. UI Component Mapping Status
| Component (MIGRATION_MAP) | sidebar.js | React/src |
|---|---|---|
Sidebar.jsx |
AgenticWriterSidebar (L38) |
✅ src/components/Sidebar.jsx |
StatusBar.jsx |
renderGlobalStatusBar (L11449) |
✅ src/components/shared/StatusBar.jsx |
WelcomeScreen.jsx |
renderWelcomeScreen (L9005) |
✅ src/components/chat/WelcomeScreen.jsx |
WritingEmptyState.jsx |
renderWritingEmptyState (L9185) |
❌ Missing |
FocusKeywordBar.jsx |
renderFocusKeywordBar (L9254) |
❌ Missing |
MessageList.jsx |
renderMessages (L9997) |
✅ src/components/chat/MessageList.jsx |
MessageItem.jsx |
renderMessageContent (L10271) |
❌ Inline in MessageList |
AgentWorkspaceCard.jsx |
renderAgentWorkspaceCard (L9460) |
❌ Missing |
ContextIndicator.jsx |
renderContextIndicator (L9630) |
❌ Missing |
ContextualActions.jsx |
renderContextualAction (L9633) |
⚠️ src/components/ContextualAction.jsx (simplified stub) |
ClarificationOverlay.jsx |
renderClarification (L8477) |
⚠️ src/components/ClarificationFlow.jsx (simplified) |
ChatInput.jsx |
Inline in renderChatTab |
✅ src/components/chat/MessageInput.jsx |
ConfigTab.jsx |
renderConfigTab (L10918) |
✅ src/components/tabs/ConfigTab.jsx |
CostTab.jsx |
renderCostTab (L12058) |
✅ src/components/tabs/CostTab.jsx |
RefineConfirmModal.jsx |
renderRefineAllConfirmModal (L6389) |
✅ src/components/RefineAllConfirm.jsx |
MentionAutocomplete.jsx |
Inline in renderChatTab |
✅ src/components/chat/MentionAutocomplete.jsx |
SlashCommandMenu.jsx |
Inline in renderChatTab |
❌ Missing |
3. Business Logic Migration Status
| Function | sidebar.js | React/src |
|---|---|---|
| Session locking | ✅ acquireSessionLock, releaseSessionLock, startLockHeartbeat |
❌ Not migrated |
| Session persistence | ✅ persistSessionMessages, hydrateSessionStateFromMessages |
❌ Not migrated |
| Chat history | ✅ loadChatHistory, loadPostSessions |
⚠️ api.getChatHistory() only |
| Markdown rendering | ✅ markdownToHtml (L10032) |
✅ src/components/chat/MarkdownRenderer.jsx |
| Block mentions | ✅ extractMentionsFromText, resolveBlockMentions |
⚠️ Partial |
| Slash commands | ✅ parseInsertCommand, getSlashOptions |
❌ Not migrated |
| Editor locking | ✅ shouldBlockEditorInput, keydownHandler |
⚠️ src/hooks/useLock.js (stub) |
| AI Undo Stack | ✅ captureEditorSnapshot, undoLastAiOperation |
❌ Not migrated |
| Refinement | ✅ handleChatRefinement, insertRefinementBlock |
❌ Not migrated |
| Title refinement | ✅ handleTitleRefinement |
❌ Not migrated |
| Planner | ✅ streamGeneratePlan, executePlanFromCard |
❌ Not migrated |
| Plan management | ✅ upsertSectionBlock, loadSectionBlocks, saveSectionBlocks |
❌ Not migrated |
| SEO Audit | ✅ runSeoAudit, handleSeoAuditFix |
❌ Not migrated |
| Meta generation | ✅ generateMetaDescription |
❌ Not migrated |
| Intent detection | ✅ classifyAgentIntent, decideAgentAction |
❌ Not migrated |
| AI Slop detection | ✅ isAiSlopRequest, getAiSlopFindingsForBlock |
❌ Not migrated |
| Cost tracking | ✅ refreshCostData |
⚠️ api.getCostTracking() only |
| Context building | ✅ buildContextBlocksForRefinement, getRefinementDiagnosis |
❌ Not migrated |
4. API Service Mapping
| API Endpoint | sidebar.js | React/src (api.js) |
|---|---|---|
| Post config CRUD | ✅ savePostConfig |
✅ api.savePostConfig() |
| Chat history | ✅ loadChatHistory |
✅ api.getChatHistory() |
| Session management | ✅ loadPostSessions, openSessionById |
⚠️ api.getSessionsByStatus() |
| Streaming chat | ✅ sendMessage |
✅ api.streamChat() |
| Writing state | ✅ saveWritingState |
❌ Missing |
| SEO audit | ✅ runSeoAudit |
✅ api.runSeoAudit() |
| Meta generation | ✅ generateMetaDescription |
❌ Missing |
| Memanto restore | ✅ Inline fetch (L2214) | ❌ Missing |
| Section blocks | ✅ loadSectionBlocks, saveSectionBlocks |
❌ Missing |
⚠️ Critical Gaps & Issues
1. Memanto Memory System Not Migrated
The migration map references memantoRestore but there's no loadMemantoRestore function exported. The legacy code fetches from /memanto/restore at L2214-2224 but this endpoint isn't in api.js.
2. Planner/Agentic Writer Core Not Migrated
The most complex parts are missing:
streamGeneratePlan(~320 lines)executePlanFromCard(~450 lines)- Section block management
- Workspace snapshot building
3. AI Slop Detection Logic Not Migrated
The sophisticated AI slop detection rules at L5545-5571 are completely missing from React.
4. Editor Lock Hook Incomplete
useLock.js exists but shouldBlockEditorInput and the keydown handler from the legacy code aren't migrated.
5. Undo Stack Not Migrated
The AI undo stack (aiUndoStack, captureEditorSnapshot, undoLastAiOperation) isn't in the React codebase.
6. Context Building Logic Not Migrated
buildContextBlocksForRefinementgetRefinementDiagnosisbuildAuditRefinementContextgetContextFromMentions
📁 File Structure Comparison
sidebar.js (Legacy - 12,347 lines)
├── ~50 useState hooks (monolithic)
├── ~200+ functions
└── Inline render functions
src/ (React - Scaffolding)
├── components/
│ ├── Sidebar.jsx ✅ Main container
│ ├── ClarificationFlow.jsx ⚠️ Simplified
│ ├── ContextualAction.jsx ⚠️ Simplified stub
│ ├── Messages.jsx ⚠️ Basic
│ ├── RefineAllConfirm.jsx ✅
│ ├── RefinementModal.jsx ✅
│ ├── chat/
│ │ ├── MarkdownRenderer.jsx ✅
│ │ ├── MentionAutocomplete.jsx ✅
│ │ ├── MessageInput.jsx ✅
│ │ ├── MessageList.jsx ✅
│ │ └── WelcomeScreen.jsx ✅
│ ├── seo/
│ ├── shared/
│ │ └── StatusBar.jsx ✅
│ ├── tabs/
│ │ ├── ChatTab.jsx ⚠️ Incomplete
│ │ ├── ConfigTab.jsx ⚠️ Incomplete
│ │ └── CostTab.jsx ⚠️ Incomplete
│ └── writing/
├── context/
│ └── SidebarContext.jsx ⚠️ Partial (missing most states)
├── hooks/
│ ├── useCost.js ⚠️ Stub
│ ├── useLock.js ⚠️ Stub
│ ├── usePostConfig.js ⚠️ Stub
│ ├── useSession.js ⚠️ Basic
│ ├── useStream.js ⚠️ Basic
│ └── useWritingState.js ⚠️ Stub
├── services/
│ └── api.js ⚠️ Partial
└── utils/
├── blockUtils.js
├── constants.js
├── errors.js
├── formatting.js
├── planUtils.js ⚠️ Partial
├── seoUtils.js
├── state.js
└── streamUtils.js
🔧 Recommendations
- Update MIGRATION_MAP.md - Mark components as
PARTIAL,STUB, orNOT MIGRATEDto reflect actual state - Prioritize Planner migration - This is the core differentiator and most complex part
- Complete Editor Lock - Before doing any editor integration testing
- Add Memanto to API - Create proper service methods for memory restore
- Write integration tests - The React components reference functions/hooks that don't exist yet
User
I actually not satisfied by new React because it losing much more than what you reported. I won't iterate it, just migrate to have 1:1
Lastly migrated version using emojis instead SVG, and losing so many styles, likely not using classes that mentioned in CSS.
Confirm: is it possible to get 1:1 by only seeing @sidebar.js PERFECTLY?
Assistant
The user is asking if it's possible to get a 1:1 migration PERFECTLY by only looking at sidebar.js. They want to know if the source code is complete enough to recreate the UI exactly.Let me think about this:
-
Yes, sidebar.js IS the complete source of truth - It contains:
- All state declarations (useState, useRef)
- All business logic functions
- All render functions with exact HTML structures
- All inline styles
- All class names
- All SVG icons (as JSX createElement calls)
- All event handlers
-
What sidebar.js does NOT have:
- CSS stylesheet (external file needed)
- CSS variable definitions (but class names are there)
- Asset images (but referenced by class)
-
Can you get 1:1 with ONLY sidebar.js?
- Logic: YES - 100% possible
- HTML structure: YES - 100% possible
- Class names: YES - 100% possible
- Inline styles: YES - 100% possible
- SVG icons: YES - the file uses
wp.element.createElementwith SVG, which is COPY-PASTABLE - CSS: NO - you need the CSS file too (but the classes are all there)
The key insight: sidebar.js uses wp.element.createElement not JSX, so:
- SVG paths ARE in the file (as
dattributes in path elements) - Class names ARE used exactly as they'd appear in CSS
- Inline styles ARE exact
So the answer is: YES, you can get 1:1 functionality and structure from sidebar.js alone - but you'll also need the CSS file to make it look right.
The real problem with the current React migration was:
-
They didn't copy the exact createElement calls
-
They swapped SVG icons for emojis
-
They simplified many components instead of preserving their complexity
-
They likely didn't reference the CSS file to understand how each class name was styled
YES. sidebar.js contains everything needed for a 1:1 migration:
| Aspect | In sidebar.js? | Can be 1:1? |
|---|---|---|
| HTML structure | ✅ Every createElement call |
YES - exact copy |
| Class names | ✅ All className values |
YES - verbatim |
| Inline styles | ✅ All style objects |
YES - verbatim |
| SVG icons | ✅ Full path d attributes |
YES - verbatim |
| State/logic | ✅ All functions | YES - 1:1 port |
| Event handlers | ✅ All onClick/onChange | YES - verbatim |
| CSS | ❌ External file | Need CSS too |
The Problem With Current React Migration
The current migration failed at Step 1 of the MIGRATION_MAP rule:
"Never guess the DOM: When porting a
renderXfunction, grep the legacysidebar.jsfor that function, copy its exact element structure, and translate it to JSX."
They didn't copy — they re-imagined. Example from renderGlobalStatusBar (L11449):
// sidebar.js has EXACT SVG paths:
wp.element.createElement('path', {
d: 'M12 3l1.5 4.5H18l-3.5 3 1.5 4.5L12 13l-4 2 1.5-4.5L6 7.5h4.5z',
fill: 'none',
stroke: 'currentColor',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round'
})
React migration has:
<span>💬</span> // WRONG ❌
What You Actually Need
sidebar.js— ✅ Source of truth for everything- CSS file — Look for it (likely
plugin.cssorsidebar.css) - MIGRATION_MAP — Remove it or ignore it; it failed
My Recommendation
Delete the React scaffold and do a true 1:1 mechanical translation:
- Parse each render function in sidebar.js line by line
- Convert
wp.element.createElement()calls to JSX - Copy SVG paths verbatim (they're already there)
- Copy class names verbatim (CSS hooks)
- Copy inline styles verbatim
sidebar.js is the complete specification. No guessing needed.
User
okay, remove new React. We will start non-guessing migration effort. Make a Perfect MATCHING plan.
List the files, get from line XX to line XX.
Zero guessing. Everything already provided IN the @sidebar.js. THe CSS is still not dequeue, so it still loaded and affecting. By using same class, same DOM, same inline CSS (if any), we still can achieve 1:1 migration
Assistant
The user wants me to create a perfect 1:1 migration plan by listing all files with their exact line numbers from sidebar.js. They want zero guessing - everything from the source file.Let me analyze the sidebar.js file structure and break it down into logical files. The file has:
- Imports/setup (L8-41)
- State declarations (L139-368)
- Helper functions (L380-1069)
- Render functions (L1070+)
- Main component (L12309+)
Let me create a detailed plan with exact line ranges for each file to be created.
Perfect 1:1 Migration Plan
Phase 1: Delete Failed React Scaffold
Delete entire src/ folder:
├── src/components/ ❌ REMOVE
├── src/context/ ❌ REMOVE
├── src/hooks/ ❌ REMOVE
├── src/services/ ❌ REMOVE
├── src/utils/ ❌ REMOVE
└── src/index.jsx ❌ REMOVE
Phase 2: Exact File List from sidebar.js
File 1: src/constants.js
Lines: 1-37
// wp.element imports, registerPlugin, PluginSidebar, etc.
File 2: src/helpers/logging.js
Lines: 17-136
// formatAiErrorMessage function (error parsing)
File 3: src/state/config.js
Lines: 163-222
// defaultPostConfig, postConfig state, applyProviderMetadata
File 4: src/state/editor.js
Lines: 224-237
// isEditorLocked, isRefinementLocked, refiningBlockIds, refineAllConfirm, etc.
File 5: src/state/seo.js
Lines: 240-253
// seoAudit, isSeoAuditing, isGeneratingMeta, activeSeoFixKey
File 6: src/state/clarification.js
Lines: 246-251, 1032-1069
// inClarification, questions, answers, detectedLanguage, clarificationMode
// buildPostConfigFromAnswers
File 7: src/state/refinement.js
Lines: 253-268, 5800-6388
// pendingRefinement, pendingEditPlan, handleChatRefinement, insertRefinementBlock
File 8: src/state/writing.js
Lines: 256-296, 2871-4767, 4321-4767
// writingState, workspaceSnapshot, streamGeneratePlan, executePlanFromCard
// currentPlanRef, sectionBlocksRef, etc.
File 9: src/helpers/api.js
Lines: 380-490, 511-569, 1195-1562, 1782-1837, 1890-2024, 2074-2514, 3633-3696
// All API calls: savePostConfig, saveWritingState, runSeoAudit, generateMetaDescription
// persistSessionMessages, acquireSessionLock, loadChatHistory, loadPostSessions
File 10: src/helpers/editor.js
Lines: 753-811, 843-968, 1020-1028
// captureEditorSnapshot, undoLastAiOperation, shouldBlockEditorInput
// keydownHandler, blockMutationEvent, toTextValue
File 11: src/helpers/timeline.js
Lines: 586-673
// updateOrCreateTimelineEntry, addActivityTimeline, activeTimelineStatuses
File 12: src/helpers/operations.js
Lines: 674-750
// beginAgentOperation, finishAgentOperation, markActiveOperationStopping
// requestRefineAllConfirmation, resolveRefineAllConfirmation
File 13: src/helpers/mentions.js
Lines: 2516-2569, 5705-5797, 6454-6605, 6613-6757
// normalizeMentionToken, extractMentionsFromText, resolveBlockMentions
// getMentionOptions, handleInsertMention, insertMention
File 14: src/helpers/blocks.js
Lines: 5363-5589, 4871-5050
// getRefineableBlocks, getListItemBlocks, getAiSlopFindingsForBlock
// createBlocksFromSerialized, reformatBlocks
File 15: src/helpers/blocks-context.js
Lines: 5443-5622
// getBlockContentForContext, getHeadingContextForBlock, getNearbyParagraphContext
// buildContextBlocksForRefinement, buildRefinementDiagnosis
File 16: src/helpers/plan.js
Lines: 3474-3891, 3535-3891, 3829-3891
// createBlockFromPlan, normalizePlanActions, buildPlanPreviewItem
// upsertSectionBlock, findSectionInsertIndex, ensurePlanTasks
File 17: src/helpers/agent.js
Lines: 3907-4320, 4222-4320
// summarizeChatHistory, detectUserIntent, classifyAgentIntent
// decideAgentAction, getPlanRuntimeSummary
File 18: src/helpers/session.js
Lines: 1640-2024, 1640-1753, 1755-1883, 2043-2207, 2301-2514, 8830-9002
// updateSnapshot, sanitizeMessagesForStorage, hydrateSessionStateFromMessages
// flushOnUnload, loadChatHistory, loadPostSessions, openSessionById
// startNewConversation, deleteConversationSession, getSessionDisplayTitle
File 19: src/helpers/seo.js
Lines: 1195-1347, 1270-1347, 1309-1328
// runSeoAudit, buildSeoAuditFixInstruction, buildAuditRefinementContext
// getSeoFixKey, getSeoAuditPatternCount, formatAuditPatternLabel
File 20: src/helpers/chat.js
Lines: 6759-8054, 8057-8474
// sendMessage (1300 lines!), submitAnswers
File 21: src/helpers/markdown.js
Lines: 10007-10270
// escapeHtml, inlineMarkdownToHtml, markdownToHtml (240+ lines)
File 22: src/helpers/refinement.js
Lines: 1349-1458, 2570-2689, 4954-5142, 5143-5279
// handleSeoAuditFix, handleTitleRefinement, reformatBlocks
// applyEditPlan, cancelEditPlan
File 23: src/helpers/keywords.js
Lines: 1072-1157, 4097-4155
// handleFocusKeywordChange, handleKeywordSelect
// extractFocusKeywordSuggestions, suggestKeywordsFromPlan
File 24: src/helpers/welcome.js
Lines: 1176-1192
// handleWelcomeStart
File 25: src/helpers/retry.js
Lines: 3189-3257
// retryLastGeneration, retryLastExecute, retryLastRefinement, retryLastChat
File 26: src/helpers/commands.js
**Lines:
User (checkpoint)
remove @MIGRATION_MAP.md and write new document for being single source of truth the migration process
Assistant
The user wants me to: 1. Delete the MIGRATION_MAP.md file 2. Write a new document that serves as the single source of truth for the migration processThe new document should be based on the perfect matching plan I outlined earlier, but formatted as a proper migration document that:
- Lists every file to create
- Shows exact line numbers from sidebar.js
- Provides clear rules for 1:1 migration
- Acts as the single source of truth
Let me write this document.
Tool Call: MIGRATION_GUIDE.md Status: Completed
Diff: MIGRATION_GUIDE.md
# WP Agentic Writer - Migration Guide
**Source:** `assets/js/sidebar.js` (12,347 lines)
**Goal:** 1:1 mechanical translation - ZERO guessing
---
## Rules
1. **COPY verbatim** - Every className, SVG path `d` attribute, inline style, and prop from `sidebar.js`
2. **Convert `wp.element.createElement()` to JSX** - No changes to structure
3. **Preserve all CSS hooks** - The existing CSS file still applies
4. **Line numbers are authoritative** - Use exact line ranges from this document
---
## File Structure
assets/js/ ├── sidebar.js # SOURCE OF TRUTH (do not edit) └── src/ ├── index.jsx # Plugin entry point ├── components/ │ ├── Sidebar.jsx # Main container │ ├── StatusBar.jsx # Navigation bar │ ├── ChatTab.jsx # Chat interface │ ├── ConfigTab.jsx # Settings tab │ ├── CostTab.jsx # Cost tracking tab │ └── ... ├── helpers/ # Business logic (ported 1:1) ├── hooks/ # React hooks └── state/ # State management
---
## Complete File List
### Components
| File | sidebar.js Lines | Description |
|------|------------------|-------------|
| `components/Sidebar.jsx` | 38-12347 | Main component (keep as monolith initially) |
| `components/StatusBar.jsx` | 11449-11583 | Navigation bar with icons |
| `components/ChatTab.jsx` | 11586-12018 | Chat tab content |
| `components/ConfigTab.jsx` | 10918-11433 | Configuration tab |
| `components/CostTab.jsx` | 12058-12304 | Cost tracking tab |
| `components/WelcomeScreen.jsx` | 9005-9182 | Welcome/start screen |
| `components/WritingEmptyState.jsx` | 9185-9251 | Empty state with SVG icon |
| `components/FocusKeywordBar.jsx` | 9254-9458 | Focus keyword input bar |
| `components/AgentWorkspaceCard.jsx` | 9460-9627 | Plan execution card |
| `components/ContextualAction.jsx` | 9633-9994 | Contextual action buttons |
| `components/MessageList.jsx` | 9997-10913 | Message rendering with markdown |
| `components/Clarification.jsx` | 8477-8828 | Clarification wizard |
| `components/RefineAllConfirm.jsx` | 6389-6453 | Refine all confirmation modal |
### Helpers (Business Logic)
| File | sidebar.js Lines | Description |
|------|------------------|-------------|
| `helpers/logging.js` | 17-136 | `formatAiErrorMessage` |
| `helpers/api.js` | 380-490, 511-569, 1195-1562, 1782-2514 | All API calls |
| `helpers/session.js` | 1640-2024, 2043-2207, 2301-2514, 8830-9002 | Session management |
| `helpers/editor.js` | 753-811, 843-968, 1020-1028 | Editor operations |
| `helpers/timeline.js` | 586-673 | Activity timeline |
| `helpers/operations.js` | 674-750 | Agent operations |
| `helpers/mentions.js` | 2516-2569, 5705-6605, 6613-6757 | Block mentions |
| `helpers/blocks.js` | 5363-5589, 4871-5050 | Block operations |
| `helpers/blocks-context.js` | 5443-5622 | Context building |
| `helpers/plan.js` | 3474-3891 | Plan operations |
| `helpers/agent.js` | 3907-4320 | AI agent logic |
| `helpers/seo.js` | 1195-1347 | SEO audit logic |
| `helpers/chat.js` | 6759-8054, 8057-8474 | Chat & answers |
| `helpers/markdown.js` | 10007-10270 | Markdown rendering |
| `helpers/refinement.js` | 1349-1458, 2570-2689, 4954-5279 | Content refinement |
| `helpers/keywords.js` | 1072-1157, 4097-4155 | Focus keywords |
| `helpers/welcome.js` | 1176-1192 | Welcome handler |
| `helpers/retry.js` | 3189-3257 | Retry logic |
### State
| File | sidebar.js Lines | Description |
|------|------------------|-------------|
| `state/config.js` | 163-222 | Post config state |
| `state/editor.js` | 224-237 | Editor lock state |
| `state/seo.js` | 240-253 | SEO audit state |
| `state/clarification.js` | 246-251, 1032-1069 | Clarification state |
| `state/refinement.js` | 253-268, 5800-6388 | Refinement state |
| `state/writing.js` | 256-296, 2871-4767 | Writing state |
---
## Migration Order
### Phase 1: Setup
1. [ ] Create `src/` directory structure
2. [ ] Create `src/index.jsx` entry point
3. [ ] Import `sidebar.js` helpers one by one
### Phase 2: Components (copy verbatim from sidebar.js)
1. [ ] `StatusBar.jsx` (L11449-11583) - SVGs are inside
2. [ ] `WelcomeScreen.jsx` (L9005-9182)
3. [ ] `WritingEmptyState.jsx` (L9185-9251) - Contains SVG icon
4. [ ] `FocusKeywordBar.jsx` (L9254-9458) - Contains SVG icon
5. [ ] `Clarification.jsx` (L8477-8828)
6. [ ] `RefineAllConfirm.jsx` (L6389-6453)
7. [ ] `ConfigTab.jsx` (L10918-11433)
8. [ ] `CostTab.jsx` (L12058-12304)
9. [ ] `AgentWorkspaceCard.jsx` (L9460-9627)
10. [ ] `ContextualAction.jsx` (L9633-9994)
11. [ ] `MessageList.jsx` (L9997-10913) - Includes markdownToHtml
12. [ ] `ChatTab.jsx` (L11586-12018) - Main chat tab
13. [ ] `Sidebar.jsx` (L38-12347) - Assemble all components
### Phase 3: Helpers
1. [ ] `helpers/logging.js`
2. [ ] `helpers/api.js`
3. [ ] `helpers/timeline.js`
4. [ ] `helpers/operations.js`
5. [ ] `helpers/editor.js`
6. [ ] `helpers/mentions.js`
7. [ ] `helpers/blocks.js`
8. [ ] `helpers/blocks-context.js`
9. [ ] `helpers/plan.js`
10. [ ] `helpers/agent.js`
11. [ ] `helpers/seo.js`
12. [ ] `helpers/chat.js`
13. [ ] `helpers/markdown.js`
14. [ ] `helpers/refinement.js`
15. [ ] `helpers/keywords.js`
16. [ ] `helpers/welcome.js`
17. [ ] `helpers/retry.js`
18. [ ] `helpers/session.js`
### Phase 4: State
1. [ ] `state/config.js`
2. [ ] `state/editor.js`
3. [ ] `state/seo.js`
4. [ ] `state/clarification.js`
5. [ ] `state/refinement.js`
6. [ ] `state/writing.js`
---
## JSX Conversion Rules
### Convert createElement to JSX
**sidebar.js:**
```js
wp.element.createElement('div', {
className: 'wpaw-status-icon-btn',
onClick: handleClick,
style: { display: 'flex', alignItems: 'center' }
}, 'text');
wp.element.createElement('svg', {
xmlns: 'http://www.w3.org/2000/svg',
width: 20,
height: 20,
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 1.5
}, wp.element.createElement('path', { d: 'M12 3l1.5...' }));
Convert to:
<div
className="wpaw-status-icon-btn"
onClick={handleClick}
style={{ display: 'flex', alignItems: 'center' }}
>
text
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
width={20}
height={20}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.5}
>
<path d="M12 3l1.5..." />
</svg>
Key Differences
| sidebar.js | JSX |
|---|---|
{ key: value } |
{ value } (remove key names for props) |
{ className: 'foo' } |
className="foo" |
{ style: { x: y } } |
style={{ x: y }} |
null children |
null |
Nested createElement |
Nested JSX elements |
Verification Checklist
After each file migration:
- All
classNamevalues match original exactly - All SVG
dpaths match original exactly - All inline
styleobjects match original exactly - All
onClick,onChange,onKeyDownhandlers preserved - All
dangerouslySetInnerHTMLpreserved (for RawHTML) - All
aria-*attributes preserved - All
roleattributes preserved
Rollback Plan
If migration breaks functionality:
- Keep
sidebar.jsunchanged - Compare DOM output manually
- Fix JSX until output matches
Success Criteria
- Every CSS class from
sidebar.jsexists in output - Every SVG icon renders identically
- All interactions (clicks, inputs) work same as before
- No visual regressions