Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions wordpress-plugin/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions wordpress-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@types/jest": "^29.5.14",
"@wordpress/api-fetch": "^7.44.0",
"@wordpress/components": "^32.6.0",
"@wordpress/compose": "^7.44.0",
"@wordpress/core-data": "^7.44.0",
"@wordpress/data": "^10.44.0",
"@wordpress/editor": "^14.44.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
// `getActiveComplementaryArea` returns this identifier (slash form),
// which is distinct from the DOM id attribute (colon form).
export const SIDEBAR_ID = 'claudaborative-editing-conversation/conversation';

// Gutenberg's collab-sidebar identifiers. Sourced from
// @wordpress/editor/src/components/collab-sidebar/constants.js — the floating
// sidebar shows unresolved notes anchored to blocks (large viewports only);
// the history sidebar is the "All notes" panel and is the small-viewport
// fallback.
export const FLOATING_NOTES_SIDEBAR = 'edit-post/collab-sidebar';
export const ALL_NOTES_SIDEBAR = 'edit-post/collab-history-sidebar';
69 changes: 58 additions & 11 deletions wordpress-plugin/src/components/ConversationPanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
import { __ } from '@wordpress/i18n';
import { Button, TextareaControl } from '@wordpress/components';
import { useViewportMatch } from '@wordpress/compose';
import { useSelect, useDispatch } from '@wordpress/data';
import { useState, useEffect, useRef, RawHTML } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
Expand All @@ -28,7 +29,11 @@ import { getCommandLabel } from '../../utils/command-i18n';
import aiActionsStore from '../../store';
import SparkleIcon from '../SparkleIcon';
import { useResizableSidebar } from './use-resizable-sidebar';
import { SIDEBAR_ID } from './constants';
import {
SIDEBAR_ID,
FLOATING_NOTES_SIDEBAR,
ALL_NOTES_SIDEBAR,
} from './constants';
import { TERMINAL_STATUSES, type CommandSlug } from '#shared/commands';
import type {
ConversationMessage,
Expand Down Expand Up @@ -94,6 +99,16 @@ export default function ConversationPanel() {
const messagesEndRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const prevStatusRef = useRef<string | null>(null);
// Holds the id of the command whose approval triggered a sidebar
// switch, so the cancel-on-close watcher below can skip cancelling
// that specific command. Scoping by id (rather than a boolean)
// ensures a stale signal can't leak into a future command's close
// event — if the approved command transitions to terminal before
// the watcher fires, the id simply won't match any subsequent
// active command.
const postApproveSwitchCommandIdRef = useRef<number | null>(null);

const isLargeViewport = useViewportMatch('medium');

// Single subscription to the interface store for our sidebar's active
// state; the hook below reuses this rather than subscribing separately.
Expand Down Expand Up @@ -164,15 +179,23 @@ export default function ConversationPanel() {
currentStatus === 'awaiting_input' &&
prevStatus !== 'awaiting_input';
// Fire once when a conversational command first enters an in-flight
// status; don't re-fire on pending → running transitions.
// status; don't re-fire on pending → running or awaiting_input →
// running. The latter matters because handleApprove switches the
// sidebar to the notes panel on approve, and an awaiting_input →
// running transition arriving on the /respond round-trip would
// otherwise re-open the conversation panel and undo the switch.
const inFlightStatuses: readonly (string | null)[] = [
'pending',
'running',
];
const alreadyInFlightStatuses: readonly (string | null)[] = [
...inFlightStatuses,
'awaiting_input',
];
const startedConversationalCommand =
isConversationalCommand &&
inFlightStatuses.includes(currentStatus) &&
!inFlightStatuses.includes(prevStatus);
!alreadyInFlightStatuses.includes(prevStatus);

if (enteredAwaitingInput || startedConversationalCommand) {
enableComplementaryArea?.('core', SIDEBAR_ID);
Expand Down Expand Up @@ -214,6 +237,15 @@ export default function ConversationPanel() {
const wasActive = prevSidebarActiveRef.current;
prevSidebarActiveRef.current = isSidebarActive;
if (wasActive && !isSidebarActive && activeCommand && isCommandActive) {
// Approve intentionally switches away to the notes sidebar while
// the command is still running the scaffold — don't treat that
// as a user-initiated close and cancel the command. Match by
// id so an unconsumed signal from a prior approve can't cause
// an unrelated command's close to silently skip cancel.
if (postApproveSwitchCommandIdRef.current === activeCommand.id) {
postApproveSwitchCommandIdRef.current = null;
return;
}
cancel(activeCommand.id);
}
}, [isSidebarActive, activeCommand, isCommandActive, cancel]);
Expand Down Expand Up @@ -260,15 +292,30 @@ export default function ConversationPanel() {

const handleApprove = () => {
if (activeCommand && !isResponding) {
const approvedCommandId = activeCommand.id;
void Promise.resolve(
respondToCommand(activeCommand.id, 'approve')
).catch(() => {
createNotice(
'error',
__('Failed to approve outline.', 'claudaborative-editing'),
{ type: 'snackbar' }
);
});
respondToCommand(approvedCommandId, 'approve')
).then(
() => {
postApproveSwitchCommandIdRef.current = approvedCommandId;
enableComplementaryArea?.(
'core',
isLargeViewport
? FLOATING_NOTES_SIDEBAR
: ALL_NOTES_SIDEBAR
);
},
() => {
createNotice(
'error',
__(
'Failed to approve outline.',
'claudaborative-editing'
),
{ type: 'snackbar' }
);
}
);
}
};

Expand Down
Loading
Loading