diff --git a/e2e-tests/ai_e2e_testing.spec.ts b/e2e-tests/ai_e2e_testing.spec.ts index 09269dd9da..893dbfc191 100644 --- a/e2e-tests/ai_e2e_testing.spec.ts +++ b/e2e-tests/ai_e2e_testing.spec.ts @@ -99,8 +99,6 @@ testSkipIfWindows( await po.navigation.goToTemplatesAndSelectTemplate("Next.js Template"); await po.chatActions.selectChatMode("build"); await po.sendPrompt("tc=basic", { timeout: Timeout.EXTRA_LONG }); - await po.sendPrompt("tc=add-neon"); - // Connect a Neon project so runs get an isolated branch copy. await po.appManagement.startDatabaseIntegrationSetup("neon"); await po.appManagement.clickConnectNeonButton(); diff --git a/e2e-tests/approve.spec.ts b/e2e-tests/approve.spec.ts index da3631be16..326b476cea 100644 --- a/e2e-tests/approve.spec.ts +++ b/e2e-tests/approve.spec.ts @@ -1,14 +1,10 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; import { expect } from "@playwright/test"; -testSkipIfWindows("write to index, approve, check preview", async ({ po }) => { - await po.setUp(); +testSkipIfWindows("write to index and check preview", async ({ po }) => { + await po.setUp({ autoApprove: true }); await po.sendPrompt("tc=write-index"); await po.snapshotMessages(); - await po.approveProposal(); - - // Should be slightly different from above, because it will say "approved" - await po.snapshotMessages(); // This can be pretty slow because it's waiting for the app to build. await expect(po.previewPanel.getPreviewIframeElement()).toBeVisible({ diff --git a/e2e-tests/attach_image.spec.ts b/e2e-tests/attach_image.spec.ts index 654ae3e0f2..7dd4979bd6 100644 --- a/e2e-tests/attach_image.spec.ts +++ b/e2e-tests/attach_image.spec.ts @@ -102,9 +102,12 @@ test("attach image - chat - upload to codebase", async ({ po }) => { const fileChooser = await fileChooserPromise; await fileChooser.setFiles("e2e-tests/fixtures/images/logo.png"); - await po.sendPrompt("[[UPLOAD_IMAGE_TO_CODEBASE]]", { - timeout: Timeout.EXTRA_LONG, - }); + await po.sendPrompt( + "tc=local-agent/upload-image-to-codebase [[UPLOAD_IMAGE_TO_CODEBASE]]", + { + timeout: Timeout.EXTRA_LONG, + }, + ); // Wait for the uploaded file card to render before snapshotting. Use .first() // because the modified-files card at the bottom of the chat now also lists @@ -113,7 +116,6 @@ test("attach image - chat - upload to codebase", async ({ po }) => { po.page.getByText("file.png", { exact: true }).first(), ).toBeVisible(); - await po.snapshotServerDump("last-message", { name: "upload-to-codebase" }); await po.snapshotMessages({ replaceDumpPath: true }); // new/image/file.png diff --git a/e2e-tests/auto_approve.spec.ts b/e2e-tests/auto_approve.spec.ts index 5e68555f26..055a51af57 100644 --- a/e2e-tests/auto_approve.spec.ts +++ b/e2e-tests/auto_approve.spec.ts @@ -1,7 +1,7 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; import { expect } from "@playwright/test"; -testSkipIfWindows("auto-approve", async ({ po }) => { +testSkipIfWindows("agentic Build applies changes", async ({ po }) => { await po.setUp({ autoApprove: true }); await po.sendPrompt("tc=write-index"); await po.snapshotMessages(); diff --git a/e2e-tests/concurrent_chat.spec.ts b/e2e-tests/concurrent_chat.spec.ts index 315645dd79..8315191a03 100644 --- a/e2e-tests/concurrent_chat.spec.ts +++ b/e2e-tests/concurrent_chat.spec.ts @@ -3,25 +3,26 @@ import { expect } from "@playwright/test"; test("concurrent chat", async ({ po }) => { await po.setUp(); - await po.sendPrompt("tc=chat1 [sleep=medium]", { + await po.sendPrompt("tc=chat1 [sleep=long]", { skipWaitForCompletion: true, }); await expect .poll(() => new URL(po.page.url()).searchParams.get("id")) .not.toBeNull(); + const chat1Id = new URL(po.page.url()).searchParams.get("id"); + expect(chat1Id).not.toBeNull(); await po.chatActions.clickNewChat(); await po.sendPrompt("tc=chat2"); await po.snapshotMessages(); - // Chat #1 tab should be visible in the chat tabs with an "in progress" indicator - // Find the tab that contains the "Chat in progress" indicator and click it - const chat1TabContainer = po.page - .locator('[aria-label="Chat in progress"]') - .locator("xpath=ancestor::div[@draggable][1]"); - await expect(chat1TabContainer).toBeVisible(); - - // Click the button inside the tab to select it - await chat1TabContainer.locator("button").first().click(); + // The background turn may complete before the foreground chat settles, but + // both chat tabs must remain independently selectable. + const chat1Tab = po.page + .getByTestId(`chat-tab-${chat1Id}`) + .locator("button") + .first(); + await expect(chat1Tab).toBeVisible(); + await chat1Tab.click(); await po.snapshotMessages({ timeout: 12_000 }); }); diff --git a/e2e-tests/context_manage.spec.ts b/e2e-tests/context_manage.spec.ts deleted file mode 100644 index 4b102e4a0e..0000000000 --- a/e2e-tests/context_manage.spec.ts +++ /dev/null @@ -1,510 +0,0 @@ -import fs from "fs"; - -import { expect } from "@playwright/test"; - -import { test, type PageObject } from "./helpers/test_helper"; - -type DumpContentPart = { - text?: string; -}; - -type DumpMessage = { - role: string; - content: string | DumpContentPart[]; -}; - -type DumpFileReference = { - path: string; - force?: boolean; -}; - -type DumpJson = { - body: { - input?: DumpMessage[]; - messages?: DumpMessage[]; - dyad_options?: { - enable_smart_files_context?: boolean; - smart_context_mode?: string; - files?: DumpFileReference[]; - versioned_files?: { - fileReferences?: DumpFileReference[]; - }; - }; - }; -}; - -const FULL_CODEBASE_PATHS = [ - ".env.foobar", - "AI_RULES.md", - "a.ts", - "exclude/exclude.ts", - "exclude/exclude.tsx", - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", - "src/components/ui/button.tsx", - "src/components/ui/helper.ts", - "src/dir/some.css", - "src/foo.ts", - "src/sub/sub1.ts", - "src/sub/sub2.tsx", - "src/very-large-file.ts", -]; - -const MANUAL_SRC_CONTEXT_PATHS = [ - "src/components/ui/helper.ts", - "src/foo.ts", - "src/sub/sub1.ts", - "src/sub/sub2.tsx", - "src/very-large-file.ts", -]; - -const MANUAL_AND_SMART_CONTEXT_PATHS = [ - "a.ts", - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", - ...MANUAL_SRC_CONTEXT_PATHS, -]; - -const FORCED_AUTO_INCLUDE_PATHS = [ - "a.ts", - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", -]; - -const FORCED_SMART_EXCLUDE_PATHS = ["a.ts", "exclude/exclude.tsx"]; - -async function readDump(po: PageObject, dumpIndex = -1): Promise { - await po.chatActions.waitForChatCompletion(); - await expect(po.page.getByTestId("messages-list")).toContainText( - "[[dyad-dump-path=", - ); - - const messagesListText = await po.page - .getByTestId("messages-list") - .textContent(); - const dumpPathMatches = [ - ...(messagesListText?.matchAll(/\[\[dyad-dump-path=([^\]]+)\]\]/g) ?? []), - ]; - const selectedIndex = - dumpIndex < 0 ? dumpPathMatches.length + dumpIndex : dumpIndex; - const dumpMatch = dumpPathMatches[selectedIndex]; - - expect(dumpMatch?.[1]).toBeTruthy(); - - return JSON.parse(fs.readFileSync(dumpMatch![1], "utf-8")) as DumpJson; -} - -function getMessageText(content: DumpMessage["content"]): string { - if (typeof content === "string") { - return content; - } - - return content.map((part) => part.text ?? "").join("\n"); -} - -function getIncludedFiles(dump: DumpJson): DumpFileReference[] { - const versionedFiles = - dump.body.dyad_options?.versioned_files?.fileReferences; - if (versionedFiles?.length) { - return versionedFiles; - } - - const files = dump.body.dyad_options?.files; - if (files?.length) { - return files; - } - - const messages = dump.body.input ?? dump.body.messages ?? []; - const messagePaths = messages.flatMap(({ content }) => - [...getMessageText(content).matchAll(//g)].map( - (match) => match[1], - ), - ); - - return [...new Set(messagePaths)].map((path) => ({ path, force: false })); -} - -function getIncludedPaths(dump: DumpJson): string[] { - return getIncludedFiles(dump) - .map(({ path }) => path) - .sort(); -} - -function getForcedPaths(dump: DumpJson): string[] { - return getIncludedFiles(dump) - .filter(({ force }) => force) - .map(({ path }) => path) - .sort(); -} - -function expectIncludedPaths(dump: DumpJson, expectedPaths: string[]) { - expect(getIncludedPaths(dump)).toEqual([...expectedPaths].sort()); -} - -function expectForcedPaths(dump: DumpJson, expectedPaths: string[]) { - expect(getForcedPaths(dump)).toEqual([...expectedPaths].sort()); -} - -async function expectVisiblePaths(po: PageObject, paths: string[]) { - const dialog = po.page.getByRole("dialog"); - for (const path of paths) { - await expect(dialog.getByText(path, { exact: true })).toBeVisible(); - } -} - -async function expectAbsentPaths(po: PageObject, paths: string[]) { - const dialog = po.page.getByRole("dialog"); - for (const path of paths) { - await expect(dialog.getByText(path, { exact: true })).toHaveCount(0); - } -} - -async function addPathAndWait( - po: PageObject, - addPath: () => Promise, - path: string, -) { - await addPath(); - await expectVisiblePaths(po, [path]); -} - -async function removeFirstPath(po: PageObject, removeButtonTestId: string) { - const removeButtons = po.page.getByTestId(removeButtonTestId); - const initialCount = await removeButtons.count(); - - expect(initialCount).toBeGreaterThan(0); - await removeButtons.first().click(); - await expect(removeButtons).toHaveCount(initialCount - 1); -} - -async function removeAllPaths(po: PageObject, removeButtonTestId: string) { - while ((await po.page.getByTestId(removeButtonTestId).count()) > 0) { - await removeFirstPath(po, removeButtonTestId); - } -} - -test("manage context - default", async ({ po }) => { - await po.setUp(); - await po.importApp("context-manage"); - - const dialog = await po.openContextFilesPicker(); - await expect(po.page.getByRole("dialog")).toContainText( - "Dyad will use the entire codebase as context.", - ); - await addPathAndWait( - po, - () => dialog.addManualContextFile("DELETETHIS"), - "DELETETHIS", - ); - await removeFirstPath(po, "manual-context-files-remove-button"); - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/**/*.ts"), - "src/**/*.ts", - ); - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/sub/**"), - "src/sub/**", - ); - await expectVisiblePaths(po, ["src/**/*.ts", "src/sub/**"]); - await expectAbsentPaths(po, ["DELETETHIS"]); - await expect( - po.page.getByTestId("manual-context-files-remove-button"), - ).toHaveCount(2); - await dialog.close(); - - await po.sendPrompt("[dump]"); - - expectIncludedPaths(await readDump(po), MANUAL_SRC_CONTEXT_PATHS); -}); - -test("manage context - smart context", async ({ po }) => { - await po.setUpDyadPro(); - await po.modelPicker.selectModel({ - provider: "Google", - model: "Gemini 2.5 Pro", - }); - await po.importApp("context-manage"); - - let dialog = await po.openContextFilesPicker(); - await expect(po.page.getByRole("dialog")).toContainText( - "Dyad will use Smart Context to automatically find the most relevant files to use as context.", - ); - await expect(po.page.getByRole("dialog")).toContainText( - "Smart Context Auto-includes", - ); - - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/**/*.ts"), - "src/**/*.ts", - ); - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/sub/**"), - "src/sub/**", - ); - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("a.ts"), - "a.ts", - ); - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("manual/**"), - "manual/**", - ); - await expectVisiblePaths(po, [ - "src/**/*.ts", - "src/sub/**", - "a.ts", - "manual/**", - ]); - await expect( - po.page.getByTestId("manual-context-files-remove-button"), - ).toHaveCount(2); - await expect( - po.page.getByTestId("auto-include-context-files-remove-button"), - ).toHaveCount(2); - await dialog.close(); - - await po.sendPrompt("[dump]"); - - const smartContextDump = await readDump(po); - expect(smartContextDump.body.dyad_options?.enable_smart_files_context).toBe( - true, - ); - expect(smartContextDump.body.dyad_options?.smart_context_mode).toBe("deep"); - expectIncludedPaths(smartContextDump, MANUAL_AND_SMART_CONTEXT_PATHS); - expectForcedPaths(smartContextDump, FORCED_AUTO_INCLUDE_PATHS); - - // Disabling smart context will automatically disable - // the auto-includes. - const proModesDialog = await po.openProModesDialog(); - await proModesDialog.setSmartContextMode("off"); - await expect - .poll(() => po.settings.recordSettings().enableProSmartFilesContextMode) - .toBe(false); - await proModesDialog.close(); - - await po.sendPrompt("[dump]"); - const balancedDump = await readDump(po); - expect(balancedDump.body.dyad_options?.enable_smart_files_context).toBe( - false, - ); - expect(balancedDump.body.dyad_options?.smart_context_mode).toBe("balanced"); - expectIncludedPaths(balancedDump, MANUAL_SRC_CONTEXT_PATHS); - - // Removing manual context files will result in all files being included. - dialog = await po.openContextFilesPicker(); - await removeAllPaths(po, "manual-context-files-remove-button"); - await expect( - po.page.getByTestId("manual-context-files-remove-button"), - ).toHaveCount(0); - await expect( - po.page.getByTestId("auto-include-context-files-remove-button"), - ).toHaveCount(0); - await dialog.close(); - - await po.sendPrompt("[dump]"); - const fullCodebaseDump = await readDump(po); - expect(fullCodebaseDump.body.dyad_options?.enable_smart_files_context).toBe( - false, - ); - expectIncludedPaths(fullCodebaseDump, FULL_CODEBASE_PATHS); -}); - -test("manage context - smart context - auto-includes only", async ({ po }) => { - await po.setUpDyadPro(); - await po.modelPicker.selectModel({ - provider: "Google", - model: "Gemini 2.5 Pro", - }); - await po.importApp("context-manage"); - - const dialog = await po.openContextFilesPicker(); - await expect(po.page.getByRole("dialog")).toContainText( - "Smart Context Auto-includes", - ); - - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("a.ts"), - "a.ts", - ); - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("manual/**"), - "manual/**", - ); - await expectVisiblePaths(po, ["a.ts", "manual/**"]); - await expect( - po.page.getByTestId("auto-include-context-files-remove-button"), - ).toHaveCount(2); - await dialog.close(); - - await po.sendPrompt("[dump]"); - - const dump = await readDump(po); - expect(dump.body.dyad_options?.enable_smart_files_context).toBe(true); - expect(dump.body.dyad_options?.smart_context_mode).toBe("deep"); - expectForcedPaths(dump, FORCED_AUTO_INCLUDE_PATHS); -}); - -test("manage context - exclude paths", async ({ po }) => { - await po.setUp(); - await po.importApp("context-manage"); - - const dialog = await po.openContextFilesPicker(); - await expect(po.page.getByRole("dialog")).toContainText("Exclude Paths"); - - // Add some include paths first - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/**/*.ts"), - "src/**/*.ts", - ); - await addPathAndWait( - po, - () => dialog.addManualContextFile("manual/**"), - "manual/**", - ); - - // Add exclude paths - await addPathAndWait( - po, - () => dialog.addExcludeContextFile("src/components/**"), - "src/components/**", - ); - await addPathAndWait( - po, - () => dialog.addExcludeContextFile("manual/exclude/**"), - "manual/exclude/**", - ); - await expectVisiblePaths(po, [ - "src/**/*.ts", - "manual/**", - "src/components/**", - "manual/exclude/**", - ]); - await expect( - po.page.getByTestId("manual-context-files-remove-button"), - ).toHaveCount(2); - await expect( - po.page.getByTestId("exclude-context-files-remove-button"), - ).toHaveCount(2); - await dialog.close(); - - await po.sendPrompt("[dump]"); - expectIncludedPaths(await readDump(po), [ - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", - "src/foo.ts", - "src/sub/sub1.ts", - "src/very-large-file.ts", - ]); - - // Test that exclude paths take precedence over include paths - const dialog2 = await po.openContextFilesPicker(); - await removeFirstPath(po, "exclude-context-files-remove-button"); // Remove src/components/** - await addPathAndWait( - po, - () => dialog2.addExcludeContextFile("src/**"), - "src/**", - ); // This should exclude everything from src - await expectVisiblePaths(po, ["manual/**", "manual/exclude/**", "src/**"]); - await expectAbsentPaths(po, ["src/components/**"]); - await dialog2.close(); - - await po.sendPrompt("[dump]"); - expectIncludedPaths(await readDump(po), [ - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", - ]); -}); - -test("manage context - exclude paths with smart context", async ({ po }) => { - await po.setUpDyadPro(); - await po.modelPicker.selectModel({ - provider: "Google", - model: "Gemini 2.5 Pro", - }); - await po.importApp("context-manage"); - - const dialog = await po.openContextFilesPicker(); - await expect(po.page.getByRole("dialog")).toContainText("Exclude Paths"); - - // Add manual context files - await addPathAndWait( - po, - () => dialog.addManualContextFile("src/**/*.ts"), - "src/**/*.ts", - ); - await addPathAndWait( - po, - () => dialog.addManualContextFile("manual/**"), - "manual/**", - ); - - // Add smart context auto-includes - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("a.ts"), - "a.ts", - ); - await addPathAndWait( - po, - () => dialog.addAutoIncludeContextFile("exclude/**"), - "exclude/**", - ); - - // Add exclude paths that should filter out some of the above - await addPathAndWait( - po, - () => dialog.addExcludeContextFile("src/components/**"), - "src/components/**", - ); - await addPathAndWait( - po, - () => dialog.addExcludeContextFile("exclude/exclude.ts"), - "exclude/exclude.ts", - ); - await expectVisiblePaths(po, [ - "src/**/*.ts", - "manual/**", - "a.ts", - "exclude/**", - "src/components/**", - "exclude/exclude.ts", - ]); - await dialog.close(); - - await po.sendPrompt("[dump]"); - const dump = await readDump(po); - const includedPaths = getIncludedPaths(dump); - - expect(dump.body.dyad_options?.enable_smart_files_context).toBe(true); - expect(includedPaths).toEqual( - expect.arrayContaining([ - "a.ts", - "exclude/exclude.tsx", - "manual/baz.json", - "manual/file.ts", - "manual/sub-manual/sub-manual.js", - "src/foo.ts", - "src/sub/sub1.ts", - "src/very-large-file.ts", - ]), - ); - expect(getForcedPaths(dump)).toEqual( - expect.arrayContaining(FORCED_SMART_EXCLUDE_PATHS), - ); - expect(includedPaths).not.toContain("exclude/exclude.ts"); - expect(includedPaths).not.toContain("src/components/ui/helper.ts"); -}); diff --git a/e2e-tests/context_window.spec.ts b/e2e-tests/context_window.spec.ts deleted file mode 100644 index 3beff75235..0000000000 --- a/e2e-tests/context_window.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { testSkipIfWindows } from "./helpers/test_helper"; - -testSkipIfWindows("context window", async ({ po }) => { - await po.setUp(); - await po.sendPrompt("tc=1"); - await po.sendPrompt("tc=2"); - await po.sendPrompt("[dump] tc=3"); - await po.snapshotServerDump(); - await po.sendPrompt("[dump] tc=4"); - await po.snapshotServerDump(); - await po.sendPrompt("[dump] tc=5"); - await po.snapshotServerDump(); - - await po.navigation.goToSettingsTab(); - const beforeSettings = po.settings.recordSettings(); - await po.page - .getByRole("combobox", { name: "Max Chat Turns in Context" }) - .click(); - await po.page.getByRole("option", { name: "Plus (5)" }).click(); - - // close combobox - // await po.page.keyboard.press("Escape"); - po.settings.snapshotSettingsDelta(beforeSettings); - await po.page.getByText("Go Back").click(); - - await po.sendPrompt("[dump] tc=6"); - await po.snapshotServerDump(); -}); diff --git a/e2e-tests/database_url_guide.spec.ts b/e2e-tests/database_url_guide.spec.ts index 2f6ad5218d..9e0c5d1f8c 100644 --- a/e2e-tests/database_url_guide.spec.ts +++ b/e2e-tests/database_url_guide.spec.ts @@ -8,8 +8,6 @@ testSkipIfWindows( await po.navigation.goToTemplatesAndSelectTemplate("Next.js Template"); await po.chatActions.selectChatMode("build"); await po.sendPrompt("tc=basic", { timeout: Timeout.EXTRA_LONG }); - await po.sendPrompt("tc=add-neon"); - await po.appManagement.startDatabaseIntegrationSetup("neon"); await po.appManagement.clickConnectNeonButton(); // Reaching the project picker proves the OAuth return advanced through diff --git a/e2e-tests/debugging_logs.spec.ts b/e2e-tests/debugging_logs.spec.ts index 707754a17f..2e7ba2930c 100644 --- a/e2e-tests/debugging_logs.spec.ts +++ b/e2e-tests/debugging_logs.spec.ts @@ -4,10 +4,9 @@ import { expect } from "@playwright/test"; testSkipIfWindows( "console logs should appear in the console", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.sendPrompt("tc=console-logs"); - await po.approveProposal(); // Wait for app to run const picker = po.page.getByTestId("preview-pick-element-button"); @@ -87,10 +86,9 @@ testSkipIfWindows( testSkipIfWindows( "network requests and responses should appear in the console", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.sendPrompt("tc=network-requests"); - await po.approveProposal(); // Wait for app to run const picker = po.page.getByTestId("preview-pick-element-button"); @@ -105,6 +103,17 @@ testSkipIfWindows( timeout: Timeout.MEDIUM, }); + // The first render can issue requests before the preview service worker + // takes control. Reload once it is ready so request/response events are + // deterministically observed by the recorder. + await iframeFrame.locator("body").evaluate(async () => { + await navigator.serviceWorker.ready; + }); + await po.previewPanel.clickPreviewRefresh(); + await expect( + iframeFrame.getByText("Network Requests Test App"), + ).toBeVisible({ timeout: Timeout.MEDIUM }); + // Wait for service worker to be ready // Service worker registration is async, so we wait for it to be active // We check by waiting for network request logs to appear, which indicates SW is ready @@ -182,11 +191,10 @@ testSkipIfWindows( testSkipIfWindows( "clicking send to chat button adds log to chat input", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); // Create an app with console output using fixture await po.sendPrompt("tc=write-index"); - await po.approveProposal(); // Wait for app to run const picker = po.page.getByTestId("preview-pick-element-button"); @@ -217,11 +225,10 @@ testSkipIfWindows( ); testSkipIfWindows("clear filters button works", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); // Create a basic app using fixture await po.sendPrompt("tc=write-index"); - await po.approveProposal(); // Wait for app to run await po.page @@ -251,11 +258,10 @@ testSkipIfWindows("clear filters button works", async ({ po }) => { }); testSkipIfWindows("clear logs button clears all logs", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); // Create an app with console logs await po.sendPrompt("tc=console-logs"); - await po.approveProposal(); // Wait for app to run const picker = po.page.getByTestId("preview-pick-element-button"); diff --git a/e2e-tests/dyad_tags_parsing.spec.ts b/e2e-tests/dyad_tags_parsing.spec.ts index 892400868a..b18ecad33c 100644 --- a/e2e-tests/dyad_tags_parsing.spec.ts +++ b/e2e-tests/dyad_tags_parsing.spec.ts @@ -4,5 +4,8 @@ testSkipIfWindows("dyad tags handles nested < tags", async ({ po }) => { await po.setUp({ autoApprove: true }); await po.importApp("minimal"); await po.sendPrompt("tc=dyad-write-angle"); - await po.snapshotAppFiles({ name: "angle-tags-handled" }); + await po.snapshotAppFiles({ + name: "angle-tags-handled", + files: ["src/foo/bar.tsx"], + }); }); diff --git a/e2e-tests/engine.spec.ts b/e2e-tests/engine.spec.ts index 019be9bbf1..7185b35216 100644 --- a/e2e-tests/engine.spec.ts +++ b/e2e-tests/engine.spec.ts @@ -48,19 +48,3 @@ testSkipIfWindows( await po.snapshotMessages({ replaceDumpPath: true }); }, ); - -testSkipIfWindows( - "regular auto should send message to engine", - async ({ po }) => { - await po.setUpDyadPro(); - const proModesDialog = await po.openProModesDialog({ - location: "home-chat-input-container", - }); - await proModesDialog.setSmartContextMode("off"); - await proModesDialog.close(); - await po.sendPrompt("[dump] tc=turbo-edits"); - - await po.snapshotServerDump("request"); - await po.snapshotMessages({ replaceDumpPath: true }); - }, -); diff --git a/e2e-tests/fixtures/engine/local-agent/basic-response.ts b/e2e-tests/fixtures/engine/local-agent/basic-response.ts new file mode 100644 index 0000000000..649740a0cd --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/basic-response.ts @@ -0,0 +1,6 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Return a simple text-only response", + turns: [{ text: "This is a simple basic response" }], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/dyad-write-angle.ts b/e2e-tests/fixtures/engine/local-agent/dyad-write-angle.ts new file mode 100644 index 0000000000..7090cb0a58 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/dyad-write-angle.ts @@ -0,0 +1,21 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Write a file whose legacy description contained angle brackets", + turns: [ + { + text: "BEFORE TOOL", + toolCalls: [ + { + name: "write_file", + args: { + path: "src/foo/bar.tsx", + content: "// BEGINNING OF FILE\n", + description: "page to use and tags", + }, + }, + ], + }, + { text: "AFTER TOOL" }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/execute-sql-no-description.ts b/e2e-tests/fixtures/engine/local-agent/execute-sql-no-description.ts new file mode 100644 index 0000000000..5b7a2eb9da --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/execute-sql-no-description.ts @@ -0,0 +1,17 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Drop a users table through the SQL tool", + turns: [ + { + text: "No description!", + toolCalls: [ + { + name: "execute_sql", + args: { query: "DROP TABLE users;" }, + }, + ], + }, + { text: "Done." }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/execute-sql.ts b/e2e-tests/fixtures/engine/local-agent/execute-sql.ts new file mode 100644 index 0000000000..7aac16448c --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/execute-sql.ts @@ -0,0 +1,20 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Create a users table through the SQL tool", + turns: [ + { + text: "Example SQL", + toolCalls: [ + { + name: "execute_sql", + args: { + query: "CREATE TABLE users (id serial primary key);", + description: "create_users_table", + }, + }, + ], + }, + { text: "Done." }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/fix-all-runtime-errors.ts b/e2e-tests/fixtures/engine/local-agent/fix-all-runtime-errors.ts new file mode 100644 index 0000000000..4aa8bbec48 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/fix-all-runtime-errors.ts @@ -0,0 +1,21 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Fix all deterministic runtime errors", + turns: [ + { + text: "Fixing all reported errors.", + toolCalls: [ + { + name: "write_file", + args: { + path: "file1.txt", + content: "A file (2)\n", + description: "Record the runtime error fix", + }, + }, + ], + }, + { text: "All reported errors are fixed." }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/fix-runtime-error.ts b/e2e-tests/fixtures/engine/local-agent/fix-runtime-error.ts new file mode 100644 index 0000000000..da342dc742 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/fix-runtime-error.ts @@ -0,0 +1,35 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Fix the deterministic runtime error fixture", + turns: [ + { + text: "Fixing the error...", + toolCalls: [ + { + name: "write_file", + args: { + path: "src/pages/Index.tsx", + content: `import { MadeWithDyad } from "@/components/made-with-dyad"; + +const Index = () => { + return ( +
+
+

No more errors!

+
+ +
+ ); +}; + +export default Index; +`, + description: "Fix the runtime error", + }, + }, + ], + }, + { text: "The runtime error is fixed." }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/fix-typescript-errors.ts b/e2e-tests/fixtures/engine/local-agent/fix-typescript-errors.ts new file mode 100644 index 0000000000..b554c493d6 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/fix-typescript-errors.ts @@ -0,0 +1,24 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Fix the deterministic TypeScript problem fixture", + turns: [ + { + text: "Fixing the selected TypeScript errors.", + toolCalls: [ + { + name: "write_file", + args: { + path: "src/bad-file.tsx", + content: `const App = () =>
Minimal imported app
; + +export default App; +`, + description: "Fix the selected TypeScript errors", + }, + }, + ], + }, + { text: "The selected TypeScript errors are fixed." }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/main-write.ts b/e2e-tests/fixtures/engine/local-agent/main-write.ts new file mode 100644 index 0000000000..4809d48c11 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/main-write.ts @@ -0,0 +1,21 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Write the main chat-flow smoke-test file", + turns: [ + { + text: "I'll create the file.", + toolCalls: [ + { + name: "write_file", + args: { + path: "file1.txt", + content: "A file (2)\n", + description: "Create the test file", + }, + }, + ], + }, + { text: "EOM" }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/security-fix.ts b/e2e-tests/fixtures/engine/local-agent/security-fix.ts index 2ac662f15c..825c34ec61 100644 --- a/e2e-tests/fixtures/engine/local-agent/security-fix.ts +++ b/e2e-tests/fixtures/engine/local-agent/security-fix.ts @@ -4,23 +4,20 @@ export const fixture: LocalAgentFixture = { description: "Fix a security issue in the codebase", turns: [ { - text: "I'll fix the security issue by removing the hardcoded secret and using environment variables instead.", + text: "I'll apply the security fix now.", toolCalls: [ { - name: "search_replace", + name: "write_file", args: { - path: "src/App.tsx", - search: "const App = () =>
Minimal imported app
;", - replace: - "const App = () =>
Secure app with env vars
;", + path: "file1.txt", + content: "security fix\n", description: "Fix security vulnerability", }, }, ], }, { - text: "I've fixed the security issue by replacing the hardcoded value with a more secure implementation using environment variables.", + text: "I've applied and committed the security fix. EOM", }, ], }; - diff --git a/e2e-tests/fixtures/engine/local-agent/upload-image-to-codebase.ts b/e2e-tests/fixtures/engine/local-agent/upload-image-to-codebase.ts new file mode 100644 index 0000000000..f3d2b5abd7 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/upload-image-to-codebase.ts @@ -0,0 +1,23 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Upload an attached image to the requested codebase path", + turns: [ + { + text: "I'll upload your image to the codebase.", + toolCalls: [ + { + name: "copy_file", + args: { + from: "attachments:logo.png", + to: "new/image/file.png", + description: "Copy uploaded image to codebase", + }, + }, + ], + }, + { + text: "I've copied the image to new/image/file.png.", + }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/write-index-2.ts b/e2e-tests/fixtures/engine/local-agent/write-index-2.ts new file mode 100644 index 0000000000..9a1d26b536 --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/write-index-2.ts @@ -0,0 +1,26 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Write the second Index page version", + turns: [ + { + text: "OK, I'm going to update the page now...", + toolCalls: [ + { + name: "write_file", + args: { + path: "src/pages/Index.tsx", + content: `const Index = () => { + return
Testing:write-index(2)!
; +}; + +export default Index; +`, + description: "write-description", + }, + }, + ], + }, + { text: "And it's done!" }, + ], +}; diff --git a/e2e-tests/fixtures/engine/local-agent/write-index.ts b/e2e-tests/fixtures/engine/local-agent/write-index.ts new file mode 100644 index 0000000000..606923ebec --- /dev/null +++ b/e2e-tests/fixtures/engine/local-agent/write-index.ts @@ -0,0 +1,26 @@ +import type { LocalAgentFixture } from "../../../../testing/fake-llm-server/localAgentTypes"; + +export const fixture: LocalAgentFixture = { + description: "Write the first Index page version", + turns: [ + { + text: "OK, I'm going to do some writing now...", + toolCalls: [ + { + name: "write_file", + args: { + path: "src/pages/Index.tsx", + content: `const Index = () => { + return
Testing:write-index!
; +}; + +export default Index; +`, + description: "write-description", + }, + }, + ], + }, + { text: "And it's done!" }, + ], +}; diff --git a/e2e-tests/helpers/page-objects/PageObject.ts b/e2e-tests/helpers/page-objects/PageObject.ts index c53de8e570..11fc8061ab 100644 --- a/e2e-tests/helpers/page-objects/PageObject.ts +++ b/e2e-tests/helpers/page-objects/PageObject.ts @@ -39,16 +39,17 @@ import { Catalog } from "./components/Catalog"; import { BrowserNotifications } from "./components/BrowserNotifications"; // Import dialog page objects -import { ContextFilesPickerDialog } from "./dialogs/ContextFilesPickerDialog"; -import { ProModesDialog } from "./dialogs/ProModesDialog"; import { Timeout } from "../constants"; const IGNORED_SNAPSHOT_FILE_PATHS = new Set([".gitattributes"]); function isIgnoredSnapshotFile(filePath: string | undefined): boolean { + const normalizedPath = + typeof filePath === "string" ? normalizePath(filePath) : undefined; return ( - typeof filePath === "string" && - IGNORED_SNAPSHOT_FILE_PATHS.has(normalizePath(filePath)) + normalizedPath !== undefined && + (IGNORED_SNAPSHOT_FILE_PATHS.has(normalizedPath) || + normalizedPath.startsWith(".dyad/")) ); } @@ -323,6 +324,33 @@ export class PageObject { await this.githubConnector.resetRepos(); } + private async setAgentToolAutoApprove(autoApprove: boolean) { + const consent = autoApprove ? "always" : "ask"; + await this.page.evaluate( + async ({ consent }) => { + const mutatingToolNames = [ + "write_file", + "search_replace", + "copy_file", + "delete_file", + "rename_file", + "add_dependency", + "execute_sql", + "add_integration", + "enable_nitro", + "restart_app", + "reinstall_and_restart_app", + ]; + await (window as any).electron.ipcRenderer.invoke("set-user-settings", { + agentToolConsents: Object.fromEntries( + mutatingToolNames.map((toolName) => [toolName, consent]), + ), + }); + }, + { consent }, + ); + } + async pinBuildChatModeForSetup() { await this.page.evaluate(async () => { await (window as any).electron.ipcRenderer.invoke("set-user-settings", { @@ -353,12 +381,13 @@ export class PageObject { } = {}) { await this.baseSetup(); await this.navigation.goToSettingsTab(); - if (autoApprove) { - await this.settings.toggleAutoApprove(); - } + await this.setAgentToolAutoApprove(autoApprove); await this.settings.setUpTestProvider(); await this.settings.setUpTestModel(); if (!enableBasicAgent) { + // Most legacy Build E2Es predate the blueprint workflow. Blueprint tests + // use Local Agent setup and explicitly opt the current app back in. + await this.settings.disableAppBlueprint(); await this.pinBuildChatModeForSetup(); } await this.navigation.goToAppsTab(); @@ -382,11 +411,10 @@ export class PageObject { } = {}) { await this.baseSetup(); await this.navigation.goToSettingsTab(); - if (autoApprove) { - await this.settings.toggleAutoApprove(); - } + await this.setAgentToolAutoApprove(autoApprove); await this.settings.setUpDyadProvider(); if (!localAgent) { + await this.settings.disableAppBlueprint(); await this.pinBuildChatModeForSetup(); } await this.navigation.goToAppsTab(); @@ -409,9 +437,8 @@ export class PageObject { async setUpAzure({ autoApprove = false }: { autoApprove?: boolean } = {}) { await this.githubConnector.clearPushEvents(); await this.navigation.goToSettingsTab(); - if (autoApprove) { - await this.settings.toggleAutoApprove(); - } + await this.setAgentToolAutoApprove(autoApprove); + await this.settings.disableAppBlueprint(); // Azure should already be configured via environment variables // so we don't need additional setup steps like setUpDyadProvider await this.navigation.goToAppsTab(); @@ -421,88 +448,6 @@ export class PageObject { // Dialog Openers // ================================ - async openContextFilesPicker() { - // Programmatically dismiss toasts using the sonner API by clicking any visible close buttons - const toastCloseButtons = this.page.locator( - "[data-sonner-toast] button[data-close-button]", - ); - const maxAttempts = 20; - let attempts = 0; - while ((await toastCloseButtons.count()) > 0 && attempts < maxAttempts) { - await toastCloseButtons - .first() - .click() - .catch(() => {}); - attempts++; - } - - // If close buttons don't work, click outside to dismiss - if ((await this.page.locator("[data-sonner-toast]").count()) > 0) { - // Click somewhere safe to dismiss toasts - await this.page.mouse.click(10, 10); - await this.page.waitForTimeout(300); - } - - // Open the auxiliary actions menu - await this.chatActions - .getChatInputContainer() - .getByTestId("auxiliary-actions-menu") - .click(); - - // Click on "Codebase context" to open the popover - await this.page.getByTestId("codebase-context-trigger").click(); - - // Wait for the popover content to be visible - await this.page - .getByTestId("manual-context-files-input") - .waitFor({ state: "visible" }); - - return new ContextFilesPickerDialog(this.page, async () => { - // Close the popover first - await this.page.keyboard.press("Escape"); - // Wait a bit for the popover to close, then close the dropdown menu - await this.page - .getByTestId("manual-context-files-input") - .waitFor({ state: "hidden" }); - await this.page.keyboard.press("Escape"); - }); - } - - async openProModesDialog({ - location = "chat-input-container", - }: { - location?: "chat-input-container" | "home-chat-input-container"; - } = {}): Promise { - const proButton = this.page - // Assumes you're on the chat page. - .getByTestId(location) - .getByRole("button", { name: "Pro", exact: true }); - await proButton.click(); - return new ProModesDialog(this.page, async () => { - await proButton.click(); - }); - } - - // ================================ - // Proposal Actions - // ================================ - - async approveProposal() { - const approveButton = this.page - .getByTestId("approve-proposal-button") - .last(); - await expect(approveButton).toBeEnabled({ timeout: Timeout.MEDIUM }); - await approveButton.click(); - await expect(approveButton).toBeHidden({ timeout: Timeout.MEDIUM }); - } - - async rejectProposal() { - const rejectButton = this.page.getByTestId("reject-proposal-button").last(); - await expect(rejectButton).toBeEnabled({ timeout: Timeout.MEDIUM }); - await rejectButton.click(); - await expect(rejectButton).toBeHidden({ timeout: Timeout.MEDIUM }); - } - async clickRestart() { await this.page.getByRole("button", { name: "Restart" }).click(); } diff --git a/e2e-tests/helpers/page-objects/components/Settings.ts b/e2e-tests/helpers/page-objects/components/Settings.ts index 15711d912a..7c50097af4 100644 --- a/e2e-tests/helpers/page-objects/components/Settings.ts +++ b/e2e-tests/helpers/page-objects/components/Settings.ts @@ -14,10 +14,6 @@ export class Settings { private fakeLlmPort: number, ) {} - async toggleAutoApprove() { - await this.page.getByRole("switch", { name: "Auto-approve" }).click(); - } - async toggleLocalAgentMode() { await this.page.getByRole("switch", { name: "Enable Agent v2" }).click(); } @@ -38,6 +34,14 @@ export class Settings { await this.page.getByRole("switch", { name: "Auto-update" }).click(); } + async disableAppBlueprint() { + await this.page.evaluate(async () => { + await (window as any).electron.ipcRenderer.invoke("set-user-settings", { + enableAppBlueprint: false, + }); + }); + } + async enableChatEventNotifications() { await expect( this.page.getByRole("heading", { level: 1, name: "Settings" }), diff --git a/e2e-tests/helpers/page-objects/dialogs/ContextFilesPickerDialog.ts b/e2e-tests/helpers/page-objects/dialogs/ContextFilesPickerDialog.ts deleted file mode 100644 index 0076a9390e..0000000000 --- a/e2e-tests/helpers/page-objects/dialogs/ContextFilesPickerDialog.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Page object for the Context Files Picker dialog. - * Handles adding and removing context files in tests. - */ - -import { Page } from "@playwright/test"; - -export class ContextFilesPickerDialog { - constructor( - public page: Page, - public close: () => Promise, - ) {} - - async addManualContextFile(path: string) { - await this.page.getByTestId("manual-context-files-input").fill(path); - await this.page.getByTestId("manual-context-files-add-button").click(); - } - - async addAutoIncludeContextFile(path: string) { - await this.page.getByTestId("auto-include-context-files-input").fill(path); - await this.page - .getByTestId("auto-include-context-files-add-button") - .click(); - } - - async removeManualContextFile() { - await this.page - .getByTestId("manual-context-files-remove-button") - .first() - .click(); - } - - async removeAutoIncludeContextFile() { - await this.page - .getByTestId("auto-include-context-files-remove-button") - .first() - .click(); - } - - async addExcludeContextFile(path: string) { - await this.page.getByTestId("exclude-context-files-input").fill(path); - await this.page.getByTestId("exclude-context-files-add-button").click(); - } - - async removeExcludeContextFile() { - await this.page - .getByTestId("exclude-context-files-remove-button") - .first() - .click(); - } -} diff --git a/e2e-tests/helpers/page-objects/dialogs/ProModesDialog.ts b/e2e-tests/helpers/page-objects/dialogs/ProModesDialog.ts deleted file mode 100644 index f2c4287ab1..0000000000 --- a/e2e-tests/helpers/page-objects/dialogs/ProModesDialog.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Page object for the Pro Modes dialog. - * Handles smart context and turbo edits mode selection. - */ - -import { Page } from "@playwright/test"; - -export class ProModesDialog { - constructor( - public page: Page, - public close: () => Promise, - ) {} - - async expandBuildModeSettings() { - const trigger = this.page.getByRole("button", { - name: "Build mode settings", - }); - if ((await trigger.getAttribute("aria-expanded")) !== "true") { - await trigger.click(); - } - } - - async setSmartContextMode(mode: "balanced" | "off" | "deep") { - await this.expandBuildModeSettings(); - await this.page - .getByTestId("smart-context-selector") - .getByRole("button", { - name: mode.charAt(0).toUpperCase() + mode.slice(1), - }) - .click(); - } - - async setTurboEditsMode(mode: "off" | "classic" | "search-replace") { - await this.expandBuildModeSettings(); - await this.page - .getByTestId("turbo-edits-selector") - .getByRole("button", { - name: - mode === "search-replace" - ? "Search & replace" - : mode.charAt(0).toUpperCase() + mode.slice(1), - }) - .click(); - } -} diff --git a/e2e-tests/helpers/page-objects/dialogs/index.ts b/e2e-tests/helpers/page-objects/dialogs/index.ts deleted file mode 100644 index 15e99635f4..0000000000 --- a/e2e-tests/helpers/page-objects/dialogs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Barrel file for dialog page objects. - */ - -export { ContextFilesPickerDialog } from "./ContextFilesPickerDialog"; -export { ProModesDialog } from "./ProModesDialog"; diff --git a/e2e-tests/helpers/page-objects/index.ts b/e2e-tests/helpers/page-objects/index.ts index bae30b8d6c..dfcfeff1d5 100644 --- a/e2e-tests/helpers/page-objects/index.ts +++ b/e2e-tests/helpers/page-objects/index.ts @@ -6,7 +6,6 @@ export { PageObject } from "./PageObject"; // Dialog page objects -export { ContextFilesPickerDialog, ProModesDialog } from "./dialogs"; // Component page objects export { diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index a7fecfff8b..787eb5ef62 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -28,8 +28,6 @@ export { // Re-export page objects export { PageObject, - ContextFilesPickerDialog, - ProModesDialog, GitHubConnector, ChatActions, PreviewPanel, diff --git a/e2e-tests/helpers/utils/dump-prettifier.ts b/e2e-tests/helpers/utils/dump-prettifier.ts index a9260f6463..588e2d82e3 100644 --- a/e2e-tests/helpers/utils/dump-prettifier.ts +++ b/e2e-tests/helpers/utils/dump-prettifier.ts @@ -51,7 +51,13 @@ export function prettifyDump( /\n[\s\S]*?<\/dyad-file>\n/g, "", ); - return `===\nrole: ${message.role}\nmessage: ${content}`; + const normalizedContent = content.replace(/^[ \t]+(?=\n)/, ""); + const separator = + normalizedContent === "" || normalizedContent.startsWith("\n") + ? "" + : " "; + const messageLine = `message:${separator}${normalizedContent}`; + return `===\nrole: ${message.role}\n${messageLine}`; }) .join("\n\n"); } diff --git a/e2e-tests/helpers/utils/stable-aria-snapshot.ts b/e2e-tests/helpers/utils/stable-aria-snapshot.ts index 348b590358..721d90c393 100644 --- a/e2e-tests/helpers/utils/stable-aria-snapshot.ts +++ b/e2e-tests/helpers/utils/stable-aria-snapshot.ts @@ -88,6 +88,13 @@ function formatButtonLine({ return `${indent}- button "${name}"${state}`; } +function isEnvironmentDependentFileDiffButton(name: string) { + return ( + /^[AMD?] .+ \+\d+(?: -\d+)?$/.test(name) && + !name.startsWith("M pnpm-lock.yaml ") + ); +} + function shouldDropLine(line: string) { const trimmed = line.trim(); @@ -126,7 +133,9 @@ export function normalizeMessagesAriaSnapshot( const button = parseButtonLine(line); if (button) { - normalizedLines.push(formatButtonLine(button)); + if (!isEnvironmentDependentFileDiffButton(button.name)) { + normalizedLines.push(formatButtonLine(button)); + } const baseIndent = button.indent.length; while (i + 1 < lines.length && isMoreIndented(lines[i + 1], baseIndent)) { i++; diff --git a/e2e-tests/local_agent_add_integration.spec.ts b/e2e-tests/local_agent_add_integration.spec.ts index aff172bd8b..a55b938824 100644 --- a/e2e-tests/local_agent_add_integration.spec.ts +++ b/e2e-tests/local_agent_add_integration.spec.ts @@ -16,7 +16,7 @@ import { Timeout, testSkipIfWindows } from "./helpers/test_helper"; testSkipIfWindows( "local-agent - finishing the database integration resumes the chat", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.waitForChatCompletion({ timeout: Timeout.LONG }); await po.chatActions.clickNewChat(); @@ -89,7 +89,7 @@ testSkipIfWindows( testSkipIfWindows( "local-agent - skipping database integration resumes without a provider", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.waitForChatCompletion({ timeout: Timeout.LONG }); await po.chatActions.clickNewChat(); diff --git a/e2e-tests/local_agent_advanced.spec.ts b/e2e-tests/local_agent_advanced.spec.ts index 0363bf939a..0644335a0e 100644 --- a/e2e-tests/local_agent_advanced.spec.ts +++ b/e2e-tests/local_agent_advanced.spec.ts @@ -4,7 +4,7 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; * Test for security review in local-agent mode */ testSkipIfWindows("local-agent - security review fix", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); @@ -19,7 +19,7 @@ testSkipIfWindows("local-agent - security review fix", async ({ po }) => { * Test for mention apps feature in local-agent mode */ testSkipIfWindows("local-agent - mention apps", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); // Import app and reference it. await po.importApp("minimal-with-ai-rules"); @@ -36,7 +36,7 @@ testSkipIfWindows("local-agent - mention apps", async ({ po }) => { * Test for enable_nitro tool in local-agent mode on a Vite app. */ testSkipIfWindows("local-agent - enable nitro", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_auto.spec.ts b/e2e-tests/local_agent_auto.spec.ts index e66de233ae..71897e935a 100644 --- a/e2e-tests/local_agent_auto.spec.ts +++ b/e2e-tests/local_agent_auto.spec.ts @@ -2,7 +2,11 @@ import { expect } from "@playwright/test"; import { testSkipIfWindows } from "./helpers/test_helper"; testSkipIfWindows("local-agent - auto model", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true, localAgentUseAutoModel: true }); + await po.setUpDyadPro({ + localAgent: true, + localAgentUseAutoModel: true, + autoApprove: true, + }); await po.page.evaluate(async () => { await (window as any).electron.ipcRenderer.invoke("set-user-settings", { enableCodeExplorer: false, diff --git a/e2e-tests/local_agent_connection_retry.spec.ts b/e2e-tests/local_agent_connection_retry.spec.ts index 4ae9708d31..da12118d68 100644 --- a/e2e-tests/local_agent_connection_retry.spec.ts +++ b/e2e-tests/local_agent_connection_retry.spec.ts @@ -10,7 +10,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; testSkipIfWindows( "local-agent - recovers from connection drop", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); @@ -62,7 +62,7 @@ testSkipIfWindows( testSkipIfWindows( "local-agent - recovers when drop happens after tool-call stream", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_file_upload.spec.ts b/e2e-tests/local_agent_file_upload.spec.ts index 9c535fa07f..ee23a0678a 100644 --- a/e2e-tests/local_agent_file_upload.spec.ts +++ b/e2e-tests/local_agent_file_upload.spec.ts @@ -11,7 +11,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; * into the codebase at the destination path. */ testSkipIfWindows("local-agent - upload file to codebase", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ autoApprove: true, localAgent: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_persistent_todos.spec.ts b/e2e-tests/local_agent_persistent_todos.spec.ts index 84fc1230e8..e5afb48e48 100644 --- a/e2e-tests/local_agent_persistent_todos.spec.ts +++ b/e2e-tests/local_agent_persistent_todos.spec.ts @@ -23,7 +23,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; testSkipIfWindows( "local-agent - persistent todos across turns", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_pre_commit.spec.ts b/e2e-tests/local_agent_pre_commit.spec.ts index 0ba87799e3..b758855769 100644 --- a/e2e-tests/local_agent_pre_commit.spec.ts +++ b/e2e-tests/local_agent_pre_commit.spec.ts @@ -72,7 +72,7 @@ async function setUpAppWithHook( po: PageObject, mode: "fix-then-pass" | "always-fail", ) { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); await po.appManagement.configureGitUser(); diff --git a/e2e-tests/local_agent_run_type_checks.spec.ts b/e2e-tests/local_agent_run_type_checks.spec.ts index 2b248d1936..8b3fe9f3bf 100644 --- a/e2e-tests/local_agent_run_type_checks.spec.ts +++ b/e2e-tests/local_agent_run_type_checks.spec.ts @@ -11,7 +11,7 @@ import path from "node:path"; testSkipIfWindows( "local-agent - run_type_checks updates problems panel", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); @@ -62,7 +62,7 @@ testSkipIfWindows( testSkipIfWindows( "local-agent - run_type_checks warns when project configuration blocks a scoped check", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); @@ -98,7 +98,7 @@ testSkipIfWindows( testSkipIfWindows( "local-agent - run_type_checks succeeds for a clean app", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); @@ -124,7 +124,7 @@ testSkipIfWindows( testSkipIfWindows( "local-agent - run_type_checks sees TypeScript replaced by rebuild", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_summarize.spec.ts b/e2e-tests/local_agent_summarize.spec.ts index 69df9c8e18..53ac4a5143 100644 --- a/e2e-tests/local_agent_summarize.spec.ts +++ b/e2e-tests/local_agent_summarize.spec.ts @@ -12,7 +12,7 @@ import { expect } from "@playwright/test"; testSkipIfWindows( "local-agent - summarize to new chat works", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/local_agent_todo_followup.spec.ts b/e2e-tests/local_agent_todo_followup.spec.ts index 10afcd066a..2d847c1a1e 100644 --- a/e2e-tests/local_agent_todo_followup.spec.ts +++ b/e2e-tests/local_agent_todo_followup.spec.ts @@ -12,7 +12,7 @@ import { testSkipIfWindows } from "./helpers/test_helper"; * Related to issue #2601 */ testSkipIfWindows("local-agent - todo follow-up loop", async ({ po }) => { - await po.setUpDyadPro({ localAgent: true }); + await po.setUpDyadPro({ localAgent: true, autoApprove: true }); await po.importApp("minimal"); await po.chatActions.selectLocalAgentMode(); diff --git a/e2e-tests/logs_server.spec.ts b/e2e-tests/logs_server.spec.ts index 566e4bce74..845b0e6396 100644 --- a/e2e-tests/logs_server.spec.ts +++ b/e2e-tests/logs_server.spec.ts @@ -9,11 +9,10 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; testSkipIfWindows( "system messages UI shows server logs with correct type", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); // Create an app to generate server logs await po.sendPrompt("tc=write-index"); - await po.approveProposal(); // Wait for app to run - this generates server logs from stdout/stderr // Use toPass() for resilience since the picker button needs time to appear and become enabled diff --git a/e2e-tests/package_manager.spec.ts b/e2e-tests/package_manager.spec.ts index 548fa950bc..e0576f5996 100644 --- a/e2e-tests/package_manager.spec.ts +++ b/e2e-tests/package_manager.spec.ts @@ -231,7 +231,7 @@ const realPnpmStrictBuildsTestSkipIfWindows = testWithConfigSkipIfWindows({ }); async function openMinimalBuildChat(po: PageObject) { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.page.evaluate( async (nodeBinDir) => { await (window as any).electron.ipcRenderer.invoke("set-user-settings", { @@ -325,13 +325,6 @@ function extendSocketFirewallTestTimeout(testInfo: TestInfo) { testInfo.setTimeout(SOCKET_FIREWALL_TEST_TIMEOUT); } -async function clickApproveProposal(po: PageObject) { - const approveButton = po.page.getByTestId("approve-proposal-button").last(); - await expect(approveButton).toBeEnabled({ timeout: Timeout.MEDIUM }); - await approveButton.click(); - await expect(approveButton).toBeDisabled({ timeout: Timeout.MEDIUM }); -} - testSkipIfWindows( "build mode - safe npm package installs through the real socket firewall path", async ({ po }, testInfo) => { @@ -343,12 +336,9 @@ testSkipIfWindows( const initialPnpmLock = await fs.readFile(pnpmLockPath, "utf8"); await fs.rm(pnpmWorkspacePath, { force: true }); - await po.sendPrompt("tc=add-safe-dependency"); - await expect(po.page.getByTestId("approve-proposal-button")).toBeVisible({ - timeout: Timeout.LONG, + await po.sendPrompt("tc=add-safe-dependency", { + timeout: Timeout.EXTRA_LONG, }); - - await clickApproveProposal(po); await expect(async () => { const packageJson = JSON.parse( await fs.readFile(packageJsonPath, "utf8"), @@ -391,15 +381,12 @@ testSkipIfWindows( const initialPackageJson = await fs.readFile(packageJsonPath, "utf8"); const initialPnpmLock = await fs.readFile(pnpmLockPath, "utf8"); - await po.sendPrompt("tc=add-unsafe-dependency"); - await expect(po.page.getByTestId("approve-proposal-button")).toBeVisible({ - timeout: Timeout.LONG, + await po.sendPrompt("tc=add-unsafe-dependency", { + timeout: Timeout.EXTRA_LONG, }); - await clickApproveProposal(po); - const errorCard = po.page.getByRole("button", { - name: /Failed to add dependencies: axois\./i, + name: /Tool 'add_dependency' failed:.*blocked npm package.*axois/i, }); await expect(errorCard).toBeVisible({ timeout: SOCKET_FIREWALL_VERDICT_TIMEOUT, diff --git a/e2e-tests/partial_response.spec.ts b/e2e-tests/partial_response.spec.ts deleted file mode 100644 index 09c5081f44..0000000000 --- a/e2e-tests/partial_response.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { test } from "./helpers/test_helper"; - -test("partial message is resumed", async ({ po }) => { - await po.setUp({ autoApprove: true }); - await po.importApp("minimal"); - await po.sendPrompt("tc=partial-write"); - - // This is a special test case which triggers a dump. - await po.snapshotServerDump("all-messages"); - await po.snapshotMessages({ replaceDumpPath: true }); - await po.snapshotAppFiles({ name: "message-resumed" }); -}); diff --git a/e2e-tests/plan_mode.spec.ts b/e2e-tests/plan_mode.spec.ts index abbc2a4a9f..67c928d717 100644 --- a/e2e-tests/plan_mode.spec.ts +++ b/e2e-tests/plan_mode.spec.ts @@ -2,8 +2,6 @@ import { expect } from "@playwright/test"; import { Timeout, testSkipIfWindows } from "./helpers/test_helper"; async function finishPlanPresentation(po: any) { - await po.page.getByRole("button", { name: "Keep going" }).click(); - await po.chatActions.waitForChatCompletion(); await expect(po.page.getByTestId("accept-plan-new-chat")).toBeVisible({ timeout: Timeout.MEDIUM, }); diff --git a/e2e-tests/preview_loading_screen.spec.ts b/e2e-tests/preview_loading_screen.spec.ts index e20c44a6ae..ab4144c2d8 100644 --- a/e2e-tests/preview_loading_screen.spec.ts +++ b/e2e-tests/preview_loading_screen.spec.ts @@ -1,5 +1,7 @@ import { testSkipIfWindows, Timeout } from "./helpers/test_helper"; import { expect } from "@playwright/test"; +import fs from "fs"; +import path from "path"; testSkipIfWindows( "preview loading screen shows spinner and server logs", @@ -39,7 +41,14 @@ testSkipIfWindows( // The next `npm run dev` will exit non-zero with "Missing script: dev", // producing server-level error entries that should flow into the // loading screen's error banner. - await po.sendPrompt("tc=break-package-json"); + const appPath = await po.appManagement.getCurrentAppPath(); + const packageJsonPath = path.join(appPath, "package.json"); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + delete packageJson.scripts.dev; + fs.writeFileSync( + packageJsonPath, + `${JSON.stringify(packageJson, null, 2)}\n`, + ); await po.clickRestart(); diff --git a/e2e-tests/problems.spec.ts b/e2e-tests/problems.spec.ts index 8f56d37cf8..d651f07b58 100644 --- a/e2e-tests/problems.spec.ts +++ b/e2e-tests/problems.spec.ts @@ -76,7 +76,7 @@ module.exports = require("./${originalEntryName}"); ); testSkipIfWindows("problems - fix all", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.importApp(MINIMAL_APP); const appPath = await po.appManagement.getCurrentAppPath(); const badFilePath = path.join(appPath, "src", "bad-file.tsx"); @@ -93,20 +93,21 @@ export default App; await po.appManagement.ensurePnpmInstall(); await po.appManagement.ensureCodeExplorerReady(); - await po.sendPrompt("tc=create-ts-errors"); await po.previewPanel.selectPreviewMode("problems"); await po.previewPanel.clickRecheckProblems(); await po.previewPanel.clickFixAllProblems(); await po.chatActions.waitForChatCompletion(); - await po.snapshotServerDump("last-message"); + expect(fs.readFileSync(badFilePath, "utf8")).not.toContain( + "nonExistentFunction", + ); await po.snapshotMessages({ replaceDumpPath: true }); }); testSkipIfWindows( "problems - select specific problems and fix", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.importApp(MINIMAL_APP); // Create multiple TS errors in one file @@ -164,7 +165,9 @@ export default App; await fixButton.click(); await po.chatActions.waitForChatCompletion(); - await po.snapshotServerDump("last-message"); + expect(fs.readFileSync(badFilePath, "utf8")).not.toContain( + "nonExistentFunction", + ); await po.snapshotMessages({ replaceDumpPath: true }); }, ); diff --git a/e2e-tests/screenshot_prompt.spec.ts b/e2e-tests/screenshot_prompt.spec.ts index 8ef9e150d1..0e32acd89c 100644 --- a/e2e-tests/screenshot_prompt.spec.ts +++ b/e2e-tests/screenshot_prompt.spec.ts @@ -72,7 +72,7 @@ test("file a bug report without a screenshot", async ({ po }) => { test("upload a chat session and report it with a screenshot", async ({ po, }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.sendPrompt("tc=write-index"); await recordIssueUrls(po.electronApp); await stubScreenshotCapture(po.electronApp); diff --git a/e2e-tests/select_component.spec.ts b/e2e-tests/select_component.spec.ts index 3a5c623d91..aa9aab3bda 100644 --- a/e2e-tests/select_component.spec.ts +++ b/e2e-tests/select_component.spec.ts @@ -169,7 +169,7 @@ testSkipIfWindows("upgrade app to select component", async ({ po }) => { }); testSkipIfWindows("select component next.js", async ({ po }) => { - await po.setUp(); + await po.setUp({ autoApprove: true }); await po.navigation.goToTemplatesAndSelectTemplate("Next.js Template"); await po.chatActions.selectChatMode("build"); diff --git a/e2e-tests/smart_context_balanced.spec.ts b/e2e-tests/smart_context_balanced.spec.ts deleted file mode 100644 index 4e459a717c..0000000000 --- a/e2e-tests/smart_context_balanced.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { testSkipIfWindows } from "./helpers/test_helper"; - -testSkipIfWindows("smart context balanced - simple", async ({ po }) => { - await po.setUpDyadPro({ autoApprove: true }); - const proModesDialog = await po.openProModesDialog({ - location: "home-chat-input-container", - }); - await proModesDialog.setSmartContextMode("balanced"); - await proModesDialog.close(); - - await po.sendPrompt("[dump]"); - - await po.snapshotServerDump("request"); - await po.snapshotMessages({ replaceDumpPath: true }); -}); diff --git a/e2e-tests/smart_context_deep.spec.ts b/e2e-tests/smart_context_deep.spec.ts deleted file mode 100644 index 7062c561af..0000000000 --- a/e2e-tests/smart_context_deep.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { testSkipIfWindows } from "./helpers/test_helper"; - -testSkipIfWindows("smart context deep - read write read", async ({ po }) => { - await po.setUpDyadPro({ autoApprove: true }); - const proModesDialog = await po.openProModesDialog({ - location: "home-chat-input-container", - }); - await proModesDialog.setSmartContextMode("deep"); - await proModesDialog.close(); - - await po.sendPrompt("tc=read-index"); - await po.sendPrompt("tc=update-index-1"); - await po.sendPrompt("tc=read-index"); - await po.sendPrompt("[dump]"); - - await po.snapshotServerDump("request"); - await po.snapshotMessages({ replaceDumpPath: true }); -}); - -testSkipIfWindows( - "smart context deep - mention app should fallback to balanced", - async ({ po }) => { - await po.setUpDyadPro(); - - // First, create an imported app. - await po.importApp("minimal-with-ai-rules"); - - await po.navigation.goToAppsTab(); - await po.chatActions.selectChatMode("build"); - const proModesDialog = await po.openProModesDialog({ - location: "home-chat-input-container", - }); - await proModesDialog.setSmartContextMode("deep"); - await proModesDialog.close(); - - // Mentioned the imported app - await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi"); - - await po.snapshotServerDump("request"); - }, -); diff --git a/e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-2.aria.yml b/e2e-tests/snapshots/approve.spec.ts_write-to-index-and-check-preview-1.aria.yml similarity index 88% rename from e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-2.aria.yml rename to e2e-tests/snapshots/approve.spec.ts_write-to-index-and-check-preview-1.aria.yml index 684dc228be..849b974f52 100644 --- a/e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-2.aria.yml +++ b/e2e-tests/snapshots/approve.spec.ts_write-to-index-and-check-preview-1.aria.yml @@ -5,6 +5,5 @@ - paragraph: And it's done! - button "Copy" - text: "[[Version 2: files changed]]" -- button "M src/pages/Index.tsx +2 -12" - button "Undo" - button "Retry" diff --git a/e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-1.aria.yml b/e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-1.aria.yml deleted file mode 100644 index 8cffadb8e4..0000000000 --- a/e2e-tests/snapshots/approve.spec.ts_write-to-index-approve-check-preview-1.aria.yml +++ /dev/null @@ -1,8 +0,0 @@ -- button "Restore to this point" -- paragraph: tc=write-index -- paragraph: OK, I'm going to do some writing now... -- 'button "Index.tsx src/pages/Index.tsx Edit Summary: write-description"' -- paragraph: And it's done! -- button "Copy" -- button "Undo" -- button "Retry" diff --git a/e2e-tests/snapshots/astro.spec.ts_astro-1.txt b/e2e-tests/snapshots/astro.spec.ts_astro-1.txt index 6d4a98d27b..e521083179 100644 --- a/e2e-tests/snapshots/astro.spec.ts_astro-1.txt +++ b/e2e-tests/snapshots/astro.spec.ts_astro-1.txt @@ -2,171 +2,18 @@ role: system message: [[SYSTEM_MESSAGE]] -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -const App = () =>
Minimal imported app
; - -export default App; - -
- - ---- -// Component script (runs at build time) -const greeting = "Hello World"; -const currentTime = new Date().toLocaleString(); ---- - -
-

{greeting}

-

Welcome to Astro!

-

Generated at: {currentTime}

-
- - - -
- - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; - -createRoot(document.getElementById("root")!).render(); - - - - -/// - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - === role: user message: Generate an AI_RULES.md file for this app. Describe the tech stack in 5-10 bullet points and describe clear rules about what libraries to use for what. === role: assistant -message: +message: + A file (2) More - EOM + EOM === role: user diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image b/e2e-tests/snapshots/attach_image.spec.ts_attach-image index a9b5f75a3b..a738ff43fe 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image @@ -1,3 +1,3 @@ === role: user -message: [{"type":"text","text":"[dump]\n\nAttachments:\n- logo.png (image/png)"},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAACbElEQVQ4EY1TTWgTQRR+M7NpQoKmPVTPQi/qoaDWQAsxIigqRS/JzYNHxVz0oAcrG6hQoV68ePNQECUpeBFBIqQiVBrbaqvgoXjRSzVJf8zfzu7MG2eWbGywFN8e3ux73/fNm/dmAP7TFCgSQHeurSC4t1eEAFET4+WjzPIq5AX5ZURMjO5GNEk7VbJKKWU9Or8WBg2cvPRxlLXiX7arVtFWNjVkzSX/VJBPK0YKRMIciI6477kjhsDrAxfJoebVUwd0bl1vBD0ChpzR5JkrKzGvjum2B8MtrphLRXGTY5RKCaioi7wr/lcgID+//Omsu4EzERg4GIIoWAygxrezwOvNhmiAoCrkCtZtqF9BPp33d86nl46jw173aWKtXWtzWi21kELDgzOo+mJCCRBIFIowBr3rOQK4bDJC90PVrf5Ayi/efDP62QBvn14Y5m0sKogOCoWy/nvL55syqOl4ppCRT6+9GxAKjgmtLYg3ldVkM4Hs0Fr4QSmxwi28T1gUHE+J9rpGdozqRvoWcmKWUMpyEXWH2IYJiq0KtQYr/qgRWc3T4lJ/IbNVx/xmmCrMXJ+ML3+wZP+Jmre5MN8/NVYoFKTB2XbJ+vYyPi9l/4hLq+XZpZMJ0BxzP3z1XGpO99qUDg897VFGEkd+3lm9lVy8e2NsceL7q/iqwvCIohIYU9MGm+pwuuOwbUVtm+D0uXIO5b57noxCWzJoSQJNIaAhm8BxMze7PGbboLFA/El0BYxqcJTJC+Vkg5PrLU4PO1KBg/jVo87jZ++Tb4PSDX5XMxdq14QOpvfI9XDMxTKPKQim9DqtY8H/Tv8HGFE+AZtzYdAAAAAASUVORK5CYII="}}] \ No newline at end of file +message: [{"type":"text","text":"[dump]"},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAACbElEQVQ4EY1TTWgTQRR+M7NpQoKmPVTPQi/qoaDWQAsxIigqRS/JzYNHxVz0oAcrG6hQoV68ePNQECUpeBFBIqQiVBrbaqvgoXjRSzVJf8zfzu7MG2eWbGywFN8e3ux73/fNm/dmAP7TFCgSQHeurSC4t1eEAFET4+WjzPIq5AX5ZURMjO5GNEk7VbJKKWU9Or8WBg2cvPRxlLXiX7arVtFWNjVkzSX/VJBPK0YKRMIciI6477kjhsDrAxfJoebVUwd0bl1vBD0ChpzR5JkrKzGvjum2B8MtrphLRXGTY5RKCaioi7wr/lcgID+//Omsu4EzERg4GIIoWAygxrezwOvNhmiAoCrkCtZtqF9BPp33d86nl46jw173aWKtXWtzWi21kELDgzOo+mJCCRBIFIowBr3rOQK4bDJC90PVrf5Ayi/efDP62QBvn14Y5m0sKogOCoWy/nvL55syqOl4ppCRT6+9GxAKjgmtLYg3ldVkM4Hs0Fr4QSmxwi28T1gUHE+J9rpGdozqRvoWcmKWUMpyEXWH2IYJiq0KtQYr/qgRWc3T4lJ/IbNVx/xmmCrMXJ+ML3+wZP+Jmre5MN8/NVYoFKTB2XbJ+vYyPi9l/4hLq+XZpZMJ0BxzP3z1XGpO99qUDg897VFGEkd+3lm9lVy8e2NsceL7q/iqwvCIohIYU9MGm+pwuuOwbUVtm+D0uXIO5b57noxCWzJoSQJNIaAhm8BxMze7PGbboLFA/El0BYxqcJTJC+Vkg5PrLU4PO1KBg/jVo87jZ++Tb4PSDX5XMxdq14QOpvfI9XDMxTKPKQim9DqtY8H/Tv8HGFE+AZtzYdAAAAAASUVORK5CYII="}}] \ No newline at end of file diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat---upload-to-codebase-1.aria.yml b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat---upload-to-codebase-1.aria.yml index 3ede4019b2..2c40d4937b 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat---upload-to-codebase-1.aria.yml +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat---upload-to-codebase-1.aria.yml @@ -3,16 +3,15 @@ - button "file1.txt file1.txt Edit" - paragraph: More EOM - button "Copy" -- text: "[[Version 2: files changed]]" - button "Restore to this point" -- paragraph: "[[UPLOAD_IMAGE_TO_CODEBASE]]" +- paragraph: tc=local-agent/upload-image-to-codebase [[UPLOAD_IMAGE_TO_CODEBASE]] - 'button "Expand image: logo.png"' -- paragraph: Uploading image to codebase +- paragraph: I'll upload your image to the codebase. - text: file.png Copy -- text: "Copied To: new/image/file.png Uploaded image to codebase" -- paragraph: "[[dyad-dump-path=*]]" +- text: "Copied From: attachments:logo.png To: new/image/file.png Copy uploaded image to codebase" +- paragraph: I've copied the image to new/image/file.png. - button "Copy" -- text: "[[Version 3: files changed]]" +- text: "[[Version 2: files changed]]" - button "A new/image/file.png" - button "Undo" - button "Retry" diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml index d346c22793..a5dd7e248a 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image---chat-1.aria.yml @@ -3,7 +3,6 @@ - button "file1.txt file1.txt Edit" - paragraph: More EOM - button "Copy" -- text: "[[Version 2: files changed]]" - button "Restore to this point" - paragraph: "[dump]" - 'button "Expand image: logo.png"' diff --git a/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml b/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml index d346c22793..a5dd7e248a 100644 --- a/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml +++ b/e2e-tests/snapshots/attach_image.spec.ts_attach-image-via-drag---chat-1.aria.yml @@ -3,7 +3,6 @@ - button "file1.txt file1.txt Edit" - paragraph: More EOM - button "Copy" -- text: "[[Version 2: files changed]]" - button "Restore to this point" - paragraph: "[dump]" - 'button "Expand image: logo.png"' diff --git a/e2e-tests/snapshots/attach_image.spec.ts_upload-to-codebase b/e2e-tests/snapshots/attach_image.spec.ts_upload-to-codebase deleted file mode 100644 index 6d39c693a6..0000000000 --- a/e2e-tests/snapshots/attach_image.spec.ts_upload-to-codebase +++ /dev/null @@ -1,3 +0,0 @@ -=== -role: user -message: [{"type":"text","text":"[[UPLOAD_IMAGE_TO_CODEBASE]]\n\nAttachments:\n\n\nFile to upload to codebase: \"logo.png\" (path: [[ATTACHMENT_PATH]])\nUse the copy_file tool when tools are available, or emit a tag otherwise, to copy this file into the codebase at the appropriate location."},{"type":"image_url","image_url":{"url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoZXuEHAAACbElEQVQ4EY1TTWgTQRR+M7NpQoKmPVTPQi/qoaDWQAsxIigqRS/JzYNHxVz0oAcrG6hQoV68ePNQECUpeBFBIqQiVBrbaqvgoXjRSzVJf8zfzu7MG2eWbGywFN8e3ux73/fNm/dmAP7TFCgSQHeurSC4t1eEAFET4+WjzPIq5AX5ZURMjO5GNEk7VbJKKWU9Or8WBg2cvPRxlLXiX7arVtFWNjVkzSX/VJBPK0YKRMIciI6477kjhsDrAxfJoebVUwd0bl1vBD0ChpzR5JkrKzGvjum2B8MtrphLRXGTY5RKCaioi7wr/lcgID+//Omsu4EzERg4GIIoWAygxrezwOvNhmiAoCrkCtZtqF9BPp33d86nl46jw173aWKtXWtzWi21kELDgzOo+mJCCRBIFIowBr3rOQK4bDJC90PVrf5Ayi/efDP62QBvn14Y5m0sKogOCoWy/nvL55syqOl4ppCRT6+9GxAKjgmtLYg3ldVkM4Hs0Fr4QSmxwi28T1gUHE+J9rpGdozqRvoWcmKWUMpyEXWH2IYJiq0KtQYr/qgRWc3T4lJ/IbNVx/xmmCrMXJ+ML3+wZP+Jmre5MN8/NVYoFKTB2XbJ+vYyPi9l/4hLq+XZpZMJ0BxzP3z1XGpO99qUDg897VFGEkd+3lm9lVy8e2NsceL7q/iqwvCIohIYU9MGm+pwuuOwbUVtm+D0uXIO5b57noxCWzJoSQJNIaAhm8BxMze7PGbboLFA/El0BYxqcJTJC+Vkg5PrLU4PO1KBg/jVo87jZ++Tb4PSDX5XMxdq14QOpvfI9XDMxTKPKQim9DqtY8H/Tv8HGFE+AZtzYdAAAAAASUVORK5CYII="}}] \ No newline at end of file diff --git a/e2e-tests/snapshots/auto_approve.spec.ts_auto-approve-1.aria.yml b/e2e-tests/snapshots/auto_approve.spec.ts_agentic-Build-applies-changes-1.aria.yml similarity index 88% rename from e2e-tests/snapshots/auto_approve.spec.ts_auto-approve-1.aria.yml rename to e2e-tests/snapshots/auto_approve.spec.ts_agentic-Build-applies-changes-1.aria.yml index 684dc228be..849b974f52 100644 --- a/e2e-tests/snapshots/auto_approve.spec.ts_auto-approve-1.aria.yml +++ b/e2e-tests/snapshots/auto_approve.spec.ts_agentic-Build-applies-changes-1.aria.yml @@ -5,6 +5,5 @@ - paragraph: And it's done! - button "Copy" - text: "[[Version 2: files changed]]" -- button "M src/pages/Index.tsx +2 -12" - button "Undo" - button "Retry" diff --git a/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-1.aria.yml b/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-1.aria.yml index 6967033f96..24f4899427 100644 --- a/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-1.aria.yml +++ b/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-1.aria.yml @@ -1,6 +1,6 @@ - button "Restore to this point" - paragraph: tc=chat2 -- paragraph: chat2 +- paragraph: chat2Task completed. - button "Copy" - button "Undo" - button "Retry" diff --git a/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-2.aria.yml b/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-2.aria.yml index 212a8d7d11..aad6b4e1c4 100644 --- a/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-2.aria.yml +++ b/e2e-tests/snapshots/concurrent_chat.spec.ts_concurrent-chat-2.aria.yml @@ -1,2 +1,6 @@ - button "Restore to this point" -- paragraph: tc=chat1 [sleep=medium] +- paragraph: tc=chat1 [sleep=long] +- paragraph: chat1Task completed. +- button "Copy" +- button "Undo" +- button "Retry" diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.aria.yml b/e2e-tests/snapshots/context_window.spec.ts_context-window-1.aria.yml deleted file mode 100644 index b5e5b09ab8..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.aria.yml +++ /dev/null @@ -1,12 +0,0 @@ -- paragraph: tc=1 -- paragraph: "1" -- paragraph: tc=2 -- paragraph: "2" -- paragraph: "[dump] tc=3" -- paragraph: /\[\[dyad-dump-path=\/Users\/will\/Documents\/GitHub\/dyad-zero\/testing\/fake-llm-server\/dist\/generated\/\d+\.json\]\]/ -- paragraph: "[dump] tc=4" -- paragraph: /\[\[dyad-dump-path=\/Users\/will\/Documents\/GitHub\/dyad-zero\/testing\/fake-llm-server\/dist\/generated\/\d+\.json\]\]/ -- paragraph: "[dump] tc=5" -- paragraph: /\[\[dyad-dump-path=\/Users\/will\/Documents\/GitHub\/dyad-zero\/testing\/fake-llm-server\/dist\/generated\/\d+\.json\]\]/ -- button "Retry": - - img \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt deleted file mode 100644 index c77d830498..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-1.txt +++ /dev/null @@ -1,999 +0,0 @@ -=== -role: system -message: [[SYSTEM_MESSAGE]] - -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Playwright test artifacts -/test-results/ -/playwright-report/ -/playwright/.cache/ -.last-run.json -.dyad/ - - - - -# Tech Stack - -- You are building a React application. -- Use TypeScript. -- Use React Router. KEEP the routes in src/App.tsx -- Always put source code in the src folder. -- Put pages into src/pages/ -- Put components into src/components/ -- The main page (default page) is src/pages/Index.tsx -- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! -- ALWAYS try to use the shadcn/ui library. -- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. - -Available packages and libraries: - -- The lucide-react package is installed for icons. -- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. -- You have ALL the necessary Radix UI components installed. -- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -# Welcome to your Dyad app - - - - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - - - - -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; -import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - -); - -export default App; - - - - -export const MadeWithDyad = () => { - return ( -
- ); -}; - -
- - Made with Dyad - -
- - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - - - - -import * as React from "react"; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener("change", onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener("change", onChange); - }, []); - - return !!isMobile; -} - - - - -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const _actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof _actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; - - - - -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - - - - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./globals.css"; - -createRoot(document.getElementById("root")!).render(); - - - - -// Update this page (the content is just a fallback if you fail to update the page) - -import { MadeWithDyad } from "@/components/made-with-dyad"; - -const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

- Start building your amazing project here! -

-
- -
- ); -}; - -export default Index; - -
- - -import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; - -const NotFound = () => { - const location = useLocation(); - - useEffect(() => { - console.error( - "404 Error: User attempted to access non-existent route:", - location.pathname, - ); - }, [location.pathname]); - - return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - -
-
- ); -}; - -export default NotFound; - -
- - -import { toast } from "sonner"; - -export const showSuccess = (message: string) => { - toast.success(message); -}; - -export const showError = (message: string) => { - toast.error(message); -}; - -export const showLoading = (message: string) => { - return toast.loading(message); -}; - -export const dismissToast = (toastId: string) => { - toast.dismiss(toastId); -}; - - - - -/// - - - - -import type { Config } from "tailwindcss"; - -export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} - - - - -import { defineConfig } from "vite"; -import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [dyadComponentTagger(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - -=== -role: user -message: tc=1 - -=== -role: assistant -message: 1 - -=== -role: user -message: tc=2 - -=== -role: assistant -message: 2 - -=== -role: user -message: [dump] tc=3 \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt deleted file mode 100644 index 54d5c84120..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-2.txt +++ /dev/null @@ -1,1007 +0,0 @@ -=== -role: system -message: [[SYSTEM_MESSAGE]] - -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Playwright test artifacts -/test-results/ -/playwright-report/ -/playwright/.cache/ -.last-run.json -.dyad/ - - - - -# Tech Stack - -- You are building a React application. -- Use TypeScript. -- Use React Router. KEEP the routes in src/App.tsx -- Always put source code in the src folder. -- Put pages into src/pages/ -- Put components into src/components/ -- The main page (default page) is src/pages/Index.tsx -- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! -- ALWAYS try to use the shadcn/ui library. -- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. - -Available packages and libraries: - -- The lucide-react package is installed for icons. -- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. -- You have ALL the necessary Radix UI components installed. -- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -# Welcome to your Dyad app - - - - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - - - - -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; -import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - -); - -export default App; - - - - -export const MadeWithDyad = () => { - return ( -
- - Made with Dyad - -
- ); -}; - -
- - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - - - - -import * as React from "react"; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener("change", onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener("change", onChange); - }, []); - - return !!isMobile; -} - - - - -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const _actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof _actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; - - - - -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - - - - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./globals.css"; - -createRoot(document.getElementById("root")!).render(); - - - - -// Update this page (the content is just a fallback if you fail to update the page) - -import { MadeWithDyad } from "@/components/made-with-dyad"; - -const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

- Start building your amazing project here! -

-
- -
- ); -}; - -export default Index; - -
- - -import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; - -const NotFound = () => { - const location = useLocation(); - - useEffect(() => { - console.error( - "404 Error: User attempted to access non-existent route:", - location.pathname, - ); - }, [location.pathname]); - - return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - -
-
- ); -}; - -export default NotFound; - -
- - -import { toast } from "sonner"; - -export const showSuccess = (message: string) => { - toast.success(message); -}; - -export const showError = (message: string) => { - toast.error(message); -}; - -export const showLoading = (message: string) => { - return toast.loading(message); -}; - -export const dismissToast = (toastId: string) => { - toast.dismiss(toastId); -}; - - - - -/// - - - - -import type { Config } from "tailwindcss"; - -export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} - - - - -import { defineConfig } from "vite"; -import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [dyadComponentTagger(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - -=== -role: user -message: tc=1 - -=== -role: assistant -message: 1 - -=== -role: user -message: tc=2 - -=== -role: assistant -message: 2 - -=== -role: user -message: [dump] tc=3 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=4 \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt deleted file mode 100644 index 01a6975ee4..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-3.txt +++ /dev/null @@ -1,1007 +0,0 @@ -=== -role: system -message: [[SYSTEM_MESSAGE]] - -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Playwright test artifacts -/test-results/ -/playwright-report/ -/playwright/.cache/ -.last-run.json -.dyad/ - - - - -# Tech Stack - -- You are building a React application. -- Use TypeScript. -- Use React Router. KEEP the routes in src/App.tsx -- Always put source code in the src folder. -- Put pages into src/pages/ -- Put components into src/components/ -- The main page (default page) is src/pages/Index.tsx -- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! -- ALWAYS try to use the shadcn/ui library. -- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. - -Available packages and libraries: - -- The lucide-react package is installed for icons. -- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. -- You have ALL the necessary Radix UI components installed. -- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -# Welcome to your Dyad app - - - - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - - - - -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; -import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - -); - -export default App; - - - - -export const MadeWithDyad = () => { - return ( -
- - Made with Dyad - -
- ); -}; - -
- - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - - - - -import * as React from "react"; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener("change", onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener("change", onChange); - }, []); - - return !!isMobile; -} - - - - -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const _actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof _actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; - - - - -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - - - - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./globals.css"; - -createRoot(document.getElementById("root")!).render(); - - - - -// Update this page (the content is just a fallback if you fail to update the page) - -import { MadeWithDyad } from "@/components/made-with-dyad"; - -const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

- Start building your amazing project here! -

-
- -
- ); -}; - -export default Index; - -
- - -import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; - -const NotFound = () => { - const location = useLocation(); - - useEffect(() => { - console.error( - "404 Error: User attempted to access non-existent route:", - location.pathname, - ); - }, [location.pathname]); - - return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - -
-
- ); -}; - -export default NotFound; - -
- - -import { toast } from "sonner"; - -export const showSuccess = (message: string) => { - toast.success(message); -}; - -export const showError = (message: string) => { - toast.error(message); -}; - -export const showLoading = (message: string) => { - return toast.loading(message); -}; - -export const dismissToast = (toastId: string) => { - toast.dismiss(toastId); -}; - - - - -/// - - - - -import type { Config } from "tailwindcss"; - -export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} - - - - -import { defineConfig } from "vite"; -import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [dyadComponentTagger(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - -=== -role: user -message: tc=2 - -=== -role: assistant -message: 2 - -=== -role: user -message: [dump] tc=3 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=4 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=5 \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt deleted file mode 100644 index 640b97820d..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt +++ /dev/null @@ -1 +0,0 @@ -+ "maxChatTurnsInContext": 5 \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt deleted file mode 100644 index f08cd4aca6..0000000000 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-5.txt +++ /dev/null @@ -1,1023 +0,0 @@ -=== -role: system -message: [[SYSTEM_MESSAGE]] - -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Playwright test artifacts -/test-results/ -/playwright-report/ -/playwright/.cache/ -.last-run.json -.dyad/ - - - - -# Tech Stack - -- You are building a React application. -- Use TypeScript. -- Use React Router. KEEP the routes in src/App.tsx -- Always put source code in the src folder. -- Put pages into src/pages/ -- Put components into src/components/ -- The main page (default page) is src/pages/Index.tsx -- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! -- ALWAYS try to use the shadcn/ui library. -- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. - -Available packages and libraries: - -- The lucide-react package is installed for icons. -- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. -- You have ALL the necessary Radix UI components installed. -- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -# Welcome to your Dyad app - - - - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - - - - -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; -import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - -); - -export default App; - - - - -export const MadeWithDyad = () => { - return ( -
- - Made with Dyad - -
- ); -}; - -
- - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - - - - -import * as React from "react"; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener("change", onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener("change", onChange); - }, []); - - return !!isMobile; -} - - - - -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const _actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof _actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; - - - - -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - - - - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./globals.css"; - -createRoot(document.getElementById("root")!).render(); - - - - -// Update this page (the content is just a fallback if you fail to update the page) - -import { MadeWithDyad } from "@/components/made-with-dyad"; - -const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

- Start building your amazing project here! -

-
- -
- ); -}; - -export default Index; - -
- - -import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; - -const NotFound = () => { - const location = useLocation(); - - useEffect(() => { - console.error( - "404 Error: User attempted to access non-existent route:", - location.pathname, - ); - }, [location.pathname]); - - return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - -
-
- ); -}; - -export default NotFound; - -
- - -import { toast } from "sonner"; - -export const showSuccess = (message: string) => { - toast.success(message); -}; - -export const showError = (message: string) => { - toast.error(message); -}; - -export const showLoading = (message: string) => { - return toast.loading(message); -}; - -export const dismissToast = (toastId: string) => { - toast.dismiss(toastId); -}; - - - - -/// - - - - -import type { Config } from "tailwindcss"; - -export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} - - - - -import { defineConfig } from "vite"; -import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [dyadComponentTagger(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - -=== -role: user -message: tc=1 - -=== -role: assistant -message: 1 - -=== -role: user -message: tc=2 - -=== -role: assistant -message: 2 - -=== -role: user -message: [dump] tc=3 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=4 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=5 - -=== -role: assistant -message: [[dyad-dump-path=*]] - -=== -role: user -message: [dump] tc=6 \ No newline at end of file diff --git a/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt b/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt index ea227121bd..8515a02be0 100644 --- a/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt +++ b/e2e-tests/snapshots/dump_messages.spec.ts_dump-messages-1.txt @@ -2,982 +2,6 @@ role: system message: [[SYSTEM_MESSAGE]] -=== -role: user -message: This is my codebase. -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# Playwright test artifacts -/test-results/ -/playwright-report/ -/playwright/.cache/ -.last-run.json -.dyad/ - - - - -# Tech Stack - -- You are building a React application. -- Use TypeScript. -- Use React Router. KEEP the routes in src/App.tsx -- Always put source code in the src folder. -- Put pages into src/pages/ -- Put components into src/components/ -- The main page (default page) is src/pages/Index.tsx -- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! -- ALWAYS try to use the shadcn/ui library. -- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. - -Available packages and libraries: - -- The lucide-react package is installed for icons. -- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. -- You have ALL the necessary Radix UI components installed. -- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - - - - - - - dyad-generated-app - - - -
- - - - -
- - -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -# Welcome to your Dyad app - - - - -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - - - - -import { Toaster } from "@/components/ui/toaster"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Routes, Route } from "react-router-dom"; -import Index from "./pages/Index"; -import NotFound from "./pages/NotFound"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - -); - -export default App; - - - - -export const MadeWithDyad = () => { - return ( -
- - Made with Dyad - -
- ); -}; - -
- - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --sidebar-background: 0 0% 98%; - - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; - } -} - -@layer base { - * { - @apply border-border; - } - - body { - @apply bg-background text-foreground; - } -} - - - - -import * as React from "react"; - -const MOBILE_BREAKPOINT = 768; - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState( - undefined, - ); - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - mql.addEventListener("change", onChange); - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - return () => mql.removeEventListener("change", onChange); - }, []); - - return !!isMobile; -} - - - - -import * as React from "react"; - -import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; - -const TOAST_LIMIT = 1; -const TOAST_REMOVE_DELAY = 1000000; - -type ToasterToast = ToastProps & { - id: string; - title?: React.ReactNode; - description?: React.ReactNode; - action?: ToastActionElement; -}; - -const _actionTypes = { - ADD_TOAST: "ADD_TOAST", - UPDATE_TOAST: "UPDATE_TOAST", - DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const; - -let count = 0; - -function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER; - return count.toString(); -} - -type ActionType = typeof _actionTypes; - -type Action = - | { - type: ActionType["ADD_TOAST"]; - toast: ToasterToast; - } - | { - type: ActionType["UPDATE_TOAST"]; - toast: Partial; - } - | { - type: ActionType["DISMISS_TOAST"]; - toastId?: ToasterToast["id"]; - } - | { - type: ActionType["REMOVE_TOAST"]; - toastId?: ToasterToast["id"]; - }; - -interface State { - toasts: ToasterToast[]; -} - -const toastTimeouts = new Map>(); - -const addToRemoveQueue = (toastId: string) => { - if (toastTimeouts.has(toastId)) { - return; - } - - const timeout = setTimeout(() => { - toastTimeouts.delete(toastId); - dispatch({ - type: "REMOVE_TOAST", - toastId: toastId, - }); - }, TOAST_REMOVE_DELAY); - - toastTimeouts.set(toastId, timeout); -}; - -export const reducer = (state: State, action: Action): State => { - switch (action.type) { - case "ADD_TOAST": - return { - ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - }; - - case "UPDATE_TOAST": - return { - ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t, - ), - }; - - case "DISMISS_TOAST": { - const { toastId } = action; - - // ! Side effects ! - This could be extracted into a dismissToast() action, - // but I'll keep it here for simplicity - if (toastId) { - addToRemoveQueue(toastId); - } else { - state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id); - }); - } - - return { - ...state, - toasts: state.toasts.map((t) => - t.id === toastId || toastId === undefined - ? { - ...t, - open: false, - } - : t, - ), - }; - } - case "REMOVE_TOAST": - if (action.toastId === undefined) { - return { - ...state, - toasts: [], - }; - } - return { - ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - }; - } -}; - -const listeners: Array<(state: State) => void> = []; - -let memoryState: State = { toasts: [] }; - -function dispatch(action: Action) { - memoryState = reducer(memoryState, action); - listeners.forEach((listener) => { - listener(memoryState); - }); -} - -type Toast = Omit; - -function toast({ ...props }: Toast) { - const id = genId(); - - const update = (props: ToasterToast) => - dispatch({ - type: "UPDATE_TOAST", - toast: { ...props, id }, - }); - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); - - dispatch({ - type: "ADD_TOAST", - toast: { - ...props, - id, - open: true, - onOpenChange: (open) => { - if (!open) dismiss(); - }, - }, - }); - - return { - id: id, - dismiss, - update, - }; -} - -function useToast() { - const [state, setState] = React.useState(memoryState); - - React.useEffect(() => { - listeners.push(setState); - return () => { - const index = listeners.indexOf(setState); - if (index > -1) { - listeners.splice(index, 1); - } - }; - }, [state]); - - return { - ...state, - toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - }; -} - -export { useToast, toast }; - - - - -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} - - - - -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./globals.css"; - -createRoot(document.getElementById("root")!).render(); - - - - -// Update this page (the content is just a fallback if you fail to update the page) - -import { MadeWithDyad } from "@/components/made-with-dyad"; - -const Index = () => { - return ( -
-
-

Welcome to Your Blank App

-

- Start building your amazing project here! -

-
- -
- ); -}; - -export default Index; - -
- - -import { useLocation } from "react-router-dom"; -import { useEffect } from "react"; - -const NotFound = () => { - const location = useLocation(); - - useEffect(() => { - console.error( - "404 Error: User attempted to access non-existent route:", - location.pathname, - ); - }, [location.pathname]); - - return ( -
-
-

404

-

Oops! Page not found

- - Return to Home - -
-
- ); -}; - -export default NotFound; - -
- - -import { toast } from "sonner"; - -export const showSuccess = (message: string) => { - toast.success(message); -}; - -export const showError = (message: string) => { - toast.error(message); -}; - -export const showLoading = (message: string) => { - return toast.loading(message); -}; - -export const dismissToast = (toastId: string) => { - toast.dismiss(toastId); -}; - - - - -/// - - - - -import type { Config } from "tailwindcss"; - -export default { - darkMode: ["class"], - content: [ - "./pages/**/*.{ts,tsx}", - "./components/**/*.{ts,tsx}", - "./app/**/*.{ts,tsx}", - "./src/**/*.{ts,tsx}", - ], - prefix: "", - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive))", - foreground: "hsl(var(--destructive-foreground))", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - sidebar: { - DEFAULT: "hsl(var(--sidebar-background))", - foreground: "hsl(var(--sidebar-foreground))", - primary: "hsl(var(--sidebar-primary))", - "primary-foreground": "hsl(var(--sidebar-primary-foreground))", - accent: "hsl(var(--sidebar-accent))", - "accent-foreground": "hsl(var(--sidebar-accent-foreground))", - border: "hsl(var(--sidebar-border))", - ring: "hsl(var(--sidebar-ring))", - }, - }, - borderRadius: { - lg: "var(--radius)", - md: "calc(var(--radius) - 2px)", - sm: "calc(var(--radius) - 4px)", - }, - keyframes: { - "accordion-down": { - from: { - height: "0", - }, - to: { - height: "var(--radix-accordion-content-height)", - }, - }, - "accordion-up": { - from: { - height: "var(--radix-accordion-content-height)", - }, - to: { - height: "0", - }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [require("tailwindcss-animate")], -} satisfies Config; - - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -// File contents excluded from context - - - -{ - "$schema": "https://openapi.vercel.sh/vercel.json", - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} - - - - -import { defineConfig } from "vite"; -import dyadComponentTagger from "@dyad-sh/react-vite-component-tagger"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [dyadComponentTagger(), react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); - - - - - -=== -role: assistant -message: OK, got it. I'm ready to help - === role: user message: [dump] \ No newline at end of file diff --git a/e2e-tests/snapshots/dyad_tags_parsing.spec.ts_angle-tags-handled.txt b/e2e-tests/snapshots/dyad_tags_parsing.spec.ts_angle-tags-handled.txt index 3a3be48174..d51f06cf35 100644 --- a/e2e-tests/snapshots/dyad_tags_parsing.spec.ts_angle-tags-handled.txt +++ b/e2e-tests/snapshots/dyad_tags_parsing.spec.ts_angle-tags-handled.txt @@ -1,194 +1,2 @@ -=== .gitignore === -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - - -=== file1.txt === -A file (2) - -=== index.html === - - - - - - dyad-generated-app - - - -
- - - - - -=== package.json === -{ - "name": "vite_react_shadcn_ts", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "build:dev": "vite build --mode development", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" - }, - "devDependencies": { - "@types/node": "^22.5.5", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react-swc": "^4.3.0", - "typescript": "^5.5.3", - "vite": "^7.3.2" - }, - "packageManager": "" -} - -=== pnpm-workspace.yaml === -[Dyad-managed pnpm workspace config] - -=== src/App.tsx === -const App = () =>
Minimal imported app
; - -export default App; - - === src/foo/bar.tsx === // BEGINNING OF FILE - -=== src/main.tsx === -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; - -createRoot(document.getElementById("root")!).render(); - - -=== src/vite-env.d.ts === -/// - - -=== tsconfig.app.json === -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": false, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitAny": false, - "noFallthroughCasesInSwitch": false, - - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["src"] -} - - -=== tsconfig.json === -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ], - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "noImplicitAny": false, - "noUnusedParameters": false, - "skipLibCheck": true, - "allowJs": true, - "noUnusedLocals": false, - "strictNullChecks": false - } -} - - -=== tsconfig.node.json === -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true - }, - "include": ["vite.config.ts"] -} - - -=== vite.config.ts === -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react-swc"; -import path from "path"; - -export default defineConfig(() => ({ - server: { - host: "::", - port: 8080, - }, - plugins: [react()], - resolve: { - alias: { - "@": path.resolve(__dirname, "./src"), - }, - }, -})); diff --git a/e2e-tests/snapshots/dynamic_models.spec.ts_remote-catalog-effort b/e2e-tests/snapshots/dynamic_models.spec.ts_remote-catalog-effort index fadbbaba45..4f81978c0b 100644 --- a/e2e-tests/snapshots/dynamic_models.spec.ts_remote-catalog-effort +++ b/e2e-tests/snapshots/dynamic_models.spec.ts_remote-catalog-effort @@ -11,490 +11,523 @@ "content": "[dump] tc=basic" } ], - "stream": true, - "reasoning_effort": "xhigh", - "dyad_options": { - "versioned_files": { - "fileIdToContent": { - "[[FILE_ID_0]]": "{\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"rewrites\": [\n {\n \"source\": \"/(.*)\",\n \"destination\": \"/index.html\"\n }\n ]\n}\n", - "[[FILE_ID_1]]": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"default\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"tailwind.config.ts\",\n \"css\": \"src/index.css\",\n \"baseColor\": \"slate\",\n \"cssVariables\": true,\n \"prefix\": \"\"\n },\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n }\n}\n", - "[[FILE_ID_2]]": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"useDefineForClassFields\": true,\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n \"module\": \"ESNext\",\n \"skipLibCheck\": true,\n\n /* Bundler mode */\n \"moduleResolution\": \"bundler\",\n \"allowImportingTsExtensions\": true,\n \"isolatedModules\": true,\n \"moduleDetection\": \"force\",\n \"noEmit\": true,\n \"jsx\": \"react-jsx\",\n\n /* Linting */\n \"strict\": false,\n \"noUnusedLocals\": false,\n \"noUnusedParameters\": false,\n \"noImplicitAny\": false,\n \"noFallthroughCasesInSwitch\": false,\n\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n }\n },\n \"include\": [\"src\"]\n}\n", - "[[FILE_ID_3]]": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"lib\": [\"ES2023\"],\n \"module\": \"ESNext\",\n \"skipLibCheck\": true,\n\n /* Bundler mode */\n \"moduleResolution\": \"bundler\",\n \"allowImportingTsExtensions\": true,\n \"isolatedModules\": true,\n \"moduleDetection\": \"force\",\n \"noEmit\": true,\n\n /* Linting */\n \"strict\": true,\n \"noUnusedLocals\": false,\n \"noUnusedParameters\": false,\n \"noFallthroughCasesInSwitch\": true\n },\n \"include\": [\"vite.config.ts\"]\n}\n", - "[[FILE_ID_4]]": "{\n \"files\": [],\n \"references\": [\n { \"path\": \"./tsconfig.app.json\" },\n { \"path\": \"./tsconfig.node.json\" }\n ],\n \"compilerOptions\": {\n \"baseUrl\": \".\",\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n },\n \"noImplicitAny\": false,\n \"noUnusedParameters\": false,\n \"skipLibCheck\": true,\n \"allowJs\": true,\n \"noUnusedLocals\": false,\n \"strictNullChecks\": false\n }\n}\n", - "[[FILE_ID_5]]": "{\n \"name\": \"vite_react_shadcn_ts\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"vite\",\n \"build\": \"vite build\",\n \"build:dev\": \"vite build --mode development\",\n \"lint\": \"eslint .\",\n \"preview\": \"vite preview\"\n },\n \"dependencies\": {\n \"@hookform/resolvers\": \"^3.9.0\",\n \"@radix-ui/react-accordion\": \"^1.2.0\",\n \"@radix-ui/react-alert-dialog\": \"^1.1.1\",\n \"@radix-ui/react-aspect-ratio\": \"^1.1.0\",\n \"@radix-ui/react-avatar\": \"^1.1.0\",\n \"@radix-ui/react-checkbox\": \"^1.1.1\",\n \"@radix-ui/react-collapsible\": \"^1.1.0\",\n \"@radix-ui/react-context-menu\": \"^2.2.1\",\n \"@radix-ui/react-dialog\": \"^1.1.2\",\n \"@radix-ui/react-dropdown-menu\": \"^2.1.1\",\n \"@radix-ui/react-hover-card\": \"^1.1.1\",\n \"@radix-ui/react-label\": \"^2.1.0\",\n \"@radix-ui/react-menubar\": \"^1.1.1\",\n \"@radix-ui/react-navigation-menu\": \"^1.2.0\",\n \"@radix-ui/react-popover\": \"^1.1.1\",\n \"@radix-ui/react-progress\": \"^1.1.0\",\n \"@radix-ui/react-radio-group\": \"^1.2.0\",\n \"@radix-ui/react-scroll-area\": \"^1.1.0\",\n \"@radix-ui/react-select\": \"^2.1.1\",\n \"@radix-ui/react-separator\": \"^1.1.0\",\n \"@radix-ui/react-slider\": \"^1.2.0\",\n \"@radix-ui/react-slot\": \"^1.1.0\",\n \"@radix-ui/react-switch\": \"^1.1.0\",\n \"@radix-ui/react-tabs\": \"^1.1.0\",\n \"@radix-ui/react-toast\": \"^1.2.1\",\n \"@radix-ui/react-toggle\": \"^1.1.0\",\n \"@radix-ui/react-toggle-group\": \"^1.1.0\",\n \"@radix-ui/react-tooltip\": \"^1.1.4\",\n \"@tanstack/react-query\": \"^5.56.2\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cmdk\": \"^1.0.0\",\n \"date-fns\": \"^3.6.0\",\n \"embla-carousel-react\": \"^8.3.0\",\n \"input-otp\": \"^1.2.4\",\n \"lucide-react\": \"^0.462.0\",\n \"next-themes\": \"^0.3.0\",\n \"react\": \"^19.2.3\",\n \"react-day-picker\": \"^9.13.0\",\n \"react-dom\": \"^19.2.3\",\n \"react-hook-form\": \"^7.53.0\",\n \"react-resizable-panels\": \"^2.1.3\",\n \"react-router-dom\": \"^6.30.4\",\n \"recharts\": \"^2.12.7\",\n \"sonner\": \"^1.5.0\",\n \"tailwind-merge\": \"^2.5.2\",\n \"tailwindcss-animate\": \"^1.0.7\",\n \"vaul\": \"^0.9.3\",\n \"zod\": \"^3.25.0\"\n },\n \"devDependencies\": {\n \"@dyad-sh/react-vite-component-tagger\": \"^0.9.0\",\n \"@eslint/js\": \"^9.9.0\",\n \"@tailwindcss/typography\": \"^0.5.15\",\n \"@types/node\": \"^22.5.5\",\n \"@types/react\": \"^19.2.8\",\n \"@types/react-dom\": \"^19.2.3\",\n \"@vitejs/plugin-react-swc\": \"^4.3.0\",\n \"autoprefixer\": \"^10.4.20\",\n \"eslint\": \"^9.9.0\",\n \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n \"eslint-plugin-react-refresh\": \"^0.4.9\",\n \"globals\": \"^15.9.0\",\n \"postcss\": \"^8.4.47\",\n \"tailwindcss\": \"^3.4.11\",\n \"typescript\": \"^5.5.3\",\n \"typescript-eslint\": \"^8.0.1\",\n \"vite\": \"^8.0.0\"\n }\n}\n", - "[[FILE_ID_6]]": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --foreground: 222.2 84% 4.9%;\n\n --card: 0 0% 100%;\n --card-foreground: 222.2 84% 4.9%;\n\n --popover: 0 0% 100%;\n --popover-foreground: 222.2 84% 4.9%;\n\n --primary: 222.2 47.4% 11.2%;\n --primary-foreground: 210 40% 98%;\n\n --secondary: 210 40% 96.1%;\n --secondary-foreground: 222.2 47.4% 11.2%;\n\n --muted: 210 40% 96.1%;\n --muted-foreground: 215.4 16.3% 46.9%;\n\n --accent: 210 40% 96.1%;\n --accent-foreground: 222.2 47.4% 11.2%;\n\n --destructive: 0 84.2% 60.2%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 214.3 31.8% 91.4%;\n --input: 214.3 31.8% 91.4%;\n --ring: 222.2 84% 4.9%;\n\n --radius: 0.5rem;\n\n --sidebar-background: 0 0% 98%;\n\n --sidebar-foreground: 240 5.3% 26.1%;\n\n --sidebar-primary: 240 5.9% 10%;\n\n --sidebar-primary-foreground: 0 0% 98%;\n\n --sidebar-accent: 240 4.8% 95.9%;\n\n --sidebar-accent-foreground: 240 5.9% 10%;\n\n --sidebar-border: 220 13% 91%;\n\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n\n .dark {\n --background: 222.2 84% 4.9%;\n --foreground: 210 40% 98%;\n\n --card: 222.2 84% 4.9%;\n --card-foreground: 210 40% 98%;\n\n --popover: 222.2 84% 4.9%;\n --popover-foreground: 210 40% 98%;\n\n --primary: 210 40% 98%;\n --primary-foreground: 222.2 47.4% 11.2%;\n\n --secondary: 217.2 32.6% 17.5%;\n --secondary-foreground: 210 40% 98%;\n\n --muted: 217.2 32.6% 17.5%;\n --muted-foreground: 215 20.2% 65.1%;\n\n --accent: 217.2 32.6% 17.5%;\n --accent-foreground: 210 40% 98%;\n\n --destructive: 0 62.8% 30.6%;\n --destructive-foreground: 210 40% 98%;\n\n --border: 217.2 32.6% 17.5%;\n --input: 217.2 32.6% 17.5%;\n --ring: 212.7 26.8% 83.9%;\n --sidebar-background: 240 5.9% 10%;\n --sidebar-foreground: 240 4.8% 95.9%;\n --sidebar-primary: 224.3 76.3% 48%;\n --sidebar-primary-foreground: 0 0% 100%;\n --sidebar-accent: 240 3.7% 15.9%;\n --sidebar-accent-foreground: 240 4.8% 95.9%;\n --sidebar-border: 240 3.7% 15.9%;\n --sidebar-ring: 217.2 91.2% 59.8%;\n }\n}\n\n@layer base {\n * {\n @apply border-border;\n }\n\n body {\n @apply bg-background text-foreground;\n }\n}\n", - "[[FILE_ID_7]]": "// File contents excluded from context", - "[[FILE_ID_8]]": "// Update this page (the content is just a fallback if you fail to update the page)\n\nimport { MadeWithDyad } from \"@/components/made-with-dyad\";\n\nconst Index = () => {\n return (\n
\n
\n

Welcome to Your Blank App

\n

\n Start building your amazing project here!\n

\n
\n \n
\n );\n};\n\nexport default Index;\n", - "[[FILE_ID_9]]": "/// \n", - "[[FILE_ID_10]]": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# Playwright test artifacts\n/test-results/\n/playwright-report/\n/playwright/.cache/\n.last-run.json\n.dyad/\n", - "[[FILE_ID_11]]": "# Tech Stack\n\n- You are building a React application.\n- Use TypeScript.\n- Use React Router. KEEP the routes in src/App.tsx\n- Always put source code in the src folder.\n- Put pages into src/pages/\n- Put components into src/components/\n- The main page (default page) is src/pages/Index.tsx\n- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components!\n- ALWAYS try to use the shadcn/ui library.\n- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects.\n\nAvailable packages and libraries:\n\n- The lucide-react package is installed for icons.\n- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again.\n- You have ALL the necessary Radix UI components installed.\n- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them.\n", - "[[FILE_ID_12]]": "# Welcome to your Dyad app\n", - "[[FILE_ID_13]]": "#root {\n max-width: 1280px;\n margin: 0 auto;\n padding: 2rem;\n text-align: center;\n}\n\n.logo {\n height: 6em;\n padding: 1.5em;\n will-change: filter;\n transition: filter 300ms;\n}\n.logo:hover {\n filter: drop-shadow(0 0 2em #646cffaa);\n}\n.logo.react:hover {\n filter: drop-shadow(0 0 2em #61dafbaa);\n}\n\n@keyframes logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: no-preference) {\n a:nth-of-type(2) .logo {\n animation: logo-spin infinite 20s linear;\n }\n}\n\n.card {\n padding: 2em;\n}\n\n.read-the-docs {\n color: #888;\n}\n", - "[[FILE_ID_14]]": "\n\n \n \n \n dyad-generated-app\n \n\n \n
\n \n \n\n", - "[[FILE_ID_15]]": "export const MadeWithDyad = () => {\n return (\n
\n \n Made with Dyad\n \n
\n );\n};\n", - "[[FILE_ID_16]]": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n};\n", - "[[FILE_ID_17]]": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n", - "[[FILE_ID_18]]": "import { cn } from \"@/lib/utils\";\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes) {\n return (\n \n );\n}\n\nexport { Skeleton };\n", - "[[FILE_ID_19]]": "import { createRoot } from \"react-dom/client\";\nimport App from \"./App.tsx\";\nimport \"./globals.css\";\n\ncreateRoot(document.getElementById(\"root\")!).render();\n", - "[[FILE_ID_20]]": "import { defineConfig } from \"vite\";\nimport dyadComponentTagger from \"@dyad-sh/react-vite-component-tagger\";\nimport react from \"@vitejs/plugin-react-swc\";\nimport path from \"path\";\n\nexport default defineConfig(() => ({\n server: {\n host: \"::\",\n port: 8080,\n },\n plugins: [dyadComponentTagger(), react()],\n resolve: {\n alias: {\n \"@\": path.resolve(__dirname, \"./src\"),\n },\n },\n}));\n", - "[[FILE_ID_21]]": "import { GripVertical } from \"lucide-react\";\nimport * as ResizablePrimitive from \"react-resizable-panels\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst ResizablePanelGroup = ({\n className,\n ...props\n}: React.ComponentProps) => (\n \n);\n\nconst ResizablePanel = ResizablePrimitive.Panel;\n\nconst ResizableHandle = ({\n withHandle,\n className,\n ...props\n}: React.ComponentProps & {\n withHandle?: boolean;\n}) => (\n div]:rotate-90\",\n className,\n )}\n {...props}\n >\n {withHandle && (\n
\n \n
\n )}\n \n);\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle };\n", - "[[FILE_ID_22]]": "import { toast } from \"sonner\";\n\nexport const showSuccess = (message: string) => {\n toast.success(message);\n};\n\nexport const showError = (message: string) => {\n toast.error(message);\n};\n\nexport const showLoading = (message: string) => {\n return toast.loading(message);\n};\n\nexport const dismissToast = (toastId: string) => {\n toast.dismiss(toastId);\n};\n", - "[[FILE_ID_23]]": "import { Toaster } from \"@/components/ui/toaster\";\nimport { Toaster as Sonner } from \"@/components/ui/sonner\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport { BrowserRouter, Routes, Route } from \"react-router-dom\";\nimport Index from \"./pages/Index\";\nimport NotFound from \"./pages/NotFound\";\n\nconst queryClient = new QueryClient();\n\nconst App = () => (\n \n \n \n \n \n \n } />\n {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL \"*\" ROUTE */}\n } />\n \n \n \n \n);\n\nexport default App;\n", - "[[FILE_ID_24]]": "import { useLocation } from \"react-router-dom\";\nimport { useEffect } from \"react\";\n\nconst NotFound = () => {\n const location = useLocation();\n\n useEffect(() => {\n console.error(\n \"404 Error: User attempted to access non-existent route:\",\n location.pathname,\n );\n }, [location.pathname]);\n\n return (\n
\n
\n

404

\n

Oops! Page not found

\n \n Return to Home\n \n
\n
\n );\n};\n\nexport default NotFound;\n", - "[[FILE_ID_25]]": "import { useTheme } from \"next-themes\";\nimport { Toaster as Sonner } from \"sonner\";\n\ntype ToasterProps = React.ComponentProps;\n\nconst Toaster = ({ ...props }: ToasterProps) => {\n const { theme = \"system\" } = useTheme();\n\n return (\n \n );\n};\n\nexport { Toaster };\n", - "[[FILE_ID_26]]": "import { useToast } from \"@/hooks/use-toast\";\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from \"@/components/ui/toast\";\n\nexport function Toaster() {\n const { toasts } = useToast();\n\n return (\n \n {toasts.map(function ({ id, title, description, action, ...props }) {\n return (\n \n
\n {title && {title}}\n {description && (\n {description}\n )}\n
\n {action}\n \n
\n );\n })}\n \n
\n );\n}\n", - "[[FILE_ID_27]]": "import { useToast, toast } from \"@/hooks/use-toast\";\n\nexport { useToast, toast };\n", - "[[FILE_ID_28]]": "import * as AspectRatioPrimitive from \"@radix-ui/react-aspect-ratio\";\n\nconst AspectRatio = AspectRatioPrimitive.Root;\n\nexport { AspectRatio };\n", - "[[FILE_ID_29]]": "import * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\";\n\nconst Collapsible = CollapsiblePrimitive.Root;\n\nconst CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;\n\nconst CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;\n\nexport { Collapsible, CollapsibleTrigger, CollapsibleContent };\n", - "[[FILE_ID_30]]": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n const [isMobile, setIsMobile] = React.useState(\n undefined,\n );\n\n React.useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => {\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n };\n mql.addEventListener(\"change\", onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener(\"change\", onChange);\n }, []);\n\n return !!isMobile;\n}\n", - "[[FILE_ID_31]]": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardDescription,\n CardContent,\n};\n", - "[[FILE_ID_32]]": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Input = React.forwardRef>(\n ({ className, type, ...props }, ref) => {\n return (\n \n );\n },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n", - "[[FILE_ID_33]]": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n
\n \n
\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<\n HTMLTableSectionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n tr]:last:border-b-0\",\n className,\n )}\n {...props}\n />\n));\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<\n HTMLTableRowElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<\n HTMLTableCellElement,\n React.ThHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<\n HTMLTableCellElement,\n React.TdHTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<\n HTMLTableCaptionElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n};\n", - "[[FILE_ID_34]]": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface TextareaProps extends React.TextareaHTMLAttributes {}\n\nconst Textarea = React.forwardRef(\n ({ className, ...props }, ref) => {\n return (\n \n );\n },\n);\nTextarea.displayName = \"Textarea\";\n\nexport { Textarea };\n", - "[[FILE_ID_35]]": "import * as React from \"react\";\n\nimport type { ToastActionElement, ToastProps } from \"@/components/ui/toast\";\n\nconst TOAST_LIMIT = 1;\nconst TOAST_REMOVE_DELAY = 1000000;\n\ntype ToasterToast = ToastProps & {\n id: string;\n title?: React.ReactNode;\n description?: React.ReactNode;\n action?: ToastActionElement;\n};\n\nconst _actionTypes = {\n ADD_TOAST: \"ADD_TOAST\",\n UPDATE_TOAST: \"UPDATE_TOAST\",\n DISMISS_TOAST: \"DISMISS_TOAST\",\n REMOVE_TOAST: \"REMOVE_TOAST\",\n} as const;\n\nlet count = 0;\n\nfunction genId() {\n count = (count + 1) % Number.MAX_SAFE_INTEGER;\n return count.toString();\n}\n\ntype ActionType = typeof _actionTypes;\n\ntype Action =\n | {\n type: ActionType[\"ADD_TOAST\"];\n toast: ToasterToast;\n }\n | {\n type: ActionType[\"UPDATE_TOAST\"];\n toast: Partial;\n }\n | {\n type: ActionType[\"DISMISS_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n }\n | {\n type: ActionType[\"REMOVE_TOAST\"];\n toastId?: ToasterToast[\"id\"];\n };\n\ninterface State {\n toasts: ToasterToast[];\n}\n\nconst toastTimeouts = new Map>();\n\nconst addToRemoveQueue = (toastId: string) => {\n if (toastTimeouts.has(toastId)) {\n return;\n }\n\n const timeout = setTimeout(() => {\n toastTimeouts.delete(toastId);\n dispatch({\n type: \"REMOVE_TOAST\",\n toastId: toastId,\n });\n }, TOAST_REMOVE_DELAY);\n\n toastTimeouts.set(toastId, timeout);\n};\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case \"ADD_TOAST\":\n return {\n ...state,\n toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),\n };\n\n case \"UPDATE_TOAST\":\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === action.toast.id ? { ...t, ...action.toast } : t,\n ),\n };\n\n case \"DISMISS_TOAST\": {\n const { toastId } = action;\n\n // ! Side effects ! - This could be extracted into a dismissToast() action,\n // but I'll keep it here for simplicity\n if (toastId) {\n addToRemoveQueue(toastId);\n } else {\n state.toasts.forEach((toast) => {\n addToRemoveQueue(toast.id);\n });\n }\n\n return {\n ...state,\n toasts: state.toasts.map((t) =>\n t.id === toastId || toastId === undefined\n ? {\n ...t,\n open: false,\n }\n : t,\n ),\n };\n }\n case \"REMOVE_TOAST\":\n if (action.toastId === undefined) {\n return {\n ...state,\n toasts: [],\n };\n }\n return {\n ...state,\n toasts: state.toasts.filter((t) => t.id !== action.toastId),\n };\n }\n};\n\nconst listeners: Array<(state: State) => void> = [];\n\nlet memoryState: State = { toasts: [] };\n\nfunction dispatch(action: Action) {\n memoryState = reducer(memoryState, action);\n listeners.forEach((listener) => {\n listener(memoryState);\n });\n}\n\ntype Toast = Omit;\n\nfunction toast({ ...props }: Toast) {\n const id = genId();\n\n const update = (props: ToasterToast) =>\n dispatch({\n type: \"UPDATE_TOAST\",\n toast: { ...props, id },\n });\n const dismiss = () => dispatch({ type: \"DISMISS_TOAST\", toastId: id });\n\n dispatch({\n type: \"ADD_TOAST\",\n toast: {\n ...props,\n id,\n open: true,\n onOpenChange: (open) => {\n if (!open) dismiss();\n },\n },\n });\n\n return {\n id: id,\n dismiss,\n update,\n };\n}\n\nfunction useToast() {\n const [state, setState] = React.useState(memoryState);\n\n React.useEffect(() => {\n listeners.push(setState);\n return () => {\n const index = listeners.indexOf(setState);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n }, [state]);\n\n return {\n ...state,\n toast,\n dismiss: (toastId?: string) => dispatch({ type: \"DISMISS_TOAST\", toastId }),\n };\n}\n\nexport { useToast, toast };\n", - "[[FILE_ID_36]]": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight } from \"lucide-react\";\nimport { DayPicker } from \"react-day-picker\";\n\nimport { cn } from \"@/lib/utils\";\nimport { buttonVariants } from \"@/components/ui/button\";\n\nexport type CalendarProps = React.ComponentProps;\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: CalendarProps) {\n return (\n \n orientation === \"left\" ? (\n \n ) : (\n \n ),\n }}\n {...props}\n />\n );\n}\nCalendar.displayName = \"Calendar\";\n\nexport { Calendar };\n", - "[[FILE_ID_37]]": "import * as React from \"react\";\nimport { ChevronLeft, ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { ButtonProps, buttonVariants } from \"@/components/ui/button\";\n\nconst Pagination = ({ className, ...props }: React.ComponentProps<\"nav\">) => (\n \n);\nPagination.displayName = \"Pagination\";\n\nconst PaginationContent = React.forwardRef<\n HTMLUListElement,\n React.ComponentProps<\"ul\">\n>(({ className, ...props }, ref) => (\n \n));\nPaginationContent.displayName = \"PaginationContent\";\n\nconst PaginationItem = React.forwardRef<\n HTMLLIElement,\n React.ComponentProps<\"li\">\n>(({ className, ...props }, ref) => (\n
  • \n));\nPaginationItem.displayName = \"PaginationItem\";\n\ntype PaginationLinkProps = {\n isActive?: boolean;\n} & Pick &\n React.ComponentProps<\"a\">;\n\nconst PaginationLink = ({\n className,\n isActive,\n size = \"icon\",\n ...props\n}: PaginationLinkProps) => (\n \n);\nPaginationLink.displayName = \"PaginationLink\";\n\nconst PaginationPrevious = ({\n className,\n ...props\n}: React.ComponentProps) => (\n \n \n Previous\n \n);\nPaginationPrevious.displayName = \"PaginationPrevious\";\n\nconst PaginationNext = ({\n className,\n ...props\n}: React.ComponentProps) => (\n \n Next\n \n \n);\nPaginationNext.displayName = \"PaginationNext\";\n\nconst PaginationEllipsis = ({\n className,\n ...props\n}: React.ComponentProps<\"span\">) => (\n \n \n More pages\n \n);\nPaginationEllipsis.displayName = \"PaginationEllipsis\";\n\nexport {\n Pagination,\n PaginationContent,\n PaginationEllipsis,\n PaginationItem,\n PaginationLink,\n PaginationNext,\n PaginationPrevious,\n};\n", - "[[FILE_ID_38]]": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst alertVariants = cva(\n \"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground\",\n {\n variants: {\n variant: {\n default: \"bg-background text-foreground\",\n destructive:\n \"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nconst Alert = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes & VariantProps\n>(({ className, variant, ...props }, ref) => (\n \n));\nAlert.displayName = \"Alert\";\n\nconst AlertTitle = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nAlertTitle.displayName = \"AlertTitle\";\n\nconst AlertDescription = React.forwardRef<\n HTMLParagraphElement,\n React.HTMLAttributes\n>(({ className, ...props }, ref) => (\n \n));\nAlertDescription.displayName = \"AlertDescription\";\n\nexport { Alert, AlertTitle, AlertDescription };\n", - "[[FILE_ID_39]]": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst badgeVariants = cva(\n \"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n {\n variants: {\n variant: {\n default:\n \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary:\n \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive:\n \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n outline: \"text-foreground\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n },\n);\n\nexport interface BadgeProps\n extends\n React.HTMLAttributes,\n VariantProps {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n return (\n
    \n );\n}\n\nexport { Badge, badgeVariants };\n", - "[[FILE_ID_40]]": "import * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Drawer = ({\n shouldScaleBackground = true,\n ...props\n}: React.ComponentProps) => (\n \n);\nDrawer.displayName = \"Drawer\";\n\nconst DrawerTrigger = DrawerPrimitive.Trigger;\n\nconst DrawerPortal = DrawerPrimitive.Portal;\n\nconst DrawerClose = DrawerPrimitive.Close;\n\nconst DrawerOverlay = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;\n\nconst DrawerContent = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, children, ...props }, ref) => (\n \n \n \n
    \n {children}\n \n \n));\nDrawerContent.displayName = \"DrawerContent\";\n\nconst DrawerHeader = ({\n className,\n ...props\n}: React.HTMLAttributes) => (\n \n);\nDrawerHeader.displayName = \"DrawerHeader\";\n\nconst DrawerFooter = ({\n className,\n ...props\n}: React.HTMLAttributes) => (\n \n);\nDrawerFooter.displayName = \"DrawerFooter\";\n\nconst DrawerTitle = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDrawerTitle.displayName = DrawerPrimitive.Title.displayName;\n\nconst DrawerDescription = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, ...props }, ref) => (\n \n));\nDrawerDescription.displayName = DrawerPrimitive.Description.displayName;\n\nexport {\n Drawer,\n DrawerPortal,\n DrawerOverlay,\n DrawerTrigger,\n DrawerClose,\n DrawerContent,\n DrawerHeader,\n DrawerFooter,\n DrawerTitle,\n DrawerDescription,\n};\n", - "[[FILE_ID_41]]": "import * as React from \"react\";\nimport { OTPInput, OTPInputContext } from \"input-otp\";\nimport { Dot } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst InputOTP = React.forwardRef<\n React.ElementRef,\n React.ComponentPropsWithoutRef\n>(({ className, containerClassName, ...props }, ref) => (\n \n));\nInputOTP.displayName = \"InputOTP\";\n\nconst InputOTPGroup = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ className, ...props }, ref) => (\n
    \n));\nInputOTPGroup.displayName = \"InputOTPGroup\";\n\nconst InputOTPSlot = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\"> & { index: number }\n>(({ index, className, ...props }, ref) => {\n const inputOTPContext = React.useContext(OTPInputContext);\n const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];\n\n return (\n \n {char}\n {hasFakeCaret && (\n
    \n
    \n
    \n )}\n
    \n );\n});\nInputOTPSlot.displayName = \"InputOTPSlot\";\n\nconst InputOTPSeparator = React.forwardRef<\n React.ElementRef<\"div\">,\n React.ComponentPropsWithoutRef<\"div\">\n>(({ ...props }, ref) => (\n
    \n \n
    \n));\nInputOTPSeparator.displayName = \"InputOTPSeparator\";\n\nexport { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };\n", - "[[FILE_ID_42]]": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { ChevronRight, MoreHorizontal } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Breadcrumb = React.forwardRef<\n HTMLElement,\n React.ComponentPropsWithoutRef<\"nav\"> & {\n separator?: React.ReactNode;\n }\n>(({ ...props }, ref) =>
  • \n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({\n className,\n ...props\n}: React.ComponentProps<\"span\">) => (\n \n \n More\n \n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbElipssis\";\n\nexport {\n Breadcrumb,\n BreadcrumbList,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbPage,\n BreadcrumbSeparator,\n BreadcrumbEllipsis,\n};\n", - "[[FILE_ID_43]]": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground hover:bg-destructive/90\",\n outline:\n \"border border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n ghost: \"hover:bg-accent hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-4 py-2\",\n sm: \"h-9 rounded-md px-3\",\n lg: \"h-11 rounded-md px-8\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes,\n VariantProps {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n \n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n", - "[[FILE_ID_44]]": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { VariantProps, cva } from \"class-variance-authority\";\nimport { PanelLeft } from \"lucide-react\";\n\nimport { useIsMobile } from \"@/hooks/use-mobile\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Sheet, SheetContent } from \"@/components/ui/sheet\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nconst SIDEBAR_COOKIE_NAME = \"sidebar:state\";\nconst SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;\nconst SIDEBAR_WIDTH = \"16rem\";\nconst SIDEBAR_WIDTH_MOBILE = \"18rem\";\nconst SIDEBAR_WIDTH_ICON = \"3rem\";\nconst SIDEBAR_KEYBOARD_SHORTCUT = \"b\";\n\ntype SidebarContext = {\n state: \"expanded\" | \"collapsed\";\n open: boolean;\n setOpen: (open: boolean) => void;\n openMobile: boolean;\n setOpenMobile: (open: boolean) => void;\n isMobile: boolean;\n toggleSidebar: () => void;\n};\n\nconst SidebarContext = React.createContext(null);\n\nfunction useSidebar() {\n const context = React.useContext(SidebarContext);\n if (!context) {\n throw new Error(\"useSidebar must be used within a SidebarProvider.\");\n }\n\n return context;\n}\n\nconst SidebarProvider = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n defaultOpen?: boolean;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n }\n>(\n (\n {\n defaultOpen = true,\n open: openProp,\n onOpenChange: setOpenProp,\n className,\n style,\n children,\n ...props\n },\n ref,\n ) => {\n const isMobile = useIsMobile();\n const [openMobile, setOpenMobile] = React.useState(false);\n\n // This is the internal state of the sidebar.\n // We use openProp and setOpenProp for control from outside the component.\n const [_open, _setOpen] = React.useState(defaultOpen);\n const open = openProp ?? _open;\n const setOpen = React.useCallback(\n (value: boolean | ((value: boolean) => boolean)) => {\n const openState = typeof value === \"function\" ? value(open) : value;\n if (setOpenProp) {\n setOpenProp(openState);\n } else {\n _setOpen(openState);\n }\n\n // This sets the cookie to keep the sidebar state.\n document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;\n },\n [setOpenProp, open],\n );\n\n // Helper to toggle the sidebar.\n const toggleSidebar = React.useCallback(() => {\n return isMobile\n ? setOpenMobile((open) => !open)\n : setOpen((open) => !open);\n }, [isMobile, setOpen, setOpenMobile]);\n\n // Adds a keyboard shortcut to toggle the sidebar.\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (\n event.key === SIDEBAR_KEYBOARD_SHORTCUT &&\n (event.metaKey || event.ctrlKey)\n ) {\n event.preventDefault();\n toggleSidebar();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [toggleSidebar]);\n\n // We add a state so that we can do data-state=\"expanded\" or \"collapsed\".\n // This makes it easier to style the sidebar with Tailwind classes.\n const state = open ? \"expanded\" : \"collapsed\";\n\n const contextValue = React.useMemo(\n () => ({\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n }),\n [\n state,\n open,\n setOpen,\n isMobile,\n openMobile,\n setOpenMobile,\n toggleSidebar,\n ],\n );\n\n return (\n \n \n \n {children}\n
    \n \n \n );\n },\n);\nSidebarProvider.displayName = \"SidebarProvider\";\n\nconst Sidebar = React.forwardRef<\n HTMLDivElement,\n React.ComponentProps<\"div\"> & {\n side?: \"left\" | \"right\";\n variant?: \"sidebar\" | \"floating\" | \"inset\";\n collapsible?: \"offcanvas\" | \"icon\" | \"none\";\n }\n>(\n (\n {\n side = \"left\",\n variant = \"sidebar\",\n collapsible = \"offcanvas\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const { isMobile, state, openMobile, setOpenMobile } = useSidebar();\n\n if (collapsible === \"none\") {\n return (\n \n {children}\n \n );\n }\n\n if (isMobile) {\n return (\n \n button]:hidden\"\n style={\n {\n \"--sidebar-width\": SIDEBAR_WIDTH_MOBILE,\n } as React.CSSProperties\n }\n side={side}\n >\n
    {children}
    \n \n
    \n );\n }\n\n return (\n