feat: implement fuzzy search and replace functionality with Levenshtein distance
- Added `applySearchReplace` function to handle search and replace operations with fuzzy matching capabilities. - Introduced tests for various scenarios including fuzzy matching with typos, exact matches, and handling whitespace differences. - Created a parser for search/replace blocks to facilitate the new functionality. - Updated prompts for search-replace operations to clarify usage and examples. - Added utility functions for text normalization and language detection based on file extensions. - Implemented a minimal stdio MCP server for local testing with tools for adding numbers and printing environment variables.
This commit is contained in:
52
e2e-tests/add_prompt_deep_link.spec.ts
Normal file
52
e2e-tests/add_prompt_deep_link.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test("add prompt via deep link with base64-encoded data", async ({
|
||||
po,
|
||||
electronApp,
|
||||
}) => {
|
||||
await po.setUp();
|
||||
await po.goToLibraryTab();
|
||||
|
||||
// Verify library is empty initially
|
||||
await expect(po.page.getByTestId("prompt-card")).not.toBeVisible();
|
||||
|
||||
// Create the prompt data to be encoded
|
||||
const promptData = {
|
||||
title: "Deep Link Test Prompt",
|
||||
description: "A prompt created via deep link",
|
||||
content: "You are a helpful assistant. Please help with:\n\n[task here]",
|
||||
};
|
||||
|
||||
// Encode the data as base64 (matching the pattern in main.ts)
|
||||
const base64Data = Buffer.from(JSON.stringify(promptData)).toString("base64");
|
||||
const deepLinkUrl = `dyad://add-prompt?data=${encodeURIComponent(base64Data)}`;
|
||||
|
||||
console.log("Triggering deep link:", deepLinkUrl);
|
||||
|
||||
// Trigger the deep link by emitting the 'open-url' event in the main process
|
||||
await electronApp.evaluate(({ app }, url) => {
|
||||
app.emit("open-url", { preventDefault: () => {} }, url);
|
||||
}, deepLinkUrl);
|
||||
|
||||
// Wait for the dialog to open and verify prefilled data
|
||||
await expect(
|
||||
po.page.getByRole("dialog").getByText("Create New Prompt"),
|
||||
).toBeVisible();
|
||||
|
||||
// Verify the form is prefilled with the correct data
|
||||
await expect(po.page.getByRole("textbox", { name: "Title" })).toHaveValue(
|
||||
promptData.title,
|
||||
);
|
||||
await expect(
|
||||
po.page.getByRole("textbox", { name: "Description (optional)" }),
|
||||
).toHaveValue(promptData.description);
|
||||
await expect(po.page.getByRole("textbox", { name: "Content" })).toHaveValue(
|
||||
promptData.content,
|
||||
);
|
||||
|
||||
// Save the prompt
|
||||
await po.page.getByRole("button", { name: "Save" }).click();
|
||||
|
||||
await expect(po.page.getByTestId("prompt-card")).toMatchAriaSnapshot();
|
||||
});
|
||||
291
e2e-tests/app_search.spec.ts
Normal file
291
e2e-tests/app_search.spec.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test("app search - basic search dialog functionality", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
await po.goToAppsTab();
|
||||
await po.page.getByTestId("search-apps-button").waitFor();
|
||||
|
||||
// Create some apps for testing
|
||||
await po.sendPrompt("create a todo application");
|
||||
|
||||
// Go back to apps list
|
||||
await po.goToAppsTab();
|
||||
await po.page.getByTestId("search-apps-button").waitFor();
|
||||
|
||||
// Create second app
|
||||
await po.sendPrompt("build a weather dashboard");
|
||||
|
||||
// Go back to apps list
|
||||
await po.goToAppsTab();
|
||||
await po.page.getByTestId("search-apps-button").waitFor();
|
||||
|
||||
// Create third app
|
||||
await po.sendPrompt("create a blog system");
|
||||
|
||||
// Go back to apps list
|
||||
await po.goToAppsTab();
|
||||
await po.page.getByTestId("search-apps-button").waitFor();
|
||||
|
||||
// Test 1: Open search dialog using the search button
|
||||
const searchButton = po.page.getByTestId("search-apps-button");
|
||||
await searchButton.click();
|
||||
|
||||
// Wait for search dialog to appear
|
||||
const dialog = po.page.getByTestId("app-search-dialog");
|
||||
await dialog.waitFor({ state: "visible", timeout: 10000 });
|
||||
|
||||
// Test 2: Close dialog with Ctrl+K (shortcut toggles)
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await dialog.waitFor({ state: "hidden", timeout: 5000 });
|
||||
|
||||
// Test 3: Open dialog again with Ctrl+K (shortcut toggles)
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await dialog.waitFor({ state: "visible", timeout: 10000 });
|
||||
|
||||
// Test 4: Search for specific term
|
||||
await po.page.getByPlaceholder("Search apps").fill("app");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Test 5: Clear search and close with Escape
|
||||
await po.page.getByPlaceholder("Search apps").clear();
|
||||
await po.page.keyboard.press("Escape");
|
||||
await dialog.waitFor({ state: "hidden", timeout: 5000 });
|
||||
});
|
||||
|
||||
test("app search - search functionality with different terms", async ({
|
||||
po,
|
||||
}) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create apps with specific content for testing
|
||||
await po.sendPrompt("create a calculator application with advanced features");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("build a task management system with priority levels");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create a weather monitoring dashboard");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Search for "calculator" - should find the calculator app through chat content
|
||||
await po.page.getByPlaceholder("Search apps").fill("calculator");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Search for "task" - should find the task management app
|
||||
await po.page.getByPlaceholder("Search apps").fill("task");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Search for "weather" - should find the weather dashboard
|
||||
await po.page.getByPlaceholder("Search apps").fill("weather");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Search for non-existent term
|
||||
await po.page.getByPlaceholder("Search apps").fill("nonexistent");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Should show empty state
|
||||
await po.page.getByTestId("app-search-empty").waitFor();
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test("app search - keyboard shortcut functionality", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create an app first
|
||||
await po.sendPrompt("create sample application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Test keyboard shortcut (Ctrl+K) to open dialog
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Close with escape
|
||||
await po.page.keyboard.press("Escape");
|
||||
await po.page.getByTestId("app-search-dialog").waitFor({ state: "hidden" });
|
||||
|
||||
// Test keyboard shortcut again
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Close with Ctrl+K (toggle)
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await po.page.getByTestId("app-search-dialog").waitFor({ state: "hidden" });
|
||||
});
|
||||
|
||||
test("app search - navigation and selection", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create multiple apps
|
||||
await po.sendPrompt("create first application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create second application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create third application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Get all app items in the search results
|
||||
const searchItems = await po.page.getByTestId(/^app-search-item-/).all();
|
||||
|
||||
if (searchItems.length > 0) {
|
||||
// Click on the first search result
|
||||
await searchItems[0].click();
|
||||
|
||||
// Dialog should close after selection
|
||||
await po.page.getByTestId("app-search-dialog").waitFor({ state: "hidden" });
|
||||
|
||||
// Should navigate to the selected app
|
||||
await po.page.waitForTimeout(1000);
|
||||
} else {
|
||||
// If no items found, just close the dialog
|
||||
await po.page.keyboard.press("Escape");
|
||||
}
|
||||
});
|
||||
|
||||
test("app search - empty search shows all apps", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create a few apps
|
||||
await po.sendPrompt("create alpha application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create beta application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create gamma application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Clear any existing search (should show all apps)
|
||||
await po.page.getByPlaceholder("Search apps").clear();
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Should show all apps in the list
|
||||
const searchItems = await po.page.getByTestId(/^app-search-item-/).all();
|
||||
console.log(`Found ${searchItems.length} apps in search results`);
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test("app search - case insensitive search", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create an app with mixed case content
|
||||
await po.sendPrompt("create a Test Application with Mixed Case Content");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Search with different cases
|
||||
await po.page.getByPlaceholder("Search apps").fill("test");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("TEST");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("Test");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("MIXED");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test("app search - partial word matching", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create an app with a long descriptive name
|
||||
await po.sendPrompt("create a comprehensive project management solution");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Search with partial words
|
||||
await po.page.getByPlaceholder("Search apps").fill("proj");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("manage");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("comp");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.getByPlaceholder("Search apps").fill("sol");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test("app search - search by app name", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create apps - note that app names are randomly generated
|
||||
await po.sendPrompt("create a todo application");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("build a weather dashboard");
|
||||
await po.goToAppsTab();
|
||||
|
||||
await po.sendPrompt("create a blog system");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Get the actual app names from the UI (these are randomly generated)
|
||||
const appItems = await po.page.getByTestId(/^app-list-item-/).all();
|
||||
const appNames: string[] = [];
|
||||
for (const item of appItems) {
|
||||
const testId = await item.getAttribute("data-testid");
|
||||
if (testId) {
|
||||
const appName = testId.replace("app-list-item-", "");
|
||||
appNames.push(appName);
|
||||
}
|
||||
}
|
||||
|
||||
// Open search dialog
|
||||
await po.page.getByTestId("search-apps-button").click();
|
||||
await po.page.getByTestId("app-search-dialog").waitFor();
|
||||
|
||||
// Test searching by actual app names (randomly generated)
|
||||
if (appNames.length > 0) {
|
||||
// Search for the first few characters of the first app name
|
||||
const firstAppName = appNames[0];
|
||||
const searchTerm = firstAppName.substring(
|
||||
0,
|
||||
Math.min(4, firstAppName.length),
|
||||
);
|
||||
await po.page.getByPlaceholder("Search apps").fill(searchTerm);
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Clear and search for second app if available
|
||||
if (appNames.length > 1) {
|
||||
await po.page.getByPlaceholder("Search apps").clear();
|
||||
const secondAppName = appNames[1];
|
||||
const secondSearchTerm = secondAppName.substring(
|
||||
0,
|
||||
Math.min(4, secondAppName.length),
|
||||
);
|
||||
await po.page.getByPlaceholder("Search apps").fill(secondSearchTerm);
|
||||
await po.page.waitForTimeout(500);
|
||||
}
|
||||
}
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
61
e2e-tests/azure_provider_settings.spec.ts
Normal file
61
e2e-tests/azure_provider_settings.spec.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { test as testWithPo } from "./helpers/test_helper";
|
||||
|
||||
testWithPo("Azure provider settings UI", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
|
||||
// Look for Azure OpenAI in the provider list
|
||||
await expect(po.page.getByText("Azure OpenAI")).toBeVisible();
|
||||
|
||||
// Navigate to Azure provider settings
|
||||
await po.page.getByText("Azure OpenAI").click();
|
||||
|
||||
// Wait for Azure settings page to load
|
||||
await po.page.waitForSelector('h1:has-text("Configure Azure OpenAI")', {
|
||||
state: "visible",
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
// Confirm the new configuration form is rendered
|
||||
await expect(
|
||||
po.page.getByText("Azure OpenAI Configuration Required"),
|
||||
).toBeVisible();
|
||||
await expect(po.page.getByLabel("Resource Name")).toBeVisible();
|
||||
await expect(po.page.getByLabel("API Key")).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Save Settings" }),
|
||||
).toBeVisible();
|
||||
|
||||
// Environment variable helper section should still be available
|
||||
await expect(
|
||||
po.page.getByText("Environment Variables (optional)"),
|
||||
).toBeVisible();
|
||||
|
||||
// FIX: disambiguate text matches to avoid strict mode violation
|
||||
await expect(
|
||||
po.page.getByText("AZURE_API_KEY", { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByText("AZURE_RESOURCE_NAME", { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
// Since no env vars are configured in the test run, both should read "Not Set"
|
||||
await expect(
|
||||
po.page
|
||||
.getByTestId("azure-api-key-status")
|
||||
.getByText("Not Set", { exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
po.page
|
||||
.getByTestId("azure-resource-name-status")
|
||||
.getByText("Not Set", { exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
// The guidance text should explain precedence between saved settings and environment variables
|
||||
await expect(
|
||||
po.page.getByText(
|
||||
"Values saved in Settings take precedence over environment variables.",
|
||||
),
|
||||
).toBeVisible();
|
||||
});
|
||||
25
e2e-tests/azure_send_message.spec.ts
Normal file
25
e2e-tests/azure_send_message.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { testWithConfigSkipIfWindows } from "./helpers/test_helper";
|
||||
|
||||
// Set environment variables before the test runs to enable Azure testing
|
||||
|
||||
const testAzure = testWithConfigSkipIfWindows({
|
||||
preLaunchHook: async () => {
|
||||
process.env.TEST_AZURE_BASE_URL = "http://localhost:3500/azure";
|
||||
process.env.AZURE_API_KEY = "fake-azure-key-for-testing";
|
||||
process.env.AZURE_RESOURCE_NAME = "fake-resource-for-testing";
|
||||
},
|
||||
});
|
||||
|
||||
testAzure("send message through Azure OpenAI", async ({ po }) => {
|
||||
// Set up Azure without test provider
|
||||
await po.setUpAzure();
|
||||
|
||||
// Select Azure model
|
||||
await po.selectTestAzureModel();
|
||||
|
||||
// Send a test prompt that returns a normal conversational response
|
||||
await po.sendPrompt("tc=basic");
|
||||
|
||||
// Verify we get a response (this means Azure integration is working)
|
||||
await po.snapshotMessages();
|
||||
});
|
||||
50
e2e-tests/chat_input.spec.ts
Normal file
50
e2e-tests/chat_input.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test("send button disabled during pending proposal", async ({ po }) => {
|
||||
await po.setUp();
|
||||
|
||||
// Send a prompt that generates a proposal
|
||||
await po.sendPrompt("Create a simple React component");
|
||||
|
||||
// Wait for proposal buttons to appear (ensuring proposal is rendered)
|
||||
await expect(po.page.getByTestId("approve-proposal-button")).toBeVisible();
|
||||
|
||||
// Type something in the input to ensure it's not disabled due to empty input
|
||||
await po.getChatInput().fill("test message");
|
||||
|
||||
// Check send button is disabled due to pending changes
|
||||
const sendButton = po.page.getByRole("button", { name: "Send message" });
|
||||
await expect(sendButton).toBeDisabled();
|
||||
|
||||
// Approve the proposal
|
||||
await po.approveProposal();
|
||||
|
||||
// Check send button is enabled again
|
||||
await expect(sendButton).toBeEnabled();
|
||||
});
|
||||
|
||||
test("send button disabled during pending proposal - reject", async ({
|
||||
po,
|
||||
}) => {
|
||||
await po.setUp();
|
||||
|
||||
// Send a prompt that generates a proposal
|
||||
await po.sendPrompt("Create a simple React component");
|
||||
|
||||
// Wait for proposal buttons to appear (ensuring proposal is rendered)
|
||||
await expect(po.page.getByTestId("reject-proposal-button")).toBeVisible();
|
||||
|
||||
// Type something in the input to ensure it's not disabled due to empty input
|
||||
await po.getChatInput().fill("test message");
|
||||
|
||||
// Check send button is disabled due to pending changes
|
||||
const sendButton = po.page.getByRole("button", { name: "Send message" });
|
||||
await expect(sendButton).toBeDisabled();
|
||||
|
||||
// Reject the proposal
|
||||
await po.rejectProposal();
|
||||
|
||||
// Check send button is enabled again
|
||||
await expect(sendButton).toBeEnabled();
|
||||
});
|
||||
@@ -22,3 +22,37 @@ test("chat mode selector - ask mode", async ({ po }) => {
|
||||
await po.snapshotServerDump("all-messages");
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
|
||||
test.skip("dyadwrite edit and save - basic flow", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
await po.clickNewChat();
|
||||
|
||||
await po.sendPrompt(
|
||||
"Create a simple React component in src/components/Hello.tsx",
|
||||
);
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.clickEditButton();
|
||||
await po.editFileContent("// Test modification\n");
|
||||
|
||||
await po.saveFile();
|
||||
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
|
||||
test("dyadwrite edit and cancel", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
await po.clickNewChat();
|
||||
|
||||
await po.sendPrompt("Create a utility function in src/utils/helper.ts");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.clickEditButton();
|
||||
|
||||
await po.editFileContent("// This should be discarded\n");
|
||||
await po.cancelEdit();
|
||||
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
|
||||
126
e2e-tests/chat_search.spec.ts
Normal file
126
e2e-tests/chat_search.spec.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test.skip("chat search - basic search dialog functionality", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
// Create some chats with specific names for testing
|
||||
await po.sendPrompt("[dump] create a todo application");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.clickNewChat();
|
||||
await po.sendPrompt("[dump] build a weather dashboard");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.clickNewChat();
|
||||
await po.sendPrompt("[dump] create a blog system");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
// Test 1: Open search dialog using the search button
|
||||
await po.page.getByTestId("search-chats-button").click();
|
||||
|
||||
// Wait for search dialog to appear
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor();
|
||||
|
||||
// Test 2: Close dialog with escape key
|
||||
await po.page.keyboard.press("Escape");
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor({ state: "hidden" });
|
||||
|
||||
// Test 3: Open dialog again and verify it shows chats
|
||||
await po.page.getByTestId("search-chats-button").click();
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor();
|
||||
|
||||
// Test 4: Search for specific term
|
||||
await po.page.getByPlaceholder("Search chats").fill("todo");
|
||||
|
||||
// Wait a moment for search results
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Test 5: Clear search and close
|
||||
await po.page.getByPlaceholder("Search chats").clear();
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test.skip("chat search - with named chats for easier testing", async ({
|
||||
po,
|
||||
}) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
// Create chats with descriptive names that will be useful for testing
|
||||
await po.sendPrompt("[dump] hello world app");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
// Use a timeout to ensure the UI has updated before trying to interact
|
||||
await po.page.waitForTimeout(1000);
|
||||
|
||||
await po.clickNewChat();
|
||||
await po.sendPrompt("[dump] todo list manager");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.page.waitForTimeout(1000);
|
||||
|
||||
await po.clickNewChat();
|
||||
await po.sendPrompt("[dump] weather forecast widget");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.page.waitForTimeout(1000);
|
||||
|
||||
// Test search functionality
|
||||
await po.page.getByTestId("search-chats-button").click();
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor();
|
||||
|
||||
// Search for "todo" - should find the todo list manager chat
|
||||
await po.page.getByPlaceholder("Search chats").fill("todo");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Search for "weather" - should find the weather forecast widget chat
|
||||
await po.page.getByPlaceholder("Search chats").fill("weather");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
// Search for non-existent term
|
||||
await po.page.getByPlaceholder("Search chats").fill("nonexistent");
|
||||
await po.page.waitForTimeout(500);
|
||||
|
||||
await po.page.keyboard.press("Escape");
|
||||
});
|
||||
|
||||
test.skip("chat search - keyboard shortcut functionality", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
// Create a chat
|
||||
await po.sendPrompt("[dump] sample app");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
// Test keyboard shortcut (Ctrl+K)
|
||||
await po.page.keyboard.press("Control+k");
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor();
|
||||
|
||||
// Close with escape
|
||||
await po.page.keyboard.press("Escape");
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor({ state: "hidden" });
|
||||
});
|
||||
|
||||
test.skip("chat search - navigation and selection", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
// Create multiple chats
|
||||
await po.sendPrompt("[dump] first application");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.clickNewChat();
|
||||
await po.sendPrompt("[dump] second application");
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
// Test selecting a chat through search
|
||||
await po.page.getByTestId("search-chats-button").click();
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor();
|
||||
|
||||
// Select the first chat item (assuming it shows "Untitled Chat" as default title)
|
||||
await po.page.getByText("Untitled Chat").first().click();
|
||||
|
||||
// Dialog should close
|
||||
await po.page.getByTestId("chat-search-dialog").waitFor({ state: "hidden" });
|
||||
});
|
||||
25
e2e-tests/concurrent_chat.spec.ts
Normal file
25
e2e-tests/concurrent_chat.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test("concurrent chat", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.sendPrompt("tc=chat1 [sleep=medium]", {
|
||||
skipWaitForCompletion: true,
|
||||
});
|
||||
// Need a short wait otherwise the click on Apps tab is ignored.
|
||||
await po.sleep(2_000);
|
||||
|
||||
await po.goToAppsTab();
|
||||
await po.sendPrompt("tc=chat2");
|
||||
await po.snapshotMessages();
|
||||
await po.clickChatActivityButton();
|
||||
|
||||
// Chat #1 will be the last in the list
|
||||
expect(
|
||||
await po.page.getByTestId(`chat-activity-list-item-1`).textContent(),
|
||||
).toContain("Chat #1");
|
||||
await po.page.getByTestId(`chat-activity-list-item-1`).click();
|
||||
await po.snapshotMessages({ timeout: 12_000 });
|
||||
|
||||
//
|
||||
});
|
||||
71
e2e-tests/copy_chat.spec.ts
Normal file
71
e2e-tests/copy_chat.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test("copy message content - basic functionality", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
await po.sendPrompt("[dump] Just say hello without creating any files");
|
||||
|
||||
await po.page
|
||||
.context()
|
||||
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
|
||||
const copyButton = po.page.getByTestId("copy-message-button").first();
|
||||
await copyButton.click();
|
||||
|
||||
const clipboardContent = await po.page.evaluate(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
|
||||
// Test that copy functionality works
|
||||
expect(clipboardContent.length).toBeGreaterThan(0);
|
||||
expect(clipboardContent).not.toContain("<dyad-");
|
||||
});
|
||||
|
||||
test("copy message content - dyad-write conversion", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
await po.sendPrompt(
|
||||
"Create a simple React component in src/components/Button.tsx",
|
||||
);
|
||||
|
||||
await po.page
|
||||
.context()
|
||||
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
|
||||
const copyButton = po.page.getByTestId("copy-message-button").first();
|
||||
await copyButton.click();
|
||||
|
||||
const clipboardContent = await po.page.evaluate(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
|
||||
// Should convert dyad-write to markdown format (flexible path matching)
|
||||
expect(clipboardContent).toContain("### File:");
|
||||
expect(clipboardContent).toContain("```");
|
||||
expect(clipboardContent).not.toContain("<dyad-write");
|
||||
});
|
||||
|
||||
test("copy button tooltip states", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.importApp("minimal");
|
||||
|
||||
await po.sendPrompt("Say hello");
|
||||
|
||||
const copyButton = po.page.getByTestId("copy-message-button").first();
|
||||
|
||||
// Check initial tooltip
|
||||
await copyButton.hover();
|
||||
const tooltip = po.page.locator('[role="tooltip"]');
|
||||
await expect(tooltip).toHaveText("Copy");
|
||||
|
||||
// Copy and check "Copied!" state
|
||||
await po.page
|
||||
.context()
|
||||
.grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await copyButton.click();
|
||||
await copyButton.hover();
|
||||
await expect(tooltip).toHaveText("Copied!");
|
||||
});
|
||||
@@ -3,10 +3,8 @@ import { test } from "./helpers/test_helper";
|
||||
test("delete custom provider should not freeze", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
await po.page.getByTestId("custom-provider-more-options").click();
|
||||
await po.page.getByTestId("delete-custom-provider").click();
|
||||
await po.page.getByRole("button", { name: "Delete Provider" }).click();
|
||||
await po.page.getByRole("button", { name: "Delete Provider" }).click();
|
||||
|
||||
// Make sure UI hasn't freezed
|
||||
await po.goToAppsTab();
|
||||
});
|
||||
|
||||
24
e2e-tests/edit_provider.spec.ts
Normal file
24
e2e-tests/edit_provider.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test("can edit custom provider", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
|
||||
// Create a provider first
|
||||
|
||||
// Edit it
|
||||
await po.page.getByTestId("edit-custom-provider").click();
|
||||
await po.page.getByRole("textbox", { name: "Display Name" }).clear();
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "Display Name" })
|
||||
.fill("Updated Test Provider");
|
||||
|
||||
await po.page.getByRole("textbox", { name: "API Base URL" }).clear();
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "API Base URL" })
|
||||
.fill("https://api.updated-test.com/v1");
|
||||
|
||||
await po.page.getByRole("button", { name: "Update Provider" }).click();
|
||||
|
||||
// Make sure UI hasn't freezed
|
||||
});
|
||||
@@ -9,28 +9,11 @@ testSkipIfWindows("send message to engine", async ({ po }) => {
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
|
||||
testSkipIfWindows(
|
||||
"send message to engine - smart context balanced",
|
||||
async ({ po }) => {
|
||||
await po.setUpDyadPro();
|
||||
const proModesDialog = await po.openProModesDialog({
|
||||
location: "home-chat-input-container",
|
||||
});
|
||||
await proModesDialog.setSmartContextMode("balanced");
|
||||
await proModesDialog.close();
|
||||
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" });
|
||||
await po.sendPrompt("[dump] tc=turbo-edits");
|
||||
|
||||
await po.snapshotServerDump("request");
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
},
|
||||
);
|
||||
|
||||
testSkipIfWindows("send message to engine - openai gpt-4.1", async ({ po }) => {
|
||||
testSkipIfWindows("send message to engine - openai gpt-5", async ({ po }) => {
|
||||
await po.setUpDyadPro();
|
||||
// By default, it's using auto which points to Flash 2.5 and doesn't
|
||||
// use engine.
|
||||
await po.selectModel({ provider: "OpenAI", model: "GPT 4.1" });
|
||||
await po.selectModel({ provider: "OpenAI", model: "GPT 5" });
|
||||
await po.sendPrompt("[dump] tc=turbo-edits");
|
||||
|
||||
await po.snapshotServerDump("request");
|
||||
|
||||
72
e2e-tests/favorite_app.spec.ts
Normal file
72
e2e-tests/favorite_app.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test.describe("Favorite App Tests", () => {
|
||||
test("Add app to favorite", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create a test app
|
||||
await po.sendPrompt("create a test app");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Get the app name from the UI (randomly generated)
|
||||
const appItems = await po.page.getByTestId(/^app-list-item-/).all();
|
||||
expect(appItems.length).toBeGreaterThan(0);
|
||||
const firstAppItem = appItems[0];
|
||||
const testId = await firstAppItem.getAttribute("data-testid");
|
||||
const appName = testId!.replace("app-list-item-", "");
|
||||
|
||||
// Get the app item (assuming it's not favorited initially)
|
||||
const appItem = po.page.locator(`[data-testid="app-list-item-${appName}"]`);
|
||||
await expect(appItem).toBeVisible();
|
||||
|
||||
// Click the favorite button
|
||||
const favoriteButton = appItem
|
||||
.locator("xpath=..")
|
||||
.locator('[data-testid="favorite-button"]');
|
||||
await expect(favoriteButton).toBeVisible();
|
||||
await favoriteButton.click();
|
||||
|
||||
// Check that the star is filled (favorited)
|
||||
const star = favoriteButton.locator("svg");
|
||||
await expect(star).toHaveClass(/fill-\[#6c55dc\]/);
|
||||
});
|
||||
|
||||
test("Remove app from favorite", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
// Create a test app
|
||||
await po.sendPrompt("create a test app");
|
||||
await po.goToAppsTab();
|
||||
|
||||
// Get the app name from the UI
|
||||
const appItems = await po.page.getByTestId(/^app-list-item-/).all();
|
||||
expect(appItems.length).toBeGreaterThan(0);
|
||||
const firstAppItem = appItems[0];
|
||||
const testId = await firstAppItem.getAttribute("data-testid");
|
||||
const appName = testId!.replace("app-list-item-", "");
|
||||
|
||||
// Get the app item
|
||||
const appItem = po.page.locator(`[data-testid="app-list-item-${appName}"]`);
|
||||
|
||||
// First, add to favorite
|
||||
const favoriteButton = appItem
|
||||
.locator("xpath=..")
|
||||
.locator('[data-testid="favorite-button"]');
|
||||
await favoriteButton.click();
|
||||
|
||||
// Check that the star is filled (favorited)
|
||||
const star = favoriteButton.locator("svg");
|
||||
await expect(star).toHaveClass(/fill-\[#6c55dc\]/);
|
||||
|
||||
// Now, remove from favorite
|
||||
const unfavoriteButton = appItem
|
||||
.locator("xpath=..")
|
||||
.locator('[data-testid="favorite-button"]');
|
||||
await expect(unfavoriteButton).toBeVisible();
|
||||
await unfavoriteButton.click();
|
||||
|
||||
// Check that the star is not filled (unfavorited)
|
||||
await expect(star).not.toHaveClass(/fill-\[#6c55dc\]/);
|
||||
});
|
||||
});
|
||||
1
e2e-tests/fixtures/azure/basic.md
Normal file
1
e2e-tests/fixtures/azure/basic.md
Normal file
@@ -0,0 +1 @@
|
||||
This is a simple basic response
|
||||
3
e2e-tests/fixtures/engine/read-index.md
Normal file
3
e2e-tests/fixtures/engine/read-index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
Read the index page:
|
||||
<dyad-read path="src/pages/Index.tsx"></dyad-read>
|
||||
Done.
|
||||
10
e2e-tests/fixtures/engine/turbo-edits-v2-trigger-fallback.md
Normal file
10
e2e-tests/fixtures/engine/turbo-edits-v2-trigger-fallback.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Example with turbo edit v2
|
||||
<dyad-search-replace path="src/pages/Index.tsx">
|
||||
<<<<<<< SEARCH
|
||||
// Intentionally DO NOT MATCH ANYTHING TO TRIGGER FALLBACK
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1>
|
||||
=======
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to the UPDATED App</h1>
|
||||
>>>>>>> REPLACE
|
||||
</dyad-search-replace>
|
||||
End of turbo edit
|
||||
9
e2e-tests/fixtures/engine/turbo-edits-v2.md
Normal file
9
e2e-tests/fixtures/engine/turbo-edits-v2.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Example with turbo edit v2
|
||||
<dyad-search-replace path="src/pages/Index.tsx">
|
||||
<<<<<<< SEARCH
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1>
|
||||
=======
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to the UPDATED App</h1>
|
||||
>>>>>>> REPLACE
|
||||
</dyad-search-replace>
|
||||
End of turbo edit
|
||||
4
e2e-tests/fixtures/engine/update-index-1.md
Normal file
4
e2e-tests/fixtures/engine/update-index-1.md
Normal file
@@ -0,0 +1,4 @@
|
||||
First read
|
||||
<dyad-write path="src/pages/Index.tsx" description="replace file">
|
||||
// this file has been replaced
|
||||
</dyad-write>
|
||||
@@ -1 +1 @@
|
||||
["should not be included b/c it's json"]
|
||||
["even json is included"]
|
||||
|
||||
132
e2e-tests/fixtures/security-review/findings.md
Normal file
132
e2e-tests/fixtures/security-review/findings.md
Normal file
@@ -0,0 +1,132 @@
|
||||
OK, let's review the security.
|
||||
|
||||
Here are variations with different severity levels.
|
||||
|
||||
Purposefully putting medium on top to make sure the severity levels are sorted correctly.
|
||||
|
||||
## Medium Severity
|
||||
|
||||
<dyad-security-finding title="Unvalidated File Upload Extensions" level="medium">
|
||||
**What**: The file upload endpoint accepts any file type without validating extensions or content, only checking file size
|
||||
|
||||
**Risk**: An attacker could upload malicious files (e.g., .exe, .php) that might be executed if the server is misconfigured, or upload extremely large files to consume storage space
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Implement a whitelist of allowed file extensions (e.g., `.jpg`, `.png`, `.pdf`)
|
||||
2. Validate file content type using magic numbers, not just the extension
|
||||
3. Store uploaded files outside the web root with random filenames
|
||||
4. Implement virus scanning for uploaded files using ClamAV or similar
|
||||
|
||||
**Relevant Files**: `src/api/upload.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
<dyad-security-finding title="Missing CSRF Protection on State-Changing Operations" level="medium">
|
||||
**What**: POST, PUT, and DELETE endpoints don't implement CSRF tokens, making them vulnerable to cross-site request forgery attacks
|
||||
|
||||
**Risk**: An attacker could trick authenticated users into unknowingly performing actions like changing their email, making purchases, or deleting data by visiting a malicious website
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Implement CSRF tokens using a library like `csurf` for Express
|
||||
2. Set `SameSite=Strict` or `SameSite=Lax` on session cookies
|
||||
3. Verify the `Origin` or `Referer` header for sensitive operations
|
||||
4. For API-only applications, consider using custom headers that browsers can't set cross-origin
|
||||
|
||||
**Relevant Files**: `src/middleware/auth.ts`, `src/api/*.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
## Critical Severity
|
||||
|
||||
<dyad-security-finding title="SQL Injection in User Lookup" level="critical">
|
||||
**What**: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands
|
||||
|
||||
**Risk**: An attacker could steal all customer data, delete your entire database, or take over admin accounts by manipulating the URL
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Use parameterized queries: `db.query('SELECT * FROM users WHERE id = ?', [userId])`
|
||||
2. Add input validation to ensure `userId` is a number
|
||||
3. Implement an ORM like Prisma or TypeORM that prevents SQL injection by default
|
||||
|
||||
**Relevant Files**: `src/api/users.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
<dyad-security-finding title="Hardcoded AWS Credentials in Source Code" level="critical">
|
||||
**What**: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access
|
||||
|
||||
**Risk**: Anyone with repository access (including former employees or compromised accounts) could spin up expensive resources, access S3 buckets with customer data, or destroy production infrastructure
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Immediately rotate the exposed credentials in AWS IAM
|
||||
2. Use environment variables and add `.env` to `.gitignore`
|
||||
3. Implement AWS Secrets Manager or similar vault solution
|
||||
4. Scan git history and purge the credentials using tools like `git-filter-repo`
|
||||
|
||||
**Relevant Files**: `src/config/aws.ts`, `src/services/s3-uploader.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
## High Severity
|
||||
|
||||
<dyad-security-finding title="Missing Authentication on Admin Endpoints" level="high">
|
||||
**What**: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity
|
||||
|
||||
**Risk**: An attacker who discovers these endpoints could modify user permissions, access sensitive reports, or change system configurations without credentials
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Add authentication middleware to all `/admin/*` routes
|
||||
2. Implement role-based access control (RBAC) to verify admin permissions
|
||||
3. Add audit logging for all administrative actions
|
||||
4. Consider implementing rate limiting on admin endpoints
|
||||
|
||||
**Relevant Files**: `src/api/admin/users.ts`, `src/api/admin/settings.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
<dyad-security-finding title="JWT Secret Using Default Value" level="high">
|
||||
**What**: The application uses a hardcoded default JWT secret ("your-secret-key") for signing authentication tokens
|
||||
|
||||
**Risk**: Attackers can forge valid JWT tokens to impersonate any user, including administrators, granting them unauthorized access to user accounts and sensitive data
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Generate a strong random secret: `openssl rand -base64 32`
|
||||
2. Store the secret in environment variables
|
||||
3. Rotate the JWT secret, which will invalidate all existing sessions
|
||||
4. Consider using RS256 (asymmetric) instead of HS256 for better security
|
||||
|
||||
**Relevant Files**: `src/auth/jwt.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
## Low Severity
|
||||
|
||||
<dyad-security-finding title="Verbose Error Messages Expose Stack Traces" level="low">
|
||||
**What**: Production error responses include full stack traces and internal file paths that are sent to end users
|
||||
|
||||
**Risk**: Attackers can use this information to map your application structure, identify frameworks and versions, and find potential attack vectors more easily
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Configure different error handlers for production vs development
|
||||
2. Log detailed errors server-side but send generic messages to clients
|
||||
3. Use an error handling middleware: `if (process.env.NODE_ENV === 'production') { /* hide details */ }`
|
||||
4. Implement centralized error logging with tools like Sentry
|
||||
|
||||
**Relevant Files**: `src/middleware/error-handler.ts`
|
||||
|
||||
</dyad-security-finding>
|
||||
|
||||
<dyad-security-finding title="Missing Security Headers" level="low">
|
||||
**What**: The application doesn't set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security`
|
||||
|
||||
**Risk**: Users may be vulnerable to clickjacking attacks, MIME-type sniffing, or man-in-the-middle attacks, though exploitation requires specific conditions
|
||||
|
||||
**Potential Solutions**:
|
||||
1. Use Helmet.js middleware: `app.use(helmet())`
|
||||
2. Configure headers manually in your web server (nginx/Apache) or application
|
||||
3. Set `Content-Security-Policy` to prevent XSS attacks
|
||||
4. Enable HSTS to enforce HTTPS connections
|
||||
|
||||
**Relevant Files**: `src/app.ts`, `nginx.conf`
|
||||
|
||||
</dyad-security-finding>
|
||||
@@ -1,19 +0,0 @@
|
||||
import { testSkipIfWindows } from "./helpers/test_helper";
|
||||
|
||||
testSkipIfWindows("claude 4 sonnet", async ({ po }) => {
|
||||
await po.setUpDyadPro();
|
||||
// Disable the pro modes so it routes to gateway.
|
||||
const proModesDialog = await po.openProModesDialog({
|
||||
location: "home-chat-input-container",
|
||||
});
|
||||
await proModesDialog.toggleTurboEdits();
|
||||
await proModesDialog.setSmartContextMode("off");
|
||||
await proModesDialog.close();
|
||||
|
||||
await po.selectModel({ provider: "Anthropic", model: "Claude 4 Sonnet" });
|
||||
|
||||
await po.sendPrompt("[dump] tc=gateway-simple");
|
||||
|
||||
await po.snapshotServerDump("request");
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
170
e2e-tests/github-import.spec.ts
Normal file
170
e2e-tests/github-import.spec.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test("should open GitHub import modal from home", async ({ po }) => {
|
||||
await po.setUp();
|
||||
// Click the "Import from Github" button
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
// Verify modal opened with import UI (showing all tabs even when not authenticated)
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Import App" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByText(
|
||||
"Import existing app from local folder or clone from Github",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
// All tabs should be visible
|
||||
await expect(
|
||||
po.page.getByRole("tab", { name: "Local Folder" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByRole("tab", { name: "Your GitHub Repos" }),
|
||||
).toBeVisible();
|
||||
await expect(po.page.getByRole("tab", { name: "GitHub URL" })).toBeVisible();
|
||||
// Local Folder tab should be active by default
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Select Folder" }),
|
||||
).toBeVisible();
|
||||
// Switch to Your GitHub Repos tab - should show GitHub connector
|
||||
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Connect to GitHub" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("should connect to GitHub and show import UI", async ({ po }) => {
|
||||
await po.setUp();
|
||||
// Open modal
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
// Switch to Your GitHub Repos tab - should show GitHub connector when not authenticated
|
||||
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
|
||||
// Connect to GitHub (reuse existing connector)
|
||||
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
|
||||
// Wait for device flow code
|
||||
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
|
||||
// After connection, should show repositories list instead of connector
|
||||
await expect(po.page.getByText("testuser/existing-app")).toBeVisible();
|
||||
// Should be able to see all tabs
|
||||
await expect(
|
||||
po.page.getByRole("tab", { name: "Your GitHub Repos" }),
|
||||
).toBeVisible();
|
||||
await expect(po.page.getByRole("tab", { name: "GitHub URL" })).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByRole("tab", { name: "Local Folder" }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("should import GitHub URL", async ({ po }) => {
|
||||
await po.setUp();
|
||||
// Open modal and connect
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
|
||||
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
|
||||
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
|
||||
// Switch to "GitHub URL" tab
|
||||
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
|
||||
// Enter URL
|
||||
await po.page
|
||||
.getByPlaceholder("https://github.com/user/repo.git")
|
||||
.fill("https://github.com/dyad-sh/nextjs-template.git");
|
||||
|
||||
// Click import
|
||||
await po.page.getByRole("button", { name: "Import", exact: true }).click();
|
||||
// Should close modal and navigate to chat
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Import App" }),
|
||||
).not.toBeVisible();
|
||||
// Verify AI_RULES generation prompt was sent
|
||||
});
|
||||
|
||||
test("should import from repository list", async ({ po }) => {
|
||||
await po.setUp();
|
||||
|
||||
// Open modal and connect
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
// Switch to Your GitHub Repos tab - should show GitHub connector when not authenticated
|
||||
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
|
||||
await po.page.getByRole("button", { name: "Connect to GitHub" }).click();
|
||||
await expect(po.page.locator("text=FAKE-CODE")).toBeVisible();
|
||||
|
||||
// Switch to Your GitHub Repos tab
|
||||
await po.page.getByRole("tab", { name: "Your GitHub Repos" }).click();
|
||||
|
||||
// Should show repositories list
|
||||
await expect(po.page.getByText("testuser/existing-app")).toBeVisible();
|
||||
|
||||
// Click the first Import button in the repo list
|
||||
await po.page.getByRole("button", { name: "Import" }).first().click();
|
||||
|
||||
// Should close modal and navigate to chat
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Import App" }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should support advanced options with custom commands", async ({ po }) => {
|
||||
await po.setUp();
|
||||
|
||||
// Open modal and connect
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
// Go to GitHub URL tab
|
||||
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
|
||||
await po.page
|
||||
.getByPlaceholder("https://github.com/user/repo.git")
|
||||
.fill("https://github.com/dyad-sh/nextjs-template.git");
|
||||
|
||||
// Open advanced options
|
||||
await po.page.getByRole("button", { name: "Advanced options" }).click();
|
||||
|
||||
// Fill one command - should show error
|
||||
await po.page.getByPlaceholder("pnpm install").fill("npm install");
|
||||
await expect(
|
||||
po.page.getByText("Both commands are required when customizing"),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Import", exact: true }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Fill both commands
|
||||
await po.page.getByPlaceholder("pnpm dev").fill("npm start");
|
||||
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Import", exact: true }),
|
||||
).toBeEnabled();
|
||||
await expect(
|
||||
po.page.getByText("Both commands are required when customizing"),
|
||||
).not.toBeVisible();
|
||||
|
||||
// Import with custom commands
|
||||
await po.page.getByRole("button", { name: "Import", exact: true }).click();
|
||||
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Import App" }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("should allow empty commands to use defaults", async ({ po }) => {
|
||||
await po.setUp();
|
||||
|
||||
// Open modal and connect
|
||||
await po.page.getByRole("button", { name: "Import App" }).click();
|
||||
|
||||
// Go to GitHub URL tab
|
||||
await po.page.getByRole("tab", { name: "GitHub URL" }).click();
|
||||
await po.page
|
||||
.getByPlaceholder("https://github.com/user/repo.git")
|
||||
.fill("https://github.com/dyad-sh/nextjs-template.git");
|
||||
|
||||
// Commands are empty by default, so import should be enabled
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: "Import", exact: true }),
|
||||
).toBeEnabled();
|
||||
|
||||
await po.page.getByRole("button", { name: "Import", exact: true }).click();
|
||||
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Import App" }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
@@ -71,16 +71,25 @@ class ProModesDialog {
|
||||
public close: () => Promise<void>,
|
||||
) {}
|
||||
|
||||
async setSmartContextMode(mode: "balanced" | "off" | "conservative") {
|
||||
async setSmartContextMode(mode: "balanced" | "off" | "deep") {
|
||||
await this.page
|
||||
.getByTestId("smart-context-selector")
|
||||
.getByRole("button", {
|
||||
name: mode.charAt(0).toUpperCase() + mode.slice(1),
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async toggleTurboEdits() {
|
||||
await this.page.getByRole("switch", { name: "Turbo Edits" }).click();
|
||||
async setTurboEditsMode(mode: "off" | "classic" | "search-replace") {
|
||||
await this.page
|
||||
.getByTestId("turbo-edits-selector")
|
||||
.getByRole("button", {
|
||||
name:
|
||||
mode === "search-replace"
|
||||
? "Search & replace"
|
||||
: mode.charAt(0).toUpperCase() + mode.slice(1),
|
||||
})
|
||||
.click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +339,7 @@ export class PageObject {
|
||||
await this.page.getByRole("button", { name: "Import" }).click();
|
||||
}
|
||||
|
||||
async selectChatMode(mode: "build" | "ask") {
|
||||
async selectChatMode(mode: "build" | "ask" | "agent") {
|
||||
await this.page.getByTestId("chat-mode-selector").click();
|
||||
await this.page.getByRole("option", { name: mode }).click();
|
||||
}
|
||||
@@ -362,7 +371,7 @@ export class PageObject {
|
||||
await expect(this.page.getByRole("dialog")).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
async snapshotAppFiles({ name }: { name: string }) {
|
||||
async snapshotAppFiles({ name, files }: { name: string; files?: string[] }) {
|
||||
const currentAppName = await this.getCurrentAppName();
|
||||
if (!currentAppName) {
|
||||
throw new Error("No app selected");
|
||||
@@ -374,10 +383,17 @@ export class PageObject {
|
||||
}
|
||||
|
||||
await expect(() => {
|
||||
const filesData = generateAppFilesSnapshotData(appPath, appPath);
|
||||
let filesData = generateAppFilesSnapshotData(appPath, appPath);
|
||||
|
||||
// Sort by relative path to ensure deterministic output
|
||||
filesData.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
||||
if (files) {
|
||||
filesData = filesData.filter((file) =>
|
||||
files.some(
|
||||
(f) => normalizePath(f) === normalizePath(file.relativePath),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const snapshotContent = filesData
|
||||
.map(
|
||||
@@ -400,7 +416,8 @@ export class PageObject {
|
||||
|
||||
async snapshotMessages({
|
||||
replaceDumpPath = false,
|
||||
}: { replaceDumpPath?: boolean } = {}) {
|
||||
timeout,
|
||||
}: { replaceDumpPath?: boolean; timeout?: number } = {}) {
|
||||
if (replaceDumpPath) {
|
||||
// Update page so that "[[dyad-dump-path=*]]" is replaced with a placeholder path
|
||||
// which is stable across runs.
|
||||
@@ -417,7 +434,9 @@ export class PageObject {
|
||||
);
|
||||
});
|
||||
}
|
||||
await expect(this.page.getByTestId("messages-list")).toMatchAriaSnapshot();
|
||||
await expect(this.page.getByTestId("messages-list")).toMatchAriaSnapshot({
|
||||
timeout,
|
||||
});
|
||||
}
|
||||
|
||||
async approveProposal() {
|
||||
@@ -431,15 +450,48 @@ export class PageObject {
|
||||
async clickRestart() {
|
||||
await this.page.getByRole("button", { name: "Restart" }).click();
|
||||
}
|
||||
////////////////////////////////
|
||||
// Inline code editor
|
||||
////////////////////////////////
|
||||
async clickEditButton() {
|
||||
await this.page.locator('button:has-text("Edit")').first().click();
|
||||
}
|
||||
|
||||
async editFileContent(content: string) {
|
||||
const editor = this.page.locator(".monaco-editor textarea").first();
|
||||
await editor.focus();
|
||||
await editor.press("Home");
|
||||
await editor.type(content);
|
||||
}
|
||||
|
||||
async saveFile() {
|
||||
await this.page.locator('[data-testid="save-file-button"]').click();
|
||||
}
|
||||
|
||||
async cancelEdit() {
|
||||
await this.page.locator('button:has-text("Cancel")').first().click();
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// Preview panel
|
||||
////////////////////////////////
|
||||
|
||||
async selectPreviewMode(mode: "code" | "problems" | "preview" | "configure") {
|
||||
async selectPreviewMode(
|
||||
mode: "code" | "problems" | "preview" | "configure" | "security",
|
||||
) {
|
||||
await this.page.getByTestId(`${mode}-mode-button`).click();
|
||||
}
|
||||
|
||||
async clickChatActivityButton() {
|
||||
await this.page.getByTestId("chat-activity-button").click();
|
||||
}
|
||||
|
||||
async snapshotChatActivityList() {
|
||||
await expect(
|
||||
this.page.getByTestId("chat-activity-list"),
|
||||
).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
async clickRecheckProblems() {
|
||||
await this.page.getByTestId("recheck-button").click();
|
||||
}
|
||||
@@ -470,8 +522,15 @@ export class PageObject {
|
||||
.click({ timeout: Timeout.EXTRA_LONG });
|
||||
}
|
||||
|
||||
async clickDeselectComponent() {
|
||||
await this.page.getByRole("button", { name: "Deselect component" }).click();
|
||||
async clickDeselectComponent(options?: { index?: number }) {
|
||||
const buttons = this.page.getByRole("button", {
|
||||
name: "Deselect component",
|
||||
});
|
||||
if (options?.index !== undefined) {
|
||||
await buttons.nth(options.index).click();
|
||||
} else {
|
||||
await buttons.first().click();
|
||||
}
|
||||
}
|
||||
|
||||
async clickPreviewMoreOptions() {
|
||||
@@ -530,12 +589,12 @@ export class PageObject {
|
||||
await expect(this.getChatInputContainer()).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
getSelectedComponentDisplay() {
|
||||
getSelectedComponentsDisplay() {
|
||||
return this.page.getByTestId("selected-component-display");
|
||||
}
|
||||
|
||||
async snapshotSelectedComponentDisplay() {
|
||||
await expect(this.getSelectedComponentDisplay()).toMatchAriaSnapshot();
|
||||
async snapshotSelectedComponentsDisplay() {
|
||||
await expect(this.getSelectedComponentsDisplay()).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
async snapshotPreview({ name }: { name?: string } = {}) {
|
||||
@@ -546,6 +605,12 @@ export class PageObject {
|
||||
});
|
||||
}
|
||||
|
||||
async snapshotSecurityFindingsTable() {
|
||||
await expect(
|
||||
this.page.getByTestId("security-findings-table"),
|
||||
).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
async snapshotServerDump(
|
||||
type: "all-messages" | "last-message" | "request" = "all-messages",
|
||||
{ name = "", dumpIndex = -1 }: { name?: string; dumpIndex?: number } = {},
|
||||
@@ -648,7 +713,7 @@ export class PageObject {
|
||||
|
||||
getChatInput() {
|
||||
return this.page.locator(
|
||||
'[data-lexical-editor="true"][aria-placeholder="Ask Dyad to build..."]',
|
||||
'[data-lexical-editor="true"][aria-placeholder^="Ask Dyad to build"]',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -664,16 +729,21 @@ export class PageObject {
|
||||
await this.page.getByRole("button", { name: "Back" }).click();
|
||||
}
|
||||
|
||||
async sendPrompt(prompt: string) {
|
||||
async sendPrompt(
|
||||
prompt: string,
|
||||
{ skipWaitForCompletion = false }: { skipWaitForCompletion?: boolean } = {},
|
||||
) {
|
||||
await this.getChatInput().click();
|
||||
await this.getChatInput().fill(prompt);
|
||||
await this.page.getByRole("button", { name: "Send message" }).click();
|
||||
await this.waitForChatCompletion();
|
||||
if (!skipWaitForCompletion) {
|
||||
await this.waitForChatCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
async selectModel({ provider, model }: { provider: string; model: string }) {
|
||||
await this.page.getByRole("button", { name: "Model: Auto" }).click();
|
||||
await this.page.getByText(provider).click();
|
||||
await this.page.getByText(provider, { exact: true }).click();
|
||||
await this.page.getByText(model, { exact: true }).click();
|
||||
}
|
||||
|
||||
@@ -701,6 +771,24 @@ export class PageObject {
|
||||
.click();
|
||||
}
|
||||
|
||||
async selectTestAzureModel() {
|
||||
await this.page.getByRole("button", { name: "Model: Auto" }).click();
|
||||
await this.page.getByText("Other AI providers").click();
|
||||
await this.page.getByText("Azure OpenAI", { exact: true }).click();
|
||||
await this.page.getByText("GPT-5", { exact: true }).click();
|
||||
}
|
||||
|
||||
async setUpAzure({ autoApprove = false }: { autoApprove?: boolean } = {}) {
|
||||
await this.githubConnector.clearPushEvents();
|
||||
await this.goToSettingsTab();
|
||||
if (autoApprove) {
|
||||
await this.toggleAutoApprove();
|
||||
}
|
||||
// Azure should already be configured via environment variables
|
||||
// so we don't need additional setup steps like setUpDyadProvider
|
||||
await this.goToAppsTab();
|
||||
}
|
||||
|
||||
async setUpTestProvider() {
|
||||
await this.page.getByText("Add custom providerConnect to").click();
|
||||
// Fill out provider dialog
|
||||
@@ -719,9 +807,7 @@ export class PageObject {
|
||||
}
|
||||
|
||||
async setUpTestModel() {
|
||||
await this.page
|
||||
.getByRole("heading", { name: "test-provider Needs Setup" })
|
||||
.click();
|
||||
await this.page.getByRole("heading", { name: "test-provider" }).click();
|
||||
await this.page.getByRole("button", { name: "Add Custom Model" }).click();
|
||||
await this.page
|
||||
.getByRole("textbox", { name: "Model ID*" })
|
||||
@@ -984,6 +1070,7 @@ export class PageObject {
|
||||
|
||||
interface ElectronConfig {
|
||||
preLaunchHook?: ({ userDataDir }: { userDataDir: string }) => Promise<void>;
|
||||
showSetupScreen?: boolean;
|
||||
}
|
||||
|
||||
// From https://github.com/microsoft/playwright/issues/8208#issuecomment-1435475930
|
||||
@@ -1046,8 +1133,10 @@ export const test = base.extend<{
|
||||
process.env.DYAD_ENGINE_URL = "http://localhost:3500/engine/v1";
|
||||
process.env.DYAD_GATEWAY_URL = "http://localhost:3500/gateway/v1";
|
||||
process.env.E2E_TEST_BUILD = "true";
|
||||
// This is just a hack to avoid the AI setup screen.
|
||||
process.env.OPENAI_API_KEY = "sk-test";
|
||||
if (!electronConfig.showSetupScreen) {
|
||||
// This is just a hack to avoid the AI setup screen.
|
||||
process.env.OPENAI_API_KEY = "sk-test";
|
||||
}
|
||||
const baseTmpDir = os.tmpdir();
|
||||
const userDataDir = path.join(baseTmpDir, `dyad-e2e-tests-${Date.now()}`);
|
||||
if (electronConfig.preLaunchHook) {
|
||||
@@ -1131,6 +1220,17 @@ export function testWithConfig(config: ElectronConfig) {
|
||||
});
|
||||
}
|
||||
|
||||
export function testWithConfigSkipIfWindows(config: ElectronConfig) {
|
||||
if (os.platform() === "win32") {
|
||||
return test.skip;
|
||||
}
|
||||
return test.extend({
|
||||
electronConfig: async ({}, use) => {
|
||||
await use(config);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Wrapper that skips tests on Windows platform
|
||||
export const testSkipIfWindows = os.platform() === "win32" ? test.skip : test;
|
||||
|
||||
@@ -1163,3 +1263,7 @@ function prettifyDump(
|
||||
})
|
||||
.join("\n\n");
|
||||
}
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return path.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
50
e2e-tests/mcp.spec.ts
Normal file
50
e2e-tests/mcp.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import path from "path";
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test("mcp - call calculator", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
await po.page.getByRole("button", { name: "Tools (MCP)" }).click();
|
||||
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "My MCP Server" })
|
||||
.fill("testing-mcp-server");
|
||||
await po.page.getByRole("textbox", { name: "node" }).fill("node");
|
||||
const testMcpServerPath = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"testing",
|
||||
"fake-stdio-mcp-server.mjs",
|
||||
);
|
||||
console.log("testMcpServerPath", testMcpServerPath);
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "path/to/mcp-server.js --flag" })
|
||||
.fill(testMcpServerPath);
|
||||
await po.page.getByRole("button", { name: "Add Server" }).click();
|
||||
await po.page
|
||||
.getByRole("button", { name: "Add Environment Variable" })
|
||||
.click();
|
||||
await po.page.getByRole("textbox", { name: "Key" }).fill("testKey1");
|
||||
await po.page.getByRole("textbox", { name: "Value" }).fill("testValue1");
|
||||
await po.page.getByRole("button", { name: "Save" }).click();
|
||||
await po.goToAppsTab();
|
||||
await po.selectChatMode("agent");
|
||||
await po.sendPrompt("[call_tool=calculator_add]", {
|
||||
skipWaitForCompletion: true,
|
||||
});
|
||||
|
||||
// Wait for consent dialog to appear
|
||||
const alwaysAllowButton = po.page.getByRole("button", {
|
||||
name: "Always allow",
|
||||
});
|
||||
await expect(alwaysAllowButton).toBeVisible();
|
||||
|
||||
// Make sure the tool call doesn't execute until consent is given
|
||||
await po.snapshotMessages();
|
||||
await alwaysAllowButton.click();
|
||||
await po.page.getByRole("button", { name: "Approve" }).click();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
await po.snapshotServerDump("all-messages");
|
||||
});
|
||||
@@ -17,5 +17,5 @@ test("mention app (with pro)", async ({ po }) => {
|
||||
await po.goToAppsTab();
|
||||
await po.sendPrompt("[dump] @app:minimal-with-ai-rules hi");
|
||||
|
||||
await po.snapshotServerDump("all-messages");
|
||||
await po.snapshotServerDump("request");
|
||||
});
|
||||
|
||||
15
e2e-tests/mention_files.spec.ts
Normal file
15
e2e-tests/mention_files.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test("mention file", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
|
||||
await po.importApp("minimal-with-ai-rules");
|
||||
await po.goToAppsTab();
|
||||
await po.getChatInput().click();
|
||||
await po.getChatInput().fill("[dump] @");
|
||||
await po.page.getByRole("menuitem", { name: "Choose AI_RULES.md" }).click();
|
||||
await po.page.getByRole("button", { name: "Send message" }).click();
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
await po.snapshotServerDump("all-messages");
|
||||
});
|
||||
58
e2e-tests/nodejs_path_configuration.spec.ts
Normal file
58
e2e-tests/nodejs_path_configuration.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
test.describe("Node.js Path Configuration", () => {
|
||||
test("should browse and set custom Node.js path", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
|
||||
const browseButton = po.page.getByRole("button", {
|
||||
name: /Browse for Node\.js/i,
|
||||
});
|
||||
await browseButton.click();
|
||||
|
||||
// Should show selecting state
|
||||
await expect(
|
||||
po.page.getByRole("button", { name: /Selecting\.\.\./i }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("should reset custom path to system default", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
|
||||
const resetButton = po.page.getByRole("button", {
|
||||
name: /Reset to Default/i,
|
||||
});
|
||||
|
||||
if (await resetButton.isVisible()) {
|
||||
await resetButton.click();
|
||||
|
||||
// Should show system PATH after reset
|
||||
await expect(po.page.getByText("System PATH:")).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("should show CheckCircle when Node.js is valid", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.goToSettingsTab();
|
||||
|
||||
// Wait for status check
|
||||
await po.page.waitForTimeout(2000);
|
||||
|
||||
// Target the specific valid status container with CheckCircle
|
||||
const validStatus = po.page.locator(
|
||||
"div.flex.items-center.gap-1.text-green-600, div.flex.items-center.gap-1.text-green-400",
|
||||
);
|
||||
|
||||
// Skip test if Node.js is not installed
|
||||
if (!(await validStatus.isVisible())) {
|
||||
test.skip();
|
||||
}
|
||||
|
||||
// If visible, check for CheckCircle icon
|
||||
await expect(validStatus).toBeVisible();
|
||||
const checkIcon = validStatus.locator("svg").first();
|
||||
await expect(checkIcon).toBeVisible();
|
||||
});
|
||||
});
|
||||
10
e2e-tests/preview_iframe.spec.ts
Normal file
10
e2e-tests/preview_iframe.spec.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { testSkipIfWindows } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
testSkipIfWindows("preview iframe has sandbox attributes", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.sendPrompt("hi");
|
||||
expect(
|
||||
await po.getPreviewIframeElement().getAttribute("sandbox"),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { test, testSkipIfWindows } from "./helpers/test_helper";
|
||||
import { test, testSkipIfWindows, Timeout } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -83,6 +83,72 @@ export default App;
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
});
|
||||
|
||||
testSkipIfWindows(
|
||||
"problems - select specific problems and fix",
|
||||
async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.importApp(MINIMAL_APP);
|
||||
|
||||
// Create multiple TS errors in one file
|
||||
const appPath = await po.getCurrentAppPath();
|
||||
const badFilePath = path.join(appPath, "src", "bad-file.tsx");
|
||||
fs.writeFileSync(
|
||||
badFilePath,
|
||||
`const App = () => <div>Minimal imported app</div>;
|
||||
nonExistentFunction1();
|
||||
nonExistentFunction2();
|
||||
nonExistentFunction3();
|
||||
|
||||
export default App;
|
||||
`,
|
||||
);
|
||||
|
||||
await po.ensurePnpmInstall();
|
||||
|
||||
// Trigger creation of problems and open problems panel
|
||||
// await po.sendPrompt("tc=create-ts-errors");
|
||||
await po.selectPreviewMode("problems");
|
||||
await po.clickRecheckProblems();
|
||||
|
||||
// Initially, all selected: button shows Fix X problems and Clear all is visible
|
||||
const fixButton = po.page.getByTestId("fix-all-button");
|
||||
await expect(fixButton).toBeVisible();
|
||||
await expect(fixButton).toContainText(/Fix \d+ problems/);
|
||||
|
||||
// Click first two rows to toggle off (deselect)
|
||||
const rows = po.page.getByTestId("problem-row");
|
||||
const rowCount = await rows.count();
|
||||
expect(rowCount).toBeGreaterThan(2);
|
||||
await rows.nth(0).click();
|
||||
await rows.nth(1).click();
|
||||
|
||||
// Button should update to reflect remaining selected
|
||||
await expect(fixButton).toContainText(/Fix 1 problem/);
|
||||
|
||||
// Clear all should switch to Select all when none selected
|
||||
// Deselect remaining rows
|
||||
for (let i = 2; i < rowCount; i++) {
|
||||
await rows.nth(i).click();
|
||||
}
|
||||
|
||||
const selectButton = po.page.getByRole("button", {
|
||||
name: /Select all/,
|
||||
});
|
||||
await expect(selectButton).toHaveText("Select all");
|
||||
|
||||
// Select all, then fix selected
|
||||
await selectButton.click();
|
||||
// Unselect the second row
|
||||
await rows.nth(1).click();
|
||||
await expect(fixButton).toContainText(/Fix 2 problems/);
|
||||
|
||||
await fixButton.click();
|
||||
await po.waitForChatCompletion();
|
||||
await po.snapshotServerDump("last-message");
|
||||
await po.snapshotMessages({ replaceDumpPath: true });
|
||||
},
|
||||
);
|
||||
|
||||
testSkipIfWindows("problems - manual edit (react/vite)", async ({ po }) => {
|
||||
await po.setUp({ enableAutoFixProblems: true });
|
||||
await po.sendPrompt("tc=1");
|
||||
@@ -101,13 +167,15 @@ export default App;
|
||||
await po.clickTogglePreviewPanel();
|
||||
|
||||
await po.selectPreviewMode("problems");
|
||||
await po.clickRecheckProblems();
|
||||
await po.snapshotProblemsPane();
|
||||
const fixButton = po.page.getByTestId("fix-all-button");
|
||||
await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG });
|
||||
await expect(fixButton).toContainText(/Fix 1 problem/);
|
||||
|
||||
fs.unlinkSync(badFilePath);
|
||||
|
||||
await po.clickRecheckProblems();
|
||||
await po.snapshotProblemsPane();
|
||||
await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG });
|
||||
await expect(fixButton).toContainText(/Fix 0 problems/);
|
||||
});
|
||||
|
||||
testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => {
|
||||
@@ -129,11 +197,13 @@ testSkipIfWindows("problems - manual edit (next.js)", async ({ po }) => {
|
||||
await po.clickTogglePreviewPanel();
|
||||
|
||||
await po.selectPreviewMode("problems");
|
||||
await po.clickRecheckProblems();
|
||||
await po.snapshotProblemsPane();
|
||||
const fixButton = po.page.getByTestId("fix-all-button");
|
||||
await expect(fixButton).toBeEnabled({ timeout: Timeout.LONG });
|
||||
await expect(fixButton).toContainText(/Fix 1 problem/);
|
||||
|
||||
fs.unlinkSync(badFilePath);
|
||||
|
||||
await po.clickRecheckProblems();
|
||||
await po.snapshotProblemsPane();
|
||||
await expect(fixButton).toBeDisabled({ timeout: Timeout.LONG });
|
||||
await expect(fixButton).toContainText(/Fix 0 problems/);
|
||||
});
|
||||
|
||||
75
e2e-tests/security_review.spec.ts
Normal file
75
e2e-tests/security_review.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { test, testSkipIfWindows } from "./helpers/test_helper";
|
||||
|
||||
// Skipping because snapshotting the security findings table is not
|
||||
// consistent across platforms because different amounts of text
|
||||
// get ellipsis'd out.
|
||||
testSkipIfWindows("security review", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.sendPrompt("tc=1");
|
||||
|
||||
await po.selectPreviewMode("security");
|
||||
|
||||
await po.page
|
||||
.getByRole("button", { name: "Run Security Review" })
|
||||
.first()
|
||||
.click();
|
||||
await po.waitForChatCompletion();
|
||||
await po.snapshotServerDump("all-messages");
|
||||
await po.snapshotSecurityFindingsTable();
|
||||
|
||||
await po.page.getByRole("button", { name: "Fix Issue" }).first().click();
|
||||
await po.waitForChatCompletion();
|
||||
await po.snapshotMessages();
|
||||
});
|
||||
|
||||
test("security review - edit and use knowledge", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.sendPrompt("tc=1");
|
||||
|
||||
await po.selectPreviewMode("security");
|
||||
await po.page.getByRole("button", { name: "Edit Security Rules" }).click();
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "# SECURITY_RULES.md\\n\\" })
|
||||
.click();
|
||||
await po.page
|
||||
.getByRole("textbox", { name: "# SECURITY_RULES.md\\n\\" })
|
||||
.fill("testing\nrules123");
|
||||
await po.page.getByRole("button", { name: "Save" }).click();
|
||||
|
||||
await po.page
|
||||
.getByRole("button", { name: "Run Security Review" })
|
||||
.first()
|
||||
.click();
|
||||
await po.waitForChatCompletion();
|
||||
await po.snapshotServerDump("all-messages");
|
||||
});
|
||||
|
||||
test("security review - multi-select and fix issues", async ({ po }) => {
|
||||
await po.setUp({ autoApprove: true });
|
||||
await po.sendPrompt("tc=1");
|
||||
|
||||
await po.selectPreviewMode("security");
|
||||
|
||||
await po.page
|
||||
.getByRole("button", { name: "Run Security Review" })
|
||||
.first()
|
||||
.click();
|
||||
await po.waitForChatCompletion();
|
||||
|
||||
// Select the first two issues using individual checkboxes
|
||||
const checkboxes = po.page.getByRole("checkbox");
|
||||
// Skip the first checkbox (select all)
|
||||
await checkboxes.nth(1).click();
|
||||
await checkboxes.nth(2).click();
|
||||
|
||||
// Wait for the "Fix X Issues" button to appear
|
||||
const fixSelectedButton = po.page.getByRole("button", {
|
||||
name: "Fix 2 Issues",
|
||||
});
|
||||
await fixSelectedButton.waitFor({ state: "visible" });
|
||||
|
||||
// Click the fix selected button
|
||||
await fixSelectedButton.click();
|
||||
await po.waitForChatCompletion();
|
||||
await po.snapshotMessages();
|
||||
});
|
||||
@@ -14,11 +14,11 @@ testSkipIfWindows("select component", async ({ po }) => {
|
||||
.click();
|
||||
|
||||
await po.snapshotPreview();
|
||||
await po.snapshotSelectedComponentDisplay();
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
await po.sendPrompt("[dump] make it smaller");
|
||||
await po.snapshotPreview();
|
||||
await expect(po.getSelectedComponentDisplay()).not.toBeVisible();
|
||||
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible();
|
||||
|
||||
await po.snapshotServerDump("all-messages");
|
||||
|
||||
@@ -27,6 +27,34 @@ testSkipIfWindows("select component", async ({ po }) => {
|
||||
await po.snapshotServerDump("last-message");
|
||||
});
|
||||
|
||||
testSkipIfWindows("select multiple components", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.sendPrompt("tc=basic");
|
||||
await po.clickTogglePreviewPanel();
|
||||
await po.clickPreviewPickElement();
|
||||
|
||||
await po
|
||||
.getPreviewIframeElement()
|
||||
.contentFrame()
|
||||
.getByRole("heading", { name: "Welcome to Your Blank App" })
|
||||
.click();
|
||||
|
||||
await po
|
||||
.getPreviewIframeElement()
|
||||
.contentFrame()
|
||||
.getByText("Made with Dyad")
|
||||
.click();
|
||||
|
||||
await po.snapshotPreview();
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
await po.sendPrompt("[dump] make both smaller");
|
||||
await po.snapshotPreview();
|
||||
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible();
|
||||
|
||||
await po.snapshotServerDump("last-message");
|
||||
});
|
||||
|
||||
testSkipIfWindows("deselect component", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.sendPrompt("tc=basic");
|
||||
@@ -40,19 +68,50 @@ testSkipIfWindows("deselect component", async ({ po }) => {
|
||||
.click();
|
||||
|
||||
await po.snapshotPreview();
|
||||
await po.snapshotSelectedComponentDisplay();
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
// Deselect the component and make sure the state has reverted
|
||||
await po.clickDeselectComponent();
|
||||
|
||||
await po.snapshotPreview();
|
||||
await expect(po.getSelectedComponentDisplay()).not.toBeVisible();
|
||||
await expect(po.getSelectedComponentsDisplay()).not.toBeVisible();
|
||||
|
||||
// Send one more prompt to make sure it's a normal message.
|
||||
await po.sendPrompt("[dump] tc=basic");
|
||||
await po.snapshotServerDump("last-message");
|
||||
});
|
||||
|
||||
testSkipIfWindows(
|
||||
"deselect individual component from multiple",
|
||||
async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.sendPrompt("tc=basic");
|
||||
await po.clickTogglePreviewPanel();
|
||||
await po.clickPreviewPickElement();
|
||||
|
||||
await po
|
||||
.getPreviewIframeElement()
|
||||
.contentFrame()
|
||||
.getByRole("heading", { name: "Welcome to Your Blank App" })
|
||||
.click();
|
||||
|
||||
await po
|
||||
.getPreviewIframeElement()
|
||||
.contentFrame()
|
||||
.getByText("Made with Dyad")
|
||||
.click();
|
||||
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
await po.clickDeselectComponent({ index: 0 });
|
||||
|
||||
await po.snapshotPreview();
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
await expect(po.getSelectedComponentsDisplay()).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
testSkipIfWindows("upgrade app to select component", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.importApp("select-component");
|
||||
@@ -94,7 +153,7 @@ testSkipIfWindows("select component next.js", async ({ po }) => {
|
||||
.click();
|
||||
|
||||
await po.snapshotPreview();
|
||||
await po.snapshotSelectedComponentDisplay();
|
||||
await po.snapshotSelectedComponentsDisplay();
|
||||
|
||||
await po.sendPrompt("[dump] make it smaller");
|
||||
await po.snapshotPreview();
|
||||
|
||||
28
e2e-tests/setup.spec.ts
Normal file
28
e2e-tests/setup.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { testWithConfig } from "./helpers/test_helper";
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
const testSetup = testWithConfig({
|
||||
showSetupScreen: true,
|
||||
});
|
||||
|
||||
testSetup("setup ai provider", async ({ po }) => {
|
||||
await po.page
|
||||
.getByRole("button", { name: "Setup Google Gemini API Key" })
|
||||
.click();
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Configure Google" }),
|
||||
).toBeVisible();
|
||||
|
||||
await po.page.getByRole("button", { name: "Go Back" }).click();
|
||||
await po.page
|
||||
.getByRole("button", { name: "Setup OpenRouter API Key" })
|
||||
.click();
|
||||
await expect(
|
||||
po.page.getByRole("heading", { name: "Configure OpenRouter" }),
|
||||
).toBeVisible();
|
||||
|
||||
await po.page.getByRole("button", { name: "Go Back" }).click();
|
||||
await po.page
|
||||
.getByRole("button", { name: "Setup other AI providers" })
|
||||
.click();
|
||||
});
|
||||
40
e2e-tests/smart_context_deep.spec.ts
Normal file
40
e2e-tests/smart_context_deep.spec.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
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.goToAppsTab();
|
||||
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");
|
||||
},
|
||||
);
|
||||
@@ -10,6 +10,6 @@ test("switching smart context mode saves the right setting", async ({ po }) => {
|
||||
await po.snapshotSettings();
|
||||
await proModesDialog.setSmartContextMode("off");
|
||||
await po.snapshotSettings();
|
||||
await proModesDialog.setSmartContextMode("conservative");
|
||||
await proModesDialog.setSmartContextMode("deep");
|
||||
await po.snapshotSettings();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
- heading "Deep Link Test Prompt" [level=3]
|
||||
- paragraph: A prompt created via deep link
|
||||
- button:
|
||||
- img
|
||||
- button:
|
||||
- img
|
||||
- text: "You are a helpful assistant. Please help with: [task here]"
|
||||
@@ -0,0 +1,6 @@
|
||||
- paragraph: tc=basic
|
||||
- paragraph: This is a simple basic response
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,20 @@
|
||||
- paragraph: Create a utility function in src/utils/helper.ts
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt typescript
|
||||
- button "Copy":
|
||||
- img
|
||||
- paragraph: More EOM
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- img
|
||||
- text: wrote 1 file(s)
|
||||
- button "Undo":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,24 @@
|
||||
- paragraph: Create a simple React component in src/components/Hello.tsx
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Cancel":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt file1.txt
|
||||
- button [disabled]:
|
||||
- img
|
||||
- img
|
||||
- code:
|
||||
- textbox "Editor content"
|
||||
- list
|
||||
- paragraph: More EOM
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- img
|
||||
- text: wrote 1 file(s)
|
||||
- button "Undo":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- paragraph: tc=chat2
|
||||
- paragraph: chat2
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- paragraph: tc=chat1 [sleep=medium]
|
||||
- paragraph: chat1
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -46,7 +46,8 @@ You need to first add Supabase to your app and then we can add auth.
|
||||
===
|
||||
role: user
|
||||
message: This is my codebase. <dyad-file path="manual/baz.json">
|
||||
// File contents excluded from context
|
||||
["even json is included"]
|
||||
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="manual/file.ts">
|
||||
|
||||
@@ -46,7 +46,8 @@ You need to first add Supabase to your app and then we can add auth.
|
||||
===
|
||||
role: user
|
||||
message: This is my codebase. <dyad-file path="manual/baz.json">
|
||||
// File contents excluded from context
|
||||
["even json is included"]
|
||||
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="manual/file.ts">
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
{
|
||||
"path": "manual/baz.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "[\"even json is included\"]\n",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
@@ -98,7 +98,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"path": "manual/baz.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "[\"even json is included\"]\n",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
@@ -68,7 +68,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -56,7 +56,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": false
|
||||
"enable_smart_files_context": false,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
{
|
||||
"path": "manual/baz.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "[\"even json is included\"]\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -114,7 +114,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": false
|
||||
"enable_smart_files_context": false,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -388,17 +388,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -413,7 +413,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": false
|
||||
"enable_smart_files_context": false,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"max_tokens": 16000,
|
||||
"max_tokens": 32000,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -383,17 +383,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -408,7 +408,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "gemini/gemini-2.5-pro",
|
||||
"max_tokens": 65535,
|
||||
"temperature": 0,
|
||||
"model": "gpt-5",
|
||||
"temperature": 1,
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
@@ -14,11 +13,7 @@
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"thinking": {
|
||||
"type": "enabled",
|
||||
"include_thoughts": true,
|
||||
"budget_tokens": 4000
|
||||
},
|
||||
"reasoning_effort": "medium",
|
||||
"dyad_options": {
|
||||
"files": [
|
||||
{
|
||||
@@ -33,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -388,17 +383,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -414,7 +409,8 @@
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced"
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
@@ -1,6 +1,10 @@
|
||||
- paragraph: "[dump] tc=turbo-edits"
|
||||
- paragraph: "[[dyad-dump-path=*]]"
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Request ID":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
File diff suppressed because one or more lines are too long
@@ -33,7 +33,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -388,17 +388,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -413,7 +413,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -388,17 +388,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -413,7 +413,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "anthropic/claude-sonnet-4-20250514",
|
||||
"max_tokens": 16000,
|
||||
"max_tokens": 32000,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt
|
||||
- paragraph: More EOM
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,14 @@
|
||||
- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt
|
||||
- paragraph: More EOM
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,7 @@
|
||||
- paragraph: "[call_tool=calculator_add]"
|
||||
- img
|
||||
- text: Tool Call
|
||||
- img
|
||||
- text: testing-mcp-server calculator_add
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
81
e2e-tests/snapshots/mcp.spec.ts_mcp---call-calculator-1.txt
Normal file
81
e2e-tests/snapshots/mcp.spec.ts_mcp---call-calculator-1.txt
Normal file
@@ -0,0 +1,81 @@
|
||||
===
|
||||
role: system
|
||||
message:
|
||||
You are an AI App Builder Agent. Your role is to analyze app development requests and gather all necessary information before the actual coding phase begins.
|
||||
|
||||
## Core Mission
|
||||
Determine what tools, APIs, data, or external resources are needed to build the requested application. Prepare everything needed for successful app development without writing any code yourself.
|
||||
|
||||
## Tool Usage Decision Framework
|
||||
|
||||
### Use Tools When The App Needs:
|
||||
- **External APIs or services** (payment processing, authentication, maps, social media, etc.)
|
||||
- **Real-time data** (weather, stock prices, news, current events)
|
||||
- **Third-party integrations** (Firebase, Supabase, cloud services)
|
||||
- **Current framework/library documentation** or best practices
|
||||
|
||||
### Use Tools To Research:
|
||||
- Available APIs and their documentation
|
||||
- Authentication methods and implementation approaches
|
||||
- Database options and setup requirements
|
||||
- UI/UX frameworks and component libraries
|
||||
- Deployment platforms and requirements
|
||||
- Performance optimization strategies
|
||||
- Security best practices for the app type
|
||||
|
||||
### When Tools Are NOT Needed
|
||||
If the app request is straightforward and can be built with standard web technologies without external dependencies, respond with:
|
||||
|
||||
**"Ok, looks like I don't need any tools, I can start building."**
|
||||
|
||||
This applies to simple apps like:
|
||||
- Basic calculators or converters
|
||||
- Simple games (tic-tac-toe, memory games)
|
||||
- Static information displays
|
||||
- Basic form interfaces
|
||||
- Simple data visualization with static data
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
- ABSOLUTELY NO CODE GENERATION
|
||||
- **Never write HTML, CSS, JavaScript, TypeScript, or any programming code**
|
||||
- **Do not create component examples or code snippets**
|
||||
- **Do not provide implementation details or syntax**
|
||||
- **Do not use <dyad-write>, <dyad-edit>, <dyad-add-dependency> OR ANY OTHER <dyad-*> tags**
|
||||
- Your job ends with information gathering and requirement analysis
|
||||
- All actual development happens in the next phase
|
||||
|
||||
## Output Structure
|
||||
|
||||
When tools are used, provide a brief human-readable summary of the information gathered from the tools.
|
||||
|
||||
When tools are not used, simply state: **"Ok, looks like I don't need any tools, I can start building."**
|
||||
|
||||
|
||||
===
|
||||
role: user
|
||||
message: [call_tool=calculator_add]
|
||||
|
||||
===
|
||||
role: assistant
|
||||
message: <dyad-mcp-tool-call server="testing-mcp-server" tool="calculator_add">
|
||||
{"a":1,"b":2}
|
||||
</dyad-mcp-tool-call>
|
||||
<dyad-mcp-tool-result server="testing-mcp-server" tool="calculator_add">
|
||||
{"content":[{"type":"text","text":"3"}],"isError":false}
|
||||
</dyad-mcp-tool-result>
|
||||
|
||||
<dyad-write path="file1.txt">
|
||||
A file (2)
|
||||
</dyad-write>
|
||||
More
|
||||
EOM
|
||||
<dyad-write path="file1.txt">
|
||||
A file (2)
|
||||
</dyad-write>
|
||||
More
|
||||
EOM
|
||||
|
||||
===
|
||||
role: user
|
||||
message: [dump]
|
||||
File diff suppressed because one or more lines are too long
1035
e2e-tests/snapshots/mention_files.spec.ts_mention-file-1.txt
Normal file
1035
e2e-tests/snapshots/mention_files.spec.ts_mention-file-1.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-downloads
|
||||
@@ -1,10 +0,0 @@
|
||||
- img
|
||||
- text: 1 error
|
||||
- button "Run checks":
|
||||
- img
|
||||
- button "Fix All":
|
||||
- img
|
||||
- img
|
||||
- img
|
||||
- text: src/bad-file.tsx 2:3
|
||||
- paragraph: Cannot find name 'nonExistentFunction'.
|
||||
@@ -1,4 +0,0 @@
|
||||
- paragraph: No problems found
|
||||
- img
|
||||
- button "Run checks":
|
||||
- img
|
||||
@@ -1,10 +0,0 @@
|
||||
- img
|
||||
- text: 1 error
|
||||
- button "Run checks":
|
||||
- img
|
||||
- button "Fix All":
|
||||
- img
|
||||
- img
|
||||
- img
|
||||
- text: src/bad-file.tsx 2:1
|
||||
- paragraph: Cannot find name 'nonExistentFunction'.
|
||||
@@ -1,4 +0,0 @@
|
||||
- paragraph: No problems found
|
||||
- img
|
||||
- button "Run checks":
|
||||
- img
|
||||
@@ -0,0 +1,21 @@
|
||||
- paragraph: "Fix these 2 TypeScript compile-time errors:"
|
||||
- list:
|
||||
- listitem: src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304)
|
||||
- code: const App = () => <div>Minimal imported app</div>; nonExistentFunction1(); // <-- TypeScript compiler error here nonExistentFunction2();
|
||||
- list:
|
||||
- listitem: src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304)
|
||||
- code: nonExistentFunction2(); nonExistentFunction3(); // <-- TypeScript compiler error here
|
||||
- paragraph: Please fix all errors in a concise way.
|
||||
- img
|
||||
- text: bad-file.ts
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: "src/bad-file.ts Summary: Fix 2 errors and introduce a new error."
|
||||
- paragraph: "[[dyad-dump-path=*]]"
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,19 @@
|
||||
===
|
||||
role: user
|
||||
message: Fix these 2 TypeScript compile-time errors:
|
||||
|
||||
1. src/bad-file.tsx:2:1 - Cannot find name 'nonExistentFunction1'. (TS2304)
|
||||
```
|
||||
const App = () => <div>Minimal imported app</div>;
|
||||
nonExistentFunction1(); // <-- TypeScript compiler error here
|
||||
nonExistentFunction2();
|
||||
```
|
||||
|
||||
2. src/bad-file.tsx:4:1 - Cannot find name 'nonExistentFunction3'. (TS2304)
|
||||
```
|
||||
nonExistentFunction2();
|
||||
nonExistentFunction3(); // <-- TypeScript compiler error here
|
||||
```
|
||||
|
||||
|
||||
Please fix all errors in a concise way.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
- paragraph: "Please fix the following 2 security issues in a simple and effective way:"
|
||||
- list:
|
||||
- listitem:
|
||||
- strong: SQL Injection in User Lookup
|
||||
- text: (critical severity)
|
||||
- strong: What
|
||||
- text: ": User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An attacker could steal all customer data, delete your entire database, or take over admin accounts by manipulating the URL"
|
||||
- paragraph:
|
||||
- strong: Potential Solutions
|
||||
- text: ":"
|
||||
- list:
|
||||
- listitem:
|
||||
- text: "Use parameterized queries:"
|
||||
- code: "`db.query('SELECT * FROM users WHERE id = ?', [userId])`"
|
||||
- listitem:
|
||||
- text: Add input validation to ensure
|
||||
- code: "`userId`"
|
||||
- text: is a number
|
||||
- listitem: Implement an ORM like Prisma or TypeORM that prevents SQL injection by default
|
||||
- paragraph:
|
||||
- strong: Relevant Files
|
||||
- text: ":"
|
||||
- code: "`src/api/users.ts`"
|
||||
- list:
|
||||
- listitem:
|
||||
- strong: Hardcoded AWS Credentials in Source Code
|
||||
- text: (critical severity)
|
||||
- strong: What
|
||||
- text: ": AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": Anyone with repository access (including former employees or compromised accounts) could spin up expensive resources, access S3 buckets with customer data, or destroy production infrastructure"
|
||||
- paragraph:
|
||||
- strong: Potential Solutions
|
||||
- text: ":"
|
||||
- list:
|
||||
- listitem: Immediately rotate the exposed credentials in AWS IAM
|
||||
- listitem:
|
||||
- text: Use environment variables and add
|
||||
- code: "`.env`"
|
||||
- text: to
|
||||
- code: "`.gitignore`"
|
||||
- listitem: Implement AWS Secrets Manager or similar vault solution
|
||||
- listitem:
|
||||
- text: Scan git history and purge the credentials using tools like
|
||||
- code: "`git-filter-repo`"
|
||||
- paragraph:
|
||||
- strong: Relevant Files
|
||||
- text: ":"
|
||||
- code: "`src/config/aws.ts`"
|
||||
- text: ","
|
||||
- code: "`src/services/s3-uploader.ts`"
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt
|
||||
- paragraph: More EOM
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- img
|
||||
- text: wrote 1 file(s)
|
||||
- button "Undo":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -0,0 +1,148 @@
|
||||
- table:
|
||||
- rowgroup:
|
||||
- row "Select all issues Level Issue Action":
|
||||
- cell "Select all issues":
|
||||
- checkbox "Select all issues"
|
||||
- cell "Level"
|
||||
- cell "Issue"
|
||||
- cell "Action"
|
||||
- rowgroup:
|
||||
- 'row "Select SQL Injection in User Lookup critical SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more Fix Issue"':
|
||||
- cell "Select SQL Injection in User Lookup":
|
||||
- checkbox "Select SQL Injection in User Lookup"
|
||||
- cell "critical":
|
||||
- img
|
||||
- 'cell "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"':
|
||||
- 'button "SQL Injection in User Lookup What: User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands Risk: An attac... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An attac..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Hardcoded AWS Credentials in Source Code critical Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more Fix Issue"':
|
||||
- cell "Select Hardcoded AWS Credentials in Source Code":
|
||||
- checkbox "Select Hardcoded AWS Credentials in Source Code"
|
||||
- cell "critical":
|
||||
- img
|
||||
- 'cell "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"':
|
||||
- 'button "Hardcoded AWS Credentials in Source Code What: AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access Risk: A... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": AWS access keys are stored directly in the codebase and committed to version control, exposing full cloud infrastructure access"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": A..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Missing Authentication on Admin Endpoints high Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more Fix Issue"':
|
||||
- cell "Select Missing Authentication on Admin Endpoints":
|
||||
- checkbox "Select Missing Authentication on Admin Endpoints"
|
||||
- cell "high":
|
||||
- img
|
||||
- 'cell "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"':
|
||||
- 'button "Missing Authentication on Admin Endpoints What: Administrative API endpoints can be accessed without authentication, relying only on URL obscurity Risk: An attacker who discovers thes... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": Administrative API endpoints can be accessed without authentication, relying only on URL obscurity"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An attacker who discovers thes..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select JWT Secret Using Default Value high JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more Fix Issue"':
|
||||
- cell "Select JWT Secret Using Default Value":
|
||||
- checkbox "Select JWT Secret Using Default Value"
|
||||
- cell "high":
|
||||
- img
|
||||
- 'cell "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"':
|
||||
- 'button "JWT Secret Using Default Value What: The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens Risk: Attackers can forge val... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": The application uses a hardcoded default JWT secret (\"your-secret-key\") for signing authentication tokens"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": Attackers can forge val..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Unvalidated File Upload Extensions medium Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more Fix Issue"':
|
||||
- cell "Select Unvalidated File Upload Extensions":
|
||||
- checkbox "Select Unvalidated File Upload Extensions"
|
||||
- cell "medium":
|
||||
- img
|
||||
- 'cell "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"':
|
||||
- 'button "Unvalidated File Upload Extensions What: The file upload endpoint accepts any file type without validating extensions or content, only checking file size Risk: An attacker coul... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": The file upload endpoint accepts any file type without validating extensions or content, only checking file size"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An attacker coul..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Missing CSRF Protection on State-Changing Operations medium Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more Fix Issue"':
|
||||
- cell "Select Missing CSRF Protection on State-Changing Operations":
|
||||
- checkbox "Select Missing CSRF Protection on State-Changing Operations"
|
||||
- cell "medium":
|
||||
- img
|
||||
- 'cell "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"':
|
||||
- 'button "Missing CSRF Protection on State-Changing Operations What: POST, PUT, and DELETE endpoints don''t implement CSRF tokens, making them vulnerable to cross-site request forgery attacks Risk: An atta... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": POST, PUT, and DELETE endpoints don't implement CSRF tokens, making them vulnerable to cross-site request forgery attacks"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An atta..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Verbose Error Messages Expose Stack Traces low Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more Fix Issue"':
|
||||
- cell "Select Verbose Error Messages Expose Stack Traces":
|
||||
- checkbox "Select Verbose Error Messages Expose Stack Traces"
|
||||
- cell "low":
|
||||
- img
|
||||
- 'cell "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"':
|
||||
- 'button "Verbose Error Messages Expose Stack Traces What: Production error responses include full stack traces and internal file paths that are sent to end users Risk: Attackers can use this in... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": Production error responses include full stack traces and internal file paths that are sent to end users"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": Attackers can use this in..."
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
- 'row "Select Missing Security Headers low Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more Fix Issue"':
|
||||
- cell "Select Missing Security Headers":
|
||||
- checkbox "Select Missing Security Headers"
|
||||
- cell "low":
|
||||
- img
|
||||
- 'cell "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"':
|
||||
- 'button "Missing Security Headers What: The application doesn''t set recommended security headers like `X-Frame-Options`, `X-Content-Type-Options`, and `Strict-Transport-Security` ... Show more"':
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": The application doesn't set recommended security headers like"
|
||||
- code: "`X-Frame-Options`"
|
||||
- text: ","
|
||||
- code: "`X-Content-Type-Options`"
|
||||
- text: ", and"
|
||||
- code: "`Strict-Transport-Security`"
|
||||
- paragraph: ...
|
||||
- button "Show more":
|
||||
- img
|
||||
- cell "Fix Issue":
|
||||
- button "Fix Issue"
|
||||
1035
e2e-tests/snapshots/security_review.spec.ts_security-review-1.txt
Normal file
1035
e2e-tests/snapshots/security_review.spec.ts_security-review-1.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
- paragraph: "Please fix the following security issue in a simple and effective way:"
|
||||
- paragraph:
|
||||
- strong: SQL Injection in User Lookup
|
||||
- text: (critical severity)
|
||||
- paragraph:
|
||||
- strong: What
|
||||
- text: ": User input flows directly into database queries without validation, allowing attackers to execute arbitrary SQL commands"
|
||||
- paragraph:
|
||||
- strong: Risk
|
||||
- text: ": An attacker could steal all customer data, delete your entire database, or take over admin accounts by manipulating the URL"
|
||||
- paragraph:
|
||||
- strong: Potential Solutions
|
||||
- text: ":"
|
||||
- list:
|
||||
- listitem:
|
||||
- text: "Use parameterized queries:"
|
||||
- code: "`db.query('SELECT * FROM users WHERE id = ?', [userId])`"
|
||||
- listitem:
|
||||
- text: Add input validation to ensure
|
||||
- code: "`userId`"
|
||||
- text: is a number
|
||||
- listitem: Implement an ORM like Prisma or TypeORM that prevents SQL injection by default
|
||||
- paragraph:
|
||||
- strong: Relevant Files
|
||||
- text: ":"
|
||||
- code: "`src/api/users.ts`"
|
||||
- img
|
||||
- text: file1.txt
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: file1.txt
|
||||
- paragraph: More EOM
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- img
|
||||
- text: wrote 1 file(s)
|
||||
- button "Undo":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
@@ -5,5 +5,4 @@
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- img
|
||||
- text: Edit with AI h1 src/pages/Index.tsx
|
||||
- text: h1 src/pages/Index.tsx
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- text: Selected Components (2)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
- img
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: a src/components/made-with-dyad.tsx
|
||||
@@ -0,0 +1,6 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -5,5 +5,4 @@
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- img
|
||||
- text: Edit with AI h1 src/pages/Index.tsx
|
||||
- text: h1 src/pages/Index.tsx
|
||||
@@ -104,7 +104,9 @@ message: This is a simple basic response
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/pages/Index.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
- text: Edit with AI h1 src/app/page.tsx
|
||||
- main:
|
||||
- heading "Blank page" [level=1]
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: h1 src/app/page.tsx
|
||||
- alert
|
||||
- button "Open Next.js Dev Tools":
|
||||
- img
|
||||
@@ -151,7 +151,9 @@ message: This is a simple basic response
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/app/page.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/app/page.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/app/page.tsx:7
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
- heading "Blank page" [level=1]
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- alert
|
||||
- button "Open Next.js Dev Tools":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: a src/components/made-with-dyad.tsx
|
||||
@@ -0,0 +1,27 @@
|
||||
===
|
||||
role: user
|
||||
message: [dump] make both smaller
|
||||
|
||||
Selected components:
|
||||
|
||||
1. Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1> // <-- EDIT HERE
|
||||
<p className="text-xl text-gray-600">
|
||||
Start building your amazing project here!
|
||||
</p>
|
||||
```
|
||||
|
||||
2. Component: a (file: src/components/made-with-dyad.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
<div className="p-4 text-center">
|
||||
<a // <-- EDIT HERE
|
||||
href="https://www.dyad.sh/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
- text: Selected Components (2)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
- img
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -0,0 +1,7 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
@@ -2,7 +2,9 @@
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/pages/Index.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
|
||||
- paragraph: tc=read-index
|
||||
- paragraph: "Read the index page:"
|
||||
- img
|
||||
- text: Index.tsx Read src/pages/Index.tsx
|
||||
- paragraph: Done.
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Request ID":
|
||||
- img
|
||||
- paragraph: tc=update-index-1
|
||||
- paragraph: First read
|
||||
- img
|
||||
- text: Index.tsx
|
||||
- button "Edit":
|
||||
- img
|
||||
- img
|
||||
- text: "src/pages/Index.tsx Summary: replace file"
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- img
|
||||
- text: wrote 1 file(s)
|
||||
- button "Request ID":
|
||||
- img
|
||||
- paragraph: tc=read-index
|
||||
- paragraph: "Read the index page:"
|
||||
- img
|
||||
- text: Index.tsx Read src/pages/Index.tsx
|
||||
- paragraph: Done.
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Request ID":
|
||||
- img
|
||||
- paragraph: "[dump]"
|
||||
- paragraph: "[[dyad-dump-path=*]]"
|
||||
- button:
|
||||
- img
|
||||
- img
|
||||
- text: Approved
|
||||
- img
|
||||
- text: less than a minute ago
|
||||
- button "Request ID":
|
||||
- img
|
||||
- button "Retry":
|
||||
- img
|
||||
File diff suppressed because one or more lines are too long
@@ -19,6 +19,7 @@
|
||||
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||
"enableProLazyEditsMode": true,
|
||||
"enableProSmartFilesContextMode": true,
|
||||
"proSmartContextOption": "deep",
|
||||
"selectedTemplateId": "react",
|
||||
"selectedChatMode": "build",
|
||||
"enableAutoFixProblems": false,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- text: "/Tokens: \\d+,\\d+ \\d+% of [\\d,.]+[bkmBKM]+ Optimize your tokens with Dyad Pro's Smart Context/"
|
||||
@@ -41,7 +41,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -396,17 +396,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -421,7 +421,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -404,17 +404,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -429,7 +429,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
},
|
||||
{
|
||||
"path": "components.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -412,17 +412,17 @@
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.app.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "tsconfig.node.json",
|
||||
"content": "// File contents excluded from context",
|
||||
"content": "{\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",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
@@ -437,7 +437,9 @@
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
"enable_smart_files_context": true,
|
||||
"smart_context_mode": "balanced",
|
||||
"app_id": 1
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"selectedModel": {
|
||||
"name": "auto",
|
||||
"provider": "auto"
|
||||
},
|
||||
"providerSettings": {
|
||||
"auto": {
|
||||
"apiKey": {
|
||||
"value": "testdyadkey",
|
||||
"encryptionType": "plaintext"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetryConsent": "unset",
|
||||
"telemetryUserId": "[UUID]",
|
||||
"hasRunBefore": true,
|
||||
"enableDyadPro": true,
|
||||
"experiments": {},
|
||||
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||
"enableProLazyEditsMode": true,
|
||||
"enableProSmartFilesContextMode": true,
|
||||
"selectedTemplateId": "react",
|
||||
"selectedChatMode": "build",
|
||||
"enableAutoFixProblems": false,
|
||||
"enableAutoUpdate": true,
|
||||
"releaseChannel": "stable",
|
||||
"isTestMode": true
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"selectedModel": {
|
||||
"name": "auto",
|
||||
"provider": "auto"
|
||||
},
|
||||
"providerSettings": {
|
||||
"auto": {
|
||||
"apiKey": {
|
||||
"value": "testdyadkey",
|
||||
"encryptionType": "plaintext"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetryConsent": "unset",
|
||||
"telemetryUserId": "[UUID]",
|
||||
"hasRunBefore": true,
|
||||
"enableDyadPro": true,
|
||||
"experiments": {},
|
||||
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||
"enableProLazyEditsMode": true,
|
||||
"proLazyEditsMode": "v1",
|
||||
"enableProSmartFilesContextMode": true,
|
||||
"selectedTemplateId": "react",
|
||||
"selectedChatMode": "build",
|
||||
"enableAutoFixProblems": false,
|
||||
"enableAutoUpdate": true,
|
||||
"releaseChannel": "stable",
|
||||
"isTestMode": true
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"selectedModel": {
|
||||
"name": "auto",
|
||||
"provider": "auto"
|
||||
},
|
||||
"providerSettings": {
|
||||
"auto": {
|
||||
"apiKey": {
|
||||
"value": "testdyadkey",
|
||||
"encryptionType": "plaintext"
|
||||
}
|
||||
}
|
||||
},
|
||||
"telemetryConsent": "unset",
|
||||
"telemetryUserId": "[UUID]",
|
||||
"hasRunBefore": true,
|
||||
"enableDyadPro": true,
|
||||
"experiments": {},
|
||||
"lastShownReleaseNotesVersion": "[scrubbed]",
|
||||
"enableProLazyEditsMode": true,
|
||||
"proLazyEditsMode": "v2",
|
||||
"enableProSmartFilesContextMode": true,
|
||||
"selectedTemplateId": "react",
|
||||
"selectedChatMode": "build",
|
||||
"enableAutoFixProblems": false,
|
||||
"enableAutoUpdate": true,
|
||||
"releaseChannel": "stable",
|
||||
"isTestMode": true
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user