feat: multi-component-selector (#1728)
<!-- This is an auto-generated description by cubic. --> ## Summary by cubic Adds multi-component selection in the preview and sends all selected components to chat for targeted edits. Updates overlays, UI, and IPC to support arrays, smarter context focusing, and cross-platform path normalization. - **New Features** - Select multiple components in the iframe; selection mode stays active until you deactivate it. - Show a scrollable list of selections with remove buttons and a Clear all; remove from the list or click an overlay in the preview to deselect. Sending clears all overlays. - Separate hover vs selected overlays with labels on hover; overlays persist after deactivation and re-position on layout changes/resizes. - Chat input and streaming now send selectedComponents; server builds per-component snippets and focuses their files in smart context. - **Migration** - Replace selectedComponentPreviewAtom with selectedComponentsPreviewAtom (ComponentSelection[]). - ChatStreamParams now uses selectedComponents; migrate any single-selection usages. - previewIframeRefAtom added for clearing overlays from the parent. <sup>Written for commit da0d64cc9e9f83fbf4b975278f6c869f0d3a8c7d. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
c4591996ea
commit
2a7f5a8909
@@ -522,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() {
|
||||
@@ -582,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 } = {}) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -5,5 +5,4 @@
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- img
|
||||
- text: Edit with AI h1 src/pages/Index.tsx
|
||||
- text: h1 src/pages/Index.tsx
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- text: Selected Components (2)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
- img
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: a src/components/made-with-dyad.tsx
|
||||
@@ -0,0 +1,6 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -5,5 +5,4 @@
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- img
|
||||
- text: Edit with AI h1 src/pages/Index.tsx
|
||||
- text: h1 src/pages/Index.tsx
|
||||
@@ -104,7 +104,9 @@ message: This is a simple basic response
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/pages/Index.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
- text: Edit with AI h1 src/app/page.tsx
|
||||
- main:
|
||||
- heading "Blank page" [level=1]
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: h1 src/app/page.tsx
|
||||
- alert
|
||||
- button "Open Next.js Dev Tools":
|
||||
- img
|
||||
@@ -151,7 +151,9 @@ message: This is a simple basic response
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/app/page.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/app/page.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
- text: Selected Components (1)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/app/page.tsx:7
|
||||
- button "Deselect component":
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
- heading "Blank page" [level=1]
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- alert
|
||||
- button "Open Next.js Dev Tools":
|
||||
- img
|
||||
@@ -0,0 +1,8 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
- text: a src/components/made-with-dyad.tsx
|
||||
@@ -0,0 +1,27 @@
|
||||
===
|
||||
role: user
|
||||
message: [dump] make both smaller
|
||||
|
||||
Selected components:
|
||||
|
||||
1. Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4">Welcome to Your Blank App</h1> // <-- EDIT HERE
|
||||
<p className="text-xl text-gray-600">
|
||||
Start building your amazing project here!
|
||||
</p>
|
||||
```
|
||||
|
||||
2. Component: a (file: src/components/made-with-dyad.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
<div className="p-4 text-center">
|
||||
<a // <-- EDIT HERE
|
||||
href="https://www.dyad.sh/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
- text: Selected Components (2)
|
||||
- button "Clear all"
|
||||
- img
|
||||
- text: h1 src/pages/Index.tsx:9
|
||||
- button "Deselect component":
|
||||
- img
|
||||
- img
|
||||
- text: a src/components/made-with-dyad.tsx:4
|
||||
- button "Deselect component":
|
||||
- img
|
||||
@@ -0,0 +1,7 @@
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
- region "Notifications alt+T"
|
||||
- heading "Welcome to Your Blank App" [level=1]
|
||||
- paragraph: Start building your amazing project here!
|
||||
- link "Made with Dyad":
|
||||
- /url: https://www.dyad.sh/
|
||||
@@ -2,7 +2,9 @@
|
||||
role: user
|
||||
message: [dump] make it smaller
|
||||
|
||||
Selected component: h1 (file: src/pages/Index.tsx)
|
||||
Selected components:
|
||||
|
||||
Component: h1 (file: src/pages/Index.tsx)
|
||||
|
||||
Snippet:
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user