feat(KB-234): add double-click edit and Escape key handling to SpecEditor

- Add double-click handler to SpecEditor for quick edit mode entry

- Implement Escape key handler to exit edit mode

- Add comprehensive double-click interaction tests

- Add CSS styles for improved editor UX
This commit is contained in:
gsxdsm
2026-03-30 19:09:52 -07:00
parent 3f2a86d4d5
commit 6e5befb73d
3 changed files with 74 additions and 2 deletions

View File

@@ -47,7 +47,7 @@ export function SpecEditor({
setFeedback("");
}, [onRequestRevision, feedback, isRequesting]);
// Keyboard shortcut: Ctrl/Cmd+Enter to save in edit mode
// Keyboard shortcut: Ctrl/Cmd+Enter to save in edit mode, Escape to cancel
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (isEditing && (e.metaKey || e.ctrlKey) && e.key === "Enter") {
@@ -55,6 +55,9 @@ export function SpecEditor({
if (canSave) {
void handleSave();
}
} else if (isEditing && e.key === "Escape") {
e.preventDefault();
handleCancelEdit();
}
};
@@ -72,6 +75,13 @@ export function SpecEditor({
setEditContent(content);
};
const handleDoubleClick = useCallback((e: React.MouseEvent) => {
if (!readOnly && !isEditing) {
e.stopPropagation();
handleEnterEditMode();
}
}, [readOnly, isEditing, content]);
const stripLeadingHeading = (text: string): string => {
return text.replace(/^#\s+[^\n]*\n+/, "");
};
@@ -130,7 +140,7 @@ export function SpecEditor({
placeholder="Enter task specification in Markdown..."
/>
) : content ? (
<div className="markdown-body">
<div className="markdown-body" onDoubleClick={handleDoubleClick}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{stripLeadingHeading(content)}
</ReactMarkdown>

View File

@@ -85,6 +85,63 @@ Test mission description.
expect(screen.getByText("Test mission description.")).toBeTruthy();
});
it("enters edit mode on double-click when not in readOnly mode", () => {
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
// Double-click on the markdown body
const markdownBody = document.querySelector(".markdown-body");
expect(markdownBody).toBeTruthy();
fireEvent.doubleClick(markdownBody!);
// Should now be in edit mode
expect(screen.getByRole("textbox")).toBeTruthy();
const editButton = screen.getByText("Edit") as HTMLButtonElement;
expect(editButton.classList.contains("btn-primary")).toBe(true);
});
it("does not enter edit mode on double-click when readOnly is true", () => {
render(<SpecEditor content={mockContent} readOnly={true} />);
// Double-click on the markdown body
const markdownBody = document.querySelector(".markdown-body");
expect(markdownBody).toBeTruthy();
fireEvent.doubleClick(markdownBody!);
// Should NOT be in edit mode (no textarea)
expect(screen.queryByRole("textbox")).toBeNull();
});
it("does not enter edit mode on double-click when already in edit mode", () => {
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
// Enter edit mode via Edit button
fireEvent.click(screen.getByText("Edit"));
expect(screen.getByRole("textbox")).toBeTruthy();
// Double-clicking again should not break anything
const markdownBody = document.querySelector(".markdown-body");
// Note: in edit mode, markdown-body is not rendered, so this query returns null
expect(markdownBody).toBeNull();
});
it("cancels edit mode when pressing Escape key", () => {
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);
// Enter edit mode
fireEvent.click(screen.getByText("Edit"));
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
// Make a change
fireEvent.change(textarea, { target: { value: "Changed content" } });
// Press Escape
fireEvent.keyDown(textarea, { key: "Escape" });
// Should be back in view mode
expect(screen.queryByRole("textbox")).toBeNull();
expect(screen.getByText("Test mission description.")).toBeTruthy();
});
it("disables Edit button when already in edit mode", () => {
render(<SpecEditor content={mockContent} onSave={mockOnSave} />);

View File

@@ -7857,6 +7857,11 @@ html .column.drag-over * {
overflow: hidden;
}
/* Double-click to edit cursor - only when not readOnly */
.spec-editor:not(.spec-editor-readonly) .markdown-body {
cursor: pointer;
}
/* Toolbar - fixed at top */
.spec-editor-toolbar {
display: flex;