Fixes #5219: Make system prompt warning dismissible

- Add systemPromptWarningDismissed boolean setting to GlobalSettings schema
- Update ExtensionState type to include the new setting
- Add state management through ExtensionStateContext with getter and setter
- Modify SystemPromptWarning component to include dismiss button with X icon
- Update ChatView to conditionally render warning based on both hasSystemPromptOverride and !systemPromptWarningDismissed
- Add test coverage for SystemPromptWarning component
- Dismiss functionality persists user preference through extension state system
This commit is contained in:
Roo Code 2025-06-30 10:07:01 +00:00
parent 3a8ba27615
commit f398eea6cb
6 changed files with 105 additions and 2 deletions

View file

@ -105,6 +105,7 @@ export const globalSettingsSchema = z.object({
historyPreviewCollapsed: z.boolean().optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
systemPromptWarningDismissed: z.boolean().optional(),
})
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

View file

@ -217,6 +217,7 @@ export type ExtensionState = Pick<
| "codebaseIndexConfig"
| "codebaseIndexModels"
| "profileThresholds"
| "systemPromptWarningDismissed"
> & {
version: string
clineMessages: ClineMessage[]

View file

@ -95,6 +95,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
customModes,
telemetrySetting,
hasSystemPromptOverride,
systemPromptWarningDismissed,
historyPreviewCollapsed, // Added historyPreviewCollapsed
soundEnabled,
soundVolume,
@ -1381,7 +1382,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
onClose={handleTaskCloseButtonClick}
/>
{hasSystemPromptOverride && (
{hasSystemPromptOverride && !systemPromptWarningDismissed && (
<div className="px-3">
<SystemPromptWarning />
</div>

View file

@ -1,15 +1,27 @@
import React from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
export const SystemPromptWarning: React.FC = () => {
const { t } = useAppTranslation()
const { setSystemPromptWarningDismissed } = useExtensionState()
const handleDismiss = () => {
setSystemPromptWarningDismissed(true)
}
return (
<div className="flex items-center px-4 py-2 mb-2 text-sm rounded bg-vscode-editorWarning-foreground text-vscode-editor-background">
<div className="flex items-center justify-center w-5 h-5 mr-2">
<span className="codicon codicon-warning" />
</div>
<span>{t("chat:systemPromptWarning")}</span>
<span className="flex-1">{t("chat:systemPromptWarning")}</span>
<button
onClick={handleDismiss}
className="flex items-center justify-center w-5 h-5 ml-2 hover:bg-vscode-editor-background hover:bg-opacity-20 rounded"
title="Dismiss warning">
<span className="codicon codicon-close" />
</button>
</div>
)
}

View file

@ -0,0 +1,83 @@
// npx vitest run src/components/chat/__tests__/SystemPromptWarning.spec.tsx
import { describe, it, expect, vi } from "vitest"
import { render, screen, fireEvent } from "@testing-library/react"
import { SystemPromptWarning } from "../SystemPromptWarning"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { useAppTranslation } from "@/i18n/TranslationContext"
// Mock the hooks
vi.mock("@/context/ExtensionStateContext")
vi.mock("@/i18n/TranslationContext")
const mockSetSystemPromptWarningDismissed = vi.fn()
const mockT = vi.fn((key: string) => {
if (key === "chat:systemPromptWarning") {
return "WARNING: Custom system prompt override active. This can severely break functionality and cause unpredictable behavior."
}
return key
})
describe("SystemPromptWarning", () => {
beforeEach(() => {
vi.clearAllMocks()
;(useExtensionState as any).mockReturnValue({
setSystemPromptWarningDismissed: mockSetSystemPromptWarningDismissed,
})
;(useAppTranslation as any).mockReturnValue({
t: mockT,
})
})
it("renders the warning message", () => {
render(<SystemPromptWarning />)
expect(screen.getByText(/WARNING: Custom system prompt override active/)).toBeInTheDocument()
})
it("renders the warning icon", () => {
render(<SystemPromptWarning />)
const warningIcon = document.querySelector(".codicon-warning")
expect(warningIcon).toBeInTheDocument()
})
it("renders the dismiss button", () => {
render(<SystemPromptWarning />)
const dismissButton = screen.getByRole("button")
expect(dismissButton).toBeInTheDocument()
expect(dismissButton).toHaveAttribute("title", "Dismiss warning")
})
it("renders the close icon in dismiss button", () => {
render(<SystemPromptWarning />)
const closeIcon = document.querySelector(".codicon-close")
expect(closeIcon).toBeInTheDocument()
})
it("calls setSystemPromptWarningDismissed when dismiss button is clicked", () => {
render(<SystemPromptWarning />)
const dismissButton = screen.getByRole("button")
fireEvent.click(dismissButton)
expect(mockSetSystemPromptWarningDismissed).toHaveBeenCalledWith(true)
expect(mockSetSystemPromptWarningDismissed).toHaveBeenCalledTimes(1)
})
it("has correct CSS classes for styling", () => {
render(<SystemPromptWarning />)
const container = document.querySelector(".flex.items-center.px-4.py-2.mb-2")
expect(container).toBeInTheDocument()
expect(container).toHaveClass("bg-vscode-editorWarning-foreground", "text-vscode-editor-background")
})
it("uses translation for the warning text", () => {
render(<SystemPromptWarning />)
expect(mockT).toHaveBeenCalledWith("chat:systemPromptWarning")
})
})

View file

@ -40,6 +40,8 @@ export interface ExtensionStateContextType extends ExtensionState {
mdmCompliant?: boolean
hasOpenedModeSelector: boolean // New property to track if user has opened mode selector
setHasOpenedModeSelector: (value: boolean) => void // Setter for the new property
systemPromptWarningDismissed?: boolean // New property to track if system prompt warning is dismissed
setSystemPromptWarningDismissed: (value: boolean) => void // Setter for the new property
condensingApiConfigId?: string
setCondensingApiConfigId: (value: string) => void
customCondensingPrompt?: string
@ -183,6 +185,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
condensingApiConfigId: "", // Default empty string for condensing API config ID
customCondensingPrompt: "", // Default empty string for custom condensing prompt
hasOpenedModeSelector: false, // Default to false (not opened yet)
systemPromptWarningDismissed: false, // Default to false (not dismissed yet)
autoApprovalEnabled: false,
customModes: [],
maxOpenTabsContext: 20,
@ -431,6 +434,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setHistoryPreviewCollapsed: (value) =>
setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })),
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),
setSystemPromptWarningDismissed: (value) =>
setState((prevState) => ({ ...prevState, systemPromptWarningDismissed: value })),
setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })),
setAutoCondenseContextPercent: (value) =>
setState((prevState) => ({ ...prevState, autoCondenseContextPercent: value })),