Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
55 changes: 35 additions & 20 deletions webview-ui/src/components/prompts/PromptsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,21 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
})
}

// keep track the state of the textareas locally because we sync them with the backend
// on separate event
const [roleDefinitionValue, setRoleDefinitionValue] = useState(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential stale state: The local state variables (e.g. roleDefinitionValue, customInstructionsValue, etc.) are initialized only once and are derived from props like visualMode, customModes, and customModePrompts. If these external dependencies change (e.g. when switching mode), the state may not update accordingly. Consider adding a useEffect to sync these states on changes.

Copy link
Contributor Author

@elianiva elianiva May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think we'd want that :p
the point is to have separate state between frontend and backend because we want the frontend to get updated onChange while the backend onBlur

if it ever gets out of sync—which i highly doubt it will?—it should be easily fixed the next time the prompts view gets re-rendered

const customMode = findModeBySlug(visualMode, customModes)
const prompt = customModePrompts?.[visualMode] as PromptComponent
return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(visualMode)
})
const [customInstructionsValue, setCustomInstructionsValue] = useState(customInstructions || "")
const [globalCustomInstructionsValue, setGlobalCustomInstructionsValue] = useState(() => {
const customMode = findModeBySlug(visualMode, customModes)
const prompt = customModePrompts?.[visualMode] as PromptComponent
return customMode?.customInstructions ?? prompt?.customInstructions ?? getCustomInstructions(mode, customModes)
})
const [supportPromptValue, setSupportPromptValue] = useState(getSupportPromptValue(activeSupportOption) || "")

return (
<Tab>
<TabHeader className="flex justify-between items-center">
Expand Down Expand Up @@ -667,19 +682,15 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{t("prompts:roleDefinition.description")}
</div>
<Textarea
value={(() => {
const customMode = findModeBySlug(visualMode, customModes)
const prompt = customModePrompts?.[visualMode] as PromptComponent
return (
customMode?.roleDefinition ??
prompt?.roleDefinition ??
getRoleDefinition(visualMode)
)
})()}
value={roleDefinitionValue}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
setRoleDefinitionValue(value)
}}
onBlur={() => {
const value = roleDefinitionValue
const customMode = findModeBySlug(visualMode, customModes)
if (customMode) {
// For custom modes, update the JSON file
Expand Down Expand Up @@ -849,19 +860,15 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
})}
</div>
<Textarea
value={(() => {
const customMode = findModeBySlug(visualMode, customModes)
const prompt = customModePrompts?.[visualMode] as PromptComponent
return (
customMode?.customInstructions ??
prompt?.customInstructions ??
getCustomInstructions(mode, customModes)
)
})()}
value={customInstructionsValue}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
setCustomInstructionsValue(value)
}}
onBlur={() => {
const value = customInstructionsValue
const customMode = findModeBySlug(visualMode, customModes)
if (customMode) {
// For custom modes, update the JSON file
Expand Down Expand Up @@ -1003,11 +1010,15 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
})}
</div>
<Textarea
value={customInstructions}
value={globalCustomInstructionsValue}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
setGlobalCustomInstructionsValue(value)
}}
onBlur={() => {
const value = globalCustomInstructionsValue
setCustomInstructions(value || undefined)
vscode.postMessage({
type: "customInstructions",
Expand Down Expand Up @@ -1081,11 +1092,15 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
</div>

<Textarea
value={getSupportPromptValue(activeSupportOption)}
value={supportPromptValue}
onChange={(e) => {
const value =
(e as unknown as CustomEvent)?.detail?.target?.value ||
((e as any).target as HTMLTextAreaElement).value
setSupportPromptValue(value)
}}
onBlur={() => {
const value = supportPromptValue
const trimmedValue = value.trim()
updateSupportPrompt(activeSupportOption, trimmedValue || undefined)
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// npx jest src/components/prompts/__tests__/PromptsView.test.tsx

import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import PromptsView from "../PromptsView"
import { ExtensionStateContext } from "@src/context/ExtensionStateContext"
Expand Down Expand Up @@ -122,6 +124,7 @@ describe("PromptsView", () => {
fireEvent.change(textarea, {
target: { value: "New prompt value" },
})
fireEvent.blur(textarea)

expect(vscode.postMessage).toHaveBeenCalledWith({
type: "updatePrompt",
Expand Down Expand Up @@ -203,6 +206,7 @@ describe("PromptsView", () => {
fireEvent.change(textarea, {
target: { value: "" },
})
fireEvent.blur(textarea)

expect(setCustomInstructions).toHaveBeenCalledWith(undefined)
expect(vscode.postMessage).toHaveBeenCalledWith({
Expand Down