diff --git a/e2e-tests/chat_mode.spec.ts b/e2e-tests/chat_mode.spec.ts index 329113b..d2ff33d 100644 --- a/e2e-tests/chat_mode.spec.ts +++ b/e2e-tests/chat_mode.spec.ts @@ -22,3 +22,37 @@ test("chat mode selector - ask mode", async ({ po }) => { await po.snapshotServerDump("all-messages"); await po.snapshotMessages({ replaceDumpPath: true }); }); + +test("dyadwrite edit and save - basic flow", async ({ po }) => { + await po.setUp({ autoApprove: true }); + await po.importApp("minimal"); + await po.clickNewChat(); + + await po.sendPrompt( + "Create a simple React component in src/components/Hello.tsx", + ); + await po.waitForChatCompletion(); + + await po.clickEditButton(); + await po.editFileContent("// Test modification\n"); + + await po.saveFile(); + + await po.snapshotMessages({ replaceDumpPath: true }); +}); + +test("dyadwrite edit and cancel", async ({ po }) => { + await po.setUp({ autoApprove: true }); + await po.importApp("minimal"); + await po.clickNewChat(); + + await po.sendPrompt("Create a utility function in src/utils/helper.ts"); + await po.waitForChatCompletion(); + + await po.clickEditButton(); + + await po.editFileContent("// This should be discarded\n"); + await po.cancelEdit(); + + await po.snapshotMessages({ replaceDumpPath: true }); +}); diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index 4bea2bb..8d3c6b7 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -431,6 +431,27 @@ export class PageObject { async clickRestart() { await this.page.getByRole("button", { name: "Restart" }).click(); } + //////////////////////////////// + // Inline code editor + //////////////////////////////// + async clickEditButton() { + await this.page.locator('button:has-text("Edit")').first().click(); + } + + async editFileContent(content: string) { + const editor = this.page.locator(".monaco-editor textarea").first(); + await editor.focus(); + await editor.press("Home"); + await editor.type(content); + } + + async saveFile() { + await this.page.locator('[data-testid="save-file-button"]').click(); + } + + async cancelEdit() { + await this.page.locator('button:has-text("Cancel")').first().click(); + } //////////////////////////////// // Preview panel diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-cancel-1.aria.yml b/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-cancel-1.aria.yml new file mode 100644 index 0000000..40baf11 --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-cancel-1.aria.yml @@ -0,0 +1,20 @@ +- paragraph: Create a utility function in src/utils/helper.ts +- img +- text: file1.txt +- button "Edit": + - img +- img +- text: file1.txt typescript +- button "Copy": + - img +- paragraph: More EOM +- img +- text: Approved +- img +- text: less than a minute ago +- img +- text: wrote 1 file(s) +- button "Undo": + - img +- button "Retry": + - img \ No newline at end of file diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-save---basic-flow-1.aria.yml b/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-save---basic-flow-1.aria.yml new file mode 100644 index 0000000..8a18bd3 --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_dyadwrite-edit-and-save---basic-flow-1.aria.yml @@ -0,0 +1,24 @@ +- paragraph: Create a simple React component in src/components/Hello.tsx +- img +- text: file1.txt +- button "Cancel": + - img +- img +- text: file1.txt file1.txt +- button [disabled]: + - img +- img +- code: + - textbox "Editor content" + - list +- paragraph: More EOM +- img +- text: Approved +- img +- text: less than a minute ago +- img +- text: wrote 1 file(s) +- button "Undo": + - img +- button "Retry": + - img \ No newline at end of file diff --git a/src/components/chat/DyadWrite.tsx b/src/components/chat/DyadWrite.tsx index 49dc09f..6b45709 100644 --- a/src/components/chat/DyadWrite.tsx +++ b/src/components/chat/DyadWrite.tsx @@ -7,9 +7,14 @@ import { Pencil, Loader, CircleX, + Edit, + X, } from "lucide-react"; import { CodeHighlight } from "./CodeHighlight"; import { CustomTagState } from "./stateTypes"; +import { FileEditor } from "../preview_panel/FileEditor"; +import { useAtomValue } from "jotai"; +import { selectedAppIdAtom } from "@/atoms/appAtoms"; interface DyadWriteProps { children?: ReactNode; @@ -30,9 +35,20 @@ export const DyadWrite: React.FC = ({ const path = pathProp || node?.properties?.path || ""; const description = descriptionProp || node?.properties?.description || ""; const state = node?.properties?.state as CustomTagState; - const inProgress = state === "pending"; - const aborted = state === "aborted"; + const aborted = state === "aborted"; + const appId = useAtomValue(selectedAppIdAtom); + const [isEditing, setIsEditing] = useState(false); + const inProgress = state === "pending"; + + const handleCancel = () => { + setIsEditing(false); + }; + + const handleEdit = () => { + setIsEditing(true); + setIsContentVisible(true); + }; // Extract filename from path const fileName = path ? path.split("/").pop() : ""; @@ -69,6 +85,35 @@ export const DyadWrite: React.FC = ({ )}
+ {!inProgress && ( + <> + {isEditing ? ( + <> + + + ) : ( + + )} + + )} {isContentVisible ? ( = ({ className="text-xs cursor-text" onClick={(e) => e.stopPropagation()} > - - {children} - + {isEditing ? ( +
+ +
+ ) : ( + + {children} + + )}
)}