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:
Kunthawat Greethong
2025-12-05 11:28:57 +07:00
parent 11986a0196
commit d22227bb13
312 changed files with 30787 additions and 2829 deletions

View 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();
});

View 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");
});

View 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();
});

View 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();
});

View 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();
});

View File

@@ -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 });
});

View 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" });
});

View 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 });
//
});

View 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!");
});

View File

@@ -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();
});

View 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
});

View File

@@ -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");

View 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\]/);
});
});

View File

@@ -0,0 +1 @@
This is a simple basic response

View File

@@ -0,0 +1,3 @@
Read the index page:
<dyad-read path="src/pages/Index.tsx"></dyad-read>
Done.

View 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

View 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

View File

@@ -0,0 +1,4 @@
First read
<dyad-write path="src/pages/Index.tsx" description="replace file">
// this file has been replaced
</dyad-write>

View File

@@ -1 +1 @@
["should not be included b/c it's json"]
["even json is included"]

View 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>

View File

@@ -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 });
});

View 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();
});

View File

@@ -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
View 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");
});

View File

@@ -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");
});

View 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");
});

View 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();
});
});

View 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();
});

View File

@@ -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/);
});

View 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();
});

View File

@@ -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
View 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();
});

View 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");
},
);

View File

@@ -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();
});

View File

@@ -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]"

View File

@@ -0,0 +1,6 @@
- paragraph: tc=basic
- paragraph: This is a simple basic response
- img
- text: less than a minute ago
- button "Retry":
- img

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,8 @@
- paragraph: tc=chat2
- paragraph: chat2
- button:
- img
- img
- text: less than a minute ago
- button "Retry":
- img

View File

@@ -0,0 +1,8 @@
- paragraph: tc=chat1 [sleep=medium]
- paragraph: chat1
- button:
- img
- img
- text: less than a minute ago
- button "Retry":
- img

View File

@@ -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">

View File

@@ -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">

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -1,7 +1,7 @@
{
"body": {
"model": "anthropic/claude-sonnet-4-20250514",
"max_tokens": 16000,
"max_tokens": 32000,
"temperature": 0,
"messages": [
{

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-downloads

View File

@@ -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'.

View File

@@ -1,4 +0,0 @@
- paragraph: No problems found
- img
- button "Run checks":
- img

View File

@@ -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'.

View File

@@ -1,4 +0,0 @@
- paragraph: No problems found
- img
- button "Run checks":
- img

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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"

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -1,3 +1,5 @@
- text: Selected Components (1)
- button "Clear all"
- img
- text: h1 src/pages/Index.tsx:9
- button "Deselect component":

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:
```

View File

@@ -1,3 +1,5 @@
- text: Selected Components (1)
- button "Clear all"
- img
- text: h1 src/pages/Index.tsx:9
- button "Deselect component":

View File

@@ -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

View File

@@ -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:
```

View File

@@ -1,3 +1,5 @@
- text: Selected Components (1)
- button "Clear all"
- img
- text: h1 src/app/page.tsx:7
- button "Deselect component":

View File

@@ -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

View File

@@ -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

View File

@@ -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"
```

View File

@@ -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

View File

@@ -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/

View File

@@ -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:
```

View File

@@ -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

View File

@@ -19,6 +19,7 @@
"lastShownReleaseNotesVersion": "[scrubbed]",
"enableProLazyEditsMode": true,
"enableProSmartFilesContextMode": true,
"proSmartContextOption": "deep",
"selectedTemplateId": "react",
"selectedChatMode": "build",
"enableAutoFixProblems": false,

View File

@@ -0,0 +1 @@
- text: "/Tokens: \\d+,\\d+ \\d+% of [\\d,.]+[bkmBKM]+ Optimize your tokens with Dyad Pro's Smart Context/"

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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