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
23 changes: 6 additions & 17 deletions frontend/e2e/flows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -712,12 +712,8 @@ for (const variant of TARGET_VARIANTS) {
const starBtn = page.getByTestId(`star-btn-${newConversationId}`);
await expect(starBtn).toBeVisible({ timeout: 5_000 });

// Promote via backend API directly (UI handler targets a mismatched path)
const promoteResp = await request.post(
`/api/attacks/${encodeURIComponent(attackResultId)}/update-main-conversation`,
{ data: { conversation_id: newConversationId } },
);
expect(promoteResp.ok()).toBeTruthy();
// Promote via the UI star button (tests the full click → API → refresh flow)
await starBtn.click();

await expect
.poll(
Expand Down Expand Up @@ -889,17 +885,10 @@ for (const variant of TARGET_VARIANTS) {
timeout: 5_000,
});

// Verify star button is visible but promote via API directly
// (UI handler targets a mismatched endpoint path)
await expect(
page.getByTestId(`star-btn-${branchConversationId}`),
).toBeVisible({ timeout: 5_000 });

const promoteResp = await request.post(
`/api/attacks/${encodeURIComponent(attackResultId)}/update-main-conversation`,
{ data: { conversation_id: branchConversationId } },
);
expect(promoteResp.ok()).toBeTruthy();
// Promote via the UI star button (tests the full click → API → refresh flow)
const starBtn = page.getByTestId(`star-btn-${branchConversationId}`);
await expect(starBtn).toBeVisible({ timeout: 5_000 });
await starBtn.click();

await expect
.poll(
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/Chat/ChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export default function ChatWindow({

try {
await attacksApi.changeMainConversation(attackResultId, convId)
setPanelRefreshKey(k => k + 1)
} catch (err) {
console.error('Failed to change main conversation:', err)
}
Expand Down Expand Up @@ -528,7 +529,12 @@ export default function ChatWindow({
onNewConversation={handleNewConversation}
onChangeMainConversation={handleChangeMainConversation}
onClose={() => setIsPanelOpen(false)}
locked={!activeTarget || isOperatorLocked || isCrossTargetLocked}
lockedReason={
!activeTarget ? 'Configure a target to enable this action.'
: isOperatorLocked ? 'Cannot modify — attack belongs to a different operator.'
: isCrossTargetLocked ? 'Cannot modify — attack was created with a different target.'
: undefined
}
refreshKey={panelRefreshKey}
/>
)}
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/components/Chat/ConversationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ interface ConversationPanelProps {
onNewConversation: () => void
onChangeMainConversation: (conversationId: string) => void
onClose: () => void
/** When true, disable mutating actions (new conversation, promote to main) */
locked?: boolean
/** When set, disables mutating actions (new conversation, promote to main) and explains why. */
lockedReason?: string
/** Increment to trigger a conversation list refresh (e.g. after sending a message) */
refreshKey?: number
}
Expand All @@ -43,7 +43,7 @@ export default function ConversationPanel({
onNewConversation,
onChangeMainConversation,
onClose,
locked,
lockedReason,
refreshKey,
}: ConversationPanelProps) {
const styles = useConversationPanelStyles()
Expand Down Expand Up @@ -107,13 +107,13 @@ export default function ConversationPanel({
)}
</div>
<div style={{ display: 'flex', gap: tokens.spacingHorizontalXXS }}>
<Tooltip content={locked ? 'Cannot modify — attack is locked' : 'New Conversation'} relationship="label">
<Tooltip content={lockedReason ?? 'New Conversation'} relationship="label">
<Button
appearance="subtle"
size="small"
icon={<AddRegular />}
onClick={onNewConversation}
disabled={!attackResultId || locked}
disabled={!attackResultId || !!lockedReason}
data-testid="new-conversation-btn"
/>
</Tooltip>
Expand Down Expand Up @@ -184,14 +184,14 @@ export default function ConversationPanel({
<Tooltip
content={conv.conversation_id === mainConversationId
? 'This is the main conversation.'
: 'Promote to main conversation.'}
: lockedReason ?? 'Promote to main conversation.'}
relationship="description"
>
<Button
appearance="subtle"
size="small"
icon={conv.conversation_id === mainConversationId ? <StarFilled /> : <StarRegular />}
disabled={conv.conversation_id === mainConversationId || locked}
disabled={conv.conversation_id === mainConversationId || !!lockedReason}
onClick={(e) => {
e.stopPropagation()
if (conv.conversation_id !== mainConversationId) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export const attacksApi = {
conversationId: string
): Promise<ChangeMainConversationResponse> => {
const response = await apiClient.post(
`/attacks/${encodeURIComponent(attackResultId)}/change-main-conversation`,
`/attacks/${encodeURIComponent(attackResultId)}/update-main-conversation`,
{ conversation_id: conversationId }
)
return response.data
Expand Down
Loading