feat(FN-1885): merge fusion/fn-1885
This commit is contained in:
@@ -2,7 +2,7 @@ import { useState, useCallback } from "react";
|
||||
import { BUILTIN_AGENT_PROMPTS, PROMPT_KEY_CATALOG } from "../utils/builtinPrompts";
|
||||
import type { AgentPromptTemplate, AgentPromptsConfig, AgentCapability } from "@fusion/core";
|
||||
import type { PromptKey } from "@fusion/core";
|
||||
import { X, Plus, Pencil, Trash2, BookOpen, Users, Settings2, ChevronDown, ChevronUp } from "lucide-react";
|
||||
import { X, Plus, Pencil, Trash2, BookOpen, Users, Settings2, ChevronDown, ChevronUp, Maximize2, Minimize2 } from "lucide-react";
|
||||
|
||||
/**
|
||||
* Props for the AgentPromptsManager component.
|
||||
@@ -135,6 +135,12 @@ export function AgentPromptsManager({
|
||||
// Override expanded state for accordion behavior
|
||||
const [expandedOverrides, setExpandedOverrides] = useState<Set<PromptKey>>(new Set());
|
||||
|
||||
// Fullscreen state for overrides tab
|
||||
const [fullscreenOverrideKey, setFullscreenOverrideKey] = useState<PromptKey | null>(null);
|
||||
|
||||
// Fullscreen state for templates tab
|
||||
const [isTemplatePromptFullscreen, setIsTemplatePromptFullscreen] = useState(false);
|
||||
|
||||
// Get custom templates from current config
|
||||
const customTemplates = value?.templates ?? [];
|
||||
|
||||
@@ -178,6 +184,7 @@ export function AgentPromptsManager({
|
||||
setIsCreating(false);
|
||||
setTemplateForm(EMPTY_TEMPLATE_FORM);
|
||||
setTemplateIdError(null);
|
||||
setIsTemplatePromptFullscreen(false);
|
||||
}, []);
|
||||
|
||||
// Handle saving a template (create or update)
|
||||
@@ -333,6 +340,16 @@ export function AgentPromptsManager({
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Toggle fullscreen for an override
|
||||
const toggleFullscreenOverride = useCallback((key: PromptKey) => {
|
||||
setFullscreenOverrideKey((prev) => (prev === key ? null : key));
|
||||
}, []);
|
||||
|
||||
// Toggle fullscreen for template prompt
|
||||
const toggleTemplatePromptFullscreen = useCallback(() => {
|
||||
setIsTemplatePromptFullscreen((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
// Check if a template ID collides with built-in
|
||||
const isBuiltinId = (id: string): boolean => {
|
||||
return BUILTIN_AGENT_PROMPTS.some((t) => t.id === id);
|
||||
@@ -431,18 +448,72 @@ export function AgentPromptsManager({
|
||||
</div>
|
||||
|
||||
<div className="prompt-template-field">
|
||||
<label htmlFor="template-prompt">Prompt</label>
|
||||
<textarea
|
||||
id="template-prompt"
|
||||
value={templateForm.prompt}
|
||||
onChange={(e) =>
|
||||
setTemplateForm((f) => ({ ...f, prompt: e.target.value }))
|
||||
}
|
||||
placeholder="Enter the system prompt for this template..."
|
||||
rows={8}
|
||||
className="prompt-template-prompt-textarea"
|
||||
data-testid="template-prompt-input"
|
||||
/>
|
||||
<div className="prompt-template-prompt-label-row">
|
||||
<label htmlFor="template-prompt">Prompt</label>
|
||||
<button
|
||||
type="button"
|
||||
className="prompt-template-fullscreen-btn"
|
||||
onClick={toggleTemplatePromptFullscreen}
|
||||
aria-label="Expand prompt to fullscreen"
|
||||
data-testid="template-prompt-fullscreen"
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{isTemplatePromptFullscreen ? (
|
||||
<div
|
||||
className="prompt-override-fullscreen"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setIsTemplatePromptFullscreen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="prompt-override-fullscreen-header">
|
||||
<div className="prompt-override-fullscreen-title">Edit Prompt</div>
|
||||
<button
|
||||
type="button"
|
||||
className="prompt-override-fullscreen-close"
|
||||
onClick={() => setIsTemplatePromptFullscreen(false)}
|
||||
data-testid="template-prompt-collapse"
|
||||
>
|
||||
<Minimize2 size={14} />
|
||||
Collapse
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
id="template-prompt-fullscreen"
|
||||
aria-label="Template prompt - fullscreen"
|
||||
className="prompt-template-prompt-textarea"
|
||||
value={templateForm.prompt}
|
||||
onChange={(e) =>
|
||||
setTemplateForm((f) => ({ ...f, prompt: e.target.value }))
|
||||
}
|
||||
placeholder="Enter the system prompt for this template..."
|
||||
rows={30}
|
||||
autoFocus
|
||||
data-testid="template-prompt-input-fullscreen"
|
||||
/>
|
||||
<div className="prompt-override-footer">
|
||||
<small className="prompt-override-hint">
|
||||
{templateForm.prompt.length} characters
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<textarea
|
||||
id="template-prompt"
|
||||
value={templateForm.prompt}
|
||||
onChange={(e) =>
|
||||
setTemplateForm((f) => ({ ...f, prompt: e.target.value }))
|
||||
}
|
||||
placeholder="Enter the system prompt for this template..."
|
||||
rows={8}
|
||||
className="prompt-template-prompt-textarea"
|
||||
data-testid="template-prompt-input"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{templateIdError && (
|
||||
@@ -722,18 +793,18 @@ export function AgentPromptsManager({
|
||||
className="prompt-override-item"
|
||||
data-testid={`override-${key}`}
|
||||
>
|
||||
<div
|
||||
className="prompt-override-header"
|
||||
onClick={() => toggleOverrideExpanded(key)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
toggleOverrideExpanded(key);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="prompt-override-info">
|
||||
<div className="prompt-override-header">
|
||||
<div
|
||||
className="prompt-override-info"
|
||||
onClick={() => toggleOverrideExpanded(key)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
toggleOverrideExpanded(key);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="prompt-override-name">
|
||||
{promptMeta.name}
|
||||
</span>
|
||||
@@ -742,53 +813,130 @@ export function AgentPromptsManager({
|
||||
<span className="prompt-override-badge">customized</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="prompt-override-expand-btn"
|
||||
aria-label={isExpanded ? "Collapse" : "Expand"}
|
||||
data-testid={`expand-${key}`}
|
||||
>
|
||||
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</button>
|
||||
<div className="prompt-override-header-actions">
|
||||
{isExpanded && (
|
||||
<button
|
||||
className="prompt-override-fullscreen-btn"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleFullscreenOverride(key);
|
||||
}}
|
||||
aria-label="Expand to fullscreen"
|
||||
data-testid={`fullscreen-${key}`}
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="prompt-override-expand-btn"
|
||||
aria-label={isExpanded ? "Collapse" : "Expand"}
|
||||
data-testid={`expand-${key}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleOverrideExpanded(key);
|
||||
}}
|
||||
>
|
||||
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="prompt-override-description">
|
||||
{promptMeta.description}
|
||||
</p>
|
||||
|
||||
{isExpanded && (
|
||||
<div className="prompt-override-editor">
|
||||
<textarea
|
||||
id={`prompt-${key}`}
|
||||
aria-label={`${promptMeta.name} prompt override (${key})`}
|
||||
className="prompt-override-textarea"
|
||||
value={currentOverride}
|
||||
onChange={(e) => {
|
||||
handlePromptOverrideChange(key, e.target.value);
|
||||
fullscreenOverrideKey === key ? (
|
||||
<div
|
||||
className="prompt-override-fullscreen"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setFullscreenOverrideKey(null);
|
||||
}
|
||||
}}
|
||||
placeholder={`Default: ${promptMeta.defaultContent.slice(0, 100)}${promptMeta.defaultContent.length > 100 ? "..." : ""}`}
|
||||
rows={4}
|
||||
data-testid={`override-input-${key}`}
|
||||
/>
|
||||
<div className="prompt-override-footer">
|
||||
{hasOverride && (
|
||||
>
|
||||
<div className="prompt-override-fullscreen-header">
|
||||
<div className="prompt-override-fullscreen-title">
|
||||
{promptMeta.name}
|
||||
<code className="prompt-override-key">{key}</code>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleResetOverride(key);
|
||||
}}
|
||||
data-testid={`reset-${key}`}
|
||||
className="prompt-override-fullscreen-close"
|
||||
onClick={() => setFullscreenOverrideKey(null)}
|
||||
data-testid={`collapse-fullscreen-${key}`}
|
||||
>
|
||||
Reset
|
||||
<Minimize2 size={14} />
|
||||
Collapse
|
||||
</button>
|
||||
)}
|
||||
<small className="prompt-override-hint">
|
||||
{hasOverride
|
||||
? "Custom override active. Click Reset to restore default."
|
||||
: `No override set. Using built-in default (${promptMeta.defaultContent.length} chars).`}
|
||||
</small>
|
||||
</div>
|
||||
<textarea
|
||||
id={`prompt-${key}-fullscreen`}
|
||||
aria-label={`${promptMeta.name} prompt override (${key}) - fullscreen`}
|
||||
className="prompt-override-textarea"
|
||||
value={currentOverride}
|
||||
onChange={(e) => {
|
||||
handlePromptOverrideChange(key, e.target.value);
|
||||
}}
|
||||
placeholder={`Default: ${promptMeta.defaultContent.slice(0, 100)}${promptMeta.defaultContent.length > 100 ? "..." : ""}`}
|
||||
rows={30}
|
||||
autoFocus
|
||||
data-testid={`override-input-fullscreen-${key}`}
|
||||
/>
|
||||
<div className="prompt-override-footer">
|
||||
{hasOverride && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => handleResetOverride(key)}
|
||||
data-testid={`reset-fullscreen-${key}`}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
<small className="prompt-override-hint">
|
||||
{hasOverride
|
||||
? "Custom override active. Click Reset to restore default."
|
||||
: `No override set. Using built-in default (${promptMeta.defaultContent.length} chars).`}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="prompt-override-editor">
|
||||
<textarea
|
||||
id={`prompt-${key}`}
|
||||
aria-label={`${promptMeta.name} prompt override (${key})`}
|
||||
className="prompt-override-textarea"
|
||||
value={currentOverride}
|
||||
onChange={(e) => {
|
||||
handlePromptOverrideChange(key, e.target.value);
|
||||
}}
|
||||
placeholder={`Default: ${promptMeta.defaultContent.slice(0, 100)}${promptMeta.defaultContent.length > 100 ? "..." : ""}`}
|
||||
rows={4}
|
||||
data-testid={`override-input-${key}`}
|
||||
/>
|
||||
<div className="prompt-override-footer">
|
||||
{hasOverride && (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleResetOverride(key);
|
||||
}}
|
||||
data-testid={`reset-${key}`}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
<small className="prompt-override-hint">
|
||||
{hasOverride
|
||||
? "Custom override active. Click Reset to restore default."
|
||||
: `No override set. Using built-in default (${promptMeta.defaultContent.length} chars).`}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -604,4 +604,327 @@ describe("AgentPromptsManager", () => {
|
||||
expect(newOverrides["executor-welcome"]).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fullscreen Expansion", () => {
|
||||
describe("Overrides tab fullscreen", () => {
|
||||
it("shows fullscreen button when override is expanded", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand the override
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Fullscreen button should be visible
|
||||
expect(screen.getByTestId("fullscreen-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("clicking fullscreen button opens fullscreen view", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand and click fullscreen
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("fullscreen-executor-welcome"));
|
||||
|
||||
// Fullscreen container should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-fullscreen-executor-welcome")).toBeTruthy();
|
||||
expect(screen.getByTestId("collapse-fullscreen-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("fullscreen textarea contains current override value", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{
|
||||
"executor-welcome": "My custom welcome message",
|
||||
}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand and go fullscreen
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("fullscreen-executor-welcome"));
|
||||
|
||||
// Verify fullscreen textarea has the value
|
||||
await waitFor(() => {
|
||||
const textarea = screen.getByTestId("override-input-fullscreen-executor-welcome") as HTMLTextAreaElement;
|
||||
expect(textarea.value).toBe("My custom welcome message");
|
||||
});
|
||||
});
|
||||
|
||||
it("clicking collapse button exits fullscreen", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand, go fullscreen, and collapse
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("fullscreen-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-fullscreen-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("collapse-fullscreen-executor-welcome"));
|
||||
|
||||
// Fullscreen should be gone, but accordion should still be expanded
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("override-input-fullscreen-executor-welcome")).toBeNull();
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("Escape key exits fullscreen", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand, go fullscreen
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("fullscreen-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-fullscreen-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Press Escape
|
||||
const textarea = screen.getByTestId("override-input-fullscreen-executor-welcome") as HTMLTextAreaElement;
|
||||
fireEvent.keyDown(textarea, { key: "Escape" });
|
||||
|
||||
// Fullscreen should be gone
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("override-input-fullscreen-executor-welcome")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("editing in fullscreen fires onPromptOverridesChange", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click Overrides tab
|
||||
fireEvent.click(screen.getByTestId("tab-overrides"));
|
||||
|
||||
// Expand, go fullscreen, and type
|
||||
await user.click(screen.getByTestId("expand-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
await user.click(screen.getByTestId("fullscreen-executor-welcome"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("override-input-fullscreen-executor-welcome")).toBeTruthy();
|
||||
});
|
||||
|
||||
const textarea = screen.getByTestId("override-input-fullscreen-executor-welcome") as HTMLTextAreaElement;
|
||||
fireEvent.change(textarea, { target: { value: "Fullscreen edit" } });
|
||||
|
||||
// Verify onPromptOverridesChange was called with updated value
|
||||
await waitFor(() => {
|
||||
expect(onPromptOverridesChange).toHaveBeenCalled();
|
||||
const lastCall = onPromptOverridesChange.mock.calls[onPromptOverridesChange.mock.calls.length - 1];
|
||||
const newOverrides = lastCall[0];
|
||||
expect(newOverrides["executor-welcome"]).toBe("Fullscreen edit");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Templates tab fullscreen", () => {
|
||||
it("shows fullscreen button in template editor", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open template editor
|
||||
await user.click(screen.getByTestId("add-template-btn"));
|
||||
|
||||
// Fullscreen button should be visible
|
||||
expect(screen.getByTestId("template-prompt-fullscreen")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("clicking fullscreen button opens fullscreen view for template prompt", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open template editor
|
||||
await user.click(screen.getByTestId("add-template-btn"));
|
||||
|
||||
// Click fullscreen button
|
||||
await user.click(screen.getByTestId("template-prompt-fullscreen"));
|
||||
|
||||
// Fullscreen container should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("template-prompt-input-fullscreen")).toBeTruthy();
|
||||
expect(screen.getByTestId("template-prompt-collapse")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("Escape key exits template prompt fullscreen", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open template editor and go fullscreen
|
||||
await user.click(screen.getByTestId("add-template-btn"));
|
||||
await user.click(screen.getByTestId("template-prompt-fullscreen"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("template-prompt-input-fullscreen")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Press Escape
|
||||
const textarea = screen.getByTestId("template-prompt-input-fullscreen") as HTMLTextAreaElement;
|
||||
fireEvent.keyDown(textarea, { key: "Escape" });
|
||||
|
||||
// Fullscreen should be gone
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("template-prompt-input-fullscreen")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("saving template resets fullscreen state", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open template editor and go fullscreen
|
||||
await user.click(screen.getByTestId("add-template-btn"));
|
||||
await user.click(screen.getByTestId("template-prompt-fullscreen"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("template-prompt-input-fullscreen")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Fill in name and save
|
||||
await user.type(screen.getByTestId("template-name-input"), "My Template");
|
||||
await user.click(screen.getByTestId("save-template-btn"));
|
||||
|
||||
// Verify fullscreen is gone after save
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("template-prompt-input-fullscreen")).toBeNull();
|
||||
expect(screen.queryByTestId("template-editor")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it("canceling template edit resets fullscreen state", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<AgentPromptsManager
|
||||
value={defaultConfig}
|
||||
onChange={onChange}
|
||||
promptOverrides={{}}
|
||||
onPromptOverridesChange={onPromptOverridesChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Open template editor and go fullscreen
|
||||
await user.click(screen.getByTestId("add-template-btn"));
|
||||
await user.click(screen.getByTestId("template-prompt-fullscreen"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("template-prompt-input-fullscreen")).toBeTruthy();
|
||||
});
|
||||
|
||||
// Cancel
|
||||
await user.click(screen.getByTestId("cancel-template-btn"));
|
||||
|
||||
// Verify fullscreen is gone after cancel
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("template-prompt-input-fullscreen")).toBeNull();
|
||||
expect(screen.queryByTestId("template-editor")).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15950,6 +15950,136 @@ html .column.drag-over * {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* === Prompt Override Fullscreen === */
|
||||
|
||||
/* Header actions container for expand + fullscreen buttons */
|
||||
.prompt-override-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
/* Expand button in override header (only visible when expanded) */
|
||||
.prompt-override-fullscreen-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen-btn:hover {
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
/* Fullscreen container — fixed position overlay */
|
||||
.prompt-override-fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10000;
|
||||
background: var(--surface);
|
||||
padding: var(--space-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-md);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
background: none;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen-close:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Fullscreen textarea fills remaining space */
|
||||
.prompt-override-fullscreen textarea {
|
||||
flex: 1;
|
||||
min-height: unset;
|
||||
resize: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Fullscreen footer at bottom */
|
||||
.prompt-override-fullscreen .prompt-override-footer {
|
||||
flex-shrink: 0;
|
||||
padding-top: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Template prompt label row with fullscreen button */
|
||||
.prompt-template-prompt-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.prompt-template-prompt-label-row label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Template prompt fullscreen button */
|
||||
.prompt-template-fullscreen-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
vertical-align: middle;
|
||||
margin-left: var(--space-xs);
|
||||
}
|
||||
|
||||
.prompt-template-fullscreen-btn:hover {
|
||||
color: var(--text);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.prompt-manager-tabs {
|
||||
@@ -15979,6 +16109,15 @@ html .column.drag-over * {
|
||||
.prompt-template-editor-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Fullscreen prompt editor mobile overrides */
|
||||
.prompt-override-fullscreen {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.prompt-override-fullscreen textarea {
|
||||
font-size: 16px; /* Prevent iOS zoom */
|
||||
}
|
||||
}
|
||||
|
||||
.new-task-modal .form-group small {
|
||||
|
||||
Reference in New Issue
Block a user